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
StructureDatabase.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// Standard library includes
18#include <array>
19
20// MARLEY includes
21#include "marley/marley_utils.hh"
22#include "marley/BackshiftedFermiGasModel.hh"
23#include "marley/Error.hh"
24#include "marley/FileManager.hh"
25#include "marley/Fragment.hh"
26#include "marley/JSON.hh"
27#include "marley/KoningDelarocheOpticalModel.hh"
28#include "marley/Logger.hh"
29#include "marley/NuclearFormFactor.hh"
30#include "marley/StandardLorentzianModel.hh"
31#include "marley/StructureDatabase.hh"
32#include "marley/TargetAtom.hh"
33
34namespace {
36 const std::string DEFAULT_OM_KEY( "Default" );
37}
38
39// Define static data members of the StructureDatabase class
40std::map< int, std::pair< int, marley::Parity > >
41 marley::StructureDatabase::jpi_table_;
42
43std::map<int, marley::Fragment> marley::StructureDatabase::fragment_table_;
44
45std::unordered_map<int, std::unique_ptr< marley::DecayScheme > >
46 marley::StructureDatabase::decay_scheme_table_;
47
48// Name of the data file which will be used to read in the ground-state nuclear
49// spin-parity values
50const std::string marley::StructureDatabase
51 ::jpi_data_file_name_ = "gs_spin_parity_table.txt";
52
53// Flag indicating whether the ground-state spin-parity data file has already
54// been loaded
55bool marley::StructureDatabase::initialized_gs_spin_parity_table_ = false;
56
58
60 std::unique_ptr<marley::DecayScheme>& ds )
61{
62 auto* temp_ptr = ds.release();
63 decay_scheme_table_.emplace(pdg, std::unique_ptr<marley::DecayScheme>(temp_ptr));
64}
65
67 const std::string& filename, DecayScheme::FileFormat format)
68{
69 int Z_ds = (pdg % 10000000)/10000;
70 int A_ds = (pdg % 10000)/10;
71
72 // Remove the previous entry (if one exists) for the given PDG code
73 decay_scheme_table_.erase(pdg);
74
75 // Add the new entry
76 decay_scheme_table_.emplace(pdg, std::make_unique<marley::DecayScheme>(
77 Z_ds, A_ds, filename, format));
78}
79
81 const std::string& filename, DecayScheme::FileFormat format)
82{
83 if (format != DecayScheme::FileFormat::talys)
84 throw marley::Error(std::string("StructureDatabase::")
85 + "find_all_nuclides() is not implemented for"
86 + " formats other than TALYS.");
87
88 // First line in a TALYS level dataset has fortran
89 // format (2i4, 2i5, 56x, i4, a2)
90 // General regex for this line:
91 static const std::regex nuclide_line("[0-9 ]{18} {56}[0-9 ]{4}.{2}");
92
93 // Open the TALYs level data file for parsing
94 std::ifstream file_in(filename);
95
96 // If the file doesn't exist or some other error
97 // occurred, complain and give up.
98 if (!file_in.good()) throw marley::Error(std::string("Could not")
99 + " read from the TALYS data file " + filename);
100
101 std::string line; // String to store the current line
102 // of the TALYS file during parsing
103
104 std::istringstream iss; // String stream used to parse the line
105
106 double Z; // atomic number
107 double A; // mass number
108
109 // Particle Data Group codes for each nuclide in the file
110 std::set<int> PDGs;
111
112 // Loop through the data file, recording all nuclide PDGs found
113 while (std::getline(file_in, line)) {
114 if (std::regex_match(line, nuclide_line)) {
115 // Load the new line into our istringstream object for parsing. Reset
116 // the stream so that we start parsing from the beginning of the string.
117 iss.str(line);
118 iss.clear();
119
120 // The first two entries on a TALYS nuclide line are Z and A.
121 iss >> Z >> A;
122
123 PDGs.insert(marley_utils::get_nucleus_pid(Z, A));
124 }
125 }
126
127 file_in.close();
128
129 return PDGs;
130}
131
133 const int particle_id)
134{
135 // If we already have the DecayScheme object stored in the lookup table,
136 // then just retrieve it
137 auto iter = decay_scheme_table_.find( particle_id );
138 if ( iter != decay_scheme_table_.end() ) return iter->second.get();
139 // If not, see if a data file for it is listed in the index
140 else {
141
142 marley::TargetAtom ta_requested( particle_id );
143 MARLEY_LOG( DEBUG, "init.structure.decay" )
144 << "Looking up structure data for " << ta_requested;
145
146 if ( !loaded_structure_index_ ) this->load_structure_index();
147 auto ds_file_iter = decay_scheme_filenames_.find( particle_id );
148
149 // If not, then just give up and return a null pointer
150 if ( ds_file_iter == decay_scheme_filenames_.end() ) {
151 MARLEY_LOG( NOTICE, "init.structure.decay" ) << "No tabulated decay"
152 " scheme available for " << ta_requested << ". The Hauser-Feshbach"
153 " statistical model will be used for de-excitation.";
154 // Cache the null result to avoid repeating the lookup
155 decay_scheme_table_[ particle_id ] = nullptr;
156 return nullptr;
157 }
158
159 // If a file is available, load all of the decay schemes present in it
160 // and add them to the lookup table. If we find the one we're looking
161 // for, return a pointer to it. Otherwise, print a warning, give up,
162 // and return a null pointer.
163 std::string ds_file_name = ds_file_iter->second;
165 std::string full_ds_file_name = fm.find_file( ds_file_name );
166 std::ifstream ds_data_file( full_ds_file_name );
167 bool found_it = false;
168 auto temp_ds = std::make_unique< marley::DecayScheme >();
169 int loaded_nuclide_count = 0;
170 while ( ds_data_file >> *temp_ds ) {
171 int ds_pdg = temp_ds->pdg();
172 if ( particle_id == ds_pdg ) found_it = true;
173 this->add_decay_scheme( ds_pdg, temp_ds );
174 marley::TargetAtom ta( ds_pdg );
175 MARLEY_LOG( DEBUG, "init.structure.decay" ) << "Added decay scheme for "
176 << ta << " from " << full_ds_file_name;
177 ++loaded_nuclide_count;
178 temp_ds = std::make_unique< marley::DecayScheme >();
179 }
180 if ( !found_it ) {
181 MARLEY_LOG( WARN, "init.structure.decay" )
182 << "Failed to load nuclear structure data for " << ta_requested
183 << " from the file " << ds_file_name;
184 // Make a nullptr entry in the lookup table to avoid duplicate attempts
185 // to load the missing data
186 decay_scheme_table_[ particle_id ] = nullptr;
187 }
188 if ( loaded_nuclide_count > 0 ) {
189 MARLEY_LOG( INFO, "init.structure.decay" ) << "Loaded structure data for "
190 << loaded_nuclide_count << " nuclides from the file "
191 << full_ds_file_name;
192 }
193
194 // Return the pointer to the newly-loaded DecayScheme (or nullptr
195 // if it could not be loaded) via recursion
196 return this->get_decay_scheme( particle_id );
197 }
198}
199
201 const int A)
202{
203 int particle_id = marley_utils::get_nucleus_pid(Z, A);
204 return get_decay_scheme( particle_id );
205}
206
208 int nucleus_pid )
209{
211 auto iter = optical_model_table_.find( nucleus_pid );
212
213 if ( iter == optical_model_table_.end() ) {
214 // The requested optical model wasn't found, so create it and add it
215 // to the table, returning a reference to the stored optical model
216 // afterwards.
217 int Z = marley_utils::get_particle_Z( nucleus_pid );
218 int A = marley_utils::get_particle_A( nucleus_pid );
219
220 if ( om_config_map_.empty() ) this->load_optical_model_params();
221
222 // Check for a nucleus-specific optical model configuration (stored under
223 // the nuclear PDG code as a string key) first. If it's not present, pull
224 // up the default set of parameters. If the default set isn't present,
225 // complain.
226 auto iter = om_config_map_.find( std::to_string(nucleus_pid) );
227 if ( iter == om_config_map_.end() ) {
228 iter = om_config_map_.find( DEFAULT_OM_KEY );
229 if ( iter == om_config_map_.end() ) throw marley::Error( "Missing '"
230 + DEFAULT_OM_KEY + "' key in optical model JSON configuration" );
231 }
232
233 const auto& om_config = iter->second;
234
235 return *( optical_model_table_.emplace( nucleus_pid,
236 std::make_unique< marley::KoningDelarocheOpticalModel >(
237 Z, A, om_config) ).first->second.get() );
238 }
239 else return *( iter->second.get() );
240}
241
243 const int Z, const int A )
244{
245 int nucleus_pid = marley_utils::get_nucleus_pid( Z, A );
246 auto iter = optical_model_table_.find( nucleus_pid );
247
248 if ( iter == optical_model_table_.end() ) {
249
250 if ( om_config_map_.empty() ) this->load_optical_model_params();
251
252 // Check for a nucleus-specific optical model configuration (stored under
253 // the nuclear PDG code as a string key) first. If it's not present, pull
254 // up the default set of parameters. If the default set isn't present,
255 // complain.
256 auto iter = om_config_map_.find( std::to_string(nucleus_pid) );
257 if ( iter == om_config_map_.end() ) {
258 iter = om_config_map_.find( DEFAULT_OM_KEY );
259 if ( iter == om_config_map_.end() ) throw marley::Error( "Missing '"
260 + DEFAULT_OM_KEY + "' key in optical model JSON configuration" );
261 }
262
263 const auto& om_config = iter->second;
264
265 // The requested optical model wasn't found, so create it and add it
266 // to the table, returning a reference to the stored optical model
267 // afterwards.
268 return *( optical_model_table_.emplace( nucleus_pid,
269 std::make_unique< marley::KoningDelarocheOpticalModel >(
270 Z, A, om_config) ).first->second.get() );
271 }
272 else return *( iter->second.get() );
273}
274
276 int nucleus_pid)
277{
278 auto iter = level_density_table_.find(nucleus_pid);
279
280 if (iter == level_density_table_.end()) {
281 // The requested level density model wasn't found, so create it and add it
282 // to the table, returning a reference to the stored level density model
283 // afterwards.
284 int Z = marley_utils::get_particle_Z( nucleus_pid );
285 int A = marley_utils::get_particle_A( nucleus_pid );
286 return *(level_density_table_.emplace(nucleus_pid,
287 std::make_unique<marley::BackshiftedFermiGasModel>(Z, A)).first
288 ->second.get());
289 }
290 else return *(iter->second.get());
291}
292
294 const int Z, const int A)
295{
296 int pid = marley_utils::get_nucleus_pid(Z, A);
297 return this->get_level_density_model( pid );
298}
299
302 const int nuc_pdg)
303{
304 int Z = marley_utils::get_particle_Z( nuc_pdg );
305 int A = marley_utils::get_particle_A( nuc_pdg );
306 return this->get_gamma_strength_function_model( Z, A );
307}
308
311 const int A)
312{
313 int pid = marley_utils::get_nucleus_pid(Z, A);
314
315 auto iter = gamma_strength_function_table_.find(pid);
316
317 if (iter == gamma_strength_function_table_.end()) {
318 // The requested gamma-ray strength function model wasn't found, so create
319 // it and add it to the table, returning a reference to the stored strength
320 // function model afterwards.
321 return *(gamma_strength_function_table_.emplace(pid,
322 std::make_unique<marley::StandardLorentzianModel>(Z, A)).first
323 ->second.get());
324 }
325 else return *(iter->second.get());
326}
327
329{
330 // Remove the decay scheme with this PDG code if it exists in the database.
331 // If it doesn't, do nothing.
332 decay_scheme_table_.erase( pdg );
333}
334
336 decay_scheme_table_.clear();
337}
338
340 const int fragment_pdg)
341{
342 // Before retrieving the fragment, make sure we've loaded the
343 // necessary data tables
344 if ( !initialized_gs_spin_parity_table_ ) initialize_jpi_table();
345 auto iter = fragment_table_.find( fragment_pdg );
346 if ( iter == fragment_table_.end() ) return nullptr;
347 else return &iter->second;
348}
349
351 const int Z, const int A)
352{
353 int fragment_pdg = marley_utils::get_nucleus_pid( Z, A );
354 return get_fragment( fragment_pdg );
355}
356
358 const int Z, const int A, int& twoJ, marley::Parity& Pi)
359{
360 int nuc_pdg = marley_utils::get_nucleus_pid( Z, A );
361 return get_gs_spin_parity( nuc_pdg, twoJ, Pi );
362}
363
365 int& twoJ, marley::Parity& Pi)
366{
367 if ( !initialized_gs_spin_parity_table_ ) initialize_jpi_table();
368 auto iter = jpi_table_.find( nuc_pdg );
369 if ( iter == jpi_table_.end() ) throw marley::Error( "Unrecognized"
370 " nuclear PDG code " + std::to_string(nuc_pdg) + " passed to"
371 " marley::StructureDatabase::get_gs_spin_parity()" );
372 auto pair = iter->second;
373 twoJ = pair.first;
374 Pi = pair.second;
375}
376
377void marley::StructureDatabase::initialize_jpi_table() {
378
379 // Instantiate the file manager and use it to find
380 // the data file containing the ground-state spin-parities
381 // for many nuclei
382 const auto& fm = marley::FileManager::Instance();
383 std::string full_jpi_file_name
384 = fm.find_file( jpi_data_file_name_ );
385
386 if ( full_jpi_file_name.empty() ) {
387 throw marley::Error( "Could not find the MARLEY nuclear ground-state"
388 " spin-parity data file " + jpi_data_file_name_ + ". Please ensure that"
389 " the folder containing it is on the MARLEY search path."
390 " If needed, the folder can be appended to the MARLEY_SEARCH_PATH"
391 " environment variable." );
392 }
393
394 MARLEY_LOG( INFO, "init.structure" ) << "Loading ground-state nuclear spin-parities from "
395 << full_jpi_file_name;
396
397 std::ifstream table_file( full_jpi_file_name );
398 int nuc_pdg, twoJ;
400 while ( table_file >> nuc_pdg >> twoJ >> Pi ) {
401 MARLEY_LOG( TRACE, "init.structure" ) << "Nucleus with PDG code " << nuc_pdg
402 << " has spin-parity " << static_cast<double>( twoJ ) / 2. << Pi;
403 jpi_table_[ nuc_pdg ] = std::pair<int, marley::Parity>( twoJ, Pi );
404 }
405
406 // Set the flag saying we've initialized the table of ground-state spin-parities.
407 // This will avoid duplicate attempts at initialization.
408 initialized_gs_spin_parity_table_ = true;
409
410 // Also initialize the table of nuclear fragment properties now that we have
411 // the needed information
412 using namespace marley_utils;
413 constexpr std::array< int, 6 > FRAGMENTS_TO_CONSIDER =
414 { NEUTRON, PROTON, DEUTERON, TRITON, HELION, ALPHA };
415
416 for ( int f_pdg : FRAGMENTS_TO_CONSIDER ) {
417 // Temporary storage
418 int f_twoJ;
419 marley::Parity f_Pi;
420
421 // Look up the spin-parity of the nuclear fragment (assumed to be emitted in
422 // its ground state) in the data table
424
425 // Create a new entry in the table of nuclear fragments that should be
426 // considered during unbound nuclear de-excitations
427 marley::StructureDatabase::fragment_table_.emplace(
428 std::make_pair( f_pdg, marley::Fragment(f_pdg, f_twoJ, f_Pi) ));
429 }
430
431}
432
433void marley::StructureDatabase::load_structure_index() {
434
435 // Instantiate the file manager and use it to find
436 // the index to the decay scheme data files
437 const auto& fm = marley::FileManager::Instance();
438 std::string full_index_file_name = fm.find_file( structure_index_filename_ );
439
440 if ( full_index_file_name.empty() ) {
441 throw marley::Error( "Could not find the MARLEY structure data index file "
442 + structure_index_filename_ + ". Please ensure that"
443 " the folder containing it is on the MARLEY search path."
444 " If needed, the folder can be appended to the MARLEY_SEARCH_PATH"
445 " environment variable." );
446 }
447
448 MARLEY_LOG( INFO, "init.structure" ) << "Loaded structure data index from "
449 << full_index_file_name;
450
451 std::ifstream index_file( full_index_file_name );
452 int nuc_pdg;
453 std::string data_file_name;
454 while ( index_file >> nuc_pdg >> data_file_name ) {
455 MARLEY_LOG( TRACE, "init.structure.decay" ) << "Nucleus with PDG code "
456 << nuc_pdg << " has a tabulated decay scheme in the file "
457 << data_file_name;
458 decay_scheme_filenames_[ nuc_pdg ] = data_file_name;
459 }
460
461 MARLEY_LOG( DEBUG, "init.structure.decay" ) << "Structure index loaded: "
462 << decay_scheme_filenames_.size() << " nuclide(s) indexed";
463
464 // Avoid duplicate loading of the structure index by setting the
465 // "already loaded" flag
466 loaded_structure_index_ = true;
467}
468
470 const marley::JSON* om_config )
471{
472 // Boolean flag for checking that JSON parsing was successful
473 bool ok;
474
475 // If we've been passed a JSON configuration, then use it
476 if ( om_config ) {
477 om_config_map_ = assign_from_json< std::map<std::string, marley::JSON> >(
478 *om_config, ok );
479 }
480 // Otherwise, fetch the default one from the standard data file
481 else {
482 const auto& fm = marley::FileManager::Instance();
483 const std::string om_file_name( "optical_model_kduq_federal_cv.js" );
484 std::string om_full_file_name = fm.find_file( om_file_name );
485
486 if ( om_full_file_name.empty() ) {
487 throw marley::Error( "Could not find the MARLEY nuclear optical model"
488 " configuration file " + om_file_name + ". Please ensure that"
489 " the folder containing it is on the MARLEY search path."
490 " If needed, the folder can be appended to the MARLEY_SEARCH_PATH"
491 " environment variable." );
492 }
493
494 MARLEY_LOG( INFO, "init.structure" ) << "Loading nuclear optical model parameters from "
495 << om_full_file_name;
496
497 auto temp_config = marley::JSON::load_file( om_full_file_name );
498 om_config_map_ = assign_from_json< std::map<std::string, marley::JSON> >(
499 temp_config, ok );
500 }
501
502 if ( !ok ) throw marley::Error( "Failed to parse optical model"
503 " JSON configuration" );
504}
Discrete level and γ-ray data for a specific nuclide.
FileFormat
The FileFormat type is used to tell the DecayScheme class which format to assume when parsing a discr...
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
static const FileManager & Instance()
Get a const reference to the singleton instance of the FileManager.
Simple container for storing reference data about each of the nuclear fragments considered by MARLEY'...
Definition Fragment.hh:27
Abstract base class for models of gamma-ray strength functions.
Abstract base class for models of nuclear level densities.
Abstract base class for nuclear optical model implementations.
Type-safe representation of a parity value (either +1 or -1)
Definition Parity.hh:25
marley::GammaStrengthFunctionModel & get_gamma_strength_function_model(const int Z, const int A)
Retrieves a gamma-ray strength function model object from the database, creating it if one did not al...
void remove_decay_scheme(int pdg)
Deletes the discrete level data in the database associated with a given nuclide.
static const marley::Fragment * get_fragment(const int fragment_pdg)
Retrieves nuclear fragment data from the database.
void load_optical_model_params(const marley::JSON *om_config=nullptr)
Helper function that initializes the map of JSON settings for the optical model parameters.
marley::OpticalModel & get_optical_model(int nucleus_pid)
Retrieves an optical model object from the database, creating it if one did not already exist.
void add_decay_scheme(int pdg, std::unique_ptr< marley::DecayScheme > &ds)
Add a DecayScheme object to the database that contains discrete level data for a specific nuclide.
std::set< int > find_all_nuclides(const std::string &filename, DecayScheme::FileFormat format=DecayScheme::FileFormat::talys)
Create a set of Particle Data Group codes for every nuclide in a discrete level data file.
static void get_gs_spin_parity(int nuc_pdg, int &twoJ, marley::Parity &Pi)
Looks up the ground-state spin-parity for a particular nuclide.
StructureDatabase()
Creates an empty database.
void emplace_decay_scheme(int pdg, const std::string &filename, DecayScheme::FileFormat format=DecayScheme::FileFormat::talys)
Construct and add a DecayScheme object to the database that contains discrete level data for a specif...
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.
void clear()
Removes all previously stored data from the database.
An atomic target for a lepton scattering reaction.
Definition TargetAtom.hh:26