Пример #1
0
def test_mass_in_msol():
    #Test the scaling, should be velocity^2 x position
    vofid, rofid = 200., 8.
    assert numpy.fabs(4. * conversion.mass_in_msol(vofid, rofid) /
                      conversion.mass_in_msol(2. * vofid, rofid) -
                      1.) < 10.**-10., 'mass_in_msol did not work as expected'
    assert numpy.fabs(2. * conversion.mass_in_msol(vofid, rofid) /
                      conversion.mass_in_msol(vofid, 2 * rofid) -
                      1.) < 10.**-10., 'mass_in_msol did not work as expected'
    return None
Пример #2
0
def test_ChandrasekharDynamicalFrictionForce_constLambda():
    # Test that the ChandrasekharDynamicalFrictionForce with constant Lambda
    # agrees with analytical solutions for circular orbits:
    # assuming that a mass remains on a circular orbit in an isothermal halo
    # with velocity dispersion sigma and for constant Lambda:
    # r_final^2 - r_initial^2 = -0.604 ln(Lambda) GM/sigma t
    # (e.g., B&T08, p. 648)
    from galpy.util import conversion
    from galpy.orbit import Orbit
    ro, vo = 8., 220.
    # Parameters
    GMs = 10.**9. / conversion.mass_in_msol(vo, ro)
    const_lnLambda = 7.
    r_init = 2.
    dt = 2. / conversion.time_in_Gyr(vo, ro)
    # Compute
    lp = potential.LogarithmicHaloPotential(normalize=1., q=1.)
    cdfc= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,const_lnLambda=const_lnLambda,
        dens=lp) # don't provide sigmar, so it gets computed using galpy.df.jeans
    o = Orbit([r_init, 0., 1., 0., 0., 0.])
    ts = numpy.linspace(0., dt, 1001)
    o.integrate(ts, [lp, cdfc], method='odeint')
    r_pred = numpy.sqrt(o.r()**2. -
                        0.604 * const_lnLambda * GMs * numpy.sqrt(2.) * dt)
    assert numpy.fabs(
        r_pred - o.r(ts[-1])
    ) < 0.01, 'ChandrasekharDynamicalFrictionForce with constant lnLambda for circular orbits does not agree with analytical prediction'
    return None
Пример #3
0
def test_ChandrasekharDynamicalFrictionForce_varLambda():
    # Test that dynamical friction with variable Lambda for small r ranges
    # gives ~ the same result as using a constant Lambda that is the mean of
    # the variable lambda
    # Also tests that giving an axisymmetric list of potentials for the
    # density works
    from galpy.util import conversion
    from galpy.orbit import Orbit
    ro, vo = 8., 220.
    # Parameters
    GMs = 10.**9. / conversion.mass_in_msol(vo, ro)
    r_init = 3.
    dt = 2. / conversion.time_in_Gyr(vo, ro)
    # Compute evolution with variable ln Lambda
    cdf= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        dens=potential.MWPotential2014,sigmar=lambda r: 1./numpy.sqrt(2.))
    o = Orbit([r_init, 0., 1., 0., 0., 0.])
    ts = numpy.linspace(0., dt, 1001)
    o.integrate(ts, [potential.MWPotential2014, cdf], method='odeint')
    lnLs = numpy.array([
        cdf.lnLambda(r, v) for (r, v) in zip(
            o.r(ts), numpy.sqrt(o.vx(ts)**2. + o.vy(ts)**2. + o.vz(ts)**2.))
    ])
    cdfc= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,const_lnLambda=numpy.mean(lnLs),
        dens=potential.MWPotential2014,sigmar=lambda r: 1./numpy.sqrt(2.))
    oc = o()
    oc.integrate(ts, [potential.MWPotential2014, cdfc], method='odeint')
    assert numpy.fabs(
        oc.r(ts[-1]) - o.r(ts[-1])
    ) < 0.05, 'ChandrasekharDynamicalFrictionForce with variable lnLambda for a short radial range is not close to the calculation using a constant lnLambda'
    return None
Пример #4
0
def test_ChandrasekharDynamicalFrictionForce_pickling():
    # Test that ChandrasekharDynamicalFrictionForce objects can/cannot be
    # pickled as expected
    import pickle
    from galpy.util import conversion
    ro, vo = 8., 220.
    # Parameters
    GMs = 10.**9. / conversion.mass_in_msol(vo, ro)
    # sigmar internally computed, should be able to be pickled
    # Compute evolution with variable ln Lambda
    cdf= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        dens=potential.MWPotential2014,
        minr=0.5,maxr=2.)
    pickled = pickle.dumps(cdf)
    cdfu = pickle.loads(pickled)
    # Test a few values
    assert numpy.fabs(cdf.Rforce(1.,0.2,v=[1.,1.,0.])\
                          -cdfu.Rforce(1.,0.2,v=[1.,1.,0.])) < 1e-10, 'Pickling of ChandrasekharDynamicalFrictionForce object does not work as expected'
    assert numpy.fabs(cdf.zforce(2.,-0.2,v=[1.,1.,0.])\
                          -cdfu.zforce(2.,-0.2,v=[1.,1.,0.])) < 1e-10, 'Pickling of ChandrasekharDynamicalFrictionForce object does not work as expected'
    # Not providing dens = Logarithmic should also work
    cdf= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        minr=0.5,maxr=2.)
    pickled = pickle.dumps(cdf)
    cdfu = pickle.loads(pickled)
    # Test a few values
    assert numpy.fabs(cdf.Rforce(1.,0.2,v=[1.,1.,0.])\
                          -cdfu.Rforce(1.,0.2,v=[1.,1.,0.])) < 1e-10, 'Pickling of ChandrasekharDynamicalFrictionForce object does not work as expected'
    assert numpy.fabs(cdf.zforce(2.,-0.2,v=[1.,1.,0.])\
                          -cdfu.zforce(2.,-0.2,v=[1.,1.,0.])) < 1e-10, 'Pickling of ChandrasekharDynamicalFrictionForce object does not work as expected'

    # Providing sigmar as a lambda function gives AttributeError
    sigmar = lambda r: 1. / r
    cdf= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        dens=potential.MWPotential2014,sigmar=sigmar,
        minr=0.5,maxr=2.)
    if PY3:
        with pytest.raises(AttributeError) as excinfo:
            pickled = pickle.dumps(cdf)
    else:
        with pytest.raises(pickle.PicklingError) as excinfo:
            pickled = pickle.dumps(cdf)
    return None
Пример #5
0
def test_ChandrasekharDynamicalFrictionForce_evaloutsideminrmaxr():
    # Test that dynamical friction returns the expected force when evaluating
    # outside of the [minr,maxr] range over which sigmar is interpolated:
    # 0 at r < minr
    # using sigmar(r) for r > maxr
    from galpy.util import conversion
    ro, vo = 8., 220.
    # Parameters
    GMs = 10.**9. / conversion.mass_in_msol(vo, ro)
    # Compute evolution with variable ln Lambda
    sigmar = lambda r: 1. / r
    cdf= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        dens=potential.MWPotential2014,sigmar=sigmar,
        minr=0.5,maxr=2.)
    # cdf 2 for checking r > maxr of cdf
    cdf2= potential.ChandrasekharDynamicalFrictionForce(\
        GMs=GMs,rhm=0.125,
        dens=potential.MWPotential2014,sigmar=sigmar,
        minr=0.5,maxr=4.)
    v = [0.1, 0., 0.]
    # r < minr
    assert numpy.fabs(
        cdf.Rforce(0.1, 0., v=v)
    ) < 1e-16, 'potential.ChandrasekharDynamicalFrictionForce at r < minr not equal to zero'
    assert numpy.fabs(
        cdf.zforce(0.1, 0., v=v)
    ) < 1e-16, 'potential.ChandrasekharDynamicalFrictionForce at r < minr not equal to zero'
    # r > maxr
    assert numpy.fabs(
        cdf.Rforce(3., 0., v=v) - cdf2.Rforce(3., 0., v=v)
    ) < 1e-10, 'potential.ChandrasekharDynamicalFrictionForce at r > maxr not as expected'
    assert numpy.fabs(
        cdf.zforce(3., 0., v=v) - cdf2.zforce(3., 0., v=v)
    ) < 1e-10, 'potential.ChandrasekharDynamicalFrictionForce at r > maxr not as expected'
    return None
Пример #6
0
class Snapshot():

    MLU = 8.  #kpc
    MVU = 220.  #km/s
    MTU = time_in_Gyr(MVU, MLU) * 1000.  #Myr
    MMU = mass_in_msol(MVU, MLU)  # msol

    def __init__(self,
                 Filename,
                 Np,
                 hf=0.4,
                 df=0.5,
                 bf=0.1,
                 Nslice=10,
                 dt=0.0078125,
                 frac=True):

        self.Filename = Filename
        self.Np = Np
        if frac:
            self.Nh = int(self.Np * hf)
            self.Nd = int(self.Np * df)
            self.Nb = int(self.Np * bf)
        else:
            self.Nh = hf
            self.Nd = df
            self.Nb = bf
        self._step = int((self.Filename.split('_')[3]).split('.')[0])
        self._Nslice = Nslice
        self._dt = dt
        self.t = self._step * dt

        self._COM = self.calc_COM()
        self._rot = self.calc_AngMom()

    def calc_COM(self):
        mtot = 0
        COMraw = np.zeros(3)
        for i in range(self._Nslice):
            offset = int(i * int(np.ceil(self.Np / self._Nslice)))
            count = int(np.ceil(self.Np / self._Nslice))
            if offset + count > self.Np:
                count = self.Np - offset
            with open(self.Filename, 'rb') as f:
                mxv = np.fromfile(f,
                                  dtype='f4',
                                  offset=offset * 4 * 7,
                                  count=count * 7)
                mxv = np.reshape(mxv, (count, 7)).astype('f8')
                mtot += np.sum(mxv[:, 0])
                COMraw += np.sum(mxv[:, 0][:, None] * (mxv[:, 1:4]), axis=0)
        del mxv
        return COMraw / mtot

    def calc_AngMom(self):
        Ltot = np.zeros(3)
        for i in range(self._Nslice):
            with open(self.Filename, 'rb') as f:
                mxv = np.fromfile(
                    f,
                    dtype='f4',
                    offset=(self.Nh * 7 + i * 7 * int(self.Nd / self._Nslice))
                    * 4,
                    count=7 * int(self.Nd / self._Nslice))
                mxv = np.reshape(mxv, (int(len(mxv) / 7), 7))
                Ltot += np.sum(np.cross((mxv[:, 1:4] - self._COM),
                                        mxv[:, 0][:, None] * mxv[:, 4:]),
                               axis=0)
        Ltot = Ltot / np.sqrt(np.sum(Ltot**2))
        rot = self.calc_Rot_matrix(Ltot)
        del mxv
        return rot

    @staticmethod
    def calc_Rot_matrix(AngMom):
        hyz = np.sqrt(AngMom[1]**2 + AngMom[2]**2)
        costh = AngMom[2] / hyz
        sinth = AngMom[1] / hyz

        Rx = np.array([[1, 0, 0], [0, costh, -sinth], [0, sinth, costh]])

        Lmid = np.matmul(Rx, AngMom)

        hxz = np.sqrt(Lmid[0]**2 + Lmid[2]**2)
        cosph = Lmid[2] / hxz
        sinph = -Lmid[0] / hxz
        Ry = np.array([[cosph, 0, sinph], [0, 1, 0], [-sinph, 0, cosph]])

        return np.matmul(Ry, Rx)

    def calc_density(self,
                     bins=(90, 90, 90),
                     lim=(30., 30., 10.),
                     adjust=True):
        Ntot = np.zeros(bins)

        for i in range(self._Nslice):
            with open(self.Filename, 'rb') as f:
                mxv = np.fromfile(f,
                                  dtype='f4',
                                  offset=i * 7 * int(self.Np / self._Nslice) *
                                  4,
                                  count=7 * int(self.Np / self._Nslice))
                mxv = np.reshape(mxv, (int(len(mxv) / 7), 7))
                if adjust:
                    mxv[:, 1:] = self.adjust(mxv[:, 1:])

                N, edges = np.histogramdd(mxv[:, 1:4],
                                          bins=bins,
                                          range=((-lim[0], lim[0]),
                                                 (-lim[1], lim[1]), (-lim[2],
                                                                     lim[2])),
                                          weights=mxv[:, 0])
                Ntot += N
        del mxv
        self.N = Ntot
        self.Nedge = edges
        return Ntot, edges

    def adjust(self, particles, vel=True):
        particles[:, :3] = np.matmul(self._rot,
                                     particles[:, :3].T - self._COM[:, None]).T
        if vel:
            particles[:, 3:] = np.matmul(self._rot, particles[:, 3:].T).T
        return particles

    def sample(self, frac=0.1):

        ncomp = [self.Nh, self.Nd, self.Nb]
        sample = np.empty([0, 7])

        for i, n in enumerate(ncomp):
            for j in range(self._Nslice):
                offset = int(np.sum(
                    ncomp[:i])) + j * int(np.ceil(n / self._Nslice))
                count = int(np.ceil(n / self._Nslice))
                if offset + count > np.sum(ncomp[:i + 1]):
                    count = np.sum(ncomp[:i + 1]) - offset
                with open(self.Filename, 'rb') as f:
                    mxv = np.fromfile(f,
                                      dtype='f4',
                                      offset=offset * 7 * 4,
                                      count=7 * count)
                    mxv = np.reshape(mxv, (int(len(mxv) / 7), 7))
                    indx = np.random.choice(count,
                                            int(count * frac),
                                            replace=False)
                    sample = np.vstack(
                        [sample, np.reshape(mxv[indx], (len(indx), 7))])
        sample[:, 0] /= frac
        sample[:, 1:] = self.sample(frac=frac)
        return sample

    def calc_vrot(self, Rs=np.linspace(0.01, 5., 51), sample=True, frac=0.01):

        try:
            return self.vrot
        except:
            if sample:
                sample = self.sample(frac=frac)
            else:
                with open(self.Filename, 'rb') as f:
                    sample = np.fromfile(f,
                                         dtype='f4',
                                         offset=4 * 7 * (self.Nh + self.Nd),
                                         count=7 * self.Nb)
                    sample = np.reshape(sample, (self.Nb, 7))
                    sample = self.adjust(sample)

            Nh = len(sample[tuple([sample[:, 0] == sample[0, 0]])])

            f = pb.new(dm=Nh, star=len(sample[:, 0]) - Nh)
            f['mass'] = sample[:, 0] / self.MMU
            f['pos'] = sample[:, 1:4] / self.MLU
            f['vel'] = sample[:, 4:] / self.MVU
            f['eps'] = np.ones(len(sample)) * 0.05 / self.MLU

            sp = galpy.potential.SnapshotRZPotential(f, num_threads=10)
            vrot = galpy.potential.calcRotcurve(sp, Rs, phi=0.)
            self.vrot = vrot
            self._Rs = Rs
            return vrot

    def calc_potential(self,
                       sample=True,
                       frac=0.01,
                       a=[27.68, 3.41, 0.536],
                       N=[5, 15, 3],
                       L=[5, 15, 3]):

        try:
            print('data/equil_potential_coefficients_N' + str(N[0]) +
                  str(N[1]) + str(N[2]) + '.txt')
            with open(
                    'data/equil_potential_coefficients_N' + str(N[0]) +
                    str(N[1]) + str(N[2]) + '.txt', 'rb') as f:
                Acos, Asin = np.load(f, allow_pickle=True)
            pot = [
                SCFPotential(Acos=Acos[i], Asin=Asin[i], a=a[i] / 8.)
                for i in range(3)
            ]
        except:
            ncomp = [self.Nh, self.Nd, self.Nb]
            Acos, Asin = np.empty((2, 3), dtype='object')

            pot = np.empty(3, dtype='object')

            fullsample = self.sample(frac=frac)
            masses = list(set(fullsample[:, 0]))

            for i, n in enumerate(ncomp):
                samples = fullsample[tuple([fullsample[:, 0] == masses[i]])]
                Acos[i], Asin[i] = scf_compute_coeffs_nbody(
                    samples[:, 1:4].T / self.MLU,
                    samples[:, 0] / self.MMU,
                    N[i],
                    L[i],
                    a=a[i] / 8.)
                pot[i] = SCFPotential(Acos=Acos[i], Asin=Asin[i], a=a[i] / 8.)

            coeff = np.vstack([Acos, Asin])
            with open(
                    'data/equil_potential_coefficients_N' + str(N[0]) +
                    str(N[1]) + str(N[2]) + '.txt', 'wb') as f:
                np.save(f, coeff)

        self.pot = list(pot)
        return list(pot)

    def analyze_SN(self,
                   Rcyl,
                   X=8.1,
                   Y=0,
                   correct=True,
                   zrange=[-2, 2],
                   nbins=51):

        calc = True
        try:
            for i, sn in enumerate(self.SN):
                if (sn.Rcyl == Rcyl) * (sn.X == X) * (sn.Y == Y):

                    # If you want a finer grid, replace the old coarser grid
                    if (nbins > sn.nbins):
                        self.SN[i] = SolarNeighbourhood(
                            self, Rcyl, X, Y, correct, zrange, nbins)
                        calc = False

                    # If it already exists, don't do it again
                    else:
                        calc = False
                else:
                    continue

            if calc:
                self.SN = np.append(
                    self.SN,
                    SolarNeighbourhood(self, Rcyl, X, Y, correct, zrange,
                                       nbins))

        except:
            self.SN = np.array(
                [SolarNeighbourhood(self, Rcyl, X, Y, correct, zrange, nbins)])

    def plot_density(self, plot_origin=False, save=False, adjust=True):

        try:
            self.N
        except:
            self.calc_density(adjust=adjust)

        fig, [ax1, ax2] = plt.subplots(2,
                                       sharex=True,
                                       gridspec_kw={'height_ratios': [3, 1]},
                                       figsize=[5, 7])
        ax1.imshow(np.log(np.sum(self.N, axis=2).T),
                   cmap='Greys',
                   extent=[-30, 30, -30, 30])
        ax2.imshow(np.log(np.sum(self.N, axis=1).T),
                   cmap='Greys',
                   extent=[-30, 30, -10, 10])

        if plot_origin:
            ax1.plot(0., 0., 'or')
            ax2.plot(0., 0., 'or')

        ax2.set_xlabel(r'$x \,\mathrm{(kpc)}$')
        ax1.set_ylabel(r'$y \,\mathrm{(kpc)}$')
        ax2.set_ylabel(r'$z \,\mathrm{(kpc)}$')
        fig.subplots_adjust(hspace=0)
        txt = ax1.annotate(r'$t=%.0f\,\mathrm{Myr}$' % (np.round(self.t, 0)),
                           (0.95, 0.95),
                           xycoords='axes fraction',
                           horizontalalignment='right',
                           verticalalignment='top',
                           size=18.)
        if save:
            plt.savefig('plots/Density' +
                        str(np.round(self.t, 0) + '.pdf', bbox_inches='tight'))
Пример #7
0
def test_amuse_potential_with_physical():
    ro, vo=8., 220.
    amp= 1e8 / conversion.mass_in_msol(ro=ro, vo=vo)
    a= 0.8 / ro

    amp_u= 1e8 * apy_u.solMass
    a_u= 0.8 * apy_u.kpc
    ro_u, vo_u= 8.*apy_u.kpc, 220.*apy_u.km/apy_u.s

    x, y, z = 3|units.kpc, 4|units.kpc, 2|units.kpc
    r = 5|units.kpc

    # ------------------------------------
    # get_potential_at_point
    pot1= potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gg1 = pot1(5/ro, 2/ro)
    gg1 = gg1.to_value(apy_u.km**2 / apy_u.s**2) if hasattr(gg1, 'unit') else gg1
    amuse_pot1= to_amuse(pot1)
    ag1= amuse_pot1.get_potential_at_point(0, x, y, z)

    assert numpy.abs(gg1 - ag1.value_in(units.kms**2)) < 1e-10

    pot2= potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gg2 = pot1(5*apy_u.kpc, 2*apy_u.kpc)
    gg2 = gg2.to_value(apy_u.km**2 / apy_u.s**2) if hasattr(gg2, 'unit') else gg2
    amuse_pot2= to_amuse(pot2)
    ag2= amuse_pot2.get_potential_at_point(0, x, y, z)

    assert numpy.abs(gg2 - ag2.value_in(units.kms**2)) < 1e-10

    assert numpy.abs(ag1 - ag2) < 1e-10|units.kms**2

    # ------------------------------------
    # test get_gravity_at_point
    pot1= potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    amuse_pot1= to_amuse(pot1)
    ax1, ay1, az1= amuse_pot1.get_gravity_at_point(0, x, y, z)

    pot2= potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    amuse_pot2= to_amuse(pot2)
    ax2, ay2, az2= amuse_pot2.get_gravity_at_point(0, x, y, z)

    assert numpy.abs(ax1 - ax2) < 1e-10|units.kms/units.Myr
    assert numpy.abs(ay1 - ay2) < 1e-10|units.kms/units.Myr
    assert numpy.abs(az1 - az2) < 1e-10|units.kms/units.Myr

    # ------------------------------------
    # test mass_density
    pot1= potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    grho1 = pot1.dens(5/ro, 2/ro)
    grho1 = grho1.to_value(apy_u.solMass/apy_u.pc**3) if hasattr(grho1, 'unit') else grho1
    amuse_pot1= to_amuse(pot1)
    arho1= amuse_pot1.mass_density(x, y, z)

    assert numpy.abs(grho1 - arho1.value_in(units.MSun/units.parsec**3)) < 1e-10

    pot2= potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    grho2= pot2.dens(5*apy_u.kpc, 2*apy_u.kpc)
    grho2= grho2.to_value(apy_u.solMass/apy_u.pc**3) if hasattr(grho2, 'unit') else grho2
    amuse_pot2= to_amuse(pot2)
    arho2= amuse_pot2.mass_density(x, y, z)

    assert numpy.abs(grho2 - arho2.value_in(units.MSun/units.parsec**3)) < 1e-10

    assert numpy.abs(arho1 - arho2) < 1e-10|units.MSun/units.parsec**3

    # ------------------------------------
    # test circular_velocity
    pot1= potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gv1= pot1.vcirc(1*apy_u.kpc)
    gv1=  gv1.to_value(apy_u.km/apy_u.s) if hasattr(gv1, 'unit') else gv1
    amuse_pot1= to_amuse(pot1)
    av1= amuse_pot1.circular_velocity(1|units.kpc)

    assert numpy.abs(gv1 - av1.value_in(units.kms)) < 1e-10

    pot2= potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gv2= pot2.vcirc(1*apy_u.kpc)
    gv2=  gv2.to_value(apy_u.km/apy_u.s) if hasattr(gv2, 'unit') else gv2
    amuse_pot2= to_amuse(pot2)
    av2= amuse_pot2.circular_velocity(1|units.kpc)

    assert numpy.abs(gv2 - av2.value_in(units.kms)) < 1e-10

    assert numpy.abs(av1 - av2) < 1e-10|units.kms

    # ------------------------------------
    # test enclosed_mass

    pot1= potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gm1= pot1.mass(1/ro)
    gm1= gm1.to_value(apy_u.solMass) if hasattr(gm1, 'unit') else gm1
    amuse_pot1= to_amuse(pot1)
    am1= amuse_pot1.enclosed_mass(1|units.kpc)

    assert numpy.abs(gm1 - am1.value_in(units.MSun)) < 3e-8

    pot2= potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gm2= pot2.mass(1*apy_u.kpc)
    gm2= gm2.to_value(apy_u.solMass) if hasattr(gm2, 'unit') else gm2
    amuse_pot2= to_amuse(pot2)
    am2= amuse_pot2.enclosed_mass(1|units.kpc)

    assert numpy.abs(gm2 - am2.value_in(units.MSun)) < 3e-8

    assert numpy.abs(am1 - am2) < 1e-10|units.MSun

    return None
def simulate_cdm_subhalos(sdf_pepper,
                          log10Msub_min=5.,
                          log10Msub_max=9.,
                          timescdm=1.,
                          mf_slope=-1.9,
                          c0kpc=2.02 * 10**(-13),
                          r=17.,
                          Xrs=5.,
                          sigma=120. / 220.,
                          rs_fit=True,
                          rs_Nbody_kpc=0.2):
    '''
    Sample amp and slope such that dN/dM = amp*M^slope and simulate subhalo impacts
    
    '''
    Mbin_edge = np.arange(log10Msub_min, log10Msub_max + 1, 1)
    Nbins = len(Mbin_edge) - 1
    #compute number of subhalos in each mass bin
    nden_bin = np.empty(Nbins)
    rate_bin = np.empty(Nbins)
    for ll in range(Nbins):
        nden_bin[ll] = nsub_cdm(Mbin_edge[ll],
                                Mbin_edge[ll + 1],
                                r=r,
                                c0kpc=c0kpc,
                                mf_slope=mf_slope)
        Mmid = 10**(0.5 * (Mbin_edge[ll] + Mbin_edge[ll + 1]))
        rate_bin[ll] = sdf_pepper.subhalo_encounters(
            sigma=sigma,
            nsubhalo=nden_bin[ll],
            bmax=Xrs *
            rs(Mmid, plummer=True, rs_fit=rs_fit, rs_Nbody_kpc=rs_Nbody_kpc))

    rate = timescdm * np.sum(rate_bin)

    Nimpact = numpy.random.poisson(rate)

    if Nbins == 1:  #if only one bin then all subhalos are assigned the same mean mass
        sample_GM = lambda: 10.**(0.5 * (Mbin_edge[0] + Mbin_edge[1])
                                  ) / conversion.mass_in_msol(vo, ro)
        timpact_sub = numpy.array(
            sdf_pepper._uniq_timpact)[numpy.random.choice(
                len(sdf_pepper._uniq_timpact),
                size=Nimpact,
                p=sdf_pepper._ptimpact)]
        # Sample angles from the part of the stream that existed then
        impact_angle_sub = numpy.array([
            sdf_pepper._icdf_stream_len[ti](numpy.random.uniform())
            for ti in timpact_sub
        ])
        GM_sub = numpy.array([sample_GM() for a in impact_angle_sub])
        rs_sub = numpy.array([
            rs(gm * conversion.mass_in_msol(vo, ro),
               rs_fit=rs_fit,
               rs_Nbody_kpc=rs_Nbody_kpc) for gm in GM_sub
        ])

    else:

        norm = 1. / quad(lambda M: M**(mf_slope + 0.5), 10**(Mbin_edge[0]), 10
                         **(Mbin_edge[Nbins]))[0]

        def cdf(M):
            return quad(lambda M: norm * (M)**(mf_slope + 0.5),
                        10**Mbin_edge[0], M)[0]

        MM = numpy.linspace(Mbin_edge[0], Mbin_edge[Nbins], 10000)

        cdfl = [cdf(i) for i in 10**MM]
        icdf = interpolate.InterpolatedUnivariateSpline(cdfl, 10**MM, k=1)
        sample_GM = lambda: icdf(numpy.random.uniform()
                                 ) / conversion.mass_in_msol(vo, ro)
        timpact_sub = numpy.array(
            sdf_pepper._uniq_timpact)[numpy.random.choice(
                len(sdf_pepper._uniq_timpact),
                size=Nimpact,
                p=sdf_pepper._ptimpact)]
        # Sample angles from the part of the stream that existed then
        impact_angle_sub = numpy.array([
            sdf_pepper._icdf_stream_len[ti](numpy.random.uniform())
            for ti in timpact_sub
        ])
        GM_sub = numpy.array([sample_GM() for a in impact_angle_sub])
        rs_sub = numpy.array([
            rs(gm * conversion.mass_in_msol(vo, ro),
               rs_fit=rs_fit,
               rs_Nbody_kpc=rs_Nbody_kpc) for gm in GM_sub
        ])

    # impact b
    impactb_sub = (2. * numpy.random.uniform(size=len(impact_angle_sub)) -
                   1.) * Xrs * rs_sub
    # velocity

    subhalovel_sub = numpy.empty((len(impact_angle_sub), 3))
    for ii in range(len(timpact_sub)):
        subhalovel_sub[ii] = sdf_pepper._draw_impact_velocities(
            timpact_sub[ii], sigma, impact_angle_sub[ii], n=1)[0]
    # Flip angle sign if necessary
    if not sdf_pepper._gap_leading: impact_angle_sub *= -1.

    return impact_angle_sub, impactb_sub, subhalovel_sub, timpact_sub, GM_sub, rs_sub