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