Source code for lal_cuda.SimIMRPhenomP

"""This submodule handles all interaction with the LALSuite C API.

It provides a class for inputs and a class for outputs and functions for
reading/writing/comparing them.  The inputs method has a 'run' method
for calling the C API.
"""

import numpy as np
import glob
import math

import lal_cuda

# Generate mocks for these if we are building for RTD
lal = lal_cuda.import_mock_RTD("lal")
lalsimulation = lal_cuda.import_mock_RTD("lalsimulation")

# Define a set of default model parameters.  These are the ones in the
# 'default' fiducial inputs file in the 'data' directory.
chi1_default = 0.1
chi2_default = 0.2
m1_default = 30
m2_default = 30
chip_default = 0.34
thetaJ_default = 1.1
alpha0_default = 1.5
distance_default = 1000
phic_default = np.pi * 0.4
fref_default = 30
flow_default = 20
fhigh_default = 80


[docs]def to_string(inputs, outputs): """Convert a paired set of inputs/outputs to an ASCII table. :param inputs: An instance of the inputs class. :param outputs: An associated instance of the outputs class. :return: A list of strings. """ r_val = '# Column 01: frequency\n' r_val += '# 02: hp - real\n' r_val += '# 03: hp - imaginary\n' r_val += '# 04: hc - real\n' r_val += '# 05: hc - imaginary\n' for f_i, hp_i, hc_i in zip(inputs.freqs, outputs.hp, outputs.hc): r_val += "%8.2f %12.5e %12.5e %12.5e %12.5e\n" % (f_i, hp_i.real, hp_i.imag, hc_i.real, hc_i.imag) return r_val
[docs]def to_binary(inputs, outputs, filename_label=None): """Convert a paired set of inputs/outputs to binary files. :param inputs: An instance of the inputs class. :param outputs: An associated instance of the outputs class. :param filename_label: An optional label for the output files. :return: None """ inputs.write(filename_label, filename_label=filename_label) outputs.write(filename_label, filename_label=filename_label)
[docs]def calc_frac_diff(x, y): """Calculate the fractional difference between two numbers. If the reference value is 0 and the value to check is not, returns 1. :param x: Value to check :param y: Reference value :return: Value """ if(y != 0): return math.fabs((x - y) / y) elif(x != 0.): return 1. else: return 0.
[docs]def calc_difference_from_reference(inputs, outputs, verbose=True): """Look for a match between the given inputs and the stored reference inputs. If found return a dictionary with absolute differences from the given outputs, otherwise print a warning to stdout if verbose=True. All reference inputs/outputs were computed by running the PhenomPCore script of this package against a version of LALSuite compiled from the commit with hash "3494e18e6d". :param inputs: An instance of the inputs class :param outputs: An associated instance of the outputs class :param verbose: Boolean controlling whether logging information is reported :return: None """ # Get a list of reference input/output files filename_ref_inputs = glob.glob(lal_cuda.full_path_datafile("inputs.dat*")) filename_ref_outputs = [ filename_ref_input_i.replace( "inputs.dat", "outputs.dat") for filename_ref_input_i in filename_ref_inputs] # Look to see if the given inputs are in the stored reference inputs filename_ref_output = None for filename_ref_input_i, filename_ref_output_i in zip(filename_ref_inputs, filename_ref_outputs): inputs_i = inputs.read(filename_ref_input_i) # Check to see if this set of inputs matches the set that has been passed if(inputs_i == inputs): inputs_ref = inputs_i filename_ref_output = filename_ref_output_i break # Perform check if a match has been found if(not filename_ref_output): lal_cuda.log.warning( "Checking could not be performed: reference data set with given inputs (%s) not found." % (inputs)) else: if(verbose): lal_cuda.log.open('Performing test...') # Read reference dataset's outputs outputs_ref = outputs.read(filename_ref_output) # Compute statistics of difference from test reference hpval_real_diff_avg = 0. hpval_imag_diff_avg = 0. hcval_real_diff_avg = 0. hcval_imag_diff_avg = 0. hpval_real_diff_max = 0. hpval_imag_diff_max = 0. hcval_real_diff_max = 0. hcval_imag_diff_max = 0. for (hp_i, hc_i, hp_ref_i, hc_ref_i) in zip(outputs.hp, outputs.hc, outputs_ref.hp, outputs_ref.hc): hpval_real_diff_i = calc_frac_diff(hp_i.real, hp_ref_i.real) hpval_imag_diff_i = calc_frac_diff(hp_i.imag, hp_ref_i.imag) hcval_real_diff_i = calc_frac_diff(hc_i.real, hc_ref_i.real) hcval_imag_diff_i = calc_frac_diff(hc_i.imag, hc_ref_i.imag) hpval_real_diff_avg += hpval_real_diff_i hpval_imag_diff_avg += hpval_imag_diff_i hcval_real_diff_avg += hcval_real_diff_i hcval_imag_diff_avg += hcval_imag_diff_i hpval_real_diff_max = max([hpval_real_diff_max, hpval_real_diff_i]) hpval_imag_diff_max = max([hpval_imag_diff_max, hpval_imag_diff_i]) hcval_real_diff_max = max([hcval_real_diff_max, hcval_real_diff_i]) hcval_imag_diff_max = max([hcval_imag_diff_max, hcval_imag_diff_i]) hpval_real_diff_avg /= float(len(outputs.hp)) hpval_imag_diff_avg /= float(len(outputs.hp)) hcval_real_diff_avg /= float(len(outputs.hc)) hcval_imag_diff_avg /= float(len(outputs.hc)) # Report results if(verbose): lal_cuda.log.comment(' Average/maximum real(hp) fractional difference: %.2e/%.2e' % (hpval_real_diff_avg, hpval_real_diff_max)) lal_cuda.log.comment(' Average/maximum imag(hp) fractional difference: %.2e/%.2e' % (hpval_imag_diff_avg, hpval_imag_diff_max)) lal_cuda.log.comment(' Average/maximum real(hc) fractional difference: %.2e/%.2e' % (hcval_real_diff_avg, hcval_real_diff_max)) lal_cuda.log.comment(' Average/maximum imag(hc) fractional difference: %.2e/%.2e' % (hcval_imag_diff_avg, hcval_imag_diff_max)) lal_cuda.log.close("Done.") return { 'hpval_real_diff_avg': hpval_real_diff_avg, 'hpval_real_diff_max': hpval_real_diff_max, 'hpval_imag_diff_avg': hpval_imag_diff_avg, 'hpval_imag_diff_max': hpval_imag_diff_max, 'hcval_real_diff_avg': hcval_real_diff_avg, 'hcval_real_diff_max': hcval_real_diff_max, 'hcval_imag_diff_avg': hcval_imag_diff_avg, 'hcval_imag_diff_max': hcval_imag_diff_max}
[docs]class outputs(object): """This class manages the output (hp and hc complex arrays) from a LALSUite model call. An instance can be created using the default constructor or the :func:`read` method. An instance can be written using the :func:`write` method. Equivalence of two instances is defined by the element-wise equivalence of their hp and hc arrays. """ def __init__(self, return_from_SimIMRPhenomPFrequencySequence=None, hp=None, hc=None): """Create an instance of the outputs class. Optionally pass complex arrays hp and hc to initialize from. :param return_from_SimIMRPhenomPFrequencySequence: The data structure returned from the LALSUite C API. :param hp: Complex floating point array :param hc: Complex floating point array """ if((isinstance(hp, np.ndarray)) and (isinstance(hc, np.ndarray)) and (isinstance(return_from_SimIMRPhenomPFrequencySequence, type(None)))): self.hp = hp self.hc = hc elif((isinstance(hp, type(None))) and (isinstance(hc, type(None))) and not (isinstance(return_from_SimIMRPhenomPFrequencySequence, type(None)))): self.hp = return_from_SimIMRPhenomPFrequencySequence[0].data.data self.hc = return_from_SimIMRPhenomPFrequencySequence[1].data.data else: lal_cuda.log.error("Invalid inputs to SimIMRPhenomPFrequencySequence outputs constructor.") exit(1)
[docs] @classmethod def read(cls, filename_datafile_in): """Create an instance of the outputs class from a binary file. :param filename_datafile_in: Filename to read from. :return: An instance of the outputs class. """ with open(lal_cuda.full_path_datafile(filename_datafile_in), "rb") as outputs_file: n_freqs = np.asscalar(np.fromfile(outputs_file, dtype=np.int32, count=1)) hp = np.fromfile(outputs_file, dtype=np.complex128, count=n_freqs) hc = np.fromfile(outputs_file, dtype=np.complex128, count=n_freqs) return(cls(hp=hp, hc=hc))
[docs] def write(self, filename_outputs_out, filename_label=None, verbose=True): """Write the instance of the output class to a binary file. :param filename_outputs_out: Filename to write to. :param filename_label: Filename modifier. :param verbose: Boolean flag indicating whether to write activity to the log. :return: None """ # Set filename if(filename_label): filename_outputs_out = "outputs.dat." + filename_label else: filename_outputs_out = "outputs.dat" if(verbose): lal_cuda.log.open("Writing outputs to '%s'..." % (filename_outputs_out), end='') with open(filename_outputs_out, "wb") as outputs_file: np.array([len(self.hp)], dtype=np.int32).tofile(outputs_file) self.hp.tofile(outputs_file) self.hc.tofile(outputs_file) if(verbose): lal_cuda.log.close("Done.")
def __eq__(self, other): """Test for equivalence of two sets of outputs.""" return np.array_equal(self.hp, other.hp) and np.array_equal(self.hc, other.hc) def __ne__(self, other): """Overrides the default implementation (unnecessary in Python 3)""" return not self.__eq__(other)
[docs]class inputs(object): def __init__( self, chi1=chi1_default, chi2=chi2_default, m1=m1_default, m2=m2_default, chip=chip_default, thetaJ=thetaJ_default, alpha0=alpha0_default, distance=distance_default, phic=phic_default, fref=fref_default, mode=1, freqs=[ flow_default, fhigh_default, -1], freqs_from_range=True, convert_units=True): """Create an instance of the inputs class, for a given set of model parameters. :param chi1: See LALSuite documentation for a description of this model parameter. :param chi2: See LALSuite documentation for a description of this model parameter. :param m1: See LALSuite documentation for a description of this model parameter. :param m2: See LALSuite documentation for a description of this model parameter. :param chip: See LALSuite documentation for a description of this model parameter. :param thetaJ: See LALSuite documentation for a description of this model parameter. :param alpha0: See LALSuite documentation for a description of this model parameter. :param distance: See LALSuite documentation for a description of this model parameter. :param phic: See LALSuite documentation for a description of this model parameter. :param fref: See LALSuite documentation for a description of this model parameter. :param mode: See LALSuite documentation for a description of this model parameter. :param freqs: Frequency array (either an element-wise array, or a 3-element description of range) :param freqs_from_range: Set to True if the freqs describes a range, rather than an element-wise array :param convert_units: """ self.chi1 = chi1 self.chi2 = chi2 self.m1 = m1 self.m2 = m2 self.distance = distance self.thetaJ = thetaJ self.alpha0 = alpha0 self.chip = chip self.phic = phic self.fref = fref self.mode = mode # Perform unit conversions, if requested if(convert_units): self.m1 = self.m1 * lal.lal.MSUN_SI self.m2 = self.m2 * lal.lal.MSUN_SI self.distance = self.distance * lal.lal.PC_SI * 100 * 1e6 # Generate frequency array if(freqs_from_range): flow = freqs[0] fhigh = freqs[1] n_freqs = freqs[2] # If n_freqs<1, then assum dfreq=1. if(n_freqs < 1): self.freqs = np.linspace(flow, fhigh, (fhigh - flow) + 1) self.n_freqs = len(self.freqs) else: self.n_freqs = n_freqs self.freqs = np.linspace(flow, fhigh, self.n_freqs) # If freqs_from_range is false, then assume that freqs specifies a list of frequencies else: self.freqs = freqs self.n_freqs = len(self.freqs)
[docs] def np_floats(self): """A numpy array of all floating-point inputs. :return: A numpy array of floats. """ # A numpy-array packaging of the floating-point input parameters return np.array([self.chi1, self.chi2, self.chip, self.thetaJ, self.m1, self.m2, self.distance, self.alpha0, self.phic, self.fref], dtype=np.float64)
[docs] def np_ints(self): """A numpy array of all integer inputs. :return: A numpy array of integers. """ # A numpy-array packaging of the integer input parameters return np.array([self.mode, self.n_freqs], dtype=np.int32)
[docs] @classmethod def read(cls, filename_datafile_in): """Create an instance of a inputs method from a binary file. :param filename_datafile_in: Filename storing inputs. :return: A object of class inputs """ with open(lal_cuda.full_path_datafile(filename_datafile_in), "rb") as inputs_file: # Read floating-point parameters inputs_np_floats = np.fromfile(inputs_file, dtype=np.float64, count=len(cls().np_floats())) chi1 = inputs_np_floats[0] chi2 = inputs_np_floats[1] chip = inputs_np_floats[2] thetaJ = inputs_np_floats[3] m1 = inputs_np_floats[4] m2 = inputs_np_floats[5] distance = inputs_np_floats[6] alpha0 = inputs_np_floats[7] phic = inputs_np_floats[8] fref = inputs_np_floats[9] # Read integer-type parameters inputs_np_ints = np.fromfile(inputs_file, dtype=np.int32, count=len(cls().np_ints())) mode = int(inputs_np_ints[0]) n_freqs = int(inputs_np_ints[1]) # Read frequency array freqs = np.fromfile(inputs_file, dtype=np.float64, count=n_freqs) return(cls(chi1=chi1, chi2=chi2, m1=m1, m2=m2, chip=chip, thetaJ=thetaJ, alpha0=alpha0, distance=distance, phic=phic, fref=fref, mode=mode, freqs=freqs, freqs_from_range=False, convert_units=False))
[docs] def write(self, filename_inputs_out, filename_label=None, verbose=True): """Write an instance of an object of class inputs to a binary file. :param filename_inputs_out: Filename to write to :param filename_label: Filename modifier. :param verbose: Boolean flag indicating whether to write activity to the log. :return: """ # Set filename if(filename_label): filename_inputs_out = "inputs.dat." + filename_label else: filename_inputs_out = "inputs.dat" if(verbose): lal_cuda.log.open("Writing inputs to '%s'..." % (filename_inputs_out), end='') with open(filename_inputs_out, "wb") as inputs_file: self.np_floats().tofile(inputs_file) self.np_ints().tofile(inputs_file) self.freqs.tofile(inputs_file) if(verbose): lal_cuda.log.close("Done.")
[docs] def run(self, buf=None, legacy=False): """Call the C-compiled model in lalsuite. If legacy is true, then assume that the compiled version of lalsuite we are using does not have PhenomP buffer support. :param buf: A buffer, as generated by the ADACS version of LALSuite :param legacy: True if using a version of LALSuite not compiled with the ADACS GPU buffer support :return: An instance of the outputs class """ if(legacy): return(outputs(return_from_SimIMRPhenomPFrequencySequence=lalsimulation.SimIMRPhenomPFrequencySequence( self.freqs, self.chi1, self.chi2, self.chip, self.thetaJ, self.m1, self.m2, self.distance, self.alpha0, self.phic, self.fref, self.mode, None))) # ... else, assume that we are working with a version of PhenomP that does have buffer support else: return(outputs(return_from_SimIMRPhenomPFrequencySequence=lalsimulation.SimIMRPhenomPFrequencySequence( self.freqs, self.chi1, self.chi2, self.chip, self.thetaJ, self.m1, self.m2, self.distance, self.alpha0, self.phic, self.fref, self.mode, None, buf)))
def __str__(self): """Return a string representation of the parameter set.""" return "chi1=%e chi2=%e m1=%e m2=%e distance=%e thetaJ=%e alpha0=%e chip=%e phic=%e fref=%e mode=%d freqs=[%e...%e]" % ( self.chi1, self.chi2, self.m1 / lal.lal.MSUN_SI, self.m2 / lal.lal.MSUN_SI, self.distance / (lal.lal.PC_SI * 100 * 1e6), self.thetaJ, self.alpha0, self.chip, self.phic, self.fref, self.mode, self.freqs[0], self.freqs[-1]) def __eq__(self, other): """Test for equivalence of two sets of inputs.""" return np.array_equal( self.np_floats(), other.np_floats()) and np.array_equal( self.np_ints(), other.np_ints()) and np.array_equal( self.freqs, other.freqs) def __ne__(self, other): """Overrides the default implementation (unnecessary in Python 3)""" return not self.__eq__(other)