StepUtils.cxx
Go to the documentation of this file.
2 
3 #include <limits.h> // for USHRT_MAX
4 #include <stdlib.h> // for abs, size_t
5 #include <cmath> // for sqrt, atan
6 #include <algorithm> // for find, max
7 #include <array> // for array, arr...
8 #include <bitset> // for bitset<>::...
9 #include <iomanip> // for operator<<
10 #include <iostream> // for cout
11 #include "larcoreobj/SimpleTypesAndConstants/RawTypes.h" // for TDCtick_t
12 #include "larcoreobj/SimpleTypesAndConstants/geo_types.h" // for PlaneID
13 #include "lardataobj/RecoBase/Hit.h" // for Hit
14 #include "larreco/RecoAlg/TCAlg/DebugStruct.h" // for DebugStuff
15 #include "larreco/RecoAlg/TCAlg/TCVertex.h" // for tcc, evt
16 #include "larreco/RecoAlg/TCAlg/Utils.h" // for SetEndPoints
17 #include <math.h> // for abs, nearb...
18 #include <numeric> // for iota
19 #include <string> // for basic_string
20 #include <utility> // for pair
21 #include <vector> // for vector
22 
24 
25 namespace tca {
26 
27  //////////////////////////////////////////
28  void StepAway(TCSlice& slc, Trajectory& tj)
29  {
30  // Step along the direction specified in the traj vector in steps of size step
31  // (wire spacing equivalents). Find hits between the last trajectory point and
32  // the last trajectory point + step. A new trajectory point is added if hits are
33  // found. Stepping continues until no signal is found for two consecutive steps
34  // or until a wire or time boundary is reached.
35 
36  tj.IsGood = false;
37  if(tj.Pts.empty()) return;
38 
39  unsigned short plane = DecodeCTP(tj.CTP).Plane;
40 
41  unsigned short lastPtWithUsedHits = tj.EndPt[1];
42 
43  unsigned short lastPt = lastPtWithUsedHits;
44  // Construct a local TP from the last TP that will be moved on each step.
45  // Only the Pos and Dir variables will be used
46  TrajPoint ltp;
47  ltp.CTP = tj.CTP;
48  ltp.Pos = tj.Pts[lastPt].Pos;
49  ltp.Dir = tj.Pts[lastPt].Dir;
50  // A second TP is cloned from the leading TP of tj, updated with hits, fit
51  // parameters,etc and possibly pushed onto tj as the next TP
52  TrajPoint tp;
53 
54  // assume it is good from here on
55  tj.IsGood = true;
56 
57  unsigned short nMissedSteps = 0;
58  // Use MaxChi chisq cut for stiff trajectories
59  bool useMaxChiCut = (tj.PDGCode == 13 || !tj.Strategy[kSlowing]);
60 
61  // Get the first forecast when there are 6 points with charge
62  tjfs.resize(1);
63  tjfs[0].nextForecastUpdate = 6;
64 
65  for(unsigned short step = 1; step < 10000; ++step) {
66  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
67  // analyze the Tj when there are 6 points to see if we should stop
68  if(npwc == 6 && StopShort(slc, tj, tcc.dbgStp)) break;
69  // Get a forecast of what is ahead.
70  if(tcc.doForecast && !tj.AlgMod[kRvPrp] && npwc == tjfs[tjfs.size() - 1].nextForecastUpdate) {
71  Forecast(slc, tj);
72  SetStrategy(slc, tj);
73  SetPDGCode(slc, tj);
74  }
75  // make a copy of the previous TP
76  lastPt = tj.Pts.size() - 1;
77  tp = tj.Pts[lastPt];
78  ++tp.Step;
79  double stepSize = tcc.VLAStepSize;
80  if(tp.AngleCode < 2) stepSize = std::abs(1/ltp.Dir[0]);
81  // move the local TP position by one step in the right direction
82  for(unsigned short iwt = 0; iwt < 2; ++iwt) ltp.Pos[iwt] += ltp.Dir[iwt] * stepSize;
83  // copy this position into tp
84  tp.Pos = ltp.Pos;
85  tp.Dir = ltp.Dir;
86  if(tcc.dbgStp) {
87  mf::LogVerbatim myprt("TC");
88  myprt<<"StepAway "<<step<<" Pos "<<tp.Pos[0]<<" "<<tp.Pos[1]<<" Dir "<<tp.Dir[0]<<" "<<tp.Dir[1]<<" stepSize "<<stepSize<<" AngCode "<<tp.AngleCode<<" Strategy";
89  for(unsigned short ibt = 0; ibt < StrategyBitNames.size(); ++ibt) {
90  if(tj.Strategy[ibt]) myprt<<" "<<StrategyBitNames[ibt];
91  } // ib
92  } // tcc.dbgStp
93  // hit the boundary of the TPC?
94  if(tp.Pos[0] < 0 || tp.Pos[0] > tcc.maxPos0[plane] ||
95  tp.Pos[1] < 0 || tp.Pos[1] > tcc.maxPos1[plane]) break;
96  // remove the old hits and other stuff
97  tp.Hits.clear();
98  tp.UseHit.reset();
99  tp.FitChi = 0; tp.Chg = 0;
100  tp.Environment.reset();
101  unsigned int wire = std::nearbyint(tp.Pos[0]);
102  if(!evt.goodWire[plane][wire]) tp.Environment[kEnvNotGoodWire] = true;
103  // append to the trajectory
104  tj.Pts.push_back(tp);
105  // update the index of the last TP
106  lastPt = tj.Pts.size() - 1;
107  // look for hits
108  bool sigOK = false;
109  AddHits(slc, tj, lastPt, sigOK);
110  // Check the stop flag
111  if(tj.EndFlag[1][kAtTj]) break;
112  // If successfull, AddHits has defined UseHit for this TP,
113  // set the trajectory endpoints, and define HitPos.
114  if(tj.Pts[lastPt].Hits.empty() && !tj.Pts[lastPt].Environment[kEnvNearSrcHit]) {
115  // Require three points with charge on adjacent wires for small angle
116  // stepping.
117  if(tj.Pts[lastPt].AngleCode == 0 && lastPt == 2) return;
118  // No close hits added.
119  ++nMissedSteps;
120  // First check for no signal in the vicinity. AddHits checks the hit collection for
121  // the current slice. This version of SignalAtTp checks the allHits collection.
122  sigOK = SignalAtTp(ltp);
123  if(lastPt > 0) {
124  // break if this is a reverse propagate activity and there was no signal (not on a dead wire)
125  if(!sigOK && tj.AlgMod[kRvPrp]) break;
126  // Ensure that there is a signal here after missing a number of steps on a LA trajectory
127  if(tj.Pts[lastPt].AngleCode > 0 && nMissedSteps > 4 && !sigOK) break;
128  // the last point with hits (used or not) is the previous point
129  unsigned short lastPtWithHits = lastPt - 1;
130  float tps = TrajPointSeparation(tj.Pts[lastPtWithHits], ltp);
131  float dwc = DeadWireCount(slc, ltp, tj.Pts[lastPtWithHits]);
132  float nMissedWires = tps * std::abs(ltp.Dir[0]) - dwc;
133  float maxWireSkip = tcc.maxWireSkipNoSignal;
134  if(sigOK) maxWireSkip = tcc.maxWireSkipWithSignal;
135  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" StepAway: no hits found at ltp "<<PrintPos(slc, ltp)
136  <<" nMissedWires "<<std::fixed<<std::setprecision(1)<<nMissedWires
137  <<" dead wire count "<<dwc<<" maxWireSkip "<<maxWireSkip<<" tj.PDGCode "<<tj.PDGCode;
138  if(nMissedWires > maxWireSkip) {
139  // We passed a number of wires without adding hits and are ready to quit.
140  // First see if there is one good unused hit on the end TP and if so use it
141  // lastPtWithHits + 1 == lastPt && tj.Pts[lastPtWithHits].Chg == 0 && tj.Pts[lastPtWithHits].Hits.size() == 1
142  if(tj.EndPt[1] < tj.Pts.size() - 1 && tj.Pts[tj.EndPt[1]+1].Hits.size() == 1) {
143  unsigned short lastLonelyPoint = tj.EndPt[1] + 1;
144  unsigned int iht = tj.Pts[lastLonelyPoint].Hits[0];
145  if(slc.slHits[iht].InTraj == 0 && tj.Pts[lastLonelyPoint].Delta < 3 * tj.Pts[lastLonelyPoint].DeltaRMS) {
146  slc.slHits[iht].InTraj = tj.ID;
147  tj.Pts[lastLonelyPoint].UseHit[0] = true;
148  DefineHitPos(slc, tj.Pts[lastLonelyPoint]);
149  SetEndPoints(tj);
150  if(tcc.dbgStp) {
151  mf::LogVerbatim("TC")<<" Added a Last Lonely Hit before breaking ";
152  PrintTP("LLH", slc, lastPt, tj.StepDir, tj.Pass, tj.Pts[lastLonelyPoint]);
153  }
154  }
155  }
156  break;
157  }
158  } // lastPt > 0
159  // no sense keeping this TP on tj if no hits were added
160  tj.Pts.pop_back();
161  continue;
162  } // tj.Pts[lastPt].Hits.empty()
163  // ensure that we actually moved
164  if(lastPt > 0 && PosSep2(tj.Pts[lastPt].Pos, tj.Pts[lastPt-1].Pos) < 0.1) return;
165  // Found hits at this location so reset the missed steps counter
166  nMissedSteps = 0;
167  // Update the last point fit, etc using the just added hit(s)
168  UpdateTraj(slc, tj);
169  // a failure occurred
170  if(tj.NeedsUpdate) return;
171  if(tj.Pts[lastPt].Chg == 0) {
172  // There are points on the trajectory by none used in the last step. See
173  // how long this has been going on
174  float tps = TrajPointSeparation(tj.Pts[tj.EndPt[1]], ltp);
175  float dwc = DeadWireCount(slc, ltp, tj.Pts[tj.EndPt[1]]);
176  float nMissedWires = tps * std::abs(ltp.Dir[0]) - dwc;
177  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Hits exist on the trajectory but are not used. Missed wires "<<std::nearbyint(nMissedWires)<<" dead wire count "<<(int)dwc;
178  // break if this is a reverse propagate activity with no dead wires
179  if(tj.AlgMod[kRvPrp] && dwc == 0) break;
180  if(nMissedWires > tcc.maxWireSkipWithSignal) break;
181  // try this out
182  if(!MaskedHitsOK(slc, tj)) {
183  return;
184  }
185  // check for a series of bad fits and stop stepping
186  if(tcc.useAlg[kStopBadFits] && nMissedWires > 4 && StopIfBadFits(slc, tj)) break;
187  // Keep stepping
188  if(tcc.dbgStp) {
189  if(tj.AlgMod[kRvPrp]) {
190  PrintTrajectory("RP", slc, tj, lastPt);
191  } else {
192  PrintTrajectory("SC", slc, tj, lastPt);
193  }
194  }
195  continue;
196  } // tp.Hits.empty()
197  if(tj.Pts.size() == 3) {
198  // ensure that the last hit added is in the same direction as the first two.
199  // This is a simple way of doing it
200  bool badTj = (PosSep2(tj.Pts[0].HitPos, tj.Pts[2].HitPos) < PosSep2(tj.Pts[0].HitPos, tj.Pts[1].HitPos));
201  // ensure that this didn't start as a small angle trajectory and immediately turn
202  // into a large angle one
203  if(!badTj && tj.Pts[lastPt].AngleCode > tcc.maxAngleCode[tj.Pass]) badTj = true;
204  // check for a large change in angle
205  if(!badTj) {
206  float dang = DeltaAngle(tj.Pts[0].Ang, tj.Pts[2].Ang);
207  if(dang > 0.5) badTj = false;
208  }
209  //check for a wacky delta
210  if(!badTj && tj.Pts[2].Delta > 2) badTj = true;
211  if(badTj) {
212  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Bad Tj found on the third point. Quit stepping.";
213  tj.IsGood = false;
214  return;
215  }
216  } // tj.Pts.size() == 3
217  // Update the local TP with the updated position and direction
218  ltp.Pos = tj.Pts[lastPt].Pos;
219  ltp.Dir = tj.Pts[lastPt].Dir;
220  if(tj.MaskedLastTP) {
221  // see if TPs have been masked off many times and if the
222  // environment is clean. If so, return and try with next pass
223  // cuts
224  if(!MaskedHitsOK(slc, tj)) {
225  if(tcc.dbgStp) {
226  if(tj.AlgMod[kRvPrp]) {
227  PrintTrajectory("RP", slc, tj, lastPt);
228  } else {
229  PrintTrajectory("SC", slc, tj, lastPt);
230  }
231  }
232  return;
233  }
234  if(tcc.dbgStp) {
235  if(tj.AlgMod[kRvPrp]) {
236  PrintTrajectory("RP", slc, tj, lastPt);
237  } else {
238  PrintTrajectory("SC", slc, tj, lastPt);
239  }
240  }
241  continue;
242  }
243  // We have added a TP with hits
244  // check for a kink. Stop crawling if one is found
245  GottaKink(slc, tj, true);
246  if(tj.EndFlag[1][kAtKink]) {
247  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" stop at kink";
248  break;
249  }
250  // See if the Chisq/DOF exceeds the maximum.
251  // UpdateTraj should have reduced the number of points fit
252  // as much as possible for this pass, so this trajectory is in trouble.
253  if(tj.Pts[lastPt].FitChi > tcc.maxChi && useMaxChiCut) {
254  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" bad FitChi "<<tj.Pts[lastPt].FitChi<<" cut "<<tcc.maxChi;
255  // remove the last point before quitting
256  UnsetUsedHits(slc, tj.Pts[lastPt]);
257  SetEndPoints(tj);
258  tj.IsGood = (NumPtsWithCharge(slc, tj, true) > tcc.minPtsFit[tj.Pass]);
259  break;
260  }
261  if(tcc.dbgStp) {
262  if(tj.AlgMod[kRvPrp]) {
263  PrintTrajectory("RP", slc, tj, lastPt);
264  } else {
265  PrintTrajectory("SC", slc, tj, lastPt);
266  }
267  } // tcc.dbgStp
268  } // step
269 
270  SetPDGCode(slc, tj);
271 
272  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"End StepAway with tj size "<<tj.Pts.size();
273 
274  } // StepAway
275 
276 //////////////////////////////////////////
277  bool StopShort(TCSlice& slc, Trajectory& tj, bool prt)
278  {
279  // Analyze the trajectory when it is short (~6 points) to look for a pattern like
280  // this QQQqqq, where Q is a large charge hit and q is a low charge hit. If this
281  // pattern is found, this function removes the last 3 points and returns true.
282  // The calling function (e.g. StepAway) should decide what to do (e.g. stop stepping)
283 
284  // don't use this function during reverse propagation
285  if(tj.AlgMod[kRvPrp]) return false;
286  if(!tcc.useAlg[kStopShort]) return false;
287 
288  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
289  if(npwc > 10) return false;
290  ParFit chgFit;
291  FitPar(slc, tj, tj.EndPt[0], npwc, 1, chgFit, 1);
292  if(prt) {
293  mf::LogVerbatim myprt("TC");
294  myprt<<"StopShort: chgFit at "<<PrintPos(slc, tj.Pts[tj.EndPt[0]]);
295  myprt<<" ChiDOF "<<chgFit.ChiDOF;
296  myprt<<" chg0 "<<chgFit.Par0<<" +/- "<<chgFit.ParErr;
297  myprt<<" slp "<<chgFit.ParSlp<<" +/- "<<chgFit.ParSlpErr;
298  } // prt
299  // Look for a poor charge fit ChiDOF and a significant negative slope. These cuts
300  // **should** prevent removing a genuine Bragg peak at the start, which should have
301  // a good ChiDOF
302  if(chgFit.ChiDOF < 2) return false;
303  if(chgFit.ParSlp > -20) return false;
304  if(prt) PrintTrajectory("SS", slc, tj, USHRT_MAX);
305  // Find the average charge using the first 3 points
306  float cnt = 0;
307  float aveChg = 0;
308  unsigned short lastHiPt = 0;
309  for(unsigned short ipt = 0; ipt < tj.Pts.size(); ++ipt) {
310  auto& tp = tj.Pts[ipt];
311  if(tp.Chg <= 0) continue;
312  aveChg += tp.Chg;
313  ++cnt;
314  lastHiPt = ipt;
315  if(cnt == 3) break;
316  } // tp
317  if(cnt < 3) return false;
318  aveChg /= cnt;
319  if(prt) mf::LogVerbatim("TC")<<" aveChg "<<(int)aveChg<<" last TP AveChg "<<(int)tj.Pts[tj.EndPt[1]].AveChg;
320  // Look for a sudden drop in the charge (< 1/2)
321  unsigned short firstLoPt = lastHiPt + 1;
322  for(unsigned short ipt = lastHiPt + 1; ipt < tj.Pts.size(); ++ipt) {
323  auto& tp = tj.Pts[ipt];
324  if(tp.Chg <= 0 || tp.Chg > 0.5 * aveChg) continue;
325  firstLoPt = ipt;
326  break;
327  } // ipt
328  if(prt) mf::LogVerbatim("TC")<<" stop tracking at "<<PrintPos(slc, tj.Pts[firstLoPt]);
329  // Remove everything from the firstLoPt to the end of the trajectory
330  for(unsigned short ipt = firstLoPt; ipt <= tj.EndPt[1]; ++ipt) UnsetUsedHits(slc, tj.Pts[ipt]);
331  SetEndPoints(tj);
332  UpdateTjChgProperties("SS", slc, tj, prt);
333  tj.AlgMod[kStopShort] = true;
334  return true;
335  } // StopShort
336 
337 //////////////////////////////////////////
339  {
340  // Determine if the tracking strategy is appropriate and make some tweaks if it isn't
341  if(tjfs.empty()) return;
342  // analyze the last forecast
343  auto& tjf = tjfs[tjfs.size() - 1];
344 
345  auto& lastTP = tj.Pts[tj.EndPt[1]];
346  // Stay in Slowing strategy if we are in it and reduce the number of points fit further
347  if(tj.Strategy[kSlowing]) {
348  lastTP.NTPsFit = 5;
349  return;
350  }
351 
352  float npwc = NumPtsWithCharge(slc, tj, false);
353  // Keep using the StiffMu strategy if the tj is long and MCSMom is high
354  if(tj.Strategy[kStiffMu] && tj.MCSMom > 800 && npwc > 200) {
355  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Keep using the StiffMu strategy";
356  return;
357  }
358  bool tkLike = (tjf.outlook < 1.5);
359  // A showering-electron-like trajectory
360  bool chgIncreasing = (tjf.chgSlope > 0);
361  // A showering-electron-like trajectory
362  bool shLike = (tjf.outlook > 2 && chgIncreasing);
363  if(!shLike) shLike = tjf.showerLikeFraction > 0.5;
364  float momRat = 0;
365  if(tj.MCSMom > 0) momRat = (float)tjf.MCSMom / (float)tj.MCSMom;
366  if(tcc.dbgStp) {
367  mf::LogVerbatim myprt("TC");
368  myprt<<"SetStrategy: npwc "<<npwc<<" outlook "<<tjf.outlook;
369  myprt<<" tj MCSMom "<<tj.MCSMom<<" forecast MCSMom "<<tjf.MCSMom;
370  myprt<<" momRat "<<std::fixed<<std::setprecision(2)<<momRat;
371  myprt<<" tkLike? "<<tkLike<<" shLike? "<<shLike;
372  myprt<<" chgIncreasing? "<<chgIncreasing;
373  myprt<<" leavesBeforeEnd? "<<tjf.leavesBeforeEnd<<" endBraggPeak? "<<tjf.endBraggPeak;
374  myprt<<" nextForecastUpdate "<<tjf.nextForecastUpdate;
375  }
376  if(tjf.outlook < 0) return;
377  // Look for a long clean muon in the forecast
378  bool stiffMu = (tkLike && tjf.MCSMom > 600 && tjf.nextForecastUpdate > 100);
379  if(stiffMu) {
380  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: High MCSMom, long forecast. Use the StiffMu strategy";
381  tj.Strategy.reset();
382  tj.Strategy[kStiffMu] = true;
383  return;
384  } // StiffMu
385  bool notStiff = (!tj.Strategy[kStiffEl] && !tj.Strategy[kStiffMu]);
386  if(notStiff && !shLike && tj.MCSMom < 100 && tjf.MCSMom < 100 && chgIncreasing) {
387  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Low MCSMom. Use the Slowing Tj strategy";
388  tj.Strategy.reset();
389  tj.Strategy[kSlowing] = true;
390  lastTP.NTPsFit = 5;
391  return;
392  } // Low MCSMom
393  if(notStiff && !shLike && tj.MCSMom < 200 && momRat < 0.7 && chgIncreasing) {
394  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Low MCSMom & low momRat. Use the Slowing Tj strategy";
395  tj.Strategy.reset();
396  tj.Strategy[kSlowing] = true;
397  lastTP.NTPsFit = 5;
398  return;
399  } // low MCSMom
400  if(!tjf.leavesBeforeEnd && tjf.endBraggPeak) {
401  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Found a Bragg peak. Use the Slowing Tj strategy";
402  tj.Strategy.reset();
403  tj.Strategy[kSlowing] = true;
404  lastTP.NTPsFit = 5;
405  return;
406  } // tracklike with Bragg peak
407  if(tkLike && tjf.nextForecastUpdate > 100 && tjf.leavesBeforeEnd && tjf.MCSMom < 500) {
408  // A long track-like trajectory that has many points fit and the outlook is track-like and
409  // it leaves the forecast polygon. Don't change the strategy but decrease the number of points fit
410  lastTP.NTPsFit /= 2;
411  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Long track-like wandered out of forecast envelope. Reduce NTPsFit to "<<lastTP.NTPsFit;
412  return;
413  } // fairly long and leaves the side
414  // a track-like trajectory that has high MCSMom in the forecast and hits a shower
415  if(tkLike && tjf.MCSMom > 600 && (tjf.foundShower || tjf.chgFitChiDOF > 20)) {
416  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: high MCSMom "<<tjf.MCSMom<<" and a shower ahead. Use the StiffEl strategy";
417  tj.Strategy.reset();
418  tj.Strategy[kStiffEl] = true;
419  // we think we know the direction (towards the shower) so startEnd is 0
420  tj.StartEnd = 0;
421  return;
422  } // Stiff electron
423  if(shLike && !tjf.leavesBeforeEnd) {
424  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"SetStrategy: Inside a shower. Use the StiffEl strategy";
425  tj.Strategy.reset();
426  tj.Strategy[kStiffEl] = true;
427  // we think we know the direction (towards the shower) so startEnd is 0
428  tj.StartEnd = 0;
429  return;
430  }
431  } // SetStrategy
432 
433  //////////////////////////////////////////
434  void Forecast(TCSlice& slc, const Trajectory& tj)
435  {
436  // Extrapolate the last TP of tj by many steps and return a forecast of what is ahead
437  // -1 error or not sure
438  // ~1 track-like with a slight chance of showers
439  // >2 shower-like
440  // nextForecastUpdate is set to the number of points on the trajectory when this function should
441  // be called again
442 
443  if(tj.Pts[tj.EndPt[1]].AngleCode == 2) return;
444 
445  // add a new forecast
446  tjfs.resize(tjfs.size() + 1);
447  // assume there is insufficient info to make a decision
448  auto& tjf = tjfs[tjfs.size() - 1];
449  tjf.outlook = -1;
450  tjf.nextForecastUpdate = USHRT_MAX;
451 
452  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
453  unsigned short istp = 0;
454  unsigned short nMissed = 0;
455 
456  bool doPrt = tcc.dbgStp;
457  // turn off annoying output from DefineHitPos
458  if(doPrt) tcc.dbgStp = false;
459  // find the minimum average TP charge. This will be used to calculate the
460  // 'effective number of hits' on a wire = total charge on the wire within the
461  // window / (minimum average TP charge). This is intended to reduce the sensitivity
462  // of this metric to the hit finder configuration
463  float minAveChg = 1E6;
464  for(unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
465  if(tj.Pts[ipt].AveChg <= 0) continue;
466  if(tj.Pts[ipt].AveChg > minAveChg) continue;
467  minAveChg = tj.Pts[ipt].AveChg;
468  } // ipt
469  if(minAveChg <= 0 || minAveChg == 1E6) return;
470  // start a forecast Tj comprised of the points in the forecast envelope
471  Trajectory fctj;
472  fctj.CTP = tj.CTP;
473  fctj.ID = evt.WorkID;
474  // make a local copy of the last point
475  auto ltp = tj.Pts[tj.EndPt[1]];
476  // Use the hits position instead of the fitted position so that a bad
477  // fit doesn't screw up the forecast.
478  float forecastWin0 = std::abs(ltp.Pos[1] - ltp.HitPos[1]);
479  if(forecastWin0 < 1) forecastWin0 = 1;
480  ltp.Pos = ltp.HitPos;
481  double stepSize = std::abs(1/ltp.Dir[0]);
482  float window = tcc.showerTag[7] * stepSize;
483  if(doPrt) {
484  mf::LogVerbatim("TC")<<"Forecast T"<<tj.ID<<" PDGCode "<<tj.PDGCode<<" npwc "<<npwc<<" minAveChg "<<(int)minAveChg<<" stepSize "<<std::setprecision(2)<<stepSize<<" window "<<window;
485  mf::LogVerbatim("TC")<<" stp ___Pos____ nTPH Chg ChgPull Delta DRMS chgWid nTkLk nShLk";
486  }
487  unsigned short plane = DecodeCTP(ltp.CTP).Plane;
488  float totHits = 0;
489  fctj.TotChg = 0;
490  float maxChg = 0;
491  unsigned short maxChgPt = 0;
492  unsigned short leavesNear = USHRT_MAX;
493  bool leavesBeforeEnd = false;
494  unsigned short showerStartNear = USHRT_MAX;
495  unsigned short showerEndNear = USHRT_MAX;
496  float nShLike = 0;
497  float nTkLike = 0;
498  unsigned short trimPts = 0;
499  for(istp = 0; istp < 1000; ++istp) {
500  // move the local TP position by one step in the right direction
501  for(unsigned short iwt = 0; iwt < 2; ++iwt) ltp.Pos[iwt] += ltp.Dir[iwt] * stepSize;
502  unsigned int wire = std::nearbyint(ltp.Pos[0]);
503  if(wire < slc.firstWire[plane]) break;
504  if(wire > slc.lastWire[plane]-1) break;
505  MoveTPToWire(ltp, (float)wire);
506  ++ltp.Step;
507  if(FindCloseHits(slc, ltp, window, kAllHits)) {
508  // Found hits or the wire is dead
509  // set all hits used so that we can use DefineHitPos. Note that
510  // the hit InTraj is not used or tested in DefineHitPos so this doesn't
511  // screw up any assns
512  if(!ltp.Environment[kEnvNotGoodWire]) {
513  nMissed = 0;
514  ltp.UseHit.set();
515  DefineHitPos(slc, ltp);
516  fctj.TotChg += ltp.Chg;
517  ltp.Delta = PointTrajDOCA(slc, ltp.HitPos[0], ltp.HitPos[1], ltp);
518  ltp.DeltaRMS = ltp.Delta / window;
519  ltp.Environment.reset();
520  totHits += ltp.Hits.size();
521  if(ltp.Chg > maxChg) {
522  maxChg = ltp.Chg;
523  maxChgPt = fctj.Pts.size();
524  }
525  // Note that ChgPull uses the average charge and charge RMS of the
526  // trajectory before it entered the forecast envelope
527  ltp.ChgPull = (ltp.Chg / minAveChg - 1) / tj.ChgRMS;
528  if((ltp.ChgPull > 3 && ltp.Hits.size() > 1) || ltp.ChgPull > 10) {
529  ++nShLike;
530  // break if it approaches the side of the envelope
531  ltp.Environment[kEnvNearShower] = true;
532  // flag a showerlike TP so it isn't used in the MCSMom calculation
533  ltp.HitPosErr2 = 100;
534  } else {
535  ++nTkLike;
536  ltp.Environment[kEnvNearShower] = false;
537  }
538  if(fctj.Pts.size() > 10) {
539  float shFrac = nShLike / (nShLike + nTkLike);
540  if(shFrac > 0.5) {
541  if(doPrt) mf::LogVerbatim("TC")<<"Getting showerlike - break";
542  break;
543  }
544  } // fctj.Pts.size() > 6
545  // break if it approaches the side of the envelope
546  if(ltp.DeltaRMS > 0.8) {
547  leavesNear = npwc + fctj.Pts.size();
548  if(doPrt) mf::LogVerbatim("TC")<<"leaves before end - break";
549  leavesBeforeEnd = true;
550  break;
551  }
552  fctj.Pts.push_back(ltp);
553  if(doPrt) {
554  mf::LogVerbatim myprt("TC");
555  myprt<<std::setw(4)<<npwc + fctj.Pts.size()<<" "<<PrintPos(slc, ltp);
556  myprt<<std::setw(5)<<ltp.Hits.size();
557  myprt<<std::setw(5)<<(int)ltp.Chg;
558  myprt<<std::fixed<<std::setprecision(1);
559  myprt<<std::setw(8)<<ltp.ChgPull;
560  myprt<<std::setw(8)<<ltp.Delta;
561  myprt<<std::setw(8)<<std::setprecision(2)<<ltp.DeltaRMS;
562  myprt<<std::setw(8)<<sqrt(ltp.HitPosErr2);
563  myprt<<std::setw(6)<<(int)nTkLike;
564  myprt<<std::setw(6)<<(int)nShLike;
565  } // doPrt
566  }
567  } else {
568  // no hits found
569  ++nMissed;
570  if(nMissed == 10) {
571  if(doPrt) mf::LogVerbatim("TC")<<"No hits found after 10 steps - break";
572  break;
573  }
574  } // no hits found
575  } // istp
576  // not enuf info to make a forecast
577  tcc.dbgStp = doPrt;
578  if(fctj.Pts.size() < 3) return;
579  // truncate and re-calculate totChg?
580  if(trimPts > 0) {
581  // truncate the forecast trajectory
582  fctj.Pts.resize(fctj.Pts.size() - trimPts);
583  // recalculate the total charge
584  fctj.TotChg = 0;
585  for(auto& tp : fctj.Pts) fctj.TotChg += tp.Chg;
586  } // showerEndNear != USHRT_MAX
587  SetEndPoints(fctj);
588  fctj.MCSMom = MCSMom(slc, fctj);
589  tjf.MCSMom = fctj.MCSMom;
590  ParFit chgFit;
591  if(maxChgPt > 0.3 * fctj.Pts.size() && maxChg > 3 * tj.AveChg) {
592  // find the charge slope from the beginning to the maxChgPt if it is well away
593  // from the start and it is very large
594  FitPar(slc, fctj, 0, maxChgPt, 1, chgFit, 1);
595  } else {
596  FitPar(slc, fctj, 0, fctj.Pts.size(), 1, chgFit, 1);
597  }
598  tjf.chgSlope = chgFit.ParSlp;
599  tjf.chgSlopeErr = chgFit.ParSlpErr;
600  tjf.chgFitChiDOF = chgFit.ChiDOF;
601  ChkStop(slc, fctj);
602  UpdateTjChgProperties("Fc", slc, fctj, false);
603  tjf.chgRMS = fctj.ChgRMS;
604  tjf.endBraggPeak = fctj.EndFlag[1][kBragg];
605  // Set outlook = Estimate of the number of hits per wire
606  tjf.outlook = fctj.TotChg / (fctj.Pts.size() * tj.AveChg);
607  // assume we got to the end
608  tjf.nextForecastUpdate = npwc + fctj.Pts.size();
609  tjf.leavesBeforeEnd = leavesBeforeEnd;
610  tjf.foundShower = false;
611  if(leavesNear < tjf.nextForecastUpdate) {
612  // left the side
613  tjf.nextForecastUpdate = leavesNear;
614  } else if(showerStartNear < tjf.nextForecastUpdate) {
615  // found a shower start
616  tjf.nextForecastUpdate = showerStartNear;
617  tjf.foundShower = true;
618  } else if(showerEndNear < tjf.nextForecastUpdate) {
619  // found a shower end
620  tjf.nextForecastUpdate = showerEndNear;
621  }
622  nShLike = 0;
623  for(auto& tp : fctj.Pts) if(tp.Environment[kEnvNearShower]) ++nShLike;
624  tjf.showerLikeFraction = (float)nShLike / (float)fctj.Pts.size();
625 
626  if(doPrt) {
627  mf::LogVerbatim myprt("TC");
628  myprt<<"Forecast T"<<tj.ID<<" tj.AveChg "<<(int)tj.AveChg;
629  myprt<<" start "<<PrintPos(slc, tj.Pts[tj.EndPt[1]])<<" cnt "<<fctj.Pts.size()<<" totChg "<<(int)fctj.TotChg;
630  myprt<<" last pos "<<PrintPos(slc, ltp);
631  myprt<<" MCSMom "<<tjf.MCSMom;
632  myprt<<" outlook "<<std::fixed<<std::setprecision(2)<<tjf.outlook;
633  myprt<<" chgSlope "<<std::setprecision(1)<<tjf.chgSlope<<" +/- "<<tjf.chgSlopeErr;
634  myprt<<" chgRMS "<<std::setprecision(1)<<tjf.chgRMS;
635  myprt<<" endBraggPeak "<<tjf.endBraggPeak;
636  myprt<<" chiDOF "<<tjf.chgFitChiDOF;
637  myprt<<" showerLike fraction "<<tjf.showerLikeFraction;
638  myprt<<" nextForecastUpdate "<<tjf.nextForecastUpdate;
639  myprt<<" leavesBeforeEnd? "<<tjf.leavesBeforeEnd;
640  myprt<<" foundShower? "<<tjf.foundShower;
641  }
642 
643  } // Forecast
644 
645  //////////////////////////////////////////
647  {
648  // A different stategy for updating a high energy electron trajectories
649  if(!tj.Strategy[kStiffEl]) return;
650  TrajPoint& lastTP = tj.Pts[tj.EndPt[1]];
651  // Set the lastPT delta before doing the fit
652  lastTP.Delta = PointTrajDOCA(slc, lastTP.HitPos[0], lastTP.HitPos[1], lastTP);
653  if(tj.Pts.size() < 30) lastTP.NTPsFit += 1;
654  FitTraj(slc, tj);
655  UpdateTjChgProperties("UET", slc, tj, tcc.dbgStp);
656  UpdateDeltaRMS(slc, tj);
657  tj.MCSMom = MCSMom(slc, tj);
658  if(tcc.dbgStp) {
659  mf::LogVerbatim("TC")<<"UpdateStiffEl: lastPt "<<tj.EndPt[1]<<" Delta "<<lastTP.Delta<<" AngleCode "<<lastTP.AngleCode<<" FitChi "<<lastTP.FitChi<<" NTPsFit "<<lastTP.NTPsFit<<" MCSMom "<<tj.MCSMom;
660  }
661  tj.NeedsUpdate = false;
662  tj.PDGCode = 111;
663  return;
664  } // UpdateStiffTj
665 
666  //////////////////////////////////////////
667  void UpdateTraj(TCSlice& slc, Trajectory& tj)
668  {
669  // Updates the last added trajectory point fit, average hit rms, etc.
670 
671  tj.NeedsUpdate = true;
672  tj.MaskedLastTP = false;
673 
674  if(tj.EndPt[1] < 1) return;
675 
676  if(tj.Strategy[kStiffEl]) {
677  UpdateStiffEl(slc, tj);
678  return;
679  }
680  unsigned int lastPt = tj.EndPt[1];
681  TrajPoint& lastTP = tj.Pts[lastPt];
682  // nothing needs to be done if the last point has no hits but is near a hit in the
683  // srcHit collection
684  if(lastTP.Hits.empty() && lastTP.Environment[kEnvNearSrcHit]) {
685  tj.NeedsUpdate = false;
686  return;
687  }
688  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
689 
690  // find the previous TP that has hits (and was therefore in the fit)
691  unsigned short prevPtWithHits = USHRT_MAX;
692  unsigned short firstFitPt = tj.EndPt[0];
693  for(unsigned short ii = 1; ii < tj.Pts.size(); ++ii) {
694  unsigned short ipt = lastPt - ii;
695  if(tj.Pts[ipt].Chg > 0) {
696  prevPtWithHits = ipt;
697  break;
698  }
699  if(ipt == 0) break;
700  } // ii
701  if(prevPtWithHits == USHRT_MAX) return;
702 
703  // define the FitChi threshold above which something will be done
704  float maxChi = 2;
705  unsigned short minPtsFit = tcc.minPtsFit[tj.Pass];
706  // just starting out?
707  if(lastPt < 4) minPtsFit = 2;
708  bool cleanMuon = (tj.PDGCode == 13 && TrajIsClean(slc, tj, tcc.dbgStp) && !tj.Strategy[kSlowing]);
709  // was !TrajIsClean...
710  if(cleanMuon) {
711  // Fitting a clean muon
712  maxChi = tcc.maxChi;
713  minPtsFit = lastPt / 3;
714  }
715 
716  // Set the lastPT delta before doing the fit
717  lastTP.Delta = PointTrajDOCA(slc, lastTP.HitPos[0], lastTP.HitPos[1], lastTP);
718 
719  // update MCSMom. First ensure that nothing bad has happened
720  if(npwc > 3 && tj.Pts[lastPt].Chg > 0 && !tj.Strategy[kSlowing]) {
721  short newMCSMom = MCSMom(slc, tj);
722  short minMCSMom = 0.5 * tj.MCSMom;
723  if(lastPt > 10 && newMCSMom < minMCSMom) {
724  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"UT: MCSMom took a nose-dive "<<newMCSMom;
725  UnsetUsedHits(slc, lastTP);
726  DefineHitPos(slc, lastTP);
727  SetEndPoints(tj);
728  tj.NeedsUpdate = false;
729  return;
730  }
731  tj.MCSMom = newMCSMom;
732  } // npwc > 3
733 
734  if(tcc.dbgStp) {
735  mf::LogVerbatim("TC")<<"UT: lastPt "<<lastPt<<" lastTP.Delta "<<lastTP.Delta<<" previous point with hits "<<prevPtWithHits<<" tj.Pts size "<<tj.Pts.size()<<" AngleCode "<<lastTP.AngleCode<<" PDGCode "<<tj.PDGCode<<" maxChi "<<maxChi<<" minPtsFit "<<minPtsFit<<" MCSMom "<<tj.MCSMom;
736  }
737 
738  UpdateTjChgProperties("UT", slc, tj, tcc.dbgStp);
739 
740  if(lastPt == 1) {
741  // Handle the second trajectory point. No error calculation. Just update
742  // the position and direction
743  lastTP.NTPsFit = 2;
744  FitTraj(slc, tj);
745  lastTP.FitChi = 0.01;
746  lastTP.AngErr = tj.Pts[0].AngErr;
747  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"UT: Second traj point pos "<<lastTP.Pos[0]<<" "<<lastTP.Pos[1]<<" dir "<<lastTP.Dir[0]<<" "<<lastTP.Dir[1];
748  tj.NeedsUpdate = false;
749  SetAngleCode(lastTP);
750  return;
751  }
752 
753  if(lastPt == 2) {
754  // Third trajectory point. Keep it simple
755  lastTP.NTPsFit = 3;
756  FitTraj(slc, tj);
757  tj.NeedsUpdate = false;
758  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"UT: Third traj point fit "<<lastTP.FitChi;
759  SetAngleCode(lastTP);
760  return;
761  }
762 
763  // Fit with > 2 TPs
764  // Keep adding hits until Chi/DOF exceeds 1
765  if(tj.Pts[prevPtWithHits].FitChi < 1 && !tj.Strategy[kSlowing]) lastTP.NTPsFit += 1;
766  // Reduce the number of points fit if the trajectory is long and chisq is getting a bit larger
767  if(lastPt > 20 && tj.Pts[prevPtWithHits].FitChi > 1.5 && lastTP.NTPsFit > minPtsFit) lastTP.NTPsFit -= 2;
768  // don't let long muon fits get too long
769  if(cleanMuon && lastPt > 200 && tj.Pts[prevPtWithHits].FitChi > 1.0) lastTP.NTPsFit -= 2;
770  FitTraj(slc, tj);
771 
772  // don't get too fancy when we are starting out
773  if(lastPt < 6) {
774  tj.NeedsUpdate = false;
775  UpdateDeltaRMS(slc, tj);
776  SetAngleCode(lastTP);
777  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Return with lastTP.FitChi "<<lastTP.FitChi<<" Chg "<<lastTP.Chg;
778  return;
779  }
780 
781  // find the first point that was fit.
782  unsigned short cnt = 0;
783  for(unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
784  unsigned short ipt = lastPt - ii;
785  if(tj.Pts[ipt].Chg > 0) {
786  firstFitPt = ipt;
787  ++cnt;
788  }
789  if(cnt == lastTP.NTPsFit) break;
790  if(ipt == 0) break;
791  }
792 
793  unsigned short ndead = DeadWireCount(slc, lastTP.HitPos[0], tj.Pts[firstFitPt].HitPos[0], tj.CTP);
794  if(lastTP.FitChi > 1.5 && tj.Pts.size() > 6) {
795  // A large chisq jump can occur if we just jumped a large block of dead wires. In
796  // this case we don't want to mask off the last TP but reduce the number of fitted points
797  // This count will be off if there a lot of dead or missing wires...
798  // reduce the number of points significantly
799  if(ndead > 5 && !cleanMuon) {
800  if(lastTP.NTPsFit > 5) lastTP.NTPsFit = 5;
801  } else {
802  // Have a longish trajectory and chisq was a bit large.
803  // Was this a sudden occurrence and the fraction of TPs are included
804  // in the fit? If so, we should mask off this
805  // TP and keep going. If these conditions aren't met, we
806  // should reduce the number of fitted points
807  float chirat = 0;
808  if(prevPtWithHits != USHRT_MAX && tj.Pts[prevPtWithHits].FitChi > 0) chirat = lastTP.FitChi / tj.Pts[prevPtWithHits].FitChi;
809  // Don't mask hits when doing RevProp. Reduce NTPSFit instead
810  tj.MaskedLastTP = (chirat > 1.5 && lastTP.NTPsFit > 0.3 * NumPtsWithCharge(slc, tj, false) && !tj.AlgMod[kRvPrp]);
811  // BB April 19, 2018: Don't mask TPs on low MCSMom Tjs
812  if(tj.MaskedLastTP && tj.MCSMom < 30) tj.MaskedLastTP = false;
813  if(tcc.dbgStp) {
814  mf::LogVerbatim("TC")<<" First fit chisq too large "<<lastTP.FitChi<<" prevPtWithHits chisq "<<tj.Pts[prevPtWithHits].FitChi<<" chirat "<<chirat<<" NumPtsWithCharge "<<NumPtsWithCharge(slc, tj, false)<<" tj.MaskedLastTP "<<tj.MaskedLastTP;
815  }
816  // we should also mask off the last TP if there aren't enough hits
817  // to satisfy the minPtsFit constraint
818  if(!tj.MaskedLastTP && NumPtsWithCharge(slc, tj, true) < minPtsFit) tj.MaskedLastTP = true;
819  } // few dead wires
820  } // lastTP.FitChi > 2 ...
821 
822  // Deal with a really long trajectory that is in trouble (uB cosmic).
823  if(tj.PDGCode == 13 && lastTP.FitChi > tcc.maxChi) {
824  if(lastTP.NTPsFit > 1.3 * tcc.muonTag[0]) {
825  lastTP.NTPsFit *= 0.8;
826  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Muon - Reduce NTPsFit "<<lastPt;
827  } else {
828  tj.MaskedLastTP = true;
829  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Muon - mask last point "<<lastPt;
830  }
831  }
832 
833  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"UT: First fit "<<lastTP.Pos[0]<<" "<<lastTP.Pos[1]<<" dir "<<lastTP.Dir[0]<<" "<<lastTP.Dir[1]<<" FitChi "<<lastTP.FitChi<<" NTPsFit "<<lastTP.NTPsFit<<" ndead wires "<<ndead<<" tj.MaskedLastTP "<<tj.MaskedLastTP;
834  if(tj.MaskedLastTP) {
835  UnsetUsedHits(slc, lastTP);
836  DefineHitPos(slc, lastTP);
837  SetEndPoints(tj);
838  lastPt = tj.EndPt[1];
839  lastTP.NTPsFit -= 1;
840  FitTraj(slc, tj);
841  UpdateTjChgProperties("UT", slc, tj, tcc.dbgStp);
842  SetAngleCode(lastTP);
843  return;
844  } else {
845  // a more gradual change in chisq. Maybe reduce the number of points
846  unsigned short newNTPSFit = lastTP.NTPsFit;
847  // reduce the number of points fit to keep Chisq/DOF < 2 adhering to the pass constraint
848  // and also a minimum number of points fit requirement for long muons
849  float prevChi = lastTP.FitChi;
850  unsigned short ntry = 0;
851  float chiCut = 1.5;
852  if(tj.Strategy[kStiffMu]) chiCut = 5;
853  while(lastTP.FitChi > chiCut && lastTP.NTPsFit > minPtsFit) {
854  if(lastTP.NTPsFit > 15) {
855  newNTPSFit = 0.7 * newNTPSFit;
856  } else if(lastTP.NTPsFit > 4) {
857  newNTPSFit -= 2;
858  } else {
859  newNTPSFit -= 1;
860  }
861  if(lastTP.NTPsFit < 3) newNTPSFit = 2;
862  if(newNTPSFit < minPtsFit) newNTPSFit = minPtsFit;
863  lastTP.NTPsFit = newNTPSFit;
864  // BB April 19: try to add a last lonely hit on a low MCSMom tj on the last try
865  if(newNTPSFit == minPtsFit && tj.MCSMom < 30) chiCut = 2;
866  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Bad FitChi "<<lastTP.FitChi<<" Reduced NTPsFit to "<<lastTP.NTPsFit<<" Pass "<<tj.Pass<<" chiCut "<<chiCut;
867  FitTraj(slc, tj);
868  tj.NeedsUpdate = true;
869  if(lastTP.FitChi > prevChi) {
870  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Chisq is increasing "<<lastTP.FitChi<<" Try to remove an earlier bad hit";
871  MaskBadTPs(slc, tj, chiCut);
872  ++ntry;
873  if(ntry == 2) break;
874  }
875  prevChi = lastTP.FitChi;
876  if(lastTP.NTPsFit == minPtsFit) break;
877  } // lastTP.FitChi > 2 && lastTP.NTPsFit > 2
878  }
879  // last ditch attempt if things look bad. Drop the last hit
880  if(tj.Pts.size() > tcc.minPtsFit[tj.Pass] && lastTP.FitChi > maxChi) {
881  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Last try. Drop last TP "<<lastTP.FitChi<<" NTPsFit "<<lastTP.NTPsFit;
882  UnsetUsedHits(slc, lastTP);
883  DefineHitPos(slc, lastTP);
884  SetEndPoints(tj);
885  lastPt = tj.EndPt[1];
886  FitTraj(slc, tj);
887  tj.MaskedLastTP = true;
888  }
889 
890  if(tj.NeedsUpdate) UpdateTjChgProperties("UT", slc, tj, tcc.dbgStp);
891 
892  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Fit done. Chi "<<lastTP.FitChi<<" NTPsFit "<<lastTP.NTPsFit;
893 
894  if(tj.EndPt[0] == tj.EndPt[1]) return;
895 
896  // Don't let the angle error get too small too soon. Stepping would stop if the first
897  // few hits on a low momentum wandering track happen to have a very good fit to a straight line.
898  // We will do this by averaging the default starting value of AngErr of the first TP with the current
899  // value from FitTraj.
900  if(lastPt < 14) {
901  float defFrac = 1 / (float)(tj.EndPt[1]);
902  lastTP.AngErr = defFrac * tj.Pts[0].AngErr + (1 - defFrac) * lastTP.AngErr;
903  }
904 
905  UpdateDeltaRMS(slc, tj);
906  SetAngleCode(lastTP);
907 
908  tj.NeedsUpdate = false;
909  return;
910 
911  } // UpdateTraj
912 
913  ////////////////////////////////////////////////
915  {
916  if(!tj.Strategy[kStiffEl]) return;
917  if(tcc.dbgStp) {
918  mf::LogVerbatim("TC")<<"inside CheckStiffTj with NumPtsWithCharge = "<<NumPtsWithCharge(slc, tj, false);
919  }
920  // Fill in any gaps with hits that were skipped, most likely delta rays on muon tracks
921  FillGaps(slc, tj);
922  // Update the trajectory parameters at the beginning of the trajectory
923  ChkBegin(slc, tj);
924  } // CheckStiffTj
925 
926  ////////////////////////////////////////////////
927  void CheckTraj(TCSlice& slc, Trajectory& tj)
928  {
929  // Check the quality of the trajectory and possibly trim it or flag it for deletion
930 
931  if(!tj.IsGood) return;
932 
933  // ensure that the end points are defined
934  SetEndPoints(tj);
935  if(tj.EndPt[0] == tj.EndPt[1]) return;
936 
937  if(tj.Strategy[kStiffEl]) {
938  CheckStiffEl(slc, tj);
939  return;
940  }
941 
942  if(tcc.dbgStp) {
943  mf::LogVerbatim("TC")<<"inside CheckTraj with NumPtsWithCharge = "<<NumPtsWithCharge(slc, tj, false);
944  }
945 
946  if(NumPtsWithCharge(slc, tj, false) < tcc.minPts[tj.Pass]) {
947  tj.IsGood = false;
948  return;
949  }
950 
951  // reduce nPtsFit to the minimum and check for a large angle kink near the ends
952 // ChkEndKink(slc, tj, tcc.dbgStp);
953 
954  // Look for a charge asymmetry between points on both sides of a high-
955  // charge point and trim points in the vicinity
956  ChkChgAsymmetry(slc, tj, tcc.dbgStp);
957 
958  // flag this tj as a junk Tj (even though it wasn't created in FindJunkTraj).
959  // Drop it and let FindJunkTraj do it's job
960  TagJunkTj(slc, tj, tcc.dbgStp);
961  if(tj.AlgMod[kJunkTj]) {
962  tj.IsGood = false;
963  return;
964  }
965 
966  tj.MCSMom = MCSMom(slc, tj);
967 
968  // See if the points at the stopping end can be included in the Tj
969  ChkStopEndPts(slc, tj, tcc.dbgStp);
970 
971  // remove any points at the end that don't have charge
972  tj.Pts.resize(tj.EndPt[1] + 1);
973 
974  // Ensure that a hit only appears once in the TJ
975  if(HasDuplicateHits(slc, tj, tcc.dbgStp)) {
976  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" HasDuplicateHits ";
977  tj.IsGood = false;
978  return;
979  }
980 
981  // See if this is a ghost trajectory
982  if(IsGhost(slc, tj)) {
983  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" CT: Ghost trajectory - trimmed hits ";
984  if(!tj.IsGood) return;
985  }
986 
987  if(tj.AlgMod[kJunkTj]) return;
988 
989  // checks are different for Very Large Angle trajectories
990  bool isVLA = (tj.Pts[tj.EndPt[1]].AngleCode == 2);
991 
992  tj.Pts.resize(tj.EndPt[1] + 1);
993 
994  // Fill in any gaps with hits that were skipped, most likely delta rays on muon tracks
995  if(!isVLA) FillGaps(slc, tj);
996 
997  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" CheckTraj MCSMom "<<tj.MCSMom<<" isVLA? "<<isVLA<<" NumPtsWithCharge "<<NumPtsWithCharge(slc, tj, false)<<" Min Req'd "<<tcc.minPts[tj.Pass];
998 
999  // Trim the end points until the TJ meets the quality cuts
1000  TrimEndPts("CT", slc, tj, tcc.qualityCuts, tcc.dbgStp);
1001  if(tj.AlgMod[kKilled]) {
1002  tj.IsGood = false;
1003  return;
1004  }
1005 
1006  TrimHiChgEndPts(slc, tj, tcc.dbgStp);
1007 
1008  // Check for a Bragg peak at both ends. This may be used by FixBegin.
1009  ChkStop(slc, tj);
1010 
1011  // Update the trajectory parameters at the beginning of the trajectory
1012  ChkBegin(slc, tj);
1013 
1014  // ignore short trajectories
1015  if(tj.EndPt[1] < 4) return;
1016 
1017  // final quality check
1018  float npwc = NumPtsWithCharge(slc, tj, true);
1019  float npts = tj.EndPt[1] - tj.EndPt[0] + 1;
1020  float frac = npwc / npts;
1021  tj.IsGood = (frac >= tcc.qualityCuts[0]);
1022  if(tj.IsGood && tj.Pass < tcc.minMCSMom.size() && !tj.Strategy[kSlowing]) tj.IsGood = (tj.MCSMom >= tcc.minMCSMom[tj.Pass]);
1023  if(tcc.dbgStp) {
1024  mf::LogVerbatim("TC")<<"CheckTraj: fraction of points with charge "<<frac<<" good traj? "<<tj.IsGood;
1025  }
1026  if(!tj.IsGood || !slc.isValid) return;
1027 
1028  // lop off high multiplicity hits at the end
1029  CheckHiMultEndHits(slc, tj);
1030 
1031  // Check for a Bragg peak at both ends. This may be used by FixBegin.
1032  ChkStop(slc, tj);
1033 
1034  } // CheckTraj
1035 
1036  ////////////////////////////////////////////////
1037  void AddHits(TCSlice& slc, Trajectory& tj, unsigned short ipt, bool& sigOK)
1038  {
1039  // Try to add hits to the trajectory point ipt on the supplied
1040  // trajectory
1041 
1042  // assume failure
1043  sigOK = false;
1044 
1045  if(tj.Pts.empty()) return;
1046  if(ipt > tj.Pts.size() - 1) return;
1047 
1048  // Call large angle hit finding if the last tp is large angle
1049  if(tj.Pts[ipt].AngleCode == 2) {
1050  AddLAHits(slc, tj, ipt, sigOK);
1051  return;
1052  }
1053 
1054  TrajPoint& tp = tj.Pts[ipt];
1055  std::vector<unsigned int> closeHits;
1056  unsigned int lastPtWithUsedHits = tj.EndPt[1];
1057 
1058  unsigned short plane = DecodeCTP(tj.CTP).Plane;
1059  unsigned int wire = std::nearbyint(tp.Pos[0]);
1060  if(wire < slc.firstWire[plane] || wire > slc.lastWire[plane]-1) return;
1061  // Move the TP to this wire
1062  MoveTPToWire(tp, (float)wire);
1063 
1064  // find the projection error to this point. Note that if this is the first
1065  // TP, lastPtWithUsedHits = 0, so the projection error is 0
1066  float dw = tp.Pos[0] - tj.Pts[lastPtWithUsedHits].Pos[0];
1067  float dt = tp.Pos[1] - tj.Pts[lastPtWithUsedHits].Pos[1];
1068  float dpos = sqrt(dw * dw + dt * dt);
1069  float projErr = dpos * tj.Pts[lastPtWithUsedHits].AngErr;
1070  // Add this to the Delta RMS factor and construct a cut
1071  float deltaCut = 3 * (projErr + tp.DeltaRMS);
1072 
1073  // The delta cut shouldn't be less than the delta of hits added on the previous step
1074  float minDeltaCut = 1.1 * tj.Pts[lastPtWithUsedHits].Delta;
1075  if(deltaCut < minDeltaCut) deltaCut = minDeltaCut;
1076 
1077  deltaCut *= tcc.projectionErrFactor;
1078  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" AddHits: calculated deltaCut "<<deltaCut<<" dw "<<dw<<" dpos "<<dpos;
1079 
1080  if(deltaCut < 0.5) deltaCut = 0.5;
1081  if(deltaCut > 3) deltaCut = 3;
1082 
1083  // TY: open it up for RevProp, since we might be following a stopping track
1084  if(tj.AlgMod[kRvPrp]) deltaCut *= 2;
1085 
1086  // loosen up a bit if we just passed a block of dead wires
1087  bool passedDeadWires = (abs(dw) > 20 && DeadWireCount(slc, tp.Pos[0], tj.Pts[lastPtWithUsedHits].Pos[0], tj.CTP) > 10);
1088  if(passedDeadWires) deltaCut *= 2;
1089  // open it up for StiffEl and Slowing strategies
1090  if(tj.Strategy[kStiffEl] || tj.Strategy[kSlowing]) deltaCut = 3;
1091 
1092  // Create a larger cut to use in case there is nothing close
1093  float bigDelta = 2 * deltaCut;
1094  unsigned int imBig = UINT_MAX;
1095  tp.Delta = deltaCut;
1096  // ignore all hits with delta larger than maxDeltaCut
1097  float maxDeltaCut = 2 * bigDelta;
1098  // apply some limits
1099  if(!passedDeadWires && maxDeltaCut > 3) {
1100  maxDeltaCut = 3;
1101  bigDelta = 1.5;
1102  }
1103 
1104  // projected time in ticks for testing the existence of a hit signal
1105  raw::TDCtick_t rawProjTick = (float)(tp.Pos[1] / tcc.unitsPerTick);
1106  if(tcc.dbgStp) {
1107  mf::LogVerbatim("TC")<<" AddHits: wire "<<wire<<" tp.Pos[0] "<<tp.Pos[0]<<" projTick "<<rawProjTick<<" deltaRMS "<<tp.DeltaRMS<<" tp.Dir[0] "<<tp.Dir[0]<<" deltaCut "<<deltaCut<<" dpos "<<dpos<<" projErr "<<projErr<<" ExpectedHitsRMS "<<ExpectedHitsRMS(slc, tp);
1108  }
1109 
1110  std::vector<unsigned int> hitsInMultiplet;
1111 
1112  geo::PlaneID planeID = DecodeCTP(tj.CTP);
1113  unsigned int ipl = planeID.Plane;
1114  if(wire > slc.lastWire[ipl]) return;
1115  // Assume a signal exists on a dead wire
1116  if(!evt.goodWire[ipl][wire]) sigOK = true;
1117  if(slc.wireHitRange[ipl][wire].first == UINT_MAX) return;
1118  unsigned int firstHit = slc.wireHitRange[ipl][wire].first;
1119  unsigned int lastHit = slc.wireHitRange[ipl][wire].second;
1120  float fwire = wire;
1121  for(unsigned int iht = firstHit; iht <= lastHit; ++iht) {
1122  if(slc.slHits[iht].InTraj == tj.ID) continue;
1123  if(slc.slHits[iht].InTraj == SHRT_MAX) continue;
1124  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1125  if(rawProjTick > hit.StartTick() && rawProjTick < hit.EndTick()) sigOK = true;
1126  float ftime = tcc.unitsPerTick * hit.PeakTime();
1127  float delta = PointTrajDOCA(slc, fwire, ftime, tp);
1128  // increase the delta cut if this is a long pulse hit
1129  bool longPulseHit = LongPulseHit(hit);
1130  if(longPulseHit) {
1131  if(delta > 3) continue;
1132  } else {
1133  if(delta > maxDeltaCut) continue;
1134  }
1135  float dt = std::abs(ftime - tp.Pos[1]);
1136  GetHitMultiplet(slc, iht, hitsInMultiplet, false);
1137  if(tcc.dbgStp && delta < 100 && dt < 100) {
1138  mf::LogVerbatim myprt("TC");
1139  myprt<<" iht "<<iht;
1140  myprt<<" "<<PrintHit(slc.slHits[iht]);
1141  myprt<<" delta "<<std::fixed<<std::setprecision(2)<<delta<<" deltaCut "<<deltaCut<<" dt "<<dt;
1142  myprt<<" BB Mult "<<hitsInMultiplet.size()<<" RMS "<<std::setprecision(1)<<hit.RMS();
1143  myprt<<" Chi "<<std::setprecision(1)<<hit.GoodnessOfFit();
1144  myprt<<" InTraj "<<slc.slHits[iht].InTraj;
1145  myprt<<" Chg "<<(int)hit.Integral();
1146  myprt<<" Signal? "<<sigOK;
1147  }
1148  if(slc.slHits[iht].InTraj == 0 && delta < bigDelta && hitsInMultiplet.size() < 3 && !tj.AlgMod[kRvPrp]) {
1149  // An available hit that is just outside the window that is not part of a large multiplet
1150  bigDelta = delta;
1151  imBig = iht;
1152  }
1153  if(longPulseHit) {
1154  if(delta > 3) continue;
1155  } else {
1156  if(delta > deltaCut) continue;
1157  }
1158 
1159  if(std::find(closeHits.begin(), closeHits.end(), iht) != closeHits.end()) continue;
1160  closeHits.push_back(iht);
1161  if(hitsInMultiplet.size() > 1) {
1162  // include all the hits in a multiplet
1163  for(auto& jht : hitsInMultiplet) {
1164  if(slc.slHits[jht].InTraj == tj.ID) continue;
1165  if(std::find(closeHits.begin(), closeHits.end(), jht) != closeHits.end()) continue;
1166  closeHits.push_back(jht);
1167  } // jht
1168  } // multiplicity > 1
1169  } // iht
1170 
1171  if(tcc.dbgStp) {
1172  mf::LogVerbatim myprt("TC");
1173  myprt<<"closeHits ";
1174  for(auto iht : closeHits) myprt<<" "<<PrintHit(slc.slHits[iht]);
1175  if(imBig < slc.slHits.size()) {
1176  myprt<<" imBig "<<PrintHit(slc.slHits[imBig]);
1177  } else {
1178  myprt<<" imBig "<<imBig;
1179  }
1180  }
1181  // check the srcHit collection if it is defined. Add the TP to the trajectory if
1182  // there is NO hit in the allHits collection but there is a hit in srcHit collection. We
1183  // can't use it for fitting, etc however
1184  bool nearSrcHit = false;
1185  if(!sigOK) nearSrcHit = NearbySrcHit(planeID, wire, (float)rawProjTick, (float)rawProjTick);
1186  sigOK = sigOK || !closeHits.empty() || nearSrcHit;
1187 
1188  if(!sigOK) {
1189  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" no signal on any wire at tp.Pos "<<tp.Pos[0]<<" "<<tp.Pos[1]<<" tick "<<(int)tp.Pos[1]/tcc.unitsPerTick<<" closeHits size "<<closeHits.size();
1190  return;
1191  }
1192  if(imBig < slc.slHits.size() && closeHits.empty()) {
1193  closeHits.push_back(imBig);
1194  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Added bigDelta hit "<<PrintHit(slc.slHits[imBig])<<" w delta = "<<bigDelta;
1195  }
1196  if(closeHits.size() > 16) closeHits.resize(16);
1197  if(nearSrcHit) tp.Environment[kEnvNearSrcHit] = true;
1198  tp.Hits.insert(tp.Hits.end(), closeHits.begin(), closeHits.end());
1199 
1200  // reset UseHit and assume that none of these hits will be used (yet)
1201  tp.UseHit.reset();
1202  // decide which of these hits should be used in the fit. Use a generous maximum delta
1203  // and require a charge check if we're not just starting out
1204  bool useChg = true;
1205  if(tj.Strategy[kStiffEl] || tj.Strategy[kSlowing]) useChg = false;
1206  FindUseHits(slc, tj, ipt, 10, useChg);
1207  DefineHitPos(slc, tp);
1208  SetEndPoints(tj);
1209  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" number of close hits "<<closeHits.size()<<" used hits "<<NumHitsInTP(tp, kUsedHits);
1210  } // AddHits
1211 
1212 
1213  ////////////////////////////////////////////////
1214  void AddLAHits(TCSlice& slc, Trajectory& tj, unsigned short ipt, bool& sigOK)
1215  {
1216  // Very Large Angle version of AddHits to be called for the last angle range
1217 
1218  if(ipt > tj.Pts.size() - 1) return;
1219  TrajPoint& tp = tj.Pts[ipt];
1220  tp.Hits.clear();
1221  tp.UseHit.reset();
1222  sigOK = false;
1223 
1224  unsigned short plane = DecodeCTP(tj.CTP).Plane;
1225 
1226  // look at adjacent wires for larger angle trajectories
1227  // We will check the most likely wire first
1228  std::vector<int> wires(1);
1229  wires[0] = std::nearbyint(tp.Pos[0]);
1230  if(wires[0] < 0 || wires[0] > (int)slc.lastWire[plane]-1) return;
1231 
1232  if(tp.AngleCode != 2) {
1233  mf::LogVerbatim("TC")<<"AddLAHits called with a bad angle code. "<<tp.AngleCode<<" Don't do this";
1234  return;
1235  }
1236  // and the adjacent wires next in the most likely order only
1237  // after the first two points have been defined
1238  if(ipt > 1) {
1239  if(tp.Dir[0] > 0) {
1240  if(wires[0] < (int)slc.lastWire[plane]-1) wires.push_back(wires[0] + 1);
1241  if(wires[0] > 0) wires.push_back(wires[0] - 1);
1242  } else {
1243  if(wires[0] > 0) wires.push_back(wires[0] - 1);
1244  if(wires[0] < (int)slc.lastWire[plane]-1) wires.push_back(wires[0] + 1);
1245  }
1246  } // ipt > 0 ...
1247 
1248  if(tcc.dbgStp) {
1249  mf::LogVerbatim myprt("TC");
1250  myprt<<" AddLAHits: Pos "<<PrintPos(slc, tp)<<" tp.AngleCode "<<tp.AngleCode<<" Wires under consideration";
1251  for(auto& wire : wires) myprt<<" "<<wire;
1252  }
1253 
1254  // a temporary tp that we can move around
1255  TrajPoint ltp = tp;
1256  // do this while testing
1257  sigOK = false;
1258 
1259  tp.Hits.clear();
1260  std::array<int, 2> wireWindow;
1261  std::array<float, 2> timeWindow;
1262  float pos1Window = tcc.VLAStepSize/2;
1263  timeWindow[0] = ltp.Pos[1] - pos1Window;
1264  timeWindow[1] = ltp.Pos[1] + pos1Window;
1265  // Put the existing hits in to a vector so we can ensure that they aren't added again
1266  std::vector<unsigned int> oldHits = PutTrajHitsInVector(tj, kAllHits);
1267 
1268  for(unsigned short ii = 0; ii < wires.size(); ++ii) {
1269  int wire = wires[ii];
1270  if(wire < 0 || wire > (int)slc.lastWire[plane]) continue;
1271  // Assume a signal exists on a dead wire
1272  if(slc.wireHitRange[plane][wire].first == UINT_MAX) sigOK = true;
1273  if(slc.wireHitRange[plane][wire].first == UINT_MAX) continue;
1274  wireWindow[0] = wire;
1275  wireWindow[1] = wire;
1276  bool hitsNear;
1277  // Look for hits using the requirement that the timeWindow overlaps with the hit StartTick and EndTick
1278  std::vector<unsigned int> closeHits = FindCloseHits(slc, wireWindow, timeWindow, plane, kAllHits, true, hitsNear);
1279  if(hitsNear) sigOK = true;
1280  for(auto& iht : closeHits) {
1281  // Ensure that none of these hits are already used by this trajectory
1282  if(slc.slHits[iht].InTraj == tj.ID) continue;
1283  // or in another trajectory in any previously added point
1284  if(std::find(oldHits.begin(), oldHits.end(), iht) != oldHits.end()) continue;
1285  tp.Hits.push_back(iht);
1286  }
1287  } // ii
1288 
1289  if(tcc.dbgStp) {
1290  mf::LogVerbatim myprt("TC");
1291  myprt<<" LAPos "<<PrintPos(slc, ltp)<<" Tick window "<<(int)(timeWindow[0]/tcc.unitsPerTick)<<" to "<<(int)(timeWindow[1]/tcc.unitsPerTick);
1292  for(auto& iht : tp.Hits) myprt<<" "<<PrintHit(slc.slHits[iht]);
1293  } // prt
1294 
1295  // no hits found
1296  if(tp.Hits.empty()) return;
1297 
1298  if(tp.Hits.size() > 16) tp.Hits.resize(16);
1299 
1300  tp.UseHit.reset();
1301  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1302  unsigned int iht = tp.Hits[ii];
1303  if(slc.slHits[iht].InTraj != 0) continue;
1304  tp.UseHit[ii] = true;
1305  slc.slHits[iht].InTraj = tj.ID;
1306  } // ii
1307  DefineHitPos(slc, tp);
1308  SetEndPoints(tj);
1309  UpdateTjChgProperties("ALAH", slc, tj, tcc.dbgStp);
1310 
1311  } // AddLAHits
1312 
1313  //////////////////////////////////////////
1315  {
1316  // Reverse the trajectory and step in the opposite direction. The
1317  // updated trajectory is returned if this process is successful
1318 
1319  if(!tcc.useAlg[kRvPrp]) return;
1320 
1321  if(tj.Pts.size() < 6) return;
1322  // only do this once
1323  if(tj.AlgMod[kRvPrp]) return;
1324 
1325  // This code can't handle VLA trajectories
1326  if(tj.Pts[tj.EndPt[0]].AngleCode == 2) return;
1327 
1328  bool prt = (tcc.dbgStp || tcc.dbgAlg[kRvPrp]);
1329 
1330  // this function requires the first TP be included in the trajectory.
1331  if(tj.EndPt[0] > 0) {
1332  tj.Pts.erase(tj.Pts.begin(), tj.Pts.begin() + tj.EndPt[0]);
1333  SetEndPoints(tj);
1334  }
1335 
1336  if(prt) mf::LogVerbatim("TC")<<"ReversePropagate: Prepping Tj "<<tj.ID<<" incoming StepDir "<<tj.StepDir;
1337 
1338  short stepDir = tj.StepDir;
1339 
1340  // find the wire on which the first TP resides
1341  unsigned int wire0 = std::nearbyint(tj.Pts[0].Pos[0]);
1342  unsigned int nextWire = wire0 - tj.StepDir;
1343 
1344  // check for dead wires
1345  geo::PlaneID planeID = DecodeCTP(tj.CTP);
1346  unsigned short ipl = planeID.Plane;
1347  while(nextWire > slc.firstWire[ipl] && nextWire < slc.lastWire[ipl]) {
1348  if(evt.goodWire[ipl][nextWire]) break;
1349  nextWire -= tj.StepDir;
1350  }
1351  if(nextWire == slc.lastWire[ipl] - 1) return;
1352  // clone the first point
1353  TrajPoint tp = tj.Pts[0];
1354  // strip off the hits
1355  tp.Hits.clear(); tp.UseHit.reset();
1356  // move it to the next wire (in the opposite direction of the step direction)
1357  MoveTPToWire(tp, (float)nextWire);
1358  // find close unused hits near this position
1359  float maxDelta = 10 * tj.Pts[tj.EndPt[1]].DeltaRMS;
1360  if(!FindCloseHits(slc, tp, maxDelta, kUnusedHits)) return;
1361  if(prt) mf::LogVerbatim("TC")<<" nUnused hits "<<tp.Hits.size()<<" at Pos "<<PrintPos(slc, tp);
1362  if(tp.Hits.empty()) return;
1363  // There are hits on the next wire. Make a working copy of the trajectory, reverse it and try
1364  // to extend it with StepAway
1365  if(prt) {
1366  mf::LogVerbatim myprt("TC");
1367  myprt<<" tp.Hits ";
1368  for(auto& iht : tp.Hits) myprt<<" "<<PrintHit(slc.slHits[iht])<<"_"<<slc.slHits[iht].InTraj;
1369  } // tcc.dbgStp
1370  //
1371  // Make a working copy of tj
1372  Trajectory tjWork = tj;
1373  // So the first shall be last and the last shall be first
1374  ReverseTraj(slc, tjWork);
1375  // Flag it to use special cuts in StepAway
1376  tjWork.AlgMod[kRvPrp] = true;
1377  // save the strategy word and set it to normal
1378  auto saveStrategy = tjWork.Strategy;
1379  tjWork.Strategy.reset();
1380  tjWork.Strategy[kNormal] = true;
1381  // Reduce the number of fitted points to a small number
1382  unsigned short lastPt = tjWork.Pts.size() - 1;
1383  if(lastPt < 4) return;
1384  // update the charge
1385  float chg = 0;
1386  float cnt = 0;
1387  for(unsigned short ii = 0; ii < 4; ++ii) {
1388  unsigned short ipt = lastPt - ii;
1389  if(tjWork.Pts[ipt].Chg == 0) continue;
1390  chg += tjWork.Pts[ipt].Chg;
1391  ++cnt;
1392  } // ii
1393  if(cnt == 0) return;
1394  if(cnt > 1) tjWork.Pts[lastPt].AveChg = chg / cnt;
1395  StepAway(slc, tjWork);
1396  if(!tj.IsGood) {
1397  if(prt) mf::LogVerbatim("TC")<<" ReversePropagate StepAway failed";
1398  return;
1399  }
1400  tjWork.Strategy = saveStrategy;
1401  // check the new stopping point
1402  ChkStopEndPts(slc, tjWork, tcc.dbgStp);
1403  // restore the original direction
1404  if(tjWork.StepDir != stepDir) ReverseTraj(slc, tjWork);
1405  tj = tjWork;
1406  // TODO: Maybe UpdateTjChgProperties should be called here
1407  // re-check the ends
1408  ChkStop(slc, tj);
1409 
1410  } // ReversePropagate
1411 
1412  ////////////////////////////////////////////////
1413  void GetHitMultiplet(const TCSlice& slc, unsigned int theHit, std::vector<unsigned int>& hitsInMultiplet, bool useLongPulseHits)
1414  {
1415  // This function attempts to return a list of hits in the current slice that are close to the
1416  // hit specified by theHit and that are similar to it. If theHit is a high-pulseheight hit (aka imTall)
1417  // and has an RMS similar to a hit on a small angle trajectory (aka Narrow) and is embedded in a series of
1418  // nearby low-pulseheight wide hits, the hit multiplet will consist of the single Tall and Narrow hit. On the
1419  // other hand, if theHit references a short and not-narrow hit, all of the hits in the series of nearby
1420  // hits will be returned. The localIndex is the index of theHit in hitsInMultiplet and shouldn't be
1421  // confused with the recob::Hit LocalIndex
1422  hitsInMultiplet.clear();
1423  // check for flagrant errors
1424  if(theHit >= slc.slHits.size()) return;
1425  if(slc.slHits[theHit].InTraj == INT_MAX) return;
1426  if(slc.slHits[theHit].allHitsIndex >= (*evt.allHits).size()) return;
1427 
1428  auto& hit = (*evt.allHits)[slc.slHits[theHit].allHitsIndex];
1429  // handle long-pulse hits
1430  if(useLongPulseHits && LongPulseHit(hit)) {
1431  // return everything in the multiplet as defined by the hit finder, but check for errors
1432  short int hitMult = hit.Multiplicity();
1433  unsigned int lIndex = hit.LocalIndex();
1434  unsigned int firstHit = 0;
1435  if(lIndex < theHit) firstHit = theHit - lIndex;
1436  for(unsigned int ii = firstHit; ii < firstHit + hitMult; ++ii) {
1437  if(ii >= slc.slHits.size()) break;
1438  auto& tmp = (*evt.allHits)[slc.slHits[ii].allHitsIndex];
1439  if(tmp.Multiplicity() == hitMult) hitsInMultiplet.push_back(ii);
1440  } // ii
1441  return;
1442  } // LongPulseHit
1443 
1444  hitsInMultiplet.resize(1);
1445  hitsInMultiplet[0] = theHit;
1446  unsigned int theWire = hit.WireID().Wire;
1447  unsigned short ipl = hit.WireID().Plane;
1448 
1449  float theTime = hit.PeakTime();
1450  float theRMS = hit.RMS();
1451  float narrowHitCut = 1.5 * evt.aveHitRMS[ipl];
1452  bool theHitIsNarrow = (theRMS < narrowHitCut);
1453  float maxPeak = hit.PeakAmplitude();
1454  unsigned int imTall = theHit;
1455  unsigned short nNarrow = 0;
1456  if(theHitIsNarrow) nNarrow = 1;
1457  // look for hits < theTime but within hitSep
1458  if(theHit > 0) {
1459  for(unsigned int iht = theHit - 1; iht != 0; --iht) {
1460  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1461  if(hit.WireID().Wire != theWire) break;
1462  if(hit.WireID().Plane != ipl) break;
1463  float hitSep = tcc.multHitSep * theRMS;
1464  float rms = hit.RMS();
1465  if(rms > theRMS) {
1466  hitSep = tcc.multHitSep * rms;
1467  theRMS = rms;
1468  }
1469  float dTick = std::abs(hit.PeakTime() - theTime);
1470  if(dTick > hitSep) break;
1471  hitsInMultiplet.push_back(iht);
1472  if(rms < narrowHitCut) ++nNarrow;
1473  float peakAmp = hit.PeakAmplitude();
1474  if(peakAmp > maxPeak) {
1475  maxPeak = peakAmp;
1476  imTall = iht;
1477  }
1478  theTime = hit.PeakTime();
1479  if(iht == 0) break;
1480  } // iht
1481  } // iht > 0
1482  // reverse the order so that hitsInMuliplet will be
1483  // returned in increasing time order
1484  if(hitsInMultiplet.size() > 1) std::reverse(hitsInMultiplet.begin(), hitsInMultiplet.end());
1485  // look for hits > theTime but within hitSep
1486  theTime = hit.PeakTime();
1487  theRMS = hit.RMS();
1488  for(unsigned int iht = theHit + 1; iht < slc.slHits.size(); ++iht) {
1489  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1490  if(hit.WireID().Wire != theWire) break;
1491  if(hit.WireID().Plane != ipl) break;
1492  if(slc.slHits[iht].InTraj == INT_MAX) continue;
1493  float hitSep = tcc.multHitSep * theRMS;
1494  float rms = hit.RMS();
1495  if(rms > theRMS) {
1496  hitSep = tcc.multHitSep * rms;
1497  theRMS = rms;
1498  }
1499  float dTick = std::abs(hit.PeakTime() - theTime);
1500  if(dTick > hitSep) break;
1501  hitsInMultiplet.push_back(iht);
1502  if(rms < narrowHitCut) ++nNarrow;
1503  float peakAmp = hit.PeakAmplitude();
1504  if(peakAmp > maxPeak) {
1505  maxPeak = peakAmp;
1506  imTall = iht;
1507  }
1508  theTime = hit.PeakTime();
1509  } // iht
1510  if(hitsInMultiplet.size() == 1) return;
1511 
1512  // Don't make a multiplet that includes a tall narrow hit with short fat hits
1513  if(nNarrow == hitsInMultiplet.size()) return;
1514  if(nNarrow == 0) return;
1515 
1516  if(theHitIsNarrow && theHit == imTall) {
1517  // theHit is narrow and it is the highest amplitude hit in the multiplet. Ignore any
1518  // others that are short and fat
1519  auto tmp = hitsInMultiplet;
1520  tmp.resize(1);
1521  tmp[0] = theHit;
1522  hitsInMultiplet = tmp;
1523  } else {
1524  // theHit is not narrow and it is not the tallest. Ignore a single hit if it is
1525  // the tallest and narrow
1526  auto& hit = (*evt.allHits)[slc.slHits[imTall].allHitsIndex];
1527  if(hit.RMS() < narrowHitCut) {
1528  unsigned short killMe = 0;
1529  for(unsigned short ii = 0; ii < hitsInMultiplet.size(); ++ii) {
1530  if(hitsInMultiplet[ii] == imTall) {
1531  killMe = ii;
1532  break;
1533  }
1534  } // ii
1535  hitsInMultiplet.erase(hitsInMultiplet.begin() + killMe);
1536  } // slc.slHits[imTall].RMS < narrowHitCut
1537  } // narrow / tall test
1538 
1539  } // GetHitMultiplet
1540 
1541  //////////////////////////////////////////
1542  float HitTimeErr(const TCSlice& slc, unsigned int iht)
1543  {
1544  if(iht > slc.slHits.size() - 1) return 0;
1545  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1546  return hit.RMS() * tcc.unitsPerTick * tcc.hitErrFac * hit.Multiplicity();
1547  } // HitTimeErr
1548 
1549  //////////////////////////////////////////
1550  float HitsTimeErr2(const TCSlice& slc, const std::vector<unsigned int>& hitVec)
1551  {
1552  // Estimates the error^2 of the time using all hits in hitVec
1553  if(hitVec.empty()) return 0;
1554  float err = tcc.hitErrFac * HitsRMSTime(slc, hitVec, kUnusedHits);
1555  return err * err;
1556  } // HitsTimeErr2
1557 
1558 
1559  ////////////////////////////////////////////////
1560  void ChkStopEndPts(TCSlice& slc, Trajectory& tj, bool prt)
1561  {
1562  // Analyze the end of the Tj after crawling has stopped to see if any of the points
1563  // should be used
1564 
1565  if(tj.EndFlag[1][kAtKink]) return;
1566  if(!tcc.useAlg[kChkStopEP]) return;
1567  if(tj.AlgMod[kJunkTj]) return;
1568  if(tj.Strategy[kStiffEl]) return;
1569 
1570  unsigned short endPt = tj.EndPt[1];
1571  // ignore VLA Tjs
1572  if(tj.Pts[endPt].AngleCode > 1) return;
1573  // don't get too carried away with this
1574  if(tj.Pts.size() - endPt > 10) return;
1575 
1576  // Get a list of hits a few wires beyond the last point on the Tj
1577  geo::PlaneID planeID = DecodeCTP(tj.CTP);
1578  unsigned short plane = planeID.Plane;
1579 
1580  // find the last point that has hits on it
1581  unsigned short lastPt = tj.Pts.size() - 1;
1582  for(lastPt = tj.Pts.size() - 1; lastPt >= tj.EndPt[1]; --lastPt) if(!tj.Pts[lastPt].Hits.empty()) break;
1583  auto& lastTP = tj.Pts[lastPt];
1584 
1585  if(tcc.dbgStp) {
1586  mf::LogVerbatim("TC")<<"CSEP: checking "<<tj.ID<<" endPt "<<endPt<<" Pts size "<<tj.Pts.size()<<" lastPt Pos "<<PrintPos(slc, lastTP.Pos);
1587  }
1588  TrajPoint ltp;
1589  ltp.CTP = tj.CTP;
1590  ltp.Pos = tj.Pts[endPt].Pos;
1591  ltp.Dir = tj.Pts[endPt].Dir;
1592  double stepSize = std::abs(1/ltp.Dir[0]);
1593  std::array<int, 2> wireWindow;
1594  std::array<float, 2> timeWindow;
1595  std::vector<int> closeHits;
1596  bool isClean = true;
1597  for(unsigned short step = 0; step < 10; ++step) {
1598  for(unsigned short iwt = 0; iwt < 2; ++iwt) ltp.Pos[iwt] += ltp.Dir[iwt] * stepSize;
1599  int wire = std::nearbyint(ltp.Pos[0]);
1600  wireWindow[0] = wire;
1601  wireWindow[1] = wire;
1602  timeWindow[0] = ltp.Pos[1] - 5;
1603  timeWindow[1] = ltp.Pos[1] + 5;
1604  bool hitsNear;
1605  auto tmp = FindCloseHits(slc, wireWindow, timeWindow, plane, kAllHits, true, hitsNear);
1606  // add close hits that are not associated with this tj
1607  for(auto iht : tmp) if(slc.slHits[iht].InTraj != tj.ID) closeHits.push_back(iht);
1608  float nWiresPast = 0;
1609  // Check beyond the end of the trajectory to see if there are hits there
1610  if(ltp.Dir[0] > 0) {
1611  // stepping +
1612  nWiresPast = ltp.Pos[0] - lastTP.Pos[0];
1613  } else {
1614  // stepping -
1615  nWiresPast = lastTP.Pos[0] - ltp.Pos[0];
1616  }
1617  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Found "<<tmp.size()<<" hits near pos "<<PrintPos(slc, ltp.Pos)<<" nWiresPast "<<nWiresPast;
1618  if(nWiresPast > 0.5) {
1619  if(!tmp.empty()) isClean = false;
1620  if(nWiresPast > 1.5) break;
1621  } // nWiresPast > 0.5
1622  } // step
1623 
1624  // count the number of available hits
1625  unsigned short nAvailable = 0;
1626  for(auto iht : closeHits) if(slc.slHits[iht].InTraj == 0) ++nAvailable;
1627 
1628  if(tcc.dbgStp) {
1629  mf::LogVerbatim myprt("TC");
1630  myprt<<"closeHits";
1631  for(auto iht : closeHits) myprt<<" "<<PrintHit(slc.slHits[iht]);
1632  myprt<<" nAvailable "<<nAvailable;
1633  myprt<<" isClean "<<isClean;
1634  } // prt
1635 
1636  if(!isClean || nAvailable != closeHits.size()) return;
1637 
1638  unsigned short originalEndPt = tj.EndPt[1] + 1;
1639  // looks clean so use all the hits
1640  for(unsigned short ipt = originalEndPt; ipt <= lastPt; ++ipt) {
1641  auto& tp = tj.Pts[ipt];
1642  bool hitsAdded = false;
1643  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1644  // This shouldn't happen but check anyway
1645  if(slc.slHits[tp.Hits[ii]].InTraj != 0) continue;
1646  tp.UseHit[ii] = true;
1647  slc.slHits[tp.Hits[ii]].InTraj = tj.ID;
1648  hitsAdded = true;
1649  } // ii
1650  if(hitsAdded) DefineHitPos(slc, tp);
1651  } // ipt
1652  tj.AlgMod[kChkStopEP] = true;
1653  SetEndPoints(tj);
1654  // Re-fitting the end might be a good idea but it's probably not necessary. The
1655  // values of Delta should have already been filled
1656 
1657  // require a Bragg peak
1658  ChkStop(slc, tj);
1659  if(!tj.EndFlag[1][kBragg]) {
1660  // restore the original
1661  for(unsigned short ipt = originalEndPt; ipt <= lastPt; ++ipt) UnsetUsedHits(slc, tj.Pts[ipt]);
1662  SetEndPoints(tj);
1663  } // no Bragg Peak
1664 
1665  UpdateTjChgProperties("CSEP", slc, tj, prt);
1666 
1667  } // ChkStopEndPts
1668 
1669  //////////////////////////////////////////
1671  {
1672  // defines HitPos, HitPosErr2 and Chg for the used hits in the trajectory point
1673 
1674  tp.Chg = 0;
1675  if(tp.Hits.empty()) return;
1676 
1677  unsigned short nused = 0;
1678  unsigned int iht = 0;
1679  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1680  if(!tp.UseHit[ii]) continue;
1681  ++nused;
1682  iht = tp.Hits[ii];
1683  if(iht >= slc.slHits.size()) return;
1684  if(slc.slHits[iht].allHitsIndex >= (*evt.allHits).size()) return;
1685  }
1686  if(nused == 0) return;
1687 
1688  // don't bother with rest of this if there is only one hit since it can
1689  // only reside on one wire
1690  if(nused == 1) {
1691  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1692  tp.Chg = hit.Integral();
1693  tp.HitPos[0] = hit.WireID().Wire;
1694  tp.HitPos[1] = hit.PeakTime() * tcc.unitsPerTick;
1695  if(LongPulseHit(hit)) {
1696  // give it a huge error^2 since the position is not well defined
1697  tp.HitPosErr2 = 100;
1698  } else {
1699  // Normalize to 1 WSE path length
1700  float pathInv = std::abs(tp.Dir[0]);
1701  if(pathInv < 0.05) pathInv = 0.05;
1702  tp.Chg *= pathInv;
1703  float wireErr = tp.Dir[1] * 0.289;
1704  float timeErr = tp.Dir[0] * HitTimeErr(slc, iht);
1705  tp.HitPosErr2 = wireErr * wireErr + timeErr * timeErr;
1706  }
1707  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"DefineHitPos: singlet "<<std::fixed<<std::setprecision(1)<<tp.HitPos[0]<<":"<<(int)(tp.HitPos[1]/tcc.unitsPerTick)<<" ticks. HitPosErr "<<sqrt(tp.HitPosErr2);
1708  return;
1709  } // nused == 1
1710 
1711  // multiple hits possibly on different wires
1712  std::vector<unsigned int> hitVec;
1713  tp.Chg = 0;
1714  std::array<float, 2> newpos;
1715  float chg;
1716  newpos[0] = 0;
1717  newpos[1] = 0;
1718  // Find the wire range for hits used in the TP
1719  unsigned int loWire = INT_MAX;
1720  unsigned int hiWire = 0;
1721  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1722  if(!tp.UseHit[ii]) continue;
1723  unsigned int iht = tp.Hits[ii];
1724  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1725  chg = hit.Integral();
1726  unsigned int wire = hit.WireID().Wire;
1727  if(wire < loWire) loWire = wire;
1728  if(wire > hiWire) hiWire = wire;
1729  newpos[0] += chg * wire;
1730  newpos[1] += chg * hit.PeakTime();
1731  tp.Chg += chg;
1732  hitVec.push_back(iht);
1733  } // ii
1734 
1735  if(tp.Chg == 0) return;
1736 
1737  tp.HitPos[0] = newpos[0] / tp.Chg;
1738  tp.HitPos[1] = newpos[1] * tcc.unitsPerTick / tp.Chg;
1739  // Normalize to 1 WSE path length
1740  float pathInv = std::abs(tp.Dir[0]);
1741  if(pathInv < 0.05) pathInv = 0.05;
1742  tp.Chg *= pathInv;
1743  // Error is the wire error (1/sqrt(12))^2 if all hits are on one wire.
1744  // Scale it by the wire range
1745  float dWire = 1 + hiWire - loWire;
1746  float wireErr = tp.Dir[1] * dWire * 0.289;
1747  float timeErr2 = tp.Dir[0] * tp.Dir[0] * HitsTimeErr2(slc, hitVec);
1748  tp.HitPosErr2 = wireErr * wireErr + timeErr2;
1749  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"DefineHitPos: multiplet "<<std::fixed<<std::setprecision(1)<<tp.HitPos[0]<<":"<<(int)(tp.HitPos[1]/tcc.unitsPerTick)<<" ticks. HitPosErr "<<sqrt(tp.HitPosErr2);
1750 
1751  } // DefineHitPos
1752 
1753 
1754  //////////////////////////////////////////
1755  void FindUseHits(TCSlice& slc, Trajectory& tj, unsigned short ipt, float maxDelta, bool useChg)
1756  {
1757  // Hits have been associated with trajectory point ipt but none are used. Here we will
1758  // decide which hits to use.
1759 
1760  if(ipt > tj.Pts.size() - 1) return;
1761  TrajPoint& tp = tj.Pts[ipt];
1762 
1763  if(tp.Hits.empty()) return;
1764  // don't check charge when starting out
1765  if(ipt < 5) useChg = false;
1766  float chgPullCut = 1000;
1767  if(useChg) chgPullCut = tcc.chargeCuts[0];
1768  // open it up for RevProp, since we might be following a stopping track
1769  if(tj.AlgMod[kRvPrp]) chgPullCut *= 2;
1770  if(tj.MCSMom < 30) chgPullCut *= 2;
1771 
1772  bool ignoreLongPulseHits = false;
1773  unsigned short npts = tj.EndPt[1] - tj.EndPt[0] + 1;
1774  if(npts < 10 || tj.AlgMod[kRvPrp]) ignoreLongPulseHits = true;
1775  float expectedHitsRMS = ExpectedHitsRMS(slc, tp);
1776  if(tcc.dbgStp) {
1777  mf::LogVerbatim("TC")<<"FUH: maxDelta "<<maxDelta<<" useChg requested "<<useChg<<" Norm AveChg "<<(int)tp.AveChg<<" tj.ChgRMS "<<std::setprecision(2)<<tj.ChgRMS<<" chgPullCut "<<chgPullCut<<" TPHitsRMS "<<(int)TPHitsRMSTick(slc, tp, kUnusedHits)<<" ExpectedHitsRMS "<<(int)expectedHitsRMS<<" AngCode "<<tp.AngleCode;
1778  }
1779 
1780  // inverse of the path length for normalizing hit charge to 1 WSE unit
1781  float pathInv = std::abs(tp.Dir[0]);
1782  if(pathInv < 0.05) pathInv = 0.05;
1783 
1784  // Find the hit that has the smallest delta and the number of available hits
1785  tp.Delta = maxDelta;
1786  float delta;
1787  unsigned short imbest = USHRT_MAX;
1788  std::vector<float> deltas(tp.Hits.size(), 100);
1789  // keep track of the best delta - even if it is used
1790  float bestDelta = maxDelta;
1791  unsigned short nAvailable = 0;
1792  unsigned short firstAvailable = USHRT_MAX;
1793  unsigned short lastAvailable = USHRT_MAX;
1794  unsigned short firstUsed = USHRT_MAX;
1795  unsigned short imBadRecoHit = USHRT_MAX;
1796  bool bestHitLongPulse = false;
1797  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1798  tp.UseHit[ii] = false;
1799  unsigned int iht = tp.Hits[ii];
1800  if(iht >= slc.slHits.size()) continue;
1801  if(slc.slHits[iht].allHitsIndex >= (*evt.allHits).size()) continue;
1802  delta = PointTrajDOCA(slc, iht, tp);
1803  if(delta < bestDelta) bestDelta = delta;
1804  if(slc.slHits[iht].InTraj > 0) {
1805  if(firstUsed == USHRT_MAX) firstUsed = ii;
1806  continue;
1807  }
1808  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1809  if(ignoreLongPulseHits && LongPulseHit(hit)) continue;
1810  if(hit.GoodnessOfFit() < 0 || hit.GoodnessOfFit() > 100) imBadRecoHit = ii;
1811  if(firstAvailable == USHRT_MAX) firstAvailable = ii;
1812  lastAvailable = ii;
1813  ++nAvailable;
1814  if(tcc.dbgStp) {
1815  if(useChg) {
1816  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" "<<ii<<" "<<PrintHit(slc.slHits[iht])<<" delta "<<delta<<" Norm Chg "<<(int)(hit.Integral() * pathInv);
1817  } else {
1818  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" "<<ii<<" "<<PrintHit(slc.slHits[iht])<<" delta "<<delta;
1819  }
1820  } // tcc.dbgStp
1821  deltas[ii] = delta;
1822  if(delta < tp.Delta) {
1823  tp.Delta = delta;
1824  imbest = ii;
1825  bestHitLongPulse = LongPulseHit(hit);
1826  }
1827  } // ii
1828 
1829  float chgWght = 0.5;
1830 
1831  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" firstAvailable "<<firstAvailable<<" lastAvailable "<<lastAvailable<<" firstUsed "<<firstUsed<<" imbest "<<imbest<<" single hit. tp.Delta "<<std::setprecision(2)<<tp.Delta<<" bestDelta "<<bestDelta<<" path length "<<1 / pathInv<<" imBadRecoHit "<<imBadRecoHit;
1832  if(imbest == USHRT_MAX || nAvailable == 0) return;
1833  unsigned int bestDeltaHit = tp.Hits[imbest];
1834 
1835  // just use the best hit if we are tracking a high energy electron and the best hit is a long pulse hit
1836  if(tj.Strategy[kStiffEl] && bestHitLongPulse) {
1837  tp.UseHit[imbest] = true;
1838  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1839  return;
1840  }
1841 
1842  // Don't try to use a multiplet if a hit in the middle is in a different trajectory
1843  if(tp.Hits.size() > 2 && nAvailable > 1 && firstUsed != USHRT_MAX && firstAvailable < firstUsed && lastAvailable > firstUsed) {
1844  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" A hit in the middle of the multiplet is used. Use only the best hit";
1845  tp.UseHit[imbest] = true;
1846  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1847  return;
1848  } // Used hit inside multiplet
1849 
1850  if(tp.AngleCode == 1) {
1851  // Get the hits that are in the same multiplet as bestDeltaHit
1852  std::vector<unsigned int> hitsInMultiplet;
1853  GetHitMultiplet(slc, bestDeltaHit, hitsInMultiplet, false);
1854  if(tcc.dbgStp) {
1855  mf::LogVerbatim myprt("TC");
1856  myprt<<" bestDeltaHit "<<PrintHit(slc.slHits[bestDeltaHit]);
1857  myprt<<" in multiplet:";
1858  for(auto& iht : hitsInMultiplet) myprt<<" "<<PrintHit(slc.slHits[iht]);
1859  }
1860  // Consider the case where a bad reco hit might be better. It is probably wider and
1861  // has more charge
1862  if(imBadRecoHit != USHRT_MAX) {
1863  unsigned int iht = tp.Hits[imBadRecoHit];
1864  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1865  if(hit.RMS() > HitsRMSTick(slc, hitsInMultiplet, kUnusedHits)) {
1866  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Using imBadRecoHit "<<PrintHit(slc.slHits[iht]);
1867  tp.UseHit[imBadRecoHit] = true;
1868  slc.slHits[iht].InTraj = tj.ID;
1869  return;
1870  }
1871  } // bad reco hit
1872  // Use the hits in the multiplet instead
1873  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1874  unsigned int iht = tp.Hits[ii];
1875  if(slc.slHits[iht].InTraj > 0) continue;
1876  if(std::find(hitsInMultiplet.begin(), hitsInMultiplet.end(), iht) == hitsInMultiplet.end()) continue;
1877  tp.UseHit[ii] = true;
1878  slc.slHits[iht].InTraj = tj.ID;
1879  } // ii
1880  return;
1881  } // isLA
1882 
1883  // don't use the best UNUSED hit if the best delta is for a USED hit and it is much better
1884  // TY: ignore for RevProp
1885  if(bestDelta < 0.5 * tp.Delta && !tj.AlgMod[kRvPrp]) return;
1886 
1887  if(!useChg || (useChg && (tj.AveChg <= 0 || tj.ChgRMS <= 0))) {
1888  // necessary quantities aren't available for more careful checking
1889  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" tj.AveChg "<<tj.AveChg<<" or tj.ChgRMS "<<tj.ChgRMS<<". Use the best hit";
1890  tp.UseHit[imbest] = true;
1891  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1892  return;
1893  }
1894 
1895  // Don't try to get fancy if we are tracking a stiff tj
1896  if(tj.PDGCode == 13 && bestDelta < 0.5) {
1897  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Tracking muon. Use the best hit";
1898  tp.UseHit[imbest] = true;
1899  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1900  return;
1901  }
1902 
1903  // The best hit is the only one available or this is a small angle trajectory
1904  if(nAvailable == 1 || tp.AngleCode == 0) {
1905  auto& hit = (*evt.allHits)[slc.slHits[bestDeltaHit].allHitsIndex];
1906  float aveChg = tp.AveChg;
1907  if(aveChg <= 0) aveChg = tj.AveChg;
1908  if(aveChg <= 0) aveChg = hit.Integral();
1909  float chgRMS = tj.ChgRMS;
1910  if(chgRMS < 0.2) chgRMS = 0.2;
1911  float bestDeltaHitChgPull = std::abs(hit.Integral() * pathInv / aveChg - 1) / chgRMS;
1912  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" bestDeltaHitChgPull "<<bestDeltaHitChgPull<<" chgPullCut "<<chgPullCut;
1913  if(bestDeltaHitChgPull < chgPullCut || tp.Delta < 0.1) {
1914  tp.UseHit[imbest] = true;
1915  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1916  } // good charge or very good delta
1917  return;
1918  } // bestDeltaHitMultiplicity == 1
1919 
1920  // Find the expected width for the angle of this TP (ticks)
1921  float expectedWidth = ExpectedHitsRMS(slc, tp);
1922 
1923  // Handle two available hits
1924  if(nAvailable == 2) {
1925  // See if these two are in the same multiplet and both are available
1926  std::vector<unsigned int> tHits;
1927  GetHitMultiplet(slc, bestDeltaHit, tHits, false);
1928  // ombest is the index of the other hit in tp.Hits that is in the same multiplet as bestDeltaHit
1929  // if we find it
1930  unsigned short ombest = USHRT_MAX;
1931  unsigned int otherHit = INT_MAX;
1932  if(tHits.size() == 2) {
1933  unsigned short localIndex = 0;
1934  if(tHits[0] == bestDeltaHit) localIndex = 1;
1935  otherHit = tHits[1 - localIndex];
1936  // get the index of this hit in tp.Hits
1937  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1938  if(slc.slHits[tp.Hits[ii]].InTraj > 0) continue;
1939  if(tp.Hits[ii] == otherHit) {
1940  ombest = ii;
1941  break;
1942  }
1943  } // ii
1944  } // tHits.size() == 2
1945  if(tcc.dbgStp) {
1946  mf::LogVerbatim("TC")<<" Doublet: imbest "<<imbest<<" ombest "<<ombest;
1947  }
1948  // The other hit exists in the tp and it is available
1949  if(ombest < tp.Hits.size()) {
1950  // compare the best delta hit and the other hit separately and the doublet as a merged pair
1951  float bestHitDeltaErr = std::abs(tp.Dir[1]) * 0.17 + std::abs(tp.Dir[0]) * HitTimeErr(slc, bestDeltaHit);
1952  // Construct a FOM starting with the delta pull
1953  float bestDeltaHitFOM = deltas[imbest] / bestHitDeltaErr;
1954  if(bestDeltaHitFOM < 0.5) bestDeltaHitFOM = 0.5;
1955  // multiply by the charge pull if it is significant
1956  auto& hit = (*evt.allHits)[slc.slHits[bestDeltaHit].allHitsIndex];
1957  float bestDeltaHitChgPull = std::abs(hit.Integral() * pathInv / tp.AveChg - 1) / tj.ChgRMS;
1958  if(bestDeltaHitChgPull > 1) bestDeltaHitFOM *= chgWght * bestDeltaHitChgPull;
1959  // scale by the ratio
1960  float rmsRat = hit.RMS() / expectedWidth;
1961  if(rmsRat < 1) rmsRat = 1 / rmsRat;
1962  bestDeltaHitFOM *= rmsRat;
1963  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" bestDeltaHit FOM "<<deltas[imbest]/bestHitDeltaErr<<" bestDeltaHitChgPull "<<bestDeltaHitChgPull<<" rmsRat "<<rmsRat<<" bestDeltaHitFOM "<<bestDeltaHitFOM;
1964  // Now do the same for the other hit
1965  float otherHitDeltaErr = std::abs(tp.Dir[1]) * 0.17 + std::abs(tp.Dir[0]) * HitTimeErr(slc, otherHit);
1966  float otherHitFOM = deltas[ombest] / otherHitDeltaErr;
1967  if(otherHitFOM < 0.5) otherHitFOM = 0.5;
1968  auto& ohit = (*evt.allHits)[slc.slHits[otherHit].allHitsIndex];
1969  float otherHitChgPull = std::abs(ohit.Integral() * pathInv / tp.AveChg - 1) / tj.ChgRMS;
1970  if(otherHitChgPull > 1) otherHitFOM *= chgWght * otherHitChgPull;
1971  rmsRat = ohit.RMS() / expectedWidth;
1972  if(rmsRat < 1) rmsRat = 1 / rmsRat;
1973  otherHitFOM *= rmsRat;
1974  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" otherHit FOM "<<deltas[ombest]/otherHitDeltaErr<<" otherHitChgPull "<<otherHitChgPull<<" rmsRat "<<rmsRat<<" otherHitFOM "<<otherHitFOM;
1975  // And for the doublet
1976  float doubletChg = hit.Integral() + ohit.Integral();
1977  float doubletTime = (hit.Integral() * hit.PeakTime() + ohit.Integral() * ohit.PeakTime()) / doubletChg;
1978  doubletChg *= pathInv;
1979  doubletTime *= tcc.unitsPerTick;
1980  float doubletWidthTick = TPHitsRMSTick(slc, tp, kUnusedHits);
1981  float doubletRMSTimeErr = doubletWidthTick * tcc.unitsPerTick;
1982  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" doublet Chg "<<doubletChg<<" doubletTime "<<doubletTime<<" doubletRMSTimeErr "<<doubletRMSTimeErr;
1983  float doubletFOM = PointTrajDOCA(slc, tp.Pos[0], doubletTime, tp) / doubletRMSTimeErr;
1984  if(doubletFOM < 0.5) doubletFOM = 0.5;
1985  float doubletChgPull = std::abs(doubletChg * pathInv / tp.AveChg - 1) / tj.ChgRMS;
1986  if(doubletChgPull > 1) doubletFOM *= chgWght * doubletChgPull;
1987  rmsRat = doubletWidthTick / expectedWidth;
1988  if(rmsRat < 1) rmsRat = 1 / rmsRat;
1989  doubletFOM *= rmsRat;
1990  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" doublet FOM "<<PointTrajDOCA(slc, tp.Pos[0], doubletTime, tp)/doubletRMSTimeErr<<" doubletChgPull "<<doubletChgPull<<" rmsRat "<<rmsRat<<" doubletFOM "<<doubletFOM;
1991  if(doubletFOM < bestDeltaHitFOM && doubletFOM < otherHitFOM) {
1992  tp.UseHit[imbest] = true;
1993  slc.slHits[bestDeltaHit].InTraj = tj.ID;
1994  tp.UseHit[ombest] = true;
1995  slc.slHits[otherHit].InTraj = tj.ID;
1996  } else {
1997  // the doublet is not the best
1998  if(bestDeltaHitFOM < otherHitFOM) {
1999  tp.UseHit[imbest] = true;
2000  slc.slHits[bestDeltaHit].InTraj = tj.ID;
2001  } else {
2002  tp.UseHit[ombest] = true;
2003  slc.slHits[otherHit].InTraj = tj.ID;
2004  } // otherHit is the best
2005  } // doublet is not the best
2006  } else {
2007  // the other hit isn't available. Just use the singlet
2008  tp.UseHit[imbest] = true;
2009  slc.slHits[bestDeltaHit].InTraj = tj.ID;
2010  }
2011  return;
2012  } // nAvailable == 2
2013  float hitsWidth = TPHitsRMSTick(slc, tp, kUnusedHits);
2014  float maxTick = tp.Pos[1] / tcc.unitsPerTick + 0.6 * expectedWidth;
2015  float minTick = tp.Pos[1] / tcc.unitsPerTick - 0.6 * expectedWidth;
2016  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Multiplet: hitsWidth "<<hitsWidth<<" expectedWidth "<<expectedWidth<<" tick range "<<(int)minTick<<" "<<(int)maxTick;
2017  // use all of the hits in the tick window
2018  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
2019  unsigned int iht = tp.Hits[ii];
2020  if(slc.slHits[iht].InTraj > 0) continue;
2021  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
2022  if(hit.PeakTime() < minTick) continue;
2023  if(hit.PeakTime() > maxTick) continue;
2024  tp.UseHit[ii] = true;
2025  slc.slHits[iht].InTraj = tj.ID;
2026  }
2027 
2028  } // FindUseHits
2029 
2030  ////////////////////////////////////////////////
2031  void FillGaps(TCSlice& slc, Trajectory& tj)
2032  {
2033  // Fill in any gaps in the trajectory with close hits regardless of charge (well maybe not quite that)
2034 
2035  if(!tcc.useAlg[kFillGaps]) return;
2036  if(tj.AlgMod[kJunkTj]) return;
2037  if(tj.ChgRMS <= 0) return;
2038 
2039  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
2040  if(npwc < 8) return;
2041 
2042  // don't consider the last few points since they would be trimmed
2043  unsigned short toPt = tj.EndPt[1] - 2;
2044  if(!tj.EndFlag[1][kBragg]) {
2045  // Don't fill gaps (with high-charge hits) near the end. Find the last point near the
2046  // end that would have normal charge if all the hit were added
2047  unsigned short cnt = 0;
2048  for(unsigned short ipt = tj.EndPt[1] - 2; ipt > tj.EndPt[0]; --ipt) {
2049  auto& tp = tj.Pts[ipt];
2050  float chg = tp.Chg;
2051  if(chg == 0) {
2052  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
2053  unsigned int iht = tp.Hits[ii];
2054  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
2055  chg += hit.Integral();
2056  }
2057  } // chg == 0
2058  float chgPull = (chg / tj.AveChg - 1) / tj.ChgRMS;
2059  if(chgPull < 2) {
2060  toPt = ipt;
2061  break;
2062  }
2063  ++cnt;
2064  if(cnt > 20) break;
2065  } // ipt
2066  } // !tj.EndFlag[1][kBragg]
2067 
2068  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"FG: Check Tj "<<tj.ID<<" from "<<PrintPos(slc, tj.Pts[tj.EndPt[0]])<<" to "<<PrintPos(slc, tj.Pts[toPt]);
2069 
2070  // start with the first point that has charge
2071  short firstPtWithChg = tj.EndPt[0];
2072  bool first = true;
2073  float maxDelta = 1;
2074  // don't let MCSMom suffer too much while filling gaps
2075  short minMCSMom = 0.7 * tj.MCSMom;
2076  while(firstPtWithChg < toPt) {
2077  short nextPtWithChg = firstPtWithChg + 1;
2078  // find the next point with charge
2079  for(nextPtWithChg = firstPtWithChg + 1; nextPtWithChg < tj.EndPt[1]; ++nextPtWithChg) {
2080  if(tj.Pts[nextPtWithChg].Chg > 0) break;
2081  } // nextPtWithChg
2082  if(nextPtWithChg == firstPtWithChg + 1) {
2083  // the next point has charge
2084  ++firstPtWithChg;
2085  continue;
2086  }
2087  // Found a gap. Require at least two consecutive points with charge after the gap
2088  if(nextPtWithChg < (tj.EndPt[1] - 1) && tj.Pts[nextPtWithChg + 1].Chg == 0) {
2089  firstPtWithChg = nextPtWithChg;
2090  continue;
2091  }
2092  // Make a bare trajectory point at firstPtWithChg that points to nextPtWithChg
2093  TrajPoint tp;
2094  if(!MakeBareTrajPoint(slc, tj.Pts[firstPtWithChg], tj.Pts[nextPtWithChg], tp)) {
2095  tj.IsGood = false;
2096  return;
2097  }
2098  // Find the maximum delta between hits and the trajectory Pos for all
2099  // hits on this trajectory
2100  if(first) {
2101  maxDelta = 2.5 * MaxHitDelta(slc, tj);
2102  first = false;
2103  } // first
2104  // define a loose charge cut using the average charge at the first point with charge
2105  float maxChg = tj.Pts[firstPtWithChg].AveChg * (1 + 2 * tcc.chargeCuts[0] * tj.ChgRMS);
2106  // Eliminate the charge cut altogether if we are close to an end
2107  if(tj.Pts.size() < 10) {
2108  maxChg = 1E6;
2109  } else {
2110  short chgCutPt = tj.EndPt[0] + 5;
2111  if(firstPtWithChg < chgCutPt) {
2112  // gap is near end 0
2113  maxChg = 1E6;
2114  } else {
2115  // check for gap near end 1
2116  chgCutPt = tj.EndPt[1] - 5;
2117  if(chgCutPt < tj.EndPt[0]) chgCutPt = tj.EndPt[0];
2118  if(nextPtWithChg > chgCutPt) maxChg = 1E6;
2119  }
2120  }
2121 
2122  // fill in the gap
2123  for(unsigned short mpt = firstPtWithChg + 1; mpt < nextPtWithChg; ++mpt) {
2124  if(tj.Pts[mpt].Chg > 0) {
2125  mf::LogVerbatim("TC")<<"FillGaps coding error: firstPtWithChg "<<firstPtWithChg<<" mpt "<<mpt<<" nextPtWithChg "<<nextPtWithChg;
2126  slc.isValid = false;
2127  return;
2128  }
2129  bool filled = false;
2130  float chg = 0;
2131  for(unsigned short ii = 0; ii < tj.Pts[mpt].Hits.size(); ++ii) {
2132  unsigned int iht = tj.Pts[mpt].Hits[ii];
2133  if(slc.slHits[iht].InTraj > 0) continue;
2134  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
2135  float delta = PointTrajDOCA(slc, iht, tp);
2136  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" FG: "<<PrintPos(slc,tj.Pts[mpt])<<" hit "<<PrintHit(slc.slHits[iht])<<" delta "<<delta<<" maxDelta "<<maxDelta<<" Chg "<<hit.Integral()<<" maxChg "<<maxChg;
2137  if(delta > maxDelta) continue;
2138  tj.Pts[mpt].UseHit[ii] = true;
2139  slc.slHits[iht].InTraj = tj.ID;
2140  chg += hit.Integral();
2141  filled = true;
2142  } // ii
2143  if(chg > maxChg || MCSMom(slc, tj) < minMCSMom) {
2144  // don't use these hits after all
2145  UnsetUsedHits(slc, tj.Pts[mpt]);
2146  filled = false;
2147  }
2148  if(filled) {
2149  DefineHitPos(slc, tj.Pts[mpt]);
2150  tj.AlgMod[kFillGaps] = true;
2151  if(tcc.dbgStp) {
2152  PrintTP("FG", slc, mpt, tj.StepDir, tj.Pass, tj.Pts[mpt]);
2153  mf::LogVerbatim("TC")<<"Check MCSMom "<<MCSMom(slc, tj);
2154  }
2155  } // filled
2156  } // mpt
2157  firstPtWithChg = nextPtWithChg;
2158  } // firstPtWithChg
2159 
2160  if(tj.AlgMod[kFillGaps]) tj.MCSMom = MCSMom(slc, tj);
2161 
2162  } // FillGaps
2163 
2164  ////////////////////////////////////////////////
2166  {
2167  // Check for many unused hits in high multiplicity TPs in work and try to use them
2168 
2169  if(!tcc.useAlg[kCHMUH]) return;
2170 
2171  // This code might do bad things to short trajectories
2172  if(NumPtsWithCharge(slc, tj, true) < 6) return;
2173  if(tj.EndPt[0] == tj.EndPt[1]) return;
2174  // Angle code 0 tjs shouldn't have any high multiplicity hits added to them
2175  if(tj.Pts[tj.EndPt[1]].AngleCode == 0) return;
2176 
2177  // count the number of unused hits multiplicity > 1 hits and decide
2178  // if the unused hits should be used. This may trigger another
2179  // call to StepAway
2180  unsigned short ii, stopPt;
2181  // Use this to see if the high multiplicity Pts are mostly near
2182  // the end or further upstream
2183  unsigned short lastMult1Pt = USHRT_MAX;
2184  // the number of TPs with > 1 hit (HiMult)
2185  unsigned short nHiMultPt = 0;
2186  // the total number of hits associated with HiMult TPs
2187  unsigned short nHiMultPtHits = 0;
2188  // the total number of used hits associated with HiMult TPs
2189  unsigned short nHiMultPtUsedHits = 0;
2190  unsigned int iht;
2191  // start counting at the leading edge and break if a hit
2192  // is found that is used in a trajectory
2193  bool doBreak = false;
2194  unsigned short jj;
2195  for(ii = 1; ii < tj.Pts.size(); ++ii) {
2196  stopPt = tj.EndPt[1] - ii;
2197  for(jj = 0; jj < tj.Pts[stopPt].Hits.size(); ++jj) {
2198  iht = tj.Pts[stopPt].Hits[jj];
2199  if(slc.slHits[iht].InTraj > 0) {
2200  doBreak = true;
2201  break;
2202  }
2203  } // jj
2204  if(doBreak) break;
2205  // require 2 consecutive multiplicity = 1 points
2206  if(lastMult1Pt == USHRT_MAX && tj.Pts[stopPt].Hits.size() == 1 && tj.Pts[stopPt-1].Hits.size() == 1) lastMult1Pt = stopPt;
2207  if(tj.Pts[stopPt].Hits.size() > 1) {
2208  ++nHiMultPt;
2209  nHiMultPtHits += tj.Pts[stopPt].Hits.size();
2210  nHiMultPtUsedHits += NumHitsInTP(tj.Pts[stopPt], kUsedHits);
2211  } // high multiplicity TP
2212  // stop looking when two consecutive single multiplicity TPs are found
2213  if(lastMult1Pt != USHRT_MAX) break;
2214  if(stopPt == 1) break;
2215  } // ii
2216  // Don't do this if there aren't a lot of high multiplicity TPs
2217  float fracHiMult = (float)nHiMultPt / (float)ii;
2218  if(lastMult1Pt != USHRT_MAX) {
2219  float nchk = tj.EndPt[1] - lastMult1Pt + 1;
2220  fracHiMult = (float)nHiMultPt / nchk;
2221  } else {
2222  fracHiMult = (float)nHiMultPt / (float)ii;
2223  }
2224  float fracHitsUsed = 0;
2225  if(nHiMultPt > 0 && nHiMultPtHits > 0) fracHitsUsed = (float)nHiMultPtUsedHits / (float)nHiMultPtHits;
2226  // Use this to limit the number of points fit for trajectories that
2227  // are close the LA tracking cut
2228  ii = tj.EndPt[1];
2229  bool sortaLargeAngle = (AngleRange(tj.Pts[ii]) == 1);
2230 
2231  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"CHMUH: First InTraj stopPt "<<stopPt<<" fracHiMult "<<fracHiMult<<" fracHitsUsed "<<fracHitsUsed<<" lastMult1Pt "<<lastMult1Pt<<" sortaLargeAngle "<<sortaLargeAngle;
2232  if(fracHiMult < 0.3) return;
2233  if(fracHitsUsed > 0.98) return;
2234 
2235  float maxDelta = 2.5 * MaxHitDelta(slc, tj);
2236 
2237  if(tcc.dbgStp) {
2238  mf::LogVerbatim("TC")<<" Pts size "<<tj.Pts.size()<<" nHiMultPt "<<nHiMultPt<<" nHiMultPtHits "<<nHiMultPtHits<<" nHiMultPtUsedHits "<<nHiMultPtUsedHits<<" sortaLargeAngle "<<sortaLargeAngle<<" maxHitDelta "<<maxDelta;
2239  }
2240 
2241  // Use next pass cuts if available
2242  if(sortaLargeAngle && tj.Pass < tcc.minPtsFit.size()-1) ++tj.Pass;
2243 
2244  // Make a copy of tj in case something bad happens
2245  Trajectory TjCopy = tj;
2246  // and the list of used hits
2247  auto inTrajHits = PutTrajHitsInVector(tj, kUsedHits);
2248  unsigned short ipt;
2249 
2250  // unset the used hits from stopPt + 1 to the end
2251  for(ipt = stopPt + 1; ipt < tj.Pts.size(); ++ipt) UnsetUsedHits(slc, tj.Pts[ipt]);
2252  SetEndPoints(tj);
2253  float delta;
2254  bool added;
2255  for(ipt = stopPt + 1; ipt < tj.Pts.size(); ++ipt) {
2256  // add hits that are within maxDelta and re-fit at each point
2257  added = false;
2258  for(ii = 0; ii < tj.Pts[ipt].Hits.size(); ++ii) {
2259  iht = tj.Pts[ipt].Hits[ii];
2260  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" ipt "<<ipt<<" hit "<<PrintHit(slc.slHits[iht])<<" inTraj "<<slc.slHits[iht].InTraj<<" delta "<<PointTrajDOCA(slc, iht, tj.Pts[ipt]);
2261  if(slc.slHits[iht].InTraj != 0) continue;
2262  delta = PointTrajDOCA(slc, iht, tj.Pts[ipt]);
2263  if(delta > maxDelta) continue;
2264  if (!NumHitsInTP(TjCopy.Pts[ipt], kUsedHits)||TjCopy.Pts[ipt].UseHit[ii]){
2265  tj.Pts[ipt].UseHit[ii] = true;
2266  slc.slHits[iht].InTraj = tj.ID;
2267  added = true;
2268  }
2269  } // ii
2270  if(added) DefineHitPos(slc, tj.Pts[ipt]);
2271  if(tj.Pts[ipt].Chg == 0) continue;
2272  tj.EndPt[1] = ipt;
2273  // This will be incremented by one in UpdateTraj
2274  if(sortaLargeAngle) tj.Pts[ipt].NTPsFit = 2;
2275  UpdateTraj(slc, tj);
2276  if(tj.NeedsUpdate) {
2277  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"UpdateTraj failed on point "<<ipt;
2278  // Clobber the used hits from the corrupted points in tj
2279  for(unsigned short jpt = stopPt + 1; jpt <= ipt; ++jpt) {
2280  for(unsigned short jj = 0; jj < tj.Pts[jpt].Hits.size(); ++jj) {
2281  if(tj.Pts[jpt].UseHit[jj]) slc.slHits[tj.Pts[jpt].Hits[jj]].InTraj = 0;
2282  } // jj
2283  } // jpt
2284  // restore the original trajectory
2285  tj = TjCopy;
2286  // restore the hits
2287  for(unsigned short jpt = stopPt + 1; jpt <= ipt; ++jpt) {
2288  for(unsigned short jj = 0; jj < tj.Pts[jpt].Hits.size(); ++jj) {
2289  if(tj.Pts[jpt].UseHit[jj]) slc.slHits[tj.Pts[jpt].Hits[jj]].InTraj = tj.ID;
2290  } // jj
2291  } // jpt
2292  return;
2293  }
2294  GottaKink(slc, tj, true);
2295  if(tcc.dbgStp) PrintTrajectory("CHMUH", slc, tj, ipt);
2296  } // ipt
2297  // if we made it here it must be OK
2298  SetEndPoints(tj);
2299  // Try to extend it, unless there was a kink
2300  if(tj.EndFlag[1][kAtKink]) return;
2301  // trim the end points although this shouldn't happen
2302  if(tj.EndPt[1] != tj.Pts.size() - 1) tj.Pts.resize(tj.EndPt[1] + 1);
2303  tj.AlgMod[kCHMUH] = true;
2304  } // CheckHiMultUnusedHits
2305 
2306  ////////////////////////////////////////////////
2308  {
2309  // mask off high multiplicity TPs at the end
2310  if(!tcc.useAlg[kCHMEH]) return;
2311  if(tj.EndFlag[1][kBragg]) return;
2312  if(tj.Pts.size() < 10) return;
2313  if(tj.Pts[tj.EndPt[1]].AngleCode == 0) return;
2314  // find the average multiplicity in the first half
2315  unsigned short aveMult= 0;
2316  unsigned short ipt, nhalf = tj.Pts.size() / 2;
2317  unsigned short cnt = 0;
2318  for(auto& tp : tj.Pts) {
2319  if(tp.Chg == 0) continue;
2320  aveMult += tp.Hits.size();
2321  ++cnt;
2322  if(cnt == nhalf) break;
2323  } // pt
2324  if(cnt == 0) return;
2325  aveMult /= cnt;
2326  if(aveMult == 0) aveMult = 1;
2327  // convert this into a cut
2328  aveMult *= 3;
2329  cnt = 0;
2330  for(ipt = tj.EndPt[1]; ipt > tj.EndPt[0]; --ipt) {
2331  if(tj.Pts[ipt].Chg == 0) continue;
2332  if(tj.Pts[ipt].Hits.size() > aveMult) {
2333  UnsetUsedHits(slc, tj.Pts[ipt]);
2334  ++cnt;
2335  continue;
2336  }
2337  break;
2338  } // ipt
2339  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"CHMEH multiplicity cut "<<aveMult<<" number of TPs masked off "<<cnt;
2340  if(cnt > 0) {
2341  tj.AlgMod[kCHMEH] = true;
2342  SetEndPoints(tj);
2343  }
2344  } // CheckHiMultEndHits
2345 
2346  //////////////////////////////////////////
2348  {
2349  // Estimate the Delta RMS of the TPs on the end of tj.
2350 
2351  unsigned int lastPt = tj.EndPt[1];
2352  TrajPoint& lastTP = tj.Pts[lastPt];
2353 
2354  if(lastTP.Chg == 0) return;
2355  if(lastPt < 6) return;
2356 
2357  unsigned short ii, ipt, cnt = 0;
2358  float sum = 0;
2359  for(ii = 1; ii < tj.Pts.size(); ++ii) {
2360  ipt = lastPt - ii;
2361  if(ipt > tj.Pts.size() - 1) break;
2362  if(tj.Pts[ipt].Chg == 0) continue;
2363  sum += PointTrajDOCA(slc, tj.Pts[ipt].Pos[0], tj.Pts[ipt].Pos[1], lastTP);
2364  ++cnt;
2365  if(cnt == lastTP.NTPsFit) break;
2366  if(ipt == 0) break;
2367  }
2368  if(cnt < 3) return;
2369  // RMS of Gaussian distribution is ~1.2 x the average
2370  // of a one-sided Gaussian distribution (since Delta is > 0)
2371  lastTP.DeltaRMS = 1.2 * sum / (float)cnt;
2372  if(lastTP.DeltaRMS < 0.02) lastTP.DeltaRMS = 0.02;
2373 
2374  } // UpdateDeltaRMS
2375 
2376  //////////////////////////////////////////
2377  void MaskBadTPs(TCSlice& slc, Trajectory& tj, float const& maxChi)
2378  {
2379  // Remove TPs that have the worst values of delta until the fit chisq < maxChi
2380 
2381  if(!tcc.useAlg[kMaskBadTPs]) return;
2382  //don't use this function for reverse propagation
2383  if(!tcc.useAlg[kRvPrp]) return;
2384 
2385  bool prt = (tcc.dbgStp || tcc.dbgAlg[kMaskBadTPs]);
2386 
2387  if(tj.Pts.size() < 3) {
2388  // mf::LogError("TC")<<"MaskBadTPs: Trajectory ID "<<tj.ID<<" too short to mask hits ";
2389  tj.IsGood = false;
2390  return;
2391  }
2392  unsigned short nit = 0;
2393  TrajPoint& lastTP = tj.Pts[tj.Pts.size() - 1];
2394  while(lastTP.FitChi > maxChi && nit < 3) {
2395  float maxDelta = 0;
2396  unsigned short imBad = USHRT_MAX;
2397  unsigned short cnt = 0;
2398  for(unsigned short ii = 1; ii < tj.Pts.size(); ++ii) {
2399  unsigned short ipt = tj.Pts.size() - 1 - ii;
2400  TrajPoint& tp = tj.Pts[ipt];
2401  if(tp.Chg == 0) continue;
2402  if(tp.Delta > maxDelta) {
2403  maxDelta = tp.Delta;
2404  imBad = ipt;
2405  }
2406  ++cnt;
2407  if(cnt == tp.NTPsFit) break;
2408  } // ii
2409  if(imBad == USHRT_MAX) return;
2410  if(prt) mf::LogVerbatim("TC")<<"MaskBadTPs: lastTP.FitChi "<<lastTP.FitChi<<" Mask point "<<imBad;
2411  // mask the point
2412  UnsetUsedHits(slc, tj.Pts[imBad]);
2413  FitTraj(slc, tj);
2414  if(prt) mf::LogVerbatim("TC")<<" after FitTraj "<<lastTP.FitChi;
2415  tj.AlgMod[kMaskBadTPs] = true;
2416  ++nit;
2417  } // lastTP.FItChi > maxChi && nit < 3
2418 
2419  } // MaskBadTPs
2420 
2421  ////////////////////////////////////////////////
2423  {
2424  // The hits in the TP at the end of the trajectory were masked off. Decide whether to continue stepping with the
2425  // current configuration (true) or whether to stop and possibly try with the next pass settings (false)
2426 
2427  if(!tcc.useAlg[kMaskHits]) return true;
2428 
2429  unsigned short lastPt = tj.Pts.size() - 1;
2430  if(tj.Pts[lastPt].Chg > 0) return true;
2431  unsigned short endPt = tj.EndPt[1];
2432 
2433  // count the number of points w/o used hits and the number with one unused hit
2434  unsigned short nMasked = 0;
2435  unsigned short nOneHit = 0;
2436  unsigned short nOKChg = 0;
2437  unsigned short nOKDelta = 0;
2438  // number of points with Pos > HitPos
2439  unsigned short nPosDelta = 0;
2440  // number of points with Delta increasing vs ipt
2441  unsigned short nDeltaIncreasing = 0;
2442  // Fake this a bit to simplify comparing the counts
2443  float prevDelta = tj.Pts[endPt].Delta;
2444  float maxOKDelta = 10 * tj.Pts[endPt].DeltaRMS;
2445  float maxOKChg = 0;
2446  // find the maximum charge point on the trajectory
2447  for(unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) if(tj.Pts[ipt].Chg > maxOKChg) maxOKChg = tj.Pts[ipt].Chg;
2448  for(unsigned short ii = 1; ii < tj.Pts.size(); ++ii) {
2449  unsigned short ipt = tj.Pts.size() - ii;
2450  auto& tp = tj.Pts[ipt];
2451  if(tp.Chg > 0) break;
2452  unsigned short nUnusedHits = 0;
2453  float chg = 0;
2454  for(unsigned short jj = 0; jj < tp.Hits.size(); ++jj) {
2455  unsigned int iht = tp.Hits[jj];
2456  if(slc.slHits[iht].InTraj != 0) continue;
2457  ++nUnusedHits;
2458  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
2459  chg += hit.Integral();
2460  } // jj
2461  if(chg < maxOKChg) ++nOKChg;
2462  if(nUnusedHits == 1) ++nOneHit;
2463  if(tp.Delta < maxOKDelta) ++nOKDelta;
2464  // count the number of points with Pos time > HitPos time
2465  if(tp.Pos[1] > tp.HitPos[1]) ++nPosDelta;
2466  // The number of increasing delta points: Note implied absolute value
2467  if(tp.Delta < prevDelta) ++nDeltaIncreasing;
2468  prevDelta = tp.Delta;
2469  ++nMasked;
2470  } // ii
2471 
2472  // determine if the hits are wandering away from the trajectory direction. This will result in
2473  // nPosDelta either being ~0 or ~equal to the number of masked points. nPosDelta should have something
2474  // in between these two extremes if we are stepping through a messy region
2475  bool driftingAway = nMasked > 2 && (nPosDelta == 0 || nPosDelta == nMasked);
2476  // Note that nDeltaIncreasing is always positive
2477  if(driftingAway && nDeltaIncreasing < nMasked - 1) driftingAway = false;
2478 
2479  if(tcc.dbgStp) {
2480  mf::LogVerbatim("TC")<<"MHOK: nMasked "<<nMasked<<" nOneHit "<<nOneHit<<" nOKChg "<<nOKChg<<" nOKDelta "<<nOKDelta<<" nPosDelta "<<nPosDelta<<" nDeltaIncreasing "<<nDeltaIncreasing<<" driftingAway? "<<driftingAway;
2481  }
2482 
2483  if(!driftingAway) {
2484  if(nMasked < 8 || nOneHit < 8) return true;
2485  if(nOKDelta != nMasked) return true;
2486  if(nOKChg != nMasked) return true;
2487  }
2488 
2489  // we would like to reduce the number of fitted points to a minimum and include
2490  // the masked hits, but we can only do that if there are enough points
2491  if(tj.Pts[endPt].NTPsFit <= tcc.minPtsFit[tj.Pass]) {
2492  // stop stepping if we have masked off more points than are in the fit
2493  if(nMasked > tj.Pts[endPt].NTPsFit) return false;
2494  return true;
2495  }
2496  // Reduce the number of points fit and try to include the points
2497  unsigned short newNTPSFit;
2498  if(tj.Pts[endPt].NTPsFit > 2 * tcc.minPtsFit[tj.Pass]) {
2499  newNTPSFit = tj.Pts[endPt].NTPsFit / 2;
2500  } else {
2501  newNTPSFit = tcc.minPtsFit[tj.Pass];
2502  }
2503  for(unsigned ipt = endPt + 1; ipt < tj.Pts.size(); ++ipt) {
2504  TrajPoint& tp = tj.Pts[ipt];
2505  for(unsigned short ii = 0; ii < tj.Pts[ipt].Hits.size(); ++ii) {
2506  unsigned int iht = tp.Hits[ii];
2507  if(slc.slHits[iht].InTraj == 0) {
2508  tp.UseHit[ii] = true;
2509  slc.slHits[iht].InTraj = tj.ID;
2510  break;
2511  }
2512  } // ii
2513  DefineHitPos(slc, tp);
2514  SetEndPoints(tj);
2515  tp.NTPsFit = newNTPSFit;
2516  FitTraj(slc, tj);
2517  if(tcc.dbgStp) PrintTrajectory("MHOK", slc, tj, ipt);
2518  } // ipt
2519 
2520  tj.AlgMod[kMaskHits] = true;
2521  UpdateTjChgProperties("MHOK", slc, tj, tcc.dbgStp);
2522  return true;
2523 
2524  } // MaskedHitsOK
2525 
2526  ////////////////////////////////////////////////
2528  {
2529  // Returns true if there are a number of Tps that were not used in the trajectory because the fit was poor and the
2530  // charge pull is not really high. This
2531 
2532  // don't consider muons
2533  if(tj.PDGCode == 13) return false;
2534  // or long straight Tjs
2535  if(tj.Pts.size() > 40 && tj.MCSMom > 200) return false;
2536 
2537  unsigned short nBadFit = 0;
2538  unsigned short nHiChg = 0;
2539  unsigned short cnt = 0;
2540  for(unsigned short ipt = tj.Pts.size() - 1; ipt > tj.EndPt[1]; --ipt ) {
2541  if(tj.Pts[ipt].FitChi > 2) ++nBadFit;
2542  if(tj.Pts[ipt].ChgPull > 3) ++nHiChg;
2543  ++cnt;
2544  if(cnt == 5) break;
2545  } // ipt
2546 
2547  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"StopIfBadFits: nBadFit "<<nBadFit<<" nHiChg "<<nHiChg;
2548  if(nBadFit > 3 && nHiChg == 0) return true;
2549  return false;
2550 
2551  } // StopIfBadFits
2552 
2553 ////////////////////////////////////////////////
2554  bool GottaKink(TCSlice& slc, Trajectory& tj, bool doTrim)
2555  {
2556  // This function returns true if it detects a kink in the trajectory
2557  // This function trims the points after a kink if one is found if doTrim is true.
2558 
2559  // tcc.kinkCuts[] fcl configuration
2560  // 0 = Number of TPs to fit at the end
2561  // 1 = Min kink significance
2562  // 2 = Use charge in significance calculation if > 0
2563  // 3 = 3D kink fit length (cm) - used in PFPUtils/SplitAtKinks
2564 
2565  // don't look for kinks if this looks a high energy electron
2566  // BB Jan 2, 2020: Return true if a kink was found but don't set the
2567  // stop-at-kink end flag
2568  if(tj.Strategy[kStiffEl]) return false;
2569  // Need at least 2 * kinkCuts[2] points with charge to find a kink
2570  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
2571  unsigned short nPtsFit = tcc.kinkCuts[0];
2572  // Set nPtsFit for slowing tjs to the last TP NTPsFit
2573  if(tj.Strategy[kSlowing]) nPtsFit = tj.Pts[tj.EndPt[1]].NTPsFit;
2574  if(npwc < 2 * nPtsFit) return false;
2575 
2576  bool useCharge = (tcc.kinkCuts[2] > 0);
2577 
2578  // find the point where a kink is expected and fit the points after that point
2579  unsigned short fitPt = USHRT_MAX;
2580  unsigned short cnt = 0;
2581  for(unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
2582  unsigned short ipt = tj.EndPt[1] - ii - 1;
2583  // stay away from the starting points which may be skewed if this is a
2584  // stopping track
2585  if(ipt <= tj.EndPt[0] + 2) break;
2586  if(tj.Pts[ipt].Chg <= 0) continue;
2587  ++cnt;
2588  // Note that the fitPt is not included in the fits in the kink significance so we need
2589  // one more point
2590  if(cnt > nPtsFit) {
2591  fitPt = ipt;
2592  break;
2593  }
2594  } // ii
2595  if(fitPt == USHRT_MAX) {
2596  if(tcc.dbgStp) {
2597  mf::LogVerbatim myprt("TC");
2598  myprt<<"GKv2 fitPt not valid. Counted "<<cnt<<" points. Need "<<nPtsFit;
2599  } // tcc.dbgStp
2600  return false;
2601  }
2602 
2603  tj.Pts[fitPt].KinkSig = KinkSignificance(slc, tj, fitPt, nPtsFit, useCharge, tcc.dbgStp);
2604 
2605  bool thisPtHasKink = (tj.Pts[fitPt].KinkSig > tcc.kinkCuts[1]);
2606  bool prevPtHasKink = (tj.Pts[fitPt - 1].KinkSig > tcc.kinkCuts[1]);
2607  if(tcc.dbgStp) {
2608  mf::LogVerbatim myprt("TC");
2609  myprt<<"GKv2 fitPt "<<fitPt<<" "<<PrintPos(slc, tj.Pts[fitPt]);
2610  myprt<<std::fixed<<std::setprecision(5);
2611  myprt<<" KinkSig "<<std::setprecision(5)<<tj.Pts[fitPt].KinkSig;
2612  myprt<<" prevPt significance "<<tj.Pts[fitPt - 1].KinkSig;
2613  if(!thisPtHasKink && !prevPtHasKink) myprt<<" no kink";
2614  if(thisPtHasKink && !prevPtHasKink) myprt<<" -> Start kink region";
2615  if(thisPtHasKink && prevPtHasKink) myprt<<" -> Inside kink region";
2616  if(!thisPtHasKink && prevPtHasKink) myprt<<" -> End kink region";
2617  } // dbgStp
2618  // See if we just passed a series of points having a high kink significance. If so,
2619  // then find the point with the maximum value and call that the kink point
2620  // Don't declare a kink (yet)
2621  if(thisPtHasKink) return false;
2622  // neither points are kink-like
2623  if(!prevPtHasKink) return false;
2624 
2625  // We have left a kink region. Find the point with the max likelihood and call
2626  // that the kink point
2627  float maxSig = tcc.kinkCuts[1];
2628  unsigned short kinkRegionLength = 0;
2629  unsigned short maxKinkPt = USHRT_MAX;
2630  for(unsigned short ipt = fitPt - 1; ipt > tj.EndPt[0]; --ipt) {
2631  auto& tp = tj.Pts[ipt];
2632  if(tp.KinkSig < 0) continue;
2633  if(tp.KinkSig > maxSig) {
2634  // track the max significance
2635  maxSig = tp.KinkSig;
2636  maxKinkPt = ipt;
2637  } // tp.KinkSig > maxSig
2638  // find the start of the kink region
2639  if(tp.KinkSig < tcc.kinkCuts[1]) break;
2640  ++kinkRegionLength;
2641  } // ipt
2642  if(maxKinkPt == USHRT_MAX) return false;
2643  // Require that the candidate kink be above the cut threshold for more than one point.
2644  // Scale the requirement by the number of points in the fit
2645  unsigned short kinkRegionLengthMin = 1 + nPtsFit / 5;
2646  if(tj.Strategy[kStiffMu]) kinkRegionLengthMin = 1 + nPtsFit / 3;
2647  if(kinkRegionLength < kinkRegionLengthMin) {
2648  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"GKv2: kink region too short "<<kinkRegionLength<<" Min "<<kinkRegionLengthMin;
2649  return false;
2650  }
2651  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"GKv2: kink at "<<PrintPos(slc, tj.Pts[maxKinkPt])<<std::setprecision(3)<<" maxSig "<<maxSig<<" kinkRegionLength "<<kinkRegionLength<<" Min "<<kinkRegionLengthMin;
2652  // don't alter the tj unless doTrim is true
2653  if(!doTrim) return true;
2654  // trim the points
2655  for(unsigned short ipt = maxKinkPt + 1; ipt <= tj.EndPt[1]; ++ipt) UnsetUsedHits(slc, tj.Pts[ipt]);
2656  SetEndPoints(tj);
2657  // trim another point if the charge of the last two points is wildly dissimilar
2658  float lastChg = tj.Pts[tj.EndPt[1]].Chg;
2659  float prevChg = tj.Pts[tj.EndPt[1] - 1].Chg;
2660  float chgAsym = std::abs(lastChg - prevChg) / (lastChg + prevChg);
2661  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"GKv2: last point after trim "<<PrintPos(slc, tj.Pts[tj.EndPt[1]])<<" chgAsym "<<chgAsym;
2662  if(chgAsym > 0.1) {
2663  UnsetUsedHits(slc, tj.Pts[tj.EndPt[1]]);
2664  SetEndPoints(tj);
2665  }
2666  tj.EndFlag[1][kAtKink] = true;
2667  return true;
2668 
2669  } // GottaKink
2670 
2671  ////////////////////////////////////////////////
2672  void ChkBegin(TCSlice& slc, Trajectory& tj)
2673  {
2674  // Check the parameters at the start of the trajectory. The first
2675  // points may not belong to this trajectory since they were added when there was
2676  // little information. This information may be updated later if ReversePropagate is used
2677 
2678  if(!tcc.useAlg[kFixBegin]) return;
2679  if(tj.AlgMod[kJunkTj]) return;
2680 
2681  // don't do anything if this tj has been modified by ReversePropagate
2682  if(tj.AlgMod[kRvPrp]) return;
2683 
2684  // don't bother with really short tjs
2685  if(tj.Pts.size() < 3) return;
2686 
2687  unsigned short atPt = tj.EndPt[1];
2688  unsigned short maxPtsFit = 0;
2689  unsigned short firstGoodChgPullPt = USHRT_MAX;
2690  for(unsigned short ipt = tj.EndPt[0]; ipt < tj.EndPt[1]; ++ipt) {
2691  auto& tp = tj.Pts[ipt];
2692  if(tp.Chg == 0) continue;
2693  if(tp.AveChg > 0 && firstGoodChgPullPt == USHRT_MAX) {
2694  if(std::abs(tp.ChgPull) < tcc.chargeCuts[0]) firstGoodChgPullPt = ipt;
2695  } // find the first good charge pull point
2696  if(tp.NTPsFit > maxPtsFit) {
2697  maxPtsFit = tp.NTPsFit;
2698  atPt = ipt;
2699  // no reason to continue if there are a good number of points fitted
2700  if(maxPtsFit > 20) break;
2701  }
2702  } // ipt
2703  // find the first point that is in this fit
2704  unsigned short firstPtFit = tj.EndPt[0];
2705  unsigned short cnt = 0;
2706  for(unsigned short ii = 1; ii < tj.Pts.size(); ++ii) {
2707  if(ii > atPt) break;
2708  unsigned short ipt = atPt - ii;
2709  if(tj.Pts[ipt].Chg == 0) continue;
2710  ++cnt;
2711  if(cnt == maxPtsFit) {
2712  firstPtFit = ipt;
2713  break;
2714  } // full count
2715  } // ii
2716 
2717  bool needsRevProp = firstPtFit > 3;
2718  unsigned short nPtsLeft = NumPtsWithCharge(slc, tj, false) - firstPtFit;
2719  if(needsRevProp) {
2720  needsRevProp = (nPtsLeft > 5);
2721  }
2722  if(tcc.dbgStp) {
2723  mf::LogVerbatim myprt("TC");
2724  myprt<<"CB: firstPtFit "<<firstPtFit<<" at "<<PrintPos(slc, tj.Pts[firstPtFit].Pos);
2725  myprt<<" atPt "<<PrintPos(slc, tj.Pts[atPt].Pos);
2726  myprt<<" nPts with charge "<<nPtsLeft;
2727  myprt<<" firstGoodChgPullPt "<<firstGoodChgPullPt;
2728  if(firstGoodChgPullPt != USHRT_MAX) myprt<<" at "<<PrintPos(slc,tj.Pts[firstGoodChgPullPt]);
2729  myprt<<" needsRevProp? "<<needsRevProp;
2730  }
2731 
2732  if(!needsRevProp && firstGoodChgPullPt == USHRT_MAX) {
2733  // check one wire on the other side of EndPt[0] to see if there are hits that are available which could
2734  // be picked up by reverse propagation
2735  TrajPoint tp = tj.Pts[0];
2736  tp.Hits.clear();
2737  tp.UseHit.reset();
2738  // Move the TP "backwards"
2739  double stepSize = tcc.VLAStepSize;
2740  if(tp.AngleCode < 2) stepSize = std::abs(1/tp.Dir[0]);
2741  tp.Pos[0] -= tp.Dir[0] * stepSize * tj.StepDir;
2742  tp.Pos[1] -= tp.Dir[1] * stepSize * tj.StepDir;
2743  // launch RevProp if this wire is dead
2744  unsigned int wire = std::nearbyint(tp.Pos[0]);
2745  unsigned short plane = DecodeCTP(tp.CTP).Plane;
2746  needsRevProp = (wire < slc.nWires[plane] && !evt.goodWire[plane][wire]);
2747  if(tcc.dbgStp && needsRevProp) mf::LogVerbatim("TC")<<"CB: Previous wire "<<wire<<" is dead. Call ReversePropagate";
2748  if(!needsRevProp && firstGoodChgPullPt != USHRT_MAX) {
2749  // check for hits on a not-dead wire
2750  // BB May 20, 2019 Do this more carefully
2751  float maxDelta = 2 * tp.DeltaRMS;
2752  if(FindCloseHits(slc, tp, maxDelta, kAllHits) && !tp.Hits.empty()) {
2753  // count used and unused hits
2754  unsigned short nused = 0;
2755  for(auto iht : tp.Hits) if(slc.slHits[iht].InTraj > 0) ++nused;
2756  if(nused == 0) {
2757  needsRevProp = true;
2758  if(tcc.dbgStp) {
2759  mf::LogVerbatim("TC")<<"CB: Found "<<tp.Hits.size()-nused<<" close unused hits found near EndPt[0] "<<PrintPos(slc, tp)<<". Call ReversePropagate";
2760  } // tcc.dbgStp
2761  } // nused = 0
2762  } // Close hits exist
2763  } // !needsRevProp
2764  } // !needsRevProp
2765 
2766  if(tcc.dbgStp) {
2767  mf::LogVerbatim("TC")<<"CB: maxPtsFit "<<maxPtsFit<<" at point "<<atPt<<" firstPtFit "<<firstPtFit<<" Needs ReversePropagate? "<<needsRevProp;
2768  }
2769 
2770  if(tcc.useAlg[kFTBRvProp] && needsRevProp) {
2771  // lop off the points before firstPtFit and reverse propagate
2772  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" clobber TPs "<<PrintPos(slc, tj.Pts[0])<<" to "<<PrintPos(slc, tj.Pts[firstPtFit])<<". Call TrimEndPts then ReversePropagate ";
2773  // first save the first TP on this trajectory. We will try to re-use it if
2774  // it isn't used during reverse propagation
2775  seeds.push_back(tj.Pts[0]);
2776  for(unsigned short ipt = 0; ipt <= firstPtFit; ++ipt) UnsetUsedHits(slc, tj.Pts[ipt]);
2777  SetEndPoints(tj);
2778  tj.AlgMod[kFTBRvProp] = true;
2779  // Check for quality and trim if necessary before reverse propagation
2780  TrimEndPts("RPi", slc, tj, tcc.qualityCuts, tcc.dbgStp);
2781  if(tj.AlgMod[kKilled]) {
2782  tj.IsGood = false;
2783  return;
2784  }
2785  ReversePropagate(slc, tj);
2786  ChkStopEndPts(slc, tj, tcc.dbgStp);
2787  }
2788  if(firstGoodChgPullPt != USHRT_MAX && firstGoodChgPullPt > atPt) atPt = firstGoodChgPullPt;
2789  // Update the fit parameters of the first points if no reverse propagation was done
2790  if(!tj.AlgMod[kRvPrp]) FixBegin(slc, tj, atPt);
2791 
2792  } // ChkBegin
2793 
2794  ////////////////////////////////////////////////
2795  void FixBegin(TCSlice& slc, Trajectory& tj, unsigned short atPt)
2796  {
2797  // Update the parameters at the beginning of the trajectory starting at point atPt
2798 
2799  if(!tcc.useAlg[kFixBegin]) return;
2800  // ignore short trajectories
2801  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
2802  if(npwc < 6) return;
2803  // ignore shower-like trajectories
2804  if(tj.PDGCode == 11) return;
2805  // ignore junk trajectories
2806  if(tj.AlgMod[kJunkTj]) return;
2807  unsigned short firstPt = tj.EndPt[0];
2808 
2809  if(atPt == tj.EndPt[0]) return;
2810 
2811  // Default is to use DeltaRMS of the last point on the Tj
2812  float maxDelta = 4 * tj.Pts[tj.EndPt[1]].DeltaRMS;
2813 
2814  // Find the max DeltaRMS of points from atPt to EndPt[1]
2815  float maxDeltaRMS = 0;
2816  for(unsigned short ipt = atPt; ipt <= tj.EndPt[1]; ++ipt) {
2817  if(tj.Pts[ipt].DeltaRMS > maxDeltaRMS) maxDeltaRMS = tj.Pts[ipt].DeltaRMS;
2818  } // ipt
2819  maxDelta = 3 * maxDeltaRMS;
2820 
2821  if(tcc.dbgStp) {
2822  mf::LogVerbatim("TC")<<"FB: atPt "<<atPt<<" firstPt "<<firstPt<<" Stops at end 0? "<<PrintEndFlag(tj, 0)<<" start vertex "<<tj.VtxID[0]<<" maxDelta "<<maxDelta;
2823  }
2824 
2825  // update the trajectory for all the points up to atPt
2826  // assume that we will use all of these points
2827  bool maskedPts = false;
2828  for(unsigned short ii = 1; ii < tj.Pts.size(); ++ii) {
2829  if(ii > atPt) break;
2830  unsigned int ipt = atPt - ii;
2831  TrajPoint& tp = tj.Pts[ipt];
2832  tp.Dir = tj.Pts[atPt].Dir;
2833  tp.Ang = tj.Pts[atPt].Ang;
2834  tp.AngErr = tj.Pts[atPt].AngErr;
2835  tp.AngleCode = tj.Pts[atPt].AngleCode;
2836  // Correct the projected time to the wire
2837  float dw = tp.Pos[0] - tj.Pts[atPt].Pos[0];
2838  if(tp.Dir[0] != 0) tp.Pos[1] = tj.Pts[atPt].Pos[1] + dw * tp.Dir[1] / tp.Dir[0];
2839  tj.Pts[ipt].Delta = PointTrajDOCA(slc, tj.Pts[ipt].HitPos[0], tj.Pts[ipt].HitPos[1], tj.Pts[ipt]);
2840  tj.Pts[ipt].DeltaRMS = tj.Pts[atPt].DeltaRMS;
2841  tj.Pts[ipt].NTPsFit = tj.Pts[atPt].NTPsFit;
2842  tj.Pts[ipt].FitChi = tj.Pts[atPt].FitChi;
2843  tj.Pts[ipt].AveChg = tj.Pts[atPt].AveChg;
2844  tj.Pts[ipt].ChgPull = (tj.Pts[ipt].Chg / tj.AveChg - 1) / tj.ChgRMS;
2845  bool badChg = (std::abs(tj.Pts[ipt].ChgPull) > tcc.chargeCuts[0]);
2846  bool maskThisPt = (tj.Pts[ipt].Delta > maxDelta || badChg);
2847  if(maskThisPt) {
2848  UnsetUsedHits(slc, tp);
2849  maskedPts = true;
2850  }
2851  if(tcc.dbgStp) {
2852  mf::LogVerbatim myprt("TC");
2853  myprt<<" Point "<<PrintPos(slc, tj.Pts[ipt].Pos)<<" Delta "<<tj.Pts[ipt].Delta<<" ChgPull "<<tj.Pts[ipt].ChgPull<<" maskThisPt? "<<maskThisPt;
2854  }
2855  if(ipt == 0) break;
2856  } // ii
2857  if(maskedPts) SetEndPoints(tj);
2858  tj.AlgMod[kFixBegin] = true;
2859 
2860  } // FixBegin
2861 
2862  ////////////////////////////////////////////////
2863  bool IsGhost(TCSlice& slc, Trajectory& tj)
2864  {
2865  // Sees if trajectory tj shares many hits with another trajectory and if so merges them.
2866 
2867  if(!tcc.useAlg[kUseGhostHits]) return false;
2868  // ensure that tj is not a saved trajectory
2869  if(tj.ID > 0) return true;
2870  // or an already killed trajectory
2871  if(tj.AlgMod[kKilled]) return true;
2872  if(tj.Pts.size() < 3) return false;
2873  if(tj.Strategy[kStiffEl]) return false;
2874 
2875  // vectors of traj IDs, and the occurrence count
2876  std::vector<int> tID;
2877  std::vector<unsigned short> tCnt;
2878 
2879  unsigned short hitCnt = 0;
2880  unsigned short nAvailable = 0;
2881  for(unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2882  for(unsigned short ii = 0; ii < tj.Pts[ipt].Hits.size(); ++ii) {
2883  // ignore hits used by this trajectory
2884  if(tj.Pts[ipt].UseHit[ii]) {
2885  ++hitCnt;
2886  continue;
2887  }
2888  unsigned int iht = tj.Pts[ipt].Hits[ii];
2889  if(slc.slHits[iht].InTraj > 0 && (unsigned int)slc.slHits[iht].InTraj <= slc.tjs.size()) {
2890  int tjid = slc.slHits[iht].InTraj;
2891  unsigned short indx;
2892  for(indx = 0; indx < tID.size(); ++indx) if(tID[indx] == tjid) break;
2893  if(indx == tID.size()) {
2894  tID.push_back(tjid);
2895  tCnt.push_back(1);
2896  } else {
2897  ++tCnt[indx];
2898  }
2899  } else {
2900  ++nAvailable;
2901  }
2902  } // ii
2903  } // ipt
2904 
2905  // Call it a ghost if > 1/3 of the hits are used by another trajectory
2906  hitCnt /= 3;
2907  int oldTjID = INT_MAX;
2908 
2909  if(tcc.dbgStp) {
2910  mf::LogVerbatim myprt("TC");
2911  myprt<<"IsGhost tj hits size cut "<<hitCnt<<" tID_tCnt";
2912  for(unsigned short ii = 0; ii < tCnt.size(); ++ii) myprt<<" "<<tID[ii]<<"_"<<tCnt[ii];
2913  myprt<<"\nAvailable hits "<<nAvailable;
2914  } // prt
2915 
2916  for(unsigned short ii = 0; ii < tCnt.size(); ++ii) {
2917  if(tCnt[ii] > hitCnt) {
2918  oldTjID = tID[ii];
2919  hitCnt = tCnt[ii];
2920  }
2921  } // ii
2922  if(oldTjID == INT_MAX) return false;
2923  int oldTjIndex = oldTjID - 1;
2924 
2925  // See if this looks like a short delta-ray on a long muon
2926  Trajectory& oTj = slc.tjs[oldTjIndex];
2927  if(oTj.PDGCode == 13 && hitCnt < 0.1 * oTj.Pts.size()) return false;
2928 
2929  // See if there are gaps in this trajectory indicating that it is really a ghost and not
2930  // just a crossing trajectory
2931  // find the range of wires spanned by oTj
2932  int wire0 = INT_MAX;
2933  int wire1 = 0;
2934  for(auto& otp : oTj.Pts) {
2935  int wire = std::nearbyint(otp.Pos[0]);
2936  if(wire < wire0) wire0 = wire;
2937  if(wire > wire1) wire1 = wire;
2938  } // tp
2939 
2940  int nwires = wire1 - wire0 + 1;
2941  std::vector<float> oTjPos1(nwires, -1);
2942  unsigned short nMissedWires = 0;
2943  for(unsigned short ipt = oTj.EndPt[0]; ipt <= oTj.EndPt[1]; ++ipt) {
2944  if(oTj.Pts[ipt].Chg == 0) continue;
2945  int wire = std::nearbyint(oTj.Pts[ipt].Pos[0]);
2946  int indx = wire - wire0;
2947  if(indx < 0 || indx > nwires - 1) continue;
2948  oTjPos1[indx] = oTj.Pts[ipt].Pos[1];
2949  ++nMissedWires;
2950  } // ipt
2951  // count the number of ghost TPs
2952  unsigned short ngh = 0;
2953  // and the number with Delta > 0 relative to oTj
2954  unsigned short nghPlus = 0;
2955  // keep track of the first point and last point appearance of oTj
2956  unsigned short firstPtInoTj = USHRT_MAX;
2957  unsigned short lastPtInoTj = 0;
2958  TrajPoint tp = tj.Pts[tj.EndPt[0]];
2959  for(unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2960  if(tj.Pts[ipt].Chg > 0) {
2961  tp = tj.Pts[ipt];
2962  continue;
2963  }
2964  int wire = std::nearbyint(tj.Pts[ipt].Pos[0]);
2965  int indx = wire - wire0;
2966  if(indx < 0 || indx > nwires - 1) continue;
2967  if(oTjPos1[indx] > 0) {
2968  // ensure that the hits in this tp are used in oTj
2969  bool HitInoTj = false;
2970  for(unsigned short ii = 0; ii < tj.Pts[ipt].Hits.size(); ++ii) {
2971  unsigned int iht = tj.Pts[ipt].Hits[ii];
2972  if(slc.slHits[iht].InTraj == oldTjID) HitInoTj = true;
2973  } // ii
2974  if(HitInoTj) {
2975  ++ngh;
2976  MoveTPToWire(tp, tj.Pts[ipt].Pos[0]);
2977  if(tp.Pos[1] > oTjPos1[indx]) ++nghPlus;
2978  if(firstPtInoTj == USHRT_MAX) firstPtInoTj = ipt;
2979  lastPtInoTj = ipt;
2980  }
2981  } // oTjHasChg[indx]
2982  } // ipt
2983 
2984  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Number of missed wires in oTj gaps "<<nMissedWires<<" Number of ghost hits in these gaps "<<ngh<<" nghPlus "<<nghPlus<<" cut "<<0.2 * nMissedWires;
2985 
2986  if(ngh < 0.2 * nMissedWires) return false;
2987  if(firstPtInoTj > lastPtInoTj) return false;
2988 
2989  // require all of the tj TPs to be on either the + or - side of the oTj trajectory
2990  if(!(nghPlus > 0.8 * ngh || nghPlus < 0.2 * ngh) ) return false;
2991 
2992  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Trajectory is a ghost of "<<oldTjID<<" first point in oTj "<<firstPtInoTj<<" last point "<<lastPtInoTj;
2993 
2994  // unset all of the shared hits
2995  for(unsigned short ipt = firstPtInoTj; ipt <= lastPtInoTj; ++ipt) {
2996  if(tj.Pts[ipt].Chg == 0) continue;
2997  UnsetUsedHits(slc, tj.Pts[ipt]);
2998  if(tcc.dbgStp) PrintTrajectory("IG", slc, tj, ipt);
2999  }
3000  // see how many points are left at the end
3001  ngh = 0;
3002  for(unsigned short ipt = lastPtInoTj; ipt <= tj.EndPt[1]; ++ipt) {
3003  if(tj.Pts[ipt].Chg > 0) ++ngh;
3004  } // ipt
3005  // clobber those too?
3006  if(ngh > 0 && ngh < tcc.minPts[tj.Pass]) {
3007  for(unsigned short ipt = lastPtInoTj; ipt <= tj.EndPt[1]; ++ipt) {
3008  if(tj.Pts[ipt].Chg > 0) UnsetUsedHits(slc, tj.Pts[ipt]);
3009  } // ipt
3010  }
3011  SetEndPoints(tj);
3012  tj.Pts.resize(tj.EndPt[1] + 1);
3013  slc.tjs[oldTjIndex].AlgMod[kUseGhostHits] = true;
3014  TrimEndPts("IG", slc, tj, tcc.qualityCuts, tcc.dbgStp);
3015  if(tj.AlgMod[kKilled]) {
3016  tj.IsGood = false;
3017  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" Failed quality cuts";
3018  return true;
3019  }
3020  tj.MCSMom = MCSMom(slc, tj);
3021  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" New tj size "<<tj.Pts.size();
3022  return true;
3023 
3024  } // IsGhost
3025 
3026  ////////////////////////////////////////////////
3027  bool IsGhost(TCSlice& slc, std::vector<unsigned int>& tHits)
3028  {
3029  // Called by FindJunkTraj to see if the passed hits are close to an existing
3030  // trajectory and if so, they will be used in that other trajectory
3031 
3032  if(!tcc.useAlg[kUseGhostHits]) return false;
3033 
3034  if(tHits.size() < 2) return false;
3035 
3036  bool prt = (tcc.dbgStp || tcc.dbgAlg[kUseGhostHits]);
3037 
3038  // find all nearby hits
3039  std::vector<unsigned int> hitsInMuliplet, nearbyHits;
3040  for(auto iht : tHits) {
3041  GetHitMultiplet(slc, iht, hitsInMuliplet, false);
3042  // prevent double counting
3043  for(auto mht : hitsInMuliplet) {
3044  if(std::find(nearbyHits.begin(), nearbyHits.end(), mht) == nearbyHits.end()) {
3045  nearbyHits.push_back(mht);
3046  }
3047  } // mht
3048  } // iht
3049 
3050  // vectors of traj IDs, and the occurrence count
3051  std::vector<unsigned int> tID, tCnt;
3052  for(auto iht : nearbyHits) {
3053  if(slc.slHits[iht].InTraj <= 0) continue;
3054  unsigned int tid = slc.slHits[iht].InTraj;
3055  unsigned short indx = 0;
3056  for(indx = 0; indx < tID.size(); ++indx) if(tID[indx] == tid) break;
3057  if(indx == tID.size()) {
3058  tID.push_back(tid);
3059  tCnt.push_back(1);
3060  } else {
3061  ++tCnt[indx];
3062  }
3063  } // iht
3064  if(tCnt.empty()) return false;
3065 
3066  // Call it a ghost if > 50% of the hits are used by another trajectory
3067  unsigned short tCut = 0.5 * tHits.size();
3068  int tid = INT_MAX;
3069 
3070  if(prt) {
3071  mf::LogVerbatim myprt("TC");
3072  myprt<<"IsGhost tHits size "<<tHits.size()<<" cut fraction "<<tCut<<" tID_tCnt";
3073  for(unsigned short ii = 0; ii < tCnt.size(); ++ii) myprt<<" "<<tID[ii]<<"_"<<tCnt[ii];
3074  } // prt
3075 
3076  for(unsigned short ii = 0; ii < tCnt.size(); ++ii) {
3077  if(tCnt[ii] > tCut) {
3078  tid = tID[ii];
3079  break;
3080  }
3081  } // ii
3082  if(tid > (int)slc.tjs.size()) return false;
3083 
3084  if(prt) mf::LogVerbatim("TC")<<" is ghost of trajectory "<<tid;
3085 
3086  // Use all hits in tHits that are found in itj
3087  for(auto& tp : slc.tjs[tid - 1].Pts) {
3088  for(unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
3089  unsigned int iht = tp.Hits[ii];
3090  if(slc.slHits[iht].InTraj != 0) continue;
3091  for(unsigned short jj = 0; jj < tHits.size(); ++jj) {
3092  unsigned int tht = tHits[jj];
3093  if(tht != iht) continue;
3094  tp.UseHit[ii] = true;
3095  slc.slHits[iht].InTraj = tid;
3096  break;
3097  } // jj
3098  } // ii
3099  } // tp
3100  slc.tjs[tid - 1].AlgMod[kUseGhostHits] = true;
3101  return true;
3102 
3103  } // IsGhost
3104 
3105  ////////////////////////////////////////////////
3106  void LastEndMerge(TCSlice& slc, CTP_t inCTP)
3107  {
3108  // last ditch attempt to merge long straight broken trajectories by averaging
3109  // all points in the trajectory and applying tight angle and separation cuts.
3110  if(slc.tjs.size() < 2) return;
3111  if(!tcc.useAlg[kLastEndMerge]) return;
3112 
3113  bool prt = tcc.dbgAlg[kLastEndMerge];
3114 
3115  // create an averaged TP for each long Trajectory
3116  std::vector<TrajPoint> tjTP;
3117  for(auto& tj : slc.tjs) {
3118  if(tj.AlgMod[kKilled]) continue;
3119  if(tj.CTP != inCTP) continue;
3120  if(tj.Pts.size() < 10) continue;
3121  if(tj.MCSMom < 100) continue;
3122  auto tjtp = CreateTPFromTj(slc, tj);
3123  if(tjtp.Chg < 0) continue;
3124  tjTP.push_back(tjtp);
3125  } // tj
3126  if(tjTP.size() < 2) return;
3127 
3128  if(prt) {
3129  mf::LogVerbatim myprt("TC");
3130  myprt<<"inside LastEndMerge slice "<<slices.size()-1<<" inCTP "<<inCTP<<" tjTPs";
3131  for(auto& tjtp : tjTP) myprt<<" T"<<tjtp.Step;
3132  }
3133 
3134  for(unsigned short pt1 = 0; pt1 < tjTP.size() - 1; ++pt1) {
3135  auto& tp1 = tjTP[pt1];
3136  auto& tj1 = slc.tjs[tp1.Step - 1];
3137  if(tj1.AlgMod[kKilled]) continue;
3138  for(unsigned short pt2 = pt1 + 1; pt2 < tjTP.size(); ++pt2) {
3139  auto& tp2 = tjTP[pt2];
3140  auto& tj2 = slc.tjs[tp2.Step - 1];
3141  if(tj2.AlgMod[kKilled]) continue;
3142  float dang = DeltaAngle(tp1.Ang, tp2.Ang);
3143  // make an angle cut
3144  if(prt && dang < 0.5) mf::LogVerbatim("TC")<<" T"<<tj1.ID<<" T"<<tj2.ID<<" dang "<<dang;
3145  if(dang > 0.2) continue;
3146  // and an impact parameter cut
3147  unsigned short ipt1, ipt2;
3148  float ip12 = PointTrajDOCA(slc, tp1.Pos[0], tp1.Pos[1], tp2);
3149  float ip21 = PointTrajDOCA(slc, tp2.Pos[0], tp2.Pos[1], tp1);
3150  if(prt) mf::LogVerbatim("TC")<<" ip12 "<<ip12<<" ip21 "<<ip21;
3151  if(ip12 > 15 && ip21 > 15) continue;
3152  float minSep = 100;
3153  // find the separation considering dead wires
3154  TrajTrajDOCA(slc, tj1, tj2, ipt1, ipt2, minSep, false);
3155  if(minSep == 100) continue;
3156  if(ipt1 >= tj1.Pts.size() || ipt2 >= tj2.Pts.size()) continue;
3157  float dwc = DeadWireCount(slc, tj1.Pts[ipt1], tj2.Pts[ipt2]);
3158  if(prt) mf::LogVerbatim("TC")<<" minSep "<<minSep<<" dwc "<<dwc;
3159  minSep -= dwc;
3160  if(minSep > 5) continue;
3161  // finally require that the proximate points are close to the ends
3162  float sep10 = PosSep(tj1.Pts[ipt1].Pos, tj1.Pts[tj1.EndPt[0]].Pos);
3163  float sep11 = PosSep(tj1.Pts[ipt1].Pos, tj1.Pts[tj1.EndPt[1]].Pos);
3164  if(sep10 > 5 && sep11 > 5) continue;
3165  unsigned short end1 = 0;
3166  if(sep11 < sep10) end1 = 1;
3167  float sep20 = PosSep(tj2.Pts[ipt2].Pos, tj2.Pts[tj2.EndPt[0]].Pos);
3168  float sep21 = PosSep(tj2.Pts[ipt2].Pos, tj2.Pts[tj2.EndPt[1]].Pos);
3169  if(sep20 > 5 && sep21 > 5) continue;
3170  unsigned short end2 = 0;
3171  if(sep21 < sep20) end2 = 1;
3172  // don't merge if there is a kink
3173  if(tj1.EndFlag[end1][kAtKink] || tj2.EndFlag[end2][kAtKink]) continue;
3174  if(prt) {
3175  mf::LogVerbatim myprt("TC");
3176  myprt<<"LEM: T"<<tj1.ID<<"_"<<PrintPos(slc, tp1);
3177  if(tj1.VtxID[end1] > 0) myprt<<"->2V"<<tj1.VtxID[end1];
3178  myprt<<" T"<<tj2.ID<<"_"<<PrintPos(slc, tp2);
3179  if(tj2.VtxID[end2] > 0) myprt<<"->2V"<<tj2.VtxID[end2];
3180  myprt<<" dang "<<std::setprecision(2)<<dang<<" ip12 "<<ip12;
3181  myprt<<" ip21 "<<ip21;
3182  myprt<<" minSep "<<minSep;
3183  myprt<<" end sep1 "<<sep10<<" "<<sep11;
3184  myprt<<" end sep2 "<<sep20<<" "<<sep21;
3185  } // prt
3186  if(tj1.VtxID[end1] > 0) {
3187  auto& vx2 = slc.vtxs[tj1.VtxID[end1] - 1];
3188  MakeVertexObsolete("LEM", slc, vx2, true);
3189  }
3190  if(tj2.VtxID[end2] > 0 && tj2.VtxID[end2] != tj1.VtxID[end1]) {
3191  auto& vx2 = slc.vtxs[tj2.VtxID[end2] - 1];
3192  MakeVertexObsolete("LEM", slc, vx2, true);
3193  }
3194  // remove Bragg flags
3195  tj1.EndFlag[end1][kBragg] = false;
3196  tj2.EndFlag[end2][kBragg] = false;
3197  unsigned int it1 = tj1.ID - 1;
3198  unsigned int it2 = tj2.ID - 1;
3199  if(!MergeAndStore(slc, it1, it2, tcc.dbgMrg)) continue;
3200  // set the AlgMod bit
3201  auto& ntj = slc.tjs[slc.tjs.size() - 1];
3202  ntj.AlgMod[kLastEndMerge] = true;
3203  // create a tp for this tj and add it to the list
3204  auto tjtp = CreateTPFromTj(slc, ntj);
3205  if(tjtp.Chg < 0) continue;
3206  if(prt) mf::LogVerbatim("TC")<<" added T"<<ntj.ID<<" to the merge list";
3207  tjTP.push_back(tjtp);
3208  break;
3209  } // pt1
3210  } // pt1
3211 
3212  } // LastEndMerge
3213 
3214  ////////////////////////////////////////////////
3216  {
3217  // Create a trajectory point by averaging the position and direction of all
3218  // TPs in the trajectory. This is used in LastEndMerge
3219  TrajPoint tjtp;
3220  // set the charge invalid
3221  tjtp.Chg = -1;
3222  if(tj.AlgMod[kKilled]) return tjtp;
3223  // stash the ID in the Step
3224  tjtp.Step = tj.ID;
3225  tjtp.CTP = tj.CTP;
3226  tjtp.Pos[0] = 0;
3227  tjtp.Pos[1] = 0;
3228  tjtp.Dir[0] = 0;
3229  tjtp.Dir[1] = 0;
3230  float cnt = 0;
3231  for(unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
3232  auto& tp = tj.Pts[ipt];
3233  if(tp.Chg <= 0) continue;
3234  tjtp.Pos[0] += tp.Pos[0];
3235  tjtp.Pos[1] += tp.Pos[1];
3236  tjtp.Dir[1] += tp.Dir[1];
3237  ++cnt;
3238  } // ipt
3239  tjtp.Pos[0] /= cnt;
3240  tjtp.Pos[1] /= cnt;
3241  tjtp.Dir[1] /= cnt;
3242  double arg = 1 - tjtp.Dir[1] * tjtp.Dir[1];
3243  if(arg < 0) arg = 0;
3244  tjtp.Dir[0] = sqrt(arg);
3245  tjtp.Ang = atan2(tjtp.Dir[1], tjtp.Dir[0]);
3246  tjtp.Chg = 1;
3247  return tjtp;
3248  } // CreateTjTP
3249 
3250  ////////////////////////////////////////////////
3251  void EndMerge(TCSlice& slc, CTP_t inCTP, bool lastPass)
3252  {
3253  // Merges trajectories end-to-end or makes vertices. Does a more careful check on the last pass
3254 
3255  if(slc.tjs.size() < 2) return;
3256  if(!tcc.useAlg[kMerge]) return;
3257 
3258  bool prt = (tcc.dbgMrg && tcc.dbgSlc && inCTP == debug.CTP);
3259  if(prt) mf::LogVerbatim("TC")<<"inside EndMerge slice "<<slices.size()-1<<" inCTP "<<inCTP<<" nTjs "<<slc.tjs.size()<<" lastPass? "<<lastPass;
3260 
3261  // Ensure that all tjs are in the same order
3262  short tccStepDir = 1;
3263  if(!tcc.modes[kStepDir]) tccStepDir = -1;
3264  for(auto& tj : slc.tjs) {
3265  if(tj.AlgMod[kKilled]) continue;
3266  if(tj.CTP != inCTP) continue;
3267  if(tj.StepDir != tccStepDir) ReverseTraj(slc, tj);
3268  } // tj
3269 
3270  unsigned short maxShortTjLen = tcc.vtx2DCuts[0];
3271 
3272  // temp vector for checking the fraction of hits near a merge point
3273  std::vector<int> tjlist(2);
3274 
3275  float minChgRMS = 0.5 * (tcc.chargeCuts[1] + tcc.chargeCuts[2]);
3276 
3277  // iterate whenever a merge occurs since tjs will change. This is not necessary
3278  // when a vertex is created however.
3279  bool iterate = true;
3280  while(iterate) {
3281  iterate = false;
3282  for(unsigned int it1 = 0; it1 < slc.tjs.size(); ++it1) {
3283  auto& tj1 = slc.tjs[it1];
3284  if(tj1.AlgMod[kKilled]) continue;
3285  if(tj1.CTP != inCTP) continue;
3286  // don't try to merge high energy electrons
3287  if(tj1.PDGCode == 111) continue;
3288  for(unsigned short end1 = 0; end1 < 2; ++end1) {
3289  // no merge if there is a vertex at the end
3290  if(tj1.VtxID[end1] > 0) continue;
3291  // make a copy of tp1 so we can mess with it
3292  TrajPoint tp1 = tj1.Pts[tj1.EndPt[end1]];
3293  // do a local fit on the lastpass only using the last 3 points
3294  if(lastPass && tp1.NTPsFit > 3) {
3295  // make a local copy of the tj
3296  auto ttj = slc.tjs[it1];
3297  auto& lastTP = ttj.Pts[ttj.EndPt[end1]];
3298  // fit the last 3 points
3299  lastTP.NTPsFit = 3;
3300  FitTraj(slc, ttj);
3301  tp1 = ttj.Pts[ttj.EndPt[end1]];
3302  } // last pass
3303  bool isVLA = (tp1.AngleCode == 2);
3304  float bestFOM = 5;
3305  if(isVLA) bestFOM = 20;
3306  float bestDOCA;
3307  unsigned int imbest = UINT_MAX;
3308  for(unsigned int it2 = 0; it2 < slc.tjs.size(); ++it2) {
3309  if(it1 == it2) continue;
3310  auto& tj2 = slc.tjs[it2];
3311  // check for consistent direction
3312  if(tj1.StepDir != tj2.StepDir) continue;
3313  if(tj2.AlgMod[kKilled]) continue;
3314  if(tj2.CTP != inCTP) continue;
3315  // don't try to merge high energy electrons
3316  if(tj2.PDGCode == 111) continue;
3317  float olf = OverlapFraction(slc, tj1, tj2);
3318  if(olf > 0.25) continue;
3319  unsigned short end2 = 1 - end1;
3320  // check for a vertex at this end
3321  if(tj2.VtxID[end2] > 0) continue;
3322  TrajPoint& tp2 = tj2.Pts[tj2.EndPt[end2]];
3323  TrajPoint& tp2OtherEnd = tj2.Pts[tj2.EndPt[end1]];
3324  // ensure that the other end isn't closer
3325  if(std::abs(tp2OtherEnd.Pos[0] - tp1.Pos[0]) < std::abs(tp2.Pos[0] - tp1.Pos[0])) continue;
3326  // ensure that the order is correct
3327  if(tj1.StepDir > 0) {
3328  if(tp2.Pos[0] < tp1.Pos[0] - 2) continue;
3329  } else {
3330  if(tp2.Pos[0] > tp1.Pos[0] + 2) continue;
3331  }
3332  // ensure that there is a signal on most of the wires between these points
3333  if(!SignalBetween(slc, tp1, tp2, 0.8)) {
3334  continue;
3335  }
3336  // Find the distance of closest approach for small angle merging
3337  // Inflate the doca cut if we are bridging a block of dead wires
3338  float dang = DeltaAngle(tp1.Ang, tp2.Ang);
3339  float doca = 15;
3340  if(isVLA) {
3341  // compare the minimum separation between Large Angle trajectories using a generous cut
3342  unsigned short ipt1, ipt2;
3343  TrajTrajDOCA(slc, tj1, tj2, ipt1, ipt2, doca);
3344  // if(prt) mf::LogVerbatim("TC")<<" isVLA check ipt1 "<<ipt1<<" ipt2 "<<ipt2<<" doca "<<doca;
3345  } else {
3346  // small angle
3347  doca = PointTrajDOCA(slc, tp1.Pos[0], tp1.Pos[1], tp2);
3348  }
3349  float fom = dang * doca;
3350  if(fom < bestFOM) {
3351  bestFOM = fom;
3352  bestDOCA = doca;
3353  imbest = it2;
3354  }
3355  } // it2
3356  // No merge/vertex candidates
3357  if(imbest == UINT_MAX) continue;
3358 
3359  // Make angle adjustments to tp1.
3360  unsigned int it2 = imbest;
3361  auto& tj2 = slc.tjs[imbest];
3362  unsigned short end2 = 1 - end1;
3363  bool loMCSMom = (tj1.MCSMom + tj2.MCSMom) < 150;
3364  // Don't use the angle at the end Pt for high momentum long trajectories in case there is a little kink at the end
3365  if(tj1.Pts.size() > 50 && tj1.MCSMom > 100) {
3366  if(end1 == 0) {
3367  tp1.Ang = tj1.Pts[tj1.EndPt[0] + 2].Ang;
3368  } else {
3369  tp1.Ang = tj1.Pts[tj1.EndPt[1] - 2].Ang;
3370  }
3371  } else if(loMCSMom) {
3372  // Low momentum - calculate the angle using the two Pts at the end
3373  unsigned short pt1, pt2;
3374  if(end1 == 0) {
3375  pt1 = tj1.EndPt[0];
3376  pt2 = pt1 + 1;
3377  } else {
3378  pt2 = tj1.EndPt[1];
3379  pt1 = pt2 - 1;
3380  }
3381  TrajPoint tpdir;
3382  if(MakeBareTrajPoint(slc, tj1.Pts[pt1], tj1.Pts[pt2], tpdir)) tp1.Ang = tpdir.Ang;
3383  } // low MCSMom
3384  // Now do the same for tj2
3385  TrajPoint tp2 = tj2.Pts[tj2.EndPt[end2]];
3386  if(tj2.Pts.size() > 50 && tj2.MCSMom > 100) {
3387  if(end1 == 0) {
3388  tp2.Ang = tj2.Pts[tj2.EndPt[0] + 2].Ang;
3389  } else {
3390  tp2.Ang = tj2.Pts[tj2.EndPt[1] - 2].Ang;
3391  }
3392  } else if(loMCSMom) {
3393  // Low momentum - calculate the angle using the two Pts at the end
3394  unsigned short pt1, pt2;
3395  if(end2 == 0) {
3396  pt1 = tj2.EndPt[0];
3397  pt2 = pt1 + 1;
3398  } else {
3399  pt2 = tj2.EndPt[1];
3400  pt1 = pt2 - 1;
3401  }
3402  TrajPoint tpdir;
3403  if(MakeBareTrajPoint(slc, tj2.Pts[pt1], tj2.Pts[pt2], tpdir)) tp2.Ang = tpdir.Ang;
3404  } // low MCSMom
3405 
3406  if(!isVLA && !SignalBetween(slc, tp1, tp2, 0.99)) continue;
3407 
3408  // decide whether to merge or make a vertex
3409  // protect against angles > pi/2
3410  float dang = acos(DotProd(tp1.Dir, tp2.Dir));
3411  float sep = PosSep(tp1.Pos, tp2.Pos);
3412  // ignore this pair if the gap between them is much longer than the length of the shortest Tj
3413  float len1 = TrajLength(slc.tjs[it1]);
3414  float len2 = TrajLength(slc.tjs[it2]);
3415  if(len1 < len2 && sep > 3 * len1) continue;
3416  if(len2 < len1 && sep > 3 * len2) continue;
3417 
3418  // default cuts for locMCSMom condition
3419  float dangCut = 1;
3420  float docaCut = 2;
3421  float kinkSig = -1;
3422  if(!loMCSMom) {
3423  unsigned short nPtsFit = tcc.kinkCuts[0];
3424  bool useChg = (tcc.kinkCuts[2] > 0);
3425  kinkSig = KinkSignificance(slc, tj1, end1, tj2, end2, nPtsFit, useChg, prt);
3426  }
3427  docaCut = 1.5;
3428  if(isVLA) docaCut = 15;
3429  float chgPull = 0;
3430  if(tp1.AveChg > tp2.AveChg) {
3431  chgPull = (tp1.AveChg / tp2.AveChg - 1) / minChgRMS;
3432  } else {
3433  chgPull = (tp2.AveChg / tp1.AveChg - 1) / minChgRMS;
3434  }
3435  // open up the cuts on the last pass
3436  float chgFracCut = tcc.vtx2DCuts[8];
3437  float chgPullCut = tcc.chargeCuts[0];
3438  if(lastPass) {
3439  docaCut *= 2;
3440  chgFracCut *= 0.5;
3441  chgPullCut *= 1.5;
3442  }
3443 
3444  // check the merge cuts. Start with doca and dang requirements
3445  bool doMerge = bestDOCA < docaCut && dang < dangCut;
3446  bool showerTjs = tj1.PDGCode == 11 || tj2.PDGCode == 11;
3447  bool hiMCSMom = tj1.MCSMom > 200 || tj2.MCSMom > 200;
3448  // add a charge similarity requirement if not shower-like or low momentum or not LA
3449  if(doMerge && !showerTjs && hiMCSMom && chgPull > tcc.chargeCuts[0] && !isVLA) doMerge = false;
3450  // ignore the charge pull cut if both are high momentum and dang is really small
3451  if(!doMerge && tj1.MCSMom > 900 && tj2.MCSMom > 900 && dang < 0.1 && bestDOCA < docaCut) doMerge = true;
3452 
3453  // do not merge if chgPull is really high
3454  if(doMerge && chgPull > 2 * chgPullCut) doMerge = false;
3455  float dwc = DeadWireCount(slc, tp1, tp2);
3456 
3457  if(doMerge) {
3458  if(lastPass) {
3459  // last pass cuts are looser but ensure that the tj after merging meets the quality cut
3460  float npwc = NumPtsWithCharge(slc, tj1, true) + NumPtsWithCharge(slc, tj2, true);
3461  auto& tp1OtherEnd = tj1.Pts[tj1.EndPt[1 - end1]];
3462  auto& tp2OtherEnd = tj2.Pts[tj2.EndPt[1 - end2]];
3463  float nwires = std::abs(tp1OtherEnd.Pos[0] - tp2OtherEnd.Pos[0]);
3464  if(nwires == 0) nwires = 1;
3465  float hitFrac = npwc / nwires;
3466  doMerge = (hitFrac > tcc.qualityCuts[0]);
3467  } else {
3468  // don't merge if the gap between them is longer than the length of the shortest Tj
3469  if(len1 < len2) {
3470  if(sep > len1) doMerge = false;
3471  } else {
3472  if(sep > len2) doMerge = false;
3473  }
3474  if(prt) mf::LogVerbatim("TC")<<" merge check sep "<<sep<<" len1 "<<len1<<" len2 "<<len2<<" dead wire count "<<dwc<<" Merge? "<<doMerge;
3475  } // not lastPass
3476  } // doMerge
3477 
3478  // Require a large charge fraction near a merge point
3479  tjlist[0] = slc.tjs[it1].ID;
3480  tjlist[1] = slc.tjs[it2].ID;
3481  float chgFrac = ChgFracNearPos(slc, tp1.Pos, tjlist);
3482  if(doMerge && bestDOCA > 1 && chgFrac < chgFracCut) doMerge = false;
3483 
3484  // Check the MCSMom asymmetry and don't merge if it is higher than the user-specified cut
3485  float momAsym = std::abs(tj1.MCSMom - tj2.MCSMom) / (float)(tj1.MCSMom + tj2.MCSMom);
3486  if(doMerge && momAsym > tcc.vtx2DCuts[9]) doMerge = false;
3487  if(doMerge && (tj1.EndFlag[end1][kAtKink] || tj2.EndFlag[end2][kAtKink])) {
3488  // don't merge if a kink exists and the tjs are not too long
3489  if(len1 < 40 && len2 < 40) doMerge = false;
3490  // Kink on one + Bragg at other end of the other
3491  if(tj1.EndFlag[end1][kAtKink] && tj2.EndFlag[1-end2][kBragg]) doMerge = false;
3492  if(tj1.EndFlag[1-end1][kBragg] && tj2.EndFlag[end2][kAtKink]) doMerge = false;
3493  }
3494 
3495  // decide if we should make a vertex instead
3496  bool doVtx = false;
3497  if(!doMerge) {
3498  // check for a significant kink
3499  doVtx = (kinkSig > tcc.kinkCuts[1]);
3500  // and a less significant kink but very close separation
3501  doVtx = (kinkSig > 0.5 * tcc.kinkCuts[1] && sep < 2);
3502  } // !doMerge
3503 
3504  if(prt) {
3505  mf::LogVerbatim myprt("TC");
3506  myprt<<" EM: T"<<slc.tjs[it1].ID<<"_"<<end1<<" - T"<<slc.tjs[it2].ID<<"_"<<end2<<" tp1-tp2 "<<PrintPos(slc, tp1)<<"-"<<PrintPos(slc, tp2);
3507  myprt<<" FOM "<<std::fixed<<std::setprecision(2)<<bestFOM;
3508  myprt<<" DOCA "<<std::setprecision(1)<<bestDOCA;
3509  myprt<<" cut "<<docaCut<<" isVLA? "<<isVLA;
3510  myprt<<" dang "<<std::setprecision(2)<<dang<<" dangCut "<<dangCut;
3511  myprt<<" chgPull "<<std::setprecision(1)<<chgPull<<" Cut "<<chgPullCut;
3512  myprt<<" chgFrac "<<std::setprecision(2)<<chgFrac;
3513  myprt<<" momAsym "<<momAsym;
3514  myprt<<" kinkSig "<<std::setprecision(1)<<kinkSig;
3515  myprt<<" doMerge? "<<doMerge;
3516  myprt<<" doVtx? "<<doVtx;
3517  }
3518 
3519  if(bestDOCA > docaCut) continue;
3520 
3521  if(doMerge) {
3522  if(prt) mf::LogVerbatim("TC")<<" Merge ";
3523  bool didMerge = false;
3524  if(end1 == 1) {
3525  didMerge = MergeAndStore(slc, it1, it2, tcc.dbgMrg);
3526  } else {
3527  didMerge = MergeAndStore(slc, it2, it1, tcc.dbgMrg);
3528  }
3529  if(didMerge) {
3530  // Set the end merge flag for the killed trajectories to aid tracing merges
3531  tj1.AlgMod[kMerge] = true;
3532  tj2.AlgMod[kMerge] = true;
3533  iterate = true;
3534  } // Merge and store successfull
3535  else {
3536  if(prt) mf::LogVerbatim("TC")<<" MergeAndStore failed ";
3537  }
3538  } else if(doVtx) {
3539  // create a vertex instead if it passes the vertex cuts
3540  VtxStore aVtx;
3541  aVtx.CTP = slc.tjs[it1].CTP;
3542  aVtx.ID = slc.vtxs.size() + 1;
3543  // keep it simple if tp1 and tp2 are very close or if the angle between them
3544  // is small
3545  if(prt) {
3546  mf::LogVerbatim("TC")<<" candidate 2V"<<aVtx.ID<<" dang "<<dang<<" sep "<<PosSep(tp1.Pos, tp2.Pos);
3547  }
3548  bool fix2V = (PosSep(tp1.Pos, tp2.Pos) < 3 || dang < 0.1);
3549  if(fix2V) {
3550  aVtx.Pos[0] = 0.5 * (tp1.Pos[0] + tp2.Pos[0]);
3551  aVtx.Pos[1] = 0.5 * (tp1.Pos[1] + tp2.Pos[1]);
3552  aVtx.Stat[kFixed] = true;
3553  aVtx.PosErr[0] = std::abs(tp1.Pos[0] - tp2.Pos[0]);
3554  aVtx.PosErr[1] = std::abs(tp1.Pos[1] - tp2.Pos[1]);
3555  } else {
3556  float sepCut = tcc.vtx2DCuts[1];
3557  bool tj1Short = (slc.tjs[it1].EndPt[1] - slc.tjs[it1].EndPt[0] < maxShortTjLen);
3558  bool tj2Short = (slc.tjs[it2].EndPt[1] - slc.tjs[it2].EndPt[0] < maxShortTjLen);
3559  if(tj1Short || tj2Short) sepCut = tcc.vtx2DCuts[1];
3560  TrajIntersection(tp1, tp2, aVtx.Pos);
3561  float dw = aVtx.Pos[0] - tp1.Pos[0];
3562  if(std::abs(dw) > sepCut) continue;
3563  float dt = aVtx.Pos[1] - tp1.Pos[1];
3564  if(std::abs(dt) > sepCut) continue;
3565  dw = aVtx.Pos[0] - tp2.Pos[0];
3566  if(std::abs(dw) > sepCut) continue;
3567  dt = aVtx.Pos[1] - tp2.Pos[1];
3568  if(std::abs(dt) > sepCut) continue;
3569  // ensure that the vertex is not closer to the other end if the tj is short
3570  // but not too short
3571  if(tj1Short && len1 > 4) {
3572  TrajPoint otp1 = slc.tjs[it1].Pts[slc.tjs[it1].EndPt[1-end1]];
3573  if(PosSep2(otp1.Pos, aVtx.Pos) < PosSep2(tp1.Pos, aVtx.Pos)) continue;
3574  }
3575  if(tj2Short && len2 > 4) {
3576  TrajPoint otp2 = slc.tjs[it2].Pts[slc.tjs[it2].EndPt[1-end2]];
3577  if(PosSep2(otp2.Pos, aVtx.Pos) < PosSep2(tp2.Pos, aVtx.Pos)) continue;
3578  }
3579  // we expect the vertex to be between tp1 and tp2
3580  if(aVtx.Pos[0] < tp1.Pos[0] && aVtx.Pos[0] < tp2.Pos[0]) {
3581  aVtx.Pos[0] = std::min(tp1.Pos[0], tp2.Pos[0]);
3582  aVtx.Stat[kFixed] = true;
3583  }
3584  if(aVtx.Pos[0] > tp1.Pos[0] && aVtx.Pos[0] > tp2.Pos[0]) {
3585  aVtx.Pos[0] = std::max(tp1.Pos[0], tp2.Pos[0]);
3586  aVtx.Stat[kFixed] = true;
3587  }
3588  } // Tps not so close
3589  // We got this far. Try a vertex fit to ensure that the errors are reasonable
3590  slc.tjs[it1].VtxID[end1] = aVtx.ID;
3591  slc.tjs[it2].VtxID[end2] = aVtx.ID;
3592  if(!aVtx.Stat[kFixed] && !FitVertex(slc, aVtx, prt)) {
3593  // back out
3594  slc.tjs[it1].VtxID[end1] = 0;
3595  slc.tjs[it2].VtxID[end2] = 0;
3596  if(prt) mf::LogVerbatim("TC")<<" Vertex fit failed ";
3597  continue;
3598  }
3599  aVtx.NTraj = 2;
3600  aVtx.Pass = slc.tjs[it1].Pass;
3601  aVtx.Topo = end1 + end2;
3602  tj1.AlgMod[kMerge] = true;
3603  tj2.AlgMod[kMerge] = true;
3604  if(!StoreVertex(slc, aVtx)) continue;
3605  SetVx2Score(slc);
3606  if(prt) {
3607  auto& newVx = slc.vtxs[slc.vtxs.size() - 1];
3608  mf::LogVerbatim("TC")<<" New 2V"<<newVx.ID<<" at "<<(int)newVx.Pos[0]<<":"<<(int)(newVx.Pos[1]/tcc.unitsPerTick)<<" Score "<<newVx.Score;
3609  }
3610  // check the score and kill it if it is below the cut
3611  // BB Oct 1, 2019. Don't kill the vertex in this function since it is
3612  // called before short trajectories are reconstructed
3613  auto& newVx2 = slc.vtxs[slc.vtxs.size() - 1];
3614  if(newVx2.Score < tcc.vtx2DCuts[7] && CompatibleMerge(slc, tj1, tj2, prt)) {
3615  if(prt) {
3616  mf::LogVerbatim myprt("TC");
3617  myprt<<" Bad vertex: Bad score? "<<(newVx2.Score < tcc.vtx2DCuts[7]);
3618  myprt<<" cut "<<tcc.vtx2DCuts[7];
3619  myprt<<" CompatibleMerge? "<<CompatibleMerge(slc, tj1, tj2, prt);
3620  }
3621  slc.tjs[it1].VtxID[end1] = 0;
3622  slc.tjs[it2].VtxID[end2] = 0;
3623  MakeVertexObsolete("EM", slc, newVx2, true);
3624  bool didMerge = false;
3625  if(end1 == 1) {
3626  didMerge = MergeAndStore(slc, it1, it2, tcc.dbgMrg);
3627  } else {
3628  didMerge = MergeAndStore(slc, it2, it1, tcc.dbgMrg);
3629  }
3630  if(didMerge) {
3631  // Set the end merge flag for the killed trajectories to aid tracing merges
3632  tj1.AlgMod[kMerge] = true;
3633  tj1.AlgMod[kMerge] = true;
3634  iterate = true;
3635  } // Merge and store successfull
3636  else {
3637  if(prt) mf::LogVerbatim("TC")<<" MergeAndStore failed ";
3638  }
3639  } // OK score
3640  } // create a vertex
3641  if(tj1.AlgMod[kKilled]) break;
3642  } // end1
3643  } // it1
3644  } // iterate
3645 
3646  } // EndMerge
3647 
3648  //////////////////////////////////////////
3649  void MaskTrajEndPoints(TCSlice& slc, Trajectory& tj, unsigned short nPts)
3650  {
3651 
3652  // Masks off (sets all hits not-Used) nPts trajectory points at the leading edge of the
3653  // trajectory, presumably because the fit including this points is poor. The position, direction
3654  // and Delta of the last nPts points is updated as well
3655 
3656  if(tj.EndFlag[1][kAtKink]) return;
3657  if(tj.Pts.size() < 3) {
3658  mf::LogError("TC")<<"MaskTrajEndPoints: Trajectory ID "<<tj.ID<<" too short to mask hits ";
3659  return;
3660  }
3661  if(nPts > tj.Pts.size() - 2) {
3662  mf::LogError("TC")<<"MaskTrajEndPoints: Trying to mask too many points "<<nPts<<" Pts.size "<<tj.Pts.size();
3663  return;
3664  }
3665 
3666  // find the last good point (with charge)
3667  unsigned short lastGoodPt = USHRT_MAX ;
3668 
3669  if(tcc.dbgStp) {
3670  mf::LogVerbatim("TC")<<"MTEP: lastGoodPt "<<lastGoodPt<<" Pts size "<<tj.Pts.size()<<" tj.IsGood "<<tj.IsGood;
3671  }
3672  if(lastGoodPt == USHRT_MAX) return;
3673  tj.EndPt[1] = lastGoodPt;
3674 
3675  //for(unsigned short ii = 0; ii < nPts; ++ii) {
3676  for(unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
3677  unsigned short ipt = tj.Pts.size() - 1 - ii;
3678  if (ipt==lastGoodPt) break;
3679  UnsetUsedHits(slc, tj.Pts[ipt]);
3680  // Reset the position and direction of the masked off points
3681  tj.Pts[ipt].Dir = tj.Pts[lastGoodPt].Dir;
3682  if(tj.Pts[lastGoodPt].AngleCode == 2) {
3683  // Very large angle: Move by path length
3684  float path = TrajPointSeparation(tj.Pts[lastGoodPt], tj.Pts[ipt]);
3685  tj.Pts[ipt].Pos[0] = tj.Pts[lastGoodPt].Pos[0] + path * tj.Pts[ipt].Dir[0];
3686  tj.Pts[ipt].Pos[1] = tj.Pts[lastGoodPt].Pos[1] + path * tj.Pts[ipt].Dir[1];
3687  } else {
3688  // Not large angle: Move by wire
3689  float dw = tj.Pts[ipt].Pos[0] - tj.Pts[lastGoodPt].Pos[0];
3690  // Correct the projected time to the wire
3691  float newpos = tj.Pts[lastGoodPt].Pos[1] + dw * tj.Pts[ipt].Dir[1] / tj.Pts[ipt].Dir[0];
3692  if(tcc.dbgStp) mf::LogVerbatim("TC")<<"MTEP: ipt "<<ipt<<" Pos[0] "<<tj.Pts[ipt].Pos[0]<<". Move Pos[1] from "<<tj.Pts[ipt].Pos[1]<<" to "<<newpos;
3693  tj.Pts[ipt].Pos[1] = tj.Pts[lastGoodPt].Pos[1] + dw * tj.Pts[ipt].Dir[1] / tj.Pts[ipt].Dir[0];
3694  }
3695  tj.Pts[ipt].Delta = PointTrajDOCA(slc, tj.Pts[ipt].HitPos[0], tj.Pts[ipt].HitPos[1], tj.Pts[ipt]);
3696  if(tcc.dbgStp) mf::LogVerbatim("TC")<<" masked ipt "<<ipt<<" Pos "<<PrintPos(slc, tj.Pts[ipt])<<" Chg "<<tj.Pts[ipt].Chg;
3697  } // ii
3698  SetEndPoints(tj);
3699 
3700  } // MaskTrajEndPoints
3701 
3702  ////////////////////////////////////////////////
3703  void ChkStop(TCSlice& slc, Trajectory& tj)
3704  {
3705  // Sets the EndFlag[kBragg] bits on the trajectory by identifying the Bragg peak
3706  // at each end. This function checks both ends, finding the point with the highest charge nearest the
3707  // end and considering the first (when end = 0) 4 points or last 4 points (when end = 1). The next
3708  // 5 - 10 points (fChkStop[0]) are fitted to a line, Q(x - x0) = Qo + (x - x0) * slope where x0 is the
3709  // wire position of the highest charge point. A large negative slope indicates that there is a Bragg
3710  // peak at the end.
3711 
3712  tj.EndFlag[0][kBragg] = false;
3713  tj.EndFlag[1][kBragg] = false;
3714  if(!tcc.useAlg[kChkStop]) return;
3715  if(tcc.chkStopCuts[0] < 0) return;
3716 
3717  if(tj.Strategy[kStiffEl]) return;
3718 
3719  // ignore trajectories that are very large angle at both ends
3720  if(tj.Pts[tj.EndPt[0]].AngleCode == 2 || tj.Pts[tj.EndPt[1]].AngleCode == 2) return;
3721 
3722  unsigned short nPtsToCheck = tcc.chkStopCuts[1];
3723  if(tj.Pts.size() < 6) return;
3724 
3725  bool prt = (tcc.dbgStp || tcc.dbgAlg[kChkStop]);
3726 
3727  if(prt) {
3728  mf::LogVerbatim("TC")<<"ChkStop: T"<<tj.ID<<" requiring "<<nPtsToCheck<<" points with charge slope > "<<tcc.chkStopCuts[0]<<" Chg/WSEU";
3729  }
3730 
3731  // find the highest charge hit in the first 3 points at each end
3732  for(unsigned short end = 0; end < 2; ++end) {
3733  tj.EndFlag[end][kBragg] = false;
3734  // require that the end point is reasonably clean
3735  auto& endTP = tj.Pts[tj.EndPt[end]];
3736  if(endTP.Hits.size() > 2) continue;
3737  if(endTP.Environment[kEnvUnusedHits]) continue;
3738  short dir = 1 - 2 * end;
3739  // find the point with the highest charge considering the first 3 points
3740  float big = 0;
3741  unsigned short hiPt = 0;
3742  float wire0 = 0;
3743  for(unsigned short ii = 0; ii < 5; ++ii) {
3744  short ipt = tj.EndPt[end] + ii * dir;
3745  if(ipt < tj.EndPt[0] || ipt > tj.EndPt[1]) break;
3746  TrajPoint& tp = tj.Pts[ipt];
3747  if(tp.Chg > big) {
3748  big = tp.Chg;
3749  wire0 = tp.Pos[0];
3750  hiPt = ipt;
3751  }
3752  } // ii
3753  // ensure that the high point is closer to the end that is being
3754  // considered than to the other end
3755  short dpt0 = hiPt - tj.EndPt[0];
3756  short dpt1 = tj.EndPt[1] - hiPt;
3757  if(end == 0 && dpt1 <= dpt0) continue;
3758  if(end == 1 && dpt0 <= dpt1) continue;
3759  if(prt) mf::LogVerbatim("TC")<<" end "<<end<<" wire0 "<<wire0<<" Chg "<<big<<" hiPt "<<hiPt;
3760  float prevChg = big;
3761  // prepare to do the fit
3762  Point2_t inPt;
3763  Vector2_t outVec, outVecErr;
3764  float chgErr, chiDOF;
3765  // Initialize
3766  Fit2D(0, inPt, chgErr, outVec, outVecErr, chiDOF);
3767  unsigned short cnt = 0;
3768  for(unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
3769  short ipt = hiPt + ii * dir;
3770  if(ipt < tj.EndPt[0] || ipt > tj.EndPt[1]) break;
3771  TrajPoint& tp = tj.Pts[ipt];
3772  if(tp.Chg == 0) continue;
3773  // quit if the charge is much larger than the previous charge
3774  if(tp.Chg > 1.5 * prevChg) continue;
3775  prevChg = tp.Chg;
3776  // Accumulate and save points
3777  inPt[0] = std::abs(tp.Pos[0] - wire0);
3778  inPt[1] = tp.Chg;
3779  // Assume 20% point-to-point charge fluctuations
3780  chgErr = 0.2 * tp.Chg;
3781  if(!Fit2D(2, inPt, chgErr, outVec, outVecErr, chiDOF)) break;
3782  ++cnt;
3783  if(cnt == nPtsToCheck) break;
3784  } // ii
3785  if(cnt < nPtsToCheck) continue;
3786  // do the fit and get the results
3787  if(!Fit2D(-1, inPt, chgErr, outVec, outVecErr, chiDOF)) continue;
3788  // check for really bad chidof indicating a major failure
3789  if(chiDOF > 500) continue;
3790  // The charge slope is negative for a stopping track in the way that the fit was constructed.
3791  // Flip the sign so we can make a cut against tcc.chkStopCuts[0] which is positive.
3792  outVec[1] = -outVec[1];
3793  bool itStops = (outVec[1] > tcc.chkStopCuts[0] && chiDOF < tcc.chkStopCuts[2] && outVec[1] > 2.5 * outVecErr[1]);
3794  if(itStops) {
3795  tj.EndFlag[end][kBragg] = true;
3796  tj.AlgMod[kChkStop] = true;
3797  if(tj.PDGCode == 11) tj.PDGCode = 0;
3798  // Put the charge at the end into tp.AveChg
3799  tj.Pts[tj.EndPt[end]].AveChg = outVec[0];
3800  if(prt) mf::LogVerbatim("TC")<<" end "<<end<<" fit chidof "<<chiDOF<<" slope "<<outVec[1]<<" +/- "<<outVecErr[1]<<" stopping";
3801  } else {
3802  if(prt) mf::LogVerbatim("TC")<<" end "<<end<<" fit chidof "<<chiDOF<<" slope "<<outVec[1]<<" +/- "<<outVecErr[1]<<" Not stopping";
3803  }
3804  } // end
3805 
3806  } // ChkStop
3807 
3808  //////////////////////TY://////////////////////////
3809  bool ChkMichel(TCSlice& slc, Trajectory& tj, unsigned short& lastGoodPt){
3810 
3811  if(!tcc.useAlg[kMichel]) return false;
3812  if(tj.PDGCode == 11 || tj.PDGCode == 111) return false;
3813 
3814  bool prt = (tcc.dbgStp || tcc.dbgAlg[kMichel]);
3815 
3816  //find number of hits that are consistent with Michel electron
3817  unsigned short nmichelhits = 0;
3818  //find number of hits that are consistent with Bragg peak
3819  unsigned short nbragghits = 0;
3820  float lastChg = 0;
3821 
3822  bool isfirsthit = true;
3823  unsigned short braggpeak = 0;
3824 
3825  for(unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
3826  if (ii>tj.EndPt[1]) continue;
3827  unsigned short ipt = tj.EndPt[1] - ii;
3828  if (tj.Pts[ipt].Chg>0){
3829  if (isfirsthit){
3830  isfirsthit = false;
3831  if (tj.Pts[ipt].ChgPull<0){
3832  ++nmichelhits;
3833  }
3834  }
3835  else{
3836  if (tj.Pts[ipt].ChgPull<0&&nmichelhits&&!nbragghits){//still Michel
3837  ++nmichelhits;
3838  }
3839  else{
3840  if (!nbragghits){
3841  ++nbragghits; //Last Bragg peak hit
3842  lastChg = tj.Pts[ipt].Chg;
3843  braggpeak = ipt;
3844  }
3845  else if (tj.Pts[ipt].Chg<lastChg){ //still Bragg peak
3846  ++nbragghits;
3847  lastChg = tj.Pts[ipt].Chg;
3848  }
3849  else break;
3850  }
3851  }
3852  }
3853  }
3854  if(prt) mf::LogVerbatim("TC")<<"ChkMichel Michel hits: "<<nmichelhits<<" Bragg peak hits: "<<nbragghits;
3855  if (nmichelhits>0&&nbragghits>2){//find Michel topology
3856  lastGoodPt = braggpeak;
3857  tj.AlgMod[kMichel] = true;
3858  return true;
3859  }
3860  else{
3861  return false;
3862  }
3863  }
3864 
3865  //////////////////////////////////////////
3866  bool MakeJunkTraj(TCSlice& slc, std::vector<unsigned int> tHits)
3867  {
3868  if(!tcc.useAlg[kJunkTj]) return false;
3869  // Make a crummy trajectory using the provided hits
3870 
3871  if(tHits.size() < 2) return false;
3872 
3873  bool prt = false;
3874  if(tcc.dbgAlg[kJunkTj]) {
3875  for(unsigned short ii = 0; ii < tHits.size(); ++ii) {
3876  if(slc.slHits[tHits[ii]].allHitsIndex == debug.Hit) {
3877  prt = true;
3878  break;
3879  }
3880  } // ii
3881  if(prt) std::cout<<"MakeJunkTraj found debug hit\n";
3882  } // tcc.dbgAlg[kJunkTj]
3883 
3884  // Start the trajectory using the first and last hits to
3885  // define a starting direction. Use the last pass settings
3886  Trajectory work;
3887  unsigned short pass = tcc.minPts.size() - 1;
3888  if(!StartTraj(slc, work, tHits[0], tHits[tHits.size()-1], pass)) return false;
3889  // make a TP for every hit
3890  work.Pts.resize(tHits.size());
3891  // and put a hit into each one
3892  for(unsigned short ii = 0; ii < tHits.size(); ++ii) {
3893  auto& tp = work.Pts[ii];
3894  unsigned int iht = tHits[ii];
3895  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
3896  tp.CTP = EncodeCTP(hit.WireID());
3897  if(tp.CTP != work.CTP) return false;
3898  tp.Hits.push_back(iht);
3899  tp.UseHit[0] = true;
3900  // don't use DefineHitPos here because the angle isn't really known yet. Just
3901  // define enough information to do a fit
3902  tp.HitPos[0] = hit.WireID().Wire;
3903  tp.HitPos[1] = hit.PeakTime() * tcc.unitsPerTick;
3904  tp.HitPosErr2 = 100;
3905  tp.Chg = hit.Integral();
3906  tp.Step = ii;
3907  tp.NTPsFit = tHits.size();
3908  // flag long-pulse hits
3909  if(LongPulseHit(hit)) tp.Environment[kEnvUnusedHits] = true;
3910  } // ii
3911  work.EndPt[0] = 0;
3912  work.EndPt[1] = tHits.size() - 1;
3913  // do an initial fit. The fit results are put in the last TP.
3914  FitTraj(slc, work);
3915  auto& lastTP = work.Pts.back();
3916  // Prepare to sort along the general direction. First find the
3917  // along and transverse position (Delta) of each TP and calculate DeltaRMS
3918  double sum = 0.;
3919  double sum2 = 0.;
3920  for(auto& tp : work.Pts) {
3921  Point2_t at;
3922  FindAlongTrans(lastTP.Pos, lastTP.Dir, tp.HitPos, at);
3923  sum += at[1];
3924  sum2 += at[1] * at[1];
3925  // store the along distance in AveChg for now
3926  tp.AveChg = at[0];
3927  tp.Delta = at[1];
3928  if(tp.Step != lastTP.Step) {
3929  tp.FitChi = lastTP.FitChi;
3930  tp.Dir = lastTP.Dir;
3931  tp.Ang = lastTP.Ang;
3932  tp.Pos[0] = lastTP.Pos[0] + at[0] * lastTP.Dir[0];
3933  tp.Pos[1] = lastTP.Pos[1] + at[0] * lastTP.Dir[1];
3934  }
3935  } // tp
3936  double npts = tHits.size();
3937  sum /= npts;
3938  double arg = sum2 - npts * sum * sum;
3939  if(arg <= 0) return false;
3940  float rms = sqrt(arg) / (npts - 1);
3941  // apply a loose Delta cut
3942  float transCut = sum + 3 * rms;
3943  std::vector<SortEntry> sortVec;
3944  SortEntry se;
3945  work.TotChg = 0;
3946  work.NeedsUpdate = false;
3947  for(auto& tp : work.Pts) {
3948  if(tp.Delta > transCut && !tp.Environment[kEnvUnusedHits]) {
3949  work.NeedsUpdate = true;
3950  continue;
3951  }
3952  se.index = tp.Step;
3953  se.val = tp.AveChg;
3954  sortVec.push_back(se);
3955  tp.DeltaRMS = rms;
3956  work.TotChg += tp.Chg;
3957  } // tp
3958  if(sortVec.size() < 3) return false;
3959  std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
3960  std::vector<TrajPoint> ntps(sortVec.size());
3961  for(unsigned short ipt = 0; ipt < sortVec.size(); ++ipt) ntps[ipt] = work.Pts[sortVec[ipt].index];
3962  work.Pts = ntps;
3963  sum = work.TotChg / (double)ntps.size();
3964  if(work.NeedsUpdate) {
3965  work.EndPt[1] = work.Pts.size() - 1;
3966  UpdateTraj(slc, work);
3967  } // needs update
3968  work.AlgMod[kJunkTj] = true;
3969  work.IsGood = true;
3970  if(prt) {
3971  PrintTrajectory("MJT", slc, work, USHRT_MAX);
3972  }
3973  // Store it
3974  return StoreTraj(slc, work);
3975  } // MakeJunkTraj
3976 
3977 } // namespace tca
std::bitset< 16 > UseHit
Definition: DataStructs.h:175
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
void Forecast(TCSlice &slc, const Trajectory &tj)
Definition: StepUtils.cxx:434
Vector2_t Dir
Definition: DataStructs.h:158
float AveChg
Calculated using ALL hits.
Definition: DataStructs.h:199
Point2_t Pos
Definition: DataStructs.h:157
short MCSMom(const TCSlice &slc, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3466
Point2_t PosErr
Definition: DataStructs.h:76
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
std::vector< Trajectory > tjs
vector of all trajectories in each plane
Definition: DataStructs.h:672
void StepAway(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:28
bool MaskedHitsOK(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2422
void SetEndPoints(Trajectory &tj)
Definition: Utils.cxx:3411
void UpdateTraj(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:667
void FitPar(const TCSlice &slc, const Trajectory &tj, unsigned short originPt, unsigned short npts, short fitDir, ParFit &pFit, unsigned short usePar)
Definition: Utils.cxx:1217
bool MakeVertexObsolete(std::string fcnLabel, TCSlice &slc, VtxStore &vx2, bool forceKill)
Definition: TCVertex.cxx:2740
std::vector< float > kinkCuts
kink finder algorithm
Definition: DataStructs.h:561
bool IsGhost(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2863
float ParSlpErr
Definition: DataStructs.h:186
struct of temporary 2D vertices (end points)
Definition: DataStructs.h:74
std::vector< unsigned int > PutTrajHitsInVector(const Trajectory &tj, HitStatus_t hitRequest)
Definition: Utils.cxx:2752
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:155
double rms(sqlite3 *db, std::string const &table_name, std::string const &column_name)
Definition: statistics.cc:40
std::vector< float > maxPos0
Definition: DataStructs.h:574
unsigned short NTPsFit
Definition: DataStructs.h:171
std::vector< unsigned short > minPtsFit
Reconstruct in several passes.
Definition: DataStructs.h:567
bool SignalAtTp(TrajPoint &tp)
Definition: Utils.cxx:2002
bool FitVertex(TCSlice &slc, VtxStore &vx, bool prt)
Definition: TCVertex.cxx:1968
std::vector< float > qualityCuts
Min points/wire, min consecutive pts after a gap.
Definition: DataStructs.h:565
void SetPDGCode(TCSlice &slc, unsigned short itj)
Definition: Utils.cxx:4348
void AddHits(TCSlice &slc, Trajectory &tj, unsigned short ipt, bool &sigOK)
Definition: StepUtils.cxx:1037
TCConfig tcc
Definition: DataStructs.cxx:8
unsigned short Step
Definition: DataStructs.h:172
vertex position fixed manually - no fitting done
Definition: DataStructs.h:95
void FindAlongTrans(Point3_t pos1, Vector3_t dir1, Point3_t pos2, Point2_t &alongTrans)
Definition: PFPUtils.cxx:3096
void PrintTrajectory(std::string someText, const TCSlice &slc, const Trajectory &tj, unsigned short tPoint)
Definition: Utils.cxx:6193
void PrintTP(std::string someText, const TCSlice &slc, unsigned short ipt, short dir, unsigned short pass, const TrajPoint &tp)
Definition: Utils.cxx:6294
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > wireHitRange
Definition: DataStructs.h:677
The data type to uniquely identify a Plane.
Definition: geo_types.h:472
void UnsetUsedHits(TCSlice &slc, TrajPoint &tp)
Definition: Utils.cxx:1073
std::vector< unsigned int > Hits
Definition: DataStructs.h:174
step from US -> DS (true) or DS -> US (false)
Definition: DataStructs.h:533
std::string PrintEndFlag(const PFPStruct &pfp, unsigned short end)
Definition: Utils.cxx:6461
bool StopShort(TCSlice &slc, Trajectory &tj, bool prt)
Definition: StepUtils.cxx:277
void SetStrategy(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:338
bool StoreTraj(TCSlice &slc, Trajectory &tj)
Definition: Utils.cxx:1087
float TotChg
Total including an estimate for dead wires.
Definition: DataStructs.h:200
short MCSMom
Normalized RMS using ALL hits. Assume it is 50% to start.
Definition: DataStructs.h:202
void FillGaps(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2031
float HitTimeErr(const TCSlice &slc, unsigned int iht)
Definition: StepUtils.cxx:1542
unsigned short Pass
Definition: DataStructs.h:78
unsigned int index
Definition: TCVertex.cxx:28
void ChkBegin(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2672
float maxWireSkipWithSignal
max number of wires to skip with a signal on them
Definition: DataStructs.h:584
std::string PrintPos(const TCSlice &slc, const TrajPoint &tp)
Definition: Utils.cxx:6524
void FindUseHits(TCSlice &slc, Trajectory &tj, unsigned short ipt, float maxDelta, bool useChg)
Definition: StepUtils.cxx:1755
void ChkChgAsymmetry(TCSlice &slc, Trajectory &tj, bool prt)
Definition: Utils.cxx:1739
float ExpectedHitsRMS(TCSlice &slc, const TrajPoint &tp)
Definition: Utils.cxx:1945
float TrajPointSeparation(const TrajPoint &tp1, const TrajPoint &tp2)
Definition: Utils.cxx:2678
void SetAngleCode(TrajPoint &tp)
Definition: Utils.cxx:771
bool MakeJunkTraj(TCSlice &slc, std::vector< unsigned int > tHits)
Definition: StepUtils.cxx:3866
float PointTrajDOCA(const TCSlice &slc, unsigned int iht, TrajPoint const &tp)
Definition: Utils.cxx:2570
double DeltaAngle(const Vector3_t v1, const Vector3_t v2)
Definition: PFPUtils.cxx:2539
bool dbgSlc
debug only in the user-defined slice? default is all slices
Definition: DataStructs.h:592
bool StartTraj(TCSlice &slc, Trajectory &tj, unsigned int fromhit, unsigned int tohit, unsigned short pass)
Definition: Utils.cxx:4999
string dir
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
bool MakeBareTrajPoint(const TCSlice &slc, unsigned int fromHit, unsigned int toHit, TrajPoint &tp)
Definition: Utils.cxx:4108
std::vector< unsigned int > lastWire
the last wire with a hit
Definition: DataStructs.h:657
bool LongPulseHit(const recob::Hit &hit)
Definition: Utils.cxx:4450
bool IsGood
set false if there is a failure or the Tj fails quality cuts
Definition: DataStructs.h:217
bool StoreVertex(TCSlice &slc, VtxStore &vx)
Definition: TCVertex.cxx:1932
bool doForecast
See TCMode_t above.
Definition: DataStructs.h:610
Q_EXPORT QTSManip setprecision(int p)
Definition: qtextstream.h:343
std::vector< float > showerTag
shower-like trajectory tagging + shower reconstruction
Definition: DataStructs.h:560
unsigned short Pass
the pass on which it was created
Definition: DataStructs.h:211
int TDCtick_t
Type representing a TDC tick.
Definition: RawTypes.h:25
TrajPoint CreateTPFromTj(TCSlice &slc, const Trajectory &tj)
Definition: StepUtils.cxx:3215
std::vector< unsigned short > maxAngleCode
max allowed angle code for each pass
Definition: DataStructs.h:569
float OverlapFraction(const TCSlice &slc, const Trajectory &tj1, const Trajectory &tj2)
Definition: Utils.cxx:711
float projectionErrFactor
Definition: DataStructs.h:585
float HitsRMSTime(const TCSlice &slc, const std::vector< unsigned int > &hitsInMultiplet, HitStatus_t hitRequest)
Definition: Utils.cxx:4235
unsigned int Hit
set to the hit index in evt.allHits if a Plane:Wire:Tick match is found
Definition: DebugStruct.h:26
bool CompatibleMerge(const TCSlice &slc, std::vector< int > &tjIDs, bool prt)
Definition: Utils.cxx:578
T abs(T value)
const std::vector< std::string > StrategyBitNames
float HitsTimeErr2(const TCSlice &slc, const std::vector< unsigned int > &hitVec)
Definition: StepUtils.cxx:1550
float HitsRMSTick(const TCSlice &slc, const std::vector< unsigned int > &hitsInMultiplet, HitStatus_t hitRequest)
Definition: Utils.cxx:4244
bool dbgStp
debug stepping using debug.Cryostat, debug.TPC, etc
Definition: DataStructs.h:593
std::array< float, 2 > Point2_t
Definition: DataStructs.h:45
std::vector< float > maxPos1
Definition: DataStructs.h:575
float unitsPerTick
scale factor from Tick to WSE equivalent units
Definition: DataStructs.h:573
use the slowing-down strategy
Definition: DataStructs.h:505
bool TrajIsClean(TCSlice &slc, Trajectory &tj, bool prt)
Definition: Utils.cxx:3440
std::vector< short > minMCSMom
Min MCSMom for each pass.
Definition: DataStructs.h:570
float DeadWireCount(const TCSlice &slc, const TrajPoint &tp1, const TrajPoint &tp2)
Definition: Utils.cxx:2139
void DefineHitPos(TCSlice &slc, TrajPoint &tp)
Definition: StepUtils.cxx:1670
DebugStuff debug
Definition: DebugStruct.cxx:4
TP is near a hit in the srcHit collection but no allHit hit exists (DUNE disambiguation error) ...
Definition: DataStructs.h:527
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:194
HLTPathStatus const pass
void CheckStiffEl(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:914
void GetHitMultiplet(const TCSlice &slc, unsigned int theHit, std::vector< unsigned int > &hitsInMultiplet, bool useLongPulseHits)
Definition: StepUtils.cxx:1413
std::vector< float > aveHitRMS
average RMS of an isolated hit
Definition: DataStructs.h:640
std::vector< TrajPoint > Pts
Trajectory points.
Definition: DataStructs.h:193
float KinkSignificance(TCSlice &slc, Trajectory &tj1, unsigned short end1, Trajectory &tj2, unsigned short end2, unsigned short nPtsFit, bool useChg, bool prt)
Definition: Utils.cxx:3054
std::vector< std::vector< bool > > goodWire
Definition: DataStructs.h:632
float ChgFracNearPos(const TCSlice &slc, const Point2_t &pos, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3234
std::vector< unsigned int > FindCloseHits(const TCSlice &slc, std::array< int, 2 > const &wireWindow, Point2_t const &timeWindow, const unsigned short plane, HitStatus_t hitRequest, bool usePeakTime, bool &hitsNear)
Definition: Utils.cxx:2841
std::vector< VtxStore > vtxs
2D vertices
Definition: DataStructs.h:678
std::array< unsigned short, 2 > EndPt
First and last point in the trajectory that has charge.
Definition: DataStructs.h:205
unsigned short PDGCode
shower-like or track-like {default is track-like}
Definition: DataStructs.h:210
void TagJunkTj(TCSlice &slc, Trajectory &tj, bool prt)
Definition: Utils.cxx:2780
short StartEnd
The starting end (-1 = don&#39;t know)
Definition: DataStructs.h:213
double PosSep2(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2571
void FitTraj(TCSlice &slc, Trajectory &tj)
Definition: Utils.cxx:806
std::string PrintHit(const TCHit &tch)
Definition: Utils.cxx:6514
Point2_t Pos
Definition: DataStructs.h:75
bool Fit2D(short mode, Point2_t inPt, float &inPtErr, Vector2_t &outVec, Vector2_t &outVecErr, float &chiDOF)
Definition: Utils.cxx:5127
string tmp
Definition: languages.py:63
std::vector< unsigned short > minPts
min number of Pts required to make a trajectory
Definition: DataStructs.h:568
static int max(int a, int b)
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:493
void FixBegin(TCSlice &slc, Trajectory &tj, unsigned short atPt)
Definition: StepUtils.cxx:2795
std::array< double, 2 > Vector2_t
Definition: DataStructs.h:46
Definition of data types for geometry description.
void ReverseTraj(TCSlice &slc, Trajectory &tj)
Definition: Utils.cxx:3292
unsigned short NumHitsInTP(const TrajPoint &tp, HitStatus_t hitRequest)
Definition: Utils.cxx:4326
void TrimEndPts(std::string fcnLabel, TCSlice &slc, Trajectory &tj, const std::vector< float > &fQualityCuts, bool prt)
Definition: Utils.cxx:1598
void err(const char *fmt,...)
Definition: message.cpp:226
std::vector< unsigned int > firstWire
the first wire with a hit
Definition: DataStructs.h:656
float ChiDOF
Definition: DataStructs.h:187
int ID
ID that is local to one slice.
Definition: DataStructs.h:207
std::vector< TCSlice > slices
Definition: DataStructs.cxx:12
std::array< unsigned short, 2 > VtxID
ID of 2D vertex.
Definition: DataStructs.h:204
Detector simulation of raw signals on wires.
void UpdateStiffEl(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:646
Q_EXPORT QTSManip setw(int w)
Definition: qtextstream.h:331
void ChkStop(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:3703
void UpdateTjChgProperties(std::string inFcnLabel, TCSlice &slc, Trajectory &tj, bool prt)
Definition: Utils.cxx:3671
std::bitset< 16 > modes
number of points to find AveChg
Definition: DataStructs.h:609
std::vector< TCHit > slHits
Definition: DataStructs.h:671
std::vector< float > chargeCuts
Definition: DataStructs.h:564
bool StopIfBadFits(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2527
bool MergeAndStore(TCSlice &slc, unsigned int itj1, unsigned int itj2, bool doPrt)
Definition: Utils.cxx:4662
void MaskBadTPs(TCSlice &slc, Trajectory &tj, float const &maxChi)
Definition: StepUtils.cxx:2377
std::bitset< 16 > Stat
Vertex status bits using kVtxBit_t.
Definition: DataStructs.h:90
int ID
set to 0 if killed
Definition: DataStructs.h:85
std::vector< TrajPoint > seeds
Definition: DataStructs.cxx:13
float ParSlp
Definition: DataStructs.h:185
unsigned short AngleCode
Definition: DataStructs.h:173
float maxWireSkipNoSignal
max number of wires to skip w/o a signal on them
Definition: DataStructs.h:583
void TrajIntersection(TrajPoint const &tp1, TrajPoint const &tp2, Point2_t &pos)
Definition: Utils.cxx:2602
void CheckTraj(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:927
Declaration of signal hit object.
double DotProd(const Vector3_t &v1, const Vector3_t &v2)
Definition: PFPUtils.h:128
unsigned int CTP_t
Definition: DataStructs.h:49
std::vector< TjForecast > tjfs
Definition: DataStructs.cxx:9
void AddLAHits(TCSlice &slc, Trajectory &tj, unsigned short ipt, bool &sigOK)
Definition: StepUtils.cxx:1214
std::bitset< 128 > useAlg
Max hit separation for making junk trajectories. < 0 to turn off.
Definition: DataStructs.h:588
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
void CheckHiMultUnusedHits(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2165
float ParErr
Definition: DataStructs.h:184
float VLAStepSize
Definition: DataStructs.h:586
short StepDir
-1 = going US (-> small wire#), 1 = going DS (-> large wire#)
Definition: DataStructs.h:212
float MaxHitDelta(TCSlice &slc, Trajectory &tj)
Definition: Utils.cxx:3274
float TrajLength(const Trajectory &tj)
Definition: Utils.cxx:2644
std::bitset< 128 > AlgMod
Bit set if algorithm AlgBit_t modifed the trajectory.
Definition: DataStructs.h:195
static unsigned int reverse(QString &chars, unsigned char *level, unsigned int a, unsigned int b)
Definition: qstring.cpp:11649
void LastEndMerge(TCSlice &slc, CTP_t inCTP)
Definition: StepUtils.cxx:3106
std::vector< short > muonTag
Definition: DataStructs.h:557
unsigned short NTraj
Definition: DataStructs.h:77
geo::PlaneID DecodeCTP(CTP_t CTP)
bool GottaKink(TCSlice &slc, Trajectory &tj, bool doTrim)
Definition: StepUtils.cxx:2554
double PosSep(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2564
void TrimHiChgEndPts(TCSlice &slc, Trajectory &tj, bool prt)
Definition: Utils.cxx:1555
std::bitset< 128 > dbgAlg
Allow user to turn on debug printing in algorithms (that print...)
Definition: DataStructs.h:589
std::bitset< 8 > Environment
Definition: DataStructs.h:176
std::vector< recob::Hit > const * allHits
Definition: DataStructs.h:624
CTP_t EncodeCTP(unsigned int cryo, unsigned int tpc, unsigned int plane)
Definition: DataStructs.h:54
std::vector< unsigned int > nWires
Definition: DataStructs.h:655
use the stiff electron strategy
Definition: DataStructs.h:503
std::array< std::bitset< 8 >, 2 > EndFlag
Definition: DataStructs.h:214
std::vector< float > chkStopCuts
Bragg peak finder configuration.
Definition: DataStructs.h:559
void EndMerge(TCSlice &slc, CTP_t inCTP, bool lastPass)
Definition: StepUtils.cxx:3251
void SetVx2Score(TCSlice &slc)
Definition: TCVertex.cxx:2278
std::vector< float > vtx2DCuts
Max position pull, max Position error rms.
Definition: DataStructs.h:552
std::bitset< 8 > Strategy
Definition: DataStructs.h:215
bool NearbySrcHit(geo::PlaneID plnID, unsigned int wire, float loTick, float hiTick)
Definition: Utils.cxx:2069
bool SignalBetween(const TCSlice &slc, const TrajPoint &tp1, const TrajPoint &tp2, const float &MinWireSignalFraction)
Definition: Utils.cxx:1805
float multHitSep
preferentially "merge" hits with < this separation
Definition: DataStructs.h:576
void MoveTPToWire(TrajPoint &tp, float wire)
Definition: Utils.cxx:2829
unsigned short NumPtsWithCharge(const TCSlice &slc, const Trajectory &tj, bool includeDeadWires)
Definition: Utils.cxx:2114
TCEvent evt
Definition: DataStructs.cxx:7
void MaskTrajEndPoints(TCSlice &slc, Trajectory &tj, unsigned short nPts)
Definition: StepUtils.cxx:3649
void UpdateDeltaRMS(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2347
void ChkStopEndPts(TCSlice &slc, Trajectory &tj, bool prt)
Definition: StepUtils.cxx:1560
bool TrajTrajDOCA(const TCSlice &slc, const Trajectory &tj1, const Trajectory &tj2, unsigned short &ipt1, unsigned short &ipt2, float &minSep)
Definition: Utils.cxx:2458
void ReversePropagate(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:1314
bool NeedsUpdate
Set true when the Tj needs to be updated.
Definition: DataStructs.h:216
void CheckHiMultEndHits(TCSlice &slc, Trajectory &tj)
Definition: StepUtils.cxx:2307
Point2_t HitPos
Definition: DataStructs.h:156
float TPHitsRMSTick(const TCSlice &slc, const TrajPoint &tp, HitStatus_t hitRequest)
Definition: Utils.cxx:4207
CTP_t CTP
set to an invalid CTP
Definition: DebugStruct.h:23
Definition: se.py:1
double HitPosErr2
Definition: DataStructs.h:159
unsigned short AngleRange(TrajPoint const &tp)
Definition: Utils.cxx:764
use the stiff muon strategy
Definition: DataStructs.h:504
bool valsDecreasing(const SortEntry &c1, const SortEntry &c2)
Definition: Utils.cxx:38
bool HasDuplicateHits(const TCSlice &slc, Trajectory const &tj, bool prt)
Definition: Utils.cxx:2810
bool ChkMichel(TCSlice &slc, Trajectory &tj, unsigned short &lastGoodPt)
Definition: StepUtils.cxx:3809