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