Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
JetScapeWriterRootHepMC.cc
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file JetScapeWriterRootHepMC.cc
1 /*******************************************************************************
2  * Copyright (c) The JETSCAPE Collaboration, 2018
3  *
4  * Modular, task-based framework for simulating all aspects of heavy-ion collisions
5  *
6  * For the list of contributors see AUTHORS.
7  *
8  * Report issues at https://github.com/JETSCAPE/JETSCAPE/issues
9  *
10  * or via email to bugs.jetscape@gmail.com
11  *
12  * Distributed under the GNU General Public License 3.0 (GPLv3 or later).
13  * See COPYING for details.
14  ******************************************************************************/
15 
16 /*******************************************************************************
17  * kk Sep 22, 2020: Significant updates to status codes
18  * to conform with Appendix A in https://arxiv.org/pdf/1912.08005.pdf
19  * We will follow Pythia's example of preserving as much of the internal status code as possible
20  * Specifically:
21  - beam particles. We don't have those, yet. If they ever become part of initial
22  state moduls, those module writers MUST set the status to 4, all we do here
23  is respect that code (for the future).
24  However, this particle won't be a parton, so we only check that for hadrons.
25  In practice, this will probably require a revamp of the graph structure, both
26  in the framework and in here. Then, probably also use GenEvent::add_beam_particle()
27  - decayed particle. We don't have those either yet, but it's a feature that may
28  come pretty soon.
29  Here, we use that exclusively to mean decayed unstable hadrons,
30  e.g. K0S -> pi pi
31  In this case, the module that produced the hadron list MUST ensure to set the
32  status of K0s to 2. The pions are final particles, see below.
33  - Final hadrons will all be forced to status 1
34  - Partons: By default, we will accept and use any code provided by the framework
35  within 11<=status<=200 and use the absolute value.
36  Parton exceptions:
37  1) If the code is not HepMC-legal (|status|<11 or >200, often 0),
38  we assign 12 to most and 11 to the final partons before hadronization
39  (to mimic the 1, 2 scheme)
40  2) If there are NO hadrons in the event, we assume the user would like to treat
41  the final partons (like 11 from above) as final particles and assign 1
42  ******************************************************************************/
43 
44 
46 #include "JetScapeLogger.h"
47 #include "HardProcess.h"
48 #include "JetScapeSignalManager.h"
49 #include "GTL/node.h"
50 #include <GTL/topsort.h>
51 
52 using HepMC3::Units;
53 
54 namespace Jetscape {
55 
57  if (GetActive())
58  Close();
59 }
60 
62  // Create event here - not actually writing
63  // TODO: GeV seems right, but I don't think we actually measure in mm
64  // Should multiply all lengths by 1e-12 probably
65  evt = GenEvent(Units::GEV, Units::MM);
66 
67  // Expects pb, pythia delivers mb
68  auto xsec = make_shared<HepMC3::GenCrossSection>();
69  xsec->set_cross_section(GetHeader().GetSigmaGen() * 1e9, 0);
70  xsec->set_cross_section(GetHeader().GetSigmaGen() * 1e9,
71  GetHeader().GetSigmaErr() * 1e9);
72  evt.set_cross_section(xsec);
73  evt.weights().push_back(GetHeader().GetEventWeight());
74 
75  auto heavyion = make_shared<HepMC3::GenHeavyIon>();
76  // see https://gitlab.cern.ch/hepmc/HepMC3/blob/master/include/HepMC/GenHeavyIon.h
77  if (GetHeader().GetNpart() > -1) {
78  // Not clear what the difference is...
79  heavyion->Ncoll_hard = GetHeader().GetNcoll();
80  heavyion->Ncoll = GetHeader().GetNcoll();
81  }
82  if (GetHeader().GetNcoll() > -1) {
83  // Hepmc separates into target and projectile.
84  // Set one? Which? Both? half to each? setting projectile for now.
85  // setting both might lead to weird problems when they get added up
86  heavyion->Npart_proj = GetHeader().GetNpart();
87  }
88  if (GetHeader().GetTotalEntropy() > -1) {
89  // nothing good in the HepMC standard. Something related to mulitplicity would work
90  }
91 
92  if (GetHeader().GetEventPlaneAngle() > -999) {
93  heavyion->event_plane_angle = GetHeader().GetEventPlaneAngle();
94  }
95 
96  evt.set_heavy_ion(heavyion);
97 
98  // also a good moment to initialize the hadron boolean
99 }
100 
102  VERBOSE(1) << "Run JetScapeWriterHepMC: Write event # " << GetCurrentEvent();
103 
104  // Have collected all vertices now.
105  // Add all vertices to the event
106  for (auto v : vertices){
107  evt.add_vertex(v);
108  }
109 
110  VERBOSE(1) << " found " << vertices.size() << " vertices in the list";
111 
112  // If there are no hadrons, promote final partons
113  // The graph support of hepmc is a bit rudimentary.
114  // easiest is to just check all childless particles
115  // Note, one could just check for status==11,
116  // but modules are allowed to assign that number to non-final partons
117  if ( !hashadrons ) {
118  VERBOSE(1) << " found no hadrons, promoting final partons to status 1";
119  for ( auto p : evt.particles() ){
120  if ( p->children().size() == 0 ){
121  if ( p->status() !=11 ){
122  JSWARN << "Found a final parton with status!=11 : status=" << p->status() << ". This should not happen";
123  }
124  p->set_status(1);
125  }
126  }
127  }
128  evt.set_event_number(GetCurrentEvent());
129  write_event(evt);
130  vertices.clear();
132 }
133 
134 //This function dumps the particles in a specific parton shower to the event
135 void JetScapeWriterRootHepMC::Write(weak_ptr<PartonShower> ps) {
136  shared_ptr<PartonShower> pShower = ps.lock();
137  if (!pShower)
138  return;
139 
140  // Need topological order, see
141  // https://hepmc.web.cern.ch/hepmc/differences.html
142  // That means if parton p1 comes into vertex v, and p2 goes out of v,
143  // then p1 has to be created (bestowed an id) before p2
144  // Take inspiration from hepmc3.0.0/interfaces/pythia8/src/Pythia8ToHepMC3.cc
145  // But pythia showers are different from our existing graph structure,
146  // So instead try to modify the first attempt to respect top. order
147  // and don't create vertices and particles more than once
148 
149  // Using GTL's topsort
150  // 1. Check that our graph is sane
151  if (!pShower->is_acyclic())
152  throw std::runtime_error(
153  "PROBLEM in JetScapeWriterHepMC: Graph is not acyclic.");
154 
155  // 2.
156  topsort topsortsearch;
157  topsortsearch.scan_whole_graph(true);
158  topsortsearch.start_node(); // defaults to first node
159  topsortsearch.run(*pShower);
160  auto nEnd =
161  topsortsearch.top_order_end(); // this is a topsort::topsort_iterator
162 
163  // Need to keep track of already created ones
164  map<int, GenParticlePtr> CreatedPartons;
165 
166  // probably unnecessary, used for consistency checks
167  map<int, bool> vused;
168  bool foundRoot = false;
169  for (auto nIt = topsortsearch.top_order_begin(); nIt != nEnd; ++nIt) {
170  // cout << *nIt << " " << nIt->indeg() << " " << nIt->outdeg() << endl;
171 
172  // Should be the only time we see this node.
173  if (vused.find(nIt->id()) != vused.end()){
174  throw std::runtime_error( "PROBLEM in JetScapeWriterHepMC: Reusing a vertex.");
175  }
176  vused[nIt->id()] = true;
177 
178  // 0. No incoming edges?
179  // ---------------------
180  // This is typically a shower initiator.
181  // HepMC needs an incoming and outgoing particle for every vertex.
182  // That could be a place to attach partons or ions.
183  // We could also do both, but as of now, JETSCAPE actually attaches a dummy vertex
184  // to the start of the initiator, that can safely go away.
185  // Previously, we attached a dummy or clone of the outgoing one here.
186  // Instead, we can just skip the vertex. Its outgoing edges will be picked up
187  // as incomers in a later vertex.
188  // Note that the [0]=>[1] connection in JETSCAPE
189  // already uses a dummy node[0], and [1] is at time t=0; removing that seems correct.
190  if (nIt->indeg() == 0) continue;
191 
192  // 1. Create a new vertex.
193  // --------------------------------------------
194  auto v = castVtxToHepMC(pShower->GetVertex(*nIt));
195 
196  // 2. Incoming edges
197  // -----------------
198  // In the current framework, it should only be one.
199  // So we will catch anything more but provide a mechanism that should work anyway.
200  if (nIt->indeg() > 1) {
201  JSWARN << "Found more than one mother parton! Should only happen if we "
202  "added medium particles. "
203  << "The code should work, but proceed with caution";
204  }
205 
206  auto inIt = nIt->in_edges_begin();
207  auto inEnd = nIt->in_edges_end();
208  for (/* nop */; inIt != inEnd; ++inIt) {
209  auto phepin = CreatedPartons.find(inIt->id());
210  if (phepin != CreatedPartons.end()) {
211  // We should already have one!
212  v->add_particle_in(phepin->second);
213  } else {
214  // This indicates we skipped an earlier vertex without incomers.
215  // JSWARN << "Incoming particle out of nowhere. This could maybe happen "
216  // "if we pick up medium particles "
217  // << " but is probably a topsort problem. Try using the code "
218  // "after this throw() but be very careful.";
219  // throw std::runtime_error("PROBLEM in JetScapeWriterHepMC: Incoming "
220  // "particle out of nowhere.");
221 
222  auto in = pShower->GetParton(*inIt);
223  auto hepin = castPartonToHepMC(in);
224  auto status = std::abs(hepin->status());
225  if ( status < 11 || status > 200) {
226  // incoming edge can't be final
227  status = 12;
228  }
229  hepin->set_status(status);
230  CreatedPartons[inIt->id()] = hepin;
231  v->add_particle_in(hepin);
232 
233  if ( nIt->outdeg() == 0 ) {
234  // However, motherless AND childless particles do exist
235  // I.e., a shower initiator that never actually showers
236  // For this, we need an out going clone, much like 3) below
237  auto hepout = castPartonToHepMC(in);
238  // Since the status information is preserved in the incomer, we'll force 11
239  // Note: if we later see in WriteEvent() that there are no hadrons, this will be overwritten to 1
240  hepout->set_status( 11 );
241  // Note that we do not register this particle. Since it's pointing nowhere it can never be reused.
242  v->add_particle_out(hepout);
243  }
244  }
245  }
246 
247  // 3. Outgoing edges?
248  // --------------------------------------------
249  // 3.1: No. Need to create one.
250  // We'll use this opportunity to copy the incomer but give it a final code
251  if (nIt->outdeg() == 0) {
252  if (nIt->indeg() != 1) {
253  // This won't work with multiple incomers (but that's pretty unphysical)
254  throw std::runtime_error("PROBLEM in JetScapeWriterHepMC: Need exactly "
255  "one parent to clone final state partons.");
256  }
257  auto in = pShower->GetParton(*(nIt->in_edges_begin()));
258  auto hepout = castPartonToHepMC(in);
259  // an outgoing edge without terminator is "final"
260  // Since the status information is preserved in the incomer, we'll force 11
261  // Note: if we later see in WriteEvent() that there are no hadrons, this will be overwritten to 1
262  hepout->set_status( 11 );
263  v->add_particle_out(hepout);
264  // Note that we do not register this particle. Since it's pointing nowhere it can never be reused.
265  }
266 
267  // 3.2: Otherwise use and register the outgoing edge
268  if (nIt->outdeg() > 0) {
269  auto outIt = nIt->out_edges_begin();
270  auto outEnd = nIt->out_edges_end();
271  for (/* nop */; outIt != outEnd; ++outIt) {
272  if (CreatedPartons.find(outIt->id()) != CreatedPartons.end()) {
273  throw std::runtime_error("PROBLEM in JetScapeWriterHepMC: Trying to "
274  "recreate a preexisting GenParticle.");
275  }
276  auto out = pShower->GetParton(*outIt);
277  auto hepout = castPartonToHepMC(out);
278  if ( !hepout->status()) {
279  // incoming and outgoing -> status 12
280  hepout->set_status(12);
281  }
282 
283  CreatedPartons[outIt->id()] = hepout;
284  v->add_particle_out(hepout);
285  }
286  }
287 
288  vertices.push_back(v);
289  }
290 }
291 
292 void JetScapeWriterRootHepMC::Write(weak_ptr<Hadron> h) {
293  auto hadron = h.lock();
294  if (!hadron)
295  return;
296 
297  // No clear source for most hadrons
298  // Also, a graph with e.g. recombination hadrons would have loops,
299  // (though the direction should still make it acyclic?)
300  // Not sure how this is supposed to be done in HepMC3
301  // Our solution: Attach all hadrons to one dedicated hadronization vertex.
302  // Future option: Have separate shower and bulk vertices?
303 
304  // Create if it doesn't exist yet
305  if (!hadronizationvertex) {
306  // dummy position
307  HepMC3::FourVector vtxPosition(0, 0, 0, 100); // set it to a late time...
308  hadronizationvertex = make_shared<GenVertex>(vtxPosition);
309 
310  // dummy mother -- could also maybe use the first/hardest shower initiator
311  HepMC3::FourVector pmom(0, 0, 0, 0);
312  make_shared<GenParticle>(pmom, 0, 0);
313  hadronizationvertex->add_particle_in(make_shared<GenParticle>(pmom, 0, 0));
314 
315  vertices.push_back(hadronizationvertex);
316  hashadrons=true;
317  }
318 
319  // now attach
320  auto hepmc = castHadronToHepMC(hadron);
321  if ( !hepmc->status() ) {
322  // unless otherwise specified, all hadrons get status 1
323  // TODO: Need to better account for short-lived hadrons
324  hepmc->set_status(1);
325  }
326  hadronizationvertex->add_particle_out(hepmc);
327 }
328 
330  if (GetActive()) {
331  JSINFO << "JetScape HepMC Writer initialized with output file = "
332  << GetOutputFileName();
333  }
334 }
335 
337  // Nothing to do
338 }
339 } // end namespace Jetscape