def test_nemo_LogarithmicHaloPotential(): lp = potential.LogarithmicHaloPotential(normalize=1.) tmax = 2. vo, ro = 210., 8.5 o = Orbit([1., 0.1, 1.1, 0.3, 0.1, 0.4], ro=ro, vo=vo) run_orbitIntegration_comparison(o, lp, tmax, vo, ro, tol=0.03) return None
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
def test_dynamfric_c_minr_warning(): from galpy.orbit import Orbit times = numpy.linspace(0., 100., 1001) #~3 Gyr at the Solar circle integrator = 'dop853_c' pot = potential.LogarithmicHaloPotential(normalize=1) # Setup orbit o = Orbit() # Setup dynamical friction object, with minr = 1, should thus reach it cdf= potential.ChandrasekharDynamicalFrictionForce(\ GMs=0.5553870441722593,rhm=5./8.,dens=pot,minr=1.) # Integrate, should raise warning with pytest.warns(None) as record: o.integrate(times, pot + cdf, method=integrator) raisedWarning = False for rec in record: # check that the message matches raisedWarning += ( str(rec.message.args[0]) == "Orbit integration with ChandrasekharDynamicalFrictionForce entered domain where r < minr and ChandrasekharDynamicalFrictionForce is turned off; initialize ChandrasekharDynamicalFrictionForce with a smaller minr to avoid this if you wish (but note that you want to turn it off close to the center for an object that sinks all the way to r=0, to avoid numerical instabilities)" ) assert raisedWarning, "Integrating an orbit that goes to r < minr with dynamical friction should have raised a warning, but didn't" return None
def test_dynamfric_c_minr(): from galpy.orbit import Orbit times = numpy.linspace(0., -100., 1001) #~3 Gyr at the Solar circle integrator = 'dop853_c' pot = potential.LogarithmicHaloPotential(normalize=1) # Setup orbit, ~ LMC o = Orbit([ 5.13200034, 1.08033051, 0.23323391, -3.48068653, 0.94950884, -1.54626091 ]) # Setup dynamical friction object, with minr = 130 st always 0 for this orbit cdf= potential.ChandrasekharDynamicalFrictionForce(\ GMs=0.5553870441722593,rhm=5./8.,dens=pot,minr=130./8.,maxr=500./8) # Integrate in C with dynamical friction o.integrate(times, pot + cdf, method=integrator) # Integrate in C without dynamical friction op = o() op.integrate(times, pot, method=integrator) # Compare r (most important) assert numpy.amax(numpy.fabs(o.r(times)-op.r(times))) < 10**-8., \ 'Dynamical friction in C does not properly use minr' return None
def predict_gd1obs(pot_params,c,b=1.,pa=0., sigv=0.4,td=10., phi1=0.,phi2=-1.,dist=10.2, pmphi1=-8.5,pmphi2=-2.05,vlos=-285., ro=_REFR0,vo=_REFV0, isob=None,nTrackChunks=8,multi=None, useTM=False, logpot=False, verbose=True): """ NAME: predict_gd1obs PURPOSE: Function that generates the location and velocity of the GD-1 stream (similar to predict_pal5obs), its width, and its length for a given potential and progenitor phase-space position INPUT: pot_params- array with the parameters of a potential model (see MWPotential2014Likelihood.setup_potential; only the basic parameters of the disk and halo are used, flattening is specified separately) c- halo flattening b= (1.) halo-squashed pa= (0.) halo PA sigv= (0.365) velocity dispersion in km/s td= (10.) stream age in Gyr phi1= (0.) Progenitor's phi1 position phi2= (-1.) Progenitor's phi2 position dist= (10.2) progenitor distance in kpc pmphi1= (-8.5) progenitor proper motion in phi1 in mas/yr pmphi2= (-2.) progenitor proper motion in phi2 in mas/yr vlos= (-250.) progenitor line-of-sight velocity in km/s ro= (project default) distance to the GC in kpc vo= (project default) circular velocity at R0 in km/s nTrackChunks= (8) nTrackChunks input to streamdf multi= (None) multi input to streamdf logpot= (False) if True, use a logarithmic potential instead isob= (None) if given, b parameter of actionAngleIsochroneApprox useTM= (True) use the TorusMapper to compute the track verbose= (True) print messages OUTPUT: HISTORY: 2016-08-12 - Written - Bovy (UofT) """ # Convert progenitor's phase-space position to l,b,... prog= Orbit(phi12_to_lb_6d(phi1,phi2,dist,pmphi1,pmphi2,vlos), lb=True,ro=ro,vo=vo,solarmotion=[-11.1,24.,7.25]) if logpot: pot= potential.LogarithmicHaloPotential(normalize=1.,q=c) else: pot= MWPotential2014Likelihood.setup_potential(pot_params,c, False,False,ro,vo, b=b,pa=pa) success= True this_useTM= useTM try: sdf= setup_sdf(pot,prog,sigv,td,ro,vo,multi=multi, isob=isob,nTrackChunks=nTrackChunks, verbose=verbose,useTM=useTM,logpot=logpot) except: # Catches errors and time-outs success= False # Check for calc. issues if not success: return (numpy.zeros((1001,6))-1000000.,False) # Try again with TM this_useTM= True this_nTrackChunks= 21 # might as well sdf= setup_sdf(pot,prog,sigv,td,ro,vo,multi=multi, isob=isob,nTrackChunks=this_nTrackChunks, verbose=verbose,useTM=this_useTM,logpot=logpot) else: success= not this_useTM # Compute the track and convert it to phi12 track_phi= convert_track_lb_to_phi12(sdf._interpolatedObsTrackLB) return (track_phi,success)
tsteady = 3 * u.Gyr N = 2 # number of arms alpha = 10 * u.deg # pitch angle, p in the paper H = 0.18 * u.kpc # arbitrary, not specified in the paper Rs = 0.38 sp = potential.SpiralArmsPotential(amp=1, N=N, alpha=alpha, H=H, Rs=Rs, phi_ref=0) phi_ref = -minimize(lambda phi: sp.dens(1, 0, phi[0]), x0=[0]).x[0] dfc = dehnendf(beta=0, profileParams=(1 / 3, 1, 0.01), correct=True, niter=20) lp = potential.LogarithmicHaloPotential(amp=1, normalize=1) # 30% density perturbation sp = potential.SpiralArmsPotential(amp=1, N=N, alpha=alpha, H=H, Rs=Rs, phi_ref=phi_ref) amp30 = abs(lp.dens(R=1, z=0, phi=0) / sp.dens(R=1, z=0, phi=0) * 0.3) n = 51 omegas = np.linspace(0, 2, 101) oortA_array = np.empty(len(omegas)) oortB_array = np.empty(len(omegas))
def predict_gd1obs( pot_params: Sequence[float], c: float, b: float = 1.0, pa: float = 0.0, sigv: float = 0.4, td: float = 10.0, dist: float = 10.2, pmphi1: float = -8.5, pmphi2: float = -2.05, phi1: float = 0.0, phi2: float = -1.0, vlos: float = -285.0, ro: float = REFR0, vo: float = REFV0, isob: Optional[bool] = None, nTrackChunks: int = 8, multi: Optional[Any] = None, useTM: bool = False, logpot: bool = False, verbose: bool = True, ): """Predict GD1-observed. Function that generates the location and velocity of the GD-1 stream, its width, and its length for a given potential and progenitor phase-space position. Parameters ---------- pot_params array with the parameters of a potential model (see mw_pot.setup_potential; only the basic parameters of the disk and halo are used, flattening is specified separately) c halo flattening b (1.) halo-squashed pa (0.) halo PA sigv (0.365) velocity dispersion in km/s td (10.) stream age in Gyr phi1 (0.) Progenitor's phi1 position phi2 (-1.) Progenitor's phi2 position dist (10.2) progenitor distance in kpc pmphi1 (-8.5) progenitor proper motion in phi1 in mas/yr pmphi2 (-2.) progenitor proper motion in phi2 in mas/yr vlos (-250.) progenitor line-of-sight velocity in km/s ro (project default) distance to the GC in kpc vo (project default) circular velocity at R0 in km/s nTrackChunks (8) nTrackChunks input to streamdf multi (None) multi input to streamdf logpot (False) if True, use a logarithmic potential instead isob (None) if given, b parameter of actionAngleIsochroneApprox useTM (True) use the TorusMapper to compute the track verbose (True) print messages Notes ----- 2016-08-12 - Written - Bovy (UofT) """ # Convert progenitor's phase-space position to l,b,... prog = Orbit( phi12_to_lb_6d(phi1, phi2, dist, pmphi1, pmphi2, vlos), lb=True, ro=ro, vo=vo, solarmotion=[-11.1, 24.0, 7.25], ) if logpot: pot = potential.LogarithmicHaloPotential(normalize=1.0, q=c) else: pot = mw_pot.setup_potential(pot_params, c, False, False, ro, vo, b=b, pa=pa) success: bool = True this_useTM = useTM try: sdf = setup_sdf( pot, prog, sigv, td, ro, vo, multi=multi, isob=isob, nTrackChunks=nTrackChunks, verbose=verbose, useTM=useTM, logpot=logpot, ) except: # Catches errors and time-outs success: bool = False # Check for calc. issues if not success: return (np.zeros((1001, 6)) - 1000000.0, False) # Try again with TM this_useTM = True this_nTrackChunks = 21 # might as well sdf = setup_sdf( pot, prog, sigv, td, ro, vo, multi=multi, isob=isob, nTrackChunks=this_nTrackChunks, verbose=verbose, useTM=this_useTM, logpot=logpot, ) else: success: bool = not this_useTM # Compute the track and convert it to phi12 track_phi = convert_track_lb_to_phi12(sdf._interpolatedObsTrackLB) return track_phi, success
def test_dynamfric_c(): import copy from galpy.orbit import Orbit from galpy.potential.Potential import _check_c from galpy.potential.mwpotentials import McMillan17 #Basic parameters for the test times = numpy.linspace(0., -100., 1001) #~3 Gyr at the Solar circle integrator = 'dop853_c' py_integrator = 'dop853' #Define all of the potentials (by hand, because need reasonable setup) MWPotential3021 = copy.deepcopy(potential.MWPotential2014) MWPotential3021[2] *= 1.5 # Increase mass by 50% pots= [potential.LogarithmicHaloPotential(normalize=1), potential.LogarithmicHaloPotential(normalize=1.3, q=0.9,b=0.7), #nonaxi potential.NFWPotential(normalize=1.,a=1.5), potential.MiyamotoNagaiPotential(normalize=.02,a=10.,b=10.), potential.MiyamotoNagaiPotential(normalize=.6,a=0.,b=3.), # special case potential.PowerSphericalPotential(alpha=2.3,normalize=2.), potential.DehnenSphericalPotential(normalize=4.,alpha=1.2), potential.DehnenCoreSphericalPotential(normalize=4.), potential.HernquistPotential(normalize=1.,a=3.5), potential.JaffePotential(normalize=1.,a=20.5), potential.DoubleExponentialDiskPotential(normalize=0.2, hr=3.,hz=0.6), potential.FlattenedPowerPotential(normalize=3.), potential.FlattenedPowerPotential(normalize=3.,alpha=0), #special case potential.IsochronePotential(normalize=2.), potential.PowerSphericalPotentialwCutoff(normalize=0.3,rc=10.), potential.PlummerPotential(normalize=.6,b=3.), potential.PseudoIsothermalPotential(normalize=.1,a=3.), potential.BurkertPotential(normalize=.2,a=2.5), potential.TriaxialHernquistPotential(normalize=1.,a=3.5, b=0.8,c=0.9), potential.TriaxialNFWPotential(normalize=1.,a=1.5,b=0.8,c=0.9), potential.TriaxialJaffePotential(normalize=1.,a=20.5,b=0.8,c=1.4), potential.PerfectEllipsoidPotential(normalize=.3,a=3.,b=0.7,c=1.5), potential.PerfectEllipsoidPotential(normalize=.3,a=3.,b=0.7,c=1.5, pa=3.,zvec=[0.,1.,0.]), #rotated potential.HomogeneousSpherePotential(normalize=0.02,R=82./8), # make sure to go to dens = 0 part, potential.interpSphericalPotential(\ rforce=potential.HomogeneousSpherePotential(normalize=0.02, R=82./8.), rgrid=numpy.linspace(0.,82./8.,201)), potential.TriaxialGaussianPotential(normalize=.03,sigma=4.,b=0.8,c=1.5,pa=3.,zvec=[1.,0.,0.]), potential.SCFPotential(Acos=numpy.array([[[1.]]]), # same as Hernquist normalize=1.,a=3.5), potential.SCFPotential(Acos=numpy.array([[[1.,0.],[.3,0.]]]), # nonaxi Asin=numpy.array([[[0.,0.],[1e-1,0.]]]), normalize=1.,a=3.5), MWPotential3021, McMillan17 # SCF + DiskSCF ] #tolerances in log10 tol = {} tol['default'] = -7. # Following are a little more difficult tol['DoubleExponentialDiskPotential'] = -4.5 tol['TriaxialHernquistPotential'] = -6. tol['TriaxialNFWPotential'] = -6. tol['TriaxialJaffePotential'] = -6. tol['MWPotential3021'] = -6. tol['HomogeneousSpherePotential'] = -6. tol['interpSphericalPotential'] = -6. # == HomogeneousSpherePotential tol['McMillan17'] = -6. for p in pots: if not _check_c(p, dens=True): continue # dynamfric not in C! pname = type(p).__name__ if pname == 'list': if isinstance(p[0],potential.PowerSphericalPotentialwCutoff) \ and len(p) > 1 \ and isinstance(p[1],potential.MiyamotoNagaiPotential) \ and len(p) > 2 \ and isinstance(p[2],potential.NFWPotential): pname = 'MWPotential3021' # Must be! else: pname = 'McMillan17' #print(pname) if pname in list(tol.keys()): ttol = tol[pname] else: ttol = tol['default'] # Setup orbit, ~ LMC o = Orbit([ 5.13200034, 1.08033051, 0.23323391, -3.48068653, 0.94950884, -1.54626091 ]) # Setup dynamical friction object if pname == 'McMillan17': cdf= potential.ChandrasekharDynamicalFrictionForce(\ GMs=0.5553870441722593,rhm=5./8.,dens=p,maxr=500./8,nr=101) ttimes = numpy.linspace(0., -30., 1001) #~1 Gyr at the Solar circle else: cdf= potential.ChandrasekharDynamicalFrictionForce(\ GMs=0.5553870441722593,rhm=5./8.,dens=p,maxr=500./8,nr=201) ttimes = times # Integrate in C o.integrate(ttimes, p + cdf, method=integrator) # Integrate in Python op = o() op.integrate(ttimes, p + cdf, method=py_integrator) # Compare r (most important) assert numpy.amax(numpy.fabs(o.r(ttimes)-op.r(ttimes))) < 10**ttol, \ 'Dynamical friction in C does not agree with dynamical friction in Python for potential {}'.format(pname) return None