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
NucleusDecayer.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/GenEvent.h"
20#include "HepMC3/GenParticle.h"
21#include "HepMC3/GenVertex.h"
22
23// MARLEY includes
24#include "marley/hepmc3_utils.hh"
25#include "marley/marley_utils.hh"
26#include "marley/Error.hh"
27#include "marley/Generator.hh"
28#include "marley/HauserFeshbachDecay.hh"
29#include "marley/Level.hh"
30#include "marley/Logger.hh"
31#include "marley/MatrixElement.hh"
32#include "marley/NucleusDecayer.hh"
33#include "marley/Parity.hh"
34
36
37namespace {
38 // In cases where no discrete level data are available, a continuum level
39 // density is used all the way down to the ground state. To avoid
40 // asymptotically approaching Ex = 0 in these cases, the de-excitation cascade
41 // will end once the excitation energy of the residual nucleus falls below
42 // this (small) value. Excitation energies below this value are considered
43 // "close enough" to the ground state for MARLEY not to worry about further
44 // de-excitations.
46 constexpr double CONTINUUM_GS_CUTOFF = 0.001; // MeV
47
48 // The size of a tolerable discrepancy (in MeV) between the excitation energy
49 // stored in the event record and other (hopefully consistent) versions of it
50 constexpr double EX_TOLERANCE = 1e-5; // MeV
51}
52
55{
56 auto undecayed_residues = marley_hepmc3::get_particles_with_status(
57 marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS, event );
58
59 MARLEY_LOG( DEBUG, "physics.deexcitation" ) << "NucleusDecayer: processing "
60 << undecayed_residues.size() << " undecayed residue(s)";
61
62 // Check the reaction process that created this event. The process types
63 // distinguish between discrete and continuum reactions, which is helpful
64 // below.
65 int proc_id = event.attribute< HepMC3::IntAttribute >(
66 "signal_process_id" )->value();
67 auto proc_type = marley_hepmc3::from_nuhepmc_proc_id( proc_id );
68 bool is_continuum_channel = false;
72 {
73 is_continuum_channel = true;
74 }
75
76 for ( auto residue : undecayed_residues ) {
77
78 // Get the residue excitation energy from the event. These values represent
79 // its state immediately following the initial two-two scattering reaction.
80 double Ex = residue->attribute< HepMC3::DoubleAttribute >( "Ex" )->value();
81 int twoJ = residue->attribute< HepMC3::IntAttribute >( "twoJ" )->value();
82 int p_int = residue->attribute< HepMC3::IntAttribute >( "parity" )->value();
83 marley::Parity P( p_int );
84
85 // If the residue is in its ground state, then there's nothing for us to do.
86 // Just continue the loop without comment.
87 if ( Ex == 0. ) continue;
88
89 MARLEY_LOG( DEBUG, "physics.deexcitation" ) << "De-exciting residue PDG "
90 << residue->pid() << ": Ex = " << Ex << " MeV, 2J = " << twoJ
91 << ", P = " << P;
92
93 // The excitation energy should be nonnegative. Complain if it's not.
94 if ( Ex < 0. ) throw marley::Error("Negative excitation energy Ex = "
95 + std::to_string(Ex) + " MeV encountered in marley::NucleusDecayer::"
96 "deexcite_residue()");
97
98 // To prevent accidental double application of the de-excitation cascade,
99 // check that the residue mass is consistent with the excitation energy
100 // stored in the event record (and thus was never decayed).
101 const auto& mt = marley::MassTable::Instance();
102 int initial_residue_pdg = residue->pid();
103 int qIon = marley_hepmc3::get_particle_charge( *residue );
104
105 // Check that the residue PDG code makes sense. If it's not a nucleus,
106 // warn the user and refuse to do the cascade.
107 if ( !marley_utils::is_ion(initial_residue_pdg) ) {
108 MARLEY_LOG( WARN, "physics.deexcitation" )
109 << "Unrecognized nuclear PDG code "
110 << initial_residue_pdg << " encountered in marley::NucleusDecayer::"
111 << "deexcite_residue(). The de-excitation cascade will be skipped";
112 continue;
113 }
114
115 double residue_mass = residue->generated_mass();
116
117 // Ground-state residue mass
118 double gs_residue_mass = mt.get_atomic_mass( initial_residue_pdg )
119 - qIon*mt.get_particle_mass( marley_utils::ELECTRON );
120
121 double expected_residue_mass = gs_residue_mass + Ex;
122
123 if ( std::abs(residue_mass - expected_residue_mass) > EX_TOLERANCE ) {
124
125 if ( std::abs(residue_mass - gs_residue_mass) <= EX_TOLERANCE ) {
126 MARLEY_LOG( WARN, "physics.deexcitation" )
127 << "Encountered ground-state nuclear remnant"
128 << " in marley::NucleusDecay::deexcite_residue(). The de-excitation"
129 << " cascade has already been applied.";
130 continue;
131 }
132
133 // If we get here, then the residue is not in its ground state but also
134 // not in the initial excited state given in the event record. Something
135 // went wrong with a partial application of a de-excitation cascade.
136 // Throw an error rather than trying to figure out how to do the right
137 // thing.
139 throw marley::Error("Partially de-excited nuclear remnant encountered"
140 " in marley::NucleusDecay::deexcite_residue().");
141 }
142
143 // Decide whether we need to start the de-excitation cascade from a
144 // discrete nuclear level or from the continuum. Do this by comparing the
145 // excitation energy from the event record to the "unbound threshold" for
146 // the residue. If we're above the unbound threshold, do a continuum decay.
147 // Also start with a continuum decay if no discrete level data are
148 // available for the residue.
149 auto* ds = gen.get_structure_db().get_decay_scheme( initial_residue_pdg );
150 double unbound_threshold = mt.unbound_threshold( initial_residue_pdg );
151
152 // If Reaction::set_level_ptrs() changes, you'll want to change this too.
153 // TODO: find a better way of keeping the two pieces of code in sync
154 // TODO: numerical round-off can cause issues with the first test, so you
155 // should revisit it again when interfacing MARLEY with other codes
156 // that use it solely as a de-excitation model. For now, the third
157 // option in the logical OR prevents issues with numerical round-off near
158 // the unbound threshold.
159 bool continuum = ( Ex > unbound_threshold )
160 || ( !ds ) || ( is_continuum_channel );
161
162 // Keep track of whether the cascade was started from the continuum
163 // or not. If it was started from a discrete level, we'll double-check that
164 // discrete level's excitation energy below.
165 bool started_from_continuum = continuum;
166
167 MARLEY_LOG( DEBUG, "physics.deexcitation" ) << "De-excitation path: "
168 << ( continuum ? "continuum (Hauser-Feshbach)" : "discrete gamma cascade" );
169
170 if ( continuum ) {
171
172 // Particles used for storage of binary decay products during the
173 // de-excitation cascade
174 auto first = std::make_shared< HepMC3::GenParticle >();
175 auto second = std::make_shared< HepMC3::GenParticle >();
176
177 // The selected level is unbound, so handle its de-excitation using
178 // the Hauser-Feshbach statistical model.
179 while ( continuum && Ex > CONTINUUM_GS_CUTOFF ) {
180
181 auto& sdb = gen.get_structure_db();
182
183 marley::HauserFeshbachDecay hfd( residue, Ex, twoJ, P, sdb );
184 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" ) << hfd;
185
186 int q_second;
187 const auto& exit_channel = hfd.do_decay( Ex, twoJ, P, first, second,
188 q_second, gen );
189
190 continuum = exit_channel.is_continuum();
191
192 double width_tot = hfd.total_width();
193 double width_ec = exit_channel.width();
194
195 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" )
196 << "Hauser-Feshbach decay to " << first->pid()
197 << " and " << second->pid();
198 MARLEY_LOG( DEBUG, "physics.deexcitation.hauser" )
199 << second->pid() << " is at Ex = " << Ex << " MeV.";
200
201 // Create a new binary decay vertex
202 auto decay_vtx = std::make_shared< HepMC3::GenVertex >();
203 decay_vtx->set_status( marley_hepmc3::NUHEPMC_HF_DECAY_VERTEX );
204
205 decay_vtx->add_particle_in( residue );
206 decay_vtx->add_particle_out( first );
207 decay_vtx->add_particle_out( second );
208
209 // Sample a decay time (MeV^{-1}) for emission of the chosen particle
210 // and store this timing information in the new binary decay vertex
211 marley_hepmc3::store_decay_time( width_ec, gen, decay_vtx, residue );
212
213 // Add the decay vertex to the event record
214 event.add_vertex( decay_vtx );
215
216 // We can now set the charge of the daughter ion because it belongs
217 // to the parent event (through the decay vertex)
218 marley_hepmc3::set_particle_charge( *second, q_second );
219
220 // We can also now set the attributes representing the daughter ion's
221 // excitation energy, spin, and parity
222 second->add_attribute( "Ex",
223 std::make_shared< HepMC3::DoubleAttribute >(Ex) );
224 second->add_attribute( "twoJ",
225 std::make_shared< HepMC3::IntAttribute >(twoJ) );
226 second->add_attribute( "parity",
227 std::make_shared< HepMC3::IntAttribute >(static_cast<int>( P )) );
228
229 // The daughter ion now takes the role of the residue for the next loop
230 // iteration
231 residue.swap( second );
232
233 // Store some information about the total and partial widths of
234 // the simulated compound nucleus decay in attributes attached to
235 // the decay vertex
236 decay_vtx->add_attribute( "TotalWidth",
237 std::make_shared< HepMC3::DoubleAttribute >(width_tot) );
238
239 decay_vtx->add_attribute( "ECWidth",
240 std::make_shared< HepMC3::DoubleAttribute >(width_ec) );
241
242 // In the case of a transition to the continuum, also store the partial
243 // differential width for the chosen spin-parity of the daughter
244 // nucleus
245 if ( continuum ) {
246 const auto& cec = dynamic_cast< const marley::ContinuumExitChannel& >(
247 exit_channel );
248
249 const auto* spw_ptr = cec.get_last_sampled_spw();
250 double width_sp = spw_ptr->diff_width;
251
252 decay_vtx->add_attribute( "SPWidth",
253 std::make_shared< HepMC3::DoubleAttribute >(width_sp) );
254
255 bool is_fragment_emission = exit_channel.emits_fragment();
256
257 if ( is_fragment_emission ) {
258 const auto* f_spw = static_cast< const marley
260 spw_ptr );
261 decay_vtx->add_attribute( "two_j_frag",
262 std::make_shared< HepMC3::IntAttribute >(f_spw->two_j_frag) );
263 decay_vtx->add_attribute( "orb_l",
264 std::make_shared< HepMC3::IntAttribute >(f_spw->orb_l) );
265 }
266
267 else {
268 // Gamma-ray emission in the continuum
269 const auto* g_spw = static_cast< const marley
271
272 decay_vtx->add_attribute( "multipolarity",
273 std::make_shared< HepMC3::IntAttribute >(g_spw->multipolarity) );
274 }
275 }
276 }
277 }
278
279 if ( !continuum ) {
280 // Either the selected initial level was bound (so it will only decay via
281 // gamma emission) or the Hauser-Feshbach decay process has now accessed
282 // a bound level in the residual nucleus. In either case, use gamma-ray
283 // decay scheme data to sample the de-excitation gammas and add them to
284 // this event's final particle list.
285 marley::DecayScheme* dec_scheme = gen.get_structure_db()
286 .get_decay_scheme( residue->pid() );
287
288 // Start the gamma cascade from this discrete level
289 marley::Level* lev = dec_scheme->get_pointer_to_closest_level( Ex );
290
291 // If we get a null level pointer from the decay scheme, complain
292 if ( !lev ) throw marley::Error( "Null nuclear level pointer encountered"
293 " in marley::NucleusDecayer::deexcite_residue()" );
294
295 // If we did not simulate any continuum decays before getting to this
296 // point, then double-check that the excitation energy from the event
297 // record and the initial level are consistent. If they're not, then
298 // complain by throwing an error.
299 if ( !started_from_continuum ) {
300 double Ex_level = lev->energy();
301 if ( std::abs(Ex - Ex_level) > EX_TOLERANCE ) {
302 throw marley::Error( "Excitation energy mismatch encountered in"
303 " marley::NucleusDecayer::deexcite_residue(). Event has Ex = "
304 + std::to_string(Ex) + " MeV while the initial discrete level has "
305 + std::to_string(Ex_level) + " MeV" );
306 }
307 }
308
309 dec_scheme->do_cascade( *lev, event, gen, residue );
310 }
311
312 } // loop over undecayed residues
313
314}
Attribute that holds a real number as a double.
Definition Attribute.h:245
Stores event-related information.
Definition GenEvent.h:47
Attribute that holds an Integer implemented as an int.
Definition Attribute.h:157
Abstract base class for ExitChannel objects that lead to the unbound continuum in the final state.
const SpinParityWidth * get_last_sampled_spw() const
Returns a pointer to the last sampled SpinParityWidth object.
Discrete level and γ-ray data for a specific nuclide.
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)
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
virtual bool is_continuum() const =0
Returns true if this channel accesses the particle-unbound continuum of nuclear levels or false other...
The MARLEY Event generator.
Definition Generator.hh:54
marley::StructureDatabase & get_structure_db()
Get a reference to the StructureDatabase owned by this Generator.
Definition Generator.cc:510
Monte Carlo implementation of the Hauser-Feshbach statistical model for decays of highly-excited nucl...
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.
A discrete nuclear energy level.
Definition Level.hh:29
double energy() const
Get the excitation energy of this level (MeV)
Definition Level.hh:135
static const MassTable & Instance()
Get a const reference to the singleton instance of the MassTable.
Definition MassTable.cc:69
TransitionType
Enumerated type that represents the possible kinds of nuclear transitions recognized by MARLEY.
virtual void process_event(HepMC3::GenEvent &event, marley::Generator &gen) override
Processes an input GenEvent object.
Type-safe representation of a parity value (either +1 or -1)
Definition Parity.hh:25
@ NC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:66
@ AntiNeutrinoCC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:65
@ NeutrinoCC_Continuum
Nuclear matrix elements contain for a transition to a continuum of nuclear levels.
Definition Reaction.hh:64
marley::DecayScheme * get_decay_scheme(const int particle_id)
Retrieves discrete level data from the database.
double diff_width
Partial differential decay width (MeV)