TCVertex.cxx
Go to the documentation of this file.
2 
10 
11 #include <algorithm>
12 #include <array>
13 #include <bitset>
14 #include <cmath>
15 #include <iomanip>
16 #include <iostream>
17 #include <limits.h>
18 #include <stdlib.h>
19 #include <string>
20 #include <vector>
21 
22 #include "TMatrixD.h"
23 #include "TVectorD.h"
24 
25 namespace tca {
26 
27  struct SortEntry {
28  unsigned int index;
29  float val;
30  };
31 
32  bool
34  {
35  return (c1.val > c2.val);
36  }
37  bool
39  {
40  return (c1.val < c2.val);
41  }
42 
43  //////////////////////////////////////////
44  void
45  MakeJunkVertices(TCSlice& slc, const CTP_t& inCTP)
46  {
47  // Vertices between poorly reconstructed tjs (especially junk slc) and normal
48  // tjs can fail because the junk tj trajectory parameters are inaccurate. This function
49  // uses proximity and not pointing to make junk vertices
50  // Don't use this if standard vertex reconstruction is disabled
51  if (tcc.vtx2DCuts[0] <= 0) return;
52  if (!tcc.useAlg[kJunkVx]) return;
53  if (slc.tjs.size() < 2) return;
54 
55  // Look for tjs that are within maxSep of the end of a Tj
56  constexpr float maxSep = 4;
57 
58  geo::PlaneID planeID = DecodeCTP(inCTP);
59  bool prt = (tcc.dbgVxJunk && tcc.dbgSlc);
60  if (prt) {
61  mf::LogVerbatim("TC") << "MakeJunkVertices: prt set for plane " << planeID.Plane
62  << " maxSep btw tjs " << maxSep;
63  }
64 
65  // make a template vertex
66  VtxStore junkVx;
67  junkVx.CTP = inCTP;
68  junkVx.Topo = 9;
69  junkVx.Stat[kJunkVx] = true;
70  junkVx.Stat[kFixed] = true;
71  // set an invalid ID
72  junkVx.ID = USHRT_MAX;
73  // put in generous errors
74  junkVx.PosErr = {{2.0, 2.0}};
75  // define a minimal score so it won't get clobbered
76  junkVx.Score = tcc.vtx2DCuts[7] + 0.1;
77 
78  // look at both ends of long tjs
79  for (unsigned short it1 = 0; it1 < slc.tjs.size() - 1; ++it1) {
80  auto& tj1 = slc.tjs[it1];
81  if (tj1.AlgMod[kKilled] || tj1.AlgMod[kHaloTj]) continue;
82  if (tj1.SSID > 0 || tj1.AlgMod[kShowerLike]) continue;
83  if (tj1.CTP != inCTP) continue;
84  if (tj1.AlgMod[kJunkTj]) continue;
85  if (TrajLength(tj1) < 10) continue;
86  if (tj1.MCSMom < 100) continue;
87  for (unsigned short end1 = 0; end1 < 2; ++end1) {
88  // existing vertex?
89  if (tj1.VtxID[end1] > 0) continue;
90  auto& tp1 = tj1.Pts[tj1.EndPt[end1]];
91  // get a list of tjs in this vicinity
92  auto tjlist = FindCloseTjs(slc, tp1, tp1, maxSep);
93  if (tjlist.empty()) continue;
94  // set to an invalid ID
95  junkVx.ID = USHRT_MAX;
96  for (auto tj2id : tjlist) {
97  auto& tj2 = slc.tjs[tj2id - 1];
98  if (tj2.CTP != inCTP) continue;
99  if (tj2id == tj1.ID) continue;
100  if (tj2.SSID > 0 || tj2.AlgMod[kShowerLike]) continue;
101  float close = maxSep;
102  unsigned short closeEnd = USHRT_MAX;
103  for (unsigned short end2 = 0; end2 < 2; ++end2) {
104  auto& tp2 = tj2.Pts[tj2.EndPt[end2]];
105  float sep = PosSep(tp1.Pos, tp2.Pos);
106  if (sep < close) {
107  close = sep;
108  closeEnd = end2;
109  } // sep
110  } // end2
111  if (closeEnd > 1) continue;
112  auto& tp2 = tj2.Pts[tj2.EndPt[closeEnd]];
113  bool signalBetween = SignalBetween(slc, tp1, tp2, 0.8);
114  if (!signalBetween) continue;
115  if (junkVx.ID == USHRT_MAX) {
116  // define the new vertex
117  junkVx.ID = slc.vtxs.size() + 1;
118  junkVx.Pos = tp1.Pos;
119  } // new vertex
120  tj2.VtxID[closeEnd] = junkVx.ID;
121  tj1.VtxID[end1] = junkVx.ID;
122  } // tjid
123  if (junkVx.ID == USHRT_MAX) continue;
124  if (!StoreVertex(slc, junkVx)) {
125  mf::LogVerbatim("TC") << "MJV: StoreVertex failed";
126  for (auto& tj : slc.tjs) {
127  if (tj.AlgMod[kKilled]) continue;
128  if (tj.VtxID[0] == junkVx.ID) tj.VtxID[0] = 0;
129  if (tj.VtxID[1] == junkVx.ID) tj.VtxID[1] = 0;
130  } // tj
131  continue;
132  } // StoreVertex failed
133  if (prt) {
134  mf::LogVerbatim("TC") << " New junk 2V" << junkVx.ID << " at " << std::fixed
135  << std::setprecision(1) << junkVx.Pos[0] << ":"
136  << junkVx.Pos[1] / tcc.unitsPerTick;
137  } // prt
138  junkVx.ID = USHRT_MAX;
139  } // end1
140  } // it1
141 
142  } // MakeJunkVertices
143 
144  //////////////////////////////////////////
145  void
147  TCSlice& slc,
148  const CTP_t& inCTP,
149  unsigned short pass)
150  {
151  // Find 2D vertices between pairs of tjs that have a same-end topology. Using an example
152  // where StepDir = 1 (end 0 is at small wire number) vertices will be found with Topo = 0
153  // with a vertex US of the ends (<) or Topo = 2 with a vertex DS of the ends (>). This is reversed
154  // if StepDir = -1. Vertices with Topo = 1 (/\) and (\/) are found in EndMerge.
155 
156  // tcc.vtx2DCuts fcl input usage
157  // 0 = maximum length of a short trajectory
158  // 1 = max vertex - trajectory separation for short trajectories
159  // 2 = max vertex - trajectory separation for long trajectories
160  // 3 = max position pull for adding TJs to a vertex
161  // 4 = max allowed vertex position error
162  // 5 = min MCSMom
163  // 6 = min Pts/Wire fraction
164  // 7 min Score
165  // 8 Min charge fraction near a merge point (not a vertex)
166  // 9 max MCSmom asymmetry for a merge
167  // 10 Require charge on wires between a vtx and the start of the tjs in induction planes? (1 = yes)
168 
169  if (tcc.vtx2DCuts[0] <= 0) return;
170  if (slc.tjs.size() < 2) return;
171 
172  bool firstPassCuts = (pass == 0);
173 
174  geo::PlaneID planeID = DecodeCTP(inCTP);
175 
176  // require charge between the vertex and the tj start points?
177  bool requireVtxTjChg = true;
178  if (tcc.vtx2DCuts[10] == 0 && int(planeID.Plane) < slc.nPlanes - 1) requireVtxTjChg = false;
179 
180  bool prt = (tcc.dbg2V && tcc.dbgSlc && debug.Plane == (int)planeID.Plane);
181  if (prt) {
182  mf::LogVerbatim("TC") << "prt set for CTP " << inCTP << " in Find2DVertices. firstPassCuts? "
183  << firstPassCuts << " requireVtxTjChg " << requireVtxTjChg;
184  PrintAllTraj(detProp, "F2DVi", slc, USHRT_MAX, slc.tjs.size());
185  }
186 
187  unsigned short maxShortTjLen = tcc.vtx2DCuts[0];
188  for (unsigned short it1 = 0; it1 < slc.tjs.size() - 1; ++it1) {
189  auto& tj1 = slc.tjs[it1];
190  if (tj1.AlgMod[kKilled] || tj1.AlgMod[kHaloTj]) continue;
191  if (tj1.SSID > 0 || tj1.AlgMod[kShowerLike]) continue;
192  if (tj1.CTP != inCTP) continue;
193  bool tj1Short = (TrajLength(tj1) < maxShortTjLen);
194  for (unsigned short end1 = 0; end1 < 2; ++end1) {
195  // vertex assignment exists?
196  if (tj1.VtxID[end1] > 0) continue;
197  // wrong end of a high energy electron?
198  if (tj1.PDGCode == 111 && end1 != tj1.StartEnd) continue;
199  // default condition is to use the end point to define the trajectory and direction
200  // at the end
201  short endPt1 = tj1.EndPt[end1];
202  float wire1 = tj1.Pts[endPt1].Pos[0];
203  // unless there are few points fitted, indicating that the trajectory fit
204  // may have been biased by the presence of another trajectory at the vertex or by
205  // other close unresolved tracks
206  if (tj1.Pts.size() > 6 && tj1.Pts[endPt1].NTPsFit < 4) {
207  if (end1 == 0 && endPt1 < int(tj1.Pts.size()) - 3) { endPt1 += 3; }
208  else if (end1 == 1 && endPt1 >= 3) {
209  endPt1 -= 3;
210  }
211  if (tj1.Pts[endPt1].Chg == 0) endPt1 = NearestPtWithChg(slc, tj1, endPt1);
212  } // few points fit at end1
213  TrajPoint tp1 = tj1.Pts[endPt1];
214  MoveTPToWire(tp1, wire1);
215  // re-purpose endPt1 to reference the end point. This will be used the find the point on
216  // tj1 that is closest to the vertex position
217  endPt1 = tj1.EndPt[end1];
218  short oendPt1 = tj1.EndPt[1 - end1];
219  // reference to the other end of tj1
220  auto& otp1 = tj1.Pts[oendPt1];
221  for (unsigned short it2 = it1 + 1; it2 < slc.tjs.size(); ++it2) {
222  auto& tj2 = slc.tjs[it2];
223  if (tj2.AlgMod[kKilled] || tj2.AlgMod[kHaloTj]) continue;
224  if (tj2.SSID > 0 || tj2.AlgMod[kShowerLike]) continue;
225  if (tj2.CTP != inCTP) continue;
226  if (tj1.VtxID[end1] > 0) continue;
227  if (tj1.MCSMom < tcc.vtx2DCuts[5] && tj2.MCSMom < tcc.vtx2DCuts[5]) continue;
228  bool tj2Short = (TrajLength(tj2) < maxShortTjLen);
229  // find the end that is closer to tp1
230  unsigned short end2 = 0;
231  if (PosSep2(tj2.Pts[tj2.EndPt[1]].Pos, tp1.Pos) <
232  PosSep2(tj2.Pts[tj2.EndPt[0]].Pos, tp1.Pos))
233  end2 = 1;
234  if (tj2.VtxID[end2] > 0) continue;
235  // wrong end of a high energy electron?
236  if (tj2.PDGCode == 111 && end2 != tj2.StartEnd) continue;
237  // check for a vertex between these tjs at the other ends
238  if (tj1.VtxID[1 - end1] > 0 && tj1.VtxID[1 - end1] == tj2.VtxID[1 - end2]) continue;
239  // see if the other ends are closer
240  unsigned short oendPt2 = tj2.EndPt[1 - end2];
241  auto& otp2 = tj2.Pts[oendPt2];
242  if (PosSep2(otp1.Pos, otp2.Pos) < PosSep2(tp1.Pos, tj2.Pts[tj2.EndPt[end2]].Pos))
243  continue;
244  short endPt2 = tj2.EndPt[end2];
245  float wire2 = tj2.Pts[endPt2].Pos[0];
246  if (tj2.Pts.size() > 6 && tj2.Pts[endPt2].NTPsFit < 4) {
247  if (end2 == 0 && endPt2 < int(tj2.Pts.size()) - 3) { endPt2 += 3; }
248  else if (end2 == 1 && endPt2 >= 3) {
249  endPt2 -= 3;
250  }
251  if (tj2.Pts[endPt2].Chg == 0) endPt2 = NearestPtWithChg(slc, tj2, endPt2);
252  } // few points fit at end1
253  TrajPoint tp2 = tj2.Pts[endPt2];
254  MoveTPToWire(tp2, wire2);
255  // re-purpose endPt2
256  endPt2 = tj2.EndPt[end2];
257  // Rough first cut on the separation between the end points of the
258  // two trajectories
259  float sepCut = 100;
260  if (std::abs(tp1.Pos[0] - tp2.Pos[0]) > sepCut) continue;
261  if (std::abs(tp1.Pos[1] - tp2.Pos[1]) > sepCut) continue;
262  float wint, tint;
263  TrajIntersection(tp1, tp2, wint, tint);
264  // make sure this is inside the TPC.
265  if (wint < 0 || wint > tcc.maxPos0[planeID.Plane] - 3) continue;
266  if (tint < 0 || tint > tcc.maxPos1[planeID.Plane]) continue;
267  // Next cut on separation between the TPs and the intersection point
268  if (tj1Short || tj2Short) { sepCut = tcc.vtx2DCuts[1]; }
269  else {
270  sepCut = tcc.vtx2DCuts[2];
271  }
272  // NewVtxCuts: require close separation on the first pass
273  if (firstPassCuts) sepCut = tcc.vtx2DCuts[1];
274  Point2_t vPos{{wint, tint}};
275  float vt1Sep = PosSep(vPos, tp1.Pos);
276  float vt2Sep = PosSep(vPos, tp2.Pos);
277  float dwc1 = DeadWireCount(slc, wint, tp1.Pos[0], tp1.CTP);
278  float dwc2 = DeadWireCount(slc, wint, tp2.Pos[0], tp1.CTP);
279  vt1Sep -= dwc1;
280  vt2Sep -= dwc2;
281  bool vtxOnDeadWire = (DeadWireCount(slc, wint, wint, tp1.CTP) == 1);
282  if (prt && vt1Sep < 200 && vt2Sep < 200) {
283  mf::LogVerbatim myprt("TC");
284  myprt << "F2DV candidate T" << tj1.ID << "_" << end1 << "-T" << tj2.ID << "_" << end2;
285  myprt << " vtx pos " << (int)wint << ":" << (int)(tint / tcc.unitsPerTick) << " tp1 "
286  << PrintPos(slc, tp1) << " tp2 " << PrintPos(slc, tp2);
287  myprt << " dwc1 " << dwc1 << " dwc2 " << dwc2 << " on dead wire? " << vtxOnDeadWire;
288  myprt << " vt1Sep " << vt1Sep << " vt2Sep " << vt2Sep << " sepCut " << sepCut;
289  }
290  if (vt1Sep > sepCut || vt2Sep > sepCut) continue;
291  // make sure that the other end isn't closer
292  if (PosSep(vPos, slc.tjs[it1].Pts[oendPt1].Pos) < vt1Sep) {
293  if (prt)
294  mf::LogVerbatim("TC") << " tj1 other end " << PrintPos(slc, tj1.Pts[oendPt1])
295  << " is closer to the vertex";
296  continue;
297  }
298  if (PosSep(vPos, slc.tjs[it2].Pts[oendPt2].Pos) < vt2Sep) {
299  if (prt)
300  mf::LogVerbatim("TC") << " tj2 other end " << PrintPos(slc, tj2.Pts[oendPt2])
301  << " is closer to the vertex";
302  continue;
303  }
304  // Ensure that the vertex position is close to the end of each Tj
305  unsigned short closePt1;
306  float doca1 = sepCut;
307  if (!TrajClosestApproach(tj1, wint, tint, closePt1, doca1)) continue;
308  // dpt1 (and dpt2) will be 0 if the vertex is at the end
309  short stepDir = -1;
310  if (tcc.modes[kStepDir]) stepDir = 1;
311  short dpt1 = stepDir * (closePt1 - endPt1);
312  if (prt)
313  mf::LogVerbatim("TC") << " endPt1 " << endPt1 << " closePt1 " << closePt1 << " dpt1 "
314  << dpt1 << " doca1 " << doca1;
315  if (dpt1 < -1) continue;
316  if (slc.tjs[it1].EndPt[1] > 4) {
317  if (dpt1 > 3) continue;
318  }
319  else {
320  // tighter cut for short trajectories
321  if (dpt1 > 2) continue;
322  }
323  unsigned short closePt2;
324  float doca2 = sepCut;
325  if (!TrajClosestApproach(tj2, wint, tint, closePt2, doca2)) continue;
326  short dpt2 = stepDir * (closePt2 - endPt2);
327  if (prt)
328  mf::LogVerbatim("TC") << " endPt2 " << endPt2 << " closePt2 " << closePt2 << " dpt2 "
329  << dpt2 << " doca2 " << doca2;
330  if (dpt2 < -1) continue;
331  if (slc.tjs[it2].EndPt[1] > 4) {
332  if (dpt2 > 3) continue;
333  }
334  else {
335  // tighter cut for short trajectories
336  if (dpt2 > 2) continue;
337  }
338  bool fixVxPos = false;
339  // fix the vertex position if there is a charge kink here
340  if (tj1.EndFlag[end1][kAtKink]) fixVxPos = true;
341  if (prt)
342  mf::LogVerbatim("TC") << " wint:tint " << (int)wint << ":"
343  << (int)(tint / tcc.unitsPerTick) << " fixVxPos? " << fixVxPos;
344  if (requireVtxTjChg) {
345  // ensure that there is a signal between these TPs and the vertex on most of the wires
346  bool signalBetween = true;
347  short dpt = abs(wint - tp1.Pos[0]);
348  if (dpt > 2 && !SignalBetween(slc, tp1, wint, tcc.vtx2DCuts[6])) {
349  if (prt) mf::LogVerbatim("TC") << " Fails SignalBetween for tp1 " << dpt;
350  signalBetween = false;
351  }
352  dpt = abs(wint - tp2.Pos[0]);
353  if (dpt > 2 && !SignalBetween(slc, tp2, wint, tcc.vtx2DCuts[6])) {
354  if (prt) mf::LogVerbatim("TC") << " Fails SignalBetween for tp2 " << dpt;
355  signalBetween = false;
356  }
357  // consider the case where the intersection point is wrong because the
358  // end TP angles are screwed up but the Tjs are close to each other near the end
359  if (!signalBetween) {
360  unsigned short ipt1, ipt2;
361  float maxSep = 3;
362  bool isClose = TrajTrajDOCA(slc, tj1, tj2, ipt1, ipt2, maxSep, false);
363  // require that they are close at the correct end
364  if (isClose) isClose = (abs(ipt1 - endPt1) < 4 && abs(ipt2 - endPt2) < 4);
365  if (isClose) {
366  if (prt)
367  mf::LogVerbatim("TC")
368  << " TrajTrajDOCA are close with minSep " << maxSep << " near "
369  << PrintPos(slc, tj1.Pts[ipt1].Pos) << " " << PrintPos(slc, tj2.Pts[ipt2].Pos);
370  // put the vertex at the TP that is closest to the intersection point
371  Point2_t vpos = {{wint, tint}};
372  if (PosSep2(tp1.Pos, vpos) < PosSep2(tp2.Pos, vpos)) {
373  wint = tp1.Pos[0];
374  tint = tp1.Pos[1];
375  }
376  else {
377  wint = tp2.Pos[0];
378  tint = tp2.Pos[1];
379  }
380  fixVxPos = true;
381  if (prt)
382  mf::LogVerbatim("TC")
383  << " new wint:tint " << (int)wint << ":" << (int)(tint / tcc.unitsPerTick);
384  }
385  else {
386  // closest approach > 3
387  continue;
388  }
389  } // no signal between
390  } // requireVtxTjChg
391  // make a new temporary vertex
392  VtxStore aVtx;
393  aVtx.Pos[0] = wint;
394  aVtx.Pos[1] = tint;
395  aVtx.NTraj = 0;
396  aVtx.Pass = tj1.Pass;
397  // Topo 0 has this topology (<) and Topo 2 has this (>)
398  aVtx.Topo = 2 * end1;
399  aVtx.ChiDOF = 0;
400  aVtx.CTP = inCTP;
401  aVtx.Stat[kOnDeadWire] = vtxOnDeadWire;
402  // fix the vertex position if we needed to move it significantly, or if it is on a dead wire
403  aVtx.Stat[kFixed] = fixVxPos;
404  aVtx.Stat[kVxIndPlnNoChg] = !requireVtxTjChg;
405  // try to fit it. We need to give it an ID to do that. Take the next
406  // available ID
407  unsigned short newVtxID = slc.vtxs.size() + 1;
408  aVtx.ID = newVtxID;
409  tj1.VtxID[end1] = newVtxID;
410  tj2.VtxID[end2] = newVtxID;
411  if (!FitVertex(slc, aVtx, prt)) {
412  tj1.VtxID[end1] = 0;
413  tj2.VtxID[end2] = 0;
414  continue;
415  }
416  // check proximity to nearby vertices
417  unsigned short mergeMeWithVx = IsCloseToVertex(slc, aVtx);
418  if (mergeMeWithVx > 0 && MergeWithVertex(slc, aVtx, mergeMeWithVx)) {
419  if (prt) mf::LogVerbatim("TC") << " Merged with close vertex " << mergeMeWithVx;
420  continue;
421  }
422  // Save it
423  if (!StoreVertex(slc, aVtx)) continue;
424  if (prt) {
425  mf::LogVerbatim myprt("TC");
426  myprt << " New vtx 2V" << aVtx.ID;
427  myprt << " Tjs " << tj1.ID << "_" << end1 << "-" << tj2.ID << "_" << end2;
428  myprt << " at " << std::fixed << std::setprecision(1) << aVtx.Pos[0] << ":"
429  << aVtx.Pos[1] / tcc.unitsPerTick;
430  }
431  AttachAnyTrajToVertex(slc, slc.vtxs.size() - 1, prt);
432  SetVx2Score(slc);
433  } // it2
434  } // end1
435  } // it1
436 
437  // only call these on the last pass
438  if (pass == USHRT_MAX) {
439  FindHammerVertices(slc, inCTP);
440  FindHammerVertices2(slc, inCTP);
441  }
442 
443  if (prt) PrintAllTraj(detProp, "F2DVo", slc, USHRT_MAX, USHRT_MAX);
444 
445  } // Find2DVertices
446 
447  //////////////////////////////////////////
448  bool
449  MergeWithVertex(TCSlice& slc, VtxStore& vx, unsigned short oVxID)
450  {
451  // Attempts to merge the trajectories attached to vx with an existing 2D vertex
452  // referenced by existingVxID. This function doesn't use the existing end0/end1 vertex association.
453  // It returns true if the merging was successful in which case the calling function should
454  // not store vx. The calling function needs to have set VtxID to vx.ID for tjs that are currently attached
455  // to vx. It assumed that vx hasn't yet been pushed onto slc.vtxs
456 
457  if (!tcc.useAlg[kVxMerge]) return false;
458 
459  bool prt = tcc.dbgVxMerge && tcc.dbgSlc;
460 
461  if (oVxID > slc.vtxs.size()) return false;
462  auto& oVx = slc.vtxs[oVxID - 1];
463  if (vx.CTP != oVx.CTP) return false;
464 
465  // get a list of tjs attached to both vertices
466  std::vector<int> tjlist = GetVtxTjIDs(slc, vx);
467  if (tjlist.empty()) return false;
468  std::vector<int> tmp = GetVtxTjIDs(slc, oVx);
469  if (tmp.empty()) return false;
470  for (auto tjid : tmp) {
471  if (std::find(tjlist.begin(), tjlist.end(), tjid) == tjlist.end()) tjlist.push_back(tjid);
472  } // tjid
473  if (tjlist.size() < 2) return false;
474  // handle the simple case
475  if (tjlist.size() == 2) {
476  // Unset the fixed bit
477  vx.Stat[kFixed] = false;
478  oVx.Stat[kFixed] = false;
479  // assign the vx tjs to oVx
480  for (auto tjid : tjlist) {
481  auto& tj = slc.tjs[tjid - 1];
482  for (unsigned short end = 0; end < 2; ++end) {
483  if (tj.VtxID[end] == vx.ID) tj.VtxID[end] = oVx.ID;
484  } // end
485  } // tjid
486  if (!FitVertex(slc, oVx, prt)) {
487  if (prt)
488  mf::LogVerbatim("TC") << "MWV: merge failed " << vx.ID << " and existing " << oVx.ID;
489  return false;
490  }
491  return true;
492  } // size = 2
493 
494  // sort by decreasing length
495  std::vector<SortEntry> sortVec(tjlist.size());
496  for (unsigned int indx = 0; indx < sortVec.size(); ++indx) {
497  sortVec[indx].index = indx;
498  auto& tj = slc.tjs[tjlist[indx] - 1];
499  sortVec[indx].val = tj.Pts.size();
500  } // indx
501  std::sort(sortVec.begin(), sortVec.end(), valDecreasing);
502  // re-order the list of Tjs
503  auto ttl = tjlist;
504  for (unsigned short ii = 0; ii < sortVec.size(); ++ii)
505  tjlist[ii] = ttl[sortVec[ii].index];
506  // Create a local vertex using the two longest slc, then add the shorter ones
507  // until the pull reaches the cut
508  VtxStore aVx;
509  aVx.CTP = vx.CTP;
510  std::vector<TrajPoint> tjpts(tjlist.size());
511  // determine which point on each Tj that will be used in the vertex fit and stash it in
512  // the traj point Step variable. This requires knowing the real position of the merged vertex
513  // which we estimate by averaging
514  std::array<float, 2> vpos;
515  vpos[0] = 0.5 * (vx.Pos[0] + oVx.Pos[0]);
516  vpos[1] = 0.5 * (vx.Pos[1] + oVx.Pos[1]);
517  for (unsigned short ii = 0; ii < tjpts.size(); ++ii) {
518  auto& tj = slc.tjs[tjlist[ii] - 1];
519  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
520  unsigned short end = CloseEnd(slc, tj, vpos);
521  // assume that we will use the end point of the tj
522  unsigned short endPt = tj.EndPt[end];
523  if (npwc > 6 && tj.Pts[endPt].NTPsFit < 4) {
524  if (end == 0) { endPt += 3; }
525  else {
526  endPt -= 3;
527  }
528  endPt = NearestPtWithChg(slc, tj, endPt);
529  } // few points fit at the end
530  if (endPt < tj.EndPt[0]) endPt = tj.EndPt[0];
531  if (endPt > tj.EndPt[1]) endPt = tj.EndPt[1];
532  // define tjpts
533  tjpts[ii].CTP = tj.CTP;
534  tjpts[ii].Pos = tj.Pts[endPt].Pos;
535  tjpts[ii].Dir = tj.Pts[endPt].Dir;
536  tjpts[ii].Ang = tj.Pts[endPt].Ang;
537  tjpts[ii].AngErr = tj.Pts[endPt].AngErr;
538  // stash the point in Step
539  tjpts[ii].Step = endPt;
540  // and the end in AngleCode
541  tjpts[ii].AngleCode = end;
542  // stash the ID in Hits
543  tjpts[ii].Hits.resize(1, tj.ID);
544  } // tjid
545  if (prt) {
546  mf::LogVerbatim myprt("TC");
547  myprt << "MWV: " << oVxID;
548  myprt << " Fit TPs";
549  for (unsigned short ii = 0; ii < tjpts.size(); ++ii) {
550  auto& tjpt = tjpts[ii];
551  myprt << " " << tjlist[ii] << "_" << tjpt.Step << "_" << PrintPos(slc, tjpt.Pos);
552  }
553  } // prt
554  // create a subset of the first two for the first fit
555  auto fitpts = tjpts;
556  fitpts.resize(2);
557  if (!FitVertex(slc, aVx, fitpts, prt)) {
558  if (prt) mf::LogVerbatim("TC") << "MWV: first fit failed ";
559  return false;
560  }
561  // Fit and add tjs to the vertex
562  bool needsUpdate = false;
563  for (unsigned short ii = 2; ii < tjlist.size(); ++ii) {
564  fitpts.push_back(tjpts[ii]);
565  if (FitVertex(slc, aVx, fitpts, prt)) { needsUpdate = false; }
566  else {
567  // remove the last Tj point and keep going
568  fitpts.pop_back();
569  needsUpdate = true;
570  }
571  } // ii
572 
573  if (needsUpdate) FitVertex(slc, aVx, fitpts, prt);
574  if (prt) mf::LogVerbatim("TC") << "MWV: done " << vx.ID << " and existing " << oVx.ID;
575 
576  // update. Remove old associations
577  for (auto& tj : slc.tjs) {
578  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
579  if (tj.CTP != vx.CTP) continue;
580  for (unsigned short end = 0; end < 2; ++end) {
581  if (tj.VtxID[end] == vx.ID) tj.VtxID[end] = 0;
582  if (tj.VtxID[end] == oVxID) tj.VtxID[end] = 0;
583  }
584  } // tj
585  // set the new associations
586  for (unsigned short ii = 0; ii < fitpts.size(); ++ii) {
587  auto& tjpt = fitpts[ii];
588  unsigned short end = tjpt.AngleCode;
589  auto& tj = slc.tjs[tjpt.Hits[0] - 1];
590  if (tj.VtxID[end] != 0) return false;
591  tj.VtxID[end] = oVxID;
592  } // ii
593 
594  // Update oVx
595  oVx.Pos = aVx.Pos;
596  oVx.PosErr = aVx.PosErr;
597  oVx.ChiDOF = aVx.ChiDOF;
598  oVx.NTraj = fitpts.size();
599  // Update the score and the charge fraction
600  SetVx2Score(slc, oVx);
601  oVx.Stat[kVxMerged] = true;
602  oVx.Stat[kFixed] = false;
603  if (prt) {
604  mf::LogVerbatim myprt("TC");
605  myprt << "MWV: " << oVxID;
606  myprt << " Done TPs";
607  for (unsigned short ii = 0; ii < fitpts.size(); ++ii) {
608  auto& tjpt = fitpts[ii];
609  myprt << " " << tjpt.Hits[0] << "_" << tjpt.AngleCode << "_" << PrintPos(slc, tjpt.Pos);
610  }
611  } // prt
612 
613  return true;
614  } // MergeWithVertex
615 
616  //////////////////////////////////////////
617  void
618  FindHammerVertices2(TCSlice& slc, const CTP_t& inCTP)
619  {
620  // Variant of FindHammerVertices with slightly different requirements:
621  // 1) tj1 is a straight trajectory with most of the points fit
622  // 2) No angle requirement between tj1 and tj2
623  // 3) Large charge near the intersection point X on tj2
624  // tj2 ---X---
625  // tj1 /
626  // tj1 /
627  // tj1 /
628  // minimum^2 DOCA of tj1 endpoint with tj2
629 
630  if (!tcc.useAlg[kHamVx2]) return;
631  // This wasn't written for test beam events
632  if (tcc.modes[kTestBeam]) return;
633 
634  bool prt = (tcc.modes[kDebug] && tcc.dbgSlc && tcc.dbgAlg[kHamVx2]);
635  if (prt) mf::LogVerbatim("TC") << "Inside HamVx2";
636 
637  for (unsigned short it1 = 0; it1 < slc.tjs.size(); ++it1) {
638  if (slc.tjs[it1].CTP != inCTP) continue;
639  if (slc.tjs[it1].AlgMod[kKilled] || slc.tjs[it1].AlgMod[kHaloTj]) continue;
640  if (slc.tjs[it1].AlgMod[kHamVx]) continue;
641  if (slc.tjs[it1].AlgMod[kHamVx2]) continue;
642  if (slc.tjs[it1].AlgMod[kJunkTj]) continue;
643  if (slc.tjs[it1].PDGCode == 111) continue;
644  unsigned short numPtsWithCharge1 = NumPtsWithCharge(slc, slc.tjs[it1], false);
645  if (numPtsWithCharge1 < 6) continue;
646  // require high MCSMom
647  if (slc.tjs[it1].MCSMom < 200) continue;
648  // Check each end of tj1
649  bool didaSplit = false;
650  for (unsigned short end1 = 0; end1 < 2; ++end1) {
651  // vertex assignment exists?
652  if (slc.tjs[it1].VtxID[end1] > 0) continue;
653  unsigned short endPt1 = slc.tjs[it1].EndPt[end1];
654  for (unsigned short it2 = 0; it2 < slc.tjs.size(); ++it2) {
655  if (it1 == it2) continue;
656  if (slc.tjs[it2].AlgMod[kKilled] || slc.tjs[it2].AlgMod[kHaloTj]) continue;
657  if (slc.tjs[it2].AlgMod[kHamVx]) continue;
658  if (slc.tjs[it2].AlgMod[kHamVx2]) continue;
659  // require that both be in the same CTP
660  if (slc.tjs[it2].CTP != inCTP) continue;
661  if (slc.tjs[it2].AlgMod[kShowerLike]) continue;
662  if (slc.tjs[it2].AlgMod[kJunkTj]) continue;
663  if (slc.tjs[it2].PDGCode == 111) continue;
664  unsigned short numPtsWithCharge2 = NumPtsWithCharge(slc, slc.tjs[it2], true);
665  if (numPtsWithCharge2 < 6) continue;
666  // ignore muon-like tjs
667  if (numPtsWithCharge2 > 100 && slc.tjs[it2].MCSMom > 500) continue;
668  // Find the minimum separation between tj1 and tj2
669  float minDOCA = 5;
670  float doca = minDOCA;
671  unsigned short closePt2 = 0;
672  TrajPointTrajDOCA(slc, slc.tjs[it1].Pts[endPt1], slc.tjs[it2], closePt2, doca);
673  if (doca == minDOCA) continue;
674  if (prt) {
675  mf::LogVerbatim myprt("TC");
676  auto& tj1 = slc.tjs[it1];
677  auto& tj2 = slc.tjs[it2];
678  myprt << " FHV2 CTP" << tj1.CTP;
679  myprt << " t" << tj1.ID << "_" << end1 << " MCSMom " << tj1.MCSMom << " ChgRMS "
680  << tj1.ChgRMS;
681  myprt << " split t" << tj2.ID << "? MCSMom " << tj2.MCSMom << " ChgRMS " << tj2.ChgRMS;
682  myprt << " doca " << doca << " tj2.EndPt[0] " << tj2.EndPt[0] << " closePt2 "
683  << closePt2;
684  myprt << " tj2.EndPt[1] " << tj2.EndPt[1];
685  } // prt
686  // ensure that the closest point is not near an end
687  if (closePt2 < slc.tjs[it2].EndPt[0] + 3) continue;
688  if (closePt2 > slc.tjs[it2].EndPt[1] - 3) continue;
689  // Find the intersection point between the tj1 end and tj2 closest Pt
690  float wint, tint;
691  TrajIntersection(slc.tjs[it1].Pts[endPt1], slc.tjs[it2].Pts[closePt2], wint, tint);
692  // make an angle cut
693  float dang = DeltaAngle(slc.tjs[it1].Pts[endPt1].Ang, slc.tjs[it2].Pts[closePt2].Ang);
694  if (dang < 0.2) continue;
695  // ensure that tj1 doesn't cross tj2 but ensuring that the closest point on tj1 is at closePt1
696  doca = 5;
697  unsigned short closePt1 = 0;
698  TrajPointTrajDOCA(slc, slc.tjs[it2].Pts[closePt2], slc.tjs[it1], closePt1, doca);
699  if (closePt1 != endPt1) continue;
700  if (prt)
701  mf::LogVerbatim("TC") << " intersection W:T " << (int)wint << ":"
702  << (int)(tint / tcc.unitsPerTick) << " dang " << dang;
703  // Find the point on tj2 that is closest to this point, overwriting closePt
704  doca = minDOCA;
705  // the point on tj2 that is closest to the intersection point
706  unsigned short intPt2;
707  TrajClosestApproach(slc.tjs[it2], wint, tint, intPt2, doca);
708  if (prt)
709  mf::LogVerbatim("TC") << " intPt2 on tj2 " << intPt2 << " at Pos "
710  << PrintPos(slc, slc.tjs[it2].Pts[intPt2]) << " doca " << doca;
711  if (doca == minDOCA) continue;
712  // find the MCSMom for both sections of tj2
713  float mcsmom = slc.tjs[it2].MCSMom;
714  float mcsmom1 = MCSMom(slc, slc.tjs[it2], slc.tjs[it2].EndPt[0], intPt2);
715  float mcsmom2 = MCSMom(slc, slc.tjs[it2], intPt2, slc.tjs[it2].EndPt[1]);
716  // require that the both MCSMoms be greater than
717  if (prt)
718  mf::LogVerbatim("TC") << " Check MCSMom after split: mcsmom1 " << mcsmom1 << " mcsmom2 "
719  << mcsmom2;
720  if (mcsmom1 < mcsmom || mcsmom2 < mcsmom) continue;
721  // start scanning for the point on tj2 that has the best IP with the end of tj1 in the direction
722  // from closePt2 -> endPt2
723  short dir = 1;
724  if (intPt2 < closePt2) dir = -1;
725  unsigned short nit = 0;
726  unsigned short ipt = intPt2;
727  float mostChg = slc.tjs[it2].Pts[ipt].Chg;
728  if (prt)
729  mf::LogVerbatim("TC") << " ipt " << ipt << " at Pos "
730  << PrintPos(slc, slc.tjs[it2].Pts[ipt]) << " chg " << mostChg;
731  while (nit < 20) {
732  ipt += dir;
733  if (ipt < 3 || ipt > slc.tjs[it2].Pts.size() - 4) break;
734  float delta = PointTrajDOCA(slc,
735  slc.tjs[it2].Pts[ipt].Pos[0],
736  slc.tjs[it2].Pts[ipt].Pos[1],
737  slc.tjs[it1].Pts[endPt1]);
738  float sep = PosSep(slc.tjs[it2].Pts[ipt].Pos, slc.tjs[it1].Pts[endPt1].Pos);
739  float dang = delta / sep;
740  float pull = dang / slc.tjs[it1].Pts[endPt1].DeltaRMS;
741  if (pull < 2 && slc.tjs[it2].Pts[ipt].Chg > mostChg) {
742  mostChg = slc.tjs[it2].Pts[ipt].Chg;
743  intPt2 = ipt;
744  }
745  }
746  // require a lot of charge in tj2 in this vicinity compared with the average.
747  float chgPull = (mostChg - slc.tjs[it2].AveChg) / slc.tjs[it2].ChgRMS;
748  if (prt) mf::LogVerbatim("TC") << " chgPull at intersection point " << chgPull;
749  if (chgPull < 10) continue;
750  // require a signal on every wire between it1 and intPt2
751  TrajPoint ltp = slc.tjs[it1].Pts[endPt1];
752  if (slc.tjs[it1].Pts[endPt1].Pos[0] < -0.4) continue;
753  unsigned int fromWire = std::nearbyint(slc.tjs[it1].Pts[endPt1].Pos[0]);
754  if (slc.tjs[it2].Pts[intPt2].Pos[0] < -0.4) continue;
755  unsigned int toWire = std::nearbyint(slc.tjs[it2].Pts[intPt2].Pos[0]);
756  if (fromWire > toWire) {
757  unsigned int tmp = fromWire;
758  fromWire = toWire;
759  toWire = tmp;
760  }
761  bool skipIt = false;
762  for (unsigned int wire = fromWire + 1; wire < toWire; ++wire) {
763  MoveTPToWire(ltp, (float)wire);
764  if (!SignalAtTp(ltp)) {
765  skipIt = true;
766  break;
767  }
768  } // wire
769  if (skipIt) continue;
770  // we have a winner
771  // create a new vertex
772  VtxStore aVtx;
773  aVtx.Pos = slc.tjs[it2].Pts[intPt2].Pos;
774  aVtx.NTraj = 3;
775  aVtx.Pass = slc.tjs[it2].Pass;
776  aVtx.Topo = 6;
777  aVtx.ChiDOF = 0;
778  aVtx.CTP = inCTP;
779  aVtx.ID = slc.vtxs.size() + 1;
780  unsigned short ivx = slc.vtxs.size();
781  if (!StoreVertex(slc, aVtx)) continue;
782  if (!SplitTraj(slc, it2, intPt2, ivx, prt)) {
783  if (prt) mf::LogVerbatim("TC") << "FHV2: Failed to split trajectory";
784  MakeVertexObsolete("HamVx2", slc, slc.vtxs[ivx], true);
785  continue;
786  }
787  slc.tjs[it1].VtxID[end1] = slc.vtxs[ivx].ID;
788  slc.tjs[it1].AlgMod[kHamVx2] = true;
789  slc.tjs[it2].AlgMod[kHamVx2] = true;
790  unsigned short newTjIndex = slc.tjs.size() - 1;
791  slc.tjs[newTjIndex].AlgMod[kHamVx2] = true;
792  AttachAnyTrajToVertex(slc, ivx, prt);
793  SetVx2Score(slc);
794  // Update the PDGCode for the chopped trajectory
795  SetPDGCode(slc, it2);
796  // and for the new trajectory
797  SetPDGCode(slc, newTjIndex);
798  if (prt)
799  mf::LogVerbatim("TC") << " FHV2: New vtx 2V" << slc.vtxs[ivx].ID << " Score "
800  << slc.vtxs[ivx].Score;
801  didaSplit = true;
802  break;
803  } // it2
804  if (didaSplit) break;
805  } // end1
806  } // it1
807  } // FindHammerVertices2
808 
809  //////////////////////////////////////////
810  void
811  FindHammerVertices(TCSlice& slc, const CTP_t& inCTP)
812  {
813  // Look for a trajectory that intersects another. Split
814  // the trajectory and make a vertex. The convention used
815  // is shown pictorially here. Trajectory tj1 must be longer
816  // than tj2
817  // tj2 ------
818  // tj1 /
819  // tj1 /
820  // tj1 /
821 
822  if (!tcc.useAlg[kHamVx]) return;
823 
824  bool prt = (tcc.modes[kDebug] && tcc.dbgSlc && tcc.dbgAlg[kHamVx]);
825  if (prt) { mf::LogVerbatim("TC") << "Inside HamVx inCTP " << inCTP; }
826 
827  for (unsigned short it1 = 0; it1 < slc.tjs.size(); ++it1) {
828  if (slc.tjs[it1].CTP != inCTP) continue;
829  if (slc.tjs[it1].AlgMod[kKilled] || slc.tjs[it1].AlgMod[kHaloTj]) continue;
830  if (slc.tjs[it1].AlgMod[kShowerLike]) continue;
831  if (slc.tjs[it1].AlgMod[kJunkTj]) continue;
832  if (slc.tjs[it1].PDGCode == 111) continue;
833  // minimum length requirements
834  unsigned short tj1len = slc.tjs[it1].EndPt[1] - slc.tjs[it1].EndPt[0] + 1;
835  if (tj1len < 5) continue;
836  // require high MCSMom
837  if (slc.tjs[it1].MCSMom < 50) continue;
838  if (prt) mf::LogVerbatim("TC") << "FHV: intersection T" << slc.tjs[it1].ID << " candidate";
839  // Check each end of tj1
840  bool didaSplit = false;
841  for (unsigned short end1 = 0; end1 < 2; ++end1) {
842  // vertex assignment exists?
843  if (slc.tjs[it1].VtxID[end1] > 0) continue;
844  unsigned short endPt1 = slc.tjs[it1].EndPt[end1];
845  for (unsigned short it2 = 0; it2 < slc.tjs.size(); ++it2) {
846  if (slc.tjs[it2].CTP != inCTP) continue;
847  if (it1 == it2) continue;
848  if (slc.tjs[it2].AlgMod[kKilled] || slc.tjs[it2].AlgMod[kHaloTj]) continue;
849  if (slc.tjs[it2].AlgMod[kJunkTj]) continue;
850  if (slc.tjs[it2].PDGCode == 111) continue;
851  // length of tj2 cut
852  unsigned short tj2len = slc.tjs[it2].EndPt[1] - slc.tjs[it2].EndPt[0] + 1;
853  if (tj2len < 6) continue;
854  // ignore very long straight trajectories (probably a cosmic muon)
855  if (tj2len > 200 && slc.tjs[it2].PDGCode == 13 && slc.tjs[it2].MCSMom > 600) continue;
856  float minDOCA = 5;
857  minDOCA /= std::abs(slc.tjs[it1].Pts[endPt1].Dir[0]);
858  float doca = minDOCA;
859  unsigned short closePt2 = 0;
860  TrajPointTrajDOCA(slc, slc.tjs[it1].Pts[endPt1], slc.tjs[it2], closePt2, doca);
861  if (doca == minDOCA) continue;
862  // ensure that the closest point is not near an end
863  if (prt)
864  mf::LogVerbatim("TC") << "FHV: Candidate T" << slc.tjs[it1].ID << " T"
865  << slc.tjs[it2].ID << " doca " << doca << " tj2.EndPt[0] "
866  << slc.tjs[it2].EndPt[0] << " closePt2 " << closePt2
867  << " tj2.EndPt[1] " << slc.tjs[it2].EndPt[1];
868  if (closePt2 < slc.tjs[it2].EndPt[0] + 3) continue;
869  if (closePt2 > slc.tjs[it2].EndPt[1] - 3) continue;
870  // make an angle cut
871  float dang = DeltaAngle(slc.tjs[it1].Pts[endPt1].Ang, slc.tjs[it2].Pts[closePt2].Ang);
872  if (prt)
873  mf::LogVerbatim("TC") << " dang " << dang << " imposing a hard cut of 0.4 for now ";
874  if (dang < 0.4) continue;
875  // check the cleanliness in this area
876  std::vector<int> tjids(2);
877  tjids[0] = slc.tjs[it1].ID;
878  tjids[1] = slc.tjs[it2].ID;
879  float chgFrac = ChgFracNearPos(slc, slc.tjs[it2].Pts[closePt2].Pos, tjids);
880  if (prt) mf::LogVerbatim("TC") << " chgFrac " << chgFrac;
881  if (chgFrac < 0.9) continue;
882  Point2_t vxpos = slc.tjs[it2].Pts[closePt2].Pos;
883  // get a better estimate of the vertex position
885  slc.tjs[it1].Pts[endPt1], slc.tjs[it2].Pts[closePt2], vxpos[0], vxpos[1]);
886  // and a better estimate of the point on tj2 where the split should be made
887  doca = minDOCA;
888  TrajClosestApproach(slc.tjs[it2], vxpos[0], vxpos[1], closePt2, doca);
889  if (prt)
890  mf::LogVerbatim("TC") << " better pos " << PrintPos(slc, vxpos) << " new closePt2 "
891  << closePt2;
892  // create a new vertex
893  VtxStore aVtx;
894  aVtx.Pos = vxpos;
895  aVtx.NTraj = 3;
896  aVtx.Pass = slc.tjs[it2].Pass;
897  aVtx.Topo = 5;
898  aVtx.ChiDOF = 0;
899  aVtx.CTP = inCTP;
900  aVtx.ID = slc.vtxs.size() + 1;
901  if (!StoreVertex(slc, aVtx)) continue;
902  unsigned short ivx = slc.vtxs.size() - 1;
903  if (!SplitTraj(slc, it2, closePt2, ivx, prt)) {
904  if (prt) mf::LogVerbatim("TC") << "FHV: Failed to split trajectory";
905  MakeVertexObsolete("HamVx", slc, slc.vtxs[slc.vtxs.size() - 1], true);
906  continue;
907  }
908  slc.tjs[it1].VtxID[end1] = slc.vtxs[ivx].ID;
909  slc.tjs[it1].AlgMod[kHamVx] = true;
910  slc.tjs[it2].AlgMod[kHamVx] = true;
911  unsigned short newTjIndex = slc.tjs.size() - 1;
912  slc.tjs[newTjIndex].AlgMod[kHamVx] = true;
913  SetVx2Score(slc);
914  // Update the PDGCode for the chopped trajectory
915  SetPDGCode(slc, it2);
916  // and for the new trajectory
917  SetPDGCode(slc, newTjIndex);
918  if (prt) {
919  auto& vx2 = slc.vtxs[ivx];
920  mf::LogVerbatim myprt("TC");
921  myprt << " new 2V" << vx2.ID << " Score " << vx2.Score << " Tjs";
922  auto tjlist = GetAssns(slc, "2V", vx2.ID, "T");
923  for (auto tid : tjlist)
924  myprt << " t" << tid;
925  }
926  didaSplit = true;
927  break;
928  } // tj2
929  if (didaSplit) break;
930  } // end1
931  } // tj1
932 
933  } // FindHammerVertices
934 
935  //////////////////////////////////////////
936  void
938  {
939  // This is kind of self-explanatory...
940 
941  if (!tcc.useAlg[kSplitTjCVx]) return;
942 
943  if (slc.vtxs.empty()) return;
944  if (slc.tjs.empty()) return;
945 
946  constexpr float docaCut = 4;
947 
948  bool prt = (tcc.modes[kDebug] && tcc.dbgSlc && tcc.dbgAlg[kSplitTjCVx]);
949  if (prt) mf::LogVerbatim("TC") << "Inside SplitTrajCrossingVertices inCTP " << inCTP;
950 
951  geo::PlaneID planeID = DecodeCTP(inCTP);
952 
953  unsigned short nTraj = slc.tjs.size();
954  for (unsigned short itj = 0; itj < nTraj; ++itj) {
955  // NOTE: Don't use a reference variable because it may get lost if the tj is split
956  if (slc.tjs[itj].CTP != inCTP) continue;
957  // obsolete trajectory
958  if (slc.tjs[itj].AlgMod[kKilled] || slc.tjs[itj].AlgMod[kHaloTj]) continue;
959  if (slc.tjs[itj].AlgMod[kJunkTj]) continue;
960  if (slc.tjs[itj].AlgMod[kSplitTjCVx]) continue;
961  // too short
962  if (slc.tjs[itj].EndPt[1] < 6) continue;
963  for (unsigned short iv = 0; iv < slc.vtxs.size(); ++iv) {
964  auto& vx2 = slc.vtxs[iv];
965  // obsolete vertex
966  if (vx2.NTraj == 0) continue;
967  // trajectory already associated with vertex
968  if (slc.tjs[itj].VtxID[0] == vx2.ID || slc.tjs[itj].VtxID[1] == vx2.ID) continue;
969  // not in the cryostat/tpc/plane
970  if (slc.tjs[itj].CTP != vx2.CTP) continue;
971  // poor quality
972  if (vx2.Score < tcc.vtx2DCuts[7]) continue;
973  float doca = docaCut;
974  // make the cut significantly larger if the vertex is in a dead
975  // wire gap to get the first TP that is just outside the gap.
976  if (vx2.Stat[kOnDeadWire]) doca = 100;
977  unsigned short closePt = 0;
978  if (!TrajClosestApproach(slc.tjs[itj], vx2.Pos[0], vx2.Pos[1], closePt, doca)) continue;
979  if (vx2.Stat[kOnDeadWire]) {
980  // special handling for vertices in dead wire regions. Find the IP between
981  // the closest point on the Tj and the vertex
982  doca = PointTrajDOCA(slc, vx2.Pos[0], vx2.Pos[1], slc.tjs[itj].Pts[closePt]);
983  }
984  if (doca > docaCut) continue;
985  if (prt)
986  mf::LogVerbatim("TC") << " doca " << doca << " btw T" << slc.tjs[itj].ID << " and 2V"
987  << slc.vtxs[iv].ID << " closePt " << closePt << " in plane "
988  << planeID.Plane;
989  // compare the length of the Tjs used to make the vertex with the length of the
990  // Tj that we want to split. Don't allow a vertex using very short Tjs to split a long
991  // Tj in the 3rd plane
992  auto vxtjs = GetVtxTjIDs(slc, vx2);
993  if (vxtjs.empty()) continue;
994  unsigned short maxPts = 0;
995  // ensure that there is a large angle between a Tj already attached to the vertex and the
996  // tj that we want to split. We might be considering a delta-ray here
997  float maxdang = 0.3;
998  float tjAng = slc.tjs[itj].Pts[closePt].Ang;
999  for (auto tjid : vxtjs) {
1000  auto& vtj = slc.tjs[tjid - 1];
1001  if (vtj.AlgMod[kDeltaRay]) continue;
1002  unsigned short npwc = NumPtsWithCharge(slc, vtj, false);
1003  if (npwc > maxPts) maxPts = npwc;
1004  unsigned short end = 0;
1005  if (vtj.VtxID[1] == slc.vtxs[iv].ID) end = 1;
1006  auto& vtp = vtj.Pts[vtj.EndPt[end]];
1007  float dang = DeltaAngle(vtp.Ang, tjAng);
1008  if (dang > maxdang) maxdang = dang;
1009  } // tjid
1010  // skip this operation if any of the Tjs in the split list are > 3 * maxPts
1011  maxPts *= 3;
1012  bool skipit = false;
1013  if (NumPtsWithCharge(slc, slc.tjs[itj], false) > maxPts && maxPts < 100) skipit = true;
1014  if (!skipit && maxdang < 0.3) skipit = true;
1015  if (prt)
1016  mf::LogVerbatim("TC") << " maxPts " << maxPts << " vxtjs[0] " << vxtjs[0] << " maxdang "
1017  << maxdang << " skipit? " << skipit;
1018  if (skipit) {
1019  // kill the vertex?
1020  if (doca < 1) MakeVertexObsolete("STCV", slc, vx2, true);
1021  continue;
1022  }
1023 
1024  // make some adjustments to closePt
1025  if (vx2.Stat[kOnDeadWire]) {
1026  // ensure that the tj will be split at the gap. The closePt point may be
1027  // on the wrong side of it
1028  auto& closeTP = slc.tjs[itj].Pts[closePt];
1029  if (slc.tjs[itj].StepDir > 0 && closePt > slc.tjs[itj].EndPt[0]) {
1030  if (closeTP.Pos[0] > vx2.Pos[0]) --closePt;
1031  }
1032  else if (slc.tjs[itj].StepDir < 0 && closePt < slc.tjs[itj].EndPt[1]) {
1033  if (closeTP.Pos[0] < vx2.Pos[0]) ++closePt;
1034  }
1035  }
1036  else {
1037  // improve closePt based on vertex position
1038  // check if closePt and EndPt[1] are the two sides of vertex
1039  // take dot product of closePt-vtx and EndPt[1]-vtx
1040  if ((slc.tjs[itj].Pts[closePt].Pos[0] - vx2.Pos[0]) *
1041  (slc.tjs[itj].Pts[slc.tjs[itj].EndPt[1]].Pos[0] - vx2.Pos[0]) +
1042  (slc.tjs[itj].Pts[closePt].Pos[1] - vx2.Pos[1]) *
1043  (slc.tjs[itj].Pts[slc.tjs[itj].EndPt[1]].Pos[1] - vx2.Pos[1]) <
1044  0 &&
1045  closePt < slc.tjs[itj].EndPt[1] - 1)
1046  ++closePt;
1047  else if ((slc.tjs[itj].Pts[closePt].Pos[0] - vx2.Pos[0]) *
1048  (slc.tjs[itj].Pts[slc.tjs[itj].EndPt[0]].Pos[0] - vx2.Pos[0]) +
1049  (slc.tjs[itj].Pts[closePt].Pos[1] - vx2.Pos[1]) *
1050  (slc.tjs[itj].Pts[slc.tjs[itj].EndPt[0]].Pos[1] - slc.vtxs[iv].Pos[1]) <
1051  0 &&
1052  closePt > slc.tjs[itj].EndPt[0] + 1)
1053  --closePt;
1054  }
1055 
1056  if (prt) {
1057  mf::LogVerbatim("TC") << "Good doca " << doca << " btw T" << slc.tjs[itj].ID << " and 2V"
1058  << vx2.ID << " closePt " << closePt << " in plane " << planeID.Plane
1059  << " CTP " << slc.vtxs[iv].CTP;
1060  PrintTP("STCV", slc, closePt, 1, slc.tjs[itj].Pass, slc.tjs[itj].Pts[closePt]);
1061  }
1062  // ensure that the closest point is not near an end
1063  if (closePt < slc.tjs[itj].EndPt[0] + 3) continue;
1064  if (closePt > slc.tjs[itj].EndPt[1] - 3) continue;
1065  if (!SplitTraj(slc, itj, closePt, iv, prt)) {
1066  if (prt) mf::LogVerbatim("TC") << "SplitTrajCrossingVertices: Failed to split trajectory";
1067  continue;
1068  }
1069  slc.tjs[itj].AlgMod[kSplitTjCVx] = true;
1070  unsigned short newTjIndex = slc.tjs.size() - 1;
1071  slc.tjs[newTjIndex].AlgMod[kSplitTjCVx] = true;
1072  // re-fit the vertex position
1073  FitVertex(slc, vx2, prt);
1074  } // iv
1075  } // itj
1076 
1077  } // SplitTrajCrossingVertices
1078 
1079  //////////////////////////////////////
1080  void
1082  {
1083  // This function is called before Find3DVertices to identify (and possibly reconcile)
1084  // Tj and 2V inconsistencies using 2D and 3D(?) information
1085  if (!tcc.useAlg[kReconcile2Vs]) return;
1086  if (slc.vtxs.empty()) return;
1087 
1088  bool prt = (tcc.dbg2V && tcc.dbgSlc);
1089 
1090  // clusters of 2Vs
1091  std::vector<std::vector<int>> vx2Cls;
1092 
1093  // iterate over planes
1094  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1095  // look for 2D vertices that are close to each other
1096  vx2Cls.clear();
1097  for (unsigned short ii = 0; ii < slc.vtxs.size() - 1; ++ii) {
1098  auto& i2v = slc.vtxs[ii];
1099  if (i2v.ID <= 0) continue;
1100  if (DecodeCTP(i2v.CTP).Plane != plane) continue;
1101  for (unsigned short jj = ii + 1; jj < slc.vtxs.size(); ++jj) {
1102  auto& j2v = slc.vtxs[jj];
1103  if (j2v.ID <= 0) continue;
1104  if (DecodeCTP(j2v.CTP).Plane != plane) continue;
1105  // make rough separation cuts
1106  float dp0 = std::abs(i2v.Pos[0] - j2v.Pos[0]);
1107  if (dp0 > 10) continue;
1108  float dp1 = std::abs(i2v.Pos[1] - j2v.Pos[1]);
1109  if (dp1 > 10) continue;
1110  // do a more careful look
1111  float err = i2v.PosErr[0];
1112  if (j2v.PosErr[0] > err) err = j2v.PosErr[0];
1113  float dp0Sig = dp0 / err;
1114  if (dp0Sig > 4) continue;
1115  err = i2v.PosErr[1];
1116  if (j2v.PosErr[1] > err) err = j2v.PosErr[1];
1117  float dp1Sig = dp1 / err;
1118  if (dp1Sig > 4) continue;
1119  // Look for one of the 2V IDs in a cluster
1120  bool gotit = false;
1121  for (auto& vx2cls : vx2Cls) {
1122  bool goti = (std::find(vx2cls.begin(), vx2cls.end(), i2v.ID) != vx2cls.end());
1123  bool gotj = (std::find(vx2cls.begin(), vx2cls.end(), j2v.ID) != vx2cls.end());
1124  if (goti && gotj) {
1125  gotit = true;
1126  break;
1127  }
1128  else if (goti) {
1129  vx2cls.push_back(j2v.ID);
1130  gotit = true;
1131  break;
1132  }
1133  else if (gotj) {
1134  gotit = true;
1135  vx2cls.push_back(i2v.ID);
1136  break;
1137  }
1138  } // vx2cls
1139  if (!gotit) {
1140  // start a new cluster with this pair
1141  std::vector<int> cls(2);
1142  cls[0] = i2v.ID;
1143  cls[1] = j2v.ID;
1144  vx2Cls.push_back(cls);
1145  } // !gotit
1146  } // jj
1147  } // ii
1148  if (vx2Cls.empty()) continue;
1149  if (prt) {
1150  mf::LogVerbatim myprt("TC");
1151  myprt << "2V clusters in plane " << plane;
1152  for (auto& vx2cls : vx2Cls) {
1153  myprt << "\n";
1154  for (auto vx2id : vx2cls)
1155  myprt << " 2V" << vx2id;
1156  } // vx2cls
1157  } // prt
1158  for (auto& vx2cls : vx2Cls) {
1159  Reconcile2VTs(slc, vx2cls, prt);
1160  } // vx2cls
1161  } // plane
1162 
1163  // See if any of the vertices have been altered. If so the environment near them,
1164  // specifically tagging overlapping trajectories, should be re-done
1165  bool VxEnvironmentNeedsUpdate = false;
1166  for (auto& vx : slc.vtxs) {
1167  if (vx.ID <= 0) continue;
1168  if (!vx.Stat[kVxEnvOK]) VxEnvironmentNeedsUpdate = true;
1169  } // vx
1170 
1171  if (VxEnvironmentNeedsUpdate) UpdateVxEnvironment(slc);
1172 
1173  } // Reconcile2Vs
1174 
1175  //////////////////////////////////////
1176  bool
1177  Reconcile2VTs(TCSlice& slc, std::vector<int>& vx2cls, bool prt)
1178  {
1179  // The 2D vertices IDs in vx2cls were clustered by the calling function. This function
1180  // checks the T -> 2V assns and possibly changes it. It returns true if an assn is changed
1181  // or if a vertex in vx2cls is made obsolete, necessitating a change to the list of 2V
1182  // clusters
1183  if (vx2cls.size() < 2) return false;
1184 
1185  // Form a list of all Tjs associated with this 2V cluster
1186  std::vector<int> t2vList;
1187 
1188  CTP_t inCTP;
1189  for (auto vx2id : vx2cls) {
1190  auto& vx2 = slc.vtxs[vx2id - 1];
1191  inCTP = vx2.CTP;
1192  // vertex clobbered? If so, vertex clustering needs to be re-done
1193  if (vx2.ID <= 0) return true;
1194  auto tlist = GetAssns(slc, "2V", vx2.ID, "T");
1195  for (auto tid : tlist)
1196  if (std::find(t2vList.begin(), t2vList.end(), tid) == t2vList.end()) t2vList.push_back(tid);
1197  } // vx2id
1198  if (t2vList.size() < 3) return false;
1199 
1200  // Sum the T -> 2V pulls
1201  float sumPulls = 0;
1202  float cnt = 0;
1203  for (auto tid : t2vList) {
1204  auto& tj = slc.tjs[tid - 1];
1205  for (unsigned short end = 0; end < 2; ++end) {
1206  if (tj.VtxID[end] <= 0) continue;
1207  if (std::find(vx2cls.begin(), vx2cls.end(), tj.VtxID[end]) == vx2cls.end()) continue;
1208  auto& vx = slc.vtxs[tj.VtxID[end] - 1];
1209  unsigned short nearEnd = 1 - FarEnd(slc, tj, vx.Pos);
1210  unsigned short fitPt = NearbyCleanPt(slc, tj, nearEnd);
1211  if (fitPt == USHRT_MAX) return false;
1212  auto& tp = tj.Pts[fitPt];
1213  sumPulls += TrajPointVertexPull(slc, tp, vx);
1214  ++cnt;
1215  } // end
1216  } // tid
1217 
1218  if (prt) {
1219  mf::LogVerbatim myprt("TC");
1220  myprt << "R2VTs: cluster:";
1221  for (auto vid : vx2cls)
1222  myprt << " 2V" << vid;
1223  myprt << " ->";
1224  for (auto tid : t2vList)
1225  myprt << " T" << tid;
1226  myprt << " sumPulls " << std::setprecision(2) << sumPulls << " cnt " << cnt;
1227  } // prt
1228 
1229  // try to fit all Tjs to one vertex. Find the average position of all of the
1230  // vertices. This will be used to find the end of the Tjs that are closest to the
1231  // presumed single vertex
1232  VtxStore oneVx;
1233  oneVx.CTP = inCTP;
1234  for (auto vid : vx2cls) {
1235  auto& vx = slc.vtxs[vid - 1];
1236  oneVx.Pos[0] += vx.Pos[0];
1237  oneVx.Pos[1] += vx.Pos[2];
1238  } // vid
1239  oneVx.Pos[0] /= vx2cls.size();
1240  oneVx.Pos[1] /= vx2cls.size();
1241  std::vector<TrajPoint> oneVxTPs(t2vList.size());
1242  for (unsigned short itj = 0; itj < t2vList.size(); ++itj) {
1243  auto& tj = slc.tjs[t2vList[itj] - 1];
1244  unsigned short nearEnd = 1 - FarEnd(slc, tj, oneVx.Pos);
1245  unsigned short fitPt = NearbyCleanPt(slc, tj, nearEnd);
1246  if (fitPt == USHRT_MAX) return false;
1247  oneVxTPs[itj] = tj.Pts[fitPt];
1248  // inflate the TP angle angle error if a TP without an overlap wasn't found
1249  if (oneVxTPs[itj].Environment[kEnvOverlap]) oneVxTPs[itj].AngErr *= 4;
1250  oneVxTPs[itj].Step = tj.ID;
1251  } // ii
1252  if (!FitVertex(slc, oneVx, oneVxTPs, prt)) return false;
1253 
1254  if (oneVx.ChiDOF < 3) {
1255  // Update the position of the first 2V in the list
1256  auto& vx = slc.vtxs[vx2cls[0] - 1];
1257  vx.Pos = oneVx.Pos;
1258  vx.PosErr = oneVx.PosErr;
1259  vx.NTraj = t2vList.size();
1260  vx.ChiDOF = oneVx.ChiDOF;
1261  vx.Topo = 14;
1262  // Set a flag that the environment near this vertex (and all other vertices in this slice)
1263  // should be revisited
1264  vx.Stat[kVxEnvOK] = false;
1265  for (unsigned short ivx = 1; ivx < vx2cls.size(); ++ivx) {
1266  auto& vx = slc.vtxs[vx2cls[ivx] - 1];
1267  MakeVertexObsolete("R2VTPs", slc, vx, true);
1268  } // ivx
1269  // now attach the trajectories
1270  for (auto tid : t2vList) {
1271  auto& tj = slc.tjs[tid - 1];
1272  unsigned short nearEnd = 1 - FarEnd(slc, tj, vx.Pos);
1273  tj.VtxID[nearEnd] = vx.ID;
1274  } // tid
1275  return true;
1276  } // oneVx.ChiDOF < 3
1277  return false;
1278  } // Reconcile2VTs
1279 
1280  //////////////////////////////////////
1281  void
1283  {
1284  // Create 3D vertices from 2D vertices. 3D vertices that are matched
1285  // in all three planes have Vtx2ID > 0 for all planes. This function re-scores all
1286  // 2D and 3D vertices and flags Tjs that have high-score 3D vertices
1287 
1288  if (tcc.vtx3DCuts[0] < 0) return;
1289  if (slc.vtxs.size() < 2) return;
1290 
1291  // create a array/vector of 2D vertex indices in each plane
1292  std::vector<std::vector<unsigned short>> vIndex(3);
1293  for (unsigned short ivx = 0; ivx < slc.vtxs.size(); ++ivx) {
1294  // obsolete vertex
1295  if (slc.vtxs[ivx].ID == 0) continue;
1296  geo::PlaneID planeID = DecodeCTP(slc.vtxs[ivx].CTP);
1297  if (planeID.TPC != slc.TPCID.TPC) continue;
1298  unsigned short plane = planeID.Plane;
1299  if (plane > 2) continue;
1300  vIndex[plane].push_back(ivx);
1301  }
1302 
1303  unsigned short vtxInPln = 0;
1304  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane)
1305  if (vIndex[plane].size() > 0) ++vtxInPln;
1306  if (vtxInPln < 2) return;
1307 
1308  float thirdPlanedXCut = 2 * tcc.vtx3DCuts[0];
1309  bool prt = (tcc.dbg3V && tcc.dbgSlc);
1310  if (prt) {
1311  mf::LogVerbatim("TC") << "Inside Find3DVertices. dX cut " << tcc.vtx3DCuts[0]
1312  << " thirdPlanedXCut " << thirdPlanedXCut;
1313  }
1314 
1315  size_t vsize = slc.vtxs.size();
1316  // vector of 2D vertices -> 3D vertices.
1317  std::vector<short> vPtr(vsize, -1);
1318  // fill temp vectors of 2D vertex X and X errors
1319  std::vector<float> vX(vsize, FLT_MAX);
1320 
1321  for (unsigned short ivx = 0; ivx < vsize; ++ivx) {
1322  if (slc.vtxs[ivx].ID <= 0) continue;
1323  if (slc.vtxs[ivx].Score < tcc.vtx2DCuts[7]) continue;
1324  if (slc.vtxs[ivx].Pos[0] < -0.4) continue;
1325  geo::PlaneID planeID = DecodeCTP(slc.vtxs[ivx].CTP);
1326  // Convert 2D vertex time error to X error
1327  double ticks = slc.vtxs[ivx].Pos[1] / tcc.unitsPerTick;
1328  vX[ivx] = detProp.ConvertTicksToX(ticks, planeID);
1329  } // ivx
1330 
1331  // temp vector of all 2D vertex matches
1332  std::vector<Vtx3Store> v3temp;
1333 
1334  unsigned int cstat = slc.TPCID.Cryostat;
1335  unsigned int tpc = slc.TPCID.TPC;
1336 
1337  TrajPoint tp;
1338  constexpr float maxSep = 4;
1339  float maxScore = 0;
1340  // i, j, k indicates 3 different wire planes
1341  // compare vertices in each view
1342  for (unsigned short ipl = 0; ipl < 2; ++ipl) {
1343  for (unsigned short ii = 0; ii < vIndex[ipl].size(); ++ii) {
1344  unsigned short ivx = vIndex[ipl][ii];
1345  if (vX[ivx] == FLT_MAX) continue;
1346  auto& ivx2 = slc.vtxs[ivx];
1347  if (ivx2.Pos[0] < -0.4) continue;
1348  unsigned int iWire = std::nearbyint(ivx2.Pos[0]);
1349  for (unsigned short jpl = ipl + 1; jpl < 3; ++jpl) {
1350  for (unsigned short jj = 0; jj < vIndex[jpl].size(); ++jj) {
1351  unsigned short jvx = vIndex[jpl][jj];
1352  if (vX[jvx] == FLT_MAX) continue;
1353  auto& jvx2 = slc.vtxs[jvx];
1354  if (jvx2.Pos[0] < -0.4) continue;
1355  unsigned int jWire = std::nearbyint(jvx2.Pos[0]);
1356  float dX = std::abs(vX[ivx] - vX[jvx]);
1357  if (dX > tcc.vtx3DCuts[0]) continue;
1358  if (prt) {
1359  mf::LogVerbatim("TC")
1360  << "F3DV: ipl " << ipl << " i2V" << ivx2.ID << " iX " << vX[ivx] << " jpl " << jpl
1361  << " j2V" << jvx2.ID << " jvX " << vX[jvx] << " W:T " << (int)jvx2.Pos[0] << ":"
1362  << (int)jvx2.Pos[1] << " dX " << dX;
1363  }
1364  double y = -1000, z = -1000;
1365  tcc.geom->IntersectionPoint(iWire, jWire, ipl, jpl, cstat, tpc, y, z);
1366  if (y < slc.yLo || y > slc.yHi || z < slc.zLo || z > slc.zHi) continue;
1367  unsigned short kpl = 3 - ipl - jpl;
1368  float kX = 0.5 * (vX[ivx] + vX[jvx]);
1369  int kWire = -1;
1370  if (slc.nPlanes > 2) {
1371  kWire = tcc.geom->WireCoordinate(y, z, kpl, tpc, cstat);
1372  std::array<int, 2> wireWindow;
1373  std::array<float, 2> timeWindow;
1374  wireWindow[0] = kWire - maxSep;
1375  wireWindow[1] = kWire + maxSep;
1376  float time =
1377  detProp.ConvertXToTicks(kX, kpl, (int)tpc, (int)cstat) * tcc.unitsPerTick;
1378  timeWindow[0] = time - maxSep;
1379  timeWindow[1] = time + maxSep;
1380  bool hitsNear;
1381  std::vector<unsigned int> closeHits =
1382  FindCloseHits(slc, wireWindow, timeWindow, kpl, kAllHits, true, hitsNear);
1383  if (prt) {
1384  mf::LogVerbatim myprt("TC");
1385  myprt << " Hits near " << kpl << ":" << kWire << ":"
1386  << (int)(time / tcc.unitsPerTick) << " = ";
1387  for (auto iht : closeHits)
1388  myprt << " " << PrintHit(slc.slHits[iht]);
1389  }
1390  if (!hitsNear) continue;
1391  } // 3-plane TPC
1392  // save this incomplete 3D vertex
1393  Vtx3Store v3d;
1394  // give it a non-zero ID so that SetVx3Score returns a valid score
1395  v3d.ID = 666;
1396  v3d.Vx2ID.resize(slc.nPlanes);
1397  v3d.Vx2ID[ipl] = ivx2.ID;
1398  v3d.Vx2ID[jpl] = jvx2.ID;
1399  if (slc.nPlanes == 2) v3d.Vx2ID[2] = -1;
1400  v3d.X = kX;
1401  // Use XErr to store dX
1402  v3d.XErr = dX;
1403  v3d.Y = y;
1404  v3d.Z = z;
1405  v3d.Wire = kWire;
1406  float posError = dX / tcc.vtx3DCuts[0];
1407  float vxScoreWght = 0;
1408  SetVx3Score(slc, v3d);
1409  vxScoreWght = tcc.vtx3DCuts[2] / v3d.Score;
1410  if (posError < 0.5) posError = 0;
1411  v3d.Score = posError + vxScoreWght;
1412  v3d.TPCID = slc.TPCID;
1413  // push the incomplete vertex onto the list
1414  v3temp.push_back(v3d);
1415 
1416  if (prt) {
1417  mf::LogVerbatim myprt("TC");
1418  myprt << " 2 Plane match i2V";
1419  myprt << slc.vtxs[ivx].ID << " P:W:T " << ipl << ":" << (int)slc.vtxs[ivx].Pos[0]
1420  << ":" << (int)slc.vtxs[ivx].Pos[1];
1421  myprt << " j2V" << slc.vtxs[jvx].ID << " P:W:T " << jpl << ":"
1422  << (int)slc.vtxs[jvx].Pos[0] << ":" << (int)slc.vtxs[jvx].Pos[1];
1423  myprt << std::fixed << std::setprecision(3);
1424  myprt << " dX " << dX << " posError " << posError << " vxScoreWght " << vxScoreWght
1425  << " Score " << v3d.Score;
1426  }
1427 
1428  if (slc.nPlanes == 2) continue;
1429 
1430  // look for a 3 plane match
1431  for (unsigned short kk = 0; kk < vIndex[kpl].size(); ++kk) {
1432  unsigned short kvx = vIndex[kpl][kk];
1433  float dX = std::abs(vX[kvx] - v3d.X);
1434  // Wire difference error
1435  float dW = tcc.wirePitch * std::abs(slc.vtxs[kvx].Pos[0] - kWire);
1436  if (dX > thirdPlanedXCut) continue;
1437  if (dW > tcc.vtx3DCuts[1]) continue;
1438  // put the Y,Z difference in YErr and ZErr
1439  double y = -1000, z = -1000;
1440  tcc.geom->IntersectionPoint(iWire, kWire, ipl, kpl, cstat, tpc, y, z);
1441  v3d.YErr = y - v3d.Y;
1442  v3d.ZErr = z - v3d.Z;
1443  v3d.Vx2ID[kpl] = slc.vtxs[kvx].ID;
1444  v3d.Wire = -1;
1445  // hijack the Score variable to hold the separation^2, weighted by the
1446  // vertex3DCuts
1447  dX = (vX[kvx] - v3d.X) / tcc.vtx3DCuts[0];
1448  float dY = v3d.YErr / tcc.vtx3DCuts[1];
1449  float dZ = v3d.ZErr / tcc.vtx3DCuts[1];
1450  posError = dX * dX + dY * dY + dZ * dZ;
1451  vxScoreWght = 0;
1452  SetVx3Score(slc, v3d);
1453  vxScoreWght = tcc.vtx3DCuts[2] / v3d.Score;
1454  if (posError < 0.5) posError = 0;
1455  v3d.Score = posError + vxScoreWght;
1456  if (v3d.Score > maxScore) maxScore = v3d.Score;
1457  if (prt)
1458  mf::LogVerbatim("TC") << " k2V" << kvx + 1 << " dX " << dX << " dW " << dW
1459  << " 3D score " << v3d.Score;
1460  v3temp.push_back(v3d);
1461  } // kk
1462  } // jj
1463  } // jpl
1464  } // ii
1465  } // ipl
1466 
1467  if (v3temp.empty()) return;
1468 
1469  // We will sort this list by increasing score. First add the maxScore for 2-plane matches so that
1470  // they are considered after the 3-plane matches
1471  maxScore += 1;
1472  for (auto& v3 : v3temp)
1473  if (v3.Wire >= 0) v3.Score += maxScore;
1474 
1475  if (prt) {
1476  mf::LogVerbatim("TC") << "v3temp list";
1477  for (auto& v3 : v3temp) {
1478  if (slc.nPlanes == 2) {
1479  mf::LogVerbatim("TC") << "2V" << v3.Vx2ID[0] << " 2V" << v3.Vx2ID[1] << " wire "
1480  << v3.Wire << " Score " << v3.Score;
1481  }
1482  else {
1483  mf::LogVerbatim("TC") << "2V" << v3.Vx2ID[0] << " 2V" << v3.Vx2ID[1] << " 2V"
1484  << v3.Vx2ID[2] << " wire " << v3.Wire << " Score " << v3.Score;
1485  }
1486  } // v3
1487  }
1488  SortEntry sEntry;
1489  std::vector<SortEntry> sortVec(v3temp.size());
1490  for (unsigned short ivx = 0; ivx < v3temp.size(); ++ivx) {
1491  sEntry.index = ivx;
1492  sEntry.val = v3temp[ivx].Score;
1493  sortVec[ivx] = sEntry;
1494  } // ivx
1495  if (sortVec.size() > 1) std::sort(sortVec.begin(), sortVec.end(), valIncreasing);
1496  // create a new vector of selected 3D vertices
1497  std::vector<Vtx3Store> v3sel;
1498  for (unsigned short ii = 0; ii < sortVec.size(); ++ii) {
1499  unsigned short ivx = sortVec[ii].index;
1500  // ensure that all 2D vertices are unique
1501  bool skipit = false;
1502  for (auto& v3 : v3sel) {
1503  for (unsigned short ipl = 0; ipl < slc.nPlanes; ++ipl) {
1504  if (v3temp[ivx].Vx2ID[ipl] == 0) continue;
1505  if (v3temp[ivx].Vx2ID[ipl] == v3.Vx2ID[ipl]) {
1506  skipit = true;
1507  break;
1508  }
1509  } // ipl
1510  if (skipit) break;
1511  } // v3
1512  if (skipit) continue;
1513  v3sel.push_back(v3temp[ivx]);
1514  } // ii
1515  v3temp.clear();
1516 
1517  if (prt) {
1518  mf::LogVerbatim myprt("TC");
1519  myprt << "v3sel list\n";
1520  for (auto& v3d : v3sel) {
1521  for (auto vx2id : v3d.Vx2ID)
1522  if (vx2id > 0) myprt << " 2V" << vx2id;
1523  myprt << " wire " << v3d.Wire << " Score " << v3d.Score;
1524  myprt << "\n";
1525  } // v3d
1526  } // prt
1527 
1528  // Count the number of incomplete vertices and store
1529  unsigned short ninc = 0;
1530  for (auto& vx3 : v3sel) {
1531  if (slc.nPlanes == 3 && vx3.Wire >= 0) ++ninc;
1532  vx3.ID = slc.vtx3s.size() + 1;
1533  ++evt.global3V_UID;
1534  vx3.UID = evt.global3V_UID;
1535  if (prt) {
1536  mf::LogVerbatim myprt("TC");
1537  myprt << " 3V" << vx3.ID;
1538  for (auto v2id : vx3.Vx2ID)
1539  myprt << " 2V" << v2id;
1540  myprt << " wire " << vx3.Wire;
1541  } // prt
1542  slc.vtx3s.push_back(vx3);
1543  // make the 2D -> 3D associations
1544  for (unsigned short ipl = 0; ipl < slc.nPlanes; ++ipl) {
1545  if (vx3.Vx2ID[ipl] == 0) continue;
1546  VtxStore& vx2 = slc.vtxs[vx3.Vx2ID[ipl] - 1];
1547  vx2.Vx3ID = vx3.ID;
1548  } // ipl
1549  } // ivx
1550 
1551  // Try to complete incomplete vertices
1552  if (ninc > 0) {
1553  CompleteIncomplete3DVerticesInGaps(detProp, slc);
1554  CompleteIncomplete3DVertices(detProp, slc);
1555  }
1556 
1557  // Score and flag Tjs that are attached to high-score vertices
1558  // First remove Tj vertex flags
1559  for (auto& tj : slc.tjs) {
1560  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
1561  geo::PlaneID planeID = DecodeCTP(tj.CTP);
1562  if (planeID.TPC != tpc || planeID.Cryostat != cstat) continue;
1563  tj.AlgMod[kTjHiVx3Score] = false;
1564  } // tj
1565  // Score the 2D vertices
1566  for (auto& vx2 : slc.vtxs) {
1567  if (vx2.ID == 0) continue;
1568  geo::PlaneID planeID = DecodeCTP(vx2.CTP);
1569  if (planeID.TPC != tpc || planeID.Cryostat != cstat) continue;
1570  SetVx2Score(slc, vx2);
1571  } // vx2
1572  // and the 3D vertices
1573  for (auto& vx3 : slc.vtx3s) {
1574  if (vx3.ID == 0) continue;
1575  SetVx3Score(slc, vx3);
1576  } // vx3
1577 
1578  } // Find3DVertices
1579 
1580  //////////////////////////////////////////
1581  unsigned short
1582  TPNearVertex(const TCSlice& slc, const TrajPoint& tp)
1583  {
1584  // Returns the index of a vertex if tp is nearby
1585  for (unsigned short ivx = 0; ivx < slc.vtxs.size(); ++ivx) {
1586  if (slc.vtxs[ivx].ID == 0) continue;
1587  if (slc.vtxs[ivx].CTP != tp.CTP) continue;
1588  if (std::abs(slc.vtxs[ivx].Pos[0] - tp.Pos[0]) > 1.2) continue;
1589  if (std::abs(slc.vtxs[ivx].Pos[1] - tp.Pos[1]) > 1.2) continue;
1590  return ivx;
1591  } // ivx
1592  return USHRT_MAX;
1593  } // TPNearVertex
1594 
1595  //////////////////////////////////////////
1596  bool
1597  AttachToAnyVertex(TCSlice& slc, PFPStruct& pfp, float maxSep, bool prt)
1598  {
1599  // Attaches to any 3D vertex but doesn't require consistency with
1600  // PFP -> Tj -> 2V -> 3V assns
1601  if (pfp.ID <= 0) return false;
1602 
1603  float pLen = Length(pfp);
1604  if (pLen == 0) return false;
1605 
1606  // save the old assignents and clear them
1607  // auto oldVx3ID = pfp.Vx3ID;
1608  for (unsigned short end = 0; end < 2; ++end)
1609  pfp.Vx3ID[end] = 0;
1610  std::array<Point3_t, 2> endPos;
1611  endPos[0] = PosAtEnd(pfp, 0);
1612  endPos[1] = PosAtEnd(pfp, 1);
1613 
1614  std::array<float, 2> foms{{100., 100.}};
1615  std::array<int, 2> vtxs{{0}};
1616  for (auto& vx3 : slc.vtx3s) {
1617  if (vx3.ID <= 0) continue;
1618  if (vx3.TPCID != pfp.TPCID) continue;
1619  std::array<float, 2> sep;
1620  Point3_t vpos = {{vx3.X, vx3.Y, vx3.Z}};
1621  sep[0] = PosSep(vpos, endPos[0]);
1622  sep[1] = PosSep(vpos, endPos[1]);
1623  unsigned short end = 0;
1624  if (sep[1] < sep[0]) end = 1;
1625  // ignore if separation is too large
1626  if (sep[end] > 100) continue;
1627  // find the direction vector between these points
1628  auto vpDir = PointDirection(vpos, endPos[end]);
1629  auto dir = DirAtEnd(pfp, end);
1630  double dotp = std::abs(DotProd(vpDir, dir));
1631  float fom = dotp * sep[end];
1632  if (prt)
1633  mf::LogVerbatim("TC") << "ATAV: P" << pfp.ID << " end " << end << " 3V" << vx3.ID << " sep "
1634  << sep[end] << " fom " << fom << " maxSep " << maxSep;
1635  // ignore if separation is too large
1636  if (sep[end] > maxSep) continue;
1637  if (fom < foms[end]) {
1638  foms[end] = fom;
1639  vtxs[end] = vx3.ID;
1640  }
1641  } // vx3
1642  bool bingo = false;
1643  for (unsigned short end = 0; end < 2; ++end) {
1644  if (vtxs[end] == 0) continue;
1645  if (prt)
1646  mf::LogVerbatim("TC") << "ATAV: set P" << pfp.ID << " end " << end << " -> 3V" << vtxs[end];
1647  pfp.Vx3ID[end] = vtxs[end];
1648  bingo = true;
1649  } // end
1650  return bingo;
1651  } // AttachToAnyVertex
1652 
1653  //////////////////////////////////////////
1654  bool
1655  AttachAnyVertexToTraj(TCSlice& slc, int tjID, bool prt)
1656  {
1657  // Try to attach a tj that is stored in slc.tjs with any vertex
1658  if (tjID <= 0 || tjID > (int)slc.tjs.size()) return false;
1659  if (slc.vtxs.empty()) return false;
1660  auto& tj = slc.tjs[tjID - 1];
1661  if (tj.AlgMod[kKilled]) return false;
1662  if (tcc.vtx2DCuts[0] <= 0) return false;
1663 
1664  unsigned short bestVx = USHRT_MAX;
1665  // Construct a FOM = (TP-Vtx pull) * (TP-Vtx sep + 1) * (Vtx Score).
1666  // The +1 keeps FOM from being 0
1667  float bestFOM = 2 * tcc.vtx2DCuts[3] * (tcc.vtx2DCuts[0] + 1) * tcc.vtx2DCuts[7];
1668  for (unsigned short ivx = 0; ivx < slc.vtxs.size(); ++ivx) {
1669  auto& vx = slc.vtxs[ivx];
1670  if (vx.ID == 0) continue;
1671  if (vx.CTP != tj.CTP) continue;
1672  // make some rough cuts
1673  std::array<float, 2> sep;
1674  sep[0] = PosSep(vx.Pos, tj.Pts[tj.EndPt[0]].Pos);
1675  sep[1] = PosSep(vx.Pos, tj.Pts[tj.EndPt[1]].Pos);
1676  unsigned short end = 0;
1677  if (sep[1] < sep[0]) end = 1;
1678  if (sep[end] > 100) continue;
1679  if (tj.VtxID[end] > 0) continue;
1680  auto& tp = tj.Pts[tj.EndPt[end]];
1681  // Pad the separation a bit so we don't get zero
1682  float fom = TrajPointVertexPull(slc, tp, vx) * (sep[end] + 1) * vx.Score;
1683  if (fom > bestFOM) continue;
1684  if (prt)
1685  mf::LogVerbatim("TC") << "AAVTT: T" << tjID << " 2V" << vx.ID << " FOM " << fom << " cut "
1686  << bestFOM;
1687  bestVx = ivx;
1688  bestFOM = fom;
1689  } // vx
1690  if (bestVx > slc.vtxs.size() - 1) return false;
1691  auto& vx = slc.vtxs[bestVx];
1692  return AttachTrajToVertex(slc, tj, vx, prt);
1693  } // AttachAnyVertexToTraj
1694 
1695  //////////////////////////////////////////
1696  bool
1697  AttachAnyTrajToVertex(TCSlice& slc, unsigned short ivx, bool prt)
1698  {
1699 
1700  if (ivx > slc.vtxs.size() - 1) return false;
1701  if (slc.vtxs[ivx].ID == 0) return false;
1702  if (tcc.vtx2DCuts[0] < 0) return false;
1703 
1704  VtxStore& vx = slc.vtxs[ivx];
1705  // Hammer vertices should be isolated and clean
1706  if (vx.Topo == 5 || vx.Topo == 6) return false;
1707 
1708  unsigned short bestTj = USHRT_MAX;
1709  // Construct a FOM = (TP-Vtx pull) * (TP-Vtx sep + 1).
1710  // The +1 keeps FOM from being 0
1711  float bestFOM = 2 * tcc.vtx2DCuts[3] * (tcc.vtx2DCuts[0] + 1);
1712  for (unsigned int itj = 0; itj < slc.tjs.size(); ++itj) {
1713  auto& tj = slc.tjs[itj];
1714  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
1715  if (tj.CTP != vx.CTP) continue;
1716  // make some rough cuts
1717  std::array<float, 2> sep;
1718  sep[0] = PosSep(vx.Pos, tj.Pts[tj.EndPt[0]].Pos);
1719  sep[1] = PosSep(vx.Pos, tj.Pts[tj.EndPt[1]].Pos);
1720  unsigned short end = 0;
1721  if (sep[1] < sep[0]) end = 1;
1722  if (sep[end] > 100) continue;
1723  if (tj.VtxID[end] > 0) continue;
1724  auto& tp = tj.Pts[tj.EndPt[end]];
1725  // Pad the separation a bit so we don't get zero
1726  float fom = TrajPointVertexPull(slc, tp, vx) * (sep[end] + 1);
1727  if (fom > bestFOM) continue;
1728  if (prt) {
1729  mf::LogVerbatim("TC") << "AATTV: T" << tj.ID << " 2V" << vx.ID << " Topo " << vx.Topo
1730  << " FOM " << fom << " cut " << bestFOM;
1731  }
1732  bestTj = itj;
1733  bestFOM = fom;
1734  } // tj
1735  if (bestTj > slc.tjs.size() - 1) return false;
1736  auto& tj = slc.tjs[bestTj];
1737  return AttachTrajToVertex(slc, tj, vx, prt);
1738  } // AttachAnyTrajToVertex
1739 
1740  //////////////////////////////////////////
1741  bool
1742  AttachTrajToVertex(TCSlice& slc, Trajectory& tj, VtxStore& vx, bool prt)
1743  {
1744  // Note that this function does not require a signal between the end of the Tj and the vertex
1745 
1746  // tcc.vtx2DCuts fcl input usage
1747  // 0 = maximum length of a short trajectory
1748  // 1 = max vertex - trajectory separation for short trajectories
1749  // 2 = max vertex - trajectory separation for long trajectories
1750  // 3 = max position pull for adding TJs to a vertex
1751  // 4 = max allowed vertex position error
1752  // 5 = min MCSMom
1753  // 6 = min Pts/Wire fraction
1754 
1755  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) return false;
1756  if (tj.CTP != vx.CTP) return false;
1757  // already attached?
1758  if (tj.VtxID[0] == vx.ID || tj.VtxID[1] == vx.ID) return false;
1759 
1760  unsigned short maxShortTjLen = tcc.vtx2DCuts[0];
1761  // square the separation cut to simplify testing in the loop
1762  float maxSepCutShort2 = tcc.vtx2DCuts[1] * tcc.vtx2DCuts[1];
1763  float maxSepCutLong2 = tcc.vtx2DCuts[2] * tcc.vtx2DCuts[2];
1764 
1765  // assume that end 0 is closest to the vertex
1766  unsigned short end = 0;
1767  float vtxTjSep2 = PosSep2(vx.Pos, tj.Pts[tj.EndPt[0]].Pos);
1768  float sep1 = PosSep2(vx.Pos, tj.Pts[tj.EndPt[1]].Pos);
1769  if (sep1 < vtxTjSep2) {
1770  // End 1 is closer
1771  end = 1;
1772  vtxTjSep2 = sep1;
1773  }
1774  // is there a vertex already assigned to this end?
1775  if (tj.VtxID[end] > 0) return false;
1776 
1777  // is the trajectory short?
1778  bool tjShort = (tj.EndPt[1] - tj.EndPt[0] < maxShortTjLen);
1779  // use the short Tj cut if the trajectory looks like an electron
1780  if (!tjShort && tj.ChgRMS > 0.5) tjShort = true;
1781  float closestApproach;
1782  // ignore bad separation between the closest tj end and the vertex
1783  if (tjShort) {
1784  if (vtxTjSep2 > maxSepCutShort2) return false;
1785  closestApproach = tcc.vtx2DCuts[1];
1786  }
1787  else {
1788  closestApproach = tcc.vtx2DCuts[2];
1789  if (vtxTjSep2 > maxSepCutLong2) return false;
1790  }
1791 
1792  // Calculate the pull on the vertex
1793  TrajPoint& tp = tj.Pts[tj.EndPt[end]];
1794  float tpVxPull = TrajPointVertexPull(slc, tp, vx);
1795  bool signalBetween = SignalBetween(slc, tp, vx.Pos[0], 0.8);
1796 
1797  // See if the vertex position is close to an end
1798  unsigned short closePt;
1799  TrajClosestApproach(tj, vx.Pos[0], vx.Pos[1], closePt, closestApproach);
1800  // count the number of points between the end of the trajectory and the vertex.
1801  // tj ------------- tj ------------
1802  // vx * >> dpt = 0 vx * >> dpt = 2
1803  short dpt;
1804  if (end == 0) { dpt = closePt - tj.EndPt[end]; }
1805  else {
1806  dpt = tj.EndPt[end] - closePt;
1807  }
1808 
1809  float length = TrajLength(tj);
1810  // don't attach it if the tj length is shorter than the separation distance
1811  if (length > 4 && length < closestApproach) return false;
1812 
1813  float pullCut = tcc.vtx2DCuts[3];
1814  // Dec 21, 2017 Loosen up the pull cut for short close slc. These are likely to
1815  // be poorly reconstructed. It is better to have them associated with the vertex
1816  // than not.
1817  if (tjShort) pullCut *= 2;
1818 
1819  if (prt) {
1820  mf::LogVerbatim myprt("TC");
1821  myprt << "ATTV: 2V" << vx.ID;
1822  myprt << " NTraj " << vx.NTraj;
1823  myprt << " oldTJs";
1824  for (unsigned short itj = 0; itj < slc.tjs.size(); ++itj) {
1825  Trajectory& tj = slc.tjs[itj];
1826  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
1827  if (tj.CTP != vx.CTP) continue;
1828  if (tj.VtxID[0] == vx.ID) myprt << " T" << tj.ID << "_0";
1829  if (tj.VtxID[1] == vx.ID) myprt << " T" << tj.ID << "_1";
1830  }
1831  myprt << " + T" << tj.ID << "_" << end << " vtxTjSep " << sqrt(vtxTjSep2) << " tpVxPull "
1832  << tpVxPull << " pullCut " << pullCut << " dpt " << dpt;
1833  }
1834  if (tpVxPull > pullCut) return false;
1835  if (dpt > 2) return true;
1836 
1837  // remove the fixed position flag if there are more than 2 tjs
1838  bool fixedBit = vx.Stat[kFixed];
1839  if (fixedBit && vx.NTraj < 2) vx.Stat[kFixed] = false;
1840 
1841  // Passed all the cuts. Attach it to the vertex and try a fit
1842  tj.VtxID[end] = vx.ID;
1843  // flag as a photon Tj so it isn't included in the fit
1844  tj.AlgMod[kPhoton] = !signalBetween;
1845  // make a copy of the vertex and fit it
1846  auto vxTmp = vx;
1847  if (FitVertex(slc, vxTmp, prt)) {
1848  SetVx2Score(slc, vxTmp);
1849  if (prt) mf::LogVerbatim("TC") << " Success";
1850  vx = vxTmp;
1851  return true;
1852  }
1853  // Keep the Tj -> Vx assn since we got this far, but don't include this end of the Tj in the fit
1854  tj.EndFlag[end][kNoFitVx] = true;
1855  if (prt)
1856  mf::LogVerbatim("TC") << " Poor fit. Keep T" << tj.ID << "-2V" << vx.ID
1857  << " assn with kNoFitVx";
1858  // restore the fixed flag
1859  vx.Stat[kFixed] = fixedBit;
1860  return true;
1861  } // AttachTrajToVertex
1862 
1863  /////////////////////////////////////////
1864  float
1865  TrajPointVertexPull(const TCSlice& slc, const TrajPoint& tp, const VtxStore& vx)
1866  {
1867  // Calculates the position pull between a trajectory point and a vertex
1868 
1869  // impact parameter between them
1870  double ip = PointTrajDOCA(slc, vx.Pos[0], vx.Pos[1], tp);
1871  // separation^2
1872  double sep2 = PosSep2(vx.Pos, tp.Pos);
1873 
1874  // Find the projection of the vertex error ellipse in a coordinate system
1875  // along the TP direction
1876  double vxErrW = vx.PosErr[0] * tp.Dir[1];
1877  double vxErrT = vx.PosErr[1] * tp.Dir[0];
1878  double vxErr2 = vxErrW * vxErrW + vxErrT * vxErrT;
1879  // add the TP position error^2
1880  vxErr2 += tp.HitPosErr2;
1881 
1882  // close together so ignore the TP projection error and return
1883  // the pull using the vertex error and TP position error
1884  if (sep2 < 1) return (float)(ip / sqrt(vxErr2));
1885 
1886  double dang = ip / sqrt(sep2);
1887 
1888  // calculate the angle error.
1889  // Start with the vertex error^2
1890  double angErr = vxErr2 / sep2;
1891  // Add the TP angle error^2
1892  angErr += tp.AngErr * tp.AngErr;
1893  if (angErr == 0) return 999;
1894  angErr = sqrt(angErr);
1895  return (float)(dang / angErr);
1896 
1897  } // TrajPointVertexPull
1898 
1899  /////////////////////////////////////////
1900  float
1901  VertexVertexPull(const TCSlice& slc, const Vtx3Store& vx1, const Vtx3Store& vx2)
1902  {
1903  // Calculates the position pull between two vertices
1904  double dx = vx1.X - vx2.X;
1905  double dy = vx1.Y - vx2.Y;
1906  double dz = vx1.Z - vx2.Z;
1907  double dxErr2 = (vx1.XErr * vx1.XErr + vx2.XErr * vx2.XErr) / 2;
1908  double dyErr2 = (vx1.YErr * vx1.YErr + vx2.YErr * vx2.YErr) / 2;
1909  double dzErr2 = (vx1.ZErr * vx1.ZErr + vx2.ZErr * vx2.ZErr) / 2;
1910  dx = dx * dx / dxErr2;
1911  dy = dy * dy / dyErr2;
1912  dz = dz * dz / dzErr2;
1913  return (float)(sqrt(dx + dy + dz) / 3);
1914  }
1915 
1916  /////////////////////////////////////////
1917  float
1918  VertexVertexPull(const TCSlice& slc, const VtxStore& vx1, const VtxStore& vx2)
1919  {
1920  // Calculates the position pull between two vertices
1921  double dw = vx1.Pos[0] - vx2.Pos[0];
1922  double dt = vx1.Pos[1] - vx2.Pos[1];
1923  double dwErr2 = (vx1.PosErr[0] * vx1.PosErr[0] + vx2.PosErr[0] * vx2.PosErr[0]) / 2;
1924  double dtErr2 = (vx1.PosErr[1] * vx1.PosErr[1] + vx2.PosErr[1] * vx2.PosErr[1]) / 2;
1925  dw = dw * dw / dwErr2;
1926  dt = dt * dt / dtErr2;
1927  return (float)sqrt(dw + dt);
1928  }
1929 
1930  ////////////////////////////////////////////////
1931  bool
1933  {
1934  // jacket around the push to ensure that the Tj and vtx CTP is consistent.
1935  // The calling function should score the vertex after the trajectories are attached
1936 
1937  if (vx.ID != int(slc.vtxs.size() + 1)) return false;
1938 
1939  ++evt.global2V_UID;
1940  vx.UID = evt.global2V_UID;
1941 
1942  unsigned short nvxtj = 0;
1943  unsigned short nok = 0;
1944  for (auto& tj : slc.tjs) {
1945  if (tj.AlgMod[kKilled]) continue;
1946  if (vx.ID == tj.VtxID[0] || vx.ID == tj.VtxID[1]) ++nvxtj;
1947  if (vx.CTP != tj.CTP) continue;
1948  if (vx.ID == tj.VtxID[0] || vx.ID == tj.VtxID[1]) ++nok;
1949  } // tj
1950 
1951  if (nok != nvxtj) {
1952  mf::LogVerbatim("TC") << "StoreVertex: vertex " << vx.ID << " Topo " << vx.Topo
1953  << " has inconsistent CTP code " << vx.CTP << " with one or more Tjs\n";
1954  for (auto& tj : slc.tjs) {
1955  if (tj.AlgMod[kKilled]) continue;
1956  if (tj.VtxID[0] == vx.ID) tj.VtxID[0] = 0;
1957  if (tj.VtxID[1] == vx.ID) tj.VtxID[1] = 0;
1958  }
1959  return false;
1960  }
1961  vx.NTraj = nok;
1962  slc.vtxs.push_back(vx);
1963  return true;
1964  } // StoreVertex
1965 
1966  /////////////////////////////////////////
1967  bool
1968  FitVertex(TCSlice& slc, VtxStore& vx, bool prt)
1969  {
1970  // Fit the vertex using T -> 2V assns
1971 
1972  // tcc.vtx2DCuts fcl input usage
1973  // 0 = maximum length of a short trajectory
1974  // 1 = max vertex - trajectory separation for short trajectories
1975  // 2 = max vertex - trajectory separation for long trajectories
1976  // 3 = max position pull for adding TJs to a vertex
1977  // 4 = max allowed vertex position error
1978  // 5 = min MCSMom
1979  // 6 = min Pts/Wire fraction
1980 
1981  if (vx.Stat[kFixed]) {
1982  if (prt) mf::LogVerbatim("TC") << " vertex position fixed. No fit allowed";
1983  return true;
1984  }
1985 
1986  // Create a vector of trajectory points that will be used to fit the vertex position
1987  std::vector<TrajPoint> vxTp;
1988  for (auto& tj : slc.tjs) {
1989  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
1990  if (tj.CTP != vx.CTP) continue;
1991  if (tj.AlgMod[kPhoton]) continue;
1992  bool added = false;
1993  if (tj.VtxID[0] == vx.ID && !tj.EndFlag[0][kNoFitVx]) {
1994  vxTp.push_back(tj.Pts[tj.EndPt[0]]);
1995  added = true;
1996  }
1997  if (tj.VtxID[1] == vx.ID && !tj.EndFlag[1][kNoFitVx]) {
1998  vxTp.push_back(tj.Pts[tj.EndPt[1]]);
1999  added = true;
2000  }
2001  // stash the ID in Step to help debugging
2002  if (added) {
2003  auto& tp = vxTp[vxTp.size() - 1];
2004  if (tj.ID > 0) tp.Step = (int)tj.ID;
2005  // inflate the angle errors for Tjs with few fitted points
2006  if (tp.NTPsFit < 4) tp.AngErr *= 4;
2007  }
2008  } // tj
2009 
2010  bool success = FitVertex(slc, vx, vxTp, prt);
2011 
2012  if (!success) return false;
2013  return true;
2014  } // FitVertex
2015 
2016  /////////////////////////////////////////
2017  bool
2018  FitVertex(TCSlice& slc, VtxStore& vx, std::vector<TrajPoint>& vxTPs, bool prt)
2019  {
2020  // Version with LSQ fit. Each TP position (P0,P1) and slope S are fit to a vertex
2021  // at position (V0, V1), using the equation P1 = V1 + (P0 - V0) * S. This is put
2022  // in the form A * V = b. V is found using V = (A^T * A)^-1 * A^T * b. This is
2023  // usually done using the TDecompSVD Solve method but here we do it step-wise to
2024  // get access to the covariance matrix (A^T * A)^-1. The pull between the TP position
2025  // and the vertex position is stored in tp.Delta
2026 
2027  if (vxTPs.size() < 2) return false;
2028  if (vxTPs.size() == 2) {
2029  vx.ChiDOF = 0.;
2030  return true;
2031  }
2032 
2033  unsigned short npts = vxTPs.size();
2034  TMatrixD A(npts, 2);
2035  TVectorD b(npts);
2036  for (unsigned short itj = 0; itj < vxTPs.size(); ++itj) {
2037  auto& tp = vxTPs[itj];
2038  double dtdw = tp.Dir[1] / tp.Dir[0];
2039  double wt = 1 / (tp.AngErr * tp.AngErr);
2040  A(itj, 0) = -dtdw * wt;
2041  A(itj, 1) = 1. * wt;
2042  b(itj) = (tp.Pos[1] - tp.Pos[0] * dtdw) * wt;
2043  } // itj
2044 
2045  TMatrixD AT(2, npts);
2046  AT.Transpose(A);
2047  TMatrixD ATA = AT * A;
2048  double* det = 0;
2049  try {
2050  ATA.Invert(det);
2051  }
2052  catch (...) {
2053  return false;
2054  }
2055  if (det == NULL) return false;
2056  TVectorD vxPos = ATA * AT * b;
2057  vx.PosErr[0] = sqrt(ATA[0][0]);
2058  vx.PosErr[1] = sqrt(ATA[1][1]);
2059  vx.Pos[0] = vxPos[0];
2060  vx.Pos[1] = vxPos[1];
2061 
2062  // Calculate Chisq
2063  vx.ChiDOF = 0;
2064  if (vxTPs.size() > 2) {
2065  for (auto& tp : vxTPs) {
2066  // highjack TP Delta for the vertex pull
2067  tp.Delta = TrajPointVertexPull(slc, tp, vx);
2068  vx.ChiDOF += tp.Delta;
2069  } // itj
2070  vx.ChiDOF /= (float)(vxTPs.size() - 2);
2071  } // vxTPs.size() > 2
2072 
2073  if (prt) {
2074  mf::LogVerbatim("TC") << "Note: TP - 2V pull is stored in TP.Delta";
2075  PrintTPHeader("FV");
2076  for (auto& tp : vxTPs)
2077  PrintTP("FV", slc, 0, 1, 1, tp);
2078  }
2079 
2080  return true;
2081  } // FitVertex
2082 
2083  //////////////////////////////////////////
2084  bool
2085  ChkVtxAssociations(TCSlice& slc, const CTP_t& inCTP)
2086  {
2087  // Check the associations
2088 
2089  // check the 2D -> 3D associations
2090  geo::PlaneID planeID = DecodeCTP(inCTP);
2091  unsigned short plane = planeID.Plane;
2092  for (auto& vx2 : slc.vtxs) {
2093  if (vx2.CTP != inCTP) continue;
2094  if (vx2.ID == 0) continue;
2095  if (vx2.Vx3ID == 0) continue;
2096  if (vx2.Vx3ID > int(slc.vtx3s.size())) {
2097  mf::LogVerbatim("TC") << "ChkVtxAssociations: Invalid vx2.Vx3ID " << vx2.Vx3ID
2098  << " in 2D vtx " << vx2.ID;
2099  return false;
2100  }
2101  auto& vx3 = slc.vtx3s[vx2.Vx3ID - 1];
2102  if (vx3.ID == 0) {
2103  mf::LogVerbatim("TC") << "ChkVtxAssociations: 2V" << vx2.ID << " thinks it is matched to 3V"
2104  << vx3.ID << " but vx3 is obsolete";
2105  return false;
2106  }
2107  if (vx3.Vx2ID[plane] != vx2.ID) {
2108  mf::LogVerbatim("TC") << "ChkVtxAssociations: 2V" << vx2.ID << " thinks it is matched to 3V"
2109  << vx3.ID << " but vx3 says no!";
2110  return false;
2111  }
2112  } // vx2
2113  // check the 3D -> 2D associations
2114  for (auto& vx3 : slc.vtx3s) {
2115  if (vx3.ID == 0) continue;
2116  if (vx3.Vx2ID[plane] == 0) continue;
2117  if (vx3.Vx2ID[plane] > (int)slc.vtxs.size()) {
2118  mf::LogVerbatim("TC") << "ChkVtxAssociations: Invalid vx3.Vx2ID " << vx3.Vx2ID[plane]
2119  << " in CTP " << inCTP;
2120  return false;
2121  }
2122  auto& vx2 = slc.vtxs[vx3.Vx2ID[plane] - 1];
2123  if (vx2.Vx3ID != vx3.ID) {
2124  mf::LogVerbatim("TC") << "ChkVtxAssociations: 3V" << vx3.ID << " thinks it is matched to 2V"
2125  << vx2.ID << " but vx2 says no!";
2126  return false;
2127  }
2128  } // vx3
2129 
2130  // check the Tj -> 2D associations
2131  for (auto& tj : slc.tjs) {
2132  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
2133  for (unsigned short end = 0; end < 2; ++end) {
2134  if (tj.VtxID[end] == 0) continue;
2135  if (tj.VtxID[end] > slc.vtxs.size()) {
2136  mf::LogVerbatim("TC") << "ChkVtxAssociations: T" << tj.ID << " thinks it is matched to 2V"
2137  << tj.VtxID[end] << " on end " << end
2138  << " but no vertex exists. Recovering";
2139  tj.VtxID[end] = 0;
2140  return false;
2141  }
2142  unsigned short ivx = tj.VtxID[end] - 1;
2143  auto& vx2 = slc.vtxs[ivx];
2144  if (vx2.ID == 0) {
2145  mf::LogVerbatim("TC") << "ChkVtxAssociations: T" << tj.ID << " thinks it is matched to 2V"
2146  << tj.VtxID[end] << " on end " << end
2147  << " but the vertex is killed. Fixing the Tj";
2148  tj.VtxID[end] = 0;
2149  return false;
2150  }
2151  } // end
2152  } // tj
2153 
2154  return true;
2155 
2156  } // ChkVtxAssociations
2157 
2158  //////////////////////////////////////////
2159  void
2161  {
2162  // reset all 3D vertex, 2D vertex and Tj high-score vertex bits in tpcid
2163 
2164  // reset the 2D vertex status bits
2165  for (auto& vx : slc.vtxs) {
2166  if (vx.ID == 0) continue;
2167  vx.Stat[kHiVx3Score] = false;
2168  } // vx
2169  // and the tj bits
2170  for (auto& tj : slc.tjs) {
2171  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
2172  tj.AlgMod[kTjHiVx3Score] = false;
2173  } // tj
2174  // Score the 2D vertices
2175  for (auto& vx : slc.vtxs) {
2176  if (vx.ID == 0) continue;
2177  SetVx2Score(slc, vx);
2178  } // vx
2179  // Score the 3D vertices
2180  for (auto& vx3 : slc.vtx3s) {
2181  if (vx3.ID == 0) continue;
2182  SetVx3Score(slc, vx3);
2183  } // vx3
2184  } // ScoreVertices
2185 
2186  //////////////////////////////////////////
2187  void
2189  {
2190  // kill 2D vertices that have low score and are not attached to a high-score 3D vertex
2191  if (slc.vtxs.empty()) return;
2192  for (auto& vx : slc.vtxs) {
2193  if (vx.ID == 0) continue;
2194  if (vx.Score > tcc.vtx2DCuts[7]) continue;
2195  if (vx.Vx3ID > 0) {
2196  auto& vx3 = slc.vtx3s[vx.Vx3ID - 1];
2197  if (vx3.Primary) continue;
2198  if (slc.vtx3s[vx.Vx3ID - 1].Score >= tcc.vtx2DCuts[7]) continue;
2199  }
2200  MakeVertexObsolete("KPV", slc, vx, false);
2201  } // vx
2202 
2203  } // KillPoorVertices
2204 
2205  //////////////////////////////////////////
2206  void
2208  {
2209  // Sets the tj and 2D vertex score bits to true
2210 
2211  if (vx3.ID == 0) return;
2212 
2213  for (unsigned short ipl = 0; ipl < slc.nPlanes; ++ipl) {
2214  if (vx3.Vx2ID[ipl] <= 0) continue;
2215  VtxStore& vx2 = slc.vtxs[vx3.Vx2ID[ipl] - 1];
2216  vx2.Stat[kHiVx3Score] = false;
2217  // transfer this to all attached tjs and vertices attached to those tjs
2218  std::vector<int> tjlist = GetVtxTjIDs(slc, vx2);
2219  std::vector<int> vxlist;
2220  while (true) {
2221  // tag Tjs and make a list of attached vertices whose high-score
2222  // bit needs to be set
2223  vxlist.clear();
2224  for (auto tjid : tjlist) {
2225  auto& tj = slc.tjs[tjid - 1];
2226  tj.AlgMod[kTjHiVx3Score] = true;
2227  for (unsigned short end = 0; end < 2; ++end) {
2228  if (tj.VtxID[end] == 0) continue;
2229  auto& vx2 = slc.vtxs[tj.VtxID[end] - 1];
2230  if (vx2.Stat[kHiVx3Score]) continue;
2231  vx2.Stat[kHiVx3Score] = true;
2232  vxlist.push_back(vx2.ID);
2233  } // end
2234  } // tjid
2235 
2236  if (vxlist.empty()) break;
2237  // re-build tjlist using vxlist
2238  std::vector<int> newtjlist;
2239  for (auto vxid : vxlist) {
2240  auto& vx2 = slc.vtxs[vxid - 1];
2241  auto tmp = GetVtxTjIDs(slc, vx2);
2242  for (auto tjid : tmp) {
2243  if (std::find(tjlist.begin(), tjlist.end(), tjid) == tjlist.end())
2244  newtjlist.push_back(tjid);
2245  } // tjid
2246  } // vxid
2247  if (newtjlist.empty()) break;
2248  tjlist = newtjlist;
2249  } // true
2250  } // ipl
2251 
2252  } // SetHighScoreBits
2253 
2254  //////////////////////////////////////////
2255  void
2257  {
2258  // Calculate the 3D vertex score and flag Tjs that are attached to high score vertices as defined
2259  // by vtx2DCuts
2260 
2261  if (vx3.ID == 0) return;
2262 
2263  vx3.Score = 0;
2264  for (unsigned short ipl = 0; ipl < slc.nPlanes; ++ipl) {
2265  if (vx3.Vx2ID[ipl] <= 0) continue;
2266  VtxStore& vx2 = slc.vtxs[vx3.Vx2ID[ipl] - 1];
2267  vx3.Score += vx2.Score;
2268  } // ipl
2269  vx3.Score /= (float)slc.nPlanes;
2270  // don't allow it to get too small or negative
2271  if (vx3.Score < 0.001) vx3.Score = 0.001;
2272  if (vx3.Score > tcc.vtx2DCuts[7]) SetHighScoreBits(slc, vx3);
2273 
2274  } // SetVx3Score
2275 
2276  //////////////////////////////////////////
2277  void
2279  {
2280  // A version that sets the score of the last added vertex
2281  if (slc.vtxs.empty()) return;
2282  auto& vx2 = slc.vtxs[slc.vtxs.size() - 1];
2283  SetVx2Score(slc, vx2);
2284  } // SetVx2Score
2285 
2286  //////////////////////////////////////////
2287  void
2289  {
2290  // Calculate the 2D vertex score
2291  if (vx2.ID == 0) return;
2292 
2293  // Don't score vertices from CheckTrajBeginChg, MakeJunkVertices or Neutral vertices. Set to the minimum
2294  if (vx2.Topo == 8 || vx2.Topo == 9 || vx2.Topo == 11 || vx2.Topo == 12) {
2295  vx2.Score = tcc.vtx2DCuts[7] + 0.1;
2296  auto vtxTjID = GetVtxTjIDs(slc, vx2);
2297  vx2.TjChgFrac = ChgFracNearPos(slc, vx2.Pos, vtxTjID);
2298  return;
2299  }
2300 
2301  // Cuts on Tjs attached to vertices
2302  constexpr float maxChgRMS = 0.25;
2303  constexpr float momBin = 50;
2304 
2305  vx2.Score = -1000;
2306  vx2.TjChgFrac = 0;
2307  if (vx2.ID == 0) return;
2308  if (tcc.vtxScoreWeights.size() < 4) return;
2309 
2310  auto vtxTjIDs = GetVtxTjIDs(slc, vx2);
2311  if (vtxTjIDs.empty()) return;
2312 
2313  // Vertex position error
2314  float vpeScore = -tcc.vtxScoreWeights[0] * (vx2.PosErr[0] + vx2.PosErr[1]);
2315 
2316  unsigned short m3Dcnt = 0;
2317  if (vx2.Vx3ID > 0) {
2318  m3Dcnt = 1;
2319  // Add another if the 3D vertex is complete
2320  unsigned short ivx3 = vx2.Vx3ID - 1;
2321  if (slc.vtx3s[ivx3].Wire < 0) m3Dcnt = 2;
2322  }
2323  float m3DScore = tcc.vtxScoreWeights[1] * m3Dcnt;
2324 
2325  vx2.TjChgFrac = ChgFracNearPos(slc, vx2.Pos, vtxTjIDs);
2326  float cfScore = tcc.vtxScoreWeights[2] * vx2.TjChgFrac;
2327 
2328  // Define a weight for each Tj
2329  std::vector<int> tjids;
2330  std::vector<float> tjwts;
2331  unsigned short cnt13 = 0;
2332  for (auto tjid : vtxTjIDs) {
2333  Trajectory& tj = slc.tjs[tjid - 1];
2334  // Feb 22 Ignore short Tjs and junk tjs
2335  if (tj.AlgMod[kJunkTj]) continue;
2336  unsigned short lenth = tj.EndPt[1] - tj.EndPt[0] + 1;
2337  if (lenth < 3) continue;
2338  float wght = (float)tj.MCSMom / momBin;
2339  // weight by the first tagged muon
2340  if (tj.PDGCode == 13) {
2341  ++cnt13;
2342  if (cnt13 == 1) wght *= 2;
2343  }
2344  // weight by charge rms
2345  if (tj.ChgRMS < maxChgRMS) ++wght;
2346  // Shower Tj
2347  if (tj.AlgMod[kShowerTj]) ++wght;
2348  // ShowerLike
2349  if (tj.AlgMod[kShowerLike]) --wght;
2350  tjids.push_back(tjid);
2351  tjwts.push_back(wght);
2352  } // tjid
2353 
2354  if (tjids.empty()) return;
2355 
2356  float tjScore = 0;
2357  float sum = 0;
2358  float cnt = 0;
2359  for (unsigned short it1 = 0; it1 < tjids.size() - 1; ++it1) {
2360  Trajectory& tj1 = slc.tjs[tjids[it1] - 1];
2361  float wght1 = tjwts[it1];
2362  // the end that has a vertex
2363  unsigned short end1 = 0;
2364  if (tj1.VtxID[1] == vx2.ID) end1 = 1;
2365  unsigned short endPt1 = tj1.EndPt[end1];
2366  // bump up the weight if there is a Bragg peak at the other end
2367  unsigned short oend1 = 1 - end1;
2368  if (tj1.EndFlag[oend1][kBragg]) ++wght1;
2369  float ang1 = tj1.Pts[endPt1].Ang;
2370  float ang1Err2 = tj1.Pts[endPt1].AngErr * tj1.Pts[endPt1].AngErr;
2371  for (unsigned short it2 = it1 + 1; it2 < tjids.size(); ++it2) {
2372  Trajectory& tj2 = slc.tjs[tjids[it2] - 1];
2373  float wght2 = tjwts[it2];
2374  unsigned end2 = 0;
2375  if (tj2.VtxID[1] == vx2.ID) end2 = 1;
2376  // bump up the weight if there is a Bragg peak at the other end
2377  unsigned short oend2 = 1 - end2;
2378  if (tj2.EndFlag[oend2][kBragg]) ++wght2;
2379  unsigned short endPt2 = tj2.EndPt[end2];
2380  float ang2 = tj2.Pts[endPt2].Ang;
2381  float ang2Err2 = tj2.Pts[endPt2].AngErr * tj2.Pts[endPt2].AngErr;
2382  float dang = DeltaAngle(ang1, ang2);
2383  float dangErr = 0.5 * sqrt(ang1Err2 + ang2Err2);
2384  if ((dang / dangErr) > 3 && wght1 > 0 && wght2 > 0) {
2385  sum += wght1 + wght2;
2386  ++cnt;
2387  }
2388  } // it2
2389  } // it1
2390  if (cnt > 0) {
2391  sum /= cnt;
2392  tjScore = tcc.vtxScoreWeights[3] * sum;
2393  }
2394  vx2.Score = vpeScore + m3DScore + cfScore + tjScore;
2395  if (tcc.dbg2V && tcc.dbgSlc && vx2.CTP == debug.CTP) {
2396  // last call after vertices have been matched to the truth. Use to optimize vtxScoreWeights using
2397  // an ntuple
2398  mf::LogVerbatim myprt("TC");
2399  bool printHeader = true;
2400  Print2V("SVx2S", myprt, vx2, printHeader);
2401  myprt << std::fixed << std::setprecision(1);
2402  myprt << " vpeScore " << vpeScore << " m3DScore " << m3DScore;
2403  myprt << " cfScore " << cfScore << " tjScore " << tjScore;
2404  myprt << " Score " << vx2.Score;
2405  }
2406  } // SetVx2Score
2407 
2408  //////////////////////////////////////////
2409  void
2411  {
2412 
2413  if (!tcc.useAlg[kComp3DVxIG]) return;
2414  if (slc.nPlanes != 3) return;
2415 
2416  bool prt = (tcc.modes[kDebug] && tcc.dbgSlc && tcc.dbgAlg[kComp3DVxIG]);
2417  if (prt) mf::LogVerbatim("TC") << "Inside CI3DVIG:";
2418 
2419  for (unsigned short iv3 = 0; iv3 < slc.vtx3s.size(); ++iv3) {
2420  Vtx3Store& vx3 = slc.vtx3s[iv3];
2421  // ignore obsolete vertices
2422  if (vx3.ID == 0) continue;
2423  // check for a completed 3D vertex
2424  if (vx3.Wire < 0) continue;
2425  unsigned short mPlane = USHRT_MAX;
2426  for (unsigned short ipl = 0; ipl < slc.nPlanes; ++ipl) {
2427  if (vx3.Vx2ID[ipl] > 0) continue;
2428  mPlane = ipl;
2429  break;
2430  } // ipl
2431  if (mPlane == USHRT_MAX) continue;
2432  CTP_t mCTP = EncodeCTP(vx3.TPCID.Cryostat, vx3.TPCID.TPC, mPlane);
2433  // require that the missing vertex be in a large block of dead wires
2434  float dwc = DeadWireCount(slc, vx3.Wire - 4, vx3.Wire + 4, mCTP);
2435  if (dwc < 5) continue;
2436  // X position of the purported missing vertex
2437  VtxStore aVtx;
2438  aVtx.ID = slc.vtxs.size() + 1;
2439  aVtx.Pos[0] = vx3.Wire;
2440  aVtx.Pos[1] = detProp.ConvertXToTicks(vx3.X, mPlane, vx3.TPCID.TPC, vx3.TPCID.Cryostat) *
2441  tcc.unitsPerTick;
2442  aVtx.CTP = mCTP;
2443  aVtx.Topo = 4;
2444  aVtx.NTraj = 0;
2445  // Give it a bogus pass to indicate it wasn't created while stepping
2446  aVtx.Pass = 9;
2447  if (prt)
2448  mf::LogVerbatim("TC") << "CI3DVIG: Incomplete vertex " << iv3 << " in plane " << mPlane
2449  << " wire " << vx3.Wire << " Made 2D vertex ";
2450  std::vector<int> tjIDs;
2451  std::vector<unsigned short> tjEnds;
2452  for (unsigned short itj = 0; itj < slc.tjs.size(); ++itj) {
2453  if (slc.tjs[itj].CTP != mCTP) continue;
2454  if (slc.tjs[itj].AlgMod[kKilled] || slc.tjs[itj].AlgMod[kHaloTj]) continue;
2455  for (unsigned short end = 0; end < 2; ++end) {
2456  unsigned short ept = slc.tjs[itj].EndPt[end];
2457  TrajPoint& tp = slc.tjs[itj].Pts[ept];
2458  unsigned short oept = slc.tjs[itj].EndPt[1 - end];
2459  TrajPoint& otp = slc.tjs[itj].Pts[oept];
2460  // ensure that this is the end closest to the vertex
2461  if (std::abs(tp.Pos[0] - aVtx.Pos[0]) > std::abs(otp.Pos[0] - aVtx.Pos[0])) continue;
2462  float doca = PointTrajDOCA(slc, aVtx.Pos[0], aVtx.Pos[1], tp);
2463  if (doca > 2) continue;
2464  float dwc = DeadWireCount(slc, aVtx.Pos[0], tp.Pos[0], tp.CTP);
2465  float ptSep;
2466  if (aVtx.Pos[0] > tp.Pos[0]) { ptSep = aVtx.Pos[0] - tp.Pos[0] - dwc; }
2467  else {
2468  ptSep = tp.Pos[0] - aVtx.Pos[0] - dwc;
2469  }
2470  if (prt)
2471  mf::LogVerbatim("TC") << "CI3DVIG: tj ID " << slc.tjs[itj].ID << " doca " << doca
2472  << " ptSep " << ptSep;
2473  if (ptSep < -2 || ptSep > 2) continue;
2474  // don't clobber an existing association
2475  if (slc.tjs[itj].VtxID[end] > 0) continue;
2476  tjIDs.push_back(slc.tjs[itj].ID);
2477  tjEnds.push_back(end);
2478  } // end
2479  } // itj
2480  if (!tjIDs.empty()) {
2481  // Determine how messy this region is
2482  aVtx.TjChgFrac = ChgFracNearPos(slc, aVtx.Pos, tjIDs);
2483  if (aVtx.TjChgFrac < 0.7) continue;
2484  aVtx.Vx3ID = vx3.ID;
2485  // Save the 2D vertex
2486  if (!StoreVertex(slc, aVtx)) continue;
2487  for (unsigned short ii = 0; ii < tjIDs.size(); ++ii) {
2488  unsigned short itj = tjIDs[ii] - 1;
2489  slc.tjs[itj].VtxID[tjEnds[ii]] = aVtx.ID;
2490  slc.tjs[itj].AlgMod[kComp3DVxIG] = true;
2491  } // ii
2492  SetVx2Score(slc);
2493  vx3.Vx2ID[mPlane] = aVtx.ID;
2494  vx3.Wire = -1;
2495  if (prt)
2496  mf::LogVerbatim("TC") << "CI3DVIG: new vtx 2V" << aVtx.ID << " points to 3V" << vx3.ID;
2497  }
2498  } // vx3
2499 
2500  } // CompleteIncomplete3DVerticesInGaps
2501 
2502  //////////////////////////////////////////
2503  void
2505  {
2506  // Look for trajectories in a plane that lack a 2D vertex as listed in
2507  // 2DVtxID that are near the projected wire. This may trigger splitting trajectories,
2508  // assigning them to a new 2D vertex and completing 3D vertices
2509 
2510  if (!tcc.useAlg[kComp3DVx]) return;
2511  if (slc.nPlanes != 3) return;
2512 
2513  bool prt = (tcc.modes[kDebug] && tcc.dbgSlc && tcc.dbgAlg[kComp3DVx]);
2514 
2515  float maxdoca = 3;
2516  if (prt) mf::LogVerbatim("TC") << "Inside CI3DV with maxdoca set to " << maxdoca;
2517  unsigned short ivx3 = 0;
2518  for (auto& vx3 : slc.vtx3s) {
2519  // ignore obsolete vertices
2520  if (vx3.ID == 0) continue;
2521  // check for a completed 3D vertex
2522  if (vx3.Wire < 0) continue;
2523  unsigned short mPlane = USHRT_MAX;
2524  // look for vertices in the induction plane in which the charge requirement wasn't imposed
2525  bool indPlnNoChgVtx = false;
2526  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
2527  if (vx3.Vx2ID[plane] > 0) {
2528  auto& vx2 = slc.vtxs[vx3.Vx2ID[plane] - 1];
2529  if (vx2.Stat[kVxIndPlnNoChg]) indPlnNoChgVtx = true;
2530  continue;
2531  }
2532  mPlane = plane;
2533  } // ipl
2534  if (mPlane == USHRT_MAX) continue;
2535  if (indPlnNoChgVtx) continue;
2536  CTP_t mCTP = EncodeCTP(vx3.TPCID.Cryostat, vx3.TPCID.TPC, mPlane);
2537  // X position of the purported missing vertex
2538  // A TP for the missing 2D vertex
2539  TrajPoint vtp;
2540  vtp.Pos[0] = vx3.Wire;
2541  vtp.Pos[1] = detProp.ConvertXToTicks(vx3.X, mPlane, vx3.TPCID.TPC, vx3.TPCID.Cryostat) *
2542  tcc.unitsPerTick;
2543  if (prt)
2544  mf::LogVerbatim("TC") << "CI3DV 3V" << vx3.ID << " Pos " << mPlane << ":"
2545  << PrintPos(slc, vtp.Pos);
2546  std::vector<int> tjIDs;
2547  std::vector<unsigned short> tjPts;
2548  for (auto& tj : slc.tjs) {
2549  if (tj.CTP != mCTP) continue;
2550  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
2551  if (tj.Pts.size() < 6) continue;
2552  if (tj.AlgMod[kComp3DVx]) continue;
2553  float doca = maxdoca;
2554  // find the closest distance between the vertex and the trajectory
2555  unsigned short closePt = 0;
2556  TrajPointTrajDOCA(slc, vtp, tj, closePt, doca);
2557  if (closePt > tj.EndPt[1]) continue;
2558  // try to improve the location of the vertex by looking for a distinctive feature on the
2559  // trajectory, e.g. high multiplicity hits or larger than normal charge
2560  if (RefineVtxPosition(slc, tj, closePt, 3, false)) vtp.Pos = tj.Pts[closePt].Pos;
2561  if (prt)
2562  mf::LogVerbatim("TC") << "CI3DV 3V" << vx3.ID << " candidate T" << tj.ID << " vtx pos "
2563  << PrintPos(slc, vtp.Pos) << " doca " << doca << " closePt "
2564  << closePt;
2565  tjIDs.push_back(tj.ID);
2566  tjPts.push_back(closePt);
2567  } // itj
2568  if (tjIDs.empty()) continue;
2569  // compare the length of the Tjs used to make the vertex with the length of the
2570  // Tj that we want to split. Don't allow a vertex using very short Tjs to split a long
2571  // Tj in the 3rd plane
2572  auto vxtjs = GetAssns(slc, "3V", vx3.ID, "T");
2573  unsigned short maxPts = 0;
2574  unsigned short minPts = USHRT_MAX;
2575  for (auto tjid : vxtjs) {
2576  auto& tj = slc.tjs[tjid - 1];
2577  unsigned short npwc = NumPtsWithCharge(slc, tj, false);
2578  if (npwc > maxPts) maxPts = npwc;
2579  if (npwc < minPts) minPts = npwc;
2580  } // tjid
2581  // skip this operation if any of the Tjs in the split list are > 3 * maxPts
2582  maxPts *= 3;
2583  bool skipit = false;
2584  for (auto tjid : tjIDs) {
2585  auto& tj = slc.tjs[tjid - 1];
2586  if (NumPtsWithCharge(slc, tj, false) > maxPts) skipit = true;
2587  } // tjid
2588  if (prt)
2589  mf::LogVerbatim("TC") << " maxPts " << maxPts << " skipit? " << skipit << " minPts "
2590  << minPts;
2591  if (skipit) continue;
2592  // 2D vertex
2593  VtxStore aVtx;
2594  unsigned short newVtxIndx = slc.vtxs.size();
2595  aVtx.ID = newVtxIndx + 1;
2596  aVtx.CTP = mCTP;
2597  aVtx.Topo = 3;
2598  aVtx.NTraj = 0;
2599  // Give it a bogus pass to indicate it wasn't created while stepping
2600  aVtx.Pass = 9;
2601  aVtx.Pos = vtp.Pos;
2602  // ensure this isn't in a messy region
2603  aVtx.TjChgFrac = ChgFracNearPos(slc, aVtx.Pos, tjIDs);
2604  if (prt)
2605  mf::LogVerbatim("TC") << " charge fraction near position " << aVtx.TjChgFrac
2606  << " cut if < 0.6";
2607  if (aVtx.TjChgFrac < 0.6) continue;
2608  if (!StoreVertex(slc, aVtx)) continue;
2609  // make a reference to the new vertex
2610  VtxStore& newVtx = slc.vtxs[slc.vtxs.size() - 1];
2611  if (prt) mf::LogVerbatim("TC") << " Stored new 2V" << newVtx.ID;
2612  // make a temporary copy so we can nudge it a bit if there is only one Tj
2613  std::array<float, 2> vpos = aVtx.Pos;
2614  for (unsigned short ii = 0; ii < tjIDs.size(); ++ii) {
2615  unsigned short itj = tjIDs[ii] - 1;
2616  unsigned short closePt = tjPts[ii];
2617  // determine which end is the closest
2618  unsigned short end = 1;
2619  // closest to the beginning?
2620  if (fabs(closePt - slc.tjs[itj].EndPt[0]) < fabs(closePt - slc.tjs[itj].EndPt[1])) end = 0;
2621  short dpt = fabs(closePt - slc.tjs[itj].EndPt[end]);
2622  if (prt) mf::LogVerbatim("TC") << " dpt " << dpt << " to end " << end;
2623  if (dpt < 2) {
2624  // close to an end
2625  if (slc.tjs[itj].VtxID[end] > 0) {
2626  // find the distance btw the existing vertex and the end of this tj
2627  auto& oldTj = slc.tjs[itj];
2628  auto& oldVx = slc.vtxs[oldTj.VtxID[end] - 1];
2629  short oldSep = fabs(oldVx.Pos[0] - oldTj.Pts[oldTj.EndPt[end]].Pos[0]);
2630  if (prt)
2631  mf::LogVerbatim("TC")
2632  << " T" << slc.tjs[itj].ID << " has vertex 2V" << slc.tjs[itj].VtxID[end]
2633  << " at end " << end << ". oldSep " << oldSep;
2634  if (dpt < oldSep) { MakeVertexObsolete("CI3DV", slc, oldVx, true); }
2635  else {
2636  continue;
2637  }
2638  } // slc.tjs[itj].VtxID[end] > 0
2639  slc.tjs[itj].VtxID[end] = slc.vtxs[newVtxIndx].ID;
2640  ++newVtx.NTraj;
2641  if (prt)
2642  mf::LogVerbatim("TC") << " attach Traj T" << slc.tjs[itj].ID << " at end " << end;
2643  slc.tjs[itj].AlgMod[kComp3DVx] = true;
2644  vpos = slc.tjs[itj].Pts[slc.tjs[itj].EndPt[end]].Pos;
2645  }
2646  else {
2647  // closePt is not near an end, so split the trajectory
2648  if (SplitTraj(slc, itj, closePt, newVtxIndx, prt)) {
2649  if (prt)
2650  mf::LogVerbatim("TC")
2651  << " SplitTraj success 2V" << slc.vtxs[newVtxIndx].ID << " at closePt " << closePt;
2652  // successfully split the Tj
2653  newVtx.NTraj += 2;
2654  }
2655  else {
2656  // split failed. Give up
2657  if (prt) mf::LogVerbatim("TC") << " SplitTraj failed";
2658  newVtx.NTraj = 0;
2659  break;
2660  }
2661  // Update the PDGCode for the chopped trajectory
2662  SetPDGCode(slc, itj);
2663  // and for the new trajectory
2664  SetPDGCode(slc, slc.tjs.size() - 1);
2665  } // closePt is not near an end, so split the trajectory
2666  slc.tjs[itj].AlgMod[kComp3DVx] = true;
2667  unsigned short newtj = slc.tjs.size() - 1;
2668  slc.tjs[newtj].AlgMod[kComp3DVx] = true;
2669  } // ii
2670  if (newVtx.NTraj == 0) {
2671  // A failure occurred. Recover
2672  if (prt) mf::LogVerbatim("TC") << " Failed. Recover and delete vertex " << newVtx.ID;
2673  MakeVertexObsolete("CI3DV", slc, newVtx, true);
2674  }
2675  else {
2676  // success
2677  vx3.Vx2ID[mPlane] = newVtx.ID;
2678  newVtx.Vx3ID = vx3.ID;
2679  vx3.Wire = -1;
2680  // set the vertex position to the start of the Tj if there is only one and fix it
2681  if (newVtx.NTraj == 1) {
2682  newVtx.Pos = vpos;
2683  newVtx.Stat[kFixed] = true;
2684  }
2685  AttachAnyTrajToVertex(slc, newVtx.ID - 1, prt);
2686  SetVx2Score(slc);
2687  if (prt) {
2688  mf::LogVerbatim myprt("TC");
2689  myprt << " Success: new 2V" << newVtx.ID << " at " << (int)newVtx.Pos[0] << ":"
2690  << (int)newVtx.Pos[1] / tcc.unitsPerTick;
2691  myprt << " points to 3V" << vx3.ID;
2692  myprt << " TjIDs:";
2693  for (auto& tjID : tjIDs)
2694  myprt << " T" << std::to_string(tjID);
2695  } // prt
2696  } // success
2697  ++ivx3;
2698  } // vx3
2699 
2700  } // CompleteIncomplete3DVertices
2701 
2702  ////////////////////////////////////////////////
2703  bool
2705  const Trajectory& tj,
2706  unsigned short& nearPt,
2707  short nPtsToChk,
2708  bool prt)
2709  {
2710  // The tj has been slated to be split somewhere near point nearPt. This function will move
2711  // the near point a bit to the most likely point of a vertex
2712 
2713  float maxChg = tj.Pts[nearPt].Chg;
2714  short maxChgPt = nearPt;
2715  unsigned short fromPt = tj.EndPt[0];
2716  short spt = (short)nearPt - (short)nPtsToChk;
2717  if (spt > (short)fromPt) fromPt = nearPt - nPtsToChk;
2718  unsigned short toPt = nearPt + nPtsToChk;
2719  if (toPt > tj.EndPt[1]) toPt = tj.EndPt[1];
2720 
2721  for (short ipt = fromPt; ipt <= toPt; ++ipt) {
2722  if (ipt < tj.EndPt[0] || ipt > tj.EndPt[1]) continue;
2723  auto& tp = tj.Pts[ipt];
2724  if (tp.Chg > maxChg) {
2725  maxChg = tp.Chg;
2726  maxChgPt = ipt;
2727  }
2728  if (prt)
2729  mf::LogVerbatim("TC") << "RVP: ipt " << ipt << " Pos " << tp.CTP << ":"
2730  << PrintPos(slc, tp.Pos) << " chg " << (int)tp.Chg << " nhits "
2731  << tp.Hits.size();
2732  } // ipt
2733  if (nearPt == maxChgPt) return false;
2734  nearPt = maxChgPt;
2735  return true;
2736  } //RefineVtxPosition
2737 
2738  ////////////////////////////////////////////////
2739  bool
2740  MakeVertexObsolete(std::string fcnLabel, TCSlice& slc, VtxStore& vx2, bool forceKill)
2741  {
2742  // Makes a 2D vertex obsolete
2743 
2744  // check for a high-score 3D vertex
2745  bool hasHighScoreVx3 = (vx2.Vx3ID > 0);
2746  if (hasHighScoreVx3 && !forceKill && slc.vtx3s[vx2.Vx3ID - 1].Score >= tcc.vtx2DCuts[7])
2747  return false;
2748 
2749  if (tcc.dbg2V || tcc.dbg3V) {
2750  mf::LogVerbatim("TC") << fcnLabel << " MVO: killing 2V" << vx2.ID;
2751  }
2752 
2753  // Kill it
2754  int vx2id = vx2.ID;
2755  if (vx2.Vx3ID > 0) {
2756  auto& vx3 = slc.vtx3s[vx2.Vx3ID - 1];
2757  for (auto& v3v2id : vx3.Vx2ID)
2758  if (v3v2id == vx2.ID) v3v2id = 0;
2759  }
2760  vx2.ID = 0;
2761  for (auto& tj : slc.tjs) {
2762  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
2763  for (unsigned short end = 0; end < 2; ++end) {
2764  if (tj.VtxID[end] != vx2id) continue;
2765  tj.VtxID[end] = 0;
2766  tj.AlgMod[kPhoton] = false;
2767  // clear the kEnvOverlap bits on the TPs
2768  for (unsigned short ii = 0; ii < tj.Pts.size(); ++ii) {
2769  if (end == 0) {
2770  unsigned short ipt = tj.EndPt[0] + ii;
2771  auto& tp = tj.Pts[ipt];
2772  if (!tp.Environment[kEnvOverlap]) break;
2773  if (ipt == tj.EndPt[1]) break;
2774  }
2775  else {
2776  unsigned short ipt = tj.EndPt[1] - ii;
2777  auto& tp = tj.Pts[ipt];
2778  if (!tp.Environment[kEnvOverlap]) break;
2779  if (ipt == tj.EndPt[0]) break;
2780  }
2781  } // ii
2782  if (tj.AlgMod[kTjHiVx3Score]) {
2783  // see if the vertex at the other end is high-score and if so, preserve the state
2784  unsigned short oend = 1 - end;
2785  if (tj.VtxID[oend] > 0) {
2786  auto& ovx2 = slc.vtxs[tj.VtxID[oend] - 1];
2787  if (!ovx2.Stat[kHiVx3Score]) tj.AlgMod[kTjHiVx3Score] = false;
2788  } // vertex at the other end
2789  } // tj.AlgMod[kTjHiVx3Score]
2790  } // end
2791  } // tj
2792 
2793  if (!hasHighScoreVx3) return true;
2794 
2795  // update the affected 3D vertex
2796  Vtx3Store& vx3 = slc.vtx3s[vx2.Vx3ID - 1];
2797  // make the 3D vertex incomplete
2798  geo::PlaneID planeID = DecodeCTP(vx2.CTP);
2799  unsigned short plane = planeID.Plane;
2800  if (vx3.Vx2ID[plane] != vx2id) return true;
2801  vx3.Vx2ID[plane] = 0;
2802  vx3.Wire = vx2.Pos[0];
2803  // Ensure that there are at least two 2D vertices left
2804  unsigned short n2D = 0;
2805  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane)
2806  if (vx3.Vx2ID[plane] > 0) ++n2D;
2807 
2808  if (n2D > 1) {
2809  // 3D vertex is incomplete
2810  // correct the score
2811  SetVx3Score(slc, vx3);
2812  return true;
2813  }
2814 
2815  // 3D vertex is obsolete
2816  // Detach all remaining 2D vertices from the 3D vertex
2817  for (auto& vx2 : slc.vtxs) {
2818  if (vx2.ID == 0) continue;
2819  if (vx2.Vx3ID == vx3.ID) vx2.Vx3ID = 0;
2820  } // vx2
2821  for (auto& pfp : slc.pfps) {
2822  for (unsigned short end = 0; end < 2; ++end)
2823  if (pfp.Vx3ID[end] == vx3.ID) pfp.Vx3ID[end] = 0;
2824  } // pfp
2825  vx3.ID = 0;
2826  return true;
2827 
2828  } // MakeVertexObsolete
2829 
2830  ////////////////////////////////////////////////
2831  bool
2833  {
2834  // Deletes a 3D vertex and 2D vertices in all planes
2835  // The 2D and 3D vertices are NOT killed if forceKill is false and the 3D vertex
2836  // has a high score
2837  if (vx3.ID <= 0) return true;
2838  if (vx3.ID > int(slc.vtx3s.size())) return false;
2839 
2840  for (auto vx2id : vx3.Vx2ID) {
2841  if (vx2id == 0 || vx2id > (int)slc.vtxs.size()) continue;
2842  auto& vx2 = slc.vtxs[vx2id - 1];
2843  MakeVertexObsolete("MVO3", slc, vx2, true);
2844  }
2845  vx3.ID = 0;
2846  return true;
2847  } // MakeVertexObsolete
2848 
2849  //////////////////////////////////////////
2850  std::vector<int>
2851  GetVtxTjIDs(const TCSlice& slc, const VtxStore& vx2)
2852  {
2853  // returns a list of trajectory IDs that are attached to vx2
2854  std::vector<int> tmp;
2855  if (vx2.ID == 0) return tmp;
2856  for (auto& tj : slc.tjs) {
2857  if (tj.AlgMod[kKilled]) continue;
2858  if (tj.CTP != vx2.CTP) continue;
2859  for (unsigned short end = 0; end < 2; ++end) {
2860  if (tj.VtxID[end] == vx2.ID) tmp.push_back(tj.ID);
2861  } // end
2862  } // tj
2863  return tmp;
2864  } // GetVtxTjIDs
2865 
2866  //////////////////////////////////////////
2867  std::vector<int>
2868  GetVtxTjIDs(const TCSlice& slc, const Vtx3Store& vx3, float& score)
2869  {
2870  // returns a list of Tjs in all planes that are attached to vx3
2871  std::vector<int> tmp;
2872  if (vx3.ID == 0) return tmp;
2873  float nvx2 = 0;
2874  score = 0;
2875  for (auto& vx2 : slc.vtxs) {
2876  if (vx2.ID == 0) continue;
2877  if (vx2.Vx3ID != vx3.ID) continue;
2878  auto vtxTjID2 = GetVtxTjIDs(slc, vx2);
2879  tmp.insert(tmp.end(), vtxTjID2.begin(), vtxTjID2.end());
2880  score += vx2.Score;
2881  ++nvx2;
2882  } // vx2
2883  if (nvx2 < 1) return tmp;
2884  // find the average score
2885  score /= nvx2;
2886  // sort by increasing ID
2887  std::sort(tmp.begin(), tmp.end());
2888  return tmp;
2889  } // GetVtxTjIDs
2890 
2891  //////////////////////////////////////////
2892  void
2894  const TCSlice& slc,
2895  const Vtx3Store& vx3,
2896  unsigned short plane,
2897  Point2_t& pos)
2898  {
2899  // returns the 2D position of the vertex in the plane
2900  pos[0] = tcc.geom->WireCoordinate(vx3.Y, vx3.Z, plane, vx3.TPCID.TPC, vx3.TPCID.Cryostat);
2901  pos[1] =
2902  detProp.ConvertXToTicks(vx3.X, plane, vx3.TPCID.TPC, vx3.TPCID.Cryostat) * tcc.unitsPerTick;
2903 
2904  } // PosInPlane
2905 
2906  /////////////////////////////////////////
2907  unsigned short
2908  IsCloseToVertex(const TCSlice& slc, const VtxStore& inVx2)
2909  {
2910  // Returns the ID of a 2D vertex having the minimum pull < user-specified cut
2911 
2912  float minPull = tcc.vtx2DCuts[3];
2913  unsigned short imBest = 0;
2914  for (auto& vx2 : slc.vtxs) {
2915  if (vx2.CTP != inVx2.CTP) continue;
2916  if (vx2.ID <= 0) continue;
2917  float pull = VertexVertexPull(slc, inVx2, vx2);
2918  if (pull < minPull) {
2919  minPull = pull;
2920  imBest = vx2.ID;
2921  }
2922  } // vx2
2923  return imBest;
2924  } // IsCloseToVertex
2925 
2926  /////////////////////////////////////////
2927  unsigned short
2928  IsCloseToVertex(const TCSlice& slc, const Vtx3Store& vx3)
2929  {
2930  // Returns the ID of a 3D vertex having the minimum pull < user-specified cut
2931 
2932  float minPull = tcc.vtx3DCuts[1];
2933  unsigned short imBest = 0;
2934  for (auto& oldvx3 : slc.vtx3s) {
2935  if (oldvx3.ID == 0) continue;
2936  if (std::abs(oldvx3.X - vx3.X) > tcc.vtx3DCuts[0]) continue;
2937  float pull = VertexVertexPull(slc, vx3, oldvx3);
2938  if (pull < minPull) {
2939  minPull = pull;
2940  imBest = oldvx3.ID;
2941  }
2942  } // oldvx3
2943  return imBest;
2944 
2945  } // IsCloseToVertex
2946 
2947 } // namespace
Expect tracks entering from the front face. Don&#39;t create neutrino PFParticles.
Definition: DataStructs.h:534
geo::Length_t WireCoordinate(double YPos, double ZPos, geo::PlaneID const &planeid) const
Returns the index of the nearest wire to the specified position.
bool valDecreasing(SortEntry c1, SortEntry c2)
Definition: TCVertex.cxx:33
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
Vector2_t Dir
Definition: DataStructs.h:158
Point2_t Pos
Definition: DataStructs.h:157
short MCSMom(const TCSlice &slc, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3466
Point2_t PosErr
Definition: DataStructs.h:76
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
std::vector< Trajectory > tjs
vector of all trajectories in each plane
Definition: DataStructs.h:672
unsigned short FarEnd(const TCSlice &slc, const PFPStruct &pfp, const Point3_t &pos)
Definition: PFPUtils.cxx:3338
float Length(const PFPStruct &pfp)
Definition: PFPUtils.cxx:3303
bool MergeWithVertex(TCSlice &slc, VtxStore &vx, unsigned short oVxID)
Definition: TCVertex.cxx:449
bool MakeVertexObsolete(std::string fcnLabel, TCSlice &slc, VtxStore &vx2, bool forceKill)
Definition: TCVertex.cxx:2740
unsigned short CloseEnd(const TCSlice &slc, const Trajectory &tj, const Point2_t &pos)
Definition: Utils.cxx:2549
struct of temporary 2D vertices (end points)
Definition: DataStructs.h:74
std::vector< int > GetAssns(TCSlice &slc, std::string type1Name, int id, std::string type2Name)
Definition: Utils.cxx:4847
static unsigned int kWire
float TrajPointVertexPull(const TCSlice &slc, const TrajPoint &tp, const VtxStore &vx)
Definition: TCVertex.cxx:1865
bool AttachAnyVertexToTraj(TCSlice &slc, int tjID, bool prt)
Definition: TCVertex.cxx:1655
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:155
std::vector< float > maxPos0
Definition: DataStructs.h:574
void CompleteIncomplete3DVerticesInGaps(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: TCVertex.cxx:2410
std::array< double, 3 > Point3_t
Definition: DataStructs.h:43
bool SignalAtTp(TrajPoint &tp)
Definition: Utils.cxx:2002
bool FitVertex(TCSlice &slc, VtxStore &vx, bool prt)
Definition: TCVertex.cxx:1968
std::vector< float > vtx3DCuts
2D vtx -> 3D vtx matching cuts
Definition: DataStructs.h:553
void SetPDGCode(TCSlice &slc, unsigned short itj)
Definition: Utils.cxx:4348
void Find3DVertices(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: TCVertex.cxx:1282
std::string string
Definition: nybbler.cc:12
void Print2V(std::string someText, mf::LogVerbatim &myprt, VtxStore &vx2, bool &printHeader)
Definition: Utils.cxx:5766
TCConfig tcc
Definition: DataStructs.cxx:8
vertex position fixed manually - no fitting done
Definition: DataStructs.h:95
void SplitTrajCrossingVertices(TCSlice &slc, CTP_t inCTP)
Definition: TCVertex.cxx:937
void PrintTP(std::string someText, const TCSlice &slc, unsigned short ipt, short dir, unsigned short pass, const TrajPoint &tp)
Definition: Utils.cxx:6294
std::vector< int > Vx2ID
Definition: DataStructs.h:116
void Reconcile2Vs(TCSlice &slc)
Definition: TCVertex.cxx:1081
Planes which measure X direction.
Definition: geo_types.h:134
The data type to uniquely identify a Plane.
Definition: geo_types.h:472
bool valIncreasing(SortEntry c1, SortEntry c2)
Definition: TCVertex.cxx:38
matched to a high-score 3D vertex
Definition: DataStructs.h:97
step from US -> DS (true) or DS -> US (false)
Definition: DataStructs.h:533
short MCSMom
Normalized RMS using ALL hits. Assume it is 50% to start.
Definition: DataStructs.h:202
bool RefineVtxPosition(TCSlice &slc, const Trajectory &tj, unsigned short &nearPt, short nPtsToChk, bool prt)
Definition: TCVertex.cxx:2704
unsigned short Pass
Definition: DataStructs.h:78
unsigned int index
Definition: TCVertex.cxx:28
std::string PrintPos(const TCSlice &slc, const TrajPoint &tp)
Definition: Utils.cxx:6524
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:212
bool TrajClosestApproach(Trajectory const &tj, float x, float y, unsigned short &closePt, float &DOCA)
Definition: Utils.cxx:2688
void PosInPlane(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, const Vtx3Store &vx3, unsigned short plane, Point2_t &pos)
Definition: TCVertex.cxx:2893
bool AttachAnyTrajToVertex(TCSlice &slc, unsigned short ivx, bool prt)
Definition: TCVertex.cxx:1697
unsigned short TPNearVertex(const TCSlice &slc, const TrajPoint &tp)
Definition: TCVertex.cxx:1582
Point3_t PosAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3292
float PointTrajDOCA(const TCSlice &slc, unsigned int iht, TrajPoint const &tp)
Definition: Utils.cxx:2570
double DeltaAngle(const Vector3_t v1, const Vector3_t v2)
Definition: PFPUtils.cxx:2539
bool dbgSlc
debug only in the user-defined slice? default is all slices
Definition: DataStructs.h:592
string dir
std::array< int, 2 > Vx3ID
Definition: DataStructs.h:292
void SetVx3Score(TCSlice &slc, Vtx3Store &vx3)
Definition: TCVertex.cxx:2256
tick ticks
Alias for common language habits.
Definition: electronics.h:78
void CompleteIncomplete3DVertices(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: TCVertex.cxx:2504
bool StoreVertex(TCSlice &slc, VtxStore &vx)
Definition: TCVertex.cxx:1932
Q_EXPORT QTSManip setprecision(int p)
Definition: qtextstream.h:343
bool dbg3V
debug 3D vertex finding
Definition: DataStructs.h:599
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
T abs(T value)
int close(int)
Closes the file descriptor fd.
struct of temporary 3D vertices
Definition: DataStructs.h:106
geo::TPCID TPCID
Definition: DataStructs.h:297
void PrintAllTraj(detinfo::DetectorPropertiesData const &detProp, std::string someText, TCSlice &slc, unsigned short itj, unsigned short ipt, bool prtVtx)
Definition: Utils.cxx:5950
std::array< float, 2 > Point2_t
Definition: DataStructs.h:45
std::vector< float > maxPos1
Definition: DataStructs.h:575
float unitsPerTick
scale factor from Tick to WSE equivalent units
Definition: DataStructs.h:573
float DeadWireCount(const TCSlice &slc, const TrajPoint &tp1, const TrajPoint &tp2)
Definition: Utils.cxx:2139
unsigned short NearbyCleanPt(const TCSlice &slc, const Trajectory &tj, unsigned short end)
Definition: Utils.cxx:2954
geo::TPCID TPCID
Definition: DataStructs.h:115
DebugStuff debug
Definition: DebugStruct.cxx:4
Vector3_t PointDirection(const Point3_t p1, const Point3_t p2)
Definition: PFPUtils.cxx:2547
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:194
HLTPathStatus const pass
the environment near the vertex was checked - See UpdateVxEnvironment
Definition: DataStructs.h:101
bool dbg2V
debug 2D vertex finding
Definition: DataStructs.h:595
std::vector< TrajPoint > Pts
Trajectory points.
Definition: DataStructs.h:193
bool ChkVtxAssociations(TCSlice &slc, const CTP_t &inCTP)
Definition: TCVertex.cxx:2085
bool Reconcile2VTs(TCSlice &slc, std::vector< int > &vx2cls, bool prt)
Definition: TCVertex.cxx:1177
int Plane
Select plane.
Definition: DebugStruct.h:22
float ChgFracNearPos(const TCSlice &slc, const Point2_t &pos, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3234
std::vector< unsigned int > FindCloseHits(const TCSlice &slc, std::array< int, 2 > const &wireWindow, Point2_t const &timeWindow, const unsigned short plane, HitStatus_t hitRequest, bool usePeakTime, bool &hitsNear)
Definition: Utils.cxx:2841
std::vector< VtxStore > vtxs
2D vertices
Definition: DataStructs.h:678
std::array< unsigned short, 2 > EndPt
First and last point in the trajectory that has charge.
Definition: DataStructs.h:205
unsigned short PDGCode
shower-like or track-like {default is track-like}
Definition: DataStructs.h:210
double ConvertXToTicks(double X, int p, int t, int c) const
void ScoreVertices(TCSlice &slc)
Definition: TCVertex.cxx:2160
double PosSep2(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2571
std::string PrintHit(const TCHit &tch)
Definition: Utils.cxx:6514
Point2_t Pos
Definition: DataStructs.h:75
string tmp
Definition: languages.py:63
bool SplitTraj(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, unsigned short itj, float XPos, bool makeVx2, bool prt)
Definition: Utils.cxx:2270
void TrajPointTrajDOCA(const TCSlice &slc, TrajPoint const &tp, Trajectory const &tj, unsigned short &closePt, float &minSep)
Definition: Utils.cxx:2433
const geo::GeometryCore * geom
Definition: DataStructs.h:578
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:493
unsigned short NearestPtWithChg(const TCSlice &slc, const Trajectory &tj, unsigned short thePt)
Definition: Utils.cxx:3520
bool IntersectionPoint(geo::WireID const &wid1, geo::WireID const &wid2, double &y, double &z) const
Returns the intersection point of two wires.
Definition of data types for geometry description.
void err(const char *fmt,...)
Definition: message.cpp:226
int ID
ID that is local to one slice.
Definition: DataStructs.h:207
std::array< unsigned short, 2 > VtxID
ID of 2D vertex.
Definition: DataStructs.h:204
vertex quality is suspect - No requirement made on chg btw it and the Tj
Definition: DataStructs.h:100
double ConvertTicksToX(double ticks, int p, int t, int c) const
std::bitset< 16 > modes
number of points to find AveChg
Definition: DataStructs.h:609
std::vector< TCHit > slHits
Definition: DataStructs.h:671
unsigned short IsCloseToVertex(const TCSlice &slc, const VtxStore &inVx2)
Definition: TCVertex.cxx:2908
float VertexVertexPull(const TCSlice &slc, const Vtx3Store &vx1, const Vtx3Store &vx2)
Definition: TCVertex.cxx:1901
std::bitset< 16 > Stat
Vertex status bits using kVtxBit_t.
Definition: DataStructs.h:90
int ID
set to 0 if killed
Definition: DataStructs.h:85
void TrajIntersection(TrajPoint const &tp1, TrajPoint const &tp2, Point2_t &pos)
Definition: Utils.cxx:2602
std::vector< float > vtxScoreWeights
Definition: DataStructs.h:554
double DotProd(const Vector3_t &v1, const Vector3_t &v2)
Definition: PFPUtils.h:128
unsigned int CTP_t
Definition: DataStructs.h:49
void SetHighScoreBits(TCSlice &slc, Vtx3Store &vx3)
Definition: TCVertex.cxx:2207
std::vector< Vtx3Store > vtx3s
3D vertices
Definition: DataStructs.h:679
geo::TPCID TPCID
Definition: DataStructs.h:664
bool AttachTrajToVertex(TCSlice &slc, Trajectory &tj, VtxStore &vx, bool prt)
Definition: TCVertex.cxx:1742
std::bitset< 128 > useAlg
Max hit separation for making junk trajectories. < 0 to turn off.
Definition: DataStructs.h:588
for(std::string line;std::getline(inFile, line);)
Definition: regex_t.cc:37
float TrajLength(const Trajectory &tj)
Definition: Utils.cxx:2644
std::bitset< 128 > AlgMod
Bit set if algorithm AlgBit_t modifed the trajectory.
Definition: DataStructs.h:195
void UpdateVxEnvironment(TCSlice &slc)
Definition: Utils.cxx:3860
unsigned short NTraj
Definition: DataStructs.h:77
geo::PlaneID DecodeCTP(CTP_t CTP)
std::vector< int > GetVtxTjIDs(const TCSlice &slc, const VtxStore &vx2)
Definition: TCVertex.cxx:2851
double PosSep(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2564
std::bitset< 128 > dbgAlg
Allow user to turn on debug printing in algorithms (that print...)
Definition: DataStructs.h:589
CTP_t EncodeCTP(unsigned int cryo, unsigned int tpc, unsigned int plane)
Definition: DataStructs.h:54
void KillPoorVertices(TCSlice &slc)
Definition: TCVertex.cxx:2188
std::array< std::bitset< 8 >, 2 > EndFlag
Definition: DataStructs.h:214
#define A
Definition: memgrp.cpp:38
static bool * b
Definition: config.cpp:1043
int ID
ID of the recob::Slice (not the sub-slice)
Definition: DataStructs.h:666
int UID
unique global ID
Definition: DataStructs.h:86
void SetVx2Score(TCSlice &slc)
Definition: TCVertex.cxx:2278
Access the description of detector geometry.
void FindHammerVertices(TCSlice &slc, const CTP_t &inCTP)
Definition: TCVertex.cxx:811
std::vector< float > vtx2DCuts
Max position pull, max Position error rms.
Definition: DataStructs.h:552
unsigned short nPlanes
Definition: DataStructs.h:665
std::vector< PFPStruct > pfps
Definition: DataStructs.h:680
std::vector< int > FindCloseTjs(const TCSlice &slc, const TrajPoint &fromTp, const TrajPoint &toTp, const float &maxDelta)
Definition: Utils.cxx:2975
bool SignalBetween(const TCSlice &slc, const TrajPoint &tp1, const TrajPoint &tp2, const float &MinWireSignalFraction)
Definition: Utils.cxx:1805
void MoveTPToWire(TrajPoint &tp, float wire)
Definition: Utils.cxx:2829
unsigned short NumPtsWithCharge(const TCSlice &slc, const Trajectory &tj, bool includeDeadWires)
Definition: Utils.cxx:2114
void MakeJunkVertices(TCSlice &slc, const CTP_t &inCTP)
Definition: TCVertex.cxx:45
TCEvent evt
Definition: DataStructs.cxx:7
void Find2DVertices(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, const CTP_t &inCTP, unsigned short pass)
Definition: TCVertex.cxx:146
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:406
bool TrajTrajDOCA(const TCSlice &slc, const Trajectory &tj1, const Trajectory &tj2, unsigned short &ipt1, unsigned short &ipt2, float &minSep)
Definition: Utils.cxx:2458
Vector3_t DirAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3283
if(!yymsg) yymsg
void FindHammerVertices2(TCSlice &slc, const CTP_t &inCTP)
Definition: TCVertex.cxx:618
float TjChgFrac
Fraction of charge near the vertex that is from hits on the vertex Tjs.
Definition: DataStructs.h:89
master switch for turning on debug mode
Definition: DataStructs.h:535
std::string to_string(ModuleType const mt)
Definition: ModuleType.h:34
bool AttachToAnyVertex(TCSlice &slc, PFPStruct &pfp, float maxSep, bool prt)
Definition: TCVertex.cxx:1597
void PrintTPHeader(std::string someText)
Definition: Utils.cxx:6285
CTP_t CTP
set to an invalid CTP
Definition: DebugStruct.h:23
double HitPosErr2
Definition: DataStructs.h:159