MARLEY (Model of Argon Reaction Low Energy Yields) v2.0.0
A Monte Carlo event generator for tens-of-MeV neutrino interactions
Loading...
Searching...
No Matches
hepmc3_utils.cc
1
4//
5// This file is part of MARLEY (Model of Argon Reaction Low Energy Yields)
6//
7// MARLEY is free software: you can redistribute it and/or modify it under the
8// terms of version 3 of the GNU General Public License as published by the
9// Free Software Foundation.
10//
11// For the full text of the license please see COPYING or
12// visit http://opensource.org/licenses/GPL-3.0
13//
14// Please respect the MCnet academic usage guidelines. See GUIDELINES
15// or visit https://www.montecarlonet.org/GUIDELINES for details.
16
17// Standard library includes
18#include <iomanip>
19#include <iostream>
20#include <map>
21#include <memory>
22#include <sstream>
23#include <string>
24#include <vector>
25
26// HepMC3 includes
27#include "HepMC3/Attribute.h"
28#include "HepMC3/FourVector.h"
29#include "HepMC3/GenCrossSection.h"
30#include "HepMC3/GenEvent.h"
31#include "HepMC3/GenRunInfo.h"
32#include "HepMC3/GenVertex.h"
33#include "HepMC3/GenParticle.h"
34
35// MARLEY includes
36#include "marley/marley_utils.hh"
37#include "marley/hepmc3_utils.hh"
38#include "marley/Error.hh"
39#include "marley/Generator.hh"
40#include "marley/JSON.hh"
41#include "marley/Reaction.hh"
42
43namespace {
44
45 constexpr int DUMMY_NUHEPMC_PROC_ID = 0;
46
47 // G.R.8 and E.C.1
48 struct NuHepMCProcess {
49 NuHepMCProcess( int procID, std::string name, std::string description )
50 : id_( procID ), name_( name ), desc_( description ) {}
51
52 int id_;
53 std::string name_;
54 std::string desc_;
55 };
56
58 const std::map< marley::Reaction::ProcessType, NuHepMCProcess >
59 ptype_to_nuhepmc_proc =
60 {
62 { 100, "vCC-discrete", "charged-current neutrino-nucleus"
63 " scattering via discrete transitions" } },
65 { 110, "anti-vCC-discrete", "charged-current antineutrino-nucleus"
66 " scattering via discrete transitions" } },
68 { 150, "NC-discrete", "neutral-current (anti)neutrino-nucleus"
69 " scattering via discrete transitions" } },
71 { 700, "v-e", "(anti)neutrino-electron elastic scattering" } },
73 { 101, "vCC-continuum", "charged-current neutrino-nucleus"
74 " scattering via continuum transitions" } },
76 { 111, "anti-vCC-continuum", "charged-current antineutrino-nucleus"
77 " scattering via continuum transitions" } },
79 { 151, "NC-continuum", "neutral-current (anti)neutrino-nucleus"
80 " scattering via continuum transitions" } },
82 { 800, "standalone-decay", "standalone nuclear de-excitation"
83 " with no simulated primary reaction" } },
84 };
85
86 // G.R.9
87 std::map< int, std::pair< std::string, std::string > >
88 vertex_status_map =
89 {
90
91 { marley_hepmc3::NUHEPMC_PRIMARY_VERTEX, { "Primary",
92 "Represents the primary interaction" } },
93
94 { marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX, { "HFDecay",
95 "Represents a nuclear de-excitation step simulated using the"
96 " Hauser-Feshbach treatment" } },
97
98 { marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX, { "GammaDecay",
99 "Represents a nuclear de-excitation step simulated using tabulated"
100 " gamma-ray branching ratios" } },
101
102 };
103
104 // G.R.10 and P.R.1
105 std::map< int, std::pair< std::string, std::string > >
106 particle_status_map =
107 {
108
109 { marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, { "Final-state",
110 "Undecayed physical particle" } },
111
112 { marley_hepmc3::NUHEPMC_PROJECTILE_STATUS, { "Projectile",
113 "Incoming beam particle" } },
114
115 { marley_hepmc3::NUHEPMC_TARGET_STATUS, { "Target",
116 "Target particle struck by incoming beam particle in"
117 " the primary interaction" } },
118
119 { marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS, { "UndecayedRemnant",
120 "Nuclear remnant before simulation of nuclear de-excitations" } },
121
122 { marley_hepmc3::NUHEPMC_INTERMEDIATE_RESIDUE_STATUS,
123 { "IntermediateRemnant",
124 "Nuclear remnant during simulation of nuclear de-excitations" } },
125
126 };
127
128 // G.R.4
129 std::vector< std::string > nuhepmc_convention_vec = {
130 "G.C.2", // flux-averaged total cross section stored on GenRunInfo
131 "G.C.3", // citation metadata for MARLEY publications
132 "E.C.1", // process ID categorization
133 "E.C.2", // total cross section per event
134 "E.C.3", // process-specific cross section per event
135 };
136
137}
138
139namespace marley_hepmc3 {
140
141 int get_nuhepmc_proc_id( const marley::Reaction::ProcessType pt ) {
142 auto itr = ptype_to_nuhepmc_proc.find( pt );
143 if ( itr != ptype_to_nuhepmc_proc.end() ) {
144 return itr->second.id_;
145 }
146 return DUMMY_NUHEPMC_PROC_ID;
147 }
148
149 marley::Reaction::ProcessType from_nuhepmc_proc_id( const int proc_id ) {
151 auto itr = std::find_if( ptype_to_nuhepmc_proc.cbegin(),
152 ptype_to_nuhepmc_proc.cend(), [ proc_id ]( auto& pair ) -> bool
153 { return pair.second.id_ == proc_id; }
154 );
155 if ( itr != ptype_to_nuhepmc_proc.end() ) {
156 pt = itr->first;
157 }
158 return pt;
159 }
160
161 void set_particle_charge( HepMC3::GenParticle& particle, int charge ) {
162 bool added_ok = particle.add_attribute( "charge",
163 std::make_shared< HepMC3::IntAttribute >(charge) );
164 if ( !added_ok ) {
165 throw marley::Error( "Failed to set particle charge in marley_hepmc3"
166 "::set_particle_charge()" );
167 }
168 }
169
170 int get_particle_charge( HepMC3::GenParticle& particle ) {
171 // Return the charge stored in the particle attributes if it is set
172 auto* q_ptr = particle.attribute< HepMC3::IntAttribute >( "charge" ).get();
173 if ( q_ptr ) return q_ptr->value();
174 // Otherwise, look up the charge based on the PDG code
175 return marley_utils::get_particle_charge( particle.pid() );
176 }
177
178 std::shared_ptr< HepMC3::GenParticle > make_particle(
179 const HepMC3::FourVector& mom4, int pdg, int status,
180 double mass )
181 {
182 auto particle = std::make_shared< HepMC3::GenParticle >(
183 mom4, pdg, status );
184 if ( mass != DUMMY_PARTICLE_MASS ) {
185 particle->set_generated_mass( mass );
186 }
187 return particle;
188 }
189
190 std::shared_ptr< HepMC3::GenParticle > make_particle(
191 int pdg, double px, double py, double pz, double E, int status,
192 double mass )
193 {
194 HepMC3::FourVector mom4( px, py, pz, E );
195 return make_particle( mom4, pdg, status, mass );
196 }
197
198 std::shared_ptr< HepMC3::GenParticle > make_particle(
199 int pdg, double px, double py, double pz, int status,
200 double mass )
201 {
202 double E = marley_utils::real_sqrt( px*px + py*py + pz*pz + mass*mass );
203 HepMC3::FourVector mom4( px, py, pz, E );
204 return make_particle( mom4, pdg, status, mass );
205 }
206
207 std::shared_ptr< HepMC3::GenParticle > make_particle(
208 int pdg, int status, double mass )
209 {
210 HepMC3::FourVector mom4;
211 if ( mass != DUMMY_PARTICLE_MASS ) {
212 mom4.set_e( mass );
213 }
214 return make_particle( mom4, pdg, status, mass );
215 }
216
217
218
219 std::vector< std::shared_ptr< HepMC3::GenParticle > >
220 get_particles_with_status( int status, HepMC3::GenEvent& ev )
221 {
222 const auto& particles = ev.particles();
223 std::vector< std::shared_ptr< HepMC3::GenParticle > > found_particles;
224 for ( auto& p : particles ) {
225 if ( p->status() == status ) {
226 found_particles.push_back( p );
227 }
228 }
229 return found_particles;
230 }
231
232 std::vector< std::shared_ptr< HepMC3::GenVertex > >
233 get_vertices_with_status( int status, HepMC3::GenEvent& ev )
234 {
235 const auto& vertices = ev.vertices();
236 std::vector< std::shared_ptr< HepMC3::GenVertex > > found_vertices;
237 for ( auto& v : vertices ) {
238 if ( v->status() == status ) {
239 found_vertices.push_back( v );
240 }
241 }
242 return found_vertices;
243 }
244
245 std::shared_ptr< HepMC3::GenParticle >
246 get_first_particle_with_status( int status, HepMC3::GenEvent& ev )
247 {
248 auto particle_ptrs = get_particles_with_status( status, ev );
249 if ( !particle_ptrs.empty() ) return particle_ptrs.front();
250 return nullptr;
251 }
252
253 std::shared_ptr< HepMC3::GenParticle > get_projectile(
254 HepMC3::GenEvent& ev )
255 {
256 return get_first_particle_with_status(
257 marley_hepmc3::NUHEPMC_PROJECTILE_STATUS, ev );
258 }
259
260 std::shared_ptr< HepMC3::GenParticle > get_target(
261 HepMC3::GenEvent& ev )
262 {
263 return get_first_particle_with_status(
264 marley_hepmc3::NUHEPMC_TARGET_STATUS, ev );
265 }
266
267 std::shared_ptr< HepMC3::GenParticle > get_ejectile(
268 HepMC3::GenEvent& ev )
269 {
270 return get_first_particle_with_status(
271 marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, ev );
272 }
273
274 std::shared_ptr< HepMC3::GenParticle > get_residue(
275 HepMC3::GenEvent& ev )
276 {
277 return get_first_particle_with_status(
278 marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS, ev );
279 }
280
281 // G.R.8
282 void prepare_process_metadata( HepMC3::GenRunInfo& run_info ) {
283 std::vector< int > proc_id_vec;
284 for ( const auto& pair : ptype_to_nuhepmc_proc ) {
285 int proc_id = pair.second.id_;
286 proc_id_vec.push_back( proc_id );
287
288 std::string attr_prefix = "NuHepMC.ProcessInfo["
289 + std::to_string( proc_id ) + "].";
290
291 run_info.add_attribute( attr_prefix + "Name",
292 std::make_shared< HepMC3::StringAttribute >(pair.second.name_) );
293
294 run_info.add_attribute( attr_prefix + "Description",
295 std::make_shared< HepMC3::StringAttribute >(pair.second.desc_) );
296 }
297
298 run_info.add_attribute( "NuHepMC.ProcessIDs",
299 std::make_shared< HepMC3::VectorIntAttribute >(proc_id_vec) );
300 }
301
302 // G.R.9
303 void prepare_vertex_status_metadata( HepMC3::GenRunInfo& run_info ) {
304 std::vector< int > status_vec;
305 for ( const auto& pair : vertex_status_map ) {
306 int status = pair.first;
307 status_vec.push_back( status );
308
309 std::string attr_prefix = "NuHepMC.VertexStatusInfo["
310 + std::to_string( status ) + "].";
311
312 run_info.add_attribute( attr_prefix + "Name",
313 std::make_shared< HepMC3::StringAttribute >(pair.second.first) );
314
315 run_info.add_attribute( attr_prefix + "Description",
316 std::make_shared< HepMC3::StringAttribute >(pair.second.second) );
317 }
318
319 run_info.add_attribute( "NuHepMC.VertexStatusIDs",
320 std::make_shared< HepMC3::VectorIntAttribute >(status_vec) );
321 }
322
323 // G.R.10
324 void prepare_particle_status_metadata( HepMC3::GenRunInfo& run_info ) {
325 std::vector< int > status_vec;
326 for ( const auto& pair : particle_status_map ) {
327 int status = pair.first;
328 status_vec.push_back( status );
329
330 std::string attr_prefix = "NuHepMC.ParticleStatusInfo["
331 + std::to_string( status ) + "].";
332
333 run_info.add_attribute( attr_prefix + "Name",
334 std::make_shared< HepMC3::StringAttribute >(pair.second.first) );
335
336 run_info.add_attribute( attr_prefix + "Description",
337 std::make_shared< HepMC3::StringAttribute >(pair.second.second) );
338 }
339
340 run_info.add_attribute( "NuHepMC.ParticleStatusIDs",
341 std::make_shared< HepMC3::VectorIntAttribute >(status_vec) );
342 }
343
344 // G.R.11
345 void prepare_non_standard_pdg_code_metadata( HepMC3::GenRunInfo& run_info )
346 {
347 // PDG code 0 is used as a dummy/absent projectile in standalone
348 // nuclear de-excitation events generated by "marley decay"
349 const std::vector< int > non_standard_PDGs = { 0 };
350 run_info.add_attribute( "NuHepMC.AdditionalParticleNumbers",
351 std::make_shared< HepMC3::VectorIntAttribute >(non_standard_PDGs) );
352
353 run_info.add_attribute( "NuHepMC.AdditionalParticleNumbers[0].Name",
354 std::make_shared< HepMC3::StringAttribute >( "Absent" ) );
355
356 run_info.add_attribute( "NuHepMC.AdditionalParticleNumbers[0].Description",
357 std::make_shared< HepMC3::StringAttribute >( "Dummy particle"
358 " representing an absent projectile in a standalone nuclear"
359 " de-excitation event (used by \"marley decay\")" ) );
360 }
361
362 // NOTE: the input flux-averaged total cross section is assumed to be in
363 // natural units (MeV^{-2})
364 void apply_nuhepmc_runinfo_conventions( HepMC3::GenRunInfo& run_info,
365 const double flux_avg_xsec )
366 {
367
368 // G.R.4
369 run_info.add_attribute( "NuHepMC.Conventions",
370 std::make_shared< HepMC3::VectorStringAttribute >(
371 nuhepmc_convention_vec )
372 );
373
374 // G.R.6
375 run_info.add_attribute( "NuHepMC.Units.CrossSection.Unit",
376 std::make_shared< HepMC3::StringAttribute >( "pb" )
377 );
378
379 run_info.add_attribute( "NuHepMC.Units.CrossSection.TargetScale",
380 std::make_shared< HepMC3::StringAttribute >( "PerAtom" )
381 );
382
383 // G.C.2
384 double xsec_picobarn = flux_avg_xsec * marley_utils::hbar_c2
385 * marley_utils::fm2_to_picobarn;
386 run_info.add_attribute( "NuHepMC.FluxAveragedTotalCrossSection",
387 std::make_shared< HepMC3::DoubleAttribute >( xsec_picobarn )
388 );
389
390 // G.C.3
391 std::vector< std::string > marley_DOIs = {
392 "10.1103/PhysRevC.103.044604",
393 "10.1016/j.cpc.2021.108123"
394 };
395
396 run_info.add_attribute( "NuHepMC.Citations.Generator.DOI",
397 std::make_shared< HepMC3::VectorStringAttribute >( marley_DOIs )
398 );
399
400 std::vector< std::string > marley_arXivs = {
401 "2010.02393",
402 "2101.11867",
403 "2604.26801"
404 };
405
406 run_info.add_attribute( "NuHepMC.Citations.Generator.arXiv",
407 std::make_shared< HepMC3::VectorStringAttribute >( marley_arXivs )
408 );
409
410 std::vector< std::string > marley_INSPIREs = {
411 "Gardiner:2020ulp",
412 "Gardiner:2021qfr"
413 };
414
415 run_info.add_attribute( "NuHepMC.Citations.Generator.InspireHEP",
416 std::make_shared< HepMC3::VectorStringAttribute >( marley_INSPIREs )
417 );
418
419 }
420
421 std::string check_run_info_compatibility(
422 const HepMC3::GenRunInfo& ref,
423 const HepMC3::GenRunInfo& candidate )
424 {
425 if ( ref.weight_names() != candidate.weight_names() ) {
426 return "weight names differ between files";
427 }
428
429 auto ref_attr = ref.attribute< HepMC3::StringAttribute >(
430 "MARLEY.JSONconfig" );
431 auto cand_attr = candidate.attribute< HepMC3::StringAttribute >(
432 "MARLEY.JSONconfig" );
433
434 if ( ref_attr && cand_attr ) {
435 try {
436 marley::JSON ref_json = marley::JSON::load( ref_attr->value() );
437 marley::JSON cand_json = marley::JSON::load( cand_attr->value() );
438
439 auto strip_run_keys = []( marley::JSON& j ) -> marley::JSON {
440 marley::JSON result = marley::JSON::object();
441 for ( const auto& [key, value] : j.object_range() ) {
442 if ( key != "seed" && key != "generate" ) {
443 result[key] = value;
444 }
445 }
446 return result;
447 };
448
449 marley::JSON ref_stripped = strip_run_keys( ref_json );
450 marley::JSON cand_stripped = strip_run_keys( cand_json );
451
452 if ( ref_stripped.dump_string() != cand_stripped.dump_string() ) {
453 return "MARLEY JSON configuration differs between files";
454 }
455 } catch ( const std::exception& e ) {
456 return "failed to parse MARLEY JSON configuration: "
457 + std::string( e.what() );
458 }
459 } else if ( static_cast< bool >( ref_attr )
460 != static_cast< bool >( cand_attr ) )
461 {
462 return "one file has MARLEY JSON configuration and the other does not";
463 }
464
465 return {};
466 }
467
468}
469
470// Handles sampling and storing a random decay time for a binary decay vertex
471void marley_hepmc3::store_decay_time( double partial_width,
472 marley::Generator& gen, std::shared_ptr< HepMC3::GenVertex >& decay_vtx,
473 const std::shared_ptr< HepMC3::GenParticle >& parent )
474{
475 // Sample a decay time (MeV^{-1}) to assign to the decay vertex
476 double decay_time = gen.sample_decay_time( partial_width );
477 MARLEY_LOG( TRACE, "physics.deexcitation.gamma" ) << "decay_time = "
478 << marley_utils::hbar * decay_time << " s";
479
480 // Convert to the appropriate time units (cm) for a NuHepMC 4-position.
481 // See marley::Reaction::make_event_object() where the units are defined.
482 decay_time *= marley_utils::hbar_c * marley_utils::fm_to_cm;
483
484 // The decay width treatment above assumes that the parent particle is
485 // at rest. Apply a (typically very small) time dilation correction
486 // since it may be moving in the laboratory frame.
487 const HepMC3::FourVector& mom4_parent = parent->momentum();
488 double E2_parent = std::pow( mom4_parent.e(), 2 );
489 double beta2_parent = mom4_parent.length2() / E2_parent;
490 double gamma_parent = 1. / marley_utils::real_sqrt( 1. - beta2_parent );
491 decay_time *= gamma_parent;
492
493 // Get the creation time of the parent particle from its starting vertex
494 const auto parent_prod_vtx = parent->production_vertex();
495 if ( !parent_prod_vtx ) throw marley::Error( "Could not access parent"
496 " particle production vertex in marley_hepmc3::store_decay_time()" );
497 double old_time = parent_prod_vtx->position().t(); // cm
498
499 // Set and store the absolute time in the decay vertex
500 // TODO: add spatial information as needed
501 double new_time = old_time + decay_time; // cm
502 HepMC3::FourVector decay_pos4;
503 decay_pos4.set_t( new_time );
504
505 decay_vtx->set_position( decay_pos4 );
506}
507
508namespace {
509
510 // ──────────────────────────────────────────────────────────────────
511 // Helpers for marley_hepmc3::print_event()
512 // ──────────────────────────────────────────────────────────────────
513
514 // Named UTF-8 constants for the Unicode characters used in the display.
515 const std::string BOX_HEAVY = "━"; // U+2501 BOX DRAWINGS HEAVY HORIZONTAL
516 const std::string THIN_CHAR = "─"; // U+2500 BOX DRAWINGS LIGHT HORIZONTAL
517 const std::string ARROW_RIGHT = "►"; // U+25BA BLACK RIGHT-POINTING POINTER
518 const std::string BOX_VERT = "│"; // U+2502 BOX DRAWINGS LIGHT VERTICAL
519
520 // Thick separator: 71 × BOX_HEAVY (━), programmatically generated.
521 const std::string THICK_SEP = []() {
522 std::string s;
523 s.reserve( 71 * 3 );
524 for ( int k = 0; k < 71; ++k ) s += BOX_HEAVY;
525 return s;
526 }();
527
528 // Compute display width of a UTF-8 string.
529 // Counts Unicode code points and skips combining characters (U+0300–U+036F)
530 // since they do not advance the terminal cursor.
531 int utf8_display_width( const std::string& s ) {
532 int w = 0;
533 size_t i = 0;
534 while ( i < s.size() ) {
535 auto c = static_cast< unsigned char >( s[i] );
536 uint32_t cp = 0;
537 size_t len = 1;
538 if ( c < 0x80u ) {
539 cp = c; len = 1;
540 } else if ( c < 0xE0u ) {
541 cp = static_cast< uint32_t >( c & 0x1Fu ) << 6;
542 if ( i + 1 < s.size() )
543 cp |= static_cast< unsigned char >( s[i+1] ) & 0x3Fu;
544 len = 2;
545 } else if ( c < 0xF0u ) {
546 cp = static_cast< uint32_t >( c & 0x0Fu ) << 12;
547 if ( i + 1 < s.size() )
548 cp |= static_cast< uint32_t >(
549 static_cast< unsigned char >( s[i+1] ) & 0x3Fu ) << 6;
550 if ( i + 2 < s.size() )
551 cp |= static_cast< unsigned char >( s[i+2] ) & 0x3Fu;
552 len = 3;
553 } else {
554 // 4-byte sequence: decode all three continuation bytes
555 cp = static_cast< uint32_t >( c & 0x07u ) << 18;
556 if ( i + 1 < s.size() )
557 cp |= static_cast< uint32_t >(
558 static_cast< unsigned char >( s[i+1] ) & 0x3Fu ) << 12;
559 if ( i + 2 < s.size() )
560 cp |= static_cast< uint32_t >(
561 static_cast< unsigned char >( s[i+2] ) & 0x3Fu ) << 6;
562 if ( i + 3 < s.size() )
563 cp |= static_cast< unsigned char >( s[i+3] ) & 0x3Fu;
564 len = 4;
565 }
566 i += len;
567 // Combining characters (U+0300–U+036F) contribute zero display width
568 if ( cp < 0x0300u || cp > 0x036Fu ) ++w;
569 }
570 return w;
571 }
572
573 // Build a thin separator line of total display width `total` characters.
574 // The prefix may contain multi-byte UTF-8 characters; fill uses THIN_CHAR.
575 std::string make_thin_sep( const std::string& prefix, int total = 71 ) {
576 std::string s = prefix;
577 int fill = total - utf8_display_width( prefix );
578 for ( int k = 0; k < fill; ++k ) s += THIN_CHAR;
579 return s;
580 }
581
582 // Left-justify s in a field of target display-width chars, padding with
583 // spaces
584 std::string left_pad( const std::string& s, int target ) {
585 int dw = utf8_display_width( s );
586 int pad = ( target > dw ) ? ( target - dw ) : 0;
587 return s + std::string( static_cast< size_t >( pad ), ' ' );
588 }
589
590 // Format the spin-parity string from twoJ and parity integer
591 std::string format_jp( int twoJ, int par ) {
592 std::string j;
593 if ( twoJ % 2 == 0 ) j = std::to_string( twoJ / 2 );
594 else j = std::to_string( twoJ ) + "/2";
595 return j + ( par >= 0 ? "+" : "-" );
596 }
597
598 // Return the 6-display-char tag for the interaction-chain primary line.
599 // All returned strings are exactly 6 display characters wide.
600 std::string chain_tag_primary( marley::Reaction::ProcessType pt,
601 int proj_pdg )
602 {
603 std::string tag = marley_utils::get_particle_symbol( proj_pdg );
604 switch ( pt ) {
609 tag += " CC ";
610 break;
613 tag += " NC ";
614 break;
616 tag += "+e- ";
617 break;
619 return " dcay ";
620 default:
621 return " ???? ";
622 }
623 return tag;
624 }
625
626 // Helper: get double attribute from particle, returns false if absent
627 bool get_double_attr( const HepMC3::ConstGenParticlePtr& p,
628 const std::string& name, double& val )
629 {
630 auto attr = p->attribute< HepMC3::DoubleAttribute >( name );
631 if ( !attr ) return false;
632 val = attr->value();
633 return true;
634 }
635
636 // Helper: get double attribute from vertex, returns false if absent
637 bool get_double_attr_vtx( const HepMC3::ConstGenVertexPtr& v,
638 const std::string& name, double& val )
639 {
640 auto attr = v->attribute< HepMC3::DoubleAttribute >( name );
641 if ( !attr ) return false;
642 val = attr->value();
643 return true;
644 }
645
646 // Helper: get int attribute from particle, returns false if absent
647 bool get_int_attr( const HepMC3::ConstGenParticlePtr& p,
648 const std::string& name, int& val )
649 {
650 auto attr = p->attribute< HepMC3::IntAttribute >( name );
651 if ( !attr ) return false;
652 val = attr->value();
653 return true;
654 }
655
656 // Helper: get int attribute from vertex, returns false if absent
657 bool get_int_attr_vtx( const HepMC3::ConstGenVertexPtr& v,
658 const std::string& name, int& val )
659 {
660 auto attr = v->attribute< HepMC3::IntAttribute >( name );
661 if ( !attr ) return false;
662 val = attr->value();
663 return true;
664 }
665
666 // Format a momentum component with explicit sign and 3 decimal places
667 std::string fmtpm( double v ) {
668 std::ostringstream ss;
669 ss << std::showpos << std::fixed << std::setprecision( 3 ) << v;
670 return ss.str();
671 }
672
673 // Print one particle line in a vertex IN or OUT block.
674 //
675 // Rules (applied in order):
676 // 1. If the particle has an "Ex" attribute and we are in the IN block
677 // (is_in_block == true): print simplified nuclear excitation line only.
678 // 2. Otherwise print the full PDG/E/momentum line, then optionally the
679 // excitation annotation below.
680 //
681 // Full-line momentum display (for OUT and primary-vertex IN):
682 // a. If |p3| < 1e-6 MeV/c: show "(at rest)"
683 // b. If px≈0 and py≈0 (projectile on z-axis): show "pz = <+val>"
684 // c. If photon (PDG 22) in gamma-cascade vertex with multipolarity > 0:
685 // show "M<L> (l = <L>)" replacing momentum
686 // d. If nuclear (PDG > 1e9) and Ex attribute absent or zero,
687 // and vertex is a de-excitation vertex (is_deex_vtx == true):
688 // show "[ground state]" replacing momentum
689 // e. Otherwise: show "p = (px, py, pz) MeV/c"
690 //
691 // Name field: 11 display chars (left-justified).
692 // PDG field: 10 chars (right-justified).
693 // Excitation annotation (31-space indent): follows OUT particle if Ex > 0.
694 void print_particle_line( std::ostream& os,
695 const HepMC3::ConstGenParticlePtr& p,
696 bool is_in_block,
697 bool is_deex_vtx,
698 bool is_gamma_vtx,
699 int vtx_multipolarity )
700 {
701 int pdg = p->pid();
702
703 double Ex = 0.;
704 int twoJ = -1;
705 int par = 1;
706 bool has_ex = get_double_attr( p, "Ex", Ex );
707 get_int_attr( p, "twoJ", twoJ );
708 get_int_attr( p, "parity", par );
709 bool excited = has_ex && ( Ex > 0. );
710
711 std::string name = marley_utils::get_particle_symbol( pdg, excited );
712
713 // ── Rule 1: simplified IN-block line for nuclear excited states ───────
714 if ( is_in_block && has_ex ) {
715 os << " " << left_pad( name, 11 )
716 << "Ex = " << std::fixed << std::setprecision( 2 ) << Ex << " MeV";
717 if ( twoJ >= 0 )
718 os << " Jπ = " << format_jp( twoJ, par ); // Jπ = ...
719 os << "\n";
720 return;
721 }
722
723 // ── Rules 2+: full particle line ─────────────────────────────────────
724 const HepMC3::FourVector& mom = p->momentum();
725 double E = mom.e();
726 double px = mom.px();
727 double py = mom.py();
728 double pz = mom.pz();
729 double p3mag = std::sqrt( px*px + py*py + pz*pz );
730
731 std::ostringstream line;
732 line << " " << left_pad( name, 11 )
733 << "E = " << std::fixed << std::setprecision( 3 )
734 << std::setw( 9 ) << E << " MeV ";
735
736 // Determine the momentum display.
737 // "ground state nucleus" in the OUT block of a de-excitation vertex:
738 // - Must be a nuclear PDG code (> 1e9)
739 // - Must be in the OUT block (not IN)
740 // - Must be in a de-excitation vertex (HF or gamma)
741 // - Must not have an excitation energy set
742 // - Excludes final-state particles in HF vertices: those are emitted
743 // fragments (e.g. α, d, t) with genuine momenta, not daughter residues.
744 // In gamma-cascade vertices all nuclear OUT particles are daughters.
745 bool is_nuclear = ( pdg > 1000000000 );
746 bool is_final_state_particle =
747 ( p->status() == marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS );
748 bool ground_state_nucleus = is_nuclear && !is_in_block && is_deex_vtx
749 && ( !has_ex || Ex == 0. )
750 && ( is_gamma_vtx || !is_final_state_particle );
751
752 if ( ground_state_nucleus ) {
753 // Rule 2d: nuclear ground state in de-excitation vertex
754 line << "[ground state]";
755 } else if ( p3mag < 1e-6 ) {
756 // Rule 2a: at rest
757 line << "(at rest)";
758 } else if ( std::abs(px) < 1e-6 && std::abs(py) < 1e-6 ) {
759 // Rule 2b: projectile aligned along z-axis
760 line << "pz = " << std::showpos << std::fixed << std::setprecision( 3 )
761 << pz << std::noshowpos;
762 } else if ( pdg == 22 && is_gamma_vtx && vtx_multipolarity > 0 ) {
763 // Rule 2c: photon with known multipolarity in gamma-cascade vertex
764 line << "M" << vtx_multipolarity
765 << " (l = " << vtx_multipolarity << ")";
766 } else {
767 // Rule 2e: general 3-momentum
768 line << "p = (" << fmtpm(px) << ", " << fmtpm(py) << ", "
769 << fmtpm(pz) << ") MeV/c";
770 }
771
772 os << line.str() << "\n";
773
774 // ── Excitation annotation (31-space indent) ───────────────────────────
775 // Applies to OUT particles with Ex > 0, but not to photons in gamma vertices.
776 if ( !is_in_block && excited && !ground_state_nucleus
777 && !( pdg == 22 && is_gamma_vtx ) && twoJ >= 0 )
778 {
779 os << " Ex = "
780 << std::fixed << std::setprecision( 2 ) << Ex
781 << " MeV Jπ = " << format_jp( twoJ, par ) << "\n";
782 }
783 }
784
785 // Get the display name for a particle in the interaction-chain summary.
786 // Appends "*" to nuclei when Ex > 0.
787 std::string chain_particle_name( const HepMC3::ConstGenParticlePtr& p ) {
788 int pdg = p->pid();
789 double Ex = 0.;
790 bool excited = get_double_attr( p, "Ex", Ex ) && ( Ex > 0. );
791 return marley_utils::get_particle_symbol( pdg, excited );
792 }
793
794 // Print the INTERACTION CHAIN section.
795 void print_interaction_chain( std::ostream& os, const HepMC3::GenEvent& ev,
797 {
798 const auto& verts = ev.vertices();
799 if ( verts.empty() ) return;
800
801 // Primary vertex
802 const auto& pv = verts.front();
803 const auto& pv_in = pv->particles_in();
804 const auto& pv_out = pv->particles_out();
805
806 // Identify projectile and target from IN particles
807 HepMC3::ConstGenParticlePtr proj = nullptr;
808 HepMC3::ConstGenParticlePtr target = nullptr;
809 if ( pv_in.size() == 2u ) {
810 const auto& pv_in1 = pv_in.front();
811 const auto& pv_in2 = pv_in.back();
812 if ( pv_in1->status() == marley_hepmc3::NUHEPMC_PROJECTILE_STATUS ) {
813 proj = pv_in1;
814 target = pv_in2;
815 }
816 else {
817 target = pv_in1;
818 proj = pv_in2;
819 }
820 }
821 else throw marley::Error( "Primary vertex without two incoming particles"
822 " encountered in marley_hepmc3::print_event()" );
823
824 // Identify ejectile and residue from OUT particles.
825 HepMC3::ConstGenParticlePtr ejectile = nullptr;
826 HepMC3::ConstGenParticlePtr residue = nullptr;
827 if ( pv_out.size() == 2u ) {
828 const auto& pv_out1 = pv_out.front();
829 const auto& pv_out2 = pv_out.back();
830 // TODO: double-check that ES events look reasonable with this recipe
831 if ( pv_out1->status()
832 == marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS )
833 {
834 residue = pv_out1;
835 ejectile = pv_out2;
836 }
837 else {
838 ejectile = pv_out1;
839 residue = pv_out2;
840 }
841 }
842 else throw marley::Error( "Primary vertex without two outgoing particles"
843 " encountered in marley_hepmc3::print_event()" );
844
845 // 6-character chain tag
846 std::string tag = chain_tag_primary( proc_type, proj->pid() );
847
848 // Build primary chain line
849 std::ostringstream pline;
850 pline << " " << chain_particle_name( proj )
851 << " (" << std::fixed << std::setprecision( 2 )
852 << proj->momentum().e() << " MeV)";
853 pline << " + " << chain_particle_name( target );
854 // ──[<tag>]──►
855 pline << " " << THIN_CHAR << THIN_CHAR << "[" << tag
856 << "]" << THIN_CHAR << THIN_CHAR << ARROW_RIGHT << " ";
857 pline << chain_particle_name( ejectile );
858 pline << " + " << chain_particle_name( residue );
859
860 // Residue excitation bracket
861 {
862 double Ex = 0.; int twoJ = -1; int par = 1;
863 bool has_ex = get_double_attr( residue, "Ex", Ex );
864 get_int_attr( residue, "twoJ", twoJ );
865 get_int_attr( residue, "parity", par );
866 if ( has_ex && Ex > 0. ) {
867 pline << " [Ex=" << std::fixed << std::setprecision( 2 ) << Ex
868 << " MeV, " << format_jp( twoJ, par ) << "]";
869 }
870 }
871 os << pline.str() << "\n";
872
873 // De-excitation chain lines
874 for ( size_t vi = 1; vi < verts.size(); ++vi ) {
875 const auto& vtx = verts[vi];
876 int vstatus = vtx->status();
877 const auto& vin = vtx->particles_in();
878 const auto& vout = vtx->particles_out();
879
880 // Choose the decay tag (6 display chars)
881 // " γ " uses γ = U+03B3 = 0xCE 0xB3 (1 display char)
882 std::string dtag;
883 if ( vstatus == marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX ) {
884 dtag = " HF ";
885 }
886 else if ( vstatus == marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX ) {
887 dtag = " γ ";
888 }
889 else {
890 dtag = " ?? ";
891 }
892
893 // IN: first particle is the decaying residue
894 HepMC3::ConstGenParticlePtr dec_in = vin.front();
895
896 // OUT: light fragment + heavy daughter nucleus
897 HepMC3::ConstGenParticlePtr frag = nullptr;
898 HepMC3::ConstGenParticlePtr daughter = nullptr;
899 if ( vout.size() != 2u ) {
900 throw marley::Error( "Binary decay vertex does not have exactly"
901 " two outgoing particles" );
902 }
903
904 auto prod1 = vout.front();
905 auto prod2 = vout.back();
906
907 if ( prod1->generated_mass() <= prod2->generated_mass() ) {
908 frag = prod1;
909 daughter = prod2;
910 }
911 else {
912 frag = prod2;
913 daughter = prod1;
914 }
915
916 std::ostringstream dline;
917 dline << " ";
918 if ( dec_in ) dline << chain_particle_name( dec_in );
919 dline << " " << THIN_CHAR << THIN_CHAR << "[" << dtag
920 << "]" << THIN_CHAR << THIN_CHAR << ARROW_RIGHT << " ";
921 if ( frag ) dline << chain_particle_name( frag );
922 dline << " + ";
923 if ( daughter ) dline << chain_particle_name( daughter );
924
925 // Daughter excitation bracket or ground-state label
926 if ( daughter ) {
927 double Ex = 0.; int twoJ = -1; int par = 1;
928 bool has_ex = get_double_attr( daughter, "Ex", Ex );
929 get_int_attr( daughter, "twoJ", twoJ );
930 get_int_attr( daughter, "parity", par );
931 if ( has_ex && Ex > 0. ) {
932 dline << " [Ex=" << std::fixed << std::setprecision( 2 ) << Ex
933 << " MeV, " << format_jp( twoJ, par ) << "]";
934 } else if ( daughter->pid() > 1000000000 ) {
935 dline << " [ground state]";
936 }
937 }
938 os << dline.str() << "\n";
939 }
940 }
941
942 // Compute the gamma-ray multipolarity string (e.g., "E1", "M2") by
943 // inspecting the spin-parity attributes of the IN and OUT particles
944 // of a gamma-cascade vertex. Returns an empty string when the needed
945 // attributes are missing.
946 //
947 // The multipolarity ℓ is approximated as the lowest value allowed
948 // by angular momentum conservation: ℓ = max(1, |J_i - J_f|).
949 // The type (E or M) is determined from the parity rule:
950 // Electric if π_i = (-1)^ℓ × π_f, Magnetic otherwise.
951 std::string compute_gamma_multipolarity_string(
952 const HepMC3::ConstGenVertexPtr& vtx )
953 {
954 const auto& in_particles = vtx->particles_in();
955 if ( in_particles.empty() ) return {};
956
957 int twoJ_i = 0, Pi = 0;
958 if ( !get_int_attr( in_particles.front(), "twoJ", twoJ_i ) ) return {};
959 if ( !get_int_attr( in_particles.front(), "parity", Pi ) ) return {};
960
961 // Find the daughter nucleus (non-photon) among OUT particles
962 const auto& out_particles = vtx->particles_out();
963 HepMC3::ConstGenParticlePtr daughter = nullptr;
964 for ( const auto& p : out_particles ) {
965 if ( p->pid() != 22 ) { daughter = p; break; }
966 }
967 if ( !daughter ) return {};
968
969 int twoJ_f = 0, Pf = 0;
970 if ( !get_int_attr( daughter, "twoJ", twoJ_f ) ) return {};
971 if ( !get_int_attr( daughter, "parity", Pf ) ) return {};
972
973 // ℓ = max(1, |J_i - J_f|). The factor 1/2 converts twoJ to J.
974 int ell = std::abs( twoJ_i - twoJ_f ) / 2;
975 if ( ell < 1 ) ell = 1;
976
977 int phase = ( ell % 2 == 0 ) ? 1 : -1; // (-1)^ℓ
978 char type = ( Pi == phase * Pf ) ? 'E' : 'M';
979
980 return type + std::to_string( ell );
981 }
982
983 // Print the full detail block for one vertex.
984 void print_vertex_block( std::ostream& os,
985 const HepMC3::ConstGenVertexPtr& vtx, int vtx_index )
986 {
987 int vstatus = vtx->status();
988 bool is_deex = ( vstatus == marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX
989 || vstatus == marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX );
990 bool is_gamma = ( vstatus == marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX );
991
992 // ── Vertex header ─────────────────────────────────────────────────────
993 std::string type_name;
994 if ( vstatus == marley_hepmc3::NUHEPMC_PRIMARY_VERTEX )
995 type_name = "PRIMARY INTERACTION";
996 else if ( vstatus == marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX )
997 type_name = "HAUSER-FESHBACH";
998 else if ( vstatus == marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX )
999 type_name = "GAMMA CASCADE";
1000 else
1001 type_name = "VERTEX (status " + std::to_string( vstatus ) + ")";
1002
1003 std::ostringstream hdr;
1004 hdr << " [V" << vtx_index << "] " << type_name;
1005
1006 // Width attributes for Hauser-Feshbach decay vertices
1007 if ( vstatus == marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX ) {
1008 double width_tot = 0., width_ec = 0.;
1009 bool has_tot = get_double_attr_vtx( vtx, "TotalWidth", width_tot );
1010 bool has_ec = get_double_attr_vtx( vtx, "ECWidth", width_ec );
1011 if ( has_tot && has_ec ) {
1012 hdr << " Γ_ec = "
1013 << std::scientific << std::setprecision( 2 ) << width_ec
1014 << " MeV Γ_tot = "
1015 << std::scientific << std::setprecision( 2 ) << width_tot
1016 << " MeV";
1017 }
1018 }
1019 // Additional info for gamma-cascade vertices
1020 else if ( is_gamma ) {
1021 {
1022 std::string xl = compute_gamma_multipolarity_string( vtx );
1023 if ( !xl.empty() ) hdr << " " << xl;
1024 }
1025 double br = 0.;
1026 if ( get_double_attr_vtx( vtx, "GammaBranchingRatio", br ) ) {
1027 hdr << " BR = " << std::scientific << std::setprecision( 2 ) << br;
1028 }
1029 double width_tot = 0.;
1030 if ( get_double_attr_vtx( vtx, "TotalWidth", width_tot ) ) {
1031 hdr << " Γ_tot = "
1032 << std::scientific << std::setprecision( 2 ) << width_tot
1033 << " MeV";
1034 }
1035 }
1036 os << hdr.str() << "\n";
1037
1038 // Retrieve multipolarity if this is a gamma-cascade vertex (used by
1039 // print_particle_line for the photon momentum display)
1040 int multi = -1;
1041 if ( is_gamma ) get_int_attr_vtx( vtx, "multipolarity", multi );
1042
1043 // ── IN particles ──────────────────────────────────────────────────────
1044 os << make_thin_sep( THIN_CHAR + THIN_CHAR + " IN " ) << "\n";
1045 for ( const auto& p : vtx->particles_in() ) {
1046 print_particle_line( os, p,
1047 /*is_in_block=*/true, is_deex, is_gamma, multi );
1048 }
1049
1050 // ── OUT particles ─────────────────────────────────────────────────────
1051 os << make_thin_sep( THIN_CHAR + THIN_CHAR + " OUT " ) << "\n";
1052 for ( const auto& p : vtx->particles_out() ) {
1053 print_particle_line( os, p,
1054 /*is_in_block=*/false, is_deex, is_gamma, multi );
1055 }
1056 }
1057
1058} // end anonymous namespace (print_event helpers)
1059
1060void marley_hepmc3::print_event( const HepMC3::GenEvent& ev,
1061 std::ostream& os )
1062{
1063 // Retrieve the process type for the input event
1066
1067 auto attr = ev.attribute< HepMC3::IntAttribute >( "signal_process_id" );
1068 if ( attr ) proc_type = marley_hepmc3::from_nuhepmc_proc_id( attr->value() );
1069 std::string proc_name = marley::Reaction::proc_type_to_string( proc_type );
1070
1071 // Top thick separator
1072 os << THICK_SEP << "\n";
1073
1074 // Header line: " MARLEY │ Event #N │ <process>"
1075 os << " MARLEY " << BOX_VERT << " Event #" << ev.event_number()
1076 << " " << BOX_VERT << " " << proc_name << "\n";
1077
1078 // Thick separator
1079 os << THICK_SEP << "\n";
1080
1081 // INTERACTION CHAIN
1082 os << " INTERACTION CHAIN\n";
1083 print_interaction_chain( os, ev, proc_type );
1084
1085 // Vertex detail blocks
1086 int vtx_idx = 1;
1087 for ( const auto& vtx : ev.vertices() ) {
1088 os << THICK_SEP << "\n";
1089 print_vertex_block( os, vtx, vtx_idx++ );
1090 }
1091
1092 // ── FINAL STATE ───────────────────────────────────────────────────────
1093 os << THICK_SEP << "\n";
1094 os << " FINAL STATE\n";
1095 for ( const auto& p : ev.particles() ) {
1096 if ( p->status() == marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS ) {
1097 // Final-state particles are ground-state; show full momentum.
1098 // Re-use print_particle_line with is_in_block=false, is_deex=false.
1099 print_particle_line( os, p,
1100 /*is_in_block=*/false,
1101 /*is_deex_vtx=*/false,
1102 /*is_gamma_vtx=*/false,
1103 /*vtx_multipolarity=*/-1 );
1104 }
1105 }
1106
1107 // ── Cross-section footer ──────────────────────────────────────────────
1108 // σ = U+03C3 = 0xCF 0x83
1109 os << THICK_SEP << "\n";
1110 {
1111 auto proc_attr = ev.attribute< HepMC3::DoubleAttribute >( "proc_xs" );
1112 auto tot_attr = ev.attribute< HepMC3::DoubleAttribute >( "tot_xs" );
1113 double proc_xs = proc_attr ? proc_attr->value() : 0.;
1114 double tot_xs = tot_attr ? tot_attr->value() : 0.;
1115 os << " σ[" << proc_name << "] = "
1116 << std::scientific << std::setprecision( 3 ) << proc_xs
1117 << " pb σ[tot] = "
1118 << std::scientific << std::setprecision( 3 ) << tot_xs << " pb\n";
1119 }
1120
1121 // ── Bottom thick separator ────────────────────────────────────────────
1122 os << THICK_SEP << "\n";
1123}
Attribute that holds a real number as a double.
Definition Attribute.h:245
double value() const
get the value associated to this Attribute.
Definition Attribute.h:271
Generic 4-vector.
Definition FourVector.h:36
void set_t(double tt)
Set time component of position/displacement.
Definition FourVector.h:108
double px() const
x-component of momentum
Definition FourVector.h:114
double py() const
y-component of momentum
Definition FourVector.h:121
double pz() const
z-component of momentum
Definition FourVector.h:128
double e() const
Energy component of momentum.
Definition FourVector.h:135
void set_e(double ee)
Set energy component of momentum.
Definition FourVector.h:137
double length2() const
Squared magnitude of (x, y, z) 3-vector.
Definition FourVector.h:148
Stores event-related information.
Definition GenEvent.h:47
const std::vector< ConstGenParticlePtr > & particles() const
Get list of particles (const)
int event_number() const
Get event number.
Definition GenEvent.h:155
std::shared_ptr< T > attribute(const std::string &name, const int &id=0) const
Get attribute of type T.
Definition GenEvent.h:418
const std::vector< ConstGenVertexPtr > & vertices() const
Get list of vertices (const)
bool add_attribute(const std::string &name, std::shared_ptr< Attribute > att)
Add an attribute to this particle.
const FourVector & momentum() const
Get momentum.
Definition GenParticle.h:97
std::shared_ptr< T > attribute(const std::string &name) const
Get attribute of type T.
void set_generated_mass(double m)
Set generated mass.
ConstGenVertexPtr production_vertex() const
Get production vertex (const version)
int pid() const
Get PDG ID.
Definition GenParticle.h:94
void add_attribute(const std::string &name, const std::shared_ptr< Attribute > &att)
add an attribute This will overwrite existing attribute if an attribute with the same name is present
Definition GenRunInfo.h:102
const std::vector< std::string > & weight_names() const
Get the vector of weight names.
Definition GenRunInfo.h:89
std::shared_ptr< T > attribute(const std::string &name) const
Get attribute of type T.
Definition GenRunInfo.h:181
Attribute that holds an Integer implemented as an int.
Definition Attribute.h:157
int value() const
get the value associated to this Attribute.
Definition Attribute.h:180
std::string value() const
get the value associated to this Attribute.
Definition Attribute.h:370
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
The MARLEY Event generator.
Definition Generator.hh:54
double sample_decay_time(double partial_width)
Sample a random decay time given a partial decay width.
Definition Generator.cc:859
ProcessType
Enumerated type describing the kind of scattering process represented by a Reaction.
Definition Reaction.hh:58
@ NC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:66
@ NC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:62
@ Unknown
Dummy value used for error handling.
Definition Reaction.hh:59
@ StandaloneDecay
Standalone nuclear de-excitation with no simulated primary reaction (used by "marley decay")
Definition Reaction.hh:67
@ AntiNeutrinoCC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:61
@ NuElectronElastic
Neutrino-electron elastic scattering.
Definition Reaction.hh:63
@ NeutrinoCC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:60
@ AntiNeutrinoCC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:65
@ NeutrinoCC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:64