LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
LArHierarchyHelper.cc
Go to the documentation of this file.
1 
9 #include "Pandora/PdgTable.h"
10 #include "Pandora/StatusCodes.h"
11 
14 
15 #include <numeric>
16 
17 namespace lar_content
18 {
19 
20 using namespace pandora;
21 
23  m_foldToLeadingShowers{false},
24  m_foldToTier{false},
25  m_foldDynamic{false},
26  m_cosAngleTolerance{0.9962f},
27  m_tier{1}
28 {
29 }
30 
31 //------------------------------------------------------------------------------------------------------------------------------------------
32 
33 LArHierarchyHelper::FoldingParameters::FoldingParameters(const bool foldDynamic, const float cosAngleTolerance) :
35  m_foldToTier{false},
36  m_foldDynamic{foldDynamic},
37  m_cosAngleTolerance{cosAngleTolerance},
38  m_tier{1}
39 {
40 }
41 
42 //------------------------------------------------------------------------------------------------------------------------------------------
43 
46  m_foldToTier{true},
47  m_foldDynamic{false},
48  m_cosAngleTolerance{0.9962f},
49  m_tier{foldingTier}
50 {
51  if (m_tier < 1)
52  {
53  std::cout << "LArHierarchyHelper: Error - attempting to fold to non-positive tier" << std::endl;
54  throw StatusCodeException(STATUS_CODE_INVALID_PARAMETER);
55  }
56 }
57 
58 //------------------------------------------------------------------------------------------------------------------------------------------
59 //------------------------------------------------------------------------------------------------------------------------------------------
60 
62  m_minPurity{0.8f},
63  m_minCompleteness{0.65f},
64  m_selectRecoHits{false}
65 {
66 }
67 
68 //------------------------------------------------------------------------------------------------------------------------------------------
69 
70 LArHierarchyHelper::QualityCuts::QualityCuts(const float minPurity, const float minCompleteness, const bool selectRecoHits) :
71  m_minPurity{minPurity},
72  m_minCompleteness{minCompleteness},
73  m_selectRecoHits{selectRecoHits}
74 {
75 }
76 
77 //------------------------------------------------------------------------------------------------------------------------------------------
78 //------------------------------------------------------------------------------------------------------------------------------------------
79 
81  m_nextNodeId{1}
82 {
83 }
84 
85 //------------------------------------------------------------------------------------------------------------------------------------------
86 
88  m_recoCriteria(recoCriteria),
89  m_nextNodeId{1}
90 {
91 }
92 
93 //------------------------------------------------------------------------------------------------------------------------------------------
94 
96 {
97  for (const auto &[pRoot, nodeVector] : m_interactions)
98  {
99  (void)pRoot;
100  for (const Node *pNode : nodeVector)
101  delete pNode;
102  }
103  m_interactions.clear();
104 }
105 
106 //------------------------------------------------------------------------------------------------------------------------------------------
107 
108 void LArHierarchyHelper::MCHierarchy::FillHierarchy(const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const FoldingParameters &foldParameters)
109 {
110  const auto predicate = [](const MCParticle *pMCParticle) { return std::abs(pMCParticle->GetParticleId()) == NEUTRON; };
111  m_mcToHitsMap.clear();
112  for (const CaloHit *pCaloHit : caloHitList)
113  {
114  try
115  {
116  const MCParticle *pMCParticle{MCParticleHelper::GetMainMCParticle(pCaloHit)};
117  m_mcToHitsMap[pMCParticle].emplace_back(pCaloHit);
118  }
119  catch (const StatusCodeException &)
120  {
121  }
122  }
123 
124  MCParticleList rootNodes;
125  for (const MCParticle *pMCParticle : mcParticleList)
126  {
127  const MCParticleList &parentList{pMCParticle->GetParentList()};
128  if (parentList.empty())
129  {
130  rootNodes.emplace_back(pMCParticle);
131  }
132  }
133 
134  for (const MCParticle *pRoot : rootNodes)
135  {
136  MCParticleSet primarySet;
137  LArHierarchyHelper::GetMCPrimaries(pRoot, primarySet);
138  MCParticleList primaries(primarySet.begin(), primarySet.end());
139  primaries.sort(LArMCParticleHelper::SortByMomentum);
141  primaries.erase(std::remove_if(primaries.begin(), primaries.end(), predicate), primaries.end());
142  if (foldParameters.m_foldToTier && foldParameters.m_tier == 1)
143  {
144  for (const MCParticle *pPrimary : primaries)
145  {
146  MCParticleList allParticles{pPrimary};
148  {
149  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles);
150  }
151  else
152  {
153  // Collect track-like and shower-like particles together, but throw out neutrons and descendents
154  MCParticleList dummy;
155  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles, allParticles, dummy);
156  }
157  CaloHitList allHits;
158  for (const MCParticle *pMCParticle : allParticles)
159  {
160  // Not all MC particles will have hits
161  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
162  {
163  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
164  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
165  }
166  }
167  m_interactions[pRoot].emplace_back(new Node(*this, allParticles, allHits));
168  }
169  }
170  else if (foldParameters.m_foldToLeadingShowers)
171  {
172  for (const MCParticle *pPrimary : primaries)
173  {
174  MCParticleList allParticles{pPrimary};
175  int pdg{std::abs(pPrimary->GetParticleId())};
176  const bool isShower{pdg == E_MINUS || pdg == PHOTON};
177  const bool isNeutron{pdg == NEUTRON};
178  if (isShower || (isNeutron && !m_recoCriteria.m_removeNeutrons))
179  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles);
180  CaloHitList allHits;
181  for (const MCParticle *pMCParticle : allParticles)
182  {
183  // ATTN - Not all MC particles will have hits
184  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
185  {
186  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
187  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
188  }
189  }
190  Node *pNode{new Node(*this, allParticles, allHits)};
191  m_interactions[pRoot].emplace_back(pNode);
192  if (!(isShower || isNeutron))
193  {
194  // Find the children of this particle and recursively add them to the hierarchy
195  const MCParticleList &children{pPrimary->GetDaughterList()};
196  for (const MCParticle *pChild : children)
197  pNode->FillHierarchy(pChild, foldParameters);
198  }
199  }
200  }
201  else if (foldParameters.m_foldDynamic)
202  {
203  for (const MCParticle *pPrimary : primaries)
204  {
205  MCParticleList leadingParticles, childParticles;
206  this->InterpretHierarchy(pPrimary, leadingParticles, childParticles, foldParameters.m_cosAngleTolerance);
207  CaloHitList allHits;
208  for (const MCParticle *pMCParticle : leadingParticles)
209  {
210  // ATTN - Not all MC particles will have hits
211  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
212  {
213  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
214  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
215  }
216  }
217 
218  Node *pNode{new Node(*this, leadingParticles, allHits)};
219  m_interactions[pRoot].emplace_back(pNode);
220  for (const MCParticle *pChild : childParticles)
221  pNode->FillHierarchy(pChild, foldParameters);
222  }
223  }
224  else
225  {
226  // Unfolded and folded to tier > 1 have the same behaviour for primaries
227  for (const MCParticle *pPrimary : primaries)
228  {
229  MCParticleList allParticles{pPrimary};
230  CaloHitList allHits;
231  for (const MCParticle *pMCParticle : allParticles)
232  {
233  // ATTN - Not all MC particles will have hits
234  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
235  {
236  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
237  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
238  }
239  }
240  Node *pNode{new Node(*this, allParticles, allHits)};
241  m_interactions[pRoot].emplace_back(pNode);
242  // Find the children of this particle and recursively add them to the hierarchy
243  const MCParticleList &children{pPrimary->GetDaughterList()};
244  for (const MCParticle *pChild : children)
245  pNode->FillHierarchy(pChild, foldParameters);
246  }
247  }
248 
249  Node *pLeadingLepton{nullptr};
250  float leadingLeptonEnergy{-std::numeric_limits<float>::max()};
251  for (const Node *pNode : m_interactions[pRoot])
252  {
253  const MCParticle *pMC{pNode->GetLeadingMCParticle()};
254  if (pMC)
255  {
256  const int pdg{std::abs(pMC->GetParticleId())};
257  if ((pdg == MU_MINUS || pdg == E_MINUS || pdg == TAU_MINUS) && pMC->GetEnergy() > leadingLeptonEnergy)
258  {
259  pLeadingLepton = const_cast<Node *>(pNode);
260  leadingLeptonEnergy = pMC->GetEnergy();
261  }
262  }
263  }
264  if (pLeadingLepton)
265  pLeadingLepton->SetLeadingLepton();
266  }
267 }
268 
269 //------------------------------------------------------------------------------------------------------------------------------------------
270 
272 {
273  if (m_interactions.find(pRoot) == m_interactions.end())
274  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
275 
276  return m_interactions.at(pRoot);
277 }
278 
279 //------------------------------------------------------------------------------------------------------------------------------------------
280 
281 void LArHierarchyHelper::MCHierarchy::GetRootMCParticles(MCParticleList &rootMCParticles) const
282 {
283  for (auto iter = m_interactions.begin(); iter != m_interactions.end(); ++iter)
284  rootMCParticles.emplace_back(iter->first);
285 }
286 
287 //------------------------------------------------------------------------------------------------------------------------------------------
288 
290  const MCParticle *const pRoot, MCParticleList &leadingParticles, MCParticleList &childParticles, const float cosAngleTolerance) const
291 {
292  leadingParticles.emplace_back(pRoot);
293  MCParticleList foldCandidates, childCandidates;
294  const MCParticleList &children{pRoot->GetDaughterList()};
295  for (const MCParticle *pMCParticle : children)
296  {
297  const LArMCParticle *pLArMCParticle{dynamic_cast<const LArMCParticle *>(pMCParticle)};
298  if (!pLArMCParticle)
299  continue;
301  {
302  // Elastic and inelastic scattering can either lead to folding, distinct nodes or disposable hits, all other processes
303  // are either distinct nodes, or disposable
304  if (pMCParticle->GetParticleId() == pRoot->GetParticleId())
305  foldCandidates.emplace_back(pMCParticle);
306  else
307  childCandidates.emplace_back(pMCParticle);
308  }
309  else if (!m_recoCriteria.m_removeNeutrons || (m_recoCriteria.m_removeNeutrons && pLArMCParticle->GetProcess() != MC_PROC_N_CAPTURE))
310  {
311  // Non-scattering process particles become leading candidates unless it's neutron capture and we're removing neutrons
312  childCandidates.emplace_back(pMCParticle);
313  }
314  }
315  const MCParticle *pBestFoldCandidate{nullptr};
316  float bestDp{std::numeric_limits<float>::max()};
317  for (const MCParticle *pMCParticle : foldCandidates)
318  {
319  if (foldCandidates.size() == 1)
320  {
321  // No alternative options, so this is either the best folding option by default, or a sufficiently large scatter to
322  // treat as a new particle for reconstruction purposes
323  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
324  pBestFoldCandidate = pMCParticle;
325  else
326  childCandidates.emplace_back(pMCParticle);
327  }
328  else
329  {
330  // Assess which, if any, of the children might be a continuation of the trajectory, otherwise move to child candidates
331  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
332  {
333  const float dp{pRoot->GetMomentum().GetMagnitude() - pMCParticle->GetMomentum().GetMagnitude()};
334  if (dp < bestDp)
335  {
336  pBestFoldCandidate = pMCParticle;
337  bestDp = dp;
338  }
339  }
340  else
341  {
342  childCandidates.emplace_back(pMCParticle);
343  }
344  }
345  }
346  if (pBestFoldCandidate)
347  {
348  leadingParticles.emplace_back(pBestFoldCandidate);
349  // Having found a particle to fold back at this level, continue to explore its downstream hierarchy for further folding
350  // opportunities and make their respective children leading particles for the folded node we are creating
351  this->CollectContinuations(pBestFoldCandidate, leadingParticles, childCandidates, cosAngleTolerance);
352  }
353  for (const MCParticle *pMCParticle : childCandidates)
354  {
355  // Consider if the child particle will produce enough downstream hits to warrant inclusion
356  if (this->IsReconstructable(pMCParticle))
357  childParticles.emplace_back(pMCParticle);
358  else
359  {
360  MCParticleList localHierarchy{pMCParticle};
361  CaloHitList localHits;
362  LArMCParticleHelper::GetAllDescendentMCParticles(pMCParticle, localHierarchy);
363  for (const MCParticle *pLocalMCParticle : localHierarchy)
364  {
365  if (m_mcToHitsMap.find(pLocalMCParticle) != m_mcToHitsMap.end())
366  {
367  const CaloHitList &caloHits(m_mcToHitsMap.at(pLocalMCParticle));
368  localHits.insert(localHits.begin(), caloHits.begin(), caloHits.end());
369  }
370  }
371  if (this->IsReconstructable(localHits))
372  childParticles.emplace_back(pMCParticle);
373  }
374  }
375 }
376 
377 //------------------------------------------------------------------------------------------------------------------------------------------
378 
380  const MCParticle *pRoot, MCParticleList &continuingParticles, MCParticleList &childParticles, const float cosAngleTolerance) const
381 {
382  const MCParticleList &children{pRoot->GetDaughterList()};
383  MCParticleList foldCandidates;
384  for (const MCParticle *pMCParticle : children)
385  {
386  const LArMCParticle *pLArMCParticle{dynamic_cast<const LArMCParticle *>(pMCParticle)};
387  if (!pLArMCParticle)
388  continue;
389  // Only elastic and inelastic scattering can lead to folding
391  {
392  if (pMCParticle->GetParticleId() == pRoot->GetParticleId())
393  foldCandidates.emplace_back(pMCParticle);
394  }
395  else if (!m_recoCriteria.m_removeNeutrons || (m_recoCriteria.m_removeNeutrons && pLArMCParticle->GetProcess() != MC_PROC_N_CAPTURE))
396  {
397  // Non-scattering process particles become leading candidates unless it's neutron capture and we're removing neutrons
398  childParticles.emplace_back(pMCParticle);
399  }
400  }
401  const MCParticle *pBestFoldCandidate{nullptr};
402  float bestDp{std::numeric_limits<float>::max()};
403  for (const MCParticle *pMCParticle : foldCandidates)
404  {
405  if (foldCandidates.size() == 1)
406  {
407  // No alternative options, so this is either the best folding option by default, or a sufficiently large scatter to
408  // treat as a new particle for reconstruction purposes
409  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
410  pBestFoldCandidate = pMCParticle;
411  }
412  else
413  {
414  // Assess which, if any, of the children might be a continuation of the trajectory, otherwise move to child candidates
415  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
416  {
417  const float dp{pRoot->GetMomentum().GetMagnitude() - pMCParticle->GetMomentum().GetMagnitude()};
418  if (dp < bestDp)
419  {
420  pBestFoldCandidate = pMCParticle;
421  bestDp = dp;
422  }
423  }
424  }
425  }
426  if (pBestFoldCandidate)
427  {
428  continuingParticles.emplace_back(pBestFoldCandidate);
429  const MCParticleList &newLeadingParticles{pBestFoldCandidate->GetDaughterList()};
430  // We need to add the children as child particles to ensure these sub-hierarchies are explored...
431  childParticles.insert(childParticles.begin(), newLeadingParticles.begin(), newLeadingParticles.end());
432  // but this current best fold candidate may have been added to the child particles by previously, so remove it
433  const auto iter{std::find(childParticles.begin(), childParticles.end(), pBestFoldCandidate)};
434  if (iter != childParticles.end())
435  childParticles.erase(iter);
436  // Having found a particle to fold back at this level, continue to explore its downstream hierarchy for further folding
437  // opportunities and make their respective children child particles for the folded node we are creating
438  LArHierarchyHelper::MCHierarchy::CollectContinuations(pBestFoldCandidate, continuingParticles, childParticles, cosAngleTolerance);
439  }
440 }
441 
442 //------------------------------------------------------------------------------------------------------------------------------------------
443 
444 void LArHierarchyHelper::MCHierarchy::GetFlattenedNodes(const MCParticle *const pRoot, NodeVector &nodeVector) const
445 {
446  NodeList queue;
447  for (const Node *pNode : m_interactions.at(pRoot))
448  {
449  nodeVector.emplace_back(pNode);
450  queue.emplace_back(pNode);
451  }
452  while (!queue.empty())
453  {
454  const NodeVector &children{queue.front()->GetChildren()};
455  queue.pop_front();
456  for (const Node *pChild : children)
457  {
458  nodeVector.emplace_back(pChild);
459  queue.emplace_back(pChild);
460  }
461  }
462 }
463 
464 //------------------------------------------------------------------------------------------------------------------------------------------
465 
467 {
468  m_nodeToIdMap.insert(std::make_pair(pNode, m_nextNodeId));
469  ++m_nextNodeId;
470 }
471 
472 //------------------------------------------------------------------------------------------------------------------------------------------
473 
475 {
476  std::string str;
477  for (const auto &[pRoot, nodeVector] : m_interactions)
478  {
479  const LArMCParticle *const pLArRoot{dynamic_cast<const LArMCParticle *const>(pRoot)};
480  if (pLArRoot)
481  str += "=== MC Interaction : PDG " + std::to_string(pLArRoot->GetParticleId()) +
482  " Energy: " + std::to_string(pLArRoot->GetEnergy()) + " Nuance: " + std::to_string(pLArRoot->GetNuanceCode()) + "\n";
483  else
484  str += "=== MC Interaction : PDG " + std::to_string(pRoot->GetParticleId()) + " Energy: " + std::to_string(pRoot->GetEnergy()) + "\n";
485  for (const Node *pNode : nodeVector)
486  str += " " + pNode->ToString("") + "\n";
487  }
488 
489  return str;
490 }
491 
492 //------------------------------------------------------------------------------------------------------------------------------------------
493 
494 bool LArHierarchyHelper::MCHierarchy::IsReconstructable(const pandora::MCParticle *pMCParticle) const
495 {
496  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
497  {
498  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
499  for (const CaloHit *pCaloHit : m_mcToHitsMap.at(pMCParticle))
500  {
501  const HitType view{pCaloHit->GetHitType()};
502  if (view == TPC_VIEW_U)
503  ++nHitsU;
504  else if (view == TPC_VIEW_V)
505  ++nHitsV;
506  else if (view == TPC_VIEW_W)
507  ++nHitsW;
508  }
509  const unsigned int nHits{nHitsU + nHitsV + nHitsW};
510  unsigned int nGoodViews{0};
511  nGoodViews += nHitsU >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
512  nGoodViews += nHitsV >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
513  nGoodViews += nHitsW >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
514 
515  return nHits >= m_recoCriteria.m_minHits && nGoodViews >= m_recoCriteria.m_minGoodViews;
516  }
517 
518  return false;
519 }
520 
521 //------------------------------------------------------------------------------------------------------------------------------------------
522 
523 bool LArHierarchyHelper::MCHierarchy::IsReconstructable(const CaloHitList &caloHits) const
524 {
525  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
526  for (const CaloHit *pCaloHit : caloHits)
527  {
528  const HitType view{pCaloHit->GetHitType()};
529  if (view == TPC_VIEW_U)
530  ++nHitsU;
531  else if (view == TPC_VIEW_V)
532  ++nHitsV;
533  else if (view == TPC_VIEW_W)
534  ++nHitsW;
535  }
536  const unsigned int nHits{nHitsU + nHitsV + nHitsW};
537  unsigned int nGoodViews{0};
538  nGoodViews += nHitsU >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
539  nGoodViews += nHitsV >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
540  nGoodViews += nHitsW >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
541 
542  return nHits >= m_recoCriteria.m_minHits && nGoodViews >= m_recoCriteria.m_minGoodViews;
543 }
544 
545 //------------------------------------------------------------------------------------------------------------------------------------------
546 //------------------------------------------------------------------------------------------------------------------------------------------
547 
548 LArHierarchyHelper::MCHierarchy::Node::Node(MCHierarchy &hierarchy, const MCParticle *pMCParticle, const int tier) :
549  m_hierarchy(hierarchy),
550  m_mainParticle(pMCParticle),
551  m_tier{tier},
552  m_pdg{0},
553  m_isLeadingLepton{false}
554 {
555  if (pMCParticle)
556  {
557  m_pdg = pMCParticle->GetParticleId();
558  m_mcParticles.emplace_back(pMCParticle);
559  }
560  m_hierarchy.RegisterNode(this);
561 }
562 
563 //------------------------------------------------------------------------------------------------------------------------------------------
564 
565 LArHierarchyHelper::MCHierarchy::Node::Node(MCHierarchy &hierarchy, const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const int tier) :
566  m_hierarchy(hierarchy),
567  m_mcParticles(mcParticleList),
568  m_caloHits(caloHitList),
569  m_mainParticle(nullptr),
570  m_tier{tier},
571  m_pdg{0},
572  m_isLeadingLepton{false}
573 {
574  if (!mcParticleList.empty())
575  {
576  m_mainParticle = mcParticleList.front();
577  m_pdg = m_mainParticle->GetParticleId();
578  }
579  m_mcParticles.sort(LArMCParticleHelper::SortByMomentum);
580  m_caloHits.sort();
581  m_hierarchy.RegisterNode(this);
582 }
583 
584 //------------------------------------------------------------------------------------------------------------------------------------------
585 
587 {
588  m_mcParticles.clear();
589  m_caloHits.clear();
590  for (const Node *node : m_children)
591  delete node;
592  m_children.clear();
593 }
594 
595 //------------------------------------------------------------------------------------------------------------------------------------------
596 
597 void LArHierarchyHelper::MCHierarchy::Node::FillHierarchy(const MCParticle *pRoot, const FoldingParameters &foldParameters)
598 {
599  if (foldParameters.m_foldDynamic)
600  {
601  MCParticleList leadingParticles, childParticles;
602  m_hierarchy.InterpretHierarchy(pRoot, leadingParticles, childParticles, foldParameters.m_cosAngleTolerance);
603  CaloHitList allHits;
604  for (const MCParticle *pMCParticle : leadingParticles)
605  {
606  // ATTN - Not all MC particles will have hits
607  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
608  {
609  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
610  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
611  }
612  }
613 
614  Node *pNode{new Node(m_hierarchy, leadingParticles, allHits, this->m_tier + 1)};
615  m_children.emplace_back(pNode);
616  for (const MCParticle *pChild : childParticles)
617  pNode->FillHierarchy(pChild, foldParameters);
618  }
619  else
620  {
621  MCParticleList allParticles{pRoot};
622  const int pdg{std::abs(pRoot->GetParticleId())};
623  const bool isShower{pdg == E_MINUS || pdg == PHOTON};
624  const bool isNeutron{pdg == NEUTRON};
625 
626  if (foldParameters.m_foldToTier && LArMCParticleHelper::GetHierarchyTier(pRoot) >= foldParameters.m_tier)
628  else if (foldParameters.m_foldToLeadingShowers && (isShower || (isNeutron && !m_hierarchy.m_recoCriteria.m_removeNeutrons)))
630  else if (m_hierarchy.m_recoCriteria.m_removeNeutrons && isNeutron)
631  return;
632 
633  CaloHitList allHits;
634  for (const MCParticle *pMCParticle : allParticles)
635  {
636  // ATTN - Not all MC particles will have hits
637  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
638  {
639  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
640  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
641  }
642  }
643 
644  if (!allParticles.empty())
645  {
646  const bool hasChildren{(foldParameters.m_foldToTier && LArMCParticleHelper::GetHierarchyTier(pRoot) < foldParameters.m_tier) ||
647  (!foldParameters.m_foldToTier && !foldParameters.m_foldToLeadingShowers) ||
648  (foldParameters.m_foldToLeadingShowers && !(isShower || isNeutron))};
649  // Only add the node if it either has children, or is a leaf node with hits
650  if (hasChildren || (!hasChildren && !allHits.empty()))
651  {
652  Node *pNode{new Node(m_hierarchy, allParticles, allHits, this->m_tier + 1)};
653  m_children.emplace_back(pNode);
654  if (hasChildren)
655  {
656  // Find the children of this particle and recursively add them to the hierarchy
657  const MCParticleList &children{pRoot->GetDaughterList()};
658  for (const MCParticle *pChild : children)
659  pNode->FillHierarchy(pChild, foldParameters);
660  }
661  }
662  }
663  }
664 }
665 
666 //------------------------------------------------------------------------------------------------------------------------------------------
667 
669 {
670  MCParticleList allParticles{pRoot};
671  if (!m_hierarchy.m_recoCriteria.m_removeNeutrons)
672  {
674  }
675  else
676  {
677  MCParticleList neutrons;
678  LArMCParticleHelper::GetAllDescendentMCParticles(pRoot, allParticles, allParticles, neutrons);
679  }
680  CaloHitList allHits;
681  for (const MCParticle *pMCParticle : allParticles)
682  {
683  // ATTN - Not all MC particles will have hits
684  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
685  {
686  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
687  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
688  }
689  }
690  if (!allParticles.empty())
691  {
692  Node *pNode{new Node(m_hierarchy, allParticles, allHits, this->m_tier + 1)};
693  m_children.emplace_back(pNode);
694  }
695 }
696 
697 //------------------------------------------------------------------------------------------------------------------------------------------
698 
700 {
701  return m_hierarchy.m_nodeToIdMap.at(this);
702 }
703 
704 //------------------------------------------------------------------------------------------------------------------------------------------
705 
707 {
708  const bool enoughHits{m_caloHits.size() >= m_hierarchy.m_recoCriteria.m_minHits};
709  if (!enoughHits)
710  return false;
711  bool enoughGoodViews{false};
712  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
713  for (const CaloHit *pCaloHit : m_caloHits)
714  {
715  switch (pCaloHit->GetHitType())
716  {
717  case TPC_VIEW_U:
718  ++nHitsU;
719  break;
720  case TPC_VIEW_V:
721  ++nHitsV;
722  break;
723  case TPC_VIEW_W:
724  ++nHitsW;
725  break;
726  default:
727  break;
728  }
729  unsigned int nGoodViews{0};
730  if (nHitsU >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
731  ++nGoodViews;
732  if (nHitsV >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
733  ++nGoodViews;
734  if (nHitsW >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
735  ++nGoodViews;
736  if (nGoodViews >= m_hierarchy.m_recoCriteria.m_minGoodViews)
737  {
738  enoughGoodViews = true;
739  break;
740  }
741  }
742 
743  return enoughGoodViews;
744 }
745 
746 //------------------------------------------------------------------------------------------------------------------------------------------
747 
749 {
750  if (m_mainParticle)
751  return LArMCParticleHelper::IsBeamParticle(m_mainParticle);
752  else
753  return false;
754 }
755 
756 //------------------------------------------------------------------------------------------------------------------------------------------
757 
759 {
760  if (m_mainParticle)
761  return LArMCParticleHelper::IsCosmicRay(m_mainParticle);
762  else
763  return false;
764 }
765 
766 //------------------------------------------------------------------------------------------------------------------------------------------
767 
768 const std::string LArHierarchyHelper::MCHierarchy::Node::ToString(const std::string &prefix) const
769 {
770  std::string str(prefix + "PDG: " + std::to_string(m_pdg) + " Energy: " + std::to_string(m_mainParticle ? m_mainParticle->GetEnergy() : 0) +
771  " Hits: " + std::to_string(m_caloHits.size()) + "\n");
772  for (const Node *pChild : m_children)
773  str += pChild->ToString(prefix + " ");
774 
775  return str;
776 }
777 
778 //------------------------------------------------------------------------------------------------------------------------------------------
779 //------------------------------------------------------------------------------------------------------------------------------------------
780 
782  m_minHits{30},
784  m_minGoodViews{2},
785  m_removeNeutrons{true}
786 {
787 }
788 
789 //------------------------------------------------------------------------------------------------------------------------------------------
790 
792  m_minHits{obj.m_minHits},
793  m_minHitsForGoodView{obj.m_minHitsForGoodView},
794  m_minGoodViews{obj.m_minGoodViews},
795  m_removeNeutrons{obj.m_removeNeutrons}
796 {
797 }
798 
799 //------------------------------------------------------------------------------------------------------------------------------------------
800 
802  const unsigned int minHits, const unsigned int minHitsForGoodView, const unsigned int minGoodViews, const bool removeNeutrons) :
803  m_minHits{minHits},
804  m_minHitsForGoodView{minHitsForGoodView},
805  m_minGoodViews{minGoodViews},
806  m_removeNeutrons{removeNeutrons}
807 {
808 }
809 
810 //------------------------------------------------------------------------------------------------------------------------------------------
811 //------------------------------------------------------------------------------------------------------------------------------------------
812 
814 {
815 }
816 
817 //------------------------------------------------------------------------------------------------------------------------------------------
818 
820 {
821  for (const auto &[pRoot, nodeVector] : m_interactions)
822  {
823  for (const Node *pNode : nodeVector)
824  delete pNode;
825  }
826  m_interactions.clear();
827 }
828 
829 //------------------------------------------------------------------------------------------------------------------------------------------
830 
831 void LArHierarchyHelper::RecoHierarchy::FillHierarchy(const PfoList &pfoList, const FoldingParameters &foldParameters)
832 {
833  PfoList rootNodes;
834  for (const ParticleFlowObject *pPfo : pfoList)
835  {
836  const PfoList &parentList{pPfo->GetParentPfoList()};
837  if (parentList.empty())
838  {
839  rootNodes.emplace_back(pPfo);
840  }
841  }
842 
843  for (const ParticleFlowObject *const pRoot : rootNodes)
844  {
845  PfoSet primarySet;
846  LArHierarchyHelper::GetRecoPrimaries(pRoot, primarySet);
847  PfoList primaries(primarySet.begin(), primarySet.end());
848  primaries.sort(LArPfoHelper::SortByNHits);
849  if (foldParameters.m_foldToTier && foldParameters.m_tier == 1)
850  {
851  for (const ParticleFlowObject *pPrimary : primaries)
852  {
853  PfoList allParticles;
854  // ATTN - pPrimary gets added to the list of downstream PFOs, not just the child PFOs
855  LArPfoHelper::GetAllDownstreamPfos(pPrimary, allParticles);
856  CaloHitList allHits;
857  for (const ParticleFlowObject *pPfo : allParticles)
858  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
859  m_interactions[pRoot].emplace_back(new Node(*this, allParticles, allHits));
860  }
861  }
862  else if (foldParameters.m_foldToLeadingShowers)
863  {
864  for (const ParticleFlowObject *pPrimary : primaries)
865  {
866  PfoList allParticles;
867  int pdg{std::abs(pPrimary->GetParticleId())};
868  const bool isShower{pdg == E_MINUS};
869  // ATTN - pPrimary gets added to the list of downstream PFOs, not just the child PFOs
870  if (isShower)
871  LArPfoHelper::GetAllDownstreamPfos(pPrimary, allParticles);
872  else
873  allParticles.emplace_back(pPrimary);
874 
875  CaloHitList allHits;
876  for (const ParticleFlowObject *pPfo : allParticles)
877  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
878  Node *pNode{new Node(*this, allParticles, allHits)};
879  m_interactions[pRoot].emplace_back(pNode);
880  if (!isShower)
881  {
882  // Find the children of this particle and recursively add them to the hierarchy
883  const PfoList &children{pPrimary->GetDaughterPfoList()};
884  for (const ParticleFlowObject *pChild : children)
885  pNode->FillHierarchy(pChild, foldParameters);
886  }
887  }
888  }
889  else
890  {
891  // Dynamic fold, Unfolded and fold to tier > 1 have the same behaviour for primaries
892  for (const ParticleFlowObject *pPrimary : primaries)
893  {
894  PfoList allParticles{pPrimary};
895  CaloHitList allHits;
896  for (const ParticleFlowObject *pPfo : allParticles)
897  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
898  Node *pNode{new Node(*this, allParticles, allHits)};
899  m_interactions[pRoot].emplace_back(pNode);
900  // Find the children of this particle and recursively add them to the hierarchy
901  const PfoList &children{pPrimary->GetDaughterPfoList()};
902  for (const ParticleFlowObject *pChild : children)
903  pNode->FillHierarchy(pChild, foldParameters.m_foldToLeadingShowers);
904  }
905  }
906  }
907 }
908 
909 //------------------------------------------------------------------------------------------------------------------------------------------
910 
912 {
913  if (m_interactions.find(pRoot) == m_interactions.end())
914  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
915 
916  return m_interactions.at(pRoot);
917 }
918 
919 //------------------------------------------------------------------------------------------------------------------------------------------
920 
922 {
923  for (auto iter = m_interactions.begin(); iter != m_interactions.end(); ++iter)
924  rootPfos.emplace_back(iter->first);
925 }
926 
927 //------------------------------------------------------------------------------------------------------------------------------------------
928 
929 void LArHierarchyHelper::RecoHierarchy::GetFlattenedNodes(const ParticleFlowObject *pRoot, NodeVector &nodeVector) const
930 {
931  NodeList queue;
932  for (const Node *pNode : m_interactions.at(pRoot))
933  {
934  nodeVector.emplace_back(pNode);
935  queue.emplace_back(pNode);
936  }
937  while (!queue.empty())
938  {
939  const NodeVector &children{queue.front()->GetChildren()};
940  queue.pop_front();
941  for (const Node *pChild : children)
942  {
943  nodeVector.emplace_back(pChild);
944  queue.emplace_back(pChild);
945  }
946  }
947 }
948 
949 //------------------------------------------------------------------------------------------------------------------------------------------
950 
952 {
953  std::string str;
954  for (const auto &[pRoot, nodeVector] : m_interactions)
955  {
956  str += "=== Reco Interaction : PDG " + std::to_string(pRoot->GetParticleId()) + "\n";
957  for (const Node *pNode : nodeVector)
958  str += " " + pNode->ToString("") + "\n";
959  }
960 
961  return str;
962 }
963 
964 //------------------------------------------------------------------------------------------------------------------------------------------
965 //------------------------------------------------------------------------------------------------------------------------------------------
966 
967 LArHierarchyHelper::RecoHierarchy::Node::Node(const RecoHierarchy &hierarchy, const ParticleFlowObject *pPfo, const int tier) :
968  m_hierarchy{hierarchy},
969  m_mainPfo{pPfo},
970  m_tier{tier},
971  m_pdg{0}
972 {
973  if (pPfo)
974  {
975  m_pdg = pPfo->GetParticleId();
976  m_pfos.emplace_back(pPfo);
977  }
978 }
979 
980 //------------------------------------------------------------------------------------------------------------------------------------------
981 
982 LArHierarchyHelper::RecoHierarchy::Node::Node(const RecoHierarchy &hierarchy, const PfoList &pfoList, const CaloHitList &caloHitList, const int tier) :
983  m_hierarchy(hierarchy),
984  m_mainPfo{nullptr},
985  m_tier{tier},
986  m_pdg{0}
987 {
988  if (!pfoList.empty())
989  {
990  m_mainPfo = pfoList.front();
991  m_pdg = pfoList.front()->GetParticleId();
992  }
993  m_pfos = pfoList;
995  m_caloHits = caloHitList;
996  m_caloHits.sort();
997 }
998 
999 //------------------------------------------------------------------------------------------------------------------------------------------
1000 
1002 {
1003  m_pfos.clear();
1004  m_caloHits.clear();
1005  for (const Node *node : m_children)
1006  delete node;
1007  m_children.clear();
1008 }
1009 
1010 //------------------------------------------------------------------------------------------------------------------------------------------
1011 
1012 void LArHierarchyHelper::RecoHierarchy::Node::FillHierarchy(const ParticleFlowObject *pRoot, const FoldingParameters &foldParameters)
1013 {
1014  PfoList allParticles;
1015  int pdg{std::abs(pRoot->GetParticleId())};
1016  const bool isShower{pdg == E_MINUS};
1017  if (foldParameters.m_foldToTier && LArPfoHelper::GetHierarchyTier(pRoot) >= foldParameters.m_tier)
1018  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
1019  else if (foldParameters.m_foldToLeadingShowers && isShower)
1020  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
1021  else
1022  allParticles.emplace_back(pRoot);
1023 
1024  CaloHitList allHits;
1025  for (const ParticleFlowObject *pPfo : allParticles)
1026  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
1027  const bool hasChildren{(foldParameters.m_foldToTier && LArPfoHelper::GetHierarchyTier(pRoot) < foldParameters.m_tier) ||
1028  (!foldParameters.m_foldToTier && !foldParameters.m_foldToLeadingShowers) || (foldParameters.m_foldToLeadingShowers && !isShower)};
1029 
1030  if (hasChildren || (!hasChildren && !allHits.empty()))
1031  {
1032  Node *pNode{new Node(m_hierarchy, allParticles, allHits, m_tier + 1)};
1033  m_children.emplace_back(pNode);
1034 
1035  if (hasChildren)
1036  {
1037  const PfoList &children{pRoot->GetDaughterPfoList()};
1038  for (const ParticleFlowObject *pChild : children)
1039  pNode->FillHierarchy(pChild, foldParameters);
1040  }
1041  }
1042 }
1043 
1044 //------------------------------------------------------------------------------------------------------------------------------------------
1045 
1046 void LArHierarchyHelper::RecoHierarchy::Node::FillFlat(const ParticleFlowObject *pRoot)
1047 {
1048  PfoList allParticles;
1049  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
1050  CaloHitList allHits;
1051  for (const ParticleFlowObject *pPfo : allParticles)
1052  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
1053  Node *pNode{new Node(m_hierarchy, allParticles, allHits, m_tier + 1)};
1054  m_children.emplace_back(pNode);
1055 }
1056 
1057 //------------------------------------------------------------------------------------------------------------------------------------------
1058 
1060 {
1061  return m_pfos;
1062 }
1063 
1064 //------------------------------------------------------------------------------------------------------------------------------------------
1065 
1067 {
1068  return m_caloHits;
1069 }
1070 
1071 //------------------------------------------------------------------------------------------------------------------------------------------
1072 
1074 {
1075  return m_pdg;
1076 }
1077 
1078 //------------------------------------------------------------------------------------------------------------------------------------------
1079 
1080 const std::string LArHierarchyHelper::RecoHierarchy::Node::ToString(const std::string &prefix) const
1081 {
1082  std::string str(
1083  prefix + "PDG: " + std::to_string(m_pdg) + " Tier: " + std::to_string(m_tier) + " Hits: " + std::to_string(m_caloHits.size()) + "\n");
1084  for (const Node *pChild : m_children)
1085  str += pChild->ToString(prefix + " ");
1086 
1087  return str;
1088 }
1089 
1090 //------------------------------------------------------------------------------------------------------------------------------------------
1091 //------------------------------------------------------------------------------------------------------------------------------------------
1092 
1094  m_pMCParticle{pMCParticle}
1095 {
1096 }
1097 
1098 //------------------------------------------------------------------------------------------------------------------------------------------
1099 
1100 void LArHierarchyHelper::MCMatches::AddRecoMatch(const RecoHierarchy::Node *pReco, const int nSharedHits, const CaloHitList &selectedRecoHits)
1101 {
1102  m_recoNodes.emplace_back(pReco);
1103  m_sharedHits.emplace_back(nSharedHits);
1104  m_selectedRecoHitsMap.insert(std::make_pair(pReco, selectedRecoHits));
1105 }
1106 
1107 //------------------------------------------------------------------------------------------------------------------------------------------
1108 
1110 {
1111  auto iter{m_selectedRecoHitsMap.find(pReco)};
1112  if (iter != m_selectedRecoHitsMap.end())
1113  return iter->second;
1114  else
1115  return pReco->GetCaloHits();
1116 }
1117 
1118 //------------------------------------------------------------------------------------------------------------------------------------------
1119 
1121 {
1122  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1123  if (iter == m_recoNodes.end())
1124  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1125  int index = iter - m_recoNodes.begin();
1126 
1127  return static_cast<int>(m_sharedHits[index]);
1128 }
1129 
1130 //------------------------------------------------------------------------------------------------------------------------------------------
1131 
1132 float LArHierarchyHelper::MCMatches::GetPurity(const RecoHierarchy::Node *pReco, const bool adcWeighted) const
1133 {
1134  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1135  if (iter == m_recoNodes.end())
1136  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1137 
1138  const CaloHitList recoHits = LArHierarchyHelper::MCMatches::GetSelectedRecoHits(pReco);
1139  const CaloHitList &mcHits{m_pMCParticle->GetCaloHits()};
1140  CaloHitVector intersection;
1141  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1142 
1143  return this->GetPurity(intersection, recoHits, adcWeighted);
1144 }
1145 
1146 //------------------------------------------------------------------------------------------------------------------------------------------
1147 
1148 float LArHierarchyHelper::MCMatches::GetPurity(const RecoHierarchy::Node *pReco, const HitType view, const bool adcWeighted) const
1149 {
1150  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1151  if (iter == m_recoNodes.end())
1152  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1153 
1154  CaloHitList recoHits;
1155  for (const CaloHit *pCaloHit : LArHierarchyHelper::MCMatches::GetSelectedRecoHits(pReco))
1156  if (pCaloHit->GetHitType() == view)
1157  recoHits.emplace_back(pCaloHit);
1158  CaloHitList mcHits;
1159  for (const CaloHit *pCaloHit : m_pMCParticle->GetCaloHits())
1160  if (pCaloHit->GetHitType() == view)
1161  mcHits.emplace_back(pCaloHit);
1162 
1163  CaloHitVector intersection;
1164  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1165 
1166  return this->GetPurity(intersection, recoHits, adcWeighted);
1167 }
1168 
1169 //------------------------------------------------------------------------------------------------------------------------------------------
1170 
1171 float LArHierarchyHelper::MCMatches::GetCompleteness(const RecoHierarchy::Node *pReco, const bool adcWeighted) const
1172 {
1173  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1174  if (iter == m_recoNodes.end())
1175  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1176 
1177  const CaloHitList recoHits = LArHierarchyHelper::MCMatches::GetSelectedRecoHits(pReco);
1178  const CaloHitList &mcHits{m_pMCParticle->GetCaloHits()};
1179  CaloHitVector intersection;
1180  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1181 
1182  return this->GetCompleteness(intersection, mcHits, adcWeighted);
1183 }
1184 
1185 //------------------------------------------------------------------------------------------------------------------------------------------
1186 
1187 float LArHierarchyHelper::MCMatches::GetCompleteness(const RecoHierarchy::Node *pReco, const HitType view, const bool adcWeighted) const
1188 {
1189  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1190  if (iter == m_recoNodes.end())
1191  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1192 
1193  CaloHitList recoHits;
1194  for (const CaloHit *pCaloHit : LArHierarchyHelper::MCMatches::GetSelectedRecoHits(pReco))
1195  if (pCaloHit->GetHitType() == view)
1196  recoHits.emplace_back(pCaloHit);
1197  CaloHitList mcHits;
1198  for (const CaloHit *pCaloHit : m_pMCParticle->GetCaloHits())
1199  if (pCaloHit->GetHitType() == view)
1200  mcHits.emplace_back(pCaloHit);
1201 
1202  CaloHitVector intersection;
1203  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1204 
1205  return this->GetCompleteness(intersection, mcHits, adcWeighted);
1206 }
1207 
1208 //------------------------------------------------------------------------------------------------------------------------------------------
1209 
1210 float LArHierarchyHelper::MCMatches::GetPurity(const CaloHitVector &intersection, const CaloHitList &recoHits, const bool adcWeighted) const
1211 {
1212  float purity{0.f};
1213  if (!intersection.empty())
1214  {
1215  if (adcWeighted)
1216  {
1217  float adcSum{0.f};
1218  for (const CaloHit *pCaloHit : recoHits)
1219  adcSum += pCaloHit->GetInputEnergy();
1220  if (adcSum > std::numeric_limits<float>::epsilon())
1221  {
1222  for (const CaloHit *pCaloHit : intersection)
1223  purity += pCaloHit->GetInputEnergy();
1224  purity /= adcSum;
1225  }
1226  }
1227  else
1228  {
1229  purity = intersection.size() / static_cast<float>(recoHits.size());
1230  }
1231  }
1232 
1233  return purity;
1234 }
1235 
1236 //------------------------------------------------------------------------------------------------------------------------------------------
1237 
1238 float LArHierarchyHelper::MCMatches::GetCompleteness(const CaloHitVector &intersection, const CaloHitList &mcHits, const bool adcWeighted) const
1239 {
1240  float completeness{0.f};
1241  if (!intersection.empty())
1242  {
1243  if (adcWeighted)
1244  {
1245  float adcSum{0.f};
1246  for (const CaloHit *pCaloHit : mcHits)
1247  adcSum += pCaloHit->GetInputEnergy();
1248  if (adcSum > std::numeric_limits<float>::epsilon())
1249  {
1250  for (const CaloHit *pCaloHit : intersection)
1251  completeness += pCaloHit->GetInputEnergy();
1252  completeness /= adcSum;
1253  }
1254  }
1255  else
1256  {
1257  completeness = intersection.size() / static_cast<float>(mcHits.size());
1258  }
1259  }
1260 
1261  return completeness;
1262 }
1263 
1264 //------------------------------------------------------------------------------------------------------------------------------------------
1265 
1267 {
1268  if (m_recoNodes.empty())
1269  return false;
1270 
1271  int nAboveThreshold{0};
1272  if (m_recoNodes.size() != 1)
1273  {
1274  for (const LArHierarchyHelper::RecoHierarchy::Node *const pNode : m_recoNodes)
1275  if (this->GetCompleteness(pNode) > 0.1f)
1276  ++nAboveThreshold;
1277  if (nAboveThreshold != 1)
1278  return false;
1279  }
1280 
1281  if (this->GetPurity(m_recoNodes.front()) < qualityCuts.m_minPurity)
1282  return false;
1283 
1284  if (this->GetCompleteness(m_recoNodes.front()) < qualityCuts.m_minCompleteness)
1285  return false;
1286 
1287  return true;
1288 }
1289 
1290 //------------------------------------------------------------------------------------------------------------------------------------------
1291 //------------------------------------------------------------------------------------------------------------------------------------------
1292 
1293 LArHierarchyHelper::MatchInfo::MatchInfo(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy) :
1294  MatchInfo(mcHierarchy, recoHierarchy, QualityCuts())
1295 {
1296 }
1297 
1298 //------------------------------------------------------------------------------------------------------------------------------------------
1299 
1300 LArHierarchyHelper::MatchInfo::MatchInfo(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy, const QualityCuts &qualityCuts) :
1301  m_mcHierarchy{mcHierarchy},
1302  m_recoHierarchy{recoHierarchy},
1303  m_qualityCuts{qualityCuts}
1304 {
1305 }
1306 
1307 //------------------------------------------------------------------------------------------------------------------------------------------
1308 
1310 {
1311  MCParticleList rootMCParticles;
1312  m_mcHierarchy.GetRootMCParticles(rootMCParticles);
1313  PfoList rootPfos;
1314  m_recoHierarchy.GetRootPfos(rootPfos);
1315  std::map<const MCHierarchy::Node *, MCMatches> mcToMatchMap;
1316 
1317  for (const MCParticle *const pRootMC : rootMCParticles)
1318  {
1319  MCHierarchy::NodeVector mcNodes;
1320  m_mcHierarchy.GetFlattenedNodes(pRootMC, mcNodes);
1321 
1322  // Get all of the hits from the MC nodes (for selecting reco hits)
1323  CaloHitList allMCHits;
1324  for (const MCHierarchy::Node *pMCNode : mcNodes)
1325  {
1326  const CaloHitList &mcHits{pMCNode->GetCaloHits()};
1327  allMCHits.insert(allMCHits.begin(), mcHits.begin(), mcHits.end());
1328  }
1329 
1330  for (const ParticleFlowObject *const pRootPfo : rootPfos)
1331  {
1332  RecoHierarchy::NodeVector recoNodes;
1333  m_recoHierarchy.GetFlattenedNodes(pRootPfo, recoNodes);
1334 
1335  std::sort(mcNodes.begin(), mcNodes.end(),
1336  [](const MCHierarchy::Node *lhs, const MCHierarchy::Node *rhs)
1337  { return lhs->GetCaloHits().size() > rhs->GetCaloHits().size(); });
1338  std::sort(recoNodes.begin(), recoNodes.end(),
1339  [](const RecoHierarchy::Node *lhs, const RecoHierarchy::Node *rhs)
1340  { return lhs->GetCaloHits().size() > rhs->GetCaloHits().size(); });
1341 
1342  for (const RecoHierarchy::Node *pRecoNode : recoNodes)
1343  {
1344  // Get the selected list of reco hits that overlap with all of the MC hits
1345  // or just use all of the hits in the reco node
1346  const CaloHitList selectedRecoHits = (m_qualityCuts.m_selectRecoHits == true)
1348  : pRecoNode->GetCaloHits();
1349 
1350  const MCHierarchy::Node *pBestNode{nullptr};
1351  size_t bestSharedHits{0};
1352  for (const MCHierarchy::Node *pMCNode : mcNodes)
1353  {
1354  if (!pMCNode->IsReconstructable())
1355  continue;
1356  const CaloHitList &mcHits{pMCNode->GetCaloHits()};
1357  CaloHitVector intersection;
1358  std::set_intersection(
1359  mcHits.begin(), mcHits.end(), selectedRecoHits.begin(), selectedRecoHits.end(), std::back_inserter(intersection));
1360 
1361  if (!intersection.empty())
1362  {
1363  const size_t sharedHits{intersection.size()};
1364  if (sharedHits > bestSharedHits)
1365  {
1366  bestSharedHits = sharedHits;
1367  pBestNode = pMCNode;
1368  }
1369  }
1370  }
1371  if (pBestNode)
1372  {
1373  auto iter{mcToMatchMap.find(pBestNode)};
1374  if (iter != mcToMatchMap.end())
1375  {
1376  MCMatches &match(iter->second);
1377  match.AddRecoMatch(pRecoNode, static_cast<int>(bestSharedHits), selectedRecoHits);
1378  }
1379  else
1380  {
1381  MCMatches match(pBestNode);
1382  match.AddRecoMatch(pRecoNode, static_cast<int>(bestSharedHits), selectedRecoHits);
1383  mcToMatchMap.insert(std::make_pair(pBestNode, match));
1384  }
1385  }
1386  else
1387  {
1388  m_unmatchedReco.emplace_back(pRecoNode);
1389  }
1390  }
1391  }
1392  }
1393 
1394  for (auto [pMCNode, matches] : mcToMatchMap)
1395  {
1396  // We need to figure out which MC interaction hierarchy the matches belongs to
1397  for (const MCParticle *const pRootMC : rootMCParticles)
1398  {
1399  MCHierarchy::NodeVector mcNodes;
1400  m_mcHierarchy.GetFlattenedNodes(pRootMC, mcNodes);
1401  if (std::find(mcNodes.begin(), mcNodes.end(), pMCNode) != mcNodes.end())
1402  {
1403  m_matches[pRootMC].emplace_back(matches);
1404  break;
1405  }
1406  }
1407  }
1408 
1409  const auto predicate = [](const MCMatches &lhs, const MCMatches &rhs)
1410  { return lhs.GetMC()->GetCaloHits().size() > rhs.GetMC()->GetCaloHits().size(); };
1411 
1412  for (const MCParticle *const pRootMC : rootMCParticles)
1413  {
1414  std::sort(m_matches[pRootMC].begin(), m_matches[pRootMC].end(), predicate);
1415 
1416  MCHierarchy::NodeVector mcNodes;
1417  m_mcHierarchy.GetFlattenedNodes(pRootMC, mcNodes);
1418 
1419  for (const MCHierarchy::Node *pMCNode : mcNodes)
1420  {
1421  if (pMCNode->IsReconstructable() && mcToMatchMap.find(pMCNode) == mcToMatchMap.end())
1422  {
1423  MCMatches match(pMCNode);
1424  m_matches[pRootMC].emplace_back(match);
1425  }
1426  }
1427  }
1428 }
1429 
1430 //------------------------------------------------------------------------------------------------------------------------------------------
1431 
1432 unsigned int LArHierarchyHelper::MatchInfo::GetNMCNodes(const MCParticle *const pRoot) const
1433 {
1434  if (m_matches.find(pRoot) == m_matches.end())
1435  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1436 
1437  return static_cast<unsigned int>(m_matches.at(pRoot).size());
1438 }
1439 
1440 //------------------------------------------------------------------------------------------------------------------------------------------
1441 
1442 unsigned int LArHierarchyHelper::MatchInfo::GetNNeutrinoMCNodes(const MCParticle *const pRoot) const
1443 {
1444  if (m_matches.find(pRoot) == m_matches.end())
1445  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1446 
1447  unsigned int nNodes{0};
1448  for (const MCMatches &match : m_matches.at(pRoot))
1449  {
1450  const MCHierarchy::Node *pNode{match.GetMC()};
1451  if (!(pNode->IsCosmicRay() || pNode->IsTestBeamParticle()))
1452  ++nNodes;
1453  }
1454 
1455  return nNodes;
1456 }
1457 
1458 //------------------------------------------------------------------------------------------------------------------------------------------
1459 
1460 unsigned int LArHierarchyHelper::MatchInfo::GetNCosmicRayMCNodes(const MCParticle *const pRoot) const
1461 {
1462  if (m_matches.find(pRoot) == m_matches.end())
1463  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1464 
1465  unsigned int nNodes{0};
1466  for (const MCMatches &match : m_matches.at(pRoot))
1467  {
1468  const MCHierarchy::Node *pNode{match.GetMC()};
1469  if (pNode->IsCosmicRay())
1470  ++nNodes;
1471  }
1472 
1473  return nNodes;
1474 }
1475 
1476 //------------------------------------------------------------------------------------------------------------------------------------------
1477 
1478 unsigned int LArHierarchyHelper::MatchInfo::GetNTestBeamMCNodes(const MCParticle *const pRoot) const
1479 {
1480  if (m_matches.find(pRoot) == m_matches.end())
1481  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1482 
1483  unsigned int nNodes{0};
1484  for (const MCMatches &match : m_matches.at(pRoot))
1485  {
1486  const MCHierarchy::Node *pNode{match.GetMC()};
1487  if (pNode->IsTestBeamParticle())
1488  ++nNodes;
1489  }
1490 
1491  return nNodes;
1492 }
1493 
1494 //------------------------------------------------------------------------------------------------------------------------------------------
1495 
1496 const CaloHitList LArHierarchyHelper::MatchInfo::GetSelectedRecoHits(const RecoHierarchy::Node *pRecoNode, const CaloHitList &allMCHits) const
1497 {
1498  // Select all of the reco node hit Ids that overlap with the allMCHits Ids
1499  CaloHitList selectedHits;
1500  if (pRecoNode)
1501  {
1502  const CaloHitList recoHits{pRecoNode->GetCaloHits()};
1503  for (const CaloHit *pRecoHit : recoHits)
1504  {
1505  const int recoId = reinterpret_cast<intptr_t>(pRecoHit->GetParentAddress());
1506  for (const CaloHit *pMCHit : allMCHits)
1507  {
1508  const int mcId = reinterpret_cast<intptr_t>(pMCHit->GetParentAddress());
1509  if (recoId == mcId)
1510  {
1511  selectedHits.emplace_back(pRecoHit);
1512  break;
1513  }
1514  }
1515  }
1516  }
1517  return selectedHits;
1518 }
1519 
1520 //------------------------------------------------------------------------------------------------------------------------------------------
1521 
1523 {
1524  MCParticleList rootMCParticles;
1525  mcHierarchy.GetRootMCParticles(rootMCParticles);
1526 
1527  for (const MCParticle *const pRootMC : rootMCParticles)
1528  {
1529  const LArHierarchyHelper::MCMatchesVector &matches{this->GetMatches(pRootMC)};
1530 
1531  MCParticleList primaries;
1532  for (const LArHierarchyHelper::MCMatches &match : matches)
1533  {
1534  const LArHierarchyHelper::MCHierarchy::Node *pMCNode{match.GetMC()};
1535  if (pMCNode->GetHierarchyTier() == 1)
1536  {
1537  const MCParticle *const pLeadingMC{pMCNode->GetLeadingMCParticle()};
1538  primaries.emplace_back(pLeadingMC);
1539  }
1540  }
1541  if (primaries.size() == 0)
1542  continue;
1543  primaries.sort(LArMCParticleHelper::SortByMomentum);
1545 
1546  const LArMCParticle *const pLArRoot{dynamic_cast<const LArMCParticle *const>(pRootMC)};
1547  if (pLArRoot)
1548  std::cout << "=== MC Interaction : PDG " << std::to_string(pLArRoot->GetParticleId())
1549  << " Energy: " << std::to_string(pLArRoot->GetEnergy()) << " Type: " << descriptor.ToString() << std::endl;
1550  else
1551  std::cout << "=== MC Interaction : PDG " << std::to_string(pRootMC->GetParticleId())
1552  << " Energy: " << std::to_string(pRootMC->GetEnergy()) << " Type: " << descriptor.ToString() << std::endl;
1553 
1554  unsigned int nNeutrinoMCParticles{this->GetNNeutrinoMCNodes(pRootMC)}, nNeutrinoRecoParticles{0};
1555  unsigned int nCosmicMCParticles{this->GetNCosmicRayMCNodes(pRootMC)}, nCosmicRecoParticles{0};
1556  unsigned int nTestBeamMCParticles{this->GetNTestBeamMCNodes(pRootMC)}, nTestBeamRecoParticles{0};
1557  std::cout << " === Matches ===" << std::endl;
1558  std::cout << std::fixed << std::setprecision(2);
1559  for (const MCMatches &match : m_matches.at(pRootMC))
1560  {
1561  const MCHierarchy::Node *pMCNode{match.GetMC()};
1562  const int pdg{pMCNode->GetParticleId()};
1563  const size_t mcHits{pMCNode->GetCaloHits().size()};
1564  const std::string tag{pMCNode->IsTestBeamParticle() ? "(Beam) " : pMCNode->IsCosmicRay() ? "(Cosmic) " : ""};
1565  std::cout << " MC " << tag << pdg << " hits " << mcHits << std::endl;
1566  const RecoHierarchy::NodeVector &nodeVector{match.GetRecoMatches()};
1567 
1568  for (const RecoHierarchy::Node *pRecoNode : nodeVector)
1569  {
1570  const CaloHitList recoHits = match.GetSelectedRecoHits(pRecoNode);
1571  const unsigned int nRecoHits{static_cast<unsigned int>(recoHits.size())};
1572  const unsigned int sharedHits{match.GetSharedHits(pRecoNode)};
1573  const float purity{match.GetPurity(pRecoNode)};
1574  const float completeness{match.GetCompleteness(pRecoNode)};
1575  if (completeness > 0.1f)
1576  std::cout << " Matched " << sharedHits << " out of " << nRecoHits << " with purity " << purity << " and completeness "
1577  << completeness << std::endl;
1578  else
1579  std::cout << " (Below threshold) " << sharedHits << " out of " << nRecoHits << " with purity " << purity
1580  << " and completeness " << completeness << std::endl;
1581  }
1582  if (nodeVector.empty())
1583  {
1584  std::cout << " Unmatched" << std::endl;
1585  }
1586  else if (match.IsQuality(this->GetQualityCuts()))
1587  {
1588  if (pMCNode->IsTestBeamParticle())
1589  ++nTestBeamRecoParticles;
1590  else if (pMCNode->IsCosmicRay())
1591  ++nCosmicRecoParticles;
1592  else
1593  ++nNeutrinoRecoParticles;
1594  }
1595  }
1596 
1597  if (LArMCParticleHelper::IsNeutrino(pRootMC))
1598  {
1599  std::cout << " Neutrino Interaction Summary:" << std::endl;
1600  if (nNeutrinoMCParticles)
1601  {
1602  std::cout << " Good final state particles: " << nNeutrinoRecoParticles << " of " << nNeutrinoMCParticles << " : "
1603  << (100 * nNeutrinoRecoParticles / static_cast<float>(nNeutrinoMCParticles)) << "%" << std::endl;
1604  }
1605  }
1606  else if (LArMCParticleHelper::IsCosmicRay(pRootMC))
1607  {
1608  std::cout << " Cosmic Ray Interaction Summary:" << std::endl;
1609  std::cout << std::fixed << std::setprecision(1);
1610  if (nCosmicMCParticles)
1611  {
1612  std::cout << " Good cosmics: " << nCosmicRecoParticles << " of " << nCosmicMCParticles << " : "
1613  << (100 * nCosmicRecoParticles / static_cast<float>(nCosmicMCParticles)) << "%" << std::endl;
1614  }
1615  }
1616  else if (LArMCParticleHelper::IsBeamParticle(pRootMC))
1617  {
1618  std::cout << " Test Beam Interaction Summary:" << std::endl;
1619  std::cout << std::fixed << std::setprecision(1);
1620  if (nTestBeamMCParticles)
1621  {
1622  std::cout << " Good test beam particles: " << nTestBeamRecoParticles << " of " << nTestBeamMCParticles << " : "
1623  << (100 * nTestBeamRecoParticles / static_cast<float>(nTestBeamMCParticles)) << "%" << std::endl;
1624  }
1625  if (nCosmicMCParticles)
1626  {
1627  std::cout << " Matched cosmics: " << nCosmicRecoParticles << " of " << nCosmicMCParticles << " : "
1628  << (100 * nCosmicRecoParticles / static_cast<float>(nCosmicMCParticles)) << "%" << std::endl;
1629  }
1630  }
1631  if (!this->GetUnmatchedReco().empty())
1632  std::cout << " Unmatched reco: " << this->GetUnmatchedReco().size() << std::endl;
1633  }
1634 }
1635 
1636 //------------------------------------------------------------------------------------------------------------------------------------------
1637 
1638 void LArHierarchyHelper::MatchInfo::GetRootMCParticles(MCParticleList &rootMCParticles) const
1639 {
1640  for (auto iter = m_matches.begin(); iter != m_matches.end(); ++iter)
1641  rootMCParticles.emplace_back(iter->first);
1642 }
1643 
1644 //------------------------------------------------------------------------------------------------------------------------------------------
1645 //------------------------------------------------------------------------------------------------------------------------------------------
1646 
1648  const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const FoldingParameters &foldParameters, MCHierarchy &hierarchy)
1649 {
1650  hierarchy.FillHierarchy(mcParticleList, caloHitList, foldParameters);
1651 }
1652 
1653 //------------------------------------------------------------------------------------------------------------------------------------------
1654 
1655 void LArHierarchyHelper::FillRecoHierarchy(const PfoList &pfoList, const FoldingParameters &foldParameters, RecoHierarchy &hierarchy)
1656 {
1657  hierarchy.FillHierarchy(pfoList, foldParameters);
1658 }
1659 
1660 //------------------------------------------------------------------------------------------------------------------------------------------
1661 
1663 {
1664  matchInfo.Match();
1665 }
1666 
1667 //------------------------------------------------------------------------------------------------------------------------------------------
1668 // private
1669 
1670 void LArHierarchyHelper::GetMCPrimaries(const MCParticle *pRoot, MCParticleSet &primaries)
1671 {
1672  try
1673  {
1674  MCParticleList visible;
1676  // Check if we still need to use sets here
1677  for (const MCParticle *const pMCParticle : visible)
1678  primaries.insert(pMCParticle);
1679  }
1680  catch (const StatusCodeException &)
1681  {
1682  if (pRoot->GetParticleId() != 111 && pRoot->GetParticleId() < 1e9)
1683  std::cout << "LArHierarchyHelper::MCHierarchy::FillHierarchy: MC particle with PDG code " << pRoot->GetParticleId()
1684  << " at address " << pRoot << " has no associated primary particle" << std::endl;
1685  }
1686 }
1687 
1688 void LArHierarchyHelper::GetRecoPrimaries(const ParticleFlowObject *pRoot, PfoSet &primaries)
1689 {
1690  if (LArPfoHelper::IsNeutrino(pRoot))
1691  {
1692  const PfoList &children{pRoot->GetDaughterPfoList()};
1693  // Check if we still need to use sets here
1694  for (const ParticleFlowObject *const pPfo : children)
1695  primaries.insert(pPfo);
1696  }
1697  else
1698  {
1699  // Might want different handling here for test beam and cosmic ray particles, but for now, just treat
1700  // a non-neutrino root node as something to be stored in the primaries set directly
1701  primaries.insert(pRoot);
1702  }
1703 }
1704 
1705 } // namespace lar_content
static void GetRecoPrimaries(const pandora::ParticleFlowObject *pRoot, PfoSet &primaries)
Retrieves the primary PFOs from a list and returns the root (neutrino) for hierarchy, if it exists.
int m_tier
The hierarchy tier for this node.
const pandora::CaloHitList & GetCaloHits() const
Retrieve the CaloHits associated with this node.
static void GetFirstVisibleMCParticles(const pandora::MCParticle *const pRoot, pandora::MCParticleList &visibleParticleList)
Get the first visible MC particles given a root particle. For example, given a neutrino this would re...
const unsigned int m_minHitsForGoodView
the minimum number of Hits for a good view
std::set< const pandora::MCParticle * > MCParticleSet
void GetRootMCParticles(pandora::MCParticleList &rootMCParticles) const
Retrieve the root MC particles of the interaction hierarchies.
static bool SortByNHits(const pandora::ParticleFlowObject *const pLhs, const pandora::ParticleFlowObject *const pRhs)
Sort pfos by number of constituent hits.
const MCMatchesVector & GetMatches(const pandora::MCParticle *const pRoot) const
Retrieve the vector of matches (this will include null matches - i.e. MC nodes with no corresponding ...
void GetFlattenedNodes(const pandora::MCParticle *const pRoot, NodeVector &nodeVector) const
Retrieve a flat vector of the ndoes in the hierarchy.
const pandora::CaloHitList GetSelectedRecoHits(const RecoHierarchy::Node *pReco) const
Retrieve the selected hits for the given reco node.
const MCHierarchy::Node * m_pMCParticle
MC node associated with any matches.
bool m_foldToTier
Whether or not to apply folding based on particle tier.
int m_tier
If folding to a tier, the tier to be combined with its child particles.
Header file for the interaction type helper class.
RecoHierarchy::NodeVector m_recoNodes
Matched reco nodes.
const NodeVector & GetInteractions(const pandora::ParticleFlowObject *pRoot) const
Retrieve the root nodes in the hierarchy for a given interaction.
void CollectContinuations(const pandora::MCParticle *pRoot, pandora::MCParticleList &continuingParticles, pandora::MCParticleList &childParticles, const float cosAngleTolerance) const
Identify downstream particles that represent continuations of the parent particle from a reconstructi...
const std::string ToString() const
Produce a string representation of the hierarchy.
pandora::IntVector m_sharedHits
Number of shared hits for each match.
void GetRootMCParticles(pandora::MCParticleList &rootMCParticles) const
Retrieve the root MC particles of the interaction hierarchies.
const std::string ToString(const std::string &prefix) const
Produce a string representation of the hierarchy.
std::map< const pandora::MCParticle *, pandora::CaloHitList > m_mcToHitsMap
The map between MC particles and calo hits.
constexpr auto abs(T v)
Returns the absolute value of the argument.
static int GetHierarchyTier(const pandora::MCParticle *const pMCParticle)
Determine the position in the hierarchy for the MCParticle.
static void GetMCPrimaries(const pandora::MCParticle *pRoot, MCParticleSet &primaries)
Retrieves the primary MC particles from a list and returns the root (neutrino) for hierarchy...
const RecoHierarchy & m_hierarchy
The parent reco hierarchy.
int m_pdg
The particle ID (track = muon, shower = electron)
void FillHierarchy(const pandora::MCParticleList &mcParticleList, const pandora::CaloHitList &caloHitList, const FoldingParameters &foldParameters)
Creates an MC hierarchy representation. Without folding this will be a mirror image of the standard M...
QualityCuts m_qualityCuts
The quality cuts to be applied to matches.
const pandora::CaloHitList & GetCaloHits() const
Retrieve the CaloHits associated with this node.
MatchInfo(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy)
Default constructor.
static void MatchHierarchies(MatchInfo &matchInfo)
Finds the matches between reconstructed and MC hierarchies.
SelectedRecoHitsMap m_selectedRecoHitsMap
Map storing the selected CaloHits for a given reco node.
const pandora::CaloHitList GetSelectedRecoHits(const RecoHierarchy::Node *pRecoNode, const pandora::CaloHitList &allMCHits) const
Get the selected reco node hits that are contained in the allMCHits list.
static void GetAllCaloHits(const pandora::ParticleFlowObject *pPfo, pandora::CaloHitList &caloHitList)
Get a list of all calo hits (including isolated) of all types from a given pfo.
Definition: LArPfoHelper.cc:76
bool IsQuality(const QualityCuts &qualityCuts) const
Get whether this match passes quality cuts.
void Match()
Match the nodes in the MC and reco hierarchies.
const MCHierarchy::Node * GetMC() const
Retrieve the MC node.
TFile f
Definition: plotHisto.C:6
MCMatches(const MCHierarchy::Node *pMCParticle)
Constructor.
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
unsigned int GetNTestBeamMCNodes(const pandora::MCParticle *const pRoot) const
Retrieve the number of test beam derived MC nodes available to match.
static bool SortByMomentum(const pandora::MCParticle *const pLhs, const pandora::MCParticle *const pRhs)
Sort mc particles by their momentum.
void FillHierarchy(const pandora::PfoList &pfoList, const FoldingParameters &foldParameters)
Creates a reconstructed hierarchy representation. Without folding this will be a mirror image of the ...
LAr mc particle class.
Definition: LArMCParticle.h:94
static bool IsCosmicRay(const pandora::MCParticle *const pMCParticle)
Return true if passed a primary cosmic ray MCParticle.
static bool IsInelasticScatter(const pandora::MCParticle *const pMCParticle)
Check whether or not an MC particle came from an inelastic scattering process.
static void FillRecoHierarchy(const pandora::PfoList &pfoList, const FoldingParameters &foldParameters, RecoHierarchy &hierarchy)
Fill a reconstructed hierarchy based on the specified folding criteria (see RecoHierarchy::FillHierar...
const pandora::MCParticle * GetLeadingMCParticle() const
Retrieve the leading MC particle associated with this node.
static int GetHierarchyTier(const pandora::ParticleFlowObject *const pPfo)
Determine the position in the hierarchy for the MCParticle.
void AddRecoMatch(const RecoHierarchy::Node *pReco, const int nSharedHits, const pandora::CaloHitList &selectedRecoHits)
Add a reconstructed node as a match for this MC node.
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
const bool m_selectRecoHits
Whether to only use reco hits that overlap with the MC particle hits.
void InterpretHierarchy(const pandora::MCParticle *const pRoot, pandora::MCParticleList &leadingParticles, pandora::MCParticleList &childParticles, const float cosAngleTolerance) const
Interpret the hierarchy below a particular particle to determine if and how it should be folded...
const float m_minPurity
The minimum purity for a match to be considered good.
static bool IsBeamParticle(const pandora::MCParticle *const pMCParticle)
Returns true if passed a primary beam MCParticle.
bool IsReconstructable() const
Return whether or not this node should be considered reconstructable.
bool IsCosmicRay() const
Check if this is a cosmic ray particle.
void FillHierarchy(const pandora::ParticleFlowObject *pRoot, const FoldingParameters &foldParameters)
Recursively fill the hierarchy based on the criteria established for this RecoHierarchy.
int GetParticleId() const
Retrieve the PDG code for the leading particle in this node.
unsigned int GetSharedHits(const RecoHierarchy::Node *pReco) const
Retrieve the number of shared hits in the match.
float GetPurity(const RecoHierarchy::Node *pReco, const bool adcWeighted=false) const
Retrieve the purity of the match.
pandora::PfoList m_pfos
The list of PFOs of which this node is composed.
const std::string ToString(const std::string &prefix) const
Produce a string representation of the hierarchy.
float m_cosAngleTolerance
Cosine of the maximum angle at which topologies can be considered continuous.
static bool IsNeutrino(const pandora::ParticleFlowObject *const pPfo)
Whether a pfo is a neutrino or (antineutrino)
NodeVector m_children
The child nodes of this node.
bool m_foldDynamic
Whether or not to use process and topological information to make folding decisions.
const RecoHierarchy::NodeVector & GetUnmatchedReco() const
Retrieve the vector of unmatched reco nodes.
const pandora::PfoList & GetRecoParticles() const
Retrieve the PFOs associated with this node.
static bool AreTopologicallyContinuous(const pandora::MCParticle *const pMCParent, const pandora::MCParticle *const pMCChild, const float cosAngleTolerance)
Determine if two MC particles are topologically continuous within a given tolerance. If the parent does not travel any distance, a travelling parent is sought and the comparison made between this and the child. If no travelling parent can be found, the particles are treated as continuous.
ReconstructabilityCriteria m_recoCriteria
The criteria used to determine if the node is reconstructable.
void Print(const MCHierarchy &mcHierarchy) const
Prints information about which reco nodes are matched to the MC nodes, information about hit sharing...
bool m_foldToLeadingShowers
Whether or not to fold shower children to the leading shower particle.
const bool m_removeNeutrons
whether to remove neutrons and their downstream particles
Header file for the lar hierarchy helper class.
const NodeVector & GetInteractions(const pandora::MCParticle *pRoot) const
Retrieve the root nodes in this hierarchy.
const std::string ToString() const
Produce a string representation of the hierarchy.
const pandora::ParticleFlowObject * m_mainPfo
The leading particle flow object for this node.
std::vector< MCMatches > MCMatchesVector
pandora::CaloHitList m_caloHits
The list of calo hits of which this node is composed.
HitType
Definition: HitType.h:12
unsigned int GetNNeutrinoMCNodes(const pandora::MCParticle *const pRoot) const
Retrieve the number of neutrino interaction derived MC nodes available to match.
const MCHierarchy & m_mcHierarchy
The MC hierarchy for the matching procedure.
bool IsReconstructable(const pandora::MCParticle *pMCParticle) const
Checks if an individual particle meets reconstructability criteria.
void SetLeadingLepton()
Tags the particle as the leading lepton.
Node(MCHierarchy &hierarchy, const pandora::MCParticle *pMCParticle, const int tier=1)
Create a node with a primary MC particle.
const RecoHierarchy & m_recoHierarchy
The Reco hierarchy for the matching procedure.
static void GetAllDownstreamPfos(const pandora::PfoList &inputPfoList, pandora::PfoList &outputPfoList)
Get a flat list of all pfos, recursively, of all daughters associated with those pfos in an input lis...
Node(const RecoHierarchy &hierarchy, const pandora::ParticleFlowObject *pPfo, const int tier=1)
Create a node with a primary PFO.
void GetRootPfos(pandora::PfoList &rootPfos) const
Retrieve the root particle flow objects of the interaction hierarchies.
static void FillMCHierarchy(const pandora::MCParticleList &mcParticleList, const pandora::CaloHitList &caloHitList, const FoldingParameters &foldParameters, MCHierarchy &hierarchy)
Fill an MC hierarchy based on the specified folding criteria (see MCHierarchy::FillHierarchy for deta...
unsigned int GetNMCNodes(const pandora::MCParticle *const pRoot) const
Retrieve the number of MC nodes available to match.
int GetParticleId() const
Retrieve the PDG code for the leading particle in this node Note, for reco objects the PDG codes repr...
void GetFlattenedNodes(const pandora::ParticleFlowObject *const pRoot, NodeVector &nodeVector) const
Retrieve a flat vector of the nodes in the hierarchy.
void FillFlat(const pandora::MCParticle *pRoot)
Fill this node by folding all descendent particles to this node.
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:69
int GetId() const
Retrieve the unique ID of this node.
int m_nextNodeId
The ID to use for the next node.
const unsigned int m_minHits
the minimum number of primary good Hits
RecoHierarchy::NodeVector m_unmatchedReco
The vector of unmatched reco nodes.
const unsigned int m_minGoodViews
the minimum number of primary good views
static InteractionDescriptor GetInteractionDescriptor(const pandora::MCParticleList &mcPrimaryList)
Get the interaction descriptor of an event.
void RegisterNode(const Node *pNode)
Register a node with the hierarchy.
static void GetAllDescendentMCParticles(const pandora::MCParticle *const pMCParticle, pandora::MCParticleList &descendentMCParticleList)
Get all descendent mc particles.
MCNodeVectorMap m_interactions
Map from incident particles (e.g. neutrino) to primaries.
float GetCompleteness(const RecoHierarchy::Node *pReco, const bool adcWeighted=false) const
Retrieve the completeness of the match.
void FillHierarchy(const pandora::MCParticle *pRoot, const FoldingParameters &foldParameters)
Recursively fill the hierarchy based on the criteria established for this MCHierarchy.
bool IsTestBeamParticle() const
Check if this is a test beam particle.
std::set< const pandora::ParticleFlowObject * > PfoSet
const float m_minCompleteness
The minimum completeness for a match to be considered good.
static bool IsElasticScatter(const pandora::MCParticle *const pMCParticle)
Check whether or not an MC particle came from an elastic scattering process.
static bool IsNeutrino(const pandora::MCParticle *const pMCParticle)
Whether a mc particle is a neutrino or antineutrino.
void FillFlat(const pandora::ParticleFlowObject *pRoot)
Fill this node by folding all descendent particles to this node.
std::map< const Node *, int > m_nodeToIdMap
A map from nodes to unique ids.
InteractionInfo m_matches
The map between an interaction and the vector of good matches from MC to reco.
unsigned int GetNCosmicRayMCNodes(const pandora::MCParticle *const pRoot) const
Retrieve the number of cosmic ray derived MC nodes available to match.