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