TkN 2.5
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
16#include <algorithm>
17#include <iostream>
18#include <fstream>
19
20#include "tklog.h"
21
22namespace tkn {
29}
30
31using namespace tkn;
32using namespace std;
33
34// Decay Mode : decay modes.
35// Mass excess : Mass excess
36// β2 : quadrupole deformation parameter obtained from the B(E2) value, as explained in the 2001 work of Raman et al. Basically,
37// β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,
38// and the B(E2) values are obtained from ENSDF. B(E2) values can have large uncertainties, or be defined as upper/lower limits.
39// 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%.
40// FY235U : thermal neutron-induced fission yields for 235-Uranium. The fission yields are normalized so that their sum is equal to 2.
41// FY238U : neutron-induced fission yields for 238-Uranium. The fission yields are normalized so that their sum is equal to 2.
42// FY239Pu : thermal neutron-induced fission yields for 239-Plutonium. The fission yields are normalized so that their sum is equal to 2.
43// FY241Pu : neutron-induced fission yields for 241-Plutonium. The fission yields are normalized so that their sum is equal to 2.
44// FY252Cf : spontaneous fission yields for 252-Californium. The fission yields are normalized so that their sum is equal to 2.
45// cFY235U : cumulative fission yields
46// cFY238U : cumulative fission yields
47// cFY239Pu : cumulative fission yields
48// cFY241Pu : cumulative fission yields
49// cFY252Cf : cumulative fission yields
50
51tkisotope_builder::tkisotope_builder(tkdatabase *_database, const char *_table_name) : tkdb_builder(_database, _table_name)
52{
53 fTable = &fDataBase->new_table(fTableName);
54
55 fTable->add_column("isotope_id", "INT PRIMARY KEY NOT NULL");
56 fTable->add_column("element_id", "INT NOT NULL");
57 fTable->add_column("mass", "INT");
58
59 // data taken from IAEA API
60
61 fTable->add_column("isotope_year_discovered", "INT");
62
63 add_measure("mass_excess", false);
64 add_measure("electric_quadrupole", false);
65 add_measure("magnetic_dipole", false);
66 add_measure("radius", false);
67
68 // data taken from NUDAT3 json file
69
70 add_measure("abundance", false);
71 add_measure("quadrupoleDeformation", false);
72
73 add_measure("FY235U", false);
74 add_measure("FY238U", false);
75 add_measure("FY239Pu", false);
76 add_measure("FY241Pu", false);
77 add_measure("FY252Cf", false);
78
79 add_measure("cFY235U", false);
80 add_measure("cFY238U", false);
81 add_measure("cFY239Pu", false);
82 add_measure("cFY241Pu", false);
83 add_measure("cFY252Cf", false);
84
85 fTable->add_column("decay_modes", "TEXT");
86
87 // columns created here but filled later by ensdf values
88
89 fTable->add_column("spin", "REAL");
90 fTable->add_column("parity", "INT");
91 fTable->add_column("spin_parity", "TEXT");
92
93 add_measure("lifetime", false);
94
95 fTable->add_constraint("FOREIGN KEY (element_id) REFERENCES ELEMENT (element_id)"); // hold this link with a constraints
96 fTable->write_to_database();
97
98 glog << info << "Creating '" << _table_name << "' table" << do_endl;
99}
100
102
103void tkisotope_builder::fill_isotope(const json_nucleus &_json_nuc)
104{
105 fIsotopeIdx++;
106
107 // glog << debug << "filling db with : " << _json_nuc.name.data() << " " << fIsotopeIdx<< do_endl;
108
109 (*fTable)["isotope_id"].set_value(fIsotopeIdx);
110 (*fTable)["element_id"].set_value(_json_nuc.Z + 1);
111 (*fTable)["mass"].set_value(_json_nuc.A);
112 (*fTable)["isotope_year_discovered"].set_value(_json_nuc.year);
113
114 fill_measure("mass_excess", _json_nuc.mass_excess, false);
115 fill_measure("abundance", _json_nuc.abundance, false);
116 fill_measure("quadrupoleDeformation", _json_nuc.quadrupoleDeformation, false);
117
118 fill_measure("FY235U", _json_nuc.FY235U, false);
119 fill_measure("FY238U", _json_nuc.FY238U, false);
120 fill_measure("FY239Pu", _json_nuc.FY239Pu, false);
121 fill_measure("FY241Pu", _json_nuc.FY241Pu, false);
122 fill_measure("FY252Cf", _json_nuc.FY252Cf, false);
123
124 fill_measure("cFY235U", _json_nuc.cFY235U, false);
125 fill_measure("cFY238U", _json_nuc.cFY238U, false);
126 fill_measure("cFY239Pu", _json_nuc.cFY239Pu, false);
127 fill_measure("cFY241Pu", _json_nuc.cFY241Pu, false);
128 fill_measure("cFY252Cf", _json_nuc.cFY252Cf, false);
129
130 fill_measure("electric_quadrupole", _json_nuc.electric_quadrupole, false);
131 fill_measure("magnetic_dipole", _json_nuc.magnetic_dipole, false);
132 fill_measure("radius", _json_nuc.radius, false);
133
134 // fill the neutron lifetime
135 if (_json_nuc.Z == 0 && _json_nuc.N == 1) {
137 lifetime.value = 613.9;
138 lifetime.err = 0.6;
139 lifetime.unit = "S";
140 lifetime.filled = true;
141 fill_measure("lifetime", lifetime, false);
142 }
143
144 tkstring decays;
145 for (auto &dec : _json_nuc.decay_modes) decays.append(tkstring::form("[%s;%g]", dec.first.data(), dec.second));
146 (*fTable)["decay_modes"].set_value(decays.data());
147
148 fTable->push_row();
149}
150
151int tkisotope_builder::fill_database(const char *_json_filename, const char *_gs_filename, int _only_charge, int _only_mass)
152{
153 glog.set_class("db_isotope_builder");
154 glog.set_method(tkstring::form("fill_database('%s','%s')", _json_filename, _gs_filename));
155
156 tkdb_table &fTable = (*fDataBase)[fTableName.data()];
157
158 tkstring message = "Building '" + fTable.get_name() + "' table";
159
160 std::ifstream ifs(_json_filename);
161 if (!ifs.good()) {
162 glog << error_v << "data file cannot be opened" << do_endl;
163 glog.clear();
164 return 1;
165 }
166
167 json jf = json::parse(ifs);
168
169 auto &nuclides = jf.at("nuclides");
170 int NNucs = nuclides.size();
171 int inuc = 0;
172
173 map<int, map<int, json_nucleus>> nuclei;
174 tkstring nread;
175
176 for (auto &nuc : nuclides) {
177 json_nucleus json_nuc = read_nucleus(nuc);
178
179 bool do_push = ((_only_charge == 0) && (_only_mass == 0));
180 if ((_only_charge == json_nuc.Z) && (_only_mass == 0)) do_push = true;
181 if ((_only_charge == json_nuc.Z) && (_only_mass == json_nuc.A)) do_push = true;
182 if ((_only_charge == 0) && (_only_mass == json_nuc.A)) do_push = true;
183
184 if (do_push) {
185 nuclei[json_nuc.Z][json_nuc.A] = json_nuc;
186 nread += tkstring::form("(%d,%d)", json_nuc.Z, json_nuc.A);
187 }
188 glog.progress_bar(NNucs, inuc, message.data());
189 inuc++;
190 }
191
192 tkn::tkstring line = "";
193 fstream file(_gs_filename, ios::in);
194 if (!file.is_open()) {
195 glog << error_v << "IAEA ground state file cannot be opened: '" << _gs_filename << "'" << do_endl;
196 glog << error_v << "mass_excess, radius, electric_quadrupole and magnetic_dipole will be missing from the database." << do_endl;
197 glog << error_v << "Download it manually from: https://nds.iaea.org/relnsd/v1/data?fields=ground_states&nuclides=all" << do_endl;
198 glog << error_v << "and place it at: " << _gs_filename << do_endl;
199 glog.clear();
200 return 1;
201 } else {
202 // read the first line to fill the <number,name> column map
203 if (!getline(file, line)) return 1;
204 int ic = 0;
205
206 std::map<int, tkn::tkstring> gs_columns;
207 for (const auto &cname : line.tokenize(",")) gs_columns[ic++] = cname;
208
209 // now read all lines and fill the <name, value> map
210 while (getline(file, line)) {
211 std::map<tkn::tkstring, tkn::tkstring> values;
212 line.replace_all(",,", ", ,");
213 line.replace_all(",,", ", ,");
214 ic = 0;
215 for (auto val : line.tokenize(",")) {
216 val.remove_all(" ");
217 values[gs_columns[ic]] = val;
218 ic++;
219 }
220 tkstring dum = tkstring::form("(%d,%d)", values["z"].atoi(), values["n"].atoi() + values["z"].atoi());
221
222 if (!nread.contains(tkstring::form("(%d,%d)", values["z"].atoi(), values["n"].atoi() + values["z"].atoi()))) continue;
223 auto &nuc = nuclei[values["z"].atoi()][values["n"].atoi() + values["z"].atoi()];
224 if (!values["massexcess"].is_empty()) nuc.mass_excess = {values["massexcess"].atof(), values["unc_me"].atof(), -1., -1., "keV", values["me_systematics"].equal_to("Y") ? ";ERR=SY" : "", true};
225 if (!values["radius"].is_empty()) nuc.radius = {values["radius"].atof(), values["unc_r"].atof(), -1., -1., "fm", "", true};
226 if (!values["electric_quadrupole"].is_empty()) nuc.electric_quadrupole = {values["electric_quadrupole"].atof(), values["unc_eq"].atof(), -1., -1., "barn", "", true};
227 if (!values["magnetic_dipole"].is_empty()) nuc.magnetic_dipole = {values["magnetic_dipole"].atof(), values["unc_md"].atof(), -1., -1., "mun", "", true};
228 if (!values["discovery"].is_empty()) nuc.year = values["discovery"].atoi();
229 }
230 }
231
232 fDataBase->exec_sql("BEGIN TRANSACTION");
233
234 inuc = 0;
235 for (auto &z : nuclei) {
236 for (auto &a : z.second) {
237 // glog << debug << "filling db with : " << a.second.name.data() << do_endl;
238
239 fill_isotope(a.second);
240 // json_nuc.print();
241 fDataBase->exec_sql(tkstring::form("CREATE VIEW nuc%s as select * from isotope INNER JOIN element on isotope.element_id=element.element_id where element.charge=%d AND isotope.mass=%d", a.second.name.data(), a.second.Z, a.second.A));
242 glog.progress_bar(NNucs, inuc, message.data());
243 inuc++;
244 }
245 }
246
247 fDataBase->exec_sql("END TRANSACTION");
248
249 fDataBase->exec_sql("CREATE INDEX element_index ON isotope(element_id)");
250 fDataBase->exec_sql("CREATE INDEX mass_index ON isotope(mass)");
251
252 glog.clear();
253
254 return 0;
255}
256
257void tkisotope_builder::read_measure(const tkstring &key, const json &entry, tkdb_table::measure_data_struct &_data, bool _fromstd)
258{
259 auto to_number = [](const json &value) {
260 if (value.is_string()) return value.get<tkstring>().atof();
261 if (value.is_number()) return value.get<double>();
262 return 0.0;
263 };
264 bool value_set = false;
265 for (auto &_item : entry.items()) {
266 const auto &_key = _item.key();
267 const auto &_val = _item.value();
268 if (_key == "formats") continue;
269 if (_key == "uncertainty") {
270 if (_val.is_object()) {
271 const auto type = _val.value("type", "");
272 if (type == "symmetric")
273 _data.err = to_number(_val.at("value"));
274 else if (type == "range") {
275 if (_val.contains("upperLimit") && _val.contains("lowerLimit")) {
276 const double upper = to_number(_val.at("upperLimit"));
277 const double lower = to_number(_val.at("lowerLimit"));
278 if (!value_set) _data.value = 0.5 * (upper + lower);
279 _data.err = 0.0;
280 _data.err_low = _data.value - lower;
281 _data.err_high = upper - _data.value;
282 }
283 }
284 } else {
285 _data.err = to_number(_val);
286 }
287 } else if (_key == "value") {
288 _data.value = to_number(_val);
289 value_set = true;
290 } else if (_key == "unit")
291 _data.unit = _val.get<tkstring>();
292 else if (_key == "evaluatorInput")
293 continue;
294 else if (_key == "isSystematics" && (_val == "true" || (_val.is_boolean() && _val.get<bool>())))
295 _data.info.append(";ERR=SY");
296 else {
297 glog << error << key << ": unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
298 cout << _val << endl;
299 }
300 }
301 if (_fromstd) {
302 // extract value and err from nudat formated values
303 if (entry.contains("formats") && entry.at("formats").contains("STD")) {
304 tkstring format = entry.at("formats").at("STD").get<tkstring>();
305 auto tokens = format.tokenize();
306 _data.value = tokens.front().atof();
307 if (tokens.size() == 2) {
308 _data.err = tokens.back().atof();
309 }
310 }
311 }
312 _data.filled = true;
313}
314
315tkisotope_builder::json_nucleus tkisotope_builder::read_nucleus(json &entry)
316{
317 json_nucleus nuc;
318 bool pause = false;
319 for (auto &item : entry.items()) {
320 auto &key = item.key();
321 auto &val = item.value();
322 if (key == "name")
323 nuc.name = val;
324 else if (key == "z")
325 nuc.Z = val;
326 else if (key == "n")
327 nuc.N = val;
328 else if (key == "a")
329 nuc.A = val;
330 else if (key == "bindingEnergy")
331 continue;
332 else if (key == "bindingEnergyLDMFit")
333 continue;
334 else if (key == "levels") {
335 // pick the ground state level when isomers are present
336 auto is_ground_state = [](const json &level) {
337 if (!level.contains("energy")) return false;
338 const auto &energy = level.at("energy");
339 if (!energy.is_object() || !energy.contains("value")) return false;
340 const auto &energy_val = energy.at("value");
341 if (energy_val.is_string()) return energy_val.get<tkstring>().atof() == 0.0;
342 if (energy_val.is_number()) return energy_val.get<double>() == 0.0;
343 return false;
344 };
345 const json *level_ptr = nullptr;
346 const auto it = std::find_if(val.begin(), val.end(), is_ground_state);
347 bool picked_ground = (it != val.end());
348 if (picked_ground)
349 level_ptr = &(*it);
350 else if (!val.empty())
351 level_ptr = &val.front();
352 if (!level_ptr) continue;
353 for (auto &_item : level_ptr->items()) {
354 const auto &_key = _item.key();
355 const auto &_val = _item.value();
356 if (_key == "decayModes") {
357 if (_val.is_object() && (_val.contains("observed") || _val.contains("predicted"))) {
358 if (_val.contains("observed")) {
359 for (const auto &mode : _val.at("observed")) {
360 tkstring type = mode.value("mode", "");
361 tkstring ratio = "-1";
362 if (mode.contains("value")) {
363 if (mode.at("value").is_string())
364 ratio = mode.at("value").get<tkstring>();
365 else if (mode.at("value").is_number())
366 ratio = tkstring::form("%g", mode.at("value").get<double>());
367 }
368 nuc.decay_modes.emplace_back(type, ratio.atof());
369 }
370 }
371 if (_val.contains("predicted")) {
372 for (const auto &mode : _val.at("predicted")) {
373 tkstring type = mode.value("mode", "");
374 tkstring ratio = "-1";
375 nuc.decay_modes.emplace_back(type, ratio.atof());
376 }
377 }
378 } else {
379 for (auto &mode : _val.items()) {
380 if (mode.value().size() == 3) {
381 tkstring type = mode.value().at("name");
382 tkstring symbol = mode.value().at("symbol");
383 tkstring ratio;
384 // unkown ratios
385 if (symbol == "?") {
386 symbol = "=";
387 ratio = "-1";
388 } else
389 ratio = mode.value().at("value");
390 nuc.decay_modes.emplace_back(type, ratio.atof());
391 } else {
392 tkstring type = mode.value().at("name");
393 type.remove_all("?");
394 tkstring symbol = "=";
395 tkstring ratio = "-1";
396 nuc.decay_modes.emplace_back(type, ratio.atof());
397 }
398 }
399 }
400 } else if (_key == "energy") {
401 if (_val.empty()) continue;
402 if (_val.size() < 2) continue;
403 double ener = 0.0;
404 const auto &energy_val = _val.at("value");
405 if (energy_val.is_string())
406 ener = energy_val.get<tkstring>().atof();
407 else if (energy_val.is_number())
408 ener = energy_val.get<double>();
409 if (ener != 0. && picked_ground) {
410 glog << error << nuc.name << ": level is not ground state ! " << _val << do_endl;
411 }
412 } else if (_key == "massExcess")
413 continue; // mass_excess is sourced exclusively from the IAEA CSV
414 else if (_key == "abundance") {
415 read_measure(_key, _val, nuc.abundance, true);
416 nuc.abundance.unit = "%";
417 }
418 // include in the ensdf parsing
419 else if (_key == "halflife" || _key == "spinAndParity" || _key == "spinParity")
420 continue;
421 else {
422 glog << error << "levels: unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
423 cout << _val << endl;
424 }
425 }
426 } else if (key == "pairingGap")
427 continue;
428 else if (key == "qValues")
429 continue;
430 else if (key == "separationEnergies")
431 continue;
432 else if (key == "webLinks")
433 continue;
434 else if (key == "BE4DBE2")
435 continue;
436 else if (key == "excitedStateEnergies") {
437 for (auto &_item : val.items()) {
438 const auto &_key = _item.key();
439 const auto &_val = _item.value();
440 if (_key == "firstExcitedStateEnergy") continue;
441 if (_key == "firstTwoPlusEnergy") continue;
442 if (_key == "firstFourPlusEnergy") continue;
443 if (_key == "firstFourPlusOverFirstTwoPlusEnergy") continue;
444 if (_key == "firstThreeMinusEnergy") continue;
445 glog << error << key << ": unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
446 cout << _val << endl;
447 }
448 } else if (key == "fissionYields") {
449 for (auto &_item : val.items()) {
450 const auto &_key = _item.key();
451 const auto &_val = _item.value();
452 if (_key == "FY235U")
453 read_measure(_key, _val, nuc.FY235U);
454 else if (_key == "FY238U")
455 read_measure(_key, _val, nuc.FY238U);
456 else if (_key == "FY239Pu")
457 read_measure(_key, _val, nuc.FY239Pu);
458 else if (_key == "FY241Pu")
459 read_measure(_key, _val, nuc.FY241Pu);
460 else if (_key == "FY252Cf")
461 read_measure(_key, _val, nuc.FY252Cf);
462 else if (_key == "cFY235U")
463 read_measure(_key, _val, nuc.cFY235U);
464 else if (_key == "cFY238U")
465 read_measure(_key, _val, nuc.cFY238U);
466 else if (_key == "cFY239Pu")
467 read_measure(_key, _val, nuc.cFY239Pu);
468 else if (_key == "cFY241Pu")
469 read_measure(_key, _val, nuc.cFY241Pu);
470 else if (_key == "cFY252Cf")
471 read_measure(_key, _val, nuc.cFY252Cf);
472 else {
473 glog << error << key << ": unkown key: " << _key << " type: " << _item.value().type_name() << do_endl;
474 cout << _val << endl;
475 }
476 }
477 } else if (key == "quadrupoleDeformation")
478 read_measure(key, val, nuc.quadrupoleDeformation);
479 else if (key == "thermalNeutronCapture")
480 continue;
481 else if (key == "thermalNeutronFission")
482 continue;
483 else if (key == "discovery")
484 continue;
485 else {
486 glog << warning << "unknown key: " << key << do_endl;
487 cout << "value: " << endl;
488 cout << val << endl;
489 pause = true;
490 }
491 }
492 if (pause) {
493 nuc.print();
494 cin.get();
495 }
496 return nuc;
497}
498
499#ifdef HAS_ROOT
500ClassImp(tkisotope_builder);
501#endif
Interface to the sqlite database.
Definition tkdatabase.h:34
tkdb_builder(tkdatabase *_database, const char *_table_name)
Representaiton of a sqlite data table.
Definition tkdb_table.h:29
Decoding of the NUDAT isotope properties.
int fill_database(const char *_json_filename, const char *_gs_filename, int _only_charge=0, int _only_mass=0)
virtual ~tkisotope_builder() override
tkisotope_builder(tkdatabase *_database, const char *_table_name="ISOTOPE")
std::string with usefull tricks from TString (ROOT) and KVString (KaliVeda) and more....
Definition tkstring.h:32
static const char * form(const char *_format,...)
Definition tkstring.cpp:452
std::vector< tkstring > tokenize(const tkstring &_delim=" ") const
Create a vector of string separated by at least one delimiter.
Definition tkstring.cpp:275
tkstring & remove_all(const tkstring &_s1)
Definition tkstring.h:193
bool contains(const char *_pat, ECaseCompare _cmp=kExact) const
Definition tkstring.h:175
tkstring & append(const tkstring &_st)
Definition tkstring.h:205
tkstring & replace_all(const tkstring &_s1, const tkstring &_s2)
Definition tkstring.h:181
double atof() const
Converts a string to double value.
Definition tkstring.cpp:212
Definition tklog.cpp:16
tklog & error_v(tklog &log)
Definition tklog.h:407
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:49