From 6940920fedb12d7d68210176501cf5c9d2952932 Mon Sep 17 00:00:00 2001 From: David Hendriks <davidhendriks93@gmail.com> Date: Sun, 6 Jun 2021 14:47:16 +0100 Subject: [PATCH] Working on the M&S grid now: added alot of verbose output, and making sure all the interpolators are freed. Now I need to add the eccentricity as options and output for plots. Also reformatted --- binarycpython/utils/distribution_functions.py | 986 +++++++++++------- binarycpython/utils/functions.py | 4 + binarycpython/utils/grid.py | 194 ++-- binarycpython/utils/grid_options_defaults.py | 13 +- 4 files changed, 773 insertions(+), 424 deletions(-) diff --git a/binarycpython/utils/distribution_functions.py b/binarycpython/utils/distribution_functions.py index 9c2e39510..68952f5a5 100644 --- a/binarycpython/utils/distribution_functions.py +++ b/binarycpython/utils/distribution_functions.py @@ -21,9 +21,18 @@ Tasks: """ import math +import json + import numpy as np + from typing import Optional, Union + from binarycpython.utils.useful_funcs import calc_period_from_sep, calc_sep_from_period +from binarycpython.utils.functions import verbose_print +from binarycpython.utils.grid_options_defaults import ( + _MS_VERBOSITY_LEVEL, + _MS_VERBOSITY_INTERPOLATOR_LEVEL, +) ### # File containing probability distributions @@ -943,9 +952,10 @@ import py_rinterpolate Moecache = {} -def poisson(lambda_val, n, nmax=None): +def poisson(lambda_val, n, nmax=None, verbosity=0): """ - Function that calculates the poisson value and normalizes TODO: improve the description + Function that calculates the poisson value and normalizes + TODO: improve the description """ cachekey = "{} {} {}".format(lambda_val, n, nmax) @@ -953,11 +963,15 @@ def poisson(lambda_val, n, nmax=None): if distribution_constants.get("poisson_cache", None): if distribution_constants["poisson_cache"].get(cachekey, None): p_val = distribution_constants["poisson_cache"][cachekey] - # print( - # "Found cached value: Poisson ({}, {}, {}) = {}\n".format( - # lambda_val, n, nmax, p_val - # ) - # ) + + verbose_print( + "\tM&S: found cached value for poisson({}, {}, {}): {}".format( + lambda_val, n, nmax, p_val + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + return p_val # Poisson distribution : note, n can be zero @@ -977,7 +991,11 @@ def poisson(lambda_val, n, nmax=None): distribution_constants["poisson_cache"] = {} distribution_constants["poisson_cache"][cachekey] = p_val - # print("Poisson ({}, {}, {}) = {}\n".format(lambda_val, n, nmax, p_val)) + verbose_print( + "\tM&S: Poisson({}, {}, {}): {}".format(lambda_val, n, nmax, p_val), + verbosity, + _MS_VERBOSITY_LEVEL, + ) return p_val @@ -989,11 +1007,15 @@ def _poisson(lambda_val, n): return (lambda_val ** n) * np.exp(-lambda_val) / (1.0 * math.factorial(n)) -def Moe_de_Stefano_2017_multiplicity_fractions(options): +def Moe_de_Stefano_2017_multiplicity_fractions(options, verbosity=0): + """ + Function that returns a list of multiplicity fractions for a given list + TODO: make an extrapolation functionality in this. log10(1.6e1) is low, we can probably go a bit further + """ # Returns a list of multiplicity fractions for a given input list of masses - global Moecache - # TODO: make an extrapolation functionality in this. log10(1.6e1) is low, we can probably go a bit further + # Use the global Moecache + global Moecache result = {} @@ -1020,6 +1042,7 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options): table=Moecache["multiplicity_table"], # Contains the table of data nparams=1, # logM1 ndata=4, # The amount of datapoints (the parameters that we want to interpolate) + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), ) if options["multiplicity_model"] == "Poisson": @@ -1029,7 +1052,8 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options): for n in range(4): result[n] = ( - options["multiplicity_modulator"][n] * poisson(multiplicity, n, 3) + options["multiplicity_modulator"][n] + * poisson(multiplicity, n, 3, verbosity) if options["multiplicity_modulator"][n] > 0 else 0 ) @@ -1053,11 +1077,13 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options): if options["normalize_multiplicities"] == "raw": # do nothing : use raw Poisson predictions pass + elif options["normalize_multiplicities"] == "norm": # simply normalize so all multiplicities add to 1 sum_result = sum([result[key] for key in result.keys()]) for key in result.keys(): result[key] = result[key] / sum_result + elif options["normalize_multiplicities"] == "merge": # if multiplicity == 1, merge binaries, triples and quads into singles # (de facto this is the same as "norm") @@ -1067,11 +1093,16 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options): # TODO: ask rob about this part. Not sure if i understand it. - multiplicitty = 0 + multiplicity = 0 for n in range(4): if options["multiplicity_modulator"][n] > 0: multiplicity = n + 1 - print("Multiplicity: {}: {}".format(multiplicity, str(result))) + + verbose_print( + "\tM&S: Multiplicity: {}: {}".format(multiplicity, str(result)), + verbosity, + _MS_VERBOSITY_LEVEL, + ) if multiplicity == 1: result[0] = 1.0 @@ -1098,24 +1129,31 @@ def Moe_de_Stefano_2017_multiplicity_fractions(options): # so do nothing pass else: - print( - "Error: in the Moe distribution, we seem to want no stars at all (multiplicity == {}).\n".format( - multiplicity - ) + msg = "\tM&S: Error: in the Moe distribution, we seem to want no stars at all (multiplicity == {})".format( + multiplicity + ) + verbose_print( + msg, + verbosity, + 0, ) + raise ValueError(msg) sum_result = sum([result[key] for key in result.keys()]) for key in result.keys(): result[key] = result[key] / sum_result - # print("Multiplicity array: {}".format(str(result))) + verbose_print( + "\tM&S: Multiplicity array: {}".format(str(result)), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # return array reference return result -# @profile -def build_q_table(options, m, p): +def build_q_table(options, m, p, verbosity=0): ############################################################ # # Build an interpolation table for q, given a mass and @@ -1132,24 +1170,34 @@ def build_q_table(options, m, p): # mass is in $opts->{$m} # period is $opts->{$p} # - ############################################################ - # Since the information from the table for M&S is independent of any choice we make, - # we need to take into account that for example our choice of minimum mass leads to a minimum q_min that is not the same as in the table - # We should ignore those parts of the table and renormalize. If we are below the lowest value of qmin in the table we need to extrapolate the data + # we need to take into account that for example our choice of minimum mass leads to + # a minimum q_min that is not the same as in the table + # We should ignore those parts of the table and renormalize. + # If we are below the lowest value of qmin in the table we need to extrapolate the data + # + # Anyway, the goal of this function is to provide some extrapolated values for q when we should sample outside of the boundaries + ############################################################ # We can check if we have a cached value for this already: # TODO: fix this cache check. incache = False if Moecache.get("rinterpolator_q_metadata", None): - if (Moecache["rinterpolator_q_metadata"][m]) and ( - Moecache["rinterpolator_q_metadata"][p] + if (Moecache["rinterpolator_q_metadata"].get(m, None)) and ( + Moecache["rinterpolator_q_metadata"].get(p, None) ): if (Moecache["rinterpolator_q_metadata"][m] == options[m]) and ( Moecache["rinterpolator_q_metadata"][p] == options[p] ): incache = True - # print("INCACHE: {}".format(incache)) + + verbose_print( + "\tM&S: build_q_table: Found cached values for m={} p={}".format( + options[m], options[p] + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # if not incache: @@ -1159,7 +1207,8 @@ def build_q_table(options, m, p): # the companions are planets qmin = options["ranges"]["M"][ 0 - ] # TODO: this lower range must not be lower than Mmin. However, since the q_min is used as a sample range for the M2, it should use this value I think. discuss + ] # TODO: this lower range must not be lower than Mmin. + # However, since the q_min is used as a sample range for the M2, it should use this value I think. discuss # TODO: fix that this works. Should depend on metallicity too I think. iirc this function does not work now. # qmax = maximum_mass_ratio_for_RLOF(options[m], options[p]) @@ -1230,10 +1279,6 @@ def build_q_table(options, m, p): val = Moecache["rinterpolator_q"].interpolate( [np.log10(options[m]), np.log10(options[p]), q] )[0] - # print("val: ", val) - # print("q: ", q) - # print("qdata: ", qdata) - # print("type(qdata): ", type(qdata)) qdata[q] = val # just below qmin, if qmin>qeps, we want nothing @@ -1267,261 +1312,361 @@ def build_q_table(options, m, p): qs[1] = 1.0 qdata[qs[1]] = qs[0] + # We actually should do the extrapolation now. else: + # Loop over both the lower end and the upper end for pre in ["low", "high"]: if require_extrapolation[pre] == 0: continue else: sign = -1 if pre == "low" else 1 end_index = 0 if pre == "low" else len(qs) - 1 - indices = [0, 1] if pre == "low" else [len(qs) - 1, len(qs) - 2] - method = options.get("q_{}_extrapolation_method", None) - qlimit = qmin if pre == "log" else qmax - - # print("Q: {} method: {}".format(pre, method)) - # print("indices: {}".format(indices)) - # print("End index: {}".format(end_index)) - # print("QS: {}".format(str(indices))) + indices = ( + [0, 1] if pre == "low" else [len(qs) - 1, len(qs) - 2] + ) # Based on whether we do the high or low end we need to use two different indices + method = options.get( + "q_{}_extrapolation_method".format(pre), None + ) + qlimit = qmin if pre == "low" else qmax + + verbose_print( + "\tM&S: build_q_table: Extrapolating: Q: {} method: {}, indices: {} End index: {}".format( + pre, method, indices, end_index + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # truncate the distribution qdata[max(0.0, min(1.0, qlimit + sign * qeps))] = 0 if method == None: # no extrapolation : just interpolate between 0.10 and 0.95 + verbose_print( + "\tM&S: build_q_table: using no extrapolations".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) continue elif method == "flat": # use the end value value and extrapolate it # with zero slope qdata[qlimit] = qdata[qs[end_index]] + verbose_print( + "\tM&S: build_q_table: using constant extrapolation".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) elif method == "linear": - # linear extrapolation - # print("Linear 2 {}".format(pre)) - dq = qs[indices[1]] - qs[indices[0]] - - if dq == 0: - # No change - # print("dq = 0") - qdata[qlimit] = qs[end_index] - else: - slope = ( - qdata[qs[indices[1]]] - qdata[qs[indices[0]]] - ) / dq - intercept = ( - qdata[qs[indices[0]]] - slope * qs[indices[0]] - ) - qdata[qlimit] = max(0.0, slope * qlimit + intercept) - # print( - # "Slope: {} intercept: {} dn/dq({}) = {}".format( - # slope, intercept, qlimit, qdata[qlimit] - # ) - # ) + qdata[qlimit] = linear_extrapolation_q( + qs=qs, + indices=indices, + qlimit=qlimit, + qdata=qdata, + end_index=end_index, + verbosity=verbosity, + ) + + verbose_print( + "\tM&S: build_q_table: using linear extrapolation".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) elif method == "plaw2": - newq = 0.05 - # use a power-law extrapolation down to q=0.05, if possible - if (qdata[qs[indices[0]]] == 0) and ( - qdata[qs[indices[1]]] == 0.0 - ): - # not possible - qdata[newq] = 0 - else: - slope = ( - np.log10(qdata[qs[indices[1]]]) - - np.log10(qdata[qs[indices[0]]]) - ) / ( - np.log10(qs[indices[1]]) - np.log10(qs[indices[0]]) - ) - intercept = np.log10( - qdata[qs[indices[0]]] - ) - slope * log10(qs[indices[0]]) - qdata[newq] = slope * newq + intercept - + qdata[qlimit] = powerlaw_extrapolation_q( + qs=qs, indices=indices, qdata=qdata, verbosity=verbosity + ) + + verbose_print( + "\tM&S: build_q_table: using powerlaw extrapolation".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) elif method == "nolowq": newq = 0.05 qdata[newq] = 0 + verbose_print( + "\tM&S: build_q_table: setting lowq to 0".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) else: - print("No other methods available") + verbose_print( + "\tM&S: build_q_table: Error no other methods available. The chosen method ({}) does not exist!".format( + method + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) raise ValueError - # TODO: consider implementing this - # elsif($method =~ /^(log)?poly(\d+)/) - # { - # ############################################################ - # # NOT WORKING / TESTED - # ############################################################ - - # # fit a polynomial of degree n in q or log10(q) - # my $dolog = defined $1 ? 1 : 0; - # my $n = $2; - - # # make xdata : list of qs - # my @xdata; - # if($dolog) - # { - # @xdata = @qs; # linear - # } - # else - # { - # @xdata = map{log10(MAX(1e-20,$_))}@qs; # log - # } - - # # make ydata : from JSON - # my @ydata = map{$qdata->{$_}}@qs; # y data - - # # make parameters: these are 1/nn - # my @parameters; # parameters - # $#parameters = $n - 1; # set length of parameters array - # { - # my $nn = $n; - # @parameters = map{1.0/$nn--}@parameters; # set all to 1.0/parameter number - # } - - # # make polynomial with parameters - # my $formula = Math::Polynomial->new(@parameters); # use a polynomial fit - # my $variable ='x'; - # my $max_iter = 100; - # my $square_residual = Algorithm::CurveFit->curve_fit( - # $formula, - # \@parameters, - # $variable, - # \@xdata, - # \@ydata, - # $max_iter); - - # # evaluate at q=0.05 - # my $newq = 0.05; - # $qdata->{$newq} = $formula->evaluate($newq); - - # print Data::Dumper::Dumper(\@parameters); - # exit; - # } - - # regenerate qs in new table + # TODO: consider implementing the poly method (see perl version) + + # regenerate qs in new table. This is now the updated list of qs where we have some extrapolated numbers tmp_table = [] for q in sorted(qdata.keys()): tmp_table.append([q, qdata[q]]) - if Moecache.get("rinterpolator_q_given_{}_log10{}".format(m, p), None): - print( - "Present interpolator: {}".format( - Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)] - ) - ) - print("Destroying present interpolator:") - interpolator = Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)] - print(interpolator) - print(type(interpolator)) - print(dir(interpolator)) - x = Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)].interpolate( - [0.5] - ) - print("Interpolated a value q=0.5: {}".format(x)) - Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)].destroy() - print(interpolator) - print(type(interpolator)) - print(dir(interpolator)) - print( - "Present interpolator: {}".format( - Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)] - ) - ) - x = Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)].interpolate( - [0.5] - ) - print("Interpolated a value q=0.5: {}".format(x)) - # del Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)] - - print("CREATING A NEW TABLE Q table") # Make an interpolation table to contain our modified data q_interpolator = py_rinterpolate.Rinterpolate( - table=tmp_table, nparams=1, ndata=1 # Contains the table of data # q # + table=tmp_table, + nparams=1, + ndata=1, # Contains the table of data # q # + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), + ) + verbose_print( + "\tM&S: build_q_table: Created a new Q table", + verbosity, + _MS_VERBOSITY_LEVEL, ) - print("CREATed A NEW TABLE Q table") - # TODO: build a check in here to see if the interpolator build was successful - # print("Can renormalize?: {}".format(can_renormalize)) if can_renormalize: + verbose_print( + "\tM&S: build_q_table: Renormalizing table", + verbosity, + _MS_VERBOSITY_LEVEL, + ) + # now we integrate and renormalize (if the table is not all zero) - # - dq = 1e-3 # resolution of the integration/renormalization - I = 0 - - # integrate: note that the value of the integral is - # meaningless to within a factor (which depends on $dq) - for q in np.arange(0, 1 + 2e-6, dq): - x = q_interpolator.interpolate([q]) - if len(x) == 0: - print("Q interpolator table interpolation failed") - print("tmp_table = {}".format(str(tmp_table))) - print("q_data = {}".format(str(qdata))) - raise ValueError - else: - I += x[0] * dq - # print("dn/dq ({}) = {} I -> = {}".format(q, x[0], I)) + I = get_integration_constant_q(q_interpolator, verbosity=verbosity) if I > 0: # normalize to 1.0 by dividing the data by 1.0/$I q_interpolator.multiply_table_column(1, 1.0 / I) # test this - I = 0 - for q in np.arange(0, 1 + 2e-6, dq): - I += q_interpolator.interpolate([q])[0] * dq - # print("Q integral: {}, {}".format(I, q_interpolator)) + new_I = get_integration_constant_q(q_interpolator, verbosity=verbosity) # fail if error in integral > 1e-6 (should be ~ machine precision) - if abs(1.0 - I) > 1e-6: - print("Error: > 1e-6 in q probability integral: {}".format(I)) - + if abs(1.0 - new_I) > 1e-6: + verbose_print( + "\tM&S: build_q_table: Error: > 1e-6 in q probability integral: {}".format( + I + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # set this new table in the cache - print( - "STORING Q INTERPOLATOR AS {}".format( - "rinterpolator_q_given_{}_log10{}".format(m, p) - ) - ) Moecache["rinterpolator_q_given_{}_log10{}".format(m, p)] = q_interpolator - print( - "STORed Q INTERPOLATOR AS {}".format( + verbose_print( + "\tM&S: build_q_table: stored q_interpolater as {}".format( "rinterpolator_q_given_{}_log10{}".format(m, p) - ) + ), + verbosity, + _MS_VERBOSITY_LEVEL, ) + # Store the values for which this table was set up in the dict if not Moecache.get("rinterpolator_q_metadata", None): Moecache["rinterpolator_q_metadata"] = {} Moecache["rinterpolator_q_metadata"][m] = options[m] Moecache["rinterpolator_q_metadata"][p] = options[p] -def Moe_de_Stefano_2017_pdf(options): - # Moe and de Stefano probability density function - # - # takes a dctionary as input (in options) with options: - # - # M1, M2, M3, M4 => masses (Msun) [M1 required, rest optional] - # P, P2, P3 => periods (days) [number: none=binary, 2=triple, 3=quadruple] - # ecc, ecc2, ecc3 => eccentricities [numbering as for P above] - # - # mmin => minimum allowed stellar mass (default 0.07) - # mmax => maximum allowed stellar mass (default 80.0) - # +def powerlaw_extrapolation_q(qdata, qs, indices, verbosity=0): + """ + Function to do the powerlaw extrapolation at the lower end of the q range + """ + newq = 0.05 + + # use a power-law extrapolation down to q=0.05, if possible + if (qdata[qs[indices[0]]] == 0.0) and (qdata[qs[indices[1]]] == 0.0): + # not possible + return 0 + + else: + slope = (np.log10(qdata[qs[indices[1]]]) - np.log10(qdata[qs[indices[0]]])) / ( + np.log10(qs[indices[1]]) - np.log10(qs[indices[0]]) + ) + intercept = np.log10(qdata[qs[indices[0]]]) - slope * np.log10(qs[indices[0]]) + + return slope * newq + intercept + + +def linear_extrapolation_q(qs, indices, qlimit, qdata, end_index, verbosity=0): + """ + Function to do the linear extrapolation for q. + """ + + # linear extrapolation + dq = qs[indices[1]] - qs[indices[0]] + + if dq == 0: + verbose_print( + "\tM&S: build_q_table: linear dq=0".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + # No change + return qs[end_index] + else: + slope = (qdata[qs[indices[1]]] - qdata[qs[indices[0]]]) / dq + + intercept = qdata[qs[indices[0]]] - slope * qs[indices[0]] + qdata[qlimit] = max(0.0, slope * qlimit + intercept) + verbose_print( + "\tM&S: build_q_table: linear Slope: {} intercept: {} dn/dq({}) = {}".format( + slope, intercept, qlimit, qdata[qlimit] + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + return max(0.0, slope * qlimit + intercept) + + +def get_integration_constant_q(q_interpolator, verbosity=0): + """ + Function to integrate the q interpolator and return the integration constant + """ + + dq = 1e-3 # resolution of the integration/renormalization + I = 0 + + # integrate: note that the value of the integral is + # meaningless to within a factor (which depends on $dq) + for q in np.arange(0, 1 + 2e-6, dq): + x = q_interpolator.interpolate([q]) + if len(x) == 0: + verbose_print( + "\tM&S: build_q_table: Q interpolator table interpolation failed.\n\t\ttmp_table = {}\n\t\tq_data = {}".format( + str(tmp_table), str(qdata) + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + raise ValueError + else: + I += x[0] * dq + # verbose_print( + # "\tM&S: build_q_table: dn/dq ({}) = {} I -> = {}".format(q, x[0], I), + # verbosity, + # _MS_VERBOSITY_LEVEL, + # ) + return I + + +def fill_data(sample_values, data_dict): + """ + Function that returns the normalized array of values for given logmass and logperiod + used for the e and q values + + TODO: make sure we do the correct thing with the dstep + """ + + data = {} + I = 0 + + dstep = float(sample_values[1]) - float(sample_values[0]) + + # Read out the data + for sample_value in sample_values: + val = data_dict[sample_value] + data[sample_value] = val + I += val + + # Normalize the data + for sample_value in sample_values: + data[sample_value] = data[sample_value] / I + + return data + + +def calc_P_integral( + options, min_P, integrals_string, interpolator_name, mass_string, verbosity=0 +): + """ + Function to calculate the P integral + + We need to renormalize this because min_per > 0, and not all periods should be included + """ + + global Moecache + max_logP = 10 + + I = 0 + dlogP = 1e-3 + + for logP in np.arange(np.log10(min_P), max_logP, dlogP): + # Loop over all the values in the table, between the min and max P + dp_dlogP = Moecache[interpolator_name].interpolate( + [np.log10(options[mass_string]), logP] + )[0] + + I += dp_dlogP * dlogP + Moecache[integrals_string][options[mass_string]] = I + verbose_print( + "\tM&S: calc_P_integral: min_P: {} integrals_string: {} interpolator_name: {} mass_string: {} I: {}".format( + min_P, integrals_string, interpolator_name, mass_string, I + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + + +def Moe_de_Stefano_2017_pdf(options, verbosity=0): + """ + Moe & distefano function to calculate the probability density. + + + takes a dctionary as input (in options) with options: + + M1, M2, M3, M4 => masses (Msun) [M1 required, rest optional] + P, P2, P3 => periods (days) [number: none=binary, 2=triple, 3=quadruple] + ecc, ecc2, ecc3 => eccentricities [numbering as for P above] + + mmin => minimum allowed stellar mass (default 0.07) + mmax => maximum allowed stellar mass (default 80.0) + """ + + verbosity_shift = -2 + + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf with options:\n\t\t{}".format( + json.dumps(options) + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) prob = [] # Value that we will return - multiplicity = options["multiplicity"] + # Get the multiplicity from the options, and if its not there, calculate it based on the + # TODO: the function below makes no sense. We NEED to pass the multiplicity in the if not options.get("multiplicity", None): - multiplicity = 1 - for n in range(2, 5): - multiplicity += 1 if options.get("M{}".format(n), None) else 0 + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Did not find a multiplicity value in the options dictionary", + verbosity, + _MS_VERBOSITY_LEVEL, + ) + raise ValueError + # multiplicity = 1 + # for n in range(2, 5): + # multiplicity += 1 if options.get("M{}".format(n), None) else 0 else: multiplicity = options["multiplicity"] - # immediately return 0 if the multiplicity modulator is 0 + # Immediately return 0 if the multiplicity modulator is 0 if options["multiplicity_modulator"][multiplicity - 1] == 0: - print("_pdf ret 0 because of mult mod\n") + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: returning 0 because of the multiplicity modulator being 0", + verbosity, + _MS_VERBOSITY_LEVEL, + ) return 0 ############################################################ # multiplicity fraction - prob.append(Moe_de_Stefano_2017_multiplicity_fractions(options)[multiplicity - 1]) + # Calculate the probabilty, or rather, fraction, of stars that belong to this mass + prob.append( + Moe_de_Stefano_2017_multiplicity_fractions(options, verbosity)[multiplicity - 1] + ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Appended multiplicity (mass1 = {}) probability ({}) to the prob list ({})".format( + options["M1"], prob[-1], prob + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) ############################################################ # always require an IMF for the primary star @@ -1530,59 +1675,63 @@ def Moe_de_Stefano_2017_pdf(options): # (dlnM = dM/M, so 1/dlnM = M/dM) # TODO: Create an n-part-powerlaw method that can have breakpoints and slopes. I'm using a three-part powerlaw now. + # TODO: is this actually the correct way? putting the M1 in there? Do we sample in logspace? prob.append(Kroupa2001(options["M1"]) * options["M1"]) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Appended Mass (m={}) probability ({}) to the prob list ({})".format( + options["M1"], prob[-1], prob + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + """ + From here we go through the multiplicities. + """ if multiplicity >= 2: + ############### + # Calculation for period of the binary + # Separation of the inner binary options["sep"] = calc_sep_from_period( options["M1"], options["M2"], options["P"] ) + # TODO: add check for min_P with instant RLOF? # Total mass inner binary: options["M1+M2"] = options["M1"] + options["M2"] - # binary, triple or quadruple system + # Set up the interpolator for the periods if not Moecache.get("rinterpolator_log10P", None): Moecache["rinterpolator_log10P"] = py_rinterpolate.Rinterpolate( table=Moecache["period_distributions"], # Contains the table of data nparams=2, # log10M, log10P ndata=2, # binary, triple + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), ) - # TODO: fix this function - # test the period distribution integrates to 1.0 - # if(0){ - # if(!defined $Moecache{'P_integrals'}->{$opts->{'M1'}}) - # { - # my $I = 0.0; - # my $dlogP = 1e-3; - # for(my $logP = 0.0; $logP < 10.0; $logP += $dlogP) - # { - # my $dp_dlogP = # dp / dlogP - # $Moecache{'rinterpolator_log10P'}->interpolate( - # [ - # log10($opts->{'M1'}), - # $logP - # ])->[0]; - # $I += $dp_dlogP * $dlogP; - # #printf "logM1 = %g, logP = %g -> dp/logP = %g\n", - # # log10($opts->{'M1'}), - # # $logP, - # # $dp_dlogP; - # } - # $Moecache{'P_integrals'}->{$opts->{'M1'}} = $I; - - # #printf "M1=%g : P integral %g\n", - # # $opts->{'M1'}, - # # $Moecache{'P_integrals'}->{$opts->{'M1'}}; - # } - # } + if not Moecache.get("P_integrals", None): + Moecache["P_integrals"] = {} - prob.append( - Moecache["rinterpolator_log10P"].interpolate( - [np.log10(options["M1"]), np.log10(options["P"])] - )[0] + # Calculate normalisation contant for P1 + if not Moecache["P_integrals"].get(options["M1"], None): + calc_P_integral( + options, 1, "P_integrals", "rinterpolator_log10P", "M1", verbosity + ) + + # Set probabilty for P1 + p_val = Moecache["rinterpolator_log10P"].interpolate( + [np.log10(options["M1"]), np.log10(options["P"])] + )[0] + p_val = p_val / Moecache["P_integrals"][options["M1"]] + prob.append(p_val) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Appended period (m={}, P={}) probability ({}) to the prob list ({})".format( + options["M1"], options["P"], prob[-1], prob + ), + verbosity, + _MS_VERBOSITY_LEVEL, ) ############################################################ @@ -1591,32 +1740,78 @@ def Moe_de_Stefano_2017_pdf(options): # we need to construct the q table for the given M1 # subject to qmin = Mmin/M1 - # Make a table storing Moe's data for q distributions + # Build the table for q + primary_mass = options["M1"] + secondary_mass = options["M2"] + m_label = "M1" + p_label = "P" + # Make a table storing Moe's data for q distributions if not Moecache.get("rinterpolator_q", None): Moecache["rinterpolator_q"] = py_rinterpolate.Rinterpolate( table=Moecache["q_distributions"], # Contains the table of data nparams=3, # log10M, log10P, q ndata=1, # + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), + ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Created new q interpolator: {}".format( + Moecache["rinterpolator_q"] + ), + verbosity, + _MS_VERBOSITY_LEVEL, ) - # Build the table for q - primary_mass = options["M1"] - secondary_mass = options["M2"] - m_label = "M1" - p_label = "P" + # Construct the q table + build_q_table(options, m_label, p_label, verbosity=verbosity) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Created q_table ".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) - build_q_table(options, m_label, p_label) + # Add probability for the mass ratio prob.append( Moecache[ "rinterpolator_q_given_{}_log10{}".format(m_label, p_label) ].interpolate([secondary_mass / primary_mass])[0] ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M={} P={} q={}) probability ({}) to the prob list ({}) ".format( + options["M1"], + options["P"], + options["M2"] / options["M1"], + prob[-1], + prob, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + ############################################################ + # Eccentricity # TODO add eccentricity calculations + # Make a table storing Moe's data for q distributions + if not Moecache.get("rinterpolator_e", None): + Moecache["rinterpolator_q"] = py_rinterpolate.Rinterpolate( + table=Moecache["q_distributions"], # Contains the table of data + nparams=3, # log10M, log10P, q + ndata=1, # + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), + ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Created new q interpolator: {}".format( + Moecache["rinterpolator_q"] + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + + # Calculations for when multiplicity is bigger than 3 + # BEWARE: binary_c does not evolve these systems actually and the code below should be revised for when binary_c actually evolves triples. + # For that reason, I would not advise to use things with multiplicity > 3 if multiplicity >= 3: - # triple or quadruple system ############################################################ # orbital period 2 = @@ -1629,23 +1824,31 @@ def Moe_de_Stefano_2017_pdf(options): # stars 1 and 2 # TODO: Is this a correct assumption? - max_sep = ( - 10.0 * options["sep"] * (1.0 + options["ecc"]) - ) # TODO: isnt this the minimal separation? + max_sep = 10.0 * options["sep"] * (1.0 + options["ecc"]) min_P2 = calc_period_from_sep(options["M1+M2"], options["mmin"], max_sep) if options["P2"] < min_P2: # period is too short : system is not hierarchical prob.append(0) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: period2 is too short: {} < {}, system is not hierarchichal. Added 0 to probability list".format( + options["P1"], min_P2 + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + # TODO: consider directly returning here + else: # period is long enough that the system is hierarchical - # hence the separation between the outer star # and inner binary options["sep2"] = calc_sep_from_period( options["M3"], options["M1+M2"], options["P2"] ) + # TODO: Consider actually removing these double interpolators. + # Not necessary to have these around, its the same dataset thats loaded if not Moecache.get("rinterpolator_log10P2", None): Moecache["rinterpolator_log10P2"] = py_rinterpolate.Rinterpolate( table=Moecache[ @@ -1653,29 +1856,37 @@ def Moe_de_Stefano_2017_pdf(options): ], # Contains the table of data nparams=2, # log10(M1+M2), log10P # TODO: Really? Is the first column of that table M1+M2? ndata=2, # binary, triple + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), ) + # Create the dictionary where we store the area of the P2 integrals if not Moecache.get("P2_integrals", None): Moecache["P2_integrals"] = {} + # Calculate the P2 integrals if they don't exist if not Moecache["P2_integrals"].get(options["M1+M2"], None): - # normalize because $min_per > 0 so not all the periods - # should be included - I_p2 = 0 - dlogP2 = 1e-3 - for logP2 in np.arange(np.log10(min_P2), 10, dlogP2): - # dp_dlogP2 = dp / dlogP - dp_dlogP2 = Moecache["rinterpolator_log10P2"].interpolate( - [np.log10(options["M1"]), logP2] - )[0] - I_p2 += dp_dlogP2 * dlogP2 - Moecache["P2_integrals"][options["M1+M2"]] = I_p2 + calc_P_integral( + options, + min_P2, + "P2_integrals", + "rinterpolator_log10P2", + "M1+M2", + verbosity, + ) + # Add the probability p_val = Moecache["rinterpolator_log10P"].interpolate( [np.log10(options["M1+M2"]), np.log10(options["P2"])] )[0] - p_val = p_val / Moecache["P2_integrals"] + p_val = p_val / Moecache["P2_integrals"][options["M1+M2"]] prob.append(p_val) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Appended period2 (m1={} m2={}, P2={}) probability ({}) to the prob list ({})".format( + options["M1"], options["M2"], options["P2"], prob[-1], prob + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) ############################################################ # mass ratio 2 = q2 = M3 / (M1+M2) @@ -1684,32 +1895,59 @@ def Moe_de_Stefano_2017_pdf(options): # subject to qmin = Mmin/(M1+M2) # # Make a table storing Moe's data for q distributions - # TODO: Check if this is correct. if not Moecache.get("rinterpolator_q", None): Moecache["rinterpolator_q"] = py_rinterpolate.Rinterpolate( table=Moecache["q_distributions"], # Contains the table of data nparams=3, # log10(M1+M2), log10P2, q ndata=1, # + verbosity=verbosity - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), + ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Created new q interpolator: {}".format( + Moecache["rinterpolator_q"] + ), + verbosity, + _MS_VERBOSITY_LEVEL, ) - # Build the table for q2 + # Set the variables for the masses and their names primary_mass = options["M1+M2"] secondary_mass = options["M3"] m_label = "M1+M2" p_label = "P2" - build_q_table(options, m_label, p_label) + # Build q table + build_q_table(options, m_label, p_label, verbosity=verbosity) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Called build_q_table", + verbosity, + _MS_VERBOSITY_LEVEL, + ) + + # Add the probability prob.append( Moecache[ "rinterpolator_q_given_{}_log10{}".format(m_label, p_label) ].interpolate([secondary_mass / primary_mass])[0] ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M1+M2={} M3={} P={} q={}) probability ({}) to the prob list ({}) ".format( + options["M1+M2"], + options["M3"], + options["P"], + secondary_mass / primary_mass, + prob[-1], + prob, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) - # TODO: ecc2 + # TODO: Implement ecc2 calculation if multiplicity == 4: # quadruple system. - # TODO: Ask Rob about the strructure of the quadruple. Is htis only double binary quadrupples? + # TODO: Ask Rob about the strructure of the quadruple. Is this only double binary quadrupples? ############################################################ # orbital period 3 @@ -1718,7 +1956,8 @@ def Moe_de_Stefano_2017_pdf(options): # as for any other stars but Pmax must be such that # sep3 < sep2 * 0.2 - max_sep3 = 0.2 * options["sep2"] * (1.0 + opts["ecc2"]) + # TODO: fix this here + max_sep3 = 0.2 * options["sep2"] * (1.0 + options["ecc2"]) max_per3 = calc_period_from_sep( options["M1+M2"], options["mmin"], max_sep3 ) @@ -1732,32 +1971,37 @@ def Moe_de_Stefano_2017_pdf(options): ], # Contains the table of data nparams=2, # log10(M1+M2), log10P3 ndata=2, # binary, triple + verbosity=verbosity + - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), ) if not Moecache.get("P2_integrals", None): Moecache["P2_integrals"] = {} if not Moecache["P2_integrals"].get(options["M1+M2"], None): - # normalize because $min_per > 0 so not all the periods - # should be included - I_p2 = 0 - dlogP2 = 1e-3 - - for logP2 in np.arange(np.log10(min_P2), 10, dlogP2): - # dp_dlogP2 = dp / dlogP - dp_dlogP2 = Moecache["rinterpolator_log10P2"].interpolate( - [np.log10(options["M1"]), logP2] - )[0] - I_p2 += dp_dlogP2 * dlogP2 - Moecache["P2_integrals"][options["M1+M2"]] = I_p2 + calc_P_integral( + options, + min_P2, + "P2_integrals", + "rinterpolator_log10P2", + "M1+M2", + verbosity, + ) # TODO: should this really be the log10P interpolator? + # p_val = Moecache["rinterpolator_log10P"].interpolate( [np.log10(options["M1+M2"]), np.log10(options["P2"])] )[0] - - p_val = p_val / Moecache["P2_integrals"] + p_val = p_val / Moecache["P2_integrals"][options["M1+M2"]] prob.append(p_val) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Appended period2 (M=4) (m1={} m2={}, P2={}) probability ({}) to the prob list ({})".format( + options["M1"], options["M2"], options["P2"], prob[-1], prob + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) ############################################################ # mass ratio 2 @@ -1773,6 +2017,8 @@ def Moe_de_Stefano_2017_pdf(options): ], # Contains the table of data nparams=3, # log10(M1+M2), log10P2, q ndata=1, # + verbosity=verbosity + - (_MS_VERBOSITY_INTERPOLATOR_LEVEL - 1), ) # Build the table for q2 @@ -1781,79 +2027,101 @@ def Moe_de_Stefano_2017_pdf(options): m_label = "M1+M2" p_label = "P2" - build_q_table(options, m_label, p_label) + build_q_table(options, m_label, p_label, verbosity=verbosity) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Created q_table ".format(), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + + # Add the probability prob.append( Moecache[ "rinterpolator_q_given_{}_log10{}".format(m_label, p_label) ].interpolate([secondary_mass / primary_mass])[0] ) + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: appended mass ratio (M1+M2={} M3={} P={} q={}) probability ({}) to the prob list ({}) ".format( + options["M1+M2"], + options["M3"], + options["P"], + secondary_mass / primary_mass, + prob[-1], + prob, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # todo ecc 3 elif multiplicity not in range(1, 5): - print("Unknown multiplicity {}\n".format(multiplicity)) - - # TODO: Translate this to get some better output - # printf "PDFPROBS (M1=%g, logP=%g q=%g) : @prob\n", - # $opts->{'M1'}, - # $multiplicity == 2 ? ($opts->{'M2'}/$opts->{'M1'}) : -999, - # $multiplicity == 2 ? log10($opts->{'P'}) : -999; + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: Unknown multiplicity {}".format( + multiplicity + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) # the final probability density is the product of all the # probability density functions - # TODO: Where do we take into account the stepsize? probdens is not a probability if we dont take a stepsize prob_dens = 1 * np.prod(prob) - print_info = 1 - if print_info: - # print("Probability density") - - if multiplicity == 1: - print( - "M1={} q=N/A log10P=N/A ({}): {} -> {}\n".format( - options["M1"], len(prob), str(prob), prob_dens - ) - ) - elif multiplicity == 2: - print( - "M1={} q={} log10P={} ({}): {} -> {}\n".format( - options["M1"], - options["M2"] / options["M1"], - np.log10(options["P"]), - len(prob), - str(prob), - prob_dens, - ) - ) - elif multiplicity == 3: - print( - "M1={} q={} log10P={} ({}): M3={}} P2={} ecc2={} : {} - {}".format( - options["M1"], - options["M2"] / options["M1"], - np.log10(options["P"]), - len(prob), - options["M3"], - np.log10("P2"), - np.log10("ecc2"), - str(prob), - prob_dens, - ) - ) - elif multiplicity == 4: - print( - "M1={} q={} log10P={} ({}) : M3={} P2={}} ecc2={} : M4={} P3={} ecc3={} : {} -> {}".format( - options["M1"], - options["M2"] / options["M1"], - np.log10(options["P"]), - len(prob), - options["M3"], - np.log10("P2"), - np.log10(options["ecc2"]), - options["M4"], - np.log10("P3"), - np.log10(options["ecc3"]), - str(prob), - prob_dens, - ) - ) + if multiplicity == 1: + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q=N/A log10P=N/A ({}): {} -> {}\n".format( + options["M1"], len(prob), str(prob), prob_dens + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + elif multiplicity == 2: + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ({}): {} -> {}\n".format( + options["M1"], + options["M2"] / options["M1"], + np.log10(options["P"]), + len(prob), + str(prob), + prob_dens, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + elif multiplicity == 3: + verbose_print( + "\tM&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ({}): M3={}} P2={} ecc2={} : {} - {}".format( + options["M1"], + options["M2"] / options["M1"], + np.log10(options["P"]), + len(prob), + options["M3"], + np.log10("P2"), + np.log10("ecc2"), + str(prob), + prob_dens, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) + elif multiplicity == 4: + verbose_print( + "M&S: Moe_de_Stefano_2017_pdf: M1={} q={} log10P={} ({}) : M3={} P2={}} ecc2={} : M4={} P3={} ecc3={} : {} -> {}".format( + options["M1"], + options["M2"] / options["M1"], + np.log10(options["P"]), + len(prob), + options["M3"], + np.log10("P2"), + np.log10(options["ecc2"]), + options["M4"], + np.log10("P3"), + np.log10(options["ecc3"]), + str(prob), + prob_dens, + ), + verbosity, + _MS_VERBOSITY_LEVEL, + ) return prob_dens diff --git a/binarycpython/utils/functions.py b/binarycpython/utils/functions.py index 3a054a3a0..0df3abe35 100644 --- a/binarycpython/utils/functions.py +++ b/binarycpython/utils/functions.py @@ -26,6 +26,8 @@ from collections import defaultdict from binarycpython import _binary_c_bindings import binarycpython.utils.moe_distefano_data as moe_distefano_data +import py_rinterpolate + ######################################################## # Unsorted ######################################################## @@ -1705,6 +1707,8 @@ def binaryc_json_serializer(obj: Any) -> Any: if inspect.isfunction(obj): return str(obj) + elif isinstance(obj, py_rinterpolate.Rinterpolate): + return str(obj) else: return obj diff --git a/binarycpython/utils/grid.py b/binarycpython/utils/grid.py index 9911206b5..7bd10cc05 100644 --- a/binarycpython/utils/grid.py +++ b/binarycpython/utils/grid.py @@ -43,6 +43,7 @@ from pathos.pools import _ProcessPool as Pool from binarycpython.utils.grid_options_defaults import ( grid_options_defaults_dict, moe_distefano_default_options, + _MS_VERBOSITY_LEVEL, ) from binarycpython.utils.custom_logging_functions import ( @@ -64,6 +65,7 @@ from binarycpython.utils.functions import ( get_moe_distefano_dataset, ) +import py_rinterpolate # from binarycpython.utils.hpc_functions import ( # get_condor_version, @@ -76,6 +78,7 @@ from binarycpython.utils.functions import ( from binarycpython.utils.distribution_functions import ( Moecache, LOG_LN_CONVERTER, + fill_data, ) from binarycpython import _binary_c_bindings @@ -743,11 +746,8 @@ class Population: ], } - # print(Moecache) - print(Moecache.keys()) - ## - # Clean up code: remove files, unset values. This is placed in the general evolve function, + # Clean up code: remove files, unset values, unload interpolators etc. This is placed in the general evolve function, # because that makes for easier control self._cleanup() @@ -928,7 +928,7 @@ class Population: # pathos_multiprocess manager = multiprocessing.Manager() - job_queue = manager.Queue(maxsize=self.grid_options['max_queue_size']) + job_queue = manager.Queue(maxsize=self.grid_options["max_queue_size"]) result_queue = manager.Queue(maxsize=self.grid_options["amt_cores"]) # Create process instances @@ -958,7 +958,6 @@ class Population: sentinel = object() for output_dict in iter(result_queue.get, sentinel): - print(output_dict) combined_output_dict = merge_dicts(combined_output_dict, output_dict) if result_queue.empty(): break @@ -1358,6 +1357,16 @@ class Population: result_queue.put(output_dict) stream_logger.debug(f"Process-{self.process_ID} is finished.") + # Clean up the interpolators if they exist + # TODO: make a cleanup function for the individual threads + # TODO: make sure this is necessary. Actually its probably not, because we have a centralized queue + verbose_print( + "Process {}: Cleaning up interpolators".format(ID), + self.grid_options["verbosity"], + 1, + ) + self._clean_interpolators() + return # Single system @@ -1597,6 +1606,9 @@ class Population: # Unload/free custom_logging_code # TODO: cleanup custom logging code. + # Clean up the interpolator functions + self._clean_interpolators() + ################################################### # Gridcode functions # @@ -1800,7 +1812,7 @@ class Population: # Add condition failed action: code_string += ( indent * (depth + 2) - + 'print("Condition for {} not met!")'.format( + + 'print("Grid generator: Condition for {} not met!")'.format( grid_variable["parameter_name"] ) + "\n" @@ -1821,7 +1833,9 @@ class Population: # Add phasevol check action: code_string += ( indent * (depth + 2) - + 'print("phasevol_{} <= 0!")'.format(grid_variable["name"]) + + 'print("Grid generator: phasevol_{} <= 0!")'.format( + grid_variable["name"] + ) + "\n" ) code_string += indent * (depth + 2) + "continue" + "\n" @@ -3079,8 +3093,17 @@ class Population: # Require input options if not options: - print("Please provide a options dictionary.") - raise ValueError + msg = "Please provide a options dictionary." + verbose_print( + "\tMoe_de_Stefano_2017: {}".format(msg), + self.grid_options["verbosity"], + 0, + ) + raise ValueError(msg) + + # Create the tmp dir + ms_tmp_dir = os.path.join(self.grid_options["tmp_dir"], "moe_distefano") + os.makedirs(ms_tmp_dir, exist_ok=True) # TODO: put this in defaults default_options = copy.deepcopy(moe_distefano_default_options) @@ -3089,7 +3112,7 @@ class Population: options = update_dicts(default_options, options) # Write options to a file - with open("/tmp/moeopts.dat", "w") as f: + with open(os.path.join(ms_tmp_dir, "moeopts.dat"), "w") as f: f.write(json.dumps(options, indent=4)) json_data = get_moe_distefano_dataset(options) @@ -3100,9 +3123,16 @@ class Population: # Get all the masses logmasses = sorted(json_data["log10M1"].keys()) if not logmasses: - print("The table does not contain masses") - raise ValueError - with open("/tmp/moe.log", "w") as logfile: + msg = "The table does not contain masses." + verbose_print( + "\tMoe_de_Stefano_2017: {}".format(msg), + self.grid_options["verbosity"], + 0, + ) + raise ValueError(msg) + + # Write to file + with open(os.path.join(ms_tmp_dir, "moe.log"), "w") as logfile: logfile.write("logâ‚â‚€Masses(M☉) {}\n".format(logmasses)) # Get all the periods and see if they are all consistently present @@ -3115,8 +3145,13 @@ class Population: current_logperiods = sorted(json_data["log10M1"][logmass]["logP"]) if not (logperiods == current_logperiods): - print("Period values are not consistent throughout the dataset") - raise ValueError + msg = "Period values are not consistent throughout the dataset" + verbose_print( + "\tMoe_de_Stefano_2017: {}".format(msg), + self.grid_options["verbosity"], + 0, + ) + raise ValueError(msg) ############################################################ # log10period binwidth : of course this assumes a fixed @@ -3126,16 +3161,22 @@ class Population: if not dlog10P == ( float(current_logperiods[i + 1]) - float(current_logperiods[i]) ): - print("Period spacing is not consistent throughout the dataset") - raise ValueError + msg = "Period spacing is not consistent throughout the dataset" + verbose_print( + "\tMoe_de_Stefano_2017: {}".format(msg), + self.grid_options["verbosity"], + 0, + ) + raise ValueError(msg) - with open("/tmp/moe.log", "a") as logfile: + # Write to file + with open( + os.path.join(self.grid_options["tmp_dir"], "moe_distefano", "moe.log"), "a" + ) as logfile: logfile.write("logâ‚â‚€Periods(days) {}\n".format(logperiods)) # Fill the global dict for logmass in logmasses: - # print("Logmass: {}".format(logmass)) - # Create the multiplicity table if not Moecache.get("multiplicity_table", None): Moecache["multiplicity_table"] = [] @@ -3151,7 +3192,6 @@ class Population: ] ) - print("Size multiplicity table: {}", len(Moecache["multiplicity_table"])) ############################################################ # a small log10period which we can shift just outside the # table to force integration out there to zero @@ -3216,10 +3256,6 @@ class Population: / dlog10P, ] ) - print( - "Size period_distributions table: {}", - len(Moecache["period_distributions"]), - ) ############################################################ # distributions as a function of mass, period, q @@ -3227,18 +3263,11 @@ class Population: # First, get a list of the qs given by Moe # qs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["q"]) - qdata = {} - I_q = 0.0 - # get the q distribution from M&S, which ranges 0.15 to 0.95 - for q in qs: - val = json_data["log10M1"][logmass]["logP"][logperiod]["q"][q] - qdata[q] = val - I_q += val - - # hence normalize to 1.0 - for q in qs: - qdata[q] = qdata[q] / I_q + # Fill the data and 'normalize' + qdata = fill_data( + qs, json_data["log10M1"][logmass]["logP"][logperiod]["q"] + ) # Create the multiplicity table if not Moecache.get("q_distributions", None): @@ -3252,18 +3281,11 @@ class Population: ############################################################ # eccentricity distributions as a function of mass, period, ecc eccs = sorted(json_data["log10M1"][logmass]["logP"][logperiod]["e"]) - ecc_data = {} - I_e = 0 - - # Read out the data - for ecc in eccs: - val = json_data["log10M1"][logmass]["logP"][logperiod]["e"][ecc] - ecc_data[ecc] = val - I_e += val - # Normalize the data - for ecc in eccs: - ecc_data[ecc] = ecc_data[ecc] / I_e + # Fill the data and 'normalize' + ecc_data = fill_data( + eccs, json_data["log10M1"][logmass]["logP"][logperiod]["e"] + ) # Create the multiplicity table if not Moecache.get("ecc_distributions", None): @@ -3279,7 +3301,7 @@ class Population: Moecache["period_distributions"].append( [ float(logmass), - float(logperiods[-1]) + 0.5 * dlog10P, + float(logperiods[-1]) + 0.5 * dlog10P, # TODO: why this shift? json_data["log10M1"][logmass]["logP"][logperiods[-1]][ "normed_bin_frac_p_dist" ] @@ -3298,14 +3320,38 @@ class Population: 0.0, ] ) - print( - "Size period_distributions table: {}".format( - len(Moecache["period_distributions"]) - ) - ) + + verbose_print( + "\tMoe_de_Stefano_2017: Length period_distributions table: {}".format( + len(Moecache["period_distributions"]) + ), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) + verbose_print( + "\tMoe_de_Stefano_2017: Length multiplicity table: {}".format( + len(Moecache["multiplicity_table"]) + ), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) + verbose_print( + "\tMoe_de_Stefano_2017: Length q table: {}".format( + len(Moecache["q_distributions"]) + ), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) + verbose_print( + "\tMoe_de_Stefano_2017: Length ecc table: {}".format( + len(Moecache["ecc_distributions"]) + ), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) # Write to logfile - with open("/tmp/moecache.json", "w") as cache_filehandle: + with open(os.path.join(ms_tmp_dir, "moecache.json"), "w") as cache_filehandle: cache_filehandle.write(json.dumps(Moecache, indent=4)) # TODO: remove this and make the plotting of the multiplicity fractions more modular @@ -3349,7 +3395,6 @@ class Population: ############################################################ # make a version of the options which can be passed to # the probability density function in the gridcode - # TODO: check if that works, if we just pass the options as a dict ############################################################ # first, the multiplicity, this is 1,2,3,4, ... @@ -3357,11 +3402,13 @@ class Population: max_multiplicity = 0 for i in range(1, 5): mod = options["multiplicity_modulator"][i - 1] - print("Check mod {} = {}".format(i, mod)) if mod > 0: max_multiplicity = i - print("Max multiplicity = {}".format(max_multiplicity)) - + verbose_print( + "\tMoe_de_Stefano_2017: Max multiplicity = {}".format(max_multiplicity), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) ###### # Setting up the grid variables @@ -3408,7 +3455,7 @@ class Population: gridtype="centred", dphasevol="dlnm1", precode='M_1 = np.exp(lnm1); options["M1"]=M_1', - probdist="Moe_de_Stefano_2017_pdf({{{}, {}, {}}}) if multiplicity == 1 else 1".format( + probdist="Moe_de_Stefano_2017_pdf({{{}, {}, {}}}, verbosity=self.grid_options['verbosity']) if multiplicity == 1 else 1".format( str(options)[1:-1], "'multiplicity': multiplicity", "'M1': M_1" ), ) @@ -3677,7 +3724,7 @@ eccentricity3=0 # the real probability. The trick we use is to strip the options_dict as a string # and add some keys to it: - probdist_addition = "Moe_de_Stefano_2017_pdf({{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}})".format( + probdist_addition = "Moe_de_Stefano_2017_pdf({{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, verbosity=self.grid_options['verbosity'])".format( str(options)[1:-1], '"multiplicity": multiplicity', '"M1": M_1', @@ -3696,3 +3743,28 @@ eccentricity3=0 self.grid_options["_grid_variables"][self.last_grid_variable()][ "probdist" ] = probdist_addition + + verbose_print( + "\tMoe_de_Stefano_2017: Added final call to the pdf function".format( + max_multiplicity + ), + self.grid_options["verbosity"], + _MS_VERBOSITY_LEVEL, + ) + + def _clean_interpolators(self): + """ + Function to clean up the interpolators after a run + + We look in the MoeCache global variable for items that are interpolators. + Should be called by the general cleanup function AND the thread cleanup function + """ + + interpolator_keys = [] + for key in Moecache.keys(): + if isinstance(Moecache[key], py_rinterpolate.Rinterpolate): + interpolator_keys.append(key) + + for key in interpolator_keys: + Moecache[key].destroy() + del Moecache[key] diff --git a/binarycpython/utils/grid_options_defaults.py b/binarycpython/utils/grid_options_defaults.py index 98bf8b72e..971a13ecd 100644 --- a/binarycpython/utils/grid_options_defaults.py +++ b/binarycpython/utils/grid_options_defaults.py @@ -19,6 +19,11 @@ import os from binarycpython.utils.custom_logging_functions import temp_dir +_MS_VERBOSITY_LEVEL = 5 +_MS_VERBOSITY_INTERPOLATOR_LEVEL = 6 +_MS_VERBOSITY_INTERPOLATOR_EXTRA_LEVEL = 7 + + # Options dict grid_options_defaults_dict = { ########################## @@ -34,7 +39,7 @@ grid_options_defaults_dict = { "_commandline_input": "", "log_runtime_systems": 0, # whether to log the runtime of the systems (1 file per thread. stored in the tmp_dir) "_actually_evolve_system": True, # Whether to actually evolve the systems of just act as if. for testing. used in _process_run_population_grid - "max_queue_size": 1000, # Maximum size of the system call queue. Can't be too big! + "max_queue_size": 1000, # Maximum size of the system call queue. Can't be too big! ########################## # Execution log: ########################## @@ -472,7 +477,7 @@ grid_options_descriptions = { "_total_mass_run": "To count the total mass that thread/process has ran", "_total_probability_weighted_mass_run": "To count the total mass * probability for each system that thread/process has ran", "_actually_evolve_system": "Whether to actually evolve the systems of just act as if. for testing. used in _process_run_population_grid", - "max_queue_size": "Maximum size of the queue that is used to feed the processes. Don't make this too big! Default: 1000. Input: int" + "max_queue_size": "Maximum size of the queue that is used to feed the processes. Don't make this too big! Default: 1000. Input: int", } ### @@ -579,8 +584,8 @@ moe_distefano_default_options = { # linear2 # plaw2 # nolowq - "q_low_extrapolation_method": "linear2", - "q_high_extrapolation_method": "linear2", + "q_low_extrapolation_method": "linear", + "q_high_extrapolation_method": "linear", } moe_distefano_default_options_description = { -- GitLab