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)