From 8fb69911ddf5d8f1c5c183feb2bbc93381b3af40 Mon Sep 17 00:00:00 2001 From: David Hendriks <davidhendriks93@gmail.com> Date: Sun, 3 Jan 2021 19:28:20 +0000 Subject: [PATCH] updating the types of the functions --- binarycpython/utils/distribution_functions.py | 26 +- binarycpython/utils/functions.py | 238 ++++++++++++++---- binarycpython/utils/useful_funcs.py | 97 +++++-- 3 files changed, 274 insertions(+), 87 deletions(-) diff --git a/binarycpython/utils/distribution_functions.py b/binarycpython/utils/distribution_functions.py index 58e487199..bb95f7168 100644 --- a/binarycpython/utils/distribution_functions.py +++ b/binarycpython/utils/distribution_functions.py @@ -4,21 +4,21 @@ Module containing the predefined distribution functions The user can use any of these distribution functions to generate probability distributions for sampling populations -There are distributions for the following properties: -- mass -- period -- mass ratio -- binary fraction - -TODO: make some things globally present? rob does this in his module..i guess it saves calculations but not sure if im gonna do that now -TODO: make global constants stuff -TODO: add eccentricity distribution: thermal -TODO: Add SFH distributions depending on redshift -TODO: Add metallicity distributions depending on redshift -TODO: Add initial rotational velocity distributions +There are distributions for the following parameters: + - mass + - period + - mass ratio + - binary fraction + +Tasks: + - TODO: make some things globally present? rob does this in his module..i guess it saves calculations but not sure if im gonna do that now + - TODO: make global constants stuff + - TODO: add eccentricity distribution: thermal + - TODO: Add SFH distributions depending on redshift + - TODO: Add metallicity distributions depending on redshift + - TODO: Add initial rotational velocity distributions """ - import math import numpy as np from typing import Optional, Union diff --git a/binarycpython/utils/functions.py b/binarycpython/utils/functions.py index 57cc3b22d..407c1caf9 100644 --- a/binarycpython/utils/functions.py +++ b/binarycpython/utils/functions.py @@ -3,6 +3,9 @@ Module containing most of the utility functions for the binarycpython package Functions here are mostly functions used in other classes/functions, or useful functions for the user + +Tasks: + - TODO: change all prints to verbose_prints """ import json @@ -11,7 +14,7 @@ import tempfile import copy import inspect import ast - +from typing import Union, Any from collections import defaultdict import h5py @@ -25,12 +28,17 @@ from binarycpython import _binary_c_bindings ######################################################## -def verbose_print(message, verbosity, minimal_verbosity): +def verbose_print(message: str, verbosity: int, minimal_verbosity: int) -> None: """ Function that decides whether to print a message based on the current verbosity and its minimum verbosity if verbosity is equal or higher than the minimum, then we print + + Args: + message: message to print + verbosity: current verbosity level + minimal_verbosity: threshold verbosity above which to print """ if verbosity >= minimal_verbosity: @@ -43,6 +51,7 @@ def remove_file(file: str, verbosity: int=0) -> None: Args: file: full filepath to the file that will be removed. + verbosity: current verbosity level (Optional) Returns: the path of a subdirectory called binary_c_python in the TMP of the filesystem @@ -81,13 +90,18 @@ def temp_dir()-> str: return path -def create_hdf5(data_dir, name): +def create_hdf5(data_dir: str, name: str) -> None: """ Function to create an hdf5 file from the contents of a directory: - settings file is selected by checking on files ending on settings - data files are selected by checking on files ending with .dat TODO: fix missing settingsfiles + + Args: + data_dir: directory containing the data files and settings file + name: name of hdf5file. + """ # Make HDF5: @@ -150,9 +164,15 @@ def create_hdf5(data_dir, name): ######################################################## -def return_binary_c_version_info(parsed=False): +def return_binary_c_version_info(parsed: bool=False) -> Union[str, dict]: """ - Function that returns the version information of binary_c + 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 """ version_info = _binary_c_bindings.return_version_info().strip() @@ -163,9 +183,15 @@ def return_binary_c_version_info(parsed=False): return version_info -def parse_binary_c_version_info(version_info_string): +def parse_binary_c_version_info(version_info_string: str) -> dict: """ - Function that parses the binary_c version info. Length function with a lot of branches + 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 = {} @@ -375,27 +401,44 @@ def parse_binary_c_version_info(version_info_string): ######################################################## -def output_lines(output): +def output_lines(output: str) -> str: """ - Function that outputs the lines that were recieved from the binary_c run. + Function that outputs the lines that were recieved from the binary_c run, but now as an iterator. + + Args: + output: raw binary_c output + + Returns: + Iterator over the lines of the binary_c output """ + + return output.splitlines() -def parse_output(output, selected_header): +def example_parse_output(output: str, selected_header: str) -> dict: """ - Function that parses output of binary_c: + Function that parses output of binary_c. This version serves as an example and is quite detailed. Custom functions can be easier: This function works in two cases: if the caught line contains output like 'example_header time=12.32 mass=0.94 ..' or if the line contains output like 'example_header 12.32 0.94' + Please dont the two cases. You can give a 'selected_header' to catch any line that starts with that. Then the values will be put into a dictionary. - TODO: Think about exporting to numpy array or pandas instead of a defaultdict + Tasks: + - TODO: Think about exporting to numpy array or pandas instead of a defaultdict + - TODO: rethink whether this function is necessary at all + - TODO: check this function again + + Args: + output: binary_c output string + selected_header: string header of the output (the start of the line that you want to process) - TODO: rethink whether this function is necessary at all + Returns: + dictionary containing parameters as keys and lists for the values """ value_dicts = [] @@ -453,13 +496,17 @@ def parse_output(output, selected_header): ######################################################## -def get_defaults(filter_values=False): +def get_defaults(filter_values: bool=False) -> dict: """ Function that calls the binaryc get args function and cast it into a dictionary. All the values are strings - filter_values: whether to filter out NULL and Function defaults. + Args: + filter_values: whether to filter out NULL and Function defaults. + + Returns: + dictionary containing the parameter name as key and the parameter default as value """ default_output = _binary_c_bindings.return_arglines() @@ -476,17 +523,28 @@ def get_defaults(filter_values=False): return default_dict -def get_arg_keys(): +def get_arg_keys() -> list: """ - Function that return the list of possible keys to give in the arg string + Function that return the list of possible keys to give in the arg string. This function calls get_defaults() + + Returns: + list of all the parameters that binary_c accepts (and has default values for, since we call get_defaults()) """ return get_defaults().keys() -def filter_arg_dict(arg_dict): +def filter_arg_dict(arg_dict: dict) -> dict: """ Function to filter out keys that contain values included in ['NULL', 'Function', ''] + + This function is called by get_defaults() + + Args: + arg_dict: dictionary containing the argument + default keypairs of binary_c + + Returns: + filtered dictionary (pairs with NULL and Function values are removed) """ old_dict = arg_dict.copy() @@ -500,23 +558,33 @@ def filter_arg_dict(arg_dict): return new_dict -def create_arg_string(arg_dict, sort=False, filter_values=False): +def create_arg_string(arg_dict: dict, sort: bool=False, filter_values: bool=False) -> str: """ - Function that creates the arg string for binary_c. + Function that creates the arg string for binary_c. Takes a dictionary containing the arguments and writes them to a string + This string is missing the 'binary_c ' at the start. - Options: - sort: sort the order of the keys. - filter_values: filters the input dict on keys that have NULL or `function` as value. + Args: + arg_dict: dictionary + sort: (optional, default = False) Boolean whether to sort the order of the keys. + filter_values: (optional, default = False) filters the input dict on keys that have NULL or `function` as value. + + Returns: + The string built up by combining all the key + value's. """ arg_string = "" + # Whether to filter the arguments if filter_values: arg_dict = filter_values(arg_dict) + # keys = sorted(arg_dict.keys()) if sort else arg_dict.keys() + + # for key in keys: arg_string += "{key} {value} ".format(key=key, value=arg_dict[key]) + arg_string = arg_string.strip() return arg_string @@ -526,9 +594,11 @@ def create_arg_string(arg_dict, sort=False, filter_values=False): ######################################################## -def get_help(param_name="", print_help=True, fail_silently=False): +def get_help(param_name: str="", print_help: bool=True, fail_silently: bool=False) -> Union[dict, None]: """ - Function that returns the help info for a given parameter. + Function that returns the help info for a given parameter, by interfacing with binary_c + + Will check whether it is a valid parameter. Binary_c will output things in the following order; - Did you mean? @@ -538,10 +608,16 @@ def get_help(param_name="", print_help=True, fail_silently=False): This function reads out that structure and catches the different components of this output - Will print a dict + Tasks: + - TODO: consider not returning None, but return empty dict - return_dict: wether to return the help info dictionary + Args: + param_name: name of the parameter that you want info from. Will get checked whether its a valid parameter name + print_help: (optional, default = True) whether to print out the help information + fail_silently: (optional, default = False) Whether to print the errors raised if the parameter isn't valid + Returns: + Dictionary containing the help info. This dictionary contains 'parameter_name', 'parameter_value_input_type', 'description', optionally 'macros' """ available_arg_keys = get_arg_keys() @@ -625,13 +701,15 @@ def get_help(param_name="", print_help=True, fail_silently=False): return None -def get_help_all(print_help=True): +def get_help_all(print_help: bool=True) -> dict: """ - Function that reads out the output of the help_all api call to binary_c + Function that reads out the output of the return_help_all api call to binary_c. This return_help_all binary_c returns all the information for the parameters, their descriptions and other properties. The output is categorized in sections. - print_help: bool, prints all the parameters and their descriptions. + Args: + print_help: (optional, default = Tru) prints all the parameters and their descriptions. - return_dict: returns a dictionary + Returns: + returns a dictionary containing dictionaries per section. These dictionaries contain the parameters and descriptions etc for all the parameters in that section """ # Call function @@ -736,10 +814,17 @@ def get_help_all(print_help=True): return help_all_dict -def get_help_super(print_help=False, fail_silently=True): +def get_help_super(print_help: bool=False, fail_silently: bool=True) -> dict: """ Function that first runs get_help_all, and then per argument also run the help function to get as much information as possible. + + Args: + print_help: (optional, default = False) Whether to print the information + fail_silently: (optional, default = True) Whether to fail silently or to print the errors + + Returns: + dictionary containing all dictionaries per section, which then contain as much info as possible per parameter. """ # Get help_all information @@ -796,12 +881,16 @@ def get_help_super(print_help=False, fail_silently=True): return help_all_super_dict -def write_binary_c_parameter_descriptions_to_rst_file(output_file): +def write_binary_c_parameter_descriptions_to_rst_file(output_file: str) -> None: """ - Function that calls the binary_c api to get the help text/descriptions for all the paramateres available in that build. + Function that calls the get_help_super() to get the help text/descriptions for all the parameters available in that build. Writes the results to a .rst file that can be included in the docs. - TODO: add the specific version to this document + Tasks: + - TODO: add the specific version git branch, git build, git commit, and binary_c version to this document + + Args: + output_file: name of the output .rst faile containing the ReStructuredText formatted output of all the binary_c parameters. """ # Get the whole arguments dictionary @@ -842,9 +931,21 @@ def write_binary_c_parameter_descriptions_to_rst_file(output_file): # logfile functions ######################################################## -def load_logfile(logfile): + +def load_logfile(logfile: str) -> None: """ Experimental function that parses the generated logfile of binary_c. + + This function is not finished and shouldn't be used yet. + + Tasks: + - TODO: + + Args: + - logfile: filename of the logfile you want to parse + + Returns: + """ with open(logfile, "r") as file: @@ -887,42 +988,61 @@ def load_logfile(logfile): ######################################################## -def inspect_dict(dict_1, indent=0, print_structure=True): +def inspect_dict(input_dict: dict, indent: int=0, print_structure: bool=True) -> dict: """ - Function to inspect a dict. + Function to (recursively) inspect a (nested) dictionary. + The object that is returned is a dictionary containing the key of the input_dict, but as value it will return the type of what the value would be in the input_dict - Works recursively if there is a nested dict. + In this way we inspect the structure of these dictionaries, rather than the exact contents. - Prints out keys and their value types + Args: + input_dict: dictionary you want to inspect + print_structure: (optional, default = True) + indent: (optional, default = 0) indent of the first output + + Returns: + Dictionary that has the same structure as the input_dict, but as values it has the type(input_dict[key]) (except if the value is a dict) """ structure_dict = {} - for key, value in dict_1.items(): + # + for key, value in input_dict.items(): structure_dict[key] = type(value) + if print_structure: print("\t" * indent, key, type(value)) + if isinstance(value, dict): structure_dict[key] = inspect_dict( value, indent=indent + 1, print_structure=print_structure ) + return structure_dict -def merge_dicts(dict_1, dict_2): +def merge_dicts(dict_1: dict, dict_2: dict) -> dict: """ - Function to merge two dictionaries. + Function to merge two dictionaries in a custom way. Behaviour: + When dict keys are only present in one of either: + - we just add the content to the new dict + When dict keys are present in both, we decide based on the value types how to combine them: - - dictionaries will be merged by calling recursively calling this function again - - numbers will be added - - (opt) lists will be appended + - dictionaries will be merged by calling recursively calling this function again + - numbers will be added + - (opt) lists will be appended + - In the case that the instances do not match: for now I will raise an error + + Args: + dict_1: first dictionary + dict_2: second dictionary - - In the case that the instances do now match: for now I will raise an error + Returns: + Merged dictionary - When dict keys are only present in one of either, we just add the content to the new dict """ # Set up new dict @@ -941,21 +1061,26 @@ def merge_dicts(dict_1, dict_2): # Add the unique keys to the new dict for key in unique_to_dict_1: + # If these items are ints or floats, then just put them in if isinstance(dict_1[key], (float, int)): new_dict[key] = dict_1[key] + # Else, to be safe we should deepcopy them else: copy_dict = copy.deepcopy(dict_1[key]) new_dict[key] = copy_dict for key in unique_to_dict_2: + # If these items are ints or floats, then just put them in if isinstance(dict_2[key], (float, int)): new_dict[key] = dict_2[key] + # Else, to be safe we should deepcopy them else: copy_dict = copy.deepcopy(dict_2[key]) new_dict[key] = copy_dict # Go over the common keys: for key in overlapping_keys: + # See whether the types are actually the same if not type(dict_1[key]) is type(dict_2[key]): print( @@ -995,6 +1120,7 @@ def merge_dicts(dict_1, dict_2): type(dict_1[key]), type(dict_2[key]) ) ) + # return new_dict @@ -1035,7 +1161,6 @@ class binarycDecoder(json.JSONDecoder): class BinaryCEncoder(json.JSONEncoder): def default(self, o): - print("inarycoij") try: str_repr = str(o) except TypeError: @@ -1046,12 +1171,18 @@ class BinaryCEncoder(json.JSONEncoder): return JSONEncoder.default(self, o) -def binaryc_json_serializer(obj): +def binaryc_json_serializer(obj: Any) -> Any: """ Custom serializer for binary_c to use when functions are present in the dictionary that we want to export. Function objects will be turned into str representations of themselves + + Args: + obj: obj being process + + Returns: + Either string representation of object if the object is a function, or the object itself """ if inspect.isfunction(obj): @@ -1066,6 +1197,13 @@ def handle_ensemble_string_to_json(raw_output): creates a working JSON dictionary out of it. Having this wrapper makes it easy to + + Args: + raw_output: raw output of the ensemble dump by binary_c + + Returns: + json.loads(raw_output, cls=binarycDecoder) + """ # return json.loads(json.dumps(ast.literal_eval(raw_output)), cls=binarycDecoder) diff --git a/binarycpython/utils/useful_funcs.py b/binarycpython/utils/useful_funcs.py index 83f43d0a2..e4c013749 100644 --- a/binarycpython/utils/useful_funcs.py +++ b/binarycpython/utils/useful_funcs.py @@ -1,68 +1,108 @@ """ Collection of useful functions. -Part of this is copied/inspired by Robs -Rob's binary_stars module - -Has functions to convert period to separation and vice versa. -calc_period_from_sep($m1,$m2,$sep) calculate the period given the separation. -calc_sep_from_period($m1,$m2,per) does the inverse. -M1,M2,separation are in solar units, period in days. -rzams($m,$z) gives you the ZAMS radius of a star -ZAMS_collision($m1,$m2,$e,$sep,$z) returns 1 if stars collide on the ZAMS - -# TODO: check whether these are correct +Part of this is copied/inspired by Rob's binary_stars module + +Functions: + - calc_period_from_sep(m1, m2, sep) calculate the period given the separation. + - calc_sep_from_period(m1, m2, per) does the inverse. + - rzams(m, z) gives you the ZAMS radius of a star + - ZAMS_collision(m1, m2, e, sep, z) returns 1 if stars collide on the ZAMS + - roche_lobe(q): returns roche lobe radius in units of separation + - ragb(m, z): radius at first thermal pulse + +Tasks: + - TODO: check whether these functions are correct """ + import math +from typing import Union AURSUN = 2.150445198804013386961742071435e02 YEARDY = 3.651995478818308811241877265275e02 -def calc_period_from_sep(M1, M2, sep): +def calc_period_from_sep(M1: Union[int, float], M2: Union[int, float], sep: Union[int, float]) -> Union[int, float]: """ calculate period from separation - args : M1 (Msol), M2 (Msol), separation (Rsun) - returns the period (days) + Args: + M1: Primary mass in solar mass + M2: Secondary mass in solar mass + sep: Separation in solar radii + + Returns: + period in years """ return YEARDY * (sep / AURSUN) * math.sqrt(sep / (AURSUN * (M1 + M2))) -def calc_sep_from_period(M1, M2, period): +def calc_sep_from_period(M1: Union[int, float], M2: Union[int, float], period: Union[int, float]) -> Union[int, float]: """ - inverse of the above function - args : M1 (Msol), M2 (Msol), period (days) + Calculate separation from period. - returns the separation (Rsun) + TODO: check whether this is still correct + + Args: + M1: Primary mass in solar mass + M2: Secondary mass in solar mass + period: Period of binary in days + + Returns: + Separation in solar radii """ return AURSUN * (period * period * (M1 + M2) / (YEARDY * YEARDY)) ** (1.0 / 3.0) -def roche_lobe(q): +def roche_lobe(q: Union[int, float]) -> Union[int, float]: """ A function to evaluate R_L/a(q), Eggleton 1983. + + # TODO: check the definition of the mass ratio + # TODO: check whether the logs are correct + + Args: + q: mass ratio of the binary (secondary/primary) + + Returns: + Roche lobe radius in units of the separation """ p = q ** (1.0 / 3.0) return 0.49 * p * p / (0.6 * p * p + math.log(1.0 + p)) -def ragb(m, z): +def ragb(m: Union[int, float], z: Union[int, float]) -> Union[int, float]: """ - Function to calculate radius of a star at first thermal pulse as a function of mass (z=0.02) + Function to calculate radius of a star in units of solar radii at first thermal pulse as a function of mass (Z=0.02 only, but also good for Z=0.0001) + + Args: + m: mass of star in units of solar mass + z: metallicity of star + + Returns: + radius at first thermal pulse in units of solar radii """ - # Z=0.02 only, but also good for Z=0.001 + return m * 40.0 + 20.0 - # in Rsun -def zams_collission(m1, m2, sep, e, z): +def zams_collission(m1: Union[int, float], m2: Union[int, float], sep: Union[int, float], e: Union[int, float], z: Union[int, float]) -> Union[int, float]: """ given m1,m2, separation and eccentricity (and metallicity) determine if two stars collide on the ZAMS + + Args: + m1: Primary mass in solar mass + m2: Secondary mass in solar mass + sep: separation in solar radii + e: eccentricity + z: metallicity + + Returns: + integer boolean whether the binary stars will collide at pericenter """ # calculate periastron distance @@ -80,6 +120,15 @@ def zams_collission(m1, m2, sep, e, z): def rzams(m, z): """ Function to determine the radius of a ZAMS star as a function of m and z: + + Based on the fits of Tout et al., 1996, MNRAS, 281, 257 + + Args: + m: mass of star in solar mass + z: metallicity + + Returns: + radius of star at ZAMS, in solar radii """ lzs = math.log10(z / 0.02) -- GitLab