Belle II Software development
HerwigLHEWriterModule.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/HerwigLHEWriterModule.h>
10
11#include <framework/logging/Logger.h>
12#include <framework/datastore/StoreArray.h>
13#include <framework/datastore/StoreObjPtr.h>
14#include <framework/dataobjects/EventMetaData.h>
15
16#include <fstream>
17#include <sstream>
18#include <iomanip>
19#include <cstdlib>
20#include <sys/stat.h>
21#include <unistd.h>
22#include <cmath>
23
24#include <TRandom.h> // gRandom
25
26using namespace Belle2;
27
28//-----------------------------------------------------------------
29REG_MODULE(HerwigLHEWriter);
30//-----------------------------------------------------------------
31
32HerwigLHEWriterModule::HerwigLHEWriterModule() : Module()
33{
34 setDescription("Stage 1 of the Herwig multistep pipeline. "
35 "Collects KKMC MCParticles (e+, e-, virtual photon/Z0, q, qbar) into a single "
36 "multi-event LHE file and writes per-event KKMC sidecar files. ");
37 // shared LHE stream must be written in event order
38
39 addParam("WorkDir", m_workDir, "Working directory for LHE and sidecar files; auto-created if empty.",
40 std::string(""));
41 addParam("ShowerScale", m_showerScale, "Parton shower scale ceiling in GeV (Herwig's SCALUP upper bound)", 11.0);
42}
43
44HerwigLHEWriterModule::~HerwigLHEWriterModule() {}
45
46//-----------------------------------------------------------------
48{
49 // Create working directory
50 if (m_workDir.empty()) {
51 char tmpl[] = "/tmp/herwig_batch_XXXXXX";
52 char* dir = mkdtemp(tmpl);
53 if (!dir) B2FATAL("HerwigLHEWriter: could not create temp directory");
54 m_workingDir = std::string(dir);
55 } else {
57 mkdir(m_workingDir.c_str(), 0755);
58 }
59 B2INFO("HerwigLHEWriter: working directory = " << m_workingDir);
60
61 // Draw batch seed from basf2 RNG - ties Herwig's batch seed to b2.set_random_seed()
62 m_batchSeed = gRandom->Integer(2000000000u) + 1u;
63 B2INFO("HerwigLHEWriter: batch seed = " << m_batchSeed);
64
65 // Open all_events.lhe and write LHEF header + init block
66 // Use m_showerScale as the nominal init-block beam energy so that per-event
67 // lab-frame beam energies (which vary due to ISR boost) never exceed it.
68 const double beamE = m_showerScale;
69 m_lheStream.open(m_workingDir + "/all_events.lhe");
70 if (!m_lheStream.is_open())
71 B2FATAL("HerwigLHEWriter: could not open " << m_workingDir << "/all_events.lhe");
72
73 m_lheStream << "<LesHouchesEvents version=\"1.0\">\n<header></header>\n";
74 m_lheStream << "<init>\n 11 -11 " << std::scientific << std::setprecision(8)
75 << beamE << " " << beamE
76 << " 0 0 -1 -1 3 1\n"
77 << " 1.0000000000e+00 0.0000000000e+00 1.0000000000e+00 1\n</init>\n";
78
79 StoreArray<MCParticle> mcParticles;
80 mcParticles.isRequired();
81}
82
83//-----------------------------------------------------------------
85{
86 // Close the LHE file
87 if (m_lheStream.is_open()) {
88 m_lheStream << "</LesHouchesEvents>\n";
89 m_lheStream.close();
90 }
91
92 // Write manifest.txt: batch seed, working directory, and per-event sidecar index used by Stage 3.
93 // Written once in terminate(): absent = Stage 1 aborted mid-run; present = all N events complete.
94 const std::string manifestPath = m_workingDir + "/manifest.txt";
95 std::ofstream mf(manifestPath);
96 if (!mf.is_open()) {
97 B2ERROR("HerwigLHEWriter: could not write manifest to " << manifestPath);
98 return;
99 }
100 mf << "# HerwigLHEWriter manifest\n";
101 mf << "work_dir " << m_workingDir << "\n";
102 mf << "batch_seed " << m_batchSeed << "\n";
103 mf << "total_events " << m_eventCounter << "\n";
104 for (const auto& kv : m_manifest)
105 mf << kv.first << " " << kv.second.sidecarFile << "\n";
106 mf.close();
107 B2INFO("HerwigLHEWriter: wrote manifest to " << manifestPath);
108 B2RESULT("HerwigLHEWriter: " << m_eventCounter << " events written to "
109 << m_workingDir << "/all_events.lhe");
110}
111
112//-----------------------------------------------------------------
114{
116
117 StoreObjPtr<EventMetaData> eventMetaData;
118 const int globalEvtNum = static_cast<int>(eventMetaData->getEvent());
119
120 StoreArray<MCParticle> mcParticles;
121
122 // Find quarks (|PDG| 1-5, no daughters); in contrast to pythia, the vphoton not needed for LHE (Herwig uses beam structure)
123 std::vector<MCParticle*> quarks;
124 for (int i = 0; i < mcParticles.getEntries(); ++i) {
125 MCParticle* mc = mcParticles[i];
126 int absPdg = std::abs(mc->getPDG());
127 if (absPdg >= 1 && absPdg <= 5 && mc->getDaughters().empty())
128 quarks.push_back(mc);
129 }
130
131 if (quarks.size() != 2) {
132 B2WARNING("HerwigLHEWriter: event " << globalEvtNum
133 << ": expected 2 quarks, got " << quarks.size() << " -- skipping LHE write");
134 return;
135 }
136
137 if (!writeLHEEventBlock(quarks)) {
138 B2ERROR("HerwigLHEWriter: failed to write LHE event block for event " << globalEvtNum);
139 return;
140 }
141
142 if (!writeKKMCSidecar(globalEvtNum)) {
143 B2ERROR("HerwigLHEWriter: failed to write KKMC sidecar for event " << globalEvtNum);
144 return;
145 }
146
147 // Record in manifest
148 std::ostringstream sidecarName;
149 sidecarName << "kkmc_evt" << std::setw(6) << std::setfill('0') << globalEvtNum << ".sidecar";
150 m_manifest[globalEvtNum].sidecarFile = sidecarName.str();
151}
152
153//-----------------------------------------------------------------
154bool HerwigLHEWriterModule::writeLHEEventBlock(const std::vector<MCParticle*>& quarks)
155{
156 if (quarks.size() != 2) return false;
157 if (!m_lheStream.is_open()) return false;
158
159 MCParticle* quark = nullptr;
160 MCParticle* antiquark = nullptr;
161 for (auto* q : quarks) {
162 if (q->getPDG() > 0) quark = q;
163 else antiquark = q;
164 }
165 if (!quark || !antiquark) return false;
166
167 const double sE = quark->getEnergy() + antiquark->getEnergy();
168 const double sPx = quark->getMomentum().X() + antiquark->getMomentum().X();
169 const double sPy = quark->getMomentum().Y() + antiquark->getMomentum().Y();
170 const double sPz = quark->getMomentum().Z() + antiquark->getMomentum().Z();
171 const double M2 = sE * sE - sPx * sPx - sPy * sPy - sPz * sPz;
172 const double scalup = (M2 > 0.0) ?
173 std::min(m_showerScale, std::sqrt(M2)) : m_showerScale;
174 const double beamEnergy = sE / 2.0;
175
176 // IDPRUP = 1 to match the single process declared in the <init> block (LPRUP=1).
177 // Herwig 7.2 rejects events with IDPRUP that don't match a declared process.
178 // Stage 3 uses sequential manifest ordering (index-based) for event mapping.
179 m_lheStream << "<event>\n 4 1 "
180 << std::scientific << std::setprecision(8)
181 << "1.00000000e+00 " << scalup << " 7.81653700e-03 1.18000000e-01\n";
182
183 m_lheStream << " 11 -1 0 0 0 0 " << std::showpos << 0.0
184 << " " << 0.0 << " " << beamEnergy << " "
185 << std::noshowpos << beamEnergy << " 0.0 0.0 -1.0\n";
186 m_lheStream << " -11 -1 0 0 0 0 " << std::showpos << 0.0
187 << " " << 0.0 << " " << -beamEnergy << " "
188 << std::noshowpos << beamEnergy << " 0.0 0.0 1.0\n";
189 {
190 double px = quark->getMomentum().X(), py = quark->getMomentum().Y(),
191 pz = quark->getMomentum().Z(), e = quark->getEnergy(), m = quark->getMass();
192 m_lheStream << " " << quark->getPDG() << " 1 1 2 101 0 "
193 << std::showpos << px << " " << py << " " << pz << " "
194 << std::noshowpos << e << " " << m << " 0.0 9.0\n";
195 }
196 {
197 double px = antiquark->getMomentum().X(), py = antiquark->getMomentum().Y(),
198 pz = antiquark->getMomentum().Z(), e = antiquark->getEnergy(), m = antiquark->getMass();
199 m_lheStream << " " << antiquark->getPDG() << " 1 1 2 0 101 "
200 << std::showpos << px << " " << py << " " << pz << " "
201 << std::noshowpos << e << " " << m << " 0.0 9.0\n";
202 }
203 m_lheStream << "</event>\n";
204 return !m_lheStream.fail();
205}
206
207//-----------------------------------------------------------------
209{
210 std::ostringstream fname;
211 fname << m_workingDir << "/kkmc_evt" << std::setw(6) << std::setfill('0') << globalEvtNum << ".sidecar";
212 std::ofstream sf(fname.str());
213 if (!sf.is_open()) return false;
214
215 StoreArray<MCParticle> mcParticles;
216 const int n = mcParticles.getEntries();
217
218 sf << "# KKMC sidecar event " << globalEvtNum << "\n";
219 sf << n << "\n";
220 sf << std::scientific << std::setprecision(10);
221 for (int i = 0; i < n; ++i) {
222 MCParticle* mc = mcParticles[i];
223 int motherIdx = -1;
224 const MCParticle* mother = mc->getMother();
225 if (mother)
226 motherIdx = mother->getArrayIndex();
227 const ROOT::Math::XYZVector& mom = mc->getMomentum();
228 const ROOT::Math::XYZVector& vtx = mc->getProductionVertex();
229 sf << mc->getPDG() << " "
230 << mc->getEnergy() << " "
231 << mom.X() << " "
232 << mom.Y() << " "
233 << mom.Z() << " "
234 << vtx.X() << " "
235 << vtx.Y() << " "
236 << vtx.Z() << " "
237 << mc->getProductionTime() << " "
238 << mc->getMass() << " "
239 << mc->getStatus() << " "
240 << motherIdx << "\n";
241 }
242 sf.close();
243 return !sf.fail();
244}
unsigned int m_batchSeed
Batch seed drawn from gRandom in initialize().
std::map< int, EventRecord > m_manifest
global_event_number -> EventRecord
std::string m_workingDir
Resolved working directory path.
virtual void initialize() override
Register required MCParticle StoreArray.
virtual void event() override
Accumulate one event into an LHE file and write its KKMC sidecar.
virtual void terminate() override
Close LHE file and write manifest.txt.
std::ofstream m_lheStream
Output stream to all_events.lhe (open from initialize to terminate).
std::string m_workDir
User-specified working dir; auto-created via mkdtemp if empty.
int m_eventCounter
Counts event() calls.
bool writeLHEEventBlock(const std::vector< MCParticle * > &quarks)
Write one <event>...</event> block to m_lheStream.
bool writeKKMCSidecar(int globalEvtNum)
Write per-event KKMC sidecar file: all MCParticles with momenta + parent links.
double m_showerScale
Maximum shower scale ceiling in GeV (default 11.0).
A Class to store the Monte Carlo particle information.
Definition MCParticle.h:32
float getEnergy() const
Return particle energy in GeV.
Definition MCParticle.h:136
float getMass() const
Return the particle mass in GeV.
Definition MCParticle.h:124
int getArrayIndex() const
Get 0-based index of the particle in the corresponding MCParticle list.
Definition MCParticle.h:233
int getPDG() const
Return PDG code of particle.
Definition MCParticle.h:101
ROOT::Math::XYZVector getMomentum() const
Return momentum.
Definition MCParticle.h:187
Base class for Modules.
Definition Module.h:72
bool isRequired(const std::string &name="")
Ensure this array/object has been registered previously.
Accessor to arrays stored in the data store.
Definition StoreArray.h:113
int getEntries() const
Get the number of objects in the array.
Definition StoreArray.h:216
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.