14#include "tkisotope_builder.h"
38const std::set<std::string> &nudat_nucleus_keys()
40 static const std::set<std::string> keys{
44 "bindingEnergyLDMFit",
46 "excitedStateEnergies",
53 "quadrupoleDeformation",
55 "thermalNeutronCapture",
56 "thermalNeutronFission",
63const std::set<std::string> &nudat_excited_state_energy_keys()
65 static const std::set<std::string> keys{
66 "firstExcitedStateEnergy",
67 "firstFourPlusEnergy",
68 "firstFourPlusOverFirstTwoPlusEnergy",
69 "firstThreeMinusEnergy",
71 "firstZeroPlusEnergy",
72 "secondTwoPlusEnergy",
77bool valid_nudat_scalar(
const json &value)
79 return value.is_string() || value.is_number();
103 fTable = &fDataBase->new_table(fTableName);
105 fTable->add_column(
"isotope_id",
"INTEGER PRIMARY KEY NOT NULL");
106 fTable->add_column(
"element_id",
"INT NOT NULL");
107 fTable->add_column(
"mass",
"INT");
111 fTable->add_column(
"isotope_year_discovered",
"INT");
113 add_measure(
"mass_excess",
false);
114 add_measure(
"electric_quadrupole",
false);
115 add_measure(
"magnetic_dipole",
false);
116 add_measure(
"radius",
false);
120 add_measure(
"abundance",
false);
121 add_measure(
"quadrupoleDeformation",
false);
123 add_measure(
"FY235U",
false);
124 add_measure(
"FY238U",
false);
125 add_measure(
"FY239Pu",
false);
126 add_measure(
"FY241Pu",
false);
127 add_measure(
"FY252Cf",
false);
129 add_measure(
"cFY235U",
false);
130 add_measure(
"cFY238U",
false);
131 add_measure(
"cFY239Pu",
false);
132 add_measure(
"cFY241Pu",
false);
133 add_measure(
"cFY252Cf",
false);
135 fTable->add_column(
"decay_modes",
"TEXT");
139 fTable->add_column(
"spin",
"REAL");
140 fTable->add_column(
"parity",
"INT");
141 fTable->add_column(
"spin_parity",
"TEXT");
143 add_measure(
"lifetime",
false);
145 fTable->add_constraint(
"FOREIGN KEY (element_id) REFERENCES ELEMENT (element_id)");
146 fTable->write_to_database();
148 glog <<
info <<
"Creating '" << _table_name <<
"' table" <<
do_endl;
154 std::string &_reason)
156 std::ifstream input(_filename, std::ios::binary);
158 _reason =
"file cannot be opened";
163 const json document = json::parse(input);
164 if (!document.is_object() || document.size() != 1 ||
165 !document.contains(
"nuclides") || !document.at(
"nuclides").is_array() ||
166 document.at(
"nuclides").empty()) {
167 _reason =
"expected one non-empty top-level 'nuclides' array";
171 std::size_t index = 0;
172 for (
const auto &nucleus : document.at(
"nuclides")) {
174 if (!nucleus.is_object()) {
175 _reason =
"nuclides[" + std::to_string(index - 1) +
"] is not an object";
179 const std::string name = nucleus.value(
"name", std::string(
"nuclides[") +
180 std::to_string(index - 1) +
"]");
181 auto fields = nucleus.items();
182 const auto unexpected_field = std::find_if(
183 fields.begin(), fields.end(),
184 [](
const auto &field) { return nudat_nucleus_keys().count(field.key()) == 0; });
185 if (unexpected_field != fields.end()) {
186 _reason = name +
": unexpected NuDat key '" + unexpected_field.key() +
"'";
190 if (!nucleus.contains(
"name") || !nucleus.at(
"name").is_string() ||
191 !nucleus.contains(
"z") || !nucleus.at(
"z").is_number_integer() ||
192 !nucleus.contains(
"n") || !nucleus.at(
"n").is_number_integer() ||
193 !nucleus.contains(
"a") || !nucleus.at(
"a").is_number_integer()) {
194 _reason = name +
": invalid or missing name/z/n/a identity";
197 if (nucleus.contains(
"levels") && !nucleus.at(
"levels").is_array()) {
198 _reason = name +
": 'levels' is not an array";
201 if (nucleus.contains(
"fissionYields") &&
202 !nucleus.at(
"fissionYields").is_object()) {
203 _reason = name +
": 'fissionYields' is not an object";
206 if (nucleus.contains(
"quadrupoleDeformation") &&
207 !nucleus.at(
"quadrupoleDeformation").is_object()) {
208 _reason = name +
": 'quadrupoleDeformation' is not an object";
211 if (nucleus.contains(
"discovery") && !nucleus.at(
"discovery").is_object()) {
212 _reason = name +
": 'discovery' is not an object";
216 if (!nucleus.contains(
"excitedStateEnergies"))
continue;
217 const auto &energies = nucleus.at(
"excitedStateEnergies");
218 if (!energies.is_object()) {
219 _reason = name +
": 'excitedStateEnergies' is not an object";
222 for (
const auto &energy : energies.items()) {
223 if (nudat_excited_state_energy_keys().count(energy.key()) == 0) {
224 _reason = name +
": unexpected excitedStateEnergies key '" +
228 if (!energy.value().is_object() || !energy.value().contains(
"value") ||
229 !valid_nudat_scalar(energy.value().at(
"value"))) {
230 _reason = name +
": invalid object for excitedStateEnergies.'" +
236 }
catch (
const std::exception &exception) {
237 _reason = exception.what();
245void tkisotope_builder::fill_isotope(
const json_nucleus &_json_nuc)
251 (*fTable)[
"isotope_id"].set_value(fIsotopeIdx);
252 (*fTable)[
"element_id"].set_value(_json_nuc.Z + 1);
253 (*fTable)[
"mass"].set_value(_json_nuc.A);
254 (*fTable)[
"isotope_year_discovered"].set_value(_json_nuc.year);
256 fill_measure(
"mass_excess", _json_nuc.mass_excess,
false);
257 fill_measure(
"abundance", _json_nuc.abundance,
false);
258 fill_measure(
"quadrupoleDeformation", _json_nuc.quadrupoleDeformation,
false);
260 fill_measure(
"FY235U", _json_nuc.FY235U,
false);
261 fill_measure(
"FY238U", _json_nuc.FY238U,
false);
262 fill_measure(
"FY239Pu", _json_nuc.FY239Pu,
false);
263 fill_measure(
"FY241Pu", _json_nuc.FY241Pu,
false);
264 fill_measure(
"FY252Cf", _json_nuc.FY252Cf,
false);
266 fill_measure(
"cFY235U", _json_nuc.cFY235U,
false);
267 fill_measure(
"cFY238U", _json_nuc.cFY238U,
false);
268 fill_measure(
"cFY239Pu", _json_nuc.cFY239Pu,
false);
269 fill_measure(
"cFY241Pu", _json_nuc.cFY241Pu,
false);
270 fill_measure(
"cFY252Cf", _json_nuc.cFY252Cf,
false);
272 fill_measure(
"electric_quadrupole", _json_nuc.electric_quadrupole,
false);
273 fill_measure(
"magnetic_dipole", _json_nuc.magnetic_dipole,
false);
274 fill_measure(
"radius", _json_nuc.radius,
false);
277 if (_json_nuc.Z == 0 && _json_nuc.N == 1) {
279 lifetime.
value = 613.9;
283 fill_measure(
"lifetime", lifetime,
false);
287 for (
auto &dec : _json_nuc.decay_modes) decays.
append(
tkstring::form(
"[%s;%g]", dec.first.data(), dec.second));
288 (*fTable)[
"decay_modes"].set_value(decays.data());
294 const char *_ame_filename,
295 const char *_charge_radii_filename,
299 glog.set_class(
"db_isotope_builder");
303 _charge_radii_filename));
305 tkdb_table &fTable = (*fDataBase)[fTableName.data()];
307 tkstring message =
"Building '" + fTable.get_name() +
"' table";
309 std::string nudat_validation_error;
311 glog <<
error_v <<
"NuDat JSON failed schema validation: "
312 << nudat_validation_error <<
do_endl;
317 std::ifstream ifs(_json_filename);
324 json jf = json::parse(ifs);
326 auto &nuclides = jf.at(
"nuclides");
327 int NNucs = nuclides.size();
330 map<int, map<int, json_nucleus>> nuclei;
332 for (
auto &nuc : nuclides) {
333 json_nucleus json_nuc = read_nucleus(nuc);
335 bool do_push = ((_only_charge == 0) && (_only_mass == 0));
336 if ((_only_charge == json_nuc.Z) && (_only_mass == 0)) do_push =
true;
337 if ((_only_charge == json_nuc.Z) && (_only_mass == json_nuc.A)) do_push =
true;
338 if ((_only_charge == 0) && (_only_mass == json_nuc.A)) do_push =
true;
341 nuclei[json_nuc.Z][json_nuc.A] = json_nuc;
343 glog.progress_bar(NNucs, inuc, message.data());
347 auto selected_nucleus = [&nuclei](
int z,
int a) -> json_nucleus * {
348 auto z_it = nuclei.find(z);
349 if (z_it == nuclei.end())
return nullptr;
350 auto a_it = z_it->second.find(a);
351 if (a_it == z_it->second.end())
return nullptr;
352 return &a_it->second;
355 std::ifstream ame_file(_ame_filename);
356 if (!ame_file.is_open()) {
357 glog <<
error_v <<
"AME mass table cannot be opened: '" << _ame_filename <<
"'" <<
do_endl;
365 int imported_masses = 0;
366 bool ame_header_found =
false;
367 std::string source_line;
368 while (std::getline(ame_file, source_line)) {
369 if (source_line.find(
"MASS EXCESS") != std::string::npos &&
370 source_line.find(
"BINDING ENERGY/A") != std::string::npos) {
371 ame_header_found =
true;
373 if (source_line.size() < 54)
continue;
375 tkstring neutron_field = source_line.substr(4, 5);
376 tkstring charge_field = source_line.substr(9, 5);
377 tkstring mass_number_field = source_line.substr(14, 5);
383 const int z = charge_field.
atoi();
384 const int a = mass_number_field.
atoi();
385 if (a <= 0 || z < 0 || z > a)
continue;
387 tkstring mass_excess_field = source_line.substr(28, 14);
388 tkstring uncertainty_field = source_line.substr(42, 12);
389 tkstring compact_mass_excess = mass_excess_field;
391 if (mass_excess_field.
contains(
"*") || compact_mass_excess.
is_empty())
continue;
393 const bool systematic = mass_excess_field.
contains(
"#") || uncertainty_field.
contains(
"#");
397 auto *nucleus = selected_nucleus(z, a);
398 if (!nucleus)
continue;
399 nucleus->mass_excess = {
400 mass_excess_field.
atof(),
401 uncertainty_field.
atof(),
402 -1., -1.,
"keV", systematic ?
";ERR=SY" :
"",
true};
405 if (!ame_header_found ||
406 (imported_masses == 0 && _only_charge == 0 && _only_mass == 0)) {
407 glog <<
error_v <<
"No valid mass excess was read from AME table: '"
408 << _ame_filename <<
"'" <<
do_endl;
413 std::ifstream radii_file(_charge_radii_filename);
414 if (!radii_file.is_open()) {
415 glog <<
error_v <<
"Charge-radii table cannot be opened: '"
416 << _charge_radii_filename <<
"'" <<
do_endl;
422 if (!std::getline(radii_file, line)) {
423 glog <<
error_v <<
"Charge-radii table is empty: '"
424 << _charge_radii_filename <<
"'" <<
do_endl;
429 std::vector<std::string> radius_header;
431 glog <<
error_v <<
"Charge-radii CSV header has unmatched quoting: '"
432 << _charge_radii_filename <<
"'" <<
do_endl;
436 std::map<tkstring, std::size_t> radius_columns;
437 for (std::size_t index = 0; index < radius_header.size(); ++index)
438 radius_columns[
tkstring(radius_header[index])] = index;
441 bool has_radius =
false;
442 bool has_radius_uncertainty =
false;
443 has_z = radius_columns.count(
"z") != 0;
444 has_a = radius_columns.count(
"a") != 0;
445 has_radius = radius_columns.count(
"radius_val") != 0;
446 has_radius_uncertainty = radius_columns.count(
"radius_unc") != 0;
447 if (!has_z || !has_a || !has_radius || !has_radius_uncertainty) {
448 glog <<
error_v <<
"Charge-radii table has an invalid header: '"
449 << _charge_radii_filename <<
"'" <<
do_endl;
454 int imported_radii = 0;
455 int malformed_radius_rows = 0;
456 while (std::getline(radii_file, line)) {
457 std::vector<std::string>
row;
459 ++malformed_radius_rows;
463 auto value = [&](
const tkstring &name) {
468 const tkstring radius_value = value(
"radius_val");
469 if (radius_value.
is_empty())
continue;
470 auto *nucleus = selected_nucleus(value(
"z").atoi(), value(
"a").atoi());
471 if (!nucleus)
continue;
474 value(
"radius_unc").atof(),
475 -1., -1.,
"fm",
"",
true};
478 if (malformed_radius_rows > 0) {
479 glog <<
error_v << malformed_radius_rows
480 <<
" charge-radii CSV rows do not match the header width" <<
do_endl;
484 if (imported_radii == 0 && _only_charge == 0 && _only_mass == 0) {
485 glog <<
error_v <<
"No published radius was read from charge-radii table: '"
486 << _charge_radii_filename <<
"'" <<
do_endl;
491 fDataBase->exec_sql(
"BEGIN TRANSACTION");
494 for (
const auto &z : nuclei) {
495 for (
const auto &a : z.second) {
498 fill_isotope(a.second);
500 glog.progress_bar(NNucs, inuc, message.data());
505 fDataBase->exec_sql(
"END TRANSACTION");
507 fDataBase->exec_sql(
"CREATE INDEX element_mass_index ON isotope(element_id,mass)");
508 fDataBase->exec_sql(
"CREATE INDEX mass_index ON isotope(mass)");
517 auto to_number = [](
const json &value) {
518 if (value.is_string())
return value.get<
tkstring>().atof();
519 if (value.is_number())
return value.get<
double>();
522 bool value_set =
false;
523 for (
auto &_item : entry.items()) {
524 const auto &_key = _item.key();
525 const auto &_val = _item.value();
526 if (_key ==
"formats")
continue;
527 if (_key ==
"uncertainty") {
528 if (_val.is_object()) {
529 const auto type = _val.value(
"type",
"");
530 if (type ==
"symmetric")
531 _data.
err = to_number(_val.at(
"value"));
532 else if (type ==
"range") {
533 if (_val.contains(
"upperLimit") && _val.contains(
"lowerLimit")) {
534 const double upper = to_number(_val.at(
"upperLimit"));
535 const double lower = to_number(_val.at(
"lowerLimit"));
536 if (!value_set) _data.
value = 0.5 * (upper + lower);
543 _data.
err = to_number(_val);
545 }
else if (_key ==
"value") {
546 _data.
value = to_number(_val);
548 }
else if (_key ==
"unit")
549 _data.
unit = _val.get<tkstring>();
550 else if (_key ==
"evaluatorInput")
552 else if (_key ==
"isSystematics" && (_val ==
"true" || (_val.is_boolean() && _val.get<
bool>())))
555 glog <<
error << key <<
": unkown key: " << _key <<
" type: " << _item.value().type_name() <<
do_endl;
556 cout << _val << endl;
561 if (entry.contains(
"formats") && entry.at(
"formats").contains(
"STD")) {
562 tkstring format = entry.at(
"formats").at(
"STD").get<tkstring>();
565 if (tokens.size() == 2) {
566 _data.
err = tokens.back().
atof();
573tkisotope_builder::json_nucleus tkisotope_builder::read_nucleus(json &entry)
577 for (
auto &item : entry.items()) {
578 auto &key = item.key();
579 auto &val = item.value();
588 else if (key ==
"bindingEnergy")
590 else if (key ==
"bindingEnergyLDMFit")
592 else if (key ==
"levels") {
594 auto is_ground_state = [](
const json &level) {
595 if (!level.contains(
"energy"))
return false;
596 const auto &energy = level.at(
"energy");
597 if (!energy.is_object() || !energy.contains(
"value"))
return false;
598 const auto &energy_val = energy.at(
"value");
599 if (energy_val.is_string())
return energy_val.get<tkstring>().atof() == 0.0;
600 if (energy_val.is_number())
return energy_val.get<
double>() == 0.0;
603 const json *level_ptr =
nullptr;
604 const auto it = std::find_if(val.begin(), val.end(), is_ground_state);
605 bool picked_ground = (it != val.end());
608 else if (!val.empty())
609 level_ptr = &val.front();
610 if (!level_ptr)
continue;
611 for (
auto &_item : level_ptr->items()) {
612 const auto &_key = _item.key();
613 const auto &_val = _item.value();
614 if (_key ==
"decayModes") {
615 if (_val.is_object() && (_val.contains(
"observed") || _val.contains(
"predicted"))) {
616 if (_val.contains(
"observed")) {
617 for (
const auto &mode : _val.at(
"observed")) {
618 tkstring type = mode.value(
"mode",
"");
619 tkstring ratio =
"-1";
620 if (mode.contains(
"value")) {
621 if (mode.at(
"value").is_string())
622 ratio = mode.at(
"value").get<tkstring>();
623 else if (mode.at(
"value").is_number())
626 nuc.decay_modes.emplace_back(type, ratio.
atof());
629 if (_val.contains(
"predicted")) {
630 for (
const auto &mode : _val.at(
"predicted")) {
631 tkstring type = mode.value(
"mode",
"");
632 tkstring ratio =
"-1";
633 nuc.decay_modes.emplace_back(type, ratio.
atof());
637 for (
auto &mode : _val.items()) {
638 if (mode.value().size() == 3) {
639 tkstring type = mode.value().at(
"name");
640 tkstring symbol = mode.value().at(
"symbol");
647 ratio = mode.value().at(
"value");
648 nuc.decay_modes.emplace_back(type, ratio.
atof());
650 tkstring type = mode.value().at(
"name");
652 tkstring symbol =
"=";
653 tkstring ratio =
"-1";
654 nuc.decay_modes.emplace_back(type, ratio.
atof());
658 }
else if (_key ==
"energy") {
659 if (_val.empty())
continue;
660 if (_val.size() < 2)
continue;
662 const auto &energy_val = _val.at(
"value");
663 if (energy_val.is_string())
664 ener = energy_val.get<tkstring>().atof();
665 else if (energy_val.is_number())
666 ener = energy_val.get<
double>();
667 if (ener != 0. && picked_ground) {
668 glog <<
error << nuc.name <<
": level is not ground state ! " << _val <<
do_endl;
670 }
else if (_key ==
"massExcess")
672 else if (_key ==
"abundance") {
673 read_measure(_key, _val, nuc.abundance,
true);
674 nuc.abundance.unit =
"%";
677 else if (_key ==
"halflife" || _key ==
"spinAndParity" || _key ==
"spinParity")
680 glog <<
error <<
"levels: unkown key: " << _key <<
" type: " << _item.value().type_name() <<
do_endl;
681 cout << _val << endl;
684 }
else if (key ==
"pairingGap")
686 else if (key ==
"qValues")
688 else if (key ==
"separationEnergies")
690 else if (key ==
"webLinks")
692 else if (key ==
"BE4DBE2")
697 else if (key ==
"excitedStateEnergies")
699 else if (key ==
"fissionYields") {
700 for (
auto &_item : val.items()) {
701 const auto &_key = _item.key();
702 const auto &_val = _item.value();
703 if (_key ==
"FY235U")
704 read_measure(_key, _val, nuc.FY235U);
705 else if (_key ==
"FY238U")
706 read_measure(_key, _val, nuc.FY238U);
707 else if (_key ==
"FY239Pu")
708 read_measure(_key, _val, nuc.FY239Pu);
709 else if (_key ==
"FY241Pu")
710 read_measure(_key, _val, nuc.FY241Pu);
711 else if (_key ==
"FY252Cf")
712 read_measure(_key, _val, nuc.FY252Cf);
713 else if (_key ==
"cFY235U")
714 read_measure(_key, _val, nuc.cFY235U);
715 else if (_key ==
"cFY238U")
716 read_measure(_key, _val, nuc.cFY238U);
717 else if (_key ==
"cFY239Pu")
718 read_measure(_key, _val, nuc.cFY239Pu);
719 else if (_key ==
"cFY241Pu")
720 read_measure(_key, _val, nuc.cFY241Pu);
721 else if (_key ==
"cFY252Cf")
722 read_measure(_key, _val, nuc.cFY252Cf);
724 glog <<
error << key <<
": unkown key: " << _key <<
" type: " << _item.value().type_name() <<
do_endl;
725 cout << _val << endl;
728 }
else if (key ==
"quadrupoleDeformation")
729 read_measure(key, val, nuc.quadrupoleDeformation);
730 else if (key ==
"thermalNeutronCapture")
732 else if (key ==
"thermalNeutronFission")
734 else if (key ==
"discovery") {
735 if (!val.is_object() || !val.contains(
"year"))
continue;
736 const auto &year = val.at(
"year");
737 if (year.is_string())
738 nuc.year = year.get<tkstring>().atoi();
739 else if (year.is_number_integer())
740 nuc.year = year.get<
int>();
744 cout <<
"value: " << endl;
Interface to the sqlite database.
tkdb_builder(tkdatabase *_database, const char *_table_name)
Representaiton of a sqlite data table.
Decoding of the NUDAT isotope properties.
static bool validate_nudat_json(const char *_filename, std::string &_reason)
Validate the NuDat JSON structure consumed by the isotope builder.
virtual ~tkisotope_builder() override
tkisotope_builder(tkdatabase *_database, const char *_table_name="ISOTOPE")
int fill_database(const char *_json_filename, const char *_ame_filename, const char *_charge_radii_filename, int _only_charge=0, int _only_mass=0)
std::string with usefull tricks from TString (ROOT) and KVString (KaliVeda) and more....
static const char * form(const char *_format,...)
std::vector< tkstring > tokenize(const tkstring &_delim=" ") const
Create a vector of string separated by at least one delimiter.
tkstring & remove_all(const tkstring &_s1)
int atoi() const
Converts a string to integer value.
bool contains(const char *_pat, ECaseCompare _cmp=kExact) const
tkstring & append(const tkstring &_st)
tkstring & replace_all(const tkstring &_s1, const tkstring &_s2)
double atof() const
Converts a string to double value.
bool parse_csv_row(const std::string &line, std::vector< std::string > &fields)
tklog & error_v(tklog &log)
std::map< tkstring, tkdb_column > row
tklog & error(tklog &log)
tklog & do_endl(tklog &log)
tklog & warning(tklog &log)
data structure used to fill a tkmeasure object from the sqlite database