View Javadoc

1   package org.lcsim.fit.helicaltrack;
2   /*
3    * HelicalTrackFitter.java
4    *
5    * Created on March 25, 2006, 6:11 PM
6    *
7    * $Id: HelicalTrackFitter.java,v 1.33 2011/02/03 18:44:28 partridge Exp $
8    */
9   
10  import hep.physics.matrix.SymmetricMatrix;
11  import hep.physics.vec.BasicHep3Vector;
12  import hep.physics.vec.Hep3Vector;
13  
14  import java.util.ArrayList;
15  import java.util.Collections;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  
20  import org.lcsim.fit.circle.CircleFit;
21  import org.lcsim.fit.circle.CircleFitter;
22  import org.lcsim.fit.line.SlopeInterceptLineFit;
23  import org.lcsim.fit.line.SlopeInterceptLineFitter;
24  import org.lcsim.fit.zsegment.ZSegmentFit;
25  import org.lcsim.fit.zsegment.ZSegmentFitter;
26  import org.lcsim.geometry.subdetector.BarrelEndcapFlag;
27  
28  /**
29   * Fit a helix to a set of space points.  First, a circle is fit to the x-y coordinates.
30   * A straight-line fit is then performed on the s-z coordinates.  If there are too few
31   * 3D hits to perform the s-z fit, the ZSegmentFitter is used to find the s-z parameters.
32   *
33   * For disk hits, the measured coordinate is r, not z.  In this case, we use
34   * an estimate of the track slope to transform the uncertainty in r to an
35   * equivalent uncertainty in z using dz = dr * slope.
36   *
37   * The r-phi and z coordinate measurements are assumed to be uncorrelated.  A block
38   * diagonal covariance matrix is formed from the results of the circle and s-z fits,
39   * ignoring any correlations between these fits.
40   *
41   * The resulting track parameters follow the "L3 Convention" (L3 Note 1666)
42   * adopted by org.lcsim.
43   * @author Norman Graf
44   * @version 2.0 (R. Partridge)
45   */
46  public class HelicalTrackFitter {
47  
48      private CircleFitter _cfitter = new CircleFitter();
49      private SlopeInterceptLineFitter _lfitter = new SlopeInterceptLineFitter();
50      private ZSegmentFitter _zfitter = new ZSegmentFitter();
51      private CircleFit _cfit;
52      private SlopeInterceptLineFit _lfit;
53      private ZSegmentFit _zfit;
54      private HelicalTrackFit _fit;
55      private double _tolerance = 3.;
56  
57      /**
58       * Status of the HelicalTrackFit.
59       */
60      public enum FitStatus {
61  
62          /**
63           * Successful Fit.
64           */
65          Success,
66          /**
67           * CircleFit failed.
68           */
69          CircleFitFailed,
70          /**
71           * Inconsistent seed hits
72           */
73          InconsistentSeed,
74          /**
75           * s-z line fit failed.
76           */
77          LineFitFailed,
78          /**
79           * ZSegmentFit failed.
80           */
81          ZSegmentFitFailed
82      };
83  
84      /**
85       * Creates a new instance of HelicalTrackFitter.
86       */
87      public HelicalTrackFitter() {
88      }
89  
90      /**
91       * Perform a helix fit using the specified coordinates and uncertainties.
92       *
93       * This is the original fit method for HelicalTrackFitter and is being kept for
94       * backwards compatibility.  Not recommened for new code, may be deprecated in the
95       * future.
96       * @param x array of x coordinates
97       * @param y array of y coordinates
98       * @param z array of z coordinates
99       * @param drphi error in r-phi hit position
100      * @param dz error in z coordinate (negative for an axial strip of length |dz|*sqrt(12))
101      * @param np number of points
102      * @return fit status
103      */
104     public FitStatus fit(double[] x, double[] y, double[] z, double[] drphi, double[] dz, int np) {
105         List<HelicalTrackHit> hitcol = new ArrayList<HelicalTrackHit>();
106         for (int i = 0; i < np; i++) {
107             Hep3Vector pos = new BasicHep3Vector(x[i], y[i], z[i]);
108             if (dz[i] > 0.) {
109                 HelicalTrackHit hit = new HelicalTrack3DHit(pos, MakeCov(pos, drphi[i], 0., dz[i]),
110                         0., 0., null, "Unknown", 0, BarrelEndcapFlag.BARREL);
111                 hitcol.add(hit);
112             } else {
113                 double zmin = z[i] - Math.abs(dz[i]);
114                 double zmax = z[i] + Math.abs(dz[i]);
115                 HelicalTrackHit hit = new HelicalTrack2DHit(pos, MakeCov(pos, drphi[i], 0., 0.),
116                         0., 0., null, "Unknown", 0, BarrelEndcapFlag.BARREL, zmin, zmax);
117                 hitcol.add(hit);
118             }
119         }
120         return fit(hitcol);
121     }
122 
123     /**
124      * Perform a helix fit of the specified HelicalTrackHits.  Multiple scattering
125      * errors are neglected when this method is used.  The track is assumed to travel
126      * on a straight line from the origin to estimate the track slope (needed to
127      * estimate an effective uncertainty in z for disk hits).
128      * @param hitcol HelicalTrackHits to be fit
129      * @return fit status
130      */
131     public FitStatus fit(List<HelicalTrackHit> hitcol) {
132         Map<HelicalTrackHit, MultipleScatter> msmap = new HashMap<HelicalTrackHit, MultipleScatter>();
133         return fit(hitcol, msmap, null);
134     }
135 
136     /**
137      * Perform a helix fit of the specified HelicalTrackHits, taking into account
138      * multiple scattering errors.  If an approximate HelicalTrackFit is provided,
139      * it will be used to obtain the track slope (needed to estimate an effective
140      * uncertainty in z for disk hits).
141      * @param hitcol HelicalTrackHits to be fit
142      * @param msmap map giving multiple scattering errors for the hits
143      * @param oldhelix approximate HelicalTrackFit (used to estimate track slope)
144      * @return fit status
145      */
146     public FitStatus fit(List<HelicalTrackHit> hitcol, Map<HelicalTrackHit, MultipleScatter> msmap, HelicalTrackFit oldhelix) {
147 
148         //  Check that we have at least 3 hits
149         int nhit = hitcol.size();
150 
151         //  Sort the hits to be monotonic in z so that we can follow a curling track
152         //  It is assumed that the first hit on a track is closer to the origin than the last hit
153         //  It is also assumed that the track won't curl through an angle > 180 degrees between
154         //  neighboring points.  This might be a problem for curlers with small dip angle.
155         Collections.sort(hitcol);
156 
157         //  See if the first hit is closer to the origin than the last hit
158         //  If not, reverse the order of the hits
159         double zfirst = hitcol.get(0).z();
160         double zlast = hitcol.get(nhit - 1).z();
161         if (Math.abs(zfirst) > Math.abs(zlast)) {
162             Collections.reverse(hitcol);
163         }
164 
165         //  Initialize the various fitter outputs
166         _cfit = null;
167         _lfit = null;
168         _zfit = null;
169         _fit = null;
170 
171         //  Create lists for the various types of hits
172         List<HelicalTrackHit> circle_hits = new ArrayList<HelicalTrackHit>();
173         List<HelicalTrackHit> pixel_hits = new ArrayList<HelicalTrackHit>();
174         List<HelicalTrackHit> strip_hits = new ArrayList<HelicalTrackHit>();
175 
176         //  Sort the hits into the appropriate lists
177         for (HelicalTrackHit hit : hitcol) {
178             //  Hits to be used in the circle fit
179             if (hit.drphi() > 0) circle_hits.add(hit);
180             //  Pixel hits
181             if (hit instanceof HelicalTrack3DHit) pixel_hits.add(hit);
182             //  Strip hits
183             if (hit instanceof HelicalTrack2DHit) strip_hits.add(hit);
184             //  Cross hits
185             if (hit instanceof HelicalTrackCross) pixel_hits.add(hit);
186         }
187 
188         //  Check to make sure we have at least 3 circle hits before proceeding
189         int nc = circle_hits.size();
190         if (nc < 3) {
191             return FitStatus.CircleFitFailed;
192         }
193 
194         //  Create the objects that will hold the fit output
195         double[] chisq = new double[2];
196         int[] ndof = new int[2];
197         double[] par = new double[5];
198         SymmetricMatrix cov = new SymmetricMatrix(5);
199 
200         //  Setup for doing the circle fit
201         double[] x = new double[nc];
202         double[] y = new double[nc];
203         double[] wrphi = new double[nc];
204 
205         //  Store the hit coordinates and weights in arrays for the circle fitter
206         for (int i = 0; i < nc; i++) {
207             HelicalTrackHit hit = circle_hits.get(i);
208             //  Store the hit position
209             x[i] = hit.x();
210             y[i] = hit.y();
211             //  Find the weight (= 1/uncertainty^2) for this hit
212             //  First get the multiple scattering uncertainty
213             double drphi_ms = 0.;
214             if (msmap.containsKey(hit)) {
215                 drphi_ms = msmap.get(hit).drphi();
216             }
217             //  Get the hit resolution and combine uncertainties in quadrature
218             double drphi_res = hit.drphi();
219             wrphi[i] = 1. / (drphi_res * drphi_res + drphi_ms * drphi_ms);
220         }
221 
222         //  Call the circle fitter and check for success
223         boolean success = _cfitter.fit(x, y, wrphi, nc);
224         if (!success) {
225             return FitStatus.CircleFitFailed;
226         }
227 
228         //  Get the results of the fit
229         _cfit = _cfitter.getfit();
230 
231         //  Calculate the arc lengths from the DCA to each hit and check for backwards hits
232         Map<HelicalTrackHit, Double> smap = getPathLengths(hitcol);
233 
234         //  If we are going around the circle in the wrong direction, fix it
235         if (smap.get(circle_hits.get(nc-1)) < 0.) {
236 
237             //  Change the circle fit parameters to reverse direction
238             _cfit = CircleFix(_cfitter.getfit());
239 
240             //  Flip the signs of the path lengths
241             for (HelicalTrackHit hit : hitcol) {
242                 double oldpath = smap.get(hit);
243                 smap.put(hit, -oldpath);
244             }
245         }
246 
247         //  Check that things make sense
248         for (HelicalTrackHit hit : smap.keySet()) {
249             if (smap.get(hit) < 0.) return FitStatus.InconsistentSeed;
250         }
251 
252         //  Save the chi^2 and dof for the circle fit
253         chisq[0] = _cfit.chisq();
254         ndof[0] = nc - 3;
255 
256         //  Save the circle fit parameters.  Note that the circle fitter has the
257         //  opposite sign convention for d0 than has been adopted by org.lcsim
258         //  (L3 Note 1666), so the sign of d0 is flipped.
259         par[HelicalTrackFit.dcaIndex] = -1. * _cfit.dca();  // fix d0 sign convention
260         par[HelicalTrackFit.phi0Index] = _cfit.phi();
261         par[HelicalTrackFit.curvatureIndex] = _cfit.curvature();
262 
263         //  Save the covariance matrix, which is passed to us as an array of
264         //  elements in "lower" order.  Note that the order of parameters
265         //  in the circle fitter (curv, phi0, d0) is different from the
266         //  ordering in the covariance matrix, so don't change this code
267         //  unless you really know what you are doing!!
268         //  Also, fix the d0 sign for the d0-omega and d0-phi0 terms.
269         cov.setElement(HelicalTrackFit.curvatureIndex, HelicalTrackFit.curvatureIndex, _cfit.cov()[0]);
270         cov.setElement(HelicalTrackFit.curvatureIndex, HelicalTrackFit.phi0Index, _cfit.cov()[1]);
271         cov.setElement(HelicalTrackFit.phi0Index, HelicalTrackFit.phi0Index, _cfit.cov()[2]);
272         cov.setElement(HelicalTrackFit.curvatureIndex, HelicalTrackFit.dcaIndex, -1. * _cfit.cov()[3]);  // fix d0 sign convention
273         cov.setElement(HelicalTrackFit.phi0Index, HelicalTrackFit.dcaIndex, -1. * _cfit.cov()[4]);  // fix d0 sign convention
274         cov.setElement(HelicalTrackFit.dcaIndex, HelicalTrackFit.dcaIndex, _cfit.cov()[5]);
275 
276         //  Check if we have enough pixel hits to do a straight-line fit of s vs z
277         int npix = pixel_hits.size();
278         if (npix > 1) {
279 
280             //  Setup for the line fit
281             double[] s = new double[npix];
282             double[] z = new double[npix];
283             double[] dz = new double[npix];
284 
285             //  Store the coordinates and errors for the line fit
286             for (int i = 0; i < npix; i++) {
287                 HelicalTrackHit hit = pixel_hits.get(i);
288                 z[i] = hit.z();
289                 dz[i] = HitUtils.zres(hit, msmap, oldhelix);
290                 s[i] = smap.get(hit);
291             }
292 
293             //  Call the line fitter and check for success
294             success = _lfitter.fit(s, z, dz, npix);
295             if (!success) {
296                 return FitStatus.LineFitFailed;
297             }
298 
299             //  Save the line fit, chi^2, and DOF
300             _lfit = _lfitter.getFit();
301             chisq[1] = _lfit.chisquared();
302             ndof[1] = npix - 2;
303 
304             //  Save the line fit parameters
305             par[HelicalTrackFit.z0Index] = _lfit.intercept();
306             par[HelicalTrackFit.slopeIndex] = _lfit.slope();
307 
308             //  Save the line fit covariance matrix elements
309             cov.setElement(HelicalTrackFit.z0Index, HelicalTrackFit.z0Index, Math.pow(_lfit.interceptUncertainty(), 2));
310             cov.setElement(HelicalTrackFit.z0Index, HelicalTrackFit.slopeIndex, _lfit.covariance());
311             cov.setElement(HelicalTrackFit.slopeIndex, HelicalTrackFit.slopeIndex, Math.pow(_lfit.slopeUncertainty(), 2));
312 
313         } else {
314 
315             //  Not enough pixel hits for a line fit, do a ZSegment fit
316 
317             //  If we have one barrel pixel hit, turn it into a pseudo strip hit
318             if (npix == 1) {
319                 //  Get the pixel hit (there should only be 1)
320                 HelicalTrackHit hit = pixel_hits.get(0);
321                 //  Only do this for barrel hits
322                 if (hit.BarrelEndcapFlag() == BarrelEndcapFlag.BARREL) {
323                 //  Create a pseudo strip hit and add it to the list of strip hits
324                     strip_hits.add(
325                             HitUtils.PixelToStrip(hit, smap, msmap, oldhelix, _tolerance));
326                 }
327             }
328 
329             //  We should always have enough hits for a ZSegment fit
330             int nstrip = strip_hits.size();
331             if (nstrip < 2) {
332                 throw new RuntimeException("Too few hits for a ZSegment fit");
333             }
334 
335             //  Setup for the ZSegment fit
336             double[] s = new double[nstrip];
337             double[] zmin = new double[nstrip];
338             double[] zmax = new double[nstrip];
339 
340             //  Store the path lengths and z limits for the ZSegment fit
341             for (int i = 0; i < nstrip; i++) {
342                 HelicalTrack2DHit hit = (HelicalTrack2DHit) strip_hits.get(i);
343                 s[i] = smap.get(hit);
344                 //  Get the multiple scattering uncertainty and adjust z limits accordingly
345                 double dz = 0.;
346                 if (msmap.containsKey(hit)) {
347                     dz = msmap.get(hit).dz();
348                 }
349                 zmin[i] = hit.zmin() - _tolerance * dz;
350                 zmax[i] = hit.zmax() + _tolerance * dz;
351             }
352 
353             //  Call the ZSegment fitter and check for success
354             success = _zfitter.fit(s, zmin, zmax);
355             if (!success) {
356                 return FitStatus.ZSegmentFitFailed;
357             }
358 
359             //  Save the ZSegment fit, chi^2, and DOF
360             _zfit = _zfitter.getFit();
361             chisq[1] = 0.;
362             ndof[1] = 0;
363 
364             //  Save the ZSegment fit parameters
365             par[HelicalTrackFit.z0Index] = _zfit.getCentroid()[0];
366             par[HelicalTrackFit.slopeIndex] = _zfit.getCentroid()[1];
367 
368             //  Save the ZSegment fit covariance matrix elements
369             cov.setElement(HelicalTrackFit.z0Index, HelicalTrackFit.z0Index,
370                     _zfit.getCovariance().e(0, 0));
371             cov.setElement(HelicalTrackFit.z0Index, HelicalTrackFit.slopeIndex,
372                     _zfit.getCovariance().e(0, 1));
373             cov.setElement(HelicalTrackFit.slopeIndex, HelicalTrackFit.slopeIndex,
374                     _zfit.getCovariance().e(1, 1));
375         }
376 
377         //  Include a chisq penalty in the s-z fit if we miss any axial strips
378         if (strip_hits.size() > 0) {
379             chisq[1] += MissedStripPenalty(strip_hits, par, cov, smap, msmap);
380         }
381 
382         //  Create the HelicalTrackFit for this helix
383         _fit = new HelicalTrackFit(par, cov, chisq, ndof, smap, msmap);
384 
385         return FitStatus.Success;
386     }
387 
388     /**
389      * Return the results of the most recent helix fit.  Returns null if the fit
390      * was not successful.
391      * @return HelicalTrackFit from the most recent helix fit
392      */
393     public HelicalTrackFit getFit() {
394         return _fit;
395     }
396 
397     /**
398      * Return the circle fit for the most recent helix fit.  Returns null if the
399      * circle fit was not successful.
400      * @return circle fit from most recent helix fit
401      */
402     public CircleFit getCircleFit() {
403         return _cfit;
404     }
405 
406     /**
407      * Return the s-z line fit for the most recent helix fit.  If the line fit failed
408      * or was not performed due to not having enough 3D hits, null is returned.
409      * @return line fit for most recent helix fit
410      */
411     public SlopeInterceptLineFit getLineFit() {
412         return _lfit;
413     }
414 
415     /**
416      * Return the ZSegmentFit for the most recent helix fit.  If the ZSegmentFit
417      * failed or was not performed, null is returned.
418      * @return z segment fit for most recent helix fit
419      */
420     public ZSegmentFit getZSegmentFit() {
421         return _zfit;
422     }
423 
424     /**
425      * Specify tolerance used in extending axial strips for ZSegmentFits to account
426      * for multiple scattering.  Each end of the strip will be extended by the
427      * product of the tolerance and the multiple scattering error.
428      * @param tolerance tolerance
429      */
430     public void setTolerance(double tolerance) {
431         _tolerance = tolerance;
432         return;
433     }
434 
435     /**
436      * Return the tolerance being used to extend axial strips for ZSegmentFits.
437      * @return tolerance
438      */
439     public double getTolerance() {
440         return _tolerance;
441     }
442 
443     public void setReferencePoint(double x, double y) {
444         _cfitter.setreferenceposition(x, y);
445         return;
446     }
447 
448     /**
449      * Create a Cartesian covariance matrix given uncertainties in polar
450      * coordinates.  It is assumed that the polar coordinate uncertainties
451      * are uncorrelated.
452      * @param pos hit position
453      * @param drphi uncertainty in the r*phi coordinate
454      * @param dr uncertainty in the r coordinate
455      * @param dz uncertainty in the z coordinate
456      * @return covariance matrix
457      */
458     private SymmetricMatrix MakeCov(Hep3Vector pos, double drphi, double dr, double dz) {
459 
460         //  Get the x, y, and r coordinates
461         double x = pos.x();
462         double y = pos.y();
463         double r2 = x * x + y * y;
464 
465         //  Create a new covariance matrix and set the non-zero elements
466         SymmetricMatrix cov = new SymmetricMatrix(3);
467         cov.setElement(0, 0, (y * y * drphi * drphi + x * x * dr * dr) / r2);
468         cov.setElement(0, 1, x * y * (dr * dr - drphi * drphi) / r2);
469         cov.setElement(1, 1, (x * x * drphi * drphi + y * y * dr * dr) / r2);
470         cov.setElement(2, 2, dz * dz);
471 
472         return cov;
473     }
474 
475     /**
476      * Check if the circle finder picks a "backwards" solution with the
477      * charged particle travelling in the wrong direction (e.g. clockwise
478      * when the actual track is going counter-clockwise).  If this happens,
479      * return a new circle fit that reverses the initial direction and
480      * changes the sign of the curvature and DCA.  If the circle fit has the
481      * particle travelling in the right direction, return the original fit.
482      * @param oldfit circle fit to be checked
483      * @param hitlist list of hits used for the circle fit
484      * @return fixed circle fit
485      */
486     private CircleFit CircleFix(CircleFit oldfit) {
487 
488         //  Reverse the direction by changing the sign of dca, curv, and adding pi to phi0
489         double dca = -oldfit.dca();
490         double phi0 = oldfit.phi() + Math.PI;
491         if (phi0 > 2. * Math.PI) {
492             phi0 -= 2. * Math.PI;
493         }
494         double curv = -oldfit.curvature();
495 
496         //  Also fix the affected covariance matrix elements
497         double[] cov = oldfit.cov();
498         cov[1] = -cov[1]; // curv - phi0 element
499         cov[4] = -cov[4]; // phi0 - dca element
500 
501         //  Return a new circle fit with the updated parameters
502         return new CircleFit(oldfit.xref(), oldfit.yref(), curv, phi0, dca, oldfit.chisq(), cov);
503     }
504 
505     /**
506      * Find the x-y path lengths for a list of hits.
507      * @param hits list of hits
508      * @return map containing the path lengths of the hits
509      */
510     private Map<HelicalTrackHit, Double> getPathLengths(List<HelicalTrackHit> hits) {
511 
512         //  Create a map to store the arc lengths
513         Map<HelicalTrackHit, Double> smap = new HashMap<HelicalTrackHit, Double>();
514 
515         //  Initialize looper tracking and iterate over ordered list of hits
516         double slast = 0.;
517         int ilast = -1;
518         double s;
519         for (int i = 0; i < hits.size(); i++) {
520 
521             // Retrieve the next hit ordered by z coordinate and check hit type
522             HelicalTrackHit hit = hits.get(i);
523             if (hit instanceof HelicalTrack2DHit) {
524 
525                 //  Axial hit - measure from the DCA (can't handle loopers)
526                 s = HelixUtils.PathLength(_cfit, hit);
527 
528             } else {
529 
530                 if (ilast < 0) {
531                     //  For the first 3D hit, measure from the DCA
532                     s = HelixUtils.PathLength(_cfit, hit);
533 
534                 } else {
535                     //  For subsequent hits, add in the arc length from the previous 3D hit
536                     s = slast + HelixUtils.PathLength(_cfit, hits.get(ilast), hit);
537                 }
538 
539                 //  Update info on the last 3D hit
540                 ilast = i;
541                 slast = s;
542             }
543 
544             //  Save the arc length for this hit
545             smap.put(hit, s);
546         }
547         return smap;
548     }
549 
550     /**
551      * Calculate a chisq penalty if any axial strip hits lie outside the strip
552      * boundaries in z.
553      * @param hitcol list of hits to check
554      * @param smap map of x-y path lengths
555      * @param msmap map of multiple scatter uncertainties
556      * @return chisq penalty
557      */
558     private double MissedStripPenalty(List<HelicalTrackHit> hitcol, double[] par, SymmetricMatrix cov,
559             Map<HelicalTrackHit, Double> smap, Map<HelicalTrackHit, MultipleScatter> msmap) {
560         //  Get the line fit parameters and uncertainties
561         double z0 = par[HelicalTrackFit.z0Index];
562         double slope = par[HelicalTrackFit.slopeIndex];
563         double cov_z0z0 = cov.e(HelicalTrackFit.z0Index, HelicalTrackFit.z0Index);
564         double cov_slsl = cov.e(HelicalTrackFit.slopeIndex, HelicalTrackFit.slopeIndex);
565         double cov_z0sl = cov.e(HelicalTrackFit.z0Index, HelicalTrackFit.slopeIndex);
566         //  Chisq will hold the penalty
567         double chisq = 0.;
568         //  Loop over HelicalTrack2DHits
569         for (HelicalTrackHit hit : hitcol) {
570             if (hit instanceof HelicalTrack2DHit) {
571                 //  Find the pathmap to this hit
572                 double s = smap.get(hit);
573                 //  Find the predicted z coordinate
574                 double zhelix = z0 + s * slope;
575                 //  Find the uncertainty^2 in the z coordinate
576                 double dzsq = cov_z0z0 + 2. * s * cov_z0sl + s * s * cov_slsl;
577                 //  Find the multiple scattering error
578                 double dz_ms = 0.;
579                 if (msmap.containsKey(hit)) {
580                     dz_ms = msmap.get(hit).dz();
581                 }
582                 //  Add the multiple scattering uncertainty
583                 dzsq += dz_ms * dz_ms;
584                 //  Find the limits in z for the strip
585                 double zmin = ((HelicalTrack2DHit) hit).zmin();
586                 double zmax = ((HelicalTrack2DHit) hit).zmax();
587                 //  Calculate a chisq penalty if the predicted z is not on the strip
588                 if (zhelix < zmin) {
589                     chisq += (zhelix - zmin) * (zhelix - zmin) / dzsq;
590                 }
591                 if (zhelix > zmax) {
592                     chisq += (zhelix - zmax) * (zhelix - zmax) / dzsq;
593                 }
594             }
595         }
596         return chisq;
597     }
598 }