LArHierarchyHelper.cc
Go to the documentation of this file.
1 /**
2  * @file larpandoracontent/LArHelpers/LArHierarchyHelper.cc
3  *
4  * @brief Implementation of the lar hierarchy helper class.
5  *
6  * $Log: $
7  */
8 
9 #include "Pandora/PdgTable.h"
10 #include "Pandora/StatusCodes.h"
11 
13 
14 #include <numeric>
15 
16 namespace lar_content
17 {
18 
19 using namespace pandora;
20 
22  m_foldToLeadingShowers{false},
23  m_foldToTier{false},
24  m_foldDynamic{false},
25  m_cosAngleTolerance{0.9962f},
26  m_tier{1}
27 {
28 }
29 
30 //------------------------------------------------------------------------------------------------------------------------------------------
31 
32 LArHierarchyHelper::FoldingParameters::FoldingParameters(const bool foldDynamic, const float cosAngleTolerance) :
34  m_foldToTier{false},
35  m_foldDynamic{foldDynamic},
36  m_cosAngleTolerance{cosAngleTolerance},
37  m_tier{1}
38 {
39 }
40 
41 //------------------------------------------------------------------------------------------------------------------------------------------
42 
45  m_foldToTier{true},
46  m_foldDynamic{false},
47  m_cosAngleTolerance{0.9962f},
48  m_tier{foldingTier}
49 {
50  if (m_tier < 1)
51  {
52  std::cout << "LArHierarchyHelper: Error - attempting to fold to non-positive tier" << std::endl;
53  throw StatusCodeException(STATUS_CODE_INVALID_PARAMETER);
54  }
55 }
56 
57 //------------------------------------------------------------------------------------------------------------------------------------------
58 //------------------------------------------------------------------------------------------------------------------------------------------
59 
61 {
62 }
63 
64 //------------------------------------------------------------------------------------------------------------------------------------------
65 
66 LArHierarchyHelper::QualityCuts::QualityCuts(const float minPurity, const float minCompleteness) :
67  m_minPurity{minPurity},
68  m_minCompleteness{minCompleteness}
69 {
70 }
71 
72 //------------------------------------------------------------------------------------------------------------------------------------------
73 //------------------------------------------------------------------------------------------------------------------------------------------
74 
76 {
77 }
78 
79 //------------------------------------------------------------------------------------------------------------------------------------------
80 
82  m_recoCriteria(recoCriteria),
83  m_pNeutrino{nullptr},
84  m_nextNodeId{1}
85 {
86 }
87 
88 //------------------------------------------------------------------------------------------------------------------------------------------
89 
91 {
92  for (const Node *pNode : m_rootNodes)
93  delete pNode;
94  m_rootNodes.clear();
95 }
96 
97 //------------------------------------------------------------------------------------------------------------------------------------------
98 
99 void LArHierarchyHelper::MCHierarchy::FillHierarchy(const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const FoldingParameters &foldParameters)
100 {
101  const auto predicate = [](const MCParticle *pMCParticle) { return std::abs(pMCParticle->GetParticleId()) == NEUTRON; };
102  m_mcToHitsMap.clear();
103  for (const CaloHit *pCaloHit : caloHitList)
104  {
105  try
106  {
107  const MCParticle *pMCParticle{MCParticleHelper::GetMainMCParticle(pCaloHit)};
108  m_mcToHitsMap[pMCParticle].emplace_back(pCaloHit);
109  }
110  catch (const StatusCodeException &)
111  {
112  }
113  }
114 
115  MCParticleSet primarySet;
116  m_pNeutrino = LArHierarchyHelper::GetMCPrimaries(mcParticleList, primarySet);
117  MCParticleList primaries(primarySet.begin(), primarySet.end());
118  primaries.sort(LArMCParticleHelper::SortByMomentum);
120  primaries.erase(std::remove_if(primaries.begin(), primaries.end(), predicate), primaries.end());
121  if (foldParameters.m_foldToTier && foldParameters.m_tier == 1)
122  {
123  for (const MCParticle *pPrimary : primaries)
124  {
125  MCParticleList allParticles{pPrimary};
127  {
128  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles);
129  }
130  else
131  {
132  // Collect track-like and shower-like particles together, but throw out neutrons and descendents
133  MCParticleList dummy;
134  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles, allParticles, dummy);
135  }
136  CaloHitList allHits;
137  for (const MCParticle *pMCParticle : allParticles)
138  {
139  // Not all MC particles will have hits
140  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
141  {
142  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
143  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
144  }
145  }
146  m_rootNodes.emplace_back(new Node(*this, allParticles, allHits));
147  }
148  }
149  else if (foldParameters.m_foldToLeadingShowers)
150  {
151  for (const MCParticle *pPrimary : primaries)
152  {
153  MCParticleList allParticles{pPrimary};
154  int pdg{std::abs(pPrimary->GetParticleId())};
155  const bool isShower{pdg == E_MINUS || pdg == PHOTON};
156  const bool isNeutron{pdg == NEUTRON};
157  if (isShower || (isNeutron && !m_recoCriteria.m_removeNeutrons))
158  LArMCParticleHelper::GetAllDescendentMCParticles(pPrimary, allParticles);
159  CaloHitList allHits;
160  for (const MCParticle *pMCParticle : allParticles)
161  {
162  // ATTN - Not all MC particles will have hits
163  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
164  {
165  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
166  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
167  }
168  }
169  Node *pNode{new Node(*this, allParticles, allHits)};
170  m_rootNodes.emplace_back(pNode);
171  if (!(isShower || isNeutron))
172  {
173  // Find the children of this particle and recursively add them to the hierarchy
174  const MCParticleList &children{pPrimary->GetDaughterList()};
175  for (const MCParticle *pChild : children)
176  pNode->FillHierarchy(pChild, foldParameters);
177  }
178  }
179  }
180  else if (foldParameters.m_foldDynamic)
181  {
182  for (const MCParticle *pPrimary : primaries)
183  {
184  MCParticleList leadingParticles, childParticles;
185  this->InterpretHierarchy(pPrimary, leadingParticles, childParticles, foldParameters.m_cosAngleTolerance);
186  CaloHitList allHits;
187  for (const MCParticle *pMCParticle : leadingParticles)
188  {
189  // ATTN - Not all MC particles will have hits
190  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
191  {
192  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
193  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
194  }
195  }
196 
197  Node *pNode{new Node(*this, leadingParticles, allHits)};
198  m_rootNodes.emplace_back(pNode);
199  for (const MCParticle *pChild : childParticles)
200  pNode->FillHierarchy(pChild, foldParameters);
201  }
202  }
203  else
204  {
205  // Unfolded and folded to tier > 1 have the same behaviour for primaries
206  for (const MCParticle *pPrimary : primaries)
207  {
208  MCParticleList allParticles{pPrimary};
209  CaloHitList allHits;
210  for (const MCParticle *pMCParticle : allParticles)
211  {
212  // ATTN - Not all MC particles will have hits
213  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
214  {
215  const CaloHitList &caloHits(m_mcToHitsMap.at(pMCParticle));
216  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
217  }
218  }
219  Node *pNode{new Node(*this, allParticles, allHits)};
220  m_rootNodes.emplace_back(pNode);
221  // Find the children of this particle and recursively add them to the hierarchy
222  const MCParticleList &children{pPrimary->GetDaughterList()};
223  for (const MCParticle *pChild : children)
224  pNode->FillHierarchy(pChild, foldParameters);
225  }
226  }
227 
228  Node *pLeadingLepton{nullptr};
229  float leadingLeptonEnergy{-std::numeric_limits<float>::max()};
230  for (const Node *pNode : m_rootNodes)
231  {
232  const MCParticle *pMC{pNode->GetLeadingMCParticle()};
233  if (pMC)
234  {
235  const int pdg{std::abs(pMC->GetParticleId())};
236  if ((pdg == MU_MINUS || pdg == E_MINUS || pdg == TAU_MINUS) && pMC->GetEnergy() > leadingLeptonEnergy)
237  {
238  pLeadingLepton = const_cast<Node *>(pNode);
239  leadingLeptonEnergy = pMC->GetEnergy();
240  }
241  }
242  }
243  if (pLeadingLepton)
244  pLeadingLepton->SetLeadingLepton();
245 }
246 
247 //------------------------------------------------------------------------------------------------------------------------------------------
248 
250  const MCParticle *const pRoot, MCParticleList &leadingParticles, MCParticleList &childParticles, const float cosAngleTolerance) const
251 {
252  leadingParticles.emplace_back(pRoot);
253  MCParticleList foldCandidates, childCandidates;
254  const MCParticleList &children{pRoot->GetDaughterList()};
255  for (const MCParticle *pMCParticle : children)
256  {
257  const LArMCParticle *pLArMCParticle{dynamic_cast<const LArMCParticle *>(pMCParticle)};
258  if (!pLArMCParticle)
259  continue;
261  {
262  // Elastic and inelastic scattering can either lead to folding, distinct nodes or disposable hits, all other processes
263  // are either distinct nodes, or disposable
264  if (pMCParticle->GetParticleId() == pRoot->GetParticleId())
265  foldCandidates.emplace_back(pMCParticle);
266  else
267  childCandidates.emplace_back(pMCParticle);
268  }
269  else if (!m_recoCriteria.m_removeNeutrons || (m_recoCriteria.m_removeNeutrons && pLArMCParticle->GetProcess() != MC_PROC_N_CAPTURE))
270  {
271  // Non-scattering process particles become leading candidates unless it's neutron capture and we're removing neutrons
272  childCandidates.emplace_back(pMCParticle);
273  }
274  }
275  const MCParticle *pBestFoldCandidate{nullptr};
276  float bestDp{std::numeric_limits<float>::max()};
277  for (const MCParticle *pMCParticle : foldCandidates)
278  {
279  if (foldCandidates.size() == 1)
280  {
281  // No alternative options, so this is either the best folding option by default, or a sufficiently large scatter to
282  // treat as a new particle for reconstruction purposes
283  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
284  pBestFoldCandidate = pMCParticle;
285  else
286  childCandidates.emplace_back(pMCParticle);
287  }
288  else
289  {
290  // Assess which, if any, of the children might be a continuation of the trajectory, otherwise move to child candidates
291  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
292  {
293  const float dp{pRoot->GetMomentum().GetMagnitude() - pMCParticle->GetMomentum().GetMagnitude()};
294  if (dp < bestDp)
295  {
296  pBestFoldCandidate = pMCParticle;
297  bestDp = dp;
298  }
299  }
300  else
301  {
302  childCandidates.emplace_back(pMCParticle);
303  }
304  }
305  }
306  if (pBestFoldCandidate)
307  {
308  leadingParticles.emplace_back(pBestFoldCandidate);
309  // Having found a particle to fold back at this level, continue to explore its downstream hierarchy for further folding
310  // opportunities and make their respective children leading particles for the folded node we are creating
311  this->CollectContinuations(pBestFoldCandidate, leadingParticles, childCandidates, cosAngleTolerance);
312  }
313  for (const MCParticle *pMCParticle : childCandidates)
314  {
315  // Consider if the child particle will produce enough downstream hits to warrant inclusion
316  if (this->IsReconstructable(pMCParticle))
317  childParticles.emplace_back(pMCParticle);
318  else
319  {
320  MCParticleList localHierarchy{pMCParticle};
321  CaloHitList localHits;
322  LArMCParticleHelper::GetAllDescendentMCParticles(pMCParticle, localHierarchy);
323  for (const MCParticle *pLocalMCParticle : localHierarchy)
324  {
325  if (m_mcToHitsMap.find(pLocalMCParticle) != m_mcToHitsMap.end())
326  {
327  const CaloHitList &caloHits(m_mcToHitsMap.at(pLocalMCParticle));
328  localHits.insert(localHits.begin(), caloHits.begin(), caloHits.end());
329  }
330  }
331  if (this->IsReconstructable(localHits))
332  childParticles.emplace_back(pMCParticle);
333  }
334  }
335 }
336 
337 //------------------------------------------------------------------------------------------------------------------------------------------
338 
340  const MCParticle *pRoot, MCParticleList &continuingParticles, MCParticleList &childParticles, const float cosAngleTolerance) const
341 {
342  const MCParticleList &children{pRoot->GetDaughterList()};
343  MCParticleList foldCandidates;
344  for (const MCParticle *pMCParticle : children)
345  {
346  const LArMCParticle *pLArMCParticle{dynamic_cast<const LArMCParticle *>(pMCParticle)};
347  if (!pLArMCParticle)
348  continue;
349  // Only elastic and inelastic scattering can lead to folding
351  {
352  if (pMCParticle->GetParticleId() == pRoot->GetParticleId())
353  foldCandidates.emplace_back(pMCParticle);
354  }
355  else if (!m_recoCriteria.m_removeNeutrons || (m_recoCriteria.m_removeNeutrons && pLArMCParticle->GetProcess() != MC_PROC_N_CAPTURE))
356  {
357  // Non-scattering process particles become leading candidates unless it's neutron capture and we're removing neutrons
358  childParticles.emplace_back(pMCParticle);
359  }
360  }
361  const MCParticle *pBestFoldCandidate{nullptr};
362  float bestDp{std::numeric_limits<float>::max()};
363  for (const MCParticle *pMCParticle : foldCandidates)
364  {
365  if (foldCandidates.size() == 1)
366  {
367  // No alternative options, so this is either the best folding option by default, or a sufficiently large scatter to
368  // treat as a new particle for reconstruction purposes
369  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
370  pBestFoldCandidate = pMCParticle;
371  }
372  else
373  {
374  // Assess which, if any, of the children might be a continuation of the trajectory, otherwise move to child candidates
375  if (LArMCParticleHelper::AreTopologicallyContinuous(pRoot, pMCParticle, cosAngleTolerance))
376  {
377  const float dp{pRoot->GetMomentum().GetMagnitude() - pMCParticle->GetMomentum().GetMagnitude()};
378  if (dp < bestDp)
379  {
380  pBestFoldCandidate = pMCParticle;
381  bestDp = dp;
382  }
383  }
384  }
385  }
386  if (pBestFoldCandidate)
387  {
388  continuingParticles.emplace_back(pBestFoldCandidate);
389  const MCParticleList &newLeadingParticles{pBestFoldCandidate->GetDaughterList()};
390  // We need to add the children as child particles to ensure these sub-hierarchies are explored...
391  childParticles.insert(childParticles.begin(), newLeadingParticles.begin(), newLeadingParticles.end());
392  // but this current best fold candidate may have been added to the child particles by previously, so remove it
393  const auto iter{std::find(childParticles.begin(), childParticles.end(), pBestFoldCandidate)};
394  if (iter != childParticles.end())
395  childParticles.erase(iter);
396  // Having found a particle to fold back at this level, continue to explore its downstream hierarchy for further folding
397  // opportunities and make their respective children child particles for the folded node we are creating
398  LArHierarchyHelper::MCHierarchy::CollectContinuations(pBestFoldCandidate, continuingParticles, childParticles, cosAngleTolerance);
399  }
400 }
401 
402 //------------------------------------------------------------------------------------------------------------------------------------------
403 
405 {
406  NodeList queue;
407  for (const Node *pNode : m_rootNodes)
408  {
409  nodeVector.emplace_back(pNode);
410  queue.emplace_back(pNode);
411  }
412  while (!queue.empty())
413  {
414  const NodeVector &children{queue.front()->GetChildren()};
415  queue.pop_front();
416  for (const Node *pChild : children)
417  {
418  nodeVector.emplace_back(pChild);
419  queue.emplace_back(pChild);
420  }
421  }
422 }
423 
424 //------------------------------------------------------------------------------------------------------------------------------------------
425 
427 {
428  m_nodeToIdMap.insert(std::make_pair(pNode, m_nextNodeId));
429  ++m_nextNodeId;
430 }
431 
432 //------------------------------------------------------------------------------------------------------------------------------------------
433 
435 {
437  for (const Node *pNode : m_rootNodes)
438  str += pNode->ToString("") + "\n";
439 
440  return str;
441 }
442 
443 //------------------------------------------------------------------------------------------------------------------------------------------
444 
445 bool LArHierarchyHelper::MCHierarchy::IsReconstructable(const pandora::MCParticle *pMCParticle) const
446 {
447  if (m_mcToHitsMap.find(pMCParticle) != m_mcToHitsMap.end())
448  {
449  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
450  for (const CaloHit *pCaloHit : m_mcToHitsMap.at(pMCParticle))
451  {
452  const HitType view{pCaloHit->GetHitType()};
453  if (view == TPC_VIEW_U)
454  ++nHitsU;
455  else if (view == TPC_VIEW_V)
456  ++nHitsV;
457  else if (view == TPC_VIEW_W)
458  ++nHitsW;
459  }
460  const unsigned int nHits{nHitsU + nHitsV + nHitsW};
461  unsigned int nGoodViews{0};
462  nGoodViews += nHitsU >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
463  nGoodViews += nHitsV >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
464  nGoodViews += nHitsW >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
465 
466  return nHits >= m_recoCriteria.m_minHits && nGoodViews >= m_recoCriteria.m_minGoodViews;
467  }
468 
469  return false;
470 }
471 
472 //------------------------------------------------------------------------------------------------------------------------------------------
473 
474 bool LArHierarchyHelper::MCHierarchy::IsReconstructable(const CaloHitList &caloHits) const
475 {
476  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
477  for (const CaloHit *pCaloHit : caloHits)
478  {
479  const HitType view{pCaloHit->GetHitType()};
480  if (view == TPC_VIEW_U)
481  ++nHitsU;
482  else if (view == TPC_VIEW_V)
483  ++nHitsV;
484  else if (view == TPC_VIEW_W)
485  ++nHitsW;
486  }
487  const unsigned int nHits{nHitsU + nHitsV + nHitsW};
488  unsigned int nGoodViews{0};
489  nGoodViews += nHitsU >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
490  nGoodViews += nHitsV >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
491  nGoodViews += nHitsW >= m_recoCriteria.m_minHitsForGoodView ? 1 : 0;
492 
493  return nHits >= m_recoCriteria.m_minHits && nGoodViews >= m_recoCriteria.m_minGoodViews;
494 }
495 
496 //------------------------------------------------------------------------------------------------------------------------------------------
497 //------------------------------------------------------------------------------------------------------------------------------------------
498 
499 LArHierarchyHelper::MCHierarchy::Node::Node(MCHierarchy &hierarchy, const MCParticle *pMCParticle, const int tier) :
500  m_hierarchy(hierarchy),
501  m_mainParticle(pMCParticle),
502  m_tier{tier},
503  m_pdg{0},
504  m_isLeadingLepton{false}
505 {
506  if (pMCParticle)
507  {
508  m_pdg = pMCParticle->GetParticleId();
509  m_mcParticles.emplace_back(pMCParticle);
510  }
511  m_hierarchy.RegisterNode(this);
512 }
513 
514 //------------------------------------------------------------------------------------------------------------------------------------------
515 
516 LArHierarchyHelper::MCHierarchy::Node::Node(MCHierarchy &hierarchy, const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const int tier) :
517  m_hierarchy(hierarchy),
518  m_mcParticles(mcParticleList),
519  m_caloHits(caloHitList),
520  m_mainParticle(nullptr),
521  m_tier{tier},
522  m_pdg{0},
523  m_isLeadingLepton{false}
524 {
525  if (!mcParticleList.empty())
526  {
527  m_mainParticle = mcParticleList.front();
528  m_pdg = m_mainParticle->GetParticleId();
529  }
530  m_mcParticles.sort(LArMCParticleHelper::SortByMomentum);
531  m_caloHits.sort();
532  m_hierarchy.RegisterNode(this);
533 }
534 
535 //------------------------------------------------------------------------------------------------------------------------------------------
536 
538 {
539  m_mcParticles.clear();
540  m_caloHits.clear();
541  for (const Node *node : m_children)
542  delete node;
543  m_children.clear();
544 }
545 
546 //------------------------------------------------------------------------------------------------------------------------------------------
547 
548 void LArHierarchyHelper::MCHierarchy::Node::FillHierarchy(const MCParticle *pRoot, const FoldingParameters &foldParameters)
549 {
550  if (foldParameters.m_foldDynamic)
551  {
552  MCParticleList leadingParticles, childParticles;
553  m_hierarchy.InterpretHierarchy(pRoot, leadingParticles, childParticles, foldParameters.m_cosAngleTolerance);
554  CaloHitList allHits;
555  for (const MCParticle *pMCParticle : leadingParticles)
556  {
557  // ATTN - Not all MC particles will have hits
558  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
559  {
560  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
561  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
562  }
563  }
564 
565  Node *pNode{new Node(m_hierarchy, leadingParticles, allHits, this->m_tier + 1)};
566  m_children.emplace_back(pNode);
567  for (const MCParticle *pChild : childParticles)
568  pNode->FillHierarchy(pChild, foldParameters);
569  }
570  else
571  {
572  MCParticleList allParticles{pRoot};
573  const int pdg{std::abs(pRoot->GetParticleId())};
574  const bool isShower{pdg == E_MINUS || pdg == PHOTON};
575  const bool isNeutron{pdg == NEUTRON};
576 
577  if (foldParameters.m_foldToTier && LArMCParticleHelper::GetHierarchyTier(pRoot) >= foldParameters.m_tier)
579  else if (foldParameters.m_foldToLeadingShowers && (isShower || (isNeutron && !m_hierarchy.m_recoCriteria.m_removeNeutrons)))
581  else if (m_hierarchy.m_recoCriteria.m_removeNeutrons && isNeutron)
582  return;
583 
584  CaloHitList allHits;
585  for (const MCParticle *pMCParticle : allParticles)
586  {
587  // ATTN - Not all MC particles will have hits
588  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
589  {
590  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
591  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
592  }
593  }
594 
595  if (!allParticles.empty())
596  {
597  const bool hasChildren{(foldParameters.m_foldToTier && LArMCParticleHelper::GetHierarchyTier(pRoot) < foldParameters.m_tier) ||
598  (!foldParameters.m_foldToTier && !foldParameters.m_foldToLeadingShowers) ||
599  (foldParameters.m_foldToLeadingShowers && !(isShower || isNeutron))};
600  // Only add the node if it either has children, or is a leaf node with hits
601  if (hasChildren || (!hasChildren && !allHits.empty()))
602  {
603  Node *pNode{new Node(m_hierarchy, allParticles, allHits, this->m_tier + 1)};
604  m_children.emplace_back(pNode);
605  if (hasChildren)
606  {
607  // Find the children of this particle and recursively add them to the hierarchy
608  const MCParticleList &children{pRoot->GetDaughterList()};
609  for (const MCParticle *pChild : children)
610  pNode->FillHierarchy(pChild, foldParameters);
611  }
612  }
613  }
614  }
615 }
616 
617 //------------------------------------------------------------------------------------------------------------------------------------------
618 
620 {
621  MCParticleList allParticles{pRoot};
622  if (!m_hierarchy.m_recoCriteria.m_removeNeutrons)
623  {
625  }
626  else
627  {
628  MCParticleList neutrons;
629  LArMCParticleHelper::GetAllDescendentMCParticles(pRoot, allParticles, allParticles, neutrons);
630  }
631  CaloHitList allHits;
632  for (const MCParticle *pMCParticle : allParticles)
633  {
634  // ATTN - Not all MC particles will have hits
635  if (m_hierarchy.m_mcToHitsMap.find(pMCParticle) != m_hierarchy.m_mcToHitsMap.end())
636  {
637  const CaloHitList &caloHits(m_hierarchy.m_mcToHitsMap.at(pMCParticle));
638  allHits.insert(allHits.begin(), caloHits.begin(), caloHits.end());
639  }
640  }
641  if (!allParticles.empty())
642  {
643  Node *pNode{new Node(m_hierarchy, allParticles, allHits, this->m_tier + 1)};
644  m_children.emplace_back(pNode);
645  }
646 }
647 
648 //------------------------------------------------------------------------------------------------------------------------------------------
649 
651 {
652  return m_hierarchy.m_nodeToIdMap.at(this);
653 }
654 
655 //------------------------------------------------------------------------------------------------------------------------------------------
656 
658 {
659  const bool enoughHits{m_caloHits.size() >= m_hierarchy.m_recoCriteria.m_minHits};
660  if (!enoughHits)
661  return false;
662  bool enoughGoodViews{false};
663  unsigned int nHitsU{0}, nHitsV{0}, nHitsW{0};
664  for (const CaloHit *pCaloHit : m_caloHits)
665  {
666  switch (pCaloHit->GetHitType())
667  {
668  case TPC_VIEW_U:
669  ++nHitsU;
670  break;
671  case TPC_VIEW_V:
672  ++nHitsV;
673  break;
674  case TPC_VIEW_W:
675  ++nHitsW;
676  break;
677  default:
678  break;
679  }
680  unsigned int nGoodViews{0};
681  if (nHitsU >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
682  ++nGoodViews;
683  if (nHitsV >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
684  ++nGoodViews;
685  if (nHitsW >= m_hierarchy.m_recoCriteria.m_minHitsForGoodView)
686  ++nGoodViews;
687  if (nGoodViews >= m_hierarchy.m_recoCriteria.m_minGoodViews)
688  {
689  enoughGoodViews = true;
690  break;
691  }
692  }
693 
694  return enoughGoodViews;
695 }
696 
697 //------------------------------------------------------------------------------------------------------------------------------------------
698 
700 {
701  if (m_mainParticle)
702  return LArMCParticleHelper::IsBeamParticle(m_mainParticle);
703  else
704  return false;
705 }
706 
707 //------------------------------------------------------------------------------------------------------------------------------------------
708 
710 {
711  if (m_mainParticle)
712  return LArMCParticleHelper::IsCosmicRay(m_mainParticle);
713  else
714  return false;
715 }
716 
717 //------------------------------------------------------------------------------------------------------------------------------------------
718 
720 {
721  std::string str(prefix + "PDG: " + std::to_string(m_pdg) + " Energy: " + std::to_string(m_mainParticle ? m_mainParticle->GetEnergy() : 0) +
722  " Hits: " + std::to_string(m_caloHits.size()) + "\n");
723  for (const Node *pChild : m_children)
724  str += pChild->ToString(prefix + " ");
725 
726  return str;
727 }
728 
729 //------------------------------------------------------------------------------------------------------------------------------------------
730 //------------------------------------------------------------------------------------------------------------------------------------------
731 
733  m_minHits{30},
735  m_minGoodViews{2},
736  m_removeNeutrons{true}
737 {
738 }
739 
740 //------------------------------------------------------------------------------------------------------------------------------------------
741 
743  m_minHits{obj.m_minHits},
744  m_minHitsForGoodView{obj.m_minHitsForGoodView},
745  m_minGoodViews{obj.m_minGoodViews},
746  m_removeNeutrons{obj.m_removeNeutrons}
747 {
748 }
749 
750 //------------------------------------------------------------------------------------------------------------------------------------------
751 
753  const unsigned int minHits, const unsigned int minHitsForGoodView, const unsigned int minGoodViews, const bool removeNeutrons) :
754  m_minHits{minHits},
755  m_minHitsForGoodView{minHitsForGoodView},
756  m_minGoodViews{minGoodViews},
757  m_removeNeutrons{removeNeutrons}
758 {
759 }
760 
761 //------------------------------------------------------------------------------------------------------------------------------------------
762 //------------------------------------------------------------------------------------------------------------------------------------------
763 
765 {
766 }
767 
768 //------------------------------------------------------------------------------------------------------------------------------------------
769 
771 {
772  for (const Node *pNode : m_rootNodes)
773  delete pNode;
774  m_rootNodes.clear();
775 }
776 
777 //------------------------------------------------------------------------------------------------------------------------------------------
778 
779 void LArHierarchyHelper::RecoHierarchy::FillHierarchy(const PfoList &pfoList, const FoldingParameters &foldParameters)
780 {
781  PfoSet primarySet;
782  m_pNeutrino = LArHierarchyHelper::GetRecoPrimaries(pfoList, primarySet);
783  PfoList primaries(primarySet.begin(), primarySet.end());
784  primaries.sort(LArPfoHelper::SortByNHits);
785  if (foldParameters.m_foldToTier && foldParameters.m_tier == 1)
786  {
787  for (const ParticleFlowObject *pPrimary : primaries)
788  {
789  PfoList allParticles;
790  // ATTN - pPrimary gets added to the list of downstream PFOs, not just the child PFOs
791  LArPfoHelper::GetAllDownstreamPfos(pPrimary, allParticles);
792  CaloHitList allHits;
793  for (const ParticleFlowObject *pPfo : allParticles)
794  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
795  m_rootNodes.emplace_back(new Node(*this, allParticles, allHits));
796  }
797  }
798  else if (foldParameters.m_foldToLeadingShowers)
799  {
800  for (const ParticleFlowObject *pPrimary : primaries)
801  {
802  PfoList allParticles;
803  int pdg{std::abs(pPrimary->GetParticleId())};
804  const bool isShower{pdg == E_MINUS};
805  // ATTN - pPrimary gets added to the list of downstream PFOs, not just the child PFOs
806  if (isShower)
807  LArPfoHelper::GetAllDownstreamPfos(pPrimary, allParticles);
808  else
809  allParticles.emplace_back(pPrimary);
810 
811  CaloHitList allHits;
812  for (const ParticleFlowObject *pPfo : allParticles)
813  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
814  Node *pNode{new Node(*this, allParticles, allHits)};
815  m_rootNodes.emplace_back(pNode);
816  if (!isShower)
817  {
818  // Find the children of this particle and recursively add them to the hierarchy
819  const PfoList &children{pPrimary->GetDaughterPfoList()};
820  for (const ParticleFlowObject *pChild : children)
821  pNode->FillHierarchy(pChild, foldParameters);
822  }
823  }
824  }
825  else
826  {
827  // Dynamic fold, Unfolded and fold to tier > 1 have the same behaviour for primaries
828  for (const ParticleFlowObject *pPrimary : primaries)
829  {
830  PfoList allParticles{pPrimary};
831  CaloHitList allHits;
832  for (const ParticleFlowObject *pPfo : allParticles)
833  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
834  Node *pNode{new Node(*this, allParticles, allHits)};
835  m_rootNodes.emplace_back(pNode);
836  // Find the children of this particle and recursively add them to the hierarchy
837  const PfoList &children{pPrimary->GetDaughterPfoList()};
838  for (const ParticleFlowObject *pChild : children)
839  pNode->FillHierarchy(pChild, foldParameters.m_foldToLeadingShowers);
840  }
841  }
842 }
843 
844 //------------------------------------------------------------------------------------------------------------------------------------------
845 
847 {
848  NodeList queue;
849  for (const Node *pNode : m_rootNodes)
850  {
851  nodeVector.emplace_back(pNode);
852  queue.emplace_back(pNode);
853  }
854  while (!queue.empty())
855  {
856  const NodeVector &children{queue.front()->GetChildren()};
857  queue.pop_front();
858  for (const Node *pChild : children)
859  {
860  nodeVector.emplace_back(pChild);
861  queue.emplace_back(pChild);
862  }
863  }
864 }
865 
866 //------------------------------------------------------------------------------------------------------------------------------------------
867 
869 {
871  for (const Node *pNode : m_rootNodes)
872  str += pNode->ToString("") + "\n";
873 
874  return str;
875 }
876 
877 //------------------------------------------------------------------------------------------------------------------------------------------
878 //------------------------------------------------------------------------------------------------------------------------------------------
879 
880 LArHierarchyHelper::RecoHierarchy::Node::Node(const RecoHierarchy &hierarchy, const ParticleFlowObject *pPfo) :
881  m_hierarchy(hierarchy),
882  m_pdg{0}
883 {
884  if (pPfo)
885  {
886  m_pdg = pPfo->GetParticleId();
887  m_pfos.emplace_back(pPfo);
888  }
889 }
890 
891 //------------------------------------------------------------------------------------------------------------------------------------------
892 
893 LArHierarchyHelper::RecoHierarchy::Node::Node(const RecoHierarchy &hierarchy, const PfoList &pfoList, const CaloHitList &caloHitList) :
894  m_hierarchy(hierarchy),
895  m_pdg{0}
896 {
897  if (!pfoList.empty())
898  m_pdg = pfoList.front()->GetParticleId();
899  m_pfos = pfoList;
901  m_caloHits = caloHitList;
902  m_caloHits.sort();
903 }
904 
905 //------------------------------------------------------------------------------------------------------------------------------------------
906 
908 {
909  m_pfos.clear();
910  m_caloHits.clear();
911  for (const Node *node : m_children)
912  delete node;
913  m_children.clear();
914 }
915 
916 //------------------------------------------------------------------------------------------------------------------------------------------
917 
918 void LArHierarchyHelper::RecoHierarchy::Node::FillHierarchy(const ParticleFlowObject *pRoot, const FoldingParameters &foldParameters)
919 {
920  PfoList allParticles;
921  int pdg{std::abs(pRoot->GetParticleId())};
922  const bool isShower{pdg == E_MINUS};
923  if (foldParameters.m_foldToTier && LArPfoHelper::GetHierarchyTier(pRoot) >= foldParameters.m_tier)
924  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
925  else if (foldParameters.m_foldToLeadingShowers && isShower)
926  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
927  else
928  allParticles.emplace_back(pRoot);
929 
930  CaloHitList allHits;
931  for (const ParticleFlowObject *pPfo : allParticles)
932  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
933  const bool hasChildren{(foldParameters.m_foldToTier && LArPfoHelper::GetHierarchyTier(pRoot) < foldParameters.m_tier) ||
934  (!foldParameters.m_foldToTier && !foldParameters.m_foldToLeadingShowers) ||
935  (foldParameters.m_foldToLeadingShowers && !isShower)};
936 
937  if (hasChildren || (!hasChildren && !allHits.empty()))
938  {
939  Node *pNode{new Node(m_hierarchy, allParticles, allHits)};
940  m_children.emplace_back(pNode);
941 
942  if (hasChildren)
943  {
944  const PfoList &children{pRoot->GetDaughterPfoList()};
945  for (const ParticleFlowObject *pChild : children)
946  pNode->FillHierarchy(pChild, foldParameters);
947  }
948  }
949 }
950 
951 //------------------------------------------------------------------------------------------------------------------------------------------
952 
953 void LArHierarchyHelper::RecoHierarchy::Node::FillFlat(const ParticleFlowObject *pRoot)
954 {
955  PfoList allParticles;
956  LArPfoHelper::GetAllDownstreamPfos(pRoot, allParticles);
957  CaloHitList allHits;
958  for (const ParticleFlowObject *pPfo : allParticles)
959  LArPfoHelper::GetAllCaloHits(pPfo, allHits);
960  Node *pNode{new Node(m_hierarchy, allParticles, allHits)};
961  m_children.emplace_back(pNode);
962 }
963 
964 //------------------------------------------------------------------------------------------------------------------------------------------
965 
967 {
968  return m_pfos;
969 }
970 
971 //------------------------------------------------------------------------------------------------------------------------------------------
972 
974 {
975  return m_caloHits;
976 }
977 
978 //------------------------------------------------------------------------------------------------------------------------------------------
979 
981 {
982  return m_pdg;
983 }
984 
985 //------------------------------------------------------------------------------------------------------------------------------------------
986 
988 {
989  std::string str(prefix + "PDG: " + std::to_string(m_pdg) + " Hits: " + std::to_string(m_caloHits.size()) + "\n");
990  for (const Node *pChild : m_children)
991  str += pChild->ToString(prefix + " ");
992 
993  return str;
994 }
995 
996 //------------------------------------------------------------------------------------------------------------------------------------------
997 //------------------------------------------------------------------------------------------------------------------------------------------
998 
999 LArHierarchyHelper::MCMatches::MCMatches(const MCHierarchy::Node *pMCParticle) : m_pMCParticle{pMCParticle}
1000 {
1001 }
1002 
1003 //------------------------------------------------------------------------------------------------------------------------------------------
1004 
1005 void LArHierarchyHelper::MCMatches::AddRecoMatch(const RecoHierarchy::Node *pReco, const int nSharedHits)
1006 {
1007  m_recoNodes.emplace_back(pReco);
1008  m_sharedHits.emplace_back(nSharedHits);
1009 }
1010 
1011 //------------------------------------------------------------------------------------------------------------------------------------------
1012 
1014 {
1015  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1016  if (iter == m_recoNodes.end())
1017  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1018  int index = iter - m_recoNodes.begin();
1019 
1020  return static_cast<int>(m_sharedHits[index]);
1021 }
1022 
1023 //------------------------------------------------------------------------------------------------------------------------------------------
1024 
1025 float LArHierarchyHelper::MCMatches::GetPurity(const RecoHierarchy::Node *pReco, const bool adcWeighted) const
1026 {
1027  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1028  if (iter == m_recoNodes.end())
1029  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1030 
1031  const CaloHitList &recoHits{pReco->GetCaloHits()};
1032  const CaloHitList &mcHits{m_pMCParticle->GetCaloHits()};
1033  CaloHitVector intersection;
1034  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1035 
1036  return this->GetPurity(intersection, recoHits, adcWeighted);
1037 }
1038 
1039 //------------------------------------------------------------------------------------------------------------------------------------------
1040 
1041 float LArHierarchyHelper::MCMatches::GetPurity(const RecoHierarchy::Node *pReco, const HitType view, const bool adcWeighted) const
1042 {
1043  (void)view;
1044  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1045  if (iter == m_recoNodes.end())
1046  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1047 
1048  CaloHitList recoHits;
1049  for (const CaloHit *pCaloHit : pReco->GetCaloHits())
1050  if (pCaloHit->GetHitType() == view)
1051  recoHits.emplace_back(pCaloHit);
1052  CaloHitList mcHits;
1053  for (const CaloHit *pCaloHit : m_pMCParticle->GetCaloHits())
1054  if (pCaloHit->GetHitType() == view)
1055  mcHits.emplace_back(pCaloHit);
1056 
1057  CaloHitVector intersection;
1058  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1059 
1060  return this->GetPurity(intersection, recoHits, adcWeighted);
1061 }
1062 
1063 //------------------------------------------------------------------------------------------------------------------------------------------
1064 
1065 float LArHierarchyHelper::MCMatches::GetCompleteness(const RecoHierarchy::Node *pReco, const bool adcWeighted) const
1066 {
1067  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1068  if (iter == m_recoNodes.end())
1069  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1070 
1071  const CaloHitList &recoHits{pReco->GetCaloHits()};
1072  const CaloHitList &mcHits{m_pMCParticle->GetCaloHits()};
1073  CaloHitVector intersection;
1074  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1075 
1076  return this->GetCompleteness(intersection, mcHits, adcWeighted);
1077 }
1078 
1079 //------------------------------------------------------------------------------------------------------------------------------------------
1080 
1081 float LArHierarchyHelper::MCMatches::GetCompleteness(const RecoHierarchy::Node *pReco, const HitType view, const bool adcWeighted) const
1082 {
1083  (void)view;
1084  auto iter{std::find(m_recoNodes.begin(), m_recoNodes.end(), pReco)};
1085  if (iter == m_recoNodes.end())
1086  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
1087 
1088  CaloHitList recoHits;
1089  for (const CaloHit *pCaloHit : pReco->GetCaloHits())
1090  if (pCaloHit->GetHitType() == view)
1091  recoHits.emplace_back(pCaloHit);
1092  CaloHitList mcHits;
1093  for (const CaloHit *pCaloHit : m_pMCParticle->GetCaloHits())
1094  if (pCaloHit->GetHitType() == view)
1095  mcHits.emplace_back(pCaloHit);
1096 
1097  CaloHitVector intersection;
1098  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1099 
1100  return this->GetCompleteness(intersection, mcHits, adcWeighted);
1101 }
1102 
1103 //------------------------------------------------------------------------------------------------------------------------------------------
1104 
1105 float LArHierarchyHelper::MCMatches::GetPurity(const CaloHitVector &intersection, const CaloHitList &recoHits, const bool adcWeighted) const
1106 {
1107  float purity{0.f};
1108  if (!intersection.empty())
1109  {
1110  if (adcWeighted)
1111  {
1112  float adcSum{0.f};
1113  for (const CaloHit *pCaloHit : recoHits)
1114  adcSum += pCaloHit->GetInputEnergy();
1115  if (adcSum > std::numeric_limits<float>::epsilon())
1116  {
1117  for (const CaloHit *pCaloHit : intersection)
1118  purity += pCaloHit->GetInputEnergy();
1119  purity /= adcSum;
1120  }
1121  }
1122  else
1123  {
1124  purity = intersection.size() / static_cast<float>(recoHits.size());
1125  }
1126  }
1127 
1128  return purity;
1129 }
1130 
1131 //------------------------------------------------------------------------------------------------------------------------------------------
1132 
1133 float LArHierarchyHelper::MCMatches::GetCompleteness(const CaloHitVector &intersection, const CaloHitList &mcHits, const bool adcWeighted) const
1134 {
1135  float completeness{0.f};
1136  if (!intersection.empty())
1137  {
1138  if (adcWeighted)
1139  {
1140  float adcSum{0.f};
1141  for (const CaloHit *pCaloHit : mcHits)
1142  adcSum += pCaloHit->GetInputEnergy();
1143  if (adcSum > std::numeric_limits<float>::epsilon())
1144  {
1145  for (const CaloHit *pCaloHit : intersection)
1146  completeness += pCaloHit->GetInputEnergy();
1147  completeness /= adcSum;
1148  }
1149  }
1150  else
1151  {
1152  completeness = intersection.size() / static_cast<float>(mcHits.size());
1153  }
1154  }
1155 
1156  return completeness;
1157 }
1158 
1159 //------------------------------------------------------------------------------------------------------------------------------------------
1160 
1162 {
1163  if (m_recoNodes.size() != 1)
1164  return false;
1165 
1166  if (this->GetPurity(m_recoNodes.front()) < qualityCuts.m_minPurity)
1167  return false;
1168 
1169  if (this->GetCompleteness(m_recoNodes.front()) < qualityCuts.m_minCompleteness)
1170  return false;
1171 
1172  return true;
1173 }
1174 
1175 //------------------------------------------------------------------------------------------------------------------------------------------
1176 //------------------------------------------------------------------------------------------------------------------------------------------
1177 
1179 {
1180 }
1181 
1182 //------------------------------------------------------------------------------------------------------------------------------------------
1183 
1185  m_pMCNeutrino{nullptr},
1186  m_pRecoNeutrino{nullptr},
1187  m_qualityCuts{qualityCuts}
1188 {
1189 }
1190 
1191 //------------------------------------------------------------------------------------------------------------------------------------------
1192 
1193 void LArHierarchyHelper::MatchInfo::Match(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy)
1194 {
1195  m_pMCNeutrino = mcHierarchy.GetNeutrino();
1196  m_pRecoNeutrino = recoHierarchy.GetNeutrino();
1197  MCHierarchy::NodeVector mcNodes;
1198  mcHierarchy.GetFlattenedNodes(mcNodes);
1199  RecoHierarchy::NodeVector recoNodes;
1200  recoHierarchy.GetFlattenedNodes(recoNodes);
1201 
1202  std::sort(mcNodes.begin(), mcNodes.end(),
1203  [](const MCHierarchy::Node *lhs, const MCHierarchy::Node *rhs) { return lhs->GetCaloHits().size() > rhs->GetCaloHits().size(); });
1204  std::sort(recoNodes.begin(), recoNodes.end(),
1205  [](const RecoHierarchy::Node *lhs, const RecoHierarchy::Node *rhs) { return lhs->GetCaloHits().size() > rhs->GetCaloHits().size(); });
1206 
1207  std::map<const MCHierarchy::Node *, MCMatches> mcToMatchMap;
1208  for (const RecoHierarchy::Node *pRecoNode : recoNodes)
1209  {
1210  const CaloHitList &recoHits{pRecoNode->GetCaloHits()};
1211  const MCHierarchy::Node *pBestNode{nullptr};
1212  size_t bestSharedHits{0};
1213  for (const MCHierarchy::Node *pMCNode : mcNodes)
1214  {
1215  if (!pMCNode->IsReconstructable())
1216  continue;
1217  const CaloHitList &mcHits{pMCNode->GetCaloHits()};
1218  CaloHitVector intersection;
1219  std::set_intersection(mcHits.begin(), mcHits.end(), recoHits.begin(), recoHits.end(), std::back_inserter(intersection));
1220 
1221  if (!intersection.empty())
1222  {
1223  const size_t sharedHits{intersection.size()};
1224  if (sharedHits > bestSharedHits)
1225  {
1226  bestSharedHits = sharedHits;
1227  pBestNode = pMCNode;
1228  }
1229  }
1230  }
1231  if (pBestNode)
1232  {
1233  auto iter{mcToMatchMap.find(pBestNode)};
1234  if (iter != mcToMatchMap.end())
1235  {
1236  MCMatches &match(iter->second);
1237  match.AddRecoMatch(pRecoNode, static_cast<int>(bestSharedHits));
1238  }
1239  else
1240  {
1241  MCMatches match(pBestNode);
1242  match.AddRecoMatch(pRecoNode, static_cast<int>(bestSharedHits));
1243  mcToMatchMap.insert(std::make_pair(pBestNode, match));
1244  }
1245  }
1246  else
1247  {
1248  m_unmatchedReco.emplace_back(pRecoNode);
1249  }
1250  }
1251 
1252  for (auto [pMCNode, matches] : mcToMatchMap)
1253  m_matches.emplace_back(matches);
1254 
1255  const auto predicate = [](const MCMatches &lhs, const MCMatches &rhs) {
1256  return lhs.GetMC()->GetCaloHits().size() > rhs.GetMC()->GetCaloHits().size();
1257  };
1258  std::sort(m_matches.begin(), m_matches.end(), predicate);
1259 
1260  for (const MCHierarchy::Node *pMCNode : mcNodes)
1261  {
1262  if (pMCNode->IsReconstructable() && mcToMatchMap.find(pMCNode) == mcToMatchMap.end())
1263  {
1264  MCMatches match(pMCNode);
1265  m_matches.emplace_back(match);
1266  }
1267  }
1268 }
1269 
1270 //------------------------------------------------------------------------------------------------------------------------------------------
1271 
1273 {
1274  return static_cast<unsigned int>(m_matches.size());
1275 }
1276 
1277 //------------------------------------------------------------------------------------------------------------------------------------------
1278 
1280 {
1281  unsigned int nNodes{0};
1282  for (const MCMatches &match : m_matches)
1283  {
1284  const MCHierarchy::Node *pNode{match.GetMC()};
1285  if (!(pNode->IsCosmicRay() || pNode->IsTestBeamParticle()))
1286  ++nNodes;
1287  }
1288 
1289  return nNodes;
1290 }
1291 
1292 //------------------------------------------------------------------------------------------------------------------------------------------
1293 
1295 {
1296  unsigned int nNodes{0};
1297  for (const MCMatches &match : m_matches)
1298  {
1299  const MCHierarchy::Node *pNode{match.GetMC()};
1300  if (pNode->IsCosmicRay())
1301  ++nNodes;
1302  }
1303 
1304  return nNodes;
1305 }
1306 
1307 //------------------------------------------------------------------------------------------------------------------------------------------
1308 
1310 {
1311  unsigned int nNodes{0};
1312  for (const MCMatches &match : m_matches)
1313  {
1314  const MCHierarchy::Node *pNode{match.GetMC()};
1315  if (pNode->IsTestBeamParticle())
1316  ++nNodes;
1317  }
1318 
1319  return nNodes;
1320 }
1321 
1322 //------------------------------------------------------------------------------------------------------------------------------------------
1323 
1325 {
1326  unsigned int nNeutrinoMCParticles{this->GetNNeutrinoMCNodes()}, nNeutrinoRecoParticles{0};
1327  unsigned int nCosmicMCParticles{this->GetNCosmicRayMCNodes()}, nCosmicRecoParticles{0}, nCosmicRecoBTParticles{0};
1328  unsigned int nTestBeamMCParticles{this->GetNTestBeamMCNodes()}, nTestBeamRecoParticles{0}, nTestBeamRecoBTParticles{0};
1329  std::cout << "=== Matches ===" << std::endl;
1330  for (const MCMatches &match : m_matches)
1331  {
1332  const MCHierarchy::Node *pMCNode{match.GetMC()};
1333  const int pdg{pMCNode->GetParticleId()};
1334  const size_t mcHits{pMCNode->GetCaloHits().size()};
1335  const std::string tag{pMCNode->IsTestBeamParticle() ? "(Beam) " : pMCNode->IsCosmicRay() ? "(Cosmic) " : ""};
1336  std::cout << "MC " << tag << pdg << " hits " << mcHits << std::endl;
1337  const RecoHierarchy::NodeVector &nodeVector{match.GetRecoMatches()};
1338 
1339  for (const RecoHierarchy::Node *pRecoNode : nodeVector)
1340  {
1341  const unsigned int recoHits{static_cast<unsigned int>(pRecoNode->GetCaloHits().size())};
1342  const unsigned int sharedHits{match.GetSharedHits(pRecoNode)};
1343  const float purity{match.GetPurity(pRecoNode)};
1344  const float completeness{match.GetCompleteness(pRecoNode)};
1345  std::cout << " Matched " << sharedHits << " out of " << recoHits << " with purity " << purity << " and completeness "
1346  << completeness << std::endl;
1347  }
1348  if (nodeVector.empty())
1349  std::cout << " Unmatched" << std::endl;
1350 
1351  if (pMCNode->IsTestBeamParticle())
1352  ++nTestBeamRecoParticles;
1353  else if (pMCNode->IsCosmicRay())
1354  ++nCosmicRecoParticles;
1355  else
1356  ++nNeutrinoRecoParticles;
1357  }
1358 
1359  if (mcHierarchy.IsNeutrinoHierarchy())
1360  {
1361  std::cout << "Neutrino Interaction Summary:" << std::endl;
1362  std::cout << std::fixed << std::setprecision(1);
1363  if (nNeutrinoMCParticles)
1364  {
1365  std::cout << "Matched final state particles: " << nNeutrinoRecoParticles << " of " << nNeutrinoMCParticles << " : "
1366  << (100 * nNeutrinoRecoParticles / static_cast<float>(nNeutrinoMCParticles)) << "%" << std::endl;
1367  }
1368  if (nCosmicMCParticles)
1369  {
1370  std::cout << "Matched cosmics: " << nCosmicRecoParticles << " of " << nCosmicMCParticles << " : "
1371  << (100 * nCosmicRecoParticles / static_cast<float>(nCosmicMCParticles)) << "%" << std::endl;
1372  }
1373  }
1374  else if (mcHierarchy.IsTestBeamHierarchy())
1375  {
1376  std::cout << "Test Beam Interaction Summary:" << std::endl;
1377  std::cout << std::fixed << std::setprecision(1);
1378  if (nTestBeamMCParticles)
1379  {
1380  std::cout << "Matched test beam particles: " << nTestBeamRecoParticles << " of " << nTestBeamMCParticles << " : "
1381  << (100 * nTestBeamRecoParticles / static_cast<float>(nTestBeamMCParticles)) << "%" << std::endl;
1382  std::cout << "Loosely matched test beam particles: " << (nTestBeamRecoParticles + nTestBeamRecoBTParticles) << " of " << nTestBeamMCParticles
1383  << " : " << (100 * (nTestBeamRecoParticles + nTestBeamRecoBTParticles) / static_cast<float>(nTestBeamMCParticles))
1384  << "%" << std::endl;
1385  }
1386  if (nCosmicMCParticles)
1387  {
1388  std::cout << "Matched cosmics: " << nCosmicRecoParticles << " of " << nCosmicMCParticles << " : "
1389  << (100 * nCosmicRecoParticles / static_cast<float>(nCosmicMCParticles)) << "%" << std::endl;
1390  std::cout << "Loosely matched cosmics: " << (nCosmicRecoParticles + nCosmicRecoBTParticles) << " of " << nCosmicMCParticles << " : "
1391  << (100 * (nCosmicRecoParticles + nCosmicRecoBTParticles) / static_cast<float>(nCosmicMCParticles)) << "%" << std::endl;
1392  }
1393  }
1394  if (!this->GetUnmatchedReco().empty())
1395  std::cout << "Unmatched reco: " << this->GetUnmatchedReco().size() << std::endl;
1396 }
1397 
1398 //------------------------------------------------------------------------------------------------------------------------------------------
1399 //------------------------------------------------------------------------------------------------------------------------------------------
1400 
1402  const MCParticleList &mcParticleList, const CaloHitList &caloHitList, const FoldingParameters &foldParameters, MCHierarchy &hierarchy)
1403 {
1404  hierarchy.FillHierarchy(mcParticleList, caloHitList, foldParameters);
1405 }
1406 
1407 //------------------------------------------------------------------------------------------------------------------------------------------
1408 
1409 void LArHierarchyHelper::FillRecoHierarchy(const PfoList &pfoList, const FoldingParameters &foldParameters, RecoHierarchy &hierarchy)
1410 {
1411  hierarchy.FillHierarchy(pfoList, foldParameters);
1412 }
1413 
1414 //------------------------------------------------------------------------------------------------------------------------------------------
1415 
1416 void LArHierarchyHelper::MatchHierarchies(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy, MatchInfo &matchInfo)
1417 {
1418  matchInfo.Match(mcHierarchy, recoHierarchy);
1419 }
1420 
1421 //------------------------------------------------------------------------------------------------------------------------------------------
1422 // private
1423 
1424 const MCParticle *LArHierarchyHelper::GetMCPrimaries(const MCParticleList &mcParticleList, MCParticleSet &primaries)
1425 {
1426  const MCParticle *pRoot{nullptr};
1427  for (const MCParticle *pMCParticle : mcParticleList)
1428  {
1429  try
1430  {
1431  const MCParticle *const pPrimary{LArMCParticleHelper::GetPrimaryMCParticle(pMCParticle)};
1433  primaries.insert(pPrimary);
1434  else
1435  primaries.insert(pPrimary);
1436  }
1437  catch (const StatusCodeException &)
1438  {
1439  if (LArMCParticleHelper::IsNeutrino(pMCParticle))
1440  pRoot = pMCParticle;
1441  else if (pMCParticle->GetParticleId() != 111 && pMCParticle->GetParticleId() < 1e9)
1442  std::cout << "LArHierarchyHelper::MCHierarchy::FillHierarchy: MC particle with PDG code " << pMCParticle->GetParticleId()
1443  << " at address " << pMCParticle << " has no associated primary particle" << std::endl;
1444  }
1445  }
1446 
1447  return pRoot;
1448 }
1449 
1450 const ParticleFlowObject *LArHierarchyHelper::GetRecoPrimaries(const PfoList &pfoList, PfoSet &primaries)
1451 {
1452  const ParticleFlowObject *pRoot{nullptr};
1453  PfoSet cosmicPfos;
1454  for (const ParticleFlowObject *pPfo : pfoList)
1455  {
1456  if (LArPfoHelper::IsNeutrino(pPfo))
1457  {
1458  pRoot = pPfo;
1459  break;
1460  }
1461  else
1462  {
1463  const ParticleFlowObject *const pParent{LArPfoHelper::GetParentPfo(pPfo)};
1464  if (pParent && LArPfoHelper::IsNeutrino(pParent))
1465  {
1466  pRoot = pParent;
1467  break;
1468  }
1469  else
1470  {
1471  // Should be in a test beam scenario
1472  const int tier{LArPfoHelper::GetHierarchyTier(pPfo)};
1473  if (tier == 0 && LArPfoHelper::IsTestBeam(pPfo))
1474  {
1475  // Triggered beam particle
1476  primaries.insert(pPfo);
1477  continue;
1478  }
1479  if (tier > 1)
1480  continue;
1481  if (!LArPfoHelper::IsTestBeam(pPfo))
1482  {
1483  // Cosmic induced
1484  cosmicPfos.insert(pPfo);
1485  }
1486  }
1487  }
1488  }
1489  if (pRoot && LArPfoHelper::IsNeutrino(pRoot))
1490  {
1491  const PfoList &children{pRoot->GetDaughterPfoList()};
1492  for (const ParticleFlowObject *pPrimary : children)
1493  primaries.insert(pPrimary);
1494  }
1495  else
1496  {
1497  for (const ParticleFlowObject *pPfo : cosmicPfos)
1498  primaries.insert(pPfo);
1499  }
1500 
1501  return pRoot;
1502 }
1503 
1504 } // namespace lar_content
const pandora::MCParticle * m_pNeutrino
The incident neutrino, if it exists.
const pandora::CaloHitList & GetCaloHits() const
Retrieve the CaloHits associated with this node.
MCMatchesVector m_matches
The vector of good matches from MC to reco.
const unsigned int m_minHitsForGoodView
the minimum number of Hits for a good view
std::set< const pandora::MCParticle * > MCParticleSet
static bool SortByNHits(const pandora::ParticleFlowObject *const pLhs, const pandora::ParticleFlowObject *const pRhs)
Sort pfos by number of constituent hits.
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.
RecoHierarchy::NodeVector m_recoNodes
Matched reco nodes.
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...
enum cvn::HType HitType
QMapNodeBase Node
Definition: qmap.cpp:41
std::string string
Definition: nybbler.cc:12
const std::string ToString() const
Produce a string representation of the hierarchy.
pandora::IntVector m_sharedHits
Number of shared hits for each match.
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.
static int GetHierarchyTier(const pandora::MCParticle *const pMCParticle)
Determine the position in the hierarchy for the MCParticle.
const RecoHierarchy & m_hierarchy
The parent reco hierarchy.
static const pandora::MCParticle * GetPrimaryMCParticle(const pandora::MCParticle *const pMCParticle)
Get the primary parent mc particle.
int m_pdg
The particle ID (track = muon, shower = electron)
void GetFlattenedNodes(NodeVector &nodeVector) const
Retrieve a flat vector of the ndoes in the hierarchy.
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.
static void MatchHierarchies(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy, MatchInfo &matchInfo)
Finds the matches between reconstructed and MC hierarchies.
const pandora::ParticleFlowObject * m_pNeutrino
The incident neutrino, if it exists.
static const pandora::ParticleFlowObject * GetRecoPrimaries(const pandora::PfoList &pfoList, PfoSet &primaries)
Retrieves the primary PFOs from a list and returns the root (neutrino) for hierarchy, if it exists.
Node(const RecoHierarchy &hierarchy, const pandora::ParticleFlowObject *pPfo)
Create a node with a primary PFO.
unsigned int GetNTestBeamMCNodes() const
Retrieve the number of test beam derived MC nodes available to match.
Q_EXPORT QTSManip setprecision(int p)
Definition: qtextstream.h:343
unsigned int GetNNeutrinoMCNodes() const
Retrieve the number of neutrino interaction derived MC nodes available to match.
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.
const MCHierarchy::Node * GetMC() const
Retrieve the MC node.
unsigned int GetNMCNodes() const
Retrieve the number of MC nodes available to match.
MCMatches(const MCHierarchy::Node *pMCParticle)
Constructor.
void Match(const MCHierarchy &mcHierarchy, const RecoHierarchy &recoHierarchy)
Match the nodes in the MC and reco hierarchies.
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 ...
T abs(T value)
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.
bool IsNeutrinoHierarchy() const
Check if this is a neutrino hierarchy.
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...
static int GetHierarchyTier(const pandora::ParticleFlowObject *const pPfo)
Determine the position in the hierarchy for the MCParticle.
static bool IsTestBeam(const pandora::ParticleFlowObject *const pPfo)
Whether a pfo is a test beam particle.
static const pandora::ParticleFlowObject * GetParentPfo(const pandora::ParticleFlowObject *const pPfo)
Get the primary parent pfo.
const pandora::MCParticle * GetNeutrino() const
Retrieve the neutrino at the root of the hierarchy if it exists.
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.
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.
static bool IsTriggeredBeamParticle(const pandora::MCParticle *const pMCParticle)
Returns true if passed a primary triggered beam MCParticle.
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.
static int max(int a, int b)
float m_cosAngleTolerance
Cosine of the maximum angle at which topologies can be considered continuous.
NodeVector m_rootNodes
The leading nodes (e.g. primary particles, cosmic rays, ...)
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 pandora::ParticleFlowObject * GetNeutrino() const
Retrieve the neutrino at the root of the hierarchy if it exists.
const bool m_removeNeutrons
whether to remove neutrons and their downstream particles
Header file for the lar hierarchy helper class.
void GetFlattenedNodes(NodeVector &nodeVector) const
Retrieve a flat vector of the nodes in the hierarchy.
const std::string ToString() const
Produce a string representation of the hierarchy.
pandora::CaloHitList m_caloHits
The list of calo hits of which this node is composed.
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.
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...
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...
cet::LibraryManager dummy("noplugin")
int GetParticleId() const
Retrieve the PDG code for the leading particle in this node Note, for reco objects the PDG codes repr...
bool IsTestBeamHierarchy() const
Check if this is a test beam hierarchy.
void FillFlat(const pandora::MCParticle *pRoot)
Fill this node by folding all descendent particles to this node.
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
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.
float GetCompleteness(const RecoHierarchy::Node *pReco, const bool adcWeighted=false) const
Retrieve the completeness of the match.
const pandora::MCParticle * m_pMCNeutrino
The parent neutrino if it exists.
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::string to_string(ModuleType const mt)
Definition: ModuleType.h:34
std::set< const pandora::ParticleFlowObject * > PfoSet
const pandora::ParticleFlowObject * m_pRecoNeutrino
The parent neutrino if it exists.
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 const pandora::MCParticle * GetMCPrimaries(const pandora::MCParticleList &mcParticleList, MCParticleSet &primaries)
Retrieves the primary MC particles from a list and returns the root (neutrino) for hierarchy...
static QCString str
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.
QTextStream & endl(QTextStream &s)
NodeVector m_rootNodes
The leading nodes (e.g. primary particles, cosmic rays, ...)
unsigned int GetNCosmicRayMCNodes() const
Retrieve the number of cosmic ray derived MC nodes available to match.
void AddRecoMatch(const RecoHierarchy::Node *pReco, const int nSharedHits)
Add a reconstructed node as a match for this MC node.