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
Generator.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 <chrono>
19#include <cmath>
20#include <limits>
21#include <string>
22
23// HepMC3 includes
24#include "HepMC3/Attribute.h"
25#include "HepMC3/GenEvent.h"
26#include "HepMC3/GenRunInfo.h"
27
28// MARLEY includes
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"
40
41// The default constructor uses the system time as the seed and a
42// default-constructured monoenergetic neutrino source. No reactions are
43// defined, so the user must call add_reaction() at least once before using a
44// default-constructed Generator.
46 : seed_( std::chrono::system_clock::now().time_since_epoch().count() ),
47 source_( new marley::MonoNeutrinoSource ),
48 structure_db_( new marley::StructureDatabase )
49{
50 print_logo();
51 reseed( seed_ );
52}
53
54// The seed-only constructor is like the default constructor, but it uses
55// a specific initial seed.
56marley::Generator::Generator( uint_fast64_t seed )
57 : seed_( seed ), source_( new marley::MonoNeutrinoSource ),
58 structure_db_( new marley::StructureDatabase )
59{
60 print_logo();
61 reseed( seed_ );
62}
63
64// Print the MARLEY logo to the logger stream(s) if you haven't already.
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';
73 printed_logo = true;
74 }
75}
76
77std::shared_ptr< HepMC3::GenEvent > marley::Generator::create_event(
78 bool attach_state )
79{
80 // (0) Initialize the run information if it has not been set up yet
81 if ( !run_info_ ) {
82 this->set_up_run_info();
83 }
84
85 // (1) Select a reacting neutrino energy and reaction using the
86 // flux-weighted total cross section(s)
87 double E_nu;
89
90 // (2) Create the prompt two-two scattering event using the
91 // sampled reaction object
92 int pdg_a = source_->get_pid();
93 std::shared_ptr< HepMC3::GenEvent > ev = r.create_event( pdg_a, E_nu, *this );
94
95 // E.C.2 and E.C.3
96 // Save total and reaction cross sections as metadata in the event.
97 // NOTE: the expected value for NuHepMC3 E.C.2 is the total cross section for
98 // the selected projectile and target, so we loop over relevant reactions
99 // manually to avoid imposing target fraction weighting to this particular
100 // calculation.
101 double totXS = 0.;
102 for ( const auto& temp_r : reactions_ ) {
103
104 // Skip reactions which involve a different target atom from the selected
105 // one
106 if ( temp_r->atomic_target().pdg() != r.atomic_target().pdg() ) continue;
107
108 // Skip reactions which involve a different projectile
109 if ( temp_r->pdg_a() != r.pdg_a() ) continue;
110
111 double temp_xsec = temp_r->total_xs( pdg_a, E_nu );
112 if ( temp_xsec > 0. ) {
113 totXS += temp_xsec;
114 }
115
116 }
117
118 // Convert to picobarns
119 totXS *= marley_utils::hbar_c2 * marley_utils::fm2_to_picobarn;
120
121 ev->add_attribute( "tot_xs",
122 std::make_shared< HepMC3::DoubleAttribute >( totXS )
123 );
124
125 double procXS = r.total_xs( pdg_a, E_nu ) * marley_utils::hbar_c2
126 * marley_utils::fm2_to_picobarn;
127
128 ev->add_attribute( "proc_xs",
129 std::make_shared< HepMC3::DoubleAttribute >( procXS )
130 );
131
132 // (3) If needed, de-excite the final-state residue
133 if ( do_deexcitations_ ) {
135 nd.process_event( *ev, *this );
136 }
137
138 // (4) If needed, rotate the event to match the desired projectile direction
139 rotator_.process_event( *ev, *this );
140
141 // (5) Finish adding metadata to the event object
142 this->finish_event_metadata( *ev, attach_state );
143
144 // Return the completed event object
145 return ev;
146}
147
149 const std::string& state_string)
150{
151 // TODO: add error handling here (check that state_string is valid)
152 std::stringstream strstr( state_string );
153 strstr >> rand_gen_;
154}
155
156void marley::Generator::reseed( uint_fast64_t seed ) {
157 // This is an attempt to do a decent job of seeding the random number
158 // generator, but optimally accomplishing this can be tricky (see, for
159 // example, http://www.pcg-random.org/posts/cpp-seeding-surprises.html)
160 seed_ = seed;
161 std::seed_seq seed_sequence{ seed_ };
162 rand_gen_.seed( seed_sequence );
163
164 MARLEY_LOG( NOTICE, "physics.generator" ) << "Seeded random number generator with "
165 << seed_;
166}
167
169 std::stringstream ss;
170 ss << rand_gen_;
171 return ss.str();
172}
173
174void marley::Generator::normalize_E_pdf() {
175
176 // If we're not ready to do the normalization, then just return without doing
177 // anything. JSONConfig may set this flag to prevent premature calls to
178 // normalize_E_pdf() as the Generator is being constructed
179 if ( dont_normalize_E_pdf_ ) return;
180
181 // This function is called whenever the reacting neutrino energy PDF changes,
182 // so reset the estimated maximum PDF value to its default. We will update
183 // this during rejection sampling.
184 E_pdf_max_ = E_PDF_MAX_DEFAULT_;
185
186 // Treat monoenergetic sources differently since they can cause
187 // problems for the standard numerical integration check
188 if ( source_->get_Emin() == source_->get_Emax() ) {
189 // Set the normalization factor back to one. It's used
190 // in the call to E_pdf() below, so we need to do this before
191 // we assign it a different value.
192 norm_ = 1.0; //
193 // Now norm_ is assigned to be the product of the total cross section times
194 // the source PDF at energy Emin
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.");
201 }
202 MARLEY_LOG( DEBUG, "physics.generator" ) << "Energy PDF normalization"
203 " factor = " << norm_;
204 }
205 else {
206 // Reset the normalization factor to its default of one until
207 // we can calculate the new value. This prevents strange things
208 // from happening when we lose precision due to an abnormally
209 // high or low norm_ value from a previous source or reaction
210 // definition.
211 norm_ = 1.;
212
213 // Update the normalization factor for use with the reacting neutrino
214 // energy probability density function
215 norm_ = marley_utils::num_integrate( [this](double E)
216 -> double { return this->E_pdf(E); }, source_->get_Emin(),
217 source_->get_Emax() );
218
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"
223 " threshold(s)." );
224 }
225 MARLEY_LOG( DEBUG, "physics.generator" ) << "Energy PDF normalization"
226 " factor = " << norm_;
227 }
228}
229
230// Sample a random double uniformly between min and max using the class
231// member random number generator rand_gen_. The inclusive flag
232// determines whether or not max is included in the range. That is,
233// when inclusive == false, the sampling is done on the interval [min, max),
234// while inclusive == true uses [min, max].
235double marley::Generator::uniform_random_double( double min, double max,
236 bool inclusive )
237{
238 // Defaults to sampling from [0,1). We will always
239 // explicitly supply the upper and lower bounds to
240 // this distribution, so we won't worry about the
241 // default setting.
242 static std::uniform_real_distribution<double> udist;
243
244 double max_to_use;
245
246 if ( inclusive ) { // sample from [min, max]
247
248 // Find the double value that comes immediately after max. This allows us
249 // to sample uniformly on [min, max] rather than [min,max). This trick
250 // comes from http://tinyurl.com/n3ocg3p.
251 max_to_use = std::nextafter( max, std::numeric_limits<double>::max() );
252 }
253 else { // sample from [min, max)
254 max_to_use = max;
255 }
256
257 std::uniform_real_distribution<double>::param_type params( min, max_to_use );
258
259 // Sample a random double from this distribution
260 return udist( rand_gen_, params );
261}
262
282 const std::function<double(double)>& f, double xmin, double xmax,
283 double& fmax, double safety_factor, double max_search_tolerance )
284{
285 // If we were passed the value marley_utils::UNKNOWN_MAX for fmax, then this
286 // signals that we need to search for the function maximum ourselves.
287 // Otherwise, we'll assume that the value passed over is good.
288 if ( fmax == marley_utils::UNKNOWN_MAX ) {
289 // This variable will be loaded with the value of x
290 // that corresponds to the maximum of f(x).
291 // We don't actually use this, but currently it's
292 // a required parameter of marley_utils::maximize
293 double x_at_max;
294
295 // Maximize the function and multiply by a safety factor just
296 // in case we didn't quite find the exact peak
297 fmax = marley_utils::maximize( f, xmin, xmax, max_search_tolerance,
298 x_at_max ) * safety_factor;
299 }
300
301 MARLEY_LOG( TRACE, "physics.generator.sampling" ) << "rejection_sample:"
302 " xmin = " << xmin << ", xmax = " << xmax << ", initial fmax = " << fmax;
303
304 double x, y, val;
305
306 do {
307 // Sample x value uniformly from [xmin, xmax]
308 x = uniform_random_double( xmin, xmax, true );
309
310 // Sample y uniformly from [0, fmax]
311 y = uniform_random_double( 0., fmax, true );
312
313 val = f( x );
314 MARLEY_LOG( TRACE, "physics.generator.sampling" ) << "rejection_sample:"
315 " trial x = " << x << ", y = " << y << ", f(x) = " << val;
316 if ( val > fmax ) {
317
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.";
321
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.";
326 }
327 }
328 // Keep sampling until you get a y value less than f(x)
329 // (the probability density function evaluated at the sampled value of x)
330 while ( y > val );
331
332 return x;
333}
334
335double marley::Generator::E_pdf( double E ) {
336
337 // Initialize the return value to zero
338 double pdf = 0.;
339
340 // Sum all of the reaction total cross sections, saving
341 // each individual value along the way. Take weighting
342 // by atom fraction in the target material into account.
343 for ( size_t j = 0, s = reactions_.size(); j < s; ++j ) {
344
345 // Get the current reaction
346 const auto& react = reactions_.at( j );
347
348 // Compute the total cross section for the current reaction for a single
349 // target atom
350 double tot_xs = react->total_xs( source_->get_pid(), E );
351
352 // If the target_ member has not been initialized, don't bother doing any
353 // weighting by atom fraction (equivalent to a weight of unity for all
354 // target atoms)
355 if ( target_ ) {
356 // If it has been configured, then apply the appropriate atom fraction
357 // weight from the target as appropriate.
358 tot_xs *= target_->atom_fraction( react->atomic_target() );
359 }
360
361 // Cache the atom-fraction-weighted total cross section for sampling a
362 // reaction mode later
363 total_xs_values_.at( j ) = tot_xs;
364
365 // Add the weighted total cross section value to the total
366 pdf += tot_xs;
367 }
368
369 // Normally, we want to fold the flux with the reaction cross section(s)
370 // in order to obtain the distribution of reacting neutrino energies
371 if ( weight_flux_ ) {
372 // Multiply the total cross section by the neutrino spectrum
373 // from the source object to get the (unnormalized) PDF
374 // for sampling reacting neutrino energies.
375 pdf *= source_->pdf( E );
376 }
377 else {
378 // If the user has specifically requested it, don't weight the
379 // energy PDF by the cross section(s), as long as at least one of them
380 // is non-vanishing
381 if ( pdf <= 0. ) return 0.;
382 pdf = source_->pdf( E );
383 }
384
385 // Divide by the normalization factor (computed when this source
386 // was made available to the Generator) to obtain the normalized PDF.
387 return pdf / norm_;
388}
389
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." );
394
395 // Store the "old" value of E_pdf_max_, i.e., the one we had before calling
396 // rejection_sample(). This will be used to check for problems.
397 double old_max = E_pdf_max_;
398
399 // TODO: protect against source_ changing E_min or E_max after you compute
400 // the normalization factor norm_ in marley::Generator::init()
401 E = rejection_sample( [this](double E_nu)
402 -> double { return this->E_pdf(E_nu); }, source_->get_Emin(),
403 source_->get_Emax(), E_pdf_max_ );
404
405 // If the value of max changed after the call to rejection_sample() and the
406 // old value wasn't UNKNOWN_MAX, then the rejection sampling routine must
407 // have encountered a PDF value that was larger than our estimated maximum.
408 // Alert the user about this and advise them to change the configuration
409 // appropriately to avoid a biased reacting neutrino energy distribution.
410 static bool issued_long_error_message = false;
411 if ( old_max != marley_utils::UNKNOWN_MAX
412 && old_max != E_pdf_max_ )
413 {
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;
428 }
429 else {
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_ << ',';
434 }
435 }
436
437 // The atom-fraction-weighted total cross section values have already been
438 // updated by the final call to E_pdf() during rejection sampling, so we can
439 // now sample a reaction type using our discrete distribution object.
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;
447}
448
450 if ( source_ ) return *source_;
451 else throw marley::Error( "Error in marley::Generator::get_source()."
452 " The member variable source_ == nullptr." );
453}
454
456 if ( target_ ) return *target_;
457 else throw marley::Error( "Error in marley::Generator::get_target()."
458 " The member variable target_ == nullptr." );
459}
460
462 std::unique_ptr<marley::NeutrinoSource> source )
463{
464 // If we're passed a nullptr, then don't bother to do anything
465 if ( source ) {
466 // Transfer ownership of the neutrino source the generator, leaving
467 // the std::unique_ptr passed to this function null afterwards.
468 source_.reset( source.release() );
469
470 // Don't bother to renormalize if there are no reactions defined yet
471 if ( reactions_.empty() ) return;
472
473 // Update the neutrino energy probability density function based on the
474 // new source spectrum
475 this->normalize_E_pdf();
476 }
477}
478
480 std::unique_ptr<marley::Reaction> reaction )
481{
482 // If we're passed a nullptr, then don't bother to do anything
483 if ( reaction ) {
484
485 // Transfer ownership to a new unique_ptr in the reactions vector, leaving
486 // the original empty
487 reactions_.push_back( std::move(reaction) );
488
489 // Add a new entry in the reaction cross sections vector
490 total_xs_values_.push_back( 0. );
491
492 // TODO: consider adding a check to see whether source_ is non-null.
493 // Right now, this shouldn't be possible, but an explicit check might
494 // be good.
495
496 // Update the neutrino energy probability density function by including the
497 // cross section for the new reaction
498 normalize_E_pdf();
499 }
500}
501
503 reactions_.clear();
504 total_xs_values_.clear();
505 // Reset the normalization factor to 1. We don't need it until we define
506 // one or more new reactions.
507 norm_ = 1.;
508}
509
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." );
514}
515
517 const std::array<double, 3>& dir_vec )
518{
519 rotator_.set_projectile_direction( dir_vec );
520
521 const auto& normalized_dir_vec = rotator_.projectile_direction();
522
523 // Print a log message announcing the change of 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 += ", ";
528 }
529 MARLEY_LOG( INFO, "physics.generator" ) << dir_msg << ')';
530}
531
532void marley::Generator::set_weight_flux( bool should_we_weight ) {
533 weight_flux_ = should_we_weight;
534}
535
537 const std::function<double(double)>& f, double xmin, double xmax,
538 double bisection_tolerance )
539{
540 // Build an approximate CDF corresponding to the integral of the input PDF.
541 // Use a polynomial approximant at Chebyshev points to do it.
544 DEFAULT_N_CHEBYSHEV);
545 auto cdf = func.cdf();
546
547 // Now that we have a CDF to use for sampling, delegate the rest of the
548 // action to the overloaded version of this function.
549 return this->inverse_transform_sample(cdf, xmin, xmax, bisection_tolerance);
550}
551
553 const marley::InterpolatingFunction& cdf, double xmin, double xmax,
554 double bisection_tolerance )
555{
556 // Sample a probability value uniformly on [0, 1]
557 double prob = uniform_random_double(0., 1., true);
558
559 // If we chose an endpoint, we're done, so just return the appropriate one
560 if ( prob == 0. ) return xmin;
561 else if ( prob == 1. ) return xmax;
562
563 // A properly normalized CDF should evaluate to unity at x = xmax. We enforce
564 // this here so that the user doesn't have to do it in advance.
565 double norm = cdf.evaluate( xmax );
566
567 // Find the x value corresponding to the sampled probability via bisection
568 // (slow but robust)
569 double a = xmin;
570 double b = xmax;
571 while ( (b - a) > bisection_tolerance ) {
572 double midpoint = (a + b) / 2.;
573 double mid_cdf = cdf.evaluate( midpoint ) / norm;
574 // If the CDF at the midpoint exactly matches our sampled
575 // probability, we're done. Just return the midpoint.
576 if ( mid_cdf == prob ) return midpoint;
577 // Otherwise, shrink the bisection interval and try again
578 else if ( mid_cdf > prob ) b = midpoint;
579 else a = midpoint; // mid_cdf < prob
580 }
581
582 // Return the midpoint of the bisection interval as our sampled x value
583 double x = (a + b) / 2.;
584 return x;
585}
586
588 // If we've disabled weighting the neutrino energy PDF
589 // by the total cross section, just return zero
590 if ( !weight_flux_ ) return 0.;
591
592 double avg_total_xs = 0.;
593
594 // For a monoenergetic source, don't bother to do the full
595 // integral
596 double Emin = source_->get_Emin();
597 double Emax = source_->get_Emax();
598 if ( Emin == Emax ) {
599 avg_total_xs = norm_ / source_->pdf( Emin );
600 }
601 else {
602 double source_norm = marley_utils::num_integrate(
603 [this](double Ev) -> double { return this->source_->pdf(Ev); },
604 Emin, Emax );
605
606 // Use the precomputed integral of the reacting neutrino energy PDF
607 avg_total_xs = norm_ / source_norm;
608 }
609 return avg_total_xs;
610}
611
612void marley::Generator::set_target( std::unique_ptr<marley::Target> target )
613{
614 // If we're passed a nullptr, then don't bother to do anything
615 if ( target ) {
616 // Transfer ownership of the neutrino target to the generator, leaving
617 // the std::unique_ptr passed to this function null afterwards.
618 target_.reset( target.release() );
619
620 // Don't bother to renormalize if there are no reactions defined yet
621 if ( reactions_.empty() ) return;
622
623 // Update the neutrino energy probability density function based on the
624 // new target composition
625 this->normalize_E_pdf();
626 }
627}
628
629double marley::Generator::total_xs( int pdg_a, double KEa, int pdg_atom ) const
630{
631 return this->total_xs( pdg_a, KEa, pdg_atom, nullptr, nullptr );
632}
633
634double marley::Generator::total_xs( int pdg_a, double KEa, int pdg_atom,
635 std::vector<size_t>* index_vec, std::vector<double>* xsec_vec ) const
636{
637 double xsec_sum = 0.;
638
639 if ( index_vec ) index_vec->clear();
640 if ( xsec_vec ) xsec_vec->clear();
641
642 for ( size_t j = 0u; j < reactions_.size(); ++j ) {
643
644 const auto& r = reactions_.at( j );
645
646 // Skip reactions which involve a different target atom
647 if ( pdg_atom != r->atomic_target().pdg() ) continue;
648
649 // Skip reactions which involve a different projectile
650 if ( pdg_a != r->pdg_a() ) continue;
651
652 // If the cross section is non-vanishing, store information about it
653 // (as appropriate) and add it to the sum
654 double xsec = r->total_xs( pdg_a, KEa );
655 if ( xsec > 0. ) {
656 xsec_sum += xsec;
657 if ( index_vec ) index_vec->push_back( j );
658 if ( xsec_vec ) xsec_vec->push_back( xsec );
659 }
660
661 }
662
663 return xsec_sum;
664}
665
666
667std::shared_ptr< HepMC3::GenEvent > marley::Generator::create_event(
668 int pdg_a, double KEa, int pdg_atom, const std::array<double, 3>& dir_vec,
669 bool attach_state )
670{
671 // (0) Initialize the run information if it has not been set up yet
672 if ( !run_info_ ) {
673 this->set_up_run_info();
674 }
675
676 // (1) Sample a reaction mode from all configured reactions that can handle
677 // the given initial-state parameters
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 );
681
682 if ( xsecs.empty() || tot_xsec <= 0. ) throw marley::Error(
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" );
687
688 // The total cross section values and indices in the full reactions_ vector
689 // have already been loaded into temporary vectors, so we can immediately use
690 // those to sample a reaction using a discrete distribution.
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) );
694
695 // (2) Create the prompt two-two scattering event using the sampled reaction
696 // object
697 std::shared_ptr< HepMC3::GenEvent > ev = r->create_event( pdg_a, KEa, *this );
698
699 // E.C.2 and E.C.3
700 // Save total and reaction cross sections as metadata in the event
701 double totXS = tot_xsec * marley_utils::hbar_c2
702 * marley_utils::fm2_to_picobarn;
703
704 ev->add_attribute( "tot_xs",
705 std::make_shared< HepMC3::DoubleAttribute >( totXS )
706 );
707
708 double procXS = xsecs.at( sampled_index ) * marley_utils::hbar_c2
709 * marley_utils::fm2_to_picobarn;
710
711 ev->add_attribute( "proc_xs",
712 std::make_shared< HepMC3::DoubleAttribute >( procXS )
713 );
714
715 // Do the usual post-processing
716
717 // (3) If needed, de-excite the final-state residue
718 if ( do_deexcitations_ ) {
720 nd.process_event( *ev, *this );
721 }
722
723 // (4) If needed, rotate the event to match the desired projectile direction
724 // Set the incident neutrino direction for this event
725 static marley::ProjectileDirectionRotator my_rotator;
726 my_rotator.set_projectile_direction( dir_vec );
727
728 // Rotate the coordinate system of the event if needed
729 my_rotator.process_event( *ev, *this );
730
731 // (5) Finish adding metadata to the event object
732 this->finish_event_metadata( *ev, attach_state );
733
734 // Return the completed event object
735 return ev;
736}
737
738// Compute the abundance-weighted total reaction cross section at fixed
739// projectile kinetic energy
740double marley::Generator::total_xs( int pdg_a, double KEa ) const {
741
742 // Initialize the return value to zero
743 double tot_xsec = 0.;
744
745 // Sum all of the reaction total cross sections. Take weighting by atom
746 // fraction in the target material into account.
747 for ( const auto& react : reactions_ ) {
748
749 // Compute the total cross section for the current reaction for a single
750 // target atom
751 double xsec = react->total_xs( pdg_a, KEa );
752
753 // If the target_ member has not been initialized, don't bother doing any
754 // weighting by atom fraction (equivalent to a weight of unity for all
755 // target atoms)
756 if ( target_ ) {
757 // If it has been configured, then apply the appropriate atom fraction
758 // weight from the target as appropriate.
759 xsec *= target_->atom_fraction( react->atomic_target() );
760 }
761
762 // Add the weighted total cross section value to the total
763 tot_xsec += xsec;
764 }
765
766 return tot_xsec;
767}
768
770
771 // G.R.1
772 run_info_ = std::make_shared< HepMC3::GenRunInfo >();
773
774 // G.R.2
775 run_info_->add_attribute( "NuHepMC.Version.Major",
776 std::make_shared< HepMC3::IntAttribute >(
777 marley_hepmc3::NUHEPMC_MAJOR_VERSION )
778 );
779
780 run_info_->add_attribute( "NuHepMC.Version.Minor",
781 std::make_shared< HepMC3::IntAttribute >(
782 marley_hepmc3::NUHEPMC_MINOR_VERSION )
783 );
784
785 run_info_->add_attribute( "NuHepMC.Version.Patch",
786 std::make_shared< HepMC3::IntAttribute >(
787 marley_hepmc3::NUHEPMC_PATCH_VERSION )
788 );
789
790 // G.R.3
791 run_info_->tools().emplace_back(
792 HepMC3::GenRunInfo::ToolInfo{ "MARLEY", MARLEY_VERSION,
793 MARLEY_GIT_REVISION }
794 );
795
796 // G.R.8
797 marley_hepmc3::prepare_process_metadata( *run_info_ );
798
799 // G.R.9
800 marley_hepmc3::prepare_vertex_status_metadata( *run_info_ );
801
802 // G.R.10
803 marley_hepmc3::prepare_particle_status_metadata( *run_info_ );
804
805 // G.R.7
806 const auto& wgt_names = weighter_->get_weight_names();
807 run_info_->set_weight_names( wgt_names );
808
809 // G.R.11
810 marley_hepmc3::prepare_non_standard_pdg_code_metadata( *run_info_ );
811
812 // G.R.4, G.R.6, G.C.2, G.C.3
813 double avg_xsec = this->flux_averaged_total_xs(); // MeV^{-2}
814 marley_hepmc3::apply_nuhepmc_runinfo_conventions( *run_info_, avg_xsec );
815
816 // Save the information needed to restore an interrupted MARLEY job
817 run_info_->add_attribute( "MARLEY.RNGseed",
818 std::make_shared< HepMC3::StringAttribute >( std::to_string(seed_) )
819 );
820
821 run_info_->add_attribute( "MARLEY.JSONconfig",
822 std::make_shared< HepMC3::StringAttribute >( json_config_ )
823 );
824
825}
826
828 bool attach_state )
829{
830 // Associate the owned run information with the event
831 this->assign_run_info( ev );
832
833 // Calculate any needed weight(s) for the event
834 weighter_->process_event( ev, *this );
835
836 // Add the generator state after the event was completed as a string
837 // attribute. This allows resuming an interrupted job from where it left off.
838 if ( attach_state ) this->add_state_to_event( ev );
839
840 // E.R.5
841 // TODO: revisit spatial position when MARLEY is interfaced with a
842 // detector geometry simulation
843 const std::vector< double > lab_pos = { 0., 0., 0. };
844 ev.add_attribute( "lab_pos",
845 std::make_shared< HepMC3::VectorDoubleAttribute >( lab_pos )
846 );
847
848}
849
850void marley::Generator::set_json_config( const marley::JSON& jc ) {
851 json_config_ = jc.dump_string();
852}
853
855 event.set_run_info( run_info_ );
856}
857
858// Sample a random decay time given a partial decay width
859double marley::Generator::sample_decay_time( double partial_width ) {
860 if ( partial_width <= 0. ) throw marley::Error( "Non-positive partial decay"
861 " width passed to marley::Generator::sample_decay_time()" );
862
863 // Mean lifetime (1/MeV)
864 double tau = 1. / partial_width;
865
866 // Find the double value that comes immediately after zero. This allows us
867 // exclude zero and sample uniformly on (0, 1]. Including zero would lead to
868 // the possibility of an infinite decay time. See http://tinyurl.com/n3ocg3p
869 // for more information.
870 double min_to_use = std::nextafter( 0., std::numeric_limits<double>::max() );
871
872 // Choose a random number uniformly on (0, 1].
873 double r = this->uniform_random_double( min_to_use, 1., true );
874
875 // Decay time in MeV^{-1}
876 double t = -tau * std::log( r );
877 return t;
878}
879
881
882 // Query the random number generator for its current state string
883 std::string state = this->get_state_string();
884
885 // Attach the random number generator state string to the event as a
886 // string attribute
887 this->add_state_to_event( ev, state );
888}
889
891 const std::string& state )
892{
893 ev.add_attribute( "MARLEY.GeneratorState",
894 std::make_shared< HepMC3::StringAttribute >( state )
895 );
896}
Stores event-related information.
Definition GenEvent.h:47
void add_attribute(const std::string &name, const std::shared_ptr< Attribute > &att, const int &id=0)
Interrnal struct for keeping track of tools.
Definition GenRunInfo.h:38
Approximates a 1D function using Chebyshev points.
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
void clear_reactions()
Clear the vector of Reaction objects owned by this Generator.
Definition Generator.cc:502
void set_source(std::unique_ptr< marley::NeutrinoSource > source)
Take ownership of a new NeutrinoSource, replacing any existing source owned by this Generator.
Definition Generator.cc:461
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
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...
Definition Generator.cc:552
void set_up_run_info()
Initializes the owned GenRunInfo object that will be used to associate run metadata with the output e...
Definition Generator.cc:769
marley::StructureDatabase & get_structure_db()
Get a reference to the StructureDatabase owned by this Generator.
Definition Generator.cc:510
const marley::NeutrinoSource & get_source() const
Get a const reference to the NeutrinoSource owned by this Generator.
Definition Generator.cc:449
double E_pdf(double E)
Probability density function that describes the distribution of reacting neutrino energies.
Definition Generator.cc:335
double sample_decay_time(double partial_width)
Sample a random decay time given a partial decay width.
Definition Generator.cc:859
void add_state_to_event(HepMC3::GenEvent &ev) const
Attach the current random number generator state to the input event as a string attribute.
Definition Generator.cc:880
void seed_using_state_string(const std::string &state_string)
Use a string to set this Generator's internal state.
Definition Generator.cc:148
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 ...
Definition Generator.cc:629
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 ...
Definition Generator.cc:281
void set_weight_flux(bool should_we_weight)
Sets the value of the weight_flux flag.
Definition Generator.cc:532
void reseed(uint_fast64_t seed)
Reseeds the Generator.
Definition Generator.cc:156
Generator()
Create a Generator using default settings.
Definition Generator.cc:45
marley::Reaction & sample_reaction(double &E)
Sample a Reaction and an energy for the reacting neutrino.
Definition Generator.cc:390
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...
Definition Generator.cc:77
void assign_run_info(HepMC3::GenEvent &event) const
Associates the owned GenRunInfo object with the input event.
Definition Generator.cc:854
void set_target(std::unique_ptr< marley::Target > target)
Take ownership of a new Target, replacing any existing target owned by this Generator.
Definition Generator.cc:612
double flux_averaged_total_xs() const
Computes the flux-averaged total cross section for all enabled neutrino reactions,...
Definition Generator.cc:587
void add_reaction(std::unique_ptr< marley::Reaction > reaction)
Take ownership of a new Reaction.
Definition Generator.cc:479
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.
Definition Generator.cc:827
const marley::Target & get_target() const
Get a const reference to the Target owned by this Generator.
Definition Generator.cc:455
void set_neutrino_direction(const std::array< double, 3 > &dir_vec)
Sets the direction of the incident neutrinos to use when generating events.
Definition Generator.cc:516
std::string get_state_string() const
Get a string that represents the current internal state of this Generator.
Definition Generator.cc:168
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.
Definition Reaction.hh:46
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.
Definition Reaction.hh:109
Container for nuclear structure information organized by nuclide.
int pdg() const
Returns the nuclear PDG code of the target atom.
Definition TargetAtom.hh:73
Description of a macroscopic target for scattering reactions.
Definition Target.hh:32