View Javadoc

1   package org.lcsim.recon.cluster.fixedcone;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.List;
6   import java.util.Map;
7   import org.lcsim.event.CalorimeterHit;
8   import org.lcsim.event.Cluster;
9   import org.lcsim.event.util.CalorimeterHitEsort;
10  import org.lcsim.event.base.BaseCluster;
11  import org.lcsim.recon.cluster.util.Clusterer;
12  import org.lcsim.recon.cluster.util.ClusterESort;
13  import org.lcsim.recon.cluster.util.FixedConeClusterPropertyCalculator;
14  
15  import org.lcsim.util.fourvec.Lorentz4Vector;
16  import org.lcsim.util.fourvec.Momentum4Vector;
17  
18  import org.lcsim.spacegeom.SpacePoint;
19  import org.lcsim.spacegeom.CartesianPoint;
20  
21  import static java.lang.Math.sin;
22  import static java.lang.Math.cos;
23  
24  
25  /**
26   * FixedConeClusterer implements a
27   * <font face="symbol">q-f </font> cone clustering algorithm
28   * that assigns all neighboring hits to the same cluster if they fall
29   * within a radius R of the cluster axis. The axis is originally defined
30   * by a seed cell, and is iteratively updated as cells are added.
31   * This version of the ClusterBuilder splits overlapping clusters
32   * by assigning cells in the overlap region to the nearest cluster axis.
33   *
34   * @author Norman A. Graf, updated by Qingmin Zhang in 19 Dec,2007.
35  ---------------------------------------------------------------* Updates:---------------------------------------------------
36   * 1. Using it you can set the predicting function for cone size based on seed energy (The function format: A*log(Eseed)+B )
37   * 2. Using it you can  apply  the cluster energy cut(it varies frome cluster to cluster according to the seed energy ) 
38   *      before the found cluster being added to the cluster List (function Format :  A*Eseed+B*Eseed*Eseed)
39   * 3. not like last version, the cone sizes of found clusters are different, so admended the rosolve() function for the overlap hits 
40   *       using the cone size information 
41   *Note: if Construction Fucntion without radius parameter is used, it means you will use predicting function. 
42   *      Meanwhile if you don't set the function, default values will be set. 
43   *      Otherwise, it's same to last version.
44  ------------------------------------------------------------------------------------------------------------------------------------
45   */
46  public class FixedConeClusterer implements Clusterer {
47  
48      private double _radius;
49      private double _seedEnergy;
50      private double _minEnergy;
51      private int _numLayers;
52      private double _samplingFraction;
53      private double[] _layerEnergy;
54      private boolean _radiusSetFlag;//it determines which one is used, preset(ture) or calculated with seed energy(false)
55      private double[] _coneFunc;//(The function format: A*log(Eseed)+B )
56      private double[] _cutFunc;//(function Format :  A*Eseed+B*Eseed*Eseed)
57      private static final double PI = Math.PI;
58      private static final double TWOPI = 2. * PI;
59      public FixedConeClusterPropertyCalculator _clusterPropertyCalculator;
60      private FixedConeDistanceMetric _dm;
61  
62      public enum FixedConeDistanceMetric {
63  
64          DOTPRODUCT, DPHIDCOSTHETA, DPHIDTHETA
65      }
66  
67      /**
68       * Constructor
69       *
70       * @param   radius The cone radius in <font face="symbol">q-f </font> space
71       * @param   seed   The minimum energy for a cone seed cell (in GeV)
72       * @param   minE   The minimum energy for a cluster (in GeV)
73       * @param   distMetric The distance metric:
74       *                     0 for dot-product method,
75       *                     1 for R<sup>2</sup> = (d phi)<sup>2 + (d(cos theta))<sup>2</sup>
76       *                       (simplified version of dot-product method)
77       *                     2 for R<sup>2</sup> = (d phi)<sup>2 + (d theta)<sup>2</sup>
78       *                       (backwards compatibility mode with old FixedConeClusterer implementation
79       *                     Anything else will be assumed to be 0.
80       */
81      public FixedConeClusterer(double seed, double minE, double[] coneFunc, double[] cutFunc, FixedConeDistanceMetric distMetric) {
82          // overwrite later with sampling fraction correction
83          _seedEnergy = seed;
84          _minEnergy = minE;
85          _dm = distMetric;//distMetric;
86  
87          _coneFunc = coneFunc;
88          _cutFunc = cutFunc;
89          _radiusSetFlag = false;
90  
91      }
92  
93      public FixedConeClusterer(double seed, double minE, FixedConeDistanceMetric distMetric) {
94          // overwrite later with sampling fraction correction
95          _seedEnergy = seed;
96          _minEnergy = minE;
97          _dm = distMetric;//distMetric;
98          //if the values aren't set, default value will be set as follows
99  
100         _coneFunc = new double[2];
101         _coneFunc[0] = 0.011347;
102         _coneFunc[1] = 0.043117;
103         _cutFunc = new double[2];
104         _cutFunc[0] = 3.2;
105         _cutFunc[1] = 2.0;
106         _radiusSetFlag = false;
107 
108     }
109 
110     public FixedConeClusterer(double radius, double seed, double minE, FixedConeDistanceMetric distMetric) {
111         _radius = radius;
112         // overwrite later with sampling fraction correction
113         _seedEnergy = seed;
114         _minEnergy = minE;
115         _dm = distMetric;//distMetric;
116 
117         _radiusSetFlag = true;
118     }
119 
120     /**
121      * Constructor with default distance metric (dot-product method)
122      *
123      * @param   radius The cone radius in <font face="symbol">q-f </font> space
124      * @param   seed   The minimum energy for a cone seed cell (in GeV)
125      * @param   minE   The minimum energy for a cluster (in GeV)
126      */
127     public FixedConeClusterer(double radius, double seed, double minE) {
128         this(radius, seed, minE, FixedConeDistanceMetric.DOTPRODUCT);
129     }
130 
131     public void setPredictFunc(double[] coneFunc, double[] cutFunc) {
132         _coneFunc = coneFunc;
133         _cutFunc = cutFunc;
134         _radiusSetFlag = false;
135     }
136 
137     public void setRadius(double radius) {
138         _radius = radius;
139         _radiusSetFlag = true;
140     }
141 
142     public void setSeed(double seed) {
143         _seedEnergy = seed;
144     }
145 
146     public void setMinE(double minE) {
147         _minEnergy = minE;
148     }
149 
150     /**
151      * Make clusters from the input map
152      */
153     public List<Cluster> createClusters(Map<Long, CalorimeterHit> in) {
154         List<CalorimeterHit> list = new ArrayList(in.values());
155         return createClusters(list);
156     }
157 
158     /**
159      * Make clusters from the input list
160      */
161     public List<Cluster> createClusters(List<CalorimeterHit> in) {
162 //        IDDecoder decoder;
163         List<Cluster> out = new ArrayList<Cluster>();
164         List<Double> radius = new ArrayList<Double>();
165         _clusterPropertyCalculator = new FixedConeClusterPropertyCalculator();
166 
167         // sort the vector in descending energy for efficiency
168         // this starts with the highest energy seeds.
169         Collections.sort(in, new CalorimeterHitEsort());
170 
171         int nclus = 0;
172         int size = in.size();
173         boolean[] used = new boolean[size];
174 
175         //   outer loop finds a seed
176         for (int i = 0; i < size; ++i) {
177             if (!used[i]) {
178                 CalorimeterHit p = in.get(i);
179                 double Eseed = p.getCorrectedEnergy();
180                 double clusterCut = 0.0;
181                 if (!_radiusSetFlag) {
182                     clusterCut = _cutFunc[0] * Eseed + _cutFunc[1] * Eseed * Eseed;
183                 }
184                 if (p.getCorrectedEnergy() > _seedEnergy) // hit p as the seed of a new cluster
185                 {
186                     if (!_radiusSetFlag) {
187                         _radius = _coneFunc[0] * Math.log(p.getCorrectedEnergy()) + _coneFunc[1];
188                     //if predicted radius is less than zero, it won't be thought as a seed
189                     }
190                     if (_radius < 0.0) {
191                         System.out.println("Engergy = " + p.getCorrectedEnergy() + ",Radius = " + _radius);
192                         break;
193                     }
194                     double rsquared = _radius * _radius;
195                     double[] pos = p.getPosition();
196                     SpacePoint sp = new CartesianPoint(pos);
197                     double phi1 = sp.phi();
198                     double sphi1 = sin(phi1);
199                     double cphi1 = cos(phi1);
200                     double theta1 = sp.theta();
201                     double stheta1 = sin(theta1);
202                     double ctheta1 = cos(theta1);
203                     
204 //                    decoder = p.getIDDecoder();
205 //                    decoder.setID(p.getCellID());
206                     double cellE = p.getCorrectedEnergy();
207 //                    double px = cellE * Math.cos(decoder.getPhi()) * Math.sin(decoder.getTheta());
208 //                    double py = cellE * Math.sin(decoder.getPhi()) * Math.sin(decoder.getTheta());
209 //                    double pz = cellE * Math.cos(decoder.getTheta());
210                     double px = cellE * cphi1 * stheta1;
211                     double py = cellE * sphi1 * stheta1;
212                     double pz = cellE * ctheta1;
213                     
214                     Lorentz4Vector sum = new Momentum4Vector(px, py, pz, cellE);
215                     double phiseed = sum.phi();
216                     double thetaseed = sum.theta();
217 
218                     // constituent cells
219                     List<CalorimeterHit> members = new ArrayList<CalorimeterHit>();
220                     members.add(p);
221                     // inner loop adds neighboring cells to seed
222 
223                     for (int j = i + 1; j < size; ++j) {
224                         if (!used[j]) {
225                             CalorimeterHit p2 = in.get(j);
226                             SpacePoint sp2 = new CartesianPoint(p2.getPosition());
227                             double phi = sp2.phi();
228                             double theta = sp2.theta();
229                             double dphi = phi - phiseed;
230 
231                             if (dphi < -PI) {
232                                 dphi += TWOPI;
233                             }
234                             if (dphi > PI) {
235                                 dphi -= TWOPI;
236                             }
237                             double dtheta = theta - thetaseed;
238                             double R2 = 0;
239                             boolean cond = false;
240 
241                             switch (_dm) {
242                                 case DPHIDCOSTHETA: // R^2 = dphi^2 + d(cos theta)^2
243 
244                                     double dcostheta = cos(theta) - cos(thetaseed);
245                                     R2 = dphi * dphi + dcostheta * dcostheta;
246                                     cond = (R2 < rsquared);
247                                     break;
248                                 case DPHIDTHETA: // R^2 = dphi^2 + dtheta^2
249 
250                                     R2 = dphi * dphi + dtheta * dtheta;
251                                     cond = (R2 < rsquared);
252                                     break;
253                                 case DOTPRODUCT:
254                                 default: // dot product method
255 
256                                     double dotp = Math.sin(theta) * Math.sin(thetaseed) *
257                                             (sin(phi) * sin(phiseed) +
258                                             cos(phi) * cos(phiseed)) +
259                                             cos(theta) * cos(thetaseed);
260                                     cond = (dotp > cos(_radius));
261                                     break;
262                             }
263 
264                             if (cond) {
265                                 //  particle within cone
266                                 cellE = p2.getCorrectedEnergy();
267                                 px = cellE * cos(phi) * sin(theta);
268                                 py = cellE * sin(phi) * sin(theta);
269                                 pz = cellE * cos(theta);
270                                 sum.plusEquals(new Momentum4Vector(px, py, pz, cellE));
271                                 members.add(p2);
272                                 // tag this element so we don't reuse it
273                                 used[j] = true;
274 
275                                 //   recalculate cone center
276                                 phiseed = sum.phi();
277                                 thetaseed = sum.theta();
278                             }
279                         }
280                     }// end of inner loop
281 
282 
283                     // if energy of cluster is large enough add it to the list
284                     if (sum.E() > clusterCut) //_minEnergy)
285                     {
286                         BaseCluster clus = new BaseCluster();
287                         clus.setPropertyCalculator(_clusterPropertyCalculator);
288                         for (CalorimeterHit hit : members) {
289                             clus.addHit(hit);
290                         }
291                         out.add(clus);
292                         radius.add(_radius);
293                         nclus++;
294                     }
295 
296                 }
297             }// end of outer loop
298 
299         }
300 
301         if (nclus > 1) {
302             // sort the clusters in descending energy
303             Collections.sort(out, new ClusterESort());
304             // loop over the found clusters and look for overlaps
305             // i.e distance between clusters is less than 2*R
306             for (int i = 0; i < out.size(); ++i) {
307                 for (int j = i + 1; j < out.size(); ++j) {
308                     double dTheta = dTheta(out.get(i), out.get(j));
309                     if (dTheta < radius.get(i) + radius.get(j)) {
310                         resolve((BaseCluster) (out.get(i)), radius.get(i), (BaseCluster) (out.get(j)), radius.get(j));
311                     }
312                 }
313             }
314         }
315 
316 //        System.out.println("found "+out.size()+ " clusters");
317         return out;
318     }
319 
320     /**
321      * Calculate the angle between two Clusters
322      *
323      * @param   c1  First Cluster
324      * @param   c2  Second Cluster
325      * @return     The angle between the two clusters
326      */
327     public double dTheta(Cluster c1, Cluster c2) {
328         _clusterPropertyCalculator.calculateProperties(c1.getCalorimeterHits());
329         Lorentz4Vector v1 = _clusterPropertyCalculator.vector();
330         _clusterPropertyCalculator.calculateProperties(c2.getCalorimeterHits());
331         Lorentz4Vector v2 = _clusterPropertyCalculator.vector();
332         double costheta = (v1.vec3dot(v2)) / (v1.p() * v2.p());
333         return Math.acos(costheta);
334     }
335 
336     /**
337      * Given two overlapping clusters, assign cells to nearest axis.
338      * The cluster axes are <em> not </em> iterated.
339      * Cluster quantities are recalculated after the split.
340      *
341      * @param   c1 First Cluster
342      * @param   c2 Second Cluster
343      */
344     public void resolve(BaseCluster c1, double coneR1, BaseCluster c2, double coneR2) {
345         // do not recalculate cluster axis until all reshuffling is done
346         // do not want the cones to shift
347         // this behavior may change in the future
348 
349         _clusterPropertyCalculator.calculateProperties(c1.getCalorimeterHits());
350         Lorentz4Vector v1 = _clusterPropertyCalculator.vector();
351         double phi1 = v1.phi();
352         double theta1 = v1.theta();
353         _clusterPropertyCalculator.calculateProperties(c2.getCalorimeterHits());
354         Lorentz4Vector v2 = _clusterPropertyCalculator.vector();
355         double phi2 = v2.phi();
356         double theta2 = v2.theta();
357         List<CalorimeterHit> cells1 = c1.getCalorimeterHits();
358         List<CalorimeterHit> cells2 = c2.getCalorimeterHits();
359         int size2 = cells2.size();
360         List<CalorimeterHit> swap = new ArrayList<CalorimeterHit>();
361 
362         // loop over cells in first cluster...
363         for (int i = 0; i < cells1.size(); ++i) {
364             CalorimeterHit p2 = (CalorimeterHit) cells1.get(i);
365             SpacePoint sp2 = new CartesianPoint(p2.getPosition());
366 //            IDDecoder decoder = p2.getIDDecoder();
367 //            decoder.setID(p2.getCellID());
368             double phi = sp2.phi();
369             double theta = sp2.theta();
370             //distance to cluster 1
371             double dphi1 = phi - phi1;
372             if (dphi1 < -PI) {
373                 dphi1 += TWOPI;
374             }
375             if (dphi1 > PI) {
376                 dphi1 -= TWOPI;
377             }
378             double dtheta1 = theta - theta1;
379             double R1 = dphi1 * dphi1 + (dtheta1) * (dtheta1);
380             // distance to cluster 2
381             double dphi2 = phi - phi2;
382             if (dphi2 < -PI) {
383                 dphi2 += TWOPI;
384             }
385             if (dphi2 > PI) {
386                 dphi2 -= TWOPI;
387             }
388             double dtheta2 = theta - theta2;
389             double R2 = dphi2 * dphi2 + (dtheta2) * (dtheta2);
390 
391             if (R2 / R1 < coneR2 / coneR1) {
392                 swap.add(p2);
393             }
394         }
395         for (CalorimeterHit h : swap) {
396             c1.removeHit(h);
397             c2.addHit(h);
398         }
399         swap = new ArrayList<CalorimeterHit>();
400         // repeat for cluster 2
401         // only loop over cells in original cluster
402         for (int i = 0; i < size2; ++i) {
403             CalorimeterHit p2 = (CalorimeterHit) cells2.get(i);
404 //            IDDecoder decoder = p2.getIDDecoder();
405 //            decoder.setID(p2.getCellID());
406             SpacePoint sp2 = new CartesianPoint(p2.getPosition());
407             double phi = sp2.phi();
408             double theta = sp2.theta();
409             //distance to cluster 1
410             double dphi1 = phi - phi1;
411             if (dphi1 < -PI) {
412                 dphi1 += TWOPI;
413             }
414             if (dphi1 > PI) {
415                 dphi1 -= TWOPI;
416             }
417             double dtheta1 = theta - theta1;
418             double R1 = dphi1 * dphi1 + (dtheta1) * (dtheta1);
419             // distance to cluster 2
420             double dphi2 = phi - phi2;
421             if (dphi2 < -PI) {
422                 dphi2 += TWOPI;
423             }
424             if (dphi2 > PI) {
425                 dphi2 -= TWOPI;
426             }
427             double dtheta2 = theta - theta2;
428             double R2 = dphi2 * dphi2 + (dtheta2) * (dtheta2);
429 
430             if (R1 / R2 < coneR1 / coneR2) {
431                 swap.add(p2);
432             }
433         }
434         for (CalorimeterHit h : swap) {
435             c2.removeHit(h);
436             c1.addHit(h);
437         }
438 
439         c1.calculateProperties();
440         c2.calculateProperties();
441     }
442 
443     public String toString() {
444         return "FixedConeClusterer with radius " + _radius + " seed Energy " + _seedEnergy + " minimum energy " + _minEnergy + "distance metric " + _dm;
445     }
446 }
447