62 std::string config_file_name;
63 if ( !args.empty() ) config_file_name = args.front();
65 if ( config_file_name.empty() || config_file_name ==
"-h"
66 || config_file_name ==
"--help" )
69 args.push_front(
"decay" );
79 marley::JSON json = marley::JSON::load_file( config_file_name );
81 if ( !json.has_key(
"reactions") ) json[
"reactions"] =
marley::JSON(
nullptr );
82 if ( !json.has_key(
"source") ) json[
"source"] =
marley::JSON(
nullptr );
93 const std::string decay_config_label(
"decay" );
95 ok = get_from_json< marley::JSON >( decay_config_label, json, decay_config );
96 if ( !ok )
throw marley::Error(
"Missing key '" + decay_config_label
97 +
"' in job configuration file" );
99 long num_events = assign_from_json< long >(
"events", decay_config, ok, 1000 );
104 if ( !decay_config.has_key(
"nucleus") ) {
105 throw marley::Error(
"Missing required \"nucleus\" key in the"
106 " \"decay\" configuration block" );
108 const marley::JSON& nuc_config = decay_config.at(
"nucleus" );
115 bool has_pdg = nuc_config.has_key(
"pdg");
116 bool has_Z = nuc_config.has_key(
"Z");
117 bool has_A = nuc_config.has_key(
"A");
119 if ( !has_pdg && !( has_Z && has_A ) ) {
120 throw marley::Error(
"The \"nucleus\" block must specify the nuclide"
121 " using either the \"pdg\" key or both the \"Z\" and \"A\" keys" );
128 nucleus_pdg = assign_from_json< int >(
"pdg", nuc_config, ok );
129 int Z_from_pdg = 0, A_from_pdg = 0;
130 if ( !pdg_to_ZA( nucleus_pdg, Z_from_pdg, A_from_pdg ) ) {
131 throw marley::Error(
"The value " + std::to_string(nucleus_pdg)
132 +
" given for \"nucleus.pdg\" is not a recognized nuclear"
133 " or nucleon PDG code" );
139 if ( has_Z && has_A ) {
140 int Z_cfg = assign_from_json< int >(
"Z", nuc_config, ok );
141 int A_cfg = assign_from_json< int >(
"A", nuc_config, ok );
144 if ( Z_cfg != Z || A_cfg != A ) {
145 throw marley::Error(
"Inconsistent nucleus specification: pdg="
146 + std::to_string(nucleus_pdg) +
" implies Z=" + std::to_string(Z)
147 +
", A=" + std::to_string(A) +
", but Z=" + std::to_string(Z_cfg)
148 +
", A=" + std::to_string(A_cfg) +
" were also given" );
154 nucleus_pdg = marley_utils::get_nucleus_pid( Z, A );
158 if ( Z < 0 )
throw marley::Error(
"Negative Z encountered" );
163 int net_charge = assign_from_json< int >(
"net_charge", nuc_config, ok, 0 );
171 bool sample_Ex = nuc_config.has_key(
"Ex_max" );
173 Ex = assign_from_json< double >(
"Ex", nuc_config, ok, -1.0 );
174 if ( !ok )
throw marley::Error(
"Missing \"Ex\" key in \"nucleus\" block"
175 " (or use \"Ex_min\" + \"Ex_max\" for uniform sampling)" );
176 if ( Ex < 0. )
throw marley::Error(
"Negative excitation energy"
177 " encountered (Ex = " + std::to_string(Ex) +
" MeV)" );
180 Ex_min = assign_from_json< double >(
"Ex_min", nuc_config, ok, -1.0 );
181 Ex_max = assign_from_json< double >(
"Ex_max", nuc_config, ok, -1.0 );
182 if ( Ex_min < 0. )
throw marley::Error(
"Negative lower excitation"
183 " energy bound (Ex_min = " + std::to_string(Ex_min) +
" MeV)" );
184 if ( Ex_max < 0. )
throw marley::Error(
"Negative upper excitation"
185 " energy bound (Ex_max = " + std::to_string(Ex_max) +
" MeV)" );
187 " (" + std::to_string(Ex_max) +
" MeV) is less than lower Ex bound"
188 " (" + std::to_string(Ex_min) +
" MeV)" );
194 std::vector< int > twoJ_vec;
195 if ( !nuc_config.has_key(
"twoJ") ) {
196 throw marley::Error(
"Missing \"twoJ\" key in \"nucleus\" block" );
200 if ( !twoJ_obj.is_array() ) {
201 throw marley::Error(
"The \"twoJ\" key in \"nucleus\" must have"
202 " a value that is a JSON array" );
204 convert_json< std::vector<int> >( twoJ_obj, twoJ_vec );
206 if ( twoJ_vec.empty() ) {
207 throw marley::Error(
"The \"twoJ\" array in \"nucleus\" must not"
210 for (
const auto& tJ : twoJ_vec ) {
211 if ( tJ < 0 )
throw marley::Error(
"Negative 2J value encountered"
212 " in \"twoJ\" array" );
213 bool even_A = ( A % 2 == 0 );
214 bool even_twoJ = ( tJ % 2 == 0 );
215 if ( even_A != even_twoJ )
throw marley::Error(
"Unphysical twoJ = "
216 + std::to_string( tJ ) +
" encountered for A = "
217 + std::to_string( A ) );
220 std::vector< double > twoJ_weights( twoJ_vec.size(), 1. );
221 std::discrete_distribution< size_t > twoJ_dist(
222 twoJ_weights.cbegin(), twoJ_weights.cend() );
227 auto parity_str = assign_from_json< std::string >(
"parity", nuc_config,
229 if ( parity_str !=
"+" && parity_str !=
"-" && parity_str !=
"random" ) {
231 + parity_str +
"\" in \"nucleus\" block."
232 " Expected \"+\", \"-\", or \"random\"" );
235 const std::vector< std::string > parity_strings = {
"+",
"-" };
236 const std::vector< double > parity_weights = { 1., 1. };
237 std::discrete_distribution< size_t > parity_dist(
238 parity_weights.cbegin(), parity_weights.cend() );
243 std::vector< std::shared_ptr<marley::OutputFile> > output_files;
245 if ( decay_config.has_key(
"output") ) {
248 " \"output\" key must have a value that is a JSON array." );
249 for (
const auto& el : output_set.array_range() ) {
250 output_files.push_back( marley::OutputFile::make_OutputFile(el) );
254 std::string out_cfg_str =
"{ format: \"ascii\","
255 " file: \"decay_events.hepmc3\", mode: \"overwrite\" }";
256 auto out_cfg = marley::JSON::load( out_cfg_str );
257 output_files.push_back( marley::OutputFile::make_OutputFile(out_cfg) );
260 for (
const auto& file : output_files ) {
261 if ( file->mode_is_resume() ) {
262 throw marley::Error(
"The \"resume\" output mode is not supported"
263 " by the \"marley decay\" command" );
272 double gs_mass = mt.get_atomic_mass( nucleus_pdg )
273 - net_charge * mt.get_particle_mass( marley_utils::ELECTRON );
275 double unbound_threshold = mt.unbound_threshold( nucleus_pdg );
277 int signal_proc_id = marley_hepmc3::get_nuhepmc_proc_id(
278 ProcType::StandaloneDecay );
284 bool warned_snap =
false;
285 bool warned_snap_sample =
false;
287 for (
long evnum = 0; evnum < num_events; ++evnum ) {
295 int twoJ = twoJ_vec.at( twoJ_index );
297 std::string par_str = parity_str;
298 if ( par_str ==
"random" ) {
300 par_str = parity_strings.at( par_index );
303 std::istringstream temp_iss( par_str );
307 if ( Ex <= unbound_threshold ) {
310 auto* lev = ds->get_pointer_to_closest_level( Ex );
313 double Ex_lev = lev->energy();
314 int twoJ_lev = lev->twoJ();
317 bool Ex_changed = ( std::abs(Ex_lev - Ex) > 1e-5 );
318 bool twoJ_changed = ( twoJ_lev != twoJ );
319 bool P_changed = (
static_cast<int>(P_lev)
320 !=
static_cast<int>(parity) );
322 if ( ( Ex_changed || twoJ_changed || P_changed ) && !warned_snap ) {
323 std::ostringstream warn_msg;
324 warn_msg <<
"User-specified nuclear state (Ex=" << Ex
325 <<
" MeV, 2J=" << twoJ
327 <<
") was snapped to nearest discrete level (Ex="
328 << Ex_lev <<
" MeV, 2J=" << twoJ_lev
329 <<
", P=" << P_lev <<
").";
330 if ( !Ex_changed ) warn_msg <<
" Ex unchanged.";
331 if ( !twoJ_changed ) warn_msg <<
" 2J unchanged.";
332 if ( !P_changed ) warn_msg <<
" Parity unchanged.";
333 warn_msg <<
" This warning will not be repeated.";
334 MARLEY_LOG( WARN,
"cmd.decay" ) << warn_msg.str();
337 if ( sample_Ex && !warned_snap_sample ) {
338 MARLEY_LOG( WARN,
"cmd.decay" ) <<
"Discrete level matching"
339 " will continue for subsequent events as Ex values below the"
340 " unbound threshold (" << unbound_threshold <<
" MeV) are"
341 " sampled. This message will not be repeated.";
342 warned_snap_sample =
true;
364 auto event = std::make_shared< HepMC3::GenEvent >(
365 HepMC3::Units::MEV, HepMC3::Units::CM );
367 event->set_event_number( evnum + 1 );
369 event->add_attribute(
"signal_process_id",
370 std::make_shared< HepMC3::IntAttribute >( signal_proc_id ) );
372 auto prim_vtx = std::make_shared< HepMC3::GenVertex >();
373 prim_vtx->set_status( marley_hepmc3::NUHEPMC_PRIMARY_VERTEX );
374 event->add_vertex( prim_vtx );
377 auto projectile = marley_hepmc3::make_particle( 0, 0., 0., 0., 0.,
378 marley_hepmc3::NUHEPMC_PROJECTILE_STATUS, 0. );
381 auto target = marley_hepmc3::make_particle( nucleus_pdg,
382 marley_hepmc3::NUHEPMC_TARGET_STATUS, gs_mass );
385 auto ejectile = marley_hepmc3::make_particle( 0, 0., 0., 0., 0.,
386 marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, 0. );
389 double m_residue = gs_mass + Ex;
390 auto residue = marley_hepmc3::make_particle( nucleus_pdg, 0., 0., 0.,
391 m_residue, marley_hepmc3::NUHEPMC_UNDECAYED_RESIDUE_STATUS, m_residue );
393 prim_vtx->add_particle_in( projectile );
394 prim_vtx->add_particle_in( target );
395 prim_vtx->add_particle_out( ejectile );
396 prim_vtx->add_particle_out( residue );
400 marley_hepmc3::set_particle_charge( *target, net_charge );
401 marley_hepmc3::set_particle_charge( *residue, net_charge );
403 residue->add_attribute(
"Ex",
404 std::make_shared< HepMC3::DoubleAttribute >( Ex ) );
405 residue->add_attribute(
"twoJ",
406 std::make_shared< HepMC3::IntAttribute >( twoJ ) );
407 residue->add_attribute(
"parity",
408 std::make_shared< HepMC3::IntAttribute >(
static_cast<int>(parity) ) );
416 for (
const auto& file : output_files ) {
417 file->write_event( event.get() );
420 std::cout <<
"Event " << evnum <<
"\n";