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