def test_sigmar_wlog_constbeta_diffdens_powerlaw(): from galpy.potential import LogarithmicHaloPotential lp = LogarithmicHaloPotential(normalize=1., q=1.) rs = numpy.linspace(0.001, 5., 101) # general beta and r^-gamma --> sigma = vc/sqrt(gamma-2beta) gamma, beta = 1., 0. assert numpy.all( numpy.fabs( numpy.array([ jeans.sigmar(lp, r, beta=beta, dens=lambda r: r**-gamma) for r in rs ]) - 1. / numpy.sqrt(gamma - 2. * beta)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=0, and power-law density r^-1' gamma, beta = 3., 0.5 assert numpy.all( numpy.fabs( numpy.array([ jeans.sigmar(lp, r, beta=beta, dens=lambda r: r**-gamma) for r in rs ]) - 1. / numpy.sqrt(gamma - 2. * beta)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=0.5, and power-law density r^-3' gamma, beta = 0., -0.5 assert numpy.all( numpy.fabs( numpy.array([ jeans.sigmar(lp, r, beta=beta, dens=lambda r: r**-gamma) for r in rs ]) - 1. / numpy.sqrt(gamma - 2. * beta)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=-0.5, and power-law density r^0' return None
def test_sigmar_wlog_linbeta(): # for log halo, dens ~ r^-gamma, and beta = -b x r --> # sigmar = vc sqrt( scipy.special.gamma(-gamma)*scipy.special.gammaincc(-gamma,2*b*r)/[(2*b*r)**-gamma*exp(-2*b*r)] from scipy import special from galpy.potential import LogarithmicHaloPotential lp = LogarithmicHaloPotential(normalize=1., q=1.) rs = numpy.linspace(0.001, 5., 101) gamma, b = -0.1, 3. assert numpy.all( numpy.fabs( numpy.array([ jeans.sigmar( lp, r, beta=lambda x: -b * x, dens=lambda x: x**-gamma) - numpy.sqrt( special.gamma(-gamma) * special.gammaincc(-gamma, 2 * b * r) / ((2 * b * r)**-gamma * numpy.exp(-2. * b * r))) for r in rs ])) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta= -b*r, and dens ~ r^-gamma' gamma, b = -0.5, 4. assert numpy.all( numpy.fabs( numpy.array([ jeans.sigmar( lp, r, beta=lambda x: -b * x, dens=lambda x: x**-gamma) - numpy.sqrt( special.gamma(-gamma) * special.gammaincc(-gamma, 2 * b * r) / ((2 * b * r)**-gamma * numpy.exp(-2. * b * r))) for r in rs ])) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta= -b*r, and dens ~ r^-gamma' return None
def test_sigmar_wlog_constbeta(): from galpy.potential import LogarithmicHaloPotential lp= LogarithmicHaloPotential(normalize=1.,q=1.) rs= numpy.linspace(0.001,5.,101) # beta = 0 --> sigma = vc/sqrt(2) assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r) for r in rs])-1./numpy.sqrt(2.)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=0' # general beta --> sigma = vc/sqrt(2-2beta) beta= 0.5 assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=beta) for r in rs])-1./numpy.sqrt(2.-2.*beta)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=0.5' beta= -0.5 assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=beta) for r in rs])-1./numpy.sqrt(2.-2.*beta)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=-0.5' return None
def test_sigmar_wlog_linbeta(): # for log halo, dens ~ r^-gamma, and beta = -b x r --> # sigmar = vc sqrt( scipy.special.gamma(-gamma)*scipy.special.gammaincc(-gamma,2*b*r)/[(2*b*r)**-gamma*exp(-2*b*r)] from scipy import special from galpy.potential import LogarithmicHaloPotential lp= LogarithmicHaloPotential(normalize=1.,q=1.) rs= numpy.linspace(0.001,5.,101) gamma, b= -0.1, 3. assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=lambda x: -b*x,dens=lambda x: x**-gamma)-numpy.sqrt(special.gamma(-gamma)*special.gammaincc(-gamma,2*b*r)/((2*b*r)**-gamma*numpy.exp(-2.*b*r))) for r in rs])) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta= -b*r, and dens ~ r^-gamma' gamma, b= -0.5, 4. assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=lambda x: -b*x,dens=lambda x: x**-gamma)-numpy.sqrt(special.gamma(-gamma)*special.gammaincc(-gamma,2*b*r)/((2*b*r)**-gamma*numpy.exp(-2.*b*r))) for r in rs])) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta= -b*r, and dens ~ r^-gamma' return None
def test_sigmar_wlog_constbeta_diffdens_powerlaw(): from galpy.potential import LogarithmicHaloPotential lp= LogarithmicHaloPotential(normalize=1.,q=1.) rs= numpy.linspace(0.001,5.,101) # general beta and r^-gamma --> sigma = vc/sqrt(gamma-2beta) gamma, beta= 1.,0. assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=beta,dens=lambda r: r**-gamma) for r in rs])-1./numpy.sqrt(gamma-2.*beta)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=0, and power-law density r^-1' gamma, beta= 3.,0.5 assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=beta,dens=lambda r: r**-gamma) for r in rs])-1./numpy.sqrt(gamma-2.*beta)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=0.5, and power-law density r^-3' gamma, beta= 0.,-0.5 assert numpy.all(numpy.fabs(numpy.array([jeans.sigmar(lp,r,beta=beta,dens=lambda r: r**-gamma) for r in rs])-1./numpy.sqrt(gamma-2.*beta)) < 1e-10), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential, beta=-0.5, and power-law density r^0' return None
def check_sigmar_against_jeans_directint(dfi,pot,tol,beta=0., rmin=None,rmax=None,bins=31): """Check that sigma_r(r) obtained from integrating over the DF agrees with that coming from the Jeans equation""" rs= numpy.linspace(rmin,rmax,bins) intsr= numpy.array([dfi.sigmar(r,use_physical=False) for r in rs]) jeanssr= numpy.array([jeans.sigmar(pot,r,beta=beta,use_physical=False) for r in rs]) assert numpy.all(numpy.fabs(intsr/jeanssr-1) < tol), \ "sigma_r(r) from direct integration does not agree with that obtained from the Jeans equation" return None
def __setstate__(self, pdict): self.__dict__ = pdict # Re-setup _dens self._dens=\ lambda R,z,phi=0.,t=0.: evaluateDensities(self._dens_pot, R,z,phi=phi,t=t, use_physical=False) # Re-setup sigmar_orig if self._dens_kwarg is None and self._sigmar_kwarg is None: self.sigmar_orig = lambda x: _INVSQRTTWO else: from galpy.df import jeans self.sigmar_orig = lambda x: jeans.sigmar( self._dens_pot, x, beta=0., use_physical=False) return None
def test_sigmar_wlog_constbeta(): from galpy.potential import LogarithmicHaloPotential lp = LogarithmicHaloPotential(normalize=1., q=1.) rs = numpy.linspace(0.001, 5., 101) # beta = 0 --> sigma = vc/sqrt(2) assert numpy.all( numpy.fabs( numpy.array([jeans.sigmar(lp, r) for r in rs]) - 1. / numpy.sqrt(2.)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=0' # general beta --> sigma = vc/sqrt(2-2beta) beta = 0.5 assert numpy.all( numpy.fabs( numpy.array([jeans.sigmar(lp, r, beta=beta) for r in rs]) - 1. / numpy.sqrt(2. - 2. * beta)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=0.5' beta = -0.5 assert numpy.all( numpy.fabs( numpy.array([jeans.sigmar(lp, r, beta=beta) for r in rs]) - 1. / numpy.sqrt(2. - 2. * beta)) < 1e-10 ), 'Radial sigma computed w/ spherical Jeans equation incorrect for LogarithmicHaloPotential and beta=-0.5' return None
def check_sigmar_against_jeans_directint_forcevmoment(dfi,pot,tol,beta=0., rmin=None,rmax=None, bins=31): """Check that sigma_r(r) obtained from integrating over the DF agrees with that coming from the Jeans equation, using the general sphericaldf class' vmomentdensity""" from galpy.df.sphericaldf import sphericaldf rs= numpy.linspace(rmin,rmax,bins) intsr= numpy.array([numpy.sqrt(sphericaldf._vmomentdensity(dfi,r,2,0)/ sphericaldf._vmomentdensity(dfi,r,0,0)) for r in rs]) jeanssr= numpy.array([jeans.sigmar(pot,r,beta=beta,use_physical=False) for r in rs]) assert numpy.all(numpy.fabs(intsr/jeanssr-1) < tol), \ "sigma_r(r) from direct integration does not agree with that obtained from the Jeans equation" return None
def __setstate__(self,pdict): self.__dict__= pdict # Re-setup _dens self._dens=\ lambda R,z,phi=0.,t=0.: evaluateDensities(self._dens_pot, R,z,phi=phi,t=t, use_physical=False) # Re-setup sigmar_orig if self._dens_kwarg is None and self._sigmar_kwarg is None: self.sigmar_orig= lambda x: _INVSQRTTWO else: from galpy.df import jeans self.sigmar_orig= lambda x: jeans.sigmar(self._dens_pot,x,beta=0., use_physical=False) return None
def check_sigmar_against_jeans(samp,pot,tol,beta=0., rmin=None,rmax=None,bins=31): """Check that sigma_r(r) obtained from a sampling agrees with that coming from the Jeans equation Does this by logarithmically binning in r between rmin and rmax""" vrs= (samp.vR()*samp.R()+samp.vz()*samp.z())/samp.r() logrs= numpy.log(samp.r()) if rmin is None: numpy.exp(numpy.amin(logrs)) if rmax is None: numpy.exp(numpy.amax(logrs)) w,e= numpy.histogram(logrs,range=[numpy.log(rmin),numpy.log(rmax)], bins=bins,weights=numpy.ones_like(logrs)) mv2,_= numpy.histogram(logrs,range=[numpy.log(rmin),numpy.log(rmax)], bins=bins,weights=vrs**2.) samp_sigr= numpy.sqrt(mv2/w) brs= numpy.exp((numpy.roll(e,-1)+e)[:-1]/2.) for ii,br in enumerate(brs): assert numpy.fabs(samp_sigr[ii]/jeans.sigmar(pot,br,beta=beta, )-1.) < tol, \ "sigma_r(r) from samples does not agree with that obtained from the Jeans equation" return None
def __init__(self,amp=1.,GMs=.1,gamma=1.,rhm=0., dens=None,sigmar=None, const_lnLambda=False,minr=0.0001,maxr=25.,nr=501, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Chandrasekhar Dynamical Friction force INPUT: amp - amplitude to be applied to the potential (default: 1) GMs - satellite mass; can be a Quantity with units of mass or Gxmass; can be adjusted after initialization by setting obj.GMs= where obj is your ChandrasekharDynamicalFrictionForce instance (note that the mass of the satellite can *not* be changed simply by multiplying the instance by a number, because he mass is not only used as an amplitude) rhm - half-mass radius of the satellite (set to zero for a black hole; can be a Quantity); can be adjusted after initialization by setting obj.rhm= where obj is your ChandrasekharDynamicalFrictionForce instance gamma - Free-parameter in :math:`\\Lambda` dens - Potential instance or list thereof that represents the density [default: LogarithmicHaloPotential(normalize=1.,q=1.)] sigmar= (None) function that gives the velocity dispersion as a function of r (has to be in natural units!); if None, computed from the dens potential using the spherical Jeans equation (in galpy.df.jeans) assuming zero anisotropy cont_lnLambda= (False) if set to a number, use a constant ln(Lambda) instead with this value minr= (0.0001) minimum r at which to apply dynamical friction: at r < minr, friction is set to zero (can be a Quantity) Interpolation: maxr= (25) maximum r for which sigmar gets interpolated; for best performance set this to the maximum r you will consider (can be a Quantity) nr= (501) number of radii to use in the interpolation of sigmar You can check that sigmar is interpolated correctly by comparing the methods sigmar [the interpolated version] and sigmar_orig [the original or directly computed version] OUTPUT: (none) HISTORY: 2011-12-26 - Started - Bovy (NYU) 2018-03-18 - Re-started: updated to r dependent Lambda form and integrated into galpy framework - Bovy (UofT) 2018-07-23 - Calculate sigmar from the Jeans equation and interpolate it; allow GMs and rhm to be set on the fly - Bovy (UofT) """ DissipativeForce.__init__(self,amp=amp*GMs,ro=ro,vo=vo, amp_units='mass') if _APY_LOADED and isinstance(rhm,units.Quantity): rhm= rhm.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(minr,units.Quantity): minr= minr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(maxr,units.Quantity): maxr= maxr.to(units.kpc).value/self._ro self._gamma= gamma self._ms= self._amp/amp # from handling in __init__ above, should be ms in galpy units self._rhm= rhm self._minr= minr self._maxr= maxr # Parse density if dens is None: from .LogarithmicHaloPotential import LogarithmicHaloPotential dens= LogarithmicHaloPotential(normalize=1.,q=1.) if sigmar is None: # we know this solution! sigmar= lambda x: _INVSQRTTWO dens= flatten_pot(dens) self._dens_pot= dens self._dens=\ lambda R,z,phi=0.,t=0.: evaluateDensities(self._dens_pot, R,z,phi=phi,t=t, use_physical=False) if sigmar is None: from galpy.df import jeans sigmar= lambda x: jeans.sigmar(self._dens_pot,x,beta=0., use_physical=False) sigmar_rs= numpy.linspace(self._minr,self._maxr,nr) self.sigmar_orig= sigmar self.sigmar= interpolate.InterpolatedUnivariateSpline(\ sigmar_rs,numpy.array([sigmar(x) for x in sigmar_rs]),k=3) if const_lnLambda: self._lnLambda= const_lnLambda else: self._lnLambda= False self._amp*= 4.*numpy.pi self._force_hash= None return None
def __init__(self,amp=1.,GMs=.1,gamma=1.,rhm=0., dens=None,sigmar=None, const_lnLambda=False,minr=0.0001,maxr=25.,nr=501, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Chandrasekhar Dynamical Friction force INPUT: amp - amplitude to be applied to the potential (default: 1) GMs - satellite mass; can be a Quantity with units of mass or Gxmass; can be adjusted after initialization by setting obj.GMs= where obj is your ChandrasekharDynamicalFrictionForce instance (note that the mass of the satellite can *not* be changed simply by multiplying the instance by a number, because he mass is not only used as an amplitude) rhm - half-mass radius of the satellite (set to zero for a black hole; can be a Quantity); can be adjusted after initialization by setting obj.rhm= where obj is your ChandrasekharDynamicalFrictionForce instance gamma - Free-parameter in :math:`\\Lambda` dens - Potential instance or list thereof that represents the density [default: LogarithmicHaloPotential(normalize=1.,q=1.)] sigmar= (None) function that gives the velocity dispersion as a function of r (has to be in natural units!); if None, computed from the dens potential using the spherical Jeans equation (in galpy.df.jeans) assuming zero anisotropy; if set to a lambda function, *the object cannot be pickled* (so set it to a real function) cont_lnLambda= (False) if set to a number, use a constant ln(Lambda) instead with this value minr= (0.0001) minimum r at which to apply dynamical friction: at r < minr, friction is set to zero (can be a Quantity) Interpolation: maxr= (25) maximum r for which sigmar gets interpolated; for best performance set this to the maximum r you will consider (can be a Quantity) nr= (501) number of radii to use in the interpolation of sigmar You can check that sigmar is interpolated correctly by comparing the methods sigmar [the interpolated version] and sigmar_orig [the original or directly computed version] OUTPUT: (none) HISTORY: 2011-12-26 - Started - Bovy (NYU) 2018-03-18 - Re-started: updated to r dependent Lambda form and integrated into galpy framework - Bovy (UofT) 2018-07-23 - Calculate sigmar from the Jeans equation and interpolate it; allow GMs and rhm to be set on the fly - Bovy (UofT) """ DissipativeForce.__init__(self,amp=amp*GMs,ro=ro,vo=vo, amp_units='mass') if _APY_LOADED and isinstance(rhm,units.Quantity): rhm= rhm.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(minr,units.Quantity): minr= minr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(maxr,units.Quantity): maxr= maxr.to(units.kpc).value/self._ro self._gamma= gamma self._ms= self._amp/amp # from handling in __init__ above, should be ms in galpy units self._rhm= rhm self._minr= minr self._maxr= maxr self._dens_kwarg= dens # for pickling self._sigmar_kwarg= sigmar # for pickling # Parse density if dens is None: from .LogarithmicHaloPotential import LogarithmicHaloPotential dens= LogarithmicHaloPotential(normalize=1.,q=1.) if sigmar is None: # we know this solution! sigmar= lambda x: _INVSQRTTWO dens= flatten_pot(dens) self._dens_pot= dens self._dens=\ lambda R,z,phi=0.,t=0.: evaluateDensities(self._dens_pot, R,z,phi=phi,t=t, use_physical=False) if sigmar is None: from galpy.df import jeans sigmar= lambda x: jeans.sigmar(self._dens_pot,x,beta=0., use_physical=False) sigmar_rs= numpy.linspace(self._minr,self._maxr,nr) self.sigmar_orig= sigmar self.sigmar= interpolate.InterpolatedUnivariateSpline(\ sigmar_rs,numpy.array([sigmar(x) for x in sigmar_rs]),k=3) if const_lnLambda: self._lnLambda= const_lnLambda else: self._lnLambda= False self._amp*= 4.*numpy.pi self._force_hash= None return None