TkN 2.7.1
Toolkit for Nuclei
Loading...
Searching...
No Matches
tkisotope_builder.cpp
1/********************************************************************************
2 * Copyright (c) : Université de Lyon 1, CNRS/IN2P3, UMR5822, *
3 * IP2I, F-69622 Villeurbanne Cedex, France *
4 * Normandie Université, ENSICAEN, UNICAEN, CNRS/IN2P3, *
5 * LPC Caen, F-14000 Caen, France *
6 * Contibutor(s) : *
7 * Jérémie Dudouet jeremie.dudouet@cnrs.fr [2020] *
8 * Diego Gruyer diego.gruyer@cnrs.fr [2020] *
9 * *
10 * Licensed under the MIT License <http://opensource.org/licenses/MIT>. *
11 * SPDX-License-Identifier: MIT *
12 ********************************************************************************/
13
14#include "tkisotope_builder.h"
15#include "tkcsv.h"
16
17#include <algorithm>
18#include <iostream>
19#include <fstream>
20#include <set>
21
22#include "tklog.h"
23
24namespace tkn {
31}
32
33using namespace tkn;
34using namespace std;
35
36namespace {
37
38const std::set<std::string> &nudat_nucleus_keys()
39{
40 static const std::set<std::string> keys{
41 "BE4DBE2",
42 "a",
43 "bindingEnergy",
44 "bindingEnergyLDMFit",
45 "discovery",
46 "excitedStateEnergies",
47 "fissionYields",
48 "levels",
49 "n",
50 "name",
51 "pairingGap",
52 "qValues",
53 "quadrupoleDeformation",
54 "separationEnergies",
55 "thermalNeutronCapture",
56 "thermalNeutronFission",
57 "webLinks",
58 "z",
59 };
60 return keys;
61}
62
63const std::set<std::string> &nudat_excited_state_energy_keys()
64{
65 static const std::set<std::string> keys{
66 "firstExcitedStateEnergy",
67 "firstFourPlusEnergy",
68 "firstFourPlusOverFirstTwoPlusEnergy",
69 "firstThreeMinusEnergy",
70 "firstTwoPlusEnergy",
71 "firstZeroPlusEnergy",
72 "secondTwoPlusEnergy",
73 };
74 return keys;
75}
76
77bool valid_nudat_scalar(const json &value)
78{
79 return value.is_string() || value.is_number();
80}
81
82} // namespace
83
84// Decay Mode : decay modes.
85// Mass excess : Mass excess
86// β2 : quadrupole deformation parameter obtained from the B(E2) value, as explained in the 2001 work of Raman et al. Basically,
87// β2=(4π/3ZR0^2)[B(E2)]^(1/2), where R0=1.2A^(1/3), Z and A are the number of protons and the number of nucleons respectively,
88// and the B(E2) values are obtained from ENSDF. B(E2) values can have large uncertainties, or be defined as upper/lower limits.
89// The cell will not be colored for the latter cases and furthermore, it will not be colored if the uncertainty of the derived β2 exceeds 50%.
90// FY235U : thermal neutron-induced fission yields for 235-Uranium. The fission yields are normalized so that their sum is equal to 2.
91// FY238U : neutron-induced fission yields for 238-Uranium. The fission yields are normalized so that their sum is equal to 2.
92// FY239Pu : thermal neutron-induced fission yields for 239-Plutonium. The fission yields are normalized so that their sum is equal to 2.
93// FY241Pu : neutron-induced fission yields for 241-Plutonium. The fission yields are normalized so that their sum is equal to 2.
94// FY252Cf : spontaneous fission yields for 252-Californium. The fission yields are normalized so that their sum is equal to 2.
95// cFY235U : cumulative fission yields
96// cFY238U : cumulative fission yields
97// cFY239Pu : cumulative fission yields
98// cFY241Pu : cumulative fission yields
99// cFY252Cf : cumulative fission yields
100
101tkisotope_builder::tkisotope_builder(tkdatabase *_database, const char *_table_name) : tkdb_builder(_database, _table_name)
102{
103 fTable = &fDataBase->new_table(fTableName);
104
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");
108
109 // data taken from the primary evaluated/compiled sources
110
111 fTable->add_column("isotope_year_discovered", "INT");
112
113 add_measure("mass_excess", false);
114 add_measure("electric_quadrupole", false);
115 add_measure("magnetic_dipole", false);
116 add_measure("radius", false);
117
118 // data taken from NUDAT3 json file
119
120 add_measure("abundance", false);
121 add_measure("quadrupoleDeformation", false);
122
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);
128
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);
134
135 fTable->add_column("decay_modes", "TEXT");
136
137 // columns created here but filled later by ensdf values
138
139 fTable->add_column("spin", "REAL");
140 fTable->add_column("parity", "INT");
141 fTable->add_column("spin_parity", "TEXT");
142
143 add_measure("lifetime", false);
144
145 fTable->add_constraint("FOREIGN KEY (element_id) REFERENCES ELEMENT (element_id)"); // hold this link with a constraints
146 fTable->write_to_database();
147
148 glog << info << "Creating '" << _table_name << "' table" << do_endl;
149}
150
152
153bool tkisotope_builder::validate_nudat_json(const char *_filename,
154 std::string &_reason)
155{
156 std::ifstream input(_filename, std::ios::binary);
157 if (!input.good()) {
158 _reason = "file cannot be opened";
159 return false;
160 }
161
162 try {
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";
168 return false;
169 }
170
171 std::size_t index = 0;
172 for (const auto &nucleus : document.at("nuclides")) {
173 ++index;
174 if (!nucleus.is_object()) {
175 _reason = "nuclides[" + std::to_string(index - 1) + "] is not an object";
176 return false;
177 }
178
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() + "'";
187 return false;
188 }
189
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";
195 return false;
196 }
197 if (nucleus.contains("levels") && !nucleus.at("levels").is_array()) {
198 _reason = name + ": 'levels' is not an array";
199 return false;
200 }
201 if (nucleus.contains("fissionYields") &&
202 !nucleus.at("fissionYields").is_object()) {
203 _reason = name + ": 'fissionYields' is not an object";
204 return false;
205 }
206 if (nucleus.contains("quadrupoleDeformation") &&
207 !nucleus.at("quadrupoleDeformation").is_object()) {
208 _reason = name + ": 'quadrupoleDeformation' is not an object";
209 return false;
210 }
211 if (nucleus.contains("discovery") && !nucleus.at("discovery").is_object()) {
212 _reason = name + ": 'discovery' is not an object";
213 return false;
214 }
215
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";
220 return false;
221 }
222 for (const auto &energy : energies.items()) {
223 if (nudat_excited_state_energy_keys().count(energy.key()) == 0) {
224 _reason = name + ": unexpected excitedStateEnergies key '" +
225 energy.key() + "'";
226 return false;
227 }
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.'" +
231 energy.key() + "'";
232 return false;
233 }
234 }
235 }
236 } catch (const std::exception &exception) {
237 _reason = exception.what();
238 return false;
239 }
240
241 _reason.clear();
242 return true;
243}
244
245void tkisotope_builder::fill_isotope(const json_nucleus &_json_nuc)
246{
247 fIsotopeIdx++;
248
249 // glog << debug << "filling db with : " << _json_nuc.name.data() << " " << fIsotopeIdx<< do_endl;
250
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);
255
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);
259
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);
265
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);
271
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);
275
276 // fill the neutron lifetime
277 if (_json_nuc.Z == 0 && _json_nuc.N == 1) {
279 lifetime.value = 613.9;
280 lifetime.err = 0.6;
281 lifetime.unit = "S";
282 lifetime.filled = true;
283 fill_measure("lifetime", lifetime, false);
284 }
285
286 tkstring decays;
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());
289
290 fTable->push_row();
291}
292
293int tkisotope_builder::fill_database(const char *_json_filename,
294 const char *_ame_filename,
295 const char *_charge_radii_filename,
296 int _only_charge,
297 int _only_mass)
298{
299 glog.set_class("db_isotope_builder");
300 glog.set_method(tkstring::form("fill_database('%s','%s','%s')",
301 _json_filename,
302 _ame_filename,
303 _charge_radii_filename));
304
305 tkdb_table &fTable = (*fDataBase)[fTableName.data()];
306
307 tkstring message = "Building '" + fTable.get_name() + "' table";
308
309 std::string nudat_validation_error;
310 if (!validate_nudat_json(_json_filename, nudat_validation_error)) {
311 glog << error_v << "NuDat JSON failed schema validation: "
312 << nudat_validation_error << do_endl;
313 glog.clear();
314 return 1;
315 }
316
317 std::ifstream ifs(_json_filename);
318 if (!ifs.good()) {
319 glog << error_v << "data file cannot be opened" << do_endl;
320 glog.clear();
321 return 1;
322 }
323
324 json jf = json::parse(ifs);
325
326 auto &nuclides = jf.at("nuclides");
327 int NNucs = nuclides.size();
328 int inuc = 0;
329
330 map<int, map<int, json_nucleus>> nuclei;
331
332 for (auto &nuc : nuclides) {
333 json_nucleus json_nuc = read_nucleus(nuc);
334
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;
339
340 if (do_push) {
341 nuclei[json_nuc.Z][json_nuc.A] = json_nuc;
342 }
343 glog.progress_bar(NNucs, inuc, message.data());
344 inuc++;
345 }
346
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;
353 };
354
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;
358 glog.clear();
359 return 1;
360 }
361
362 // AME2020 fixed-width format:
363 // a1,i3,i5,i5,i5,1x,a3,a4,1x,f14.6,f12.6,...
364 // A '#' in place of a decimal point marks an estimated value.
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;
372 }
373 if (source_line.size() < 54) continue;
374
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);
378 neutron_field.remove_all(" ");
379 charge_field.remove_all(" ");
380 mass_number_field.remove_all(" ");
381 if (neutron_field.is_empty() || charge_field.is_empty() || mass_number_field.is_empty()) continue;
382
383 const int z = charge_field.atoi();
384 const int a = mass_number_field.atoi();
385 if (a <= 0 || z < 0 || z > a) continue;
386
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;
390 compact_mass_excess.remove_all(" ");
391 if (mass_excess_field.contains("*") || compact_mass_excess.is_empty()) continue;
392
393 const bool systematic = mass_excess_field.contains("#") || uncertainty_field.contains("#");
394 mass_excess_field.replace_all("#", ".");
395 uncertainty_field.replace_all("#", ".");
396
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};
403 ++imported_masses;
404 }
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;
409 glog.clear();
410 return 1;
411 }
412
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;
417 glog.clear();
418 return 1;
419 }
420
421 tkstring line;
422 if (!std::getline(radii_file, line)) {
423 glog << error_v << "Charge-radii table is empty: '"
424 << _charge_radii_filename << "'" << do_endl;
425 glog.clear();
426 return 1;
427 }
428
429 std::vector<std::string> radius_header;
430 if (!detail::parse_csv_row(line.data(), radius_header)) {
431 glog << error_v << "Charge-radii CSV header has unmatched quoting: '"
432 << _charge_radii_filename << "'" << do_endl;
433 glog.clear();
434 return 1;
435 }
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;
439 bool has_z = false;
440 bool has_a = false;
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;
450 glog.clear();
451 return 1;
452 }
453
454 int imported_radii = 0;
455 int malformed_radius_rows = 0;
456 while (std::getline(radii_file, line)) {
457 std::vector<std::string> row;
458 if (!detail::parse_csv_row(line.data(), row) || row.size() != radius_header.size()) {
459 ++malformed_radius_rows;
460 continue;
461 }
462
463 auto value = [&](const tkstring &name) {
464 tkstring result = row.at(radius_columns.at(name));
465 result.remove_all(" ");
466 return result;
467 };
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;
472 nucleus->radius = {
473 radius_value.atof(),
474 value("radius_unc").atof(),
475 -1., -1., "fm", "", true};
476 ++imported_radii;
477 }
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;
481 glog.clear();
482 return 1;
483 }
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;
487 glog.clear();
488 return 1;
489 }
490
491 fDataBase->exec_sql("BEGIN TRANSACTION");
492
493 inuc = 0;
494 for (const auto &z : nuclei) {
495 for (const auto &a : z.second) {
496 // glog << debug << "filling db with : " << a.second.name.data() << do_endl;
497
498 fill_isotope(a.second);
499 // json_nuc.print();
500 glog.progress_bar(NNucs, inuc, message.data());
501 inuc++;
502 }
503 }
504
505 fDataBase->exec_sql("END TRANSACTION");
506
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)");
509
510 glog.clear();
511
512 return 0;
513}
514
515void tkisotope_builder::read_measure(const tkstring &key, const json &entry, tkdb_table::measure_data_struct &_data, bool _fromstd)
516{
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>();
520 return 0.0;
521 };
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);
537 _data.err = 0.0;
538 _data.err_low = _data.value - lower;
539 _data.err_high = upper - _data.value;
540 }
541 }
542 } else {
543 _data.err = to_number(_val);
544 }
545 } else if (_key == "value") {
546 _data.value = to_number(_val);
547 value_set = true;
548 } else if (_key == "unit")
549 _data.unit = _val.get<tkstring>();
550 else if (_key == "evaluatorInput")
551 continue;
552 else if (_key == "isSystematics" && (_val == "true" || (_val.is_boolean() && _val.get<bool>())))
553 _data.info.append(";ERR=SY");
554 else {
555 glog << error << key << ": unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
556 cout << _val << endl;
557 }
558 }
559 if (_fromstd) {
560 // extract value and err from nudat formated values
561 if (entry.contains("formats") && entry.at("formats").contains("STD")) {
562 tkstring format = entry.at("formats").at("STD").get<tkstring>();
563 auto tokens = format.tokenize();
564 _data.value = tokens.front().atof();
565 if (tokens.size() == 2) {
566 _data.err = tokens.back().atof();
567 }
568 }
569 }
570 _data.filled = true;
571}
572
573tkisotope_builder::json_nucleus tkisotope_builder::read_nucleus(json &entry)
574{
575 json_nucleus nuc;
576 bool pause = false;
577 for (auto &item : entry.items()) {
578 auto &key = item.key();
579 auto &val = item.value();
580 if (key == "name")
581 nuc.name = val;
582 else if (key == "z")
583 nuc.Z = val;
584 else if (key == "n")
585 nuc.N = val;
586 else if (key == "a")
587 nuc.A = val;
588 else if (key == "bindingEnergy")
589 continue;
590 else if (key == "bindingEnergyLDMFit")
591 continue;
592 else if (key == "levels") {
593 // pick the ground state level when isomers are present
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;
601 return false;
602 };
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());
606 if (picked_ground)
607 level_ptr = &(*it);
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())
624 ratio = tkstring::form("%g", mode.at("value").get<double>());
625 }
626 nuc.decay_modes.emplace_back(type, ratio.atof());
627 }
628 }
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());
634 }
635 }
636 } else {
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");
641 tkstring ratio;
642 // unkown ratios
643 if (symbol == "?") {
644 symbol = "=";
645 ratio = "-1";
646 } else
647 ratio = mode.value().at("value");
648 nuc.decay_modes.emplace_back(type, ratio.atof());
649 } else {
650 tkstring type = mode.value().at("name");
651 type.remove_all("?");
652 tkstring symbol = "=";
653 tkstring ratio = "-1";
654 nuc.decay_modes.emplace_back(type, ratio.atof());
655 }
656 }
657 }
658 } else if (_key == "energy") {
659 if (_val.empty()) continue;
660 if (_val.size() < 2) continue;
661 double ener = 0.0;
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;
669 }
670 } else if (_key == "massExcess")
671 continue; // mass_excess is sourced exclusively from the primary AME table
672 else if (_key == "abundance") {
673 read_measure(_key, _val, nuc.abundance, true);
674 nuc.abundance.unit = "%";
675 }
676 // include in the ensdf parsing
677 else if (_key == "halflife" || _key == "spinAndParity" || _key == "spinParity")
678 continue;
679 else {
680 glog << error << "levels: unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
681 cout << _val << endl;
682 }
683 }
684 } else if (key == "pairingGap")
685 continue;
686 else if (key == "qValues")
687 continue;
688 else if (key == "separationEnergies")
689 continue;
690 else if (key == "webLinks")
691 continue;
692 else if (key == "BE4DBE2")
693 continue;
694 // Level energies are sourced from ENSDF. These NuDat summary fields,
695 // including firstZeroPlusEnergy and secondTwoPlusEnergy added on
696 // 2026-09-17, are schema-validated above but intentionally not imported.
697 else if (key == "excitedStateEnergies")
698 continue;
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);
723 else {
724 glog << error << key << ": unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
725 cout << _val << endl;
726 }
727 }
728 } else if (key == "quadrupoleDeformation")
729 read_measure(key, val, nuc.quadrupoleDeformation);
730 else if (key == "thermalNeutronCapture")
731 continue;
732 else if (key == "thermalNeutronFission")
733 continue;
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>();
741 }
742 else {
743 glog << warning << "unknown key: " << key << do_endl;
744 cout << "value: " << endl;
745 cout << val << endl;
746 pause = true;
747 }
748 }
749 if (pause) {
750 nuc.print();
751 cin.get();
752 }
753 return nuc;
754}
755
756#ifdef HAS_ROOT
757ClassImp(tkisotope_builder);
758#endif
Interface to the sqlite database.
Definition tkdatabase.h:39
tkdb_builder(tkdatabase *_database, const char *_table_name)
Representaiton of a sqlite data table.
Definition tkdb_table.h:31
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....
Definition tkstring.h:33
static const char * form(const char *_format,...)
Definition tkstring.cpp:438
std::vector< tkstring > tokenize(const tkstring &_delim=" ") const
Create a vector of string separated by at least one delimiter.
Definition tkstring.cpp:292
bool is_empty() const
Definition tkstring.h:145
tkstring & remove_all(const tkstring &_s1)
Definition tkstring.h:208
int atoi() const
Converts a string to integer value.
Definition tkstring.cpp:210
bool contains(const char *_pat, ECaseCompare _cmp=kExact) const
Definition tkstring.h:184
tkstring & append(const tkstring &_st)
Definition tkstring.h:220
tkstring & replace_all(const tkstring &_s1, const tkstring &_s2)
Definition tkstring.h:196
double atof() const
Converts a string to double value.
Definition tkstring.cpp:226
bool parse_csv_row(const std::string &line, std::vector< std::string > &fields)
Definition tkcsv.h:14
Definition tklog.cpp:16
tklog & error_v(tklog &log)
Definition tklog.h:407
std::map< tkstring, tkdb_column > row
Definition tkdb_table.h:28
tklog & info(tklog &log)
Definition tklog.h:313
tklog & error(tklog &log)
Definition tklog.h:344
tklog & do_endl(tklog &log)
Definition tklog.h:212
tklog & warning(tklog &log)
Definition tklog.h:331
data structure used to fill a tkmeasure object from the sqlite database
Definition tkdb_table.h:51