View Javadoc

1   package org.lcsim.event.base;
2   
3   import java.util.ArrayList;
4   import java.util.HashSet;
5   import java.util.List;
6   import java.util.Set;
7   
8   import org.lcsim.event.CalorimeterHit;
9   import org.lcsim.event.Cluster;
10  
11  /**
12   * <p>
13   * This is a concrete implementation of the {@link org.lcsim.event.Cluster} LCIO interface.
14   * <p>
15   * This version is an overhaul of the previous base class, with the following changes:
16   * <ul>
17   * <li>Added several constructors with argument lists, including one that is fully qualified and another that takes a list of hits only.</li>
18   * <li>Removed the prior implementation of subdetector energies in favor of a simple set method. (This part of the API is basically unused anyways.)</li>
19   * <li>Added set methods for all class variables, where they were missing.</li>
20   * <li>Added access to particle ID based on the official API.</li>
21   * <li>Added a few utility methods for adding lists of hits and clusters.</li>
22   * <li>Added a method for setting a hit contribution that is different from the corrected energy.</li>
23   * <li>Simplified the {@link #calculateProperties()} method so that it doesn't do a bunch of array copies.</li>
24   * <li>Added a copy constructor and implementation of {@link #clone()} method.</li>
25   * </ul>
26   *
27   * @see org.lcsim.event.Cluster
28   * @see org.lcsim.event.CalorimeterHit
29   * @see org.lcsim.event.base.ClusterPropertyCalculator
30   * @see org.lcsim.event.base.TensorClusterPropertyCalculator
31   * @author Jeremy McCormick <jeremym@slac.stanford.edu>
32   * @author Norman Graf <ngraf@slac.stanford.edu>
33   */
34  public class BaseCluster implements Cluster {
35  
36      protected ClusterPropertyCalculator calc = new TensorClusterPropertyCalculator();
37      protected List<Cluster> clusters = new ArrayList<Cluster>();
38      protected double[] directionError = new double[6];
39  
40      protected double energy;
41      protected double energyError;
42  
43      protected List<Double> hitContributions = new ArrayList<Double>();
44      protected List<CalorimeterHit> hits = new ArrayList<CalorimeterHit>();
45  
46      protected double iphi;
47      protected double itheta;
48  
49      protected boolean needsPropertyCalculation = true;
50      protected int pid;
51  
52      protected double[] position = new double[3];
53  
54      protected double[] positionError = new double[6];
55      protected double[] shapeParameters;
56  
57      protected double[] subdetectorEnergies = new double[0];
58      protected int type;
59  
60      /**
61       * The no argument constructor.
62       */
63      public BaseCluster() {
64      }
65  
66      /**
67       * Copy constructor, which will create new arrays and lists in this object so the copied cluster's data is not incorrectly referenced.
68       * <p>
69       * The hits in the <code>CalorimeterHit</code> list are not themselves copied.
70       *
71       * @param cluster the <code>BaseCluster</code> to copy
72       */
73      public BaseCluster(final Cluster cluster) {
74  
75          // Copy the hit list.
76          if (cluster.getCalorimeterHits() != null) {
77              for (final CalorimeterHit hit : cluster.getCalorimeterHits()) {
78                  this.hits.add(hit);
79              }
80          }
81  
82          // Copy hit contributions.
83          if (cluster.getHitContributions() != null) {
84              this.hitContributions = new ArrayList<Double>();
85              for (final Double contribution : cluster.getHitContributions()) {
86                  this.hitContributions.add(contribution);
87              }
88          }
89  
90          // Set energy and energy error.
91          this.energy = cluster.getEnergy();
92          this.energyError = cluster.getEnergyError();
93  
94          // Copy position into new array.
95          if (cluster.getPosition() != null) {
96              this.position = new double[cluster.getPosition().length];
97              System.arraycopy(cluster.getPosition(), 0, this.position, 0, cluster.getPosition().length);
98          }
99  
100         // Copy position error into new array.
101         if (cluster.getPositionError() != null) {
102             this.positionError = new double[cluster.getPositionError().length];
103             System.arraycopy(cluster.getPositionError(), 0, this.positionError, 0, cluster.getPositionError().length);
104         }
105 
106         // Set iphi and itheta.
107         this.iphi = cluster.getIPhi();
108         this.itheta = cluster.getITheta();
109 
110         // Copy direction error into new array.
111         if (cluster.getDirectionError() != null) {
112             this.directionError = new double[cluster.getDirectionError().length];
113             System.arraycopy(cluster.getDirectionError(), 0, this.directionError, 0, cluster.getDirectionError().length);
114         }
115 
116         // Copy shape parameters into new array.
117         if (cluster.getShape() != null) {
118             this.shapeParameters = new double[cluster.getShape().length];
119             System.arraycopy(cluster.getShape(), 0, this.shapeParameters, 0, cluster.getShape().length);
120         }
121 
122         // Copy type and PID.
123         this.type = cluster.getType();
124         this.pid = cluster.getParticleId();
125     }
126 
127     /**
128      * Basic constructor that takes a list of hits. It will apply the default energy calculation.
129      *
130      * @param hits the list of CalorimeterHits
131      */
132     public BaseCluster(final List<CalorimeterHit> hits) {
133         this.addHits(hits);
134     }
135 
136     /**
137      * Almost fully qualified constructor, if the cluster's properties are already calculated. The energy given here will override the value
138      * calculated from the hits. If this is not desired, then another constructor should be used instead. This constructor does not allow setting hit
139      * contributions that are different from the hit energies.
140      *
141      * @param hits the list of hits
142      * @param energy the total energy
143      * @param energyError the energy error
144      * @param position the position
145      * @param positionError the position error
146      * @param iphi the intrinsic phi
147      * @param itheta the intrinsic theta
148      * @param directionError the direction error
149      * @param shapeParameters the shape parameters
150      */
151     public BaseCluster(final List<CalorimeterHit> hits, final double energy, final double energyError, final double[] position,
152             final double[] positionError, final double iphi, final double itheta, final double[] directionError, final double[] shapeParameters,
153             final int type, final int pid) {
154 
155         this.addHits(hits);
156 
157         // This will override the energy calculated from the hits, by design!
158         this.energy = energy;
159         this.energyError = energyError;
160 
161         this.position = position;
162         this.positionError = positionError;
163 
164         this.iphi = iphi;
165         this.itheta = itheta;
166 
167         this.directionError = directionError;
168         this.shapeParameters = shapeParameters;
169 
170         this.type = type;
171         this.pid = pid;
172 
173         this.needsPropertyCalculation = false;
174     }
175 
176     /**
177      * Add a sub-cluster to the cluster.
178      *
179      * @param cluster the cluster to add
180      */
181     public void addCluster(final Cluster cluster) {
182         clusters.add(cluster);
183         final List<CalorimeterHit> clusterHits = cluster.getCalorimeterHits();
184         for (int i = 0; i < clusterHits.size(); i++) {
185             hits.add(clusterHits.get(i));
186             hitContributions.add(clusterHits.get(i).getCorrectedEnergy());
187         }
188         energy += cluster.getEnergy();
189         needsPropertyCalculation = true;
190     }
191 
192     /**
193      ***************************************************** Implementation of get methods from the interface. *
194      */
195 
196     /**
197      * Add a list of sub-clusters to the cluster.
198      *
199      * @param the list of clusters to add
200      */
201     public void addClusters(final List<Cluster> clusters) {
202         for (final Cluster cluster : clusters) {
203             this.addCluster(cluster);
204         }
205     }
206 
207     /**
208      * Add a hit to the cluster with default energy contribution.
209      *
210      * @param hit the hit to add
211      */
212     public void addHit(final CalorimeterHit hit) {
213         this.addHit(hit, hit.getCorrectedEnergy());
214         needsPropertyCalculation = true;
215     }
216 
217     /**
218      * Add a hit to the cluster with specified energy contribution.
219      *
220      * @param hit the hit to add
221      * @param contribution the energy contribution of the hit [GeV]
222      */
223     public void addHit(final CalorimeterHit hit, final double contribution) {
224         hits.add(hit);
225         hitContributions.add(contribution);
226         energy += contribution;
227         needsPropertyCalculation = true;
228     }
229 
230     /**
231      * Add a list of hits to the cluster.
232      *
233      * @param the list of hits to add
234      */
235     public void addHits(final List<CalorimeterHit> hits) {
236         for (final CalorimeterHit hit : hits) {
237             this.addHit(hit);
238         }
239     }
240 
241     /**
242      * Calculate the properties of this cluster using the current <code>ClusterPropertyCalculator</code>. The calculated properties will be set on the
243      * following class variables:<br/>
244      * {@link #position}, {@link #positionError}, {@link #iphi}, {@link #itheta}, {@link #directionError}, and {@link #shapeParameters}. Then
245      * {@link #needsPropertyCalculation} will be set to <code>false</code> until the cluster's state changes.
246      */
247     public void calculateProperties() {
248         if (!this.hasPropertyCalculator()) {
249             throw new RuntimeException("No ClusterPropertyCalculator is set on this object.");
250         }
251         calc.calculateProperties(this);
252         this.setPosition(calc.getPosition());
253         this.setPositionError(calc.getPositionError());
254         this.setIPhi(calc.getIPhi());
255         this.setITheta(calc.getITheta());
256         this.setDirectionError(calc.getDirectionError());
257         this.setShapeParameters(calc.getShapeParameters());
258         this.setNeedsPropertyCalculation(false);
259     }
260 
261     /**
262      * Calculate properties if needs property calculation.
263      */
264     void checkCalculateProperties() {
265         if (this.needsPropertyCalculation()) {
266             this.calculateProperties();
267         }
268     }
269 
270     /**
271      * Clone to a new object using the copy constructor.
272      *
273      * @return the new object
274      */
275     @Override
276     public Object clone() {
277         return new BaseCluster(this);
278     }
279 
280     /**
281      * Get the list of CalorimeterHits of this cluster. The hits are not necessarily unique in the list.
282      *
283      * @return the hits comprising the cluster
284      */
285     @Override
286     public List<CalorimeterHit> getCalorimeterHits() {
287         return hits;
288     }
289 
290     /**
291      * Get the clusters that are part of this cluster.
292      *
293      * @return the clusters comprising the cluster
294      */
295     @Override
296     public List<Cluster> getClusters() {
297         return clusters;
298     }
299 
300     /**
301      * Get the direction error of the cluster as a double array of size 6.
302      *
303      * @return the direction error of the cluster
304      */
305     @Override
306     public double[] getDirectionError() {
307         this.checkCalculateProperties();
308         return directionError;
309     }
310 
311     /**
312      * Get the energy of the cluster, which by default will be the sum of the CalorimeterHit corrected energy values.
313      *
314      * @return the energy of the cluster
315      */
316     @Override
317     public double getEnergy() {
318         return energy;
319     }
320 
321     /**
322      * Get the energy error.
323      *
324      * @return the energy error
325      */
326     @Override
327     public double getEnergyError() {
328         return energyError;
329     }
330 
331     /**
332      * Get the individual hit contribution energies. This should be an array of the same size as the hit list. By default this array contains the
333      * hit's corrected energies, but the contributions may be set to different values on a per hit basis using the
334      * {@link #addHit(CalorimeterHit, double)} method.
335      *
336      * @return the individual hit contribution energies
337      */
338     @Override
339     public double[] getHitContributions() {
340         final double[] arrayCopy = new double[hitContributions.size()];
341         for (int i = 0; i < hitContributions.size(); i++) {
342             arrayCopy[i] = hitContributions.get(i);
343         }
344         return arrayCopy;
345     }
346 
347     /**
348      * Get the intrinsic phi direction of the cluster.
349      *
350      * @return the intrinsic phi direction of the cluster
351      */
352     @Override
353     public double getIPhi() {
354         this.checkCalculateProperties();
355         return iphi;
356     }
357 
358     /**
359      * Get the intrinsic theta direction of the cluster.
360      *
361      * @return the intrinsic theta direction of the cluster
362      */
363     @Override
364     public double getITheta() {
365         this.checkCalculateProperties();
366         return itheta;
367     }
368 
369     /**
370      ********************************** Implementation of set methods. *
371      */
372 
373     /**
374      * Get the PDG ID of the particle hypothesis.
375      *
376      * @return the PID
377      */
378     @Override
379     public int getParticleId() {
380         return pid;
381     }
382 
383     /**
384      * Get the position of the cluster as a double array of size 3.
385      *
386      * @return the position of the cluster
387      */
388     @Override
389     public double[] getPosition() {
390         this.checkCalculateProperties();
391         return position;
392     }
393 
394     /**
395      * Get the position error of the cluster as a double array of size 6.
396      *
397      * @return the position error of the cluster
398      */
399     @Override
400     public double[] getPositionError() {
401         this.checkCalculateProperties();
402         return positionError;
403     }
404 
405     /**
406      * Get the shape parameters of the cluster as a double array of unspecified size.
407      *
408      * @return the shape parameters of the cluster
409      */
410     @Override
411     public double[] getShape() {
412         this.checkCalculateProperties();
413         return shapeParameters;
414     }
415 
416     /**
417      * Get the number of hits in the cluster, including hits in sub-clusters. Hits belonging to more than one cluster are counted once.
418      *
419      * @return the size of the cluster
420      */
421     @Override
422     public int getSize() {
423         final Set<CalorimeterHit> hitSet = new HashSet<CalorimeterHit>(this.hits);
424         for (final Cluster cluster : clusters) {
425             hitSet.addAll(cluster.getCalorimeterHits());
426         }
427         final int size = hitSet.size();
428         hitSet.clear();
429         return size;
430     }
431 
432     /**
433      * Get the list of subdetector energy contributions. The ordering and meaning of this array is unspecified by this class.
434      *
435      * @return the list of subdetector energy contributions
436      */
437     @Override
438     public double[] getSubdetectorEnergies() {
439         return subdetectorEnergies;
440     }
441 
442     /**
443      * Get a value defining the type of this cluster.
444      *
445      * @return the type of this cluster
446      */
447     @Override
448     public int getType() {
449         return type;
450     }
451 
452     /**
453      * Return <code>true</code> if property calculator is set.
454      *
455      * @return <code>true</code> if property calculator is set
456      */
457     public boolean hasPropertyCalculator() {
458         return calc != null;
459     }
460 
461     /**
462      * Return <code>true</code> if cluster is flagged as needed a property calculation.
463      *
464      * @return <code>true</code> if cluster needs property calculation
465      */
466     public boolean needsPropertyCalculation() {
467         return needsPropertyCalculation;
468     }
469 
470     /**
471      * Remove a hit from the cluster.
472      *
473      * @param hit the hit to remove
474      */
475     public void removeHit(final CalorimeterHit hit) {
476         final int index = hits.indexOf(hit);
477         hits.remove(hit);
478         final double hitEnergy = hit.getCorrectedEnergy();
479         energy -= hitEnergy;
480         hitContributions.remove(index);
481         needsPropertyCalculation = true;
482     }
483 
484     /**
485      * Set the direction error of the cluster.
486      *
487      * @param directionError the direction error of the cluster
488      * @throws IllegalArgumentException if array is wrong size
489      */
490     public void setDirectionError(final double[] directionError) {
491         if (directionError.length != 6) {
492             throw new IllegalArgumentException("The directionError array argument has the wrong length: " + position.length);
493         }
494         this.directionError = directionError;
495     }
496 
497     /**
498      **************************************************** Convenience methods for adding hits and clusters *
499      */
500 
501     /**
502      * Set a total energy of this cluster, overriding any energy value that may have been automatically calculated from hit energies.
503      *
504      * @param energy the total energy of this cluster
505      */
506     public void setEnergy(final double energy) {
507         this.energy = energy;
508     }
509 
510     /**
511      * Set the error on the energy measurement.
512      *
513      * @param energyError the error on the energ measurement
514      */
515     public void setEnergyError(final double energyError) {
516         this.energyError = energyError;
517     }
518 
519     /**
520      * Set the intrinsic phi of the cluster.
521      *
522      * @param iphi the intrinsic phi of the cluster
523      */
524     public void setIPhi(final double iphi) {
525         this.iphi = iphi;
526     }
527 
528     /**
529      * Set the intrinsic theta of the cluster.
530      *
531      * @param iphi The intrinsic theta of the cluster.
532      */
533     public void setITheta(final double itheta) {
534         this.itheta = itheta;
535     }
536 
537     /**
538      * Manually set whether the cluster needs property calculation.
539      *
540      * @param needsPropertyCalculation <code>true</code> if cluster needs property calculation
541      */
542     public void setNeedsPropertyCalculation(final boolean needsPropertyCalculation) {
543         this.needsPropertyCalculation = needsPropertyCalculation;
544     }
545 
546     /**
547      * Get the PDG ID of the particle hypothesis.
548      *
549      * @return the PID
550      */
551     public void setParticleId(final int pid) {
552         this.pid = pid;
553     }
554 
555     /**
556      ************************************** ClusterPropertyCalculator methods. *
557      */
558 
559     /**
560      * Set the position of the cluster.
561      *
562      * @param position the position of the cluster
563      * @throws IllegalArgumentException if array is wrong size
564      */
565     public void setPosition(final double[] position) {
566         if (position.length != 3) {
567             throw new IllegalArgumentException("The position array argument has the wrong length: " + position.length);
568         }
569         this.position = position;
570     }
571 
572     /**
573      * Set the position error of the cluster.
574      *
575      * @param positionError the position error of the cluster
576      * @throws IllegalArgumentException if array is wrong size
577      */
578     public void setPositionError(final double[] positionError) {
579         if (positionError.length != 6) {
580             throw new IllegalArgumentException("The positionError array argument has the wrong length: " + position.length);
581         }
582         this.positionError = positionError;
583     }
584 
585     /**
586      * Set a property calculator for computing position, etc.
587      *
588      * @param calc the property calculator
589      */
590     public void setPropertyCalculator(final ClusterPropertyCalculator calc) {
591         this.calc = calc;
592     }
593 
594     /**
595      * Set the shape parameters of the cluster.
596      *
597      * @param shapeParameters the shape parameters
598      */
599     public void setShapeParameters(final double[] shapeParameters) {
600         this.shapeParameters = shapeParameters;
601     }
602 
603     /**
604      * Set the subdetector energies.
605      *
606      * @param subdetectorEnergies the subdetector energies
607      */
608     public void setSubdetectorEnergies(final double[] subdetectorEnergies) {
609         this.subdetectorEnergies = subdetectorEnergies;
610     }
611 
612     /**
613      * Set the type of the cluster.
614      *
615      * @param type the type of the cluster
616      */
617     public void setType(final int type) {
618         this.type = type;
619     }
620 }