def sample_ecc(self, ecc_model='sana12', size=None): """Sample the eccentricity according to a user specified model Parameters ---------- ecc_model : string 'thermal' samples from a thermal eccentricity distribution following `Heggie (1975) <http://adsabs.harvard.edu/abs/1975MNRAS.173..729H>`_ 'uniform' samples from a uniform eccentricity distribution 'sana12' samples from the eccentricity distribution from `Sana+2012 <https://ui.adsabs.harvard.edu/abs/2012Sci...337..444S/abstract>_` 'circular' assumes zero eccentricity for all systems DEFAULT = 'sana12' size : int, optional number of eccentricities to sample NOTE: this is set in cosmic-pop call as Nstep Returns ------- ecc : array array of sampled eccentricities with size=size """ if ecc_model=='thermal': a_0 = np.random.uniform(0.0, 1.0, size) ecc = a_0**0.5 return ecc elif ecc_model=='uniform': ecc = np.random.uniform(0.0, 1.0, size) return ecc elif ecc_model=='sana12': from cosmic.utils import rndm ecc = rndm(a=0.001, b=0.9, g=-0.45, size=size) return ecc elif ecc_model=='circular': ecc = np.zeros(size) return ecc else: raise Error('You have specified an unsupported model. Please choose from thermal, uniform, sana12, or circular')
def sample_primary(self, primary_model='kroupa93', size=None): """Sample the primary mass (always the most massive star) from a user-selected model kroupa93 follows Kroupa (1993), normalization comes from `Hurley 2002 <https://arxiv.org/abs/astro-ph/0201220>`_ between 0.08 and 150 Msun salpter55 follows `Salpeter (1955) <http://adsabs.harvard.edu/abs/1955ApJ...121..161S>`_ between 0.08 and 150 Msun kroupa01 follows Kroupa (2001) <https://arxiv.org/abs/astro-ph/0009005> between 0.08 and 100 Msun Parameters ---------- primary_model : str, optional model for mass distribution; choose from: kroupa93 follows Kroupa (1993), normalization comes from `Hurley 2002 <https://arxiv.org/abs/astro-ph/0201220>`_ valid for masses between 0.1 and 100 Msun salpter55 follows `Salpeter (1955) <http://adsabs.harvard.edu/abs/1955ApJ...121..161S>`_ valid for masses between 0.1 and 100 Msun kroupa01 follows Kroupa (2001), normalization comes from `Hurley 2002 <https://arxiv.org/abs/astro-ph/0009005>`_ valid for masses between 0.1 and 100 Msun Default kroupa93 size : int, optional number of initial primary masses to sample NOTE: this is set in cosmic-pop call as Nstep Returns ------- a_0 : array Sampled primary masses sampled_mass : float Total amount of mass sampled """ if primary_model == 'kroupa93': total_sampled_mass = 0 multiplier = 1 a_0 = np.random.uniform(0.0, 1, size) low_cutoff = 0.740074 high_cutoff = 0.908422 lowIdx, = np.where(a_0 <= low_cutoff) midIdx, = np.where((a_0 > low_cutoff) & (a_0 < high_cutoff)) highIdx, = np.where(a_0 >= high_cutoff) a_0[lowIdx] = rndm(a=0.08, b=0.5, g=-0.3, size=len(lowIdx)) a_0[midIdx] = rndm(a=0.50, b=1.0, g=-1.2, size=len(midIdx)) a_0[highIdx] = rndm(a=1.0, b=150.0, g=-1.7, size=len(highIdx)) total_sampled_mass += np.sum(a_0) return a_0, total_sampled_mass elif primary_model == 'kroupa01': total_sampled_mass = 0 multiplier = 1 a_0 = np.random.uniform(0.0, 1, size) low_cutoff = 0.37148816884988606 high_cutoff = 0.8496015751162523 lowIdx, = np.where(a_0 <= low_cutoff) midIdx, = np.where((a_0 > low_cutoff) & (a_0 < high_cutoff)) highIdx, = np.where(a_0 >= high_cutoff) a_0[lowIdx] = rndm(a=0.01, b=0.08, g=0.7, size=len(lowIdx)) a_0[midIdx] = rndm(a=0.08, b=0.5, g=-0.3, size=len(midIdx)) a_0[highIdx] = rndm(a=0.5, b=100.0, g=-1.3, size=len(highIdx)) total_sampled_mass += np.sum(a_0) return a_0, total_sampled_mass elif primary_model == 'salpeter55': total_sampled_mass = 0 multiplier = 1 a_0 = rndm(a=0.08, b=150, g=-1.35, size=size * multiplier) total_sampled_mass += np.sum(a_0) return a_0, total_sampled_mass
def sample_primary(self, primary_min, primary_max, primary_model='kroupa93', size=None): """Sample the primary mass (always the most massive star) from a user-selected model kroupa93 follows Kroupa (1993), normalization comes from `Hurley 2002 <https://arxiv.org/abs/astro-ph/0201220>`_ between 0.1 and 100 Msun salpter55 follows `Salpeter (1955) <http://adsabs.harvard.edu/abs/1955ApJ...121..161S>`_ between 0.1 and 100 Msun Parameters ---------- primary_min : float minimum initial primary mass [Msun] primary_max : float maximum initial primary mass [Msun] primary_model : str, optional model for mass distribution; choose from: kroupa93 follows Kroupa (1993), normalization comes from `Hurley 2002 <https://arxiv.org/abs/astro-ph/0201220>`_ valid for masses between 0.1 and 100 Msun salpter55 follows `Salpeter (1955) <http://adsabs.harvard.edu/abs/1955ApJ...121..161S>`_ valid for masses between 0.1 and 100 Msun Default kroupa93 size : int, optional number of initial primary masses to sample NOTE: this is set in runFixedPop call as Nstep Returns ------- a_0 : array Sampled primary masses total_sampled_mass : float Total amount of mass sampled """ if primary_model=='kroupa93': # If the final binary contains a compact object (BH or NS), # we want to evolve 'size' binaries that could form a compact # object so we over sample the initial population if primary_max >= 150.0: a_0 = np.random.uniform(0.0, 0.9999797, size*500) elif primary_max >= 30.0: a_0 = np.random.uniform(0.0, 0.9999797, size*50) else: a_0 = np.random.uniform(0.0, 0.9999797, size) low_cutoff = 0.740074 high_cutoff=0.908422 lowIdx, = np.where(a_0 <= low_cutoff) midIdx, = np.where((a_0 > low_cutoff) & (a_0 < high_cutoff)) highIdx, = np.where(a_0 >= high_cutoff) a_0[lowIdx] = ((0.1) ** (-3.0/10.0) - (a_0[lowIdx] / 0.968533)) ** (-10.0/3.0) a_0[midIdx] = ((0.5) ** (-6.0/5.0) - ((a_0[midIdx] - low_cutoff) / 0.129758)) ** (-5.0/6.0) a_0[highIdx] = (1 - ((a_0[highIdx] - high_cutoff) / 0.0915941)) ** (-10.0/17.0) total_sampled_mass = np.sum(a_0) a_0 = a_0[a_0 >= primary_min] a_0 = a_0[a_0 <= primary_max] return a_0, total_sampled_mass elif primary_model=='salpeter55': # If the final binary contains a compact object (BH or NS), # we want to evolve 'size' binaries that could form a compact # object so we over sample the initial population if primary_max == 150.0: a_0 = rndm(a=0.1, b=100, g=-1.35, size=size*500) elif primary_max == 50.0: a_0 = rndm(a=0.1, b=100, g=-1.35, size=size*50) else: a_0 = rndm(a=0.1, b=100, g=-1.35, size=size) total_sampled_mass = np.sum(a_0) a_0 = a_0[a_0 >= primary_min] a_0 = a_0[a_0 <= primary_max] return a_0, total_sampled_mass
def sample_porb(self, mass1, mass2, ecc, porb_model='sana12', size=None): """Sample the orbital period according to the user-specified model Parameters ---------- mass1 : array primary masses mass2 : array secondary masses ecc : array eccentricity model : string selects which model to sample orbital periods, choices include: log_uniform : semi-major axis flat in log space from RRLO < 0.5 up to 1e5 Rsun according to `Abt (1983) <http://adsabs.harvard.edu/abs/1983ARA%26A..21..343A>`_ and consistent with Dominik+2012,2013 and then converted to orbital period in days using Kepler III sana12 : power law orbital period between 0.15 < log(P/day) < 5.5 following `Sana+2012 <https://ui.adsabs.harvard.edu/abs/2012Sci...337..444S/abstract>_` renzo19 : power law orbital period for m1 > 15Msun binaries from `Sana+2012 <https://ui.adsabs.harvard.edu/abs/2012Sci...337..444S/abstract>_` following the implementation of `Renzo+2019 <https://ui.adsabs.harvard.edu/abs/2019A%26A...624A..66R/abstract>_` and flat in log otherwise Returns ------- porb : array orbital period with array size equalling array size of mass1 and mass2 in units of days """ if porb_model == 'log_uniform': q = mass2/mass1 RL_fac = (0.49*q**(2./3.)) / (0.6*q**(2./3.) + np.log(1+q**1./3.)) q2 = mass1/mass2 RL_fac2 = (0.49*q2**(2./3.)) / (0.6*q2**(2./3.) + np.log(1+q2**1./3.)) try: ind_lo, = np.where(mass1 < 1.66) ind_hi, = np.where(mass1 >= 1.66) rad1 = np.zeros(len(mass1)) rad1[ind_lo] = 1.06*mass1[ind_lo]**0.945 rad1[ind_hi] = 1.33*mass1[ind_hi]**0.555 except: if mass1 < 1.66: rad1 = 1.06*mass1**0.945 else: rad1 = 1.33*mass1**0.555 try: ind_lo, = np.where(mass2 < 1.66) ind_hi, = np.where(mass2 >= 1.66) rad2 = np.zeros(len(mass2)) rad2[ind_lo] = 1.06*mass2[ind_lo]**0.945 rad2[ind_hi] = 1.33*mass2[ind_hi]**0.555 except: if mass2 < 1.66: rad2 = 1.06*mass1**0.945 else: rad2 = 1.33*mass1**0.555 # include the factor for the eccentricity RL_max = 2*rad1/RL_fac ind_switch, = np.where(RL_max < 2*rad2/RL_fac2) if len(ind_switch) >= 1: RL_max[ind_switch] = 2*rad2/RL_fac2[ind_switch] a_min = RL_max*(1+ecc) a_0 = np.random.uniform(np.log(a_min), np.log(1e5), size) # convert out of log space a_0 = np.exp(a_0) # convert to au rsun_au = 0.00465047 a_0 = a_0*rsun_au # convert to orbital period in years yr_day = 365.24 porb_yr = ((a_0**3.0)/(mass1+mass2))**0.5 porb = porb_yr*yr_day elif porb_model == 'sana12': from cosmic.utils import rndm porb = 10**rndm(a=0.15, b=5.5, g=-0.55, size=size) elif porb_model == 'renzo19': from cosmic.utils import rndm porb = 10**(np.random.uniform(0.15, 5.5, size)) ind_massive, = np.where(mass1 > 15) porb[ind_massive] = 10**rndm(a=0.15, b=5.5, g=-0.55, size=len(ind_massive)) else: raise ValueError('You have supplied a non-supported model; Please choose either log_flat, sana12, or renzo19') return porb