Beispiel #1
0
    def dadt(self):
        """GW-Driven Hardening and Eccentricity evolution.

        Returns
        -------
        dadt : (N,) scalar
            Derivative of semi-major axis vs. time (negative).
        decdt : scalar or (N,) scalar
            Derivative of eccentricity vs. time (negative).
        taus : (N,) scalar
            Hardening timescale (a / da/dt) due to GW emission.

        """
        m1 = self._evolver.m1[:, np.newaxis]
        m2 = self._evolver.m2[:, np.newaxis]
        rads = self._evolver.rads[np.newaxis, :]
        # eccs = 0.0
        print(m1.shape, m2.shape, rads.shape)
        print(zmath.stats_str(m1 / MSOL))
        print(zmath.stats_str(m2 / MSOL))
        dadt = -_CONST_DIMENS * (64.0 / 5.0) * m1 * m2 * (m1 + m2) * np.power(
            rads, -3.0)
        '''
        decdt = np.zeros_like(dadt)
        # Only bother with the eccentricity terms if non-zero
        if np.any(eccs > 0.0):
            e2 = np.square(eccs)
            ecc_fact_a = (1 + (73.0/24)*e2 + (37.0/96)*e2*e2)*np.power((1.0-e2), -3.5)
            ecc_fact_e = (eccs + (121.0/304)*eccs*e2)*np.power((1.0-e2), -2.5)
            decdt = -_CONST_DIMENS * (304.0/15.0) * m1 * m2 * (m1+m2) * np.power(seps, -4.0)

            dadt *= ecc_fact_a
            decdt *= ecc_fact_e
        '''

        self.check_timescale("GW",
                             None,
                             1e-2 * PC,
                             extr=[1e4, 1e12],
                             dadt=dadt)

        # taus = -rads/dadt
        # return dadt, decdt, taus

        return dadt
Beispiel #2
0
    def init_mass_profile_arrays(self):
        # SF_EFF_FRAC = 0.2
        # RMAX_FACT = 5.0
        PROFILE_POWLAW_MAX = -0.1
        # PROFILE_POWLAW_MAX = -2.9
        PROFILE_POWLAW_MIN = -2.9

        verbose = self._verbose
        # names = self._names

        if verbose:
            print("Running `EvolveLZK.init_mass_profile_arrays()")

        rads = self.rads
        vols = 4 * np.pi * np.power(rads, 3) / 3
        dv = np.concatenate(([vols[0]], vols[1:] - vols[:-1]))
        # dv = 4*np.pi*np.power(rads, 2) * np.concatenate(([rads[0]], np.diff(rads)))

        for ii, pt in enumerate(self.PARTICLE_NAMES):
            aa = self._dens_prof_norms[ii]
            gg = self._dens_prof_gammas[ii]
            gg = np.clip(gg, PROFILE_POWLAW_MIN, PROFILE_POWLAW_MAX)
            dens = aa[:, np.newaxis] * np.power(rads[np.newaxis, :] / PC,
                                                -gg[:, np.newaxis])
            mass = np.cumsum(dens * dv[np.newaxis, :], axis=-1)
            # mass.append(_mass)
            dvar = "dens_" + pt
            mvar = "mass_" + pt
            if verbose:
                print("Setting `{}`, `{}`".format(dvar, mvar))
            setattr(self, dvar, dens)
            setattr(self, mvar, mass)

        warnings.warn("Using fixed velocity dispersion!")
        self.vdisp = self.gal_vdisp[:, np.newaxis] * np.ones_like(rads)[
            np.newaxis, :]
        print("vdisp[pc] = " +
              zmath.stats_str(self.vdisp[:, self._rad_ind], log=False))

        return
Beispiel #3
0
    def check_timescale(self, name, tau, rad=PC, extr=None, dadt=None):
        EXTR = [1e6, 1e16]
        if extr is None:
            extr = EXTR
        else:
            for ii in range(2):
                extr[ii] = EXTR[ii] if extr[ii] is None else extr[ii]

        if tau is None:
            tau = -self._evolver.rads / dadt

        # rad_ind = self._evolver._rad_ind
        rad_ind = zmath.argnearest(self._evolver.rads, rad)
        rad_rad = self._evolver.rads[rad_ind] / PC
        stats = zmath.stats_str(tau[:, rad_ind] / YR, log=False)
        print(name + " T[{:.0e} pc]/YR = ".format(rad_rad) + stats)
        tau_med = np.median(tau[:, rad_ind] / YR)
        if (tau_med < extr[0]) or (tau_med > extr[1]):
            err = name + " timescale looks wrong!  (vs. {:.1e}, {:.1e} [yr])".format(
                *extr)
            raise ValueError(err)

        return
Beispiel #4
0
def calc_dmdt_for_details(core=None):
    """Calculate mass-differences as estimate for accretion rates, add/overwrite in HDF5 files.
    """
    core = Core.load(core)
    log = core.log
    cosmo = core.cosmo
    NUM_SNAPS = core.sets.NUM_SNAPS
    CONV_ILL_TO_CGS = core.cosmo.CONV_ILL_TO_CGS

    # log.warning("WARNING: testing `calc_dmdt_for_details`!")
    # for snap in [135]:
    for snap in core.tqdm(range(NUM_SNAPS)):
        fname = core.paths.fname_details_snap(snap)
        log.debug("Snap {}: '{}'".format(snap, fname))
        with h5py.File(fname, 'a') as data:
            scales = data[DETAILS.SCALES]
            if scales.size == 0:
                continue

            # These are already sorted by ID and scale-factor, so contiguous and chronological
            # for each BH
            masses = data[DETAILS.MASSES][:] * CONV_ILL_TO_CGS.MASS
            mdots = data[DETAILS.MDOTS]

            u_inds = data[DETAILS.UNIQUE_INDICES]
            u_counts = data[DETAILS.UNIQUE_COUNTS]

            # Calculate mass-differences
            dmdts = np.zeros_like(masses)
            count = 0
            count_all = 0

            for ii, nn in zip(u_inds, u_counts):
                j0 = slice(ii, ii + nn - 1)
                j1 = slice(ii + 1, ii + nn)
                # t0 = cosmo.a_to_tage(scales[j0])
                # t1 = cosmo.a_to_tage(scales[j1])
                z0 = cosmo._a_to_z(scales[j0])
                z1 = cosmo._a_to_z(scales[j1])
                t0 = cosmo.age(z0).cgs.value
                t1 = cosmo.age(z1).cgs.value
                m0 = masses[j0]
                m1 = masses[j1]
                dm = m1 - m0
                dt = t1 - t0

                ss = np.ones_like(dm)
                neg = (dm < 0.0) | (dt < 0.0)
                ss[neg] *= -1

                inds = (dt != 0.0)
                dmdts[j1][inds] = ss[inds] * np.fabs(dm[inds] / dt[inds])

                count += np.count_nonzero(inds)
                count_all += inds.size

            dmdts = dmdts / CONV_ILL_TO_CGS.MDOT
            log.info("dM/dt nonzero : " +
                     zmath.frac_str(np.count_nonzero(dmdts), masses.size))
            log.info("mdots : " + zmath.stats_str(mdots, filter='>'))
            log.info("dmdts : " + zmath.stats_str(dmdts, filter='>'))

    return
Beispiel #5
0
def interp_bad_grid_vals(grid, grid_temps, grid_valid):
    grid_temps = np.copy(grid_temps)
    bads = grid_valid & np.isclose(grid_temps, 0.0)
    shape = [len(gg) for gg in grid]
    logging.warning("Fixing bad values: {}".format(zmath.frac_str(bads)))

    neighbors = []
    good_neighbors = []
    bads_inds = np.array(np.where(bads)).T
    for bad in tqdm.tqdm(bads_inds):
        nbs = []

        # print(bad)
        cnt = 0
        for dim in range(4):
            for side in [-1, +1]:
                test = [bb for bb in bad]
                test[dim] += side
                if test[dim] < 0 or test[dim] >= shape[dim]:
                    continue
                test = tuple(test)
                # print("\t", test)
                # print("\t", temps[test])
                nbs.append(test)
                if grid_temps[test] > 0.0:
                    cnt += 1
        neighbors.append(nbs)
        good_neighbors.append(cnt)

    num_nbs = [len(nbs) for nbs in neighbors]
    logging.warning("All  neighbors: {}".format(zmath.stats_str(num_nbs)))
    logging.warning("Good neighbors: {}".format(
        zmath.stats_str(good_neighbors)))
    goods = np.zeros(len(neighbors))

    MAX_TRIES = 10
    still_bad = list(np.argsort(good_neighbors)[::-1])
    tries = 0
    while len(still_bad) > 0 and tries < MAX_TRIES:
        keep_bad = []
        for kk, ii in enumerate(still_bad):
            values = np.zeros(num_nbs[ii])
            for jj, nbr in enumerate(neighbors[ii]):
                values[jj] = grid_temps[nbr]

            cnt = np.count_nonzero(values)
            if cnt == 0:
                keep_bad.append(kk)
                continue

            new = np.sum(np.log10(values[values > 0])) / cnt
            loc = tuple(bads_inds[ii])
            # print("\t", loc, new, cnt)
            grid_temps[loc] = 10**new
            goods[ii] = cnt

        still_bad = [still_bad[kk] for kk in keep_bad]
        num_still = len(still_bad)
        logging.warning("Try: {}, still_bad: {}".format(tries, num_still))
        if (tries + 1 >= MAX_TRIES) and (num_still > 0):
            logging.error("After {} tries, still {} bad!!".format(
                tries, num_still))

        tries += 1

    logging.warning("Filled neighbors: {}".format(zmath.stats_str(goods)))
    logging.warning("Full temps array: {}".format(
        zmath.stats_str(grid_temps[grid_valid])))
    return grid_temps
Beispiel #6
0
    def __init__(self, fname, e_0=0.0, verbose=False):
        # names = ['dm', 'gas', 'star']
        # self._names = names
        num_steps = self.NUM_STEPS

        input_data = np.genfromtxt(fname, names=True, dtype=None)
        # warnings.warn("\n!!!!DOWNSAMPLING!!!!\n")
        # NUM = 1000
        # inds = np.random.choice(input_data.shape[0], NUM, replace=False)
        # input_data = input_data[inds]
        self.z = input_data['redshift']

        self._input_data = input_data
        self._verbose = verbose
        self._cosmo = cosmopy.get_cosmology()

        if verbose:
            print("input_data.shape = {}".format(input_data.shape))
            print("names = {}".format(", ".join(input_data.dtype.names)))

        keys_masses = ['mass_new_prev_in', 'mass_new_prev_out']
        masses = np.array([input_data[kk] for kk in keys_masses]).T * MSOL
        self.masses = masses

        keys_snaps = ['snap_prev_in', 'snapshot_prev_out', 'snapshot_fin_out']
        self.snaps = np.array([input_data[kk] for kk in keys_snaps]).T

        # keys_gammas = ['dm_gamma', 'gas_gamma', 'star_gamma']
        # self.gammas = np.array([-input_data[kk + "_gamma"] for kk in names]).T

        # m2, m1 = masses.T
        # mt = m1 + m2
        # mr = m2/m1
        #
        self.m1 = masses.max(axis=-1)
        self.m2 = masses.min(axis=-1)
        mt = self.m1 + self.m2
        self.mtot = mt
        mr = self.m2 / self.m1
        self.mrat = mr

        self.redz_form = input_data['redshift']
        self.tage_form = self._cosmo.age(self.redz_form).cgs.value

        self.sep0 = input_data['separation'] * 1000 * PC
        self.gal_vdisp = input_data['vel_disp_fin_out'] * 1e5
        self.gal_mstar = input_data['stellar_mass_fin_out'] * MSOL
        self.gal_mtot = input_data['total_mass_fin_out'] * MSOL
        # self.dens_prof_star = [input_data['star_norm'] * MSOL, input_data['star_gamma']]
        # self.dens_prof_gas = [input_data['gas_norm'] * MSOL, input_data['gas_gamma']]
        # self.dens_prof_dm = [input_data['dm_norm'] * MSOL, input_data['dm_gamma']]
        # These are for radii in units of 1 pc, and gamma is the negative value
        norms = [
            input_data[nn + "_norm"] * _DENS_CONV for nn in self.PARTICLE_NAMES
        ]
        gammas = [input_data[nn + "_gamma"] for nn in self.PARTICLE_NAMES]
        for ii in range(len(norms)):
            bads = (norms[ii] < 0.0)
            num_bad = np.count_nonzero(bads)
            if num_bad > 0:
                num_all = bads.size
                frac = num_bad / num_all
                reset_perc = 10.0
                reset_val = np.percentile(norms[ii][~bads], reset_perc)
                print("BAD ", self.PARTICLE_NAMES[ii],
                      " {}/{} = {}".format(num_bad, num_all, frac))
                print("\tresetting to {:.1f}%ile value: {:.1e}".format(
                    reset_perc, reset_val))
                norms[ii][bads] = reset_val

        self._dens_prof_norms = np.array(norms)
        self._dens_prof_gammas = np.array(gammas)

        print("_dens_prof_norms.shape = ", self._dens_prof_norms.shape)
        # self.mdot = zastro.eddington_accretion(mt)
        # warnings.warn("CHECK UNITS OF MDOT")
        # Illustris units ===>  g/s
        self.mdot = input_data['mdot_sum'] * (1e10 * MSOL) / (0.978 * GYR)

        self.eccen = e_0

        self.num_binaries = self.m1.size
        self.num_steps = num_steps
        self._shape = (self.num_binaries, self.num_steps)
        # These are all in CGS
        self.rad_isco = 3 * radius_schwarzschild(self.m2)
        sep_extr = [self.rad_isco.min(), self.sep0.max()]
        # print("sep_extr = ", sep_extr, np.array(sep_extr)/PC)
        self.rads = np.logspace(*np.log10(sep_extr), self.NUM_STEPS)

        # Binary circular velocity
        self.vcirc = vel_circ(mt[:, np.newaxis], mr[:, np.newaxis],
                              self.rads[np.newaxis, :])
        rad_ind = zmath.argnearest(self.rads, PC)
        self._rad_ind = rad_ind

        if verbose:
            print("Mtot = " + zmath.stats_str(mt, log=True))
            print("Mrat = " + zmath.stats_str(mr, log=False))
            print("redz_form = " + zmath.stats_str(self.redz_form, log=False))
            print("sepa = " + zmath.stats_str(self.sep0, log=False))
            print("gal_mstar = " + zmath.stats_str(self.gal_mstar, log=True))
            print("gal_mtot = " + zmath.stats_str(self.gal_mtot, log=True))
            print("vcirc[pc] = " +
                  zmath.stats_str(self.vcirc[:, rad_ind], log=False))
            print("Mdot = " + zmath.stats_str(self.mdot, log=False))
            fedd = self.mdot / (mt * 7e-15)
            print("fedd = " + zmath.stats_str(fedd, log=False))
            # for gg, nn in zip(self.gammas.T, names):
            #     print("{} = ".format(nn) + zmath.stats_str(gg))
            for ii, pn in enumerate(self.PARTICLE_NAMES):
                aa = self._dens_prof_norms[ii]
                gg = self._dens_prof_gammas[ii]
                print("{}".format(pn))
                print("\tnorms  = " + zmath.stats_str(aa))
                print("\tgammas = " + zmath.stats_str(-gg))

        self.init_mass_profile_arrays()
        self.init_integral_arrays()
        self.calc_critical_radii()

        return
Beispiel #7
0
    def calculate_timescale(self):
        # time_coal = 0.0
        eccen_final = 0.0
        verbose = self._verbose
        if verbose:
            print("EvolveLZK.calculate_timescale()")

        self.init_integral_arrays()
        self.integrate()

        rads = self.rads
        rads_2d = rads[np.newaxis, :]
        dadt = self.dadt
        num_binaries, num_rads = dadt.shape

        dr = np.concatenate((np.diff(rads), [0.0])) * np.ones_like(dadt)
        alive = (self.rad_isco[:, np.newaxis] <=
                 rads_2d) & (rads_2d <= self.sep0[:, np.newaxis])

        # Calculate the durations for normal, mid-evolution steps
        times = np.zeros_like(dadt)
        inds = (dadt != 0.0)
        inds[:, -1] = False
        times[inds] = -dr[inds] / dadt[inds]

        # Calculate first step duration
        # -----------------------------------------------

        # Find the last valid index (largest separation) for each binary
        # `argmax` returns the first index, so reverse it to get the last index
        beg = np.argmax(np.flip(alive, axis=-1), axis=-1)
        # (un)reverse the index numbers
        beg = (num_rads - 1) - beg

        dr_beg = self.sep0 - rads[beg]
        times[:, beg] = -dr_beg / dadt[:, beg]

        # Calculate last step duration
        # -----------------------------------------------
        ''' # NOTE: this will always be a tiny duration... so just ignore it
        # Find the first valid index (smallest separation) for each binary
        end = np.argmax(alive)
        # Then go one index lower, minimum 0 (binary with r[0] == ISCO)
        end = np.maximum(end - 1, 0)
        # Set 'alive' values to True for this extra piece of a step
        dr_end = rads[end] - self.rad_isco
        times[end] = - dr_end / dadt[end]
        '''

        # Find cumulative lifetimes
        durs = np.sum(times * alive, axis=-1)

        # Find coalescence redshift
        tage_coal = self.tage_form + durs
        redz_coal = self._cosmo.tage_to_z(tage_coal)
        inds_coal = np.isfinite(redz_coal)
        num_coal = np.count_nonzero(inds_coal)
        frac = num_coal / num_binaries

        if verbose:
            print("EvolveLZK.calculate_timescale()")
            print("Lifetimes: " + zmath.stats_str(durs / GYR) + " [Gyr]")
            print("Coalescence redshifts: " + zmath.stats_str(redz_coal))
            print("\t(valid) Coalescence redshifts: " +
                  zmath.stats_str(redz_coal[redz_coal >= 0.0]))
            print("{}/{} = {:.4f} coalesce before z=0".format(
                num_coal, num_binaries, frac))

        self.times = times
        self.durs = durs
        self.tage_coal = tage_coal
        self.redz_coal = redz_coal
        self.inds_coal = inds_coal

        self.m1 = self.m1 / MSOL  # changed from original version to return mass in solar masses
        self.m2 = self.m2 / MSOL  # changed from original version to return mass in solar masses
        durs = durs / (
            ct.Julian_year
        )  # changed from original version so that it returns in years
        return durs, eccen_final