Exemplo n.º 1
0
 def integ(z, TheTa1, TheTa2):
     TheTa = np.sqrt(TheTa1**2 + TheTa2**2)
     R = D_d * TheTa
     NFW_p = NFWPotential(amp=nfw_M0, a=nfw_a, normalize=False)
     Densidad = NFW_p.dens(R, z)
     Kappa = 2 * Densidad
     return Kappa / (SIGMA_CRIT**2)
Exemplo n.º 2
0
 def POTDEF1(z, TheTa2):
     TheTa = np.sqrt(TheTa2**2 + theta1[l]**2)
     R = D_d * TheTa
     NFW_p = NFWPotential(amp=M_0, a=r_s, normalize=False)
     Sigma = NFW_p.dens(R, z)
     kappa = Sigma / SIGMA_CRIT
     return (4 / theta2[l]) * TheTa2 * kappa / SIGMA_CRIT**2
Exemplo n.º 3
0
def minimize_function(x, rho_nfw_target, rho_midplane_target,
                      density_conversion):
    """
    Computes the chi^2 penalty for a galactic potential for the purpose of finding an NFWPotential and MiyamotoNagaiPotential
    circular velocity normalization that yeild the desired midplane and nfw physical densities
    :param x: numpy array of proposed disk and nfw circular velocity normalizations (in that order)
    :param rho_nfw_target: desired nfw_normalization in physical M_sun / pc^2
    :param rho_midplane_target: desired midplane density in physical M_sun / pc^2
    :param density_conversion: a conversion factor between galpy internal density units and physical M_sun / pc^2
    :return: chi^2 penalty
    """

    galactic_potential = [
        PowerSphericalPotentialwCutoff(normalize=0.05, alpha=1.8, rc=1.9 / 8.),
        MiyamotoNagaiPotential(a=3. / 8., b=0.28 / 8., normalize=x[0]),
        NFWPotential(a=2., normalize=x[1])
    ]
    nfw_potential = NFWPotential(a=2., normalize=x[1])

    rho = evaluateDensities(galactic_potential, R=1.,
                            z=0.) * density_conversion
    rho_nfw = evaluateDensities(nfw_potential, R=1, z=0.) * density_conversion
    dx = (rho - rho_midplane_target)**2 / 0.000001**2 + (
        rho_nfw - rho_nfw_target)**2 / 0.000001**2

    return dx**0.5
Exemplo n.º 4
0
	def test_leapfrog_nfw_galpy(self):
		# Compare a more complicated set of initial conditions to the galpy
		# outputs (which are slow but known to work).

		# Set the parameters for a slightly offset circular orbit
		rho_0 = 0.1
		r_scale = 1
		G = 0.8962419740798497

		dt = 0.001
		num_dt = int(5e4)

		pos_nfw_array = np.tile(np.zeros(3,dtype=np.float64),(num_dt,1))
		save_pos = np.zeros((num_dt+1,3))
		save_vel = np.zeros((num_dt+1,3))

		# Change the rho_0 and r_scale to a fixed array in time
		rho_0_array = rho_0*np.ones(num_dt,dtype=np.float64)
		r_scale_array = r_scale*np.ones(num_dt,dtype=np.float64)

		r_init = 20
		pos_init = np.array([r_init,0,0],dtype=np.float64)

		M_r = 4*np.pi*rho_0*r_scale**3*(np.log((r_scale+r_init)/r_scale)+
			r_scale/(r_scale+r_init) - 1)
		v_r = np.sqrt(G*M_r/r_init)
		v_kick = 1e-1
		vel_init = np.array([v_kick,v_r,0],dtype=np.float64)

		integrator.leapfrog_int_nfw(pos_init,vel_init,rho_0_array,
			r_scale_array,pos_nfw_array,dt,save_pos,save_vel)

		# Compare to galpy orbits
		o=Orbit([r_init,v_kick,v_r,0,0,0])
		nfw = NFWPotential(a=r_scale,amp=rho_0*G*4*np.pi*r_scale**3)
		ts = np.linspace(0,dt*num_dt,num_dt+1)
		o.integrate(ts,nfw,method='leapfrog',dt=dt)

		np.testing.assert_almost_equal(o.x(ts),save_pos[:,0])
		np.testing.assert_almost_equal(o.y(ts),save_pos[:,1])
		np.testing.assert_almost_equal(o.z(ts),save_pos[:,2])

		# Make the kick bigger and see what happens
		v_kick = 5e-1
		pos_init = np.array([r_init,0,0],dtype=np.float64)
		vel_init = np.array([v_kick,v_r,v_kick],dtype=np.float64)
		integrator.leapfrog_int_nfw(pos_init,vel_init,rho_0_array,
			r_scale_array,pos_nfw_array,dt,save_pos,save_vel)

		# Do the same for galpy
		o=Orbit([r_init,v_kick,v_r,0,v_kick,0])
		nfw = NFWPotential(a=r_scale,amp=rho_0*G*4*np.pi*r_scale**3)
		ts = np.linspace(0,dt*num_dt,num_dt+1)
		o.integrate(ts,nfw,method='leapfrog',dt=dt)

		np.testing.assert_almost_equal(o.x(ts),save_pos[:,0])
		np.testing.assert_almost_equal(o.y(ts),save_pos[:,1])
		np.testing.assert_almost_equal(o.z(ts),save_pos[:,2])
Exemplo n.º 5
0
def get_halo_potentials(masses,
                        mass_definition,
                        concentration_at_8=18.0,
                        physical_off=False):

    halo_potentials = []
    for m in masses:

        if mass_definition == 'HERNQUIST':
            c = sample_concentration_herquist(m, concentration_at_8)
            pot = HernquistPotential(amp=0.5 * m * apu.solMass, a=c * apu.kpc)

        elif mass_definition == 'NFW':
            c = sample_concentration_nfw(m, concentration_at_8)
            pot = NFWPotential(mvir=m / 10**12, conc=c)

        else:
            raise Exception('mass definition must be HERNQUIST or NFW')

        if physical_off:
            galpy.potential.turn_physical_off(pot)

        halo_potentials.append(pot)

    return halo_potentials
Exemplo n.º 6
0
    def setup(self):

        nfw_norm = [0.2, 0.4, 0.6]
        disk_norm = [0.15, 0.3, 0.45]
        pot_list = []

        for normnfw in nfw_norm:
            for normdisk in disk_norm:
                pot = [
                    PowerSphericalPotentialwCutoff(normalize=0.05,
                                                   alpha=1.8,
                                                   rc=1.9 / 8.),
                    MiyamotoNagaiPotential(a=3. / 8.,
                                           b=0.28 / 8.,
                                           normalize=normdisk),
                    NFWPotential(a=2., normalize=normnfw)
                ]
                pot_extension = PotentialExtension(pot, 2, 120, 10)
                pot_list.append(pot_extension)

        self.disk_norm = disk_norm
        self.nfw_norm = nfw_norm
        self.pot_list = pot_list

        self.tabulated_potential = TabulatedPotential(self.pot_list,
                                                      self.nfw_norm,
                                                      self.disk_norm)
Exemplo n.º 7
0
    def setup(self):

        nfw_norm = [0.2, 0.4, 0.6]
        disk_norm = [0.15, 0.3, 0.45]
        scale_height = [0.25, 0.27, 0.29]
        pot_list = []

        for normnfw in nfw_norm:
            for normdisk in disk_norm:
                for scale_h in scale_height:
                    pot = [
                        PowerSphericalPotentialwCutoff(normalize=0.05,
                                                       alpha=1.8,
                                                       rc=1.9 / 8.),
                        MiyamotoNagaiPotential(a=3. / 8.,
                                               b=scale_h / 8.,
                                               normalize=normdisk),
                        NFWPotential(a=2., normalize=normnfw)
                    ]
                    pot_extension = PotentialExtension(
                        pot, 2, 120, 3, compute_action_angle=True)
                    pot_list.append(pot_extension)

        self.disk_norm = disk_norm
        self.nfw_norm = nfw_norm
        self.scale_height = scale_height
        self.pot_list = pot_list

        self.tabulated_potential = TabulatedPotential3D(
            self.pot_list, self.nfw_norm, self.disk_norm, self.scale_height)
Exemplo n.º 8
0
def render_subhalos(mlow,
                    mhigh,
                    f_sub,
                    log_slope,
                    m_host,
                    via_lactea_kde,
                    c8,
                    galactic_potential,
                    global_potential_extension,
                    time_Gyr,
                    mdef='HERNQUIST',
                    dr_max=8,
                    pass_through_disk_limit=3):

    N_halos = normalization(f_sub, log_slope, m_host, mlow, mhigh)

    sample_orbits_0 = generate_sample_orbits_kde(N_halos, via_lactea_kde,
                                                 galactic_potential, time_Gyr)
    # print('generated ' + str(N_halos) + ' halos... ')
    rs_host, r_core = 25, 25.
    args, func = (rs_host, r_core), core_nfw_pdf
    inds_keep = filter_orbits_NFW(sample_orbits_0, time_Gyr, func, args)
    sample_orbits_1 = [sample_orbits_0[idx] for idx in inds_keep]

    nearby_orbits_1_inds, _ = passed_near_solar_neighorhood(
        sample_orbits_1,
        time_Gyr,
        global_potential_extension,
        R_solar=8,
        dr_max=dr_max,
        pass_through_disk_limit=pass_through_disk_limit,
        tdep=True)

    nearby_orbits_1 = [sample_orbits_0[idx] for idx in nearby_orbits_1_inds]
    n_nearby_1 = len(nearby_orbits_1)

    halo_masses_1 = sample_mass_function(n_nearby_1, log_slope, mlow, mhigh)

    halo_potentials = []

    if mdef == 'HERNQUIST':
        concentrations = sample_concentration_herquist(halo_masses_1, c8)

        for m, c in zip(halo_masses_1, concentrations):
            pot = HernquistPotential(amp=0.5 * m * apu.solMass, a=c * apu.kpc)
            halo_potentials.append(pot)

    elif mdef == 'NFW':
        concentrations = sample_concentration_nfw(halo_masses_1, c8)
        for m, c in zip(halo_masses_1, concentrations):
            pot = NFWPotential(mvir=m / 10**12, conc=c)
            halo_potentials.append(pot)

    subhalo_orbit_list = []
    for orb in nearby_orbits_1:
        orb.turn_physical_off()
        subhalo_orbit_list.append(orb)

    return subhalo_orbit_list, halo_potentials
Exemplo n.º 9
0
def galpy_nfw_orbit():
    # Setting up the potential
    nfw = NFWPotential(conc=C, mvir=M_200, H=70.0, wrtcrit=True, overdens=200)
    nfw.turn_physical_on()
    vxvv = [
        8.0 * units.kpc,
        0.0 * units.km / units.s,
        240.0 * units.km / units.s,
        0.0 * units.pc,
        5.0 * units.km / units.s,
    ]

    # Calculating the orbit
    ts = np.linspace(0.0, 0.58, 1000) * units.Gyr
    o = Orbit(vxvv=vxvv)
    o.integrate(ts, nfw, method="odeint")

    return o
Exemplo n.º 10
0
def obj(x,data,bp,dp,hp):
    #x=[fd,fh,vc,rs,hdisk]
    if x[0] > 1. or x[0] < 0.: return numpy.finfo(numpy.dtype(numpy.float64)).max
    if x[1] > 1. or x[1] < 0.: return numpy.finfo(numpy.dtype(numpy.float64)).max
    if (1.-x[0]-x[1]) > 1. or (1.-x[0]-x[1]) < 0.: return numpy.finfo(numpy.dtype(numpy.float64)).max
    if x[2] < 0. or x[3] < 0. or x[4] < 0.: return numpy.finfo(numpy.dtype(numpy.float64)).max
    if x[2] > 2. or x[3] > 10. or x[4] > 2.: return numpy.finfo(numpy.dtype(numpy.float64)).max
    if False:
        #Renormalize potentials, intially normalized to 1./3. each
        bp= copy.deepcopy(bp)
        dp= copy.deepcopy(dp)
        hp= copy.deepcopy(hp)
        bp._amp*= (1.-x[0]-x[1])**2.*9.
        dp._amp*= x[0]**2.*9.
        hp._amp*= x[1]**2.*9.
        #Re-define disk scale length and halo scale length
        if _dexp:
            dp._hr= x[4]
        else:
            dp._a= x[4]
        hp._a= x[3]
    else:
        #Set-up
        bp= HernquistPotential(a=0.6/_REFR0,normalize=1.-x[0]-x[1])
        if _dexp:
            dp= DoubleExponentialDiskPotential(normalize=x[0],
                                               hr=x[4],
                                               hz=0.3/_REFR0)
        else:
            dp= MiyamotoNagaiPotential(normalize=x[0],
                                       a=x[4],
                                       b=0.3/_REFR0)
        hp= NFWPotential(normalize=x[1],
                         a=x[3])
    #Re-normalize data
    vcircdata= copy.copy(data)
    vcircdata[:,1]/= x[2]
    vcircdata[:,2]/= x[2]
    #Vcirc chi2
    vcmodel= numpy.zeros(vcircdata.shape[0])
    for ii in range(vcircdata.shape[0]):
        vcmodel[ii]= numpy.sqrt(vcircdata[ii,0]\
                                    *numpy.fabs(evaluateRforces(vcircdata[ii,0],
                                                                0.,[bp,dp,hp])))
    #print vcircdata[:,0], vcmodel
    chi2= numpy.sum((vcircdata[:,1]-vcmodel)**2./vcircdata[:,2]**2.)
    #Add scale length measurement
    chi2+= (x[4]-_diskscale)**2./_diskscaleerr**2.
    #Add dark matter density at the Solar radius
    #print hp.dens(1.,0.),_rhodm*x[2]**2.
    #chi2+= (hp.dens(1.,0.)-_rhodm*x[2]**2.)**2./_rhodmerr**2./x[2]**4.
    return chi2
Exemplo n.º 11
0
def sample_galactic_potential(sigma_norm):

    sigma_scale = 1.

    mwpot = [
        PowerSphericalPotentialwCutoff(normalize=sigma_norm * 0.05,
                                       alpha=1.8,
                                       rc=sigma_scale * 1.9 / 8.),
        MiyamotoNagaiPotential(a=sigma_scale * 3. / 8.,
                               b=sigma_scale * 0.28 / 8.,
                               normalize=sigma_norm * 0.6),
        NFWPotential(a=sigma_scale * 2., normalize=sigma_norm * 0.35)
    ]

    return mwpot
Exemplo n.º 12
0
def test_overview():
    from galpy.potential import NFWPotential
    np= NFWPotential(normalize=1.)
    from galpy.orbit import Orbit
    o= Orbit(vxvv=[1.,0.1,1.1,0.1,0.02,0.])
    from galpy.actionAngle import actionAngleSpherical
    aA= actionAngleSpherical(pot=np)
    js= aA(o)
    assert numpy.fabs((js[0]-0.00980542)/js[0]) < 10.**-3., 'Action calculation in the overview section has changed'
    assert numpy.fabs((js[1]-1.1)/js[0]) < 10.**-3., 'Action calculation in the overview section has changed'
    assert numpy.fabs((js[2]-0.00553155)/js[0]) < 10.**-3., 'Action calculation in the overview section has changed'
    from galpy.df import quasiisothermaldf
    qdf= quasiisothermaldf(1./3.,0.2,0.1,1.,1.,
                           pot=np,aA=aA)
    assert numpy.fabs((qdf(o)-61.57476085)/61.57476085) < 10.**-3., 'qdf calculation in the overview section has changed'
    return None
Exemplo n.º 13
0
def MWPotential(Ms=0.76, rs=24.8, c=1., T=True):
    '''
        Milky Way potential from Marchetti 2017b -- see galpy for the definitions of the potential components

        Parameters
        ----------
            Ms : float
                NFW profile scale mass in units of e12 Msun
            rs : float
                Radial profile in units of kpc
            c : float
                Axis ratio
            T : bool
                If True, use triaxialNFWPotential
    '''

    # NFW profile
    Ms = Ms * 1e12 * u.Msun
    rs = rs * u.kpc

    #Disk
    Md = 1e11 * u.Msun
    ad = 6.5 * u.kpc
    bd = 260. * u.pc

    #Bulge
    Mb = 3.4 * 1e10 * u.Msun
    Rb = 0.7 * u.kpc

    #BH mass in 1e6 Msun
    Mbh = 4e6 * u.Msun
    if (T):
        halop = TriaxialNFWPotential(amp=Ms, a=rs, c=c, normalize=False)
    else:
        halop = NFWPotential(amp=Ms, a=rs, normalize=False)
    diskp = MiyamotoNagaiPotential(amp=Md, a=ad, b=bd, normalize=False)
    bulgep = HernquistPotential(
        amp=2 * Mb, a=Rb,
        normalize=False)  #Factor 2 because of the galpy definition
    bh = KeplerPotential(amp=Mbh, normalize=False)

    return [halop, diskp, bulgep, bh]
Exemplo n.º 14
0
    def test_minimize_func(self):

        disk_norm, nfw_norm = solve_normalizations(self.rho_nfw_target,
                                                   self.rho_midplane_target,
                                                   self.density_conversion)

        pot = [
            PowerSphericalPotentialwCutoff(normalize=0.05,
                                           alpha=1.8,
                                           rc=1.9 / 8.),
            MiyamotoNagaiPotential(a=3. / 8., b=0.28 / 8.,
                                   normalize=disk_norm),
            NFWPotential(a=2., normalize=nfw_norm)
        ]
        rho = evaluateDensities(pot, R=1., z=0) * self.density_conversion
        npt.assert_almost_equal(rho, self.rho_midplane_target, 3)

        disk_norm, nfw_norm = solve_normalizations(self.rho_nfw_target,
                                                   self.rho_nfw_target * 0.5,
                                                   self.density_conversion)
        npt.assert_equal(True, disk_norm < 0)
Exemplo n.º 15
0
    def calc_potentials_vs_time(self, differential=False, method='astropy'):
        """Calculates the gravitational potentials of each component for all redshift steps using galpy
        The gas and stars are represented by a double exponential profile by default. 
        The DM is represented by a NFW profile. 
        Can construct potential in both astropy units (method=='astropy') or galpy's 'natural' units (method=='natural') for the purposes of interpolation. 

        Stars can be calculated using a differential mass profile with varying scale radii, but in this case it is best to interpolate the potentials first
        """

        if method == 'astropy':
            print(
                "Calculating galactic potentials at each redshift using astropy units...\n"
            )
        elif method == 'natural':
            print(
                "Calculating galactic potentials at each redshift using galpy's natural units...\n"
            )
        else:
            raise NameError(
                'Method {0:s} for constructing the potential not recognized!'.
                format(method))

        # lists for saving potentials at each step
        stars_potentials = []
        gas_potentials = []
        dm_potentials = []
        full_potentials = []

        if self.disk_profile not in [
                'RazorThinExponential', 'DoubleExponential'
        ]:
            raise NameError('Disk profile {0:s} not recognized!'.format(
                self.disk_profile))

        # iterate over each time-step until when the sgrb occurred
        for ii, zz in enumerate(self.redz):

            # --- get the gas and DM masses at this step
            if method == 'astropy':
                mgas = self.mass_gas[ii]
                mdm = self.mass_dm[ii]
            elif method == 'natural':
                mgas = utils.Mphys_to_nat(self.mass_gas[ii])
                mdm = utils.Mphys_to_nat(self.mass_dm[ii])

            # --- if differential stellar potential not being used, just take the total stellar mass at each timestep
            # --- this is also done for the first differential timestep
            if (differential == False) or (ii == 0):
                if method == 'astropy':
                    mstar = self.mass_stars[ii]
                elif method == 'natural':
                    mstar = utils.Mphys_to_nat(self.mass_stars[ii])
            else:
                if method == 'astropy':
                    mstar = self.mass_stars[ii] - self.mass_stars[ii - 1]
                elif method == 'natural':
                    mstar = utils.Mphys_to_nat(self.mass_stars[ii] -
                                               self.mass_stars[ii - 1])

            # --- get the scale lengths for the baryons and halo at this redshift step
            if method == 'astropy':
                rs_baryons = self.Rscale_baryons[ii]
                rs_dm = self.Rscale_dm[ii]
                # if galaxy hasn't formed yet, give the potentials neglible scale sizes to avoid dividing by 0
                if rs_baryons == 0:
                    rs_baryons = 1e-10 * rs_baryons.unit
                if rs_dm == 0:
                    rs_dm = 1e-10 * rs_dm.unit
            elif method == 'natural':
                rs_baryons = utils.Rphys_to_nat(self.Rscale_baryons[ii])
                rs_dm = utils.Rphys_to_nat(self.Rscale_dm[ii])
                # if galaxy hasn't formed yet, give the potentials neglible scale sizes to avoid dividing by 0
                if rs_baryons == 0:
                    rs_baryons = 1e-10
                if rs_dm == 0:
                    rs_dm = 1e-10

            # --- construct the stellar and gas potentials

            if self.disk_profile == 'RazorThinExponential':
                # for a razor-thin disk, the amplitude is mdisk / (2 * pi * rs**2)
                amp_stars = mstar / (4 * np.pi * rs_baryons**2)
                amp_gas = mgas / (4 * np.pi * rs_baryons**2)
                # construct the potentials at this timestep
                stars_potential = RazorThinExponentialDiskPotential(
                    amp=amp_stars, hr=rs_baryons)
                gas_potential = RazorThinExponentialDiskPotential(
                    amp=amp_gas, hr=rs_baryons)

            elif self.disk_profile == 'DoubleExponential':
                # for a double exponential disk, the amplitude is mdisk / (2 * pi * rs**2 * 2*rz)
                amp_stars = mstar / (4 * np.pi * rs_baryons**2 *
                                     (self.z_scale * rs_baryons))
                amp_gas = mgas / (4 * np.pi * rs_baryons**2 *
                                  (self.z_scale * rs_baryons))
                stars_potential = DoubleExponentialDiskPotential(
                    amp=amp_stars, hr=rs_baryons, hz=self.z_scale * rs_baryons)
                gas_potential = DoubleExponentialDiskPotential(
                    amp=amp_gas, hr=rs_baryons, hz=self.z_scale * rs_baryons)

            # --- construct the DM potentials

            amp_dm = mdm
            dm_potential = NFWPotential(amp=amp_dm, a=rs_dm)

            # --- add the potentials to the lists for each step
            stars_potentials.append(stars_potential)
            gas_potentials.append(gas_potential)
            dm_potentials.append(dm_potential)

            # --- if differential is specified, we use *all* the stellar profiles up to this point
            if differential == True:
                combined_potentials = stars_potentials[:]
                combined_potentials.extend([gas_potential, dm_potential])
            else:
                combined_potentials = [
                    stars_potential, gas_potential, dm_potential
                ]
            full_potentials.append(combined_potentials)

        if method == 'astropy':
            self.stars_potentials = stars_potentials
            self.gas_potentials = gas_potentials
            self.dm_potentials = dm_potentials
            self.full_potentials = full_potentials
        if method == 'natural':
            self.stars_potentials_natural = stars_potentials
            self.gas_potentials_natural = gas_potentials
            self.dm_potentials_natural = dm_potentials
            self.full_potentials_natural = full_potentials

        return
Exemplo n.º 16
0
    def __init__(self,amp=1.,a=2.,b=1.,c=1.,zvec=None,pa=None,
                 normalize=False,
                 conc=None,mvir=None,
                 glorder=50,vo=None,ro=None,
                 H=70.,Om=0.3,overdens=200.,wrtcrit=False):
        """
        NAME:

           __init__

        PURPOSE:

           Initialize a triaxial NFW potential

        INPUT:

           amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass

           a - scale radius (can be Quantity)

           b - y-to-x axis ratio of the density

           c - z-to-x axis ratio of the density

           zvec= (None) If set, a unit vector that corresponds to the z axis

           pa= (None) If set, the position angle of the x axis

           glorder= (50) if set, compute the relevant force and potential integrals with Gaussian quadrature of this order

           normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1.


           Alternatively, NFW potentials can be initialized using 

              conc= concentration

              mvir= virial mass in 10^12 Msolar

           in which case you also need to supply the following keywords
           
              H= (default: 70) Hubble constant in km/s/Mpc
           
              Om= (default: 0.3) Omega matter
       
              overdens= (200) overdensity which defines the virial radius

              wrtcrit= (False) if True, the overdensity is wrt the critical density rather than the mean matter density
           
           ro=, vo= distance and velocity scales for translation into internal units (default from configuration file)

        OUTPUT:

           (none)

        HISTORY:

           2016-05-30 - Written - Bovy (UofT)

        """
        Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass')
        if _APY_LOADED and isinstance(a,units.Quantity):
            a= a.to(units.kpc).value/self._ro
        self.alpha= 1
        self.beta= 3
        self._b= b
        self._b2= self._b**2.
        self._c= c
        self._c2= self._c**2.
        self._setup_gl(glorder)
        self._setup_zvec_pa(zvec,pa)
        self._force_hash= None
        if conc is None:
            self.a= a
            if normalize or \
                    (isinstance(normalize,(int,float)) \
                         and not isinstance(normalize,bool)):
                self.normalize(normalize)
        else:
            from galpy.potential import NFWPotential
            dum= NFWPotential(mvir=mvir,conc=conc,ro=self._ro,vo=self._vo,
                              H=H,Om=Om,wrtcrit=wrtcrit,overdens=overdens)
            self.a= dum.a
            self._amp= dum._amp
        self._scale= self.a
        self.hasC= not self._glorder is None
        self.hasC_dxdv= False
        if not self._aligned or numpy.fabs(self._b-1.) > 10.**-10.:
            self.isNonAxi= True
        return None
Exemplo n.º 17
0
class MessyStreamData:

    nfwp = NFWPotential(normalize=1., a=14. / 10.)
    G = 4.302e-3  #pc*M_sun^-1*(km/s)^2

    def __init__(self,
                 mass,
                 rstream,
                 rsub,
                 impact,
                 subvel,
                 t,
                 nstars,
                 sigmav,
                 std,
                 psi0=[-20. / 180. * np.pi, 20 / 180. * np.pi]):
        self.nump = nstars
        self.psi0 = np.sort(np.random.uniform(psi0[0], psi0[1], size=nstars))

        self.vysigma = sigmav
        self.dxstd = std[0]
        self.dxdotstd = std[1]

        self.m = mass
        self.r0 = rstream
        self.rs = rsub
        self.b = impact
        self.wvec = subvel
        self.t = t * 1.0227  # Myr*1.02(pc/(km/s)/Myr) - converts to time in units pc/(km/s)
        self.vy = self.nfwp.vcirc(self.r0 / 10000.) * 168.2
        self.noisyvy = self.vy + np.random.normal(scale=self.vysigma,
                                                  size=nstars)

        self.wperp = np.sqrt(subvel[0]**2 + subvel[2]**2)
        self.wpar = self.noisyvy - subvel[1]
        self.w = np.sqrt(self.wperp**2 + self.wpar**2)

        self.gamma = self.calc_gamma()
        '''
        print('vy:',np.mean(self.noisyvy),np.std(self.noisyvy))
        print('gamma:',np.mean(self.gamma),np.std(self.gamma))
        print('wpar:',np.mean(self.wpar),np.std(self.wpar))
        print('wperp:',np.mean(self.wperp),np.std(self.wperp))
        print('w:',np.mean(self.w),np.std(self.w))
        print('tau:',np.mean(self.w*self.r0**2/2./self.G/self.m), np.std(self.w*self.r0**2/2./self.G/self.m))
        '''
        self.dv = self.calc_dv(self.psi0)
        self.psi, self.rho = self.calc_psi_rho(self.psi0)

        plt.figure()
        plt.plot(self.psi0, self.psi, '.')
        plt.xlabel(r'$\psi_o$ (rad)')
        plt.ylabel(r'$\psi$ (rad)')
        plt.show()

        self.dx = self.calc_dx(self.psi0, self.dxstd)
        self.dxdot = self.calc_dxdot(self.psi0, self.dxdotstd)

        sorteddata = np.array([
            (self.psi[i], self.dx[0, i], self.dx[1, i], self.dx[2, i],
             self.dxdot[0, i], self.dxdot[1, i], self.dxdot[2, i])
            for i in np.argsort(self.psi)
        ])
        self.data = [
            sorteddata[:, 0],
            np.transpose(sorteddata[:, 1:4]),
            np.transpose(sorteddata[:, 4:7])
        ]

    def gT(self):
        angle = self.gamma * self.noisyvy * self.t / self.r0
        return angle

    def calc_gamma(self):
        g = 3. + (self.r0 / 10000.)**2 * 168.2**2 * self.nfwp.R2deriv(
            self.r0 / 10000., 0.) / self.noisyvy**2.

        return np.sqrt(g)

    def calc_dv(self, psi0):
        M = self.m
        r0 = self.r0
        wperp = self.wperp
        wpar = self.wpar
        w = self.w
        wvec = self.wvec
        b = self.b
        rs = self.rs
        G = self.G
        nump = len(psi0)

        deltav = np.zeros([3, nump])
        deltav[0] = 2. * G * M / r0 / wperp**2 / w * (
            b * w**2 * wvec[2] / wperp -
            psi0 * wpar * wvec[0]) / (psi0**2 +
                                      (b**2 + rs**2 / r0**2) * w**2 / wperp**2)
        deltav[1] = -2. * G * M * psi0 / r0 / w / (
            psi0**2 + (b**2 + rs**2 / r0**2) * w**2 / wperp**2)
        deltav[2] = -2. * G * M / r0 / wperp**2 / w * (
            b * w**2 * wvec[0] / wperp +
            psi0 * wpar * wvec[2]) / (psi0**2 +
                                      (b**2 + rs**2 / r0**2) * w**2 / wperp**2)

        return deltav

    def calc_psi_rho(self, psi0):
        gam = self.calc_gamma()
        gT = self.gT()

        t = self.t
        r0 = self.r0
        vy = self.noisyvy
        wperp = self.wperp
        wpar = self.wpar
        wvec = self.wvec
        w = self.w
        b = self.b
        rs = self.rs

        tau = w * r0**2 / 2. / self.G / self.m

        f = (4. - gam**2) / gam**2 * t / tau - 4. * np.sin(
            gT) / gam**3 * r0 / vy / tau + 2. * (1. - np.cos(
                gT)) / gam**2 * wpar * wvec[0] / wperp**2 * r0 / vy / tau
        g = 2. * (1 - np.cos(gT)) * b * w**2 * wvec[2] * r0 / (
            gam**2 * wperp**3 * vy * tau)
        B2 = (b**2 + rs**2 / r0**2) * w**2 / wperp**2

        psi = psi0 + (f * psi0 - g) / (psi0**2 + B2) - (self.vy -
                                                        self.noisyvy) * t / r0
        rho = (1. + (f * B2 - f * psi0**2 + 2. * g * psi0) /
               (psi0**2 + B2)**2)**(-1)

        return psi, rho

    def calc_dx(self, psi0, std):
        r0 = self.r0
        vy = self.noisyvy
        dv = self.calc_dv(psi0)
        gam = self.calc_gamma()

        gT = self.gT()
        nump = len(psi0)

        noisedx = np.zeros([3, nump])
        noisedx[0] = np.random.normal(scale=std[0], size=nump)
        noisedx[2] = np.random.normal(scale=std[2], size=nump)

        deltax = np.zeros([3, nump])
        deltax[0] = 2. * r0 * dv[1] / vy * (
            1. - np.cos(gT)
        ) / gam**2 + r0 * dv[0] / vy * np.sin(gT) / self.gamma + noisedx[0]
        deltax[2] = dv[2] / vy * np.sin(vy * self.t / r0) + noisedx[2]

        return deltax

    def calc_dxdot(self, psi0, std):
        vy = self.noisyvy
        dv = self.calc_dv(psi0)
        gamma = self.calc_gamma()
        gT = self.gT()
        nump = len(psi0)

        noisedxdot = np.zeros([3, nump])

        for i in range(3):
            noisedxdot[i] = np.random.normal(scale=std[i], size=nump)

        deltaxdot = np.zeros([3, nump])
        deltaxdot[0] = 2. * dv[1] / gamma * np.sin(gT) + dv[0] * np.cos(
            gT) + noisedxdot[0]
        deltaxdot[1] = -dv[1] * (
            2. - gamma**2) / gamma**2 + 2. * dv[1] * np.cos(
                gT) / gamma**2 - dv[0] * np.sin(gT) / gamma + noisedxdot[1]
        deltaxdot[2] = dv[2] * np.cos(vy * self.t / self.r0) + noisedxdot[2]

        return deltaxdot
Exemplo n.º 18
0
    return gas_dens(R,z)+stellar_dens(R,z)+bulge_dens(R,z)+NFW_dens(R,z)

def sech(x):
    return 1./np.cosh(x)


#dicts used in DiskSCFPotential 
sigmadict = [{'type':'exp','h':Rd_HI,'amp':Sigma0_HI, 'Rhole':Rm_HI},
             {'type':'exp','h':Rd_H2,'amp':Sigma0_H2, 'Rhole':Rm_H2},
             {'type':'exp','h':Rd_thin,'amp':Sigma0_thin, 'Rhole':0.},
             {'type':'exp','h':Rd_thick,'amp':Sigma0_thick, 'Rhole':0.}]

hzdict = [{'type':'sech2', 'h':zd_HI},
          {'type':'sech2', 'h':zd_H2},
          {'type':'exp', 'h':0.3/ro},
          {'type':'exp', 'h':0.9/ro}]

#generate separate disk and halo potential - and combined potential
McMillan_bulge=\
    mySCFPotential(Acos=scf_compute_coeffs_axi(bulge_dens,20,10,a=0.1)[0],
                 a=0.1,ro=ro,vo=vo)
McMillan_disk = myDiskSCFPotential(dens=lambda R,z: gas_stellar_dens(R,z),
                                 Sigma=sigmadict, hz=hzdict,
                                 a=2.5, N=30, L=30,ro=ro,vo=vo)
McMillan_halo = NFWPotential(amp = rho0_halo*(4*np.pi*rh**3),
                             a = rh,ro=ro,vo=vo)
McMillan2017 = [McMillan_disk,McMillan_halo,McMillan_bulge]


                                                            
Exemplo n.º 19
0
	def test_calc_neg_grad_nfw(self):
		# Test that calculating the nfw negative gradient returns the same
		# results as galpy (but faster hopefully :))
		r_scale = 2
		rho_0 = 0.1
		G = 0.8962419740798497
		nfw = NFWPotential(a=r_scale,amp=rho_0*G*4*np.pi*r_scale**3)

		pos_nfw = np.zeros(3,dtype=np.float64)
		pos = np.zeros(3,dtype=np.float64)

		# Start just by testing a couple of different values of radius 1,2, and
		# 3.
		thetas = np.random.rand(10).astype(np.float64)*2*np.pi
		rs = np.array([1,2,3],dtype=np.float64)
		for theta in thetas:
			for r in rs:
				# Update the position
				pos[0] = r*np.cos(theta)
				pos[1] = r*np.sin(theta)

				# Compare both magnitudes
				r_force = nfw.Rforce(r,0)
				neg_grad = integrator.calc_neg_grad_nfw(rho_0,r_scale,pos_nfw,
					pos)

				self.assertAlmostEqual(np.abs(r_force),
					np.sqrt(np.sum(np.square(neg_grad))),places=5)

				# Ensure the direction is correct
				np.testing.assert_almost_equal(
					neg_grad/np.sqrt(np.sum(np.square(neg_grad))),
					-pos/r)

		# Check that the force softening scale is being used.
		thetas = np.random.rand(10).astype(np.float64)*2*np.pi
		rs = np.array([1,2,3],dtype=np.float64)
		force_softening = 1
		for theta in thetas:
			for r in rs:
				# Update the position
				pos[0] = r*np.cos(theta)
				pos[1] = r*np.sin(theta)

				# Compare both magnitudes
				r_force = nfw.Rforce(r,0)
				neg_grad = integrator.calc_neg_grad_nfw(rho_0,r_scale,pos_nfw,
					pos,force_softening=force_softening)

				self.assertGreater(np.abs(r_force),
					np.sqrt(np.sum(np.square(neg_grad))))

				# Ensure the direction is still correct
				np.testing.assert_almost_equal(
					neg_grad/np.sqrt(np.sum(np.square(neg_grad))),
					-pos/r)

		# Check that the box boundary is being used correctly
		box_length = 4
		pos[0] = 3
		pos[1] = 1
		r_force = nfw.Rforce(np.sqrt(2),0)
		neg_grad = integrator.calc_neg_grad_nfw(rho_0,r_scale,pos_nfw,pos,
			box_length=box_length)
		# Test that it's calculating the radius correctly
		self.assertAlmostEqual(np.abs(r_force),
			np.sqrt(np.sum(np.square(neg_grad))),places=5)
		# Test that the direction is correct
		np.testing.assert_almost_equal(
			neg_grad/np.sqrt(np.sum(np.square(neg_grad))),
			np.array([1.0,-1.0,0.0])/np.sqrt(2))
Exemplo n.º 20
0
def calc_es():
    savefilename= 'myes.sav'
    if os.path.exists(savefilename):
        savefile= open(savefilename,'rb')
        mye= pickle.load(savefile)
        e= pickle.load(savefile)
        savefile.close()
    else:
       #Read data
        dialect= csv.excel
        dialect.skipinitialspace=True
        reader= csv.reader(open('../data/Dierickx-etal-tab2.txt','r'),delimiter=' ',dialect=dialect)
        vxvs= []
        es= []
        vphis= []
        vxs= []
        vys= []
        vzs= []
        ls= []
        for row in reader:
            thisra= float(row[3])
            thisdec= float(row[4])
            thisd= float(row[17])/1000.
            thispmra= float(row[13])
            thispmdec= float(row[15])
            thisvlos= float(row[11])
            thise= float(row[26])
            vxvs.append([thisra,thisdec,thisd,thispmra,thispmdec,thisvlos])
            es.append(thise)
            vphis.append(float(row[25]))
            vxs.append(float(row[19]))
            vys.append(float(row[21]))
            vzs.append(float(row[23]))
            ls.append(float(row[5]))
        vxvv= nu.array(vxvs)
        e= nu.array(es)
        vphi= nu.array(vphis)
        vx= nu.array(vxs)
        vy= nu.array(vys)
        vz= nu.array(vzs)
        l= nu.array(ls)

        #Define potential
        lp= LogarithmicHaloPotential(normalize=1.)
        mp= MiyamotoNagaiPotential(a=0.5,b=0.0375,amp=1.,normalize=.6)
        np= NFWPotential(a=4.5,normalize=.35)
        hp= HernquistPotential(a=0.6/8,normalize=0.05)
        ts= nu.linspace(0.,20.,10000)
        
        mye= nu.zeros(len(e))
        for ii in range(len(e)):
           #Integrate the orbit
            o= Orbit(vxvv[ii,:],radec=True,vo=220.,ro=8.)
            o.integrate(ts,lp)
            mye[ii]= o.e()
            

        #Save
        savefile= open(savefilename,'wb')
        pickle.dump(mye,savefile)
        pickle.dump(e,savefile)
        savefile.close()

    #plot
    plot.print()
    plot.plot(nu.array([0.,1.]),nu.array([0.,1.]),'k-',
              xlabel=r'$\mathrm{Dierickx\ et\ al.}\ e$',
              ylabel=r'$\mathrm{galpy}\ e$')
    plot.plot(e,mye,'k,',overplot=True)
    plot.end_print('myee.png')

    plot.print()
    plot.hist(e,bins=30,xlabel=r'$\mathrm{Dierickx\ et\ al.}\ e$')
    plot.end_print('ehist.png')

    plot.print()
    plot.hist(mye,bins=30,xlabel=r'$\mathrm{galpy}\ e$')
    plot.end_print('myehist.png')
Exemplo n.º 21
0
def fitMass():
    #Read data
    vcircdata= numpy.loadtxt('vcirc.txt',comments='#',delimiter='|')
    vcircdata[:,0]/= _REFR0
    vcircdata[:,1]/= _REFV0
    vcircdata[:,2]/= _REFV0
    #Set-up
    bp= HernquistPotential(a=0.6/_REFR0,normalize=1./3.)
    if _dexp:
        dp= DoubleExponentialDiskPotential(normalize=1./3.,
                                           hr=3.25/_REFR0,
                                           hz=0.3/_REFR0)
    else:
        dp= MiyamotoNagaiPotential(normalize=1./3.,
                                   a=3.25/_REFR0,
                                   b=0.3/_REFR0)
    hp= NFWPotential(normalize=1./3.,
                     a=5./_REFR0)
    init= numpy.array([0.6,0.35,218./_REFV0,15./_REFR0,3.25/_REFR0])
    #print _convertrho
    out= optimize.fmin_powell(obj,init,args=(vcircdata,bp,dp,hp),
                              callback=cb)
    print out
    #Calculate mass
    #Halo mass
    halo= halomass(out)
    bulge= halomass(out)
    disk= diskmass(out)
    print halo, disk, bulge, totalmass(out)
    #Sample
    samples= bovy_mcmc.markovpy(out,0.05,
                                (lambda x: -obj(x,vcircdata,bp,dp,hp)),
                                (),
                                isDomainFinite=[[True,True],
                                                [True,True],
                                                [True,True],
                                                [True,True],
                                                [True,True]],
                                domain=[[0.,1.],
                                        [0.,1.],
                                        [0.,2.],
                                        [0.,10.],
                                        [0.,2.]],
                                nwalkers=8,
                                nsamples=10000)
    print "Done with sampling ..."
    print numpy.mean(numpy.array(samples),axis=0)
    print numpy.std(numpy.array(samples),axis=0)
    samples= numpy.random.permutation(samples)[0:500]
    #halo
    totalmasssamples= []
    for s in samples:
        totalmasssamples.append(halomass(s))
    totalmasssamples= numpy.array(totalmasssamples)
    print "halo mass: ", numpy.mean(totalmasssamples), numpy.std(totalmasssamples)
    print sixtyeigthinterval(halomass(out),totalmasssamples,quantile=.68)
    #total
    totalmasssamples= []
    for s in samples:
        totalmasssamples.append(totalmass(s))
    totalmasssamples= numpy.array(totalmasssamples)
    print "total mass: ", numpy.mean(totalmasssamples), numpy.std(totalmasssamples)
    print sixtyeigthinterval(totalmass(out),totalmasssamples,quantile=.68)
    bovy_plot.bovy_print()
    bovy_plot.bovy_hist(totalmasssamples,bins=16,range=[0.,2.])
    bovy_plot.bovy_end_print('totalmass.png')
    return None
    #disk
    totalmasssamples= []
    for s in samples:
        totalmasssamples.append(diskmass(s))
    totalmasssamples= numpy.array(totalmasssamples)
    print "disk mass: ", numpy.mean(totalmasssamples), numpy.std(totalmasssamples)
    #bulge
    totalmasssamples= []
    for s in samples:
        totalmasssamples.append(bulgemass(s))
    totalmasssamples= numpy.array(totalmasssamples)
    print "bulge mass: ", numpy.mean(totalmasssamples), numpy.std(totalmasssamples)
    return None
Exemplo n.º 22
0
def halomass(out):
    hp= NFWPotential(normalize=out[1],
                     a=out[3])
    return integrate.quad((lambda x: hp.dens(x,0.)*x**2.),0.,250./_REFR0)[0]*4.*numpy.pi*_convertmass/out[2]**2.
Exemplo n.º 23
0
    # 'corr_colnames':None,
}

cart_colnames = {
    # 'main_colnames':None,
    # 'error_colnames':None,
    # 'corr_colnames':None,
}

from galpy.potential import PowerSphericalPotentialwCutoff,\
    MiyamotoNagaiPotential, NFWPotential, verticalfreq, MWPotential2014

scale_height_factor = 2.0
bp = PowerSphericalPotentialwCutoff(alpha=1.8, rc=1.9 / 8., normalize=0.05)
mp = MiyamotoNagaiPotential(a=3. / 8., b=scale_height_factor * 0.28 / 8.,
                            normalize=.6)
np = NFWPotential(a=16 / 8., normalize=.35)
my_mwpotential2014 = [bp, mp, np]
orbit = {
    'potential': my_mwpotential2014, # TC: varied params randomly...
}

special = {
    'component':'sphere',       # parameterisation for the origin
}

advanced = {
    'burnin_steps':500,        # emcee parameters, number of steps for each burnin iteraton
    'sampling_steps':500,
}
Exemplo n.º 24
0
def calcj(rotcurve):
    if rotcurve == 'flat':
        savefilename = 'myjs.sav'
    elif rotcurve == 'power':
        savefilename = 'myjs_power.sav'
    if os.path.exists(savefilename):
        savefile = open(savefilename, 'rb')
        myjr = pickle.load(savefile)
        myjp = pickle.load(savefile)
        mywr = pickle.load(savefile)
        mywp = pickle.load(savefile)
        mye = pickle.load(savefile)
        myzmax = pickle.load(savefile)
        e = pickle.load(savefile)
        zmax = pickle.load(savefile)
        savefile.close()
    else:
        dialect = csv.excel
        dialect.skipinitialspace = True
        reader = csv.reader(open('../data/gcs.tsv', 'r'),
                            delimiter='|',
                            dialect=dialect)
        vxvs = []
        es = []
        zmaxs = []
        for row in reader:
            if row[0][0] == '#':
                continue
            thisra = row[0]
            thisdec = row[1]
            thisd = read_float(row[2]) / 1000.
            if thisd > 0.2: continue
            thisu = read_float(row[3])
            thisv = read_float(row[4])
            thisw = read_float(row[5])
            thise = read_float(row[6])
            thiszmax = read_float(row[7])
            if thisd == -9999 or thisu == -9999 or thisv == -9999 or thisw == -9999:
                continue
            vxvs.append([
                hms_to_rad(thisra),
                dms_to_rad(thisdec), thisd, thisu, thisv, thisw
            ])
            es.append(thise)
            zmaxs.append(thiszmax)
        vxvv = nu.array(vxvs)
        e = nu.array(es)
        zmax = nu.array(zmaxs)

        #Define potential
        lp = LogarithmicHaloPotential(normalize=1.)
        pp = PowerSphericalPotential(normalize=1., alpha=-2.)
        mp = MiyamotoNagaiPotential(a=0.5, b=0.0375, amp=1., normalize=.6)
        np = NFWPotential(a=4.5, normalize=.35)
        hp = HernquistPotential(a=0.6 / 8, normalize=0.05)
        ts = nu.linspace(0., 20., 10000)

        myjr = nu.zeros(len(e))
        myjp = nu.zeros(len(e))
        mywr = nu.zeros(len(e))
        mywp = nu.zeros(len(e))
        mye = nu.zeros(len(e))
        myzmax = nu.zeros(len(e))
        for ii in range(len(e)):
            #Integrate the orbit
            o = Orbit(vxvv[ii, :], radec=True, uvw=True, vo=220., ro=8.)
            #o.integrate(ts,[mp,np,hp])
            if rotcurve == 'flat':
                o.integrate(ts, lp)
                mye[ii] = o.e()
                myzmax[ii] = o.zmax() * 8.
                print e[ii], mye[ii], zmax[ii], myzmax[ii]
                o = o.toPlanar()
                myjr[ii] = o.jr(lp)[0]
            else:
                o = o.toPlanar()
                myjr[ii] = o.jr(pp)[0]
            myjp[ii] = o.jp()[0]
            mywr[ii] = o.wr()[0]
            mywp[ii] = o.wp()

        #Save
        savefile = open(savefilename, 'wb')
        pickle.dump(myjr, savefile)
        pickle.dump(myjp, savefile)
        pickle.dump(mywr, savefile)
        pickle.dump(mywp, savefile)
        pickle.dump(mye, savefile)
        pickle.dump(myzmax, savefile)
        pickle.dump(e, savefile)
        pickle.dump(zmax, savefile)
        savefile.close()

    #plot
    if rotcurve == 'flat':
        plot.bovy_print()
        plot.bovy_plot(nu.array([0., 1.]),
                       nu.array([0., 1.]),
                       'k-',
                       xlabel=r'$\mathrm{Holmberg\ et\ al.}\ e$',
                       ylabel=r'$\mathrm{galpy}\ e$')
        plot.bovy_plot(e, mye, 'k,', overplot=True)
        plot.bovy_end_print('myee.png')

        plot.bovy_print()
        plot.bovy_plot(
            nu.array([0., 2.5]),
            nu.array([0., 2.5]),
            'k-',
            xlabel=r'$\mathrm{Holmberg\ et\ al.}\ z_{\mathrm{max}}$',
            ylabel=r'$\mathrm{galpy}\ z_{\mathrm{max}}$')
        plot.bovy_plot(zmax, myzmax, 'k,', overplot=True)
        plot.bovy_end_print('myzmaxzmax.png')

    plot.bovy_print()
    plot.bovy_plot(myjp,
                   myjr / 2. / nu.pi,
                   'k,',
                   xlabel=r'$J_{\phi}$',
                   ylabel=r'$J_R/2\pi$',
                   xrange=[0.7, 1.3],
                   yrange=[0., 0.05])
    if rotcurve == 'flat':
        plot.bovy_end_print('jrjp.png')
    else:
        plot.bovy_end_print('jrjp_power.png')
Exemplo n.º 25
0
class Stream:
    '''
    These are the variables common to all streams.

    G - The gravitational constant
    nfwp - NFW potential for the host galaxy of the streams and subhalos
    nump - number of psi0 data points at which to calculate the different variables
    psi0 - the range of initial angles to the different points along the stream.
           psi0 = 0 occurs at the point of closest approach of the subhalo along the stream.
    '''

    nfwp = NFWPotential(normalize=1., a=14. / 10.)
    G = 4.302e-3  #pc*M_sun^-1*(km/s)^2
    nump = 1000
    '''
    There are several functions which can be called on the stream.

    __init__ - initializes a stream with the parameters passe to it and calculates several important parameters

        Input parameters:
            mass (Solar masses) - mass of the subhalo
            rstream (pc) - radius of the circular orbit of the stream around the galaxy centre
            rsub (pc) - Radius of the subhalo
            impact - impact parameter of the subhalo
            subvel (km/s) - Velocity of the subhalo in coordinates specified by the stream 
            t (Myr) - Time elapsed since time of closest approach of the subhalo

        Calculated parameters:
            wperp (km/s) - Perpendicular component of the relative velocity to points in the stream
            wpar (km/s) - Parallel component of relative velocity 
            w (km/s) - Total magnitude of relative velocity between the subha and points in the stream

            dv (km/s) - Calcultes the initial kick to velocity as a function of angle. Not time dependent.
            psi (radians) - Calculates the new angles to stars after a time, t.
            rho - Calculates the density of the stream after a time, t.
            dx (dx[0] (pc), dx[2] (radians)) - Change to position of stars in the stream after a time, t.
            dxdot (km/s) - Change in velocity of stars in the stream after a time, t.
            
            gT (radians) - calculates a very commonly used angle in several functions
            gamma - 
    '''
    def __init__(self,
                 mass,
                 rstream,
                 rsub,
                 impact,
                 subvel,
                 t,
                 psi0=[-20. / 180. * np.pi, 20 / 180. * np.pi]):

        self.psi0 = np.linspace(psi0[0], psi0[1], self.nump)

        self.m = mass
        self.r0 = rstream
        self.rs = rsub
        self.b = impact
        self.wvec = subvel
        self.t = t * 1.02  # Myr*1.02(pc/(km/s)/Myr) - converts to time in units pc/(km/s)
        self.vy = self.nfwp.vcirc(self.r0 / 10000.) * 168.2

        self.wperp = np.sqrt(subvel[0]**2 + subvel[2]**2)
        self.wpar = self.vy - subvel[1]
        self.w = np.sqrt(subvel[0]**2 + subvel[2]**2 +
                         (self.vy - subvel[1])**2)

        self.gamma = self.calc_gamma()
        self.dv = self.calc_dv(self.psi0)
        self.psi, self.rho = self.calc_psi_rho(self.psi0)
        self.dx = self.calc_dx(self.psi0)
        self.dxdot = self.calc_dxdot(self.psi0)

    def gT(self):
        angle = self.gamma * self.vy * self.t / self.r0
        return angle

    def calc_gamma(self):
        g = 3. + (self.r0 / 10000.)**2 * 168.2**2 * self.nfwp.R2deriv(
            self.r0 / 10000., 0.) / self.vy**2.

        return np.sqrt(g)

    def calc_dv(self, psi0):
        M = self.m
        r0 = self.r0
        wperp = self.wperp
        wpar = self.wpar
        w = self.w
        wvec = self.wvec
        b = self.b
        rs = self.rs
        G = self.G
        nump = len(psi0)

        deltav = np.zeros([3, nump])
        deltav[0] = 2. * G * M / r0 / wperp**2 / w * (
            b * w**2 * wvec[2] / wperp -
            psi0 * wpar * wvec[0]) / (psi0**2 +
                                      (b**2 + rs**2 / r0**2) * w**2 / wperp**2)
        deltav[1] = -2. * G * M * psi0 / r0 / w / (
            psi0**2 + (b**2 + rs**2 / r0**2) * w**2 / wperp**2)
        deltav[2] = -2. * G * M / r0 / wperp**2 / w * (
            b * w**2 * wvec[0] / wperp +
            psi0 * wpar * wvec[2]) / (psi0**2 +
                                      (b**2 + rs**2 / r0**2) * w**2 / wperp**2)

        return deltav

    def calc_psi_rho(self, psi0):
        gam = self.calc_gamma()
        gT = self.gT()

        t = self.t
        r0 = self.r0
        vy = self.vy
        wperp = self.wperp
        wpar = self.wpar
        wvec = self.wvec
        w = self.w
        b = self.b
        rs = self.rs

        tau = w * r0**2 / 2. / self.G / self.m

        f = (4. - gam**2) / gam**2 * t / tau - 4. * np.sin(
            gT) / gam**3 * r0 / vy / tau + 2. * (1. - np.cos(
                gT)) / gam**2 * wpar * wvec[0] / wperp**2 * r0 / vy / tau
        g = 2. * (1 - np.cos(gT)) * b * w**2 * wvec[2] * r0 / (
            gam**2 * wperp**3 * vy * tau)
        B2 = (b**2 + rs**2 / r0**2) * w**2 / wperp**2

        psi = psi0 + (f * psi0 - g) / (psi0**2 + B2)
        rho = (1. + (f * B2 - f * psi0**2 + 2. * g * psi0) /
               (psi0**2 + B2)**2)**(-1)

        return psi, rho

    def calc_dx(self, psi0):
        r0 = self.r0
        vy = self.vy
        dv = self.calc_dv(psi0)
        gam = self.calc_gamma()

        gT = self.gT()
        nump = len(psi0)

        deltax = np.zeros([3, nump])
        deltax[0] = 2. * r0 * dv[1] / vy * (1. - np.cos(
            gT)) / gam**2 + r0 * dv[0] / vy * np.sin(gT) / self.gamma
        deltax[2] = dv[2] / vy * np.sin(vy * self.t / r0)

        return deltax

    def calc_dxdot(self, psi0):
        vy = self.vy
        dv = self.calc_dv(psi0)
        gamma = self.calc_gamma()
        gT = self.gT()
        nump = len(psi0)

        deltaxdot = np.zeros([3, nump])
        deltaxdot[0] = 2. * dv[1] / gamma * np.sin(gT) + dv[0] * np.cos(gT)
        deltaxdot[1] = -dv[1] * (2. - gamma**2) / gamma**2 + 2. * dv[
            1] * np.cos(gT) / gamma**2 - dv[0] * np.sin(gT) / gamma
        deltaxdot[2] = dv[2] * np.cos(vy * self.t / self.r0)

        return deltaxdot