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