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
marley::NucleusDecayer Class Reference

EventProcessor that handles nuclear de-excitations. More...

#include <NucleusDecayer.hh>

Inheritance diagram for marley::NucleusDecayer:
marley::EventProcessor

Public Member Functions

virtual void process_event (HepMC3::GenEvent &event, marley::Generator &gen) override
 Processes an input GenEvent object.
 

Detailed Description

EventProcessor that handles nuclear de-excitations.

Definition at line 23 of file NucleusDecayer.hh.

Constructor & Destructor Documentation

◆ NucleusDecayer()

marley::NucleusDecayer::NucleusDecayer ( )
inline

Definition at line 27 of file NucleusDecayer.hh.

27{}

Member Function Documentation

◆ process_event()

void marley::NucleusDecayer::process_event ( HepMC3::GenEvent & ev,
marley::Generator & gen )
overridevirtual

Processes an input GenEvent object.

Parameters
[in,out]evThe GenEvent object to be processed
[in]genIf needed, the Generator object to use during processing (e.g., for obtaining random numbers)
Todo
Revisit this

Implements marley::EventProcessor.

Definition at line 53 of file NucleusDecayer.cc.

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
259 ::FragmentContinuumExitChannel::FragmentSpinParityWidth* >(
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
270 ::GammaContinuumExitChannel::GammaSpinParityWidth* >( spw_ptr );
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}
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.
marley::StructureDatabase & get_structure_db()
Get a reference to the StructureDatabase owned by this Generator.
Definition Generator.cc:510
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
@ 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.

References marley::Reaction::AntiNeutrinoCC_Continuum, marley::ContinuumExitChannel::SpinParityWidth::diff_width, marley::DecayScheme::do_cascade(), marley::HauserFeshbachDecay::do_decay(), marley::Level::energy(), marley::StructureDatabase::get_decay_scheme(), marley::ContinuumExitChannel::get_last_sampled_spw(), marley::DecayScheme::get_pointer_to_closest_level(), marley::Generator::get_structure_db(), marley::MassTable::Instance(), marley::ExitChannel::is_continuum(), marley::Reaction::NC_Continuum, and marley::Reaction::NeutrinoCC_Continuum.

Referenced by marley::CommandHandler::cmd_decay(), marley::Generator::create_event(), and marley::Generator::create_event().


The documentation for this class was generated from the following files: