Belle II Software development
HerwigHepMCFragmentationModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <generators/modules/herwigfragmentation/HerwigHepMCFragmentationModule.h>
10#include <generators/modules/herwigfragmentation/HerwigFragHelper.h>
11
12#include <framework/logging/Logger.h>
13#include <framework/datastore/StoreArray.h>
14#include <framework/datastore/StoreObjPtr.h>
15#include <framework/dataobjects/EventMetaData.h>
16#include <framework/utilities/FileSystem.h>
17#include <mdst/dataobjects/MCParticle.h>
18#include <framework/particledb/EvtGenDatabasePDG.h>
19
20#include <HepMC/IO_GenEvent.h>
21#include <HepMC/GenEvent.h>
22
23#include <EvtGenBase/EvtPDL.hh>
24#include <EvtGenBase/EvtDecayTable.hh>
25
26#include <filesystem>
27#include <fstream>
28#include <sstream>
29#include <iomanip>
30#include <cmath>
31#include <sys/stat.h>
32
33using namespace Belle2;
34
35//-----------------------------------------------------------------
36REG_MODULE(HerwigHepMCFragmentation);
37//-----------------------------------------------------------------
38
39HerwigHepMCFragmentationModule::HerwigHepMCFragmentationModule() : Module()
40{
41 setDescription("Stage 3 of the Herwig multistep pipeline. "
42 "Reads pre-generated HepMC events and KKMC sidecar files, reconstructs "
43 "the full MCParticle truth tree, and applies EvtGen + PHOTOS decays. "
44 "No subprocess calls in event() (thus parallelizable with c_ParallelProcessingCertified).");
45
46 setPropertyFlags(c_ParallelProcessingCertified);
47
48 addParam("WorkDir", m_workDir, "Working directory written by HerwigLHEWriterModule.", std::string(""));
49 addParam("UseEvtGen", m_useEvtGen, "Use EvtGen for particle decays", true);
50 addParam("DecFile", m_decFile, "EvtGen decay file",
51 FileSystem::findFile("decfiles/dec/DECAY_BELLE2.DEC", true));
52 addParam("UserDecFile", m_userDecFile, "EvtGen user decay file path", std::string(""));
53 addParam("CoherentMixing", m_coherentMixing, "Coherent B0-B0bar mixing", true);
54 addParam("KeepTempFiles", m_keepTempFiles,
55 "Keep WorkDir and all pipeline files after terminate(). "
56 "False (default): delete WorkDir recursively after all events are processed. "
57 "True: keep intermediate files.", false);
58}
59
60HerwigHepMCFragmentationModule::~HerwigHepMCFragmentationModule()
61{
62 // Free all loaded HepMC events
63 for (auto& kv : m_hepmcEvents)
64 delete kv.second;
65 m_hepmcEvents.clear();
66}
67
68//-----------------------------------------------------------------
69// Initialize
70//-----------------------------------------------------------------
72{
73 if (m_workDir.empty())
74 B2FATAL("HerwigHepMCFragmentation: WorkDir parameter must be set to the directory "
75 "written by HerwigLHEWriterModule.");
76
77 m_hepmcPath = m_workDir + "/all_events.hepmc";
78
79 struct stat hepmcStat;
80 if (stat(m_hepmcPath.c_str(), &hepmcStat) != 0)
81 B2FATAL("HerwigHepMCFragmentation: HepMC file not found: " << m_hepmcPath);
82
83 if (!loadManifest())
84 B2FATAL("HerwigHepMCFragmentation: failed to read manifest from " << m_workDir);
85
86 if (!loadHepMCEvents())
87 B2FATAL("HerwigHepMCFragmentation: failed to load HepMC events from " << m_hepmcPath);
88
89 // EvtGen setup - called once; interface owns internal state that must persist across event() calls
90 if (m_useEvtGen) {
91 try {
93 B2INFO("HerwigHepMCFragmentation: EvtGen initialized");
94 } catch (std::exception& e) {
95 B2ERROR("EvtGen initialization failed: " << e.what());
96 m_useEvtGen = false;
97 }
98 }
99
100 m_helper = std::make_unique<HerwigFragHelper>();
101 StoreArray<MCParticle> mcParticles;
102 mcParticles.registerInDataStore();
103
104 B2INFO("HerwigHepMCFragmentation: loaded " << m_hepmcEvents.size()
105 << " HepMC events from " << m_hepmcPath);
106}
107
108//-----------------------------------------------------------------
109// Terminate
110//-----------------------------------------------------------------
112{
113 double rate = m_eventCounter > 0 ?
114 100.0 * m_successCounter / m_eventCounter : 0.0;
115 B2RESULT("HerwigHepMCFragmentation: " << m_successCounter << "/" << m_eventCounter
116 << " events (" << std::fixed << std::setprecision(2) << rate << "%)");
117 if (rate < 97.0 && m_eventCounter > 100)
118 B2WARNING("Success rate below 97% threshold");
119
120 if (!m_keepTempFiles && !m_workDir.empty()) {
121 try {
122 std::filesystem::remove_all(m_workDir);
123 B2INFO("HerwigHepMCFragmentation: removed working directory " << m_workDir);
124 } catch (std::exception& e) {
125 B2WARNING("HerwigHepMCFragmentation: could not remove working directory "
126 << m_workDir << ": " << e.what());
127 }
128 }
129}
130
131//-----------------------------------------------------------------
132// Event
133//-----------------------------------------------------------------
135{
137
138 StoreObjPtr<EventMetaData> eventMetaData;
139 const int globalEvtNum = static_cast<int>(eventMetaData->getEvent());
140
141 // Look up the HepMC event for this global event number
142 auto evtIt = m_hepmcEvents.find(globalEvtNum);
143 if (evtIt == m_hepmcEvents.end()) {
144 // Herwig skipped this event
145 setReturnValue(0); return;
146 }
147 HepMC::GenEvent* hepmc = evtIt->second;
148
149 m_mcParticleGraph.clear(); // must clear before each event
150
151 // Reconstruct KKMC truth sub-tree from sidecar
152 std::vector<int> quarkIndices;
153 if (!loadKKMCSidecar(globalEvtNum, quarkIndices)) {
154 B2ERROR("HerwigHepMCFragmentation: failed to load sidecar for event " << globalEvtNum);
155 setReturnValue(0); return;
156 }
157
158 // Map quark graph-indices: quarks are added from sidecar; quarkIndices already populated.
159 // Add Herwig hadrons to the graph (appended after the KKMC particles already in the graph)
160 int nAdded = m_helper->addHepMCToGraph(hepmc, m_mcParticleGraph, quarkIndices);
161 if (nAdded < 0) {
162 B2ERROR("HerwigHepMCFragmentation: addHepMCToGraph failed for event " << globalEvtNum);
163 setReturnValue(0); return;
164 }
165
166 // First generateList: sync graph to StoreArray before EvtGen pass
167 m_mcParticleGraph.generateList("",
171
172 // EvtGen decays + PHOTOS (four-layer filter with PDG remaps for Herwig-specific particles).
173 // applyEvtGenDecays() returns false only on exception - stable/unknown particles are
174 // handled inside the loop via continue and never affect the return value.
175 // A false return means EvtGen crashed mid-decay and the second generateList() did not
176 // run, so the StoreArray is incomplete. Treated as a failed event.
177 if (m_useEvtGen) {
178 if (!applyEvtGenDecays()) {
179 B2ERROR("HerwigHepMCFragmentation: EvtGen decays failed for event "
180 << globalEvtNum << " - event skipped (incomplete MCParticle tree)");
181 setReturnValue(0); return;
182 }
183 }
184
187}
188
189//-----------------------------------------------------------------
190// loadManifest
191//-----------------------------------------------------------------
193{
194 const std::string manifestPath = m_workDir + "/manifest.txt";
195 std::ifstream mf(manifestPath);
196 if (!mf.is_open()) {
197 B2ERROR("HerwigHepMCFragmentation: cannot open " << manifestPath);
198 return false;
199 }
200
201 std::string line;
202 while (std::getline(mf, line)) {
203 if (line.empty() || line[0] == '#') continue;
204 std::istringstream ss(line);
205 std::string key;
206 ss >> key;
207 if (key == "work_dir" || key == "batch_seed" || key == "total_events") continue;
208 // Every other line: event_number sidecar_filename
209 try {
210 int evtNum = std::stoi(key);
211 std::string sidecarFile;
212 ss >> sidecarFile;
213 if (!sidecarFile.empty())
214 m_sidecarMap[evtNum] = m_workDir + "/" + sidecarFile;
215 } catch (...) {
216 B2WARNING("HerwigHepMCFragmentation: skipping manifest line: " << line);
217 }
218 }
219 B2DEBUG(10, "HerwigHepMCFragmentation: manifest loaded, " << m_sidecarMap.size() << " events");
220 return !m_sidecarMap.empty();
221}
222
223//-----------------------------------------------------------------
224// loadHepMCEvents
225//-----------------------------------------------------------------
227{
228 HepMC::IO_GenEvent ascii_in(m_hepmcPath.c_str(), std::ios::in);
229 HepMC::GenEvent* evt;
230 int count = 0;
231 bool signal_id_ok = true;
232 std::vector<HepMC::GenEvent*> ordered; // for fallback sequential mapping
233
234 while ((evt = ascii_in.read_next_event()) != nullptr) {
235 ordered.push_back(evt);
236 const int spid = evt->signal_process_id();
237 if (spid > 0) {
238 m_hepmcEvents[spid] = evt;
239 } else {
240 signal_id_ok = false;
241 }
242 count++;
243 }
244
245 if (count == 0) {
246 B2ERROR("HerwigHepMCFragmentation: no events read from " << m_hepmcPath);
247 return false;
248 }
249
250 // Check if signal_process_id-based mapping is valid (all non-zero, all unique)
251 if (!signal_id_ok || static_cast<int>(m_hepmcEvents.size()) != count) {
252 B2WARNING("HerwigHepMCFragmentation: signal_process_id not unique or missing. "
253 "Herwig 7.2.0 sets signal_process_id=-1 so we use sequential manifest-to-HepMC ordering for this version.");
254
255 // Fall back: map manifest order -> HepMC order
256 m_hepmcEvents.clear();
257 int hepmc_idx = 0;
258 for (const auto& kv : m_sidecarMap) {
259 if (hepmc_idx >= count) break;
260 m_hepmcEvents[kv.first] = ordered[hepmc_idx];
261 hepmc_idx++;
262 }
263 if (hepmc_idx < count) {
264 B2WARNING("HerwigHepMCFragmentation: " << count - hepmc_idx
265 << " HepMC events could not be mapped to manifest entries.");
266 // Free unmapped HepMC events: they are in ordered[] but not in m_hepmcEvents
267 for (; hepmc_idx < count; ++hepmc_idx)
268 delete ordered[hepmc_idx];
269 }
270 } else {
271 B2INFO("HerwigHepMCFragmentation: signal_process_id mapping verified for "
272 << count << " events.");
273 }
274
275 return !m_hepmcEvents.empty();
276}
277
278//-----------------------------------------------------------------
279// loadKKMCSidecar
280//-----------------------------------------------------------------
282 std::vector<int>& quarkIndices)
283{
284 quarkIndices.clear();
285
286 auto it = m_sidecarMap.find(globalEvtNum);
287 if (it == m_sidecarMap.end()) {
288 B2ERROR("HerwigHepMCFragmentation: no sidecar entry for event " << globalEvtNum);
289 return false;
290 }
291
292 std::ifstream sf(it->second);
293 if (!sf.is_open()) {
294 B2ERROR("HerwigHepMCFragmentation: cannot open sidecar " << it->second);
295 return false;
296 }
297
298 std::string line;
299 // Skip comment header
300 std::getline(sf, line); // "# KKMC sidecar event N"
301
302 int n = 0;
303 sf >> n;
304 if (n <= 0) { B2ERROR("HerwigHepMCFragmentation: empty sidecar for event " << globalEvtNum); return false; }
305
306 // Add N KKMC particles to graph in order; record graph indices
307 const int graphBase = static_cast<int>(m_mcParticleGraph.size()); // should be 0 (clear() was called)
308
309 struct SidecarEntry {
310 int pdg, status, motherIdx;
311 double e, px, py, pz, vx, vy, vz, vt, mass;
312 };
313 std::vector<SidecarEntry> entries(n);
314
315 // Read all entries first (needed so we can set parent links after all particles are added)
316 for (int i = 0; i < n; ++i) {
317 auto& e = entries[i];
318 sf >> e.pdg >> e.e >> e.px >> e.py >> e.pz
319 >> e.vx >> e.vy >> e.vz >> e.vt
320 >> e.mass >> e.status >> e.motherIdx;
321 }
322 sf.close();
323
324 for (int i = 0; i < n; ++i) {
325 const auto& entry = entries[i];
326 m_mcParticleGraph.addParticle();
328
329 p.setPDG(entry.pdg);
330 p.setMomentum(ROOT::Math::XYZVector(entry.px, entry.py, entry.pz));
331 p.setMass(entry.mass);
332 // Recalculate E from p and m so the 4-momentum is exactly on-shell
333 const double e2 = entry.px * entry.px + entry.py * entry.py
334 + entry.pz * entry.pz + entry.mass * entry.mass;
335 p.setEnergy(e2 > 0.0 ? std::sqrt(e2) : 0.0);
336 p.setProductionVertex(ROOT::Math::XYZVector(entry.vx, entry.vy, entry.vz));
337 p.setProductionTime(entry.vt);
338 p.setStatus(entry.status);
339
340 // Restore parent link using local sidecar index
341 if (entry.motherIdx >= 0 && entry.motherIdx < n)
342 p.comesFrom(m_mcParticleGraph[graphBase + entry.motherIdx]);
343
344 // Record quark indices for HerwigFragHelper fallback parent linking
345 const int absPdg = std::abs(entry.pdg);
346 if (absPdg >= 1 && absPdg <= 5 && entry.motherIdx >= 0)
347 quarkIndices.push_back(graphBase + i);
348 }
349
350 return true;
351}
352
353//-----------------------------------------------------------------
354// applyEvtGenDecays
355//-----------------------------------------------------------------
357{
358 if (!m_useEvtGen) return true;
359 try {
360 int nDecayed = 0;
361 const size_t nBefore = m_mcParticleGraph.size();
362
363 for (size_t i = 0; i < nBefore; ++i) {
365
366 // Layer 1: skip particles that already have daughters
367 if (gp.getFirstDaughter() != 0) continue;
368
369 // Layer 2a: Herwig 7.2.0 uses PDG 10433 for D_s1(2460); EvtGen calls it D_s1+.
370 // Remove if upgraded Herwig resolved this issue.
371 if (std::abs(gp.getPDG()) == 10433) {
372 const char* name = (gp.getPDG() > 0) ? "D_s1+" : "D_s1-";
373 const auto* part = EvtGenDatabasePDG::Instance()->GetParticle(name);
374 if (part) gp.setPDG(part->PdgCode());
375 }
376
377 // Layer 2b: Herwig uses PDG 10221 for f_0(1370); EvtGen calls it f'_0
378 if (gp.getPDG() == 10221) {
379 const auto* part = EvtGenDatabasePDG::Instance()->GetParticle("f'_0");
380 if (part) gp.setPDG(part->PdgCode());
381 }
382
383 // Layer 3: skip particles absent from evt.pdl
384 const EvtId id = EvtPDL::evtIdFromStdHep(gp.getPDG());
385 if (id.getId() < 0) continue;
386
387 // Layer 4: skip particles with no decay modes in DECAY_BELLE2.DEC or UserDecFile
388 if (EvtDecayTable::getInstance()->getNModes(id) == 0) continue;
389
390 int result = m_evtGenInterface.simulateDecay(m_mcParticleGraph, gp);
391 if (result > 0) nDecayed++;
392 }
393
394 // Second generateList: write EvtGen daughters and PHOTOS photons to StoreArray
395 m_mcParticleGraph.generateList("",
399 return true;
400 } catch (std::exception& e) {
401 B2ERROR("EvtGen decay failed: " << e.what());
402 return false;
403 }
404}
static EvtGenDatabasePDG * Instance()
Instance method that loads the EvtGen table.
static std::string findFile(const std::string &path, bool silent=false)
Search for given file or directory in local or central release directory, and return absolute path if...
std::string m_userDecFile
EvtGen user decay file.
int m_successCounter
Counts successfully processed events.
bool m_coherentMixing
Coherent B0/B0bar mixing in EvtGen.
std::map< int, std::string > m_sidecarMap
Mapping from global event number to sidecar filename (basename only).
virtual void initialize() override
Open and validate HepMC file; load all events and manifest; initialize EvtGen.
virtual void event() override
Load one HepMC event, reconstruct MCParticle truth tree, apply EvtGen decays.
virtual void terminate() override
Log success rate; warn if below 97% threshold.
bool applyEvtGenDecays()
Apply EvtGen filter and write decayed particles to StoreArray.
std::string m_hepmcPath
Resolved path to all_events.hepmc.
std::map< int, HepMC::GenEvent * > m_hepmcEvents
Mapping from global basf2 event number to HepMC event stored in memory.
bool m_useEvtGen
Use EvtGen for particle decays (default true).
std::string m_workDir
Working directory written by HerwigLHEWriterModule (Stage 1).
bool m_keepTempFiles
If false, delete WorkDir and all pipeline files in terminate().
bool loadManifest()
Load the manifest.txt written by HerwigLHEWriterModule::terminate().
bool loadKKMCSidecar(int globalEvtNum, std::vector< int > &quarkIndices)
Add KKMC particles from sidecar file to m_mcParticleGraph.
MCParticleGraph m_mcParticleGraph
Particle graph rebuilt per event.
std::unique_ptr< HerwigFragHelper > m_helper
Two-pass HepMC-to-graph helper.
bool loadHepMCEvents()
Load all HepMC events from all_events.hepmc into m_hepmcEvents map.
EvtGenInterface m_evtGenInterface
Persistent EvtGen interface; setup() called once in initialize().
Class to represent Particle data in graph.
@ c_checkCyclic
Check for cyclic dependencies.
@ c_clearParticles
Clear the particle list before adding the graph.
@ c_setDecayInfo
Set decay time and vertex.
void setPDG(int pdg)
Set PDG code of the particle.
Definition MCParticle.h:324
int getPDG() const
Return PDG code of particle.
Definition MCParticle.h:101
int getFirstDaughter() const
Get 1-based index of first daughter, 0 if no daughters.
Definition MCParticle.h:240
Base class for Modules.
Definition Module.h:72
void setReturnValue(int value)
Sets the return value for this module as integer.
Definition Module.cc:220
bool registerInDataStore(DataStore::EStoreFlags storeFlags=DataStore::c_WriteOut)
Register the object/array in the DataStore.
Accessor to arrays stored in the data store.
Definition StoreArray.h:113
Type-safe access to single objects in the data store.
Definition StoreObjPtr.h:96
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition Module.h:649
Abstract base class for different kinds of events.