LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
PFPUtils.cxx
Go to the documentation of this file.
2 
4 
5 #include "TDecompSVD.h"
6 #include "TMatrixD.h"
7 #include "TVectorD.h"
8 
9 #include <algorithm>
10 #include <array>
11 #include <bitset>
12 #include <cmath>
13 #include <iomanip>
14 #include <iostream>
15 #include <limits.h>
16 #include <limits>
17 #include <stdlib.h>
18 #include <utility>
19 #include <vector>
20 
35 
36 namespace tca {
37 
38  using namespace detail; // SortEntry, valsDecreasing(), valsIncreasing();
39 
41  void StitchPFPs()
42  {
43  // Stitch PFParticles in different TPCs. This does serious damage to PFPStruct and should
44  // only be called from TrajCluster module just before making PFParticles to put in the event
45  if (slices.size() < 2) return;
46  if (tcc.geom->NTPC() == 1) return;
47  if (tcc.pfpStitchCuts.size() < 2) return;
48  if (tcc.pfpStitchCuts[0] <= 0) return;
49 
50  bool prt = tcc.dbgStitch;
51 
52  if (prt) {
53  mf::LogVerbatim myprt("TC");
54  std::string fcnLabel = "SP";
55  myprt << fcnLabel << " cuts " << sqrt(tcc.pfpStitchCuts[0]) << " " << tcc.pfpStitchCuts[1]
56  << "\n";
57  bool printHeader = true;
58  for (size_t isl = 0; isl < slices.size(); ++isl) {
59  if (debug.Slice >= 0 && int(isl) != debug.Slice) continue;
60  auto& slc = slices[isl];
61  if (slc.pfps.empty()) continue;
62  for (auto& pfp : slc.pfps)
63  PrintP(myprt, pfp, printHeader);
64  } // slc
65  } // prt
66 
67  // lists of pfp UIDs to stitch
68  std::vector<std::vector<int>> stLists;
69  for (std::size_t sl1 = 0; sl1 < slices.size() - 1; ++sl1) {
70  auto& slc1 = slices[sl1];
71  for (std::size_t sl2 = sl1 + 1; sl2 < slices.size(); ++sl2) {
72  auto& slc2 = slices[sl2];
73  // look for PFParticles in the same recob::Slice
74  if (slc1.ID != slc2.ID) continue;
75  for (auto& p1 : slc1.pfps) {
76  if (p1.ID <= 0) continue;
77  // Can't stitch shower PFPs
78  if (p1.PDGCode == 1111) continue;
79  for (auto& p2 : slc2.pfps) {
80  if (p2.ID <= 0) continue;
81  // Can't stitch shower PFPs
82  if (p2.PDGCode == 1111) continue;
83  float maxSep2 = tcc.pfpStitchCuts[0];
84  float maxCth = tcc.pfpStitchCuts[1];
85  bool gotit = false;
86  for (unsigned short e1 = 0; e1 < 2; ++e1) {
87  auto pos1 = PosAtEnd(p1, e1);
88  // require the end to be close to a TPC boundary
89  if (InsideFV(slc1, p1, e1)) continue;
90  auto dir1 = DirAtEnd(p1, e1);
91  for (unsigned short e2 = 0; e2 < 2; ++e2) {
92  auto pos2 = PosAtEnd(p2, e2);
93  // require the end to be close to a TPC boundary
94  if (InsideFV(slc2, p2, e2)) continue;
95  auto dir2 = DirAtEnd(p2, e2);
96  float sep = PosSep2(pos1, pos2);
97  if (sep > maxSep2) continue;
98  float cth = std::abs(DotProd(dir1, dir2));
99  if (cth < maxCth) continue;
100  maxSep2 = sep;
101  maxCth = cth;
102  gotit = true;
103  } // e2
104  } // e1
105  if (!gotit) continue;
106  if (prt) {
107  mf::LogVerbatim myprt("TC");
108  myprt << "Stitch slice " << slc1.ID << " P" << p1.UID << " TPC " << p1.TPCID.TPC;
109  myprt << " and P" << p2.UID << " TPC " << p2.TPCID.TPC;
110  myprt << " sep " << sqrt(maxSep2) << " maxCth " << maxCth;
111  }
112  // see if either of these are in a list
113  bool added = false;
114  for (auto& pm : stLists) {
115  bool p1InList = (std::find(pm.begin(), pm.end(), p1.UID) != pm.end());
116  bool p2InList = (std::find(pm.begin(), pm.end(), p2.UID) != pm.end());
117  if (p1InList || p2InList) {
118  if (p1InList) pm.push_back(p2.UID);
119  if (p2InList) pm.push_back(p1.UID);
120  added = true;
121  }
122  } // pm
123  if (added) continue;
124  // start a new list
125  std::vector<int> tmp(2);
126  tmp[0] = p1.UID;
127  tmp[1] = p2.UID;
128  stLists.push_back(tmp);
129  break;
130  } // p2
131  } // p1
132  } // sl2
133  } // sl1
134  if (stLists.empty()) return;
135 
136  for (auto& stl : stLists) {
137  // Find the endpoints of the stitched pfp
138  float minZ = 1E6;
139  std::pair<unsigned short, unsigned short> minZIndx;
140  unsigned short minZEnd = 2;
141  for (auto puid : stl) {
142  auto slcIndex = GetSliceIndex("P", puid);
143  if (slcIndex.first == USHRT_MAX) continue;
144  auto& pfp = slices[slcIndex.first].pfps[slcIndex.second];
145  for (unsigned short end = 0; end < 2; ++end) {
146  auto pos = PosAtEnd(pfp, end);
147  if (pos[2] < minZ) {
148  minZ = pos[2];
149  minZIndx = slcIndex;
150  minZEnd = end;
151  }
152  } // end
153  } // puid
154  if (minZEnd > 1) continue;
155  // preserve the pfp with the min Z position
156  auto& pfp = slices[minZIndx.first].pfps[minZIndx.second];
157  if (prt) mf::LogVerbatim("TC") << "SP: P" << pfp.UID;
158  // add the Tjs in the other slices to it
159  for (auto puid : stl) {
160  if (puid == pfp.UID) continue;
161  auto sIndx = GetSliceIndex("P", puid);
162  if (sIndx.first == USHRT_MAX) continue;
163  auto& opfp = slices[sIndx.first].pfps[sIndx.second];
164  if (prt) mf::LogVerbatim("TC") << " +P" << opfp.UID;
165  pfp.TjUIDs.insert(pfp.TjUIDs.end(), opfp.TjUIDs.begin(), opfp.TjUIDs.end());
166  if (prt) mf::LogVerbatim();
167  // Check for parents and daughters
168  if (opfp.ParentUID > 0) {
169  auto pSlcIndx = GetSliceIndex("P", opfp.ParentUID);
170  if (pSlcIndx.first < slices.size()) {
171  auto& parpfp = slices[pSlcIndx.first].pfps[pSlcIndx.second];
172  std::replace(parpfp.DtrUIDs.begin(), parpfp.DtrUIDs.begin(), opfp.UID, pfp.UID);
173  } // valid pSlcIndx
174  } // has a parent
175  for (auto dtruid : opfp.DtrUIDs) {
176  auto dSlcIndx = GetSliceIndex("P", dtruid);
177  if (dSlcIndx.first < slices.size()) {
178  auto& dtrpfp = slices[dSlcIndx.first].pfps[dSlcIndx.second];
179  dtrpfp.ParentUID = pfp.UID;
180  } // valid dSlcIndx
181  } // dtruid
182  // declare it obsolete
183  opfp.ID = 0;
184  } // puid
185  } // stl
186 
187  } // StitchPFPs
188 
190  detinfo::DetectorPropertiesData const& detProp,
191  TCSlice& slc)
192  {
193  // Match Tjs in 3D and create PFParticles
194 
195  if (tcc.match3DCuts[0] <= 0) return;
196 
198 
199  // clear the TP -> P assn Tjs so that all are considered
200  for (auto& tj : slc.tjs) {
201  for (auto& tp : tj.Pts)
202  tp.InPFP = 0;
203  } // tj
204 
205  bool prt = (tcc.dbgPFP && tcc.dbgSlc);
206 
207  // Match these points in 3D
208  std::vector<MatchStruct> matVec;
209 
210  // iterate twice (at most), looking for 3-plane matches in long tjs in 3-plane TPCs on the
211  // first iteration, 3-plane matches in short tjs on the second iteration.
212  // and 2-plane matches + dead regions in 3-plane TPCs on the last iteration
213  slc.mallTraj.clear();
214 
215  unsigned short maxNit = 2;
216  if (slc.nPlanes == 2) maxNit = 1;
217  if (std::nearbyint(tcc.match3DCuts[2]) == 0) maxNit = 1;
218  // fill the mAllTraj vector with TPs if we aren't using SpacePoints
219  if (evt.sptHits.empty()) FillmAllTraj(detProp, slc);
220  for (unsigned short nit = 0; nit < maxNit; ++nit) {
221  matVec.clear();
222  if (slc.nPlanes == 3 && nit == 0) {
223  // look for match triplets
224  Match3Planes(slc, matVec);
225  }
226  else {
227  // look for match doublets requiring a dead region in the 3rd plane for 3-plane TPCs
228  Match2Planes(slc, matVec);
229  }
230  if (matVec.empty()) continue;
231  if (prt) {
232  mf::LogVerbatim myprt("TC");
233  myprt << "nit " << nit << " MVI Count Tjs\n";
234  for (unsigned int indx = 0; indx < matVec.size(); ++indx) {
235  auto& ms = matVec[indx];
236  myprt << std::setw(5) << indx << std::setw(6) << (int)ms.Count;
237  for (auto tid : ms.TjIDs)
238  myprt << " T" << tid;
239  // count the number of TPs in all Tjs
240  float tpCnt = 0;
241  for (auto tid : ms.TjIDs) {
242  auto& tj = slc.tjs[tid - 1];
243  tpCnt += NumPtsWithCharge(slc, tj, false);
244  } // tid
245  float frac = ms.Count / tpCnt;
246  myprt << " matFrac " << std::fixed << std::setprecision(3) << frac;
247  myprt << "\n";
248  } // indx
249  } // prt
250  MakePFParticles(clockData, detProp, slc, matVec, nit);
251  } // nit
252 
253  // a last debug print
254  if (tcc.dbgPFP && debug.MVI != UINT_MAX) {
255  for (auto& pfp : slc.pfps)
256  if (tcc.dbgPFP && pfp.MVI == debug.MVI)
257  PrintTP3Ds(clockData, detProp, "FPFP", slc, pfp, -1);
258  } // last debug print
259 
260  slc.mallTraj.resize(0);
261 
262  } // FindPFParticles
263 
266  detinfo::DetectorPropertiesData const& detProp,
267  TCSlice& slc,
268  std::vector<MatchStruct> matVec,
269  unsigned short matVec_Iter)
270  {
271  // Makes PFParticles using Tjs listed in matVec
272  if (matVec.empty()) return;
273 
274  bool prt = (tcc.dbgPFP && tcc.dbgSlc);
275 
276  // create a PFParticle for each valid match combination
277  for (std::size_t indx = 0; indx < matVec.size(); ++indx) {
278  // tone down the level of printing in ReSection
279  bool foundMVI = (tcc.dbgPFP && indx == debug.MVI && matVec_Iter == debug.MVI_Iter);
280  if (foundMVI) prt = true;
281  auto& ms = matVec[indx];
282  if (foundMVI) {
283  std::cout << "found MVI " << indx << " in MakePFParticles ms.Count = " << ms.Count << "\n";
284  }
285  // ignore dead matches
286  if (ms.Count == 0) continue;
287  // count the number of TPs that are available (not already 3D-matched) and used in a pfp
288  float npts = 0;
289  for (std::size_t itj = 0; itj < ms.TjIDs.size(); ++itj) {
290  auto& tj = slc.tjs[ms.TjIDs[itj] - 1];
291  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt)
292  if (tj.Pts[ipt].InPFP == 0) ++npts;
293  } // tjID
294  // Create a vector of PFPs for this match so that we can split it later on if a kink is found
295  std::vector<PFPStruct> pfpVec(1);
296  pfpVec[0] = CreatePFP(slc);
297  // Define the starting set of tjs that were matched. TPs from other tjs may be added later
298  pfpVec[0].TjIDs = ms.TjIDs;
299  pfpVec[0].MVI = indx;
300  // fill the TP3D points using the 2D trajectory points for Tjs in TjIDs. All
301  // points are put in one section
302  if (!MakeTP3Ds(detProp, slc, pfpVec[0], foundMVI)) {
303  if (foundMVI) mf::LogVerbatim("TC") << " MakeTP3Ds failed. Too many points already used ";
304  continue;
305  }
306  // fit all the points to get the general direction
307  if (!FitSection(detProp, slc, pfpVec[0], 0)) continue;
308  if (pfpVec[0].SectionFits[0].ChiDOF > 500 && !pfpVec[0].Flags[kSmallAngle]) {
309  if (foundMVI)
310  mf::LogVerbatim("TC") << " crazy high ChiDOF P" << pfpVec[0].ID << " "
311  << pfpVec[0].SectionFits[0].ChiDOF << "\n";
312  Recover(detProp, slc, pfpVec[0], foundMVI);
313  continue;
314  }
315  // sort the points by the distance along the general direction vector
316  if (!SortSection(pfpVec[0], 0)) continue;
317  // define a junk pfp to be short with low MCSMom. These are likely to be shower-like
318  // pfps. A simple 3D line fit will be done. No attempt will be made to reconstruct it
319  // in sections or to look for kinks
320  npts = pfpVec[0].TP3Ds.size();
321  pfpVec[0].AlgMod[kJunk3D] = (npts < 20 && MCSMom(slc, pfpVec[0].TjIDs) < 50) || (npts < 10);
322  if (prt) {
323  auto& pfp = pfpVec[0];
324  mf::LogVerbatim myprt("TC");
325  myprt << " indx " << matVec_Iter << "/" << indx << " Count " << std::setw(5)
326  << (int)ms.Count;
327  myprt << " P" << pfpVec[0].ID;
328  myprt << " ->";
329  for (auto& tjid : pfp.TjIDs)
330  myprt << " T" << tjid;
331  myprt << " projInPlane";
332  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
333  CTP_t inCTP = EncodeCTP(pfp.TPCID.Cryostat, pfp.TPCID.TPC, plane);
334  auto tp = MakeBareTP(detProp, pfp.SectionFits[0].Pos, pfp.SectionFits[0].Dir, inCTP);
335  myprt << " " << std::setprecision(2) << tp.Delta;
336  } // plane
337  myprt << " maxTjLen " << (int)MaxTjLen(slc, pfp.TjIDs);
338  myprt << " MCSMom " << MCSMom(slc, pfp.TjIDs);
339  myprt << " PDGCodeVote " << PDGCodeVote(clockData, detProp, slc, pfp);
340  myprt << " nTP3Ds " << pfp.TP3Ds.size();
341  myprt << " Reco3DRange "
342  << Find3DRecoRange(slc, pfp, 0, (unsigned short)tcc.match3DCuts[3], 1);
343  } // prt
344  if (foundMVI) { PrintTP3Ds(clockData, detProp, "FF", slc, pfpVec[0], -1); }
345  for (unsigned short ip = 0; ip < pfpVec.size(); ++ip) {
346  auto& pfp = pfpVec[ip];
347  // set the end flag bits
348  geo::TPCID tpcid;
349  for (unsigned short end = 0; end < 2; ++end) {
350  // first set them all to 0
351  pfp.EndFlag[end].reset();
352  auto pos = PosAtEnd(pfp, end);
353  if (!InsideTPC(pos, tpcid)) pfp.EndFlag[end][kOutFV] = true;
354  } // end
355  // Set kink flag and create a vertex between this pfp and the previous one that was stored
356  if (ip > 0) {
357  pfp.EndFlag[0][kAtKink] = true;
358  Vtx3Store vx3;
359  vx3.TPCID = pfp.TPCID;
360  vx3.X = pfp.TP3Ds[0].Pos[0];
361  vx3.Y = pfp.TP3Ds[0].Pos[1];
362  vx3.Z = pfp.TP3Ds[0].Pos[2];
363  // TODO: Errors, Score?
364  vx3.Score = 100;
365  vx3.Vx2ID.resize(slc.nPlanes);
366  vx3.Wire = -2;
367  vx3.ID = slc.vtx3s.size() + 1;
368  vx3.Primary = false;
369  ++evt.global3V_UID;
370  vx3.UID = evt.global3V_UID;
371  slc.vtx3s.push_back(vx3);
372  pfp.Vx3ID[0] = vx3.ID;
373  auto& prevPFP = slc.pfps[slc.pfps.size() - 1];
374  prevPFP.Vx3ID[1] = vx3.ID;
375  } // ip > 0
376  // check for a valid two-plane match with a Tj in the third plane for long pfps.
377  // For short pfps, it is possible that a Tj would be too short to be reconstructed
378  // in the third plane.
379  if (pfp.TjIDs.size() == 2 && slc.nPlanes == 3 && pfp.TP3Ds.size() > 20 &&
380  !ValidTwoPlaneMatch(detProp, slc, pfp)) {
381  continue;
382  }
383  // Skip this combination if it isn't reconstructable in 3D
384  if (Find3DRecoRange(slc, pfp, 0, (unsigned short)tcc.match3DCuts[3], 1) == USHRT_MAX)
385  continue;
386  // See if it possible to reconstruct in more than one section
387  pfp.Flags[kCanSection] = CanSection(slc, pfp);
388  // Do a fit in multiple sections if the initial fit is poor
389  if (pfp.SectionFits[0].ChiDOF < tcc.match3DCuts[5]) {
390  // Good fit with one section
391  pfp.Flags[kNeedsUpdate] = false;
392  }
393  else if (pfp.Flags[kCanSection]) {
394  if (!ReSection(detProp, slc, pfp, foundMVI)) continue;
395  } // CanSection
396  if (foundMVI) { PrintTP3Ds(clockData, detProp, "RS", slc, pfp, -1); }
397  // FillGaps3D looks for gaps in the TP3Ds vector caused by broken trajectories and
398  // inserts new TP3Ds if there are hits in the gaps. This search is only done in a
399  // plane if the projection of the pfp results in a large angle where 2D reconstruction
400  // is likely to be poor - not true for TCWork2
401  FillGaps3D(clockData, detProp, slc, pfp, foundMVI);
402  // Check the TP3D -> TP assn, resolve conflicts and set TP -> InPFP
403  if (!ReconcileTPs(slc, pfp, foundMVI)) continue;
404  // Look for mis-placed 2D and 3D vertices
405  ReconcileVertices(slc, pfp, foundMVI);
406  // Set isGood
407  for (auto& tp3d : pfp.TP3Ds) {
408  if (tp3d.Flags[kTP3DBad]) continue;
409  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
410  if (tp.Environment[kEnvOverlap]) tp3d.Flags[kTP3DGood] = false;
411  } // tp3d
412  FilldEdx(clockData, detProp, slc, pfp);
413  pfp.PDGCode = PDGCodeVote(clockData, detProp, slc, pfp);
414  if (tcc.dbgPFP && pfp.MVI == debug.MVI)
415  PrintTP3Ds(clockData, detProp, "STORE", slc, pfp, -1);
416  if (!StorePFP(slc, pfp)) break;
417  } // ip (iterate over split pfps)
418  } // indx (iterate over matchVec entries)
419  slc.mallTraj.resize(0);
420  } // MakePFParticles
421 
423  bool ReconcileTPs(TCSlice& slc, PFPStruct& pfp, bool prt)
424  {
425  // Reconcile TP -> P assns before the pfp is stored. The TP3D -> TP is defined but
426  // the TP -> P assn may not have been done. This function overwrites the TjIDs
427  // vector to be the list of Tjs that contribute > 80% of their TPs to this pfp.
428  // This function returns true if the assns are consistent.
429 
430  if (!tcc.useAlg[kRTPs3D]) return true;
431  if (pfp.Flags[kSmallAngle]) return true;
432  if (pfp.TjIDs.empty()) return false;
433  if (pfp.TP3Ds.empty()) return false;
434  if (pfp.ID <= 0) return false;
435 
436  // Tj ID, TP count
437  std::vector<std::pair<int, float>> tjTPCnt;
438  for (auto& tp3d : pfp.TP3Ds) {
439  if (tp3d.Flags[kTP3DBad]) continue;
440  if (tp3d.TjID <= 0) return false;
441  // compare the TP3D -> TP -> P assn with the P -> TP assn
442  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
443  if (tp.InPFP > 0 && tp.InPFP != pfp.ID) return false;
444  // find the (Tj ID, TP count) pair in the list
445  unsigned short indx = 0;
446  for (indx = 0; indx < tjTPCnt.size(); ++indx)
447  if (tjTPCnt[indx].first == tp3d.TjID) break;
448  if (indx == tjTPCnt.size()) tjTPCnt.push_back(std::make_pair(tp3d.TjID, 0));
449  ++tjTPCnt[indx].second;
450  // make the TP -> P assn
451  tp.InPFP = pfp.ID;
452  } // tp3d
453 
454  std::vector<int> nTjIDs;
455  for (auto& tjtpcnt : tjTPCnt) {
456  auto& tj = slc.tjs[tjtpcnt.first - 1];
457  float npwc = NumPtsWithCharge(slc, tj, false);
458  if (tjtpcnt.second > 0.8 * npwc) nTjIDs.push_back(tjtpcnt.first);
459  } // tjtpcnt
460  if (prt) { mf::LogVerbatim("TC") << "RTPs3D: P" << pfp.ID << " nTjIDs " << nTjIDs.size(); }
461  // TODO: is this really a failure?
462  if (nTjIDs.size() < 2) { return false; }
463  pfp.TjIDs = nTjIDs;
464 
465  return true;
466  } // ReconcileTPs
467 
470  {
471  // Reconciles TP ownership conflicts between PFParticles
472  // Make a one-to-one TP -> P assn and look for one-to-many assns.
473  // Note: Comparing the pulls for a TP to two different PFParticles generally results
474  // in selecting the first PFParticle that was made which is not too surprising considering
475  // the order in which they were created. This comparison has been commented out in favor
476  // of simply keeping the old assn and removing the new one by setting IsBad true.
477 
478  if (!tcc.useAlg[kRTPs3D]) return;
479 
480  // make a list of T -> P assns
481  std::vector<int> TinP;
482  for (auto& pfp : slc.pfps) {
483  if (pfp.ID <= 0) continue;
484  if (pfp.Flags[kSmallAngle]) continue;
485  for (std::size_t ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt) {
486  auto& tp3d = pfp.TP3Ds[ipt];
487  if (tp3d.TjID <= 0) continue;
488  if (std::find(TinP.begin(), TinP.end(), tp3d.TjID) == TinP.end()) TinP.push_back(tp3d.TjID);
489  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
490  if (tp.InPFP > 0) {
491  // an assn exists. Set the overlap bit and check consistency
492  tp.Environment[kEnvOverlap] = true;
493  // keep the previous assn (since it was created earlier and is more credible) and remove the new one
494  tp3d.Flags[kTP3DBad] = true;
495  tp3d.Flags[kTP3DGood] = false;
496  tp.InPFP = 0;
497  }
498  else {
499  // no assn exists
500  tp.InPFP = pfp.ID;
501  } // tp.InPFP > 0
502  } // ipt
503  } // pfp
504  } // ReconcileTPs
505 
507  void MakePFPTjs(TCSlice& slc)
508  {
509  // This function clobbers all of the tjs that are used in TP3Ds in the pfp and replaces
510  // them with new tjs that have a consistent set of TPs to prepare for putting them
511  // into the event. Note that none of the Tjs are attached to 2D vertices.
512  if (!tcc.useAlg[kMakePFPTjs]) return;
513 
514  // kill trajectories
515  std::vector<int> killme;
516  for (auto& pfp : slc.pfps) {
517  if (pfp.ID <= 0) continue;
518  for (auto& tp3d : pfp.TP3Ds) {
519  if (tp3d.TjID <= 0) continue;
520  if (tp3d.Flags[kTP3DBad]) continue;
521  if (std::find(killme.begin(), killme.end(), tp3d.TjID) == killme.end())
522  killme.push_back(tp3d.TjID);
523  } // tp3d
524  } // pfp
525 
526  bool prt = (tcc.dbgPFP);
527 
528  for (auto tid : killme)
529  MakeTrajectoryObsolete(slc, (unsigned int)(tid - 1));
530 
531  // Make template trajectories in each plane. These will be re-used by
532  // each PFParticle
533  std::vector<Trajectory> ptjs(slc.nPlanes);
534  // define the basic tj variables
535  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
536  ptjs[plane].Pass = 0;
537  ptjs[plane].CTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, plane);
538  // This Tj wasn't created by stepping
539  ptjs[plane].StepDir = 0;
540  // It was created by this function however
541  ptjs[plane].AlgMod[kMakePFPTjs] = true;
542  // and is 3D matched
543  ptjs[plane].AlgMod[kMat3D] = true;
544  } // plane
545 
546  // now make the new Tjs
547  for (auto& pfp : slc.pfps) {
548  if (pfp.ID <= 0) continue;
549  // initialize the tjs
550  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
551  ptjs[plane].Pts.clear();
552  --evt.WorkID;
553  if (evt.WorkID == INT_MIN) evt.WorkID = -1;
554  ptjs[plane].ID = evt.WorkID;
555  } // plane
556  pfp.TjIDs.clear();
557  // iterate through all of the TP3Ds, adding TPs to the TJ in the appropriate plane.
558  // The assumption here is that TP order reflects the TP3D order
559  for (auto& tp3d : pfp.TP3Ds) {
560  if (tp3d.TjID <= 0) continue;
561  if (tp3d.Flags[kTP3DBad]) continue;
562  // make a copy of the 2D TP
563  auto tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
564  if (tp.InPFP > 0 && tp.InPFP != pfp.ID) continue;
565  tp.InPFP = pfp.ID;
566  // the TP Step isn't useful anymore, so stash the original TJ ID into it
567  tp.Step = tp3d.TjID;
568  unsigned short plane = DecodeCTP(tp.CTP).Plane;
569  // append it to Pts
570  ptjs[plane].Pts.push_back(tp);
571  } // tp3d
572  // finish defining each of the Tjs and store them
573  // new tj ID indexed by plane
574  std::vector<int> tids(ptjs.size(), 0);
575  for (unsigned short plane = 0; plane < ptjs.size(); ++plane) {
576  auto& tj = ptjs[plane];
577  if (tj.Pts.size() < 2) continue;
578  tj.PDGCode = pfp.PDGCode;
579  tj.MCSMom = MCSMom(slc, tj);
580  if (!StoreTraj(slc, tj)) continue;
581  // associate it with the pfp
582  auto& newTj = slc.tjs.back();
583  pfp.TjIDs.push_back(newTj.ID);
584  tids[plane] = newTj.ID;
585  } // tj
586  // preserve the PFP -> 3V -> 2V -> T assns
587  for (unsigned short end = 0; end < 2; ++end) {
588  if (pfp.Vx3ID[end] <= 0) continue;
589  auto& vx3 = slc.vtx3s[pfp.Vx3ID[end] - 1];
590  for (unsigned short plane = 0; plane < ptjs.size(); ++plane) {
591  if (tids[plane] == 0) continue;
592  if (vx3.Vx2ID[plane] <= 0) continue;
593  auto& vx2 = slc.vtxs[vx3.Vx2ID[plane] - 1];
594  auto& tj = slc.tjs[tids[plane] - 1];
595  auto tend = CloseEnd(tj, vx2.Pos);
596  tj.VtxID[tend] = vx2.ID;
597  if (prt)
598  mf::LogVerbatim("TC") << "MPFPTjs: 3V" << vx3.ID << " -> 2V" << vx2.ID << " -> T"
599  << tj.ID << "_" << tend << " in plane " << plane;
600  } // plane
601  } // end
602  } // pfp
603  } // MakePFPTjs
604 
607  {
608  // Find wire intersections and put them in evt.wireIntersections
609 
610  // see if anything needs to be done
611  if (!evt.wireIntersections.empty() && evt.wireIntersections[0].tpc == slc.TPCID.TPC) return;
612 
613  evt.wireIntersections.clear();
614 
615  unsigned int cstat = slc.TPCID.Cryostat;
616  unsigned int tpc = slc.TPCID.TPC;
617  // find the minMax number of wires in each plane of the TPC
618  unsigned int maxWire = slc.nWires[0];
619  for (auto nw : slc.nWires)
620  if (nw < maxWire) maxWire = nw;
621  // Start looking for intersections in the middle
622  unsigned int firstWire = maxWire / 2;
623 
624  // find a valid wire intersection in all plane combinations
625  std::vector<std::pair<unsigned short, unsigned short>> pln1pln2;
626  for (unsigned short pln1 = 0; pln1 < slc.nPlanes - 1; ++pln1) {
627  for (unsigned short pln2 = pln1 + 1; pln2 < slc.nPlanes; ++pln2) {
628  auto p1p2 = std::make_pair(pln1, pln2);
629  if (std::find(pln1pln2.begin(), pln1pln2.end(), p1p2) != pln1pln2.end()) continue;
630  // find two wires that have a valid intersection
631  for (unsigned int wire = firstWire; wire < maxWire; ++wire) {
632  double y00, z00;
633  if (!tcc.geom->IntersectionPoint(
634  geo::WireID{cstat, tpc, pln1, wire}, geo::WireID{cstat, tpc, pln2, wire}, y00, z00))
635  continue;
636  // increment by one wire in pln1 and find another valid intersection
637  double y10, z10;
638  if (!tcc.geom->IntersectionPoint(geo::WireID{cstat, tpc, pln1, wire + 10},
639  geo::WireID{cstat, tpc, pln2, wire},
640  y10,
641  z10))
642  continue;
643  // increment by one wire in pln2 and find another valid intersection
644  double y01, z01;
645  if (!tcc.geom->IntersectionPoint(geo::WireID{cstat, tpc, pln1, wire},
646  geo::WireID{cstat, tpc, pln2, wire + 10},
647  y01,
648  z01))
649  continue;
650  TCWireIntersection tcwi;
651  tcwi.tpc = tpc;
652  tcwi.pln1 = pln1;
653  tcwi.pln2 = pln2;
654  tcwi.wir1 = wire;
655  tcwi.wir2 = wire;
656  tcwi.y = y00;
657  tcwi.z = z00;
658  tcwi.dydw1 = (y10 - y00) / 10;
659  tcwi.dzdw1 = (z10 - z00) / 10;
660  tcwi.dydw2 = (y01 - y00) / 10;
661  tcwi.dzdw2 = (z01 - z00) / 10;
662  evt.wireIntersections.push_back(tcwi);
663  break;
664  } // wire
665  } // pln2
666  } // pln1
667  } // FillWireIntersections
668 
670  bool TCIntersectionPoint(unsigned int wir1,
671  unsigned int wir2,
672  unsigned int pln1,
673  unsigned int pln2,
674  float& y,
675  float& z)
676  {
677  // A TrajCluster analog of geometry IntersectionPoint that uses local wireIntersections with
678  // float precision. The (y,z) position is only used to match TPs between planes - not for 3D fitting
679  if (evt.wireIntersections.empty()) return false;
680  if (pln1 == pln2) return false;
681 
682  if (pln1 > pln2) {
683  std::swap(pln1, pln2);
684  std::swap(wir1, wir2);
685  }
686 
687  for (auto& wi : evt.wireIntersections) {
688  if (wi.pln1 != pln1) continue;
689  if (wi.pln2 != pln2) continue;
690  // estimate the position using the wire differences
691  double dw1 = wir1 - wi.wir1;
692  double dw2 = wir2 - wi.wir2;
693  y = (float)(wi.y + dw1 * wi.dydw1 + dw2 * wi.dydw2);
694  z = (float)(wi.z + dw1 * wi.dzdw1 + dw2 * wi.dzdw2);
695  return true;
696  } // wi
697  return false;
698  } // TCIntersectionPoint
699 
701  void Match3PlanesSpt(TCSlice& slc, std::vector<MatchStruct>& matVec)
702  {
703  // fill matVec using SpacePoint -> Hit -> TP -> tj assns
704  if (evt.sptHits.empty()) return;
705 
706  // create a local vector of allHit -> Tj assns and populate it
707  std::vector<int> inTraj((*evt.allHits).size(), 0);
708  for (auto& tch : slc.slHits)
709  inTraj[tch.allHitsIndex] = tch.InTraj;
710 
711  // the TJ IDs for one match
712  std::array<int, 3> tIDs;
713  // vector for matched Tjs
714  std::vector<std::array<int, 3>> mtIDs;
715  // and a matching vector for the count
716  std::vector<unsigned short> mCnt;
717  // ignore Tj matches after hitting a user-defined limit
718  unsigned short maxCnt = USHRT_MAX;
719  if (tcc.match3DCuts[1] < (float)USHRT_MAX) maxCnt = (unsigned short)tcc.match3DCuts[1];
720  // a list of those Tjs
721  std::vector<unsigned short> tMaxed;
722 
723  unsigned int tpc = slc.TPCID.TPC;
724 
725  for (auto& sptHits : evt.sptHits) {
726  if (sptHits.size() != 3) continue;
727  // ensure that the SpacePoint is in the requested TPC
728  if (!SptInTPC(sptHits, tpc)) continue;
729  unsigned short cnt = 0;
730  for (unsigned short plane = 0; plane < 3; ++plane) {
731  unsigned int iht = sptHits[plane];
732  if (iht == UINT_MAX) continue;
733  if (inTraj[iht] <= 0) continue;
734  tIDs[plane] = inTraj[iht];
735  ++cnt;
736  } // iht
737  if (cnt != 3) continue;
738  // look for it in the list of tj combinations
739  unsigned short indx = 0;
740  for (indx = 0; indx < mtIDs.size(); ++indx)
741  if (tIDs == mtIDs[indx]) break;
742  if (indx == mtIDs.size()) {
743  // not found so add it to mtIDs and add another element to mCnt
744  mtIDs.push_back(tIDs);
745  mCnt.push_back(0);
746  }
747  ++mCnt[indx];
748  if (mCnt[indx] == maxCnt) {
749  // add the Tjs to the list
750  tMaxed.insert(tMaxed.end(), tIDs[0]);
751  tMaxed.insert(tMaxed.end(), tIDs[1]);
752  tMaxed.insert(tMaxed.end(), tIDs[2]);
753  break;
754  } // hit maxCnt
755  ++cnt;
756  } // sptHit
757 
758  std::vector<SortEntry> sortVec;
759  for (unsigned short indx = 0; indx < mCnt.size(); ++indx) {
760  auto& tIDs = mtIDs[indx];
761  // find the fraction of TPs on the shortest tj that are matched
762  float minTPCnt = USHRT_MAX;
763  for (auto tid : tIDs) {
764  auto& tj = slc.tjs[tid - 1];
765  float tpcnt = NumPtsWithCharge(slc, tj, false);
766  if (tpcnt < minTPCnt) minTPCnt = tpcnt;
767  } // tid
768  float frac = (float)mCnt[indx] / minTPCnt;
769  // ignore matches with a very low match fraction
770  if (frac < 0.05) continue;
771  SortEntry se;
772  se.index = indx;
773  se.val = mCnt[indx];
774  sortVec.push_back(se);
775  } // ii
776  if (sortVec.size() > 1) std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
777 
778  matVec.resize(sortVec.size());
779 
780  for (unsigned short ii = 0; ii < sortVec.size(); ++ii) {
781  unsigned short indx = sortVec[ii].index;
782  auto& ms = matVec[ii];
783  ms.Count = mCnt[indx];
784  ms.TjIDs.resize(3);
785  for (unsigned short plane = 0; plane < 3; ++plane)
786  ms.TjIDs[plane] = mtIDs[indx][plane];
787  } // indx
788 
789  } // Match3PlanesSpt
790 
792  bool SptInTPC(const std::array<unsigned int, 3>& sptHits, unsigned int tpc)
793  {
794  // returns true if a hit referenced in sptHits resides in the requested tpc. We assume
795  // that if one does, then all of them do
796 
797  unsigned int ahi = UINT_MAX;
798  for (auto ii : sptHits)
799  if (ii != UINT_MAX) {
800  ahi = ii;
801  break;
802  }
803  if (ahi >= (*evt.allHits).size()) return false;
804  // get a reference to the hit and see if it is in the desired tpc
805  auto& hit = (*evt.allHits)[ahi];
806  if (hit.WireID().TPC == tpc) return true;
807  return false;
808 
809  } // SptInTPC
810 
812  void Match3Planes(TCSlice& slc, std::vector<MatchStruct>& matVec)
813  {
814  // A simpler and faster version of MatchPlanes that only creates three plane matches
815 
816  if (slc.nPlanes != 3) return;
817 
818  // use SpacePoint -> Hit -> TP assns?
819  if (!evt.sptHits.empty()) {
820  Match3PlanesSpt(slc, matVec);
821  return;
822  }
823 
824  if (slc.mallTraj.empty()) return;
825  float xcut = tcc.match3DCuts[0];
826  double yzcut = 1.5 * tcc.wirePitch;
827 
828  // the TJ IDs for one match
829  std::array<unsigned short, 3> tIDs;
830  // vector for matched Tjs
831  std::vector<std::array<unsigned short, 3>> mtIDs;
832  // and a matching vector for the count
833  std::vector<unsigned short> mCnt;
834  // ignore Tj matches after hitting a user-defined limit
835  unsigned short maxCnt = USHRT_MAX;
836  if (tcc.match3DCuts[1] < (float)USHRT_MAX) maxCnt = (unsigned short)tcc.match3DCuts[1];
837  // a list of those Tjs
838  std::vector<unsigned short> tMaxed;
839 
840  for (std::size_t ipt = 0; ipt < slc.mallTraj.size() - 1; ++ipt) {
841  auto& iTjPt = slc.mallTraj[ipt];
842  // see if we hit the maxCnt limit
843  if (std::find(tMaxed.begin(), tMaxed.end(), iTjPt.id) != tMaxed.end()) continue;
844  auto& itp = slc.tjs[iTjPt.id - 1].Pts[iTjPt.ipt];
845  unsigned int iPlane = iTjPt.plane;
846  unsigned int iWire = std::nearbyint(itp.Pos[0]);
847  tIDs[iPlane] = iTjPt.id;
848  bool hitMaxCnt = false;
849  for (std::size_t jpt = ipt + 1; jpt < slc.mallTraj.size() - 1; ++jpt) {
850  auto& jTjPt = slc.mallTraj[jpt];
851  // ensure that the planes are different
852  if (jTjPt.plane == iTjPt.plane) continue;
853  // check for x range overlap. We know that jTjPt.xlo is >= iTjPt.xlo because of the sort
854  if (jTjPt.xlo > iTjPt.xhi) continue;
855  // break out if the x range difference becomes large
856  if (jTjPt.xlo > iTjPt.xhi + xcut) break;
857  // see if we hit the maxCnt limit
858  if (std::find(tMaxed.begin(), tMaxed.end(), jTjPt.id) != tMaxed.end()) continue;
859  auto& jtp = slc.tjs[jTjPt.id - 1].Pts[jTjPt.ipt];
860  unsigned short jPlane = jTjPt.plane;
861  unsigned int jWire = jtp.Pos[0];
862  Point2_t ijPos;
863  if (!TCIntersectionPoint(iWire, jWire, iPlane, jPlane, ijPos[0], ijPos[1])) continue;
864  tIDs[jPlane] = jTjPt.id;
865  for (std::size_t kpt = jpt + 1; kpt < slc.mallTraj.size(); ++kpt) {
866  auto& kTjPt = slc.mallTraj[kpt];
867  // ensure that the planes are different
868  if (kTjPt.plane == iTjPt.plane || kTjPt.plane == jTjPt.plane) continue;
869  if (kTjPt.xlo > iTjPt.xhi) continue;
870  // break out if the x range difference becomes large
871  if (kTjPt.xlo > iTjPt.xhi + xcut) break;
872  // see if we hit the maxCnt limit
873  if (std::find(tMaxed.begin(), tMaxed.end(), kTjPt.id) != tMaxed.end()) continue;
874  auto& ktp = slc.tjs[kTjPt.id - 1].Pts[kTjPt.ipt];
875  unsigned short kPlane = kTjPt.plane;
876  unsigned int kWire = ktp.Pos[0];
877  Point2_t ikPos;
878  if (!TCIntersectionPoint(iWire, kWire, iPlane, kPlane, ikPos[0], ikPos[1])) continue;
879  if (std::abs(ijPos[0] - ikPos[0]) > yzcut) continue;
880  if (std::abs(ijPos[1] - ikPos[1]) > yzcut) continue;
881  // we have a match
882  tIDs[kPlane] = kTjPt.id;
883  // look for it in the list
884  unsigned int indx = 0;
885  for (indx = 0; indx < mtIDs.size(); ++indx)
886  if (tIDs == mtIDs[indx]) break;
887  if (indx == mtIDs.size()) {
888  // not found so add it to mtIDs and add another element to mCnt
889  mtIDs.push_back(tIDs);
890  mCnt.push_back(0);
891  }
892  ++mCnt[indx];
893  if (mCnt[indx] == maxCnt) {
894  // add the Tjs to the list
895  tMaxed.insert(tMaxed.end(), tIDs[0]);
896  tMaxed.insert(tMaxed.end(), tIDs[1]);
897  tMaxed.insert(tMaxed.end(), tIDs[2]);
898  hitMaxCnt = true;
899  break;
900  } // hit maxCnt
901  } // kpt
902  if (hitMaxCnt) break;
903  } // jpt
904  } // ipt
905 
906  if (mCnt.empty()) return;
907 
908  std::vector<SortEntry> sortVec;
909  for (std::size_t indx = 0; indx < mCnt.size(); ++indx) {
910  auto& tIDs = mtIDs[indx];
911  // count the number of TPs in all Tjs
912  float tpCnt = 0;
913  for (auto tid : tIDs) {
914  auto& tj = slc.tjs[tid - 1];
915  tpCnt += NumPtsWithCharge(slc, tj, false);
916  } // tid
917  float frac = mCnt[indx] / tpCnt;
918  frac /= 3;
919  // ignore matches with a very low match fraction
920  if (frac < 0.05) continue;
921  SortEntry se;
922  se.index = indx;
923  se.val = mCnt[indx];
924  sortVec.push_back(se);
925  } // ii
926  if (sortVec.size() > 1) std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
927 
928  matVec.resize(sortVec.size());
929 
930  for (std::size_t ii = 0; ii < sortVec.size(); ++ii) {
931  unsigned short indx = sortVec[ii].index;
932  auto& ms = matVec[ii];
933  ms.Count = mCnt[indx];
934  ms.TjIDs.resize(3);
935  for (unsigned short plane = 0; plane < 3; ++plane)
936  ms.TjIDs[plane] = (int)mtIDs[indx][plane];
937  } // indx
938 
939  } // Match3Planes
940 
942  void Match2Planes(TCSlice& slc, std::vector<MatchStruct>& matVec)
943  {
944  // A simpler faster version of MatchPlanes that only creates two plane matches
945 
946  matVec.clear();
947  if (slc.mallTraj.empty()) return;
948 
949  int cstat = slc.TPCID.Cryostat;
950  int tpc = slc.TPCID.TPC;
951 
952  float xcut = tcc.match3DCuts[0];
953 
954  // the TJ IDs for one match
955  std::array<unsigned short, 2> tIDs;
956  // vector for matched Tjs
957  std::vector<std::array<unsigned short, 2>> mtIDs;
958  // and a matching vector for the count
959  std::vector<unsigned short> mCnt;
960  // ignore Tj matches after hitting a user-defined limit
961  unsigned short maxCnt = USHRT_MAX;
962  if (tcc.match3DCuts[1] < (float)USHRT_MAX) maxCnt = (unsigned short)tcc.match3DCuts[1];
963  // a list of those Tjs
964  std::vector<unsigned short> tMaxed;
965 
966  for (std::size_t ipt = 0; ipt < slc.mallTraj.size() - 1; ++ipt) {
967  auto& iTjPt = slc.mallTraj[ipt];
968  // see if we hit the maxCnt limit
969  if (std::find(tMaxed.begin(), tMaxed.end(), iTjPt.id) != tMaxed.end()) continue;
970  auto& itp = slc.tjs[iTjPt.id - 1].Pts[iTjPt.ipt];
971  unsigned short iPlane = iTjPt.plane;
972  unsigned int iWire = itp.Pos[0];
973  bool hitMaxCnt = false;
974  for (std::size_t jpt = ipt + 1; jpt < slc.mallTraj.size() - 1; ++jpt) {
975  auto& jTjPt = slc.mallTraj[jpt];
976  // ensure that the planes are different
977  if (jTjPt.plane == iTjPt.plane) continue;
978  // check for x range overlap. We know that jTjPt.xlo is >= iTjPt.xlo because of the sort
979  if (jTjPt.xlo > iTjPt.xhi) continue;
980  // break out if the x range difference becomes large
981  if (jTjPt.xlo > iTjPt.xhi + xcut) break;
982  // see if we hit the maxCnt limit
983  if (std::find(tMaxed.begin(), tMaxed.end(), jTjPt.id) != tMaxed.end()) continue;
984  auto& jtp = slc.tjs[jTjPt.id - 1].Pts[jTjPt.ipt];
985  unsigned short jPlane = jTjPt.plane;
986  unsigned int jWire = jtp.Pos[0];
987  Point3_t ijPos;
988  ijPos[0] = itp.Pos[0];
989  if (!tcc.geom->IntersectionPoint(geo::WireID(cstat, tpc, iPlane, iWire),
990  geo::WireID(cstat, tpc, jPlane, jWire),
991  ijPos[1],
992  ijPos[2]))
993  continue;
994  tIDs[0] = iTjPt.id;
995  tIDs[1] = jTjPt.id;
996  // swap the order so that the == operator works correctly
997  if (tIDs[0] > tIDs[1]) std::swap(tIDs[0], tIDs[1]);
998  // look for it in the list
999  std::size_t indx = 0;
1000  for (indx = 0; indx < mtIDs.size(); ++indx)
1001  if (tIDs == mtIDs[indx]) break;
1002  if (indx == mtIDs.size()) {
1003  // not found so add it to mtIDs and add another element to mCnt
1004  mtIDs.push_back(tIDs);
1005  mCnt.push_back(0);
1006  }
1007  ++mCnt[indx];
1008  if (mCnt[indx] == maxCnt) {
1009  // add the Tjs to the list
1010  tMaxed.insert(tMaxed.end(), tIDs[0]);
1011  tMaxed.insert(tMaxed.end(), tIDs[1]);
1012  hitMaxCnt = true;
1013  break;
1014  } // hit maxCnt
1015  if (hitMaxCnt) break;
1016  } // jpt
1017  } // ipt
1018 
1019  if (mCnt.empty()) return;
1020 
1021  std::vector<SortEntry> sortVec;
1022  for (std::size_t indx = 0; indx < mCnt.size(); ++indx) {
1023  auto& tIDs = mtIDs[indx];
1024  // count the number of TPs in all Tjs
1025  float tpCnt = 0;
1026  for (auto tid : tIDs) {
1027  auto& tj = slc.tjs[tid - 1];
1028  tpCnt += NumPtsWithCharge(slc, tj, false);
1029  } // tid
1030  float frac = mCnt[indx] / tpCnt;
1031  frac /= 2;
1032  // ignore matches with a very low match fraction
1033  if (frac < 0.05) continue;
1034  SortEntry se;
1035  se.index = indx;
1036  se.val = mCnt[indx];
1037  sortVec.push_back(se);
1038  } // ii
1039  if (sortVec.size() > 1) std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
1040 
1041  matVec.resize(sortVec.size());
1042 
1043  for (std::size_t ii = 0; ii < sortVec.size(); ++ii) {
1044  unsigned short indx = sortVec[ii].index;
1045  auto& ms = matVec[ii];
1046  ms.Count = mCnt[indx];
1047  ms.TjIDs.resize(2);
1048  for (unsigned short plane = 0; plane < 2; ++plane)
1049  ms.TjIDs[plane] = (int)mtIDs[indx][plane];
1050  } // indx
1051 
1052  } // Match2Planes
1053 
1055  bool Update(detinfo::DetectorPropertiesData const& detProp, const TCSlice& slc, PFPStruct& pfp)
1056  {
1057  // This function only updates SectionFits that need to be re-sorted or re-fit. It returns
1058  // false if there was a serious error indicating that the pfp should be abandoned
1059  if (pfp.TP3Ds.empty() || pfp.SectionFits.empty()) return false;
1060 
1061  // special handling for small angle tracks
1062  if (pfp.AlgMod[kSmallAngle]) {
1063  for (unsigned short sfi = 0; sfi < pfp.SectionFits.size(); ++sfi) {
1064  auto& sf = pfp.SectionFits[sfi];
1065  if (!sf.NeedsUpdate) continue;
1066  if (!SortSection(pfp, sfi)) return false;
1067  sf.NPts = 0;
1068  sf.ChiDOF = 0;
1069  for (unsigned short ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt) {
1070  auto& tp3d = pfp.TP3Ds[ipt];
1071  if (tp3d.SFIndex < sfi) continue;
1072  if (tp3d.SFIndex > sfi) break;
1073  ++sf.NPts;
1074  double delta = tp3d.Pos[0] - tp3d.TPX;
1075  sf.ChiDOF += delta * delta / tp3d.TPXErr2;
1076  } // ipt
1077  if (sf.NPts < 5) { sf.ChiDOF = 0; }
1078  else {
1079  sf.ChiDOF /= (float)(sf.NPts - 4);
1080  }
1081  sf.NeedsUpdate = false;
1082  } // sfi
1083  pfp.Flags[kNeedsUpdate] = false;
1084  return true;
1085  } // kSmallAngle
1086 
1087  for (std::size_t sfi = 0; sfi < pfp.SectionFits.size(); ++sfi) {
1088  auto& sf = pfp.SectionFits[sfi];
1089  if (!sf.NeedsUpdate) continue;
1090  if (!FitSection(detProp, slc, pfp, sfi)) return false;
1091  if (!SortSection(pfp, sfi)) return false;
1092  sf.NeedsUpdate = false;
1093  } // sfi
1094 
1095  // ensure that all points (good or not) have a valid SFIndex
1096  for (auto& tp3d : pfp.TP3Ds) {
1097  if (tp3d.SFIndex >= pfp.SectionFits.size()) SetSection(detProp, pfp, tp3d);
1098  } // tp3d
1099  pfp.Flags[kNeedsUpdate] = false;
1100  return true;
1101  } // Update
1102 
1105  const TCSlice& slc,
1106  PFPStruct& pfp,
1107  bool prt)
1108  {
1109  // Re-fit the TP3Ds in sections and add/remove sections to keep ChiDOF of each section close to 1.
1110  // This function only fails when there is a serious error, otherwise if reasonable fits cannot be
1111  // achieved, the CanSection flag is set false.
1112  if (pfp.SectionFits.empty()) return false;
1113  // This function shouldn't be called if this is the case but it isn't a major failure if it is
1114  if (!pfp.Flags[kCanSection]) return true;
1115  if (pfp.Flags[kSmallAngle]) return true;
1116  // Likewise this shouldn't be attempted if there aren't at least 3 points in 2 planes in 2 sections
1117  // but it isn't a failure
1118  if (pfp.TP3Ds.size() < 12) {
1119  pfp.Flags[kCanSection] = false;
1120  return true;
1121  }
1122 
1123  prt = (pfp.MVI == debug.MVI);
1124 
1125  // try to keep ChiDOF between chiLo and chiHi
1126  float chiLo = 0.5 * tcc.match3DCuts[5];
1127  float chiHi = 1.5 * tcc.match3DCuts[5];
1128 
1129  // clobber the old sections if more than one exists
1130  if (pfp.SectionFits.size() > 1) {
1131  // make one section
1132  pfp.SectionFits.resize(1);
1133  // put all of the points in it and fit
1134  for (auto& tp3d : pfp.TP3Ds) {
1135  tp3d.SFIndex = 0;
1136  tp3d.Flags[kTP3DGood] = true;
1137  }
1138  auto& sf = pfp.SectionFits[0];
1139  if (!FitSection(detProp, slc, pfp, 0)) { return false; }
1140  if (sf.ChiDOF < tcc.match3DCuts[5]) return true;
1141  } // > 1 SectionFit
1142  // sort by distance from the start
1143  if (!SortSection(pfp, 0)) return false;
1144  // require a minimum of 3 points in 2 planes
1145  unsigned short min2DPts = 3;
1146  unsigned short fromPt = 0;
1147  // set the section index to invalid for all points
1148  for (auto& tp3d : pfp.TP3Ds)
1149  tp3d.SFIndex = USHRT_MAX;
1150  // Guess how many points should be added in each iteration
1151  unsigned short nPtsToAdd = pfp.TP3Ds.size() / 4;
1152  // the actual number of points that will be fit in the section
1153  unsigned short nPts = nPtsToAdd;
1154  // the minimum number of points
1155  unsigned short nPtsMin = Find3DRecoRange(slc, pfp, fromPt, min2DPts, 1) - fromPt + 1;
1156  if (nPtsMin >= pfp.TP3Ds.size()) {
1157  pfp.Flags[kCanSection] = false;
1158  return true;
1159  }
1160  float chiDOF = 0;
1161  if (nPts < nPtsMin) nPts = nPtsMin;
1162  // Try to reduce the number of iterations for long pfps
1163  if (pfp.TP3Ds.size() > 100) {
1164  unsigned short nhalf = pfp.TP3Ds.size() / 2;
1165  FitTP3Ds(detProp, slc, pfp, fromPt, nhalf, USHRT_MAX);
1166  if (chiDOF < tcc.match3DCuts[5]) nPts = nhalf;
1167  }
1168  bool lastSection = false;
1169  for (unsigned short sfIndex = 0; sfIndex < 20; ++sfIndex) {
1170  // Try to add/remove points in each section no more than 20 times
1171  float chiDOFPrev = 0;
1172  short nHiChi = 0;
1173  for (unsigned short nit = 0; nit < 10; ++nit) {
1174  // Decide how many points to add or subtract after doing the fit
1175  unsigned short nPtsNext = nPts;
1176  if (!FitTP3Ds(detProp, slc, pfp, fromPt, nPts, USHRT_MAX)) { nPtsNext += 1.5 * nPtsToAdd; }
1177  else if (chiDOF < chiLo) {
1178  // low chiDOF
1179  if (nHiChi > 2) {
1180  // declare it close enough if several attempts were made
1181  nPtsNext = 0;
1182  }
1183  else {
1184  nPtsNext += nPtsToAdd;
1185  } // nHiChi < 2
1186  nHiChi = 0;
1187  }
1188  else if (chiDOF > chiHi) {
1189  // high chiDOF
1190  ++nHiChi;
1191  if (nHiChi == 1 && chiDOFPrev > tcc.match3DCuts[5]) {
1192  // reduce the number of points by 1/2 on the first attempt
1193  nPtsNext /= 2;
1194  }
1195  else {
1196  // that didn't work so start subtracting groups of points
1197  short npnext = (short)nPts - nHiChi * 5;
1198  // assume this won't work
1199  nPtsNext = 0;
1200  if (npnext > nPtsMin) nPtsNext = npnext;
1201  }
1202  }
1203  else {
1204  // just right
1205  nPtsNext = 0;
1206  }
1207  // check for passing the end
1208  if (fromPt + nPtsNext >= pfp.TP3Ds.size()) {
1209  nPtsNext = pfp.TP3Ds.size() - fromPt;
1210  lastSection = true;
1211  }
1212  if (prt) {
1213  mf::LogVerbatim myprt("TC");
1214  myprt << " RS: P" << pfp.ID << " sfi/nit/npts " << sfIndex << "/" << nit << "/" << nPts;
1215  myprt << std::fixed << std::setprecision(1) << " chiDOF " << chiDOF;
1216  myprt << " fromPt " << fromPt;
1217  myprt << " nPtsNext " << nPtsNext;
1218  myprt << " nHiChi " << nHiChi;
1219  myprt << " lastSection? " << lastSection;
1220  }
1221  if (nPtsNext == 0) break;
1222  // see if this is the last section
1223  if (lastSection) break;
1224  if (chiDOF == chiDOFPrev) {
1225  if (prt) mf::LogVerbatim("TC") << " MVI " << pfp.MVI << " chiDOF not changing\n";
1226  break;
1227  }
1228  nPts = nPtsNext;
1229  chiDOFPrev = chiDOF;
1230  } // nit
1231  // finished this section. Assign the points to it
1232  unsigned short toPt = fromPt + nPts;
1233  if (toPt > pfp.TP3Ds.size()) toPt = pfp.TP3Ds.size();
1234  for (unsigned short ipt = fromPt; ipt < toPt; ++ipt)
1235  pfp.TP3Ds[ipt].SFIndex = sfIndex;
1236  // See if there are enough points remaining to reconstruct another section if this isn't known
1237  // to be the last section
1238  if (!lastSection) {
1239  // this will be the first point in the next section
1240  unsigned short nextFromPt = fromPt + nPts;
1241  // See if it will have enough points to be reconstructed
1242  unsigned short nextToPtMin = Find3DRecoRange(slc, pfp, nextFromPt, min2DPts, 1);
1243  if (nextToPtMin == USHRT_MAX) {
1244  // not enough points so this is the last section
1245  lastSection = true;
1246  // assign the remaining points to the last section
1247  for (std::size_t ipt = nextFromPt; ipt < pfp.TP3Ds.size(); ++ipt)
1248  pfp.TP3Ds[ipt].SFIndex = sfIndex;
1249  }
1250  } // !lastSection
1251  // Do a final fit and update the points. Don't worry about a poor ChiDOF
1252  FitSection(detProp, slc, pfp, sfIndex);
1253  if (!SortSection(pfp, 0)) { return false; }
1254  if (lastSection) break;
1255  // Prepare for the next section.
1256  fromPt = fromPt + nPts;
1257  nPts = nPtsToAdd;
1258  nPtsMin = Find3DRecoRange(slc, pfp, fromPt, min2DPts, 1) - fromPt + 1;
1259  if (nPtsMin >= pfp.TP3Ds.size()) break;
1260  // add a new section
1261  pfp.SectionFits.resize(pfp.SectionFits.size() + 1);
1262  } // snit
1263 
1264  // see if the last sf is valid
1265  if (pfp.SectionFits.size() > 1 && pfp.SectionFits.back().ChiDOF < 0) {
1266  unsigned short badSFI = pfp.SectionFits.size() - 1;
1267  // remove it
1268  pfp.SectionFits.pop_back();
1269  for (std::size_t ipt = pfp.TP3Ds.size() - 1; ipt > 0; --ipt) {
1270  auto& tp3d = pfp.TP3Ds[ipt];
1271  if (tp3d.SFIndex < badSFI) break;
1272  --tp3d.SFIndex;
1273  }
1274  pfp.SectionFits.back().NeedsUpdate = true;
1275  } // bad last SF
1276 
1277  // Ensure that the points at the end are in the last section
1278  for (std::size_t ipt = pfp.TP3Ds.size() - 1; ipt > 0; --ipt) {
1279  auto& tp3d = pfp.TP3Ds[ipt];
1280  if (tp3d.SFIndex < pfp.SectionFits.size()) break;
1281  tp3d.SFIndex = pfp.SectionFits.size() - 1;
1282  pfp.Flags[kNeedsUpdate] = true;
1283  pfp.SectionFits[tp3d.SFIndex].NeedsUpdate = true;
1284  } // tp3d
1285 
1286  Update(detProp, slc, pfp);
1287 
1288  // set CanSection false if the chisq is poor in any section
1289  for (auto& sf : pfp.SectionFits) {
1290  if (sf.ChiDOF > tcc.match3DCuts[5]) pfp.Flags[kCanSection] = false;
1291  }
1292 
1293  return true;
1294  } // resection
1295 
1297  void CountBadPoints(const TCSlice& slc,
1298  const PFPStruct& pfp,
1299  unsigned short fromPt,
1300  unsigned short toPt,
1301  unsigned short& nBadPts,
1302  unsigned short& firstBadPt)
1303  {
1304  // Count the number of points whose pull exceeds tcc.match3DCuts[4]
1305  firstBadPt = USHRT_MAX;
1306  nBadPts = 0;
1307  if (fromPt > pfp.TP3Ds.size() - 1) {
1308  nBadPts = USHRT_MAX;
1309  return;
1310  }
1311  if (toPt > pfp.TP3Ds.size()) toPt = pfp.TP3Ds.size();
1312  bool first = true;
1313  for (unsigned short ipt = fromPt; ipt < toPt; ++ipt) {
1314  auto& tp3d = pfp.TP3Ds[ipt];
1315  if (!tp3d.Flags[kTP3DGood]) continue;
1316  // don't clobber a point if it is on a TP that is overlapping another Tj. This will
1317  // happen for points close to a vertex and when trajectories cross
1318  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
1319  if (tp.Environment[kEnvOverlap]) continue;
1320  if (PointPull(tp3d) < tcc.match3DCuts[4]) continue;
1321  ++nBadPts;
1322  if (first) {
1323  first = false;
1324  firstBadPt = ipt;
1325  }
1326  } // ipt
1327  } // CountBadPoints
1328 
1330  bool CanSection(const TCSlice& slc, const PFPStruct& pfp)
1331  {
1332  // analyze the TP3D vector to determine if it can be reconstructed in 3D in more than one section with
1333  // the requirement that there are at least 3 points in two planes
1334  if (pfp.AlgMod[kJunk3D]) return false;
1335  if (pfp.AlgMod[kSmallAngle]) return false;
1336  if (pfp.TP3Ds.size() < 12) return false;
1337  unsigned short toPt = Find3DRecoRange(slc, pfp, 0, 3, 1);
1338  if (toPt > pfp.TP3Ds.size()) return false;
1339  unsigned short nextToPt = Find3DRecoRange(slc, pfp, toPt, 3, 1);
1340  if (nextToPt > pfp.TP3Ds.size()) return false;
1341  return true;
1342  } // CanSection
1343 
1345  unsigned short Find3DRecoRange(const TCSlice& slc,
1346  const PFPStruct& pfp,
1347  unsigned short fromPt,
1348  unsigned short min2DPts,
1349  short dir)
1350  {
1351  // Scans the TP3Ds vector starting at fromPt until it finds min2DPts in two planes. It returns
1352  // with the index of that point (+1) in the TP3Ds vector. The dir variable defines the scan direction in
1353  // the TP3Ds vector
1354  if (fromPt > pfp.TP3Ds.size() - 1) return USHRT_MAX;
1355  if (pfp.TP3Ds.size() < 2 * min2DPts) return USHRT_MAX;
1356  if (dir == 0) return USHRT_MAX;
1357 
1358  std::vector<unsigned short> cntInPln(slc.nPlanes);
1359  for (std::size_t ii = 0; ii < pfp.TP3Ds.size(); ++ii) {
1360  unsigned short ipt = fromPt + ii;
1361  if (dir < 0) ipt = fromPt - ii;
1362  if (ipt >= pfp.TP3Ds.size()) break;
1363  auto& tp3d = pfp.TP3Ds[ipt];
1364  if (!tp3d.Flags[kTP3DGood]) continue;
1365  unsigned short plane = DecodeCTP(slc.tjs[tp3d.TjID - 1].CTP).Plane;
1366  ++cntInPln[plane];
1367  unsigned short enufInPlane = 0;
1368  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane)
1369  if (cntInPln[plane] >= min2DPts) ++enufInPlane;
1370  if (enufInPlane > 1) return ipt + 1;
1371  if (dir < 0 && ipt == 0) break;
1372  } // ipt
1373  return USHRT_MAX;
1374  } // Find3DRecoRange
1375 
1377  void GetRange(const PFPStruct& pfp,
1378  unsigned short sfIndex,
1379  unsigned short& fromPt,
1380  unsigned short& npts)
1381  {
1382  fromPt = USHRT_MAX;
1383  if (sfIndex >= pfp.SectionFits.size()) return;
1384  if (pfp.TP3Ds.empty()) return;
1385  fromPt = USHRT_MAX;
1386  npts = 0;
1387  // Note that no test is made for not-good TP3Ds here since that would give a wrong npts count
1388  for (std::size_t ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt) {
1389  auto& tp3d = pfp.TP3Ds[ipt];
1390  if (tp3d.SFIndex < sfIndex) continue;
1391  if (tp3d.SFIndex > sfIndex) break;
1392  if (fromPt == USHRT_MAX) fromPt = ipt;
1393  ++npts;
1394  } // ipt
1395  } // GetRange
1396 
1399  const TCSlice& slc,
1400  PFPStruct& pfp,
1401  unsigned short sfIndex)
1402  {
1403  // Fits the TP3D points in the selected section to a 3D line with the origin at the center of
1404  // the section
1405  if (pfp.TP3Ds.size() < 4) return false;
1406  if (sfIndex >= pfp.SectionFits.size()) return false;
1407  // don't fit a small angle PFP
1408  if (pfp.Flags[kSmallAngle]) return true;
1409 
1410  unsigned short fromPt = USHRT_MAX;
1411  unsigned short npts = 0;
1412  GetRange(pfp, sfIndex, fromPt, npts);
1413  if (fromPt == USHRT_MAX) return false;
1414  if (npts < 4) return false;
1415 
1416  // check for errors
1417  for (unsigned short ipt = fromPt; ipt < fromPt + npts; ++ipt) {
1418  auto& tp3d = pfp.TP3Ds[ipt];
1419  if (tp3d.SFIndex != sfIndex) return false;
1420  } // ipt
1421 
1422  // fit these points and update
1423  return FitTP3Ds(detProp, slc, pfp, fromPt, npts, sfIndex);
1424 
1425  } // FitSection
1426 
1429  const TCSlice& slc,
1430  const std::vector<TP3D>& tp3ds,
1431  unsigned short fromPt,
1432  short fitDir,
1433  unsigned short nPtsFit)
1434  {
1435  // fits the points and returns the fit results in a SectionFit struct. This function assumes that the
1436  // vector of TP3Ds exists in the slc.TPCID
1437 
1438  SectionFit sf;
1439  sf.ChiDOF = 999;
1440  if (nPtsFit < 5) return sf;
1441  if (!(fitDir == -1 || fitDir == 1)) return sf;
1442  if (fitDir == 1 && fromPt + nPtsFit > tp3ds.size()) return sf;
1443  if (fitDir == -1 && fromPt < 3) return sf;
1444 
1445  // put the offset, cosine-like and sine-like components in a vector
1446  std::vector<std::array<double, 3>> ocs(slc.nPlanes);
1447  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1448  auto planeID = geo::PlaneID{slc.TPCID, plane};
1449  // plane offset
1450  ocs[plane][0] = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 0}, planeID);
1451  // get the "cosine-like" component
1452  ocs[plane][1] = tcc.geom->WireCoordinate(geo::Point_t{0, 1, 0}, planeID) - ocs[plane][0];
1453  // the "sine-like" component
1454  ocs[plane][2] = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 1}, planeID) - ocs[plane][0];
1455  } // plane
1456 
1457  const unsigned int nvars = 4;
1458  unsigned int npts = 0;
1459 
1460  // count the number of TPs in each plane
1461  std::vector<unsigned short> cntInPln(slc.nPlanes, 0);
1462  // and define the X position for the fit origin
1463  double x0 = 0.;
1464  for (short ii = 0; ii < nPtsFit; ++ii) {
1465  short ipt = fromPt + fitDir * ii;
1466  if (ipt < 0 || ipt >= (short)tp3ds.size()) break;
1467  auto& tp3d = tp3ds[ipt];
1468  if (!tp3d.Flags[kTP3DGood]) continue;
1469  if (tp3d.TPXErr2 < 0.0001) return sf;
1470  x0 += tp3d.TPX;
1471  unsigned short plane = DecodeCTP(tp3d.CTP).Plane;
1472  ++cntInPln[plane];
1473  ++npts;
1474  } // ipt
1475  if (npts < 6) return sf;
1476  // ensure there are at least three points in at least two planes
1477  unsigned short enufInPlane = 0;
1478  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane)
1479  if (cntInPln[plane] > 2) ++enufInPlane;
1480  if (enufInPlane < 2) return sf;
1481 
1482  x0 /= (double)npts;
1483 
1484  TMatrixD A(npts, nvars);
1485  // vector holding the Wire number
1486  TVectorD w(npts);
1487  unsigned short cnt = 0;
1488  double weight = 1;
1489  for (short ii = 0; ii < nPtsFit; ++ii) {
1490  short ipt = fromPt + fitDir * ii;
1491  auto& tp3d = tp3ds[ipt];
1492  if (!tp3d.Flags[kTP3DGood]) continue;
1493  unsigned short plane = DecodeCTP(tp3d.CTP).Plane;
1494  double x = tp3d.TPX - x0;
1495  A[cnt][0] = weight * ocs[plane][1];
1496  A[cnt][1] = weight * ocs[plane][2];
1497  A[cnt][2] = weight * ocs[plane][1] * x;
1498  A[cnt][3] = weight * ocs[plane][2] * x;
1499  w[cnt] = weight * (tp3d.Wire - ocs[plane][0]);
1500  ++cnt;
1501  } // ipt
1502 
1503  TDecompSVD svd(A);
1504  bool ok;
1505  TVectorD tVec = svd.Solve(w, ok);
1506  if (!ok) return sf;
1507  double norm = sqrt(1 + tVec[2] * tVec[2] + tVec[3] * tVec[3]);
1508 
1509  norm *= -1;
1510 
1511  sf.Dir[0] = 1 / norm;
1512  sf.Dir[1] = tVec[2] / norm;
1513  sf.Dir[2] = tVec[3] / norm;
1514  sf.Pos[0] = x0;
1515  sf.Pos[1] = tVec[0];
1516  sf.Pos[2] = tVec[1];
1517  sf.NPts = npts;
1518 
1519  // Calculate errors from sigma * (A^T * A)^(-1) where sigma is the
1520  // error on the wire number (= 1)
1521  TMatrixD AT(nvars, npts);
1522  AT.Transpose(A);
1523  TMatrixD ATA = AT * A;
1524  double* det = 0;
1525  try {
1526  ATA.Invert(det);
1527  }
1528  catch (...) {
1529  return sf;
1530  }
1531  sf.DirErr[1] = -sqrt(ATA[2][2]) / norm;
1532  sf.DirErr[2] = -sqrt(ATA[3][3]) / norm;
1533 
1534  // calculate ChiDOF
1535  sf.ChiDOF = 0;
1536  // project this 3D vector into a TP in every plane
1537  std::vector<TrajPoint> plnTP(slc.nPlanes);
1538  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1539  CTP_t inCTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, plane);
1540  plnTP[plane] = MakeBareTP(detProp, sf.Pos, sf.Dir, inCTP);
1541  } // plane
1542  // a local position
1543  Point3_t pos;
1544  sf.DirErr[0] = 0.;
1545  for (short ii = 0; ii < nPtsFit; ++ii) {
1546  short ipt = fromPt + fitDir * ii;
1547  auto& tp3d = tp3ds[ipt];
1548  if (!tp3d.Flags[kTP3DGood]) continue;
1549  unsigned short plane = DecodeCTP(tp3d.CTP).Plane;
1550  double dw = tp3d.Wire - plnTP[plane].Pos[0];
1551  // dt/dW was stored in DeltaRMS by MakeBareTP
1552  double t = dw * plnTP[plane].DeltaRMS;
1553  for (unsigned short xyz = 0; xyz < 3; ++xyz)
1554  pos[xyz] = sf.Pos[xyz] + t * sf.Dir[xyz];
1555  // Note that the tp3d position is directly above the wire position and not the
1556  // point at the distance of closest approach. Delta is the difference in the
1557  // drift direction in cm
1558  double delta = pos[0] - tp3d.TPX;
1559  sf.ChiDOF += delta * delta / tp3d.TPXErr2;
1560  // estimate the X slope error ~ X direction vector with an overly simple average
1561  double dangErr = delta / dw;
1562  sf.DirErr[0] += dangErr * dangErr;
1563  } // indx
1564  sf.DirErr[0] = sqrt(sf.DirErr[0]) / (double)nPtsFit;
1565  sf.ChiDOF /= (float)(npts - 4);
1566  return sf;
1567 
1568  } // FitTP3Ds
1569 
1572  const TCSlice& slc,
1573  PFPStruct& pfp,
1574  unsigned short fromPt,
1575  unsigned short nPtsFit,
1576  unsigned short sfIndex)
1577  {
1578  // Fit points in the pfp.TP3Ds vector fromPt. This function
1579  // doesn't update the TP3Ds unless sfIndex refers to a valid SectionFit in the pfp.
1580  // No check is made to ensure that the TP3D SFIndex variable is compatible with sfIndex
1581 
1582  if (nPtsFit < 5) return false;
1583  if (fromPt + nPtsFit > pfp.TP3Ds.size()) return false;
1584 
1585  auto sf = FitTP3Ds(detProp, slc, pfp.TP3Ds, fromPt, 1, nPtsFit);
1586  if (sf.ChiDOF > 900) return false;
1587 
1588  // don't update the pfp?
1589  if (sfIndex >= pfp.SectionFits.size()) return true;
1590 
1591  // update the pfp Sectionfit
1592  pfp.SectionFits[sfIndex] = sf;
1593  // update the TP3Ds
1594  // project this 3D vector into a TP in every plane
1595  std::vector<TrajPoint> plnTP(slc.nPlanes);
1596  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1597  CTP_t inCTP = EncodeCTP(pfp.TPCID.Cryostat, pfp.TPCID.TPC, plane);
1598  plnTP[plane] = MakeBareTP(detProp, sf.Pos, sf.Dir, inCTP);
1599  } // plane
1600  Point3_t pos;
1601  bool needsSort = false;
1602  double prevAlong = 0;
1603  for (unsigned short ipt = fromPt; ipt < fromPt + nPtsFit; ++ipt) {
1604  auto& tp3d = pfp.TP3Ds[ipt];
1605  unsigned short plane = DecodeCTP(tp3d.CTP).Plane;
1606  double dw = tp3d.Wire - plnTP[plane].Pos[0];
1607  // dt/dW was stored in DeltaRMS by MakeBareTP
1608  double t = dw * plnTP[plane].DeltaRMS;
1609  if (ipt == fromPt) { prevAlong = t; }
1610  else {
1611  if (t < prevAlong) needsSort = true;
1612  prevAlong = t;
1613  }
1614  for (unsigned short xyz = 0; xyz < 3; ++xyz)
1615  pos[xyz] = sf.Pos[xyz] + t * sf.Dir[xyz];
1616  // Note that the tp3d position is directly above the wire position and not the
1617  // distance of closest approach. The Delta variable is the difference in the
1618  // drift direction in cm
1619  double delta = pos[0] - tp3d.TPX;
1620  tp3d.Pos = pos;
1621  tp3d.Dir = sf.Dir;
1622  tp3d.along = t;
1623  if (tp3d.Flags[kTP3DGood]) sf.ChiDOF += delta * delta / tp3d.TPXErr2;
1624  } // ipt
1625  if (needsSort) SortSection(pfp, sfIndex);
1626  pfp.SectionFits[sfIndex].NeedsUpdate = false;
1627  return true;
1628 
1629  } // FitTP3Ds
1630 
1632  void ReconcileVertices(TCSlice& slc, PFPStruct& pfp, bool prt)
1633  {
1634  // Checks for mis-placed 2D and 3D vertices and either attaches them
1635  // to a vertex or deletes(?) the vertex while attempting to preserve or
1636  // correct the P -> T -> 2V -> 3V assn. After this is done, the function
1637  // TCVertex/AttachToAnyVertex is called.
1638  // This function returns true if something was done to the pfp that requires
1639  // a re-definition of the pfp, e.g. adding or removing TP3Ds. Note that this
1640  // never occurs as the function is currently written
1641 
1642  if (tcc.vtx3DCuts.size() < 3) return;
1643  if (pfp.TP3Ds.empty()) return;
1644  if (pfp.Flags[kJunk3D]) return;
1645  if (pfp.Flags[kSmallAngle]) return;
1646 
1647  // first make a list of all Tjs
1648  std::vector<int> tjList;
1649  for (auto& tp3d : pfp.TP3Ds) {
1650  if (!tp3d.Flags[kTP3DGood]) continue;
1651  // ignore single hits
1652  if (tp3d.TjID <= 0) continue;
1653  if (std::find(tjList.begin(), tjList.end(), tp3d.TjID) == tjList.end())
1654  tjList.push_back(tp3d.TjID);
1655  } // tp3d
1656  // look for 3D vertices associated with these Tjs and list of
1657  // orphan 2D vertices - those that are not matched to 3D vertices
1658  std::vector<int> vx2List, vx3List;
1659  for (auto tid : tjList) {
1660  auto& tj = slc.tjs[tid - 1];
1661  for (unsigned short end = 0; end < 2; ++end) {
1662  if (tj.VtxID[end] <= 0) continue;
1663  auto& vx2 = slc.vtxs[tj.VtxID[end] - 1];
1664  if (vx2.Vx3ID > 0) {
1665  if (std::find(vx3List.begin(), vx3List.end(), vx2.Vx3ID) == vx3List.end())
1666  vx3List.push_back(vx2.Vx3ID);
1667  // 3D vertex exists
1668  }
1669  else {
1670  // no 3D vertex
1671  if (std::find(vx2List.begin(), vx2List.end(), tj.VtxID[end]) == vx2List.end())
1672  vx2List.push_back(tj.VtxID[end]);
1673  } // no 3D vertex
1674  } // end
1675  } // tid
1676  // no vertex reconciliation is necessary
1677  if (vx2List.empty() && vx3List.empty()) return;
1678  if (prt) {
1679  mf::LogVerbatim myprt("TC");
1680  myprt << "RV: P" << pfp.ID << " ->";
1681  for (auto tid : tjList)
1682  myprt << " T" << tid;
1683  myprt << " ->";
1684  for (auto vid : vx3List)
1685  myprt << " 3V" << vid;
1686  if (!vx2List.empty()) {
1687  myprt << " orphan";
1688  for (auto vid : vx2List)
1689  myprt << " 2V" << vid;
1690  }
1691  } // prt
1692  // Just kill the orphan 2D vertices regardless of their score.
1693  // This is an indicator that the vertex was created between two tjs
1694  // that maybe should have been reconstructed as one or alternatively
1695  // as two Tjs. This decision presumes the existence of a 3D kink
1696  // algorithm that doesn't yet exist...
1697  for (auto vid : vx2List) {
1698  auto& vx2 = slc.vtxs[vid - 1];
1699  MakeVertexObsolete("RV", slc, vx2, true);
1700  } // vx2List
1701  // ignore the T -> 2V -> 3V assns (if any exist) and try to directly
1702  // attach to 3D vertices at both ends
1703  AttachToAnyVertex(slc, pfp, tcc.vtx3DCuts[2], prt);
1704  // check for differences and while we are here, see if the pfp was attached
1705  // to a neutrino vertex and the direction is wrong
1706  int neutrinoVx = 0;
1707  if (!slc.pfps.empty()) {
1708  auto& npfp = slc.pfps[0];
1709  bool neutrinoPFP = (npfp.PDGCode == 12 || npfp.PDGCode == 14);
1710  if (neutrinoPFP) neutrinoVx = npfp.Vx3ID[0];
1711  } // pfps exist
1712  unsigned short neutrinoVxEnd = 2;
1713  for (unsigned short end = 0; end < 2; ++end) {
1714  // see if a vertex got attached
1715  if (pfp.Vx3ID[end] <= 0) continue;
1716  if (pfp.Vx3ID[end] == neutrinoVx) neutrinoVxEnd = end;
1717  // see if this is a vertex in the list using the T -> 2V -> 3V assns
1718  if (std::find(vx3List.begin(), vx3List.end(), pfp.Vx3ID[end]) != vx3List.end()) continue;
1719  } // end
1720  if (neutrinoVxEnd < 2 && neutrinoVxEnd != 0) Reverse(pfp);
1721 
1722  return;
1723  } // ReconcileVertices
1724 
1727  detinfo::DetectorPropertiesData const& detProp,
1728  TCSlice& slc,
1729  PFPStruct& pfp,
1730  bool prt)
1731  {
1732  // Look for gaps in each plane in the TP3Ds vector in planes in which
1733  // the projection of the pfp angle is large (~> 60 degrees). Hits
1734  // reconstructed at large angles are poorly reconstructed which results
1735  // in poorly reconstructed 2D trajectories
1736 
1737  if (pfp.ID <= 0) return;
1738  if (pfp.TP3Ds.empty()) return;
1739  if (pfp.SectionFits.empty()) return;
1740  if (!tcc.useAlg[kFillGaps3D]) return;
1741  if (pfp.Flags[kJunk3D]) return;
1742  if (pfp.Flags[kSmallAngle]) return;
1743 
1744  // Only print APIR details if MVI is set
1745  bool foundMVI = (tcc.dbgPFP && pfp.MVI == debug.MVI);
1746 
1747  // make a copy in case something goes wrong
1748  auto pWork = pfp;
1749 
1750  unsigned short nPtsAdded = 0;
1751  unsigned short fromPt = 0;
1752  unsigned short toPt = pWork.TP3Ds.size();
1753  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1754  CTP_t inCTP = EncodeCTP(pWork.TPCID.Cryostat, pWork.TPCID.TPC, plane);
1755  unsigned short nWires, nAdd;
1756  AddPointsInRange(clockData,
1757  detProp,
1758  slc,
1759  pWork,
1760  fromPt,
1761  toPt,
1762  inCTP,
1763  tcc.match3DCuts[4],
1764  nWires,
1765  nAdd,
1766  foundMVI);
1767  if (pWork.Flags[kNeedsUpdate]) Update(detProp, slc, pWork);
1768  nPtsAdded += nAdd;
1769  } // plane
1770  if (prt) mf::LogVerbatim("TC") << "FG3D P" << pWork.ID << " added " << nPtsAdded << " points";
1771  if (pWork.Flags[kNeedsUpdate] && !Update(detProp, slc, pWork)) return;
1772  pfp = pWork;
1773  return;
1774  } // FillGaps3D
1775 
1778  const TCSlice& slc,
1779  const PFPStruct& pfp)
1780  {
1781  // This function checks the third plane in the PFP when only two Tjs are 3D-matched to
1782  // ensure that the reason for the lack of a 3rd plane match is that it is in a dead region.
1783  // This function should be used after an initial fit is done and the TP3Ds are sorted
1784  if (pfp.TjIDs.size() != 2) return false;
1785  if (slc.nPlanes != 3) return false;
1786  if (pfp.TP3Ds.empty()) return false;
1787 
1788  // find the third plane
1789  std::vector<unsigned short> planes;
1790  for (auto tid : pfp.TjIDs)
1791  planes.push_back(DecodeCTP(slc.tjs[tid - 1].CTP).Plane);
1792  unsigned short thirdPlane = 3 - planes[0] - planes[1];
1793  CTP_t inCTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, thirdPlane);
1794  // Project the 3D position at the start into the third plane
1795  auto tp = MakeBareTP(detProp, pfp.TP3Ds[0].Pos, inCTP);
1796  unsigned int wire0 = 0;
1797  if (tp.Pos[0] > 0) wire0 = std::nearbyint(tp.Pos[0]);
1798  if (wire0 > slc.nWires[thirdPlane]) wire0 = slc.nWires[thirdPlane];
1799  // Do the same for the end
1800  unsigned short lastPt = pfp.TP3Ds.size() - 1;
1801  tp = MakeBareTP(detProp, pfp.TP3Ds[lastPt].Pos, inCTP);
1802  unsigned int wire1 = 0;
1803  if (tp.Pos[0] > 0) wire1 = std::nearbyint(tp.Pos[0]);
1804  if (wire1 > slc.nWires[thirdPlane]) wire1 = slc.nWires[thirdPlane];
1805  if (wire0 == wire1) return !evt.goodWire[thirdPlane][wire0];
1806  if (wire1 < wire0) std::swap(wire0, wire1);
1807  // count the number of good wires
1808  int dead = 0;
1809  int wires = wire1 - wire0;
1810  for (unsigned int wire = wire0; wire < wire1; ++wire)
1811  if (!evt.goodWire[thirdPlane][wire]) ++dead;
1812  // require that most of the wires are dead
1813  return (dead > 0.8 * wires);
1814  } // ValidTwoPlaneMatch
1815 
1818  detinfo::DetectorPropertiesData const& detProp,
1819  TCSlice& slc,
1820  PFPStruct& pfp,
1821  unsigned short fromPt,
1822  unsigned short toPt,
1823  CTP_t inCTP,
1824  float maxPull,
1825  unsigned short& nWires,
1826  unsigned short& nAdd,
1827  bool prt)
1828  {
1829  // Try to insert 2D trajectory points into the 3D trajectory point vector pfp.TP3Ds.
1830  // This function inserts new TP3Ds and sets the NeedsUpdate flags true.
1831  // The calling function should call Update
1832  // Note that maxPull is used for the charge pull as well as the position pull
1833  nWires = 0;
1834  nAdd = 0;
1835  if (fromPt > toPt) return;
1836  if (toPt >= pfp.TP3Ds.size()) toPt = pfp.TP3Ds.size() - 1;
1837 
1838  // Find the average dE/dx so we can apply a generous min/max dE/dx cut
1839  float dEdXAve = 0;
1840  float dEdXRms = 0;
1841  Average_dEdX(clockData, detProp, slc, pfp, dEdXAve, dEdXRms);
1842  float dEdxMin = 0.5, dEdxMax = 50.;
1843  if (dEdXAve > 0.5) {
1844  dEdxMin = dEdXAve - maxPull * dEdXRms;
1845  if (dEdxMin < 0.5) dEdxMin = 0.5;
1846  dEdxMax = dEdXAve + maxPull * dEdXRms;
1847  if (dEdxMax > 50.) dEdxMax = 50.;
1848  } // dEdXAve > 0.5
1849 
1850  // Split the range into sub-ranges; one for each SectionFit and make 2D TPs at the
1851  // start of each sub-range.
1852  std::vector<TrajPoint> sfTPs;
1853  // form a list of TPs that are used in this CTP
1854  std::vector<std::pair<int, unsigned short>> tpUsed;
1855  for (auto& tp3d : pfp.TP3Ds) {
1856  if (tp3d.CTP != inCTP) continue;
1857  tpUsed.push_back(std::make_pair(tp3d.TjID, tp3d.TPIndex));
1858  } // tp3d
1859  unsigned int toWire = 0;
1860  unsigned int fromWire = UINT_MAX;
1861 
1862  unsigned short inSF = USHRT_MAX;
1863  unsigned short pln = DecodeCTP(inCTP).Plane;
1864  for (unsigned short ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt) {
1865  auto& tp3d = pfp.TP3Ds[ipt];
1866  // Skip if not the last point and we already found a point in this SectionFit
1867  if (ipt < pfp.TP3Ds.size() - 1 && tp3d.SFIndex == inSF) continue;
1868  unsigned int wire;
1869  if (tp3d.CTP == inCTP) {
1870  // Found the first tp3d in a new SectionFit and it is in the right CTP
1871  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
1872  sfTPs.push_back(tp);
1873  wire = std::nearbyint(tp.Pos[0]);
1874  if (wire >= slc.nWires[pln]) break;
1875  }
1876  else {
1877  // Found the first tp3d in a new SectionFit and it is in a different CTP.
1878  // Make a TP in the right CTP
1879  auto tp = MakeBareTP(detProp, tp3d.Pos, tp3d.Dir, inCTP);
1880  wire = std::nearbyint(tp.Pos[0]);
1881  if (wire >= slc.nWires[pln]) break;
1882  sfTPs.push_back(tp);
1883  }
1884  if (wire < fromWire) fromWire = wire;
1885  if (wire > toWire) toWire = wire;
1886  inSF = tp3d.SFIndex;
1887  } // tp3d
1888  if (sfTPs.empty()) return;
1889  // reverse the vector if necessary so the wires will be in increasing order
1890  if (sfTPs.size() > 1 && sfTPs[0].Pos[0] > sfTPs.back().Pos[0]) {
1891  std::reverse(sfTPs.begin(), sfTPs.end());
1892  }
1893 
1894  // set a generous search window in WSE units
1895  float window = 50;
1896 
1897  if (prt)
1898  mf::LogVerbatim("TC") << "APIR: inCTP " << inCTP << " fromWire " << fromWire << " toWire "
1899  << toWire;
1900 
1901  // iterate over the sub-ranges
1902  for (unsigned short subr = 0; subr < sfTPs.size(); ++subr) {
1903  auto& fromTP = sfTPs[subr];
1904  unsigned int toWireInSF = toWire;
1905  if (subr < sfTPs.size() - 1) toWireInSF = std::nearbyint(sfTPs[subr + 1].Pos[0]);
1906  SetAngleCode(fromTP);
1907  unsigned int fromWire = std::nearbyint(fromTP.Pos[0]);
1908  if (fromWire > toWire) continue;
1909  if (prt)
1910  mf::LogVerbatim("TC") << " inCTP " << inCTP << " subr " << subr << " fromWire " << fromWire
1911  << " toWireInSF " << toWireInSF;
1912  for (unsigned int wire = fromWire; wire <= toWireInSF; ++wire) {
1913  MoveTPToWire(fromTP, (float)wire);
1914  if (!FindCloseHits(slc, fromTP, window, kUsedHits)) continue;
1915  if (fromTP.Environment[kEnvNotGoodWire]) continue;
1916  float bestPull = maxPull;
1917  TP3D bestTP3D;
1918  for (auto iht : fromTP.Hits) {
1919  if (slc.slHits[iht].InTraj <= 0) continue;
1920  // this hit is used in a TP so find the tpIndex
1921  auto& utj = slc.tjs[slc.slHits[iht].InTraj - 1];
1922  unsigned short tpIndex = 0;
1923  for (tpIndex = utj.EndPt[0]; tpIndex <= utj.EndPt[1]; ++tpIndex) {
1924  auto& utp = utj.Pts[tpIndex];
1925  if (utp.Chg <= 0) continue;
1926  // This doesn't check for UseHit true but that is probably ok here
1927  if (std::find(utp.Hits.begin(), utp.Hits.end(), iht) != utp.Hits.end()) break;
1928  } // ipt
1929  if (tpIndex > utj.EndPt[1]) continue;
1930  // see if it is already used in this pfp
1931  std::pair<int, unsigned short> tppr = std::make_pair(utj.ID, tpIndex);
1932  if (std::find(tpUsed.begin(), tpUsed.end(), tppr) != tpUsed.end()) continue;
1933  tpUsed.push_back(tppr);
1934  auto& utp = utj.Pts[tpIndex];
1935  // see if it is used in a different PFP
1936  if (utp.InPFP > 0) continue;
1937  // or if it overlaps another trajectory near a 2D vertex
1938  if (utp.Environment[kEnvOverlap]) continue;
1939  auto newTP3D = CreateTP3D(detProp, slc, utj.ID, tpIndex);
1940  if (!SetSection(detProp, pfp, newTP3D)) continue;
1941  // set the direction to the direction of the SectionFit it is in so we can calculate dE/dx
1942  newTP3D.Dir = pfp.SectionFits[newTP3D.SFIndex].Dir;
1943  float pull = PointPull(newTP3D);
1944  float dedx = dEdx(clockData, detProp, slc, newTP3D);
1945  // Require a good pull and a consistent dE/dx (MeV/cm)
1946  bool useIt = (pull < bestPull && dedx > dEdxMin && dedx < dEdxMax);
1947  if (!useIt) continue;
1948  bestTP3D = newTP3D;
1949  bestPull = pull;
1950  } // iht
1951  if (bestPull < maxPull) {
1952  if (prt && bestPull < 10) {
1953  mf::LogVerbatim myprt("TC");
1954  auto& tp = slc.tjs[bestTP3D.TjID - 1].Pts[bestTP3D.TPIndex];
1955  myprt << "APIR: P" << pfp.ID << " added TP " << PrintPos(tp);
1956  myprt << " pull " << std::fixed << std::setprecision(2) << bestPull;
1957  myprt << " dx " << bestTP3D.TPX - bestTP3D.Pos[0] << " in section " << bestTP3D.SFIndex;
1958  }
1959  if (InsertTP3D(pfp, bestTP3D) == USHRT_MAX) continue;
1960  ++nAdd;
1961  } // bestPull < maxPull
1962  } // wire
1963  } // subr
1964  } // AddPointsInRange
1965 
1967  unsigned short InsertTP3D(PFPStruct& pfp, TP3D& tp3d)
1968  {
1969  // inserts the tp3d into the section defined by tp3d.SFIndex
1970  if (tp3d.SFIndex >= pfp.SectionFits.size()) return USHRT_MAX;
1971  // Find the first occurrence of this SFIndex
1972  std::size_t ipt = 0;
1973  for (ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt)
1974  if (tp3d.SFIndex == pfp.TP3Ds[ipt].SFIndex) break;
1975  if (ipt == pfp.TP3Ds.size()) return USHRT_MAX;
1976  // next see if we can insert it so that re-sorting of this section isn't required
1977  auto lastTP3D = pfp.TP3Ds.back();
1978  if (ipt == 0 && tp3d.along < pfp.TP3Ds[0].along) {
1979  // insert at the beginning. No search needs to be done
1980  }
1981  else if (tp3d.SFIndex == lastTP3D.SFIndex && tp3d.along > lastTP3D.along) {
1982  // insert at the end. Use push_back and return
1983  pfp.TP3Ds.push_back(tp3d);
1984  pfp.SectionFits[tp3d.SFIndex].NeedsUpdate = true;
1985  pfp.Flags[kNeedsUpdate] = true;
1986  return pfp.TP3Ds.size() - 1;
1987  }
1988  else {
1989  for (std::size_t iipt = ipt; iipt < pfp.TP3Ds.size() - 1; ++iipt) {
1990  // break out if the next point is in a different section
1991  if (pfp.TP3Ds[iipt + 1].SFIndex != tp3d.SFIndex) break;
1992  if (tp3d.along > pfp.TP3Ds[iipt].along && tp3d.along < pfp.TP3Ds[iipt + 1].along) {
1993  ipt = iipt + 1;
1994  break;
1995  }
1996  } // iipt
1997  } // insert in the middle
1998  pfp.TP3Ds.insert(pfp.TP3Ds.begin() + ipt, tp3d);
1999  pfp.SectionFits[tp3d.SFIndex].NeedsUpdate = true;
2000  pfp.Flags[kNeedsUpdate] = true;
2001  return ipt;
2002  } // InsertTP3D
2003 
2005  bool SortSection(PFPStruct& pfp, unsigned short sfIndex)
2006  {
2007  // sorts the TP3Ds by the distance from the start of a fit section
2008 
2009  if (sfIndex > pfp.SectionFits.size() - 1) return false;
2010  auto& sf = pfp.SectionFits[sfIndex];
2011  if (sf.Pos[0] == 0.0 && sf.Pos[1] == 0.0 && sf.Pos[2] == 0.0) return false;
2012 
2013  // a temp vector of points in this section
2014  std::vector<TP3D> temp;
2015  // and the index into TP3Ds
2016  std::vector<unsigned short> indx;
2017  // See if the along variable is monotonically increasing
2018  float prevAlong = 0;
2019  bool first = true;
2020  bool needsSort = false;
2021  for (std::size_t ii = 0; ii < pfp.TP3Ds.size(); ++ii) {
2022  auto& tp3d = pfp.TP3Ds[ii];
2023  if (tp3d.SFIndex != sfIndex) continue;
2024  if (first) {
2025  first = false;
2026  prevAlong = tp3d.along;
2027  }
2028  else {
2029  if (tp3d.along < prevAlong) needsSort = true;
2030  prevAlong = tp3d.along;
2031  }
2032  temp.push_back(tp3d);
2033  indx.push_back(ii);
2034  } // tp3d
2035  if (temp.empty()) return false;
2036  // no sort needed?
2037  if (temp.size() == 1) return true;
2038  if (!needsSort) {
2039  sf.NeedsUpdate = false;
2040  return true;
2041  }
2042  // see if the points are not-contiguous
2043  bool contiguous = true;
2044  for (std::size_t ipt = 1; ipt < indx.size(); ++ipt) {
2045  if (indx[ipt] != indx[ipt - 1] + 1) contiguous = false;
2046  } // ipt
2047  if (!contiguous) { return false; }
2048 
2049  std::vector<SortEntry> sortVec(temp.size());
2050  for (std::size_t ii = 0; ii < temp.size(); ++ii) {
2051  sortVec[ii].index = ii;
2052  sortVec[ii].val = temp[ii].along;
2053  } // ipt
2054  std::sort(sortVec.begin(), sortVec.end(), valsIncreasing);
2055  for (std::size_t ii = 0; ii < temp.size(); ++ii) {
2056  // overwrite the tp3d
2057  auto& tp3d = pfp.TP3Ds[indx[ii]];
2058  tp3d = temp[sortVec[ii].index];
2059  } // ii
2060  sf.NeedsUpdate = false;
2061  return true;
2062  } // SortSection
2063 
2066  TCSlice& slc,
2067  PFPStruct& pfp,
2068  bool prt)
2069  {
2070  // try to recover from a poor initial fit
2071  if (pfp.AlgMod[kSmallAngle]) return;
2072  if (pfp.SectionFits.size() != 1) return;
2073  if (pfp.TP3Ds.size() < 20) return;
2074  if (!CanSection(slc, pfp)) return;
2075 
2076  // make a copy
2077  auto p2 = pfp;
2078  // try two sections
2079  p2.SectionFits.resize(2);
2080  unsigned short halfPt = p2.TP3Ds.size() / 2;
2081  for (unsigned short ipt = halfPt; ipt < p2.TP3Ds.size(); ++ipt)
2082  p2.TP3Ds[ipt].SFIndex = 1;
2083  // Confirm that both sections can be reconstructed
2084  unsigned short toPt = Find3DRecoRange(slc, p2, 0, 3, 1);
2085  if (toPt > p2.TP3Ds.size()) return;
2086  toPt = Find3DRecoRange(slc, p2, halfPt, 3, 1);
2087  if (toPt > p2.TP3Ds.size()) return;
2088  if (!FitSection(detProp, slc, p2, 0) || !FitSection(detProp, slc, p2, 1)) {
2089  if (prt) {
2090  mf::LogVerbatim myprt("TC");
2091  myprt << "Recover failed MVI " << p2.MVI << " in TPC " << p2.TPCID.TPC;
2092  for (auto tid : p2.TjIDs)
2093  myprt << " T" << tid;
2094  } // prt
2095  return;
2096  }
2097  if (prt) mf::LogVerbatim("TC") << "Recover: P" << pfp.ID << " success";
2098  pfp = p2;
2099 
2100  } // Recover
2101 
2104  TCSlice& slc,
2105  PFPStruct& pfp,
2106  bool prt)
2107  {
2108  // Create and populate the TP3Ds vector. This function is called before the first
2109  // fit is done so the TP3D along variable can't be determined. It returns false
2110  // if a majority of the tj points in TjIDs are already assigned to a different pfp
2111  pfp.TP3Ds.clear();
2112  if (!pfp.TP3Ds.empty() || pfp.SectionFits.size() != 1) return false;
2113 
2114  // Look for TPs that are ~parallel to the wire plane and trajectory points
2115  // where the min/max Pos[1] value is not near an end.
2116  // number of Small Angle Tjs
2117  // stash the inflection point index in the TjUIDs vector
2118  pfp.TjUIDs.resize(pfp.TjIDs.size(), -1);
2119  // count TPs with charge in all of the Tjs
2120  float cnt = 0;
2121  // and the number of TPs available for use
2122  float avail = 0;
2123  // and the number of junk Tjs
2124  unsigned short nJunk = 0;
2125  unsigned short nSA = 0;
2126  for (unsigned short itj = 0; itj < pfp.TjIDs.size(); ++itj) {
2127  auto& tj = slc.tjs[pfp.TjIDs[itj] - 1];
2128  if (tj.AlgMod[kJunkTj]) ++nJunk;
2129  float posMin = 1E6;
2130  unsigned short iptMin = USHRT_MAX;
2131  float posMax = -1E6;
2132  unsigned short iptMax = USHRT_MAX;
2133  float aveAng = 0;
2134  float npwc = 0;
2135  for (unsigned short ipt = tj.EndPt[0]; ipt < tj.EndPt[1]; ++ipt) {
2136  auto& tp = tj.Pts[ipt];
2137  if (tp.Chg <= 0) continue;
2138  ++cnt;
2139  if (tp.InPFP > 0) continue;
2140  ++avail;
2141  if (tp.Pos[1] > posMax) {
2142  posMax = tp.Pos[1];
2143  iptMax = ipt;
2144  }
2145  if (tp.Pos[1] < posMin) {
2146  posMin = tp.Pos[1];
2147  iptMin = ipt;
2148  }
2149  aveAng += tp.Ang;
2150  ++npwc;
2151  } // ipt
2152  if (npwc == 0) continue;
2153  aveAng /= npwc;
2154  if (std::abs(aveAng) < 0.05) ++nSA;
2155  // No problem if the min/max points are near the ends
2156  if (iptMin > tj.EndPt[0] + 4 && iptMin < tj.EndPt[1] - 4) pfp.TjUIDs[itj] = iptMin;
2157  if (iptMax > tj.EndPt[0] + 4 && iptMax < tj.EndPt[1] - 4) pfp.TjUIDs[itj] = iptMax;
2158  } // tid
2159  if (avail < 0.8 * cnt) return false;
2160  // small angle trajectory?
2161  if (nSA > 1) pfp.AlgMod[kSmallAngle] = true;
2162  if (prt)
2163  mf::LogVerbatim("TC") << " P" << pfp.ID << " MVI " << pfp.MVI << " nJunkTj " << nJunk
2164  << " SmallAngle? " << pfp.AlgMod[kSmallAngle];
2165 
2166  if (pfp.AlgMod[kSmallAngle]) return MakeSmallAnglePFP(detProp, slc, pfp, prt);
2167 
2168  // Add the points associated with the Tjs that were used to create the PFP
2169  for (auto tid : pfp.TjIDs) {
2170  auto& tj = slc.tjs[tid - 1];
2171  // There is one TP for every hit in a junk Tj so we can skip one, if there is only one
2172  if (nJunk == 1 && tj.AlgMod[kJunkTj]) continue;
2173  // All of the Tj's may be junk, especially for those at very high angle, so the
2174  // X position of the TP's isn't high quality. Inflate the errors below.
2175  bool isJunk = tj.AlgMod[kJunkTj];
2176  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2177  auto& tp = tj.Pts[ipt];
2178  if (tp.Chg <= 0) continue;
2179  if (tp.InPFP > 0) continue;
2180  ++avail;
2181  auto tp3d = CreateTP3D(detProp, slc, tid, ipt);
2182  if (tp3d.Flags[kTP3DBad]) continue;
2183  tp3d.SFIndex = 0;
2184  if (isJunk) tp3d.TPXErr2 *= 4;
2185  // We need to assume that all points are good or the first fit will fail
2186  tp3d.Flags[kTP3DGood] = true;
2187  pfp.TP3Ds.push_back(tp3d);
2188  } // ipt
2189  } // tid
2190  if (prt) mf::LogVerbatim("TC") << " has " << pfp.TP3Ds.size() << " TP3Ds";
2191  return true;
2192  } // MakeTP3Ds
2193 
2196  TCSlice& slc,
2197  PFPStruct& pfp,
2198  bool prt)
2199  {
2200  // Create and populate the TP3Ds vector for a small-angle track. The standard track fit
2201  // will fail for these tracks. The kSmallAngle AlgMod bit
2202  // is set true. Assume that the calling function, MakeTP3Ds, has decided that this is a
2203  // small-angle track.
2204 
2205  if (!tcc.useAlg[kSmallAngle]) return false;
2206  if (pfp.TjIDs.size() < 2) return false;
2207 
2208  std::vector<SortEntry> sortVec(pfp.TjIDs.size());
2209  unsigned short sbCnt = 0;
2210  for (unsigned short itj = 0; itj < pfp.TjIDs.size(); ++itj) {
2211  sortVec[itj].index = itj;
2212  auto& tj = slc.tjs[pfp.TjIDs[itj] - 1];
2213  sortVec[itj].val = NumPtsWithCharge(slc, tj, false);
2214  if (pfp.TjUIDs[itj] > 0) ++sbCnt;
2215  } // ipt
2216  std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
2217 
2218  // Decide whether to use the inflection points to add another section. Inflection
2219  // points must exist in the two longest Tjs
2220  unsigned short tlIndex = sortVec[0].index;
2221  unsigned short nlIndex = sortVec[1].index;
2222  auto& tlong = slc.tjs[pfp.TjIDs[tlIndex] - 1];
2223  auto& nlong = slc.tjs[pfp.TjIDs[nlIndex] - 1];
2224  bool twoSections = (sbCnt > 1 && pfp.TjUIDs[tlIndex] > 0 && pfp.TjUIDs[nlIndex] > 0);
2225  unsigned short tStartPt = tlong.EndPt[0];
2226  unsigned short tEndPt = tlong.EndPt[1];
2227  unsigned short nStartPt = nlong.EndPt[0];
2228  unsigned short nEndPt = nlong.EndPt[1];
2229  if (twoSections) {
2230  pfp.SectionFits.resize(2);
2231  tEndPt = pfp.TjUIDs[tlIndex];
2232  nEndPt = pfp.TjUIDs[nlIndex];
2233  if (prt) {
2234  mf::LogVerbatim myprt("TC");
2235  myprt << "MakeSmallAnglePFP: creating two sections using points";
2236  myprt << " T" << tlong.ID << "_" << tEndPt;
2237  myprt << " T" << nlong.ID << "_" << nEndPt;
2238  } // prt
2239  } // two Sections
2240  std::vector<Point3_t> sfEndPos;
2241  for (unsigned short isf = 0; isf < pfp.SectionFits.size(); ++isf) {
2242  // get the start and end TPs in this section
2243  auto& ltp0 = tlong.Pts[tStartPt];
2244  auto& ltp1 = tlong.Pts[tEndPt];
2245  auto& ntp0 = nlong.Pts[nStartPt];
2246  auto& ntp1 = nlong.Pts[nEndPt];
2247  // Get the 3D end points
2248  auto start = MakeTP3D(detProp, ltp0, ntp0);
2249  auto end = MakeTP3D(detProp, ltp1, ntp1);
2250  if (!start.Flags[kTP3DGood] || !end.Flags[kTP3DGood]) {
2251  std::cout << " Start/end fail in section " << isf << ". Add recovery code\n";
2252  return false;
2253  } // failure
2254  if (!InsideTPC(start.Pos, pfp.TPCID)) {
2255  mf::LogVerbatim("TC") << " Start is outside the TPC " << start.Pos[0] << " " << start.Pos[1]
2256  << " " << start.Pos[2];
2257  }
2258  if (!InsideTPC(end.Pos, pfp.TPCID)) {
2259  mf::LogVerbatim("TC") << " End is outside the TPC " << end.Pos[0] << " " << end.Pos[1]
2260  << " " << end.Pos[2];
2261  }
2262  if (isf == 0) sfEndPos.push_back(start.Pos);
2263  sfEndPos.push_back(end.Pos);
2264  auto& sf = pfp.SectionFits[isf];
2265  // Find the start and end positions
2266  for (unsigned short xyz = 0; xyz < 3; ++xyz) {
2267  sf.Dir[xyz] = end.Pos[xyz] - start.Pos[xyz];
2268  sf.Pos[xyz] = (end.Pos[xyz] + start.Pos[xyz]) / 2.;
2269  }
2270  SetMag(sf.Dir, 1.);
2271  sf.ChiDOF = 0.;
2272  sf.NPts = 0;
2273  // move the start/end point indices
2274  tStartPt = tEndPt + 1;
2275  tEndPt = tlong.EndPt[1];
2276  nStartPt = nEndPt + 1;
2277  nEndPt = nlong.EndPt[1];
2278  } // isf
2279  // Create TP3Ds
2280  // a temporary vector to hold TP3Ds for the second SectionFit
2281  std::vector<TP3D> sf2pts;
2282  for (unsigned short itj = 0; itj < sortVec.size(); ++itj) {
2283  int tid = pfp.TjIDs[sortVec[itj].index];
2284  // don't add points for the Tj that doesn't have an inflection point. It is
2285  // probably broken and would probably be put in the wrong section
2286  if (twoSections && pfp.TjUIDs[sortVec[itj].index] < 0) continue;
2287  auto& tj = slc.tjs[tid - 1];
2288  unsigned short sb = tj.EndPt[1];
2289  if (twoSections && pfp.TjUIDs[sortVec[itj].index] > 0) sb = pfp.TjUIDs[sortVec[itj].index];
2290  // count the number of good TPs in each section
2291  std::vector<double> npwc(pfp.SectionFits.size(), 0);
2292  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2293  auto& tp = tj.Pts[ipt];
2294  if (tp.Chg <= 0) continue;
2295  if (ipt > sb) { ++npwc[1]; }
2296  else {
2297  ++npwc[0];
2298  }
2299  } // ipt
2300  double length = PosSep(sfEndPos[0], sfEndPos[1]);
2301  double step = length / npwc[0];
2302  double along = -length / 2;
2303  unsigned short sfi = 0;
2304  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2305  auto& tp = tj.Pts[ipt];
2306  if (tp.Chg <= 0) continue;
2307  auto tp3d = CreateTP3D(detProp, slc, tid, ipt);
2308  if (tp3d.Flags[kTP3DBad]) continue;
2309  if (ipt == sb + 1) {
2310  sfi = 1;
2311  length = PosSep(sfEndPos[1], sfEndPos[2]);
2312  step = length / npwc[1];
2313  along = -length / 2;
2314  }
2315  tp3d.SFIndex = sfi;
2316  auto& sf = pfp.SectionFits[sfi];
2317  ++sf.NPts;
2318  tp3d.along = along;
2319  for (unsigned short xyz = 0; xyz < 3; ++xyz)
2320  tp3d.Pos[xyz] = sf.Pos[xyz] + along * sf.Dir[xyz];
2321  tp3d.Dir = sf.Dir;
2322  along += step;
2323  double delta = tp3d.Pos[0] - tp3d.TPX;
2324  sf.ChiDOF += delta * delta / tp3d.TPXErr2;
2325  // Assume that all points are good
2326  tp3d.Flags[kTP3DGood] = true;
2327  if (sfi == 0) { pfp.TP3Ds.push_back(tp3d); }
2328  else {
2329  sf2pts.push_back(tp3d);
2330  }
2331  } // ipt
2332  } // tid
2333  if (pfp.TP3Ds.size() < 4) return false;
2334  for (auto& sf : pfp.SectionFits) {
2335  if (sf.NPts < 5) return false;
2336  sf.ChiDOF /= (float)(sf.NPts - 4);
2337  } // sf
2338  if (!SortSection(pfp, 0)) return false;
2339  if (!sf2pts.empty()) {
2340  // append the points and sort
2341  pfp.TP3Ds.insert(pfp.TP3Ds.end(), sf2pts.begin(), sf2pts.end());
2342  if (!SortSection(pfp, 1)) return false;
2343  } // two sections
2344  pfp.Flags[kCanSection] = false;
2345  pfp.AlgMod[kSmallAngle] = true;
2346  if (prt) {
2347  mf::LogVerbatim("TC") << "Created SmallAngle P" << pfp.ID << " with " << pfp.TP3Ds.size()
2348  << " points in " << pfp.SectionFits.size() << " sections\n";
2349  }
2350  return true;
2351  } // MakeSmallAnglePFP
2352 
2354  void Reverse(PFPStruct& pfp)
2355  {
2356  // reverse the PFParticle
2357  std::reverse(pfp.TP3Ds.begin(), pfp.TP3Ds.end());
2358  std::reverse(pfp.SectionFits.begin(), pfp.SectionFits.end());
2359  for (std::size_t sfi = 0; sfi < pfp.SectionFits.size(); ++sfi) {
2360  auto& sf = pfp.SectionFits[sfi];
2361  // flip the direction vector
2362  for (unsigned short xyz = 0; xyz < 3; ++xyz)
2363  sf.Dir[xyz] *= -1;
2364  } // sf
2365  // correct the along variable
2366  for (auto& tp3d : pfp.TP3Ds)
2367  tp3d.along *= -1;
2368  std::swap(pfp.dEdx[0], pfp.dEdx[1]);
2369  std::swap(pfp.dEdxErr[0], pfp.dEdxErr[1]);
2370  std::swap(pfp.Vx3ID[0], pfp.Vx3ID[1]);
2371  std::swap(pfp.EndFlag[0], pfp.EndFlag[1]);
2372  } // Reverse
2373 
2376  {
2377  // Fills the mallTraj vector with trajectory points in the tpc and sorts
2378  // them by increasing X
2379 
2380  int cstat = slc.TPCID.Cryostat;
2381  int tpc = slc.TPCID.TPC;
2382 
2383  // define mallTraj
2384  slc.mallTraj.clear();
2385  Tj2Pt tj2pt;
2386  unsigned short cnt = 0;
2387 
2388  // try to reduce CPU time by not attempting to match tjs that are near muons
2389  bool muFuzzCut = (tcc.match3DCuts.size() > 6 && tcc.match3DCuts[6] > 0);
2390 
2391  float rms = tcc.match3DCuts[0];
2392  for (auto& tj : slc.tjs) {
2393  if (tj.AlgMod[kKilled] || tj.AlgMod[kHaloTj]) continue;
2394  // ignore already matched
2395  if (tj.AlgMod[kMat3D]) continue;
2396  geo::PlaneID planeID = DecodeCTP(tj.CTP);
2397  if ((int)planeID.Cryostat != cstat) continue;
2398  if ((int)planeID.TPC != tpc) continue;
2399  int plane = planeID.Plane;
2400  if (tj.ID <= 0) continue;
2401  unsigned short tjID = tj.ID;
2402  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
2403  auto& tp = tj.Pts[ipt];
2404  if (tp.Chg <= 0) continue;
2405  if (tp.Pos[0] < -0.4) continue;
2406  // ignore already matched
2407  if (tp.InPFP > 0) continue;
2408  if (muFuzzCut && tp.Environment[kEnvNearMuon]) continue;
2409  tj2pt.wire = std::nearbyint(tp.Pos[0]);
2410  ++cnt;
2411  // don't try matching if the wire doesn't exist
2412  if (!tcc.geom->HasWire(geo::WireID(cstat, tpc, plane, tj2pt.wire))) continue;
2413  float xpos = detProp.ConvertTicksToX(tp.Pos[1] / tcc.unitsPerTick, plane, tpc, cstat);
2414  tj2pt.xlo = xpos - rms;
2415  tj2pt.xhi = xpos + rms;
2416  tj2pt.plane = plane;
2417  tj2pt.id = tjID;
2418  tj2pt.ipt = ipt;
2419  tj2pt.npts = tj.EndPt[1] - tj.EndPt[0] + 1;
2420  slc.mallTraj.push_back(tj2pt);
2421  } // tp
2422  } // tj
2423 
2424  // sort by increasing x
2425  std::vector<SortEntry> sortVec(slc.mallTraj.size());
2426  for (std::size_t ipt = 0; ipt < slc.mallTraj.size(); ++ipt) {
2427  // populate the sort vector
2428  sortVec[ipt].index = ipt;
2429  sortVec[ipt].val = slc.mallTraj[ipt].xlo;
2430  } // ipt
2431  // sort by increasing xlo
2432  std::sort(sortVec.begin(), sortVec.end(), valsIncreasing);
2433  // put slc.mallTraj into sorted order
2434  auto tallTraj = slc.mallTraj;
2435  for (std::size_t ii = 0; ii < sortVec.size(); ++ii)
2436  slc.mallTraj[ii] = tallTraj[sortVec[ii].index];
2437 
2438  } // FillmAllTraj
2439 
2442  const TrajPoint& itp,
2443  const TrajPoint& jtp)
2444  {
2445  // Make a 3D trajectory point using two 2D trajectory points. The TP3D Pos and Wire
2446  // variables are defined using itp. The SectionFit variables are un-defined
2447  TP3D tp3d;
2448  tp3d.TPIndex = 0;
2449  tp3d.TjID = 0;
2450  tp3d.CTP = itp.CTP;
2451  // assume failure
2452  tp3d.Flags[kTP3DGood] = false;
2453  tp3d.Dir = {{0.0, 0.0, 1.0}};
2454  tp3d.Pos = {{999.0, 999.0, 999.0}};
2455  geo::PlaneID iPlnID = DecodeCTP(itp.CTP);
2456  geo::PlaneID jPlnID = DecodeCTP(jtp.CTP);
2457  if (iPlnID == jPlnID) return tp3d;
2458  double upt = tcc.unitsPerTick;
2459  double ix = detProp.ConvertTicksToX(itp.Pos[1] / upt, iPlnID);
2460  double jx = detProp.ConvertTicksToX(jtp.Pos[1] / upt, jPlnID);
2461 
2462  // don't continue if the points are wildly far apart in X
2463  double dx = std::abs(ix - jx);
2464  if (dx > 20) return tp3d;
2465  tp3d.Pos[0] = (ix + jx) / 2;
2466  tp3d.TPX = ix;
2467  // Fake the error
2468  tp3d.TPXErr2 = dx;
2469  // determine the wire orientation and offsets using WireCoordinate
2470  // wire = yp * OrthY + zp * OrthZ - Wire0 = cs * yp + sn * zp - wire0
2471  // wire offset
2472  double iw0 = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 0}, iPlnID);
2473  // cosine-like component
2474  double ics = tcc.geom->WireCoordinate(geo::Point_t{0, 1, 0}, iPlnID) - iw0;
2475  // sine-like component
2476  double isn = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 1}, iPlnID) - iw0;
2477  double jw0 = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 0}, jPlnID);
2478  double jcs = tcc.geom->WireCoordinate(geo::Point_t{0, 1, 0}, jPlnID) - jw0;
2479  double jsn = tcc.geom->WireCoordinate(geo::Point_t{0, 0, 1}, jPlnID) - jw0;
2480  double den = isn * jcs - ics * jsn;
2481  if (den == 0) return tp3d;
2482  double iPos0 = itp.Pos[0];
2483  double jPos0 = jtp.Pos[0];
2484  // Find the Z position of the intersection
2485  tp3d.Pos[2] = (jcs * (iPos0 - iw0) - ics * (jPos0 - jw0)) / den;
2486  // and the Y position
2487  bool useI = std::abs(ics) > std::abs(jcs);
2488  if (useI) { tp3d.Pos[1] = (iPos0 - iw0 - isn * tp3d.Pos[2]) / ics; }
2489  else {
2490  tp3d.Pos[1] = (jPos0 - jw0 - jsn * tp3d.Pos[2]) / jcs;
2491  }
2492 
2493  // Now find the direction. Protect against large angles first
2494  if (jtp.Dir[1] == 0) {
2495  // Going either in the +X direction or -X direction
2496  if (jtp.Dir[0] > 0) { tp3d.Dir[0] = 1; }
2497  else {
2498  tp3d.Dir[0] = -1;
2499  }
2500  tp3d.Dir[1] = 0;
2501  tp3d.Dir[2] = 0;
2502  return tp3d;
2503  } // jtp.Dir[1] == 0
2504 
2505  tp3d.Wire = iPos0;
2506 
2507  // make a copy of itp and shift it by many wires to avoid precision problems
2508  double itp2_0 = itp.Pos[0] + 100;
2509  double itp2_1 = itp.Pos[1];
2510  if (std::abs(itp.Dir[0]) > 0.01) itp2_1 += 100 * itp.Dir[1] / itp.Dir[0];
2511  // Create a second Point3 for the shifted point
2512  Point3_t pos2;
2513  // Find the X position corresponding to the shifted point
2514  pos2[0] = detProp.ConvertTicksToX(itp2_1 / upt, iPlnID);
2515  // Convert X to Ticks in the j plane and then to WSE units
2516  double jtp2Pos1 = detProp.ConvertXToTicks(pos2[0], jPlnID) * upt;
2517  // Find the wire position (Pos0) in the j plane that this corresponds to
2518  double jtp2Pos0 = (jtp2Pos1 - jtp.Pos[1]) * (jtp.Dir[0] / jtp.Dir[1]) + jtp.Pos[0];
2519  // Find the Y,Z position using itp2 and jtp2Pos0
2520  pos2[2] = (jcs * (itp2_0 - iw0) - ics * (jtp2Pos0 - jw0)) / den;
2521  if (useI) { pos2[1] = (itp2_0 - iw0 - isn * pos2[2]) / ics; }
2522  else {
2523  pos2[1] = (jtp2Pos0 - jw0 - jsn * pos2[2]) / jcs;
2524  }
2525  double sep = PosSep(tp3d.Pos, pos2);
2526  if (sep == 0) return tp3d;
2527  for (unsigned short ixyz = 0; ixyz < 3; ++ixyz)
2528  tp3d.Dir[ixyz] = (pos2[ixyz] - tp3d.Pos[ixyz]) / sep;
2529  tp3d.Flags[kTP3DGood] = true;
2530  return tp3d;
2531 
2532  } // MakeTP3D
2533 
2535  double DeltaAngle(const Vector3_t v1, const Vector3_t v2)
2536  {
2537  if (v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2]) return 0;
2538  return acos(DotProd(v1, v2));
2539  }
2540 
2543  {
2544  // Finds the direction vector between the two points from p1 to p2
2545  Vector3_t dir;
2546  for (unsigned short xyz = 0; xyz < 3; ++xyz)
2547  dir[xyz] = p2[xyz] - p1[xyz];
2548  if (dir[0] == 0 && dir[1] == 0 && dir[2] == 0) return dir;
2549  if (!SetMag(dir, 1)) {
2550  dir[0] = 0;
2551  dir[1] = 0;
2552  dir[3] = 0;
2553  }
2554  return dir;
2555  } // PointDirection
2556 
2558  double PosSep(const Point3_t& pos1, const Point3_t& pos2)
2559  {
2560  return sqrt(PosSep2(pos1, pos2));
2561  } // PosSep
2562 
2564  double PosSep2(const Point3_t& pos1, const Point3_t& pos2)
2565  {
2566  // returns the separation distance^2 between two positions in 3D
2567  double d0 = pos1[0] - pos2[0];
2568  double d1 = pos1[1] - pos2[1];
2569  double d2 = pos1[2] - pos2[2];
2570  return d0 * d0 + d1 * d1 + d2 * d2;
2571  } // PosSep2
2572 
2574  bool SetMag(Vector3_t& v1, double mag)
2575  {
2576  double den = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2];
2577  if (den == 0) return false;
2578  den = sqrt(den);
2579 
2580  v1[0] *= mag / den;
2581  v1[1] *= mag / den;
2582  v1[2] *= mag / den;
2583  return true;
2584  } // SetMag
2585 
2587  void FilldEdx(detinfo::DetectorClocksData const& clockData,
2588  detinfo::DetectorPropertiesData const& detProp,
2589  const TCSlice& slc,
2590  PFPStruct& pfp)
2591  {
2592  // Fills dE/dx variables in the pfp struct
2593 
2594  // don't attempt to find dE/dx at the end of a shower
2595  unsigned short numEnds = 2;
2596  if (pfp.PDGCode == 1111) numEnds = 1;
2597 
2598  // set dE/dx to 0 to indicate that a valid dE/dx is expected
2599  for (unsigned short end = 0; end < numEnds; ++end) {
2600  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane)
2601  pfp.dEdx[end][plane] = 0;
2602  } // end
2603 
2604  // square of the maximum length that is used for finding the average dE/dx
2605  float maxSep2 = 5 * tcc.wirePitch;
2606  maxSep2 *= maxSep2;
2607 
2608  for (unsigned short end = 0; end < numEnds; ++end) {
2609  std::vector<float> cnt(slc.nPlanes);
2610  short dir = 1 - 2 * end;
2611  auto endPos = PosAtEnd(pfp, end);
2612  for (std::size_t ii = 0; ii < pfp.TP3Ds.size(); ++ii) {
2613  unsigned short ipt;
2614  if (dir > 0) { ipt = ii; }
2615  else {
2616  ipt = pfp.TP3Ds.size() - ii - 1;
2617  }
2618  if (ipt >= pfp.TP3Ds.size()) break;
2619  auto& tp3d = pfp.TP3Ds[ipt];
2620  if (tp3d.Flags[kTP3DBad]) continue;
2621  if (PosSep2(tp3d.Pos, endPos) > maxSep2) break;
2622  // require good points
2623  if (!tp3d.Flags[kTP3DGood]) continue;
2624  float dedx = dEdx(clockData, detProp, slc, tp3d);
2625  if (dedx < 0.5) continue;
2626  unsigned short plane = DecodeCTP(tp3d.CTP).Plane;
2627  pfp.dEdx[end][plane] += dedx;
2628  ++cnt[plane];
2629  } // ii
2630  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
2631  if (cnt[plane] == 0) continue;
2632  pfp.dEdx[end][plane] /= cnt[plane];
2633  } // plane
2634  } // end
2635 
2636  } // FilldEdx
2637 
2640  detinfo::DetectorPropertiesData const& detProp,
2641  const TCSlice& slc,
2642  PFPStruct& pfp,
2643  float& dEdXAve,
2644  float& dEdXRms)
2645  {
2646  // Return a simple average of dE/dx and rms using ALL points in all planes, not
2647  // just those at the ends ala FilldEdx
2648  dEdXAve = -1.;
2649  dEdXRms = -1.;
2650 
2651  double sum = 0;
2652  double sum2 = 0;
2653  double cnt = 0;
2654  for (auto& tp3d : pfp.TP3Ds) {
2655  if (!tp3d.Flags[kTP3DGood] || tp3d.Flags[kTP3DBad]) continue;
2656  double dedx = dEdx(clockData, detProp, slc, tp3d);
2657  if (dedx < 0.5 || dedx > 80.) continue;
2658  sum += dedx;
2659  sum2 += dedx * dedx;
2660  ++cnt;
2661  } // tp3d
2662  if (cnt < 3) return;
2663  dEdXAve = sum / cnt;
2664  // Use a default rms of 30% of the average
2665  dEdXRms = 0.3 * dEdXAve;
2666  double arg = sum2 - cnt * dEdXAve * dEdXAve;
2667  if (arg < 0) return;
2668  dEdXRms = sqrt(arg) / (cnt - 1);
2669  // don't return a too-small rms
2670  double minRms = 0.05 * dEdXAve;
2671  if (dEdXRms < minRms) dEdXRms = minRms;
2672  } // Average_dEdX
2673 
2675  float dEdx(detinfo::DetectorClocksData const& clockData,
2676  detinfo::DetectorPropertiesData const& detProp,
2677  const TCSlice& slc,
2678  TP3D& tp3d)
2679  {
2680  if (!tp3d.Flags[kTP3DGood]) return 0;
2681  if (tp3d.TjID > (int)slc.slHits.size()) return 0;
2682  if (tp3d.TjID <= 0) return 0;
2683 
2684  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
2685  if (tp.Environment[kEnvOverlap]) return 0;
2686 
2687  double dQ = 0.;
2688  double time = 0;
2689  for (unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
2690  if (!tp.UseHit[ii]) continue;
2691  auto& hit = (*evt.allHits)[slc.slHits[tp.Hits[ii]].allHitsIndex];
2692  dQ += hit.Integral();
2693  } // ii
2694  time = tp.Pos[1] / tcc.unitsPerTick;
2695  geo::PlaneID plnID = DecodeCTP(tp.CTP);
2696  if (dQ == 0) return 0;
2697  double angleToVert = tcc.geom->Plane(plnID).ThetaZ() - 0.5 * ::util::pi<>();
2698  double cosgamma =
2699  std::abs(std::sin(angleToVert) * tp3d.Dir[1] + std::cos(angleToVert) * tp3d.Dir[2]);
2700  if (cosgamma < 1.E-5) return 0;
2701  double dx = tcc.geom->WirePitch(plnID) / cosgamma;
2702  double dQdx = dQ / dx;
2703  double t0 = 0;
2704  float dedx = tcc.caloAlg->dEdx_AREA(clockData, detProp, dQdx, time, plnID.Plane, t0);
2705  if (std::isinf(dedx)) dedx = 0;
2706  return dedx;
2707  } // dEdx
2708 
2711  const TCSlice& slc,
2712  int tjID,
2713  unsigned short tpIndex)
2714  {
2715  // create a TP3D with a single TP. Note that the SectionFit in which it
2716  // should be placed and the 3D position can't be determined until the the TP3D is
2717  // associated with a pfp. See SetSection()
2718 
2719  TP3D tp3d;
2720  tp3d.Flags[kTP3DBad] = true;
2721  if (tjID <= 0 || tjID > (int)slc.tjs.size()) return tp3d;
2722  auto& tj = slc.tjs[tjID - 1];
2723  if (tpIndex < tj.EndPt[0] || tpIndex > tj.EndPt[1]) return tp3d;
2724  tp3d.TjID = tjID;
2725  tp3d.TPIndex = tpIndex;
2726  auto& tp2 = tj.Pts[tp3d.TPIndex];
2727  auto plnID = DecodeCTP(tp2.CTP);
2728  tp3d.CTP = tp2.CTP;
2729  double tick = tp2.HitPos[1] / tcc.unitsPerTick;
2730  tp3d.TPX = detProp.ConvertTicksToX(tick, plnID);
2731  // Get the RMS of the TP in WSE units and convert to cm
2732  float rms = TPHitsRMSTime(slc, tp2, kAllHits) * tcc.wirePitch;
2733  // inflate the error for large angle TPs
2734  if (tp2.AngleCode == 1) rms *= 2;
2735  // a more careful treatment for long-pulse hits
2736  if (tp2.AngleCode > 1) {
2737  std::vector<unsigned int> hitMultiplet;
2738  for (std::size_t ii = 0; ii < tp2.Hits.size(); ++ii) {
2739  if (!tp2.UseHit[ii]) continue;
2740  GetHitMultiplet(slc, tp2.Hits[ii], hitMultiplet, true);
2741  if (hitMultiplet.size() > 1) break;
2742  } // ii
2743  rms = HitsRMSTime(slc, hitMultiplet, kAllHits) * tcc.wirePitch;
2744  // the returned RMS is closer to the FWHM, so divide by 2
2745  rms /= 2;
2746  } // tp2.AngleCode > 1
2747  tp3d.TPXErr2 = rms * rms;
2748  tp3d.Wire = tp2.Pos[0];
2749  // Can't declare it good since Pos and SFIndex aren't defined
2750  tp3d.Flags[kTP3DGood] = false;
2751  tp3d.Flags[kTP3DBad] = false;
2752  return tp3d;
2753  } // CreateTP3D
2754 
2756  bool SetSection(detinfo::DetectorPropertiesData const& detProp, PFPStruct& pfp, TP3D& tp3d)
2757  {
2758  // Determine which SectionFit this tp3d should reside in, then calculate
2759  // the 3D position and the distance from the center of the SectionFit
2760 
2761  if (tp3d.Wire < 0) return false;
2762  if (pfp.SectionFits.empty()) return false;
2763  if (pfp.SectionFits[0].Pos[0] == -10.0) return false;
2764  if (pfp.Flags[kSmallAngle]) return true;
2765 
2766  auto plnID = DecodeCTP(tp3d.CTP);
2767 
2768  if (pfp.SectionFits.size() == 1) { tp3d.SFIndex = 0; }
2769  else {
2770  // Find the section center that is closest to this point in the wire coordinate
2771  float best = 1E6;
2772  for (std::size_t sfi = 0; sfi < pfp.SectionFits.size(); ++sfi) {
2773  auto& sf = pfp.SectionFits[sfi];
2774  float sfWire = tcc.geom->WireCoordinate(geo::Point_t{0, sf.Pos[1], sf.Pos[2]}, plnID);
2775  float sep = std::abs(sfWire - tp3d.Wire);
2776  if (sep < best) {
2777  best = sep;
2778  tp3d.SFIndex = sfi;
2779  }
2780  } // sfi
2781  } // pfp.SectionFits.size() > 1
2782  auto& sf = pfp.SectionFits[tp3d.SFIndex];
2783  auto plnTP = MakeBareTP(detProp, sf.Pos, sf.Dir, tp3d.CTP);
2784  // the number of wires relative to the SectionFit center
2785  double dw = tp3d.Wire - plnTP.Pos[0];
2786  // dt/dW was stored in DeltaRMS
2787  double t = dw * plnTP.DeltaRMS;
2788  // define the 3D position
2789  for (unsigned short xyz = 0; xyz < 3; ++xyz)
2790  tp3d.Pos[xyz] = sf.Pos[xyz] + t * sf.Dir[xyz];
2791  tp3d.along = t;
2792  tp3d.Flags[kTP3DGood] = true;
2793  return true;
2794  } // SetSection
2795 
2797  float PointPull(const TP3D& tp3d)
2798  {
2799  // returns the pull that the tp3d will cause in the pfp section fit. This
2800  // currently only uses position but eventually will include charge
2801  return std::abs(tp3d.Pos[0] - tp3d.TPX) / sqrt(tp3d.TPXErr2);
2802  } // PointPull
2803 
2806  {
2807  // The calling function should define the size of pfp.TjIDs
2808  PFPStruct pfp;
2809  pfp.ID = slc.pfps.size() + 1;
2810  pfp.ParentUID = 0;
2811  pfp.TPCID = slc.TPCID;
2812  // initialize arrays for both ends
2813  if (slc.nPlanes < 4) {
2814  pfp.dEdx[0].resize(slc.nPlanes, -1);
2815  pfp.dEdx[1].resize(slc.nPlanes, -1);
2816  pfp.dEdxErr[0].resize(slc.nPlanes, -1);
2817  pfp.dEdxErr[1].resize(slc.nPlanes, -1);
2818  }
2819  // create a single section fit to hold the start/end positions and direction
2820  pfp.SectionFits.resize(1);
2821  return pfp;
2822  } // CreatePFP
2823 
2826  {
2827  // Ensure that all PFParticles have a start vertex. It is possible for
2828  // PFParticles to be attached to a 3D vertex that is later killed.
2829  if (!slc.isValid) return;
2830  if (slc.pfps.empty()) return;
2831 
2832  for (auto& pfp : slc.pfps) {
2833  if (pfp.ID == 0) continue;
2834  if (pfp.Vx3ID[0] > 0) continue;
2835  if (pfp.SectionFits.empty()) continue;
2836  Vtx3Store vx3;
2837  vx3.TPCID = pfp.TPCID;
2838  vx3.Vx2ID.resize(slc.nPlanes);
2839  // Flag it as a PFP vertex that isn't required to have matched 2D vertices
2840  vx3.Wire = -2;
2841  Point3_t startPos;
2842  if (pfp.TP3Ds.empty()) {
2843  // must be a neutrino pfp
2844  startPos = pfp.SectionFits[0].Pos;
2845  }
2846  else if (!pfp.TP3Ds.empty()) {
2847  // normal pfp
2848  startPos = pfp.TP3Ds[0].Pos;
2849  }
2850  vx3.X = startPos[0];
2851  vx3.Y = startPos[1];
2852  vx3.Z = startPos[2];
2853  vx3.ID = slc.vtx3s.size() + 1;
2854  vx3.Primary = false;
2855  ++evt.global3V_UID;
2856  vx3.UID = evt.global3V_UID;
2857  slc.vtx3s.push_back(vx3);
2858  pfp.Vx3ID[0] = vx3.ID;
2859  } // pfp
2860  } // PFPVertexCheck
2861 
2864  {
2865  /*
2866  This function reconciles vertices, PFParticles and slc, then
2867  defines the parent (j) - daughter (i) relationship and PDGCode. Here is a
2868  description of the conventions:
2869 
2870  V1 is the highest score 3D vertex in this tpcid so a neutrino PFParticle P1 is defined.
2871  V4 is a high-score vertex that has lower score than V1. It is declared to be a
2872  primary vertex because its score is higher than V5 and it is not associated with the
2873  neutrino interaction
2874  V6 was created to adhere to the convention that all PFParticles, in this case P9,
2875  be associated with a start vertex. There is no score for V6. P9 is it's own parent
2876  but is not a primary PFParticle.
2877 
2878  P1 - V1 - P2 - V2 - P4 - V3 - P5 V4 - P6 V6 - P9
2879  \ \
2880  P3 P7 - V5 - P8
2881 
2882  The PrimaryID in this table is the ID of the PFParticle that is attached to the
2883  primary vertex, which may or may not be a neutrino interaction vertex.
2884  The PrimaryID is returned by the PrimaryID function
2885  PFP parentID DtrIDs PrimaryID
2886  -----------------------------------
2887  P1 P1 P2, P3 P1
2888  P2 P1 P4 P2
2889  P3 P1 none P3
2890  P4 P2 P5 P2
2891  P5 P4 none P2
2892 
2893  P6 P6 none P6
2894  P7 P7 P8 P7
2895 
2896  P9 P9 none 0
2897 
2898  */
2899  if (slc.pfps.empty()) return;
2900  if (tcc.modes[kTestBeam]) return;
2901 
2902  int neutrinoPFPID = 0;
2903  for (auto& pfp : slc.pfps) {
2904  if (pfp.ID == 0) continue;
2905  if (!tcc.modes[kTestBeam] && neutrinoPFPID == 0 && (pfp.PDGCode == 12 || pfp.PDGCode == 14)) {
2906  neutrinoPFPID = pfp.ID;
2907  break;
2908  }
2909  } // pfp
2910 
2911  // define the end vertex if the Tjs have end vertices
2912  constexpr unsigned short end1 = 1;
2913  for (auto& pfp : slc.pfps) {
2914  if (pfp.ID == 0) continue;
2915  // already done?
2916  if (pfp.Vx3ID[end1] > 0) continue;
2917  // ignore shower-like pfps
2918  if (IsShowerLike(slc, pfp.TjIDs)) continue;
2919  // count 2D -> 3D matched vertices
2920  unsigned short cnt3 = 0;
2921  unsigned short vx3id = 0;
2922  // list of unmatched 2D vertices that should be merged
2923  std::vector<int> vx2ids;
2924  for (auto tjid : pfp.TjIDs) {
2925  auto& tj = slc.tjs[tjid - 1];
2926  if (tj.VtxID[end1] == 0) continue;
2927  auto& vx2 = slc.vtxs[tj.VtxID[end1] - 1];
2928  if (vx2.Vx3ID == 0) {
2929  if (vx2.Topo == 1 && vx2.NTraj == 2) vx2ids.push_back(vx2.ID);
2930  continue;
2931  }
2932  if (vx3id == 0) vx3id = vx2.Vx3ID;
2933  if (vx2.Vx3ID == vx3id) ++cnt3;
2934  } // tjid
2935  if (cnt3 > 1) {
2936  // ensure it isn't attached at the other end
2937  if (pfp.Vx3ID[1 - end1] == vx3id) continue;
2938  pfp.Vx3ID[end1] = vx3id;
2939  } // cnt3 > 1
2940  } // pfp
2941 
2942  // Assign a PDGCode to each PFParticle and look for a parent
2943  for (auto& pfp : slc.pfps) {
2944  if (pfp.ID == 0) continue;
2945  // skip a neutrino PFParticle
2946  if (pfp.PDGCode == 12 || pfp.PDGCode == 14 || pfp.PDGCode == 22) continue;
2947  // Define a PFP parent if there are two or more Tjs that are daughters of
2948  // Tjs that are used by the same PFParticle
2949  int pfpParentID = INT_MAX;
2950  unsigned short nParent = 0;
2951  for (auto tjid : pfp.TjIDs) {
2952  auto& tj = slc.tjs[tjid - 1];
2953  if (tj.ParentID <= 0) continue;
2954  auto parPFP = GetAssns(slc, "T", tj.ParentID, "P");
2955  if (parPFP.empty()) continue;
2956  if (pfpParentID == INT_MAX) pfpParentID = parPFP[0];
2957  if (parPFP[0] == pfpParentID) ++nParent;
2958  } // ii
2959  if (nParent > 1) {
2960  auto& ppfp = slc.pfps[pfpParentID - 1];
2961  // set the parent UID
2962  pfp.ParentUID = ppfp.UID;
2963  // add to the parent daughters list
2964  ppfp.DtrUIDs.push_back(pfp.UID);
2965  } // nParent > 1
2966  } // ipfp
2967  // associate primary PFParticles with a neutrino PFParticle
2968  if (neutrinoPFPID > 0) {
2969  auto& neutrinoPFP = slc.pfps[neutrinoPFPID - 1];
2970  int vx3id = neutrinoPFP.Vx3ID[1];
2971  for (auto& pfp : slc.pfps) {
2972  if (pfp.ID == 0 || pfp.ID == neutrinoPFPID) continue;
2973  if (pfp.Vx3ID[0] != vx3id) continue;
2974  pfp.ParentUID = (size_t)neutrinoPFPID;
2975  neutrinoPFP.DtrUIDs.push_back(pfp.ID);
2976  if (pfp.PDGCode == 111) neutrinoPFP.PDGCode = 12;
2977  } // pfp
2978  } // neutrino PFP exists
2979  } // DefinePFPParents
2980 
2982  bool StorePFP(TCSlice& slc, PFPStruct& pfp)
2983  {
2984  // stores the PFParticle in the slice
2985  bool neutrinoPFP = (pfp.PDGCode == 12 || pfp.PDGCode == 14);
2986  if (!neutrinoPFP) {
2987  if (pfp.TjIDs.empty()) return false;
2988  if (pfp.PDGCode != 1111 && pfp.TP3Ds.size() < 2) return false;
2989  }
2990 
2991  if (pfp.Flags[kSmallAngle]) {
2992  // Make the PFP -> TP assn
2993  for (auto& tp3d : pfp.TP3Ds) {
2994  if (tp3d.TPIndex != USHRT_MAX) slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex].InPFP = pfp.ID;
2995  }
2996  }
2997 
2998  // ensure that the InPFP flag is set
2999  unsigned short nNotSet = 0;
3000  for (auto& tp3d : pfp.TP3Ds) {
3001  if (tp3d.Flags[kTP3DBad]) continue;
3002  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
3003  if (tp.InPFP != pfp.ID) ++nNotSet;
3004  } // tp3d
3005  if (nNotSet > 0) return false;
3006  // check the ID and correct it if it is wrong
3007  if (pfp.ID != (int)slc.pfps.size() + 1) pfp.ID = slc.pfps.size() + 1;
3008  ++evt.globalP_UID;
3009  pfp.UID = evt.globalP_UID;
3010 
3011  // set the 3D match flag
3012  for (auto tjid : pfp.TjIDs) {
3013  auto& tj = slc.tjs[tjid - 1];
3014  tj.AlgMod[kMat3D] = true;
3015  } // tjid
3016 
3017  slc.pfps.push_back(pfp);
3018  return true;
3019  } // StorePFP
3020 
3022  bool InsideFV(const TCSlice& slc, const PFPStruct& pfp, unsigned short end)
3023  {
3024  // returns true if the end of the pfp is inside the fiducial volume of the TPC
3025  if (pfp.ID <= 0) return false;
3026  if (end > 1) return false;
3027  if (pfp.SectionFits.empty()) return false;
3028  // require that the points are sorted which ensures that the start and end points
3029  // are the first and last points in the TP3Ds vector
3030  if (pfp.Flags[kNeedsUpdate]) return false;
3031  bool neutrinoPFP = pfp.PDGCode == 12 || pfp.PDGCode == 14;
3032 
3033  float abit = 5;
3034  Point3_t pos;
3035  if (neutrinoPFP) { pos = pfp.SectionFits[0].Pos; }
3036  else if (end == 0) {
3037  pos = pfp.TP3Ds[0].Pos;
3038  }
3039  else {
3040  pos = pfp.TP3Ds[pfp.TP3Ds.size() - 1].Pos;
3041  }
3042  return (pos[0] > slc.xLo + abit && pos[0] < slc.xHi - abit && pos[1] > slc.yLo + abit &&
3043  pos[1] < slc.yHi - abit && pos[2] > slc.zLo + abit && pos[2] < slc.zHi - abit);
3044 
3045  } // InsideFV
3046 
3048  bool InsideTPC(const Point3_t& pos, geo::TPCID& inTPCID)
3049  {
3050  // determine which TPC this point is in. This function returns false
3051  // if the point is not inside any TPC
3052  float abit = 5;
3053  for (const geo::TPCGeo& TPC : tcc.geom->Iterate<geo::TPCGeo>()) {
3054  auto const world = TPC.GetCenter();
3055  // reduce the active area of the TPC by a bit to be consistent with FillWireHitRange
3056  if (pos[0] < world.X() - TPC.HalfWidth() + abit) continue;
3057  if (pos[0] > world.X() + TPC.HalfWidth() - abit) continue;
3058  if (pos[1] < world.Y() - TPC.HalfHeight() + abit) continue;
3059  if (pos[1] > world.Y() + TPC.HalfHeight() - abit) continue;
3060  if (pos[2] < world.Z() - TPC.Length() / 2 + abit) continue;
3061  if (pos[2] > world.Z() + TPC.Length() / 2 - abit) continue;
3062  inTPCID = TPC.ID();
3063  return true;
3064  } // tpcid
3065  return false;
3066  } // InsideTPC
3067 
3069  void FindAlongTrans(Point3_t pos1, Vector3_t dir1, Point3_t pos2, Point2_t& alongTrans)
3070  {
3071  // Calculate the distance along and transvers to the direction vector from pos1 to pos2
3072  alongTrans[0] = 0;
3073  alongTrans[1] = 0;
3074  if (pos1[0] == pos2[0] && pos1[1] == pos2[1] && pos1[2] == pos2[2]) return;
3075  auto ptDir = PointDirection(pos1, pos2);
3076  SetMag(dir1, 1.0);
3077  double costh = DotProd(dir1, ptDir);
3078  if (costh > 1) costh = 1;
3079  double sep = PosSep(pos1, pos2);
3080  alongTrans[0] = costh * sep;
3081  double sinth = sqrt(1 - costh * costh);
3082  alongTrans[1] = sinth * sep;
3083  } // FindAlongTrans
3084 
3087  Vector3_t p1Dir,
3088  Point3_t p2,
3089  Vector3_t p2Dir,
3090  Point3_t& intersect,
3091  float& doca)
3092  {
3093  // Point - vector version
3094  Point3_t p1End, p2End;
3095  for (unsigned short xyz = 0; xyz < 3; ++xyz) {
3096  p1End[xyz] = p1[xyz] + 10 * p1Dir[xyz];
3097  p2End[xyz] = p2[xyz] + 10 * p2Dir[xyz];
3098  }
3099  return LineLineIntersect(p1, p1End, p2, p2End, intersect, doca);
3100  } // PointDirIntersect
3101 
3104  Point3_t p2,
3105  Point3_t p3,
3106  Point3_t p4,
3107  Point3_t& intersect,
3108  float& doca)
3109  {
3110  /*
3111  Calculate the line segment PaPb that is the shortest route between
3112  two lines P1P2 and P3P4. Calculate also the values of mua and mub where
3113  Pa = P1 + mua (P2 - P1)
3114  Pb = P3 + mub (P4 - P3)
3115  Return FALSE if no solution exists.
3116  http://paulbourke.net/geometry/pointlineplane/
3117  */
3118 
3119  Point3_t p13, p43, p21;
3120  double d1343, d4321, d1321, d4343, d2121;
3121  double numer, denom;
3122  constexpr double EPS = std::numeric_limits<double>::min();
3123 
3124  p13[0] = p1[0] - p3[0];
3125  p13[1] = p1[1] - p3[1];
3126  p13[2] = p1[2] - p3[2];
3127  p43[0] = p4[0] - p3[0];
3128  p43[1] = p4[1] - p3[1];
3129  p43[2] = p4[2] - p3[2];
3130  if (std::abs(p43[0]) < EPS && std::abs(p43[1]) < EPS && std::abs(p43[2]) < EPS) return (false);
3131  p21[0] = p2[0] - p1[0];
3132  p21[1] = p2[1] - p1[1];
3133  p21[2] = p2[2] - p1[2];
3134  if (std::abs(p21[0]) < EPS && std::abs(p21[1]) < EPS && std::abs(p21[2]) < EPS) return (false);
3135 
3136  d1343 = p13[0] * p43[0] + p13[1] * p43[1] + p13[2] * p43[2];
3137  d4321 = p43[0] * p21[0] + p43[1] * p21[1] + p43[2] * p21[2];
3138  d1321 = p13[0] * p21[0] + p13[1] * p21[1] + p13[2] * p21[2];
3139  d4343 = p43[0] * p43[0] + p43[1] * p43[1] + p43[2] * p43[2];
3140  d2121 = p21[0] * p21[0] + p21[1] * p21[1] + p21[2] * p21[2];
3141 
3142  denom = d2121 * d4343 - d4321 * d4321;
3143  if (std::abs(denom) < EPS) return (false);
3144  numer = d1343 * d4321 - d1321 * d4343;
3145 
3146  double mua = numer / denom;
3147  double mub = (d1343 + d4321 * mua) / d4343;
3148 
3149  intersect[0] = p1[0] + mua * p21[0];
3150  intersect[1] = p1[1] + mua * p21[1];
3151  intersect[2] = p1[2] + mua * p21[2];
3152  Point3_t pb;
3153  pb[0] = p3[0] + mub * p43[0];
3154  pb[1] = p3[1] + mub * p43[1];
3155  pb[2] = p3[2] + mub * p43[2];
3156  doca = PosSep(intersect, pb);
3157  // average the closest points
3158  for (unsigned short xyz = 0; xyz < 3; ++xyz)
3159  intersect[xyz] += pb[xyz];
3160  for (unsigned short xyz = 0; xyz < 3; ++xyz)
3161  intersect[xyz] /= 2;
3162  return true;
3163  } // LineLineIntersect
3164 
3167  const TCSlice& slc,
3168  Point3_t pos1,
3169  Point3_t pos2)
3170  {
3171  // Step between pos1 and pos2 and find the fraction of the points that have nearby hits
3172  // in each plane. This function returns -1 if something is fishy, but this doesn't mean
3173  // that there is no charge. Note that there is no check for charge precisely at the pos1 and pos2
3174  // positions
3175  float sep = PosSep(pos1, pos2);
3176  if (sep == 0) return -1;
3177  unsigned short nstep = sep / tcc.wirePitch;
3178  auto dir = PointDirection(pos1, pos2);
3179  float sum = 0;
3180  float cnt = 0;
3181  TrajPoint tp;
3182  for (unsigned short step = 0; step < nstep; ++step) {
3183  for (unsigned short xyz = 0; xyz < 3; ++xyz)
3184  pos1[xyz] += tcc.wirePitch * dir[xyz];
3185  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
3186  tp.CTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, plane);
3187  geo::PlaneID const planeID{slc.TPCID, plane};
3188  tp.Pos[0] = tcc.geom->WireCoordinate(geo::Point_t{0, pos1[1], pos1[2]}, planeID);
3189  tp.Pos[1] = detProp.ConvertXToTicks(pos1[0], plane, slc.TPCID.TPC, slc.TPCID.Cryostat) *
3190  tcc.unitsPerTick;
3191  ++cnt;
3192  if (SignalAtTp(tp)) ++sum;
3193  } // plane
3194  } // step
3195  if (cnt == 0) return -1;
3196  return sum / cnt;
3197  } // ChgFracBetween
3198 
3201  const TCSlice& slc,
3202  const PFPStruct& pfp,
3203  unsigned short end)
3204  {
3205  // returns the charge fraction near the end of the pfp. Note that this function
3206  // assumes that there is only one Tj in a plane.
3207  if (pfp.ID == 0) return 0;
3208  if (pfp.TjIDs.empty()) return 0;
3209  if (end > 1) return 0;
3210  if (pfp.TPCID != slc.TPCID) return 0;
3211  if (pfp.SectionFits.empty()) return 0;
3212 
3213  float sum = 0;
3214  float cnt = 0;
3215  // keep track of the lowest value and maybe reject it
3216  float lo = 1;
3217  float hi = 0;
3218  auto pos3 = PosAtEnd(pfp, end);
3219  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
3220  CTP_t inCTP = EncodeCTP(pfp.TPCID.Cryostat, pfp.TPCID.TPC, plane);
3221  std::vector<int> tjids(1);
3222  for (auto tjid : pfp.TjIDs) {
3223  auto& tj = slc.tjs[tjid - 1];
3224  if (tj.CTP != inCTP) continue;
3225  tjids[0] = tjid;
3226  Point2_t pos2;
3227  geo::PlaneID planeID = geo::PlaneID{pfp.TPCID, plane};
3228  pos2[0] = tcc.geom->WireCoordinate(geo::Point_t{0, pos3[1], pos3[2]}, planeID);
3229  if (pos2[0] < -0.4) continue;
3230  // check for dead wires
3231  unsigned int wire = std::nearbyint(pos2[0]);
3232  if (wire > slc.nWires[plane]) continue;
3233  if (slc.wireHitRange[plane][wire].first == UINT_MAX) continue;
3234  pos2[1] = detProp.ConvertXToTicks(pos3[0], planeID) * tcc.unitsPerTick;
3235  float cf = ChgFracNearPos(slc, pos2, tjids);
3236  if (cf < lo) lo = cf;
3237  if (cf > hi) hi = cf;
3238  sum += cf;
3239  ++cnt;
3240  } // tjid
3241  } // plane
3242  if (cnt == 0) return 0;
3243  if (cnt > 1 && lo < 0.3 && hi > 0.8) {
3244  sum -= lo;
3245  --cnt;
3246  }
3247  return sum / cnt;
3248  } // ChgFracNearEnd
3249 
3251  Vector3_t DirAtEnd(const PFPStruct& pfp, unsigned short end)
3252  {
3253  if (end > 1 || pfp.SectionFits.empty()) return {{0., 0., 0.}};
3254  if (end == 0) return pfp.SectionFits[0].Dir;
3255  return pfp.SectionFits[pfp.SectionFits.size() - 1].Dir;
3256  } // PosAtEnd
3257 
3259  Point3_t PosAtEnd(const PFPStruct& pfp, unsigned short end)
3260  {
3261  if (end > 1 || pfp.SectionFits.empty()) return {{0., 0., 0.}};
3262  // handle a neutrino pfp that doesn't have any TP3Ds
3263  if (pfp.TP3Ds.empty()) return pfp.SectionFits[0].Pos;
3264  if (end == 0) return pfp.TP3Ds[0].Pos;
3265  return pfp.TP3Ds[pfp.TP3Ds.size() - 1].Pos;
3266  } // PosAtEnd
3267 
3269  float Length(const PFPStruct& pfp)
3270  {
3271  if (pfp.TP3Ds.empty()) return 0;
3272  return PosSep(pfp.TP3Ds[0].Pos, pfp.TP3Ds[pfp.TP3Ds.size() - 1].Pos);
3273  } // Length
3274 
3276  bool SectionStartEnd(const PFPStruct& pfp,
3277  unsigned short sfIndex,
3278  unsigned short& startPt,
3279  unsigned short& endPt)
3280  {
3281  // this assumes that the TP3Ds vector is sorted
3282  startPt = USHRT_MAX;
3283  endPt = USHRT_MAX;
3284  if (sfIndex >= pfp.SectionFits.size()) return false;
3285 
3286  bool first = true;
3287  for (std::size_t ipt = 0; ipt < pfp.TP3Ds.size(); ++ipt) {
3288  auto& tp3d = pfp.TP3Ds[ipt];
3289  if (tp3d.SFIndex < sfIndex) continue;
3290  if (first) {
3291  first = false;
3292  startPt = ipt;
3293  } // first
3294  if (tp3d.SFIndex > sfIndex) break;
3295  endPt = ipt;
3296  } // ipt
3297  return true;
3298 
3299  } // SectionStartEnd
3300 
3302  unsigned short FarEnd(const PFPStruct& pfp, const Point3_t& pos)
3303  {
3304  // Returns the end (0 or 1) of the pfp that is furthest away from the position pos
3305  if (pfp.ID == 0) return 0;
3306  if (pfp.TP3Ds.empty()) return 0;
3307  auto& pos0 = pfp.TP3Ds[0].Pos;
3308  auto& pos1 = pfp.TP3Ds[pfp.TP3Ds.size() - 1].Pos;
3309  if (PosSep2(pos1, pos) > PosSep2(pos0, pos)) return 1;
3310  return 0;
3311  } // FarEnd
3312 
3315  detinfo::DetectorPropertiesData const& detProp,
3316  const TCSlice& slc,
3317  PFPStruct& pfp)
3318  {
3319  // returns a vote using PDG code assignments from dE/dx. A PDGCode of -1 is
3320  // returned if there was a failure and returns 0 if no decision can be made
3321  if (pfp.TP3Ds.empty()) return -1;
3322 
3323  // try to do better using dE/dx
3324  float dEdXAve = 0;
3325  float dEdXRms = 0;
3326  Average_dEdX(clockData, detProp, slc, pfp, dEdXAve, dEdXRms);
3327  if (dEdXAve < 0) return 0;
3328  // looks like a proton if dE/dx is high and the rms is low
3329  dEdXRms /= dEdXAve;
3330  float length = Length(pfp);
3331  float mcsmom = 0;
3332  float chgrms = 0;
3333  float cnt = 0;
3334  for (auto tjid : pfp.TjIDs) {
3335  auto& tj = slc.tjs[tjid - 1];
3336  float el = ElectronLikelihood(slc, tj);
3337  if (el <= 0) continue;
3338  mcsmom += MCSMom(slc, tj);
3339  chgrms += tj.ChgRMS;
3340  ++cnt;
3341  } // tjid
3342  if (cnt < 2) return 0;
3343  mcsmom /= cnt;
3344  chgrms /= cnt;
3345  int vote = 0;
3346  // call anything longer than 150 cm a muon
3347  if (length > 150) vote = 13;
3348  // or shorter with low dE/dx and really straight
3349  if (vote == 0 && length > 50 && dEdXAve < 2.5 && mcsmom > 500) vote = 13;
3350  // protons have high dE/dx, high MCSMom and low charge rms
3351  if (vote == 0 && dEdXAve > 3.0 && mcsmom > 200 && chgrms < 0.4) vote = 2212;
3352  // electrons have low MCSMom and large charge RMS
3353  if (vote == 0 && mcsmom < 50 && chgrms > 0.4) vote = 11;
3354  return vote;
3355  } // PDGCodeVote
3356 
3359  detinfo::DetectorPropertiesData const& detProp,
3360  std::string someText,
3361  const TCSlice& slc,
3362  const PFPStruct& pfp,
3363  short printPts)
3364  {
3365  if (pfp.TP3Ds.empty()) return;
3366  mf::LogVerbatim myprt("TC");
3367  myprt << someText << " pfp P" << pfp.ID << " MVI " << pfp.MVI;
3368  for (auto tid : pfp.TjIDs)
3369  myprt << " T" << tid;
3370  myprt << " Flags: CanSection? " << pfp.Flags[kCanSection];
3371  myprt << " NeedsUpdate? " << pfp.Flags[kNeedsUpdate];
3372  myprt << " Algs:";
3373  for (unsigned short ib = 0; ib < pAlgModSize; ++ib) {
3374  if (pfp.AlgMod[ib]) myprt << " " << AlgBitNames[ib];
3375  } // ib
3376  myprt << "\n";
3377  if (!pfp.SectionFits.empty()) {
3378  myprt << someText
3379  << " SFI ________Pos________ ________Dir_______ _____EndPos________ ChiDOF NPts "
3380  "NeedsUpdate?\n";
3381  for (std::size_t sfi = 0; sfi < pfp.SectionFits.size(); ++sfi) {
3382  myprt << someText << std::setw(4) << sfi;
3383  auto& sf = pfp.SectionFits[sfi];
3384  myprt << std::fixed << std::setprecision(1);
3385  unsigned short startPt = 0, endPt = 0;
3386  if (SectionStartEnd(pfp, sfi, startPt, endPt)) {
3387  auto& start = pfp.TP3Ds[startPt].Pos;
3388  myprt << std::setw(7) << start[0] << std::setw(7) << start[1] << std::setw(7) << start[2];
3389  }
3390  else {
3391  myprt << " Invalid";
3392  }
3393  myprt << std::fixed << std::setprecision(2);
3394  myprt << std::setw(7) << sf.Dir[0] << std::setw(7) << sf.Dir[1] << std::setw(7)
3395  << sf.Dir[2];
3396  myprt << std::fixed << std::setprecision(1);
3397  if (endPt < pfp.TP3Ds.size()) {
3398  auto& end = pfp.TP3Ds[endPt].Pos;
3399  myprt << std::setw(7) << end[0] << std::setw(7) << end[1] << std::setw(7) << end[2];
3400  }
3401  else {
3402  myprt << " Invalid";
3403  }
3404  myprt << std::setprecision(1) << std::setw(6) << sf.ChiDOF;
3405  myprt << std::setw(6) << sf.NPts;
3406  myprt << std::setw(6) << sf.NeedsUpdate;
3407  myprt << "\n";
3408  } // sec
3409  } // SectionFits
3410  if (printPts < 0) {
3411  // print the head if we print all points
3412  myprt << someText << " Note: GBH = TP3D Flags. G = Good, B = Bad, H = High dE/dx \n";
3413  myprt
3414  << someText
3415  << " ipt SFI ________Pos________ Delta Pull GBH Path along dE/dx S? T_ipt_P:W:T\n";
3416  }
3417  unsigned short fromPt = 0;
3418  unsigned short toPt = pfp.TP3Ds.size() - 1;
3419  if (printPts >= 0) fromPt = toPt;
3420  // temp kink angle for each point
3421  std::vector<float> dang(pfp.TP3Ds.size(), -1);
3422  for (unsigned short ipt = fromPt; ipt <= toPt; ++ipt) {
3423  auto tp3d = pfp.TP3Ds[ipt];
3424  myprt << someText << std::setw(4) << ipt;
3425  myprt << std::setw(4) << tp3d.SFIndex;
3426  myprt << std::fixed << std::setprecision(1);
3427  myprt << std::setw(7) << tp3d.Pos[0] << std::setw(7) << tp3d.Pos[1] << std::setw(7)
3428  << tp3d.Pos[2];
3429  myprt << std::setprecision(1) << std::setw(6) << (tp3d.Pos[0] - tp3d.TPX);
3430  float pull = PointPull(tp3d);
3431  myprt << std::setprecision(1) << std::setw(6) << pull;
3432  myprt << std::setw(3) << tp3d.Flags[kTP3DGood] << tp3d.Flags[kTP3DBad];
3433  myprt << std::setw(7) << std::setprecision(1) << PosSep(tp3d.Pos, pfp.TP3Ds[0].Pos);
3434  myprt << std::setw(7) << std::setprecision(1) << tp3d.along;
3435  myprt << std::setw(6) << std::setprecision(2) << dEdx(clockData, detProp, slc, tp3d);
3436  // print SignalAtTP in each plane
3437  myprt << " ";
3438  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
3439  CTP_t inCTP = EncodeCTP(pfp.TPCID.Cryostat, pfp.TPCID.TPC, plane);
3440  auto tp = MakeBareTP(detProp, tp3d.Pos, inCTP);
3441  myprt << " " << SignalAtTp(tp);
3442  } // plane
3443  if (tp3d.TjID > 0) {
3444  auto& tp = slc.tjs[tp3d.TjID - 1].Pts[tp3d.TPIndex];
3445  myprt << " T" << tp3d.TjID << "_" << tp3d.TPIndex << "_" << PrintPos(tp) << " "
3446  << TPEnvString(tp);
3447  }
3448  else {
3449  myprt << " UNDEFINED";
3450  }
3451  myprt << "\n";
3452  } // ipt
3453  } // PrintTP3Ds
3454 } // namespace
Expect tracks entering from the front face. Don&#39;t create neutrino PFParticles.
Definition: DataStructs.h:525
Float_t x
Definition: compare.C:6
calo::CalorimetryAlg * caloAlg
Definition: DataStructs.h:570
code to link reconstructed objects back to the MC truth information
Vector2_t Dir
Definition: DataStructs.h:153
details::range_type< T > Iterate() const
Initializes the specified ID with the ID of the first cryostat.
Definition: GeometryCore.h:541
Point2_t Pos
Definition: DataStructs.h:152
unsigned short NPts
Definition: DataStructs.h:244
short MCSMom(const TCSlice &slc, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3380
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
float Wire
Definition: DataStructs.h:254
bool dbgStitch
debug PFParticle stitching
Definition: DataStructs.h:596
unsigned int NTPC(CryostatID const &cryoid=cryostat_zero) const
Returns the total number of TPCs in the specified cryostat.
Definition: GeometryCore.h:686
void Average_dEdX(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp, float &dEdXAve, float &dEdXRms)
Definition: PFPUtils.cxx:2639
Length_t WireCoordinate(Point_t const &pos, PlaneID const &planeid) const
Returns the index of the nearest wire to the specified position.
void MakePFParticles(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, std::vector< MatchStruct > matVec, unsigned short matVec_Iter)
Definition: PFPUtils.cxx:265
std::array< std::vector< float >, 2 > dEdxErr
Definition: DataStructs.h:286
bool MakeVertexObsolete(std::string fcnLabel, TCSlice &slc, VtxStore &vx2, bool forceKill)
Definition: TCVertex.cxx:2690
int TjID
ID of the trajectory -> TP3D assn.
Definition: DataStructs.h:256
bool ReconcileTPs(TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:423
bool InsideFV(const TCSlice &slc, const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3022
static unsigned int kWire
const std::vector< std::string > AlgBitNames
Definition: DataStructs.cxx:16
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:150
bool InsideTPC(const Point3_t &pos, geo::TPCID &inTPCID)
Definition: PFPUtils.cxx:3048
bool HasWire(WireID const &wireid) const
Returns whether we have the specified wire.
std::array< double, 3 > Point3_t
Definition: DataStructs.h:41
bool SignalAtTp(TrajPoint &tp)
Definition: Utils.cxx:1963
std::vector< float > vtx3DCuts
2D vtx -> 3D vtx matching cuts
Definition: DataStructs.h:544
bool SortSection(PFPStruct &pfp, unsigned short sfIndex)
Definition: PFPUtils.cxx:2005
TCConfig tcc
Definition: DataStructs.cxx:9
unsigned short id
Definition: DataStructs.h:143
Float_t den
Definition: plot.C:35
Declaration of signal hit object.
void FindAlongTrans(Point3_t pos1, Vector3_t dir1, Point3_t pos2, Point2_t &alongTrans)
Definition: PFPUtils.cxx:3069
Float_t y
Definition: compare.C:6
std::bitset< pAlgModSize > AlgMod
Definition: DataStructs.h:299
Double_t z
Definition: plot.C:276
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > wireHitRange
Definition: DataStructs.h:668
std::vector< int > Vx2ID
Definition: DataStructs.h:111
The data type to uniquely identify a Plane.
Definition: geo_types.h:463
Geometry information for a single TPC.
Definition: TPCGeo.h:36
Point3_t Pos
center position of this section
Definition: DataStructs.h:240
bool StoreTraj(TCSlice &slc, Trajectory &tj)
Definition: Utils.cxx:1073
bool LineLineIntersect(Point3_t p1, Point3_t p2, Point3_t p3, Point3_t p4, Point3_t &intersect, float &doca)
Definition: PFPUtils.cxx:3103
bool IsShowerLike(TCSlice const &slc, std::vector< int > const &TjIDs)
Definition: TCShower.cxx:1891
constexpr auto abs(T v)
Returns the absolute value of the argument.
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:211
for(Int_t i=0;i< nentries;i++)
Definition: comparison.C:30
static unsigned int kPlane
void SetAngleCode(TrajPoint &tp)
Definition: Utils.cxx:764
PFPStruct CreatePFP(const TCSlice &slc)
Definition: PFPUtils.cxx:2805
Float_t tmp
Definition: plot.C:35
Point3_t PosAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3259
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 Match2Planes(TCSlice &slc, std::vector< MatchStruct > &matVec)
Definition: PFPUtils.cxx:942
bool CanSection(const TCSlice &slc, const PFPStruct &pfp)
Definition: PFPUtils.cxx:1330
std::array< int, 2 > Vx3ID
Definition: DataStructs.h:287
float TPHitsRMSTime(const TCSlice &slc, const TrajPoint &tp, HitStatus_t hitRequest)
Definition: Utils.cxx:4090
unsigned int MVI
MatchVec Index for detailed 3D matching.
Definition: DebugStruct.h:29
void FilldEdx(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp)
Definition: PFPUtils.cxx:2587
void ReconcileVertices(TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:1632
std::string TPEnvString(const TrajPoint &tp)
Definition: Utils.cxx:6187
std::string PrintPos(const TrajPoint &tp)
Definition: Utils.cxx:6353
std::vector< std::array< unsigned int, 3 > > sptHits
SpacePoint -> Hits assns by plane.
Definition: DataStructs.h:626
float MaxTjLen(const TCSlice &slc, std::vector< int > &tjIDs)
Definition: Utils.cxx:2566
bool Update(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp)
Definition: PFPUtils.cxx:1055
void MakePFPTjs(TCSlice &slc)
Definition: PFPUtils.cxx:507
unsigned short Find3DRecoRange(const TCSlice &slc, const PFPStruct &pfp, unsigned short fromPt, unsigned short min2DPts, short dir)
Definition: PFPUtils.cxx:1345
bool MakeSmallAnglePFP(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:2195
Vector3_t DirErr
and direction error
Definition: DataStructs.h:242
double ThetaZ() const
Angle of the wires from positive z axis; .
Definition: PlaneGeo.cxx:646
unsigned short plane
Definition: DataStructs.h:141
void AddPointsInRange(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, PFPStruct &pfp, unsigned short fromPt, unsigned short toPt, CTP_t inCTP, float maxPull, unsigned short &nWires, unsigned short &nAdd, bool prt)
Definition: PFPUtils.cxx:1817
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
unsigned short npts
Definition: DataStructs.h:146
std::bitset< 8 > Flags
Definition: DataStructs.h:297
float HitsRMSTime(const TCSlice &slc, const std::vector< unsigned int > &hitsInMultiplet, HitStatus_t hitRequest)
Definition: Utils.cxx:4123
Access the description of detector geometry.
std::vector< int > TjUIDs
Definition: DataStructs.h:281
unsigned short ipt
Definition: DataStructs.h:144
PlaneGeo const & Plane(PlaneID const &planeid) const
Returns the specified wire.
CTP_t CTP
Definition: DataStructs.h:257
struct of temporary 3D vertices
Definition: DataStructs.h:101
Vector3_t Dir
Definition: DataStructs.h:251
geo::TPCID TPCID
Definition: DataStructs.h:292
if(nlines<=0)
void PFPVertexCheck(TCSlice &slc)
Definition: PFPUtils.cxx:2825
Vector3_t Dir
and direction
Definition: DataStructs.h:241
std::vector< Tj2Pt > mallTraj
vector of trajectory points ordered by increasing X
Definition: DataStructs.h:664
void FillGaps3D(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:1726
std::array< float, 2 > Point2_t
Definition: DataStructs.h:43
int PDGCodeVote(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp)
Definition: PFPUtils.cxx:3314
float unitsPerTick
scale factor from Tick to WSE equivalent units
Definition: DataStructs.h:564
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
void FillWireIntersections(TCSlice &slc)
Definition: PFPUtils.cxx:606
Float_t E
Definition: plot.C:20
bool MakeTP3Ds(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:2103
int UID
unique global ID
Definition: DataStructs.h:113
unsigned short TPIndex
and the TP index
Definition: DataStructs.h:258
unsigned int wire
Definition: DataStructs.h:137
void Match3Planes(TCSlice &slc, std::vector< MatchStruct > &matVec)
Definition: PFPUtils.cxx:812
void GetHitMultiplet(const TCSlice &slc, unsigned int theHit, std::vector< unsigned int > &hitsInMultiplet, bool useLongPulseHits)
Definition: StepUtils.cxx:1521
bool TCIntersectionPoint(unsigned int wir1, unsigned int wir2, unsigned int pln1, unsigned int pln2, float &y, float &z)
Definition: PFPUtils.cxx:670
float ElectronLikelihood(const TCSlice &slc, const Trajectory &tj)
Definition: Utils.cxx:3143
bool FitSection(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp, unsigned short sfIndex)
Definition: PFPUtils.cxx:1398
std::vector< std::vector< bool > > goodWire
Definition: DataStructs.h:623
Point3_t Pos
position of the trajectory
Definition: DataStructs.h:250
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
float ChgFracBetween(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, Point3_t pos1, Point3_t pos2)
Definition: PFPUtils.cxx:3166
TrajPoint MakeBareTP(detinfo::DetectorPropertiesData const &detProp, const Point3_t &pos, CTP_t inCTP)
Definition: Utils.cxx:3927
unsigned short pln2
Definition: DataStructs.h:122
std::bitset< 8 > Flags
Definition: DataStructs.h:260
std::vector< VtxStore > vtxs
2D vertices
Definition: DataStructs.h:669
tick_as<> tick
Tick number, represented by std::ptrdiff_t.
Definition: electronics.h:73
double ConvertXToTicks(double X, int p, int t, int c) const
void CountBadPoints(const TCSlice &slc, const PFPStruct &pfp, unsigned short fromPt, unsigned short toPt, unsigned short &nBadPts, unsigned short &firstBadPt)
Definition: PFPUtils.cxx:1297
bool ReSection(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:1104
std::vector< float > match3DCuts
3D matching cuts
Definition: DataStructs.h:553
std::vector< SectionFit > SectionFits
Definition: DataStructs.h:283
void MakeTrajectoryObsolete(TCSlice &slc, unsigned int itj)
Definition: Utils.cxx:2137
double TPXErr2
(X position error)^2
Definition: DataStructs.h:253
double PosSep2(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2564
const geo::GeometryCore * geom
Definition: DataStructs.h:569
bool SectionStartEnd(const PFPStruct &pfp, unsigned short sfIndex, unsigned short &startPt, unsigned short &endPt)
Definition: PFPUtils.cxx:3276
bool PointDirIntersect(Point3_t p1, Vector3_t p1Dir, Point3_t p2, Vector3_t p2Dir, Point3_t &intersect, float &doca)
Definition: PFPUtils.cxx:3086
The data type to uniquely identify a TPC.
Definition: geo_types.h:381
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:481
constexpr unsigned int pAlgModSize
Definition: DataStructs.h:277
void PrintP(mf::LogVerbatim &myprt, PFPStruct const &pfp, bool &printHeader)
Definition: Utils.cxx:5450
bool valsIncreasing(const SortEntry &c1, const SortEntry &c2)
Definition: Utils.cxx:42
bool SetMag(Vector3_t &v1, double mag)
Definition: PFPUtils.cxx:2574
Definition of data types for geometry description.
unsigned short FarEnd(const PFPStruct &pfp, const Point3_t &pos)
Definition: PFPUtils.cxx:3302
float dEdx(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, TP3D &tp3d)
Definition: PFPUtils.cxx:2675
std::vector< TCSlice > slices
Definition: DataStructs.cxx:13
Detector simulation of raw signals on wires.
void GetRange(const PFPStruct &pfp, unsigned short sfIndex, unsigned short &fromPt, unsigned short &npts)
Definition: PFPUtils.cxx:1377
unsigned short InsertTP3D(PFPStruct &pfp, TP3D &tp3d)
Definition: PFPUtils.cxx:1967
SectionFit FitTP3Ds(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, const std::vector< TP3D > &tp3ds, unsigned short fromPt, short fitDir, unsigned short nPtsFit)
Definition: PFPUtils.cxx:1428
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
void Recover(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, PFPStruct &pfp, bool prt)
Definition: PFPUtils.cxx:2065
TP3D CreateTP3D(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, int tjID, unsigned short tpIndex)
Definition: PFPUtils.cxx:2710
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
double weight
Definition: plottest35.C:25
bool ValidTwoPlaneMatch(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, const PFPStruct &pfp)
Definition: PFPUtils.cxx:1777
double DotProd(const Vector3_t &v1, const Vector3_t &v2)
Definition: PFPUtils.h:126
unsigned int CTP_t
Definition: DataStructs.h:47
std::vector< Vtx3Store > vtx3s
3D vertices
Definition: DataStructs.h:670
geo::TPCID TPCID
Definition: DataStructs.h:655
void FillmAllTraj(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: PFPUtils.cxx:2375
std::bitset< 128 > useAlg
Max hit separation for making junk trajectories. < 0 to turn off.
Definition: DataStructs.h:579
Float_t norm
Encapsulate the construction of a single detector plane.
Contains all timing reference information for the detector.
TDirectory * dir
Definition: macro.C:5
std::vector< float > pfpStitchCuts
cuts for stitching between TPCs
Definition: DataStructs.h:557
TP3D MakeTP3D(detinfo::DetectorPropertiesData const &detProp, const TrajPoint &itp, const TrajPoint &jtp)
Definition: PFPUtils.cxx:2441
geo::PlaneID DecodeCTP(CTP_t CTP)
float along
distance from the start Pos of the section
Definition: DataStructs.h:255
double PosSep(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2558
std::vector< int > TjIDs
Definition: DataStructs.h:280
float PointPull(const TP3D &tp3d)
Definition: PFPUtils.cxx:2797
std::vector< TCWireIntersection > wireIntersections
Definition: DataStructs.h:632
std::vector< recob::Hit > const * allHits
Definition: DataStructs.h:615
bool valsDecreasing(const SortEntry &c1, const SortEntry &c2)
Definition: Utils.cxx:38
CTP_t EncodeCTP(unsigned int cryo, unsigned int tpc, unsigned int plane)
Definition: DataStructs.h:51
double dEdx_AREA(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop, recob::Hit const &hit, double pitch, double T0=0) const
std::pair< unsigned short, unsigned short > GetSliceIndex(std::string typeName, int uID)
Definition: Utils.cxx:4939
unsigned short MVI_Iter
MVI iteration - see FindPFParticles.
Definition: DebugStruct.h:30
std::vector< unsigned int > nWires
Definition: DataStructs.h:646
std::array< double, 3 > Vector3_t
Definition: DataStructs.h:42
std::vector< TP3D > TP3Ds
Definition: DataStructs.h:282
int ID
ID of the recob::Slice (not the sub-slice)
Definition: DataStructs.h:657
std::array< std::vector< float >, 2 > dEdx
Definition: DataStructs.h:285
void Match3PlanesSpt(TCSlice &slc, std::vector< MatchStruct > &matVec)
Definition: PFPUtils.cxx:701
unsigned short pln1
Definition: DataStructs.h:121
unsigned short nPlanes
Definition: DataStructs.h:656
void FindPFParticles(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: PFPUtils.cxx:189
std::vector< PFPStruct > pfps
Definition: DataStructs.h:671
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
TCEvent evt
Definition: DataStructs.cxx:8
unsigned short MVI
Definition: DataStructs.h:296
double TPX
X position of the TP or the single hit.
Definition: DataStructs.h:252
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:399
bool StorePFP(TCSlice &slc, PFPStruct &pfp)
Definition: PFPUtils.cxx:2982
Collection of Physical constants used in LArSoft.
Double_t sum
Definition: plot.C:31
float ChgFracNearEnd(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3200
void PrintTP3Ds(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, std::string someText, const TCSlice &slc, const PFPStruct &pfp, short printPts)
Definition: PFPUtils.cxx:3358
size_t ParentUID
Definition: DataStructs.h:291
Vector3_t DirAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3251
void StitchPFPs()
Definition: PFPUtils.cxx:41
Float_t w
Definition: plot.C:20
bool SetSection(detinfo::DetectorPropertiesData const &detProp, PFPStruct &pfp, TP3D &tp3d)
Definition: PFPUtils.cxx:2756
Length_t WirePitch(PlaneID const &planeid=plane_zero) const
Returns the distance between two consecutive wires.
void DefinePFPParents(TCSlice &slc)
Definition: PFPUtils.cxx:2863
bool AttachToAnyVertex(TCSlice &slc, PFPStruct &pfp, float maxSep, bool prt)
Definition: TCVertex.cxx:1572
constexpr TPCID()=default
Default constructor: an invalid TPC ID.
Encapsulate the construction of a single detector plane.
std::array< std::bitset< 8 >, 2 > EndFlag
Definition: DataStructs.h:298
bool SptInTPC(const std::array< unsigned int, 3 > &sptHits, unsigned int tpc)
Definition: PFPUtils.cxx:792
void Reverse(PFPStruct &pfp)
Definition: PFPUtils.cxx:2354
unsigned short SFIndex
and the section fit index
Definition: DataStructs.h:259
unsigned int index
Definition: DataStructs.h:36