def add_point_by_masses(self, mass1, mass2, spin1z, spin2z, vary_fupper=False): """ Add a point to the template bank. This differs from add point to bank as it assumes that the chi coordinates and the products needed to use vary_fupper have not already been calculated. This function calculates these products and then calls add_point_by_chi_coords. This function also carries out a number of sanity checks (eg. is the point within the ranges given by mass_range_params) that add_point_by_chi_coords does not do for speed concerns. Parameters ----------- mass1 : float Mass of the heavier body mass2 : float Mass of the lighter body spin1z : float Spin of the heavier body spin2z : float Spin of the lighter body """ # Test that masses are the expected way around (ie. mass1 > mass2) if mass2 > mass1: if not self.spin_warning_given: warn_msg = "Am adding a template where mass2 > mass1. The " warn_msg += "convention is that mass1 > mass2. Swapping mass1 " warn_msg += "and mass2 and adding point to bank. This message " warn_msg += "will not be repeated." logging.warn(warn_msg) self.spin_warning_given = True # These that masses obey the restrictions of mass_range_params if self.mass_range_params.is_outside_range(mass1, mass2, spin1z, spin2z): err_msg = "Point with masses given by " err_msg += "%f %f %f %f " %(mass1, mass2, spin1z, spin2z) err_msg += "(mass1, mass2, spin1z, spin2z) is not consistent " err_msg += "with the provided command-line restrictions on masses " err_msg += "and spins." raise ValueError(err_msg) # Get chi coordinates chi_coords = coord_utils.get_cov_params(mass1, mass2, spin1z, spin2z, self.metric_params, self.ref_freq) # Get mus and best fupper for this point, if needed if vary_fupper: mass_dict = {} mass_dict['m1'] = numpy.array([mass1]) mass_dict['m2'] = numpy.array([mass2]) mass_dict['s1z'] = numpy.array([spin1z]) mass_dict['s2z'] = numpy.array([spin2z]) freqs = numpy.array([self.frequency_map.keys()], dtype=float) freq_cutoff = coord_utils.return_nearest_cutoff(\ self.upper_freq_formula, mass_dict, freqs) freq_cutoff = freq_cutoff[0] lambdas = coord_utils.get_chirp_params\ (mass1, mass2, spin1z, spin2z, self.metric_params.f0, self.metric_params.pnOrder) mus = [] for freq in self.frequency_map: mus.append(coord_utils.get_mu_params(lambdas, self.metric_params, freq) ) mus = numpy.array(mus) else: freq_cutoff=None mus=None self.add_point_by_chi_coords(chi_coords, mass1, mass2, spin1z, spin2z, point_fupper=freq_cutoff, mus=mus)
def get_mass_distribution(bestMasses, scaleFactor, massRangeParams, metricParams, fUpper, numJumpPoints=100, chirpMassJumpFac=0.0001, etaJumpFac=0.01, spin1zJumpFac=0.01, spin2zJumpFac=0.01): """ Given a set of masses, this function will create a set of points nearby in the mass space and map these to the xi space. Parameters ----------- bestMasses : list Contains [ChirpMass, eta, spin1z, spin2z]. Points will be placed around tjos scaleFactor : float This parameter describes the radius away from bestMasses that points will be placed in. massRangeParams : massRangeParameters instance Instance holding all the details of mass ranges and spin ranges. metricParams : metricParameters instance Structure holding all the options for construction of the metric and the eigenvalues, eigenvectors and covariance matrix needed to manipulate the space. fUpper : float The value of fUpper that was used when obtaining the xi_i coordinates. This lets us know how to rotate potential physical points into the correct xi_i space. This must be a key in metricParams.evals, metricParams.evecs and metricParams.evecsCV (ie. we must know how to do the transformation for the given value of fUpper) numJumpPoints : int, optional (default = 100) The number of points that will be generated every iteration chirpMassJumpFac : float, optional (default=0.0001) The jump points will be chosen with fractional variation in chirpMass up to this multiplied by scaleFactor. etaJumpFac : float, optional (default=0.01) The jump points will be chosen with fractional variation in eta up to this multiplied by scaleFactor. spin1zJumpFac : float, optional (default=0.01) The jump points will be chosen with absolute variation in spin1z up to this multiplied by scaleFactor. spin2zJumpFac : float, optional (default=0.01) The jump points will be chosen with absolute variation in spin2z up to this multiplied by scaleFactor. Returns -------- Totmass : numpy.array Total mass of the resulting points Eta : numpy.array Symmetric mass ratio of the resulting points Spin1z : numpy.array Spin of the heavier body of the resulting points Spin2z : numpy.array Spin of the smaller body of the resulting points Diff : numpy.array Mass1 - Mass2 of the resulting points Mass1 : numpy.array Mass1 (mass of heavier body) of the resulting points Mass2 : numpy.array Mass2 (mass of smaller body) of the resulting points new_xis : list of numpy.array Position of points in the xi coordinates """ # FIXME: It would be better if rejected values could be drawn from the # full possible mass/spin distribution. However speed in this function is # a major factor and must be considered. bestChirpmass = bestMasses[0] bestEta = bestMasses[1] bestSpin1z = bestMasses[2] bestSpin2z = bestMasses[3] # Firstly choose a set of values for masses and spins chirpmass = bestChirpmass * (1 - (numpy.random.random(numJumpPoints)-0.5) \ * chirpMassJumpFac * scaleFactor ) etaRange = massRangeParams.maxEta - massRangeParams.minEta currJumpFac = etaJumpFac * scaleFactor if currJumpFac > etaRange: currJumpFac = etaRange eta = bestEta * ( 1 - (numpy.random.random(numJumpPoints) - 0.5) \ * currJumpFac) maxSpinMag = max(massRangeParams.maxNSSpinMag, massRangeParams.maxBHSpinMag) minSpinMag = min(massRangeParams.maxNSSpinMag, massRangeParams.maxBHSpinMag) # Note that these two are cranged by spinxzFac, *not* spinxzFac/spinxz currJumpFac = spin1zJumpFac * scaleFactor if currJumpFac > maxSpinMag: currJumpFac = maxSpinMag # Actually set the new spin trial points if massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): curr_spin_1z_jump_fac = currJumpFac curr_spin_2z_jump_fac = currJumpFac # Check spins aren't going to be unphysical if currJumpFac > massRangeParams.maxBHSpinMag: curr_spin_1z_jump_fac = massRangeParams.maxBHSpinMag if currJumpFac > massRangeParams.maxNSSpinMag: curr_spin_2z_jump_fac = massRangeParams.maxNSSpinMag spin1z = bestSpin1z + ( (numpy.random.random(numJumpPoints) - 0.5) \ * curr_spin_1z_jump_fac) spin2z = bestSpin2z + ( (numpy.random.random(numJumpPoints) - 0.5) \ * curr_spin_2z_jump_fac) else: # If maxNSSpinMag is very low (0) and maxBHSpinMag is high we can # find it hard to place any points. So mix these when # masses are swapping between the NS and BH. curr_spin_bh_jump_fac = currJumpFac curr_spin_ns_jump_fac = currJumpFac # Check spins aren't going to be unphysical if currJumpFac > massRangeParams.maxBHSpinMag: curr_spin_bh_jump_fac = massRangeParams.maxBHSpinMag if currJumpFac > massRangeParams.maxNSSpinMag: curr_spin_ns_jump_fac = massRangeParams.maxNSSpinMag spin1z = numpy.zeros(numJumpPoints, dtype=float) spin2z = numpy.zeros(numJumpPoints, dtype=float) split_point = int(numJumpPoints / 2) # So set the first half to be at least within the BH range and the # second half to be at least within the NS range spin1z[:split_point] = bestSpin1z + \ ( (numpy.random.random(split_point) - 0.5)\ * curr_spin_bh_jump_fac) spin1z[split_point:] = bestSpin1z + \ ( (numpy.random.random(numJumpPoints-split_point) - 0.5)\ * curr_spin_ns_jump_fac) spin2z[:split_point] = bestSpin2z + \ ( (numpy.random.random(split_point) - 0.5)\ * curr_spin_bh_jump_fac) spin2z[split_point:] = bestSpin2z + \ ( (numpy.random.random(numJumpPoints-split_point) - 0.5)\ * curr_spin_ns_jump_fac) # Point[0] is always set to the original point chirpmass[0] = bestChirpmass eta[0] = bestEta spin1z[0] = bestSpin1z spin2z[0] = bestSpin2z # Remove points where eta becomes unphysical eta[eta > massRangeParams.maxEta] = massRangeParams.maxEta if massRangeParams.minEta: eta[eta < massRangeParams.minEta] = massRangeParams.minEta else: eta[eta < 0.0001] = 0.0001 # Total mass, masses and mass diff totmass = chirpmass / (eta**(3. / 5.)) diff = (totmass * totmass * (1 - 4 * eta))**0.5 mass1 = (totmass + diff) / 2. mass2 = (totmass - diff) / 2. # Check the validity of the spin values # Do the first spin if maxSpinMag == 0: # Shortcut if non-spinning pass elif massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): # Simple case where I don't have to worry about correlation with mass numploga = abs(spin1z) > massRangeParams.maxBHSpinMag spin1z[numploga] = 0 else: # Do have to consider masses boundary_mass = massRangeParams.ns_bh_boundary_mass numploga1 = numpy.logical_and( mass1 >= boundary_mass, abs(spin1z) <= massRangeParams.maxBHSpinMag) numploga2 = numpy.logical_and( mass1 < boundary_mass, abs(spin1z) <= massRangeParams.maxNSSpinMag) numploga = numpy.logical_or(numploga1, numploga2) numploga = numpy.logical_not(numploga) spin1z[numploga] = 0 # Same for the second spin if maxSpinMag == 0: # Shortcut if non-spinning pass elif massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): numplogb = abs(spin2z) > massRangeParams.maxNSSpinMag spin2z[numplogb] = 0 else: # Do have to consider masses boundary_mass = massRangeParams.ns_bh_boundary_mass numplogb1 = numpy.logical_and( mass2 >= boundary_mass, abs(spin2z) <= massRangeParams.maxBHSpinMag) numplogb2 = numpy.logical_and( mass2 < boundary_mass, abs(spin2z) <= massRangeParams.maxNSSpinMag) numplogb = numpy.logical_or(numplogb1, numplogb2) numplogb = numpy.logical_not(numplogb) spin2z[numplogb] = 0 if (maxSpinMag) and (numploga[0] or numplogb[0]): raise ValueError("Cannot remove the guide point!") # And remove points where the individual masses are outside of the physical # range. Or the total masses are. # These "removed" points will have metric distances that will be much, much # larger than any thresholds used in the functions in brute_force_utils.py # and will always be rejected. An unphysical value cannot be used as it # would result in unphysical metric distances and cause failures. totmass[mass1 < massRangeParams.minMass1] = 0.0001 totmass[mass1 > massRangeParams.maxMass1] = 0.0001 totmass[mass2 < massRangeParams.minMass2] = 0.0001 totmass[mass2 > massRangeParams.maxMass2] = 0.0001 # There is some numerical error which can push this a bit higher. We do # *not* want to reject the initial guide point. This error comes from # Masses -> totmass, eta -> masses conversion, we will have points pushing # onto the boudaries of the space. totmass[totmass > massRangeParams.maxTotMass * 1.0001] = 0.0001 totmass[totmass < massRangeParams.minTotMass * 0.9999] = 0.0001 if massRangeParams.max_chirp_mass: totmass[chirpmass > massRangeParams.max_chirp_mass * 1.0001] = 0.0001 if massRangeParams.min_chirp_mass: totmass[chirpmass < massRangeParams.min_chirp_mass * 0.9999] = 0.0001 if totmass[0] < 0.00011: raise ValueError("Cannot remove the guide point!") mass1[totmass < 0.00011] = 0.0001 mass2[totmass < 0.00011] = 0.0001 # Then map to xis new_xis = get_cov_params(mass1, mass2, spin1z, spin2z, metricParams, fUpper) return totmass, eta, spin1z, spin2z, mass1, mass2, new_xis
def get_mass_distribution(bestMasses, scaleFactor, massRangeParams, metricParams, fUpper, numJumpPoints=100, chirpMassJumpFac=0.0001, etaJumpFac=0.01, spin1zJumpFac=0.01, spin2zJumpFac=0.01): """ Given a set of masses, this function will create a set of points nearby in the mass space and map these to the xi space. Parameters ----------- bestMasses : list Contains [ChirpMass, eta, spin1z, spin2z]. Points will be placed around tjos scaleFactor : float This parameter describes the radius away from bestMasses that points will be placed in. massRangeParams : massRangeParameters instance Instance holding all the details of mass ranges and spin ranges. metricParams : metricParameters instance Structure holding all the options for construction of the metric and the eigenvalues, eigenvectors and covariance matrix needed to manipulate the space. fUpper : float The value of fUpper that was used when obtaining the xi_i coordinates. This lets us know how to rotate potential physical points into the correct xi_i space. This must be a key in metricParams.evals, metricParams.evecs and metricParams.evecsCV (ie. we must know how to do the transformation for the given value of fUpper) numJumpPoints : int, optional (default = 100) The number of points that will be generated every iteration chirpMassJumpFac : float, optional (default=0.0001) The jump points will be chosen with fractional variation in chirpMass up to this multiplied by scaleFactor. etaJumpFac : float, optional (default=0.01) The jump points will be chosen with fractional variation in eta up to this multiplied by scaleFactor. spin1zJumpFac : float, optional (default=0.01) The jump points will be chosen with absolute variation in spin1z up to this multiplied by scaleFactor. spin2zJumpFac : float, optional (default=0.01) The jump points will be chosen with absolute variation in spin2z up to this multiplied by scaleFactor. Returns -------- Chirpmass : numpy.array chirp mass of the resulting points Totmass : numpy.array Total mass of the resulting points Eta : numpy.array Symmetric mass ratio of the resulting points Spin1z : numpy.array Spin of the heavier body of the resulting points Spin2z : numpy.array Spin of the smaller body of the resulting points Diff : numpy.array Mass1 - Mass2 of the resulting points Mass1 : numpy.array Mass1 (mass of heavier body) of the resulting points Mass2 : numpy.array Mass2 (mass of smaller body) of the resulting points Beta : numpy.array 1.5PN spin phasing coefficient of the resulting points Sigma : numpy.array 2PN spin phasing coefficient of the resulting points Gamma : numpy.array 2.5PN spin phasing coefficient of the resulting points Chis : numpy.array 0.5 * (spin1z + spin2z) for the resulting points new_xis : list of numpy.array Position of points in the xi coordinates """ # FIXME: It would be better if rejected values could be drawn from the # full possible mass/spin distribution. However speed in this function is # a major factor and must be considered. bestChirpmass = bestMasses[0] bestEta = bestMasses[1] bestSpin1z = bestMasses[2] bestSpin2z = bestMasses[3] # Firstly choose a set of values for masses and spins chirpmass = bestChirpmass * (1 - (numpy.random.random(numJumpPoints)-0.5) \ * chirpMassJumpFac * scaleFactor ) etaRange = massRangeParams.maxEta - massRangeParams.minEta currJumpFac = etaJumpFac * scaleFactor if currJumpFac > etaRange: currJumpFac = etaRange eta = bestEta * ( 1 - (numpy.random.random(numJumpPoints) - 0.5) \ * currJumpFac) maxSpinMag = max(massRangeParams.maxNSSpinMag, massRangeParams.maxBHSpinMag) minSpinMag = min(massRangeParams.maxNSSpinMag, massRangeParams.maxBHSpinMag) # Note that these two are cranged by spinxzFac, *not* spinxzFac/spinxz currJumpFac = spin1zJumpFac * scaleFactor if currJumpFac > maxSpinMag: currJumpFac = maxSpinMag # Actually set the new spin trial points if massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): curr_spin_1z_jump_fac = currJumpFac curr_spin_2z_jump_fac = currJumpFac # Check spins aren't going to be unphysical if currJumpFac > massRangeParams.maxBHSpinMag: curr_spin_1z_jump_fac = massRangeParams.maxBHSpinMag if currJumpFac > massRangeParams.maxNSSpinMag: curr_spin_2z_jump_fac = massRangeParams.maxNSSpinMag spin1z = bestSpin1z + ( (numpy.random.random(numJumpPoints) - 0.5) \ * curr_spin_1z_jump_fac) spin2z = bestSpin2z + ( (numpy.random.random(numJumpPoints) - 0.5) \ * curr_spin_2z_jump_fac) else: # If maxNSSpinMag is very low (0) and maxBHSpinMag is high we can # find it hard to place any points. So mix these when # masses are swapping between the NS and BH. curr_spin_bh_jump_fac = currJumpFac curr_spin_ns_jump_fac = currJumpFac # Check spins aren't going to be unphysical if currJumpFac > massRangeParams.maxBHSpinMag: curr_spin_bh_jump_fac = massRangeParams.maxBHSpinMag if currJumpFac > massRangeParams.maxNSSpinMag: curr_spin_ns_jump_fac = massRangeParams.maxNSSpinMag spin1z = numpy.zeros(numJumpPoints, dtype=float) spin2z = numpy.zeros(numJumpPoints, dtype=float) split_point = int(numJumpPoints/2) # So set the first half to be at least within the BH range and the # second half to be at least within the NS range spin1z[:split_point] = bestSpin1z + \ ( (numpy.random.random(split_point) - 0.5)\ * curr_spin_bh_jump_fac) spin1z[split_point:] = bestSpin1z + \ ( (numpy.random.random(numJumpPoints-split_point) - 0.5)\ * curr_spin_ns_jump_fac) spin2z[:split_point] = bestSpin2z + \ ( (numpy.random.random(split_point) - 0.5)\ * curr_spin_bh_jump_fac) spin2z[split_point:] = bestSpin2z + \ ( (numpy.random.random(numJumpPoints-split_point) - 0.5)\ * curr_spin_ns_jump_fac) # Point[0] is always set to the original point chirpmass[0] = bestChirpmass eta[0] = bestEta spin1z[0] = bestSpin1z spin2z[0] = bestSpin2z # Remove points where eta becomes unphysical eta[eta > massRangeParams.maxEta] = massRangeParams.maxEta if massRangeParams.minEta: eta[eta < massRangeParams.minEta] = massRangeParams.minEta else: eta[eta < 0.0001] = 0.0001 # Total mass, masses and mass diff totmass = chirpmass / (eta**(3./5.)) diff = (totmass*totmass * (1-4*eta))**0.5 mass1 = (totmass + diff)/2. mass2 = (totmass - diff)/2. # Check the validity of the spin values # Do the first spin if maxSpinMag == 0: # Shortcut if non-spinning pass elif massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): # Simple case where I don't have to worry about correlation with mass numploga = abs(spin1z) > massRangeParams.maxBHSpinMag spin1z[numploga] = 0 else: # Do have to consider masses boundary_mass = massRangeParams.ns_bh_boundary_mass numploga1 = numpy.logical_and(mass1 >= boundary_mass, abs(spin1z) <= massRangeParams.maxBHSpinMag) numploga2 = numpy.logical_and(mass1 < boundary_mass, abs(spin1z) <= massRangeParams.maxNSSpinMag) numploga = numpy.logical_or(numploga1, numploga2) numploga = numpy.logical_not(numploga) spin1z[numploga] = 0 # Same for the second spin if maxSpinMag == 0: # Shortcut if non-spinning pass elif massRangeParams.nsbhFlag or (maxSpinMag == minSpinMag): numplogb = abs(spin2z) > massRangeParams.maxNSSpinMag spin2z[numplogb] = 0 else: # Do have to consider masses boundary_mass = massRangeParams.ns_bh_boundary_mass numplogb1 = numpy.logical_and(mass2 >= boundary_mass, abs(spin2z) <= massRangeParams.maxBHSpinMag) numplogb2 = numpy.logical_and(mass2 < boundary_mass, abs(spin2z) <= massRangeParams.maxNSSpinMag) numplogb = numpy.logical_or(numplogb1, numplogb2) numplogb = numpy.logical_not(numplogb) spin2z[numplogb] = 0 # Get the various spin-derived quantities beta, sigma, gamma, chis = get_beta_sigma_from_aligned_spins(eta, spin1z, spin2z) if (maxSpinMag) and (numploga[0] or numplogb[0]): raise ValueError("Cannot remove the guide point!") # And remove points where the individual masses are outside of the physical # range. Or the total masses are. # These "removed" points will have metric distances that will be much, much # larger than any thresholds used in the functions in brute_force_utils.py # and will always be rejected. An unphysical value cannot be used as it # would result in unphysical metric distances and cause failures. totmass[mass1 < massRangeParams.minMass1] = 0.0001 totmass[mass1 > massRangeParams.maxMass1] = 0.0001 totmass[mass2 < massRangeParams.minMass2] = 0.0001 totmass[mass2 > massRangeParams.maxMass2] = 0.0001 # There is some numerical error which can push this a bit higher. We do # *not* want to reject the initial guide point. This error comes from # Masses -> totmass, eta -> masses conversion, we will have points pushing # onto the boudaries of the space. totmass[totmass > massRangeParams.maxTotMass*1.0001] = 0.0001 totmass[totmass < massRangeParams.minTotMass*0.9999] = 0.0001 if massRangeParams.max_chirp_mass: totmass[chirpmass > massRangeParams.max_chirp_mass*1.0001] = 0.0001 if massRangeParams.min_chirp_mass: totmass[chirpmass < massRangeParams.min_chirp_mass*0.9999] = 0.0001 if totmass[0] < 0.00011: raise ValueError("Cannot remove the guide point!") # Then map to xis new_xis = get_cov_params(totmass, eta, beta, sigma, gamma, chis, metricParams, fUpper) return chirpmass, totmass, eta, spin1z, spin2z, diff, mass1, mass2, beta, \ sigma, gamma, chis, new_xis