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
ContinuumNuclearReaction.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// HepMC3 includes
18#include "HepMC3/FourVector.h"
19#include "HepMC3/GenParticle.h"
20
21// MARLEY includes
22#include "marley/Error.hh"
23#include "marley/Generator.hh"
24#include "marley/MassTable.hh"
25#include "marley/ContinuumNuclearReaction.hh"
26#include "marley/marley_utils.hh"
27#include "marley/hepmc3_utils.hh"
28
30
31// Initialize the static class member defining how to deal with sub-continuum
32// cross-section strength. By default, we pick the "accumulate" option.
34 = SubContinuumMode::ACCUMULATE;
35
36// Initialize the static class member specifying a mapping between strings
37// and SubContinuumMode enum values
38const std::map< SubContinuumMode, std::string >
40 { SubContinuumMode::IGNORE, "ignore" },
41 { SubContinuumMode::MIRROR, "mirror" },
42 { SubContinuumMode::ACCUMULATE, "accumulate" }
43};
44
45marley::ContinuumNuclearReaction::ContinuumNuclearReaction(
46 Reaction::ProcessType pt, int pdg_a, int pdg_b, int pdg_c, int pdg_d,
47 int q_d, const std::shared_ptr<TabulatedXSec>& txsec,
48 const std::string& source_file )
49 : marley::NuclearReaction( pt, pdg_a, pdg_b, pdg_c, pdg_d, q_d,
50 source_file ),
51 xsec_( txsec )
52{
53}
54
56 double KEa ) const
57{
58 if ( pdg_a != pdg_a_ ) return 0.;
59 return xsec_->integral( pdg_a, KEa );
60}
61
62std::shared_ptr< HepMC3::GenEvent > marley::ContinuumNuclearReaction
63 ::create_event( int pdg_a, double KEa, marley::Generator& gen ) const
64{
65 // TODO: reduce code duplication here with DiscreteNuclearReaction using the
66 // common base class NuclearReaction
67
68 // Check that the projectile supplied to this event is correct. If not, alert
69 // the user that this event does not use the requested projectile.
70 if ( pdg_a != pdg_a_ ) throw marley::Error( "Could not create this event."
71 " The requested projectile particle ID, " + std::to_string( pdg_a )
72 + ", does not match the projectile particle ID, "
73 + std::to_string( pdg_a_ ) + ", in the reaction dataset." );
74
75 // Sample a final residue energy level. First, check to make sure the given
76 // projectile energy is above threshold for this reaction.
77 if ( KEa < KEa_threshold_ ) throw std::range_error( "Could"
78 " not create this event. Projectile kinetic energy "
79 + std::to_string( KEa ) + " MeV is below the threshold value "
80 + std::to_string( KEa_threshold_ ) + " MeV." );
81
82 // Select a specific multipole to use for the current event using the
83 // individual total cross sections
84 std::vector< double > multipole_weights;
85 std::vector< marley::TabulatedXSec::MultipoleLabel > multipoles;
86 std::vector< double > diff_max_values;
87 const auto& table_map = xsec_->get_table_map();
88 double sum_of_xsecs = 0.;
89 for ( const auto& pair : table_map ) {
90 const auto& ml = pair.first;
91 double diff_max;
92 double total_xsec = xsec_->integral( pdg_a_, KEa, ml, diff_max );
93
94 sum_of_xsecs += total_xsec;
95
96 multipole_weights.push_back( total_xsec );
97 multipoles.push_back( ml );
98 diff_max_values.push_back( diff_max );
99 }
100
101 // If there are no multipole weights, we can't go on. Complain if this
102 // is the case.
103 if ( multipole_weights.empty() ) {
104 throw marley::Error( "Could not create this event. The TabulatedXSec"
105 " object associated with this reaction does not own any nuclear response"
106 " tables." );
107 }
108
109 // Complain if the total cross section (the sum of all partial cross
110 // sections) is zero or negative (the latter is just to cover all
111 // possibilities).
112 if ( sum_of_xsecs <= 0. ) {
113 throw marley::Error( "Could not create this event. All multipole total"
114 " cross sections are nonpositive." );
115 }
116
117 // Create a discrete distribution based on the weights. This will be
118 // used to choose a single multipole for the current event.
119 std::discrete_distribution< size_t > multipole_dist(
120 multipole_weights.begin(), multipole_weights.end() );
121
122 // Sample a matrix_element using our discrete distribution and the
123 // current set of weights
124 size_t multipole_index = gen.sample_from_distribution( multipole_dist );
125
126 // Label of the multipole chosen for this event
127 const auto& sampled_ml = multipoles.at( multipole_index );
128 // Maximum value of the differential cross section for this multipole.
129 // This will be used for rejection sampling of inclusive kinematics below.
130 double diff_max = diff_max_values.at( multipole_index );
131
132 // ResponseTable object to use for computing the differential cross section
133 // during kinematic sampling below
134 const auto& rt = xsec_->get_table( sampled_ml );
135
136 // Get the values of the energy transfer that correspond to the edges
137 // of the table of nuclear responses. Note that the table is actually given
138 // in terms of the effective energy transfer, which differs by delta_ias
139 // from the actual energy transfer. We therefore apply a shift here
140 // to correct for this.
141 double table_wmin = rt.w_min() - xsec_->delta_ias();
142 double table_wmax = rt.w_max() - xsec_->delta_ias();
143
144 // Choose a reasonable sampling interval for the energy transfer
145 double Ea = KEa + ma_; // Projectile total energy
146
147 // Set the lower bound for the energy transfer to be either zero or
148 // the lowest tabulated value (whichever is larger)
149 double wmin = std::max( 0., table_wmin );
150
151 // Set the upper bound for the energy transfer to be either the projectile
152 // energy minus the final lepton mass or the highest tabulated value
153 // (whichever is lower)
154 double wmax = std::min( Ea - mc_, table_wmax );
155
156 // Sample values for the energy transfer and scattering cosine using the
157 // differential cross section for the chosen multipole.
158 // Use a simple rejection sampling technique.
159 double w, ctl, diff, y;
160 int sampling_attempts = 0;
161 bool recomputed_diff_max = false;
162 do {
163 // Occasionally the value of diff_max retrieved above can be extremely
164 // overestimated when using an optimized version of the total cross
165 // section calculation (which relies on interpolation). This typically
166 // happens very close to threshold and leads to sampling getting stuck
167 // due to a very low acceptance efficiency.
168 //
169 // To guard against this situation, when the number of sampling attempts
170 // exceeds a large value, the estimate of the maximum differential cross
171 // section diff_max is recalculated at exactly the input projectile kinetic
172 // energy rather than relying on the precomputed value. The updated
173 // estimate is then used in a new set of sampling attempts.
174 if ( sampling_attempts > marley_utils::LARGE_NUMBER_OF_ITERATIONS ) {
175
176 if ( recomputed_diff_max ) {
177 // If we make it here, then we are still hitting a huge number of
178 // iterations in this sampling loop despite recalculating the maximum
179 // differential cross section. This suggests that we are stuck in an
180 // infinite loop, so abort with an exception indicating the problem.
181 throw marley::Error( "Reached maximum iteration count for rejection"
182 " sampling in marley::ContinuumNuclearReaction::create_event()" );
183 }
184
185 // The value of diff_max is updated by this call to
186 // marley::TabulatedXSec::compute_integral()
187 xsec_->compute_integral( pdg_a_, KEa, sampled_ml, diff_max );
188 sampling_attempts = 0;
189 recomputed_diff_max = true;
190 }
191
192 w = gen.uniform_random_double( wmin, wmax, true );
193 ctl = gen.uniform_random_double( -1., 1., true );
194 diff = xsec_->diff_xsec( pdg_a_, KEa, w, ctl, sampled_ml );
195 y = gen.uniform_random_double( 0., diff_max, true );
196 ++sampling_attempts;
197
198 // If reassignment is enabled and the excitation energy corresponding to the
199 // sampled energy transfer w falls below the continuum, reassign the value
200 // of the energy transfer to lie within the continuum. The scattering cosine
201 // ctl and projectile kinetic energy are needed for the calculation but are
202 // never altered. If reassignment is attempted but fails due to kinematic
203 // limits, then force another iteration of this rejection sampling loop even
204 // if the event would have been accepted without reassignment.
205 //
206 // NOTE: The reassignment operation is included in the condition of the
207 // do-while loop for efficiency. There is no need to perform the
208 // reassignment for w values that would be rejected anyway. Use of the
209 // logical OR operation (||) will only evaluate the first condition if it
210 // is false, thus skipping the reassignment when it is obviously
211 // unnecessary.
212 } while ( y > diff || !this->reassign_sub_continuum(w, ctl, KEa) );
213
214 // Sample a lab-frame azimuthal scattering angle uniformly
215 double phi_c = gen.uniform_random_double( 0., marley_utils::two_pi, false );
216
217 // Load the initial residue twoJ and parity values into twoJ and P. These
218 // variables are included in the event record and used by NucleusDecayer to
219 // start the Hauser-Feshbach decay cascade.
220 // NOTE: right now, these are taken directly from the multipole involved in
221 // the current event. This is only valid for scattering on a 0+ target
222 // nucleus
223 // TODO: revisit this assumption and do something better
224 int twoJ = 2 * sampled_ml.J_;
225 marley::Parity P = sampled_ml.Pi_; // defaults to positive parity
226
227 // Sine of the ejectile scattering angle
228 double stl = marley_utils::real_sqrt( 1. - std::pow(ctl, 2) );
229
230 // Calculate the full kinematics of the primary interaction based on the
231 // lepton scattering cosine (ctl) and energy transfer (w) sampled above.
232
233 // Determine the components of the ejectile's lab-frame 4-momentum
234 double Ec = Ea - w;
235 double pc = marley_utils::real_sqrt( Ec*Ec - mc_*mc_ );
236 double pc_x = stl * std::cos( phi_c ) * pc;
237 double pc_y = stl * std::sin( phi_c ) * pc;
238 double pc_z = ctl * pc;
239
240 // Determine the magnitude of the lab-frame 3-momentum of the projectile
241 double pa = marley_utils::real_sqrt( Ea*Ea - ma_*ma_ );
242
243 // Construct the lab-frame 4-momenta of the projectile, target, and ejectile
244 HepMC3::FourVector pro_mom4( 0., 0., pa, Ea );
245 HepMC3::FourVector tar_mom4( 0., 0., 0., mb_ );
246 HepMC3::FourVector eje_mom4( pc_x, pc_y, pc_z, Ec );
247
248 // Get the 4-momentum of the residue in the lab frame using conservation
249 double Ed = pro_mom4.e() + tar_mom4.e() - eje_mom4.e();
250 double pd_x = pro_mom4.px() + tar_mom4.px() - eje_mom4.px();
251 double pd_y = pro_mom4.py() + tar_mom4.py() - eje_mom4.py();
252 double pd_z = pro_mom4.pz() + tar_mom4.pz() - eje_mom4.pz();
253
254 // Determine the residue mass from its 4-momentum
255 md_ = marley_utils::real_sqrt( Ed*Ed - pd_x*pd_x - pd_y*pd_y - pd_z*pd_z );
256
257 // The excitation energy is the mass difference between this mass and
258 // the residue's ground-state mass
259 double Ex = md_ - md_gs_;
260
261 // Create particle objects representing the ejectile and residue
262 auto ejectile = marley_hepmc3::make_particle( eje_mom4, pdg_c_,
263 marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, mc_ );
264
265 auto residue = marley_hepmc3::make_particle( pdg_d_, pd_x, pd_y, pd_z, Ed,
266 marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS, md_ );
267
268 // Make the event object (this also sets the charge and nuclear level
269 // attributes)
270 auto event = this->make_nuclear_event_object( KEa, ejectile, residue, Ex,
271 twoJ, P );
272
273 return event;
274}
275
276// Adds an indication to the description that the daughter nucleus will always
277// be left in an excited state.
278// TODO: revisit this as needed. I assume here that the
279// ContinuumNuclearReaction class will always be used for calculations in the
280// unbound continuum of nuclear levels
285
286// Implements a requirement that the energy transfer sampled in create_event()
287// lies within the excitation energy continuum. According to the user
288// configuration, this requirement can be turned on or off and defined in
289// different ways.
291 const double ctl, const double KEa ) const
292{
293 // If reassignment of the sub-continuum strength is disabled, then this
294 // function returns immediately without doing anything
295 if ( sc_mode_ == SubContinuumMode::IGNORE ) return true;
296
297 // Lab-frame total energy and 3-momentum of the projectile
298 double Ea = KEa + ma_;
299 double pa = marley_utils::real_sqrt( Ea*Ea - ma_*ma_ );
300
301 // Lab-frame total energy and 3-momentum of the ejectile
302 double Ec = Ea - w;
303 double pc = marley_utils::real_sqrt( Ec*Ec - mc_*mc_ );
304
305 // Squared magnitude of the 3-momentum transfer
306 double kappa2 = pa*pa + pc*pc - 2.*pa*pc*ctl;
307
308 // Total energy and excitation energy of the residue
309 double Ed = mb_ + w;
310 double Ex = marley_utils::real_sqrt( Ed*Ed - kappa2 ) - md_gs_;
311
312 // Get the "unbound threshold" used to determine the start of the continuum
313 const auto& mt = marley::MassTable::Instance();
314 double unbound_threshold = mt.unbound_threshold( pdg_d_ );
315
316 // If the excitation energy corresponding to the sampled energy transfer
317 // is already within the continuum, no special action is needed. Just return
318 // without making any modifications.
319 if ( Ex >= unbound_threshold ) return true;
320
321 // If we've made it here, then we need to reassign the excitation energy
322 // and compute a new value of the energy transfer. First choose the new
323 // excitation energy based on the recipe selected by the user configuration.
324 if ( sc_mode_ == SubContinuumMode::ACCUMULATE ) {
325 // For the "accumulate" option, just update the excitation energy to be
326 // exactly at the unbound threshold
327 Ex = unbound_threshold;
328
329 MARLEY_LOG( DEBUG, "physics.reaction" ) << "Excitation energy " << Ex
330 << " MeV is below the unbound threshold " << unbound_threshold
331 << " MeV. Sampling exactly at the unbound threshold.";
332 }
333 else if ( sc_mode_ == SubContinuumMode::MIRROR ) {
334 // For the "mirror" option, "reflect" the original excitation energy to the
335 // upper side of the unbound threshold so it is the same distance above
336 // as it was originally below.
337 Ex = 2.*unbound_threshold - Ex;
338
339 MARLEY_LOG( DEBUG, "physics.reaction" ) << "Excitation energy " << Ex
340 << " MeV is below the unbound threshold " << unbound_threshold
341 << " MeV. Mirroring the energy transfer around the unbound"
342 << " threshold.";
343 }
344 else {
345 throw marley::Error( "Unrecognized sub-continuum mode encountered"
346 " in marley::ContinuumNuclearReaction::reassign_sub_continuum()" );
347 return false;
348 }
349
350 // Solve for the new outgoing lepton total energy given the updated
351 // excitation energy value. Update the value of Ec with the solution.
352 Ec = this->get_Ec_from_Ex( Ex, ctl, KEa );
353
354 // Now update the energy transfer accordingly
355 w = Ea - Ec;
356
357 // If the total energy of the outgoing lepton is now below its rest mass,
358 // then the reassignment procedure failed due to the kinematic threshold.
359 // Indicate this failure in the return value.
360 if ( Ec < mc_ ) return false;
361
362 // Otherwise, everything worked out, so indicate success.
363 return true;
364}
365
367 const double cos_theta, const double KEa, double* jacobian ) const
368{
369 // Mass of the final-state ion (including excitation energy)
370 double md = md_gs_ + Ex;
371
372 // Total energy of the projectile
373 double Ea = KEa + ma_;
374
375 // Total energy of the two-body system in the lab frame
376 double Etot = Ea + mb_;
377
378 // Construct helper variables
379 double pa = marley_utils::real_sqrt( Ea*Ea - ma_*ma_ );
380 double help = md*md - mc_*mc_ + pa*pa - Etot*Etot;
381 double other_help = 4.*pa*pa*cos_theta*cos_theta;
382
383 // Quadratic coefficients (a*Ec^2 + b*Ec + c == 0)
384 double a = 4.*Etot*Etot - other_help;
385 double b = 4.*Etot*help;
386 double c = help*help + other_help*mc_*mc_;
387
388 // Get both solutions to the quadratic equation
389 double sol_plus, sol_minus;
390 marley_utils::solve_quadratic_equation( a, b, c,
391 sol_plus, sol_minus );
392
393 // Now for a trick: due to the way we derived the results above,
394 // the two solutions correspond to positive (sol_plus) and
395 // negative (sol_minus) values of cos_theta, with the two
396 // solutions exactly equal when cos_theta == 0. Choose the
397 // appropriate one to return here.
398 double Ec = sol_plus;
399 if ( cos_theta < 0. ) Ec = sol_minus;
400
401 if ( jacobian ) {
402 double pc = marley_utils::real_sqrt( Ec*Ec - mc_*mc_ );
403 *jacobian = md / ( Ea + mb_ - pa*Ec*cos_theta/pc );
404 }
405
406 return Ec;
407}
408
409// Convert a string to a SubContinuumMode value
410SubContinuumMode marley::ContinuumNuclearReaction
411 ::sub_continuum_mode_from_string( const std::string& str )
412{
413 for ( const auto& pair : sc_mode_string_map_ ) {
414 if ( str == pair.second ) return pair.first;
415 }
416 throw marley::Error( "The string \"" + str + "\" was not recognized"
417 " as a valid sub-continuum mode setting" );
418}
419
420// Convert a SubContinuumMode value to a string
421std::string marley::ContinuumNuclearReaction::string_from_sub_continuum_mode(
422 SubContinuumMode mode )
423{
424 auto it = sc_mode_string_map_.find( mode );
425 if ( it != sc_mode_string_map_.end() ) return it->second;
426 else throw marley::Error( "Unrecognized sub-continuum mode value encountered"
427 " in marley::ContinuumNuclearReaction::string_from_sub_continuum_mode()" );
428}
Generic 4-vector.
Definition FourVector.h:36
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
virtual void set_description() override
Creates the description string based on the PDG code values for the initial and final particles.
static SubContinuumMode sc_mode_
Indicates the desired method for handling events with excitation energies originally sampled below th...
virtual double total_xs(int pdg_a, double KEa) const override
Compute the reaction's total cross section (MeV -2)
bool reassign_sub_continuum(double &w, const double ctl, const double KEa) const
Helper function for create_event() that potentially reassigns the value of the energy transfer.
std::shared_ptr< TabulatedXSec > xsec_
Helper object that handles cross section calculations.
static const std::map< SubContinuumMode, std::string > sc_mode_string_map_
Helper map used for conversions between a SubContinuumMode value and a std::string.
double get_Ec_from_Ex(const double Ex, const double cos_theta, const double KEa, double *jacobian=nullptr) const
Helper function for reassign_sub_continuum() that solves for the outgoing lepton total energy that co...
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
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
auto sample_from_distribution(RandomNumberDistribution &rnd) -> decltype(std::declval< RandomNumberDistribution & >().operator()(std::declval< std::mt19937_64 & >()))
Sample from an arbitrary probability distribution (defined here as any object that implements an oper...
Definition Generator.hh:193
static const MassTable & Instance()
Get a const reference to the singleton instance of the MassTable.
Definition MassTable.cc:69
virtual std::shared_ptr< HepMC3::GenEvent > make_nuclear_event_object(double KEa, double pc_cm, double cos_theta_c_cm, double phi_c_cm, double Ec_cm, double Ed_cm, double E_level, int twoJ, const marley::Parity &P) const
Helper function that makes a complete event object for a nuclear reaction.
virtual void set_description()
Creates the description string based on the PDG code values for the initial and final particles.
double KEa_threshold_
Lab-frame kinetic energy of the projectile at threshold for this reaction (i.e., the residue is produ...
double md_gs_
Ground state mass (MeV) of the residue.
Type-safe representation of a parity value (either +1 or -1)
Definition Parity.hh:25
int pdg_a_
PDG code for the projectile.
Definition Reaction.hh:142
double md_
Residue mass (MeV)
Definition Reaction.hh:154
double mc_
Ejectile mass (MeV)
Definition Reaction.hh:149
std::string description_
String that contains a formula describing the reaction.
Definition Reaction.hh:157
int pdg_c_
PDG code for the ejectile.
Definition Reaction.hh:144
int pdg_d_
PDG code for the residue.
Definition Reaction.hh:145
int pdg_a() const
Get the projectile PDG code.
Definition Reaction.hh:109
double ma_
Projectile mass (MeV)
Definition Reaction.hh:147
double mb_
Target mass (MeV)
Definition Reaction.hh:148