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