Skip to content

Backgrounds in ePIC simulation ​

How signal events get mixed with the official ePIC background into 2 µs timeframes, what gets called when, and what flags actually matter.

TL;DR

generator  →  abconv (afterburner)  →  SignalBackgroundMerger  →  npsim  →  eicrecon
   (rest frame)     (beam effects        (Poisson into 2 µs       (with status-
                     applied once,        timeframes, every         offset flags)
                     cached on XrootD)    source tagged with a
                                          generator-status offset)

The big picture ​

ePIC's background-mixed simulations are built in three independent stages.

  1. Afterburner — Applies crossing angle, beam effects.

  2. SignalBackgroundMerger — Pulls one already-afterburned signal file and background files (four or five with different backgrounds), and Poisson-samples them into 2 µs timeframes. Each background source's particles get a fixed generator-status offset added so downstream code can tell what came from where.

  3. npsim — runs Geant4 on the merged HepMC3. Needs special flags (compared to usual EPIC runs) to be told that the shifted status codes (2001, 3001, 4001, 5001, …) also mean "stable, propagate" / "decayed, skip", otherwise it drops them silently.

The merger ​

Repo: eic/HEPMC_Merger · CLI binary SignalBackgroundMerger · Spack package: hepmcmerger

For each 2 µs timeframe (default 2000 ns) the merger:

  • places exactly one signal event (because the campaigns set --signalFreq 0, which is the merger's "guaranteed one signal per slice" mode),
  • draws each background source's event count from a Poisson with mean freq × slice_length,
  • distributes particles uniformly in time inside the slice,
  • and adds the source's status value to every MCParticle.generatorStatus it touches.

The current campaign convention for status offsets is:

SourceOffsetStable codeDecay code
signal012
synrad200020012002
ebrems300030013002
ecoulomb400040014002
etouschek500050015002
p-beam-gas600060016002

(See eic/eic.github.io / background_mixed_samples for the official conventions and worked examples with timeframe diagrams. Note: as of 2026-09 that page lists etouschek = 4000 / ecoulomb = 5000 — swapped relative to the config_data/*.json cocktails actually used by the campaigns, which assign coulomb = 4000 / touschek = 5000 as in the table above. The JSONs are the ground truth for what ran.)

Where the cocktail recipes live ​

Repo: eic/simulation_campaign_datasets

config_data/*.json — one cocktail per beam-energy / vacuum-condition combination. Each file is a list of entries like:

json
[
  {"file": "root://.../synrad_18x275_run001....hepmc3.tree.root",
   "freq": 3324000, "skip": 0.0, "status": 2000},
  {"file": "root://.../GETaLM..._ElectronBeamGas_18GeV_….hepmc3.tree.root",
   "freq": 316.94,  "skip": 0.0, "status": 3000},
  {"file": "root://.../electron_coulomb_18x275_….hepmc3.tree.root",
   "freq": 0.86,    "skip": 0.0, "status": 4000},
  {"file": "root://.../electron_touschek_18x275_….hepmc3.tree.root",
   "freq": 0.55,    "skip": 0.0, "status": 5000}
]

Field meanings:

FieldMeaning
fileXrootD URL of an already-prepared background HepMC3.
freqPoisson rate in kHz (so 3 324 000 kHz = 3.3 GHz for synrad).
skipNumber of events to skip before sampling. 0.0 in the current JSONs — run.sh ignores it and computes its own per-job skip (rate-scaled by task index plus a seed-driven random offset) so parallel jobs read decorrelated windows of the same source.
statusGenerator-status offset added to every particle from this source.

Merger command (what the campaign actually runs) ​

bash
SignalBackgroundMerger \
    --rngSeed     <per-job seed> \
    --nSlices     <events_per_task> \
    --signalSkip  <events already consumed by earlier tasks> \
    --signalFile  <afterburned signal>.hepmc3.tree.root \
    --signalFreq  0 \
    --signalStatus 0 \
    --intWindow   2000 \
    --bgFile <synrad_url>   3324000 <skip> 2000 \
    --bgFile <egas_url>      316.94 <skip> 3000 \
    --bgFile <coulomb_url>     0.86 <skip> 4000 \
    --bgFile <touschek_url>    0.55 <skip> 5000 \
    --outputFile  merged.hepmc3.tree.root

(--bgFile takes <file> <freq kHz> <n-events-to-skip> <status offset>; the per-source <skip> is computed by run.sh per job, see above. Cocktails with hadron beam gas add a fifth --bgFile ... 6000 line.)

--signalFreq 0 is the special "one signal per slice" mode. Setting it to a nonzero kHz value would make signals Poisson-sampled too, which is the right thing for low-rate processes like SIDIS-pythia6 where you may want events that are pure background.

There is a second mixer

eic/TimeframeBuilder is the newer mixer (evaluated in detector_benchmarks). It can mix in pre-simulated edm4hep files too, supports beam-attachment / bunch-crossing discretisation / Gaussian time spread, and is reviewed via CI capybara reports. Production campaigns still use SignalBackgroundMerger, so that's what this pipeline calls.

Stage 3 — npsim with status-offset flags ​

DD4hep / npsim by default treats only generatorStatus == 1 particles as Geant4 primaries and == 2 as decayed-intermediate. The merger has just created particles with statuses like 2001 or 3002 — unknown numbers, silently dropped.

The two flags that fix this:

bash
npsim ... \
    --hepmc3.useHepMC3                  true \
    --runType                           batch \
    --physics.alternativeStableStatuses "1 2001 3001 4001 5001 6001" \
    --physics.alternativeDecayStatuses  "2 2002 3002 4002 5002 6002" \
    --inputFiles                        merged.hepmc3.tree.root \
    --outputFile                        sim.edm4hep.root

The campaign's run.sh builds these lists dynamically from the cocktail: signalStatus+1/+2 plus status+1/+2 for every --bgFile source. So a four-source (vacuum) cocktail yields "1 2001 3001 4001 5001", and the hgas cocktails with proton beam gas add 6001 / 6002 as shown above.

  1. The lists fully replace, not augment. Forgetting 1 in the stable list = signal events stop reaching Geant4.
  2. Don't forget --hepmc3.useHepMC3 true. Without it npsim falls back to the older HepMC2 reader, which handles .hepmc3.tree.root input differently and may not honour the alternative status lists at all.
  3. --runType batch, not run. run is for steering/gun input; batch is the right mode for --inputFiles.

The defining file in DD4hep is DDG4/python/DDSim/Helper/Physics.py — that's where _alternativeStableStatuses / _alternativeDecayStatuses live, and they are exposed verbatim on the npsim CLI.

If you write a steering file instead of using CLI flags:

python
from DDSim.DD4hepSimulation import DD4hepSimulation
SIM = DD4hepSimulation()
SIM.physics.alternativeStableStatuses = {1, 2001, 3001, 4001, 5001}
SIM.physics.alternativeDecayStatuses  = {2, 2002, 3002, 4002, 5002}
SIM.hepmc3.useHepMC3 = True

Where the campaign code lives (for cross-reference) ​

WhatRepo
Afterburner (abconv)eic/afterburner
Signal-background mergereic/HEPMC_Merger
Newer timeframe mixereic/TimeframeBuilder
Per-job campaign driver (run.sh)eic/simulation_campaign_hepmc3
Condor submitter, BG-fetch glueeic/job_submission_condor — archived 2026-08; eic/job_submission_slurm is the active submitter repo
Cocktail JSONs + CSV chunk manifestseic/simulation_campaign_datasets
ePIC production docs (release tags, paths)eic/epic-prod
Official background-mixed sample docseic/eic.github.io / background_mixed_samples
Spack package for the mergerspack_repo/eic/packages/hepmcmerger/package.py in eic/eic-spack
Background-mixed Rucio metadata (is_background_mixed)scripts/register_to_rucio.py in eic/simulation_campaign_hepmc3

XrootD / Rucio access ​

Outputs of the campaign land in xroots://dtn2201.jlab.org//eic/eic2/EPIC/{RECO,FULL,LOG}/<release>/<detector>/... and are registered to Rucio under account eicprod with a metadata schema that includes is_background_mixed: true|false. The update-data-md skill in this repo queries Rucio with that flag to rebuild docs/data.md.

Mixed simulation outputs from official campaigns can also be browsed directly:

root://dtn-eic.jlab.org//volatile/eic/EPIC/FULL/<release>/epic_craterlake/Bkg_1SignalPer2usFrame/...
root://dtn-eic.jlab.org//volatile/eic/EPIC/RECO/<release>/epic_craterlake/Bkg_1SignalPer2usFrame/...

(Increment the <release> tag — e.g. 25.06.1 — to find the latest campaign. RECO is preserved across campaigns; FULL is not always.)

Released under the MIT License.