10Herwig multistep pipeline API for basf2.
12Provides add_herwig_continuum_generator() for e+e- -> qqbar continuum event generation
13using a 3-stage batch architecture (KKMC + Herwig + EvtGen).
16 from herwig import add_herwig_continuum_generator
17 add_herwig_continuum_generator(path, finalstate='ccbar', nevents=5000)
25from b2test_utils
import run_in_subprocess
28def _run_herwig_batch(work_dir, template_path, lhe_path, seed, nevents,
29 tune_content, shower_mode, herwig_path='Herwig'):
30 """Run Herwig for all N events in a single Herwig subprocess invocation.
32 Reads herwig_fragmentation.in, substitutes template variables, and writes batch.in.
34 runname =
'HerwigFrag_batch'
35 hepmc_file = os.path.join(work_dir,
'all_events.hepmc')
36 cache_file = os.path.join(work_dir,
'all_events.lhe.cache')
37 in_file = os.path.join(work_dir,
'batch.in')
39 with open(template_path)
as f:
43 'off':
'set LHEHandler:CascadeHandler NULL',
44 'minimal':
'set ShowerHandler:MaxTry 1',
45 'full':
'# Full shower mode (default)',
48 content = content.replace(
'{lhe_file}', lhe_path)
49 content = content.replace(
'{cache_file}', cache_file)
50 content = content.replace(
'{seed}', str(seed))
51 content = content.replace(
'{nevents}', str(nevents))
52 content = content.replace(
'{print_event}',
'0')
53 content = content.replace(
'{output_file}', hepmc_file)
54 content = content.replace(
'{runname}', runname)
55 content = content.replace(
'{shower_config}', shower_config)
56 content = content.replace(
'{tune_settings}', tune_content)
58 with open(in_file,
'w')
as f:
61 log_file = os.path.join(work_dir,
'herwig_batch.log')
62 with open(log_file,
'w')
as log:
63 subprocess.run([herwig_path,
'read', in_file],
64 cwd=work_dir, stdout=log, stderr=log)
67 if not os.path.exists(hepmc_file)
or os.path.getsize(hepmc_file) == 0:
69 f
'Herwig batch run produced no HepMC output. See {log_file}')
74def _read_batch_seed(work_dir):
75 """Read the batch seed from manifest.txt written by HerwigLHEWriterModule."""
76 manifest_path = os.path.join(work_dir,
'manifest.txt')
77 with open(manifest_path)
as f:
80 if line.startswith(
'batch_seed '):
81 return int(line.split()[1])
82 raise RuntimeError(f
'batch_seed not found in {manifest_path}')
85def _stage1_worker(finalstate, work_dir, seed, nevents, stage1_exp, stage1_run,
86 eventType, shower_scale, beamparametersLabel):
87 """Run KKMC + HerwigLHEWriter in a forked subprocess for Stage 1.
89 Global tags and BeamParameters are inherited from the parent process unless
90 beamparametersLabel is explicitly set, in which case add_beamparameters() overrides the DB.
92 b2.set_random_seed(seed)
94 'uubar':
'data/generators/kkmc/uubar_nohadronization.input.dat',
95 'ddbar':
'data/generators/kkmc/ddbar_nohadronization.input.dat',
96 'ssbar':
'data/generators/kkmc/ssbar_nohadronization.input.dat',
97 'ccbar':
'data/generators/kkmc/ccbar_nohadronization.input.dat',
99 path = b2.create_path()
100 if beamparametersLabel
is not None:
101 from beamparameters
import add_beamparameters
102 add_beamparameters(path, beamparametersLabel)
103 path.add_module(
'EventInfoSetter',
104 expList=stage1_exp, runList=stage1_run, evtNumList=[nevents])
105 path.add_module(
'KKGenInput',
106 tauinputFile=b2.find_file(kkmc_input_files[finalstate]),
107 KKdefaultFile=b2.find_file(
'data/generators/kkmc/KK2f_defaults.dat'),
108 taudecaytableFile=
'',
109 kkmcoutputfilename=os.path.join(work_dir, f
'kkmc_{finalstate}.txt'),
111 path.add_module(
'HerwigLHEWriter',
113 ShowerScale=shower_scale)
117def add_herwig_continuum_generator(path, finalstate, nevents=None, userdecfile='', *,
118 herwigParameterFile='',
123 beamparametersLabel=None,
124 keep_temp_files=None,
126 skip_on_failure=True,
129 Three-stage Herwig continuum generator (multistep pipeline).
131 Stage 1 (runs in a forked subprocess): KKMC generates N quark pairs,
132 HerwigLHEWriterModule writes all_events.lhe + per-event KKMC sidecars + manifest.txt.
134 Stage 2 (runs via subprocess): one 'Herwig read batch.in' call
135 processes all N LHE events in one Herwig invocation and writes all_events.hepmc.
137 Stage 3 (added to path, zero subprocess calls per event): HerwigHepMCFragmentationModule
138 reads all_events.hepmc and sidecar files, reconstructs MCParticles + truth tree,
139 and applies EvtGen. Supports multiprocessing with basf2 -p K.
142 path (basf2.Path): path for Stage 3 module
143 finalstate (str): uubar, ddbar, ssbar, ccbar
144 nevents (int or None): number of events for Stages 1+2.
145 Default None: auto-derived from the CLI -n/--events flag via
146 Belle2.Environment.Instance().getNumberEventsOverride().
147 Pass an explicit integer for programmatic use (no CLI -n flag).
148 B2FATAL if both are absent or zero.
149 userdecfile (str): EvtGen user decfile; '' means no user decfile
150 herwigParameterFile (str): Herwig tune .dat file; '' uses herwig_belle2.dat
151 work_dir (str): working directory path; None = auto-created via mkdtemp
152 seed (int or None): Seed for Stage 1 (KKMC) and Stage 2 (Herwig batch).
153 Default None: automatically derived from the current basf2 random seed via
154 SHA-256, so a single b2.set_random_seed() call
155 at the top of the steering script seeds all three stages.
156 Pass an explicit integer to override the auto-derived seed.
157 expList (list[int]): experiment number for the Stage 1 EventInfoSetter.
158 Default None -> [0]. The standard globaltag provides Y(4S) BeamParameters
159 for exp=0/run=0, so the default gives Y(4S) generation without any
160 additional setup. The expList must match the steering script's EventInfoSetter
161 to resolve the beam parameters correctly.
162 runList (list[int]): run number for the Stage 1 EventInfoSetter.
163 Default None -> [0]. Same rule as expList; the runList must match the steering script's EventInfoSetter
164 to resolve the beam parameters correctly.
165 beamparametersLabel (str or None): hardcode a beam-energy preset in Stage 1
166 (e.g. 'Y1S', 'Y4S', 'Y4S-off', 'Y5S') via add_beamparameters().
167 Caveat: add_beamparameters() is considered deprecated,
168 e.g., it places the IP at (0, 0, 0).
169 Default None: KKGenInput reads the BeamParameters from the DB.
170 keep_temp_files (bool or None): controls whether WorkDir and all pipeline
171 files (LHE, HepMC, sidecars, manifest, Herwig log) are deleted after
172 the run completes. Deletion is performed by HerwigHepMCFragmentationModule
173 in terminate(), after all events are processed and sidecars are no longer
175 Default None (auto-derive):
176 work_dir=None (auto mkdtemp) -> keep_temp_files resolves to False
177 (temp dir is created for this run only; deleted when done).
178 work_dir='<explicit path>' -> keep_temp_files resolves to True
179 (set keep_temp_files to False explicitly to override).
180 shower_scale (float): parton shower scale ceiling in GeV, written as
181 SCALUP in each LHE <event> block and as the nominal beam energy in
182 the LHE <init> block. Per-event SCALUP = min(shower_scale, sqrt(M2)),
183 where M2 is the Lorentz-invariant mass squared of the quark pair.
184 Note: shower_scale >= sqrt(s) is recommended to ensure SCALUP is never
185 capped below the hard-process scale; violating this truncates the parton
186 shower for events where the quark pair mass exceeds shower_scale.
187 However, shower_scale must not be set below sqrt(s)/2 as the LHE <init> block beam-energy
188 would fall below the actual per-event beam energy, causing Herwig to reject events
189 (empty HepMC file and a RuntimeError in Stage 2).
190 Default 11.0 GeV satisfies up to Y(5S).
191 skip_on_failure (bool): Default True, skip events where Herwig failed
192 eventType (str): event type label, KKMC metadata
195 from ROOT
import Belle2
as _Belle2
196 nevents = _Belle2.Environment.Instance().getNumberEventsOverride()
199 'add_herwig_continuum_generator: nevents must be > 0. '
200 'Use -n N on the CLI or pass nevents=N explicitly.'
209 seed = int(hashlib.sha256(b2.get_random_seed().encode()).hexdigest(), 16) % 2000000000 + 1
213 user_specified_work_dir = work_dir
is not None
215 work_dir = tempfile.mkdtemp(prefix=
'herwig_pipeline_')
217 os.makedirs(work_dir, exist_ok=
True)
221 if keep_temp_files
is None:
222 keep_temp_files = user_specified_work_dir
224 b2.B2INFO(f
'add_herwig_continuum_generator: work_dir = {work_dir}')
227 _kkmc_input_files = {
228 'uubar':
'data/generators/kkmc/uubar_nohadronization.input.dat',
229 'ddbar':
'data/generators/kkmc/ddbar_nohadronization.input.dat',
230 'ssbar':
'data/generators/kkmc/ssbar_nohadronization.input.dat',
231 'ccbar':
'data/generators/kkmc/ccbar_nohadronization.input.dat',
233 if finalstate
not in _kkmc_input_files:
234 b2.B2FATAL(f
'add_herwig_continuum_generator: finalstate must be uubar/ddbar/ssbar/ccbar, got {finalstate}')
237 herwig_config = b2.find_file(
'data/generators/modules/herwigfragmentation/herwig_belle2.dat')
238 if herwigParameterFile:
239 herwig_config = herwigParameterFile
240 decay_file = b2.find_file(
'decfiles/dec/DECAY_BELLE2.DEC')
243 b2.B2INFO(f
'add_herwig_continuum_generator: using custom userdecfile {userdecfile}')
244 decay_user = userdecfile
249 b2.B2INFO(f
'[Stage 1] Running KKMC to generate {nevents} {finalstate} quark pairs ...')
251 stage1_exp = expList
if expList
is not None else [0]
252 stage1_run = runList
if runList
is not None else [0]
254 exitcode = run_in_subprocess(
255 target=_stage1_worker,
256 finalstate=finalstate,
260 stage1_exp=stage1_exp,
261 stage1_run=stage1_run,
263 shower_scale=shower_scale,
264 beamparametersLabel=beamparametersLabel,
268 f
'[Stage 1] HerwigLHEWriter subprocess failed (exit {exitcode})')
270 manifest_path = os.path.join(work_dir,
'manifest.txt')
271 if not os.path.exists(manifest_path):
272 raise RuntimeError(f
'[Stage 1] manifest.txt not created in {work_dir}')
273 b2.B2INFO(
'[Stage 1] Complete. manifest.txt written.')
278 b2.B2INFO(f
'[Stage 2] Running Herwig batch read for {nevents} events ...')
280 template = b2.find_file(
281 'data/generators/modules/herwigfragmentation/herwig_fragmentation.in')
282 batch_seed = _read_batch_seed(work_dir)
283 with open(herwig_config)
as f:
284 tune_content = f.read()
285 lhe_path = os.path.join(work_dir,
'all_events.lhe')
287 _run_herwig_batch(work_dir, template, lhe_path,
288 seed=batch_seed, nevents=nevents,
289 tune_content=tune_content, shower_mode=
'full')
291 hepmc_path = os.path.join(work_dir,
'all_events.hepmc')
292 b2.B2INFO(f
'[Stage 2] Complete. HepMC written to {hepmc_path}')
297 fragmentation = path.add_module(
298 'HerwigHepMCFragmentation',
302 UserDecFile=decay_user,
304 KeepTempFiles=keep_temp_files,
307 empty = b2.create_path()
308 fragmentation.if_value(
'<1', empty)