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
DecayScheme.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 <algorithm>
19#include <cmath>
20#include <fstream>
21#include <iomanip>
22#include <iostream>
23#include <regex>
24#include <string>
25#include <vector>
26
27// HepMC3 includes
28#include "HepMC3/GenEvent.h"
29#include "HepMC3/GenParticle.h"
30#include "HepMC3/GenVertex.h"
31
32// MARLEY includes
33#include "marley/hepmc3_utils.hh"
34#include "marley/marley_utils.hh"
35#include "marley/DecayScheme.hh"
36#include "marley/Generator.hh"
37#include "marley/marley_kinematics.hh"
38#include "marley/Logger.hh"
39#include "marley/HauserFeshbachDecay.hh"
40
41// Returns a pointer to the level owned by this decay scheme object
42// that has the closest excitation energy to E_level (E_level
43// has units of MeV).
45 double E_level)
46{
47 // If this decay scheme doesn't own any levels, return nullptr immediately
48 size_t num_levels = levels_.size();
49 if (num_levels == 0) return nullptr;
50
51 // Search for the level whose energy is closest to the given value of E_level
52 size_t e_index = level_lower_bound_index(E_level);
53
54 if (e_index == num_levels) {
55 // The given energy is greater than every level energy in our decay scheme.
56 // We will therefore assume that the desired level is the highest level.
57 // Its index is given by one less than the number of elements in the sorted
58 // vector, so subtract one from our previous result.
59 --e_index;
60 }
61 else if (e_index > 0) {
62 // If the calculated index does not correspond to the first element, we
63 // still need to check which of the two levels found (one on each side) is
64 // really the closest. Do so and reassign the index if needed.
65 if (std::abs(E_level - levels_.at(e_index)->energy())
66 > std::abs(E_level - levels_.at(e_index - 1)->energy()))
67 {
68 --e_index;
69 }
70 }
71
72 // Return a pointer to the selected level object
73 return levels_.at(e_index).get();
74}
75
77 return marley_utils::get_nucleus_pid( Z_, A_ );
78}
79
82 std::shared_ptr< HepMC3::GenParticle >& residue )
83{
84 int qIon = marley_hepmc3::get_particle_charge( *residue );
85
86 MARLEY_LOG( DEBUG, "physics.deexcitation.gamma" )
87 << "Beginning gamma cascade at level with energy "
88 << initial_level.energy() << " MeV";
89
90 marley::Level* p_current_level = &initial_level;
91
93
94 // Initialize some variables used in the gamma cascade loop below
95 bool cascade_finished = false;
96 double gamma_branching_ratio = 0.;
97 double level_total_width = 0.;
98
99 while ( !cascade_finished ) {
100
101 // Randomly select a gamma to produce while storing its branching ratio
102 const marley::Gamma* p_gamma = p_current_level->sample_gamma( gen,
103 &gamma_branching_ratio );
104
105 if ( !p_gamma ) {
106 MARLEY_LOG( DEBUG, "physics.deexcitation.gamma" )
107 << " this level does not have any gammas";
108 cascade_finished = true;
109 }
110 else {
111
112 // Get the total decay width (MeV) for the initial level
113 level_total_width = marley_utils::hbar * marley_utils::log_2
114 / p_current_level->half_life();
115
116 // Update the current level now that the gamma has been emitted
117 p_current_level = p_gamma->end_level();
118 if ( !p_current_level ) {
119 throw marley::Error( "This gamma does not have an end level."
120 " Cannot continue cascade." );
121 }
122 MARLEY_LOG( DEBUG, "physics.deexcitation.gamma" )
123 << std::setprecision(15) << std::scientific
124 << " emitted gamma with energy "
125 << p_gamma->energy() << " MeV. New level has energy "
126 << p_current_level->energy() << " MeV.";
127
128 // Get the excitation energy of the end level. This will be added to
129 // the ground state mass of the nucleus to determine its
130 // post-gamma-emission mass.
131 double Exf = p_current_level->energy();
132
133 // Create new particle objects to represent the emitted gamma and
134 // recoiling nucleus
135 auto gamma = marley_hepmc3::make_particle( marley_utils::PHOTON,
136 marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, 0.0 );
137
138 int pdg = marley_utils::get_nucleus_pid( Z_, A_ );
139
140 auto nucleus = marley_hepmc3::make_particle( pdg,
141 marley_hepmc3::NUHEPMC_INTERMEDIATE_RESIDUE_STATUS,
142 mt.get_atomic_mass(pdg) + Exf
143 - qIon*mt.get_particle_mass(marley_utils::ELECTRON) );
144
145 // Create a new binary decay vertex
146 auto decay_vtx = std::make_shared< HepMC3::GenVertex >();
147 decay_vtx->set_status( marley_hepmc3::NUHEPMC_GAMMA_DECAY_VERTEX );
148
149 decay_vtx->add_particle_in( residue );
150 decay_vtx->add_particle_out( gamma );
151 decay_vtx->add_particle_out( nucleus );
152
153 // Sample a decay time (MeV^{-1}) for emission of the chosen gamma-ray
154 // and store this timing information in the new binary decay vertex
155 double gamma_partial_width = gamma_branching_ratio * level_total_width;
156 marley_hepmc3::store_decay_time( gamma_partial_width, gen, decay_vtx,
157 residue );
158
159 // Add the decay vertex to the event record
160 event.add_vertex( decay_vtx );
161
162 // We can set the charge attribute now that the daughter nucleus
163 // belongs to the decay vertex (and thus the parent event)
164 marley_hepmc3::set_particle_charge( *nucleus, qIon );
165
166 // We can also now set the attributes representing the
167 // excitation energy, spin, and parity of the daughter nucleus
168 nucleus->add_attribute( "Ex",
169 std::make_shared< HepMC3::DoubleAttribute >(Exf) );
170 nucleus->add_attribute( "twoJ",
171 std::make_shared< HepMC3::IntAttribute >( p_current_level->twoJ() )
172 );
173 nucleus->add_attribute( "parity", std::make_shared< HepMC3::IntAttribute >(
174 static_cast< int >(p_current_level->parity()) )
175 );
176
177 // If the total decay width is finite (equivalently, there is a tabulated
178 // value for the level half-life), then store it in an attribute
179 // attached to the vertex
180 if ( std::isfinite(level_total_width) ) {
181 decay_vtx->add_attribute( "TotalWidth",
182 std::make_shared<HepMC3::DoubleAttribute>(level_total_width)
183 );
184 }
185
186 // Also store the selected gamma-ray's branching ratio
187 decay_vtx->add_attribute( "GammaBranchingRatio",
188 std::make_shared< HepMC3::DoubleAttribute>( gamma_branching_ratio )
189 );
190
191 // Sample a direction assuming that the gammas are emitted isotropically
192 // in the nucleus's rest frame.
193 // sample from [-1, 1]
194 double gamma_cos_theta = gen.uniform_random_double( -1.0, 1.0, true );
195 // sample from [0, 2*pi)
196 double gamma_phi = gen.uniform_random_double( 0., 2.*marley_utils::pi,
197 false );
198
199 MARLEY_LOG( TRACE, "physics.deexcitation.gamma" )
200 << " sampled gamma direction: cos_theta = " << gamma_cos_theta
201 << ", phi = " << gamma_phi << " rad";
202
203 // Determine the final energies and momenta for the recoiling nucleus and
204 // emitted gamma ray. Store them in the final state particle objects.
205 marley_kinematics::two_body_decay( residue, gamma, nucleus,
206 gamma_cos_theta, gamma_phi );
207
208 // Update the residue for this event to take into account changes from
209 // gamma ray emission
210 residue.swap( nucleus );
211 }
212 }
213
214 MARLEY_LOG( DEBUG, "physics.deexcitation.gamma" )
215 << "Finished gamma cascade at level with energy "
216 << p_current_level->energy();
217
218 residue->set_status( marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS );
219}
220
221marley::DecayScheme::DecayScheme( int Z, int A ) : Z_( Z ), A_( A )
222{
223}
224
225marley::DecayScheme::DecayScheme( int Z, int A, const std::string& filename,
227{
228 this->parse( filename, ff );
229}
230
231void marley::DecayScheme::parse_talys( const std::string& filename ) {
232 // First line in a TALYS level dataset has fortran
233 // format (2i4, 2i5, 56x, i4, a2)
234 // General regex for this line:
235 // std::regex nuclide_line("[0-9 ]{18} {56}[0-9 ]{4}.{2}");
236 std::string nuc_id = marley_utils::nuc_id( Z_, A_ );
237
238 // Make the last character of the nuc_id lowercase to follow the TALYS
239 // convention
240 nuc_id.back() = tolower( nuc_id.back() );
241
242 const std::regex nuclide_line( "[0-9 ]{18} {57}" + nuc_id );
243
244 // Open the TALYs level data file for parsing
245 std::ifstream file_in( filename );
246
247 // If the file doesn't exist or some other error
248 // occurred, complain and give up.
249 if ( !file_in.good() ) throw marley::Error( "Could not read from the"
250 " TALYS data file " + filename );
251
252 // String to store the current line of the TALYS file during parsing
253 std::string line;
254 bool found_decay_scheme = false;
255
256 while ( std::getline(file_in, line) ) {
257 if ( std::regex_match(line, nuclide_line) ) {
258 found_decay_scheme = true;
259 break;
260 }
261 }
262
263 if ( !found_decay_scheme ) throw marley::Error( "Gamma decay scheme data"
264 " (adopted levels, gammas) for " + marley_utils::nucid_to_symbol( nuc_id )
265 + " could not be found in the TALYS data file " + filename );
266
267 MARLEY_LOG( DEBUG, "init.structure.decay" )
268 << "Gamma decay scheme data for " + nuc_id
269 << " found. Using TALYS dataset ";
270 MARLEY_LOG( DEBUG, "init.structure.decay" ) << line;
271
272 // Dummy integer and number of excited levels for this nuclide
273 int dummy, num_excited_levels;
274
275 // Read in the number of excited levels from the first line of data
276 std::istringstream iss( line );
277 iss >> dummy >> dummy >> dummy >> num_excited_levels;
278
279 for ( int l_idx = 0; l_idx <= num_excited_levels; ++l_idx ) {
280
281 // Get the next line of the file. This will be a discrete level record
282 std::getline( file_in, line );
283
284 // Load the new line into our istringstream object for parsing. Reset
285 // the stream so that we start parsing from the beginning of the string.
286 iss.str( line );
287 iss.clear();
288
289 // Read in this level's index, energy, spin, parity, and
290 // number of gamma transitions
291 int level_num, pi, num_gammas;
292 double level_energy, spin, half_life;
293 iss >> level_num >> level_energy >> spin >> pi >> num_gammas >> half_life;
294
295 // Compute two times the spin so that we can represent half-integer
296 // nuclear level spins as integers
297 int twoJ = std::round( 2 * spin );
298
299 // Create a parity object to use when constructing the level
300 marley::Parity parity = marley::Parity( pi );
301
302 // Construct a new level object and add it to the decay scheme. Get
303 // a pointer to the newly-added level
304 marley::Level& current_level = add_level( marley::Level(level_energy,
305 twoJ, parity, half_life) );
306
307 for ( int g_idx = 0; g_idx < num_gammas; ++g_idx ) {
308
309 // Get the next line of the file. This will be a gamma record
310 std::getline( file_in, line );
311
312 // Load the new line into our istringstream object for parsing. Reset
313 // the stream so that we start parsing from the beginning of the string.
314 iss.str( line );
315 iss.clear();
316
317 // Read in the index of the final level and branching ratio
318 // for this gamma transition
319 int gamma_final_level_num;
320 double br;
321 iss >> gamma_final_level_num >> br;
322
323 // Process this gamma if it has a nonvanishing branching ratio
324 if ( br > 0. ) {
325
326 marley::Level* final_level = levels_.at( gamma_final_level_num ).get();
327
328 // Compute the gamma ray's energy in MeV by subtracting the energy
329 // of the final level from the energy of the initial level
330 double gamma_energy = level_energy - final_level->energy();
331
332 // Create the new Gamma object for the current level
333 current_level.add_gamma( gamma_energy, br, final_level );
334 }
335 }
336 }
337
338 file_in.close();
339}
340
341void marley::DecayScheme::print_report( std::ostream& ostr ) const {
342 // Cycle through each of the levels owned by this decay scheme
343 // object in order of increasing energy
344 for ( const auto& lev : levels_ ) {
345 int twoj = lev->twoJ();
346 std::string spin = std::to_string( twoj / 2 );
347 // If 2*J is odd, then the level has half-integer spin
348 if ( twoj % 2 ) spin += "/2";
349 marley::Parity parity = lev->parity();
350
351 ostr << "Level at " << lev->energy() << " MeV has spin-parity "
352 << spin << parity << " and half-life " << lev->half_life() << " s\n";
353
354 std::vector< marley::Gamma >& gammas = lev->gammas();
355
356 // Cycle through each of the gammas owned by the current level
357 // (according to the ENSDF specification, these will already be
358 // sorted in order of increasing energy)
359 for ( const auto& g : gammas ) {
360 ostr << " has a gamma with energy " << g.energy() << " MeV";
361 ostr << " (transition to level at "
362 << g.end_level()->energy() << " MeV)" << '\n';
363 ostr << " and relative intensity " << g.relative_intensity() << '\n';
364 }
365 }
366}
367
368void marley::DecayScheme::print_latex_table( std::ostream& ostr ) {
369
370 std::string nuc_id = marley_utils::nuc_id( Z_, A_ );
371
372 std::string caption_beginning =
373 std::string("{\\textbf{Levels") +
374 " and $\\boldsymbol{\\gamma}$ transitions \n for " +
375 "\\isotope[\\boldsymbol{" +
376 marley_utils::trim_copy(nuc_id.substr(0,3)) +
377 "}]{\\textbf{" + nuc_id.substr(3,1) +
378 marley_utils::trim_copy(
379 marley_utils::to_lowercase(nuc_id.substr(4,1))) +
380 "}} \n";// from file " + filename + " ";
381
382 ostr << marley_utils::latex_table_1;
383
384 ostr << caption_beginning + "}}\\\\\n";
385
386 ostr << marley_utils::latex_table_2;
387
388 ostr << caption_beginning + " -- \\textit{continued}}} \\\\\n";
389
390 ostr << marley_utils::latex_table_3;
391
392 // Cycle through each of the levels owned by this decay scheme
393 // object in order of increasing energy
394 for (const auto& lev : levels_ ) {
395
396 std::string sp = lev->spin_parity_string();
397
398 ostr << lev->energy() << " & " << sp << " & ";
399
400 const auto& gammas = lev->gammas();
401
402 // If there aren't any gammas for this level, finish writing
403 // the current row of the table. Add extra space between this
404 // level and the next one.
405 if ( gammas.empty() ) {
406 ostr << " & &";
407 // If this is the last row of the table, don't add extra space.
408 if (lev == levels_.back()) ostr << '\n';
409 else ostr << " \\\\ \\addlinespace[\\ExtraRowSpace]\n";
410 }
411
412 // Cycle through each of the gammas owned by the current level
413 for ( const auto& g : gammas ) {
414 // If this is not the first gamma, add empty columns
415 // for the level energy and spin-parity
416 if ( &g != &gammas.front() ) ostr << " & & ";
417 // Output information about the current gamma
418 ostr << g.energy() << " & " << g.relative_intensity()
419 << " & " << g.end_level()->energy();
420 // Add vertical space after the final gamma row. Also prevent page breaks
421 // in the middle of a list of gammas by outputting a star at the end of
422 // each row except the final gamma row.
423 if ( &g == &gammas.back() ) {
424 // Don't add the extra row space for the very last row in the table
425 if ( lev == levels_.back() ) ostr << '\n';
426 else ostr << " \\\\ \\addlinespace[\\ExtraRowSpace]" << '\n';
427 }
428 else ostr << " \\\\*" << '\n';
429 }
430 }
431 ostr << marley_utils::latex_table_4 << '\n';
432}
433
434// Finds the index for the first level with excitation energy not less than Ex
436 const auto E_begin = marley::Level::make_energy_iterator( levels_.cbegin() );
437 const auto E_end = marley::Level::make_energy_iterator( levels_.cend() );
438
439 const auto closest_E_iter = std::lower_bound( E_begin, E_end, Ex );
440 return std::distance( E_begin, closest_E_iter );
441}
442
443// Adds a new level to the decay scheme and returns a reference to it
445{
446 // Compute the numerical index for where we will insert the new level
447 size_t index = level_lower_bound_index( level.energy() );
448
449 // Insert the new level into the decay scheme
450 levels_.insert( levels_.begin() + index,
451 std::make_unique< marley::Level >(level) );
452
453 // Return a reference to the newly-added level
454 return *levels_.at( index );
455}
456
457void marley::DecayScheme::print( std::ostream& out ) const {
458
459 size_t num_levels = levels_.size();
460
461 out << Z_ << ' ' << A_ << ' ' << num_levels << '\n';
462
463 for ( const auto& lev : levels_ ) {
464 out << " " << lev->energy() << ' ' << lev->twoJ() << ' '
465 << lev->parity() << ' ' << lev->gammas().size() << '\n';
466 for ( const auto& g : lev->gammas() ) {
467 out << " " << g.energy() << ' ' << g.relative_intensity();
468
469 const auto cit = std::find_if( levels_.cbegin(), levels_.cend(),
470 [&g]( const std::unique_ptr< marley::Level >& l )
471 -> bool { return l.get() == g.end_level(); } );
472
473 int level_f_idx = -1;
474 if ( cit != levels_.cend() ) {
475 level_f_idx = std::distance(levels_.cbegin(), cit);
476 }
477 out << " " << level_f_idx << '\n';
478 }
479 }
480}
481
483
484 levels_.clear();
485
486 int num_levels;
487 in >> Z_ >> A_ >> num_levels;
488
489 // If we had trouble parsing the decay scheme header, then
490 // just return the stream without doing anything else.
491 if ( !in ) return;
492
493 double energy, ri, half_life;
494 int two_j, num_gammas, level_f_idx;
496
497 for ( int i = 0; i < num_levels; ++i ) {
498 in >> energy >> two_j >> pi >> num_gammas >> half_life;
499
500 marley::Level& l = add_level( marley::Level(energy, two_j, pi, half_life) );
501 for ( int j = 0; j < num_gammas; ++j ) {
502 in >> energy >> ri >> level_f_idx;
503 l.add_gamma( energy, ri, levels_.at(level_f_idx).get() );
504 }
505 }
506
507 // Remove levels above the unbound threshold (these will always be
508 // handled using a continuous level density treatment)
509 const auto& mt = marley::MassTable::Instance();
510 double unbound_Ex = mt.unbound_threshold( Z_, A_ );
511
512 // TODO: replace with std::erase_if when updating to C++20
513 auto iter_new_end = std::remove_if( levels_.begin(), levels_.end(),
514 [ unbound_Ex ]( const std::unique_ptr< marley::Level >& lev ) -> bool {
515 double lvl_Ex = lev->energy();
516 bool unbound = ( lvl_Ex > unbound_Ex );
517 return unbound;
518 }
519 );
520 levels_.erase( iter_new_end, levels_.end() );
521
522}
523
524void marley::DecayScheme::parse( const std::string& filename,
526{
527 // Parse the data file using the appropriate format
528 switch (ff) {
529
530 case FileFormat::native:
531 this->parse_native( filename );
532 break;
533
534 case FileFormat::talys:
535 this->parse_talys( filename );
536 break;
537
538 // Add more data file formats as needed
539
540 default:
541 throw marley::Error( "Unsupported file format passed to"
542 " marley::DecayScheme constructor." );
543 }
544
545}
546
547void marley::DecayScheme::parse_native( const std::string& filename ) {
548
549 // Open the level data file for parsing
550 std::ifstream file_in( filename );
551
552 // If the file doesn't exist or some other error
553 // occurred, complain and give up.
554 if ( !file_in.good() ) throw marley::Error( "Could not read from the"
555 " data file " + filename );
556
557 read_from_stream( file_in );
558
559 file_in.close();
560}
Stores event-related information.
Definition GenEvent.h:47
void set_status(int status)
Set status code.
void read_from_stream(std::istream &in)
Use a std::istream to initialize this DecayScheme object, replacing any previous data.
int pdg() const
Returns the nuclear PDG code corresponding to Z and A.
std::vector< std::unique_ptr< marley::Level > > levels_
Level objects owned by this DecayScheme.
int Z_
Atomic number.
marley::Level & add_level(const marley::Level &level)
Add a level to the DecayScheme.
void print_latex_table(std::ostream &ostr=std::cout)
Print LaTeX source code that gives a tabular representation of the DecayScheme object.
void do_cascade(marley::Level &initial_level, HepMC3::GenEvent &event, marley::Generator &gen, std::shared_ptr< HepMC3::GenParticle > &residue)
Simulates nuclear de-excitation via γ-ray emission(s)
int A_
Mass number.
size_t level_lower_bound_index(double Ex)
Get the index of the first level whose energy is not less than Ex.
FileFormat
The FileFormat type is used to tell the DecayScheme class which format to assume when parsing a discr...
int A() const
Get the mass number.
int Z() const
Get the atomic number.
void print(std::ostream &out=std::cout) const
Print this DecayScheme object to a std::ostream.
void print_report(std::ostream &ostr=std::cout) const
Print a human-readable text representation of the DecayScheme object.
marley::Level * get_pointer_to_closest_level(double E_level)
Gets a pointer to the Level in the DecayScheme whose excitation energy is closest to E_level.
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
A gamma-ray transition between two nuclear levels.
Definition Gamma.hh:25
marley::Level * end_level() const
Get a pointer to the Level that absorbs this γ-ray.
Definition Gamma.hh:80
double energy() const
Get the energy of the emitted γ-ray (MeV)
Definition Gamma.hh:84
The MARLEY Event generator.
Definition Generator.hh:54
double uniform_random_double(double min, double max, bool inclusive)
Sample a random number uniformly on either [min, max) or [min, max].
Definition Generator.cc:235
A discrete nuclear energy level.
Definition Level.hh:29
static marley::IteratorToPointerMember< It, double > make_energy_iterator(It it)
Convert an iterator that points to a marley::Level* (or a smart pointer to a marley::Level) into an i...
Definition Level.hh:155
double half_life() const
Get the level half-life (s)
Definition Level.hh:144
marley::Gamma & add_gamma(const marley::Gamma &gamma)
Add a new gamma-ray transition to this level.
Definition Level.cc:55
const marley::Gamma * sample_gamma(marley::Generator &gen, double *prob_ptr=nullptr)
Choose a gamma owned by this level randomly based on the relative intensities of all of the gammas.
Definition Level.cc:30
marley::Parity parity() const
Get the level parity.
Definition Level.hh:141
int twoJ() const
Get two times the level spin.
Definition Level.hh:138
double energy() const
Get the excitation energy of this level (MeV)
Definition Level.hh:135
Singleton lookup table for particle and atomic masses.
Definition MassTable.hh:30
static const MassTable & Instance()
Get a const reference to the singleton instance of the MassTable.
Definition MassTable.cc:69
double get_atomic_mass(int pdg_code, bool theory_ok=true) const
Get the mass of an atom.
Definition MassTable.cc:95
double get_particle_mass(int pdg_code) const
Get the mass of a particle.
Definition MassTable.cc:84
Type-safe representation of a parity value (either +1 or -1)
Definition Parity.hh:25