""" 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 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 ######################################################## 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 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 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