Skip to content

Step-2 D*/V0 (KS, Lambda) ntuplizer + 2D pointing constraint#44

Open
davidwalter2 wants to merge 15 commits into
WMass:WmassNanoProd_10_6_26from
davidwalter2:WmassCalib_KsToPiPi_10_6_26
Open

Step-2 D*/V0 (KS, Lambda) ntuplizer + 2D pointing constraint#44
davidwalter2 wants to merge 15 commits into
WMass:WmassNanoProd_10_6_26from
davidwalter2:WmassCalib_KsToPiPi_10_6_26

Conversation

@davidwalter2

Copy link
Copy Markdown

Summary

Adds step-2 ntuplizers for V0/D* alignment-calibration channels and a 2D-transverse pointing constraint to the unified two-track CVH refit. The five commits build on each other:

  1. ffd5c30 — Jpsi and Upsilon cfg after clean up — small config tidy-up of the existing J/psi and Upsilon runners.
  2. 2008e5e — preliminary ntuplizer for D0 with custom track fit (emanca) — adds DstToD0PiCandidateProducer (D* -> D0 pi candidate finder) and a dedicated ResidualGlobalCorrectionMakerTwoTrackKPiG4e class for the asymmetric K/pi pair.
  3. f2c0c7f — Add KS and Lambda step-2 ntuplizers (V0CandidateProducer + CVH refit) — generic V0CandidateProducer (configurable daughter masses, optional both-assignment swap for asymmetric V0s) plus channel cfis and runners for KS -> pi+pi- and Lambda -> p pi-.
  4. 800021d — Unify TwoTrackG4e classes and split V0 candidate cfis per channel — collapses the duplicate TwoTrackG4e/TwoTrackKPiG4e classes into one configurable class (per-daughter masses, respectTrackOrder, optional L1 trigger), saving ~2700 lines. Per-channel cfis (PiPi, ProtonPi, KPi) configure the same C++.
  5. d6c9cf0 — Add optional 2D-transverse pointing constraint to TwoTrackG4e — new optional doPointingConstraint / pointingSigma cfi parameters. Constraint requires the V0 transverse momentum to coincide with the BS->SV flight vector. One scalar chi^2 term, +1 ndof; default off so legacy J/psi/Upsilon/D* runners are unchanged.

Validation (data, KS pointing OFF -> ON):

  • KS: 1985 -> 1949 entries, mass 0.49861 -> 0.49863 GeV, RMS 4.59 -> 4.56 MeV
  • Lambda: 807 -> 766 entries, mass 1.12599 -> 1.12566 GeV, RMS 17.6 -> 17.5 MeV
  • D0: no RMS gain, chi^2 +15 (BS reference too coarse for prompt charm; pointing should be off for D*).

Net diff: +1570 lines / -31 lines across 15 files in Analysis/HitAnalyzer/. No changes outside Analysis/HitAnalyzer/. The parallel magnet-field PR #42 has been intentionally rebased out of this branch; the two efforts are orthogonal.

Test plan

  • Build clean: scram b -j8 succeeds with no warnings on a fresh checkout.
  • Default behaviour for J/psi runner unchanged (entry counts, mass peak position bit-identical to current main).
  • KS / Lambda / D* step-2 runners produce expected entry counts (1985 / 807 / 152 in the smoke test).
  • With doPointingConstraint = True, KS / Lambda mass RMS narrows slightly; convergence stays within 1-2 extra iterations.
  • D* pointing-on regression noted; no changes to default D* config.

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

emanca and others added 5 commits May 2, 2026 10:17
Adds the step-2 chain for the KS->pi+pi- and Lambda0->p pi- alignment
channels, mirroring the existing D*->D0 pi step-2 implementation.

Step-2 reads the per-channel ALCARECO output (ALCARECOTkAlKsToPiPi /
ALCARECOTkAlLambdaToProtonPi), re-pairs the deduplicated daughter tracks,
applies V0Producer-like displaced-vertex cuts, then runs the existing
ResidualGlobalCorrectionMakerTwoTrackKPiG4e CVH refit on the chosen pair.
The ALCARECO format is treated as just "tracks + hits + clusters", with
no assumption that pair structure or V0 candidate info is preserved -- the
candidate finder rebuilds candidates from scratch.

Files:

* plugins/V0CandidateProducer.cc -- analog of DstToD0PiCandidateProducer
  for 2-body V0 decays. Iterates opposite-sign pairs from the input
  TrackCollection, runs KinematicParticleVertexFitter with configurable
  per-daughter masses, applies vertex p-value, mass-window, transverse
  pointing, and Lxy/sigma cuts using a fresh BeamSpot. For asymmetric
  decays (Lambda) tries both (m1, m2) and (m2, m1) assignments and keeps
  the one closer to the nominal V0 mass. Outputs the daughter tracks of
  the highest-pT surviving candidate in pair order, suitable for the
  downstream ntuplizer with respectTrackOrder=True.

* plugins/BuildFile.xml -- adds DataFormats/BeamSpot dependency
  (V0 displaced-vertex cuts need the beamspot; D* candidate finder did
  not require it because the 3-body Delta-mass cut was sufficient).

* python/V0CandidateProducer_cfi.py -- defaults tuned for KS;
  override daughterMass1, expectedV0Mass, mass window, and
  tryBothAssignments for Lambda.

* python/ResidualGlobalCorrectionMakerTwoTrackPiPiG4e_cfi.py
* python/ResidualGlobalCorrectionMakerTwoTrackProtonPiG4e_cfi.py
  -- per-channel cfi clones of the existing CVH ntuplizer C++ class
  (no new C++ -- the class is already mass-parameterised). The KS
  variant sets both daughter masses to the pion mass; the Lambda
  variant sets daughter1 to the proton mass and daughter2 to the
  pion mass, with respectTrackOrder=True.

* test/runGlobalCorRecKsFromAlca.py
* test/runGlobalCorRecLambdaFromAlca.py
  -- runners that load the corresponding cfis, produce a fresh
  offlineBeamSpot from the standard service (BeamSpot is not stored
  in the ALCARECO output), and chain V0CandidateProducer -> CVH
  ntuplizer. Validated on the existing TkAlKsToPiPi_8h.root and
  TkAlLambdaToProtonPi_8h.root files: KS peak at PDG within 0.001 MeV,
  Lambda peak at PDG within 0.5 MeV, both with ~5 MeV resolution.

Note: a follow-up cleanup is planned to generalise
ResidualGlobalCorrectionMakerTwoTrackG4e (the symmetric J/psi version)
to take per-daughter masses via cfi, retire the K-pi-specific clone,
and have one canonical class for all two-body decays. That refactor
is orthogonal to this commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two cleanup steps building on the previous KS/Lambda step-2 commit:

1) Unify ResidualGlobalCorrectionMakerTwoTrackG4e and
   ResidualGlobalCorrectionMakerTwoTrackKPiG4e into a single class.

   The two classes shared >2700 lines of identical code; the K-Pi
   variant was a clone-and-modify with three real additions on top of
   the symmetric J/psi version: per-daughter masses (kaonMass/pionMass),
   a respectTrackOrder flag, and an L1 trigger decision block. All three
   are useful generically (not K-Pi specific), so the right move is to
   keep the more general code and retire the original.

   Concretely, ResidualGlobalCorrectionMakerTwoTrackG4e.cc is replaced
   by the (renamed) K-Pi version with parameter names changed to be
   channel-agnostic:
     kaonMass    -> daughterMass1
     pionMass    -> daughterMass2
     kaonMassErr -> daughterMass1Err
     pionMassErr -> daughterMass2Err
   All new parameters (daughterMass1/2, daughterMass1/2Err, minPairMass,
   maxPairMass, respectTrackOrder, doL1Trigger, l1Results, l1Triggers)
   are made optional with backward-compatible defaults: muon mass for
   the daughters, no pair-mass cut, no track-order constraint, no L1
   filter. So the existing J/psi/Upsilon runner cfgs (which simply
   don't pass any of these) reduce exactly to the previous behaviour.

   ResidualGlobalCorrectionMakerTwoTrackKPiG4e.cc is deleted. The three
   channel-specific cfi clones (KPi for D*, PiPi for KS, ProtonPi for
   Lambda) are updated to instantiate the unified class with the new
   parameter names; their content is otherwise unchanged.

2) Split the generic V0CandidateProducer_cfi into channel-specific
   cfis matching the DstToD0PiCandidateProducer_cfi pattern.

   V0CandidateProducer_cfi.py is replaced by:
     KsToPiPiCandidateProducer_cfi.py        (KS-tuned: both pions, symmetric)
     LambdaToProtonPiCandidateProducer_cfi.py (Lambda-tuned: p+pi, asymmetric)

   The C++ class V0CandidateProducer is unchanged; only the cfi
   wrappers are now per-channel. Runners load the matching cfi.

Validation: all three V0 step-2 channels reproduce identical entry
counts and mass-peak positions before and after the refactor:
  KS     1985 entries, peak at 0.4976 GeV (PDG 0.4976)
  Lambda  807 entries, peak at 1.1162 GeV (PDG 1.1157)
  D*      152 entries, peak at 1.860  GeV (PDG 1.8648)
The legacy J/psi runner cfg loads cleanly under the unified class
(falling back to muon-mass defaults via existsAs<>); a future bit-level
J/psi tree comparison would complete the validation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New cfi parameters (default off, no behaviour change for existing
J/psi/Upsilon/D*/V0 runners):
  doPointingConstraint  - enable the soft chi^2 pointing term
  pointingSigma         - angular pointing resolution in radians (default 1e-3)

The constraint requires the V0 transverse momentum to align with the
flight vector from the beamspot to the secondary vertex. One scalar
chi^2 term per event with sigma_g = pointingSigma * Lxy * |p_xy|, applied
once at the start of the per-iteration constraint block (id == 0 guard);
+1 ndof. Touches vertex-state indices 0..5 (daughter qop/lambda/phi)
and 7..8 (vertex xy); index 6 (d0) and 9 (vertex z) are not coupled.

Validated on KS (1985 -> 1949 entries, mass RMS 4.59 -> 4.56 MeV) and
Lambda (807 -> 766 entries, RMS 17.6 -> 17.5 MeV). D0 chi^2 increases
by +15 with no RMS gain — the BS reference is too coarse for prompt
charm and a PV reference would be needed there.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@davidwalter2 davidwalter2 changed the title Step-2 V0 ntuplizers (KS, Lambda, D*) + 2D pointing constraint Step-2 D*/V0 (KS, Lambda) ntuplizer + 2D pointing constraint May 2, 2026
davidwalter2 and others added 10 commits May 4, 2026 14:28
Adds three optional InputTags to the CVH two-track ntuplizer:
dedxHarmonic2, dedxPixelHarmonic2 and dedxAllHarmonic2. When set, the
producer reads the corresponding ValueMap<float>s projected onto the
ALCARECO selected-track collection (via DeDxValueMapProjector in the
ALCARECO step) and fills new branches Mu{plus,minus}_dedx{,Pixel,All}Harmonic2
in the per-event tree.

The ALCARECO ValueMaps are keyed on the V0DaughterTrackProducer output
collection, which differs from the V0CandidateProducer output the CVH
ntuplizer reads downstream. Lookup is done by matching each fit track's
preserved TrackExtraRef.key() against the source collection (a new
dedxSourceTracks InputTag identifies that source). The joint estimator
read is independently optional via mayConsume so legacy ALCARECO inputs
without that ValueMap fall back to NaN per-track values.

Channel cfis (PiPi, ProtonPi, KPi) point at the matching ALCARECO
ValueMap names by default. Runners are repointed to the multifile
test ALCARECO output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
D0/D* (DstToD0PiCandidateProducer): harmonize the D0, D*, and delta-m
windows with the Stage-1 ALCARECO selector (AlignmentThreeBodyDecayTrackSelector
in ALCARECOTkAlDstToD0Pi_cff.py): D0 mass to PDG +/-50 MeV [1.81483, 1.91483],
D* mass to [1.860, 2.160], delta-m to [0.14243, 0.14843] (+/-3 MeV around
0.14543). The previous looser windows admitted swapped K/pi mass-assignment
configurations that the ALCARECO already rejected.

KS (KsToPiPiCandidateProducer): V0 mass window tightened from +/-100 MeV
to [0.44, 0.56] (PDG +/-60 MeV) to suppress combinatorial background.

Lambda (LambdaToProtonPiCandidateProducer): V0 mass window tightened from
+/-65 MeV to [1.07, 1.16] (-46 MeV / +44 MeV around PDG) to suppress
combinatorial background.

KS and Lambda runner cfgs have their now-redundant override blocks
removed -- they pick up the new tighter cfi defaults.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reverts the iteration to its pre-V0 form `for (auto jtrack = itrack + 1; ...)`
and removes the parallel selection machinery added in 800021d:

  - useFixedPairOrder / firstTrack / secondTrack helpers
  - respectTrackOrder cfi parameter (and the ntuplizer member)
  - minPairMass / maxPairMass raw-mass cut and cfi parameter
  - opposite-sign charge filter (itrack->charge() + jtrack->charge() != 0)
  - now-obsolete `if (jtrack == itrack) continue;` (guaranteed by j > i)

Selection responsibility now lives entirely in the candidate producers
(V0CandidateProducer for KS/Lambda, DstToD0PiCandidateProducer for D0):
the charge requirement, the post-vertex-fit V0/D0 mass window, the
pointing and Lxy-significance cuts, and the choice of which track is
slot 0 vs slot 1 are all enforced upstream. The CVH ntuplizer is now
pure "refit + write tree", with no event-level selection of its own.

Behaviorally unchanged for the V0 channels we currently run on (the 2
tracks per event are already pre-selected and ordered upstream). The
`j > i` form also generalises naturally to >1 candidate per event (a
deferred multi-candidate fix we plan in V0CandidateProducer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…S veto

V0CandidateProducer + DstToD0PiCandidateProducer: skip input tracks with
isLooper() == true at the start of the candidate search (the CVH refit
downstream cannot handle them and was discarding the events anyway).
For DstToD0PiCandidateProducer this is applied to the K and D0-pion
slots only -- the soft pion track is not propagated downstream by the
CVH so its looper status is left as-is.

V0CandidateProducer: replace the single legacy `daughterMassErr` with
two independent parameters `daughterMass1Err`, `daughterMass2Err`
(falling back to `daughterMassErr` and then 1e-6 for back-compat). The
mass-error pairing follows the mass pairing in fitV0(), so for the
Lambda channel's tryBothAssignments=True swap the proton-mass error
stays paired with the proton mass even when applied to the swapped
input track.

V0CandidateProducer: add an optional K_S mass veto (`applyKsVeto`,
`ksVetoMass`, `ksVetoWindow`, `ksVetoPionMass`). When enabled, a
candidate whose two daughter tracks evaluated under the (pi+, pi-)
hypothesis fall within +/- ksVetoWindow of the K_S PDG mass is
rejected before the kinematic fit. Default off so the K_S producer
itself is unaffected; turned on in the Lambda cfi (+/-10 MeV window)
to suppress real K_S contamination at low Lambda pT, where the
wrong-mass-hypothesis K_S mass falls inside the Lambda window.

LambdaToProtonPi cfi: also bump daughter masses to PDG precision
(0.93827208943 / 0.13957039 with their PDG uncertainties).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Geant4ePropagator: add an optional 8th argument `particleNameOverride`
to propagateGenericWithJacobianAltD. When non-empty it is used directly
in the G4ErrorFreeTrajState constructor instead of the propagator's
constructor-bound `theParticleName` (default "mu"). Empty string
preserves the existing muon behaviour for J/psi / Upsilon callers.
Also adds three per-failure-point counters (`plimit`, `ierr`, `maxlen`)
and detailed kinematic prints at each failure exit -- used to diagnose
the Geant4e propagation-failure rate on V0 daughters. The destructor
prints a one-line summary of total calls and per-mode failure counts.

G4ErrorPhysicsListCustom: register K+/-, anti-proton particle
definitions in addition to the existing gamma / e+/- / mu+/- / pi+/-
/ proton. Without these, G4ErrorTrajState::BuildCharge() aborts with
a fatal G4Exception when asked to propagate a kaon / anti-proton (D0
and Lambda-bar daughters).

ResidualGlobalCorrectionMakerTwoTrackG4e: read per-daughter G4 base
particle names from cfi (`daughterParticleName1/2`), construct the
full G4 particle name per track from (base, charge) -- "<base>+"/"-"
for mesons/leptons, "proton"/"anti_proton" for the baryon case -- and
pass it to propagateGenericWithJacobianAltD(). Empty cfi -> empty
override -> propagator falls back to muon (back-compatible with
existing J/psi/Upsilon runners).

V0 channel cfis (PiPi, ProtonPi, KPi): set daughterParticleName1/2
to "pi"/"pi", "proton"/"pi", "kaon"/"pi". Bump daughter masses and
errors to PDG values, and correct the previous massConstraintWidth
values from "lifetime in ms" to "natural width in GeV" (Gamma =
hbar/tau): K_S 7.351e-15, Lambda 2.502e-15, D0 1.605e-12.

V0 runner cfgs (KS, Lambda, D0): override the global Geant4ePropagator
PropagationPtotLimit from the default 0.5 GeV down to 0.05 GeV. The
0.5 GeV threshold was tuned for muon-spectrometer extrapolation but
rejects ~70% of V0-daughter pion / soft-proton tracks (lab |p| can
fall below 0.5 GeV at low Lambda pT or for backward-emitted daughters).
On a 500-event Lambda quickcheck this drops the per-call propagation
failure rate from ~190 to ~70 per 500 events (~64% reduction).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Analysis/HitAnalyzer/{interface,plugins}/ParticleProperties.{h,cc}
with two free functions in namespace ana_hitanalyzer:

  ParticleProperties getParticleProperties(const std::string &base);
       returns {mass, massErr} (GeV) for the given particle base name.
       Recognised: "mu", "pi", "kaon", "proton", "e".

  std::string g4ParticleName(const std::string &base, int charge);
       formats the G4 particle name for the propagator override:
         empty base   -> empty (caller falls back to propagator default)
         "proton"     -> "proton" / "anti_proton"
         everything else -> base + "+" / "-".

All four producers / ntuplizers in HitAnalyzer that previously held
their own copies of these constants now look them up from the single
table:

  V0CandidateProducer        : daughter mass + err from
                               daughterParticleName1/2
  DstToD0PiCandidateProducer : kaon, pion, soft-pion masses hard-coded
                               by name (this channel is K + pi + pi)
  ResidualGlobalCorrectionMakerG4e (single-track):
                               trackMass from trackParticleName, plus
                               per-track G4-name override on every
                               propagator call (mu/pi/kaon/proton).
  ResidualGlobalCorrectionMakerTwoTrackG4e:
                               daughterMass1/2 + err from
                               daughterParticleName1/2.

The base class no longer carries g4ParticleName as a static helper --
it lives in the shared utility instead, so V0CandidateProducer (which
is not in the CVH inheritance hierarchy) can use the same
implementation.

Channel cfis simplified accordingly: removed the redundant numeric
daughterMass1/2 and daughterMass1/2Err (and kaonMass / pionMass /
softPionMass for D*) cms.double() entries -- they are now derived
from the particle name in one place. Updating a particle's PDG mass
becomes a one-line change in ParticleProperties.cc rather than a
scatter of cms.double()s across half a dozen cfis.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stage-1 ALCAReco now persists *Resonances VertexCompositeCandidateCollection
collections for KS, Lambda, J/psi, Upsilon, Z (via the new stage-1
VertexCompositeCandidateRemapper). Switch the CVH refit to consume them
directly: one tree row is emitted per persisted candidate, no
re-pairing of tracks at stage-2.

ResidualGlobalCorrectionMakerBase + ResidualGlobalCorrectionMakerTwoTrackG4e:

- Add an optional `srcCandidates` InputTag (default empty) and an
  inputCandidates_ token in the base class, registered only when the
  label is non-empty.
- At the top of produce(), build a vector of (Track*, Track*) pairs:
  if srcCandidates is set, iterate the persisted candidates and use
  cand.daughter(0/1)->track() refs; otherwise fall back to the legacy
  j>i outer-product over the input TrackCollection.
- Replace the existing nested loop with a single flat loop over the
  pair vector; per-pair body unchanged.
- Tighten the dE/dx parameter-existence check to require non-empty
  InputTag labels (existsAs<>() passes for empty InputTag('') but
  getByToken would then throw ProductNotFound at runtime).
- BuildFile: -Wno-error -Wno-unused-variable to keep pre-existing
  unused-variable warnings from failing the build on full recompile.

cfis:

- New ResidualGlobalCorrectionMakerTwoTrack{JpsiMuMu,UpsilonMuMu,ZMuMu}G4e_cfi
  configurations targeting the persisted *Resonances candidates.
- ResidualGlobalCorrectionMakerTwoTrack{PiPi,ProtonPi,KPi}G4e_cfi: default
  srcCandidates set to ALCARECOTkAl{KsToPiPi,LambdaToProtonPi,DstToD0Pi}Resonances.

Removed V0CandidateProducer.cc and its KsToPiPiCandidateProducer_cfi /
LambdaToProtonPiCandidateProducer_cfi -- all V0 ALCAReco now publishes
the *Resonances candidates, no legacy samples need stage-2 re-pairing.
runGlobalCorRec{Ks,Lambda}FromAlca.py simplified to drop the
candidate-producer step (CVH module reads the candidates from the cfi
default).

Adds Charmonium-flavored runner copies (runCvhJpsiCandidateDriven{,100files}.py,
runGlobalCorRec{Ks,Lambda}From{CharmoniumAlca,Charmonium100files}.py) for
running the new pipeline end-to-end on the Charmonium-derived ALCAReco
that the stage-1 work now produces.

Test plan:
- 1-file Charmonium: 706 J/psi candidates -> 702 entries refit (4 dropped
  by isLooper skip), 0/82329 Geant4e propagation failures, J/psi mass =
  3.093 +/- 0.068 GeV.
- 100-file Charmonium (merged via edmCopyPickMerge): 9-variant scan
  (KS / Lambda / J/psi x nv_np / v_np / v_p) completed with 0 failures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…4e path limit

1. cmsRun crashed in TTree::AutoSave at ~3 k events with fillGrads=true,
   the trace ending in GenericWriteAction inside the tree-metadata
   streamer. SetAutoSave(0) disables the in-job metadata save (the tree
   is still written at Close), eliminating that crash path.

2. Slow scalar baskets never flushed during the run, so peak RSS reached
   3.4 GB after one hour at 100 kB/event. SetAutoFlush(-100 MB) flushes
   all open baskets every 100 MB accumulated, capping peak near 1.5 GB
   even on 800 k-event jobs.

3. Geant4ePropagator's max-path-length cutoff had been tightened from
   10 m to 200 cm for V0 / mixed-particle debugging; reverting so long
   J/psi muon trajectories are no longer truncated at 200 cm.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reads ALCARECOTkAlJpsiMuMu directly (no candidate producer required;
relies on the legacy in-module pair loop when srcCandidates is unset).
Knobs (VarParsing 'analysis', all optional with sensible defaults):
  input        local path or root:// URL of one ALCARECO file
  nEvents      -1 = all
  fillJac      per-track Jacobians (default true)
  fillGrads    per-event gradient + packed Hessian (default false)
  doMassConstraint   apply J/psi mass constraint (default false)
  useIdealGeometry   default true; pass 0 to use real alignment
  goldenJson   optional path; applied at the source as lumisToProcess

Also installs an HLTHighLevel pre-filter using the same J/psi paths the
producer records decisions for, so events that fired none are skipped
before geopro/CVH.

Driven by calibration_studies/slurm/submit.sh via --config and
--cmsrun-args.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s runs

Root cause of the OOM kills observed in production array 5163621
(~35 % SIGSEGV failures with malloc-time crashes when MaxRSS hit the
4 GB cgroup cap):

SetAutoFlush(-100 MB) does NOT mean "flush every 100 MB" in ROOT 6.14.
It converts the byte threshold to a fixed entry count at first fill,
based on the size of the first event. For our tree the first fill is
~80 kB so ROOT recorded fAutoFlush = 1248 entries. Later events have
a much heavier hesspackedv (up to ~150 kB), so 1248 entries already
buffer 100-200 MB of basket data per cluster. With ~100 branches each
holding their open basket, peak basket memory drifts toward ~400 MB
on top of the ~1.5 GB CMSSW baseline -- and the slowest-filling
branches accumulate even more between AutoFlush boundaries.

Fix: SetAutoFlush(200). Predictable 200-entry clusters; per-cluster
basket footprint ~20 MB regardless of per-event size. Validated on a
30 k-event smoke (j5164555):
  fAutoFlush stored as 200 (was 1248)
  MaxRSS 2.25 GB at 30 k events  (was 2.74 GB at 20 k events)
  bounded growth, fits in --mem 4G with comfortable headroom

Wall time slightly improves (~0.097 s/event vs 0.13 s/event) because
small clusters stall less on flush.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants