Simulate nuclear de-excitations.
60 {
61
62 std::string config_file_name;
63 if ( !args.empty() ) config_file_name = args.front();
64
65 if ( config_file_name.empty() || config_file_name == "-h"
66 || config_file_name == "--help" )
67 {
68 args.clear();
69 args.push_front( "decay" );
71 }
72
73
74
75
76
77
78
79 marley::JSON json = marley::JSON::load_file( config_file_name );
80
81 if ( !json.has_key("reactions") ) json["reactions"] = marley::JSON( nullptr );
82 if ( !json.has_key("source") ) json["source"] = marley::JSON( nullptr );
83
84 marley::JSONConfig jc( json );
85 marley::Generator gen = jc.create_generator();
87
88 bool ok;
89
90
91
92
93 const std::string decay_config_label( "decay" );
94 marley::JSON decay_config;
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" );
98
99 long num_events = assign_from_json< long >( "events", decay_config, ok, 1000 );
100
101
102
103
104 if ( !decay_config.has_key("nucleus") ) {
105 throw marley::Error( "Missing required \"nucleus\" key in the"
106 " \"decay\" configuration block" );
107 }
108 const marley::JSON& nuc_config = decay_config.at( "nucleus" );
109
110
111
112
113
114
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");
118
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" );
122 }
123
124 int nucleus_pdg = 0;
125 int Z = 0, A = 0;
126
127 if ( has_pdg ) {
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" );
134 }
135 Z = Z_from_pdg;
136 A = A_from_pdg;
137 }
138
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 );
142 if ( has_pdg ) {
143
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" );
149 }
150 }
151 else {
152 Z = Z_cfg;
153 A = A_cfg;
154 nucleus_pdg = marley_utils::get_nucleus_pid( Z, A );
155 }
156 }
157
158 if ( Z < 0 ) throw marley::Error( "Negative Z encountered" );
159 if ( A < 1 ) throw marley::Error( "A < 1 encountered" );
160
161
162
163 int net_charge = assign_from_json< int >( "net_charge", nuc_config, ok, 0 );
164
165
166
167
168 double Ex = 0.;
169 double Ex_min = 0.;
170 double Ex_max = 0.;
171 bool sample_Ex = nuc_config.has_key( "Ex_max" );
172 if ( !sample_Ex ) {
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)" );
178 }
179 else {
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)" );
186 if ( Ex_max < Ex_min ) throw marley::Error( "Upper Ex bound"
187 " (" + std::to_string(Ex_max) + " MeV) is less than lower Ex bound"
188 " (" + std::to_string(Ex_min) + " MeV)" );
189 }
190
191
192
193
194 std::vector< int > twoJ_vec;
195 if ( !nuc_config.has_key("twoJ") ) {
196 throw marley::Error( "Missing \"twoJ\" key in \"nucleus\" block" );
197 }
198 else {
199 const marley::JSON& twoJ_obj = nuc_config.at( "twoJ" );
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" );
203 }
204 convert_json< std::vector<int> >( twoJ_obj, twoJ_vec );
205 }
206 if ( twoJ_vec.empty() ) {
207 throw marley::Error( "The \"twoJ\" array in \"nucleus\" must not"
208 " be empty" );
209 }
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 ) );
218 }
219
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() );
223
224
225
226
227 auto parity_str = assign_from_json< std::string >( "parity", nuc_config,
228 ok, "+" );
229 if ( parity_str != "+" && parity_str != "-" && parity_str != "random" ) {
230 throw marley::Error( "Invalid \"parity\" setting \""
231 + parity_str + "\" in \"nucleus\" block."
232 " Expected \"+\", \"-\", or \"random\"" );
233 }
234
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() );
239
240
241
242
243 std::vector< std::shared_ptr<marley::OutputFile> > output_files;
244
245 if ( decay_config.has_key("output") ) {
246 marley::JSON output_set = decay_config.at( "output" );
247 if ( !output_set.is_array() ) throw marley::Error( "The"
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) );
251 }
252 }
253 else {
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) );
258 }
259
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" );
264 }
265 }
266
267
268
269
271
272 double gs_mass = mt.get_atomic_mass( nucleus_pdg )
273 - net_charge * mt.get_particle_mass( marley_utils::ELECTRON );
274
275 double unbound_threshold = mt.unbound_threshold( nucleus_pdg );
276
277 int signal_proc_id = marley_hepmc3::get_nuhepmc_proc_id(
278 ProcType::StandaloneDecay );
279
280
281
282
283
284 bool warned_snap = false;
285 bool warned_snap_sample = false;
286
287 for ( long evnum = 0; evnum < num_events; ++evnum ) {
288
289
290 if ( sample_Ex ) {
292 }
293
295 int twoJ = twoJ_vec.at( twoJ_index );
296
297 std::string par_str = parity_str;
298 if ( par_str == "random" ) {
300 par_str = parity_strings.at( par_index );
301 }
302 marley::Parity parity;
303 std::istringstream temp_iss( par_str );
304 temp_iss >> parity;
305
306
307 if ( Ex <= unbound_threshold ) {
309 if ( ds ) {
310 auto* lev = ds->get_pointer_to_closest_level( Ex );
311
312
313 double Ex_lev = lev->energy();
314 int twoJ_lev = lev->twoJ();
315 marley::Parity P_lev = lev->parity();
316
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) );
321
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
326 << ", P=" << parity
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();
335 warned_snap = true;
336
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;
343 }
344 }
345
346
347 Ex = Ex_lev;
348 twoJ = twoJ_lev;
349 parity = P_lev;
350 }
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364 auto event = std::make_shared< HepMC3::GenEvent >(
365 HepMC3::Units::MEV, HepMC3::Units::CM );
366
367 event->set_event_number( evnum + 1 );
368
369 event->add_attribute( "signal_process_id",
370 std::make_shared< HepMC3::IntAttribute >( signal_proc_id ) );
371
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 );
375
376
377 auto projectile = marley_hepmc3::make_particle( 0, 0., 0., 0., 0.,
378 marley_hepmc3::NUHEPMC_PROJECTILE_STATUS, 0. );
379
380
381 auto target = marley_hepmc3::make_particle( nucleus_pdg,
382 marley_hepmc3::NUHEPMC_TARGET_STATUS, gs_mass );
383
384
385 auto ejectile = marley_hepmc3::make_particle( 0, 0., 0., 0., 0.,
386 marley_hepmc3::NUHEPMC_FINAL_STATE_STATUS, 0. );
387
388
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 );
392
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 );
397
398
399
400 marley_hepmc3::set_particle_charge( *target, net_charge );
401 marley_hepmc3::set_particle_charge( *residue, net_charge );
402
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) ) );
409
410
411 marley::NucleusDecayer nd;
413
415
416 for ( const auto& file : output_files ) {
417 file->write_event( event.get() );
418 }
419
420 std::cout << "Event " << evnum << "\n";
421 }
422
423 return true;
424}
double uniform_random_double(double min, double max, bool inclusive)
Sample a random number uniformly on either [min, max) or [min, max].
void set_up_run_info()
Initializes the owned GenRunInfo object that will be used to associate run metadata with the output e...
marley::StructureDatabase & get_structure_db()
Get a reference to the StructureDatabase owned by this Generator.
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...
void finish_event_metadata(HepMC3::GenEvent &ev, bool attach_state=false)
Add final pieces of metadata (e.g., the RNG state) to an otherwise complete event.
static const MassTable & Instance()
Get a const reference to the singleton instance of the MassTable.
virtual void process_event(HepMC3::GenEvent &event, marley::Generator &gen) override
Processes an input GenEvent object.
marley::DecayScheme * get_decay_scheme(const int particle_id)
Retrieves discrete level data from the database.