LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
TCShower.cxx
Go to the documentation of this file.
2 #include "cetlib/search_path.h"
4 
15 
16 #include <algorithm>
17 #include <array>
18 #include <bitset>
19 #include <climits>
20 #include <cmath>
21 #include <iomanip>
22 #include <iostream>
23 #include <string>
24 #include <utility>
25 #include <vector>
26 
27 #include "TMVA/Reader.h"
28 
29 namespace tca {
30 
31  using namespace detail;
32 
34  void ConfigureMVA(TCConfig& tcc, std::string fMVAShowerParentWeights)
35  {
36  // Define the reference to the MVA reader used to determine the best
37  // shower parent PFParticle
38  cet::search_path sp("FW_SEARCH_PATH");
39  if (!tcc.showerParentReader) return;
40  std::string fullFileSpec;
41  sp.find_file(fMVAShowerParentWeights, fullFileSpec);
42  if (fullFileSpec == "") {
43  tcc.showerParentReader = 0;
44  return;
45  }
46  tcc.showerParentVars.resize(9);
47  tcc.showerParentReader->AddVariable("fShEnergy", &tcc.showerParentVars[0]);
48  tcc.showerParentReader->AddVariable("fPfpEnergy", &tcc.showerParentVars[1]);
49  tcc.showerParentReader->AddVariable("fMCSMom", &tcc.showerParentVars[2]);
50  tcc.showerParentReader->AddVariable("fPfpLen", &tcc.showerParentVars[3]);
51  tcc.showerParentReader->AddVariable("fSep", &tcc.showerParentVars[4]);
52  tcc.showerParentReader->AddVariable("fDang1", &tcc.showerParentVars[5]);
53  tcc.showerParentReader->AddVariable("fDang2", &tcc.showerParentVars[6]);
54  tcc.showerParentReader->AddVariable("fChgFrac", &tcc.showerParentVars[7]);
55  tcc.showerParentReader->AddVariable("fInShwrProb", &tcc.showerParentVars[8]);
56  tcc.showerParentReader->BookMVA("BDT", fullFileSpec);
57  } // ConfigureTMVA
58 
61  TCSlice& slc,
62  ShowerStruct3D& ss3,
63  bool prt)
64  {
65  // The shower ChgPos and Dir were found by the calling function but Dir
66  // may be inconsistent with the 2D shower directions
67  if (ss3.ID == 0) return false;
68 
69  if (prt) {
70  mf::LogVerbatim myprt("TC");
71  myprt << "Inside FSS: 3S" << ss3.ID << " ->";
72  for (auto cid : ss3.CotIDs)
73  myprt << " 2S" << cid;
74  myprt << " Vx 3V" << ss3.Vx3ID;
75  } // prt
76 
77  // find a parent Tj in the list of 2D showers that is the farthest away from the
78  // shower center
79  unsigned short useParentCID = 0;
80  float maxParentSep = 0;
81  unsigned short usePtSepCID = 0;
82  float maxPtSep = 0;
83  // assume that the 2D shower direction is consistent with the 3D shower direction. This
84  // variable is only used when no parent exists
85  bool dirOK = true;
86  for (auto cid : ss3.CotIDs) {
87  auto& ss = slc.cots[cid - 1];
88  // Find the position, direction and projection in this plane
89  auto& stj = slc.tjs[ss.ShowerTjID - 1];
90  auto chgCtrTP = MakeBareTP(detProp, ss3.ChgPos, ss3.Dir, stj.CTP);
91  // projection too small in this view?
92  if (chgCtrTP.Delta < 0.5) continue;
93  auto& startTP = stj.Pts[0];
94  float sep = PosSep(startTP.Pos, chgCtrTP.Pos);
95  if (ss.ParentID > 0) {
96  if (sep > maxParentSep) {
97  maxParentSep = sep;
98  useParentCID = cid;
99  }
100  }
101  else if (sep > maxPtSep) {
102  // no parent exists.
103  maxPtSep = sep;
104  usePtSepCID = cid;
105  float costh = DotProd(chgCtrTP.Dir, startTP.Dir);
106  if (costh < 0) dirOK = false;
107  }
108  } // ci
109  if (useParentCID == 0 && usePtSepCID == 0) return false;
110 
111  unsigned short useCID = useParentCID;
112  if (useCID == 0) {
113  useCID = usePtSepCID;
114  if (!dirOK) ReverseShower("FSS", slc, useCID, prt);
115  }
116 
117  // now define the start and length
118  auto& ss = slc.cots[useCID - 1];
119  auto& stj = slc.tjs[ss.ShowerTjID - 1];
120 
121  auto chgCtrTP = MakeBareTP(detProp, ss3.ChgPos, ss3.Dir, stj.CTP);
122  if (ss3.Vx3ID > 0) {
123  auto& vx3 = slc.vtx3s[ss3.Vx3ID - 1];
124  ss3.Start[0] = vx3.X;
125  ss3.Start[1] = vx3.Y;
126  ss3.Start[2] = vx3.Z;
127  }
128  else {
129  // no start vertex
130  auto& startTP = stj.Pts[0];
131  // The 2D separation btw the shower start and the shower center, converted
132  // to the 3D separation
133  float sep = tcc.wirePitch * PosSep(startTP.Pos, stj.Pts[1].Pos) / chgCtrTP.Delta;
134  // set the start position
135  for (unsigned short xyz = 0; xyz < 3; ++xyz)
136  ss3.Start[xyz] = ss3.ChgPos[xyz] - sep * ss3.Dir[xyz];
137  }
138  // now do the end position
139  auto& endTP = stj.Pts[2];
140  float sep = tcc.wirePitch * PosSep(endTP.Pos, chgCtrTP.Pos) / chgCtrTP.Delta;
141  for (unsigned short xyz = 0; xyz < 3; ++xyz)
142  ss3.End[xyz] = ss3.ChgPos[xyz] + sep * ss3.Dir[xyz];
143  ss3.Len = PosSep(ss3.Start, ss3.End);
144  auto& startTP = stj.Pts[0];
145  sep = PosSep(startTP.Pos, endTP.Pos);
146  ss3.OpenAngle = (endTP.DeltaRMS - startTP.DeltaRMS) / sep;
147  ss3.OpenAngle /= chgCtrTP.Delta;
148  return true;
149 
150  } // FindShowerStart
151 
154  {
155  // Finish defining the showers, create a companion PFParticle for each one.
156  // Note to the reader: This code doesn't use MakeVertexObsolete to kill vertices using the assumption
157  // that Finish3DShowers is being called after reconstruction is complete, in which case there is no
158  // need to re-calculate the 2D and 3D vertex score which could potentially screw up the decisions that have
159  // already been made.
160 
161  // See if any need to be finished
162  bool noShowers = true;
163  for (auto& ss3 : slc.showers) {
164  if (ss3.ID == 0) continue;
165  noShowers = false;
166  }
167  if (noShowers) return;
168 
169  ChkAssns("Fin3D", slc);
170 
171  // create a pfp and define the mother-daughter relationship. At this point, the shower parent PFP (if
172  // one exists) is a track-like pfp that might be the daughter of another pfp, e.g. the neutrino. This
173  // association is changed from shower ParentID -> parent pfp, to shower PFP -> parent pfp
174  for (auto& ss3 : slc.showers) {
175  if (ss3.ID == 0) continue;
176  if (ss3.PFPIndex != USHRT_MAX) continue;
177  auto showerPFP = CreatePFP(slc);
178  showerPFP.TjIDs.resize(ss3.CotIDs.size());
179  for (unsigned short ii = 0; ii < ss3.CotIDs.size(); ++ii) {
180  unsigned short cid = ss3.CotIDs[ii];
181  if (cid == 0 || cid > slc.cots.size()) return;
182  auto& ss = slc.cots[cid - 1];
183  auto& stj = slc.tjs[ss.ShowerTjID - 1];
184  showerPFP.TjIDs[ii] = stj.ID;
185  } // ci
186  showerPFP.PDGCode = 1111;
187  auto& sf = showerPFP.SectionFits[0];
188  sf.Pos = ss3.Start;
189  sf.Dir = ss3.Dir;
190  sf.DirErr = ss3.DirErr;
191  showerPFP.Vx3ID[0] = ss3.Vx3ID;
192  sf.Dir = ss3.Dir;
193  // dEdx is indexed by plane for pfps and by 2D shower index for 3D showers
194  for (auto cid : ss3.CotIDs) {
195  auto& ss = slc.cots[cid - 1];
196  unsigned short plane = DecodeCTP(ss.CTP).Plane;
197  auto& stj = slc.tjs[ss.ShowerTjID - 1];
198  showerPFP.dEdx[0][plane] = stj.dEdx[0];
199  showerPFP.dEdxErr[0][plane] = 0.3 * stj.dEdx[0];
200  } // ci
201  ss3.PFPIndex = slc.pfps.size();
202  if (ss3.ParentID > 0) {
203  // see if this is a daughter
204  auto& dtrPFP = slc.pfps[ss3.ParentID - 1];
205  if (dtrPFP.ParentUID > 0) {
206  // Transfer the daughter <-> parent assn
207  auto slcIndx = GetSliceIndex("P", dtrPFP.ParentUID);
208  auto& parPFP = slices[slcIndx.first].pfps[slcIndx.second];
209  showerPFP.ParentUID = parPFP.UID;
210  std::replace(parPFP.DtrUIDs.begin(), parPFP.DtrUIDs.end(), dtrPFP.UID, showerPFP.UID);
211  dtrPFP.ParentUID = 0;
212  } // dtrPFP.ParentID > 0
213  } // ss3.ParentID > 0
214  slc.pfps.push_back(showerPFP);
215  } // ss3
216 
217  // Transfer Tj hits from InShower Tjs to the shower Tj. This kills the InShower Tjs but doesn't consider how
218  // this action affects vertices
219  if (!TransferTjHits(slc, false)) return;
220 
221  // Associate shower Tj hits with 3D showers
222  for (auto& ss3 : slc.showers) {
223  if (ss3.ID == 0) continue;
224  for (unsigned short ii = 0; ii < ss3.CotIDs.size(); ++ii) {
225  unsigned short cid = ss3.CotIDs[ii];
226  auto& ss = slc.cots[cid - 1];
227  for (auto tjid : ss.TjIDs) {
228  Trajectory& tj = slc.tjs[tjid - 1];
229  auto tjHits = PutTrajHitsInVector(tj, kUsedHits);
230  ss3.Hits.insert(ss3.Hits.end(), tjHits.begin(), tjHits.end());
231  // kill vertices associated with the Tj unless it is the neutrino primary vtx
232  for (unsigned short end = 0; end < 2; ++end) {
233  if (tj.VtxID[end] == 0) continue;
234  auto& vx2 = slc.vtxs[tj.VtxID[end] - 1];
235  if (vx2.Vx3ID <= 0) {
236  // This is a 2D vertex that is not matched in 3D. Kill it. Shouldn't need to
237  // use MakeVertexObsolete here...
238  vx2.ID = 0;
239  continue;
240  }
241  // vx2 is matched in 3D. Kill it if it is NOT the neutrino primary
242  auto& vx3 = slc.vtx3s[vx2.Vx3ID - 1];
243  if (vx3.Neutrino) continue;
244  vx3.ID = 0;
245  } // end
246  } // tjid
247  } // ii
248  } // ss3
249 
250  // kill PFParticles
251  if (!slc.pfps.empty()) {
252  for (auto& pfp : slc.pfps) {
253  if (pfp.ID == 0) continue;
254  if (pfp.TjIDs.empty()) continue;
255  unsigned short ndead = 0;
256  for (auto tjid : pfp.TjIDs) {
257  auto& tj = slc.tjs[tjid - 1];
258  if (tj.AlgMod[kKilled]) ++ndead;
259  } // tjid
260  if (ndead == 0) continue;
261  pfp.ID = 0;
262  } // pfp
263  } // pfps not empty
264 
265  // kill orphan 2D vertices
266  for (auto& vx2 : slc.vtxs) {
267  if (vx2.ID == 0) continue;
268  auto vxtjs = GetAssns(slc, "2V", vx2.ID, "T");
269  if (vxtjs.empty()) vx2.ID = 0;
270  } // vx2
271 
272  // kill orphan vertices
273  for (auto& vx3 : slc.vtx3s) {
274  if (vx3.ID == 0) continue;
275  auto vxtjs = GetAssns(slc, "3V", vx3.ID, "T");
276  if (vxtjs.empty()) {
277  vx3.ID = 0;
278  continue;
279  }
280  } // vx3
281 
282  } // Finish3DShowers
283 
286  {
287  // Find 2D showers using 3D-matched trajectories. This returns true if showers were found
288  // which requires re-doing the 3D trajectory match
289 
290  bool reconstruct = (tcc.showerTag[0] == 2) || (tcc.showerTag[0] == 4);
291  if (!reconstruct) return false;
292 
293  bool prt2S = (tcc.dbgSlc && tcc.dbg2S);
294  bool prt3S = (tcc.dbgSlc && tcc.dbg3S);
295 
296  std::string fcnLabel = "FS";
297 
298  geo::TPCGeo const& TPC = tcc.geom->TPC(slc.TPCID);
299  // check for already-existing showers
300  for (unsigned short plane = 0; plane < TPC.Nplanes(); ++plane) {
301  CTP_t inCTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, plane);
302  for (auto& ss : slc.cots)
303  if (ss.CTP == inCTP) return false;
304  }
305 
306  if (prt2S) {
307  PrintPFPs("FSi", slc);
308  PrintAllTraj(detProp, "FSi", slc, USHRT_MAX, 0);
309  }
310 
311  // lists of Tj IDs in plane, (list1, list2, list3, ...)
312  std::vector<std::vector<std::vector<int>>> bigList(slc.nPlanes);
313  for (unsigned short plane = 0; plane < TPC.Nplanes(); ++plane) {
314  std::vector<std::vector<int>> tjList;
315  // Make lists of lists of ShowerLike tjs that will become showers
316  // FindCots(fcnLabel, slc, inCTP, tjList, prt2S);
317  SaveTjInfo(slc, tjList, "TJL");
318  if (tjList.empty()) continue;
319  bigList[plane] = tjList;
320  } // plane
321  unsigned short nPlanesWithShowers = 0;
322  for (unsigned short plane = 0; plane < TPC.Nplanes(); ++plane)
323  if (!bigList.empty()) ++nPlanesWithShowers;
324  if (nPlanesWithShowers < 2) return false;
325  for (unsigned short plane = 0; plane < TPC.Nplanes(); ++plane) {
326  CTP_t inCTP = EncodeCTP(slc.TPCID.Cryostat, slc.TPCID.TPC, plane);
327  // print detailed debug info for one plane
328  bool prtCTP = (prt2S && inCTP == debug.CTP);
329  // Create a shower for each one
330  for (auto& tjl : bigList[plane]) {
331  if (tjl.empty()) continue;
332  if (prtCTP) {
333  mf::LogVerbatim myprt("TC");
334  myprt << "Plane " << plane << " tjList";
335  for (auto& tjID : tjl)
336  myprt << " " << tjID;
337  }
338  auto ss = CreateSS(slc, tjl);
339  if (ss.ID == 0) continue;
340  if (!UpdateShower(fcnLabel, slc, ss, prtCTP)) continue;
341  SaveTjInfo(slc, ss, "DS");
342  FindNearbyTjs(fcnLabel, slc, ss, prtCTP);
343  // don't try to do anything else here until all of the showers are defined
344  if (!StoreShower(fcnLabel, slc, ss)) MakeShowerObsolete(fcnLabel, slc, ss, prtCTP);
345  } // tjl
346  ChkAssns(fcnLabel, slc);
347  // try to merge showers in this plane using the lists of nearby Tjs
348  if (inCTP == UINT_MAX) continue;
349  if (slc.cots.empty()) continue;
350  if (prtCTP) Print2DShowers("tjl", slc, inCTP, false);
351  MergeShowerChain(fcnLabel, slc, inCTP, prtCTP);
352  SaveAllCots(slc, inCTP, "MSC");
353  if (prtCTP) Print2DShowers("MSCo", slc, inCTP, false);
354  MergeOverlap(fcnLabel, slc, inCTP, prtCTP);
355  SaveAllCots(slc, inCTP, "MO");
356  if (prtCTP) Print2DShowers("MO", slc, inCTP, false);
357  MergeNearby2DShowers(fcnLabel, slc, inCTP, prtCTP);
358  SaveAllCots(slc, inCTP, "MSS");
359  // merge sub-showers with other showers
360  MergeSubShowers(fcnLabel, slc, inCTP, prtCTP);
361  // merge sub-showers with shower-like tjs
362  MergeSubShowersTj(fcnLabel, slc, inCTP, prtCTP);
363  SaveAllCots(slc, inCTP, "MNrby");
364  if (prtCTP) Print2DShowers("Nrby", slc, inCTP, false);
365  bool tryMerge = false;
366  for (unsigned short ii = 0; ii < slc.cots.size(); ++ii) {
367  auto& ss = slc.cots[ii];
368  if (ss.ID == 0) continue;
369  if (ss.CTP != inCTP) continue;
370  if (AddTjsInsideEnvelope(fcnLabel, slc, ss, prtCTP)) tryMerge = true;
371  if (tcc.modes[kSaveShowerTree]) SaveAllCots(slc, inCTP, "Merge");
372  }
373  if (tryMerge) MergeNearby2DShowers(fcnLabel, slc, inCTP, prtCTP);
374  SaveAllCots(slc, inCTP, "ATj2");
375  if (prtCTP) Print2DShowers("ATIE", slc, inCTP, false);
376  } // plane
377  if (slc.cots.empty()) return false;
378  if (prt3S) Print2DShowers("B4", slc, USHRT_MAX, false);
379  // Match in 3D, make 3D showers and define them
380  // Match2DShowers(fcnLabel, slc, prt3S);
381  SaveAllCots(slc, "M2DS");
382  // Reconcile pfp and shower assns before the Parent search
383  Reconcile3D(fcnLabel, slc, false, prt3S);
384  SaveAllCots(slc, "R3D");
385  for (auto& ss3 : slc.showers) {
386  if (ss3.ID == 0) continue;
387  FindParent(detProp, fcnLabel, slc, ss3, prt3S);
388  } // ss3
389  // Reconcile pfp and shower assns again
390  Reconcile3D(fcnLabel, slc, true, prt3S);
391  if (prt3S) Print2DShowers("M2DS", slc, USHRT_MAX, false);
392  SaveAllCots(slc, "FP");
393 
394  // kill low energy 3D showers
395  for (auto& ss3 : slc.showers) {
396  if (ss3.ID == 0) continue;
397  bool killMe = (ShowerEnergy(ss3) < tcc.showerTag[3]);
398  if (killMe) MakeShowerObsolete(fcnLabel, slc, ss3, prt3S);
399  } // ss3
400 
401  // kill 2D showers that are either below threshold or have only one tj
402  for (auto& ss : slc.cots) {
403  if (ss.ID == 0) continue;
404  if (ss.SS3ID > 0) continue;
405  bool killMe = (ss.TjIDs.size() == 1 || ss.Energy < tcc.showerTag[3]);
406  // too small aspect ratio -> something track-like with some shower-like fuzz
407  if (ss.AspectRatio < tcc.showerTag[10]) killMe = true;
408  if (killMe) MakeShowerObsolete(fcnLabel, slc, ss, prt3S);
409  } // ss
410 
411  unsigned short nNewShowers = 0;
412  for (auto& ss : slc.cots) {
413  if (ss.ID == 0) continue;
414  if (ss.TjIDs.empty()) continue;
415  SaveTjInfo(slc, ss, "Done");
416  ++nNewShowers;
417  } // ss
418 
419  return (nNewShowers > 0);
420 
421  } // FindShowers3D
422 
424  bool Reconcile3D(std::string inFcnLabel, TCSlice& slc, bool parentSearchDone, bool prt)
425  {
426  // Reconcile pfp and shower assns
427 
428  std::string fcnLabel = inFcnLabel + ".R3D2";
429 
430  if (prt) Print2DShowers("R3D2i", slc, USHRT_MAX, false);
431 
432  // consider them pair-wise
433  if (slc.showers.size() > 1) {
434  for (unsigned short ii = 0; ii < slc.showers.size() - 1; ++ii) {
435  auto iss3 = slc.showers[ii];
436  if (iss3.ID == 0) continue;
437  auto iPInSS3 = GetAssns(slc, "3S", iss3.ID, "P");
438  if (prt) {
439  mf::LogVerbatim myprt("TC");
440  myprt << fcnLabel << " 3S" << iss3.ID << " ->";
441  for (auto pid : iPInSS3)
442  myprt << " P" << pid;
443  } // prt
444  for (unsigned short jj = ii + 1; jj < slc.showers.size(); ++jj) {
445  auto jss3 = slc.showers[jj];
446  if (jss3.ID == 0) continue;
447  auto jPInSS3 = GetAssns(slc, "3S", jss3.ID, "P");
448  auto shared = SetIntersection(iPInSS3, jPInSS3);
449  if (shared.empty()) continue;
450  if (prt) {
451  mf::LogVerbatim myprt("TC");
452  myprt << fcnLabel << " Conflict i3S" << iss3.ID << " and j3S" << jss3.ID << " share";
453  for (auto pid : shared)
454  myprt << " P" << pid;
455  } // prt
456  // Compare the InShower likelihoods
457  for (auto pid : shared) {
458  auto& pfp = slc.pfps[pid - 1];
459  float iProb = InShowerProb(slc, iss3, pfp);
460  float jProb = InShowerProb(slc, jss3, pfp);
461  if (prt)
462  mf::LogVerbatim("TC")
463  << fcnLabel << " i3S" << iss3.ID << " prob " << std::setprecision(3) << iProb
464  << " j3S" << jss3.ID << " prob " << jProb;
465  if (iProb > jProb) {
466  // remove the remnants of pfp from jss3
467  RemovePFP(fcnLabel, slc, pfp, jss3, true, prt);
468  // and add them to iss3
469  AddPFP(fcnLabel, slc, pfp.ID, iss3, true, prt);
470  }
471  else {
472  RemovePFP(fcnLabel, slc, pfp, iss3, true, prt);
473  AddPFP(fcnLabel, slc, pfp.ID, jss3, true, prt);
474  }
475  } // pid
476  } // jj
477  } // ii
478  } // > 1 shower
479 
480  // Look for an in-shower pfp that is not the shower parent that is attached to a vertex.
481  // Remove the attachment and any parent - daughter assn
482  if (parentSearchDone) {
483  for (auto& ss3 : slc.showers) {
484  if (ss3.ID == 0) continue;
485  auto PIn3S = GetAssns(slc, "3S", ss3.ID, "P");
486  for (auto pid : PIn3S) {
487  if (pid == ss3.ParentID) continue;
488  auto& pfp = slc.pfps[pid - 1];
489  for (unsigned short end = 0; end < 2; ++end) {
490  if (pfp.Vx3ID[end] <= 0) continue;
491  if (prt) {
492  mf::LogVerbatim myprt("TC");
493  myprt << fcnLabel << " Detach 3S" << ss3.ID << " -> P" << pfp.ID << "_" << end
494  << " -> 3V" << pfp.Vx3ID[end];
495  if (pfp.ParentUID > 0) myprt << " ->Parent P" << pfp.ParentUID;
496  }
497  // remove P -> P parent-daughter assn
498  pfp.Vx3ID[end] = 0;
499  if (pfp.ParentUID > 0) {
500  auto slcIndx = GetSliceIndex("P", pfp.ParentUID);
501  auto& parentPFP = slices[slcIndx.first].pfps[slcIndx.second];
502  std::vector<int> newDtrUIDs;
503  for (auto did : parentPFP.DtrUIDs)
504  if (did != pfp.UID) newDtrUIDs.push_back(did);
505  parentPFP.DtrUIDs = newDtrUIDs;
506  } // pfp Parent exists
507  } // end
508  } // pid
509  } // ss3
510  } // parentSearchDone
511 
512  // now look for 2D showers that not matched in 3D and have tjs
513  // that are 3D-matched
514  for (auto& ss : slc.cots) {
515  if (ss.ID == 0) continue;
516  if (ss.SS3ID > 0) continue;
517  std::vector<int> matchedTjs;
518  for (auto tid : ss.TjIDs)
519  if (slc.tjs[tid - 1].AlgMod[kMat3D]) matchedTjs.push_back(tid);
520  if (matchedTjs.empty()) continue;
521  // try to merge it with an existing 3D-matched shower
522  int mergeWith3S = 0;
523  // The merge is compatible if it only matches to one shower
524  bool isCompatible = true;
525  for (auto tid : matchedTjs) {
526  auto TInP = GetAssns(slc, "T", tid, "P");
527  if (TInP.empty()) continue;
528  // do some more checking. See what other showers the Tjs in the pfp
529  // may belong to in other planes
530  auto PIn3S = GetAssns(slc, "P", TInP[0], "3S");
531  for (auto sid : PIn3S) {
532  // require that the energy be lower
533  auto& ss3 = slc.showers[sid - 1];
534  if (ss.Energy > ShowerEnergy(ss3)) continue;
535  if (mergeWith3S == 0) mergeWith3S = sid;
536  if (mergeWith3S > 0 && mergeWith3S != sid) isCompatible = false;
537  } // sid
538  } // tid
539  if (prt) {
540  mf::LogVerbatim myprt("TC");
541  myprt << fcnLabel << " 2S" << ss.ID << " is not 3D-matched but has 3D-matched Tjs:";
542  for (auto tid : matchedTjs) {
543  myprt << " T" << tid;
544  auto TInP = GetAssns(slc, "T", tid, "P");
545  if (!TInP.empty()) { myprt << "->P" << TInP[0]; } // TInP not empty
546  } // tid
547  } // prt
548  if (mergeWith3S == 0 && ss.Energy < 50) {
549  // kill it
550  MakeShowerObsolete(fcnLabel, slc, ss, prt);
551  }
552  else if (mergeWith3S > 0 && isCompatible) {
553  auto& ss3 = slc.showers[mergeWith3S - 1];
554  for (auto cid : ss3.CotIDs) {
555  auto& oss = slc.cots[cid - 1];
556  if (oss.CTP != ss.CTP) continue;
557  if (!UpdateShower(fcnLabel, slc, ss3, prt)) return false;
558  break;
559  } // cid
560  } // mergeWith3S > 0
561  } // ss
562 
563  if (prt) Print2DShowers("R3D2o", slc, USHRT_MAX, false);
564 
565  ChkAssns(fcnLabel, slc);
566 
567  return true;
568  } // Reconcile3D
569 
571  bool Reconcile3D(std::string inFcnLabel, TCSlice& slc, ShowerStruct3D& ss3, bool prt)
572  {
573  // checks consistency between pfparticles, showers and tjs associated with ss3
574  if (ss3.ID == 0) return false;
575  // it isn't a failure if there is a 3D shower in two planes
576  if (ss3.CotIDs.size() < 3) return true;
577  std::string fcnLabel = inFcnLabel + ".R3D";
578 
579  if (prt) Print2DShowers("R3Di", slc, USHRT_MAX, false);
580 
581  // make local copies so we can recover from a failure
582  auto oldSS3 = ss3;
583  std::vector<ShowerStruct> oldSS(ss3.CotIDs.size());
584  for (unsigned short ii = 0; ii < ss3.CotIDs.size(); ++ii) {
585  oldSS[ii] = slc.cots[ss3.CotIDs[ii] - 1];
586  }
587 
588  std::vector<std::vector<int>> plist(ss3.CotIDs.size());
589  for (unsigned short ci = 0; ci < ss3.CotIDs.size(); ++ci) {
590  auto& ss = slc.cots[ss3.CotIDs[ci] - 1];
591  for (auto tid : ss.TjIDs) {
592  auto tToP = GetAssns(slc, "T", tid, "P");
593  if (tToP.empty()) continue;
594  // there should only be one pfp for a tj
595  int pid = tToP[0];
596  if (std::find(plist[ci].begin(), plist[ci].end(), pid) == plist[ci].end())
597  plist[ci].push_back(pid);
598  } // tid
599  } // ci
600  // count the occurrence of each pfp
601  std::vector<std::array<int, 2>> p_cnt;
602  for (auto& pl : plist) {
603  for (auto pid : pl) {
604  unsigned short indx = 0;
605  for (indx = 0; indx < p_cnt.size(); ++indx)
606  if (p_cnt[indx][0] == pid) break;
607  if (indx == p_cnt.size()) {
608  // not found so add it
609  p_cnt.push_back(std::array<int, 2>{{pid, 1}});
610  }
611  else {
612  ++p_cnt[indx][1];
613  }
614  } // pid
615  } // pl
616  if (prt) {
617  mf::LogVerbatim myprt("TC");
618  myprt << fcnLabel << " 3S" << ss3.ID << "\n";
619  for (unsigned short ci = 0; ci < ss3.CotIDs.size(); ++ci) {
620  myprt << " -> 2S" << ss3.CotIDs[ci] << " ->";
621  for (auto pid : plist[ci])
622  myprt << " P" << pid;
623  myprt << "\n";
624  } // ci
625  myprt << " P<ID>_count:";
626  for (auto& pc : p_cnt)
627  myprt << " P" << pc[0] << "_" << pc[1];
628  } // prt
629 
630  for (auto& pc : p_cnt) {
631  // matched in all planes?
632  if (pc[1] == (int)ss3.CotIDs.size()) continue;
633  if (pc[1] == 2) {
634  // missing a tj in a plane or is this a two-plane pfp?
635  auto& pfp = slc.pfps[pc[0] - 1];
636  if (pfp.TjIDs.size() > 2) {
637  // ensure that none of the tjs in this pfp are included in a different shower
638  auto PIn2S = GetAssns(slc, "P", pfp.ID, "2S");
639  auto sDiff = SetDifference(PIn2S, ss3.CotIDs);
640  if (!sDiff.empty() &&
641  std::find(ss3.CotIDs.begin(), ss3.CotIDs.end(), sDiff[0]) == ss3.CotIDs.end())
642  continue;
643  if (prt) {
644  mf::LogVerbatim myprt("TC");
645  myprt << fcnLabel << " 3S" << ss3.ID << " P" << pfp.ID << " ->";
646  for (auto sid : PIn2S)
647  myprt << " 2S" << sid;
648  myprt << " sDiff";
649  for (auto sid : sDiff)
650  myprt << " 2S" << sid;
651  } // prt
652  // missed a tj in a 2D shower so "add the PFP to the shower" and update it
653  if (AddPFP(fcnLabel, slc, pfp.ID, ss3, true, prt)) {
654  // Update the local copies
655  oldSS3 = ss3;
656  if (ss3.CotIDs.size() != oldSS.size()) return false;
657  for (unsigned short ii = 0; ii < ss3.CotIDs.size(); ++ii)
658  oldSS[ii] = slc.cots[ss3.CotIDs[ii] - 1];
659  }
660  else {
661  // restore the previous state
662  ss3 = oldSS3;
663  for (unsigned short ii = 0; ii < oldSS.size(); ++ii) {
664  auto& ss = oldSS[ii];
665  slc.cots[ss.ID - 1] = ss;
666  } // ii
667  } // AddPFP failed
668  } // pfp.TjIDs.size() > 2
669  }
670  else {
671  // only one occurrence. check proximity to ss3
672  auto& pfp = slc.pfps[pc[0] - 1];
673  unsigned short nearEnd = 1 - FarEnd(pfp, ss3.ChgPos);
674  float prob = InShowerProb(slc, ss3, pfp);
675  auto pos = PosAtEnd(pfp, nearEnd);
676  float sep = PosSep(pos, ss3.ChgPos);
677  if (prt) {
678  mf::LogVerbatim myprt("TC");
679  myprt << fcnLabel << " one occurrence: P" << pfp.ID << "_" << nearEnd
680  << " closest to ChgPos";
681  myprt << " ChgPos " << std::fixed << std::setprecision(1) << ss3.ChgPos[0] << " "
682  << ss3.ChgPos[1] << " " << ss3.ChgPos[2];
683  myprt << " sep " << sep;
684  myprt << " InShowerProb " << prob;
685  } // prt
686  if (sep < 30 && prob > 0.3 && AddPFP(fcnLabel, slc, pfp.ID, ss3, true, prt)) {
687  if (prt) mf::LogVerbatim("TC") << " AddPFP success";
688  }
689  else if (!RemovePFP(fcnLabel, slc, pfp, ss3, true, prt)) {
690  if (prt) mf::LogVerbatim("TC") << " RemovePFP failed";
691  }
692  } // only one occurrence.
693  } // pc
694 
695  if (!UpdateShower(fcnLabel, slc, ss3, prt)) return false;
696  ChkAssns(fcnLabel, slc);
697  if (prt) Print2DShowers("R3Do", slc, USHRT_MAX, false);
698 
699  return true;
700 
701  } // Reconcile3D
702 
704  void KillVerticesInShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
705  {
706  // make the vertices inside the shower envelope obsolete and update dontCluster
707  if (ss.ID == 0) return;
708  if (!tcc.useAlg[kKillInShowerVx]) return;
709  std::string fcnLabel = inFcnLabel + ".KVIS";
710 
711  for (auto& vx2 : slc.vtxs) {
712  if (vx2.ID == 0) continue;
713  if (vx2.CTP != ss.CTP) continue;
714  // ensure it isn't associated with a neutrino vertex
715  if (vx2.Vx3ID > 0 && slc.vtx3s[vx2.Vx3ID - 1].Neutrino) continue;
716  if (!PointInsideEnvelope(vx2.Pos, ss.Envelope)) continue;
717  if (prt)
718  mf::LogVerbatim("TC") << fcnLabel << " Clobber 2V" << vx2.ID << " -> 3V" << vx2.Vx3ID
719  << " inside 2S" << ss.ID;
720  // update dontCluster
721  for (auto& dc : slc.dontCluster) {
722  if (dc.TjIDs[0] == 0) continue;
723  if (dc.Vx2ID != vx2.ID) continue;
724  if (prt)
725  mf::LogVerbatim("TC") << fcnLabel << " Remove T" << dc.TjIDs[0] << "-T" << dc.TjIDs[0]
726  << " in dontCluster";
727  dc.TjIDs[0] = 0;
728  dc.TjIDs[1] = 0;
729  } // dc
730  if (vx2.Vx3ID > 0) {
731  auto TIn3V = GetAssns(slc, "3V", vx2.Vx3ID, "T");
732  for (auto tid : TIn3V)
733  slc.tjs[tid - 1].AlgMod[kKillInShowerVx] = true;
734  auto& vx3 = slc.vtx3s[vx2.Vx3ID - 1];
735  MakeVertexObsolete(slc, vx3);
736  }
737  else {
738  auto TIn2V = GetAssns(slc, "2V", vx2.ID, "T");
739  for (auto tid : TIn2V)
740  slc.tjs[tid - 1].AlgMod[kKillInShowerVx] = true;
741  MakeVertexObsolete("KVIS", slc, vx2, true);
742  }
743  } // vx2
744 
745  } // KillVerticesInShower
746 
748  bool CompleteIncompleteShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct3D& ss3, bool prt)
749  {
750  // Find low-energy two-plane showers and try to complete it by making a 2D shower in the third
751  // plane using 3D matched tjs
752 
753  if (slc.nPlanes != 3) return false;
754  if (ss3.CotIDs.size() != 2) return false;
755 
756  if (!tcc.useAlg[kCompleteShower]) return false;
757 
758  std::string fcnLabel = inFcnLabel + ".CIS";
759  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID;
760 
761  auto& iss = slc.cots[ss3.CotIDs[0] - 1];
762  auto& jss = slc.cots[ss3.CotIDs[1] - 1];
763  // make a list of pfps for each SS
764  std::vector<int> iplist;
765  for (auto tid : iss.TjIDs) {
766  auto plist = GetAssns(slc, "T", tid, "P");
767  if (!plist.empty()) iplist.insert(iplist.end(), plist.begin(), plist.end());
768  } // tid
769  std::vector<int> jplist;
770  for (auto tid : jss.TjIDs) {
771  auto plist = GetAssns(slc, "T", tid, "P");
772  if (!plist.empty()) jplist.insert(jplist.end(), plist.begin(), plist.end());
773  } // tid
774  // look for pfps that have tjs in both showers
775  auto shared = SetIntersection(iplist, jplist);
776  if (shared.empty()) return false;
777  // put the list of tjs for both SS into a flat vector to simplify searching
778  std::vector<int> flat = iss.TjIDs;
779  flat.insert(flat.end(), jss.TjIDs.begin(), jss.TjIDs.end());
780  // make a list of tjs in the k plane that maybe should made into a shower if they
781  // aren't already in a shower that failed the 3D match
782  std::vector<int> ktlist;
783  for (auto pid : shared) {
784  auto& pfp = slc.pfps[pid - 1];
785  for (auto tid : pfp.TjIDs) {
786  // ignore the tjs that are already in the shower in the other planes
787  if (std::find(flat.begin(), flat.end(), tid) != flat.end()) continue;
788  if (std::find(ktlist.begin(), ktlist.end(), tid) == ktlist.end()) ktlist.push_back(tid);
789  // look for 2D vertices attached to this tj and add all attached tjs to ktlist
790  auto& tj = slc.tjs[tid - 1];
791  for (unsigned short end = 0; end < 2; ++end) {
792  if (tj.VtxID[end] <= 0) continue;
793  auto& vx2 = slc.vtxs[tj.VtxID[end] - 1];
794  auto TIn2V = GetAssns(slc, "2V", vx2.ID, "T");
795  for (auto vtid : TIn2V) {
796  if (std::find(ktlist.begin(), ktlist.end(), vtid) == ktlist.end())
797  ktlist.push_back(vtid);
798  }
799  } // end
800  } // tid
801  } // pid
802  if (ktlist.empty()) return false;
803  // list of 2D showers that include tjs in ktlist
804  std::vector<int> ksslist;
805  for (auto tid : ktlist) {
806  auto& tj = slc.tjs[tid - 1];
807  if (tj.SSID == 0) continue;
808  // ignore showers that are 3D-matched. This case should be handled elsewhere by a merging function
809  auto& ss = slc.cots[tj.SSID - 1];
810  if (ss.SS3ID > 0) {
811  if (prt)
812  mf::LogVerbatim("TC") << fcnLabel << " Found existing T" << tid << " -> 2S" << ss.ID
813  << " -> 3S" << ss.SS3ID << " assn. Give up";
814  return false;
815  }
816  if (std::find(ksslist.begin(), ksslist.end(), ss.ID) == ksslist.end())
817  ksslist.push_back(ss.ID);
818  } // tid
819  // find the shower energy for this list
820  float ktlistEnergy = ShowerEnergy(slc, ktlist);
821  if (prt) {
822  mf::LogVerbatim myprt("TC");
823  myprt << fcnLabel << " 3S" << ss3.ID << "\n";
824  myprt << " -> i2S" << iss.ID << " ->";
825  for (auto pid : iplist)
826  myprt << " P" << pid;
827  myprt << "\n";
828  myprt << " -> j2S" << jss.ID << " ->";
829  for (auto pid : jplist)
830  myprt << " P" << pid;
831  myprt << "\n";
832  geo::PlaneID iPlaneID = DecodeCTP(iss.CTP);
833  geo::PlaneID jPlaneID = DecodeCTP(jss.CTP);
834  unsigned short kplane = 3 - iPlaneID.Plane - jPlaneID.Plane;
835  myprt << " kplane " << kplane << " ktlist:";
836  for (auto tid : ktlist)
837  myprt << " T" << tid;
838  myprt << " ktlistEnergy " << ktlistEnergy;
839  if (ksslist.empty()) { myprt << "\n No matching showers in kplane"; }
840  else {
841  myprt << "\n";
842  myprt << " Candidate showers:";
843  for (auto ssid : ksslist) {
844  myprt << " 2S" << ssid;
845  auto& sst = slc.cots[ssid - 1];
846  if (sst.SS3ID > 0) myprt << "_3S" << sst.SS3ID;
847  } // ssid
848  } // ssList not empty
849  } // prt
850  if (ksslist.size() > 1) {
851  if (prt)
852  mf::LogVerbatim("TC") << fcnLabel
853  << " Found more than 1 shower. Need some better code here";
854  return false;
855  }
856  if (ktlistEnergy > 2 * ShowerEnergy(ss3)) {
857  if (prt)
858  mf::LogVerbatim("TC") << fcnLabel
859  << " ktlistEnergy exceeds 2 * ss3 energy. Need some better code here";
860  return false;
861  } // ktlistEnergy too high
862 
863  if (ksslist.empty()) {
864  // no 2D shower so make one using ktlist
865  auto kss = CreateSS(slc, ktlist);
866  if (kss.ID == 0) return false;
867  kss.SS3ID = ss3.ID;
868  if (prt)
869  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " create new 2S" << kss.ID
870  << " from ktlist";
871  if (!UpdateShower(fcnLabel, slc, kss, prt)) {
872  if (prt) mf::LogVerbatim("TC") << fcnLabel << " UpdateShower failed 2S" << kss.ID;
873  MakeShowerObsolete(fcnLabel, slc, kss, prt);
874  return false;
875  } // UpdateShower failed
876  if (!StoreShower(fcnLabel, slc, kss)) {
877  if (prt) mf::LogVerbatim("TC") << fcnLabel << " StoreShower failed";
878  MakeShowerObsolete(fcnLabel, slc, kss, prt);
879  return false;
880  } // StoreShower failed
881  ss3.CotIDs.push_back(kss.ID);
882  auto& stj = slc.tjs[kss.ShowerTjID - 1];
883  stj.AlgMod[kCompleteShower] = true;
884  ss3.NeedsUpdate = true;
885  return true;
886  } // ksslist empty
887 
888  // associate ksslist[0] with 3S
889  auto& ss = slc.cots[ksslist[0] - 1];
890  if (prt)
891  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " found pfp-matched 2S" << ss.ID;
892  ss.SS3ID = ss3.ID;
893  ss3.CotIDs.push_back(ss.ID);
894  auto& stj = slc.tjs[ss.ShowerTjID - 1];
895  stj.AlgMod[kCompleteShower] = true;
896  ss3.NeedsUpdate = true;
897 
898  ChkAssns(fcnLabel, slc);
899  return true;
900 
901  } // CompleteIncompleteShower
902 
904  bool UpdateShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
905  {
906  // This is intended to be a single function replacement for FCC, FA, USWP, etc. The calling
907  // function should have set NeedsUpdate true. A complete re-build is done if the ShPts vector
908  // is empty. This is only required if a tj is removed from the shower. When adding a tj
909  // to the shower the AddTj function appends the tj points to ShPts but doesn't fill
910  // the ShPts RotPos values.
911  // This function doesn't alter or check associations btw showers and tjs.
912 
913  if (ss.ID == 0) return false;
914  if (ss.TjIDs.empty()) return false;
915  if (ss.ShowerTjID <= 0 || ss.ShowerTjID > (int)slc.tjs.size()) return false;
916  if (ss.ParentID > 0 && ss.ParentID > (int)slc.tjs.size()) return false;
917  auto& stj = slc.tjs[ss.ShowerTjID - 1];
918  if (stj.Pts.size() != 3) return false;
919 
920  std::string fcnLabel = inFcnLabel + ".U2S";
921 
922  if (!ss.NeedsUpdate && !ss.ShPts.empty()) return true;
923 
924  // initialize the variables that will be defined in this function
925  ss.Energy = 0; // This is just ShowerEnergy(stj.TotChg) and could be deleted
926  ss.AspectRatio = 10;
927  // Direction FOM (0 = good). This is a property of the shower shape and is not
928  // defined by the presence or absence of a parent tj start point
929  ss.DirectionFOM = 10;
930  // Total charge of all hits in the shower
931  stj.TotChg = 0;
932  for (auto& stp : stj.Pts) {
933  // Shower start, charge center, and shower end
934  stp.Pos = {{0.0, 0.0}};
935  // Charge weighted average of hits this section (TP) along the shower
936  stp.HitPos = {{0.0, 0.0}};
937  // Direction from the start to the charge center - same for all TPs
938  stp.Dir = {{0.0, 0.0}};
939  // Hit charge in each section
940  stp.Chg = 0;
941  // transverse rms of hit positions relative to HitPos in this section
942  stp.DeltaRMS = 0;
943  // number of hits in this section
944  stp.NTPsFit = 0;
945  } // stp
946 
947  ss.ShPts.clear();
948  for (auto tjid : ss.TjIDs) {
949  if (tjid <= 0 || tjid > (int)slc.tjs.size()) return false;
950  auto& tj = slc.tjs[tjid - 1];
951  if (tj.CTP != ss.CTP) return false;
952  if (tj.AlgMod[kShowerTj]) return false;
953  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
954  TrajPoint& tp = tj.Pts[ipt];
955  for (unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
956  if (!tp.UseHit[ii]) continue;
957  unsigned int iht = tp.Hits[ii];
958  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
959  if (hit.Integral() <= 0) continue;
960  ShowerPoint shpt;
961  shpt.HitIndex = iht;
962  shpt.TID = tj.ID;
963  shpt.Chg = hit.Integral();
964  shpt.Pos[0] = hit.WireID().Wire;
965  shpt.Pos[1] = hit.PeakTime() * tcc.unitsPerTick;
966  ss.ShPts.push_back(shpt);
967  } // ii
968  } // ipt
969  } // tjid
970  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " nShPts " << ss.ShPts.size();
971 
972  if (ss.ShPts.size() < 3) return false;
973 
974  // find the charge center and total charge
975  auto& stp1 = stj.Pts[1];
976  for (auto& shpt : ss.ShPts) {
977  stp1.Pos[0] += shpt.Chg * shpt.Pos[0];
978  stp1.Pos[1] += shpt.Chg * shpt.Pos[1];
979  stj.TotChg += shpt.Chg;
980  } // shpt
981  if (stj.TotChg <= 0) return false;
982  stp1.Pos[0] /= stj.TotChg;
983  stp1.Pos[1] /= stj.TotChg;
984  ss.Energy = ChgToMeV(stj.TotChg);
985  if (prt)
986  mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " Chg ctr " << PrintPos(stp1.Pos)
987  << " Energy " << (int)ss.Energy << " MeV";
988 
989  // find the direction using the shower parent if one exists
990  if (ss.ParentID > 0) {
991  // Set the direction to be the start of the parent to the shower center
992  auto& ptj = slc.tjs[ss.ParentID - 1];
993  // find the parent end farthest away from the charge center
994  unsigned short pend = FarEnd(ptj, stp1.Pos);
995  auto& ptp = ptj.Pts[ptj.EndPt[pend]];
996  stp1.Dir = PointDirection(ptp.Pos, stp1.Pos);
997  stp1.Ang = atan2(stp1.Dir[1], stp1.Dir[0]);
998  }
999  else {
1000  // find the shower direction using the points
1001  double sum = 0.;
1002  double sumx = 0.;
1003  double sumy = 0.;
1004  double sumxy = 0.;
1005  double sumx2 = 0.;
1006  for (auto& shpt : ss.ShPts) {
1007  sum += shpt.Chg;
1008  double xx = shpt.Pos[0] - stp1.Pos[0];
1009  double yy = shpt.Pos[1] - stp1.Pos[1];
1010  sumx += shpt.Chg * xx;
1011  sumy += shpt.Chg * yy;
1012  sumxy += shpt.Chg * xx * yy;
1013  sumx2 += shpt.Chg * xx * xx;
1014  } // shpt
1015  double delta = sum * sumx2 - sumx * sumx;
1016  if (delta == 0) return false;
1017  // A is the intercept (This should be ~0 )
1018  // double A = (sumx2 * sumy - sumx * sumxy) / delta;
1019  // B is the slope
1020  double B = (sumxy * sum - sumx * sumy) / delta;
1021  stp1.Ang = atan(B);
1022  stp1.Dir[0] = cos(stp1.Ang);
1023  stp1.Dir[1] = sin(stp1.Ang);
1024  } // no shower parent
1025 
1026  // TODO: ss.Angle should be eliminated. The shower tj Ang should be used instead
1027  ss.Angle = stp1.Ang;
1028  if (prt)
1029  mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " dir " << std::fixed
1030  << std::setprecision(2) << stp1.Dir[0] << " " << stp1.Dir[1]
1031  << " Angle " << stp1.Ang;
1032  for (unsigned short ipt = 0; ipt < 3; ++ipt) {
1033  if (ipt == 1) continue;
1034  stj.Pts[ipt].Dir = stp1.Dir;
1035  stj.Pts[ipt].Ang = stp1.Ang;
1036  } // ipt
1037 
1038  // fill the RotPos vector and sort
1039  std::vector<SortEntry> sortVec(ss.ShPts.size());
1040  unsigned short indx = 0;
1041  double cs = cos(-stp1.Ang);
1042  double sn = sin(-stp1.Ang);
1043  for (auto& shpt : ss.ShPts) {
1044  double xx = shpt.Pos[0] - stp1.Pos[0];
1045  double yy = shpt.Pos[1] - stp1.Pos[1];
1046  shpt.RotPos[0] = cs * xx - sn * yy;
1047  shpt.RotPos[1] = sn * xx + cs * yy;
1048  sortVec[indx].index = indx;
1049  sortVec[indx].val = shpt.RotPos[0];
1050  ++indx;
1051  } // shpt
1052  std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
1053  // put the points vector into the sorted order
1054  auto tPts = ss.ShPts;
1055  for (unsigned short ii = 0; ii < ss.ShPts.size(); ++ii)
1056  ss.ShPts[ii] = tPts[sortVec[ii].index];
1057 
1058  // Calculate the aspect ratio
1059  Point2_t alongTrans{{0.0, 0.0}};
1060  for (auto& shpt : ss.ShPts) {
1061  alongTrans[0] += shpt.Chg * std::abs(shpt.RotPos[0]);
1062  alongTrans[1] += shpt.Chg * std::abs(shpt.RotPos[1]);
1063  } // shpt
1064  alongTrans[0] /= stj.TotChg;
1065  alongTrans[1] /= stj.TotChg;
1066  if (alongTrans[1] == 0) return false;
1067  ss.AspectRatio = alongTrans[1] / alongTrans[0];
1068  if (prt)
1069  mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " AspectRatio " << ss.AspectRatio;
1070 
1071  // analyze the charge in three sections. Fill the stj HitPos and find DeltaRMS
1072  if (!AnalyzeRotPos(fcnLabel, slc, ss, prt)) return false;
1073 
1074  // Reverse the shower direction if needed and define the start point
1075  if (ss.ParentID > 0) {
1076  // The direction was defined by the start of a parent to the charge center. Check the consistency
1077  // with ShPts and reverse if needed
1078  auto& ptj = slc.tjs[ss.ParentID - 1];
1079  // find the parent end farthest away from the charge center
1080  unsigned short pend = FarEnd(ptj, stp1.Pos);
1081  auto& ptp = ptj.Pts[ptj.EndPt[pend]];
1082  auto& firstShPt = ss.ShPts[0];
1083  auto& lastShPt = ss.ShPts[ss.ShPts.size() - 1];
1084  if (PosSep2(ptp.Pos, lastShPt.Pos) < PosSep2(ptp.Pos, firstShPt.Pos))
1085  ReverseShower(fcnLabel, slc, ss, prt);
1086  stj.Pts[0].Pos = ptp.Pos;
1087  }
1088  else {
1089  // no parent exists. Compare the DeltaRMS at the ends
1090  if (stj.Pts[2].DeltaRMS < stj.Pts[0].DeltaRMS) ReverseShower(fcnLabel, slc, ss, prt);
1091  stj.Pts[0].Pos = ss.ShPts[0].Pos;
1092  } // no parent
1093 
1094  if (stj.Pts[2].DeltaRMS > 0) ss.DirectionFOM = stj.Pts[0].DeltaRMS / stj.Pts[2].DeltaRMS;
1095  // define the end point
1096  stj.Pts[2].Pos = ss.ShPts[ss.ShPts.size() - 1].Pos;
1097 
1098  DefineEnvelope(fcnLabel, slc, ss, prt);
1099  ss.NeedsUpdate = false;
1100  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " updated";
1101  return true;
1102 
1103  } // UpdateShower
1104 
1106  bool UpdateShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct3D& ss3, bool prt)
1107  {
1108  // Updates the 3D shower presumably because the 2D showers were changed or need to be updated.
1109  // This function returns false if there was a failure.
1110 
1111  if (ss3.ID == 0) return false;
1112  if (ss3.CotIDs.size() < 2) return false;
1113 
1114  std::string fcnLabel = inFcnLabel + ".U3S";
1115 
1116  // see if any of the 2D showers need an update
1117  for (auto cid : ss3.CotIDs) {
1118  auto& ss = slc.cots[cid - 1];
1119  if (ss.NeedsUpdate && prt)
1120  std::cout << fcnLabel << " ********* 3S" << ss3.ID << " 2S" << ss.ID
1121  << " needs an update...\n";
1122  UpdateShower(fcnLabel, slc, ss, prt);
1123  } // ci
1124 
1125  // check consistency
1126  if (ss3.ParentID > 0) {
1127  auto& pfp = slc.pfps[ss3.ParentID - 1];
1128  unsigned short pend = FarEnd(pfp, ss3.ChgPos);
1129  if (pfp.Vx3ID[pend] != ss3.Vx3ID) {
1130  if (prt)
1131  std::cout << fcnLabel << " ********* 3S" << ss3.ID << " has parent P" << ss3.ParentID
1132  << " with a vertex that is not attached the shower\n";
1133  }
1134  } // ss3.ParentID > 0
1135 
1136  // Find the average position and direction using pairs of 2D shower Tjs
1137  std::array<Point3_t, 3> pos;
1138  // the direction of all points in 2D showers is the same
1139  Vector3_t dir;
1140  std::array<double, 3> chg;
1141  for (unsigned short ipt = 0; ipt < 3; ++ipt) {
1142  chg[ipt] = 0;
1143  for (unsigned short xyz = 0; xyz < 3; ++xyz) {
1144  pos[ipt][xyz] = 0;
1145  dir[xyz] = 0;
1146  }
1147  } // ipt
1148  unsigned short nok = 0;
1149  for (unsigned short ipt = 0; ipt < 3; ++ipt) {
1150  if (chg[ipt] == 0) continue;
1151  for (unsigned short xyz = 0; xyz < 3; ++xyz)
1152  pos[ipt][xyz] /= chg[ipt];
1153  SetMag(dir, 1);
1154  ++nok;
1155  } // ipt
1156 
1157  if (nok != 3) return false;
1158  ss3.ChgPos = pos[1];
1159 
1160  if (ss3.ParentID > 0) {
1161  // There is a 3D-matched pfp at the shower start. The end that is farthest away from the
1162  // shower center should be shower start
1163  auto& pfp = slc.pfps[ss3.ParentID - 1];
1164  unsigned short pend = FarEnd(pfp, ss3.ChgPos);
1165  ss3.Start = PosAtEnd(pfp, pend);
1166  ss3.Dir = dir;
1167  }
1168  else {
1169  ss3.Dir = dir;
1170  ss3.Start = pos[0];
1171  }
1172  // define the end
1173  ss3.End = pos[2];
1174  ss3.Len = PosSep(ss3.Start, ss3.End);
1175 
1176  // dE/dx, energy, etc
1177  for (auto cid : ss3.CotIDs) {
1178  auto& ss = slc.cots[cid - 1];
1179  auto& stj = slc.tjs[ss.ShowerTjID - 1];
1180  unsigned short plane = DecodeCTP(ss.CTP).Plane;
1181  ss3.Energy[plane] = ss.Energy;
1182  // TODO: calculate the errors in some better way
1183  ss3.EnergyErr[plane] = 0.3 * ss.Energy;
1184  // TODO: what does MIPEnergy mean anyway?
1185  ss3.MIPEnergy[plane] = ss3.EnergyErr[plane];
1186  ss3.MIPEnergyErr[plane] = ss.Energy;
1187  ss3.dEdx[plane] = stj.dEdx[0];
1188  ss3.dEdxErr[plane] = 0.3 * stj.dEdx[0];
1189  } // ci
1190 
1191  ss3.NeedsUpdate = false;
1192  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " updated";
1193  return true;
1194 
1195  } // UpdateShower
1196 
1199  std::string inFcnLabel,
1200  TCSlice& slc,
1201  ShowerStruct3D& ss3,
1202  bool prt)
1203  {
1204  float fom = 0;
1205  float cnt = 0;
1206  for (unsigned short ii = 0; ii < ss3.CotIDs.size() - 1; ++ii) {
1207  unsigned short icid = ss3.CotIDs[ii];
1208  for (unsigned short jj = ii + 1; jj < ss3.CotIDs.size(); ++jj) {
1209  unsigned short jcid = ss3.CotIDs[jj];
1210  fom += Match3DFOM(detProp, inFcnLabel, slc, icid, jcid, prt);
1211  ++cnt;
1212  } // cj
1213  } // ci
1214  if (cnt == 0) return 100;
1215  return fom / cnt;
1216  } // Match3DFOM
1217 
1220  std::string inFcnLabel,
1221  TCSlice& slc,
1222  int icid,
1223  int jcid,
1224  int kcid,
1225  bool prt)
1226  {
1227  if (icid == 0 || icid > (int)slc.cots.size()) return 100;
1228  if (jcid == 0 || jcid > (int)slc.cots.size()) return 100;
1229  if (kcid == 0 || kcid > (int)slc.cots.size()) return 100;
1230 
1231  float ijfom = Match3DFOM(detProp, inFcnLabel, slc, icid, jcid, prt);
1232  float jkfom = Match3DFOM(detProp, inFcnLabel, slc, jcid, kcid, prt);
1233 
1234  return 0.5 * (ijfom + jkfom);
1235 
1236  } // Match3DFOM
1237 
1240  std::string inFcnLabel,
1241  TCSlice& slc,
1242  int icid,
1243  int jcid,
1244  bool prt)
1245  {
1246  // returns a Figure of Merit for a 3D match of two showers
1247  if (icid == 0 || icid > (int)slc.cots.size()) return 100;
1248  if (jcid == 0 || jcid > (int)slc.cots.size()) return 100;
1249 
1250  auto& iss = slc.cots[icid - 1];
1251  auto& istj = slc.tjs[iss.ShowerTjID - 1];
1252  auto& jss = slc.cots[jcid - 1];
1253  auto& jstj = slc.tjs[jss.ShowerTjID - 1];
1254 
1255  if (iss.CTP == jss.CTP) return 100;
1256 
1257  std::string fcnLabel = inFcnLabel + ".MFOM";
1258 
1259  float energyAsym = std::abs(iss.Energy - jss.Energy) / (iss.Energy + jss.Energy);
1260 
1261  // don't apply the asymmetry cut on low energy showers
1262  if ((iss.Energy > 200 || jss.Energy > 200) && energyAsym > 0.5) return 50;
1263 
1264  geo::PlaneID iPlnID = DecodeCTP(iss.CTP);
1265  geo::PlaneID jPlnID = DecodeCTP(jss.CTP);
1266 
1267  // compare match at the charge center
1268  float ix = detProp.ConvertTicksToX(istj.Pts[1].Pos[1] / tcc.unitsPerTick, iPlnID);
1269  float jx = detProp.ConvertTicksToX(jstj.Pts[1].Pos[1] / tcc.unitsPerTick, jPlnID);
1270  float pos1fom = std::abs(ix - jx) / 10;
1271 
1272  float mfom = energyAsym * pos1fom;
1273 
1274  if (prt) {
1275  mf::LogVerbatim myprt("TC");
1276  myprt << fcnLabel << " i2S" << iss.ID << " j2S" << jss.ID;
1277  myprt << std::fixed << std::setprecision(2);
1278  myprt << " pos1fom " << pos1fom;
1279  myprt << " energyAsym " << energyAsym;
1280  myprt << " mfom " << mfom;
1281  }
1282 
1283  return mfom;
1284  } // Madtch3DFOM
1285 
1287  void MergeTjList(std::vector<std::vector<int>>& tjList)
1288  {
1289  // Merge the lists of Tjs in the lists if they share a common Tj ID
1290 
1291  if (tjList.size() < 2) return;
1292 
1293  bool didMerge = true;
1294  while (didMerge) {
1295  didMerge = false;
1296  for (unsigned short itl = 0; itl < tjList.size() - 1; ++itl) {
1297  if (tjList[itl].empty()) continue;
1298  for (unsigned short jtl = itl + 1; jtl < tjList.size(); ++jtl) {
1299  if (tjList[itl].empty()) continue;
1300  auto& itList = tjList[itl];
1301  auto& jtList = tjList[jtl];
1302  // See if the j Tj is in the i tjList
1303  bool jtjInItjList = false;
1304  for (auto& jtj : jtList) {
1305  if (std::find(itList.begin(), itList.end(), jtj) != itList.end()) {
1306  jtjInItjList = true;
1307  break;
1308  }
1309  if (jtjInItjList) break;
1310  } // jtj
1311  if (jtjInItjList) {
1312  // append the jtList to itList
1313  itList.insert(itList.end(), jtList.begin(), jtList.end());
1314  // clear jtList
1315  jtList.clear();
1316  didMerge = true;
1317  }
1318  } // jtl
1319  } // itl
1320  } // didMerge
1321 
1322  // erase the deleted elements
1323  unsigned short imEmpty = 0;
1324  while (imEmpty < tjList.size()) {
1325  for (imEmpty = 0; imEmpty < tjList.size(); ++imEmpty)
1326  if (tjList[imEmpty].empty()) break;
1327  if (imEmpty < tjList.size()) tjList.erase(tjList.begin() + imEmpty);
1328  } // imEmpty < tjList.size()
1329 
1330  // sort the lists by increasing ID and remove duplicates
1331  for (auto& tjl : tjList) {
1332  std::sort(tjl.begin(), tjl.end());
1333  auto last = std::unique(tjl.begin(), tjl.end());
1334  tjl.erase(last, tjl.end());
1335  } // tjl
1336 
1337  } // MergeTjList
1338 
1340  bool RemovePFP(std::string inFcnLabel,
1341  TCSlice& slc,
1342  PFPStruct& pfp,
1343  ShowerStruct3D& ss3,
1344  bool doUpdate,
1345  bool prt)
1346  {
1347  // removes the tjs in the pfp from the ss3 2D showers and optionally update. This function only returns
1348  // false if there was a failure. The absence of any pfp Tjs in ss3 is not considered a failure
1349 
1350  if (pfp.ID == 0 || ss3.ID == 0) return false;
1351 
1352  std::string fcnLabel = inFcnLabel + ".RemP";
1353  for (auto tid : pfp.TjIDs) {
1354  for (auto cid : ss3.CotIDs) {
1355  auto& ss = slc.cots[cid - 1];
1356  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tid) == ss.TjIDs.end()) continue;
1357  if (!RemoveTj(fcnLabel, slc, tid, ss, doUpdate, prt)) return false;
1358  ss3.NeedsUpdate = true;
1359  } // cid
1360  } // ptid
1361 
1362  if (doUpdate && ss3.NeedsUpdate) UpdateShower(fcnLabel, slc, ss3, prt);
1363  return true;
1364 
1365  } // Remove PFP
1366 
1368  bool AddPFP(std::string inFcnLabel,
1369  TCSlice& slc,
1370  int pID,
1371  ShowerStruct3D& ss3,
1372  bool doUpdate,
1373  bool prt)
1374  {
1375  // Add the tjs in the pfp with id = pID to the 2D showers in ss3 and optionally update everything. This
1376  // function returns true if the addition was successful or if the Tjs in the pfp are already in ss3.
1377  // This function returns false if there was a failure. There isn't any error recovery.
1378 
1379  std::string fcnLabel = inFcnLabel + ".AddP";
1380 
1381  if (pID <= 0 || pID > (int)slc.pfps.size()) return false;
1382  auto& pfp = slc.pfps[pID - 1];
1383 
1384  if (pfp.TPCID != ss3.TPCID) {
1385  mf::LogVerbatim("TC") << fcnLabel << " P" << pID << " is in the wrong TPC for 3S" << ss3.ID;
1386  return false;
1387  }
1388 
1389  for (auto tid : pfp.TjIDs) {
1390  auto& tj = slc.tjs[tid - 1];
1391  // is this tj in a 2D shower that is in a 3D shower that is not this shower?
1392  if (tj.SSID > 0) {
1393  auto& ss = slc.cots[tj.SSID - 1];
1394  if (ss.SS3ID > 0 && ss.SS3ID != ss3.ID) {
1395  if (prt)
1396  mf::LogVerbatim("TC") << fcnLabel << " Conflict: 3S" << ss3.ID << " adding P" << pfp.ID
1397  << " -> T" << tid << " is in 2S" << tj.SSID << " that is in 3S"
1398  << ss.SS3ID << " that is not this shower";
1399  return false;
1400  } // conflict
1401  // tj is in the correct 2D shower so nothing needs to be done
1402  if (prt)
1403  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " adding P" << pfp.ID << " -> T"
1404  << tid << " is in the correct shower 2S" << tj.SSID;
1405  continue;
1406  } // pfp tj is in a shower
1407  if (prt) {
1408  mf::LogVerbatim myprt("TC");
1409  myprt << fcnLabel << " 3S" << ss3.ID << " adding P" << pfp.ID << " -> T" << tid;
1410  myprt << " tj.SSID 2S" << tj.SSID;
1411  } // prt
1412  // add it to the shower in the correct CTP
1413  for (auto& cid : ss3.CotIDs) {
1414  auto& ss = slc.cots[cid - 1];
1415  if (ss.CTP != tj.CTP) continue;
1416  // Add it to the shower.
1417  AddTj(fcnLabel, slc, tid, ss, doUpdate, prt);
1418  ss3.NeedsUpdate = true;
1419  break;
1420  } // cid
1421  } // tid
1422 
1423  if (doUpdate && ss3.NeedsUpdate) UpdateShower(fcnLabel, slc, ss3, prt);
1424  return true;
1425 
1426  } // AddPFP
1427 
1429  bool AddTj(std::string inFcnLabel,
1430  TCSlice& slc,
1431  int tjID,
1432  ShowerStruct& ss,
1433  bool doUpdate,
1434  bool prt)
1435  {
1436  // Adds the Tj to the shower and optionally updates the shower variables
1437 
1438  if (tjID <= 0 || tjID > (int)slc.tjs.size()) return false;
1439 
1440  std::string fcnLabel = inFcnLabel + ".AddT";
1441 
1442  Trajectory& tj = slc.tjs[tjID - 1];
1443 
1444  if (tj.CTP != ss.CTP) {
1445  mf::LogVerbatim("TC") << fcnLabel << " T" << tjID << " is in the wrong CTP for 2S" << ss.ID;
1446  return false;
1447  }
1448 
1449  if (tj.SSID > 0) {
1450  if (tj.SSID == ss.ID) {
1451  if (prt) mf::LogVerbatim("TC") << fcnLabel << " T" << tjID << " is already in 2S" << ss.ID;
1452  return true;
1453  }
1454  else {
1455  if (prt)
1456  mf::LogVerbatim("TC") << fcnLabel << " Can't add T" << tjID << " to 2S" << ss.ID
1457  << ". it is already used in 2S" << tj.SSID;
1458  return false;
1459  }
1460  } // tj.SSID > 0
1461 
1462  ss.TjIDs.push_back(tjID);
1463  tj.SSID = ss.ID;
1464  std::sort(ss.TjIDs.begin(), ss.TjIDs.end());
1465  // remove this ID from the NearTjIDs list
1466  for (unsigned short ii = 0; ii < ss.NearTjIDs.size(); ++ii) {
1467  if (ss.NearTjIDs[ii] == tjID) ss.NearTjIDs.erase(ss.NearTjIDs.begin() + ii);
1468  }
1469  // count the hits in the TPs
1470  unsigned short cnt = 0;
1471  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
1472  TrajPoint& tp = tj.Pts[ipt];
1473  if (tp.Chg == 0) continue;
1474  for (unsigned short ii = 0; ii < tp.Hits.size(); ++ii)
1475  if (tp.UseHit[ii]) ++cnt;
1476  } // ipt
1477  unsigned short newSize = ss.ShPts.size() + cnt;
1478  cnt = ss.ShPts.size();
1479  ss.ShPts.resize(newSize);
1480  // now add them
1481  for (unsigned short ipt = tj.EndPt[0]; ipt <= tj.EndPt[1]; ++ipt) {
1482  TrajPoint& tp = tj.Pts[ipt];
1483  if (tp.Chg == 0) continue;
1484  for (unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
1485  if (tp.UseHit[ii]) {
1486  unsigned int iht = tp.Hits[ii];
1487  ss.ShPts[cnt].HitIndex = iht;
1488  ss.ShPts[cnt].TID = tj.ID;
1489  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
1490  ss.ShPts[cnt].Chg = hit.Integral();
1491  ss.ShPts[cnt].Pos[0] = hit.WireID().Wire;
1492  ss.ShPts[cnt].Pos[1] = hit.PeakTime() * tcc.unitsPerTick;
1493  ++cnt;
1494  }
1495  }
1496  } // ipt
1497  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Added T" << tj.ID << " to 2S" << ss.ID;
1498  ss.NeedsUpdate = true;
1499 
1500  if (doUpdate) return UpdateShower(fcnLabel, slc, ss, prt);
1501  return true;
1502 
1503  } // AddTj
1504 
1506  bool RemoveTj(std::string inFcnLabel,
1507  TCSlice& slc,
1508  int TjID,
1509  ShowerStruct& ss,
1510  bool doUpdate,
1511  bool prt)
1512  {
1513  // Removes the Tj from a shower
1514 
1515  if (TjID > (int)slc.tjs.size()) return false;
1516 
1517  std::string fcnLabel = inFcnLabel + ".RTj";
1518 
1519  // make sure it isn't already in a shower
1520  Trajectory& tj = slc.tjs[TjID - 1];
1521 
1522  if (tj.SSID != ss.ID) {
1523  if (prt)
1524  mf::LogVerbatim("TC") << fcnLabel << " Can't Remove T" << TjID << " from 2S" << ss.ID
1525  << " because it's not in this shower";
1526  // This isn't a failure
1527  return true;
1528  }
1529  tj.AlgMod[kShwrParent] = false;
1530 
1531  bool gotit = false;
1532  for (unsigned short ii = 0; ii < ss.TjIDs.size(); ++ii) {
1533  if (TjID == ss.TjIDs[ii]) {
1534  ss.TjIDs.erase(ss.TjIDs.begin() + ii);
1535  gotit = true;
1536  break;
1537  }
1538  } // ii
1539  if (!gotit) return false;
1540  tj.SSID = 0;
1541  // Removing a parent Tj?
1542  if (TjID == ss.ParentID) ss.ParentID = 0;
1543  // re-build everything?
1544  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Remove T" << TjID << " from 2S" << ss.ID;
1545  // removed the only tj
1546  if (ss.TjIDs.empty()) {
1547  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Removed the last Tj. Killing 2S" << ss.ID;
1548  MakeShowerObsolete(fcnLabel, slc, ss, prt);
1549  return true;
1550  }
1551  // clear out the shower points to force a complete update when UpdateShower is next called
1552  ss.ShPts.clear();
1553  if (doUpdate) {
1554  ss.NeedsUpdate = true;
1555  return UpdateShower(fcnLabel, slc, ss, prt);
1556  }
1557  return true;
1558  } // RemoveTj
1559 
1562  std::string inFcnLabel,
1563  TCSlice& slc,
1564  ShowerStruct3D& ss3,
1565  bool prt)
1566  {
1567  // look for a parent pfp for the shower.The 2D showers associated with it
1568  // The parent should be at the start of the shower (shend = 0) if it is well-defined
1569  // (has small AspectRatio and small DirectionFOM). A search is also made for a parent at
1570  // the "wrong" end of the shower (shend = 1). The best one at the wrong end is used if
1571  // no parent is found at the shower start and the shower is poorly defined.
1572  //
1573  // This function returns false if there was a failure. Not finding a parent is not a failure
1574 
1575  if (ss3.ID == 0) return false;
1576  if (ss3.CotIDs.size() < 2) return false;
1577  // Use the MVA reader
1578  if (!tcc.showerParentReader) return false;
1579  if (tcc.showerParentVars.size() != 9) return false;
1580  if (!tcc.useAlg[kShwrParent]) return false;
1581 
1582  std::string fcnLabel = inFcnLabel + ".FPar";
1583  int truPFP = 0;
1584 
1585  double energy = ShowerEnergy(ss3);
1586  // the energy is probably under-estimated since there isn't a parent yet.
1587  energy *= 1.2;
1588  double shMaxAlong, along95;
1589  ShowerParams(energy, shMaxAlong, along95);
1590  if (prt)
1591  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " Estimated energy " << (int)energy
1592  << " MeV shMaxAlong " << shMaxAlong << " along95 " << along95
1593  << " truPFP " << truPFP;
1594 
1595  // look for the pfp that has a reasonable probability of being in the shower but with the
1596  // minimum along distance from the shower center.
1597  // This image explains the concept. The *'s represents the points in 2D showers that define
1598  // the charge center in 3D, ChgPos. We are looking for a pfp parent denoted by ----. The end
1599  // that is farthest from ChgPos is labeled P (pfp.XYZ[pend] in the code). The expected distance
1600  // from the shower start to shower Max, shMaxAlong, is found from ShowerParams. The longitudinal
1601  // and transverse distance of P relative to the shower center is alongTrans. The first cut on a
1602  // candidate parent is made requiring that D (alongTrans[0]) > 0.5 * shMaxAlong.
1603  //
1604  // __________shend = 0________________ __________shend = 1________________
1605  // **********
1606  // ****** ****** ****** ******
1607  // P-----*****ChgPos****** ** *****ChgPos******-------P
1608  // ******** ******* *******
1609  // |----> along |---> along
1610  // |<----D------>| |<----D------>|
1611  // |<----shMaxAlong--->| |<----shMaxAlong--->|
1612  //
1613  // Candidate parent ID for each end and the FOM
1614  std::array<int, 2> parID{{0, 0}};
1615  std::array<float, 2> parFOM{{-1E6, -1E6}};
1616 
1617  // temp vector to flag pfps that are already parents - indexed by ID
1618  std::vector<bool> isParent(slc.pfps.size() + 1, false);
1619  for (auto& oldSS3 : slc.showers) {
1620  if (oldSS3.ID == 0) continue;
1621  isParent[oldSS3.ParentID] = true;
1622  } // pfp
1623 
1624  // put the tjs associated with this shower in a flat vector
1625  auto TjsInSS3 = GetAssns(slc, "3S", ss3.ID, "T");
1626  if (TjsInSS3.empty()) return false;
1627 
1628  for (auto& pfp : slc.pfps) {
1629  if (pfp.ID == 0) continue;
1630  bool dprt = (pfp.ID == truPFP);
1631  if (pfp.TPCID != ss3.TPCID) continue;
1632  // ignore neutrinos
1633  if (pfp.PDGCode == 14 || pfp.PDGCode == 14) continue;
1634  // ignore shower pfps
1635  if (pfp.PDGCode == 1111) continue;
1636  // ignore existing parents
1637  if (isParent[pfp.ID]) continue;
1638  // check for inconsistent pfp - shower tjs
1639  if (DontCluster(slc, pfp.TjIDs, TjsInSS3)) continue;
1640  // ignore if the pfp energy is larger than the shower energy
1641  float pfpEnergy = 0;
1642  float minEnergy = 1E6;
1643  for (auto tid : pfp.TjIDs) {
1644  auto& tj = slc.tjs[tid - 1];
1645  float energy = ChgToMeV(tj.TotChg);
1646  pfpEnergy += energy;
1647  if (energy < minEnergy) minEnergy = energy;
1648  }
1649  pfpEnergy -= minEnergy;
1650  pfpEnergy /= (float)(pfp.TjIDs.size() - 1);
1651  if (dprt)
1652  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " P" << pfp.ID << " E "
1653  << pfpEnergy;
1654  if (pfpEnergy > energy) continue;
1655  // find the end that is farthest away
1656  unsigned short pEnd = FarEnd(pfp, ss3.ChgPos);
1657  auto pos = PosAtEnd(pfp, pEnd);
1658  auto pToS = PointDirection(pos, ss3.ChgPos);
1659  double costh1 = std::abs(DotProd(pToS, ss3.Dir));
1660  if (costh1 < 0.4) continue;
1661  auto dir = DirAtEnd(pfp, pEnd);
1662  float costh2 = DotProd(pToS, dir);
1663  // distance^2 between the pfp end and the shower start, charge center, and shower end
1664  float distToStart2 = PosSep2(pos, ss3.Start);
1665  float distToChgPos2 = PosSep2(pos, ss3.ChgPos);
1666  float distToEnd2 = PosSep2(pos, ss3.End);
1667  if (dprt)
1668  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " P" << pfp.ID << "_" << pEnd
1669  << " distToStart " << sqrt(distToStart2) << " distToChgPos "
1670  << sqrt(distToChgPos2) << " distToEnd " << sqrt(distToEnd2);
1671  // find the end of the shower closest to the pfp
1672  unsigned short shEnd = 0;
1673  if (distToEnd2 < distToStart2) shEnd = 1;
1674  // This can't be a parent if the pfp end is closer to the shower center than the start or the end
1675  if (shEnd == 0 && distToChgPos2 < distToStart2) continue;
1676  if (shEnd == 1 && distToChgPos2 < distToEnd2) continue;
1677  if (dprt)
1678  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << "_" << shEnd << " P" << pfp.ID
1679  << "_" << pEnd << " costh1 " << costh1;
1680  Point2_t alongTrans;
1681  // find the longitudinal and transverse components of the pfp start point relative to the
1682  // shower center
1683  FindAlongTrans(ss3.ChgPos, ss3.Dir, pos, alongTrans);
1684  if (dprt)
1685  mf::LogVerbatim("TC") << fcnLabel << " alongTrans " << alongTrans[0] << " "
1686  << alongTrans[1];
1687  // find the probability this point is inside the shower. Offset by the expected
1688  // shower max distance. distToShowerMax will be > 0 if the pfp end is closer to
1689  // ChgPos than expected from the parameterization
1690  float distToShowerMax = shMaxAlong - std::abs(alongTrans[0]);
1691  float prob = InShowerProbLong(energy, distToShowerMax);
1692  if (dprt) mf::LogVerbatim("TC") << fcnLabel << " prob " << prob;
1693  if (prob < 0.1) continue;
1694  float chgFrac = 0;
1695  float totSep = 0;
1696  // find the charge fraction btw the pfp start and the point that is
1697  // half the distance to the charge center in each plane
1698  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
1699  CTP_t inCTP = EncodeCTP(ss3.TPCID.Cryostat, ss3.TPCID.TPC, plane);
1700  int ssid = 0;
1701  for (auto cid : ss3.CotIDs) {
1702  auto& ss = slc.cots[cid - 1];
1703  if (ss.CTP != inCTP) continue;
1704  ssid = ss.ID;
1705  break;
1706  } // cid
1707  if (ssid == 0) continue;
1708  auto tpFrom = MakeBareTP(detProp, pos, pToS, inCTP);
1709  auto& ss = slc.cots[ssid - 1];
1710  auto& stp1 = slc.tjs[ss.ShowerTjID - 1].Pts[1];
1711  float sep = PosSep(tpFrom.Pos, stp1.Pos);
1712  float toPos = tpFrom.Pos[0] + 0.5 * tpFrom.Dir[0] * sep;
1713  float cf = ChgFracBetween(tpFrom, toPos);
1714  // weight by the separation in the plane
1715  totSep += sep;
1716  chgFrac += sep * cf;
1717  } // plane
1718  if (totSep > 0) chgFrac /= totSep;
1719  // load the MVA variables
1721  tcc.showerParentVars[1] = pfpEnergy;
1722  tcc.showerParentVars[2] = MCSMom(slc, pfp.TjIDs);
1723  auto startPos = PosAtEnd(pfp, 0);
1724  auto endPos = PosAtEnd(pfp, 1);
1725  tcc.showerParentVars[3] = PosSep(startPos, endPos);
1726  tcc.showerParentVars[4] = sqrt(distToChgPos2);
1727  tcc.showerParentVars[5] = acos(costh1);
1728  tcc.showerParentVars[6] = acos(costh2);
1729  tcc.showerParentVars[7] = chgFrac;
1730  tcc.showerParentVars[8] = prob;
1731  float candParFOM = tcc.showerParentReader->EvaluateMVA("BDT");
1732 
1733  if (prt) {
1734  mf::LogVerbatim myprt("TC");
1735  myprt << fcnLabel;
1736  myprt << " 3S" << ss3.ID << "_" << shEnd;
1737  myprt << " P" << pfp.ID << "_" << pEnd << " ParentVars";
1738  for (auto var : tcc.showerParentVars)
1739  myprt << " " << std::fixed << std::setprecision(2) << var;
1740  myprt << " candParFOM " << candParFOM;
1741  } // prt
1742  if (candParFOM > parFOM[shEnd]) {
1743  parFOM[shEnd] = candParFOM;
1744  parID[shEnd] = pfp.ID;
1745  }
1746  } // pfp
1747 
1748  if (parID[0] == 0 && parID[1] == 0) return true;
1749 
1750  // decide which to use
1751  int bestPFP = 0;
1752  // find the average DirectionFOM to help decide
1753  float aveDirFOM = 0;
1754  float fom3D = 0;
1755  for (auto cid : ss3.CotIDs)
1756  aveDirFOM += slc.cots[cid - 1].DirectionFOM;
1757  aveDirFOM /= (float)ss3.CotIDs.size();
1758  if (prt) {
1759  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " parID[0] " << parID[0] << " fom "
1760  << parFOM[0] << " parID[1] " << parID[1] << " fom " << parFOM[1]
1761  << " aveDirFOM " << aveDirFOM;
1762  }
1763  if (parID[0] > 0 && parID[1] > 0 && aveDirFOM > 0.3) {
1764  // candidates at both ends and the direction is not well known. Take
1765  // the one with the best FOM
1766  bestPFP = parID[0];
1767  fom3D = parFOM[0];
1768  if (parFOM[1] > parFOM[0]) {
1769  bestPFP = parID[1];
1770  fom3D = parFOM[1];
1771  }
1772  }
1773  else if (parID[0] > 0) {
1774  bestPFP = parID[0];
1775  fom3D = parFOM[0];
1776  }
1777  else {
1778  bestPFP = parID[1];
1779  fom3D = parFOM[1];
1780  }
1781  if (bestPFP == 0) return true;
1782 
1783  if (prt)
1784  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " setting P" << bestPFP
1785  << " as the parent " << fom3D;
1786 
1787  // make local copies so we can recover from a failure
1788  auto oldSS3 = ss3;
1789  std::vector<ShowerStruct> oldSS(ss3.CotIDs.size());
1790  for (unsigned short ii = 0; ii < ss3.CotIDs.size(); ++ii) {
1791  oldSS[ii] = slc.cots[ss3.CotIDs[ii] - 1];
1792  }
1793 
1794  ss3.ParentID = bestPFP;
1795  auto& pfp = slc.pfps[bestPFP - 1];
1796  unsigned short pend = FarEnd(pfp, ss3.ChgPos);
1797  ss3.Vx3ID = pfp.Vx3ID[pend];
1798 
1799  if (SetParent(detProp, fcnLabel, slc, pfp, ss3, prt) && UpdateShower(fcnLabel, slc, ss3, prt)) {
1800  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " successful update";
1801  return true;
1802  }
1803 
1804  if (prt) mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " Failed. Recovering...";
1805  ss3 = oldSS3;
1806  for (unsigned short ii = 0; ii < oldSS.size(); ++ii) {
1807  auto& ss = oldSS[ii];
1808  slc.cots[ss.ID - 1] = ss;
1809  } // ii
1810  return false;
1811 
1812  } // FindParent
1813 
1816  std::string inFcnLabel,
1817  TCSlice& slc,
1818  PFPStruct& pfp,
1819  ShowerStruct3D& ss3,
1820  bool prt)
1821  {
1822  // set the pfp as the parent of ss3. The calling function should do the error recovery
1823  if (pfp.ID == 0 || ss3.ID == 0) return false;
1824  if (ss3.CotIDs.empty()) return false;
1825 
1826  std::string fcnLabel = inFcnLabel + ".SP";
1827 
1828  for (auto cid : ss3.CotIDs) {
1829  auto& ss = slc.cots[cid - 1];
1830  auto& stj = slc.tjs[ss.ShowerTjID - 1];
1831  stj.VtxID[0] = 0;
1832  if (ss.ParentID > 0) {
1833  auto& oldParent = slc.tjs[ss.ParentID - 1];
1834  oldParent.AlgMod[kShwrParent] = false;
1835  ss.ParentID = 0;
1836  ss.ParentFOM = 10;
1837  } // remove old parents
1838  // add new parents
1839  for (auto tjid : pfp.TjIDs) {
1840  auto& tj = slc.tjs[tjid - 1];
1841  if (tj.CTP != ss.CTP) continue;
1842  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tjid) == ss.TjIDs.end()) {
1843  // Add the tj but don't update yet
1844  if (!AddTj(fcnLabel, slc, tjid, ss, false, prt)) return false;
1845  } // parent not in ss
1846  // Don't define it to be the parent if it is short and the pfp projection in this plane is low
1847  auto pos = PosAtEnd(pfp, 0);
1848  auto dir = DirAtEnd(pfp, 0);
1849  auto tp = MakeBareTP(detProp, pos, dir, tj.CTP);
1850  unsigned short npts = tj.EndPt[1] - tj.EndPt[0] + 1;
1851  if (tp.Delta > 0.5 || npts > 20) {
1852  if (prt)
1853  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " parent P" << pfp.ID << " -> T"
1854  << tjid << " -> 2S" << ss.ID << " parent";
1855  }
1856  else {
1857  if (prt)
1858  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " parent P" << pfp.ID << " -> T"
1859  << tjid << " low projection in plane " << tp.Delta
1860  << ". Not a parent";
1861  continue;
1862  }
1863  ss.ParentID = tjid;
1864  ss.NeedsUpdate = true;
1865  // set the ss start vertex
1866  if (ss3.Vx3ID > 0) {
1867  auto& vx3 = slc.vtx3s[ss3.Vx3ID - 1];
1868  auto v2list = GetAssns(slc, "3V", vx3.ID, "2V");
1869  for (unsigned short end = 0; end < 2; ++end) {
1870  if (tj.VtxID[end] <= 0) continue;
1871  if (std::find(v2list.begin(), v2list.end(), tj.VtxID[end]) != v2list.end())
1872  stj.VtxID[0] = tj.VtxID[end];
1873  } // end
1874  } // ss3.Vx3ID > 0
1875  // and update
1876  if (!UpdateShower(fcnLabel, slc, ss, prt)) return false;
1877  } // tjid
1878  } // cid
1879  ss3.ParentID = pfp.ID;
1880 
1881  unsigned short pEnd = FarEnd(pfp, ss3.ChgPos);
1882  ss3.Vx3ID = pfp.Vx3ID[pEnd];
1883  float fom3D = ParentFOM(fcnLabel, slc, pfp, ss3, prt);
1884  for (auto cid : ss3.CotIDs)
1885  slc.cots[cid - 1].ParentFOM = fom3D;
1886 
1887  return true;
1888  } // SetParent
1889 
1891  bool IsShowerLike(TCSlice const& slc, std::vector<int> const& TjIDs)
1892  {
1893  // Vote for the list of Tjs (assumed associated with a PFParticle) being shower-like
1894  if (TjIDs.empty()) return false;
1895  unsigned short cnt = 0;
1896  for (auto tid : TjIDs) {
1897  if (tid <= 0 || tid > (int)slc.tjs.size()) continue;
1898  if (slc.tjs[tid - 1].AlgMod[kShowerLike] > 0) ++cnt;
1899  } // tjid
1900  return (cnt > 1);
1901  } // IsInShower
1902 
1904  void ShowerParams(double showerEnergy, double& shMaxAlong, double& along95)
1905  {
1906  // Returns summary properties of photon showers parameterized in the energy range 50 MeV < E_gamma < 1 GeV:
1907  // shMaxAlong = the longitudinal distance (cm) between the start of the shower and the center of charge
1908  // along95 = the longitudinal distance (cm) between the start of the shower and 95% energy containment
1909  // all units are in cm
1910  if (showerEnergy < 10) {
1911  shMaxAlong = 0;
1912  along95 = 0;
1913  return;
1914  }
1915  shMaxAlong = 16 * log(showerEnergy / 15);
1916  // The 95% containment is reduced a bit at higher energy
1917  double scale = 2.75 - 9.29E-4 * showerEnergy;
1918  if (scale < 2) scale = 2;
1919  along95 = scale * shMaxAlong;
1920  } // ShowerParams
1921 
1923  double ShowerParamTransRMS(double showerEnergy, double along)
1924  {
1925  // returns the pareameterized width rms of a shower at along relative to the shower max
1926  double shMaxAlong, shE95Along;
1927  ShowerParams(showerEnergy, shMaxAlong, shE95Along);
1928  if (shMaxAlong <= 0) return 0;
1929  double tau = (along + shMaxAlong) / shMaxAlong;
1930  // The shower width is modeled as a simple cone that scales with tau
1931  double rms = -0.4 + 2.5 * tau;
1932  if (rms < 0.5) rms = 0.5;
1933  return rms;
1934  } // ShowerParamTransRMS
1935 
1937  double InShowerProbLong(double showerEnergy, double along)
1938  {
1939  // Returns the likelihood that the point at position along (cm) is inside an EM shower
1940  // having showerEnergy (MeV). The variable along is relative to shower max.
1941 
1942  if (showerEnergy < 10) return 0;
1943 
1944  double shMaxAlong, shE95Along;
1945  ShowerParams(showerEnergy, shMaxAlong, shE95Along);
1946  // 50% of the shower energy is deposited between 0 < shMaxAlong < 1, which should be obvious considering
1947  // that is the definition of the shower max, so the probability should be ~1 at shMaxAlong = 1.
1948  // The Geant study shows that 95% of the energy is contained within 2.5 * shMax and has a small dependence
1949  // on the shower energy, which is modeled in ShowerParams. This function uses a
1950  // sigmoid likelihood function is constructed with these constraints using the scaling variable tau
1951  double tau = (along + shMaxAlong) / shMaxAlong;
1952  if (tau < -1 || tau > 4) return 0;
1953 
1954  double tauHalf, width;
1955  if (tau > 0) {
1956  tauHalf = 1.7;
1957  width = 0.35;
1958  }
1959  else {
1960  // Allow for some uncertainty in the shower start position
1961  tau = -tau;
1962  tauHalf = 0.2;
1963  width = 0.1;
1964  }
1965 
1966  double prob = 1 / (1 + exp((tau - tauHalf) / width));
1967  return prob;
1968 
1969  } // InShowrProbLong
1970 
1972  double InShowerProbTrans(double showerEnergy, double along, double trans)
1973  {
1974  // Returns the likelihood that the point, (along, trans) (cm), is inside an EM shower having energy showerEnergy (MeV)
1975  // where along is relative to the shower start position and trans is the radial distance.
1976 
1977  if (showerEnergy < 10) return 0;
1978  double rms = ShowerParamTransRMS(showerEnergy, along);
1979  trans = std::abs(trans);
1980  double prob = exp(-0.5 * trans / rms);
1981  return prob;
1982 
1983  } // InShowerProbTrans
1984 
1986  double InShowerProbParam(double showerEnergy, double along, double trans)
1987  {
1988  return InShowerProbLong(showerEnergy, along) * InShowerProbTrans(showerEnergy, along, trans);
1989  } // InShowerProbParam
1990 
1992  float InShowerProb(TCSlice& slc, const ShowerStruct3D& ss3, const PFPStruct& pfp)
1993  {
1994  // returns a likelihood (0 - 1) that the pfp particle belongs in shower ss3
1995 
1996  if (ss3.ID == 0 || pfp.ID == 0) return 0;
1997  float sum = 0;
1998  float cnt = 0;
1999  for (auto cid : ss3.CotIDs) {
2000  auto& ss = slc.cots[cid - 1];
2001  if (ss.ID == 0) continue;
2002  for (auto tid : pfp.TjIDs) {
2003  auto& tj = slc.tjs[tid - 1];
2004  if (tj.CTP != ss.CTP) continue;
2005  sum += InShowerProb(slc, ss, tj);
2006  ++cnt;
2007  } // tid
2008  } //cid
2009  if (cnt == 0) return 0;
2010  return sum / cnt;
2011 
2012  } // InShowerProb
2013 
2015  float InShowerProb(TCSlice& slc, const ShowerStruct& ss, const Trajectory& tj)
2016  {
2017  // returns a likelihood (0 - 1) that the tj particle belongs in shower ss
2018  // Keep it simple: construct a FOM, take the inverse and limit it to the range 0 - 1
2019  if (ss.ID == 0 || tj.ID == 0) return 0;
2020  if (ss.CTP != tj.CTP) return 0;
2021 
2022  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2023  if (stj.Pts.size() != 3) return 0;
2024  unsigned short closePt1, closePt2;
2025  float doca = 1E6;
2026  TrajTrajDOCA(slc, stj, tj, closePt1, closePt2, doca);
2027  if (doca == 1E6) return 0;
2028  float showerLen = PosSep(stj.Pts[0].Pos, stj.Pts[2].Pos);
2029  // make a rough separation cut. Return a small but non-zero value
2030  if (doca > 5 * showerLen) return 0.01;
2031  auto& stp = stj.Pts[closePt1];
2032  if (stp.DeltaRMS == 0) return 0;
2033  auto& ttp = tj.Pts[closePt2];
2034  Point2_t alongTrans;
2035  FindAlongTrans(stp.Pos, stp.Dir, ttp.Pos, alongTrans);
2036  float rms = stp.DeltaRMS;
2037  if (rms < 1) rms = 1;
2038  float arg = alongTrans[1] / rms;
2039  float radProb = exp(-0.5 * arg * arg);
2040  // This is a fake but may be OK if this function is called before the shower is well-defined
2041  rms = showerLen;
2042  arg = alongTrans[0] / rms;
2043  float longProb = exp(-0.5 * arg * arg);
2044  float costh = std::abs(DotProd(stp.Dir, ttp.Dir));
2045  float prob = radProb * longProb * costh;
2046  return prob;
2047 
2048  } // InShowerProb
2049 
2051  float ParentFOM(std::string inFcnLabel,
2052  TCSlice& slc,
2053  PFPStruct& pfp,
2054  ShowerStruct3D& ss3,
2055  bool prt)
2056  {
2057  // Returns an average weighted parent FOM for all trajectories in the pfp being a parent of the 2D showers in ss3
2058  if (ss3.ID == 0) return 1000;
2059  float sum = 0;
2060  float wsum = 0;
2061  std::string fcnLabel = inFcnLabel + ".P3FOM";
2062  float dum1, dum2;
2063  for (auto cid : ss3.CotIDs) {
2064  auto& ss = slc.cots[cid - 1];
2065  if (ss.ID == 0) continue;
2066  // look for the 3D matched tj in this CTP
2067  int tjid = 0;
2068  for (auto tid : pfp.TjIDs) {
2069  auto& tj = slc.tjs[tid - 1];
2070  if (tj.ID == 0) continue;
2071  if (tj.CTP == ss.CTP) tjid = tid;
2072  } // tid
2073  if (tjid == 0) continue;
2074  auto& ptj = slc.tjs[tjid - 1];
2075  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2076  // determine which end is farthest away from the shower center
2077  unsigned short ptjEnd = FarEnd(ptj, stj.Pts[1].Pos);
2078  auto& farTP = ptj.Pts[ptj.EndPt[ptjEnd]];
2079  float chgCtrSep2 = PosSep2(farTP.Pos, stj.Pts[1].Pos);
2080  if (chgCtrSep2 < PosSep2(farTP.Pos, stj.Pts[0].Pos) &&
2081  chgCtrSep2 < PosSep2(farTP.Pos, stj.Pts[2].Pos))
2082  continue;
2083  float fom = ParentFOM(fcnLabel, slc, ptj, ptjEnd, ss, dum1, dum2, prt);
2084  // ignore failures
2085  if (fom > 50) continue;
2086  // weight by the 1/aspect ratio
2087  float wt = 1;
2088  if (ss.AspectRatio > 0) wt = 1 / ss.AspectRatio;
2089  sum += wt * fom;
2090  wsum += wt;
2091  } // cid
2092  if (wsum == 0) return 100;
2093  float fom = sum / wsum;
2094  if (prt)
2095  mf::LogVerbatim("TC") << fcnLabel << " 3S" << ss3.ID << " P" << pfp.ID << " fom "
2096  << std::fixed << std::setprecision(3) << fom;
2097  return fom;
2098  } // ParentFOM
2099 
2101  float ParentFOM(std::string inFcnLabel,
2102  TCSlice& slc,
2103  Trajectory& tj,
2104  unsigned short& tjEnd,
2105  ShowerStruct& ss,
2106  float& tp1Sep,
2107  float& vx2Score,
2108  bool prt)
2109  {
2110  // returns a FOM for the trajectory at the end point being the parent of ss and the end which
2111  // was matched.
2112 
2113  vx2Score = 0;
2114  tp1Sep = 0;
2115 
2116  if (tjEnd > 1) return 1000;
2117  if (ss.Energy == 0) return 1000;
2118 
2119  if (ss.ID == 0) return 1000;
2120  if (ss.TjIDs.empty()) return 1000;
2121  if (ss.ShowerTjID == 0) return 1000;
2122 
2123  std::string fcnLabel = inFcnLabel + ".PFOM";
2124 
2125  if (ss.AspectRatio > 0.5) {
2126  if (prt)
2127  mf::LogVerbatim("TC") << fcnLabel << " 2S" << ss.ID << " poor AspectRatio "
2128  << ss.AspectRatio << " FOM not calculated";
2129  return 100;
2130  }
2131 
2132  float fom = 0;
2133  float cnt = 0;
2134  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2135  TrajPoint& stp0 = stj.Pts[0];
2136  // Shower charge center TP
2137  TrajPoint& stp1 = stj.Pts[1];
2138  // get the end that is farthest away from the shower center
2139  tjEnd = FarEnd(tj, stp1.Pos);
2140  // prospective parent TP
2141  TrajPoint& ptp = tj.Pts[tj.EndPt[tjEnd]];
2142  // find the along and trans components in WSE units relative to the
2143  // shower center
2144  Point2_t alongTrans;
2145  FindAlongTrans(stp1.Pos, stp1.Dir, ptp.Pos, alongTrans);
2146  // We can return here if the shower direction is well defined and
2147  // alongTrans[0] is > 0
2148  if (ss.AspectRatio < 0.2 && ss.DirectionFOM < 0.5 && alongTrans[0] > 0) return 100;
2149  tp1Sep = std::abs(alongTrans[0]);
2150  // Find the expected shower start relative to shower max (cm)
2151  double shMaxAlong, shE95Along;
2152  ShowerParams(ss.Energy, shMaxAlong, shE95Along);
2153  double alongcm = tcc.wirePitch * tp1Sep;
2154  // InShowerProbLong expects the longitudinal distance relative to shower max so it
2155  // should be < 0
2156  float prob = InShowerProbLong(ss.Energy, -alongcm);
2157  if (prob < 0.05) return 100;
2158  // The transverse position must certainly be less than the longitudinal distance
2159  // to shower max.
2160  if (alongTrans[1] > shMaxAlong) return 100;
2161  // longitudinal contribution to fom with 1 Xo error error (14 cm)
2162  float longFOM = std::abs(alongcm + shMaxAlong) / 14;
2163  fom += longFOM;
2164  ++cnt;
2165  // transverse contribution
2166  float transFOM = -1;
2167  if (stp0.DeltaRMS > 0) {
2168  transFOM = alongTrans[1] / stp0.DeltaRMS;
2169  fom += transFOM;
2170  ++cnt;
2171  }
2172  // make a tp between the supposed parent TP and the shower center
2173  TrajPoint tp;
2174  if (!MakeBareTrajPoint(ptp, stp1, tp)) return 100;
2175  // we have three angles to compare. The ptp angle, the shower angle and
2176  // the tp angle.
2177  float dang1 = DeltaAngle(ptp.Ang, stp1.Ang);
2178  float dang1FOM = dang1 / 0.1;
2179  fom += dang1FOM;
2180  ++cnt;
2181  float dang2 = DeltaAngle(ptp.Ang, tp.Ang);
2182  float dang2FOM = dang1 / 0.1;
2183  fom += dang2FOM;
2184  ++cnt;
2185  // the environment near the parent start should be clean.
2186  std::vector<int> tjlist(1);
2187  tjlist[0] = tj.ID;
2188  // check for a vertex at this end and include the vertex tjs if the vertex is close
2189  // to the expected shower max position
2190  float vx2Sep = 0;
2191  // put in a largish FOM value for Tjs that don't have a vertex
2192  float vxFOM = 10;
2193  if (tj.VtxID[tjEnd] > 0) {
2194  VtxStore& vx2 = slc.vtxs[tj.VtxID[tjEnd] - 1];
2195  vx2Sep = PosSep(vx2.Pos, stp1.Pos);
2196  vx2Score = vx2.Score;
2197  tjlist = GetAssns(slc, "2V", vx2.ID, "T");
2198  vxFOM = std::abs(shMaxAlong - vx2Sep) / 20;
2199  } // 2D vertex exists
2200  fom += vxFOM;
2201  ++cnt;
2202  float chgFrac = ChgFracNearPos(slc, ptp.Pos, tjlist);
2203  float chgFracFOM = (1 - chgFrac) / 0.1;
2204  fom += chgFracFOM;
2205  ++cnt;
2206  // Fraction of wires that have a signal between the parent start and the shower center
2207  float chgFracBtw = ChgFracBetween(ptp, stp1.Pos[0]);
2208  float chgFrcBtwFOM = (1 - chgFrac) / 0.05;
2209  fom += chgFrcBtwFOM;
2210  ++cnt;
2211 
2212  // take the average
2213  fom /= cnt;
2214  // divide by the InShowerProbability
2215  fom /= prob;
2216 
2217  if (prt) {
2218  mf::LogVerbatim myprt("TC");
2219  myprt << fcnLabel;
2220  myprt << " 2S" << ss.ID;
2221  myprt << " T" << tj.ID << "_" << tjEnd << " Pos " << PrintPos(ptp);
2222  myprt << std::fixed << std::setprecision(2);
2223  myprt << " along " << std::fixed << std::setprecision(1) << alongTrans[0] << " fom "
2224  << longFOM;
2225  myprt << " trans " << alongTrans[1] << " fom " << transFOM;
2226  myprt << " prob " << prob;
2227  myprt << " dang1 " << dang1 << " fom " << dang1FOM;
2228  myprt << " dang2 " << dang2 << " fom " << dang2FOM;
2229  myprt << " vx2Score " << vx2Score << " fom " << vxFOM;
2230  myprt << " chgFrac " << chgFrac << " fom " << chgFracFOM;
2231  myprt << " chgFracBtw " << chgFracBtw << " fom " << chgFrcBtwFOM;
2232  myprt << " FOM " << fom;
2233  }
2234  return fom;
2235 
2236  } // ParentFOM
2237 
2239  bool WrongSplitTj(std::string inFcnLabel,
2240  TCSlice& slc,
2241  Trajectory& tj,
2242  unsigned short tjEnd,
2243  bool prt)
2244  {
2245  // Returns true if the trajectory was split by a 3D vertex match and the end of this trajectory is further
2246  // away from the shower than the partner trajectory
2247  // Here is a cartoon showing what we are trying to prevent. The shower is represented by a box. The trajectory
2248  // that is shown as (---*---) was originally reconstructed as a single trajectory. It was later split at the * position
2249  // by matching in 3D into two trajectories with ID = 1 and 2. We don't want to consider Tj 1 using end 0 as a parent for
2250  // the shower. Tj is more likely to be the real parent
2251  //
2252  // 1111111111 2222222 TjID
2253  // 0 1 0 1 Tj end
2254  // --------------
2255  // | |
2256  // ----------*------- |
2257  // | |
2258  // --------------
2259  if (!tj.AlgMod[kComp3DVx]) return false;
2260  if (tjEnd > 1) return false;
2261 
2262  std::string fcnLabel = inFcnLabel + ".WSTj";
2263 
2264  // See if the other end is the end that was split. It should have a vertex with Topo = 8 or 11
2265  unsigned short otherEnd = 1 - tjEnd;
2266  // if(prt) mf::LogVerbatim("TC")<<"WSTj: otherEnd "<<otherEnd<<" vtxID "<<tj.VtxID[otherEnd];
2267  if (tj.VtxID[otherEnd] == 0) return false;
2268  unsigned short ivx = tj.VtxID[otherEnd] - 1;
2269  // A vertex exists but not a 3D split vertex
2270  if (slc.vtxs[ivx].Topo != 8 && slc.vtxs[ivx].Topo != 10) return false;
2271  if (prt)
2272  mf::LogVerbatim("TC") << fcnLabel << " Primary candidate " << tj.ID
2273  << " was split by a 3D vertex";
2274  return true;
2275 
2276  } // WrongSplitTj
2277 
2279  void MergeNearby2DShowers(std::string inFcnLabel, TCSlice& slc, const CTP_t& inCTP, bool prt)
2280  {
2281  if (!tcc.useAlg[kMergeNrShowers]) return;
2282  if (slc.cots.empty()) return;
2283 
2284  std::string fcnLabel = inFcnLabel + ".MNS";
2285 
2286  if (prt) {
2287  mf::LogVerbatim myprt("TC");
2288  myprt << fcnLabel << " list";
2289  for (auto& ss : slc.cots) {
2290  if (ss.CTP != inCTP) continue;
2291  if (ss.ID == 0) continue;
2292  myprt << " ss.ID " << ss.ID << " NearTjs";
2293  for (auto& id : ss.NearTjIDs)
2294  myprt << " " << id;
2295  myprt << "\n";
2296  }
2297  } // prt
2298 
2299  bool keepMerging = true;
2300  while (keepMerging) {
2301  keepMerging = false;
2302  for (unsigned short ci1 = 0; ci1 < slc.cots.size() - 1; ++ci1) {
2303  ShowerStruct& ss1 = slc.cots[ci1];
2304  if (ss1.CTP != inCTP) continue;
2305  if (ss1.ID == 0) continue;
2306  if (ss1.TjIDs.empty()) continue;
2307  // put the inshower tjs and the nearby tjs into one list
2308  std::vector<int> ss1list = ss1.TjIDs;
2309  ss1list.insert(ss1list.end(), ss1.NearTjIDs.begin(), ss1.NearTjIDs.end());
2310  for (unsigned short ci2 = ci1 + 1; ci2 < slc.cots.size(); ++ci2) {
2311  ShowerStruct& ss2 = slc.cots[ci2];
2312  if (ss2.CTP != inCTP) continue;
2313  if (ss2.ID == 0) continue;
2314  if (ss2.TjIDs.empty()) continue;
2315  if (DontCluster(slc, ss1.TjIDs, ss2.TjIDs)) continue;
2316  std::vector<int> ss2list = ss2.TjIDs;
2317  ss2list.insert(ss2list.end(), ss2.NearTjIDs.begin(), ss2.NearTjIDs.end());
2318  std::vector<int> shared = SetIntersection(ss1list, ss2list);
2319  if (shared.empty()) continue;
2320  if (prt) {
2321  mf::LogVerbatim myprt("TC");
2322  myprt << fcnLabel << " Merge 2S" << ss2.ID << " into 2S" << ss1.ID
2323  << "? shared nearby:";
2324  for (auto tjid : shared)
2325  myprt << " T" << tjid;
2326  } // prt
2327  // add the shared Tjs to ss1 if they meet the requirements
2328  bool doMerge = false;
2329  for (auto& tjID : shared) {
2330  bool inSS1 = (std::find(ss1.TjIDs.begin(), ss1.TjIDs.end(), tjID) != ss1.TjIDs.end());
2331  bool inSS2 = (std::find(ss2.TjIDs.begin(), ss2.TjIDs.end(), tjID) != ss2.TjIDs.end());
2332  if (inSS1 && !inSS2) doMerge = true;
2333  if (!inSS1 && inSS2) doMerge = true;
2334  // need to add it?
2335  if (inSS1 || inSS2) continue;
2336  auto& tj = slc.tjs[tjID - 1];
2337  // ignore long muons
2338  if (tj.PDGCode == 13 && tj.Pts.size() > 100 && tj.ChgRMS < 0.5) {
2339  if (prt)
2340  mf::LogVerbatim("TC")
2341  << fcnLabel << " T" << tj.ID << " looks like a muon. Don't add it";
2342  continue;
2343  }
2344  // see if it looks like a muon in 3D
2345  if (tj.AlgMod[kMat3D]) {
2346  auto TInP = GetAssns(slc, "T", tj.ID, "P");
2347  if (!TInP.empty()) {
2348  auto& pfp = slc.pfps[TInP[0] - 1];
2349  if (pfp.PDGCode == 13 && MCSMom(slc, pfp.TjIDs) > 500) continue;
2350  } // TInP not empty
2351  } // 3D matched
2352  if (AddTj(fcnLabel, slc, tjID, ss1, false, prt)) doMerge = true;
2353  } // tjID
2354  if (!doMerge) continue;
2355  if (MergeShowersAndStore(fcnLabel, slc, ss1.ID, ss2.ID, prt)) {
2356  Trajectory& stj = slc.tjs[ss1.ShowerTjID - 1];
2357  stj.AlgMod[kMergeNrShowers] = true;
2358  if (prt) mf::LogVerbatim("TC") << fcnLabel << " success";
2359  keepMerging = true;
2360  break;
2361  }
2362  } // ci2
2363  } // ci1
2364  } // keepMerging
2365 
2366  ChkAssns(fcnLabel, slc);
2367 
2368  } //MergeNearby2DShowers
2369 
2371  void MergeOverlap(std::string inFcnLabel, TCSlice& slc, const CTP_t& inCTP, bool prt)
2372  {
2373  // Merge showers whose envelopes overlap each other
2374 
2375  /*
2376  # 0 Mode (<= 0 OFF, 1 = find showers before 3D match, 2 = find showers after 3D match)
2377  # 1 Max Tj MCSMom for a shower tag
2378  # 2 Max separation
2379  # 3 Min energy (MeV)
2380  # 4 rms width factor
2381  # 5 Min shower 1/2 width (WSE units)
2382  # 6 Min total Tj Pts
2383  # 7 Min Tjs
2384  # 8 max parent FOM
2385  # 9 max direction FOM
2386  # 10 max aspect ratio
2387  # 11 Debug in CTP (>10 debug cotID + 10)
2388  */
2389 
2390  if (tcc.showerTag[2] <= 0) return;
2391  if (!tcc.useAlg[kMergeOverlap]) return;
2392  if (slc.cots.empty()) return;
2393 
2394  std::string fcnLabel = inFcnLabel + ".MO";
2395 
2396  // Require that the maximum separation is about two radiation lengths
2397  if (prt)
2398  mf::LogVerbatim("TC") << fcnLabel << " checking using separation cut " << tcc.showerTag[2];
2399 
2400  float sepCut2 = tcc.showerTag[2] * tcc.showerTag[2];
2401 
2402  // Iterate if a merge is done
2403  bool didMerge = true;
2404  while (didMerge) {
2405  didMerge = false;
2406  // See if the envelopes overlap
2407  for (unsigned short ict = 0; ict < slc.cots.size() - 1; ++ict) {
2408  auto& iss = slc.cots[ict];
2409  if (iss.ID == 0) continue;
2410  if (iss.TjIDs.empty()) continue;
2411  if (iss.CTP != inCTP) continue;
2412  for (unsigned short jct = ict + 1; jct < slc.cots.size(); ++jct) {
2413  auto& jss = slc.cots[jct];
2414  if (jss.ID == 0) continue;
2415  if (jss.TjIDs.empty()) continue;
2416  if (jss.CTP != iss.CTP) continue;
2417  if (DontCluster(slc, iss.TjIDs, jss.TjIDs)) continue;
2418  bool doMerge = false;
2419  for (auto& ivx : iss.Envelope) {
2420  doMerge = PointInsideEnvelope(ivx, jss.Envelope);
2421  if (doMerge) break;
2422  } // ivx
2423  if (!doMerge) {
2424  for (auto& jvx : jss.Envelope) {
2425  doMerge = PointInsideEnvelope(jvx, iss.Envelope);
2426  if (doMerge) break;
2427  } // ivx
2428  }
2429  if (!doMerge) {
2430  // check proximity between the envelopes
2431  for (auto& ivx : iss.Envelope) {
2432  for (auto& jvx : jss.Envelope) {
2433  if (PosSep2(ivx, jvx) < sepCut2) {
2434  if (prt)
2435  mf::LogVerbatim("TC")
2436  << fcnLabel << " Envelopes 2S" << iss.ID << " 2S" << jss.ID << " are close "
2437  << PosSep(ivx, jvx) << " cut " << tcc.showerTag[2];
2438  doMerge = true;
2439  break;
2440  }
2441  } // jvx
2442  if (doMerge) break;
2443  } // ivx
2444  } // !domerge
2445  if (!doMerge) continue;
2446  // check the relative positions and angle differences. Determine which tps are the
2447  // closest. Don't merge if the closest points are at the shower start and the angle
2448  // difference is large
2449  unsigned short iClosePt = 0;
2450  unsigned short jClosePt = 0;
2451  float close = 1E6;
2452  auto& istj = slc.tjs[iss.ShowerTjID - 1];
2453  auto& jstj = slc.tjs[jss.ShowerTjID - 1];
2454  for (unsigned short ipt = 0; ipt < 3; ++ipt) {
2455  for (unsigned short jpt = 0; jpt < 3; ++jpt) {
2456  float sep = PosSep2(istj.Pts[ipt].Pos, jstj.Pts[jpt].Pos);
2457  if (sep < close) {
2458  close = sep;
2459  iClosePt = ipt;
2460  jClosePt = jpt;
2461  }
2462  } // jpt
2463  } // ipt
2464  float costh = DotProd(istj.Pts[0].Dir, jstj.Pts[0].Dir);
2465  if (iClosePt == 0 && jClosePt == 0 && costh < 0.955) {
2466  if (prt)
2467  mf::LogVerbatim("TC")
2468  << fcnLabel << " showers are close at the start points with costh " << costh
2469  << ". Don't merge";
2470  continue;
2471  }
2472  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Merge " << iss.ID << " and " << jss.ID;
2473  if (MergeShowersAndStore(fcnLabel, slc, iss.ID, jss.ID, prt)) {
2474  Trajectory& stj = slc.tjs[iss.ShowerTjID - 1];
2475  stj.AlgMod[kMergeOverlap] = true;
2476  didMerge = true;
2477  break;
2478  }
2479  else {
2480  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Merge failed";
2481  }
2482  } // jct
2483  } // ict
2484  } // didMerge
2485 
2486  ChkAssns(fcnLabel, slc);
2487 
2488  } // MergeOverlap
2489 
2491  void MergeShowerChain(std::string inFcnLabel, TCSlice& slc, const CTP_t& inCTP, bool prt)
2492  {
2493  // Merge chains of 3 or more showers that lie on a line
2494 
2495  if (!tcc.useAlg[kMergeShChain]) return;
2496 
2497  std::string fcnLabel = inFcnLabel + ".MSC";
2498 
2499  if (prt) mf::LogVerbatim("TC") << fcnLabel << ": MergeShowerChain inCTP " << inCTP;
2500 
2501  std::vector<int> sids;
2502  std::vector<TrajPoint> tpList;
2503  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
2504  ShowerStruct& iss = slc.cots[ict];
2505  if (iss.ID == 0) continue;
2506  if (iss.TjIDs.empty()) continue;
2507  if (iss.CTP != inCTP) continue;
2508  // ignore wimpy showers
2509  if (iss.Energy < 50) continue;
2510  // save the shower ID
2511  sids.push_back(iss.ID);
2512  // and the shower center TP
2513  tpList.push_back(slc.tjs[iss.ShowerTjID - 1].Pts[1]);
2514  } // ict
2515  if (sids.size() < 3) return;
2516 
2517  // sort by wire so the chain order is reasonable
2518  std::vector<SortEntry> sortVec(sids.size());
2519  for (unsigned short ii = 0; ii < sortVec.size(); ++ii) {
2520  sortVec[ii].index = ii;
2521  sortVec[ii].val = tpList[ii].Pos[0];
2522  }
2523  std::sort(sortVec.begin(), sortVec.end(), valsDecreasing);
2524  auto tsids = sids;
2525  auto ttpList = tpList;
2526  for (unsigned short ii = 0; ii < sortVec.size(); ++ii) {
2527  unsigned short indx = sortVec[ii].index;
2528  sids[ii] = tsids[indx];
2529  tpList[ii] = ttpList[indx];
2530  }
2531 
2532  // TODO: These cuts should be generalized somehow
2533  float minSep = 150;
2534  float maxDelta = 30;
2535  for (unsigned short ii = 0; ii < sids.size() - 2; ++ii) {
2536  auto& iss = slc.cots[sids[ii] - 1];
2537  if (iss.ID == 0) continue;
2538  unsigned short jj = ii + 1;
2539  auto& jss = slc.cots[sids[jj] - 1];
2540  if (jss.ID == 0) continue;
2541  std::vector<int> chain;
2542  float sepij = PosSep(tpList[ii].Pos, tpList[jj].Pos);
2543  if (sepij > minSep) continue;
2544  bool skipit = DontCluster(slc, iss.TjIDs, jss.TjIDs);
2545  if (prt)
2546  mf::LogVerbatim("TC") << fcnLabel << " i2S" << iss.ID << " " << PrintPos(tpList[ii].Pos)
2547  << " j2S" << jss.ID << " " << PrintPos(tpList[jj].Pos) << " sepij "
2548  << sepij << " skipit? " << skipit;
2549  if (skipit) continue;
2550  // draw a line between these points
2551  TrajPoint tp;
2552  MakeBareTrajPoint(tpList[ii], tpList[jj], tp);
2553  for (unsigned short kk = jj + 1; kk < sids.size(); ++kk) {
2554  auto& kss = slc.cots[sids[kk] - 1];
2555  if (kss.ID == 0) continue;
2556  if (DontCluster(slc, iss.TjIDs, kss.TjIDs)) continue;
2557  if (DontCluster(slc, jss.TjIDs, kss.TjIDs)) continue;
2558  float sepjk = PosSep(tpList[jj].Pos, tpList[kk].Pos);
2559  float delta = PointTrajDOCA(tpList[kk].Pos[0], tpList[kk].Pos[1], tp);
2560  if (prt) {
2561  mf::LogVerbatim myprt("TC");
2562  myprt << fcnLabel << " k2S" << kss.ID << " " << PrintPos(tpList[kk].Pos) << " sepjk "
2563  << sepjk << " delta " << delta;
2564  if (sepjk > minSep || delta > maxDelta) {
2565  myprt << " failed separation " << minSep << " or delta cut " << maxDelta;
2566  }
2567  else {
2568  myprt << " add to the chain";
2569  }
2570  } // prt
2571  if (sepjk > minSep || delta > maxDelta) {
2572  // clear a short chain?
2573  if (chain.size() > 2) {
2574  // merge this chain
2575  int newID = MergeShowers(fcnLabel, slc, chain, prt);
2576  if (prt) {
2577  mf::LogVerbatim myprt("TC");
2578  myprt << fcnLabel << " merged chain";
2579  for (auto ssID : chain)
2580  myprt << " 2S" << ssID;
2581  myprt << " -> 2S" << newID;
2582  } // prt
2583  } // long chain
2584  chain.clear();
2585  break;
2586  }
2587  else {
2588  // add this shower to the chain
2589  if (chain.empty()) {
2590  chain.resize(3);
2591  chain[0] = sids[ii];
2592  chain[1] = sids[jj];
2593  chain[2] = sids[kk];
2594  }
2595  else {
2596  chain.push_back(sids[kk]);
2597  }
2598  // Refine the TP position and direction
2599  MakeBareTrajPoint(tpList[ii], tpList[kk], tp);
2600  } // add to an existing chain
2601  } // kk
2602  // push the last one
2603  if (chain.size() > 2) {
2604  int newID = MergeShowers(fcnLabel, slc, chain, prt);
2605  if (prt) {
2606  mf::LogVerbatim myprt("TC");
2607  myprt << fcnLabel << " merged chain";
2608  for (auto ssID : chain)
2609  myprt << " " << ssID;
2610  myprt << " -> new ssID " << newID;
2611  } // prt
2612  } // long chain
2613  } // ii
2614 
2615  ChkAssns(fcnLabel, slc);
2616 
2617  } // MergeShowerChain
2618 
2620  void MergeSubShowersTj(std::string inFcnLabel, TCSlice& slc, const CTP_t& inCTP, bool prt)
2621  {
2622  // merge small showers that are downstream of shower-like tjs. This algorithm is written
2623  // for low-energy showers with are likely to be sparse and poorly defined.
2624 
2625  if (!tcc.useAlg[kMergeSubShowersTj]) return;
2626 
2627  std::string fcnLabel = inFcnLabel + ".MSSTj";
2628 
2629  struct TjSS {
2630  int ssID;
2631  int tjID;
2632  float dang;
2633  };
2634  std::vector<TjSS> tjss;
2635 
2636  // temp vector for DontCluster
2637  std::vector<int> tjid(1);
2638  for (auto& ss : slc.cots) {
2639  if (ss.ID == 0) continue;
2640  if (ss.CTP != inCTP) continue;
2641  // TODO: Evaluate this cut
2642  if (ss.Energy > 300) continue;
2643  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2644  auto stp0 = stj.Pts[0];
2645  float bestDang = 0.3;
2646  int bestTj = 0;
2647  // look for a Tj that has higher energy than the shower
2648  for (auto& tj : slc.tjs) {
2649  if (tj.AlgMod[kKilled]) continue;
2650  if (tj.AlgMod[kHaloTj]) continue;
2651  if (tj.CTP != ss.CTP) continue;
2652  // require that it isn't in any shower
2653  if (tj.SSID > 0) continue;
2654  // require it to be not short
2655  if (NumPtsWithCharge(slc, tj, false) < 10) continue;
2656  // and satisfy the ShowerLike MCSMom cut. It is unlikely to be tagged shower-like
2657  if (tj.MCSMom > tcc.showerTag[1]) continue;
2658  // check consistency
2659  tjid[0] = tj.ID;
2660  if (DontCluster(slc, tjid, ss.TjIDs)) continue;
2661  float tjEnergy = ChgToMeV(tj.TotChg);
2662  // find the end that is furthest away from the shower center
2663  unsigned short farEnd = FarEnd(tj, stj.Pts[1].Pos);
2664  // compare MCSMom at the far end and the near end
2665  unsigned short midpt = 0.5 * (tj.EndPt[0] + tj.EndPt[1]);
2666  float mom1 = MCSMom(slc, tj, tj.EndPt[farEnd], midpt);
2667  float mom2 = MCSMom(slc, tj, tj.EndPt[1 - farEnd], midpt);
2668  float asym = (mom1 - mom2) / (mom1 + mom2);
2669  auto& farTP = tj.Pts[tj.EndPt[farEnd]];
2670  // IP btw the far end TP and the shower center
2671  float doca = PointTrajDOCA(stp0.Pos[0], stp0.Pos[1], farTP);
2672  float sep = PosSep(farTP.Pos, stp0.Pos);
2673  float dang = doca / sep;
2674  if (prt) {
2675  mf::LogVerbatim myprt("TC");
2676  myprt << fcnLabel << " Candidate 2S" << ss.ID << " T" << tj.ID << "_" << farEnd;
2677  myprt << " ShEnergy " << (int)ss.Energy << " tjEnergy " << (int)tjEnergy;
2678  myprt << " doca " << doca << " sep " << sep << " dang " << dang << " asym " << asym;
2679  }
2680  if (tjEnergy < ss.Energy) continue;
2681  if (asym < 0.5) continue;
2682  // TODO: This should be done more carefully
2683  // separation cut 100 WSE ~ 30 cm in uB
2684  if (sep > 100) continue;
2685  if (dang > bestDang) continue;
2686  bestDang = dang;
2687  bestTj = tj.ID;
2688  } // tj
2689  if (bestTj == 0) continue;
2690  TjSS match;
2691  match.ssID = ss.ID;
2692  match.tjID = bestTj;
2693  match.dang = bestDang;
2694  tjss.push_back(match);
2695  } // ss
2696 
2697  if (tjss.empty()) return;
2698 
2699  // ensure that a tj is only put in one shower
2700  bool keepGoing = true;
2701  while (keepGoing) {
2702  keepGoing = false;
2703  float bestDang = 0.3;
2704  int bestMatch = 0;
2705  for (unsigned short mat = 0; mat < tjss.size(); ++mat) {
2706  auto& match = tjss[mat];
2707  // already used
2708  if (match.dang < 0) continue;
2709  if (match.dang < bestDang) bestMatch = mat;
2710  } // mat
2711  if (bestMatch > 0) {
2712  auto& match = tjss[bestMatch];
2713  auto& ss = slc.cots[match.ssID - 1];
2714  if (!AddTj(fcnLabel, slc, match.tjID, ss, true, prt)) {
2715  if (prt) mf::LogVerbatim("TC") << " Failed";
2716  continue;
2717  }
2718  match.dang = -1;
2719  // set the AlgMod bit
2720  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2721  stj.AlgMod[kMergeSubShowersTj] = true;
2722  keepGoing = true;
2723  } // found bestMatch
2724  } // keepGoing
2725 
2726  ChkAssns(fcnLabel, slc);
2727 
2728  } // MergeSubShowersTj
2729 
2731  void MergeSubShowers(std::string inFcnLabel, TCSlice& slc, const CTP_t& inCTP, bool prt)
2732  {
2733  // Merge small showers that are downstream of larger showers
2734 
2735  if (!tcc.useAlg[kMergeSubShowers]) return;
2736 
2737  std::string fcnLabel = inFcnLabel + ".MSS";
2738  bool newCuts = (tcc.showerTag[0] == 4);
2739  constexpr float radLen = 14 / 0.3;
2740 
2741  if (prt) {
2742  if (newCuts) {
2743  mf::LogVerbatim("TC") << fcnLabel << " MergeSubShowers checking using ShowerParams";
2744  }
2745  else {
2746  mf::LogVerbatim("TC") << fcnLabel
2747  << " MergeSubShowers checking using radiation length cut ";
2748  }
2749  } // prt
2750 
2751  bool keepMerging = true;
2752  while (keepMerging) {
2753  keepMerging = false;
2754  // sort by decreasing energy
2755  std::vector<SortEntry> sortVec;
2756  for (auto& ss : slc.cots) {
2757  if (ss.ID == 0) continue;
2758  if (ss.CTP != inCTP) continue;
2759  SortEntry se;
2760  se.index = ss.ID - 1;
2761  se.val = ss.Energy;
2762  sortVec.push_back(se);
2763  } // ss
2764  if (sortVec.size() < 2) return;
2765  std::sort(sortVec.begin(), sortVec.end(), valsIncreasing);
2766  for (unsigned short ii = 0; ii < sortVec.size() - 1; ++ii) {
2767  ShowerStruct& iss = slc.cots[sortVec[ii].index];
2768  if (iss.ID == 0) continue;
2769  // this shouldn't be done to showers that are ~round
2770  if (iss.AspectRatio > 0.5) continue;
2771  TrajPoint& istp1 = slc.tjs[iss.ShowerTjID - 1].Pts[1];
2772  double shMaxAlong, along95;
2773  ShowerParams((double)iss.Energy, shMaxAlong, along95);
2774  // convert along95 to a separation btw shower max and along95
2775  along95 -= shMaxAlong;
2776  // convert to WSE
2777  along95 /= tcc.wirePitch;
2778  for (unsigned short jj = ii + 1; jj < sortVec.size(); ++jj) {
2779  ShowerStruct& jss = slc.cots[sortVec[jj].index];
2780  if (jss.ID == 0) continue;
2781  if (DontCluster(slc, iss.TjIDs, jss.TjIDs)) continue;
2782  TrajPoint& jstp1 = slc.tjs[jss.ShowerTjID - 1].Pts[1];
2783  if (newCuts) {
2784  // find the longitudinal and transverse separation using the higher energy
2785  // shower which probably is better defined.
2786  Point2_t alongTrans;
2787  FindAlongTrans(istp1.Pos, istp1.Dir, jstp1.Pos, alongTrans);
2788  // the lower energy shower is at the wrong end of the higher energy shower if alongTrans[0] < 0
2789  if (alongTrans[0] < 0) continue;
2790  // increase the cut if the second shower is < 10% of the first shower
2791  float alongCut = along95;
2792  if (jss.Energy < 0.1 * iss.Energy) alongCut *= 1.5;
2793  float probLong = InShowerProbLong(iss.Energy, alongTrans[0]);
2794  float probTran = InShowerProbTrans(iss.Energy, alongTrans[0], alongTrans[1]);
2795  if (prt) {
2796  mf::LogVerbatim myprt("TC");
2797  myprt << fcnLabel << " Candidate i2S" << iss.ID << " E = " << (int)iss.Energy
2798  << " j2S" << jss.ID << " E = " << (int)jss.Energy;
2799  myprt << " along " << std::fixed << std::setprecision(1) << alongTrans[0] << " trans "
2800  << alongTrans[1];
2801  myprt << " alongCut " << alongCut << " probLong " << probLong << " probTran "
2802  << probTran;
2803  } // prt
2804  if (alongTrans[0] > alongCut) continue;
2805  if (alongTrans[1] > alongTrans[0]) continue;
2806  }
2807  else {
2808  // old cuts
2809  float sep = PosSep(istp1.Pos, jstp1.Pos);
2810  float trad = sep / radLen;
2811  // Find the IP between them using the projection of the one with the lowest aspect ratio
2812  float delta = 9999;
2813  if (iss.AspectRatio < jss.AspectRatio) {
2814  delta = PointTrajDOCA(jstp1.Pos[0], jstp1.Pos[1], istp1);
2815  }
2816  else {
2817  delta = PointTrajDOCA(istp1.Pos[0], istp1.Pos[1], jstp1);
2818  }
2819  // See if delta is consistent with the cone angle of the i shower
2820  float dang = delta / sep;
2821  if (prt)
2822  mf::LogVerbatim("TC") << fcnLabel << " Candidate i2S" << iss.ID << " j2S" << jss.ID
2823  << " separation " << (int)sep << " radiation lengths " << trad
2824  << " delta " << delta << " dang " << dang;
2825  if (trad > 3) continue;
2826  // There must be a correlation between dang and the energy of these showers...
2827  if (dang > 0.3) continue;
2828  } // old cuts
2829 
2830  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Merge them. Re-find shower center, etc";
2831  if (MergeShowersAndStore(fcnLabel, slc, iss.ID, jss.ID, prt)) {
2832  Trajectory& stj = slc.tjs[iss.ShowerTjID - 1];
2833  stj.AlgMod[kMergeSubShowers] = true;
2834  keepMerging = true;
2835  break;
2836  }
2837  } // jj
2838  if (keepMerging) break;
2839  } // ii
2840  } // keepMerging
2841 
2842  ChkAssns(fcnLabel, slc);
2843 
2844  } // MergeSubShowers
2845 
2847  int MergeShowers(std::string inFcnLabel, TCSlice& slc, std::vector<int> ssIDs, bool prt)
2848  {
2849  // merge a list of showers and return the ID of the merged shower.
2850  // Returns 0 if there was a failure.
2851 
2852  std::string fcnLabel = inFcnLabel + ".MS";
2853  if (ssIDs.size() < 2) return 0;
2854  // check for a valid ID
2855  for (auto ssID : ssIDs)
2856  if (ssID <= 0 || ssID > (int)slc.cots.size()) return 0;
2857  // check for the same CTP and consistent assns
2858  int ss3Assn = 0;
2859  auto& ss0 = slc.cots[ssIDs[0] - 1];
2860  std::vector<int> tjl;
2861  for (auto ssID : ssIDs) {
2862  auto& ss = slc.cots[ssID - 1];
2863  if (ss.CTP != ss0.CTP) return 0;
2864  tjl.insert(tjl.end(), ss.TjIDs.begin(), ss.TjIDs.end());
2865  if (ss.SS3ID > 0 && ss3Assn == 0) ss3Assn = ss.SS3ID;
2866  if (ss.SS3ID > 0 && ss.SS3ID != ss3Assn) return 0;
2867  } // ssID
2868  // ensure the InShower Tjs are valid
2869  for (auto tjID : tjl) {
2870  auto& tj = slc.tjs[tjID - 1];
2871  if (tj.CTP != ss0.CTP || tj.AlgMod[kKilled]) return 0;
2872  } // tjID
2873 
2874  // mark the old showers killed
2875  for (auto ssID : ssIDs) {
2876  auto& ss = slc.cots[ssID - 1];
2877  ss.ID = 0;
2878  // kill the shower Tj
2879  auto& stj = slc.tjs[ss.ShowerTjID - 1];
2880  stj.AlgMod[kKilled] = true;
2881  } // tjID
2882 
2883  // in with the new
2884  auto newss = CreateSS(slc, tjl);
2885  if (newss.ID == 0) return 0;
2886 
2887  for (auto tid : tjl) {
2888  auto& tj = slc.tjs[tid - 1];
2889  tj.SSID = newss.ID;
2890  } // tid
2891  newss.SS3ID = ss3Assn;
2892 
2893  // define the new shower
2894  if (!UpdateShower(fcnLabel, slc, newss, prt)) {
2895  MakeShowerObsolete(fcnLabel, slc, newss, prt);
2896  return 0;
2897  }
2898  // store it
2899  if (!StoreShower(fcnLabel, slc, newss)) {
2900  MakeShowerObsolete(fcnLabel, slc, newss, prt);
2901  return 0;
2902  }
2903  return newss.ID;
2904 
2905  } // MergeShowers
2906 
2908  bool MergeShowersAndStore(std::string inFcnLabel, TCSlice& slc, int icotID, int jcotID, bool prt)
2909  {
2910  // Merge showers using shower indices. The icotID shower is modified in-place.
2911  // The jcotID shower is declared obsolete. This function also re-defines the shower and
2912  // preserves the icotID Parent ID.
2913 
2914  if (icotID <= 0 || icotID > (int)slc.cots.size()) return false;
2915  ShowerStruct& iss = slc.cots[icotID - 1];
2916  if (iss.ID == 0) return false;
2917  if (iss.TjIDs.empty()) return false;
2918  if (iss.ShowerTjID <= 0) return false;
2919 
2920  if (jcotID <= 0 || jcotID > (int)slc.cots.size()) return false;
2921  ShowerStruct& jss = slc.cots[jcotID - 1];
2922  if (jss.TjIDs.empty()) return false;
2923  if (jss.ID == 0) return false;
2924  if (jss.ShowerTjID <= 0) return false;
2925 
2926  if (iss.CTP != jss.CTP) return false;
2927 
2928  std::string fcnLabel = inFcnLabel + ".MSAS";
2929 
2930  if (iss.SS3ID > 0 && jss.SS3ID > 0 && iss.SS3ID != jss.SS3ID) return false;
2931 
2932  Trajectory& itj = slc.tjs[iss.ShowerTjID - 1];
2933  Trajectory& jtj = slc.tjs[jss.ShowerTjID - 1];
2934  if (!itj.Pts[1].Hits.empty() || !jtj.Pts[1].Hits.empty()) return false;
2935 
2936  iss.TjIDs.insert(iss.TjIDs.end(), jss.TjIDs.begin(), jss.TjIDs.end());
2937  // make a new trajectory using itj as a template
2938  Trajectory ktj = itj;
2939  ktj.ID = slc.tjs.size() + 1;
2940 
2941  slc.tjs.push_back(ktj);
2942  // kill jtj
2943  MakeTrajectoryObsolete(slc, iss.ShowerTjID - 1);
2944  MakeTrajectoryObsolete(slc, jss.ShowerTjID - 1);
2945  slc.tjs[iss.ShowerTjID - 1].ParentID = ktj.ID;
2946  slc.tjs[jss.ShowerTjID - 1].ParentID = ktj.ID;
2947  if (prt)
2948  mf::LogVerbatim("TC") << fcnLabel << " killed stj T" << iss.ShowerTjID << " and T"
2949  << jss.ShowerTjID << " new T" << ktj.ID;
2950  // revise the shower
2951  iss.ShowerTjID = ktj.ID;
2952  // transfer the list of Tj IDs
2953  iss.TjIDs.insert(iss.TjIDs.end(), jss.TjIDs.begin(), jss.TjIDs.begin());
2954  std::sort(iss.TjIDs.begin(), iss.TjIDs.end());
2955  // correct the assn
2956  for (auto tid : iss.TjIDs) {
2957  auto& tj = slc.tjs[tid - 1];
2958  tj.SSID = iss.ID;
2959  } // tid
2960  // transfer a 2S -> 3S assn
2961  if (iss.SS3ID == 0 && jss.SS3ID > 0) iss.SS3ID = jss.SS3ID;
2962  // merge the list of nearby Tjs
2963  iss.NearTjIDs.insert(iss.NearTjIDs.end(), jss.NearTjIDs.begin(), jss.NearTjIDs.end());
2964  // transfer the TruParentID if it is in jss
2965  if (jss.TruParentID > 0) iss.TruParentID = jss.TruParentID;
2966  iss.NeedsUpdate = true;
2967  // force a full update
2968  iss.ShPts.clear();
2969  jss.ID = 0;
2970  bool success = UpdateShower(fcnLabel, slc, iss, prt);
2971  KillVerticesInShower(fcnLabel, slc, iss, prt);
2972 
2973  return success;
2974 
2975  } // MergeShowersAndStore
2976 
2978  bool MergeShowerTjsAndStore(TCSlice& slc, unsigned short istj, unsigned short jstj, bool prt)
2979  {
2980  // Merge showers using showerTj indices
2981  // This function is called from MergeAndStore whose function is to merge two line-like
2982  // trajectories and store them. This function was called because at least one of the
2983  // trajectories is a shower Tj. Assume that the decision to merge them has been made elsewhere.
2984 
2985  if (istj > slc.tjs.size() - 1) return false;
2986  if (jstj > slc.tjs.size() - 1) return false;
2987 
2988  Trajectory& itj = slc.tjs[istj];
2989  Trajectory& jtj = slc.tjs[jstj];
2990 
2991  std::string fcnLabel = "MSTJ";
2992 
2993  if (prt)
2994  mf::LogVerbatim("TC") << fcnLabel << " MergeShowerTjsAndStore Tj IDs " << itj.ID << " "
2995  << jtj.ID;
2996 
2997  // First we check to make sure that both are shower Tjs.
2998  if (!itj.AlgMod[kShowerTj] && !jtj.AlgMod[kShowerTj]) {
2999  if (prt) mf::LogVerbatim("TC") << " One of these isn't a shower Tj";
3000  return false;
3001  }
3002 
3003  // We need to keep the convention used in MergeAndStore to create a new merged trajectory
3004  // and kill the two fragments. This doesn't require making a new shower however. We can just
3005  // re-purpose one of the existing showers
3006  int icotID = GetCotID(slc, itj.ID);
3007  if (icotID == 0) return false;
3008  ShowerStruct& iss = slc.cots[icotID - 1];
3009  if (iss.ID == 0) return false;
3010  if (iss.TjIDs.empty()) return false;
3011  int jcotID = GetCotID(slc, jtj.ID);
3012  if (jcotID == 0) return false;
3013  ShowerStruct& jss = slc.cots[jcotID - 1];
3014  if (jss.ID == 0) return false;
3015  if (jss.TjIDs.empty()) return false;
3016 
3017  return MergeShowersAndStore(fcnLabel, slc, icotID, jcotID, prt);
3018 
3019  } // MergeShowerTjsAndStore
3020 
3022  bool AnalyzeRotPos(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3023  {
3024  // The RotPos vector was filled and sorted by increasing distance along the shower axis.
3025  // This function divides the RotPos points into 3 sections and puts the transverse rms width in the
3026  // three sections into the shower Tj TrajPoint DeltaRMS variable. It also calculates the charge and number of shower
3027  // points closest to each TrajPoint. The
3028 
3029  if (ss.ID == 0) return false;
3030  if (ss.ShPts.empty()) return false;
3031  Trajectory& stj = slc.tjs[ss.ShowerTjID - 1];
3032  if (stj.Pts.size() != 3) return false;
3033 
3034  std::string fcnLabel = inFcnLabel + ".ARP";
3035 
3036  for (auto& tp : stj.Pts) {
3037  tp.Chg = 0;
3038  tp.DeltaRMS = 0;
3039  tp.NTPsFit = 0;
3040  tp.HitPos = {{0.0, 0.0}};
3041  }
3042 
3043  float minAlong = ss.ShPts[0].RotPos[0];
3044  float maxAlong = ss.ShPts[ss.ShPts.size() - 1].RotPos[0];
3045  float sectionLength = (maxAlong - minAlong) / 3;
3046  float sec0 = minAlong + sectionLength;
3047  float sec2 = maxAlong - sectionLength;
3048  // iterate over the shower points (aka hits)
3049  for (auto& spt : ss.ShPts) {
3050  // The point on the shower Tj to which the charge will assigned
3051  unsigned short ipt = 0;
3052  if (spt.RotPos[0] < sec0) {
3053  // closest to point 0
3054  ipt = 0;
3055  }
3056  else if (spt.RotPos[0] > sec2) {
3057  // closest to point 2
3058  ipt = 2;
3059  }
3060  else {
3061  // closest to point 1
3062  ipt = 1;
3063  }
3064  stj.Pts[ipt].Chg += spt.Chg;
3065  // Average the absolute value of the transverse position in lieu of
3066  // using the sum of the squares. The result is ~15% higher than the actual
3067  // rms which is OK since this is used to find the transverse size of the shower
3068  // which is not a precisely defined quantity anyway
3069  stj.Pts[ipt].DeltaRMS += spt.Chg * std::abs(spt.RotPos[1]);
3070  ++stj.Pts[ipt].NTPsFit;
3071  // Average the charge center at each point
3072  stj.Pts[ipt].HitPos[0] += spt.Chg * spt.Pos[0];
3073  stj.Pts[ipt].HitPos[1] += spt.Chg * spt.Pos[1];
3074  } // spt
3075 
3076  for (auto& tp : stj.Pts) {
3077  if (tp.Chg > 0) {
3078  tp.DeltaRMS /= tp.Chg;
3079  tp.HitPos[0] /= tp.Chg;
3080  tp.HitPos[1] /= tp.Chg;
3081  }
3082  } // tp
3083 
3084  // require that there is charge in point 0 and 2. Point 1 may not have charge if
3085  // we are constructing a sparse shower that is not yet well-defined
3086  if (stj.Pts[0].Chg == 0 || stj.Pts[2].Chg == 0) return false;
3087 
3088  // ensure that the charge center is defined
3089  if (stj.Pts[1].Chg == 0) {
3090  // do a simple interpolation
3091  stj.Pts[1].HitPos[0] = 0.5 * (stj.Pts[0].HitPos[0] + stj.Pts[2].HitPos[0]);
3092  stj.Pts[1].HitPos[1] = 0.5 * (stj.Pts[0].HitPos[1] + stj.Pts[2].HitPos[1]);
3093  }
3094  if (stj.Pts[2].DeltaRMS > 0) { ss.DirectionFOM = stj.Pts[0].DeltaRMS / stj.Pts[2].DeltaRMS; }
3095  else {
3096  ss.DirectionFOM = 10;
3097  }
3098  if (prt) {
3099  mf::LogVerbatim myprt("TC");
3100  myprt << fcnLabel << " 2S" << ss.ID;
3101  myprt << " HitPos[0] " << std::fixed << std::setprecision(1);
3102  myprt << stj.Pts[1].HitPos[0] << " " << stj.Pts[1].HitPos[1] << " " << stj.Pts[1].HitPos[2];
3103  myprt << " DeltaRMS " << std::setprecision(2);
3104  myprt << stj.Pts[0].DeltaRMS << " " << stj.Pts[1].DeltaRMS << " " << stj.Pts[2].DeltaRMS;
3105  myprt << " DirectionFOM " << std::fixed << std::setprecision(2) << ss.DirectionFOM;
3106  }
3107  return true;
3108 
3109  } // AnalyzeRotPos
3110 
3112  void ReverseShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3113  {
3114  // Reverses the shower and the shower tj
3115 
3116  if (ss.ID == 0) return;
3117  if (ss.TjIDs.empty()) return;
3118 
3119  std::string fcnLabel = inFcnLabel + ".RevSh";
3120 
3121  std::reverse(ss.ShPts.begin(), ss.ShPts.end());
3122  // change the sign of RotPos
3123  for (auto& sspt : ss.ShPts) {
3124  sspt.RotPos[0] = -sspt.RotPos[0];
3125  sspt.RotPos[1] = -sspt.RotPos[1];
3126  }
3127  // flip the shower angle
3128  if (ss.Angle > 0) { ss.Angle -= M_PI; }
3129  else {
3130  ss.Angle += M_PI;
3131  }
3132  if (ss.DirectionFOM != 0) ss.DirectionFOM = 1 / ss.DirectionFOM;
3133  auto& stj = slc.tjs[ss.ShowerTjID - 1];
3134  ReverseTraj(stj);
3135  DefineEnvelope(fcnLabel, slc, ss, prt);
3136  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Reversed shower. Shower angle = " << ss.Angle;
3137  } // ReverseShower
3138 
3140  void ReverseShower(std::string inFcnLabel, TCSlice& slc, int cotID, bool prt)
3141  {
3142  // Reverses the shower and the shower tj
3143 
3144  if (cotID > (int)slc.cots.size()) return;
3145  ShowerStruct& ss = slc.cots[cotID - 1];
3146  if (ss.ID == 0) return;
3147  ReverseShower(inFcnLabel, slc, ss, prt);
3148  }
3149 
3151  void MakeShowerObsolete(std::string inFcnLabel, TCSlice& slc, ShowerStruct3D& ss3, bool prt)
3152  {
3153  // set the ss3 ID = 0 and remove 2D shower -> 3D shower associations. The 2D showers are not
3154  // declared obsolete
3155  for (auto cid : ss3.CotIDs) {
3156  if (cid == 0 || (unsigned short)cid > slc.cots.size()) continue;
3157  auto& ss = slc.cots[cid - 1];
3158  if (ss.SS3ID > 0 && ss.SS3ID != ss3.ID) continue;
3159  ss.SS3ID = 0;
3160  } // cid
3161  if (prt) {
3162  std::string fcnLabel = inFcnLabel + ".MSO";
3163  mf::LogVerbatim("TC") << fcnLabel << " Killed 3S" << ss3.ID;
3164  }
3165  ss3.ID = 0;
3166  } // MakeShowerObsolete
3167 
3169  void MakeShowerObsolete(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3170  {
3171  // Gracefully kills the shower and the associated shower Tj
3172 
3173  if (ss.ID == 0) return;
3174 
3175  std::string fcnLabel = inFcnLabel + ".MSO";
3176 
3177  auto& stp1 = slc.tjs[ss.ShowerTjID - 1].Pts[1];
3178  if (!stp1.Hits.empty()) return;
3179 
3180  // clear a 3S -> 2S assn
3181  if (ss.SS3ID > 0 && ss.SS3ID <= (int)slc.showers.size()) {
3182  auto& ss3 = slc.showers[ss.SS3ID - 1];
3183  std::vector<int> newCIDs;
3184  for (auto cid : ss3.CotIDs) {
3185  if (cid != ss.ID) newCIDs.push_back(cid);
3186  } // cid
3187  ss3.CotIDs = newCIDs;
3188  } // ss3 assn exists
3189 
3190  // Kill the shower Tj if it exists. This also releases the hits
3191  if (ss.ShowerTjID > 0) MakeTrajectoryObsolete(slc, ss.ShowerTjID - 1);
3192 
3193  // Restore the original InShower Tjs
3194  // Unset the killed bit
3195  for (auto& tjID : ss.TjIDs) {
3196  Trajectory& tj = slc.tjs[tjID - 1];
3197  tj.AlgMod[kKilled] = false;
3198  // clear all of the shower-related bits
3199  tj.SSID = 0;
3200  tj.AlgMod[kShwrParent] = false;
3201  tj.AlgMod[kMergeOverlap] = false;
3202  tj.AlgMod[kMergeSubShowers] = false;
3203  tj.AlgMod[kMergeNrShowers] = false;
3204  tj.AlgMod[kMergeShChain] = false;
3205  } // tjID
3206  if (prt)
3207  mf::LogVerbatim("TC") << fcnLabel << " Killed 2S" << ss.ID << " and ST" << ss.ShowerTjID;
3208  ss.ID = 0;
3209 
3210  } // MakeShowerObsolete
3211 
3213  bool DontCluster(TCSlice const& slc,
3214  const std::vector<int>& tjlist1,
3215  const std::vector<int>& tjlist2)
3216  {
3217  // returns true if a pair of tjs in the two lists are in the dontCluster vector
3218  if (tjlist1.empty() || tjlist2.empty()) return false;
3219  if (slc.dontCluster.empty()) return false;
3220  for (auto tid1 : tjlist1) {
3221  for (auto tid2 : tjlist2) {
3222  int ttid1 = tid1;
3223  if (ttid1 > tid2) std::swap(ttid1, tid2);
3224  for (auto& dc : slc.dontCluster)
3225  if (dc.TjIDs[0] == ttid1 && dc.TjIDs[1] == tid2) return true;
3226  } // dc
3227  } // tid1
3228  return false;
3229  } // DontCluster
3230 
3232  void TagShowerLike(TCSlice& slc, const CTP_t& inCTP)
3233  {
3234  // Tag Tjs as InShower if they have MCSMom < ShowerTag[1] and there are more than
3235  // ShowerTag[6] other Tjs with a separation < ShowerTag[2].
3236 
3237  if (tcc.showerTag[0] <= 0) return;
3238  if (slc.tjs.size() > 20000) return;
3239  float typicalChgRMS = 0.5 * (tcc.chargeCuts[1] + tcc.chargeCuts[2]);
3240 
3241  bool prt = (tcc.dbgSlc && tcc.dbg2S && inCTP == debug.CTP);
3242 
3243  // clear out old tags and make a list of Tjs to consider
3244  std::vector<std::vector<int>> tjLists;
3245  std::vector<int> tjids;
3246  for (auto& tj : slc.tjs) {
3247  if (tj.CTP != inCTP) continue;
3248  if (tj.AlgMod[kKilled]) continue;
3249  if (tj.AlgMod[kHaloTj]) continue;
3250  tj.AlgMod[kShowerLike] = false;
3251  if (tj.AlgMod[kShowerTj]) continue;
3252  // ignore Tjs with Bragg peaks
3253  bool skipit = false;
3254  for (unsigned short end = 0; end < 2; ++end)
3255  if (tj.EndFlag[end][kBragg]) skipit = true;
3256  if (skipit) continue;
3257  short npwc = NumPtsWithCharge(slc, tj, false);
3258  // Don't expect any (primary) electron to be reconstructed as a single trajectory for
3259  // more than ~2 radiation lengths ~ 30 cm for uB ~ 100 wires
3260  if (npwc > 100) continue;
3261  // allow short Tjs.
3262  if (npwc > 5) {
3263  // Increase the MCSMom cut if the Tj is long and the charge RMS is high to reduce sensitivity
3264  // to the fcl configuration. A primary electron may be reconstructed as one long Tj with large
3265  // charge rms and possibly high MCSMom or as several nearby shorter Tjs with lower charge rms
3266  float momCut = tcc.showerTag[1];
3267  if (tj.ChgRMS > typicalChgRMS) momCut *= tj.ChgRMS / typicalChgRMS;
3268  if (tj.MCSMom > momCut) continue;
3269  }
3270  tjids.push_back(tj.ID);
3271  } // tj
3272 
3273  if (tjids.size() < 2) return;
3274 
3275  for (unsigned short it1 = 0; it1 < tjids.size() - 1; ++it1) {
3276  Trajectory& tj1 = slc.tjs[tjids[it1] - 1];
3277  for (unsigned short it2 = it1 + 1; it2 < tjids.size(); ++it2) {
3278  Trajectory& tj2 = slc.tjs[tjids[it2] - 1];
3279  unsigned short ipt1, ipt2;
3280  float doca = tcc.showerTag[2];
3281  // Find the separation between Tjs without considering dead wires
3282  TrajTrajDOCA(slc, tj1, tj2, ipt1, ipt2, doca, false);
3283  if (doca == tcc.showerTag[2]) continue;
3284  // make tighter cuts for user-defined short Tjs
3285  // found a close pair. See if one of these is in an existing cluster of Tjs
3286  bool inlist = false;
3287  for (unsigned short it = 0; it < tjLists.size(); ++it) {
3288  bool tj1InList =
3289  (std::find(tjLists[it].begin(), tjLists[it].end(), tj1.ID) != tjLists[it].end());
3290  bool tj2InList =
3291  (std::find(tjLists[it].begin(), tjLists[it].end(), tj2.ID) != tjLists[it].end());
3292  if (tj1InList || tj2InList) {
3293  // add the one that is not in the list
3294  if (!tj1InList) tjLists[it].push_back(tj1.ID);
3295  if (!tj2InList) tjLists[it].push_back(tj2.ID);
3296  inlist = true;
3297  break;
3298  }
3299  if (inlist) break;
3300  } // it
3301  // start a new list with this pair?
3302  if (!inlist) {
3303  std::vector<int> newlist(2);
3304  newlist[0] = tj1.ID;
3305  newlist[1] = tj2.ID;
3306  tjLists.push_back(newlist);
3307  }
3308  } // it2
3309  } // it1
3310  if (tjLists.empty()) return;
3311 
3312  // mark them all as ShowerLike Tjs
3313  for (auto& tjl : tjLists) {
3314  // ignore small clusters
3315  if (tjl.size() < 3) continue;
3316  for (auto& tjID : tjl) {
3317  auto& tj = slc.tjs[tjID - 1];
3318  tj.AlgMod[kShowerLike] = true;
3319  } // tjid
3320  } // tjl
3321 
3322  if (prt) {
3323  unsigned short nsh = 0;
3324  for (auto& tjl : tjLists) {
3325  for (auto& tjID : tjl) {
3326  auto& tj = slc.tjs[tjID - 1];
3327  if (tj.AlgMod[kShowerLike]) ++nsh;
3328  } // tjid
3329  } // tjl
3330  mf::LogVerbatim("TC") << "TagShowerLike tagged " << nsh << " Tjs vertices in CTP " << inCTP;
3331  } // prt
3332  } // TagShowerLike
3333 
3335  void FindNearbyTjs(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3336  {
3337  // Find Tjs that are near the shower but are not included in it
3338  ss.NearTjIDs.clear();
3339 
3340  // check for a valid envelope
3341  if (ss.Envelope.empty()) return;
3342  auto& stj = slc.tjs[ss.ShowerTjID - 1];
3343 
3344  std::string fcnLabel = inFcnLabel + ".FNTj";
3345 
3346  std::vector<int> ntj;
3347 
3348  // set max distance of closest approach ~ 5 radiation lengths ~200 WSE
3349  constexpr float fiveRadLen = 200;
3350 
3351  // look for vertices inside the envelope
3352  for (auto vx : slc.vtxs) {
3353  if (vx.CTP != ss.CTP) continue;
3354  if (vx.ID == 0) continue;
3355  if (!PointInsideEnvelope(vx.Pos, ss.Envelope)) continue;
3356  auto vxTjIDs = GetAssns(slc, "2V", vx.ID, "T");
3357  for (auto tjID : vxTjIDs) {
3358  // ignore those that are in the shower
3359  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tjID) != ss.TjIDs.end()) continue;
3360  // or already in the list
3361  if (std::find(ntj.begin(), ntj.end(), tjID) != ntj.end()) continue;
3362  ntj.push_back(tjID);
3363  } // tjID
3364  } // vx
3365 
3366  // Check for tj points inside the envelope
3367  for (auto& tj : slc.tjs) {
3368  if (tj.CTP != ss.CTP) continue;
3369  if (tj.AlgMod[kKilled]) continue;
3370  // not a showerTj
3371  if (tj.AlgMod[kShowerTj]) continue;
3372  // make sure it's not in the shower
3373  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tj.ID) != ss.TjIDs.end()) continue;
3374  // or already in the list
3375  if (std::find(ntj.begin(), ntj.end(), tj.ID) != ntj.end()) continue;
3376  // check proximity of long high MCSMom Tjs to the shower center
3377  if (tj.Pts.size() > 40 && tj.MCSMom > 200) {
3378  float delta = PointTrajDOCA(stj.Pts[1].Pos[0], stj.Pts[1].Pos[1], tj.Pts[tj.EndPt[0]]);
3379  // TODO: This could be done much better
3380  if (delta < 20) {
3381  float doca = fiveRadLen;
3382  unsigned short spt = 0, ipt = 0;
3383  TrajTrajDOCA(slc, stj, tj, spt, ipt, doca);
3384  if (doca < fiveRadLen) {
3385  ntj.push_back(tj.ID);
3386  continue;
3387  }
3388  }
3389  } // long hi-MCSMom tj
3390  // don't need to check every point. Every third should be enough
3391  bool isInside = false;
3392  for (unsigned short ipt = tj.EndPt[0]; ipt < tj.EndPt[1]; ipt += 3) {
3393  if (PointInsideEnvelope(tj.Pts[ipt].Pos, ss.Envelope)) {
3394  isInside = true;
3395  break;
3396  }
3397  } // ipt
3398  // check the last point which was probably missed above
3399  if (!isInside) {
3400  unsigned short ipt = tj.EndPt[1];
3401  isInside = PointInsideEnvelope(tj.Pts[ipt].Pos, ss.Envelope);
3402  }
3403  if (isInside) ntj.push_back(tj.ID);
3404  } // tj
3405  if (ntj.size() > 1) std::sort(ntj.begin(), ntj.end());
3406  if (prt)
3407  mf::LogVerbatim("TC") << fcnLabel << " Found " << ntj.size() << " Tjs near ss.ID " << ss.ID;
3408  ss.NearTjIDs = ntj;
3409 
3410  } // FindNearbyTjs
3411 
3413  void AddCloseTjsToList(TCSlice& slc, unsigned short itj, std::vector<int> list)
3414  {
3415  // Searches the trajectory points for hits that are used in a different trajectory and add
3416  // them to the list if any are found, and the MCSMomentum is not too large
3417  if (itj > slc.tjs.size() - 1) return;
3418 
3419  //short maxMom = (short)(2 * tcc.showerTag[1]);
3420  short maxMom = tcc.showerTag[1];
3421  //XL: why is maxMom is twice of the shower tag [1]?
3422  for (auto& tp : slc.tjs[itj].Pts) {
3423  for (unsigned short ii = 0; ii < tp.Hits.size(); ++ii) {
3424  // ignore hits that are used in this trajectory
3425  if (tp.UseHit[ii]) continue;
3426  unsigned int iht = tp.Hits[ii];
3427  // ignore if there is no hit -> Tj association
3428  if (slc.slHits[iht].InTraj <= 0) continue;
3429  if ((unsigned int)slc.slHits[iht].InTraj > slc.tjs.size()) continue;
3430  // check the momentum
3431  Trajectory& tj = slc.tjs[slc.slHits[iht].InTraj - 1];
3432  if (tj.MCSMom > maxMom) continue;
3433  // if(tj.AlgMod[kTjHiVx3Score]) continue;
3434  // see if it is already in the list
3435  if (std::find(list.begin(), list.end(), slc.slHits[iht].InTraj) != list.end()) continue;
3436  list.push_back(slc.slHits[iht].InTraj);
3437  } // ii
3438  } // tp
3439  } // AddCloseTjsToList
3440 
3442  void DefineEnvelope(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3443  {
3444 
3445  if (ss.ID == 0) return;
3446  if (ss.TjIDs.empty()) return;
3447  Trajectory& stj = slc.tjs[ss.ShowerTjID - 1];
3448  // shower Tj isn't fully defined yet
3449  if (stj.Pts[0].Pos[0] == 0) return;
3450 
3451  std::string fcnLabel = inFcnLabel + ".DE";
3452 
3453  ss.Envelope.resize(4);
3454  TrajPoint& stp0 = stj.Pts[0];
3455  TrajPoint& stp1 = stj.Pts[1];
3456  TrajPoint& stp2 = stj.Pts[2];
3457 
3458  // construct the Envelope polygon. Start with a rectangle using the fixed 1/2 width fcl input
3459  // expanded by the rms width at each end to create a polygon. The polygon is constructed along
3460  // the Pos[0] direction and then rotated into the ShowerTj direction. Use sTp1 as the origin.
3461  // First vertex
3462  ss.Envelope[0][0] = -PosSep(stp0.Pos, stp1.Pos);
3463  ss.Envelope[0][1] = tcc.showerTag[5] + tcc.showerTag[4] * stp0.DeltaRMS;
3464  // second vertex
3465  ss.Envelope[1][0] = PosSep(stp1.Pos, stp2.Pos);
3466  ss.Envelope[1][1] = tcc.showerTag[5] + tcc.showerTag[4] * stp2.DeltaRMS;
3467  // third and fourth are reflections of the first and second
3468  ss.Envelope[2][0] = ss.Envelope[1][0];
3469  ss.Envelope[2][1] = -ss.Envelope[1][1];
3470  ss.Envelope[3][0] = ss.Envelope[0][0];
3471  ss.Envelope[3][1] = -ss.Envelope[0][1];
3472 
3473  float length = ss.Envelope[1][0] - ss.Envelope[0][0];
3474  float width = ss.Envelope[0][1] + ss.Envelope[1][1];
3475  ss.EnvelopeArea = length * width;
3476 
3477  // Rotate into the stp1 coordinate system
3478  float cs = cos(stp1.Ang);
3479  float sn = sin(stp1.Ang);
3480  for (auto& vtx : ss.Envelope) {
3481  // Rotate along the stj shower axis
3482  float pos0 = cs * vtx[0] - sn * vtx[1];
3483  float pos1 = sn * vtx[0] + cs * vtx[1];
3484  // translate
3485  vtx[0] = pos0 + stp1.Pos[0];
3486  vtx[1] = pos1 + stp1.Pos[1];
3487  } // vtx
3488  // Find the charge density inside the envelope
3489  ss.ChgDensity = (stp0.Chg + stp1.Chg + stp2.Chg) / ss.EnvelopeArea;
3490  if (prt) {
3491  mf::LogVerbatim myprt("TC");
3492  myprt << fcnLabel << " 2S" << ss.ID << " Envelope";
3493  for (auto& vtx : ss.Envelope)
3494  myprt << " " << (int)vtx[0] << ":" << (int)(vtx[1] / tcc.unitsPerTick);
3495  myprt << " Area " << (int)ss.EnvelopeArea;
3496  myprt << " ChgDensity " << ss.ChgDensity;
3497  }
3498  // This is the last function used to update a shower
3499  ss.NeedsUpdate = false;
3500  } // DefineEnvelope
3501 
3503  bool AddTjsInsideEnvelope(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss, bool prt)
3504  {
3505  // This function adds Tjs to the shower. It updates the shower parameters.
3506 
3507  if (ss.Envelope.empty()) return false;
3508  if (ss.ID == 0) return false;
3509  if (ss.TjIDs.empty()) return false;
3510 
3511  std::string fcnLabel = inFcnLabel + ".ATIE";
3512 
3513  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Checking 2S" << ss.ID;
3514 
3515  std::vector<int> tmp(1);
3516  unsigned short nadd = 0;
3517  for (auto& tj : slc.tjs) {
3518  if (tj.CTP != ss.CTP) continue;
3519  if (tj.AlgMod[kKilled]) continue;
3520  if (tj.SSID > 0) continue;
3521  if (tj.AlgMod[kShowerTj]) continue;
3522  // See if this Tjs is attached to a neutrino vertex.
3523  if (tj.ParentID == 0) continue;
3524  int neutPrimTj = NeutrinoPrimaryTjID(slc, tj);
3525  if (neutPrimTj > 0 && neutPrimTj != tj.ID) {
3526  // The Tj is connected to a primary Tj that is associated with a neutrino primary.
3527  // Don't allow tjs to be added to the shower that are not connected to this neutrino primary (if
3528  // one exists)
3529  if (ss.ParentID > 0 && neutPrimTj != ss.ParentID) continue;
3530  } // neutrino primary tj
3531  // This shouldn't be necessary but ensure that the Tj ID appears only once in ss.TjIDs
3532  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tj.ID) != ss.TjIDs.end()) continue;
3533  // check consistency
3534  tmp[0] = tj.ID;
3535  if (DontCluster(slc, tmp, ss.TjIDs)) continue;
3536  // See if both ends are outside the envelope
3537  bool end0Inside = PointInsideEnvelope(tj.Pts[tj.EndPt[0]].Pos, ss.Envelope);
3538  bool end1Inside = PointInsideEnvelope(tj.Pts[tj.EndPt[1]].Pos, ss.Envelope);
3539  if (!end0Inside && !end1Inside) continue;
3540  if (end0Inside && end1Inside) {
3541  // TODO: See if the Tj direction is compatible with the shower?
3542  if (AddTj(fcnLabel, slc, tj.ID, ss, false, prt)) ++nadd;
3543  ++nadd;
3544  continue;
3545  } // both ends inside
3546  // Require high momentum Tjs be aligned with the shower axis
3547  // TODO also require high momentum Tjs close to the shower axis?
3548 
3549  if (tj.MCSMom > 200) {
3550  float tjAngle = tj.Pts[tj.EndPt[0]].Ang;
3551  float dangPull = std::abs(tjAngle - ss.AngleErr) / ss.AngleErr;
3552  if (prt)
3553  mf::LogVerbatim("TC") << fcnLabel << " high MCSMom " << tj.MCSMom << " dangPull "
3554  << dangPull;
3555  if (dangPull > 2) continue;
3556  } // high momentum
3557  if (AddTj(fcnLabel, slc, tj.ID, ss, false, prt)) { ++nadd; }
3558  else {
3559  if (prt) mf::LogVerbatim("TC") << fcnLabel << " AddTj failed to add T" << tj.ID;
3560  }
3561  } // tj
3562 
3563  if (nadd > 0) {
3564  if (prt) mf::LogVerbatim("TC") << fcnLabel << " Added " << nadd << " trajectories ";
3565  ss.NeedsUpdate = true;
3566  UpdateShower(fcnLabel, slc, ss, prt);
3567  return true;
3568  }
3569  else {
3570  if (prt) mf::LogVerbatim("TC") << fcnLabel << " No new trajectories added to envelope ";
3571  ss.NeedsUpdate = false;
3572  return false;
3573  }
3574 
3575  } // AddTjsInsideEnvelope
3576 
3578  bool AddLooseHits(TCSlice& slc, int cotID, bool prt)
3579  {
3580  // Add hits that are inside the envelope to the shower if they are loose, i.e. not
3581  // used by any trajectory. This function returns true if the set of hits is different than
3582  // the current set. The calling function should update the shower if this is the case.
3583 
3584  ShowerStruct& ss = slc.cots[cotID - 1];
3585  if (ss.Envelope.empty()) return false;
3586  if (ss.ID == 0) return false;
3587  if (ss.TjIDs.empty()) return false;
3588 
3589  geo::PlaneID planeID = DecodeCTP(ss.CTP);
3590  unsigned short ipl = planeID.Plane;
3591 
3592  Trajectory& stj = slc.tjs[ss.ShowerTjID - 1];
3593  TrajPoint& stp0 = stj.Pts[0];
3594  // start a list of new hits
3595  std::vector<unsigned int> newHits;
3596 
3597  // look for hits inside the envelope. Find the range of wires that spans the envelope
3598  float fLoWire = 1E6;
3599  float fHiWire = 0;
3600  // and the range of ticks
3601  float loTick = 1E6;
3602  float hiTick = 0;
3603  for (auto& vtx : ss.Envelope) {
3604  if (vtx[0] < fLoWire) fLoWire = vtx[0];
3605  if (vtx[0] > fHiWire) fHiWire = vtx[0];
3606  if (vtx[1] < loTick) loTick = vtx[1];
3607  if (vtx[1] > hiTick) hiTick = vtx[1];
3608  } // vtx
3609  loTick /= tcc.unitsPerTick;
3610  hiTick /= tcc.unitsPerTick;
3611  unsigned int loWire = std::nearbyint(fLoWire);
3612  unsigned int hiWire = std::nearbyint(fHiWire) + 1;
3613  if (hiWire > slc.lastWire[ipl] - 1) hiWire = slc.lastWire[ipl] - 1;
3614  std::array<float, 2> point;
3615  for (unsigned int wire = loWire; wire < hiWire; ++wire) {
3616  // skip bad wires or no hits on the wire
3617  if (slc.wireHitRange[ipl][wire].first == UINT_MAX) continue;
3618  unsigned int firstHit = slc.wireHitRange[ipl][wire].first;
3619  unsigned int lastHit = slc.wireHitRange[ipl][wire].second;
3620  for (unsigned int iht = firstHit; iht < lastHit; ++iht) {
3621  // used in a trajectory?
3622  if (slc.slHits[iht].InTraj != 0) continue;
3623  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
3624  // inside the tick range?
3625  if (hit.PeakTime() < loTick) continue;
3626  // Note that hits are sorted by increasing time so we can break here
3627  if (hit.PeakTime() > hiTick) break;
3628  // see if this hit is inside the envelope
3629  point[0] = hit.WireID().Wire;
3630  point[1] = hit.PeakTime() * tcc.unitsPerTick;
3631  if (!PointInsideEnvelope(point, ss.Envelope)) continue;
3632  newHits.push_back(iht);
3633  } // iht
3634  } // wire
3635 
3636  // no new hits and no old hits. Nothing to do
3637  if (newHits.empty()) {
3638  if (prt) mf::LogVerbatim("TC") << "ALH: No new loose hits found";
3639  return false;
3640  }
3641 
3642  // Update
3643  stp0.Hits.insert(stp0.Hits.end(), newHits.begin(), newHits.end());
3644  for (auto& iht : newHits)
3645  slc.slHits[iht].InTraj = stj.ID;
3646 
3647  if (prt)
3648  mf::LogVerbatim("TC") << "ALH: Added " << stp0.Hits.size() << " hits to stj " << stj.ID;
3649  return true;
3650 
3651  } // AddLooseHits
3652 
3654  void FindStartChg(std::string inFcnLabel, TCSlice& slc, int cotID, bool prt)
3655  {
3656  // Finds the charge at the start of a shower and puts it in AveChg of the first
3657  // point of the shower Tj. This is only done when there is no parent.
3658  if (cotID > (int)slc.cots.size()) return;
3659 
3660  ShowerStruct& ss = slc.cots[cotID - 1];
3661  if (ss.ID == 0) return;
3662  if (ss.TjIDs.empty()) return;
3663  if (ss.ShowerTjID == 0) return;
3664  if (ss.ParentID > 0) return;
3665  auto& stp0 = slc.tjs[ss.ShowerTjID - 1].Pts[0];
3666 
3667  std::string fcnLabel = inFcnLabel + ".FSC";
3668 
3669  stp0.AveChg = 0;
3670 
3671  if (ss.AspectRatio > tcc.showerTag[10] || ss.DirectionFOM > tcc.showerTag[9]) {
3672  if (prt)
3673  mf::LogVerbatim("TC") << fcnLabel << " Not possible due to poor AspectRatio "
3674  << ss.AspectRatio << " or ss.DirectionFOM " << ss.DirectionFOM;
3675  return;
3676  }
3677 
3678  // Create and fill a vector of the charge at the beginning of the shower in 1 WSE bins
3679  auto schg = StartChgVec(slc, cotID);
3680  if (schg.empty()) return;
3681 
3682  // Look for two consecutive charge entries. Use the second one
3683  // for the initial guess at the charge
3684  unsigned short startPt = USHRT_MAX;
3685  float chg = 0;
3686  for (unsigned short ii = 0; ii < schg.size() - 1; ++ii) {
3687  if (schg[ii] > 0 && schg[ii + 1] > 0) {
3688  startPt = ii + 1;
3689  chg = schg[ii + 1];
3690  break;
3691  }
3692  }
3693  if (startPt == USHRT_MAX) return;
3694 
3695  // get an initial average and rms using all the points
3696  float ave = 0;
3697  float rms = 0;
3698  float cnt = 0;
3699  for (unsigned short ii = startPt; ii < schg.size() - 1; ++ii) {
3700  ave += schg[ii];
3701  rms += schg[ii] * schg[ii];
3702  ++cnt;
3703  } // ii
3704  ave /= cnt;
3705  rms = rms - cnt * ave * ave;
3706  if (rms < 0) return;
3707  rms = sqrt(rms / (cnt - 1));
3708 
3709  if (prt) {
3710  mf::LogVerbatim myprt("TC");
3711  myprt << fcnLabel << " schg:";
3712  for (unsigned short ii = 0; ii < 20; ++ii)
3713  myprt << " " << (int)schg[ii];
3714  myprt << "\n First guess at the charge " << (int)chg << " average charge of all bins "
3715  << (int)ave << " rms " << (int)rms;
3716  }
3717 
3718  // initial guess at the charge rms
3719  rms = 0.8 * chg;
3720 
3721  unsigned short nBinsAverage = 5;
3722  double maxChg = 2 * chg;
3723  for (unsigned short nit = 0; nit < 2; ++nit) {
3724  double sum = 0;
3725  double sum2 = 0;
3726  double cnt = 0;
3727  for (unsigned short ii = startPt; ii < schg.size() - 1; ++ii) {
3728  // break if we find 2 consecutive high charge points
3729  if (schg[ii] > maxChg && schg[ii + 1] > maxChg) break;
3730  // or two zeros
3731  if (schg[ii] == 0 && schg[ii + 1] == 0) break;
3732  if (schg[ii] > maxChg) continue;
3733  sum += schg[ii];
3734  sum2 += schg[ii] * schg[ii];
3735  ++cnt;
3736  if (cnt == nBinsAverage) break;
3737  } // ii
3738  // check for a failure
3739  if (cnt < 3) {
3740  if (prt)
3741  mf::LogVerbatim("TC") << fcnLabel << " nit " << nit << " cnt " << cnt
3742  << " is too low. sum " << (int)sum << " maxChg " << (int)maxChg;
3743  // try to recover. Pick the next point
3744  ++startPt;
3745  chg = schg[startPt];
3746  maxChg = 2 * chg;
3747  continue;
3748  }
3749  // convert sum to the average charge
3750  chg = sum / cnt;
3751  double arg = sum2 - cnt * chg * chg;
3752  if (arg < 0) break;
3753  rms = sqrt(arg / (cnt - 1));
3754  // don't allow the rms to get crazy
3755  double maxrms = 0.5 * sum;
3756  if (rms > maxrms) rms = maxrms;
3757  maxChg = chg + 2 * rms;
3758  if (prt)
3759  mf::LogVerbatim("TC") << fcnLabel << " nit " << nit << " cnt " << cnt << " chg " << (int)chg
3760  << " rms " << (int)rms << " maxChg " << (int)maxChg
3761  << " nBinsAverage " << nBinsAverage;
3762  nBinsAverage = 20;
3763  } // nit
3764 
3765  stp0.AveChg = chg;
3766 
3767  if (prt)
3768  mf::LogVerbatim("TC") << fcnLabel << " 2S" << cotID << " Starting charge " << (int)stp0.AveChg
3769  << " startPt " << startPt;
3770 
3771  } // FindStartChg
3772 
3774  std::vector<float> StartChgVec(TCSlice& slc, int cotID)
3775  {
3776  // Returns a histogram vector of the charge in bins of 1 WSE unit at the start of the shower
3777 
3778  ShowerStruct& ss = slc.cots[cotID - 1];
3779  constexpr unsigned short nbins = 20;
3780  std::vector<float> schg(nbins);
3781  if (ss.ID == 0) return schg;
3782  if (ss.TjIDs.empty()) return schg;
3783  TrajPoint& stp1 = slc.tjs[ss.ShowerTjID - 1].Pts[1];
3784 
3785  // move the min along point back by 2 WSE so that most of the charge in the hits in the
3786  // first point is included in the histogram
3787  float minAlong = ss.ShPts[0].RotPos[0] - 2;
3788 
3789  float maxTrans = 4;
3790  // Tighten up on the maximum allowed transverse position if there is a parent
3791  if (ss.ParentID > 0) maxTrans = 1;
3792  float cs = cos(-ss.Angle);
3793  float sn = sin(-ss.Angle);
3794  std::array<float, 2> chgPos;
3795  float along, arg;
3796 
3797  for (auto& sspt : ss.ShPts) {
3798  unsigned short indx = (unsigned short)((sspt.RotPos[0] - minAlong));
3799  if (indx > nbins - 1) break;
3800  // Count the charge if it is within a few WSE transverse from the shower axis
3801  if (std::abs(sspt.RotPos[1]) > maxTrans) continue;
3802  unsigned int iht = sspt.HitIndex;
3803  auto& hit = (*evt.allHits)[slc.slHits[iht].allHitsIndex];
3804  float peakTime = hit.PeakTime();
3805  float amp = hit.PeakAmplitude();
3806  float rms = hit.RMS();
3807  chgPos[0] = hit.WireID().Wire - stp1.Pos[0];
3808  for (float time = peakTime - 2.5 * rms; time < peakTime + 2.5 * rms; ++time) {
3809  chgPos[1] = time * tcc.unitsPerTick - stp1.Pos[1];
3810  along = cs * chgPos[0] - sn * chgPos[1];
3811  if (along < minAlong) continue;
3812  indx = (unsigned short)(along - minAlong);
3813  if (indx > nbins - 1) continue;
3814  arg = (time - peakTime) / rms;
3815  schg[indx] += amp * exp(-0.5 * arg * arg);
3816  } // time
3817  } // sspt
3818 
3819  return schg;
3820  } // StartChgVec
3821 
3823  void DumpShowerPts(TCSlice& slc, int cotID)
3824  {
3825  // Print the shower points to the screen. The user should probably pipe the output to a text file
3826  // then grep this file for the character string PTS which is piped to a text file which can then be
3827  // imported into Excel, etc
3828  // Finds the charge at the start of a shower
3829  if (cotID > (int)slc.cots.size()) return;
3830 
3831  ShowerStruct& ss = slc.cots[cotID - 1];
3832  if (ss.ID == 0) return;
3833  if (ss.TjIDs.empty()) return;
3834  std::cout << "PTS Pos0 Pos1 RPos0 RPos1 Chg TID\n";
3835  for (auto& pt : ss.ShPts) {
3836  std::cout << "PTS " << std::fixed << std::setprecision(1) << pt.Pos[0] << " " << pt.Pos[1]
3837  << " " << pt.RotPos[0] << " " << pt.RotPos[1];
3838  std::cout << " " << (int)pt.Chg << " " << pt.TID;
3839  std::cout << "\n";
3840  }
3841 
3842  } // DumpShowerPts
3843 
3845  bool TransferTjHits(TCSlice& slc, bool prt)
3846  {
3847  // Transfer InShower hits in all TPCs and planes to the shower Tjs
3848 
3849  bool newShowers = false;
3850  for (auto& ss : slc.cots) {
3851  if (ss.ID == 0) continue;
3852  if (ss.ShowerTjID == 0) continue;
3853  // Tp 1 of stj will get all of the shower hits
3854  Trajectory& stj = slc.tjs[ss.ShowerTjID - 1];
3855  if (!stj.Pts[1].Hits.empty()) {
3856  std::cout << "TTjH: ShowerTj T" << stj.ID << " already has " << stj.Pts[1].Hits.size()
3857  << " hits\n";
3858  continue;
3859  }
3860  // Note that UseHit is not used since the size is limited to 16
3861  for (auto& tjID : ss.TjIDs) {
3862  unsigned short itj = tjID - 1;
3863  if (slc.tjs[itj].AlgMod[kShowerTj]) {
3864  std::cout << "TTjH: Coding error. T" << tjID << " is a ShowerTj but is in TjIDs\n";
3865  continue;
3866  }
3867  if (slc.tjs[itj].SSID <= 0) {
3868  std::cout << "TTjH: Coding error. Trying to transfer T" << tjID
3869  << " hits but it isn't an InShower Tj\n";
3870  continue;
3871  }
3872  auto thits = PutTrajHitsInVector(slc.tjs[itj], kUsedHits);
3873  // associate all hits with the charge center TP
3874  stj.Pts[1].Hits.insert(stj.Pts[1].Hits.end(), thits.begin(), thits.end());
3875  // kill Tjs that are in showers
3876  slc.tjs[itj].AlgMod[kKilled] = true;
3877  } // tjID
3878  // re-assign the hit -> stj association
3879  for (auto& iht : stj.Pts[1].Hits)
3880  slc.slHits[iht].InTraj = stj.ID;
3881  newShowers = true;
3882  } // ss
3883 
3884  if (prt) mf::LogVerbatim("TC") << "TTJH: success? " << newShowers;
3885  return newShowers;
3886  } // TransferTjHits
3887 
3889  int GetCotID(TCSlice& slc, int ShowerTjID)
3890  {
3891  for (unsigned short ii = 0; ii < slc.cots.size(); ++ii) {
3892  if (ShowerTjID == slc.cots[ii].ShowerTjID) return ii + 1;
3893  } // iii
3894  return 0;
3895 
3896  } // GetCotID
3897 
3899  double ShowerEnergy(const ShowerStruct3D& ss3)
3900  {
3901  if (ss3.ID == 0) return 0;
3902  if (ss3.Energy.empty()) return 0;
3903  double ave = 0;
3904  for (auto e : ss3.Energy) {
3905  ave += e;
3906  } // e
3907  ave /= ss3.Energy.size();
3908  return ave;
3909  } // ShowerEnergy
3910 
3912  float ShowerEnergy(TCSlice& slc, const std::vector<int> tjIDs)
3913  {
3914  // Calculate energy using the total charge of all hits in each tj in the shower
3915  if (tjIDs.empty()) return 0;
3916  float sum = 0;
3917  for (auto tid : tjIDs) {
3918  auto& tj = slc.tjs[tid - 1];
3919  sum += tj.TotChg;
3920  } // tid
3921  return ChgToMeV(sum);
3922  } // ShowerEnergy
3923 
3925  float ChgToMeV(float chg)
3926  {
3927  // Conversion from shower charge to energy in MeV. The calibration factor
3928  // was found by processing 500 pizero events with StudyPiZeros using StudyMode
3929  return 0.012 * chg;
3930  }
3931 
3933  bool StoreShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct3D& ss3)
3934  {
3935  // Store a 3D shower. This function sets the 3S -> 2S assns using CotIDs and ensures
3936  // that the 2S -> 3S assns are OK.
3937 
3938  std::string fcnLabel = inFcnLabel + ".S3S";
3939  if (ss3.ID <= 0) {
3940  std::cout << fcnLabel << " Invalid ID";
3941  return false;
3942  }
3943  if (ss3.CotIDs.size() < 2) {
3944  std::cout << fcnLabel << " not enough CotIDs";
3945  return false;
3946  }
3947 
3948  // check the 2S -> 3S assns
3949  for (auto& ss : slc.cots) {
3950  if (ss.ID == 0) continue;
3951  if (ss.SS3ID == ss3.ID &&
3952  std::find(ss3.CotIDs.begin(), ss3.CotIDs.end(), ss.ID) == ss3.CotIDs.end()) {
3953  std::cout << fcnLabel << " Bad assn: 2S" << ss.ID << " -> 3S" << ss3.ID
3954  << " but it's not inCotIDs.\n";
3955  return false;
3956  }
3957  } // ss
3958 
3959  // check the 3S -> 2S assns
3960  for (auto cid : ss3.CotIDs) {
3961  if (cid <= 0 || cid > (int)slc.cots.size()) return false;
3962  auto& ss = slc.cots[cid - 1];
3963  if (ss.SS3ID > 0 && ss.SS3ID != ss3.ID) {
3964  std::cout << fcnLabel << " Bad assn: 3S" << ss3.ID << " -> 2S" << cid << " but 2S -> 3S"
3965  << ss.SS3ID << "\n";
3966  return false;
3967  }
3968  } // cid
3969 
3970  // set the 2S -> 3S assns
3971  for (auto cid : ss3.CotIDs)
3972  slc.cots[cid - 1].SS3ID = ss3.ID;
3973 
3974  ++evt.global3S_UID;
3975  ss3.UID = evt.global3S_UID;
3976 
3977  slc.showers.push_back(ss3);
3978  return true;
3979 
3980  } // StoreShower
3981 
3983  bool StoreShower(std::string inFcnLabel, TCSlice& slc, ShowerStruct& ss)
3984  {
3985  // Store a ShowerStruct
3986  std::string fcnLabel = inFcnLabel + ".S2S";
3987  if (ss.ID <= 0) {
3988  std::cout << fcnLabel << " Invalid ID";
3989  return false;
3990  }
3991  if (ss.TjIDs.empty()) {
3992  std::cout << fcnLabel << " Fail: No TjIDs in 2S" << ss.ID << "\n";
3993  return false;
3994  }
3995  if (ss.ParentID > 0) {
3996  if (ss.ParentID > (int)slc.tjs.size()) {
3997  std::cout << fcnLabel << " Fail: 2S" << ss.ID << " has an invalid ParentID T" << ss.ParentID
3998  << "\n";
3999  return false;
4000  }
4001  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), ss.ParentID) != ss.TjIDs.end()) {
4002  std::cout << fcnLabel << " Fail: 2S" << ss.ID << " ParentID is not in TjIDs.\n";
4003  return false;
4004  }
4005  } // ss.ParentID > 0
4006 
4007  // check the ID
4008  if (ss.ID != (int)slc.cots.size() + 1) {
4009  std::cout << fcnLabel << " Correcting the ID 2S" << ss.ID << " -> 2S" << slc.cots.size() + 1;
4010  ss.ID = slc.cots.size() + 1;
4011  }
4012 
4013  // set the tj shower bits
4014  for (auto& tjID : ss.TjIDs) {
4015  Trajectory& tj = slc.tjs[tjID - 1];
4016  tj.SSID = ss.ID;
4017  tj.AlgMod[kShwrParent] = false;
4018  if (tj.ID == ss.ParentID) tj.AlgMod[kShwrParent] = true;
4019  } // tjID
4020 
4021  ++evt.global2S_UID;
4022  ss.UID = evt.global2S_UID;
4023 
4024  slc.cots.push_back(ss);
4025  return true;
4026 
4027  } // StoreShower
4028 
4031  {
4032  // create a 3D shower and size the vectors that are indexed by plane
4033 
4034  ShowerStruct3D ss3;
4035  ss3.TPCID = slc.TPCID;
4036  ss3.ID = slc.showers.size() + 1;
4037  ss3.Energy.resize(slc.nPlanes);
4038  ss3.EnergyErr.resize(slc.nPlanes);
4039  ss3.MIPEnergy.resize(slc.nPlanes);
4040  ss3.MIPEnergyErr.resize(slc.nPlanes);
4041  ss3.dEdx.resize(slc.nPlanes);
4042  ss3.dEdxErr.resize(slc.nPlanes);
4043 
4044  return ss3;
4045 
4046  } // CreateSS3
4047 
4049  ShowerStruct CreateSS(TCSlice& slc, const std::vector<int>& tjl)
4050  {
4051  // Create a shower and shower Tj using Tjs in the list. If tjl is empty a
4052  // MC cheat shower is created inCTP. Note that inCTP is only used if tjl is empty.
4053  // A companion shower tj is created and stored but the ShowerStruct is not.
4054  ShowerStruct ss;
4055 
4056  // Create the shower tj
4057  Trajectory stj;
4058  stj.CTP = slc.tjs[tjl[0] - 1].CTP;
4059 
4060  // with three points
4061  stj.Pts.resize(3);
4062  for (auto& stp : stj.Pts) {
4063  stp.CTP = stj.CTP;
4064  // set all UseHit bits true so we don't get confused
4065  stp.UseHit.set();
4066  }
4067  stj.EndPt[0] = 0;
4068  stj.EndPt[1] = 2;
4069  stj.ID = slc.tjs.size() + 1;
4070  // Declare that stj is a shower Tj
4071  stj.AlgMod[kShowerTj] = true;
4072  stj.PDGCode = 1111;
4073  slc.tjs.push_back(stj);
4074  // define the ss
4075  ss.ID = slc.cots.size() + 1;
4076  ss.CTP = stj.CTP;
4077  // assign all TJ IDs to this ShowerStruct
4078  ss.TjIDs = tjl;
4079  // declare them to be InShower
4080  for (auto tjid : tjl) {
4081  auto& tj = slc.tjs[tjid - 1];
4082  if (tj.CTP != stj.CTP) {
4083  ss.ID = 0;
4084  return ss;
4085  }
4086  tj.SSID = ss.ID;
4087  } // tjid
4088  ss.ShowerTjID = stj.ID;
4089  ss.Envelope.resize(4);
4090  return ss;
4091 
4092  } // CreateSS
4093 
4095  bool ChkAssns(std::string inFcnLabel, TCSlice& slc)
4096  {
4097  // check tj - ss assns
4098 
4099  std::string fcnLabel = inFcnLabel + ".ChkAssns";
4100  for (auto& ss : slc.cots) {
4101  if (ss.ID == 0) continue;
4102  for (auto tid : ss.TjIDs) {
4103  auto& tj = slc.tjs[tid - 1];
4104  if (tj.SSID != ss.ID) {
4105  std::cout << fcnLabel << " ***** Error: 2S" << ss.ID << " -> TjIDs T" << tid
4106  << " != tj.SSID 2S" << tj.SSID << "\n";
4107  return false;
4108  }
4109  } // tid
4110  // check 2S -> 3S
4111  if (ss.SS3ID > 0 && ss.SS3ID <= (int)slc.showers.size()) {
4112  auto& ss3 = slc.showers[ss.SS3ID - 1];
4113  if (std::find(ss3.CotIDs.begin(), ss3.CotIDs.end(), ss.ID) == ss3.CotIDs.end()) {
4114  std::cout << fcnLabel << " ***** Error: 2S" << ss.ID << " -> 3S" << ss.SS3ID
4115  << " but the shower says no\n";
4116  return false;
4117  }
4118  } // ss.SS3ID > 0
4119  } // ss
4120  for (auto& tj : slc.tjs) {
4121  if (tj.AlgMod[kKilled]) continue;
4122  if (tj.SSID < 0) {
4123  std::cout << fcnLabel << " ***** Error: T" << tj.ID << " tj.SSID is fubar\n";
4124  tj.SSID = 0;
4125  return false;
4126  }
4127  if (tj.SSID == 0) continue;
4128  auto& ss = slc.cots[tj.SSID - 1];
4129  if (std::find(ss.TjIDs.begin(), ss.TjIDs.end(), tj.ID) != ss.TjIDs.end()) continue;
4130  std::cout << fcnLabel << " ***** Error: T" << tj.ID << " tj.SSID = 2S" << tj.SSID
4131  << " but the shower says no\n";
4132  return false;
4133  } // tj
4134 
4135  for (auto& ss3 : slc.showers) {
4136  if (ss3.ID == 0) continue;
4137  for (auto cid : ss3.CotIDs) {
4138  auto& ss = slc.cots[cid - 1];
4139  if (ss.SS3ID != ss3.ID) {
4140  std::cout << fcnLabel << " ***** Error: 3S" << ss3.ID << " -> 2S" << cid
4141  << " but it thinks it belongs to 3S" << ss.SS3ID << "\n";
4142  return false;
4143  }
4144  } // cid
4145  } // ss3
4146  return true;
4147  } // ChkAssns
4148 
4151  std::string fcnLabel,
4152  TCSlice const& slc)
4153  {
4154  if (slc.showers.empty()) return;
4155  mf::LogVerbatim myprt("TC");
4156  myprt << fcnLabel << " 3D showers \n";
4157  for (auto& ss3 : slc.showers) {
4158  myprt << fcnLabel << " 3S" << ss3.ID << " 3V" << ss3.Vx3ID;
4159  myprt << " parentID " << ss3.ParentID;
4160  myprt << " ChgPos" << std::fixed;
4161  for (unsigned short xyz = 0; xyz < 3; ++xyz)
4162  myprt << " " << std::setprecision(0) << ss3.ChgPos[xyz];
4163  myprt << " Dir";
4164  for (unsigned short xyz = 0; xyz < 3; ++xyz)
4165  myprt << " " << std::setprecision(2) << ss3.Dir[xyz];
4166  myprt << " posInPlane";
4167  std::vector<float> projInPlane(slc.nPlanes);
4168  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
4169  CTP_t inCTP = EncodeCTP(ss3.TPCID.Cryostat, ss3.TPCID.TPC, plane);
4170  auto tp = MakeBareTP(detProp, ss3.ChgPos, ss3.Dir, inCTP);
4171  myprt << " " << PrintPos(tp.Pos);
4172  projInPlane[plane] = tp.Delta;
4173  } // plane
4174  myprt << " projInPlane";
4175  for (unsigned short plane = 0; plane < slc.nPlanes; ++plane) {
4176  myprt << " " << std::fixed << std::setprecision(2) << projInPlane[plane];
4177  } // plane
4178  for (auto cid : ss3.CotIDs) {
4179  auto& ss = slc.cots[cid - 1];
4180  myprt << "\n 2S" << ss.ID;
4181  auto& stj = slc.tjs[ss.ShowerTjID - 1];
4182  myprt << " ST" << stj.ID;
4183  myprt << " " << PrintPos(stj.Pts[stj.EndPt[0]].Pos) << " - "
4184  << PrintPos(stj.Pts[stj.EndPt[1]].Pos);
4185  } // ci
4186  if (ss3.NeedsUpdate) myprt << " *** Needs update";
4187  myprt << "\n";
4188  } // sss3
4189  } // PrintShowers
4190 
4192  void Print2DShowers(std::string someText,
4193  TCSlice const& slc,
4194  CTP_t inCTP,
4195  bool printKilledShowers)
4196  {
4197  // Prints a one-line summary of 2D showers
4198  if (slc.cots.empty()) return;
4199 
4200  mf::LogVerbatim myprt("TC");
4201 
4202  // see how many lines were are going to print
4203  bool printAllCTP = (inCTP == USHRT_MAX);
4204  if (!printAllCTP) {
4205  unsigned short nlines = 0;
4206  for (const auto& ss : slc.cots) {
4207  if (!printAllCTP && ss.CTP != inCTP) continue;
4208  if (!printKilledShowers && ss.ID == 0) continue;
4209  ++nlines;
4210  } // ss
4211  if (nlines == 0) {
4212  myprt << someText << " Print2DShowers: Nothing to print";
4213  return;
4214  }
4215  } // !printAllCTP
4216 
4217  bool printHeader = true;
4218  bool printExtras = false;
4219  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
4220  const auto& ss = slc.cots[ict];
4221  if (!printAllCTP && ss.CTP != inCTP) continue;
4222  if (!printKilledShowers && ss.ID == 0) continue;
4223  PrintShower(someText, slc, ss, printHeader, printExtras);
4224  printHeader = false;
4225  } // ss
4226  // List of Tjs
4227  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
4228  const auto& ss = slc.cots[ict];
4229  if (!printAllCTP && ss.CTP != inCTP) continue;
4230  if (!printKilledShowers && ss.ID == 0) continue;
4231  myprt << someText << std::fixed;
4232  std::string sid = "2S" + std::to_string(ss.ID);
4233  myprt << std::setw(5) << sid;
4234  myprt << " Tjs";
4235  for (auto id : ss.TjIDs)
4236  myprt << " T" << id;
4237  myprt << "\n";
4238  } // ict
4239  // Print the envelopes
4240  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
4241  const auto& ss = slc.cots[ict];
4242  if (!printAllCTP && ss.CTP != inCTP) continue;
4243  if (!printKilledShowers && ss.ID == 0) continue;
4244  myprt << someText << std::fixed;
4245  std::string sid = "2S" + std::to_string(ss.ID);
4246  myprt << std::setw(5) << sid;
4247  myprt << " Envelope";
4248  for (auto& vtx : ss.Envelope)
4249  myprt << " " << (int)vtx[0] << ":" << (int)(vtx[1] / tcc.unitsPerTick);
4250  myprt << "\n";
4251  } // ict
4252  // List of nearby Tjs
4253  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
4254  const auto& ss = slc.cots[ict];
4255  if (!printAllCTP && ss.CTP != inCTP) continue;
4256  if (!printKilledShowers && ss.ID == 0) continue;
4257  myprt << someText << std::fixed;
4258  std::string sid = "2S" + std::to_string(ss.ID);
4259  myprt << std::setw(5) << sid;
4260  myprt << " Nearby";
4261  for (auto id : ss.NearTjIDs)
4262  myprt << " T" << id;
4263  myprt << "\n";
4264  } // ict
4265  // don't cluster list
4266  myprt << "DontCluster";
4267  for (auto& dc : slc.dontCluster) {
4268  if (dc.TjIDs[0] > 0) myprt << " T" << dc.TjIDs[0] << "-T" << dc.TjIDs[1];
4269  } // dc
4270  myprt << "\nDontCluster";
4271  for (unsigned short ict = 0; ict < slc.cots.size(); ++ict) {
4272  const auto& iss = slc.cots[ict];
4273  if (iss.ID == 0) continue;
4274  for (unsigned short jct = ict + 1; jct < slc.cots.size(); ++jct) {
4275  const auto& jss = slc.cots[jct];
4276  if (jss.ID == 0) continue;
4277  if (DontCluster(slc, iss.TjIDs, jss.TjIDs)) myprt << " 2S" << iss.ID << "-2S" << jss.ID;
4278  } // jct
4279  } // ict
4280  } // Print2DShowers
4281 
4283  void PrintShower(std::string someText,
4284  TCSlice const& slc,
4285  const ShowerStruct& ss,
4286  bool printHeader,
4287  bool printExtras)
4288  {
4289  // print a single shower and a header (optional) and the extra variables like TjIDs, envelope, etc
4290  mf::LogVerbatim myprt("TC");
4291 
4292  if (printHeader) {
4293  myprt << someText
4294  << " ID CTP ParID ParFOM TruParID Energy nTjs dFOM AspRat stj vx0 __Pos0___ "
4295  "Chg(k) dRMS __Pos1___ Chg(k) dRMS __Pos2___ Chg(k) dRMS Angle SS3ID PFPID\n";
4296  } // printHeader
4297 
4298  myprt << someText << std::fixed;
4299  std::string sid = "2S" + std::to_string(ss.ID);
4300  myprt << std::setw(5) << sid;
4301  myprt << std::setw(6) << ss.CTP;
4302  sid = "NA";
4303  if (ss.ParentID > 0) sid = "T" + std::to_string(ss.ParentID);
4304  myprt << std::setw(7) << sid;
4305  myprt << std::setw(7) << std::setprecision(2) << ss.ParentFOM;
4306  myprt << std::setw(9) << ss.TruParentID;
4307  myprt << std::setw(7) << (int)ss.Energy;
4308  myprt << std::setw(5) << ss.TjIDs.size();
4309  myprt << std::setw(6) << std::setprecision(2) << ss.DirectionFOM;
4310  myprt << std::setw(7) << std::setprecision(2) << ss.AspectRatio;
4311  const auto& stj = slc.tjs[ss.ShowerTjID - 1];
4312  std::string tid = "T" + std::to_string(stj.ID);
4313  myprt << std::setw(5) << tid;
4314  std::string vid = "NA";
4315  if (stj.VtxID[0] > 0) vid = "2V" + std::to_string(stj.VtxID[0]);
4316  myprt << std::setw(5) << vid;
4317  for (auto& spt : stj.Pts) {
4318  myprt << std::setw(10) << PrintPos(spt.Pos);
4319  myprt << std::setw(7) << std::fixed << std::setprecision(1) << spt.Chg / 1000;
4320  // myprt<<std::setw(5)<<spt.NTPsFit;
4321  myprt << std::setw(5) << std::setprecision(1) << spt.DeltaRMS;
4322  } // spt
4323  myprt << std::setw(6) << std::setprecision(2) << stj.Pts[1].Ang;
4324  std::string sss = "NA";
4325  if (ss.SS3ID > 0) sss = "3S" + std::to_string(ss.SS3ID);
4326  myprt << std::setw(6) << sss;
4327  if (ss.SS3ID > 0 && ss.SS3ID < (int)slc.showers.size()) {
4328  auto& ss3 = slc.showers[ss.SS3ID - 1];
4329  if (ss3.PFPIndex < slc.pfps.size()) {
4330  std::string pid = "P" + std::to_string(ss3.PFPIndex + 1);
4331  myprt << std::setw(6) << pid;
4332  }
4333  else {
4334  myprt << std::setw(6) << "NA";
4335  }
4336  }
4337  else {
4338  myprt << std::setw(6) << "NA";
4339  }
4340  if (ss.NeedsUpdate) myprt << " *** Needs update";
4341 
4342  if (!printExtras) return;
4343  myprt << "\n";
4344 
4345  myprt << someText << std::fixed;
4346  sid = "2S" + std::to_string(ss.ID);
4347  myprt << std::setw(5) << sid;
4348  myprt << " Tjs";
4349  for (auto id : ss.TjIDs)
4350  myprt << " T" << id;
4351  myprt << "\n";
4352  myprt << someText << std::fixed;
4353  sid = "2S" + std::to_string(ss.ID);
4354  myprt << std::setw(5) << sid;
4355  myprt << " Envelope";
4356  for (auto& vtx : ss.Envelope)
4357  myprt << " " << (int)vtx[0] << ":" << (int)(vtx[1] / tcc.unitsPerTick);
4358  myprt << "\n";
4359  myprt << someText << std::fixed;
4360  sid = "2S" + std::to_string(ss.ID);
4361  myprt << std::setw(5) << sid;
4362  myprt << " Nearby";
4363  for (auto id : ss.NearTjIDs)
4364  myprt << " T" << id;
4365 
4366  } // PrintShower
4367 
4368 } // namespace tca
bool AddTj(std::string inFcnLabel, TCSlice &slc, int tjID, ShowerStruct &ss, bool doUpdate, bool prt)
Definition: TCShower.cxx:1429
bool TransferTjHits(TCSlice &slc, bool prt)
Definition: TCShower.cxx:3845
std::bitset< 16 > UseHit
Definition: DataStructs.h:170
int UID
unique global ID
Definition: DataStructs.h:331
Vector2_t Dir
Definition: DataStructs.h:153
Point2_t Pos
Definition: DataStructs.h:152
short MCSMom(const TCSlice &slc, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3380
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
std::vector< Trajectory > tjs
vector of all trajectories in each plane
Definition: DataStructs.h:663
void MergeNearby2DShowers(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2279
void ConfigureMVA(TCConfig &tcc, std::string fMVAShowerParentWeights)
Definition: TCShower.cxx:34
bool MakeVertexObsolete(std::string fcnLabel, TCSlice &slc, VtxStore &vx2, bool forceKill)
Definition: TCVertex.cxx:2690
Double_t xx
Definition: macro.C:12
bool FindParent(detinfo::DetectorPropertiesData const &detProp, std::string inFcnLabel, TCSlice &slc, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:1561
void ReverseShower(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:3112
struct of temporary 2D vertices (end points)
Definition: DataStructs.h:69
std::vector< unsigned int > PutTrajHitsInVector(const Trajectory &tj, HitStatus_t hitRequest)
Definition: Utils.cxx:2688
std::vector< ShowerStruct > cots
Definition: DataStructs.h:672
void MergeShowerChain(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2491
std::vector< double > dEdxErr
Definition: DataStructs.h:354
double InShowerProbLong(double showerEnergy, double along)
Definition: TCShower.cxx:1937
bool WrongSplitTj(std::string inFcnLabel, TCSlice &slc, Trajectory &tj, unsigned short tjEnd, bool prt)
Definition: TCShower.cxx:2239
bool ChkAssns(std::string inFcnLabel, TCSlice &slc)
Definition: TCShower.cxx:4095
int UID
unique global ID
Definition: DataStructs.h:360
std::vector< int > NearTjIDs
Definition: DataStructs.h:319
std::vector< ShowerStruct3D > showers
Definition: DataStructs.h:675
ShowerStruct3D CreateSS3(TCSlice &slc)
Definition: TCShower.cxx:4030
std::vector< Point2_t > Envelope
Definition: DataStructs.h:325
TCConfig tcc
Definition: DataStructs.cxx:9
void PrintShowers(detinfo::DetectorPropertiesData const &detProp, std::string fcnLabel, TCSlice const &slc)
Definition: TCShower.cxx:4150
unsigned int Nplanes() const
Number of planes in this tpc.
Definition: TPCGeo.h:137
Declaration of signal hit object.
void FindAlongTrans(Point3_t pos1, Vector3_t dir1, Point3_t pos2, Point2_t &alongTrans)
Definition: PFPUtils.cxx:3069
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > wireHitRange
Definition: DataStructs.h:668
The data type to uniquely identify a Plane.
Definition: geo_types.h:463
Geometry information for a single TPC.
Definition: TPCGeo.h:36
std::vector< unsigned int > Hits
Definition: DataStructs.h:169
std::vector< double > MIPEnergy
Definition: DataStructs.h:351
int GetCotID(TCSlice &slc, int ShowerTjID)
Definition: TCShower.cxx:3889
std::vector< int > TjIDs
Definition: DataStructs.h:318
short MCSMom
Normalized RMS using ALL hits. Assume it is 50% to start.
Definition: DataStructs.h:197
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.
bool DontCluster(TCSlice const &slc, const std::vector< int > &tjlist1, const std::vector< int > &tjlist2)
Definition: TCShower.cxx:3213
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:211
PFPStruct CreatePFP(const TCSlice &slc)
Definition: PFPUtils.cxx:2805
void KillVerticesInShower(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:704
Float_t tmp
Definition: plot.C:35
Point3_t PosAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3259
void SaveAllCots(TCSlice &slc, const CTP_t &inCTP, std::string someText)
Definition: TCShTree.cxx:179
float PointTrajDOCA(const TCSlice &slc, unsigned int iht, TrajPoint const &tp)
Definition: Utils.cxx:2513
double DeltaAngle(const Vector3_t v1, const Vector3_t v2)
Definition: PFPUtils.cxx:2535
bool FindShowers3D(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc)
Definition: TCShower.cxx:285
double ShowerEnergy(const ShowerStruct3D &ss3)
Definition: TCShower.cxx:3899
bool dbgSlc
debug only in the user-defined slice? default is all slices
Definition: DataStructs.h:583
bool MakeBareTrajPoint(const TCSlice &slc, unsigned int fromHit, unsigned int toHit, TrajPoint &tp)
Definition: Utils.cxx:4006
void TagShowerLike(TCSlice &slc, const CTP_t &inCTP)
Definition: TCShower.cxx:3232
std::vector< unsigned int > lastWire
the last wire with a hit
Definition: DataStructs.h:648
std::array< int, 2 > Vx3ID
Definition: DataStructs.h:287
void PrintPFPs(std::string someText, TCSlice const &slc)
Definition: Utils.cxx:6276
std::vector< int > CotIDs
Definition: DataStructs.h:356
std::string PrintPos(const TrajPoint &tp)
Definition: Utils.cxx:6353
double InShowerProbTrans(double showerEnergy, double along, double trans)
Definition: TCShower.cxx:1972
std::vector< ShowerPoint > ShPts
Definition: DataStructs.h:320
bool FindShowerStart(detinfo::DetectorPropertiesData const &detProp, TCSlice &slc, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:60
ShowerStruct CreateSS(TCSlice &slc, const std::vector< int > &tjl)
Definition: TCShower.cxx:4049
std::vector< T > SetIntersection(const std::vector< T > &set1, const std::vector< T > &set2)
Definition: Utils.h:385
bool AddLooseHits(TCSlice &slc, int cotID, bool prt)
Definition: TCShower.cxx:3578
void MergeTjList(std::vector< std::vector< int >> &tjList)
Definition: TCShower.cxx:1287
std::vector< float > showerTag
shower-like trajectory tagging + shower reconstruction
Definition: DataStructs.h:551
double InShowerProbParam(double showerEnergy, double along, double trans)
Definition: TCShower.cxx:1986
TPCGeo const & TPC(TPCID const &tpcid=tpc_zero) const
Returns the specified TPC.
Definition: GeometryCore.h:722
nlines
double ShowerParamTransRMS(double showerEnergy, double along)
Definition: TCShower.cxx:1923
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
void AddCloseTjsToList(TCSlice &slc, unsigned short itj, std::vector< int > list)
Definition: TCShower.cxx:3413
unsigned short TID
Definition: DataStructs.h:311
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:289
Access the description of detector geometry.
save shower tree
Definition: DataStructs.h:533
void DefineEnvelope(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:3442
void MergeSubShowersTj(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2620
std::vector< DontClusterStruct > dontCluster
Definition: DataStructs.h:674
Double_t scale
Definition: plot.C:24
int NeutrinoPrimaryTjID(const TCSlice &slc, const Trajectory &tj)
Definition: Utils.cxx:445
if(nlines<=0)
std::array< float, 2 > Point2_t
Definition: DataStructs.h:43
float unitsPerTick
scale factor from Tick to WSE equivalent units
Definition: DataStructs.h:564
void DumpShowerPts(TCSlice &slc, int cotID)
Definition: TCShower.cxx:3823
DebugStuff debug
Definition: DebugStruct.cxx:4
Vector3_t PointDirection(const Point3_t p1, const Point3_t p2)
Definition: PFPUtils.cxx:2542
CTP_t CTP
Cryostat, TPC, Plane code.
Definition: DataStructs.h:189
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
float InShowerProb(TCSlice &slc, const ShowerStruct3D &ss3, const PFPStruct &pfp)
Definition: TCShower.cxx:1992
bool MergeShowerTjsAndStore(TCSlice &slc, unsigned short istj, unsigned short jstj, bool prt)
Definition: TCShower.cxx:2978
std::vector< TrajPoint > Pts
Trajectory points.
Definition: DataStructs.h:188
TMVA::Reader * showerParentReader
Definition: DataStructs.h:571
float ChgFracNearPos(const TCSlice &slc, const Point2_t &pos, const std::vector< int > &tjIDs)
Definition: Utils.cxx:3162
std::vector< float > showerParentVars
Definition: DataStructs.h:572
Float_t sn
Definition: plot.C:23
float ChgFracBetween(detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, Point3_t pos1, Point3_t pos2)
Definition: PFPUtils.cxx:3166
double energy
Definition: plottest35.C:25
TrajPoint MakeBareTP(detinfo::DetectorPropertiesData const &detProp, const Point3_t &pos, CTP_t inCTP)
Definition: Utils.cxx:3927
TMarker * pt
Definition: egs.C:25
void SaveTjInfo(TCSlice &slc, std::vector< std::vector< int >> &tjList, std::string stageName)
Definition: TCShTree.cxx:14
void FindNearbyTjs(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:3335
std::vector< VtxStore > vtxs
2D vertices
Definition: DataStructs.h:669
std::array< unsigned short, 2 > EndPt
First and last point in the trajectory that has charge.
Definition: DataStructs.h:200
unsigned short PDGCode
shower-like or track-like {default is track-like}
Definition: DataStructs.h:205
bool PointInsideEnvelope(const Point2_t &Point, const std::vector< Point2_t > &Envelope)
Definition: Utils.cxx:3245
void MergeOverlap(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2371
void MakeTrajectoryObsolete(TCSlice &slc, unsigned int itj)
Definition: Utils.cxx:2137
double PosSep2(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2564
Point2_t Pos
Definition: DataStructs.h:70
Float_t mat
Definition: plot.C:38
const geo::GeometryCore * geom
Definition: DataStructs.h:569
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:481
bool valsIncreasing(const SortEntry &c1, const SortEntry &c2)
Definition: Utils.cxx:42
bool SetMag(Vector3_t &v1, double mag)
Definition: PFPUtils.cxx:2574
Definition of data types for geometry description.
bool AddPFP(std::string inFcnLabel, TCSlice &slc, int pID, ShowerStruct3D &ss3, bool doUpdate, bool prt)
Definition: TCShower.cxx:1368
unsigned short FarEnd(const PFPStruct &pfp, const Point3_t &pos)
Definition: PFPUtils.cxx:3302
int ID
ID that is local to one slice.
Definition: DataStructs.h:202
std::vector< TCSlice > slices
Definition: DataStructs.cxx:13
std::array< unsigned short, 2 > VtxID
ID of 2D vertex.
Definition: DataStructs.h:199
Detector simulation of raw signals on wires.
float ParentFOM(std::string inFcnLabel, TCSlice &slc, PFPStruct &pfp, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:2051
bool AddTjsInsideEnvelope(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:3503
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
std::vector< float > chargeCuts
Definition: DataStructs.h:555
int ID
set to 0 if killed
Definition: DataStructs.h:80
bool MergeShowersAndStore(std::string inFcnLabel, TCSlice &slc, int icotID, int jcotID, bool prt)
Definition: TCShower.cxx:2908
std::vector< int > GetAssns(TCSlice const &slc, std::string type1Name, int id, std::string type2Name)
Definition: Utils.cxx:4701
std::vector< float > StartChgVec(TCSlice &slc, int cotID)
Definition: TCShower.cxx:3774
std::vector< double > EnergyErr
Definition: DataStructs.h:350
std::vector< double > MIPEnergyErr
Definition: DataStructs.h:352
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
bool RemoveTj(std::string inFcnLabel, TCSlice &slc, int TjID, ShowerStruct &ss, bool doUpdate, bool prt)
Definition: TCShower.cxx:1506
geo::TPCID TPCID
Definition: DataStructs.h:655
std::bitset< 128 > useAlg
Max hit separation for making junk trajectories. < 0 to turn off.
Definition: DataStructs.h:579
int MergeShowers(std::string inFcnLabel, TCSlice &slc, std::vector< int > ssIDs, bool prt)
Definition: TCShower.cxx:2847
void ShowerParams(double showerEnergy, double &shMaxAlong, double &along95)
Definition: TCShower.cxx:1904
in close()
TDirectory * dir
Definition: macro.C:5
std::bitset< 128 > AlgMod
Bit set if algorithm AlgBit_t modifed the trajectory.
Definition: DataStructs.h:190
std::vector< double > Energy
Definition: DataStructs.h:349
void PrintShower(std::string someText, TCSlice const &slc, const ShowerStruct &ss, bool printHeader, bool printExtras)
Definition: TCShower.cxx:4283
geo::PlaneID DecodeCTP(CTP_t CTP)
double PosSep(const Point3_t &pos1, const Point3_t &pos2)
Definition: PFPUtils.cxx:2558
std::vector< int > TjIDs
Definition: DataStructs.h:280
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
unsigned int HitIndex
Definition: DataStructs.h:309
std::pair< unsigned short, unsigned short > GetSliceIndex(std::string typeName, int uID)
Definition: Utils.cxx:4939
std::array< double, 3 > Vector3_t
Definition: DataStructs.h:42
int ID
ID of the recob::Slice (not the sub-slice)
Definition: DataStructs.h:657
bool RemovePFP(std::string inFcnLabel, TCSlice &slc, PFPStruct &pfp, ShowerStruct3D &ss3, bool doUpdate, bool prt)
Definition: TCShower.cxx:1340
std::vector< T > SetDifference(const std::vector< T > &set1, const std::vector< T > &set2)
Definition: Utils.h:407
void MergeSubShowers(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2731
void FindStartChg(std::string inFcnLabel, TCSlice &slc, int cotID, bool prt)
Definition: TCShower.cxx:3654
unsigned short nPlanes
Definition: DataStructs.h:656
void Finish3DShowers(TCSlice &slc)
Definition: TCShower.cxx:153
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:69
int SSID
ID of a 2D shower struct that this tj is in.
Definition: DataStructs.h:204
std::vector< PFPStruct > pfps
Definition: DataStructs.h:671
bool AnalyzeRotPos(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:3022
float Match3DFOM(detinfo::DetectorPropertiesData const &detProp, std::string inFcnLabel, TCSlice &slc, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:1198
void PrintAllTraj(detinfo::DetectorPropertiesData const &detProp, std::string someText, TCSlice const &slc, unsigned short itj, unsigned short ipt, bool prtVtx)
Definition: Utils.cxx:5790
unsigned short NumPtsWithCharge(const TCSlice &slc, const Trajectory &tj, bool includeDeadWires)
Definition: Utils.cxx:2071
float ChgToMeV(float chg)
Definition: TCShower.cxx:3925
TCEvent evt
Definition: DataStructs.cxx:8
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:399
Double_t sum
Definition: plot.C:31
Float_t e
Definition: plot.C:35
bool UpdateShower(std::string inFcnLabel, TCSlice &slc, ShowerStruct &ss, bool prt)
Definition: TCShower.cxx:904
void Print2DShowers(std::string someText, TCSlice const &slc, CTP_t inCTP, bool printKilledShowers)
Definition: TCShower.cxx:4192
std::vector< double > dEdx
Definition: DataStructs.h:353
bool TrajTrajDOCA(const TCSlice &slc, const Trajectory &tj1, const Trajectory &tj2, unsigned short &ipt1, unsigned short &ipt2, float &minSep)
Definition: Utils.cxx:2406
Vector3_t DirAtEnd(const PFPStruct &pfp, unsigned short end)
Definition: PFPUtils.cxx:3251
bool CompleteIncompleteShower(std::string inFcnLabel, TCSlice &slc, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:748
bool SetParent(detinfo::DetectorPropertiesData const &detProp, std::string inFcnLabel, TCSlice &slc, PFPStruct &pfp, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:1815
CTP_t CTP
set to an invalid CTP
Definition: DebugStruct.h:23
void MakeShowerObsolete(std::string inFcnLabel, TCSlice &slc, ShowerStruct3D &ss3, bool prt)
Definition: TCShower.cxx:3151
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:109
Encapsulate the construction of a single detector plane.
bool StoreShower(std::string inFcnLabel, TCSlice &slc, ShowerStruct3D &ss3)
Definition: TCShower.cxx:3933
bool Reconcile3D(std::string inFcnLabel, TCSlice &slc, bool parentSearchDone, bool prt)
Definition: TCShower.cxx:424
void ReverseTraj(Trajectory &tj)
Definition: Utils.cxx:3218
unsigned int index
Definition: DataStructs.h:36