24#include "HepMC3/Attribute.h"
25#include "HepMC3/GenEvent.h"
26#include "HepMC3/GenRunInfo.h"
29#include "marley/marley_utils.hh"
30#include "marley/hepmc3_utils.hh"
31#include "marley/ChebyshevInterpolatingFunction.hh"
32#include "marley/Error.hh"
33#include "marley/Generator.hh"
34#include "marley/JSON.hh"
35#include "marley/Logger.hh"
36#include "marley/NucleusDecayer.hh"
37#include "marley/Reaction.hh"
38#include "marley/StructureDatabase.hh"
39#include "marley/Weighter.hh"
46 : seed_( std::chrono::system_clock::now().time_since_epoch().count() ),
65void marley::Generator::print_logo() {
66 static bool printed_logo =
false;
67 if ( !printed_logo ) {
68 MARLEY_LOG( NOTICE,
"physics.generator" ) <<
'\n' << marley_utils::marley_logo
69 <<
"\nDon't worry about a thing,\n'Cause every little thing"
70 <<
" gonna be all right.\n-- Bob, \"Three Little Birds\"\n\n"
71 <<
"Model of Argon Reaction Low Energy Yields\n"
72 <<
"version " << MARLEY_VERSION <<
'\n';
92 int pdg_a = source_->get_pid();
93 std::shared_ptr< HepMC3::GenEvent > ev = r.
create_event( pdg_a, E_nu, *
this );
102 for (
const auto& temp_r : reactions_ ) {
109 if ( temp_r->pdg_a() != r.
pdg_a() )
continue;
111 double temp_xsec = temp_r->total_xs( pdg_a, E_nu );
112 if ( temp_xsec > 0. ) {
119 totXS *= marley_utils::hbar_c2 * marley_utils::fm2_to_picobarn;
122 std::make_shared< HepMC3::DoubleAttribute >( totXS )
125 double procXS = r.
total_xs( pdg_a, E_nu ) * marley_utils::hbar_c2
126 * marley_utils::fm2_to_picobarn;
129 std::make_shared< HepMC3::DoubleAttribute >( procXS )
133 if ( do_deexcitations_ ) {
139 rotator_.process_event( *ev, *
this );
149 const std::string& state_string)
152 std::stringstream strstr( state_string );
161 std::seed_seq seed_sequence{ seed_ };
162 rand_gen_.seed( seed_sequence );
164 MARLEY_LOG( NOTICE,
"physics.generator" ) <<
"Seeded random number generator with "
169 std::stringstream ss;
174void marley::Generator::normalize_E_pdf() {
179 if ( dont_normalize_E_pdf_ )
return;
184 E_pdf_max_ = E_PDF_MAX_DEFAULT_;
188 if ( source_->get_Emin() == source_->get_Emax() ) {
195 norm_ = E_pdf( source_->get_Emin() );
196 if ( norm_ <= 0. || std::isnan(norm_) ) {
197 throw marley::Error(
"The total cross section for all defined reactions"
198 " is <= 0 or NaN for the neutrino energy defined in a monoenergetic"
199 " source. Please verify that your neutrino source produces particles"
200 " above threshold for at least one reaction.");
202 MARLEY_LOG( DEBUG,
"physics.generator" ) <<
"Energy PDF normalization"
203 " factor = " << norm_;
215 norm_ = marley_utils::num_integrate( [
this](
double E)
216 ->
double {
return this->E_pdf(E); }, source_->get_Emin(),
217 source_->get_Emax() );
219 if ( norm_ <= 0. || std::isnan(norm_) ) {
220 throw marley::Error(
"The integral of the cross-section-weighted"
221 " neutrino flux is <= 0 or NaN. Please verify that your neutrino"
222 " source spectrum produces significant flux above the reaction"
225 MARLEY_LOG( DEBUG,
"physics.generator" ) <<
"Energy PDF normalization"
226 " factor = " << norm_;
242 static std::uniform_real_distribution<double> udist;
251 max_to_use = std::nextafter( max, std::numeric_limits<double>::max() );
257 std::uniform_real_distribution<double>::param_type params( min, max_to_use );
260 return udist( rand_gen_, params );
282 const std::function<
double(
double)>& f,
double xmin,
double xmax,
283 double& fmax,
double safety_factor,
double max_search_tolerance )
288 if ( fmax == marley_utils::UNKNOWN_MAX ) {
297 fmax = marley_utils::maximize( f, xmin, xmax, max_search_tolerance,
298 x_at_max ) * safety_factor;
301 MARLEY_LOG( TRACE,
"physics.generator.sampling" ) <<
"rejection_sample:"
302 " xmin = " << xmin <<
", xmax = " << xmax <<
", initial fmax = " << fmax;
314 MARLEY_LOG( TRACE,
"physics.generator.sampling" ) <<
"rejection_sample:"
315 " trial x = " << x <<
", y = " << y <<
", f(x) = " << val;
318 MARLEY_LOG( WARN,
"physics.generator.sampling" ) <<
"PDF value f(x) = "
319 << val <<
" at x = " << x <<
" exceeded the estimated maximum"
320 <<
" fmax = " << fmax <<
" during rejection sampling.";
322 fmax = val * safety_factor;
323 MARLEY_LOG( WARN,
"physics.generator.sampling" )
324 <<
"A new estimate fmax = " << val * safety_factor
325 <<
" will now be adopted.";
343 for (
size_t j = 0, s = reactions_.size(); j < s; ++j ) {
346 const auto& react = reactions_.at( j );
350 double tot_xs = react->total_xs( source_->get_pid(), E );
358 tot_xs *= target_->atom_fraction( react->atomic_target() );
363 total_xs_values_.at( j ) = tot_xs;
371 if ( weight_flux_ ) {
375 pdf *= source_->pdf( E );
381 if ( pdf <= 0. )
return 0.;
382 pdf = source_->pdf( E );
391 if ( reactions_.empty() )
throw marley::Error(
"Cannot sample"
392 " a reaction in marley::Generator::sample_reaction(). The vector of"
393 " marley::Reaction objects owned by this generator is empty." );
397 double old_max = E_pdf_max_;
402 ->
double {
return this->
E_pdf(E_nu); }, source_->get_Emin(),
403 source_->get_Emax(), E_pdf_max_ );
410 static bool issued_long_error_message =
false;
411 if ( old_max != marley_utils::UNKNOWN_MAX
412 && old_max != E_pdf_max_ )
414 if ( !issued_long_error_message ) {
415 MARLEY_LOG( ERROR,
"physics.generator.sampling" )
416 <<
"Estimation of the maximum PDF value failed when"
417 <<
" using a rejection method to sample reacting neutrino energies.\n"
418 <<
"This may occur when, e.g., an incident neutrino flux"
419 <<
" is used that includes multiple sharp peaks.\n"
420 <<
"To avoid biasing the energy distribution, please rerun the"
421 <<
" simulation after adding the following line to the MARLEY job"
422 <<
" configuration file:\n"
423 <<
" energy_pdf_max: " << E_pdf_max_ <<
",\n"
424 <<
"If this error message persists after raising energy_pdf_max to a"
425 <<
" relatively high value, please contact the MARLEY developers for"
426 <<
" troubleshooting help.";
427 issued_long_error_message =
true;
430 MARLEY_LOG( ERROR,
"physics.generator.sampling" )
431 <<
"The maximum PDF value for sampling reacting"
432 <<
" neutrino energies was exceeded again. The new estimated maximum is"
433 <<
"\n energy_pdf_max: " << E_pdf_max_ <<
',';
440 std::discrete_distribution<size_t>::param_type
441 params( total_xs_values_.begin(), total_xs_values_.end() );
442 size_t r_index = r_index_dist_( rand_gen_, params );
443 auto& sampled_reaction = *reactions_.at( r_index );
444 MARLEY_LOG( DEBUG,
"physics.generator" ) <<
"Sampled reaction: "
445 << sampled_reaction.get_description() <<
" at E_nu = " << E <<
" MeV";
446 return sampled_reaction;
450 if ( source_ )
return *source_;
451 else throw marley::Error(
"Error in marley::Generator::get_source()."
452 " The member variable source_ == nullptr." );
456 if ( target_ )
return *target_;
457 else throw marley::Error(
"Error in marley::Generator::get_target()."
458 " The member variable target_ == nullptr." );
462 std::unique_ptr<marley::NeutrinoSource> source )
468 source_.reset( source.release() );
471 if ( reactions_.empty() )
return;
475 this->normalize_E_pdf();
480 std::unique_ptr<marley::Reaction> reaction )
487 reactions_.push_back( std::move(reaction) );
490 total_xs_values_.push_back( 0. );
504 total_xs_values_.clear();
511 if ( structure_db_ )
return *structure_db_;
512 else throw marley::Error(
"Error in marley::Generator::get_structure_db()."
513 " The member variable structure_db_ == nullptr." );
517 const std::array<double, 3>& dir_vec )
519 rotator_.set_projectile_direction( dir_vec );
521 const auto& normalized_dir_vec = rotator_.projectile_direction();
524 std::string dir_msg(
"Incident neutrino direction: (");
525 for (
size_t i = 0; i < 3; ++i ) {
526 dir_msg += std::to_string( normalized_dir_vec[i] );
527 if ( i < 2 ) dir_msg +=
", ";
529 MARLEY_LOG( INFO,
"physics.generator" ) << dir_msg <<
')';
533 weight_flux_ = should_we_weight;
537 const std::function<
double(
double)>& f,
double xmin,
double xmax,
538 double bisection_tolerance )
544 DEFAULT_N_CHEBYSHEV);
545 auto cdf = func.cdf();
554 double bisection_tolerance )
560 if ( prob == 0. )
return xmin;
561 else if ( prob == 1. )
return xmax;
571 while ( (b - a) > bisection_tolerance ) {
572 double midpoint = (a + b) / 2.;
573 double mid_cdf = cdf.
evaluate( midpoint ) / norm;
576 if ( mid_cdf == prob )
return midpoint;
578 else if ( mid_cdf > prob ) b = midpoint;
583 double x = (a + b) / 2.;
590 if ( !weight_flux_ )
return 0.;
592 double avg_total_xs = 0.;
596 double Emin = source_->get_Emin();
597 double Emax = source_->get_Emax();
598 if ( Emin == Emax ) {
599 avg_total_xs = norm_ / source_->pdf( Emin );
602 double source_norm = marley_utils::num_integrate(
603 [
this](
double Ev) ->
double {
return this->source_->pdf(Ev); },
607 avg_total_xs = norm_ / source_norm;
618 target_.reset( target.release() );
621 if ( reactions_.empty() )
return;
625 this->normalize_E_pdf();
631 return this->
total_xs( pdg_a, KEa, pdg_atom,
nullptr,
nullptr );
635 std::vector<size_t>* index_vec, std::vector<double>* xsec_vec )
const
637 double xsec_sum = 0.;
639 if ( index_vec ) index_vec->clear();
640 if ( xsec_vec ) xsec_vec->clear();
642 for (
size_t j = 0u; j < reactions_.size(); ++j ) {
644 const auto& r = reactions_.at( j );
647 if ( pdg_atom != r->atomic_target().pdg() )
continue;
650 if ( pdg_a != r->pdg_a() )
continue;
654 double xsec = r->total_xs( pdg_a, KEa );
657 if ( index_vec ) index_vec->push_back( j );
658 if ( xsec_vec ) xsec_vec->push_back( xsec );
668 int pdg_a,
double KEa,
int pdg_atom,
const std::array<double, 3>& dir_vec,
678 std::vector<size_t> indices;
679 std::vector<double> xsecs;
680 double tot_xsec = this->
total_xs( pdg_a, KEa, pdg_atom, &indices, &xsecs );
683 "Cannot create an event for a projectile with kinetic energy = "
684 + std::to_string(KEa) +
" MeV and PDG code " + std::to_string(pdg_a)
685 +
" striking an atom with PDG code " + std::to_string(pdg_atom)
686 +
". The total cross section vanishes for all configured reactions" );
691 std::discrete_distribution< size_t > react_dist( xsecs.begin(), xsecs.end() );
692 size_t sampled_index = react_dist( rand_gen_ );
693 auto& r = reactions_.at( indices.at(sampled_index) );
697 std::shared_ptr< HepMC3::GenEvent > ev = r->create_event( pdg_a, KEa, *
this );
701 double totXS = tot_xsec * marley_utils::hbar_c2
702 * marley_utils::fm2_to_picobarn;
705 std::make_shared< HepMC3::DoubleAttribute >( totXS )
708 double procXS = xsecs.at( sampled_index ) * marley_utils::hbar_c2
709 * marley_utils::fm2_to_picobarn;
712 std::make_shared< HepMC3::DoubleAttribute >( procXS )
718 if ( do_deexcitations_ ) {
726 my_rotator.set_projectile_direction( dir_vec );
743 double tot_xsec = 0.;
747 for (
const auto& react : reactions_ ) {
751 double xsec = react->total_xs( pdg_a, KEa );
759 xsec *= target_->atom_fraction( react->atomic_target() );
772 run_info_ = std::make_shared< HepMC3::GenRunInfo >();
775 run_info_->add_attribute(
"NuHepMC.Version.Major",
776 std::make_shared< HepMC3::IntAttribute >(
777 marley_hepmc3::NUHEPMC_MAJOR_VERSION )
780 run_info_->add_attribute(
"NuHepMC.Version.Minor",
781 std::make_shared< HepMC3::IntAttribute >(
782 marley_hepmc3::NUHEPMC_MINOR_VERSION )
785 run_info_->add_attribute(
"NuHepMC.Version.Patch",
786 std::make_shared< HepMC3::IntAttribute >(
787 marley_hepmc3::NUHEPMC_PATCH_VERSION )
791 run_info_->tools().emplace_back(
793 MARLEY_GIT_REVISION }
797 marley_hepmc3::prepare_process_metadata( *run_info_ );
800 marley_hepmc3::prepare_vertex_status_metadata( *run_info_ );
803 marley_hepmc3::prepare_particle_status_metadata( *run_info_ );
806 const auto& wgt_names = weighter_->get_weight_names();
807 run_info_->set_weight_names( wgt_names );
810 marley_hepmc3::prepare_non_standard_pdg_code_metadata( *run_info_ );
814 marley_hepmc3::apply_nuhepmc_runinfo_conventions( *run_info_, avg_xsec );
817 run_info_->add_attribute(
"MARLEY.RNGseed",
818 std::make_shared< HepMC3::StringAttribute >( std::to_string(seed_) )
821 run_info_->add_attribute(
"MARLEY.JSONconfig",
822 std::make_shared< HepMC3::StringAttribute >( json_config_ )
834 weighter_->process_event( ev, *
this );
843 const std::vector< double > lab_pos = { 0., 0., 0. };
845 std::make_shared< HepMC3::VectorDoubleAttribute >( lab_pos )
850void marley::Generator::set_json_config(
const marley::JSON& jc ) {
851 json_config_ = jc.dump_string();
855 event.set_run_info( run_info_ );
860 if ( partial_width <= 0. )
throw marley::Error(
"Non-positive partial decay"
861 " width passed to marley::Generator::sample_decay_time()" );
864 double tau = 1. / partial_width;
870 double min_to_use = std::nextafter( 0., std::numeric_limits<double>::max() );
876 double t = -tau * std::log( r );
891 const std::string& state )
894 std::make_shared< HepMC3::StringAttribute >( state )
Stores event-related information.
void add_attribute(const std::string &name, const std::shared_ptr< Attribute > &att, const int &id=0)
Approximates a 1D function using Chebyshev points.
Base class for all exceptions thrown by MARLEY functions.
void clear_reactions()
Clear the vector of Reaction objects owned by this Generator.
void set_source(std::unique_ptr< marley::NeutrinoSource > source)
Take ownership of a new NeutrinoSource, replacing any existing source owned by this Generator.
double uniform_random_double(double min, double max, bool inclusive)
Sample a random number uniformly on either [min, max) or [min, max].
double inverse_transform_sample(const marley::InterpolatingFunction &cdf, double xmin, double xmax, double bisection_tolerance=1e-12)
Sample from a given 1D cumulative density function cdf(x) on the interval [xmin, xmax] using bisectio...
void set_up_run_info()
Initializes the owned GenRunInfo object that will be used to associate run metadata with the output e...
marley::StructureDatabase & get_structure_db()
Get a reference to the StructureDatabase owned by this Generator.
const marley::NeutrinoSource & get_source() const
Get a const reference to the NeutrinoSource owned by this Generator.
double E_pdf(double E)
Probability density function that describes the distribution of reacting neutrino energies.
double sample_decay_time(double partial_width)
Sample a random decay time given a partial decay width.
void add_state_to_event(HepMC3::GenEvent &ev) const
Attach the current random number generator state to the input event as a string attribute.
void seed_using_state_string(const std::string &state_string)
Use a string to set this Generator's internal state.
double total_xs(int pdg_a, double KEa, int pdg_atom) const
Computes the total cross section at fixed energy for all configured reactions involving a particular ...
double rejection_sample(const std::function< double(double)> &f, double xmin, double xmax, double &fmax, double safety_factor=1.01, double max_search_tolerance=DEFAULT_REJECTION_SAMPLING_TOLERANCE_)
Sample from a given 1D probability density function f(x) on the interval [xmin, xmax] using a simple ...
void set_weight_flux(bool should_we_weight)
Sets the value of the weight_flux flag.
void reseed(uint_fast64_t seed)
Reseeds the Generator.
Generator()
Create a Generator using default settings.
marley::Reaction & sample_reaction(double &E)
Sample a Reaction and an energy for the reacting neutrino.
std::shared_ptr< HepMC3::GenEvent > create_event(bool attach_state=false)
Create an Event using the NeutrinoSource, Target, Reaction, and StructureDatabase objects owned by th...
void assign_run_info(HepMC3::GenEvent &event) const
Associates the owned GenRunInfo object with the input event.
void set_target(std::unique_ptr< marley::Target > target)
Take ownership of a new Target, replacing any existing target owned by this Generator.
double flux_averaged_total_xs() const
Computes the flux-averaged total cross section for all enabled neutrino reactions,...
void add_reaction(std::unique_ptr< marley::Reaction > reaction)
Take ownership of a new Reaction.
void finish_event_metadata(HepMC3::GenEvent &ev, bool attach_state=false)
Add final pieces of metadata (e.g., the RNG state) to an otherwise complete event.
const marley::Target & get_target() const
Get a const reference to the Target owned by this Generator.
void set_neutrino_direction(const std::array< double, 3 > &dir_vec)
Sets the direction of the incident neutrinos to use when generating events.
std::string get_state_string() const
Get a string that represents the current internal state of this Generator.
Abstract base class for an approximate representation of a 1D continuous function.
virtual double evaluate(double x) const =0
Returns an approximate value of the represented function.
Monoenergetic neutrino source.
Abstract base class for all objects that describe the incident neutrino energy distribution.
EventProcessor that handles nuclear de-excitations.
virtual void process_event(HepMC3::GenEvent &event, marley::Generator &gen) override
Processes an input GenEvent object.
If needed, rotates the coordinate system of a GenEvent so that the projectile 3-momentum lies along a...
virtual void process_event(HepMC3::GenEvent &ev, marley::Generator &gen) override
Rotates all 3-momenta in the input event so that the projectile 3-momentum lies along dir_vec_ in the...
Abstract base class that represents a 2 → 2 scattering reaction.
virtual double total_xs(int pdg_a, double KEa) const =0
Compute the reaction's total cross section (MeV -2)
virtual marley::TargetAtom atomic_target() const =0
Returns the target atom involved in this reaction.
virtual std::shared_ptr< HepMC3::GenEvent > create_event(int pdg_a, double KEa, marley::Generator &gen) const =0
Create an event object for this reaction.
int pdg_a() const
Get the projectile PDG code.
Container for nuclear structure information organized by nuclide.
int pdg() const
Returns the nuclear PDG code of the target atom.
Description of a macroscopic target for scattering reactions.