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
HauserFeshbachDecay.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#include <memory>
18
19#include "marley/ExitChannel.hh"
20#include "marley/Generator.hh"
21#include "marley/Logger.hh"
22#include "marley/MassTable.hh"
23#include "marley/HauserFeshbachDecay.hh"
24#include "marley/StructureDatabase.hh"
25#include "marley/marley_utils.hh"
26#include "marley/hepmc3_utils.hh"
27
29 const std::shared_ptr< HepMC3::GenParticle >&
30 compound_nucleus, double Exi, int twoJi, marley::Parity Pi,
31 marley::StructureDatabase& sdb ) : compound_nucleus_( compound_nucleus ),
32 Exi_( Exi ), twoJi_( twoJi ), Pi_( Pi )
33{
34 build_exit_channels( sdb );
35}
36
37void marley::HauserFeshbachDecay::build_exit_channels(
39{
40 // Remove any pre-existing ExitChannel objects, just in case
41 exit_channels_.clear();
42
43 int pdgi = compound_nucleus_->pid();
44 int Zi = marley_utils::get_particle_Z( pdgi );
45 int Ai = marley_utils::get_particle_A( pdgi );
46
47 // Get net charge of initial ion
48 int qi = marley_hepmc3::get_particle_charge( *compound_nucleus_ );
49
50 // Get the initial nuclear level density (MeV^{-1}) in the vicinity of the
51 // initial nuclear level. This will be used to apply an overall normalization
52 // factor when computing exit channel decay widths. This isn't strictly
53 // needed for MC sampling, but it's helpful to work with physically
54 // meaningful units when possible.
56 double rho_i = ldm.level_density( Exi_, twoJi_, Pi_ );
57
58 total_width_ = 0.; // total compound nucleus decay width
59
60 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" ) << "Building exit"
61 " channels for PDG " << pdgi << " (Z=" << Zi << ", A=" << Ai
62 << ") at Ex = " << Exi_ << " MeV, 2J = " << twoJi_ << ", P = " << Pi_;
63
64 for ( const auto& pair : sdb.fragments() ) {
65
66 const marley::Fragment& f = pair.second;
67
68 // Get information about the current fragment
69 int fragment_pid = f.get_pid();
70
71 // Get information about the final-state nucleus
72 int Zf = Zi - f.get_Z(); // atomic number
73 int Af = Ai - f.get_A(); // mass number
74 int pdg_final = marley_utils::get_nucleus_pid( Zf, Af );
75
76 // If the final proton number goes negative or the final nucleon number is
77 // zero or less, then we won't have a valid decay, so just move on if
78 // either of those is the case.
79 if ( Zf < 0 || Af < 1 ) continue;
80
81 // Avoid remnants consisting only of multiple neutrons
82 if ( Zf == 0 && Af > 1 ) continue;
83
84 // Don't emit fragments with a higher mass number than the residue itself.
85 // This is already covered by swapping the residue/fragment roles.
86 if ( f.get_A() > Af ) continue;
87
88 // Approximate the ground state mass of the ion formed when the fragment f
89 // is emitted by adding (Za - qi) electron masses to the atomic mass for
90 // the final nucleus.
91 const auto& mt = marley::MassTable::Instance();
92 double Sa = mt.get_fragment_separation_energy( Zi, Ai, fragment_pid );
93
94 // Get discrete level data (if any) and models for the final nucleus
95 marley::DecayScheme* ds = sdb.get_decay_scheme( pdg_final );
96
97 // Determine the maximum excitation energy available after fragment
98 // emission in the final nucleus. This is simply the difference
99 // between the initial excitation energy and the fragment separation
100 // energy.
101 double Exf_max = Exi_ - Sa;
102
103 // Check if emission of this fragment is energetically allowed. If we're
104 // exactly at threshold, still refuse to emit the fragment to avoid
105 // numerical problems.
106 if ( Exf_max <= 0. ) continue;
107
108 // Let the continuum go down to 0 MeV unless there is a decay scheme object
109 // available for the final nuclide (we'll check this in a second).
110 double E_c_min = 0.;
111
112 // If discrete level data are available for the final nuclide, get decay
113 // widths for each accessible level
114 if ( ds ) {
115
116 // Get a vector of pointers to levels in the decay scheme. The levels are
117 // sorted in order of increasing excitation energy.
118 const auto& levels = ds->get_levels();
119
120 // Use the maximum discrete level energy from the decay scheme object as
121 // the lower bound for the continuum
122 // TODO: consider whether this is the best approach
123 if ( levels.size() > 0 ) E_c_min = levels.back()->energy();
124
125 // Loop over the final discrete nuclear levels in order of increasing
126 // energy until the new level energy exceeds the maximum value. For each
127 // energetically allowed level, if a transition to it for a given
128 // fragment orbital angular momentum l and total angular momentum j
129 // conserves parity, then compute an optical model transmission
130 // coefficient and add it to the total.
131 for (const auto& level : levels) {
132 double Exf = level->energy();
133 if ( Exf < Exf_max ) {
134
135 // Store information for this decay channel
136 auto ec = std::make_unique< marley::FragmentDiscreteExitChannel >(
137 pdgi, qi, Exi_, twoJi_, Pi_, rho_i, sdb, *level, f );
138
139 total_width_ += ec->width();
140 MARLEY_LOG( TRACE, "physics.deexcitation.hauser.widths" )
141 << " fragment " << marley_utils::particle_symbols.at( fragment_pid )
142 << " -> discrete level at " << Exf << " MeV, width = "
143 << ec->width() << " MeV";
144
145 exit_channels_.push_back( std::move(ec) );
146 }
147 else break;
148 }
149 }
150
151 // If transitions to the energy continuum are possible, include the
152 // continuum in the decay channels
153 if ( Exf_max > E_c_min ) {
154
155 // Create an ExitChannel object to handle decays to the continuum
156 auto ec = std::make_unique< marley::FragmentContinuumExitChannel >(
157 pdgi, qi, Exi_, twoJi_, Pi_, rho_i, sdb, E_c_min, f );
158
159 total_width_ += ec->width();
160 MARLEY_LOG( TRACE, "physics.deexcitation.hauser.widths" )
161 << " fragment " << marley_utils::particle_symbols.at( fragment_pid )
162 << " -> continuum [" << E_c_min << ", " << Exf_max << "] MeV,"
163 << " width = " << ec->width() << " MeV";
164
165 exit_channels_.push_back( std::move(ec) );
166 }
167 }
168
169 marley::DecayScheme* ds = sdb.get_decay_scheme( pdgi );
170
171 // For gamma-ray emission, let the continuum go down to Ex = 0 MeV unless
172 // there is a decay scheme object available (we'll check this in a second).
173 double E_c_min = 0.;
174
175 // If discrete level data is available for this nuclide, get gamma decay
176 // widths for each accessible level
177 if ( ds ) {
178
179 // Loop over the final discrete nuclear levels in order of increasing
180 // energy until the new level energy exceeds the maximum value. For each
181 // energetically allowed level, compute a gamma ray transmission
182 // coefficient for it
183 const auto& levels = ds->get_levels();
184
185 // Use the maximum discrete level energy from the decay scheme object as
186 // the lower bound for the continuum.
187 // TODO: consider whether this is the best approach
188 if ( levels.size() > 0 ) E_c_min = levels.back()->energy();
189
190 for (const auto& level_f : levels) {
191 double Exf = level_f->energy();
192 if (Exf < Exi_) {
193 auto ec = std::make_unique< marley::GammaDiscreteExitChannel >( pdgi,
194 qi, Exi_, twoJi_, Pi_, rho_i, sdb, *level_f );
195
196 total_width_ += ec->width();
197 MARLEY_LOG( TRACE, "physics.deexcitation.hauser.widths" )
198 << " gamma -> discrete level at " << Exf << " MeV, width = "
199 << ec->width() << " MeV";
200
201 exit_channels_.push_back( std::move(ec) );
202 }
203 else break;
204 }
205 }
206
207 // If gamma transitions to the energy continuum are possible, include them
208 // in the possible decay channels
209 if ( Exi_ > E_c_min ) {
210
211 // Create an exit channel object to handle gamma-ray emission into the
212 // continuum
213 auto ec = std::make_unique< marley::GammaContinuumExitChannel >( pdgi,
214 qi, Exi_, twoJi_, Pi_, rho_i, sdb, E_c_min );
215
216 total_width_ += ec->width();
217 MARLEY_LOG( TRACE, "physics.deexcitation.hauser.widths" )
218 << " gamma -> continuum [" << E_c_min << ", " << Exi_ << "] MeV,"
219 << " width = " << ec->width() << " MeV";
220
221 exit_channels_.push_back( std::move(ec) );
222 }
223
224 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" ) << "Total compound"
225 " nucleus width = " << total_width_ << " MeV ("
226 << exit_channels_.size() << " exit channels)";
227}
228
230 double& Exf, int& twoJf, marley::Parity& Pf,
231 std::shared_ptr< HepMC3::GenParticle >& emitted_particle,
232 std::shared_ptr< HepMC3::GenParticle >& residual_nucleus,
233 int& qIon, marley::Generator& gen )
234{
235 const auto& ec = this->sample_exit_channel( gen );
236
237 ec->do_decay( Exf, twoJf, Pf, compound_nucleus_, emitted_particle,
238 residual_nucleus, qIon, gen );
239
240 return *ec;
241}
242
243void marley::HauserFeshbachDecay::print(std::ostream& out) const {
244
245 out << "Compound nucleus " << compound_nucleus_->pid()
246 << " with Ex = " << Exi_ << ", spin = " << twoJi_ / 2;
247 if (twoJi_ % 2) out << ".5";
248 out << ", and parity = " << Pi_ << '\n';
249 out << "Total width = " << total_width_ << " MeV\n";
250 out << "Mean lifetime = " << marley_utils::hbar / total_width_ << " s\n";
251 for (const auto& ec : exit_channels_) {
252 double width = ec->width();
253 bool continuum = ec->is_continuum();
254 bool frag = ec->emits_fragment();
255 int pdg = ec->emitted_particle_pdg();
256 std::string symbol = marley_utils::particle_symbols.at( pdg );
257 out << " ";
258 if ( frag ) out << symbol;
259 else out << "gamma-ray";
260 if ( continuum ) out << " emission to the continuum width = ";
261 else {
262 auto* dec = dynamic_cast< marley::DiscreteExitChannel* >( ec.get() );
263 if ( !dec ) throw marley::Error( "Dynamic cast failed in marley::"
264 "HauserFeshbachDecay::print()" );
265 out << " emission to level at " << dec->get_final_level().energy()
266 << " MeV width = ";
267 }
268 out << width << " MeV\n";
269 }
270}
271
272const std::unique_ptr< marley::ExitChannel >&
274 marley::Generator& gen ) const
275{
276 // Throw an error if all decays are impossible
277 if ( total_width_ <= 0. ) throw marley::Error( "Cannot sample an exit channel"
278 " for a Hauser-Feshbach decay. All partial decay widths are zero." );
279
280 // Sample an exit channel using a std::discrete distribution and a table of
281 // partial decay widths
282 const auto widths_begin
283 = marley::ExitChannel::make_width_iterator( exit_channels_.cbegin() );
284 const auto widths_end
285 = marley::ExitChannel::make_width_iterator( exit_channels_.cend() );
286
287 std::discrete_distribution<size_t> exit_channel_dist( widths_begin,
288 widths_end );
289 size_t exit_channel_index = gen.sample_from_distribution( exit_channel_dist );
290
291 const auto& ec = exit_channels_.at( exit_channel_index );
292
293 // Log which exit channel was selected and its branching fraction
294 double width_ec = ec->width();
295 double branching = ( total_width_ > 0. ) ? ( width_ec / total_width_ ) : 0.;
296 int ec_pdg = ec->emitted_particle_pdg();
297 std::string ec_symbol = marley_utils::particle_symbols.count( ec_pdg )
298 ? marley_utils::particle_symbols.at( ec_pdg ) : std::to_string( ec_pdg );
299 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" ) << "Selected exit channel:"
300 " emits " << ec_symbol
301 << ( ec->is_continuum() ? " to continuum" : " to discrete level" )
302 << ", width = " << width_ec << " MeV"
303 << ", branching fraction = " << branching;
304
305 return ec;
306}
Discrete level and γ-ray data for a specific nuclide.
const std::vector< std::unique_ptr< marley::Level > > & get_levels() const
Get a const reference to the vector that holds the Level objects.
Abstract base class for ExitChannel objects that lead to discrete nuclear levels in the final state.
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
Abstract base class for compound nucleus de-excitation channels.
static marley::IteratorToPointerMember< It, double > make_width_iterator(It it)
Convert an iterator that points to an ExitChannel object into an iterator to the ExitChannel's width_...
Simple container for storing reference data about each of the nuclear fragments considered by MARLEY'...
Definition Fragment.hh:27
int get_Z() const
Get the atomic number of this fragment.
Definition Fragment.hh:82
int get_A() const
Get the mass number of this fragment.
Definition Fragment.hh:85
int get_pid() const
Get the PDG particle ID for this fragment.
Definition Fragment.hh:73
The MARLEY Event generator.
Definition Generator.hh:54
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
const marley::ExitChannel & do_decay(double &Exf, int &twoJf, marley::Parity &Pf, std::shared_ptr< HepMC3::GenParticle > &emitted_particle, std::shared_ptr< HepMC3::GenParticle > &residual_nucleus, int &qIon, marley::Generator &gen)
Simulates a decay of the compound nucleus.
void print(std::ostream &out) const
Print information about the possible decay channels to a std::ostream.
HauserFeshbachDecay(const std::shared_ptr< HepMC3::GenParticle > &compound_nucleus, double Exi, int twoJi, marley::Parity Pi, marley::StructureDatabase &sdb)
const std::unique_ptr< marley::ExitChannel > & sample_exit_channel(marley::Generator &gen) const
Helper function for do_decay(). Samples an ExitChannel using the partial decay widths as weights.
Abstract base class for models of nuclear level densities.
virtual double level_density(double Ex)=0
static const MassTable & Instance()
Get a const reference to the singleton instance of the MassTable.
Definition MassTable.cc:69
Type-safe representation of a parity value (either +1 or -1)
Definition Parity.hh:25
Container for nuclear structure information organized by nuclide.
static const std::map< int, marley::Fragment > & fragments()
Retrieves a const reference to the table of Fragment objects.
marley::DecayScheme * get_decay_scheme(const int particle_id)
Retrieves discrete level data from the database.
marley::LevelDensityModel & get_level_density_model(const int nucleus_pid)
Retrieves a level density model object from the database, creating it if one did not already exist.