diff --git a/utils/atomic/convertWeightsToHeader.py b/utils/atomic/convertWeightsToHeader.py new file mode 100644 index 0000000..48466d4 --- /dev/null +++ b/utils/atomic/convertWeightsToHeader.py @@ -0,0 +1,209 @@ +import pandas as pd + +# Define fixed-width column specifications based on the format: +# a1 (width 1), i3 (width 3), i5 (width 5), i5 (width 5), i5 (width 5), +# 1x (skip 1), a3 (width 3), a4 (width 4), 1x (skip 1), +# f14.6 (width 14), f12.6 (width 12), f13.5 (width 13), +# 1x (skip 1), f10.5 (width 10), 1x (skip 1), +# a2 (width 2), f13.5 (width 13), f11.5 (width 11), +# 1x (skip 1), i3 (width 3), 1x (skip 1), +# f13.6 (width 13), f12.6 (width 12) +# Compute cumulative positions (0-indexed): +colSpecs = [ + (0, 1), # control + (1, 4), # NZ + (4, 9), # N + (9, 14), # Z + (14, 19), # A + # skip 1 char at position 19; next field starts at 20 + (20, 23), # el + (23, 27), # o + # skip 1 char at position 27; next field starts at 28 + (28, 42), # massExcess (f14.6) + (42, 54), # massExcessUnc (f12.6) + (54, 67), # bindingEnergy (f13.5) + # skip 1 char at position 67; next field starts at 68 + (68, 78), # bindingEnergyUnc (f10.5) + # skip 1 char at position 78; next field starts at 79 + (79, 81), # betaCode (a2) + (81, 94), # betaDecayEnergy (f13.5) + (94, 105), # betaDecayEnergyUnc (f11.5) + # skip 1 char at position 105; next field starts at 106 + (106, 109),# atomicMassInt (i3) + # skip 1 char at position 109; next field starts at 110 + (110, 123),# atomicMassFrac (f13.6) + (123, 135) # atomicMassUnc (f12.6) +] + +# Define column names (using camelCase for variables) +columnNames = [ + "control", + "nz", + "n", + "z", + "a", + "el", + "o", + "massExcess", + "massExcessUnc", + "bindingEnergy", + "bindingEnergyUnc", + "betaCode", + "betaDecayEnergy", + "betaDecayEnergyUnc", + "atomicMassInt", + "atomicMassFrac", + "atomicMassUnc" +] + +def combine_atomic_mass(row): + """ + Combine the integer and fractional parts of the atomic mass. + For example, if atomicMassInt is '1' and atomicMassFrac is '008664.91590', + this function returns float('1008664.91590'). + """ + intPart = str(row["atomicMassInt"]).strip() + fracPart = str(row["atomicMassFrac"]).strip() + try: + combined = int(intPart) + float(fracPart)/1e6 + return combined + except ValueError: + return None + +def mkInstanceName(row): + """ + Make a c++ instance name from the element and atomic number. + """ + speciesName = f"{row['el'].strip()}-{row['a']}" + return speciesName.replace("-", "_") + +def formatSpecies(row): + """ + Format c++ instantiation of Species struct from row data. + """ + name = f"{row['el'].strip()}-{row['a']}" + instanceName = name.replace("-", "_") + nz = int(row['nz']) + n = int(row['n']) + z = int(row['z']) + a = int(row['a']) + bindingEnergy = float(row['bindingEnergy']) + atomicMass = float(row['atomicMass']) + atomicMassUnc = float(row['atomicMassUnc']) + NaN = "std::numeric_limits::quiet_NaN()" + try: + betaDecayEnergy = float(row['betaDecayEnergy'].replace("#", "").replace("*", "")) + except ValueError: + betaDecayEnergy = NaN + instantiation = f"static const Species {instanceName}(\"{name}\", \"{row['el']}\", {nz}, {n}, {z}, {a}, {bindingEnergy}, \"{row['betaCode']}\", {betaDecayEnergy}, {atomicMass}, {atomicMassUnc});" + return instantiation + +def formatHeader(dataFrame): + """ + Format c++ header file from DataFrame. + """ + header = f"""#ifndef SPECIES_MASS_DATA_H +#define SPECIES_MASS_DATA_H +#include +#include +#include + +namespace chemSpecies {{ + struct Species {{ + const std::string_view m_name; //< Name of the species + const std::string_view m_el; //< Element symbol + const int m_nz; //< NZ + const int m_n; //< N + const int m_z; //< Z + const int m_a; //< A + const double m_bindingEnergy; //< Binding energy + const std::string_view m_betaCode; //< Beta decay code + const double m_betaDecayEnergy; //< Beta decay energy + const double m_atomicMass; //< Atomic mass + const double m_atomicMassUnc; //< Atomic mass uncertainty + + Species(const std::string_view name, const std::string_view el, const int nz, const int n, const int z, const int a, const double bindingEnergy, const std::string_view betaCode, const double betaDecayEnergy, const double atomicMass, const double atomicMassUnc) + : m_name(name), m_el(el), m_nz(nz), m_n(n), m_z(z), m_a(a), m_bindingEnergy(bindingEnergy), m_betaCode(betaCode), m_betaDecayEnergy(betaDecayEnergy), m_atomicMass(atomicMass), m_atomicMassUnc(atomicMassUnc) {{}}; + + + double mass() const {{ + return m_atomicMass; + }} + + double massUnc() const {{ + return m_atomicMassUnc; + }} + + double bindingEnergy() const {{ + return m_bindingEnergy; + }} + + double betaDecayEnergy() const {{ + return m_betaDecayEnergy; + }} + + std::string_view betaCode() const {{ + return m_betaCode; + }} + + std::string_view name() const {{ + return m_name; + }} + + std::string_view el() const {{ + return m_el; + }} + + int nz() const {{ + return m_nz; + }} + + int n() const {{ + return m_n; + }} + + int z() const {{ + return m_z; + }} + + int a() const {{ + return m_a; + }} + + friend std::ostream& operator<<(std::ostream& os, const Species& species) {{ + os << static_cast(species.m_name) << " (" << species.m_atomicMass << " u)"; + return os; + }} + }}; + {'\n '.join([formatSpecies(row) for index, row in dataFrame.iterrows()])} + static const std::unordered_map species = {{ + {'\n '.join([f'{{"{row["el"].strip()}-{row["a"]}", {mkInstanceName(row)}}},' for index, row in dataFrame.iterrows()])} + }}; +}}; // namespace chemSpecies +#endif // SPECIES_MASS_DATA_H +""" + return header + +if __name__ == "__main__": + import argparse + import os + parser = argparse.ArgumentParser(description="Convert mass data to c++ header file.") + parser.add_argument("input", help="Input file path.") + parser.add_argument("-o", "--output", help="Output file path.", default="../../assets/static/atomic/include/atomicSpecies.h") + args = parser.parse_args() + + + if not os.path.exists(args.input): + raise FileNotFoundError(f"File not found: {args.input}") + + # Read the file (adjust the skiprows value if your header differs) + dataFrame = pd.read_fwf(args.input, colspecs=colSpecs, names=columnNames, skiprows=36) + + # Combine the two atomic mass fields into one float column + dataFrame["atomicMass"] = dataFrame.apply(combine_atomic_mass, axis=1) + dataFrame.drop(columns=["atomicMassInt", "atomicMassFrac"], inplace=True) + + # Format the header + header = formatHeader(dataFrame) + with open(args.output, "w") as f: + f.write(header) \ No newline at end of file diff --git a/utils/atomic/readme.md b/utils/atomic/readme.md new file mode 100644 index 0000000..b066b1d --- /dev/null +++ b/utils/atomic/readme.md @@ -0,0 +1,13 @@ +# Information +Simple python utility for turning the file assets/atomic/weights.dat into a c++ header which can be included to provide easy access to all atomic weights inside 4DSSE + +## Requirments +In order to use this utility you will need + +- Python +- Pandas + +## Usage +```bash +python convertWeightsToHeader.py -o atomicWeights.h +``` \ No newline at end of file