Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
c0899fa
Introduce compact truth graph data structure
felicepantaleo Mar 5, 2026
c8ec262
Add necessary build and dictionary files for TruthGraph
felicepantaleo Mar 5, 2026
0d0c995
Implement TruthGraphProducer for event graph construction
felicepantaleo Mar 5, 2026
cff8453
Add dumper module for graph visualization and inspection
felicepantaleo Mar 5, 2026
31ce40f
Add plugin build configuration for producer and dumper modules
felicepantaleo Mar 5, 2026
a48dd47
Create standalone truth logical graph
felicepantaleo May 8, 2026
49d1207
Draft README
felicepantaleo May 11, 2026
fbc4f71
Add possibility to filter mother by pdgid
felicepantaleo May 11, 2026
08470a6
Add generator status flags to truth graphs
felicepantaleo May 11, 2026
f39077f
Collapse intermediate same-PDG GEN particle chains
felicepantaleo May 11, 2026
8ca07ac
Dump information also as DOT file attributes
tcuisset May 11, 2026
8546040
Quote DOT attributes in truth graph dumper
felicepantaleo May 11, 2026
ced60b7
Add extra DOT file labels to TruthLogicalGraphDumper (#64)
tcuisset May 11, 2026
d4441c5
format
felicepantaleo May 11, 2026
2bd36ec
Introduce logical graph post-processing step
felicepantaleo May 11, 2026
523b036
Keep GEN momentum for merged logical truth particles
felicepantaleo May 11, 2026
4a54f19
Process multiple files at same time
felicepantaleo May 12, 2026
91e8d6d
Build GEN-SIM particle links only for primary SimTracks
felicepantaleo May 12, 2026
5e74161
Improve TruthLogicalGraph DOT dumping
felicepantaleo May 12, 2026
55d8d42
Label TruthGraph DOT edges with explicit edge types
felicepantaleo May 12, 2026
a393d5e
Dot simToGen edge
felicepantaleo May 12, 2026
749908a
Add SimHits to SimTracks
felicepantaleo May 12, 2026
aadec46
Add new SimHitToRecHitMap
felicepantaleo May 12, 2026
979e429
Add associated rechits and energy to truth particles
felicepantaleo May 12, 2026
4b29150
Update README
felicepantaleo May 12, 2026
c8629b2
adding rechits
felicepantaleo May 13, 2026
76f2963
adding possibility to hide truth particles with no simhits
felicepantaleo May 13, 2026
0429b70
add argparser
IzaakWN May 13, 2026
6cb9983
Separate postprocessing
felicepantaleo May 13, 2026
180b280
make input file optional (#66)
IzaakWN May 13, 2026
f611c2c
dump simhits & rechits (#67)
tcuisset May 13, 2026
6169163
Add unit tests for TruthLogicalGraphPostProcessor
felicepantaleo May 15, 2026
3405130
Create enableTruth ProcessModifier
felicepantaleo May 15, 2026
78911e1
Create enableTruth Run4 workflows
felicepantaleo May 15, 2026
e3e64b4
Merge vertices if overlapping
felicepantaleo Jun 4, 2026
1425190
Produce Truth graph with validation
felicepantaleo Jun 4, 2026
380b1b6
Adding statusFlags
felicepantaleo Jun 8, 2026
639b3b9
TruthInfo: fix vertex time units, add SimVertex->GenVertex link, enri…
felicepantaleo Jun 12, 2026
c5afa72
TruthInfo: add barrel/forward PFRecHit and tracker SimHit tables, mat…
felicepantaleo Jun 12, 2026
187318c
TruthInfo: clang-format TrackerSimHitFlatTableProducer
felicepantaleo Jun 13, 2026
5437a5c
TruthInfo: add seed and decay-pattern selection to the logical truth …
felicepantaleo Jun 13, 2026
589dc39
enableTruth: keep full SimTrack ancestry in SIM so the truth graph st…
felicepantaleo Jun 13, 2026
6effc20
TruthInfo: focused selection with typed ISR/underlying-event source n…
felicepantaleo Jun 13, 2026
cf598d8
TruthInfo: add truth-graph connectivity, gallery and relval tooling
felicepantaleo Jun 13, 2026
8e1e986
TruthInfo: add truth::Branch design notes
felicepantaleo Jun 13, 2026
2a9f34e
TruthInfo: truth::Branch subgraph view with generic hit-based matchin…
felicepantaleo Jun 13, 2026
80dc449
TruthInfo: update Branch design notes with implementation status
felicepantaleo Jun 13, 2026
7afc9f9
TruthInfo: validate Branch reproduces CaloParticle/SimCluster hit con…
felicepantaleo Jun 13, 2026
f5f2e0e
format
felicepantaleo Jun 13, 2026
efe2b70
TruthInfo: validate Branch reproduces TrackingParticle track->truth a…
felicepantaleo Jun 13, 2026
2613821
TruthInfo: add author headers and prototype-status notice
felicepantaleo Jun 13, 2026
c7e144f
TruthInfo: add TruthGraph topology checker
felicepantaleo Jun 13, 2026
6558201
TruthInfo: attach SIM tracks to immediate GEN vertex; drop position v…
felicepantaleo Jun 13, 2026
eac7cb6
format
felicepantaleo Jun 13, 2026
bba5dfa
TruthInfo: Phase-A pileup prototype (mixed truth graph from crossing …
felicepantaleo Jun 14, 2026
87f8607
TruthInfo: Phase-B B1 - TruthGraphAccumulator (mixed graph via Mixing…
felicepantaleo Jun 14, 2026
2e96387
TruthInfo: B1 GEN handling - collapsed gen for pileup
felicepantaleo Jun 14, 2026
fce7b87
TruthInfo: optimize hot paths (allocation-free traversals, flat hit/i…
felicepantaleo Jun 14, 2026
f523cd1
TruthInfo: H2 - visited-set lowest common ancestor (drop dense distan…
felicepantaleo Jun 15, 2026
d2a67b5
HGCalAssociatorProducers: M5 - DetIdRecHitMap as a sorted vector (dro…
felicepantaleo Jun 15, 2026
1d3b7b5
TruthInfo: M7 - path-compress the collapse representative walk
felicepantaleo Jun 15, 2026
2fe5a2f
HGCalAssociatorProducers: drop redundant std::pair/vector dictionarie…
felicepantaleo Jun 15, 2026
2f9126a
TruthInfo: BranchHGCalValidator - DQM performance plots for Branch vs…
felicepantaleo Jun 15, 2026
f4f3f1a
TruthInfo: TruthBranchCaloAssociationProducer - TICL-style Branch<->c…
felicepantaleo Jun 15, 2026
a07b2e9
TruthInfo: Branch DQM validation sequence + efficiency harvester
felicepantaleo Jun 15, 2026
678715e
TruthInfo: reco-side Branch validation and generic hit exposure
felicepantaleo Jun 15, 2026
bb56817
TruthInfo: prune hitless SIM subgraphs from the logical graph
felicepantaleo Jun 15, 2026
7801cdd
TruthInfo: option to root a selection directly at its seeds
felicepantaleo Jun 15, 2026
70c962d
TruthInfo: clang-format BranchRecoValidator / BranchTrackingValidator
felicepantaleo Jun 15, 2026
f91addc
TruthInfo: per-interaction Interaction vertex over Upstream/Underlyin…
felicepantaleo Jun 15, 2026
37c001e
TruthInfo: key Interaction vertices by EncodedEventId, not genEvent
felicepantaleo Jun 15, 2026
7fee346
TruthInfo: generalize LogicalGraphHitIndex to N detector channels
felicepantaleo Jun 16, 2026
3dd0b20
TruthInfo: fill the MTD hit-index channel from MtdSimLayerClusters
felicepantaleo Jun 16, 2026
5e47536
TruthInfo: fill the Muon channel and link MTD hits to reco FTLClusters
felicepantaleo Jun 16, 2026
ba37766
TruthInfo: per-subdetector channel selection in the hit-index producer
felicepantaleo Jun 16, 2026
7e1d136
TruthInfo: skip the subgraph build for empty hit-index channels
felicepantaleo Jun 16, 2026
7433c96
TruthInfo: give artificial source vertices an Internal domain and the…
felicepantaleo Jun 16, 2026
be7d282
TruthInfo: makeTruthGallery renders the -d 0 signal view
felicepantaleo Jun 16, 2026
19a09c8
TruthInfo: run the Branch validators in the release validation behind…
felicepantaleo Jun 16, 2026
588ae3c
SimG4Core: ReconnectDroppedAncestors - orphan-free SimVertex history …
felicepantaleo Jun 16, 2026
f7e65d3
SimG4Core: enableTruth uses ReconnectDroppedAncestors instead of Pers…
felicepantaleo Jun 16, 2026
333252a
TruthInfo: Branch validators - 'truth object -> best-matched Branch' …
felicepantaleo Jun 17, 2026
0d33ef7
TruthInfo: annotate logical-graph vertices with their physical reason
felicepantaleo Jun 17, 2026
b9817d1
TruthInfo: flag back-scattered (albedo) particles in the logical graph
felicepantaleo Jun 17, 2026
9fc6c16
TruthInfo: raise n_sharing_branches histogram cap to 50
felicepantaleo Jun 17, 2026
70c150c
TruthInfo: add makeBranchValidationPlots.sh wrapper for the Branch DQ…
felicepantaleo Jun 17, 2026
76d0e15
TruthInfo: add raw energy response plots to the HGCal Branch validator
felicepantaleo Jun 17, 2026
acd16ee
TruthInfo: keepProductionSiblings option to show the seed's hard co-p…
felicepantaleo Jun 17, 2026
a582655
TruthInfo: enable keepProductionSiblings for the VBF gallery view only
felicepantaleo Jun 17, 2026
445c414
TruthInfo: per-process graph-selection presets (truthGraphSelections)
felicepantaleo Jun 17, 2026
4c2f9c3
TruthInfo: document truthGraphSelections presets in README_tools
felicepantaleo Jun 17, 2026
2ab0821
TruthInfo: composable signalOnly / keepBunchCrossings pile-up filter
felicepantaleo Jun 17, 2026
dccb96a
TruthInfo: fix GeomDet include for clang-tidy (CommonDetUnit -> Commo…
felicepantaleo Jun 18, 2026
18b95fa
TruthInfo: big on-canvas particle name and vertex reason in the DOT l…
felicepantaleo Jun 19, 2026
9b761df
SimG4Core: scope ReconnectDroppedAncestors fully to enableTruth (no b…
felicepantaleo Jun 20, 2026
5db28cf
SimG4Core,PhysicsTools/TruthInfo: regression tests guarding the SimTr…
felicepantaleo Jun 20, 2026
eb4105f
TruthInfo,SimG4Core: address PR review comments (quick wins)
felicepantaleo Jun 20, 2026
d8006dc
TruthInfo: custom single-top and ttbar-POWHEG gallery samples + rechi…
felicepantaleo Jun 20, 2026
7343473
SimDataFormats/TruthInfo: move the truth-graph data formats out of Ph…
felicepantaleo Jun 21, 2026
d6b48ef
TruthInfo,SimCalorimetry: address EDM-dictionary review (class versio…
felicepantaleo Jun 22, 2026
ae96f2f
SimCalorimetry: rename SimHitToRecHitMapProducer to DetIdToRecHitMapP…
felicepantaleo Jun 22, 2026
5324444
PhysicsTools/TruthInfo: dedicated single-top selection preset
felicepantaleo Jun 22, 2026
f2fe608
PhysicsTools/TruthInfo: sort branch association maps best-match-first
felicepantaleo Jun 28, 2026
99208b8
PhysicsTools/TruthInfo: apply the pile-up filter independently of seeds
felicepantaleo Jun 28, 2026
ede1c10
PhysicsTools/TruthInfo: count each subgraph descendant hit once
felicepantaleo Jun 28, 2026
af1dc73
PhysicsTools/TruthInfo: emit each SIM decay edge once per vertex
felicepantaleo Jun 28, 2026
a69ab9d
PhysicsTools/TruthInfo: fall back to SIM kinematics on GEN payload miss
felicepantaleo Jun 28, 2026
01db5b0
PhysicsTools/TruthInfo: drop premature PFRecHit handle dereference in…
felicepantaleo Jun 28, 2026
eeed2bc
SimDataFormats/TruthInfo: guard navigation getters on invalid views
felicepantaleo Jun 28, 2026
5503c59
PhysicsTools,SimCalorimetry: keep dumper recHit list in sync with the…
felicepantaleo Jun 28, 2026
d9a1aee
PhysicsTools/TruthInfo: guard RecHit table handle and emit energy/time
felicepantaleo Jun 28, 2026
af12de7
PhysicsTools/TruthInfo: branch-normalized reverse score, explicit emp…
felicepantaleo Jun 28, 2026
0e38432
SimDataFormats,PhysicsTools: make truth-graph traversals cycle-safe
felicepantaleo Jun 28, 2026
d9b1480
PhysicsTools/TruthInfo: fill processType/backscatter in pileup producers
felicepantaleo Jun 28, 2026
91aa477
PhysicsTools/TruthInfo: hide subgraphs only when hitless in every cha…
felicepantaleo Jun 28, 2026
867070a
SimCalorimetry/HGCalAssociatorProducers: bind recHit tag lists by con…
felicepantaleo Jun 28, 2026
3eb5fac
SimDataFormats/TruthInfo: name the hit-index accessor locals descript…
felicepantaleo Jun 28, 2026
6ab82f9
SimCalorimetry/HGCalAssociatorProducers: enforce DetIdRecHitMap build…
felicepantaleo Jun 28, 2026
6e4ea37
SimDataFormats/TruthInfo: order HitChannel inner -> outer
felicepantaleo Jun 28, 2026
32079e4
SimG4Core/Application: make ReconnectDroppedAncestors a baseline para…
felicepantaleo Jun 28, 2026
2c858f2
SimDataFormats,PhysicsTools: encapsulate truth-graph data members
felicepantaleo Jun 28, 2026
7ec62e5
SimDataFormats,PhysicsTools: one-class-per-header split + kInvalidRec…
felicepantaleo Jun 28, 2026
431c7b4
PhysicsTools/TruthInfo: cover diboson / ttX / VH / HH / W+jets / DY-n…
felicepantaleo Jun 28, 2026
82bbc25
PhysicsTools/TruthInfo: add diboson (WW) and VH (ZH) gallery samples
felicepantaleo Jun 28, 2026
67163f4
PhysicsTools/TruthInfo: test that every particle grounds on an artifi…
felicepantaleo Jun 28, 2026
c7987cb
PhysicsTools/TruthInfo: don't render edgeless particles in the DOT dump
felicepantaleo Jun 28, 2026
0196308
PhysicsTools/TruthInfo: make the DOT graph layout configurable
felicepantaleo Jun 28, 2026
050607e
TruthGraphProducer: fill GenStatusFlags from HepMC via MCTruthHelper
felicepantaleo Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Configuration/ProcessModifiers/python/enableTruth_cff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Original author: Felice Pantaleo (CERN) <felice.pantaleo@cern.ch>
# Part of the MC-truth-graph prototype - under heavy development, not yet open
# to external contributions (see PhysicsTools/TruthInfo/README.md).

import FWCore.ParameterSet.Config as cms

enableTruth = cms.Modifier()
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,49 @@ def condition(self, fragment, stepList, key, hasHarvest):
upgradeWFs['ticlv5_TrackLinkingGNN'].step3 = {'--procModifiers': 'ticlv5_TrackLinkingGNN'}
upgradeWFs['ticlv5_TrackLinkingGNN'].step4 = {'--procModifiers': 'ticlv5_TrackLinkingGNN'}



class UpgradeWorkflow_enableTruth(UpgradeWorkflow):
def setup_(self, step, stepName, stepDict, k, properties):
# enableTruth runs the truth-graph producers in RecoGlobal (step3) and,
# in GenSim (step1), keeps the full ancestor branch of every stored
# SimTrack (g4SimHits PersistencyEmin -> 0 via the modifier) so the
# truth graph stays connected to the generator. The Branch validators run
# in the RecoGlobal VALIDATION and their efficiency harvesting in
# HARVESTGlobal (step4), so the modifier must reach the harvesting step too.
if 'GenSim' in step or 'RecoGlobal' in step or 'HARVESTGlobal' in step:
stepDict[stepName][k] = deepcopy(stepDict[step][k])

if '--procModifiers' in stepDict[stepName][k]:
stepDict[stepName][k]['--procModifiers'] += ',enableTruth'
else:
stepDict[stepName][k]['--procModifiers'] = 'enableTruth'

def condition(self, fragment, stepList, key, hasHarvest):
return 'Run4' in key


upgradeWFs['enableTruth'] = UpgradeWorkflow_enableTruth(
steps = [
'GenSim',
'GenSimHLBeamSpot',
'GenSimHLBeamSpot14',
'GenSimHLBeamSpotCloseBy',
'RecoGlobal',
'HARVESTGlobal',
],
PU = [
'GenSim',
'GenSimHLBeamSpot',
'GenSimHLBeamSpot14',
'GenSimHLBeamSpotCloseBy',
'RecoGlobal',
'HARVESTGlobal',
],
suffix = '_enableTruth',
offset = 0.88,
)

# L3 Tracker Muon Outside-In reconstruction first
class UpgradeWorkflow_phase2L3MuonsOIFirst(UpgradeWorkflow):
def setup_(self, step, stepName, stepDict, k, properties):
Expand Down
14 changes: 14 additions & 0 deletions PhysicsTools/TruthInfo/BuildFile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<use name="FWCore/Framework"/>
<use name="FWCore/MessageLogger"/>
<use name="FWCore/Utilities"/>
<use name="DataFormats/Common"/>
<use name="DataFormats/Math"/>
<use name="FWCore/ParameterSet"/>
<use name="SimDataFormats/EncodedEventId"/>
<use name="SimDataFormats/Associations"/>
<use name="SimDataFormats/TruthInfo"/>
<use name="heppdt"/>

<export>
<lib name="1"/>
</export>
90 changes: 90 additions & 0 deletions PhysicsTools/TruthInfo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# TruthInfo prototype

> **Status: under heavy development — not open to external contributions.**
> This is an experimental prototype: its data model, APIs, and configuration
> change frequently and without notice, and it targets **Phase-2 (Run 4) only**
> (no Phase-1/Run-2 support). Please do not submit external changes or depend on
> it in production at this stage.
>
> **Original author and maintainer:** Felice Pantaleo (CERN),
> <felice.pantaleo@cern.ch>.

A prototype **MC-truth graph** for CMS: a single, navigable, physics-oriented
abstraction of the generator + simulation truth history of an event, with
calorimeter and tracker hit indices layered on top. It replaces the need to
cross-navigate the many low-level truth collections (HepMC, GenParticles,
SimTracks/SimVertices, TrackingParticles, SimClusters, CaloParticles, SimHits,
RecHits) by hand.

## Documentation

The **authoritative, maintained documentation** lives on the project website:

> **http://cms-truth.docs.cern.ch/**

It covers the data model, how to enable and use the graph (navigation API, the
`truth::Branch` view, the hit index, matching reco objects), the physics findings,
validation, pileup, and the roadmap. The MkDocs sources are in the companion
`cms-truth-docs` repository. Start with the **"How to use the graph"** page.

## The three layers

1. **`TruthGraph`** (raw) — a compact CSR graph built directly from HepMC +
`SimTrack`/`SimVertex` by `TruthGraphProducer`.
2. **`truth::Graph`** (logical) — a user-facing bipartite Particle↔Vertex graph
built by `TruthLogicalGraphProducer`; GEN and SIM are merged where robustly
associated, with navigation (`parents()`, `descendants()`,
`firstCommonAncestor()`, `hasAncestorPdgId()`, …) and the `truth::Branch`
subgraph view + `BranchSelector` selection.
3. **`truth::LogicalGraphHitIndex`** — per-particle direct vs aggregated subgraph
calorimeter and tracker hits, built by `LogicalGraphHitIndexProducer` (with the
DetId→RecHit map from `DetIdToRecHitMapProducer`).

Producer chain (order matters): `truthGraphProducer` → `truthLogicalGraphProducer`
→ `detIdToRecHitMapProducer` → `truthLogicalGraphHitIndexProducer`. In a release
job these run behind the `enableTruth` process modifier (the
`truthGraphPrevalidation` sequence in `Validation/Configuration`).

## Package layout

- `interface/` + `src/` — the data formats and algorithms: `TruthGraph`,
`truth::Graph`, `Branch`, `BranchSelector`, `LogicalGraphHitIndex`,
`BranchHitAssociator`, the `truth::recoHits` adapters (`RecoHitAdapters.h`), and
`TruthLogicalGraphPostProcessor` (merge/collapse/filter; covered by the cppunit).
- `plugins/` — the producers above, the DOT dumpers, the flat-table producers, the
pileup `TruthGraphAccumulator`/`TruthGraphMixedProducer`, the association-map
producers (`TruthBranchCaloAssociationProducer`,
`TruthBranchTrackingAssociationProducer`), and the DQM validators
(`BranchHGCalValidator`, `BranchTrackingValidator`, the generic
`BranchRecoValidator`).
- `python/` — `truthGraphValidation_cff` (producers + association maps + DQM
analyzers) and `truthGraphDQMHarvester_cff` (efficiency/fake/merge harvesting).
- `scripts/` — `makeTruthGraphValidationPlots.py` (renders the Branch validation
plots / sample overlays).
- `test/` — cppunit unit tests and standalone `cmsRun` drivers (graph dumps,
topology checks, association/DQM smoke tests).

## Build, check, test

All commands assume the CMSSW environment (`cd $CMSSW_BASE/src && cmsenv`):

```bash
scram b -j 8 # build
scram b code-format code-checks -j 8 # clang-format + clang-tidy (must pass)
scram b runtests # cppunit unit tests
```

## Run standalone on a step3.root

```bash
# Dump per-event DOT graphs (options: -n, -m/--merge, -c/--collapse, -o, -t)
cmsRun test/dumpTruthGraphsFromGENSIMRECO_cfg.py path/to/step3.root -n 5

# Branch DQM validators (calo / tracking / generic reco-side)
cmsRun test/validateBranchDQM_cfg.py path/to/step3.root -n 5
cmsRun test/validateBranchTrackingDQM_cfg.py path/to/step3.root -n 5
cmsRun test/validateBranchRecoDQM_cfg.py path/to/step3.root -n 5
```

See the website for the full configuration reference, the navigation API with
examples, and the validation results.
151 changes: 151 additions & 0 deletions PhysicsTools/TruthInfo/doc/branch_design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# `truth::Branch` — design proposal (for discussion)

## Concept
A `truth::Branch` is a **coherent connected subgraph** of `truth::Graph`: a chosen
root (a particle, or a small set such as `Z -> mu mu`) together with a defined
**closure** of its descendants, plus the detector footprint attached through
`LogicalGraphHitIndex`. Like `Particle`/`Vertex`, it is a lightweight **view**
into a `Graph`, not an owning copy.

A Branch is the natural target for truth-reco association when a reconstructed
object does not map to a single truth particle:
- a jet <-> a parton's whole shower branch,
- an ECAL supercluster <-> a photon + its conversion e+e- branch,
- a tau-jet <-> the visible tau-decay branch,
- a b-jet <-> the b-quark branch (with its B-hadron sub-branch).

It is built directly on phases 1-3: the selection (seed PDG / heavy-flavor)
chooses the root, the downstream closure defines the extent, the ISR/underlying
-event roles + genEvent/eventId give provenance, and the hit index gives the
detector footprint.

## Construction & closure policy
```cpp
Branch b = graph.branch(rootParticle, closure);
```
`closure` selects which members belong to the branch and is always evaluated on
the fly from the `Graph` (a Branch is never an EDM product):
- `Subtree` - the root and all descendants (default);
- `StableLeaves`- the root plus only its final-state descendants;
- `DepthN` - descendants down to N generations;
- `UntilPdgId` - stop the closure at a species (e.g. stop at stable hadrons,
or at the B hadron for a b-branch);
- `Predicate` - stop on a user predicate (stop at any heavy-flavor hadron, at a
detector-boundary crossing, on a custom lambda), so closures
are extensible without new enum values.
The phase-1-3 postprocessing already computes member sets; a Branch makes that
set a first-class, queryable object.

**Decision:** the Branch is a **view, recomputed on demand** — stateless, no
stored member list, not an EDM product. Any caching needed for performance lives
in the *matching layer* (below), scoped to a batch of objects, not in the Branch.

## Data model
```cpp
class Branch {
Graph const* graph_;
std::vector<uint32_t> roots_; // usually 1
std::vector<uint32_t> members_; // closure (materialized)
// optional caches: p4 sums, DetId set, hit spans
};
```
A Branch carries provenance via its root (`genEvent`/`eventId`), so pile-up
branches stay distinguishable when graphs are overlaid.

## Queries the Branch should answer

### A. Matching reco objects (the substrate is detector-agnostic; metrics are pluggable)
- `members()`, `stableLeaves()`, `chargedStableLeaves()`.
- `hits(closure)` - aggregated direct/subgraph SimHits + matched RecHits over all members (LogicalGraphHitIndex already gives this per particle).
- `detIds()`, `energy(Detector)` - sim/rec energy summed over the branch in a subdetector.
- `sharedHitFraction(recoObject)` / `sharedHits(recoObject)` - tracking-style metric.
- `energyFraction(recoCluster)` - calorimeter-style metric.
- `matchScore(recoObject, Metric)` - **Metric is a strategy on the Branch**: shared-hits (tracking), energy-fraction (calo), time-aware (MTD). New detectors add a Metric without touching Branch.
- `containsSimTrack(id)`, `containsDetId(id)`.

**Batch / many-to-many matching.** Single-object queries go through the Branch
metric strategy above. For associating *collections* — N reco <-> 1 sim (split
tracks, calo fragments -> one particle) and N sim <-> 1 reco (a jet <- a branch)
— a free `BranchMatcher(branches, recoObjects, Metric)` builds the inverted
`hit/DetId -> branch` index **once and caches it for the duration of the call**,
then emits a weighted bipartite association in both directions (cf. reco's
`RecoToSimCollection`/`SimToRecoCollection`). The Branch stays stateless; the
cache lives in the matcher.

**Hit ranges and `std::span`.** If `LogicalGraphHitIndex` lays hits out in graph
-topological order, a `Subtree` branch's hits are a **contiguous range** — i.e.
exactly the precomputed subgraph-hit `std::span` of its root, returned with zero
gather. The matcher can then count shared hits / energy by a sorted-range
merge-join rather than hashing, which is the cache-friendly path. (This needs the
hit-index builder to guarantee the topological layout; see follow-up below.)

### B. Tagging (flavor / origin / process)
- `rootPdgId()`, `originPdgId(targets)` (= `firstAncestorWithPdgId` from the root).
- `hasAncestorPdgId(id)` - is this branch from a top? a Z? the hard scatter?
- `heavyFlavorContent()` - does the branch contain a b/c hadron (b/c-tag truth)? (reuses the phase-2 flavor classifier).
- `isFromHardScatter()` / `isFromPileup()` - via the genEvent/eventId provenance (phase 1).
- `decayChannel()` - decay mode of the root (Z->mumu vs Z->ee, tau hadronic vs leptonic, prong count).
- `flightLength()` / `displacedVertex()` - production->decay displacement (Lxy) for b/tau lifetime tagging.

### C. Physics performance
- `p4(MemberSelector)` - branch four-momentum over {all | stable | charged | visible}.
- `visibleEnergy()` / `invisibleEnergy()` - missing energy from neutrinos/LSP.
- `chargedFraction()`, `emFraction()`, `hadronicFraction()` - for jet response/composition.
- `response(recoObject)` = recoE / branchE; efficiency/fake bookkeeping via matching.
- boundary-crossing kinematics from `Checkpoint`s - for propagation/calibration studies.

### D. Branch <-> Branch relations
- `commonAncestor(other)` - do two branches come from the same top / same Z? (generalizes `lowestCommonAncestor`).
- `merged(other)` - combine two branches (and their hit content) into one (e.g. the two Z-decay legs).
- `deltaR(other)`, `overlap(other)` (shared members/hits) - for splitting/merging studies.

## Cross-cutting principles
- **Substrate vs metric**: the Branch holds structure + hits; matching *definitions*
stay detector-aware and use-case dependent (tracking != calo != timing).
- **Provenance-aware**: every Branch knows its source event (primary vs pile-up).
- **Composable**: branches merge/split; queries compose with the existing
navigation (`ancestors`, `firstCommonAncestor`, `firstAncestorWithPdgId`).
- **Built on what exists**: selection (phase 1-2), navigation (phase 3),
hit index (existing) — Branch is the unifying view, not new infrastructure.

## Resolved decisions
1. **View, recomputed on demand** — the Branch stores no member list and is not
an EDM product.
2. **Derived on the fly** from `Graph` + closure (never persisted). Member-id
lists are cheap to recompute; hit aggregates are not stored on the Branch.
3. **Metric strategy on the Branch** for single-object scores; a free,
cache-holding `BranchMatcher` for batch many-to-many association.
4. **`merged()` unions member sets lazily** (no cached hit aggregates),
consistent with the view model.
5. **Closures include predicate-based stops**, in addition to the fixed
`{Subtree, StableLeaves, DepthN, UntilPdgId}` set.

## Implementation status
Implemented (library level, all unit-tested):
- **Hit layout** (`LogicalGraphHitIndex`): a particle's subgraph hits are a
contiguous, detId-sorted `std::span`, so a `Subtree` branch's hits are
`subgraphHits(root)` with zero gather and are merge-join ready.
- **`truth::Branch`** (`interface/Branch.h`): the view, with closures
`Subtree / StableLeaves / DepthN / UntilPdgId / Predicate`, members/leaves,
p4 / visible / invisible energy, origin (`originWithPdgId`), heavy-flavor
content, pile-up provenance (`bunchCrossing`/`event`/`isSignal`/`isFromPileup`),
and relations (`commonAncestor`, `merged`).
- **`truth::BranchHitAssociator`** (`interface/BranchHitAssociator.h`): the
generic, batch-cached matcher. **Customization point**: any reco object that
exposes `R::truthHits()` returning a range of `truth::RecoHit`
(`{detId, energy, fraction}`) is matchable — the `HasTruthHits<R>` concept. It
caches the inverted `detId -> roots` index once, then `bestBranches(reco)`
merge-joins the object's sorted hits against each candidate's sorted subgraph
span. Metrics: `SharedEnergy` (HGCal-style score) and `SharedHits`.
- **`truth::BranchSelector`** (`interface/BranchSelector.h`): pt/eta/pdgId/charge
+ signal/in-time selection, mirroring TrackingParticleSelector/CaloParticleSelector.

## Remaining (EDProducer wiring)
- Wrap `BranchHitAssociator` in EDProducers that consume real reco collections
(tracks, tracksters/PFclusters, jets) and emit `ticl::AssociationMap`
(`mapWithSharedEnergyAndScore`) products in both directions, mirroring
`AllTracksterToSimTracksterAssociatorsByHitsProducer` and the
TrackingParticle<->reco::Track associator, but with a Branch in place of the
SimTrackster/TrackingParticle.
- A tracker variant keyed on shared `(trackId, EncodedEventId)` SimTrack hits
(the QuickTrackAssociatorByHits metric) for track<->branch matching.
Loading