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