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