"""
File containing the class object containing the functions to handle binary_c version info.
This class will be used to extend the population object
NOTE: could these functions not just be normal functions rather than class methods? I see hardly any use of the self
"""
# pylint: disable=E0203
import copy
import os
from typing import Union
from binarycpython import _binary_c_bindings
from binarycpython.utils.functions import isfloat
[docs]class version_info:
"""
Class object containing the functions to handle binary_c version info.
This class will be used to extend the population object
"""
def __init__(self, **kwargs):
"""
Init function for the version_info class
"""
return
########################################################
# version_info functions
########################################################
[docs] def return_binary_c_version_info(self, parsed: bool = True) -> Union[str, dict]:
"""
Function that returns the version information of binary_c. This function calls the function
_binary_c_bindings.return_version_info()
Args:
parsed: Boolean flag whether to parse the version_info output of binary_c. default = False
Returns:
Either the raw string of binary_c or a parsed version of this in the form of a nested
dictionary
"""
found_prev = False
if "BINARY_C_MACRO_HEADER" in os.environ:
# the env var is already present. lets save that and put that back later
found_prev = True
prev_value = os.environ["BINARY_C_MACRO_HEADER"]
#
os.environ["BINARY_C_MACRO_HEADER"] = "macroxyz"
# Get version_info
raw_version_info = _binary_c_bindings.return_version_info().strip()
# delete value
del os.environ["BINARY_C_MACRO_HEADER"]
# put stuff back if we found a previous one
if found_prev:
os.environ["BINARY_C_MACRO_HEADER"] = prev_value
# parse if wanted
if parsed:
parsed_version_info = self.parse_binary_c_version_info(raw_version_info)
return parsed_version_info
return raw_version_info
[docs] def parse_binary_c_version_info(self, version_info_string: str) -> dict:
"""
Function that parses the binary_c version info. Long function with a lot of branches
Args:
version_info_string: raw output of version_info call to binary_c
Returns:
Parsed version of the version info, which is a dictionary containing the keys: 'isotopes' for isotope info, 'argpairs' for argument pair info (TODO: explain), 'ensembles' for ensemble settings/info, 'macros' for macros, 'elements' for atomic element info, 'DTlimit' for (TODO: explain), 'nucleosynthesis_sources' for nucleosynthesis sources, and 'miscellaneous' for all those that were not caught by the previous groups. 'git_branch', 'git_build', 'revision' and 'email' are also keys, but its clear what those contain.
"""
version_info_dict = {}
# Clean data and put in correct shape
splitted = version_info_string.strip().splitlines()
cleaned = {el.strip() for el in splitted if not el == ""}
##########################
# Network:
# Split off all the networks and parse the info.
networks = {el for el in cleaned if el.startswith("Network ")}
cleaned = cleaned - networks
networks_dict = {}
for el in networks:
network_dict = {}
split_info = el.split("Network ")[-1].strip().split("==")
network_number = int(split_info[0])
network_dict["network_number"] = network_number
network_info_split = split_info[1].split(" is ")
shortname = network_info_split[0].strip()
network_dict["shortname"] = shortname
if not network_info_split[1].strip().startswith(":"):
network_split_info_extra = network_info_split[1].strip().split(":")
longname = network_split_info_extra[0].strip()
network_dict["longname"] = longname
implementation = (
network_split_info_extra[1].strip().replace("implemented in", "")
)
if implementation:
network_dict["implemented_in"] = [
i.strip("()") for i in implementation.strip().split()
]
networks_dict[network_number] = copy.deepcopy(network_dict)
version_info_dict["networks"] = networks_dict if networks_dict else None
##########################
# Isotopes:
# Split off
isotopes = {el for el in cleaned if el.startswith("Isotope ")}
cleaned -= isotopes
isotope_dict = {}
for el in isotopes:
split_info = el.split("Isotope ")[-1].strip().split(" is ")
isotope_info = split_info[-1]
name = isotope_info.split(" ")[0].strip()
# Get details
mass_g = float(
isotope_info.split(",")[0].split("(")[1].split("=")[-1][:-2].strip()
)
mass_amu = float(
isotope_info.split(",")[0].split("(")[-1].split("=")[-1].strip()
)
mass_mev = float(
isotope_info.split(",")[-3].split("=")[-1].replace(")", "").strip()
)
A = int(isotope_info.split(",")[-1].strip().split("=")[-1].replace(")", ""))
Z = int(isotope_info.split(",")[-2].strip().split("=")[-1])
#
isotope_dict[int(split_info[0])] = {
"name": name,
"Z": Z,
"A": A,
"mass_mev": mass_mev,
"mass_g": mass_g,
"mass_amu": mass_amu,
}
version_info_dict["isotopes"] = isotope_dict if isotope_dict else None
##########################
# Arg pairs:
# Split off
argpairs = {el for el in cleaned if el.startswith("ArgPair")}
cleaned -= argpairs
argpair_dict = {}
for el in sorted(argpairs):
split_info = el.split("ArgPair ")[-1].split(" ")
if not argpair_dict.get(split_info[0], None):
argpair_dict[split_info[0]] = {split_info[1]: split_info[2]}
else:
argpair_dict[split_info[0]][split_info[1]] = split_info[2]
version_info_dict["argpairs"] = argpair_dict if argpair_dict else None
##########################
# ensembles:
# Split off
ensembles = {el for el in cleaned if el.startswith("Ensemble")}
cleaned -= ensembles
ensemble_dict = {}
ensemble_filter_dict = {}
for el in ensembles:
split_info = el.split("Ensemble ")[-1].split(" is ")
if len(split_info) > 1:
if not split_info[0].startswith("filter"):
ensemble_dict[int(split_info[0])] = split_info[-1]
else:
filter_no = int(split_info[0].replace("filter ", ""))
ensemble_filter_dict[filter_no] = split_info[-1]
version_info_dict["ensembles"] = ensemble_dict if ensemble_dict else None
version_info_dict["ensemble_filters"] = (
ensemble_filter_dict if ensemble_filter_dict else None
)
##########################
# macros:
# Split off
macros = {el for el in cleaned if el.startswith("macroxyz")}
cleaned -= macros
param_type_dict = {
"STRING": str,
"FLOAT": float,
"MACRO": str,
"INT": int,
"LONG_INT": int,
"UINT": int,
}
macros_dict = {}
for el in macros:
split_info = el.split("macroxyz ")[-1].split(" : ")
param_type = split_info[0]
new_split = "".join(split_info[1:]).split(" is ")
param_name = new_split[0].strip()
param_value = " is ".join(new_split[1:])
param_value = param_value.strip()
# print("macro ",param_name,"=",param_value," float?",isfloat(param_value)," int?",isint(param_value))
# If we're trying to set the value to "on", check that
# it doesn't already exist. If it does, do nothing, as the
# extra information is better than just "on"
if param_name in macros_dict:
# print("already exists (is ",macros_dict[param_name]," float? ",isfloat(macros_dict[param_name]),", int? ",isint(macros_dict[param_name]),") : check that we can improve it")
if macros_dict[param_name] == "on":
# update with better value
store = True
elif (
isfloat(macros_dict[param_name]) is False
and isfloat(param_value) is True
):
# store the number we now have to replace the non-number we had
store = True
else:
# don't override existing number
store = False
# if store:
# print("Found improved macro value of param",param_name,", was ",macros_dict[param_name],", is",param_value)
# else:
# print("Cannot improve: use old value")
else:
store = True
if store:
# Sometimes the macros have extra information behind it.
# Needs an update in outputting by binary_c (RGI: what does this mean David???)
try:
macros_dict[param_name] = param_type_dict[param_type](param_value)
except ValueError:
macros_dict[param_name] = str(param_value)
version_info_dict["macros"] = macros_dict if macros_dict else None
##########################
# Elements:
# Split off:
elements = {el for el in cleaned if el.startswith("Element")}
cleaned -= elements
# Fill dict:
elements_dict = {}
for el in elements:
split_info = el.split("Element ")[-1].split(" : ")
name_info = split_info[0].split(" is ")
# get isotope info
isotopes = {}
if not split_info[-1][0] == "0":
isotope_string = split_info[-1].split(" = ")[-1]
isotopes = {
int(split_isotope.split("=")[0]): split_isotope.split("=")[1]
for split_isotope in isotope_string.split(" ")
}
elements_dict[int(name_info[0])] = {
"name": name_info[-1],
"atomic_number": int(name_info[0]),
"amt_isotopes": len(isotopes),
"isotopes": isotopes,
}
version_info_dict["elements"] = elements_dict if elements_dict else None
##########################
# dt_limits:
# split off
dt_limits = {el for el in cleaned if el.startswith("DTlimit")}
cleaned -= dt_limits
# Fill dict
dt_limits_dict = {}
for el in dt_limits:
split_info = el.split("DTlimit ")[-1].split(" : ")
dt_limits_dict[split_info[1].strip()] = {
"index": int(split_info[0]),
"value": float(split_info[-1]),
}
version_info_dict["dt_limits"] = dt_limits_dict if dt_limits_dict else None
##############################
# Units
units = {el for el in cleaned if el.startswith("Unit ")}
cleaned -= units
units_dict = {}
for el in units:
split_info = el.split("Unit ")[-1].split(",")
s = split_info[0].split(" is ")
if len(s) == 2:
long, short = [i.strip().strip('"') for i in s]
elif len(s) == 1:
long, short = None, s[0]
else:
print("Warning: Failed to split unit string {}".format(el))
to_cgs = (split_info[1].split())[3].strip().strip('"')
code_units = split_info[2].split()
code_unit_type_num = int(code_units[3].strip().strip('"'))
code_unit_type = code_units[4].strip().strip('"')
code_unit_cgs_value = code_units[9].strip().strip('"').strip(")")
units_dict[long] = {
"long": long,
"short": short,
"to_cgs": to_cgs,
"code_unit_type_num": code_unit_type_num,
"code_unit_type": code_unit_type,
"code_unit_cgs_value": code_unit_cgs_value,
}
# Add the list of units
units = {el for el in cleaned if el.startswith("Units: ")}
cleaned -= units
for el in units:
el = el[7:] # removes "Units: "
units_dict["units list"] = el.strip("Units:")
version_info_dict["units"] = units_dict
##########################
# Nucleosynthesis sources:
# Split off
nucsyn_sources = {el for el in cleaned if el.startswith("Nucleosynthesis")}
cleaned -= nucsyn_sources
# Fill dict
nucsyn_sources_dict = {}
for el in nucsyn_sources:
split_info = el.split("Nucleosynthesis source")[-1].strip().split(" is ")
nucsyn_sources_dict[int(split_info[0])] = split_info[-1]
version_info_dict["nucleosynthesis_sources"] = (
nucsyn_sources_dict if nucsyn_sources_dict else None
)
##########################
# miscellaneous:
# All those that I didn't catch with the above filters. Could try to get some more out though.
misc_dict = {}
# Filter out git revision
git_revision = [el for el in cleaned if el.startswith("git revision")]
misc_dict["git_revision"] = (
git_revision[0].split("git revision ")[-1].replace('"', "")
)
cleaned -= set(git_revision)
# filter out git url
git_url = [el for el in cleaned if el.startswith("git URL")]
misc_dict["git_url"] = git_url[0].split("git URL ")[-1].replace('"', "")
cleaned -= set(git_url)
# filter out version
version = [el for el in cleaned if el.startswith("Version")]
misc_dict["version"] = str(version[0].split("Version ")[-1])
cleaned -= set(version)
git_branch = [el for el in cleaned if el.startswith("git branch")]
misc_dict["git_branch"] = (
git_branch[0].split("git branch ")[-1].replace('"', "")
)
cleaned -= set(git_branch)
build = [el for el in cleaned if el.startswith("Build")]
misc_dict["build"] = build[0].split("Build: ")[-1].replace('"', "")
cleaned -= set(build)
email = [el for el in cleaned if el.startswith("Email")]
misc_dict["email"] = email[0].split("Email ")[-1].split(",")
cleaned -= set(email)
other_items = {el for el in cleaned if " is " in el}
cleaned -= other_items
for el in other_items:
split = el.split(" is ")
key = split[0].strip()
val = " is ".join(split[1:]).strip()
if key in misc_dict:
misc_dict[key + " (alt)"] = val
else:
misc_dict[key] = val
misc_dict["uncaught"] = list(cleaned)
version_info_dict["miscellaneous"] = misc_dict if misc_dict else None
return version_info_dict
[docs] def minimum_stellar_mass(self):
"""
Function to return the minimum stellar mass (in Msun) from binary_c.
"""
if not self._minimum_stellar_mass:
self._minimum_stellar_mass = self.return_binary_c_version_info(parsed=True)[
"macros"
]["BINARY_C_MINIMUM_STELLAR_MASS"]
return self._minimum_stellar_mass