From fd02032da19f02367f45f57473c9bd94ad52ca33 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 4 Dec 2017 17:59:52 -0500 Subject: [PATCH 01/62] Add conversion of momenta in spheroidal coordinates to cylindrical coordinates --- galpy/util/bovy_coords.py | 44 +++++++++++++++++++++++++++++++++++++++ tests/test_coords.py | 30 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/galpy/util/bovy_coords.py b/galpy/util/bovy_coords.py index eba2de4bc..7be59151c 100644 --- a/galpy/util/bovy_coords.py +++ b/galpy/util/bovy_coords.py @@ -1860,6 +1860,50 @@ def vRvz_to_pupv(vR,vz,R,z,delta=1.,oblate=False,uv=False): pv= delta*(vR*sc.sinh(u)*sc.cos(v)-vz*sc.cosh(u)*sc.sin(v)) return (pu,pv) +def pupv_to_vRvz(pu,pv,u,v,delta=1.,oblate=False): + """ + NAME: + + pupv_to_vRvz + + PURPOSE: + + calculate cylindrical vR and vz from momenta in prolate or oblate confocal u and v coordinatesfor a given focal length delta + + INPUT: + + pu - u momentum + + pv - v momentum + + u - u coordinate + + v - v coordinate + + delta= focus + + oblate= (False) if True, compute oblate confocal coordinates instead of prolate + + + OUTPUT: + + (vR,vz) + + HISTORY: + + 2017-12-04 - Written - Bovy (UofT) + + """ + if oblate: + denom= delta*(sc.sinh(u)**2.+sc.cos(v)**2.) + vR= (pu*sc.sinh(u)*sc.sin(v)+pv*sc.cosh(u)*sc.cos(v))/denom + vz= (pu*sc.cosh(u)*sc.cos(v)-pv*sc.sinh(u)*sc.sin(v))/denom + else: + denom= delta*(sc.sinh(u)**2.+sc.sin(v)**2.) + vR= (pu*sc.cosh(u)*sc.sin(v)+pv*sc.sinh(u)*sc.cos(v))/denom + vz= (pu*sc.sinh(u)*sc.cos(v)-pv*sc.cosh(u)*sc.sin(v))/denom + return (vR,vz) + def Rz_to_lambdanu(R,z,ac=5.,Delta=1.): """ NAME: diff --git a/tests/test_coords.py b/tests/test_coords.py index 3b0c5e9cc..a6a2fa49a 100644 --- a/tests/test_coords.py +++ b/tests/test_coords.py @@ -1015,6 +1015,36 @@ def test_vRvz_to_pupv_oblate(): assert numpy.fabs(bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True)[1]-bovy_coords.vRvz_to_pupv(vR,vz,*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True,uv=True)[1]) < 10.**-3., 'vRvz_to_pupv with and without pre-computed u,v do not agree for oblate spheroidal coordinates' return None +def test_pupv_to_vRvz(): + # Test that this is the inverse of vRvz_to_pupv + delta= 0.5 + R,z= delta/2., delta*3. + vR, vz= 0.2,-0.5 + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + # Another one + delta= 1.5 + R,z= delta*2., -delta/3. + vR, vz= -0.2,0.5 + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + return None + +def test_pupv_to_vRvz_oblate(): + # Test that this is the inverse of vRvz_to_pupv + delta= 0.5 + R,z= delta/2., delta*3. + vR, vz= 0.2,-0.5 + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + # Another one + delta= 1.5 + R,z= delta*2., -delta/3. + vR, vz= -0.2,0.5 + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + return None + def test_lbd_to_XYZ_jac(): #Just position l,b,d= 180.,30.,2. From 941141ea5b8b5457e388cc31863695c645875029 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 4 Dec 2017 20:46:06 -0500 Subject: [PATCH 02/62] Fix python 2.7 syntax error in pupv test --- tests/test_coords.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/test_coords.py b/tests/test_coords.py index a6a2fa49a..8b321b867 100644 --- a/tests/test_coords.py +++ b/tests/test_coords.py @@ -1020,14 +1020,18 @@ def test_pupv_to_vRvz(): delta= 0.5 R,z= delta/2., delta*3. vR, vz= 0.2,-0.5 - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + u,v= bovy_coords.Rz_to_uv(R,z,delta=delta) + pu,pv= bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta) + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' # Another one delta= 1.5 R,z= delta*2., -delta/3. vR, vz= -0.2,0.5 - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta),*bovy_coords.Rz_to_uv(R,z,delta=delta),delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + u,v= bovy_coords.Rz_to_uv(R,z,delta=delta) + pu,pv= bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta) + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' return None def test_pupv_to_vRvz_oblate(): @@ -1035,14 +1039,18 @@ def test_pupv_to_vRvz_oblate(): delta= 0.5 R,z= delta/2., delta*3. vR, vz= 0.2,-0.5 - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + u,v= bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True) + pu,pv= bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True) + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' # Another one delta= 1.5 R,z= delta*2., -delta/3. vR, vz= -0.2,0.5 - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' - assert numpy.fabs(bovy_coords.pupv_to_vRvz(*bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True),*bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True),delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + u,v= bovy_coords.Rz_to_uv(R,z,delta=delta,oblate=True) + pu,pv= bovy_coords.vRvz_to_pupv(vR,vz,R,z,delta=delta,oblate=True) + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta,oblate=True)[0]-vR) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' + assert numpy.fabs(bovy_coords.pupv_to_vRvz(pu,pv,u,v,delta=delta,oblate=True)[1]-vz) < 1e-8, 'pupv_to_vRvz is not the inverse of vRvz_to_pupv' return None def test_lbd_to_XYZ_jac(): From c5a0b5606391f0bc74fc01e54c942717ffc6bff9 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 4 Dec 2017 20:48:44 -0500 Subject: [PATCH 03/62] Add docs for new pupv_to_vRvz function --- doc/source/reference/bovycoords.rst | 1 + doc/source/reference/coordspupvtovRvz.rst | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 doc/source/reference/coordspupvtovRvz.rst diff --git a/doc/source/reference/bovycoords.rst b/doc/source/reference/bovycoords.rst index 1e66145bf..9e7b2366c 100644 --- a/doc/source/reference/bovycoords.rst +++ b/doc/source/reference/bovycoords.rst @@ -24,6 +24,7 @@ self-explanatory names: pmllpmbb_to_pmrapmdec pmrapmdec_to_pmllpmbb pmrapmdec_to_custom + pupv_to_vRvz radec_to_lb radec_to_custom rectgal_to_sphergal diff --git a/doc/source/reference/coordspupvtovRvz.rst b/doc/source/reference/coordspupvtovRvz.rst new file mode 100644 index 000000000..9d13c9949 --- /dev/null +++ b/doc/source/reference/coordspupvtovRvz.rst @@ -0,0 +1,4 @@ +galpy.util.bovy_coords.pupv_to_vRvz +=========================================== + +.. autofunction:: galpy.util.bovy_coords.pupv_to_vRvz \ No newline at end of file From bd8b52542dbaee83f322e0529ad5ac889eb9689b Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 4 Dec 2017 20:55:36 -0500 Subject: [PATCH 04/62] Tweak fix to pupv_to_vRvz docs [ci skip] --- galpy/util/bovy_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galpy/util/bovy_coords.py b/galpy/util/bovy_coords.py index 7be59151c..fbb125ec5 100644 --- a/galpy/util/bovy_coords.py +++ b/galpy/util/bovy_coords.py @@ -1868,7 +1868,7 @@ def pupv_to_vRvz(pu,pv,u,v,delta=1.,oblate=False): PURPOSE: - calculate cylindrical vR and vz from momenta in prolate or oblate confocal u and v coordinatesfor a given focal length delta + calculate cylindrical vR and vz from momenta in prolate or oblate confocal u and v coordinates for a given focal length delta INPUT: From ce693b4ff4ea0ac0acfbf038708bf0d89e8dc325 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 12 Dec 2017 22:56:23 -0500 Subject: [PATCH 05/62] Add C function to compute umin,umax,vmin in the Staeckel approximation --- .../actionAngle_c_ext/actionAngleStaeckel.c | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c index 4f77cfc81..812a36341 100644 --- a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c +++ b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c @@ -87,6 +87,10 @@ struct u0EqArg{ Function Declarations */ void calcu0(int,double *,double *,int,int *,double *,double,double *,int *); +void actionAngleStaeckel_uminUmaxVmin(int,double *,double *,double *,double *, + double *,double *,int,int *,double *, + double,double *, + double *,double *,int *); void actionAngleStaeckel_actions(int,double *,double *,double *,double *, double *,double *,int,int *,double *,double, double *,double *,int *); @@ -271,6 +275,110 @@ void calcu0(int ndata, free(actionAngleArgs); *err= status; } +void actionAngleStaeckel_uminUmaxVmin(int ndata, + double *R, + double *vR, + double *vT, + double *z, + double *vz, + double *u0, + int npot, + int * pot_type, + double * pot_args, + double delta, + double *umin, + double *umax, + double *vmin, + int * err){ + // Just copied this over from actionAngleStaeckel_actions below, not elegant + // but does the job... + int ii; + //Set up the potentials + struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); + parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); + //E,Lz + double *E= (double *) malloc ( ndata * sizeof(double) ); + double *Lz= (double *) malloc ( ndata * sizeof(double) ); + calcEL(ndata,R,vR,vT,z,vz,E,Lz,npot,actionAngleArgs); + //Calculate all necessary parameters + double *ux= (double *) malloc ( ndata * sizeof(double) ); + double *vx= (double *) malloc ( ndata * sizeof(double) ); + Rz_to_uv_vec(ndata,R,z,ux,vx,delta); + double *coshux= (double *) malloc ( ndata * sizeof(double) ); + double *sinhux= (double *) malloc ( ndata * sizeof(double) ); + double *sinvx= (double *) malloc ( ndata * sizeof(double) ); + double *cosvx= (double *) malloc ( ndata * sizeof(double) ); + double *pux= (double *) malloc ( ndata * sizeof(double) ); + double *pvx= (double *) malloc ( ndata * sizeof(double) ); + double *sinh2u0= (double *) malloc ( ndata * sizeof(double) ); + double *cosh2u0= (double *) malloc ( ndata * sizeof(double) ); + double *v0= (double *) malloc ( ndata * sizeof(double) ); + double *sin2v0= (double *) malloc ( ndata * sizeof(double) ); + double *potu0v0= (double *) malloc ( ndata * sizeof(double) ); + double *potupi2= (double *) malloc ( ndata * sizeof(double) ); + double *I3U= (double *) malloc ( ndata * sizeof(double) ); + double *I3V= (double *) malloc ( ndata * sizeof(double) ); + UNUSED int chunk= CHUNKSIZE; +#pragma omp parallel for schedule(static,chunk) private(ii) + for (ii=0; ii < ndata; ii++){ + *(coshux+ii)= cosh(*(ux+ii)); + *(sinhux+ii)= sinh(*(ux+ii)); + *(cosvx+ii)= cos(*(vx+ii)); + *(sinvx+ii)= sin(*(vx+ii)); + *(pux+ii)= delta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); + *(pvx+ii)= delta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) + - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); + *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); + *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); + *(v0+ii)= 0.5 * M_PI; //*(vx+ii); + *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); + *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),delta, + npot,actionAngleArgs); + *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) + - 0.5 * *(pux+ii) * *(pux+ii) / delta / delta + - 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinhux+ii) / *(sinhux+ii) + - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) + *evaluatePotentialsUV(*(ux+ii),*(v0+ii),delta, + npot,actionAngleArgs) + + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); + *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,delta, + npot,actionAngleArgs); + *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) + + 0.5 * *(pvx+ii) * *(pvx+ii) / delta / delta + + 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinvx+ii) / *(sinvx+ii) + - *(cosh2u0+ii) * *(potupi2+ii) + + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) + * evaluatePotentialsUV(*(u0+ii),*(vx+ii),delta, + npot,actionAngleArgs); + } + //Calculate 'peri' and 'apo'centers + calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + potu0v0,npot,actionAngleArgs); + calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, + npot,actionAngleArgs); + //Free + free_potentialArgs(npot,actionAngleArgs); + free(actionAngleArgs); + free(E); + free(Lz); + free(ux); + free(vx); + free(coshux); + free(sinhux); + free(sinvx); + free(cosvx); + free(pux); + free(pvx); + free(sinh2u0); + free(cosh2u0); + free(v0); + free(sin2v0); + free(potu0v0); + free(potupi2); + free(I3U); + free(I3V); +} void actionAngleStaeckel_actions(int ndata, double *R, double *vR, From 01beeb372a2cfa359748a20b32f70be48db1c9ff Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 12 Dec 2017 22:57:02 -0500 Subject: [PATCH 06/62] Compute eccentricity, zmax, rperi, and rap in the Staeckel approximation --- galpy/actionAngle_src/actionAngleStaeckel.py | 131 +++++++++++++++++- .../actionAngle_src/actionAngleStaeckel_c.py | 91 ++++++++++++ galpy/util/bovy_conversion.py | 21 ++- 3 files changed, 235 insertions(+), 8 deletions(-) diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index 42bb5b0a7..eed4b5fbb 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -21,7 +21,8 @@ from galpy.util import bovy_coords #for prolate confocal transforms from galpy.util import galpyWarning from galpy.util.bovy_conversion import physical_conversion, \ - potential_physical_input + potential_physical_input, physical_conversion_actionAngle, \ + actionAngle_physical_input from galpy.actionAngle_src.actionAngle import actionAngle, UnboundError import galpy.actionAngle_src.actionAngleStaeckel_c as actionAngleStaeckel_c from galpy.actionAngle_src.actionAngleStaeckel_c import _ext_loaded as ext_loaded @@ -86,6 +87,46 @@ def __init__(self,*args,**kwargs): self._check_consistent_units() return None + @actionAngle_physical_input + @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) + def EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-12 - Written - Bovy (UofT) + + """ + umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) + rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=self._delta)[0] + rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=self._delta) + rap= nu.sqrt(rap_tmp**2.+zmax**2.) + e= (rap-rperi)/(rap+rperi) + return (e,zmax,rperi,rap) + def _evaluate(self,*args,**kwargs): """ NAME: @@ -312,6 +353,94 @@ def _actionsFreqsAngles(self,*args,**kwargs): warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) raise NotImplementedError("actionsFreqs with c=False not implemented") + def _uminumaxvmin(self,*args,**kwargs): + """ + NAME: + _uminumaxvmin + PURPOSE: + evaluate u_min, u_max, and v_min + INPUT: + Either: + a) R,vR,vT,z,vz + b) Orbit instance: initial condition used if that's it, orbit(t) + if there is a time given as well + c= True/False; overrides the object's c= keyword to use C or not + OUTPUT: + (umin,umax,vmin) + HISTORY: + 2017-12-12 - Written - Bovy (UofT) + """ + if ((self._c and not ('c' in kwargs and not kwargs['c']))\ + or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ + and _check_c(self._pot): + if len(args) == 5: #R,vR.vT, z, vz + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + if isinstance(R,float): + R= nu.array([R]) + vR= nu.array([vR]) + vT= nu.array([vT]) + z= nu.array([z]) + vz= nu.array([vz]) + Lz= R*vT + if self._useu0: + #First calculate u0 + if 'u0' in kwargs: + u0= nu.asarray(kwargs['u0']) + else: + E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) + +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) + u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(E,Lz, + self._pot, + self._delta)[0] + kwargs.pop('u0',None) + else: + u0= None + umin, umax, vmin, err= \ + actionAngleStaeckel_c.actionAngleUminUmaxVminStaeckel_c(\ + self._pot,self._delta,R,vR,vT,z,vz,u0=u0) + if err == 0: + return (umin,umax,vmin) + else: #pragma: no cover + raise RuntimeError("C-code for calculation actions failed; try with c=False") + else: + if 'c' in kwargs and kwargs['c'] and not self._c: #pragma: no cover + warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) + kwargs.pop('c',None) + if (len(args) == 5 or len(args) == 6) \ + and isinstance(args[0],nu.ndarray): + oumin= nu.zeros((len(args[0]))) + oumax= nu.zeros((len(args[0]))) + ovmin= nu.zeros((len(args[0]))) + for ii in range(len(args[0])): + if len(args) == 5: + targs= (args[0][ii],args[1][ii],args[2][ii], + args[3][ii],args[4][ii]) + elif len(args) == 6: + targs= (args[0][ii],args[1][ii],args[2][ii], + args[3][ii],args[4][ii],args[5][ii]) + tumin,tumax,tvmin= self._uminumaxvmin(\ + *targs,**copy.copy(kwargs)) + oumin[ii]= tumin + oumax[ii]= tumax + ovmin[ii]= tvmin + return (oumin,oumax,ovmin) + else: + #Set up the actionAngleStaeckelSingle object + aASingle= actionAngleStaeckelSingle(*args,pot=self._pot, + delta=self._delta) + umin, umax= aASingle.calcUminUmax() + vmin= aASingle.calcVmin() + return (umin,umax,vmin) + class actionAngleStaeckelSingle(actionAngle): """Action-angle formalism for axisymmetric potentials using Binney (2012)'s Staeckel approximation""" def __init__(self,*args,**kwargs): diff --git a/galpy/actionAngle_src/actionAngleStaeckel_c.py b/galpy/actionAngle_src/actionAngleStaeckel_c.py index 367095cc2..f933df0fb 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel_c.py +++ b/galpy/actionAngle_src/actionAngleStaeckel_c.py @@ -405,3 +405,94 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): return (jr,jz,Omegar,Omegaphi,Omegaz,Angler, Anglephi,Anglez,err.value) +def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): + """ + NAME: + actionAngleUminUmaxVminStaeckel_c + PURPOSE: + Use C to calculate umin, umax, and vmin using the Staeckel approximation + INPUT: + pot - Potential or list of such instances + delta - focal length of prolate spheroidal coordinates + R, vR, vT, z, vz - coordinates (arrays) + OUTPUT: + (umin,umax,vmin,err) + umin,umax,vmin : array, shape (len(R)) + err - non-zero if error occured + HISTORY: + 2017-12-12 - Written - Bovy (UofT) + """ + if u0 is None: + u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=delta) + #Parse the potential + npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + + #Set up result arrays + umin= numpy.empty(len(R)) + umax= numpy.empty(len(R)) + vmin= numpy.empty(len(R)) + err= ctypes.c_int(0) + + #Set up the C code + ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') + actionAngleStaeckel_actionsFunc= _lib.actionAngleStaeckel_uminUmaxVmin + actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_int, + ndpointer(dtype=numpy.int32,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_double, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.POINTER(ctypes.c_int)] + + #Array requirements, first store old order + f_cont= [R.flags['F_CONTIGUOUS'], + vR.flags['F_CONTIGUOUS'], + vT.flags['F_CONTIGUOUS'], + z.flags['F_CONTIGUOUS'], + vz.flags['F_CONTIGUOUS'], + u0.flags['F_CONTIGUOUS']] + R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) + vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) + vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) + z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) + vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) + u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) + umin= numpy.require(umin,dtype=numpy.float64,requirements=['C','W']) + umax= numpy.require(umax,dtype=numpy.float64,requirements=['C','W']) + vmin= numpy.require(vmin,dtype=numpy.float64,requirements=['C','W']) + + #Run the C code + actionAngleStaeckel_actionsFunc(len(R), + R, + vR, + vT, + z, + vz, + u0, + ctypes.c_int(npot), + pot_type, + pot_args, + ctypes.c_double(delta), + umin, + umax, + vmin, + ctypes.byref(err)) + + #Reset input arrays + if f_cont[0]: R= numpy.asfortranarray(R) + if f_cont[1]: vR= numpy.asfortranarray(vR) + if f_cont[2]: vT= numpy.asfortranarray(vT) + if f_cont[3]: z= numpy.asfortranarray(z) + if f_cont[4]: vz= numpy.asfortranarray(vz) + if f_cont[5]: u0= numpy.asfortranarray(u0) + + return (umin,umax,vmin,err.value) + diff --git a/galpy/util/bovy_conversion.py b/galpy/util/bovy_conversion.py index a2d38de90..5cd6b9938 100644 --- a/galpy/util/bovy_conversion.py +++ b/galpy/util/bovy_conversion.py @@ -710,7 +710,7 @@ def wrapper(*args,**kwargs): return wrapper def physical_conversion_actionAngle(quantity,pop=False): """Decorator to convert to physical coordinates for the actionAngle methods: - quantity= call, actionsFreqs, or actionsFreqsAngles""" + quantity= call, actionsFreqs, or actionsFreqsAngles (or EccZmaxRperiRap for actionAngleStaeckel)""" def wrapper(method): @wraps(method) def wrapped(*args,**kwargs): @@ -730,12 +730,12 @@ def wrapped(*args,**kwargs): if pop and 'ro' in kwargs: kwargs.pop('ro') if pop and 'vo' in kwargs: kwargs.pop('vo') if use_physical and not vo is None and not ro is None: - # Always need the action - fac= [ro*vo,ro*vo,ro*vo] - if _APY_UNITS: - u= [units.kpc*units.km/units.s, - units.kpc*units.km/units.s, - units.kpc*units.km/units.s] + if 'call' in quantity or 'actions' in quantity: + fac= [ro*vo,ro*vo,ro*vo] + if _APY_UNITS: + u= [units.kpc*units.km/units.s, + units.kpc*units.km/units.s, + units.kpc*units.km/units.s] if 'Freqs' in quantity: FreqsFac= freq_in_Gyr(vo,ro) fac.extend([FreqsFac,FreqsFac,FreqsFac]) @@ -747,6 +747,13 @@ def wrapped(*args,**kwargs): if _APY_UNITS: Freqsu= units.Gyr**-1. u.extend([units.rad,units.rad,units.rad]) + if 'EccZmaxRperiRap' in quantity: + fac= [1.,ro,ro,ro] + if _APY_UNITS: + u= [1., + units.kpc, + units.kpc, + units.kpc] out= method(*args,**kwargs) if _APY_UNITS: newOut= () From 94e716da430a94befedbade86eed71ff5225f3cc Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 12 Dec 2017 23:06:53 -0500 Subject: [PATCH 07/62] Add basic test of eccentricity and zmax computed in the Staeckel approximation --- tests/test_actionAngle.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 82565f423..b90c54cb5 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -894,6 +894,67 @@ def test_actionAngleStaeckel_circular_angles_c(): assert numpy.fabs(js[8]) < 10.**-8., 'Circular orbit does not have zero angles' return None +#Basic sanity checking of the actionAngleStaeckel ecc, zmax, rperi, rap calc. +def test_actionAngleStaeckel_basic_EccZmaxRperiRap(): + from galpy.actionAngle import actionAngleStaeckel + from galpy.potential import MWPotential + aAS= actionAngleStaeckel(pot=MWPotential,delta=0.71,c=False) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,0.99,0.0,0.0 + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,1.,0.01,0.0 + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + +#Basic sanity checking of the actionAngleStaeckel ecc, zmax, rperi, rap calc. +def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0(): + from galpy.actionAngle import actionAngleStaeckel + from galpy.potential import MWPotential + aAS= actionAngleStaeckel(pot=MWPotential,delta=0.71,c=False,useu0=True) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + +#Basic sanity checking of the actionAngleStaeckel ecc, zmax, rperi, rap calc. +def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0_c(): + from galpy.actionAngle import actionAngleStaeckel + from galpy.potential import MWPotential + aAS= actionAngleStaeckel(pot=MWPotential,delta=0.71,c=True,useu0=True) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + #Test the actions of an actionAngleStaeckel def test_actionAngleStaeckel_conserved_actions(): from galpy.potential import MWPotential From ae08c376c4083f79976bbcab94af053488f4273e Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 12 Dec 2017 23:21:37 -0500 Subject: [PATCH 08/62] Add test of conservation of eccentricity, zmax, rperi, rap in a large variety of potentials --- tests/test_actionAngle.py | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index b90c54cb5..8c7d5af01 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -1137,6 +1137,62 @@ def test_actionAngleStaeckel_linear_angles_u0(): ntimes=1001,u0=1.23) #need fine sampling for de-period return None +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleStaeckel +def test_actionAngleStaeckel_conserved_EccZmaxRperiRap(): + from galpy.potential import MWPotential + from galpy.actionAngle import actionAngleStaeckel + from galpy.orbit import Orbit + aAS= actionAngleStaeckel(pot=MWPotential,c=False,delta=0.71) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,0.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAS,obs,MWPotential, + -2.,-2.,-2.,-2.,ntimes=101) + return None + +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleStaeckel +def test_actionAngleStaeckel_conserved_EccZmaxRperiRap_ecc(): + from galpy.potential import MWPotential + from galpy.actionAngle import actionAngleStaeckel + from galpy.orbit import Orbit + aAS= actionAngleStaeckel(pot=MWPotential,c=False,delta=0.71) + obs= Orbit([1.1,0.2, 1.3, 0.3,0.,2.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAS,obs,MWPotential, + -1.8,-1.4,-1.8,-1.8,ntimes=101) + return None + +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleStaeckel +def test_actionAngleStaeckel_conserved_EccZmaxRperiRap_c(): + from galpy.potential import MWPotential, DoubleExponentialDiskPotential, \ + FlattenedPowerPotential, interpRZPotential, KuzminDiskPotential, \ + TriaxialHernquistPotential, TriaxialJaffePotential, \ + TriaxialNFWPotential, SCFPotential, DiskSCFPotential + from galpy.actionAngle import actionAngleStaeckel + from galpy.orbit import Orbit + from galpy.orbit_src.FullOrbit import ext_loaded + ip= interpRZPotential(RZPot=MWPotential, + rgrid=(numpy.log(0.01),numpy.log(20.),101), + zgrid=(0.,1.,101),logR=True,use_c=True,enable_c=True, + interpPot=True,interpRforce=True,interpzforce=True) + pots= [MWPotential, + DoubleExponentialDiskPotential(normalize=1.), + FlattenedPowerPotential(normalize=1.), + FlattenedPowerPotential(normalize=1.,alpha=0.), + KuzminDiskPotential(normalize=1.,a=1./8.), + TriaxialHernquistPotential(normalize=1.,c=0.2,pa=1.1), # tests rot, but not well + TriaxialNFWPotential(normalize=1.,c=0.3,pa=1.1), + TriaxialJaffePotential(normalize=1.,c=0.4,pa=1.1), + SCFPotential(normalize=1.), + DiskSCFPotential(normalize=1.), + ip] + for pot in pots: + aAS= actionAngleStaeckel(pot=pot,c=True,delta=0.71) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAS,obs,pot, + -1.8,-1.3,-1.8,-1.8, + ntimes=101) + return None + +#HERE + #Test the actionAngleStaeckel against an isochrone potential: actions def test_actionAngleStaeckel_otherIsochrone_actions(): from galpy.potential import IsochronePotential @@ -2668,3 +2724,19 @@ def check_actionAngle_linear_angles(aA,obs,pot, maxdev= numpy.amax(numpy.fabs(devs)) assert maxdev < 10.**toldaz, 'Maximum deviation from linear trend in the vertical angles is %g' % maxdev return None + +#Test that the ecc, zmax, rperi, rap are conserved along an orbit +def check_actionAngle_conserved_EccZmaxRperiRap(aA,obs,pot,tole,tolzmax, + tolrperi,tolrap, + ntimes=1001): + times= numpy.linspace(0.,100.,ntimes) + obs.integrate(times,pot,method='dopr54_c') + es,zmaxs,rperis,raps= aA.EccZmaxRperiRap(\ + obs.R(times),obs.vR(times),obs.vT(times),obs.z(times), + obs.vz(times),obs.phi(times)) + assert numpy.amax(numpy.fabs(es/numpy.mean(es)-1)) < 10.**tole, 'Eccentricity conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(es/numpy.mean(es)-1))) + assert numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1)) < 10.**tolzmax, 'Zmax conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1))) + assert numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1)) < 10.**tolrperi, 'Rperi conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1))) + assert numpy.amax(numpy.fabs(raps/numpy.mean(raps)-1)) < 10.**tolrap, 'Rap conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(raps/numpy.mean(raps)-1))) + return None + From c09049c5785ef49b5d76f6ccddc87dbc0b4718e5 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 12 Dec 2017 23:52:40 -0500 Subject: [PATCH 09/62] Tests of physical units for EccZmaxRperiRap --- tests/test_quantity.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_quantity.py b/tests/test_quantity.py index 93ae2a6eb..616b5ca76 100644 --- a/tests/test_quantity.py +++ b/tests/test_quantity.py @@ -2717,6 +2717,8 @@ def test_actionAngle_method_returntype(): assert isinstance(aA.actionsFreqs(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqs does not return Quantity when it should' for ii in range(9): assert isinstance(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqsAngles does not return Quantity when it should' + for ii in range(3): + assert isinstance(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method EccZmaxRperiRap does not return Quantity when it should' # actionAngleIsochroneApprox aA= actionAngleIsochroneApprox(pot=MWPotential,b=0.8,ro=8.,vo=220.) for ii in range(3): @@ -2835,6 +2837,15 @@ def test_actionAngle_method_returnunit(): aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad) except units.UnitConversionError: raise AssertionError('actionAngle function actionsFreqsAngles does not return Quantity with the right units') + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') + for ii in range(1,4): + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') # actionAngleIsochroneApprox aA= actionAngleIsochroneApprox(pot=MWPotential,b=0.8,ro=8.,vo=220.) for ii in range(3): @@ -2926,6 +2937,9 @@ def test_actionAngle_method_value(): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(1/units.Gyr).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]*bovy_conversion.freq_in_Gyr(vo,ro)) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' for ii in range(6,9): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0]) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' + for ii in range(1,4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]*ro) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' # actionAngleIsochroneApprox aA= actionAngleIsochroneApprox(pot=MWPotential,b=0.8,ro=ro,vo=vo) aAnu= actionAngleIsochroneApprox(pot=MWPotential,b=0.8) @@ -3175,6 +3189,8 @@ def test_actionAngle_method_inputAsQuantity(): assert numpy.fabs(aA.actionsFreqsAngles(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method actionsFreqsAngles does not return the correct value when input is Quantity' for ii in range(6,9): assert numpy.fabs(aA.actionsFreqsAngles(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method actionsFreqsAngles does not return the correct value when input is Quantity' + for ii in range(4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method EccZmaxRperiRap does not return the correct value when input is Quantity' # actionAngleIsochroneApprox aA= actionAngleIsochroneApprox(pot=MWPotential,b=0.8,ro=ro,vo=vo) aAnu= actionAngleIsochroneApprox(pot=MWPotential,b=0.8) From 89b492756ab79e994f7a51687caa2e65fb0cc6fe Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 12:53:50 -0500 Subject: [PATCH 10/62] Implement approximate eccentricity, zmax, rperi, and rapo in actionAngleStaeckelGrid --- .../actionAngleStaeckelGrid.py | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/galpy/actionAngle_src/actionAngleStaeckelGrid.py b/galpy/actionAngle_src/actionAngleStaeckelGrid.py index 71e4f20a2..159cf7fbc 100644 --- a/galpy/actionAngle_src/actionAngleStaeckelGrid.py +++ b/galpy/actionAngle_src/actionAngleStaeckelGrid.py @@ -19,6 +19,8 @@ import galpy.potential from galpy.potential_src.Potential import _evaluatePotentials from galpy.util import multi, bovy_coords +from galpy.util.bovy_conversion import physical_conversion_actionAngle, \ + actionAngle_physical_input _PRINTOUTSIDEGRID= False _APY_LOADED= True try: @@ -29,6 +31,7 @@ class actionAngleStaeckelGrid(actionAngle): """Action-angle formalism for axisymmetric potentials using Binney (2012)'s Staeckel approximation, grid-based interpolation""" def __init__(self,pot=None,delta=None,Rmax=5., nE=25,npsi=25,nLz=30,numcores=1, + interpecc=False, **kwargs): """ NAME: @@ -44,6 +47,8 @@ def __init__(self,pot=None,delta=None,Rmax=5., nE=, npsi=, nLz= grid size + interpecc= (False) if True, also interpolate the approximate eccentricity, zmax, rperi, and rapo + numcores= number of cpus to use to parallellize ro= distance from vantage point to GC (kpc; can be Quantity) @@ -58,6 +63,8 @@ def __init__(self,pot=None,delta=None,Rmax=5., 2012-11-29 - Written - Bovy (IAS) + 2017-12-15 - Written - Bovy (UofT) + """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) @@ -153,6 +160,13 @@ def __init__(self,pot=None,delta=None,Rmax=5., numpy.zeros(len(thisR)), #z thisv*numpy.sin(thispsi), #vz fixed_quad=True) + if interpecc: + mecc, mzmax, mrperi, mrap=\ + self._aA.EccZmaxRperiRap(thisR, #R + thisv*numpy.cos(thispsi), #vR + thisLzs/thisR, #vT + numpy.zeros(len(thisR)), #z + thisv*numpy.sin(thispsi)) #vz if isinstance(self._pot,galpy.potential.interpRZPotential) and hasattr(self._pot,'_origPot'): #Interpolated potentials have problems with extreme orbits indx= (mjr == 9999.99) @@ -165,22 +179,58 @@ def __init__(self,pot=None,delta=None,Rmax=5., numpy.zeros(numpy.sum(indx)), #z thisv[indx]*numpy.sin(thispsi[indx]), #vz fixed_quad=True) + if interpecc: + mecc[indx], mzmax[indx], mrperi[indx], mrap[indx]=\ + self._aA.EccZmaxRperiRap(thisR, #R + thisv*numpy.cos(thispsi), #vR + thisLzs/thisR, #vT + numpy.zeros(len(thisR)), #z + thisv*numpy.sin(thispsi)) #vz jr= numpy.reshape(mjr,(nLz,nE,npsi)) jz= numpy.reshape(mjz,(nLz,nE,npsi)) + if interpecc: + ecc= numpy.reshape(mecc,(nLz,nE,npsi)) + zmax= numpy.reshape(mzmax,(nLz,nE,npsi)) + rperi= numpy.reshape(mrperi,(nLz,nE,npsi)) + rap= numpy.reshape(mrap,(nLz,nE,npsi)) + zmaxLzE= numpy.zeros((nLz)) + rperiLzE= numpy.zeros((nLz)) + rapLzE= numpy.zeros((nLz)) for ii in range(nLz): jrLzE[ii]= numpy.nanmax(jr[ii,(jr[ii,:,:] != 9999.99)])#:,:]) jzLzE[ii]= numpy.nanmax(jz[ii,(jz[ii,:,:] != 9999.99)])#:,:]) + if interpecc: + zmaxLzE[ii]= numpy.nanmax(zmax[ii,(jz[ii,:,:] != 9999.99)]) + rperiLzE[ii]= numpy.nanmax(rperi[ii,(jz[ii,:,:] != 9999.99)]) + rapLzE[ii]= numpy.nanmax(rap[ii,(jz[ii,:,:] != 9999.99)]) jrLzE[(jrLzE == 0.)]= numpy.nanmin(jrLzE[(jrLzE > 0.)]) jzLzE[(jzLzE == 0.)]= numpy.nanmin(jzLzE[(jzLzE > 0.)]) + if interpecc: + zmaxLzE[(zmaxLzE == 0.)]= numpy.nanmin(zmaxLzE[(zmaxLzE > 0.)]) + rperiLzE[(rperiLzE == 0.)]= numpy.nanmin(rperiLzE[(rperiLzE > 0.)]) + rapLzE[(rapLzE == 0.)]= numpy.nanmin(rapLzE[(rapLzE > 0.)]) for ii in range(nLz): jr[ii,:,:]/= jrLzE[ii] jz[ii,:,:]/= jzLzE[ii] + if interpecc: + zmax[ii,:,:]/= zmaxLzE[ii] + rperi[ii,:,:]/= rperiLzE[ii] + rap[ii,:,:]/= rapLzE[ii] #Deal w/ 9999.99 jr[(jr > 1.)]= 1. jz[(jz > 1.)]= 1. #Deal w/ NaN jr[numpy.isnan(jr)]= 0. jz[numpy.isnan(jz)]= 0. + if interpecc: + ecc[(ecc > 1.)]= 1. + ecc[numpy.isnan(ecc)]= 0. + zmax[(zmax > 1.)]= 1. + zmax[numpy.isnan(zmax)]= 0. + rperi[(rperi > 1.)]= 1. + rperi[numpy.isnan(rperi)]= 0. + rap[(rap > 1.)]= 1. + rap[numpy.isnan(rap)]= 0. #First interpolate the maxima self._jr= jr self._jz= jz @@ -191,6 +241,23 @@ def __init__(self,pot=None,delta=None,Rmax=5., numpy.log(jrLzE+10.**-5.),k=3) self._jzLzInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(jzLzE+10.**-5.),k=3) + if interpecc: + self._ecc= ecc + self._zmax= zmax + self._rperi= rperi + self._rap= rap + self._zmaxLzE= zmaxLzE + self._rperiLzE= rperiLzE + self._rapLzE= rapLzE + self._zmaxLzInterp=\ + interpolate.InterpolatedUnivariateSpline(self._Lzs, + numpy.log(zmaxLzE+10.**-5.),k=3) + self._rperiLzInterp=\ + interpolate.InterpolatedUnivariateSpline(self._Lzs, + numpy.log(rperiLzE+10.**-5.),k=3) + self._rapLzInterp=\ + interpolate.InterpolatedUnivariateSpline(self._Lzs, + numpy.log(rapLzE+10.**-5.),k=3) #Interpolate u0 self._logu0Interp= interpolate.RectBivariateSpline(self._Lzs, y, @@ -199,10 +266,155 @@ def __init__(self,pot=None,delta=None,Rmax=5., #spline filter jr and jz, such that they can be used with ndimage.map_coordinates self._jrFiltered= ndimage.spline_filter(numpy.log(self._jr+10.**-10.),order=3) self._jzFiltered= ndimage.spline_filter(numpy.log(self._jz+10.**-10.),order=3) + if interpecc: + self._eccFiltered= ndimage.spline_filter(numpy.log(self._ecc+10.**-10.),order=3) + self._zmaxFiltered= ndimage.spline_filter(numpy.log(self._zmax+10.**-10.),order=3) + self._rperiFiltered= ndimage.spline_filter(numpy.log(self._rperi+10.**-10.),order=3) + self._rapFiltered= ndimage.spline_filter(numpy.log(self._rap+10.**-10.),order=3) # Check the units self._check_consistent_units() return None + @actionAngle_physical_input + @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) + def EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-15 - Written - Bovy (UofT) + + """ + if len(args) == 5: #R,vR.vT, z, vz + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + Lz= R*vT + Phi= _evaluatePotentials(self._pot,R,z) + E= Phi+vR**2./2.+vT**2./2.+vz**2./2. + thisERL= -numpy.exp(self._ERLInterp(Lz))+self._ERLmax + thisERa= -numpy.exp(self._ERaInterp(Lz))+self._ERamax + if isinstance(R,numpy.ndarray): + indx= ((E-thisERa)/(thisERL-thisERa) > 1.)\ + *(((E-thisERa)/(thisERL-thisERa)-1.) < 10.**-2.) + E[indx]= thisERL[indx] + indx= ((E-thisERa)/(thisERL-thisERa) < 0.)\ + *((E-thisERa)/(thisERL-thisERa) > -10.**-2.) + E[indx]= thisERa[indx] + indx= (Lz < self._Lzmin) + indx+= (Lz > self._Lzmax) + indx+= ((E-thisERa)/(thisERL-thisERa) > 1.) + indx+= ((E-thisERa)/(thisERL-thisERa) < 0.) + indxc= True^indx + ecc= numpy.empty(R.shape) + zmax= numpy.empty(R.shape) + rperi= numpy.empty(R.shape) + rap= numpy.empty(R.shape) + if numpy.sum(indxc) > 0: + u0= numpy.exp(self._logu0Interp.ev(Lz[indxc], + (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])))) + sinh2u0= numpy.sinh(u0)**2. + thisEr= self.Er(R[indxc],z[indxc],vR[indxc],vz[indxc], + E[indxc],Lz[indxc],sinh2u0,u0) + thisEz= self.Ez(R[indxc],z[indxc],vR[indxc],vz[indxc], + E[indxc],Lz[indxc],sinh2u0,u0) + thisv2= self.vatu0(E[indxc],Lz[indxc],u0,self._delta*numpy.sinh(u0),retv2=True) + cos2psi= 2.*thisEr/thisv2/(1.+sinh2u0) #latter is cosh2u0 + cos2psi[(cos2psi > 1.)*(cos2psi < 1.+10.**-5.)]= 1. + indxCos2psi= (cos2psi > 1.) + indxCos2psi+= (cos2psi < 0.) + indxc[indxc]= True^indxCos2psi#Handle these two cases as off-grid + indx= True^indxc + psi= numpy.arccos(numpy.sqrt(cos2psi[True^indxCos2psi])) + coords= numpy.empty((3,numpy.sum(indxc))) + coords[0,:]= (Lz[indxc]-self._Lzmin)/(self._Lzmax-self._Lzmin)*(self._nLz-1.) + y= (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])) + coords[1,:]= y*(self._nE-1.) + coords[2,:]= psi/numpy.pi*2.*(self._npsi-1.) + ecc[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._eccFiltered, + coords, + order=3, + prefilter=False))-10.**-10.) + rperi[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rperiFiltered, + coords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._rperiLzInterp(Lz[indxc]))-10.**-5.) + # We do rap below with zmax + #Switch to Ez-calculated psi + sin2psi= 2.*thisEz[True^indxCos2psi]/thisv2[True^indxCos2psi]/(1.+sinh2u0[True^indxCos2psi]) #latter is cosh2u0 + sin2psi[(sin2psi > 1.)*(sin2psi < 1.+10.**-5.)]= 1. + indxSin2psi= (sin2psi > 1.) + indxSin2psi+= (sin2psi < 0.) + indxc[indxc]= True^indxSin2psi#Handle these two cases as off-grid + indx= True^indxc + psiz= numpy.arcsin(numpy.sqrt(sin2psi[True^indxSin2psi])) + newcoords= numpy.empty((3,numpy.sum(indxc))) + newcoords[0:2,:]= coords[0:2,True^indxSin2psi] + newcoords[2,:]= psiz/numpy.pi*2.*(self._npsi-1.) + zmax[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._zmaxFiltered, + newcoords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._zmaxLzInterp(Lz[indxc]))-10.**-5.) + rap[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rapFiltered, + newcoords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._rapLzInterp(Lz[indxc]))-10.**-5.) + if numpy.sum(indx) > 0: + eccindiv, zmaxindiv, rperiindiv, rapindiv=\ + self._aA.EccZmaxRperiRap(R[indx], + vR[indx], + vT[indx], + z[indx], + vz[indx], + **kwargs) + ecc[indx]= eccindiv + zmax[indx]= zmaxindiv + rperi[indx]= rperiindiv + rap[indx]= rapindiv + else: + ecc,zmax,rperi,rap= self.EccZmaxRperiRap(numpy.array([R]), + numpy.array([vR]), + numpy.array([vT]), + numpy.array([z]), + numpy.array([vz]), + **kwargs) + return (ecc[0],zmax[0],rperi[0],rap[0]) + ecc[ecc < 0.]= 0. + zmax[zmax < 0.]= 0. + rperi[rperi < 0.]= 0. + rap[rap < 0.]= 0. + return (ecc,zmax,rperi,rap) + def _evaluate(self,*args,**kwargs): """ NAME: From 66f5b69c67c99d0ec9345e187a44d1f601e0a789 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 13:42:50 -0500 Subject: [PATCH 11/62] Add a bunch of tests of grid-based eccentricity etc. calculation --- tests/test_actionAngle.py | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 8c7d5af01..eeb7d7c40 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -1291,25 +1291,35 @@ def test_actionAngleStaeckel_otherIsochrone_angles(): assert daz < 10.**-4., 'actionAngleStaeckel applied to isochrone potential fails for az at %g%%' % (daz*100.) return None -#Basic sanity checking of the actionAngleStaeckelGrid actions (incl. conserved, bc takes a lot of time) +#Basic sanity checking of the actionAngleStaeckelGrid actions (incl. conserved and ecc etc., bc takes a lot of time) def test_actionAngleStaeckelGrid_basicAndConserved_actions(): from galpy.actionAngle import actionAngleStaeckelGrid from galpy.orbit import Orbit from galpy.potential import MWPotential - aAA= actionAngleStaeckelGrid(pot=MWPotential,delta=0.71,c=False,nLz=20) + aAA= actionAngleStaeckelGrid(pot=MWPotential,delta=0.71,c=False,nLz=20, + interpecc=True) #circular orbit R,vR,vT,z,vz= 1.,0.,1.,0.,0. assert numpy.fabs(aAA.JR(R,vR,vT,z,vz,0.)) < 10.**-16., 'Circular orbit in the MWPotential does not have Jr=0' assert numpy.fabs(aAA.Jz(R,vR,vT,z,vz,0.)) < 10.**-16., 'Circular orbit in the MWPotential does not have Jz=0' + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 js= aAA(Orbit([R,vR,vT,z,vz])) assert numpy.fabs(js[0]) < 10.**-4., 'Close-to-circular orbit in the MWPotential does not have small Jr' assert numpy.fabs(js[2]) < 10.**-3., 'Close-to-circular orbit in the MWPotential does not have small Jz' + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' #Check that actions are conserved along the orbit obs= Orbit([1.05, 0.02, 1.05, 0.03,0.]) check_actionAngle_conserved_actions(aAA,obs,MWPotential, -1.2,-8.,-1.7,ntimes=101) + # and the eccentricity etc. + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,MWPotential, + -2.,-2.,-2.,-2.,ntimes=101) return None #Basic sanity checking of the actionAngleStaeckel actions @@ -1380,6 +1390,51 @@ def test_actionAngleStaeckelGrid_Isochrone_actions(): assert djz < 10.**-1.2, 'actionAngleStaeckel applied to isochrone potential fails for Jz at %f%%' % (djz*100.) return None +#Basic sanity checking of the actionAngleStaeckelGrid eccentricity etc. +def test_actionAngleStaeckelGrid_basic_EccZmaxRperiRap_c(): + from galpy.actionAngle import actionAngleStaeckelGrid + from galpy.potential import MWPotential, interpRZPotential + rzpot= interpRZPotential(RZPot=MWPotential, + rgrid=(numpy.log(0.01),numpy.log(20.),201), + logR=True, + zgrid=(0.,1.,101), + interpPot=True,use_c=True,enable_c=True, + zsym=True) + aAA= actionAngleStaeckelGrid(pot=rzpot,delta=0.71,c=True,interpecc=True) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,0.99,0.0,0.0 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,1.,0.01,0.0 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + +#Test the actions of an actionAngleStaeckel +def test_actionAngleStaeckelGrid_conserved_EccZmaxRperiRap_c(): + from galpy.potential import MWPotential + from galpy.actionAngle import actionAngleStaeckelGrid + from galpy.orbit import Orbit + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.]) + aAA= actionAngleStaeckelGrid(pot=MWPotential,delta=0.71,c=True, + interpecc=True) + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,MWPotential, + -2.,-2.,-2.,-2.,ntimes=101) + return None + #Test the actionAngleIsochroneApprox against an isochrone potential: actions def test_actionAngleIsochroneApprox_otherIsochrone_actions(): from galpy.potential import IsochronePotential @@ -2733,7 +2788,7 @@ def check_actionAngle_conserved_EccZmaxRperiRap(aA,obs,pot,tole,tolzmax, obs.integrate(times,pot,method='dopr54_c') es,zmaxs,rperis,raps= aA.EccZmaxRperiRap(\ obs.R(times),obs.vR(times),obs.vT(times),obs.z(times), - obs.vz(times),obs.phi(times)) + obs.vz(times)) assert numpy.amax(numpy.fabs(es/numpy.mean(es)-1)) < 10.**tole, 'Eccentricity conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(es/numpy.mean(es)-1))) assert numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1)) < 10.**tolzmax, 'Zmax conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1))) assert numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1)) < 10.**tolrperi, 'Rperi conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1))) From d7fb011469f515ae169734aa1a9a4c03044562ef Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 13:43:11 -0500 Subject: [PATCH 12/62] Better handling of badly calculated eccentricities, zmaxs, etc. in grid-based calculation setup --- .../actionAngleStaeckelGrid.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/galpy/actionAngle_src/actionAngleStaeckelGrid.py b/galpy/actionAngle_src/actionAngleStaeckelGrid.py index 159cf7fbc..dc4866fb9 100644 --- a/galpy/actionAngle_src/actionAngleStaeckelGrid.py +++ b/galpy/actionAngle_src/actionAngleStaeckelGrid.py @@ -181,11 +181,11 @@ def __init__(self,pot=None,delta=None,Rmax=5., fixed_quad=True) if interpecc: mecc[indx], mzmax[indx], mrperi[indx], mrap[indx]=\ - self._aA.EccZmaxRperiRap(thisR, #R - thisv*numpy.cos(thispsi), #vR - thisLzs/thisR, #vT - numpy.zeros(len(thisR)), #z - thisv*numpy.sin(thispsi)) #vz + self._aA.EccZmaxRperiRap(thisR[indx], #R + thisv[indx]*numpy.cos(thispsi[indx]), #vR + thisLzs[indx]/thisR[indx], #vT + numpy.zeros(numpy.sum(indx)), #z + thisv[indx]*numpy.sin(thispsi[indx])) #vz jr= numpy.reshape(mjr,(nLz,nE,npsi)) jz= numpy.reshape(mjz,(nLz,nE,npsi)) if interpecc: @@ -200,9 +200,9 @@ def __init__(self,pot=None,delta=None,Rmax=5., jrLzE[ii]= numpy.nanmax(jr[ii,(jr[ii,:,:] != 9999.99)])#:,:]) jzLzE[ii]= numpy.nanmax(jz[ii,(jz[ii,:,:] != 9999.99)])#:,:]) if interpecc: - zmaxLzE[ii]= numpy.nanmax(zmax[ii,(jz[ii,:,:] != 9999.99)]) - rperiLzE[ii]= numpy.nanmax(rperi[ii,(jz[ii,:,:] != 9999.99)]) - rapLzE[ii]= numpy.nanmax(rap[ii,(jz[ii,:,:] != 9999.99)]) + zmaxLzE[ii]= numpy.amax(zmax[ii,numpy.isfinite(zmax[ii])]) + rperiLzE[ii]= numpy.amax(rperi[ii,numpy.isfinite(rperi[ii])]) + rapLzE[ii]= numpy.amax(rap[ii,numpy.isfinite(rap[ii])]) jrLzE[(jrLzE == 0.)]= numpy.nanmin(jrLzE[(jrLzE > 0.)]) jzLzE[(jzLzE == 0.)]= numpy.nanmin(jzLzE[(jzLzE > 0.)]) if interpecc: @@ -225,12 +225,16 @@ def __init__(self,pot=None,delta=None,Rmax=5., if interpecc: ecc[(ecc > 1.)]= 1. ecc[numpy.isnan(ecc)]= 0. + ecc[numpy.isinf(ecc)]= 1. zmax[(zmax > 1.)]= 1. zmax[numpy.isnan(zmax)]= 0. + zmax[numpy.isinf(zmax)]= 1. rperi[(rperi > 1.)]= 1. rperi[numpy.isnan(rperi)]= 0. + rperi[numpy.isinf(rperi)]= 0. # typically orbits that can reach 0 rap[(rap > 1.)]= 1. rap[numpy.isnan(rap)]= 0. + rap[numpy.isinf(rap)]= 1. #First interpolate the maxima self._jr= jr self._jz= jz From e5ccc8c9a875a2b1cb374893a16abff7296e95f7 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 17:31:07 -0500 Subject: [PATCH 13/62] Make EccZmaxRperiRap a general actionAngle function, to more easily generalize it for different actionAngle methods --- galpy/actionAngle_src/actionAngle.py | 37 +++ galpy/actionAngle_src/actionAngleStaeckel.py | 78 +++-- .../actionAngleStaeckelGrid.py | 278 +++++++++--------- 3 files changed, 213 insertions(+), 180 deletions(-) diff --git a/galpy/actionAngle_src/actionAngle.py b/galpy/actionAngle_src/actionAngle.py index dbbe881da..9c072fc56 100644 --- a/galpy/actionAngle_src/actionAngle.py +++ b/galpy/actionAngle_src/actionAngle.py @@ -305,6 +305,43 @@ def actionsFreqsAngles(self,*args,**kwargs): except AttributeError: #pragma: no cover raise NotImplementedError("'actionsFreqsAngles' method not implemented for this actionAngle module") + @actionAngle_physical_input + @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) + def EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-12 - Written - Bovy (UofT) + + """ + try: + return self._EccZmaxRperiRap(*args,**kwargs) + except AttributeError: #pragma: no cover + raise NotImplementedError("'EccZmaxRperiRap' method not implemented for this actionAngle module") class UnboundError(Exception): #pragma: no cover def __init__(self, value): diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index eed4b5fbb..d68057630 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -87,46 +87,6 @@ def __init__(self,*args,**kwargs): self._check_consistent_units() return None - @actionAngle_physical_input - @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) - def EccZmaxRperiRap(self,*args,**kwargs): - """ - NAME: - - EccZmaxRperiRap - - PURPOSE: - - evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation - - INPUT: - - Either: - - a) R,vR,vT,z,vz[,phi]: - - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - - OUTPUT: - - (e,zmax,rperi,rap) - - HISTORY: - - 2017-12-12 - Written - Bovy (UofT) - - """ - umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) - rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=self._delta)[0] - rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=self._delta) - rap= nu.sqrt(rap_tmp**2.+zmax**2.) - e= (rap-rperi)/(rap+rperi) - return (e,zmax,rperi,rap) - def _evaluate(self,*args,**kwargs): """ NAME: @@ -353,6 +313,44 @@ def _actionsFreqsAngles(self,*args,**kwargs): warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) raise NotImplementedError("actionsFreqs with c=False not implemented") + def _EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + _EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-12 - Written - Bovy (UofT) + + """ + umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) + rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=self._delta)[0] + rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=self._delta) + rap= nu.sqrt(rap_tmp**2.+zmax**2.) + e= (rap-rperi)/(rap+rperi) + return (e,zmax,rperi,rap) + def _uminumaxvmin(self,*args,**kwargs): """ NAME: diff --git a/galpy/actionAngle_src/actionAngleStaeckelGrid.py b/galpy/actionAngle_src/actionAngleStaeckelGrid.py index dc4866fb9..4132cab71 100644 --- a/galpy/actionAngle_src/actionAngleStaeckelGrid.py +++ b/galpy/actionAngle_src/actionAngleStaeckelGrid.py @@ -279,146 +279,6 @@ def __init__(self,pot=None,delta=None,Rmax=5., self._check_consistent_units() return None - @actionAngle_physical_input - @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) - def EccZmaxRperiRap(self,*args,**kwargs): - """ - NAME: - - EccZmaxRperiRap - - PURPOSE: - - evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation - - INPUT: - - Either: - - a) R,vR,vT,z,vz[,phi]: - - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - - OUTPUT: - - (e,zmax,rperi,rap) - - HISTORY: - - 2017-12-15 - Written - Bovy (UofT) - - """ - if len(args) == 5: #R,vR.vT, z, vz - R,vR,vT, z, vz= args - elif len(args) == 6: #R,vR.vT, z, vz, phi - R,vR,vT, z, vz, phi= args - else: - self._parse_eval_args(*args) - R= self._eval_R - vR= self._eval_vR - vT= self._eval_vT - z= self._eval_z - vz= self._eval_vz - Lz= R*vT - Phi= _evaluatePotentials(self._pot,R,z) - E= Phi+vR**2./2.+vT**2./2.+vz**2./2. - thisERL= -numpy.exp(self._ERLInterp(Lz))+self._ERLmax - thisERa= -numpy.exp(self._ERaInterp(Lz))+self._ERamax - if isinstance(R,numpy.ndarray): - indx= ((E-thisERa)/(thisERL-thisERa) > 1.)\ - *(((E-thisERa)/(thisERL-thisERa)-1.) < 10.**-2.) - E[indx]= thisERL[indx] - indx= ((E-thisERa)/(thisERL-thisERa) < 0.)\ - *((E-thisERa)/(thisERL-thisERa) > -10.**-2.) - E[indx]= thisERa[indx] - indx= (Lz < self._Lzmin) - indx+= (Lz > self._Lzmax) - indx+= ((E-thisERa)/(thisERL-thisERa) > 1.) - indx+= ((E-thisERa)/(thisERL-thisERa) < 0.) - indxc= True^indx - ecc= numpy.empty(R.shape) - zmax= numpy.empty(R.shape) - rperi= numpy.empty(R.shape) - rap= numpy.empty(R.shape) - if numpy.sum(indxc) > 0: - u0= numpy.exp(self._logu0Interp.ev(Lz[indxc], - (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])))) - sinh2u0= numpy.sinh(u0)**2. - thisEr= self.Er(R[indxc],z[indxc],vR[indxc],vz[indxc], - E[indxc],Lz[indxc],sinh2u0,u0) - thisEz= self.Ez(R[indxc],z[indxc],vR[indxc],vz[indxc], - E[indxc],Lz[indxc],sinh2u0,u0) - thisv2= self.vatu0(E[indxc],Lz[indxc],u0,self._delta*numpy.sinh(u0),retv2=True) - cos2psi= 2.*thisEr/thisv2/(1.+sinh2u0) #latter is cosh2u0 - cos2psi[(cos2psi > 1.)*(cos2psi < 1.+10.**-5.)]= 1. - indxCos2psi= (cos2psi > 1.) - indxCos2psi+= (cos2psi < 0.) - indxc[indxc]= True^indxCos2psi#Handle these two cases as off-grid - indx= True^indxc - psi= numpy.arccos(numpy.sqrt(cos2psi[True^indxCos2psi])) - coords= numpy.empty((3,numpy.sum(indxc))) - coords[0,:]= (Lz[indxc]-self._Lzmin)/(self._Lzmax-self._Lzmin)*(self._nLz-1.) - y= (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])) - coords[1,:]= y*(self._nE-1.) - coords[2,:]= psi/numpy.pi*2.*(self._npsi-1.) - ecc[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._eccFiltered, - coords, - order=3, - prefilter=False))-10.**-10.) - rperi[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rperiFiltered, - coords, - order=3, - prefilter=False))-10.**-10.)*(numpy.exp(self._rperiLzInterp(Lz[indxc]))-10.**-5.) - # We do rap below with zmax - #Switch to Ez-calculated psi - sin2psi= 2.*thisEz[True^indxCos2psi]/thisv2[True^indxCos2psi]/(1.+sinh2u0[True^indxCos2psi]) #latter is cosh2u0 - sin2psi[(sin2psi > 1.)*(sin2psi < 1.+10.**-5.)]= 1. - indxSin2psi= (sin2psi > 1.) - indxSin2psi+= (sin2psi < 0.) - indxc[indxc]= True^indxSin2psi#Handle these two cases as off-grid - indx= True^indxc - psiz= numpy.arcsin(numpy.sqrt(sin2psi[True^indxSin2psi])) - newcoords= numpy.empty((3,numpy.sum(indxc))) - newcoords[0:2,:]= coords[0:2,True^indxSin2psi] - newcoords[2,:]= psiz/numpy.pi*2.*(self._npsi-1.) - zmax[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._zmaxFiltered, - newcoords, - order=3, - prefilter=False))-10.**-10.)*(numpy.exp(self._zmaxLzInterp(Lz[indxc]))-10.**-5.) - rap[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rapFiltered, - newcoords, - order=3, - prefilter=False))-10.**-10.)*(numpy.exp(self._rapLzInterp(Lz[indxc]))-10.**-5.) - if numpy.sum(indx) > 0: - eccindiv, zmaxindiv, rperiindiv, rapindiv=\ - self._aA.EccZmaxRperiRap(R[indx], - vR[indx], - vT[indx], - z[indx], - vz[indx], - **kwargs) - ecc[indx]= eccindiv - zmax[indx]= zmaxindiv - rperi[indx]= rperiindiv - rap[indx]= rapindiv - else: - ecc,zmax,rperi,rap= self.EccZmaxRperiRap(numpy.array([R]), - numpy.array([vR]), - numpy.array([vT]), - numpy.array([z]), - numpy.array([vz]), - **kwargs) - return (ecc[0],zmax[0],rperi[0],rap[0]) - ecc[ecc < 0.]= 0. - zmax[zmax < 0.]= 0. - rperi[rperi < 0.]= 0. - rap[rap < 0.]= 0. - return (ecc,zmax,rperi,rap) - def _evaluate(self,*args,**kwargs): """ NAME: @@ -583,6 +443,144 @@ def JR(self,*args,**kwargs): """ return self(*args,**kwargs)[0] + def _EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-15 - Written - Bovy (UofT) + + """ + if len(args) == 5: #R,vR.vT, z, vz + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + Lz= R*vT + Phi= _evaluatePotentials(self._pot,R,z) + E= Phi+vR**2./2.+vT**2./2.+vz**2./2. + thisERL= -numpy.exp(self._ERLInterp(Lz))+self._ERLmax + thisERa= -numpy.exp(self._ERaInterp(Lz))+self._ERamax + if isinstance(R,numpy.ndarray): + indx= ((E-thisERa)/(thisERL-thisERa) > 1.)\ + *(((E-thisERa)/(thisERL-thisERa)-1.) < 10.**-2.) + E[indx]= thisERL[indx] + indx= ((E-thisERa)/(thisERL-thisERa) < 0.)\ + *((E-thisERa)/(thisERL-thisERa) > -10.**-2.) + E[indx]= thisERa[indx] + indx= (Lz < self._Lzmin) + indx+= (Lz > self._Lzmax) + indx+= ((E-thisERa)/(thisERL-thisERa) > 1.) + indx+= ((E-thisERa)/(thisERL-thisERa) < 0.) + indxc= True^indx + ecc= numpy.empty(R.shape) + zmax= numpy.empty(R.shape) + rperi= numpy.empty(R.shape) + rap= numpy.empty(R.shape) + if numpy.sum(indxc) > 0: + u0= numpy.exp(self._logu0Interp.ev(Lz[indxc], + (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])))) + sinh2u0= numpy.sinh(u0)**2. + thisEr= self.Er(R[indxc],z[indxc],vR[indxc],vz[indxc], + E[indxc],Lz[indxc],sinh2u0,u0) + thisEz= self.Ez(R[indxc],z[indxc],vR[indxc],vz[indxc], + E[indxc],Lz[indxc],sinh2u0,u0) + thisv2= self.vatu0(E[indxc],Lz[indxc],u0,self._delta*numpy.sinh(u0),retv2=True) + cos2psi= 2.*thisEr/thisv2/(1.+sinh2u0) #latter is cosh2u0 + cos2psi[(cos2psi > 1.)*(cos2psi < 1.+10.**-5.)]= 1. + indxCos2psi= (cos2psi > 1.) + indxCos2psi+= (cos2psi < 0.) + indxc[indxc]= True^indxCos2psi#Handle these two cases as off-grid + indx= True^indxc + psi= numpy.arccos(numpy.sqrt(cos2psi[True^indxCos2psi])) + coords= numpy.empty((3,numpy.sum(indxc))) + coords[0,:]= (Lz[indxc]-self._Lzmin)/(self._Lzmax-self._Lzmin)*(self._nLz-1.) + y= (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])) + coords[1,:]= y*(self._nE-1.) + coords[2,:]= psi/numpy.pi*2.*(self._npsi-1.) + ecc[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._eccFiltered, + coords, + order=3, + prefilter=False))-10.**-10.) + rperi[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rperiFiltered, + coords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._rperiLzInterp(Lz[indxc]))-10.**-5.) + # We do rap below with zmax + #Switch to Ez-calculated psi + sin2psi= 2.*thisEz[True^indxCos2psi]/thisv2[True^indxCos2psi]/(1.+sinh2u0[True^indxCos2psi]) #latter is cosh2u0 + sin2psi[(sin2psi > 1.)*(sin2psi < 1.+10.**-5.)]= 1. + indxSin2psi= (sin2psi > 1.) + indxSin2psi+= (sin2psi < 0.) + indxc[indxc]= True^indxSin2psi#Handle these two cases as off-grid + indx= True^indxc + psiz= numpy.arcsin(numpy.sqrt(sin2psi[True^indxSin2psi])) + newcoords= numpy.empty((3,numpy.sum(indxc))) + newcoords[0:2,:]= coords[0:2,True^indxSin2psi] + newcoords[2,:]= psiz/numpy.pi*2.*(self._npsi-1.) + zmax[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._zmaxFiltered, + newcoords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._zmaxLzInterp(Lz[indxc]))-10.**-5.) + rap[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rapFiltered, + newcoords, + order=3, + prefilter=False))-10.**-10.)*(numpy.exp(self._rapLzInterp(Lz[indxc]))-10.**-5.) + if numpy.sum(indx) > 0: + eccindiv, zmaxindiv, rperiindiv, rapindiv=\ + self._aA.EccZmaxRperiRap(R[indx], + vR[indx], + vT[indx], + z[indx], + vz[indx], + **kwargs) + ecc[indx]= eccindiv + zmax[indx]= zmaxindiv + rperi[indx]= rperiindiv + rap[indx]= rapindiv + else: + ecc,zmax,rperi,rap= self.EccZmaxRperiRap(numpy.array([R]), + numpy.array([vR]), + numpy.array([vT]), + numpy.array([z]), + numpy.array([vz]), + **kwargs) + return (ecc[0],zmax[0],rperi[0],rap[0]) + ecc[ecc < 0.]= 0. + zmax[zmax < 0.]= 0. + rperi[rperi < 0.]= 0. + rap[rap < 0.]= 0. + return (ecc,zmax,rperi,rap) + def vatu0(self,E,Lz,u0,R,retv2=False): """ NAME: From e223227fdecc83e59d23db99d7d794a5fa8e19c5 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 17:50:22 -0500 Subject: [PATCH 14/62] Tweaks to EccZmaxRperiRap tests to more fully cover all cases --- tests/test_actionAngle.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index eeb7d7c40..12fb722d6 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -933,7 +933,7 @@ def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0(): assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 - te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz,u0=1.15) assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None @@ -942,10 +942,11 @@ def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0(): def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0_c(): from galpy.actionAngle import actionAngleStaeckel from galpy.potential import MWPotential + from galpy.orbit import Orbit aAS= actionAngleStaeckel(pot=MWPotential,delta=0.71,c=True,useu0=True) #circular orbit R,vR,vT,z,vz= 1.,0.,1.,0.,0. - te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + te,tzmax,_,_= aAS.EccZmaxRperiRap(Orbit([R,vR,vT,z,vz])) assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit @@ -1156,7 +1157,8 @@ def test_actionAngleStaeckel_conserved_EccZmaxRperiRap_ecc(): aAS= actionAngleStaeckel(pot=MWPotential,c=False,delta=0.71) obs= Orbit([1.1,0.2, 1.3, 0.3,0.,2.]) check_actionAngle_conserved_EccZmaxRperiRap(aAS,obs,MWPotential, - -1.8,-1.4,-1.8,-1.8,ntimes=101) + -1.8,-1.4,-1.8,-1.8,ntimes=101, + inclphi=True) return None #Test the conservation of ecc, zmax, rperi, rap of an actionAngleStaeckel @@ -2783,12 +2785,17 @@ def check_actionAngle_linear_angles(aA,obs,pot, #Test that the ecc, zmax, rperi, rap are conserved along an orbit def check_actionAngle_conserved_EccZmaxRperiRap(aA,obs,pot,tole,tolzmax, tolrperi,tolrap, - ntimes=1001): + ntimes=1001,inclphi=False): times= numpy.linspace(0.,100.,ntimes) obs.integrate(times,pot,method='dopr54_c') - es,zmaxs,rperis,raps= aA.EccZmaxRperiRap(\ - obs.R(times),obs.vR(times),obs.vT(times),obs.z(times), - obs.vz(times)) + if inclphi: + es,zmaxs,rperis,raps= aA.EccZmaxRperiRap(\ + obs.R(times),obs.vR(times),obs.vT(times),obs.z(times), + obs.vz(times),obs.phi(times)) + else: + es,zmaxs,rperis,raps= aA.EccZmaxRperiRap(\ + obs.R(times),obs.vR(times),obs.vT(times),obs.z(times), + obs.vz(times)) assert numpy.amax(numpy.fabs(es/numpy.mean(es)-1)) < 10.**tole, 'Eccentricity conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(es/numpy.mean(es)-1))) assert numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1)) < 10.**tolzmax, 'Zmax conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(zmaxs/numpy.mean(zmaxs)-1))) assert numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1)) < 10.**tolrperi, 'Rperi conservation fails at %g%%' % (100.*numpy.amax(numpy.fabs(rperis/numpy.mean(rperis)-1))) From 784701496af3380cee1e672b3f8e7bb8fdb8437c Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 18:35:12 -0500 Subject: [PATCH 15/62] Tweaks to a few more tests to cover all EccZmaxRperiRap cases --- tests/test_actionAngle.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 12fb722d6..6c97ccfa7 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -933,7 +933,7 @@ def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0(): assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 - te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz,u0=1.15) + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None @@ -951,7 +951,8 @@ def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0_c(): assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 - te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz) + print("Should be here") + te,tzmax,_,_= aAS.EccZmaxRperiRap(R,vR,vT,z,vz,u0=1.15) assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None @@ -1396,6 +1397,7 @@ def test_actionAngleStaeckelGrid_Isochrone_actions(): def test_actionAngleStaeckelGrid_basic_EccZmaxRperiRap_c(): from galpy.actionAngle import actionAngleStaeckelGrid from galpy.potential import MWPotential, interpRZPotential + from galpy.orbit import Orbit rzpot= interpRZPotential(RZPot=MWPotential, rgrid=(numpy.log(0.01),numpy.log(20.),201), logR=True, @@ -1420,7 +1422,7 @@ def test_actionAngleStaeckelGrid_basic_EccZmaxRperiRap_c(): assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' #Another close-to-circular orbit R,vR,vT,z,vz= 1.0,0.0,1.,0.01,0.0 - te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + te,tzmax,_,_= aAA.EccZmaxRperiRap(Orbit([R,vR,vT,z,vz])) assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None @@ -1430,11 +1432,12 @@ def test_actionAngleStaeckelGrid_conserved_EccZmaxRperiRap_c(): from galpy.potential import MWPotential from galpy.actionAngle import actionAngleStaeckelGrid from galpy.orbit import Orbit - obs= Orbit([1.05, 0.02, 1.05, 0.03,0.]) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) aAA= actionAngleStaeckelGrid(pot=MWPotential,delta=0.71,c=True, interpecc=True) check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,MWPotential, - -2.,-2.,-2.,-2.,ntimes=101) + -2.,-2.,-2.,-2.,ntimes=101, + inclphi=True) return None #Test the actionAngleIsochroneApprox against an isochrone potential: actions From d310f66b32491a6c00ad2a7af9c5cd4fba295624 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 18:58:20 -0500 Subject: [PATCH 16/62] Travis build fails for ecc of close to circular orbit, can't reproduce locally; e < 0 seems like the only issue that's possible --- galpy/actionAngle_src/actionAngleStaeckelGrid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/galpy/actionAngle_src/actionAngleStaeckelGrid.py b/galpy/actionAngle_src/actionAngleStaeckelGrid.py index 4132cab71..1f9970918 100644 --- a/galpy/actionAngle_src/actionAngleStaeckelGrid.py +++ b/galpy/actionAngle_src/actionAngleStaeckelGrid.py @@ -223,6 +223,7 @@ def __init__(self,pot=None,delta=None,Rmax=5., jr[numpy.isnan(jr)]= 0. jz[numpy.isnan(jz)]= 0. if interpecc: + ecc[(ecc < 0.)]= 0. ecc[(ecc > 1.)]= 1. ecc[numpy.isnan(ecc)]= 0. ecc[numpy.isinf(ecc)]= 1. From 02e17f7d959871e65dca3baafd7ab86c9a295d1b Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 15 Dec 2017 19:15:58 -0500 Subject: [PATCH 17/62] Fix quotation in wrapper docs [ci skip] --- doc/source/potential.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/potential.rst b/doc/source/potential.rst index 68260fdb8..fd625e6d4 100644 --- a/doc/source/potential.rst +++ b/doc/source/potential.rst @@ -235,7 +235,7 @@ for ``DehnenBarPotential``. Thus we can compare the two >>> print(dp.Rforce(0.9,0.3,phi=3.,t=-2.)-dswp.Rforce(0.9,0.3,phi=3.,t=-2.)) # 0.0 -The wrapper ``SolidBodyRotationWrapperPotential`` allows one to make any potential rotate around the z axis. This can be used, for example, to make general bar-shaped potentials, which one could construct from a basis-function expansion with ``SCFPotential``, rotate without having to implement the rotation directly. As an example consider this ``SoftenedNeedleBarPotential (which has a potential-specific implementation of rotation) +The wrapper ``SolidBodyRotationWrapperPotential`` allows one to make any potential rotate around the z axis. This can be used, for example, to make general bar-shaped potentials, which one could construct from a basis-function expansion with ``SCFPotential``, rotate without having to implement the rotation directly. As an example consider this ``SoftenedNeedleBarPotential`` (which has a potential-specific implementation of rotation) >>> sp= SoftenedNeedleBarPotential(normalize=1.,omegab=1.8,pa=0.) From e22dcbc006e3ee8cba2cbc2b22753a275acc9308 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 18 Dec 2017 09:58:17 -0500 Subject: [PATCH 18/62] Add a bunch of papers [ci skip] --- doc/source/index.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index d8216a5bc..a26640c66 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -237,10 +237,16 @@ The following is a list of publications using ``galpy``; please let me (bovy at Uses ``galpy.orbit`` integration in ``MWPotential2014`` to investigate the orbit and its uncertainty of 2MASS J151113.24–213003.0, an extremely metal-poor field star with measureable r-process abundances, and of other similar metal-poor stars. The authors find that all of these stars are on highly eccentric orbits, possibly indicating that they originated in dwarf galaxies. #. *The Gaia-ESO Survey: Churning through the Milky Way*, M. R. Hayden, A. Recio-Blanco, P. de Laverny, et al. (2017) *Astron. & Astrophys.*, in press (`arXiv/1711.05751 `_): Employs ``galpy.orbit`` integration in ``MWPotential2014`` to study the orbital characteristics (eccentricity, pericentric radius) of a sample of 2,364 stars observed in the Milky Way as part of the Gaia-ESO survey. -#. *The Evolution of the Galactic Thick Disk with the LAMOST Survey*, Chengdong Li & Gang Zhao (2017) *Astrophys. J.*, **850**, 25s (`2017ApJ...850...25L `_): +#. *The Evolution of the Galactic Thick Disk with the LAMOST Survey*, Chengdong Li & Gang Zhao (2017) *Astrophys. J.*, **850**, 25 (`2017ApJ...850...25L `_): Uses ``galpy.orbit`` integration in ``MWPotential2014`` to investigate the orbital characteristics (eccentricity, maximum height above the plane, angular momentum) of a sample of about 2,000 stars in the thicker-disk component of the Milky Way. #. *The Orbit and Origin of the Ultra-faint Dwarf Galaxy Segue 1*, T. K. Fritz, M. Lokken, N. Kallivayalil, A. Wetzel, S. T. Linden, P. Zivick, & E. J. Tollerud (2017) *Astrophys. J.*, submitted (`arXiv/1711.09097 `_): Employs ``galpy.orbit`` integration in ``MWPotential2014`` and a version of this potential with a more massive dark-matter halo to investigate the orbit and origin of the dwarf-spheroidal galaxy Segue 1 using a newly measured proper motion with SDSS and LBC data. +#. *Prospects for detection of hypervelocity stars with Gaia*, T. Marchetti, O. Contigiani, E. M. Rossi, J. G. Albert, A. G. A. Brown, & A. Sesana (2017) *Mon. Not. Roy. Astron. Soc.*, submitted (`arXiv/1711.11397 `_): + Uses ``galpy.orbit`` integration in a custom Milky-Way-like potential built from ``galpy.potential`` models to create mock catalogs of hypervelocity stars in the Milky Way for different ejection mechanisms and study the prospects of their detection with *Gaia*. +#. *The AMBRE project: The thick thin disk and thin thick disk of the Milky Way*, Hayden, M. R., Recio-Blanco, A., de Laverny, P., Mikolaitis, S., & Worley, C. C. (2017) *Astron. & Astrophys.*, **608**, L1 (`arXiv/1712.02358 `_): + Employs ``galpy.orbit`` integration in ``MWPotential2014`` to characterize the orbits of 494 nearby stars analyzed as part of the AMBRE project to learn about their distribution within the Milky Way. +#. *KELT-21b: A Hot Jupiter Transiting the Rapidly-Rotating Metal-Poor Late-A Primary of a Likely Hierarchical Triple System*, Marshall C. Johnson, Joseph E. Rodriguez, George Zhou, et al. (2017) *Astrophys. J.*, submitted (`arXiv/1712.03241 `_): + Uses ``galpy.orbit`` integration in ``MWPotential2014`` to investigate the Galactic orbit of KELT-21b, a hot jupiter around a low-metallicity A-type star. Indices and tables From 8f8628641a941e598dca3fcde147e54a909b2b9c Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 11:27:05 -0500 Subject: [PATCH 19/62] Add EccZmaxRperiRap method to actionAngleAdiabatic --- galpy/actionAngle_src/actionAngleAdiabatic.py | 102 ++++++++++++++++++ .../actionAngle_src/actionAngleAdiabatic_c.py | 83 ++++++++++++++ .../actionAngle_c_ext/actionAngleAdiabatic.c | 46 ++++++++ 3 files changed, 231 insertions(+) diff --git a/galpy/actionAngle_src/actionAngleAdiabatic.py b/galpy/actionAngle_src/actionAngleAdiabatic.py index 821738527..b05d98b86 100644 --- a/galpy/actionAngle_src/actionAngleAdiabatic.py +++ b/galpy/actionAngle_src/actionAngleAdiabatic.py @@ -160,6 +160,108 @@ def _evaluate(self,*args,**kwargs): else: return (aAAxi.JR(**kwargs),aAAxi._R*aAAxi._vT,aAAxi.Jz(**kwargs)) + def _EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + _EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the adiabatic approximation + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-21 - Written - Bovy (UofT) + + """ + if ((self._c and not ('c' in kwargs and not kwargs['c']))\ + or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ + and _check_c(self._pot): + if len(args) == 5: #R,vR.vT, z, vz + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + if isinstance(R,float): + R= nu.array([R]) + vR= nu.array([vR]) + vT= nu.array([vT]) + z= nu.array([z]) + vz= nu.array([vz]) + rperi,Rap,zmax, err= actionAngleAdiabatic_c.actionAngleRperiRapZmaxAdiabatic_c(\ + self._pot,self._gamma,R,vR,vT,z,vz) + if err == 0: + rap= nu.sqrt(Rap**2.+zmax**2.) + ecc= (rap-rperi)/(rap+rperi) + return (ecc,zmax,rperi,rap) + else: #pragma: no cover + raise RuntimeError("C-code for calculation actions failed; try with c=False") + else: + if 'c' in kwargs and kwargs['c'] and not self._c: + warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) #pragma: no cover + kwargs.pop('c',None) + if (len(args) == 5 or len(args) == 6) \ + and isinstance(args[0],nu.ndarray): + oecc= nu.zeros((len(args[0]))) + orperi= nu.zeros((len(args[0]))) + orap= nu.zeros((len(args[0]))) + ozmax= nu.zeros((len(args[0]))) + for ii in range(len(args[0])): + if len(args) == 5: + targs= (args[0][ii],args[1][ii],args[2][ii], + args[3][ii],args[4][ii]) + elif len(args) == 6: + targs= (args[0][ii],args[1][ii],args[2][ii], + args[3][ii],args[4][ii],args[5][ii]) + tecc, tzmax, trperi,trap= self._EccZmaxRperiRap(\ + *targs,**copy.copy(kwargs)) + oecc[ii]= tecc + ozmax[ii]= tzmax + orperi[ii]= trperi + orap[ii]= trap + return (oecc,ozmax,orperi,orap) + else: + #Set up the actionAngleAxi object + self._parse_eval_args(*args) + if isinstance(self._pot,list): + thispot= [p.toPlanar() for p in self._pot] + else: + thispot= self._pot.toPlanar() + if isinstance(self._pot,list): + thisverticalpot= [p.toVertical(self._eval_R) for p in self._pot] + else: + thisverticalpot= self._pot.toVertical(self._eval_R) + aAAxi= actionAngleAxi(*args,pot=thispot, + verticalPot=thisverticalpot, + gamma=self._gamma) + rperi,Rap= aAAxi.calcRapRperi(**kwargs) + zmax= aAAxi.calczmax(**kwargs) + rap= nu.sqrt(Rap**2.+zmax**2.) + return ((rap-rperi)/(rap+rperi),zmax,rperi,rap) + def calcRapRperi(self,*args,**kwargs): """ NAME: diff --git a/galpy/actionAngle_src/actionAngleAdiabatic_c.py b/galpy/actionAngle_src/actionAngleAdiabatic_c.py index 7d21ce456..8fe59f44a 100644 --- a/galpy/actionAngle_src/actionAngleAdiabatic_c.py +++ b/galpy/actionAngle_src/actionAngleAdiabatic_c.py @@ -116,3 +116,86 @@ def actionAngleAdiabatic_c(pot,gamma,R,vR,vT,z,vz): return (jr,jz,err.value) +def actionAngleRperiRapZmaxAdiabatic_c(pot,gamma,R,vR,vT,z,vz): + """ + NAME: + actionAngleRperiRapZmaxAdiabatic_c + PURPOSE: + Use C to calculate planar (Rperi,Rap) and the maximum height Zmax using the adiabatic approximation (rap = sqrt(Rap^2+Zmax^2)) + INPUT: + pot - Potential or list of such instances + gamma - as in Lz -> Lz+\gamma * J_z + R, vR, vT, z, vz - coordinates (arrays) + OUTPUT: + (Rperi,Rap,Zmax,err) + Rperi,Rap,Zmax : array, shape (len(R)) + err - non-zero if error occured + HISTORY: + 2017-12-21 - Written - Bovy (UofT) + """ + #Parse the potential + npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + + #Set up result arrays + rperi= numpy.empty(len(R)) + rap= numpy.empty(len(R)) + zmax= numpy.empty(len(R)) + err= ctypes.c_int(0) + + #Set up the C code + ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') + actionAngleAdiabatic_actionsFunc= _lib.actionAngleAdiabatic_RperiRapZmax + actionAngleAdiabatic_actionsFunc.argtypes= [ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_int, + ndpointer(dtype=numpy.int32,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_double, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.POINTER(ctypes.c_int)] + + #Array requirements, first store old order + f_cont= [R.flags['F_CONTIGUOUS'], + vR.flags['F_CONTIGUOUS'], + vT.flags['F_CONTIGUOUS'], + z.flags['F_CONTIGUOUS'], + vz.flags['F_CONTIGUOUS']] + R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) + vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) + vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) + z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) + vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) + rperi= numpy.require(rperi,dtype=numpy.float64,requirements=['C','W']) + rap= numpy.require(rap,dtype=numpy.float64,requirements=['C','W']) + zmax= numpy.require(zmax,dtype=numpy.float64,requirements=['C','W']) + + #Run the C code + actionAngleAdiabatic_actionsFunc(len(R), + R, + vR, + vT, + z, + vz, + ctypes.c_int(npot), + pot_type, + pot_args, + ctypes.c_double(gamma), + rperi, + rap, + zmax, + ctypes.byref(err)) + + #Reset input arrays + if f_cont[0]: R= numpy.asfortranarray(R) + if f_cont[1]: vR= numpy.asfortranarray(vR) + if f_cont[2]: vT= numpy.asfortranarray(vT) + if f_cont[3]: z= numpy.asfortranarray(z) + if f_cont[4]: vz= numpy.asfortranarray(vz) + + return (rperi,rap,zmax,err.value) diff --git a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c index 09690146d..97a678133 100644 --- a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c +++ b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c @@ -38,6 +38,9 @@ struct JzAdiabaticArg{ /* Function Declarations */ +void actionAngleAdiabatic_RperiRapZmax(int,double *,double *,double *,double *, + double *,int,int *,double *,double, + double *,double *,double *,int *); void actionAngleAdiabatic_actions(int,double *,double *,double *,double *, double *,int,int *,double *,double, double *,double *,int *); @@ -85,6 +88,49 @@ inline void calcEREzL(int ndata, /* MAIN FUNCTIONS */ +void actionAngleAdiabatic_RperiRapZmax(int ndata, + double *R, + double *vR, + double *vT, + double *z, + double *vz, + int npot, + int * pot_type, + double * pot_args, + double gamma, + double *rperi, + double *rap, + double *zmax, + int * err){ + int ii; + //Set up the potentials + struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); + parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); + //ER, Ez, Lz + double *ER= (double *) malloc ( ndata * sizeof(double) ); + double *Ez= (double *) malloc ( ndata * sizeof(double) ); + double *Lz= (double *) malloc ( ndata * sizeof(double) ); + calcEREzL(ndata,R,vR,vT,z,vz,ER,Ez,Lz,npot,actionAngleArgs); + //Calculate peri and apocenters + double *jz= (double *) malloc ( ndata * sizeof(double) ); + calcZmax(ndata,zmax,z,R,Ez,npot,actionAngleArgs); + calcJzAdiabatic(ndata,jz,zmax,R,Ez,npot,actionAngleArgs,10); + //Adjust planar effective potential for gamma + UNUSED int chunk= CHUNKSIZE; +#pragma omp parallel for schedule(static,chunk) private(ii) + for (ii=0; ii < ndata; ii++){ + *(Lz+ii)= fabs( *(Lz+ii) ) + gamma * *(jz+ii); + *(ER+ii)+= 0.5 * *(Lz+ii) * *(Lz+ii) / *(R+ii) / *(R+ii) + - 0.5 * *(vT+ii) * *(vT+ii); + } + calcRapRperi(ndata,rperi,rap,R,ER,Lz,npot,actionAngleArgs); + free_potentialArgs(npot,actionAngleArgs); + free(actionAngleArgs); + free(ER); + free(Ez); + free(Lz); + free(jz); +} void actionAngleAdiabatic_actions(int ndata, double *R, double *vR, From 7869ba8136b4ab3ad5d261b337f91b3d8010c30d Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 11:27:21 -0500 Subject: [PATCH 20/62] Tests of EccZmaxRperiRap actionAngleAdiabatic routines --- tests/test_actionAngle.py | 115 +++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 6c97ccfa7..580585540 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -461,6 +461,69 @@ def test_actionAngleAdiabatic_zerolz_actions_c(): assert numpy.fabs(js[0]-js2[0]) < 10.**-6., 'Orbit with zero angular momentum does not have the correct Jr' return None +#Basic sanity checking of the actionAngleAdiabatic ecc, zmax, rperi, rap calc. +def test_actionAngleAdiabatic_basic_EccZmaxRperiRap(): + from galpy.actionAngle import actionAngleAdiabatic + from galpy.potential import MWPotential + aAA= actionAngleAdiabatic(pot=MWPotential,gamma=1.) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,0.99,0.0,0.0 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + #Another close-to-circular orbit + R,vR,vT,z,vz= 1.0,0.0,1.,0.01,0.0 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + +#Basic sanity checking of the actionAngleAdiabatic ecc, zmax, rperi, rap calc. +def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_gamma0(): + from galpy.actionAngle import actionAngleAdiabatic + from galpy.potential import MWPotential + aAA= actionAngleAdiabatic(pot=MWPotential,gamma=0.,useu0=True) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + +#Basic sanity checking of the actionAngleAdiabatic ecc, zmax, rperi, rap calc. +def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_u0_c(): + from galpy.actionAngle import actionAngleAdiabatic + from galpy.potential import MWPotential + from galpy.orbit import Orbit + aAA= actionAngleAdiabatic(pot=MWPotential,gamma=1.,c=True) + #circular orbit + R,vR,vT,z,vz= 1.,0.,1.,0.,0. + te,tzmax,_,_= aAA.EccZmaxRperiRap(Orbit([R,vR,vT,z,vz])) + assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' + assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' + #Close-to-circular orbit + R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 + print("Should be here") + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz,u0=1.15) + assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' + assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' + return None + #Test the actions of an actionAngleAdiabatic def test_actionAngleAdiabatic_conserved_actions(): from galpy.potential import MWPotential @@ -524,6 +587,56 @@ def test_actionAngleAdiabatic_conserved_actions_interppot_c(): -1.4,-8.,-1.7,ntimes=101) return None +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleAdiabatic +def test_actionAngleAdiabatic_conserved_EccZmaxRperiRap(): + from galpy.potential import MWPotential + from galpy.actionAngle import actionAngleAdiabatic + from galpy.orbit import Orbit + aAA= actionAngleAdiabatic(pot=MWPotential,c=False,gamma=1.) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,0.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,MWPotential, + -1.7,-1.4,-2.,-2.,ntimes=101) + return None + +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleAdiabatic +def test_actionAngleAdiabatic_conserved_EccZmaxRperiRap_ecc(): + from galpy.potential import MWPotential + from galpy.actionAngle import actionAngleAdiabatic + from galpy.orbit import Orbit + aAA= actionAngleAdiabatic(pot=MWPotential,c=False,gamma=1.) + obs= Orbit([1.1,0.2, 1.3, 0.1,0.,2.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,MWPotential, + -1.1,-0.4,-1.8,-1.8,ntimes=101, + inclphi=True) + return None + +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleAdiabatic +def test_actionAngleAdiabatic_conserved_EccZmaxRperiRap_singlepot_c(): + from galpy.potential import MiyamotoNagaiPotential + from galpy.actionAngle import actionAngleAdiabatic + from galpy.orbit import Orbit + mp= MiyamotoNagaiPotential(normalize=1.) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) + aAA= actionAngleAdiabatic(pot=mp,c=True) + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,mp, + -1.7,-1.4,-2.,-2.,ntimes=101) + return None + +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleAdiabatic +def test_actionAngleAdiabatic_conserved_EccZmaxRperiRa_interppot_c(): + from galpy.potential import MWPotential, interpRZPotential + from galpy.actionAngle import actionAngleAdiabatic + from galpy.orbit import Orbit + ip= interpRZPotential(RZPot=MWPotential, + rgrid=(numpy.log(0.01),numpy.log(20.),101), + zgrid=(0.,1.,101),logR=True,use_c=True,enable_c=True, + interpPot=True,interpRforce=True,interpzforce=True) + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) + aAA= actionAngleAdiabatic(pot=ip,c=True) + check_actionAngle_conserved_EccZmaxRperiRap(aAA,obs,ip, + -1.7,-1.4,-2.,-2.,ntimes=101) + return None + #Test the actionAngleAdiabatic against an isochrone potential: actions def test_actionAngleAdiabatic_Isochrone_actions(): from galpy.potential import IsochronePotential @@ -1194,8 +1307,6 @@ def test_actionAngleStaeckel_conserved_EccZmaxRperiRap_c(): ntimes=101) return None -#HERE - #Test the actionAngleStaeckel against an isochrone potential: actions def test_actionAngleStaeckel_otherIsochrone_actions(): from galpy.potential import IsochronePotential From 98d4ef8bce11f445b3157259044daf73a40bb431 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 11:30:43 -0500 Subject: [PATCH 21/62] Add API docs on EccZmaxRperiRap --- doc/source/reference/aa.rst | 1 + doc/source/reference/aaecczmaxrperirap.rst | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 doc/source/reference/aaecczmaxrperirap.rst diff --git a/doc/source/reference/aa.rst b/doc/source/reference/aa.rst index 899bf8c21..ce50cf33e 100644 --- a/doc/source/reference/aa.rst +++ b/doc/source/reference/aa.rst @@ -18,6 +18,7 @@ for more info (e.g., ``?actionAngleIsochrone.__call__``) __call__ actionsFreqs actionsFreqsAngles + EccZmaxRperiRap turn_physical_off turn_physical_on diff --git a/doc/source/reference/aaecczmaxrperirap.rst b/doc/source/reference/aaecczmaxrperirap.rst new file mode 100644 index 000000000..e462b5343 --- /dev/null +++ b/doc/source/reference/aaecczmaxrperirap.rst @@ -0,0 +1,4 @@ +galpy.actionAngle.actionAngle.EccZmaxRperiRap +============================================= + +.. automethod:: galpy.actionAngle.actionAngle.EccZmaxRperiRap From bc33aa2c57022173676b897be777b87b7065646a Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:25:20 -0500 Subject: [PATCH 22/62] EccZmaxRperiRap for actionAngleIsochrone (kepler approx.) --- galpy/actionAngle_src/actionAngleIsochrone.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/galpy/actionAngle_src/actionAngleIsochrone.py b/galpy/actionAngle_src/actionAngleIsochrone.py index 9b4f04796..e618be0e1 100644 --- a/galpy/actionAngle_src/actionAngleIsochrone.py +++ b/galpy/actionAngle_src/actionAngleIsochrone.py @@ -12,9 +12,11 @@ # ############################################################################### import copy +import warnings import numpy as nu from galpy.actionAngle_src.actionAngle import actionAngle from galpy.potential import IsochronePotential +from galpy.util import galpyWarning _APY_LOADED= True try: from astropy import units @@ -298,3 +300,75 @@ def _actionsFreqsAngles(self,*args,**kwargs): anglez= anglez % (2.*nu.pi) return (Jr,Jphi,Jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) + def _EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + _EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter for an isochrone potential + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-22 - Written - Bovy (UofT) + + """ + if len(args) == 5: #R,vR.vT, z, vz pragma: no cover + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + if isinstance(R,float): + R= nu.array([R]) + vR= nu.array([vR]) + vT= nu.array([vT]) + z= nu.array([z]) + vz= nu.array([vz]) + if self._c: #pragma: no cover + pass + else: + Lz= R*vT + Lx= -z*vT + Ly= z*vR-R*vz + L2= Lx*Lx+Ly*Ly+Lz*Lz + E= self._ip(R,z)+vR**2./2.+vT**2./2.+vz**2./2. + if self.b == 0: + warnings.warn("zmax for point-mass (b=0) isochrone potential is only approximate, because it assumes that zmax is attained at rap, which is not necessarily the case",galpyWarning) + a= -self.amp/2./E + me2= L2/self.amp/a + e= nu.sqrt(1.-me2) + rperi= a*(1.-e) + rap= a*(1.+e) + else: + smin= 0.5*((2.*E-self.amp/self.b)\ + +nu.sqrt((2.*E-self.amp/self.b)**2. + +2.*E*(4.*self.amp/self.b+L2/self.b**2.)))/E + smax= 2.-self.amp/E/self.b-smin + rperi= smin*nu.sqrt(1.-2./smin)*self.b + rap= smax*nu.sqrt(1.-2./smax)*self.b + return ((rap-rperi)/(rap+rperi),rap*nu.sqrt(1.-Lz**2./L2), + rperi,rap) From b8708164035c2a2c2c396afa083026c0ba688d96 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:25:35 -0500 Subject: [PATCH 23/62] Tests of EccZmaxRperiRap for actionAngleIsochrone (kepler approx.) --- tests/test_actionAngle.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 580585540..e640d0436 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -50,6 +50,52 @@ def test_actionAngleIsochrone_basic_freqs(): assert numpy.fabs((jos[5]-ip.verticalfreq(1.))/ip.verticalfreq(1.)) < 10.**-2., 'Close-to-circular orbit in the isochrone potential does not have Oz=nu at %g%%' % (100.*numpy.fabs((jos[5]-ip.verticalfreq(1.))/ip.verticalfreq(1.))) return None +# Test that EccZmaxRperiRap for an IsochronePotential are correctly computed +# by comparing to a numerical orbit integration +def test_actionAngleIsochrone_EccZmaxRperiRap_againstOrbit(): + from galpy.potential import IsochronePotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleIsochrone + ip= IsochronePotential(normalize=1.,b=1.2) + aAI= actionAngleIsochrone(ip=ip) + o= Orbit([1.,0.1,1.1,0.2,0.03,0.]) + ecc, zmax, rperi, rap= aAI.EccZmaxRperiRap(o) + ts= numpy.linspace(0.,100.,100001) + o.integrate(ts,ip) + assert numpy.fabs(ecc-o.e()) < 1e-10, 'Analytically calculated eccentricity does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(zmax-o.zmax()) < 1e-5, 'Analytically calculated zmax does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(rperi-o.rperi()) < 1e-10, 'Analytically calculated rperi does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(rap-o.rap()) < 1e-10, 'Analytically calculated rap does not agree with numerically calculated one for an IsochronePotential' + # Another one + o= Orbit([1.,0.1,1.1,0.2,-0.3,0.]) + ecc, zmax, rperi, rap= aAI.EccZmaxRperiRap(o.R(),o.vR(),o.vT(), + o.z(),o.vz(),o.phi()) + ts= numpy.linspace(0.,100.,100001) + o.integrate(ts,ip) + assert numpy.fabs(ecc-o.e()) < 1e-10, 'Analytically calculated eccentricity does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(zmax-o.zmax()) < 1e-3, 'Analytically calculated zmax does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(rperi-o.rperi()) < 1e-10, 'Analytically calculated rperi does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(rap-o.rap()) < 1e-10, 'Analytically calculated rap does not agree with numerically calculated one for an IsochronePotential' + return None + +# Test that EccZmaxRperiRap for an IsochronePotential are correctly computed +# by comparing to a numerical orbit integration for a Kepler potential +def test_actionAngleIsochrone_EccZmaxRperiRap_againstOrbit_kepler(): + from galpy.potential import IsochronePotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleIsochrone + ip= IsochronePotential(normalize=1.,b=0) + aAI= actionAngleIsochrone(ip=ip) + o= Orbit([1.,0.1,1.1,0.2,0.03,0.]) + ecc, zmax, rperi, rap= aAI.EccZmaxRperiRap(o.R(),o.vR(),o.vT(),o.z(),o.vz()) + ts= numpy.linspace(0.,100.,100001) + o.integrate(ts,ip) + assert numpy.fabs(ecc-o.e()) < 1e-10, 'Analytically calculated eccentricity does not agree with numerically calculated one for an IsochronePotential' + # Don't do zmax, because zmax for Kepler is approximate + assert numpy.fabs(rperi-o.rperi()) < 1e-10, 'Analytically calculated rperi does not agree with numerically calculated one for an IsochronePotential' + assert numpy.fabs(rap-o.rap()) < 1e-10, 'Analytically calculated rap does not agree with numerically calculated one for an IsochronePotential' + return None + #Test the actions of an actionAngleIsochrone def test_actionAngleIsochrone_conserved_actions(): from galpy.potential import IsochronePotential From df606cce2f13273c3d58f923d32a74cdf25a3ba4 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:31:32 -0500 Subject: [PATCH 24/62] EccZmaxRperiRap for actionAngleSpherical --- galpy/actionAngle_src/actionAngleSpherical.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/galpy/actionAngle_src/actionAngleSpherical.py b/galpy/actionAngle_src/actionAngleSpherical.py index 52af24f25..c53f0a561 100644 --- a/galpy/actionAngle_src/actionAngleSpherical.py +++ b/galpy/actionAngle_src/actionAngleSpherical.py @@ -309,6 +309,78 @@ def _actionsFreqsAngles(self,*args,**kwargs): return (nu.array(Jr),Jphi,Jz,nu.array(Or),Op,Oz, ar,ap,az) + def _EccZmaxRperiRap(self,*args,**kwargs): + """ + NAME: + + _EccZmaxRperiRap + + PURPOSE: + + evaluate the eccentricity, maximum height above the plane, peri- and apocenter for a spherical potential + + INPUT: + + Either: + + a) R,vR,vT,z,vz[,phi]: + + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + + OUTPUT: + + (e,zmax,rperi,rap) + + HISTORY: + + 2017-12-22 - Written - Bovy (UofT) + + """ + if len(args) == 5: #R,vR.vT, z, vz + R,vR,vT, z, vz= args + elif len(args) == 6: #R,vR.vT, z, vz, phi + R,vR,vT, z, vz, phi= args + else: + self._parse_eval_args(*args) + R= self._eval_R + vR= self._eval_vR + vT= self._eval_vT + z= self._eval_z + vz= self._eval_vz + if isinstance(R,float): + R= nu.array([R]) + vR= nu.array([vR]) + vT= nu.array([vT]) + z= nu.array([z]) + vz= nu.array([vz]) + if self._c: #pragma: no cover + pass + else: + Lz= R*vT + Lx= -z*vT + Ly= z*vR-R*vz + L2= Lx*Lx+Ly*Ly+Lz*Lz + L= nu.sqrt(L2) + #Set up an actionAngleAxi object for EL and rap/rperi calculations + axiR= nu.sqrt(R**2.+z**2.) + axivT= L/axiR + axivR= (R*vR+z*vz)/axiR + rperi, rap= [], [] + for ii in range(len(axiR)): + axiaA= actionAngleAxi(axiR[ii],axivR[ii],axivT[ii], + pot=self._2dpot) + trperi,trap= axiaA.calcRapRperi() + rperi.append(trperi) + rap.append(trap) + rperi= nu.array(rperi) + rap= nu.array(rap) + return ((rap-rperi)/(rap+rperi),rap*nu.sqrt(1.-Lz**2./L2), + rperi,rap) + def _calc_jr(self,rperi,rap,E,L,fixed_quad,**kwargs): if fixed_quad: return integrate.fixed_quad(_JrSphericalIntegrand, From 211a23b330996e7da40795290978cbc9a3aab21b Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:31:47 -0500 Subject: [PATCH 25/62] Tests of EccZmaxRperiRap for actionAngleSpherical --- tests/test_actionAngle.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index e640d0436..f774c9b0d 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -246,6 +246,34 @@ def test_actionAngleSpherical_basic_freqsAngles(): assert numpy.fabs((jos[5]-lp.verticalfreq(1.))/lp.verticalfreq(1.)) < 10.**-1.9, 'Close-to-circular orbit in the spherical LogarithmicHaloPotential does not have Oz=nu at %g%%' % (100.*numpy.fabs((jos[5]-lp.verticalfreq(1.))/lp.verticalfreq(1.))) return None +# Test that EccZmaxRperiRap for a spherical potential are correctly computed +# by comparing to a numerical orbit integration +def test_actionAngleSpherical_EccZmaxRperiRap_againstOrbit(): + from galpy.potential import LogarithmicHaloPotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleSpherical + lp= LogarithmicHaloPotential(normalize=1.,q=1.) + aAS= actionAngleSpherical(pot=lp) + o= Orbit([1.,0.1,1.1,0.2,0.03,0.]) + ecc, zmax, rperi, rap= aAS.EccZmaxRperiRap(o) + ts= numpy.linspace(0.,100.,100001) + o.integrate(ts,lp) + assert numpy.fabs(ecc-o.e()) < 1e-9, 'Analytically calculated eccentricity does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(zmax-o.zmax()) < 1e-4, 'Analytically calculated zmax does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(rperi-o.rperi()) < 1e-8, 'Analytically calculated rperi does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(rap-o.rap()) < 1e-8, 'Analytically calculated rap does not agree with numerically calculated one for a spherical potential' + # Another one + o= Orbit([1.,0.1,1.1,0.2,-0.3,0.]) + ecc, zmax, rperi, rap= aAS.EccZmaxRperiRap(o.R(),o.vR(),o.vT(), + o.z(),o.vz(),o.phi()) + ts= numpy.linspace(0.,100.,100001) + o.integrate(ts,lp) + assert numpy.fabs(ecc-o.e()) < 1e-9, 'Analytically calculated eccentricity does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(zmax-o.zmax()) < 1e-3, 'Analytically calculated zmax does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(rperi-o.rperi()) < 1e-8, 'Analytically calculated rperi does not agree with numerically calculated one for a spherical potential' + assert numpy.fabs(rap-o.rap()) < 1e-8, 'Analytically calculated rap does not agree with numerically calculated one for a spherical potential' + return None + #Test the actions of an actionAngleSpherical def test_actionAngleSpherical_conserved_actions(): from galpy import potential @@ -326,6 +354,19 @@ def test_actionAngleSpherical_linear_angles_fixed_quad(): fixed_quad=True) return None +#Test the conservation of ecc, zmax, rperi, rap of an actionAngleSpherical +def test_actionAngleSpherical_conserved_EccZmaxRperiRap_ecc(): + from galpy.potential import NFWPotential + from galpy.actionAngle import actionAngleSpherical + from galpy.orbit import Orbit + np= NFWPotential(normalize=1.,a=2.) + aAS= actionAngleSpherical(pot=np) + obs= Orbit([1.1,0.2, 1.3, 0.1,0.,2.]) + check_actionAngle_conserved_EccZmaxRperiRap(aAS,obs,np, + -1.1,-0.4,-1.8,-1.8,ntimes=101, + inclphi=True) + return None + #Test the actionAngleSpherical against an isochrone potential: actions def test_actionAngleSpherical_otherIsochrone_actions(): from galpy.potential import IsochronePotential From fd95429cdfe87d9ea7c187b852cb1b49f768fb00 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:33:50 -0500 Subject: [PATCH 26/62] Some tweaks to the EccZmaxRperiRap tests for actionAngleAdiabatic --- tests/test_actionAngle.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index f774c9b0d..c9e629a3f 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -593,20 +593,19 @@ def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_gamma0(): return None #Basic sanity checking of the actionAngleAdiabatic ecc, zmax, rperi, rap calc. -def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_u0_c(): +def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_gamma_c(): from galpy.actionAngle import actionAngleAdiabatic from galpy.potential import MWPotential from galpy.orbit import Orbit aAA= actionAngleAdiabatic(pot=MWPotential,gamma=1.,c=True) #circular orbit - R,vR,vT,z,vz= 1.,0.,1.,0.,0. - te,tzmax,_,_= aAA.EccZmaxRperiRap(Orbit([R,vR,vT,z,vz])) + R,vR,vT,z,vz,phi= 1.,0.,1.,0.,0.,2. + te,tzmax,_,_= aAA.EccZmaxRperiRap(Orbit([R,vR,vT,z,vz,phi])) assert numpy.fabs(te) < 10.**-16., 'Circular orbit in the MWPotential does not have e=0' assert numpy.fabs(tzmax) < 10.**-16., 'Circular orbit in the MWPotential does not have zmax=0' #Close-to-circular orbit - R,vR,vT,z,vz= 1.01,0.01,1.,0.01,0.01 - print("Should be here") - te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz,u0=1.15) + R,vR,vT,z,vz,phi= 1.01,0.01,1.,0.01,0.01,2. + te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz,phi) assert numpy.fabs(te) < 10.**-2., 'Close-to-circular orbit in the MWPotential does not have small eccentricity' assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None From 0b4f9fb459d61747f1ad81e55d2bfab1cc0c75ee Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 17:37:46 -0500 Subject: [PATCH 27/62] Tests of quantity handling in EccZmaxRperiRap for actionAngle modules Isochrone, Spherical, and Adiabatic --- tests/test_quantity.py | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_quantity.py b/tests/test_quantity.py index 616b5ca76..05d71fa85 100644 --- a/tests/test_quantity.py +++ b/tests/test_quantity.py @@ -2696,6 +2696,8 @@ def test_actionAngle_method_returntype(): assert isinstance(aA.actionsFreqs(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqs does not return Quantity when it should' for ii in range(9): assert isinstance(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqsAngles does not return Quantity when it should' + for ii in range(3): + assert isinstance(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method EccZmaxRperiRap does not return Quantity when it should' # actionAngleSpherical pot= PlummerPotential(normalize=1.,b=0.7) aA= actionAngleSpherical(pot=pot,ro=8.,vo=220.) @@ -2705,10 +2707,14 @@ def test_actionAngle_method_returntype(): assert isinstance(aA.actionsFreqs(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqs does not return Quantity when it should' for ii in range(9): assert isinstance(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method actionsFreqsAngles does not return Quantity when it should' + for ii in range(3): + assert isinstance(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method EccZmaxRperiRap does not return Quantity when it should' # actionAngleAdiabatic aA= actionAngleAdiabatic(pot=MWPotential,ro=8.,vo=220.) for ii in range(3): assert isinstance(aA(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method __call__ does not return Quantity when it should' + for ii in range(3): + assert isinstance(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii],units.Quantity), 'actionAngleIsochrone method EccZmaxRperiRap does not return Quantity when it should' # actionAngleStaeckel aA= actionAngleStaeckel(pot=MWPotential,delta=0.45,ro=8.,vo=220.) for ii in range(3): @@ -2765,6 +2771,15 @@ def test_actionAngle_method_returnunit(): aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad) except units.UnitConversionError: raise AssertionError('actionAngle function actionsFreqsAngles does not return Quantity with the right units') + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') + for ii in range(1,4): + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') # actionAngleSpherical pot= PlummerPotential(normalize=1.,b=0.7) aA= actionAngleSpherical(pot=pot,ro=8.,vo=220.) @@ -2798,6 +2813,15 @@ def test_actionAngle_method_returnunit(): aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad) except units.UnitConversionError: raise AssertionError('actionAngle function actionsFreqsAngles does not return Quantity with the right units') + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') + for ii in range(1,4): + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') # actionAngleAdiabatic aA= actionAngleAdiabatic(pot=MWPotential,ro=8.,vo=220.) for ii in range(3): @@ -2805,6 +2829,15 @@ def test_actionAngle_method_returnunit(): aA(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc*units.km/units.s) except units.UnitConversionError: raise AssertionError('actionAngle function __call__ does not return Quantity with the right units') + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') + for ii in range(1,4): + try: + aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc) + except units.UnitConversionError: + raise AssertionError('actionAngle function EccZmaxRperiRap does not return Quantity with the right units') # actionAngleStaeckel aA= actionAngleStaeckel(pot=MWPotential,delta=0.45,ro=8.,vo=220.) for ii in range(3): @@ -2901,6 +2934,9 @@ def test_actionAngle_method_value(): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(1/units.Gyr).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]*bovy_conversion.freq_in_Gyr(vo,ro)) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' for ii in range(6,9): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0]) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' + for ii in range(1,4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]*ro) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' # actionAngleSpherical pot= PlummerPotential(normalize=1.,b=0.7) aA= actionAngleSpherical(pot=pot,ro=ro,vo=vo) @@ -2917,11 +2953,17 @@ def test_actionAngle_method_value(): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(1/units.Gyr).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]*bovy_conversion.freq_in_Gyr(vo,ro)) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' for ii in range(6,9): assert numpy.fabs(aA.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.rad).value-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle function actionsFreqsAngles does not return Quantity with the right value' + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0]) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' + for ii in range(1,4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]*ro) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' # actionAngleAdiabatic aA= actionAngleAdiabatic(pot=MWPotential,ro=ro,vo=vo) aAnu= actionAngleAdiabatic(pot=MWPotential) for ii in range(3): assert numpy.fabs(aA(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc*units.km/units.s).value-aAnu(1.1,0.1,1.1,0.1,0.2,0.)[ii]*ro*vo) < 10.**-8., 'actionAngle function __call__ does not return Quantity with the right value' + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0].to(units.dimensionless_unscaled).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[0]) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' + for ii in range(1,4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii].to(units.kpc).value-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]*ro) < 10.**-8., 'actionAngle function EccZmaxRperiRap does not return Quantity with the right value' # actionAngleStaeckel aA= actionAngleStaeckel(pot=MWPotential,delta=0.45,ro=ro,vo=vo) aAnu= actionAngleStaeckel(pot=MWPotential,delta=0.45) @@ -3153,6 +3195,8 @@ def test_actionAngle_method_inputAsQuantity(): assert numpy.fabs(aA.actionsFreqsAngles(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method actionsFreqsAngles does not return the correct value when input is Quantity' for ii in range(6,9): assert numpy.fabs(aA.actionsFreqsAngles(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.actionsFreqsAngles(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method actionsFreqsAngles does not return the correct value when input is Quantity' + for ii in range(4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method EccZmaxRperiRap does not return the correct value when input is Quantity' # actionAngleSpherical pot= PlummerPotential(normalize=1.,b=0.7) aA= actionAngleSpherical(pot=pot,ro=ro,vo=vo) @@ -3174,6 +3218,8 @@ def test_actionAngle_method_inputAsQuantity(): aAnu= actionAngleAdiabatic(pot=MWPotential) for ii in range(3): assert numpy.fabs(aA(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method __call__ does not return the correct value when input is Quantity' + for ii in range(4): + assert numpy.fabs(aA.EccZmaxRperiRap(1.1*ro*units.kpc,0.1*vo*units.km/units.s,1.1*vo*units.km/units.s,0.1*ro*units.kpc,0.2*vo*units.km/units.s,0.*units.rad,use_physical=False)[ii]-aAnu.EccZmaxRperiRap(1.1,0.1,1.1,0.1,0.2,0.)[ii]) < 10.**-8., 'actionAngle method EccZmaxRperiRap does not return the correct value when input is Quantity' # actionAngleStaeckel aA= actionAngleStaeckel(pot=MWPotential,delta=0.45,ro=ro,vo=vo) aAnu= actionAngleStaeckel(pot=MWPotential,delta=0.45) From ae8a1b504d98e065fd338f70eaddd50f8b4842f1 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 21:17:56 -0500 Subject: [PATCH 28/62] Tweaks to EccZmaxRperiRap tests for spherical and adiabatic actionAngle modules to fully cover all cases --- tests/test_actionAngle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index c9e629a3f..4dd9a5aa7 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -265,7 +265,7 @@ def test_actionAngleSpherical_EccZmaxRperiRap_againstOrbit(): # Another one o= Orbit([1.,0.1,1.1,0.2,-0.3,0.]) ecc, zmax, rperi, rap= aAS.EccZmaxRperiRap(o.R(),o.vR(),o.vT(), - o.z(),o.vz(),o.phi()) + o.z(),o.vz()) ts= numpy.linspace(0.,100.,100001) o.integrate(ts,lp) assert numpy.fabs(ecc-o.e()) < 1e-9, 'Analytically calculated eccentricity does not agree with numerically calculated one for a spherical potential' @@ -578,8 +578,9 @@ def test_actionAngleAdiabatic_basic_EccZmaxRperiRap(): #Basic sanity checking of the actionAngleAdiabatic ecc, zmax, rperi, rap calc. def test_actionAngleAdiabatic_basic_EccZmaxRperiRap_gamma0(): from galpy.actionAngle import actionAngleAdiabatic - from galpy.potential import MWPotential - aAA= actionAngleAdiabatic(pot=MWPotential,gamma=0.,useu0=True) + from galpy.potential import MiyamotoNagaiPotential + mp= MiyamotoNagaiPotential(normalize=1.,a=1.5,b=0.3) + aAA= actionAngleAdiabatic(pot=mp,gamma=0.,c=False) #circular orbit R,vR,vT,z,vz= 1.,0.,1.,0.,0. te,tzmax,_,_= aAA.EccZmaxRperiRap(R,vR,vT,z,vz) From 810c35f485ebd6344639c688d1b993bfab784598 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 22 Dec 2017 23:51:10 -0500 Subject: [PATCH 29/62] Don't run gcov for codecov upload to fix faulty codecov LCOV_EXCL handling --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 932a84e8a..32a65176f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ after_success: - lcov --capture --base-directory . --directory build/temp.linux-x86_64-2.7/galpy/ --no-external --output-file coverage_full.info - lcov --remove coverage_full.info 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/*' -o coverage.info # Codecov - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then bash <(curl -s https://codecov.io/bash); fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then bash <(curl -s https://codecov.io/bash) -X gcov; fi # coveralls: combine, generate json, and upload - coveralls-lcov -v -n coverage.info > coverage.c.json - coveralls-merge coverage.c.json From b12ec56b89ae7c03e64338433c389c188f92e59c Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 13:46:14 -0500 Subject: [PATCH 30/62] Add automagic calculation of delta for orbit interface staeckel approximation; change default orbit-interface method to Staeckel --- galpy/orbit_src/Orbit.py | 26 +++++++++++++------------- galpy/orbit_src/OrbitTop.py | 21 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/galpy/orbit_src/Orbit.py b/galpy/orbit_src/Orbit.py index 928d47e05..ad7f22a7a 100644 --- a/galpy/orbit_src/Orbit.py +++ b/galpy/orbit_src/Orbit.py @@ -1027,7 +1027,7 @@ def jr(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1078,7 +1078,7 @@ def jp(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1129,7 +1129,7 @@ def jz(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1180,7 +1180,7 @@ def wr(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1227,7 +1227,7 @@ def wp(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1274,7 +1274,7 @@ def wz(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1321,7 +1321,7 @@ def Tr(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1374,7 +1374,7 @@ def Tp(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1426,7 +1426,7 @@ def TrTp(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1471,7 +1471,7 @@ def Tz(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1524,7 +1524,7 @@ def Or(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1573,7 +1573,7 @@ def Op(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' @@ -1621,7 +1621,7 @@ def Oz(self,pot=None,**kwargs): pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index 48761c826..a5de9c4bd 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -12,6 +12,7 @@ except ImportError: _APY_LOADED= False from galpy import actionAngle +from galpy.potential import PotentialError import galpy.util.bovy_plot as plot import galpy.util.bovy_coords as coords from galpy.util.bovy_conversion import physical_conversion @@ -1196,7 +1197,7 @@ def _resetaA(self,pot=None,type=None): else: pass #Already set up - def _setupaA(self,pot=None,type='adiabatic',**kwargs): + def _setupaA(self,pot=None,type='staeckel',**kwargs): """ NAME: _setupaA @@ -1204,7 +1205,7 @@ def _setupaA(self,pot=None,type='adiabatic',**kwargs): set up an actionAngle module for this Orbit INPUT: pot - potential - type= ('adiabatic') type of actionAngle module to use + type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' @@ -1213,6 +1214,7 @@ def _setupaA(self,pot=None,type='adiabatic',**kwargs): HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written in terms of new actionAngle modules - Bovy (IAS) + 2017-12-25 - Changed default method to 'staeckel' and automatic delta estimation - Bovy (UofT) """ if hasattr(self,'_aA'): if not self._resetaA(pot=pot,type=type): return None @@ -1228,7 +1230,22 @@ def _setupaA(self,pot=None,type='adiabatic',**kwargs): self._aA= actionAngle.actionAngleAdiabatic(pot=self._aAPot, **kwargs) elif self._aAType.lower() == 'staeckel': + try: + delta= \ + kwargs.pop('delta', + actionAngle.estimateDeltaStaeckel(self._aAPot, + self.R(), + self.z())) + except PotentialError as e: + if '_R2deriv' in repr(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') + elif 'non-axi' in repr(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') + pass + else: + raise self._aA= actionAngle.actionAngleStaeckel(pot=self._aAPot, + delta=delta, **kwargs) elif self._aAType.lower() == 'isochroneapprox': from galpy.actionAngle_src.actionAngleIsochroneApprox import actionAngleIsochroneApprox From 396e6ccfb925530848a5fd0d04bf02aeaf28b6f0 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 13:46:46 -0500 Subject: [PATCH 31/62] Tests of automagic estimation of delta in orbit-interface staeckel approximation --- tests/test_actionAngle.py | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 4dd9a5aa7..bc020929a 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -1,5 +1,6 @@ from __future__ import print_function, division import os +import pytest import warnings import numpy from galpy.util import galpyWarning @@ -2692,6 +2693,54 @@ def test_orbit_interface_staeckel(): assert maxdev < 10.**-16., 'Orbit interface for actionAngleStaeckel does not return the same as actionAngle interface' return None +# Further tests of the Orbit interface for actionAngleStaeckel +def test_orbit_interface_staeckel_defaultdelta(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel, estimateDeltaStaeckel + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) + est_delta= estimateDeltaStaeckel(MWPotential2014,obs.R(),obs.z()) + # Just need to trigger delta estimation in orbit + jr_orb= obs.jr(pot=MWPotential2014,type='staeckel') + assert numpy.fabs(est_delta-obs._orb._aA._delta) < 1e-10, 'Directly estimated delta does not agree with Orbit-interface-estimated delta' + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=est_delta) + acfs= numpy.array(list(aAS.actionsFreqsAngles(obs))).reshape(9) + type= 'staeckel' + acfso= numpy.array([obs.jr(pot=MWPotential2014,type=type,delta=0.71), + obs.jp(pot=MWPotential2014,type=type), + obs.jz(pot=MWPotential2014,type=type), + obs.Or(pot=MWPotential2014,type=type), + obs.Op(pot=MWPotential2014,type=type), + obs.Oz(pot=MWPotential2014,type=type), + obs.wr(pot=MWPotential2014,type=type), + obs.wp(pot=MWPotential2014,type=type), + obs.wz(pot=MWPotential2014,type=type)]) + maxdev= numpy.amax(numpy.abs(acfs-acfso)) + assert maxdev < 10.**-16., 'Orbit interface for actionAngleStaeckel does not return the same as actionAngle interface' + return None + +def test_orbit_interface_staeckel_PotentialErrors(): + # staeckel approx. w/ automatic delta should fail if delta cannot be found + from galpy.potential import TwoPowerSphericalPotential, SpiralArmsPotential + from galpy.potential import PotentialError + from galpy.orbit import Orbit + obs= Orbit([1.05, 0.02, 1.05, 0.03,0.,2.]) + # Currently doesn't have second derivs + tp= TwoPowerSphericalPotential(normalize=1.,alpha=1.2,beta=2.5) + # Check that this potential indeed does not have second derivs + with pytest.raises(PotentialError,message='TwoPowerSphericalPotential appears to now have second derivatives, means that it cannot be used to test exceptions based on not having the second derivatives any longer') as excinfo: + dummy= tp.R2deriv(1.,0.1) + # Now check that estimating delta fails + with pytest.raises(PotentialError,message='TwoPowerSphericalPotential appears to now have second derivatives, means that it cannot be used to test exceptions based on not having the second derivatives any longer') as excinfo: + obs.jr(pot=tp,type='staeckel') + assert 'second derivatives' in str(excinfo.value), 'Estimating delta for potential lacking second derivatives should have failed with a message about the lack of second derivatives' + # Generic non-axi + sp= SpiralArmsPotential() + with pytest.raises(PotentialError,message='TwoPowerSphericalPotential appears to now have second derivatives, means that it cannot be used to test exceptions based on not having the second derivatives any longer') as excinfo: + obs.jr(pot=sp,type='staeckel') + assert 'not axisymmetric' in str(excinfo.value), 'Estimating delta for a non-axi potential should have failed with a message about the fact that the potential is non-axisymmetric' + return None + # Test the Orbit interface for actionAngleAdiabatic def test_orbit_interface_adiabatic(): from galpy.potential import MWPotential From 5051cd76ed121fdba9a7720f19ad31f5ee9e5b3d Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 18:08:26 -0500 Subject: [PATCH 32/62] Set default delta to very small for planar orbits' staeckel approximation; tweak to error message --- galpy/orbit_src/OrbitTop.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index a5de9c4bd..1664440f9 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1230,20 +1230,21 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): self._aA= actionAngle.actionAngleAdiabatic(pot=self._aAPot, **kwargs) elif self._aAType.lower() == 'staeckel': - try: - delta= \ - kwargs.pop('delta', - actionAngle.estimateDeltaStaeckel(self._aAPot, - self.R(), - self.z())) - except PotentialError as e: - if '_R2deriv' in repr(e): - raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') - elif 'non-axi' in repr(e): - raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') - pass - else: - raise + if len(self.vxvv) < 5: # planar + delta= kwargs.pop('delta',1e-10) # can't go all the way to zero + else: + try: + delta= \ + kwargs.pop('delta', + actionAngle.estimateDeltaStaeckel(\ + self._aAPot,self.R(),self.z())) + except PotentialError as e: + if 'deriv' in repr(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') + elif 'non-axi' in repr(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') + else: + raise self._aA= actionAngle.actionAngleStaeckel(pot=self._aAPot, delta=delta, **kwargs) From 5df2202964afc7a9bb1cf8be058b95c6e0fc4a1f Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 18:44:52 -0500 Subject: [PATCH 33/62] Make sure z =/= 0 when estimating delta for 3D orbit (because this does not work for z=0) and fall back onto spherical actionAngle when delta < 1e-8 --- galpy/orbit_src/OrbitTop.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index 1664440f9..8712dc1c5 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1237,7 +1237,8 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): delta= \ kwargs.pop('delta', actionAngle.estimateDeltaStaeckel(\ - self._aAPot,self.R(),self.z())) + self._aAPot,self.R(), + self.z()+(2.*(self.z() >= 0)-1.)*1e-10)) # try to make sure this is not 0 except PotentialError as e: if 'deriv' in repr(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') @@ -1245,9 +1246,12 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') else: raise - self._aA= actionAngle.actionAngleStaeckel(pot=self._aAPot, - delta=delta, - **kwargs) + if delta < 1e-6: + self._setupaA(pot=pot,type='spherical') + else: + self._aA= actionAngle.actionAngleStaeckel(pot=self._aAPot, + delta=delta, + **kwargs) elif self._aAType.lower() == 'isochroneapprox': from galpy.actionAngle_src.actionAngleIsochroneApprox import actionAngleIsochroneApprox self._aA= actionAngleIsochroneApprox(pot=self._aAPot, From ce55e681bd18222998bdb5f64c7a4b947e81b43f Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 20:38:39 -0500 Subject: [PATCH 34/62] Compute e, rperi, rap, and zmax using EccZmaxRperiRap for 3D orbits, make staeckel the default (no change for 2D) --- galpy/orbit_src/FullOrbit.py | 30 ++++++-------- galpy/orbit_src/Orbit.py | 76 +++++++++++++++++++++++++++++----- galpy/orbit_src/RZOrbit.py | 30 ++++++-------- galpy/orbit_src/planarOrbit.py | 4 +- 4 files changed, 94 insertions(+), 46 deletions(-) diff --git a/galpy/orbit_src/FullOrbit.py b/galpy/orbit_src/FullOrbit.py index 836e36f19..f2df9d9ca 100644 --- a/galpy/orbit_src/FullOrbit.py +++ b/galpy/orbit_src/FullOrbit.py @@ -279,7 +279,7 @@ def Ez(self,*args,**kwargs): t=t[ii],use_physical=False)\ +thiso[4,ii]**2./2. for ii in range(len(t))]) - def e(self,analytic=False,pot=None): + def e(self,analytic=False,pot=None,**kwargs): """ NAME: e @@ -294,11 +294,10 @@ def e(self,analytic=False,pot=None): 2010-09-15 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return (rap-rperi)/(rap+rperi) + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[0] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @@ -319,11 +318,10 @@ def rap(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return rap + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[3] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amax(self.rs) @@ -344,11 +342,10 @@ def rperi(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return rperi + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[2] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amin(self.rs) @@ -370,11 +367,10 @@ def zmax(self,analytic=False,pot=None,**kwargs): 2012-06-01 - Added analytic calculation - Bovy (IAS) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - zmax= self._aA.calczmax(self) - return zmax + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[1] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) def fit(self,vxvv,vxvv_err=None,pot=None,radec=False,lb=False, diff --git a/galpy/orbit_src/Orbit.py b/galpy/orbit_src/Orbit.py index ad7f22a7a..286f9d3e8 100644 --- a/galpy/orbit_src/Orbit.py +++ b/galpy/orbit_src/Orbit.py @@ -858,7 +858,7 @@ def Jacobi(self,*args,**kwargs): if not isinstance(out,float) and len(out) == 1: return out[0] else: return out - def e(self,analytic=False,pot=None): + def e(self,analytic=False,pot=None,**kwargs): """ NAME: @@ -866,14 +866,26 @@ def e(self,analytic=False,pot=None): PURPOSE: - calculate the eccentricity + calculate the eccentricity, either numerically from the numerical orbit integration or using analytical means INPUT: - analytic - compute this analytically + analytic(= False) compute this analytically pot - potential to use for analytical calculation + For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): + + type= ('staeckel') type of actionAngle module to use + + 1) 'adiabatic': assuming motion splits into R and z + + 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) + + 3) 'spherical': for spherical potentials, exact + + +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) + OUTPUT: eccentricity @@ -882,9 +894,11 @@ def e(self,analytic=False,pot=None): 2010-09-15 - Written - Bovy (NYU) + 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) + """ _check_consistent_units(self,pot) - return self._orb.e(analytic=analytic,pot=pot) + return self._orb.e(analytic=analytic,pot=pot,**kwargs) def rap(self,analytic=False,pot=None,**kwargs): """ @@ -894,14 +908,26 @@ def rap(self,analytic=False,pot=None,**kwargs): PURPOSE: - calculate the apocenter radius + calculate the apocenter radius, either numerically from the numerical orbit integration or using analytical means INPUT: - analytic - compute this analytically + analytic(= False) compute this analytically pot - potential to use for analytical calculation + For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): + + type= ('staeckel') type of actionAngle module to use + + 1) 'adiabatic': assuming motion splits into R and z + + 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) + + 3) 'spherical': for spherical potentials, exact + + +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) + ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output @@ -914,6 +940,8 @@ def rap(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) + 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) + """ _check_consistent_units(self,pot) return self._orb.rap(analytic=analytic,pot=pot,**kwargs) @@ -926,14 +954,26 @@ def rperi(self,analytic=False,pot=None,**kwargs): PURPOSE: - calculate the pericenter radius + calculate the pericenter radius, either numerically from the numerical orbit integration or using analytical means INPUT: - analytic - compute this analytically + analytic(= False) compute this analytically pot - potential to use for analytical calculation + For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): + + type= ('staeckel') type of actionAngle module to use + + 1) 'adiabatic': assuming motion splits into R and z + + 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) + + 3) 'spherical': for spherical potentials, exact + + +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) + ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output @@ -946,6 +986,8 @@ def rperi(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) + 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) + """ _check_consistent_units(self,pot) return self._orb.rperi(analytic=analytic,pot=pot,**kwargs) @@ -958,14 +1000,26 @@ def zmax(self,analytic=False,pot=None,**kwargs): PURPOSE: - calculate the maximum vertical height + calculate the maximum vertical height, either numerically from the numerical orbit integration or using analytical means INPUT: - analytic - compute this analytically + analytic(= False) compute this analytically pot - potential to use for analytical calculation + For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): + + type= ('staeckel') type of actionAngle module to use + + 1) 'adiabatic': assuming motion splits into R and z + + 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) + + 3) 'spherical': for spherical potentials, exact + + +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) + ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output @@ -978,6 +1032,8 @@ def zmax(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) + 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) + """ _check_consistent_units(self,pot) return self._orb.zmax(analytic=analytic,pot=pot,**kwargs) diff --git a/galpy/orbit_src/RZOrbit.py b/galpy/orbit_src/RZOrbit.py index d8fce53ba..849d85c61 100644 --- a/galpy/orbit_src/RZOrbit.py +++ b/galpy/orbit_src/RZOrbit.py @@ -256,7 +256,7 @@ def Jacobi(self,*args,**kwargs): kwargs.pop('use_physical') return out - def e(self,analytic=False,pot=None): + def e(self,analytic=False,pot=None,**kwargs): """ NAME: e @@ -271,11 +271,10 @@ def e(self,analytic=False,pot=None): 2010-09-15 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return (rap-rperi)/(rap+rperi) + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[0] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @@ -296,11 +295,10 @@ def rap(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return rap + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[3] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amax(self.rs) @@ -321,11 +319,10 @@ def rperi(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - (rperi,rap)= self._aA.calcRapRperi(self) - return rperi + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[2] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amin(self.rs) @@ -344,11 +341,10 @@ def zmax(self,analytic=False,pot=None,**kwargs): 2010-09-20 - Written - Bovy (NYU) """ if analytic: - self._setupaA(pot=pot,type='adiabatic') - zmax= self._aA.calczmax(self) - return zmax + self._setupaA(pot=pot,**kwargs) + return self._aA.EccZmaxRperiRap(self)[1] if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) def plotEz(self,*args,**kwargs): diff --git a/galpy/orbit_src/planarOrbit.py b/galpy/orbit_src/planarOrbit.py index fb8c7da18..b1e153299 100644 --- a/galpy/orbit_src/planarOrbit.py +++ b/galpy/orbit_src/planarOrbit.py @@ -72,7 +72,7 @@ def e(self,analytic=False,pot=None): (rperi,rap)= self._aA.calcRapRperi(self) return (rap-rperi)/(rap+rperi) if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @@ -472,7 +472,7 @@ def e(self,analytic=False,pot=None): (rperi,rap)= self._aA.calcRapRperi(self) return (rap-rperi)/(rap+rperi) if not hasattr(self,'orbit'): - raise AttributeError("Integrate the orbit first") + raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) From bc256c94004977444304918f92b44232146277da Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 20:39:06 -0500 Subject: [PATCH 35/62] Edit tests of analytic ecc, rperi, rap, and zmax orbit calculations for new implementation --- tests/test_orbit.py | 95 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/tests/test_orbit.py b/tests/test_orbit.py index 016207f96..a74b65e64 100644 --- a/tests/test_orbit.py +++ b/tests/test_orbit.py @@ -1180,13 +1180,27 @@ def test_analytic_ecc_rperi_rap(): o.integrate(times,ptp,method=integrator) #Eccentricity tecc= o.e() - tecc_analytic= o.e(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + tecc_analytic= o.e(analytic=True,type='adiabatic') + else: + tecc_analytic= o.e(analytic=True) #print p, integrator, tecc, tecc_analytic, (tecc-tecc_analytic)**2. assert (tecc-tecc_analytic)**2. < 10.**ttol, \ "Analytically computed eccentricity does not agree with numerical estimate for potential %s and integrator %s, by %g" %(p,integrator,(tecc-tecc_analytic)**2.) #Pericenter radius trperi= o.rperi() - trperi_analytic= o.rperi(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trperi_analytic= o.rperi(analytic=True,type='adiabatic') + else: + trperi_analytic= o.rperi(analytic=True) #print p, integrator, trperi, trperi_analytic, (trperi-trperi_analytic)**2. assert (trperi-trperi_analytic)**2. < 10.**ttol, \ "Analytically computed pericenter radius does not agree with numerical estimate for potential %s and integrator %s" %(p,integrator) @@ -1194,7 +1208,14 @@ def test_analytic_ecc_rperi_rap(): "Pericenter in physical coordinates does not agree with physical-scale times pericenter in normalized coordinates for potential %s and integrator %s" %(p,integrator) #Apocenter radius trap= o.rap() - trap_analytic= o.rap(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trap_analytic= o.rap(analytic=True,type='adiabatic') + else: + trap_analytic= o.rap(analytic=True) #print p, integrator, trap, trap_analytic, (trap-trap_analytic)**2. assert (trap-trap_analytic)**2. < 10.**ttol, \ "Analytically computed apocenter radius does not agree with numerical estimate for potential %s and integrator %s by %g" %(p,integrator,(trap-trap_analytic)**2.) @@ -1219,13 +1240,27 @@ def test_analytic_ecc_rperi_rap(): o.integrate(times,ptp,method=integrator) #Eccentricity tecc= o.e() - tecc_analytic= o.e(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + tecc_analytic= o.e(analytic=True,type='adiabatic') + else: + tecc_analytic= o.e(analytic=True) #print p, integrator, tecc, tecc_analytic, (tecc-tecc_analytic)**2. assert (tecc-tecc_analytic)**2. < 10.**ttol, \ "Analytically computed eccentricity does not agree with numerical estimate for potential %s and integrator %s" %(p,integrator) #Pericenter radius trperi= o.rperi() - trperi_analytic= o.rperi(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trperi_analytic= o.rperi(analytic=True,type='adiabatic') + else: + trperi_analytic= o.rperi(analytic=True) #print p, integrator, trperi, trperi_analytic, (trperi-trperi_analytic)**2. assert (trperi-trperi_analytic)**2. < 10.**ttol, \ "Analytically computed pericenter radius does not agree with numerical estimate for potential %s and integrator %s" %(p,integrator) @@ -1233,7 +1268,14 @@ def test_analytic_ecc_rperi_rap(): "Pericenter in physical coordinates does not agree with physical-scale times pericenter in normalized coordinates for potential %s and integrator %s" %(p,integrator) #Apocenter radius trap= o.rap() - trap_analytic= o.rap(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trap_analytic= o.rap(analytic=True,type='adiabatic') + else: + trap_analytic= o.rap(analytic=True) #print p, integrator, trap, trap_analytic, (trap-trap_analytic)**2. assert (trap-trap_analytic)**2. < 10.**ttol, \ "Analytically computed apocenter radius does not agree with numerical estimate for potential %s and integrator %s by %g" %(p,integrator,(trap-trap_analytic)) @@ -1258,13 +1300,27 @@ def test_analytic_ecc_rperi_rap(): o.integrate(times,ptp,method=integrator) #Eccentricity tecc= o.e() - tecc_analytic= o.e(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + tecc_analytic= o.e(analytic=True,type='adiabatic') + else: + tecc_analytic= o.e(analytic=True) #print p, integrator, tecc, tecc_analytic, (tecc-tecc_analytic)**2. assert (tecc-tecc_analytic)**2. < 10.**ttol, \ "Analytically computed eccentricity does not agree with numerical estimate by %g for potential %s and integrator %s" %((tecc-tecc_analytic)**2.,p,integrator) #Pericenter radius trperi= o.rperi() - trperi_analytic= o.rperi(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trperi_analytic= o.rperi(analytic=True,type='adiabatic') + else: + trperi_analytic= o.rperi(analytic=True) #print p, integrator, trperi, trperi_analytic, (trperi-trperi_analytic)**2. assert (trperi-trperi_analytic)**2. < 10.**ttol, \ "Analytically computed pericenter radius does not agree with numerical estimate for potential %s and integrator %s" %(p,integrator) @@ -1272,7 +1328,14 @@ def test_analytic_ecc_rperi_rap(): "Pericenter in physical coordinates does not agree with physical-scale times pericenter in normalized coordinates for potential %s and integrator %s" %(p,integrator) #Apocenter radius trap= o.rap() - trap_analytic= o.rap(analytic=True) + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + trap_analytic= o.rap(analytic=True,type='adiabatic') + else: + trap_analytic= o.rap(analytic=True) #print p, integrator, trap, trap_analytic, (trap-trap_analytic)**2. assert (trap-trap_analytic)**2. < 10.**ttol, \ "Analytically computed apocenter radius does not agree with numerical estimate for potential %s and integrator %s" %(p,integrator) @@ -1310,7 +1373,8 @@ def test_analytic_zmax(): pots.remove(p) #tolerances in log10 tol= {} - tol['default']= -10. + tol['default']= -9. + tol['IsochronePotential']= -4. #these are more difficult tol['DoubleExponentialDiskPotential']= -6. #these are more difficult tol['RazorThinExponentialDiskPotential']= -4. #these are more difficult tol['KuzminKutuzovStaeckelPotential']= -4. #these are more difficult @@ -1360,8 +1424,15 @@ def test_analytic_zmax(): else: o.integrate(times,tp,method=integrator) tzmax= o.zmax() - tzmax_analytic= o.zmax(analytic=True) - #print p, integrator, tzmax, tzmax_analytic, (tzmax-tzmax_analytic)**2. + if ii < 2 and (p == 'BurkertPotential' + or 'SCFPotential' in p + or 'FlattenedPower' in p + or 'RazorThinExponential' in p + or 'TwoPowerSpherical' in p): # no Rzderiv currently + tzmax_analytic= o.zmax(analytic=True,type='adiabatic') + else: + tzmax_analytic= o.zmax(analytic=True) + #print(p, integrator, tzmax, tzmax_analytic, (tzmax-tzmax_analytic)**2.) assert (tzmax-tzmax_analytic)**2. < 10.**ttol, \ "Analytically computed zmax does not agree by %g with numerical estimate for potential %s and integrator %s" %(numpy.fabs(tzmax-tzmax_analytic),p,integrator) assert (o.zmax(ro=8.)/8.-tzmax_analytic)**2. < 10.**ttol, \ From 00d042362494707270bdefe4838e257acd403ab8 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 20:58:38 -0500 Subject: [PATCH 36/62] Add example of analytic ecc, rperi, rap, and zmax calculation to docs --- doc/source/orbit.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/source/orbit.rst b/doc/source/orbit.rst index 972bf785f..e292a5ece 100644 --- a/doc/source/orbit.rst +++ b/doc/source/orbit.rst @@ -298,6 +298,11 @@ its eccentricity, and the maximal height above the plane of the orbit >>> o.rap(), o.rperi(), o.e(), o.zmax() # (1.2581455175173673,0.97981663263371377,0.12436710999105324,0.11388132751079502) +These four quantities can also be computed using analytical means (exact or approximations depending on the potential) by specifying ``analytic=True`` + +>>> o.rap(analytic=True), o.rperi(analytic=True), o.e(analytic=True), o.zmax(analytic=True) +# (1.2581448917376636,0.97981640959995842,0.12436697719989584,0.11390708640305315) + We can also calculate the energy of the orbit, either in the potential that the orbit was integrated in, or in another potential: From f25a9a4ca0ddf6ff576e83cfa8919987527d3aca Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:16:04 -0500 Subject: [PATCH 37/62] Always return e,rperi,rap,zmax as a number --- galpy/orbit_src/FullOrbit.py | 8 ++++---- galpy/orbit_src/RZOrbit.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/galpy/orbit_src/FullOrbit.py b/galpy/orbit_src/FullOrbit.py index f2df9d9ca..187e05d48 100644 --- a/galpy/orbit_src/FullOrbit.py +++ b/galpy/orbit_src/FullOrbit.py @@ -295,7 +295,7 @@ def e(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[0] + return float(self._aA.EccZmaxRperiRap(self)[0]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): @@ -319,7 +319,7 @@ def rap(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[3] + return float(self._aA.EccZmaxRperiRap(self)[3]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): @@ -343,7 +343,7 @@ def rperi(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[2] + return float(self._aA.EccZmaxRperiRap(self)[2]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): @@ -368,7 +368,7 @@ def zmax(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[1] + return float(self._aA.EccZmaxRperiRap(self)[1]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) diff --git a/galpy/orbit_src/RZOrbit.py b/galpy/orbit_src/RZOrbit.py index 849d85c61..cd59e4e36 100644 --- a/galpy/orbit_src/RZOrbit.py +++ b/galpy/orbit_src/RZOrbit.py @@ -272,7 +272,7 @@ def e(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[0] + return float(self._aA.EccZmaxRperiRap(self)[0]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): @@ -296,7 +296,7 @@ def rap(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[3] + return float(self._aA.EccZmaxRperiRap(self)[3]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): @@ -320,7 +320,7 @@ def rperi(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[2] + return float(self._aA.EccZmaxRperiRap(self)[2]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): @@ -342,7 +342,7 @@ def zmax(self,analytic=False,pot=None,**kwargs): """ if analytic: self._setupaA(pot=pot,**kwargs) - return self._aA.EccZmaxRperiRap(self)[1] + return float(self._aA.EccZmaxRperiRap(self)[1]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) From 48f1fea6e36b559c3d90c7cde05cf26e05f5a221 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:24:08 -0500 Subject: [PATCH 38/62] Return orbit-interface actions etc. as floats rather than arrays (consistent with other outputs) --- galpy/orbit_src/Orbit.py | 76 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/galpy/orbit_src/Orbit.py b/galpy/orbit_src/Orbit.py index 286f9d3e8..97f997e31 100644 --- a/galpy/orbit_src/Orbit.py +++ b/galpy/orbit_src/Orbit.py @@ -1115,9 +1115,9 @@ def jr(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA(self(),use_physical=False)[0] + return float(self._orb._aA(self(),use_physical=False)[0]) else: - return self._orb._aA(self,use_physical=False)[0] + return float(self._orb._aA(self,use_physical=False)[0]) @physical_conversion('action') def jp(self,pot=None,**kwargs): @@ -1166,9 +1166,9 @@ def jp(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA(self(),use_physical=False)[1] + return float(self._orb._aA(self(),use_physical=False)[1]) else: - return self._orb._aA(self,use_physical=False)[1] + return float(self._orb._aA(self,use_physical=False)[1]) @physical_conversion('action') def jz(self,pot=None,**kwargs): @@ -1217,9 +1217,9 @@ def jz(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA(self(),use_physical=False)[2] + return float(self._orb._aA(self(),use_physical=False)[2]) else: - return self._orb._aA(self,use_physical=False)[2] + return float(self._orb._aA(self,use_physical=False)[2]) @physical_conversion('angle') def wr(self,pot=None,**kwargs): @@ -1262,11 +1262,11 @@ def wr(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqsAngles(self(), - use_physical=False)[6][0] + return float(self._orb._aA.actionsFreqsAngles(self(), + use_physical=False)[6][0]) else: - return self._orb._aA.actionsFreqsAngles(self, - use_physical=False)[6][0] + return float(self._orb._aA.actionsFreqsAngles(self, + use_physical=False)[6][0]) @physical_conversion('angle') def wp(self,pot=None,**kwargs): @@ -1309,11 +1309,11 @@ def wp(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqsAngles(self(), - use_physical=False)[7][0] + return float(self._orb._aA.actionsFreqsAngles(self(), + use_physical=False)[7][0]) else: - return self._orb._aA.actionsFreqsAngles(self, - use_physical=False)[7][0] + return float(self._orb._aA.actionsFreqsAngles(self, + use_physical=False)[7][0]) @physical_conversion('angle') def wz(self,pot=None,**kwargs): @@ -1356,11 +1356,11 @@ def wz(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqsAngles(self(), - use_physical=False)[8][0] + return float(self._orb._aA.actionsFreqsAngles(self(), + use_physical=False)[8][0]) else: - return self._orb._aA.actionsFreqsAngles(self, - use_physical=False)[8][0] + return float(self._orb._aA.actionsFreqsAngles(self, + use_physical=False)[8][0]) @physical_conversion('time') def Tr(self,pot=None,**kwargs): @@ -1409,11 +1409,11 @@ def Tr(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return 2.*nu.pi/self._orb._aA.actionsFreqs(self(), - use_physical=False)[3][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), + use_physical=False)[3][0]) else: - return 2.*nu.pi/self._orb._aA.actionsFreqs(self, - use_physical=False)[3][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, + use_physical=False)[3][0]) @physical_conversion('time') def Tp(self,pot=None,**kwargs): @@ -1462,11 +1462,11 @@ def Tp(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return 2.*nu.pi/self._orb._aA.actionsFreqs(self(), - use_physical=False)[4][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), + use_physical=False)[4][0]) else: - return 2.*nu.pi/self._orb._aA.actionsFreqs(self, - use_physical=False)[4][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, + use_physical=False)[4][0]) def TrTp(self,pot=None,**kwargs): """ @@ -1508,9 +1508,9 @@ def TrTp(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqs(self())[4][0]/self._orb._aA.actionsFreqs(self())[3][0]*nu.pi + return float(self._orb._aA.actionsFreqs(self())[4][0]/self._orb._aA.actionsFreqs(self())[3][0]*nu.pi) else: - return self._orb._aA.actionsFreqs(self)[4][0]/self._orb._aA.actionsFreqs(self)[3][0]*nu.pi + return float(self._orb._aA.actionsFreqs(self)[4][0]/self._orb._aA.actionsFreqs(self)[3][0]*nu.pi) @physical_conversion('time') def Tz(self,pot=None,**kwargs): @@ -1559,11 +1559,11 @@ def Tz(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return 2.*nu.pi/self._orb._aA.actionsFreqs(self(), - use_physical=False)[5][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), + use_physical=False)[5][0]) else: - return 2.*nu.pi/self._orb._aA.actionsFreqs(self, - use_physical=False)[5][0] + return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, + use_physical=False)[5][0]) @physical_conversion('frequency') def Or(self,pot=None,**kwargs): @@ -1610,9 +1610,9 @@ def Or(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqs(self(),use_physical=False)[3][0] + return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[3][0]) else: - return self._orb._aA.actionsFreqs(self,use_physical=False)[3][0] + return float(self._orb._aA.actionsFreqs(self,use_physical=False)[3][0]) @physical_conversion('frequency') def Op(self,pot=None,**kwargs): @@ -1658,9 +1658,9 @@ def Op(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqs(self(),use_physical=False)[4][0] + return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[4][0]) else: - return self._orb._aA.actionsFreqs(self,use_physical=False)[4][0] + return float(self._orb._aA.actionsFreqs(self,use_physical=False)[4][0]) @physical_conversion('frequency') def Oz(self,pot=None,**kwargs): @@ -1706,9 +1706,9 @@ def Oz(self,pot=None,**kwargs): _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': - return self._orb._aA.actionsFreqs(self(),use_physical=False)[5][0] + return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[5][0]) else: - return self._orb._aA.actionsFreqs(self,use_physical=False)[5][0] + return float(self._orb._aA.actionsFreqs(self,use_physical=False)[5][0]) def time(self,*args,**kwargs): """ From d7a565262fa3ecec41ab62ea1525fa19ad6af674 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:24:19 -0500 Subject: [PATCH 39/62] Only adjust z for small z --- galpy/orbit_src/OrbitTop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index 8712dc1c5..6de5545d9 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1238,7 +1238,7 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): kwargs.pop('delta', actionAngle.estimateDeltaStaeckel(\ self._aAPot,self.R(), - self.z()+(2.*(self.z() >= 0)-1.)*1e-10)) # try to make sure this is not 0 + self.z()+(nu.fabs(self.z()) < 1e-8) * (2.*(self.z() >= 0)-1.)*1e-10)) # try to make sure this is not 0 except PotentialError as e: if 'deriv' in repr(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') From ffb88cfe2a87c9a9b310b59067af9145516c1269 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:30:48 -0500 Subject: [PATCH 40/62] Edit docs for new float return value for orbit-interface actions and for new staeckel default --- doc/source/actionAngle.rst | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/doc/source/actionAngle.rst b/doc/source/actionAngle.rst index 3980d0abd..bf6c5d4be 100644 --- a/doc/source/actionAngle.rst +++ b/doc/source/actionAngle.rst @@ -839,29 +839,32 @@ instance >>> from galpy.potential import MWPotential2014 >>> o= Orbit([1.,0.1,1.1,0.,0.25,0.]) -and we can then calculate the actions (default is to use the adiabatic -approximation) +and we can then calculate the actions (default is to use the staeckel +approximation with an automatically-estimated delta parameter, but +this can be adjusted) >>> o.jr(MWPotential2014), o.jp(MWPotential2014), o.jz(MWPotential2014) -# (0.01685643005901713, 1.1, 0.015897730620467752) +# (0.018194068808944613,1.1,0.01540155584446606) ``o.jp`` here gives the azimuthal action (which is the *z* component of the angular momentum for axisymmetric potentials). We can also use -the other methods described above, but note that these require extra -parameters related to the approximation to be specified (see above): +the other methods described above or adjust the parameters of the +approximation (see above): >>> o.jr(MWPotential2014,type='staeckel',delta=0.4), o.jp(MWPotential2014,type='staeckel',delta=0.4), o.jz(MWPotential2014,type='staeckel',delta=0.4) -# (array([ 0.01922167]), array([ 1.1]), array([ 0.01527683])) +# (0.019221672966336707, 1.1, 0.015276825017286827) +>>> o.jr(MWPotential2014,type='adiabatic'), o.jp(MWPotential2014,type='adiabatic'), o.jz(MWPotential2014,type='adiabatic') +# (0.016856430059017123, 1.1, 0.015897730620467752) >>> o.jr(MWPotential2014,type='isochroneApprox',b=0.8), o.jp(MWPotential2014,type='isochroneApprox',b=0.8), o.jz(MWPotential2014,type='isochroneApprox',b=0.8) -# (array([ 0.01906609]), array([ 1.1]), array([ 0.01528049])) +# (0.019066091295488922, 1.1, 0.015280492319332751) These two methods give very precise actions for this orbit (both are converged to about 1%) and they agree very well >>> (o.jr(MWPotential2014,type='staeckel',delta=0.4)-o.jr(MWPotential2014,type='isochroneApprox',b=0.8))/o.jr(MWPotential2014,type='isochroneApprox',b=0.8) -# array([ 0.00816012]) ->>> (o.jz(MWPotential2014,type='staeckel',delta=0.4)-o.jz(MWPotential2014,type='isochroneApprox',b=0.8))/o.jz(MWPotential2014,type='isochroneApprox',b=0.8) -# array([-0.00024]) +# 0.00816012408818143 +>>> (o.jz(MWPotential2014,type='staeckel',delta=0.4)-o.jz(MWPotential2014,type='isochroneApprox',b=0.8))/o.jz(MWPotential2014,type='isochroneApprox',b=0.8) +# 0.00023999894566772273 .. WARNING:: Once an action, frequency, or angle is calculated for a given type of calculation (e.g., staeckel), the parameters for that type are fixed in the Orbit instance. Call o.resetaA() to reset the action-angle instance used when using different parameters (i.e., different ``delta=`` for staeckel or different ``b=`` for isochroneApprox. From 654dcf12d8621f11822f17a4bbb0d7e93d9dd976 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:35:40 -0500 Subject: [PATCH 41/62] Add some of the new eccentricity functions to HISTORY, also the new default method for computing Orbit instance's actions etc. --- HISTORY.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/HISTORY.txt b/HISTORY.txt index 4b23a456f..62d604743 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,6 +1,12 @@ v1.3 (2017-XX-XX) ================== +- Added a fast and precise method for approximating an orbit's + eccentricity, peri- and apocenter radii, and maximum height above + the midplane using the Staeckel approximation (see Mackereth & Bovy + 2018); available as an actionAngle method EccZmaxRperiRap and for + Orbit instances through the e, rperi, rap, and zmax methods. + - Added support for potential wrappers---classes that wrap existing potentials to modify their behavior (#307). See the documentation on potentials and the potential API for more information on these. @@ -52,6 +58,10 @@ v1.3 (2017-XX-XX) which the radial dependence of the potential changes from R^p to R^-p; also added C implementation of CosmphiDiskPotential. +- Changed default method for computing actions, frequencies, and + angles for Orbit instances to be the Staeckel approximation with an + automatically-estimated delta parameter. + - Allow transformations of (ra,dec) and (pmra,pmdec) to custom coordinate systems. From e24f35a526c2673fcb811809e5d957ef7e4bc405 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:44:31 -0500 Subject: [PATCH 42/62] Make sure to use internal units when estimating delta for an orbit --- galpy/orbit_src/OrbitTop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index 6de5545d9..51d5dc79a 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1237,8 +1237,8 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): delta= \ kwargs.pop('delta', actionAngle.estimateDeltaStaeckel(\ - self._aAPot,self.R(), - self.z()+(nu.fabs(self.z()) < 1e-8) * (2.*(self.z() >= 0)-1.)*1e-10)) # try to make sure this is not 0 + self._aAPot,self.R(use_physical=False), + self.z(use_physical=False)+(nu.fabs(self.z(use_physical=False)) < 1e-8) * (2.*(self.z(use_physical=False) >= 0)-1.)*1e-10)) # try to make sure this is not 0 except PotentialError as e: if 'deriv' in repr(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') From 8db4382875b152d35bcc068c2b44f265b29ddb06 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 21:55:25 -0500 Subject: [PATCH 43/62] Use str representation of error for python2 compatibility --- galpy/orbit_src/OrbitTop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index 51d5dc79a..f65ee17d5 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1240,9 +1240,9 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): self._aAPot,self.R(use_physical=False), self.z(use_physical=False)+(nu.fabs(self.z(use_physical=False)) < 1e-8) * (2.*(self.z(use_physical=False) >= 0)-1.)*1e-10)) # try to make sure this is not 0 except PotentialError as e: - if 'deriv' in repr(e): + if 'deriv' in str(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') - elif 'non-axi' in repr(e): + elif 'non-axi' in str(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') else: raise From 9ecd137c4663be998bad46d66fb2e3bb4d3bd86b Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 22:48:39 -0500 Subject: [PATCH 44/62] Remove unused planar case from _setupaA; don't test exception raising --- galpy/orbit_src/OrbitTop.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/galpy/orbit_src/OrbitTop.py b/galpy/orbit_src/OrbitTop.py index f65ee17d5..2a8b887a4 100644 --- a/galpy/orbit_src/OrbitTop.py +++ b/galpy/orbit_src/OrbitTop.py @@ -1230,22 +1230,19 @@ def _setupaA(self,pot=None,type='staeckel',**kwargs): self._aA= actionAngle.actionAngleAdiabatic(pot=self._aAPot, **kwargs) elif self._aAType.lower() == 'staeckel': - if len(self.vxvv) < 5: # planar - delta= kwargs.pop('delta',1e-10) # can't go all the way to zero - else: - try: - delta= \ - kwargs.pop('delta', - actionAngle.estimateDeltaStaeckel(\ - self._aAPot,self.R(use_physical=False), - self.z(use_physical=False)+(nu.fabs(self.z(use_physical=False)) < 1e-8) * (2.*(self.z(use_physical=False) >= 0)-1.)*1e-10)) # try to make sure this is not 0 - except PotentialError as e: - if 'deriv' in str(e): - raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') - elif 'non-axi' in str(e): - raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') - else: - raise + try: + delta= \ + kwargs.pop('delta', + actionAngle.estimateDeltaStaeckel(\ + self._aAPot,self.R(use_physical=False), + self.z(use_physical=False)+(nu.fabs(self.z(use_physical=False)) < 1e-8) * (2.*(self.z(use_physical=False) >= 0)-1.)*1e-10)) # try to make sure this is not 0 + except PotentialError as e: + if 'deriv' in str(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') + elif 'non-axi' in str(e): + raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') + else: #pragma: no cover + raise if delta < 1e-6: self._setupaA(pot=pot,type='spherical') else: From 4fee38700490814dad4205a68121f2a80e054cce Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 25 Dec 2017 22:58:43 -0500 Subject: [PATCH 45/62] Deprecate calczmax function in actionAngleAdiabatic, because no longer used; use EccZmaxRperiRap instead --- galpy/actionAngle_src/actionAngleAdiabatic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/galpy/actionAngle_src/actionAngleAdiabatic.py b/galpy/actionAngle_src/actionAngleAdiabatic.py index b05d98b86..4408610db 100644 --- a/galpy/actionAngle_src/actionAngleAdiabatic.py +++ b/galpy/actionAngle_src/actionAngleAdiabatic.py @@ -290,7 +290,7 @@ def calcRapRperi(self,*args,**kwargs): gamma=self._gamma) return aAAxi.calcRapRperi(**kwargs) - def calczmax(self,*args,**kwargs): + def calczmax(self,*args,**kwargs): #pragma: no cover """ NAME: calczmax @@ -306,6 +306,7 @@ def calczmax(self,*args,**kwargs): HISTORY: 2012-06-01 - Written - Bovy (IAS) """ + warnings.warn("actionAngleAdiabatic.calczmax function will soon be deprecated; please contact galpy's maintainer if you require this function") #Set up the actionAngleAxi object self._parse_eval_args(*args) if isinstance(self._pot,list): From 0f32a44a885cb3973503a78f732bbd334a2ca756 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Tue, 26 Dec 2017 19:48:32 -0500 Subject: [PATCH 46/62] Generalize actionAngleStaeckel C code to allow different delta for different points --- .../actionAngle_src/actionAngleStaeckel_c.py | 75 +++- .../actionAngle_c_ext/actionAngleStaeckel.c | 367 ++++++++++-------- 2 files changed, 262 insertions(+), 180 deletions(-) diff --git a/galpy/actionAngle_src/actionAngleStaeckel_c.py b/galpy/actionAngle_src/actionAngleStaeckel_c.py index f933df0fb..f1c382f26 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel_c.py +++ b/galpy/actionAngle_src/actionAngleStaeckel_c.py @@ -59,6 +59,10 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + #Parse delta + delta= numpy.atleast_1d(delta) + ndelta= len(delta) + #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) @@ -77,7 +81,8 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), - ctypes.c_double, + ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] @@ -88,13 +93,15 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], - u0.flags['F_CONTIGUOUS']] + u0.flags['F_CONTIGUOUS'], + delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) + delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) @@ -109,7 +116,8 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int(npot), pot_type, pot_args, - ctypes.c_double(delta), + ctypes.c_int(ndelta), + delta, jr, jz, ctypes.byref(err)) @@ -121,6 +129,7 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) + if f_cont[6]: delta= numpy.asfortranarray(delta) return (jr,jz,err.value) @@ -148,6 +157,10 @@ def actionAngleStaeckel_calcu0(E,Lz,pot,delta): u0= numpy.empty(len(E)) err= ctypes.c_int(0) + #Parse delta + delta= numpy.atleast_1d(delta) + ndelta= len(delta) + #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.calcu0 @@ -157,15 +170,18 @@ def actionAngleStaeckel_calcu0(E,Lz,pot,delta): ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), - ctypes.c_double, + ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [E.flags['F_CONTIGUOUS'], - Lz.flags['F_CONTIGUOUS']] + Lz.flags['F_CONTIGUOUS'], + delta.flags['F_CONTIGUOUS']] E= numpy.require(E,dtype=numpy.float64,requirements=['C','W']) Lz= numpy.require(Lz,dtype=numpy.float64,requirements=['C','W']) + delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) #Run the C code @@ -175,13 +191,15 @@ def actionAngleStaeckel_calcu0(E,Lz,pot,delta): ctypes.c_int(npot), pot_type, pot_args, - ctypes.c_double(delta), + ctypes.c_int(ndelta), + delta, u0, ctypes.byref(err)) #Reset input arrays if f_cont[0]: E= numpy.asfortranarray(E) if f_cont[1]: Lz= numpy.asfortranarray(Lz) + if f_cont[2]: delta= numpy.asfortranarray(delta) return (u0,err.value) @@ -208,6 +226,10 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + #Parse delta + delta= numpy.atleast_1d(delta) + ndelta= len(delta) + #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) @@ -229,7 +251,8 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), - ctypes.c_double, + ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), @@ -243,13 +266,15 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], - u0.flags['F_CONTIGUOUS']] + u0.flags['F_CONTIGUOUS'], + delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) + delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) @@ -268,7 +293,8 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int(npot), pot_type, pot_args, - ctypes.c_double(delta), + ctypes.c_int(ndelta), + delta, jr, jz, Omegar, @@ -283,6 +309,7 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) + if f_cont[6]: delta= numpy.asfortranarray(delta) return (jr,jz,Omegar,Omegaphi,Omegaz,err.value) @@ -309,6 +336,10 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + #Parse delta + delta= numpy.atleast_1d(delta) + ndelta= len(delta) + #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) @@ -333,7 +364,8 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), - ctypes.c_double, + ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), @@ -350,13 +382,15 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], - u0.flags['F_CONTIGUOUS']] + u0.flags['F_CONTIGUOUS'], + delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) + delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) @@ -379,7 +413,8 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): ctypes.c_int(npot), pot_type, pot_args, - ctypes.c_double(delta), + ctypes.c_int(ndelta), + delta, jr, jz, Omegar, @@ -397,6 +432,7 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) + if f_cont[6]: delta= numpy.asfortranarray(delta) badAngle = Anglephi != 9999.99 Anglephi[badAngle]= (Anglephi[badAngle] + phi[badAngle] % (2.*numpy.pi)) % (2.*numpy.pi) @@ -427,6 +463,10 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) + #Parse delta + delta= numpy.atleast_1d(delta) + ndelta= len(delta) + #Set up result arrays umin= numpy.empty(len(R)) umax= numpy.empty(len(R)) @@ -446,7 +486,8 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), - ctypes.c_double, + ctypes.c_int, + ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), @@ -458,13 +499,15 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], - u0.flags['F_CONTIGUOUS']] + u0.flags['F_CONTIGUOUS'], + delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) + delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) umin= numpy.require(umin,dtype=numpy.float64,requirements=['C','W']) umax= numpy.require(umax,dtype=numpy.float64,requirements=['C','W']) vmin= numpy.require(vmin,dtype=numpy.float64,requirements=['C','W']) @@ -480,7 +523,8 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ctypes.c_int(npot), pot_type, pot_args, - ctypes.c_double(delta), + ctypes.c_int(ndelta), + delta, umin, umax, vmin, @@ -493,6 +537,7 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) + if f_cont[6]: delta= numpy.asfortranarray(delta) return (umin,umax,vmin,err.value) diff --git a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c index 812a36341..852ae5e7e 100644 --- a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c +++ b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c @@ -86,55 +86,57 @@ struct u0EqArg{ /* Function Declarations */ -void calcu0(int,double *,double *,int,int *,double *,double,double *,int *); +void calcu0(int,double *,double *,int,int *,double *,int,double*, + double *,int *); void actionAngleStaeckel_uminUmaxVmin(int,double *,double *,double *,double *, double *,double *,int,int *,double *, - double,double *, + int,double *,double *, double *,double *,int *); void actionAngleStaeckel_actions(int,double *,double *,double *,double *, - double *,double *,int,int *,double *,double, - double *,double *,int *); + double *,double *,int,int *,double *,int, + double *,double *,double *,int *); void actionAngleStaeckel_actionsFreqsAngles(int,double *,double *,double *, double *,double *,double *, int,int *,double *, - double,double *,double *,double *, + int,double *,double *,double *, double *,double *,double *, - double *,double *,int *); + double *,double *,double *,int *); void actionAngleStaeckel_actionsFreqs(int,double *,double *,double *,double *, double *,double *,int,int *,double *, - double,double *,double *,double *, + int,double *,double *,double *,double *, double *,double *,int *); void calcAnglesStaeckel(int,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, - double *,double,double *,double *,double *,double *, - double *,double *,double *,double *,double *,int, - struct potentialArg *,int); + double *,int,double *,double *,double *,double *, + double *,double *,double *,double *,double *,double *, + int,struct potentialArg *,int); void calcFreqsFromDerivsStaeckel(int,double *,double *,double *, double *,double *,double *, double *,double *,double *,double *); void calcdI3dJFromDerivsStaeckel(int,double *,double *,double *,double *, double *,double *,double *,double *); void calcJRStaeckel(int,double *,double *,double *,double *,double *,double *, - double,double *,double *,double *,double *,double *,int, - struct potentialArg *,int); -void calcJzStaeckel(int,double *,double *,double *,double *,double *,double, - double *,double *,double *,double *,int, + int,double *,double *,double *,double *,double *,double *, + int,struct potentialArg *,int); +void calcJzStaeckel(int,double *,double *,double *,double *,double *,int, + double *,double *,double *,double *,double *,int, struct potentialArg *,int); void calcdJRStaeckel(int,double *,double *,double *,double *,double *, - double *,double *,double *, - double,double *,double *,double *,double *,double *,int, + double *,double *,double *,int, + double *,double *,double *,double *,double *,double *,int, struct potentialArg *,int); void calcdJzStaeckel(int,double *,double *,double *,double *,double *, - double *,double *,double,double *,double *,double *, + double *,double *,int,double *,double *,double *,double *, double *,int, struct potentialArg *,int); void calcUminUmax(int,double *,double *,double *,double *,double *,double *, - double *,double,double *,double *,double *,double *,double *, - int,struct potentialArg *); -void calcVmin(int,double *,double *,double *,double *,double *,double *,double, - double *,double *,double *,double *,int,struct potentialArg *); + double *,int,double *,double *,double *,double *,double *, + double *,int,struct potentialArg *); +void calcVmin(int,double *,double *,double *,double *,double *,double *,int, + double *,double *,double *,double *,double *,int, + struct potentialArg *); double JRStaeckelIntegrandSquared(double,void *); double JRStaeckelIntegrand(double,void *); double JzStaeckelIntegrandSquared(double,void *); @@ -172,14 +174,17 @@ inline void Rz_to_uv_vec(int ndata, double *z, double *u, double *v, - double delta){ + int ndelta, + double * delta){ int ii; - double d12, d22, coshu, cosv; + double d12, d22, coshu, cosv,tdelta; + int delta_stride= ndelta == 1 ? 0 : 1; for (ii=0; ii < ndata; ii++) { - d12= (*(z+ii)+delta)*(*(z+ii)+delta)+(*(R+ii))*(*(R+ii)); - d22= (*(z+ii)-delta)*(*(z+ii)-delta)+(*(R+ii))*(*(R+ii)); - coshu= 0.5/delta*(sqrt(d12)+sqrt(d22)); - cosv= 0.5/delta*(sqrt(d12)-sqrt(d22)); + tdelta= *(delta+ii*delta_stride); + d12= (*(z+ii)+tdelta)*(*(z+ii)+tdelta)+(*(R+ii))*(*(R+ii)); + d22= (*(z+ii)-tdelta)*(*(z+ii)-tdelta)+(*(R+ii))*(*(R+ii)); + coshu= 0.5/tdelta*(sqrt(d12)+sqrt(d22)); + cosv= 0.5/tdelta*(sqrt(d12)-sqrt(d22)); *u++= acosh(coshu); *v++= acos(cosv); } @@ -215,7 +220,8 @@ void calcu0(int ndata, int npot, int * pot_type, double * pot_args, - double delta, + int ndelta, + double * delta, double *u0, int * err){ int ii; @@ -225,7 +231,6 @@ void calcu0(int ndata, //setup the function to be minimized gsl_function u0Eq; struct u0EqArg * params= (struct u0EqArg *) malloc ( sizeof (struct u0EqArg) ); - params->delta= delta; params->nargs= npot; params->actionAngleArgs= actionAngleArgs; //Setup solver @@ -237,10 +242,12 @@ void calcu0(int ndata, T = gsl_min_fminimizer_brent; s = gsl_min_fminimizer_alloc (T); u0Eq.function = &u0Equation; + int delta_stride= ndelta == 1 ? 0 : 1; for (ii=0; ii < ndata; ii++){ //Setup function + params->delta= *(delta+ii*delta_stride); params->E= *(E+ii); - params->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + params->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); u0Eq.params = params; //Find starting points for minimum u_guess= 1.; @@ -285,7 +292,8 @@ void actionAngleStaeckel_uminUmaxVmin(int ndata, int npot, int * pot_type, double * pot_args, - double delta, + int ndelta, + double * delta, double *umin, double *umax, double *vmin, @@ -293,6 +301,7 @@ void actionAngleStaeckel_uminUmaxVmin(int ndata, // Just copied this over from actionAngleStaeckel_actions below, not elegant // but does the job... int ii; + double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); @@ -303,7 +312,7 @@ void actionAngleStaeckel_uminUmaxVmin(int ndata, //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); - Rz_to_uv_vec(ndata,R,z,ux,vx,delta); + Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); @@ -318,44 +327,46 @@ void actionAngleStaeckel_uminUmaxVmin(int ndata, double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; -#pragma omp parallel for schedule(static,chunk) private(ii) +#pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ + tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); - *(pux+ii)= delta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); - *(pvx+ii)= delta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) + *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); - *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),delta, + *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - - 0.5 * *(pux+ii) * *(pux+ii) / delta / delta - - 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinhux+ii) / *(sinhux+ii) + - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta + - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) - *evaluatePotentialsUV(*(ux+ii),*(v0+ii),delta, + *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); - *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,delta, + *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) - + 0.5 * *(pvx+ii) * *(pvx+ii) / delta / delta - + 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinvx+ii) / *(sinvx+ii) + + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) - * evaluatePotentialsUV(*(u0+ii),*(vx+ii),delta, + * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers - calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs); - calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, + calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, + sin2v0,potu0v0,npot,actionAngleArgs); + calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Free free_potentialArgs(npot,actionAngleArgs); @@ -389,11 +400,13 @@ void actionAngleStaeckel_actions(int ndata, int npot, int * pot_type, double * pot_args, - double delta, + int ndelta, + double * delta, double *jr, double *jz, int * err){ int ii; + double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); @@ -404,7 +417,7 @@ void actionAngleStaeckel_actions(int ndata, //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); - Rz_to_uv_vec(ndata,R,z,ux,vx,delta); + Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); @@ -419,53 +432,55 @@ void actionAngleStaeckel_actions(int ndata, double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; -#pragma omp parallel for schedule(static,chunk) private(ii) +#pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ + tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); - *(pux+ii)= delta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); - *(pvx+ii)= delta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) + *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); - *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),delta, + *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - - 0.5 * *(pux+ii) * *(pux+ii) / delta / delta - - 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinhux+ii) / *(sinhux+ii) + - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta + - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) - *evaluatePotentialsUV(*(ux+ii),*(v0+ii),delta, + *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); - *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,delta, + *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) - + 0.5 * *(pvx+ii) * *(pvx+ii) / delta / delta - + 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinvx+ii) / *(sinvx+ii) + + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) - * evaluatePotentialsUV(*(u0+ii),*(vx+ii),delta, + * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); - calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs); - calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, + calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, + sin2v0,potu0v0,npot,actionAngleArgs); + calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions - calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,10); - calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, - npot,actionAngleArgs,10); + calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, + potupi2,npot,actionAngleArgs,10); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); @@ -498,7 +513,8 @@ void calcJRStaeckel(int ndata, double * E, double * Lz, double * I3U, - double delta, + int ndelta, + double * delta, double * u0, double * sinh2u0, double * v0, @@ -516,12 +532,12 @@ void calcJRStaeckel(int ndata, gsl_function * JRInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JRStaeckelArg * params= (struct JRStaeckelArg *) malloc ( nthreads * sizeof (struct JRStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ @@ -541,8 +557,9 @@ void calcJRStaeckel(int ndata, continue; } //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); @@ -553,7 +570,7 @@ void calcJRStaeckel(int ndata, (JRInt+tid)->params = params+tid; //Integrate *(jr+ii)= gsl_integration_glfixed (JRInt+tid,*(umin+ii),*(umax+ii),T) - * sqrt(2.) * delta / M_PI; + * sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(JRInt); free(params); @@ -565,7 +582,8 @@ void calcJzStaeckel(int ndata, double * E, double * Lz, double * I3V, - double delta, + int ndelta, + double * delta, double * u0, double * cosh2u0, double * sinh2u0, @@ -582,12 +600,12 @@ void calcJzStaeckel(int ndata, gsl_function * JzInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JzStaeckelArg * params= (struct JzStaeckelArg *) malloc ( nthreads * sizeof (struct JzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ @@ -607,8 +625,9 @@ void calcJzStaeckel(int ndata, continue; } //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); @@ -618,7 +637,7 @@ void calcJzStaeckel(int ndata, (JzInt+tid)->params = params+tid; //Integrate *(jz+ii)= gsl_integration_glfixed (JzInt+tid,*(vmin+ii),M_PI/2.,T) - * 2 * sqrt(2.) * delta / M_PI; + * 2 * sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(JzInt); free(params); @@ -634,7 +653,8 @@ void actionAngleStaeckel_actionsFreqs(int ndata, int npot, int * pot_type, double * pot_args, - double delta, + int ndelta, + double * delta, double *jr, double *jz, double *Omegar, @@ -642,6 +662,7 @@ void actionAngleStaeckel_actionsFreqs(int ndata, double *Omegaz, int * err){ int ii; + double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); @@ -652,7 +673,7 @@ void actionAngleStaeckel_actionsFreqs(int ndata, //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); - Rz_to_uv_vec(ndata,R,z,ux,vx,delta); + Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); @@ -667,53 +688,55 @@ void actionAngleStaeckel_actionsFreqs(int ndata, double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; -#pragma omp parallel for schedule(static,chunk) private(ii) +#pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ + tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); - *(pux+ii)= delta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); - *(pvx+ii)= delta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) + *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); - *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),delta, + *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - - 0.5 * *(pux+ii) * *(pux+ii) / delta / delta - - 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinhux+ii) / *(sinhux+ii) + - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta + - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) - *evaluatePotentialsUV(*(ux+ii),*(v0+ii),delta, + *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); - *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,delta, + *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) - + 0.5 * *(pvx+ii) * *(pvx+ii) / delta / delta - + 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinvx+ii) / *(sinvx+ii) + + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) - * evaluatePotentialsUV(*(u0+ii),*(vx+ii),delta, + * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); - calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs); - calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, + calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, + sin2v0,potu0v0,npot,actionAngleArgs); + calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions - calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,10); - calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, - npot,actionAngleArgs,10); + calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, + potupi2,npot,actionAngleArgs,10); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); @@ -723,10 +746,10 @@ void actionAngleStaeckel_actionsFreqs(int ndata, double *dJzdI3= (double *) malloc ( ndata * sizeof(double) ); double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, - umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,10); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, - vmin,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, + vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,10); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, @@ -773,7 +796,8 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, int npot, int * pot_type, double * pot_args, - double delta, + int ndelta, + double * delta, double *jr, double *jz, double *Omegar, @@ -784,6 +808,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, double *Anglez, int * err){ int ii; + double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); @@ -794,7 +819,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); - Rz_to_uv_vec(ndata,R,z,ux,vx,delta); + Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); @@ -809,53 +834,55 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; -#pragma omp parallel for schedule(static,chunk) private(ii) +#pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ + tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); - *(pux+ii)= delta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); - *(pvx+ii)= delta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) + *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); - *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),delta, + *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - - 0.5 * *(pux+ii) * *(pux+ii) / delta / delta - - 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinhux+ii) / *(sinhux+ii) + - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta + - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) - *evaluatePotentialsUV(*(ux+ii),*(v0+ii),delta, + *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); - *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,delta, + *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) - + 0.5 * *(pvx+ii) * *(pvx+ii) / delta / delta - + 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta / *(sinvx+ii) / *(sinvx+ii) + + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) - * evaluatePotentialsUV(*(u0+ii),*(vx+ii),delta, + * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); - calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs); - calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, + calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, + sin2v0,potu0v0,npot,actionAngleArgs); + calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions - calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,10); - calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0,potupi2, - npot,actionAngleArgs,10); + calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, + potupi2,npot,actionAngleArgs,10); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); @@ -865,10 +892,10 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, double *dJzdI3= (double *) malloc ( ndata * sizeof(double) ); double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, - umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,10); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, - vmin,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, + vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,10); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, @@ -883,7 +910,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3, ux,vx,pux,pvx, - umin,umax,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, + umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0, vmin,I3V,cosh2u0,potupi2, npot,actionAngleArgs,10); @@ -981,7 +1008,8 @@ void calcdJRStaeckel(int ndata, double * E, double * Lz, double * I3U, - double delta, + int ndelta, + double * delta, double * u0, double * sinh2u0, double * v0, @@ -1000,12 +1028,12 @@ void calcdJRStaeckel(int ndata, gsl_function * dJRInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) malloc ( nthreads * sizeof (struct dJRStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid) \ @@ -1029,8 +1057,9 @@ void calcdJRStaeckel(int ndata, continue; } //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); @@ -1046,19 +1075,19 @@ void calcdJRStaeckel(int ndata, *(djrdE+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdEHighStaeckelIntegrand; *(djrdE+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); - *(djrdE+ii)*= delta / M_PI / sqrt(2.); + *(djrdE+ii)*= *(delta+ii*delta_stride) / M_PI / sqrt(2.); //then calculate djrdLz (dJRInt+tid)->function = &dJRdLzLowStaeckelIntegrand; *(djrdLz+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdLzHighStaeckelIntegrand; *(djrdLz+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); - *(djrdLz+ii)*= - *(Lz+ii) / M_PI / sqrt(2.) / delta; + *(djrdLz+ii)*= - *(Lz+ii) / M_PI / sqrt(2.) / *(delta+ii*delta_stride); //then calculate djrdI3 (dJRInt+tid)->function = &dJRdI3LowStaeckelIntegrand; *(djrdI3+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdI3HighStaeckelIntegrand; *(djrdI3+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); - *(djrdI3+ii)*= -delta / M_PI / sqrt(2.); + *(djrdI3+ii)*= - *(delta+ii*delta_stride) / M_PI / sqrt(2.); } free(dJRInt); free(params); @@ -1072,7 +1101,8 @@ void calcdJzStaeckel(int ndata, double * E, double * Lz, double * I3V, - double delta, + int ndelta, + double * delta, double * u0, double * cosh2u0, double * sinh2u0, @@ -1090,12 +1120,12 @@ void calcdJzStaeckel(int ndata, gsl_function * dJzInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) malloc ( nthreads * sizeof (struct dJzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid) \ @@ -1119,8 +1149,9 @@ void calcdJzStaeckel(int ndata, continue; } //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); @@ -1136,21 +1167,21 @@ void calcdJzStaeckel(int ndata, *(djzdE+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdEHighStaeckelIntegrand; *(djzdE+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); - *(djzdE+ii)*= sqrt(2.) * delta / M_PI; + *(djzdE+ii)*= sqrt(2.) * *(delta+ii*delta_stride) / M_PI; //Then calculate dJzdLz (dJzInt+tid)->function = &dJzdLzLowStaeckelIntegrand; //Integrate *(djzdLz+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdLzHighStaeckelIntegrand; *(djzdLz+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); - *(djzdLz+ii)*= - *(Lz+ii) * sqrt(2.) / M_PI / delta; + *(djzdLz+ii)*= - *(Lz+ii) * sqrt(2.) / M_PI / *(delta+ii*delta_stride); //Then calculate dJzdI3 (dJzInt+tid)->function = &dJzdI3LowStaeckelIntegrand; //Integrate *(djzdI3+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdI3HighStaeckelIntegrand; *(djzdI3+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); - *(djzdI3+ii)*= sqrt(2.) * delta / M_PI; + *(djzdI3+ii)*= sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(dJzInt); free(params); @@ -1181,7 +1212,8 @@ void calcAnglesStaeckel(int ndata, double * E, double * Lz, double * I3U, - double delta, + int ndelta, + double * delta, double * u0, double * sinh2u0, double * v0, @@ -1207,15 +1239,14 @@ void calcAnglesStaeckel(int ndata, struct dJRStaeckelArg * paramsu= (struct dJRStaeckelArg *) malloc ( nthreads * sizeof (struct dJRStaeckelArg) ); struct dJzStaeckelArg * paramsv= (struct dJzStaeckelArg *) malloc ( nthreads * sizeof (struct dJzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ - (paramsu+tid)->delta= delta; (paramsu+tid)->nargs= nargs; (paramsu+tid)->actionAngleArgs= actionAngleArgs; - (paramsv+tid)->delta= delta; (paramsv+tid)->nargs= nargs; (paramsv+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid,midpoint,Or1,Or2,I3r1,I3r2,phitmp) \ @@ -1239,8 +1270,9 @@ void calcAnglesStaeckel(int ndata, continue; } //Setup u function + (paramsu+tid)->delta= *(delta+ii*delta_stride); (paramsu+tid)->E= *(E+ii); - (paramsu+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (paramsu+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (paramsu+tid)->I3U= *(I3U+ii); (paramsu+tid)->u0= *(u0+ii); (paramsu+tid)->sinh2u0= *(sinh2u0+ii); @@ -1259,9 +1291,9 @@ void calcAnglesStaeckel(int ndata, (AngleuInt+tid)->function = &dJRdI3HighStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdLzHighStaeckelIntegrand; - *(Anglephi+ii)= M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / delta / sqrt(2.); - Or1*= delta / sqrt(2.); - I3r1*= delta / sqrt(2.); + *(Anglephi+ii)= M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); + Or1*= *(delta+ii*delta_stride) / sqrt(2.); + I3r1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= M_PI * *(dJRdE+ii) - Or1; I3r1= M_PI * *(dJRdI3+ii) - I3r1; } @@ -1272,9 +1304,9 @@ void calcAnglesStaeckel(int ndata, (AngleuInt+tid)->function = &dJRdI3LowStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdLzLowStaeckelIntegrand; - *(Anglephi+ii)= - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / delta / sqrt(2.); - Or1*= delta / sqrt(2.); - I3r1*= delta / sqrt(2.); + *(Anglephi+ii)= - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); + Or1*= *(delta+ii*delta_stride) / sqrt(2.); + I3r1*= *(delta+ii*delta_stride) / sqrt(2.); } } else { @@ -1282,32 +1314,33 @@ void calcAnglesStaeckel(int ndata, mid= sqrt( ( *(umax+ii) - *(ux+ii) ) ); (AngleuInt+tid)->function = &dJRdEHighStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); - Or1*= delta / sqrt(2.); + Or1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= M_PI * *(dJRdE+ii) + Or1; (AngleuInt+tid)->function = &dJRdI3HighStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); - I3r1*= delta / sqrt(2.); + I3r1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1= M_PI * *(dJRdI3+ii) + I3r1; (AngleuInt+tid)->function = &dJRdLzHighStaeckelIntegrand; - *(Anglephi+ii)= M_PI * *(dJRdLz+ii) - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / delta / sqrt(2.); + *(Anglephi+ii)= M_PI * *(dJRdLz+ii) - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); } else { mid= sqrt( ( *(ux+ii) - *(umin+ii) ) ); (AngleuInt+tid)->function = &dJRdELowStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); - Or1*= delta / sqrt(2.); + Or1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= 2. * M_PI * *(dJRdE+ii) - Or1; (AngleuInt+tid)->function = &dJRdI3LowStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); - I3r1*= delta / sqrt(2.); + I3r1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1= 2. * M_PI * *(dJRdI3+ii) - I3r1; (AngleuInt+tid)->function = &dJRdLzLowStaeckelIntegrand; - *(Anglephi+ii)= 2. * M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / delta / sqrt(2.); + *(Anglephi+ii)= 2. * M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); } } //Setup v function + (paramsv+tid)->delta= *(delta+ii*delta_stride); (paramsv+tid)->E= *(E+ii); - (paramsv+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (paramsv+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (paramsv+tid)->I3V= *(I3V+ii); (paramsv+tid)->u0= *(u0+ii); (paramsv+tid)->cosh2u0= *(cosh2u0+ii); @@ -1321,13 +1354,13 @@ void calcAnglesStaeckel(int ndata, mid = ( *(vx+ii) > 0.5 * M_PI ) ? sqrt( (M_PI - *(vx+ii) - *(vmin+ii))): sqrt( *(vx+ii) - *(vmin+ii)); (AnglevInt+tid)->function = &dJzdELowStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - Or2*= delta / sqrt(2.); + Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3LowStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - I3r2*= delta / sqrt(2.); + I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzLowStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - phitmp*= - *(Lz+ii) / delta / sqrt(2.); + phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) > 0.5 * M_PI ) { Or2= M_PI * *(dJzdE+ii) - Or2; I3r2= M_PI * *(dJzdI3+ii) - I3r2; @@ -1338,13 +1371,13 @@ void calcAnglesStaeckel(int ndata, mid= sqrt( fabs ( 0.5 * M_PI - *(vx+ii) ) ); (AnglevInt+tid)->function = &dJzdEHighStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - Or2*= delta / sqrt(2.); + Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3HighStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - I3r2*= delta / sqrt(2.); + I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzHighStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - phitmp*= - *(Lz+ii) / delta / sqrt(2.); + phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) > 0.5 * M_PI ) { Or2= 0.5 * M_PI * *(dJzdE+ii) + Or2; I3r2= 0.5 * M_PI * *(dJzdI3+ii) + I3r2; @@ -1362,13 +1395,13 @@ void calcAnglesStaeckel(int ndata, mid = ( *(vx+ii) > 0.5 * M_PI ) ? sqrt( (M_PI - *(vx+ii) - *(vmin+ii))): sqrt( *(vx+ii) - *(vmin+ii)); (AnglevInt+tid)->function = &dJzdELowStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - Or2*= delta / sqrt(2.); + Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3LowStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - I3r2*= delta / sqrt(2.); + I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzLowStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - phitmp*= - *(Lz+ii) / delta / sqrt(2.); + phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) < 0.5 * M_PI ) { Or2= 2. * M_PI * *(dJzdE+ii) - Or2; I3r2= 2. * M_PI * *(dJzdI3+ii) - I3r2; @@ -1384,13 +1417,13 @@ void calcAnglesStaeckel(int ndata, mid= sqrt( fabs ( 0.5 * M_PI - *(vx+ii) ) ); (AnglevInt+tid)->function = &dJzdEHighStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - Or2*= delta / sqrt(2.); + Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3HighStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - I3r2*= delta / sqrt(2.); + I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzHighStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); - phitmp*= - *(Lz+ii) / delta / sqrt(2.); + phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) < 0.5 * M_PI ) { Or2= 1.5 * M_PI * *(dJzdE+ii) + Or2; I3r2= 1.5 * M_PI * *(dJzdI3+ii) + I3r2; @@ -1437,7 +1470,8 @@ void calcUminUmax(int ndata, double * E, double * Lz, double * I3U, - double delta, + int ndelta, + double * delta, double * u0, double * sinh2u0, double * v0, @@ -1462,11 +1496,11 @@ void calcUminUmax(int ndata, double u_lo, u_hi; T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ @@ -1479,8 +1513,9 @@ void calcUminUmax(int ndata, tid = 0; #endif //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); @@ -1536,7 +1571,7 @@ void calcUminUmax(int ndata, *(umin+ii)= *(ux+ii); u_lo= *(ux+ii) + 0.000001; u_hi= 1.1 * (*(ux+ii) + 0.000001); - while ( GSL_FN_EVAL(JRRoot+tid,u_hi) >= 0. && u_hi < asinh(37.5/delta)) { + while ( GSL_FN_EVAL(JRRoot+tid,u_hi) >= 0. && u_hi < asinh(37.5/ *(delta+ii*delta_stride))) { u_lo= u_hi; //this makes sure that brent evaluates using previous u_hi*= 1.1; } @@ -1606,7 +1641,7 @@ void calcUminUmax(int ndata, //Find starting points for maximum u_lo= *(ux+ii); u_hi= 1.1 * *(ux+ii); - while ( GSL_FN_EVAL(JRRoot+tid,u_hi) > 0. && u_hi < asinh(37.5/delta)) { + while ( GSL_FN_EVAL(JRRoot+tid,u_hi) > 0. && u_hi < asinh(37.5/ *(delta+ii*delta_stride))) { u_lo= u_hi; //this makes sure that brent evaluates using previous u_hi*= 1.1; } @@ -1654,7 +1689,8 @@ void calcVmin(int ndata, double * E, double * Lz, double * I3V, - double delta, + int ndelta, + double * delta, double * u0, double * cosh2u0, double * sinh2u0, @@ -1677,11 +1713,11 @@ void calcVmin(int ndata, double v_lo, v_hi; T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ - (params+tid)->delta= delta; (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } + int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ @@ -1694,8 +1730,9 @@ void calcVmin(int ndata, tid = 0; #endif //Setup function + (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); - (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / delta / delta; + (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); From 111a57ac7908a811f093ccf70c902b6e55ee523c Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Wed, 27 Dec 2017 13:33:55 -0500 Subject: [PATCH 47/62] Use metaclass to assign each actionAngle module's docstring to the corresponding actionAngle method (without the _ etc.); clean up all docstrings so they are correct --- galpy/actionAngle_src/actionAngle.py | 29 +++++++++- galpy/actionAngle_src/actionAngleAdiabatic.py | 31 ++++------- .../actionAngleAdiabaticGrid.py | 11 ++-- galpy/actionAngle_src/actionAngleIsochrone.py | 47 ++++++---------- .../actionAngleIsochroneApprox.py | 44 +++++++-------- galpy/actionAngle_src/actionAngleSpherical.py | 49 +++++++---------- galpy/actionAngle_src/actionAngleStaeckel.py | 55 +++++++++---------- .../actionAngleStaeckelGrid.py | 25 +++------ 8 files changed, 133 insertions(+), 158 deletions(-) diff --git a/galpy/actionAngle_src/actionAngle.py b/galpy/actionAngle_src/actionAngle.py index 9c072fc56..78208ee16 100644 --- a/galpy/actionAngle_src/actionAngle.py +++ b/galpy/actionAngle_src/actionAngle.py @@ -1,3 +1,6 @@ +from future.utils import with_metaclass +import types +import copy import math as m from galpy.util import config from galpy.util.bovy_conversion import physical_conversion_actionAngle, \ @@ -7,7 +10,31 @@ from astropy import units except ImportError: _APY_LOADED= False -class actionAngle(object): +# Metaclass for copying docstrings from subclass methods, first func +# to copy func +def copyfunc(func): + return types.FunctionType(func.__code__,func.__globals__, + name=func.__name__, + argdefs=func.__defaults__, + closure=func.__closure__) +class MetaActionAngle(type): + """Metaclass to assign subclass' docstrings for methods _evaluate, _actionsFreqs, _actionsFreqsAngles, and _EccZmaxRperiRap to their public cousins __call__, actionsFreqs, etc.""" + def __new__(meta,name,bases,attrs): + for key in copy.copy(attrs): # copy bc size changes + if key[0] == '_': + skey= copy.copy(key[1:]) + if skey == 'evaluate': skey= '__call__' + for base in bases: + original= getattr(base,skey,None) + if original is not None: + funccopy= copyfunc(original) + funccopy.__doc__= attrs[key].__doc__ + attrs[skey]= funccopy + break + return type.__new__(meta,name,bases,attrs) + +# Python 2 & 3 compatible way to have a metaclass +class actionAngle(with_metaclass(MetaActionAngle,object)): """Top-level class for actionAngle classes""" def __init__(self,ro=None,vo=None): """ diff --git a/galpy/actionAngle_src/actionAngleAdiabatic.py b/galpy/actionAngle_src/actionAngleAdiabatic.py index 4408610db..854b67632 100644 --- a/galpy/actionAngle_src/actionAngleAdiabatic.py +++ b/galpy/actionAngle_src/actionAngleAdiabatic.py @@ -74,18 +74,20 @@ def __init__(self,*args,**kwargs): def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation scipy.integrate.quadrature keywords _justjr, _justjz= if True, only calculate the radial or vertical action (internal use) OUTPUT: - (jr,lz,jz), where jr=[jr,jrerr], and jz=[jz,jzerr] + (jr,lz,jz) HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ @@ -163,33 +165,20 @@ def _evaluate(self,*args,**kwargs): def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: - - _EccZmaxRperiRap - + EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: - evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the adiabatic approximation - INPUT: - Either: - a) R,vR,vT,z,vz[,phi]: - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation OUTPUT: - (e,zmax,rperi,rap) - HISTORY: - 2017-12-21 - Written - Bovy (UofT) - """ if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ diff --git a/galpy/actionAngle_src/actionAngleAdiabaticGrid.py b/galpy/actionAngle_src/actionAngleAdiabaticGrid.py index 7de7f6c15..1d78e6bab 100644 --- a/galpy/actionAngle_src/actionAngleAdiabaticGrid.py +++ b/galpy/actionAngle_src/actionAngleAdiabaticGrid.py @@ -201,15 +201,16 @@ def __init__(self,pot=None,zmax=1.,gamma=1.,Rmax=5., def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well - scipy.integrate.quadrature keywords + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + scipy.integrate.quadrature keywords (used when directly evaluating a point off the grid) OUTPUT: (jr,lz,jz) HISTORY: diff --git a/galpy/actionAngle_src/actionAngleIsochrone.py b/galpy/actionAngle_src/actionAngleIsochrone.py index e618be0e1..6e19eec36 100644 --- a/galpy/actionAngle_src/actionAngleIsochrone.py +++ b/galpy/actionAngle_src/actionAngleIsochrone.py @@ -85,15 +85,15 @@ def __init__(self,*args,**kwargs): def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well - scipy.integrate.quadrature keywords + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz) HISTORY: @@ -135,15 +135,15 @@ def _evaluate(self,*args,**kwargs): def _actionsFreqs(self,*args,**kwargs): """ NAME: - _actionsFreqs + actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well - scipy.integrate.quadrature keywords + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: @@ -191,16 +191,15 @@ def _actionsFreqs(self,*args,**kwargs): def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: - _actionsFreqsAngles + actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: - evaluate the actions, frequencies, and angles - (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) + evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: - a) R,vR,vT,z,vz,phi (MUST HAVE PHI) - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well - scipy.integrate.quadrature keywords + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: @@ -303,33 +302,19 @@ def _actionsFreqsAngles(self,*args,**kwargs): def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: - _EccZmaxRperiRap - PURPOSE: - evaluate the eccentricity, maximum height above the plane, peri- and apocenter for an isochrone potential - INPUT: - Either: - a) R,vR,vT,z,vz[,phi]: - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - OUTPUT: - (e,zmax,rperi,rap) - HISTORY: - 2017-12-22 - Written - Bovy (UofT) - """ if len(args) == 5: #R,vR.vT, z, vz pragma: no cover R,vR,vT, z, vz= args diff --git a/galpy/actionAngle_src/actionAngleIsochroneApprox.py b/galpy/actionAngle_src/actionAngleIsochroneApprox.py index 7b25b1790..3754a5f2b 100644 --- a/galpy/actionAngle_src/actionAngleIsochroneApprox.py +++ b/galpy/actionAngle_src/actionAngleIsochroneApprox.py @@ -10,6 +10,8 @@ # # methods: # __call__: returns (jr,lz,jz) +# actionsFreqs: returns (jr,lz,jz,Or,Op,Oz) +# actionsFreqsAngles: returns (jr,lz,jz,Or,Op,Oz,ar,ap,az) # ############################################################################### import math @@ -122,17 +124,15 @@ def __init__(self,*args,**kwargs): def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz: - 1) floats: phase-space value for single object - 2) numpy.ndarray: [N] phase-space values for N objects - 3) numpy.ndarray: [N,M] phase-space values for N objects at M - times - b) Orbit instance or list thereof; can be integrated already + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument cumul= if True, return the cumulative average actions (to look at convergence) OUTPUT: @@ -184,17 +184,18 @@ def _evaluate(self,*args,**kwargs): def _actionsFreqs(self,*args,**kwargs): """ NAME: - _actionsFreqs + actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: - a) R,vR,vT,z,vz: - 1) floats: phase-space value for single object - 2) numpy.ndarray: [N] phase-space values for N objects - 3) numpy.ndarray: [N,M] phase-space values for N objects at M - times - b) Orbit instance or list thereof; can be integrated already + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + maxn= (default: object-wide default) Use a grid in vec(n) up to this n (zero-based) + ts= if set, the phase-space points correspond to these times (IF NOT SET, WE ASSUME THAT ts IS THAT THAT IS ASSOCIATED WITH THIS OBJECT) + _firstFlip= (False) if True and Orbits are given, the backward part of the orbit is integrated first and stored in the Orbit object OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: @@ -206,18 +207,15 @@ def _actionsFreqs(self,*args,**kwargs): def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: - _actionsFreqsAngles + actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: - evaluate the actions, frequencies, and angles - (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) + evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: - a) R,vR,vT,z,vz: - 1) floats: phase-space value for single object - 2) numpy.ndarray: [N] phase-space values for N objects - 3) numpy.ndarray: [N,M] phase-space values for N objects at M - times - b) Orbit instance or list thereof; can be integrated already + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument maxn= (default: object-wide default) Use a grid in vec(n) up to this n (zero-based) ts= if set, the phase-space points correspond to these times (IF NOT SET, WE ASSUME THAT ts IS THAT THAT IS ASSOCIATED WITH THIS OBJECT) _firstFlip= (False) if True and Orbits are given, the backward part of the orbit is integrated first and stored in the Orbit object diff --git a/galpy/actionAngle_src/actionAngleSpherical.py b/galpy/actionAngle_src/actionAngleSpherical.py index c53f0a561..2f5903728 100644 --- a/galpy/actionAngle_src/actionAngleSpherical.py +++ b/galpy/actionAngle_src/actionAngleSpherical.py @@ -71,16 +71,17 @@ def __init__(self,*args,**kwargs): def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration - scipy.integrate.quadrature keywords + scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz) HISTORY: @@ -135,16 +136,17 @@ def _evaluate(self,*args,**kwargs): def _actionsFreqs(self,*args,**kwargs): """ NAME: - _actionsFreqs + actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration - scipy.integrate.quadrature keywords + scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: @@ -211,17 +213,18 @@ def _actionsFreqs(self,*args,**kwargs): def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: - _actionsFreqsAngles + actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,ar,ap,az) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration - scipy.integrate.quadrature keywords + scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,ar,aphi,az) HISTORY: @@ -312,33 +315,19 @@ def _actionsFreqsAngles(self,*args,**kwargs): def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: - - _EccZmaxRperiRap - + EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: - evaluate the eccentricity, maximum height above the plane, peri- and apocenter for a spherical potential - INPUT: - Either: - a) R,vR,vT,z,vz[,phi]: - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - OUTPUT: - (e,zmax,rperi,rap) - HISTORY: - 2017-12-22 - Written - Bovy (UofT) - """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index d68057630..a3d8d44f2 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -90,7 +90,7 @@ def __init__(self,*args,**kwargs): def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: @@ -98,8 +98,11 @@ def _evaluate(self,*args,**kwargs): a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well - c= True/False; overrides the object's c= keyword to use C or not - scipy.integrate.quadrature keywords + u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + When not using C: + fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) + scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz) HISTORY: @@ -177,7 +180,7 @@ def _evaluate(self,*args,**kwargs): def _actionsFreqs(self,*args,**kwargs): """ NAME: - _actionsFreqs + actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: @@ -185,7 +188,11 @@ def _actionsFreqs(self,*args,**kwargs): a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well - scipy.integrate.quadrature keywords + u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + When not using C: + fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) + scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: @@ -245,16 +252,19 @@ def _actionsFreqs(self,*args,**kwargs): def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: - _actionsFreqsAngles + actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: - evaluate the actions, frequencies, and angles - (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) + evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: - a) R,vR,vT,z,vz,phi (MUST HAVE PHI) + a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well - scipy.integrate.quadrature keywords + u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + When not using C: + fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) + scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: @@ -316,33 +326,20 @@ def _actionsFreqsAngles(self,*args,**kwargs): def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: - - _EccZmaxRperiRap - + EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: - evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation - INPUT: - Either: - - a) R,vR,vT,z,vz[,phi]: - - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - + a) R,vR,vT,z,vz + b) Orbit instance: initial condition used if that's it, orbit(t) + if there is a time given as well + u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) + c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation OUTPUT: - (e,zmax,rperi,rap) - HISTORY: - 2017-12-12 - Written - Bovy (UofT) - """ umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=self._delta)[0] diff --git a/galpy/actionAngle_src/actionAngleStaeckelGrid.py b/galpy/actionAngle_src/actionAngleStaeckelGrid.py index 1f9970918..18fa98977 100644 --- a/galpy/actionAngle_src/actionAngleStaeckelGrid.py +++ b/galpy/actionAngle_src/actionAngleStaeckelGrid.py @@ -283,13 +283,16 @@ def __init__(self,pot=None,delta=None,Rmax=5., def _evaluate(self,*args,**kwargs): """ NAME: - _evaluate + __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: - R,vR,vT,z,vz - scipy.integrate.quadrature keywords (for off-the-grid calcs) + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + Keywords for actionAngleStaeckel.__call__ for off-the-grid evaluations OUTPUT: (jr,lz,jz) HISTORY: @@ -447,33 +450,19 @@ def JR(self,*args,**kwargs): def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: - - EccZmaxRperiRap - + EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: - evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation - INPUT: - Either: - a) R,vR,vT,z,vz[,phi]: - 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) - 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) - b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument - OUTPUT: - (e,zmax,rperi,rap) - HISTORY: - 2017-12-15 - Written - Bovy (UofT) - """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args From cb9909dfa3408099b52b23844c1c970731c596e0 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Thu, 28 Dec 2017 10:31:49 -0500 Subject: [PATCH 48/62] Allow object-wide delta to be overriden in individual actionAngleStaeckel methods and for delta to be different for different phase-space points --- galpy/actionAngle_src/actionAngleStaeckel.py | 88 ++++++++++++------- .../actionAngle_src/actionAngleStaeckel_c.py | 8 +- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index a3d8d44f2..f48acd6d7 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -95,9 +95,11 @@ def _evaluate(self,*args,**kwargs): evaluate the actions (jr,lz,jz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation When not using C: @@ -107,7 +109,9 @@ def _evaluate(self,*args,**kwargs): (jr,lz,jz) HISTORY: 2012-11-27 - Written - Bovy (IAS) + 2017-12-27 - Allowed individual delta for each point - Bovy (UofT) """ + delta= kwargs.pop('delta',self._delta) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -136,14 +140,13 @@ def _evaluate(self,*args,**kwargs): else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) - u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(E,Lz, - self._pot, - self._delta)[0] + u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ + E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, err= actionAngleStaeckel_c.actionAngleStaeckel_c(\ - self._pot,self._delta,R,vR,vT,z,vz,u0=u0) + self._pot,delta,R,vR,vT,z,vz,u0=u0) if err == 0: return (jr,Lz,jz) else: #pragma: no cover @@ -164,7 +167,12 @@ def _evaluate(self,*args,**kwargs): elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) - tjr,tlz,tjz= self(*targs,**copy.copy(kwargs)) + tkwargs= copy.copy(kwargs) + try: + tkwargs['delta']= delta[ii] + except TypeError: + tkwargs['delta']= delta + tjr,tlz,tjz= self(*targs,**tkwargs) ojr[ii]= tjr ojz[ii]= tjz olz[ii]= tlz @@ -172,7 +180,7 @@ def _evaluate(self,*args,**kwargs): else: #Set up the actionAngleStaeckelSingle object aASingle= actionAngleStaeckelSingle(*args,pot=self._pot, - delta=self._delta) + delta=delta) return (aASingle.JR(**copy.copy(kwargs)), aASingle._R*aASingle._vT, aASingle.Jz(**copy.copy(kwargs))) @@ -185,9 +193,11 @@ def _actionsFreqs(self,*args,**kwargs): evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation When not using C: @@ -198,6 +208,7 @@ def _actionsFreqs(self,*args,**kwargs): HISTORY: 2013-08-28 - Written - Bovy (IAS) """ + delta= kwargs.pop('delta',self._delta) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -226,14 +237,13 @@ def _actionsFreqs(self,*args,**kwargs): else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) - u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(E,Lz, - self._pot, - self._delta)[0] + u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ + E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, err= actionAngleStaeckel_c.actionAngleFreqStaeckel_c(\ - self._pot,self._delta,R,vR,vT,z,vz,u0=u0) + self._pot,delta,R,vR,vT,z,vz,u0=u0) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: @@ -257,9 +267,11 @@ def _actionsFreqsAngles(self,*args,**kwargs): evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation When not using C: @@ -270,6 +282,7 @@ def _actionsFreqsAngles(self,*args,**kwargs): HISTORY: 2013-08-28 - Written - Bovy (IAS) """ + delta= kwargs.pop('delta',self._delta) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -300,14 +313,13 @@ def _actionsFreqsAngles(self,*args,**kwargs): else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) - u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(E,Lz, - self._pot, - self._delta)[0] + u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ + E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, angler, anglephi,anglez, err= actionAngleStaeckel_c.actionAngleFreqAngleStaeckel_c(\ - self._pot,self._delta,R,vR,vT,z,vz,phi,u0=u0) + self._pot,delta,R,vR,vT,z,vz,phi,u0=u0) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: @@ -331,9 +343,11 @@ def _EccZmaxRperiRap(self,*args,**kwargs): evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation INPUT: Either: - a) R,vR,vT,z,vz - b) Orbit instance: initial condition used if that's it, orbit(t) - if there is a time given as well + a) R,vR,vT,z,vz[,phi]: + 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) + 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) + b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument + delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation OUTPUT: @@ -341,9 +355,10 @@ def _EccZmaxRperiRap(self,*args,**kwargs): HISTORY: 2017-12-12 - Written - Bovy (UofT) """ + delta= kwargs.get('delta',self._delta) umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) - rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=self._delta)[0] - rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=self._delta) + rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=delta)[0] + rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=delta) rap= nu.sqrt(rap_tmp**2.+zmax**2.) e= (rap-rperi)/(rap+rperi) return (e,zmax,rperi,rap) @@ -365,6 +380,7 @@ def _uminumaxvmin(self,*args,**kwargs): HISTORY: 2017-12-12 - Written - Bovy (UofT) """ + delta= kwargs.pop('delta',self._delta) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -393,15 +409,14 @@ def _uminumaxvmin(self,*args,**kwargs): else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) - u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(E,Lz, - self._pot, - self._delta)[0] + u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ + E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None umin, umax, vmin, err= \ actionAngleStaeckel_c.actionAngleUminUmaxVminStaeckel_c(\ - self._pot,self._delta,R,vR,vT,z,vz,u0=u0) + self._pot,delta,R,vR,vT,z,vz,u0=u0) if err == 0: return (umin,umax,vmin) else: #pragma: no cover @@ -422,8 +437,13 @@ def _uminumaxvmin(self,*args,**kwargs): elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) + tkwargs= copy.copy(kwargs) + try: + tkwargs['delta']= delta[ii] + except TypeError: + tkwargs['delta']= delta tumin,tumax,tvmin= self._uminumaxvmin(\ - *targs,**copy.copy(kwargs)) + *targs,**tkwargs) oumin[ii]= tumin oumax[ii]= tumax ovmin[ii]= tvmin @@ -431,7 +451,7 @@ def _uminumaxvmin(self,*args,**kwargs): else: #Set up the actionAngleStaeckelSingle object aASingle= actionAngleStaeckelSingle(*args,pot=self._pot, - delta=self._delta) + delta=delta) umin, umax= aASingle.calcUminUmax() vmin= aASingle.calcVmin() return (umin,umax,vmin) diff --git a/galpy/actionAngle_src/actionAngleStaeckel_c.py b/galpy/actionAngle_src/actionAngleStaeckel_c.py index f1c382f26..0066d5dc1 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel_c.py +++ b/galpy/actionAngle_src/actionAngleStaeckel_c.py @@ -55,7 +55,7 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): 2012-12-01 - Written - Bovy (IAS) """ if u0 is None: - u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=delta) + u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) @@ -222,7 +222,7 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): 2013-08-23 - Written - Bovy (IAS) """ if u0 is None: - u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=delta) + u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) @@ -332,7 +332,7 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): 2013-08-27 - Written - Bovy (IAS) """ if u0 is None: - u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=delta) + u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) @@ -459,7 +459,7 @@ def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): 2017-12-12 - Written - Bovy (UofT) """ if u0 is None: - u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=delta) + u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) From 1bc3ae08039c795fd225a526dcf32948362a0d00 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Thu, 28 Dec 2017 10:31:56 -0500 Subject: [PATCH 49/62] Tests of individual delta --- tests/test_actionAngle.py | 200 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index bc020929a..f4531201f 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -1158,6 +1158,206 @@ def test_actionAngleStaeckel_basic_EccZmaxRperiRap_u0_c(): assert numpy.fabs(tzmax) < 2.*10.**-2., 'Close-to-circular orbit in the MWPotential does not have small zmax' return None +#Test that using different delta for different phase-space points works +def test_actionAngleStaeckel_indivdelta_actions(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # actions with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=False) + jr0,jp0,jz0= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=False) + jr1,jp1,jz1= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with individual delta + jri,jpi,jzi= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2]),delta=deltas) + # Check that they agree as expected + assert numpy.fabs(jr0[0]-jri[0]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jr1[1]-jri[1]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz0[0]-jzi[0]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz1[1]-jzi[1]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + +def test_actionAngleStaeckel_indivdelta_actions_c(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # actions with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=True) + jr0,jp0,jz0= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=True) + jr1,jp1,jz1= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with individual delta + jri,jpi,jzi= aAS(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2]),delta=deltas) + # Check that they agree as expected + assert numpy.fabs(jr0[0]-jri[0]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jr1[1]-jri[1]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz0[0]-jzi[0]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz1[1]-jzi[1]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + +def test_actionAngleStaeckel_indivdelta_freqs_c(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # actions with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=True) + jr0,jp0,jz0,or0,op0,oz0= aAS.actionsFreqs(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=True) + jr1,jp1,jz1,or1,op1,oz1= aAS.actionsFreqs(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2])) + # actions with individual delta + jri,jpi,jzi,ori,opi,ozi= aAS.actionsFreqs(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2]), + delta=deltas) + # Check that they agree as expected + assert numpy.fabs(jr0[0]-jri[0]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jr1[1]-jri[1]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz0[0]-jzi[0]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz1[1]-jzi[1]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(or0[0]-ori[0]) < 1e-10, 'Radial frequencyaction computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(or1[1]-ori[1]) < 1e-10, 'Radial frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(op0[0]-opi[0]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(op1[1]-opi[1]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(oz0[0]-ozi[0]) < 1e-10, 'Azimuthal frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(oz1[1]-ozi[1]) < 1e-10, 'Vertical frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + +def test_actionAngleStaeckel_indivdelta_angles_c(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # actions with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=True) + jr0,jp0,jz0,or0,op0,oz0,ar0,ap0,az0=\ + aAS.actionsFreqsAngles(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=True) + jr1,jp1,jz1,or1,op1,oz1,ar1,ap1,az1=\ + aAS.actionsFreqsAngles(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2])) + # actions with individual delta + jri,jpi,jzi,ori,opi,ozi,ari,api,azi=\ + aAS.actionsFreqsAngles(o.R(ts[:2]),o.vR(ts[:2]), + o.vT(ts[:2]),o.z(ts[:2]), + o.vz(ts[:2]),o.phi(ts[:2]), + delta=deltas) + # Check that they agree as expected + assert numpy.fabs(jr0[0]-jri[0]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jr1[1]-jri[1]) < 1e-10, 'Radial action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz0[0]-jzi[0]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(jz1[1]-jzi[1]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(or0[0]-ori[0]) < 1e-10, 'Radial frequencyaction computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(or1[1]-ori[1]) < 1e-10, 'Radial frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(op0[0]-opi[0]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(op1[1]-opi[1]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(oz0[0]-ozi[0]) < 1e-10, 'Azimuthal frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(oz1[1]-ozi[1]) < 1e-10, 'Vertical frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ar0[0]-ari[0]) < 1e-10, 'Radial frequencyaction computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ar1[1]-ari[1]) < 1e-10, 'Radial frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ap0[0]-api[0]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ap1[1]-api[1]) < 1e-10, 'Azimuthal computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(az0[0]-azi[0]) < 1e-10, 'Azimuthal frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(az1[1]-azi[1]) < 1e-10, 'Vertical frequency computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + +def test_actionAngleStaeckel_indivdelta_EccZmaxRperiRap(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=False) + e0,z0,rp0,ra0= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=False) + e1,z1,rp1,ra1= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with individual delta + ei,zi,rpi,rai= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2]),delta=deltas) + # Check that they agree as expected + assert numpy.fabs(e0[0]-ei[0]) < 1e-10, 'Eccentricity computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(e1[1]-ei[1]) < 1e-10, 'Eccentricity computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(z0[0]-zi[0]) < 1e-10, 'Zmax computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(z1[1]-zi[1]) < 1e-10, 'Zmax computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(rp0[0]-rpi[0]) < 1e-10, 'Pericenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(rp1[1]-rpi[1]) < 1e-10, 'Pericenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ra0[0]-rai[0]) < 1e-10, 'Apocenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ra1[1]-rai[1]) < 1e-10, 'Apocenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + +def test_actionAngleStaeckel_indivdelta_EccZmaxRperiRap_c(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + deltas= [0.2,0.4] + # with one delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[0],c=True) + e0,z0,rp0,ra0= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with another delta + aAS= actionAngleStaeckel(pot=MWPotential2014,delta=deltas[1],c=True) + e1,z1,rp1,ra1= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2])) + # actions with individual delta + ei,zi,rpi,rai= aAS.EccZmaxRperiRap(o.R(ts[:2]),o.vR(ts[:2]),o.vT(ts[:2]), + o.z(ts[:2]),o.vz(ts[:2]),delta=deltas) + # Check that they agree as expected + assert numpy.fabs(e0[0]-ei[0]) < 1e-10, 'Eccentricity computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(e1[1]-ei[1]) < 1e-10, 'Eccentricity computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(z0[0]-zi[0]) < 1e-10, 'Zmax computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(z1[1]-zi[1]) < 1e-10, 'Zmax computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(rp0[0]-rpi[0]) < 1e-10, 'Pericenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(rp1[1]-rpi[1]) < 1e-10, 'Pericenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ra0[0]-rai[0]) < 1e-10, 'Apocenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + assert numpy.fabs(ra1[1]-rai[1]) < 1e-10, 'Apocenter computed with invidual delta does not agree with that computed using the fixed orbit-wide default' + return None + #Test the actions of an actionAngleStaeckel def test_actionAngleStaeckel_conserved_actions(): from galpy.potential import MWPotential From 006917997c46637d48ceaa8ba9198611b9bb223c Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Thu, 28 Dec 2017 10:32:21 -0500 Subject: [PATCH 50/62] Doc tweak --- galpy/actionAngle_src/actionAngle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/galpy/actionAngle_src/actionAngle.py b/galpy/actionAngle_src/actionAngle.py index 78208ee16..7ba10c4c5 100644 --- a/galpy/actionAngle_src/actionAngle.py +++ b/galpy/actionAngle_src/actionAngle.py @@ -303,8 +303,7 @@ def actionsFreqsAngles(self,*args,**kwargs): PURPOSE: - evaluate the actions, frequencies, and angles - (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) + evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: From d094f613b3747b7e30e00a7899040a0904d220fb Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Thu, 28 Dec 2017 10:33:22 -0500 Subject: [PATCH 51/62] Add individual focal lengths (delta) for actionAngleStaeckel to HISTORY --- HISTORY.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HISTORY.txt b/HISTORY.txt index 62d604743..734610913 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -62,6 +62,9 @@ v1.3 (2017-XX-XX) angles for Orbit instances to be the Staeckel approximation with an automatically-estimated delta parameter. +- Generalized actionAngleStaeckel to allow for different focal lengths + delta for different phase-space points. + - Allow transformations of (ra,dec) and (pmra,pmdec) to custom coordinate systems. From b76df2695c18ee89ba77c1df2e3cc145fd1feca4 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Thu, 28 Dec 2017 10:46:15 -0500 Subject: [PATCH 52/62] Get with_metaclass from six, because already a dependency --- galpy/actionAngle_src/actionAngle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galpy/actionAngle_src/actionAngle.py b/galpy/actionAngle_src/actionAngle.py index 7ba10c4c5..ffd92a728 100644 --- a/galpy/actionAngle_src/actionAngle.py +++ b/galpy/actionAngle_src/actionAngle.py @@ -1,4 +1,4 @@ -from future.utils import with_metaclass +from six import with_metaclass import types import copy import math as m From fc1487505b939fe10d09e199f6c3d1920d3f0df6 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 29 Dec 2017 20:09:25 -0500 Subject: [PATCH 53/62] Allow the order of the Gauss-Legendre integration to be specified (instance-wide or for individual methods) in actionAngleStaeckel --- galpy/actionAngle_src/actionAngleStaeckel.py | 21 +++++++++--- .../actionAngle_src/actionAngleStaeckel_c.py | 19 +++++++++-- .../actionAngle_c_ext/actionAngleStaeckel.c | 33 ++++++++++--------- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index f48acd6d7..f34aaefb5 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -49,6 +49,8 @@ def __init__(self,*args,**kwargs): c= if True, always use C for calculations + order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action, frequency, and angle integrals + ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) @@ -81,6 +83,7 @@ def __init__(self,*args,**kwargs): self._c= False self._useu0= kwargs.get('useu0',False) self._delta= kwargs['delta'] + self._order= kwargs.get('order',10) if _APY_LOADED and isinstance(self._delta,units.Quantity): self._delta= self._delta.to(units.kpc).value/self._ro # Check the units @@ -102,6 +105,7 @@ def _evaluate(self,*args,**kwargs): delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + order= (object-wide default, int) number of points to use in the Gauss-Legendre numerical integration of the relevant action integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords @@ -112,6 +116,7 @@ def _evaluate(self,*args,**kwargs): 2017-12-27 - Allowed individual delta for each point - Bovy (UofT) """ delta= kwargs.pop('delta',self._delta) + order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -146,7 +151,7 @@ def _evaluate(self,*args,**kwargs): else: u0= None jr, jz, err= actionAngleStaeckel_c.actionAngleStaeckel_c(\ - self._pot,delta,R,vR,vT,z,vz,u0=u0) + self._pot,delta,R,vR,vT,z,vz,u0=u0,order=order) if err == 0: return (jr,Lz,jz) else: #pragma: no cover @@ -200,6 +205,7 @@ def _actionsFreqs(self,*args,**kwargs): delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action and frequency integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords @@ -209,6 +215,7 @@ def _actionsFreqs(self,*args,**kwargs): 2013-08-28 - Written - Bovy (IAS) """ delta= kwargs.pop('delta',self._delta) + order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -243,7 +250,7 @@ def _actionsFreqs(self,*args,**kwargs): else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, err= actionAngleStaeckel_c.actionAngleFreqStaeckel_c(\ - self._pot,delta,R,vR,vT,z,vz,u0=u0) + self._pot,delta,R,vR,vT,z,vz,u0=u0,order=order) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: @@ -274,6 +281,7 @@ def _actionsFreqsAngles(self,*args,**kwargs): delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation + order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action, frequency, and angle integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords @@ -283,6 +291,7 @@ def _actionsFreqsAngles(self,*args,**kwargs): 2013-08-28 - Written - Bovy (IAS) """ delta= kwargs.pop('delta',self._delta) + order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): @@ -319,7 +328,7 @@ def _actionsFreqsAngles(self,*args,**kwargs): else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, angler, anglephi,anglez, err= actionAngleStaeckel_c.actionAngleFreqAngleStaeckel_c(\ - self._pot,delta,R,vR,vT,z,vz,phi,u0=u0) + self._pot,delta,R,vR,vT,z,vz,phi,u0=u0,order=order) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: @@ -615,6 +624,7 @@ def JR(self,**kwargs): umin, umax= self.calcUminUmax() #print self._ux, self._pux, (umax-umin)/umax if (umax-umin)/umax < 10.**-6: return nu.array([0.]) + order= kwargs.pop('order',10) if kwargs.pop('fixed_quad',False): # factor in next line bc integrand=/2delta^2 self._JR= 1./nu.pi*nu.sqrt(2.)*self._delta\ @@ -625,7 +635,7 @@ def JR(self,**kwargs): self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), - n=10, + n=order, **kwargs)[0] else: self._JR= 1./nu.pi*nu.sqrt(2.)*self._delta\ @@ -657,6 +667,7 @@ def Jz(self,**kwargs): return self._JZ vmin= self.calcVmin() if (nu.pi/2.-vmin) < 10.**-7: return nu.array([0.]) + order= kwargs.pop('order',10) if kwargs.pop('fixed_quad',False): # factor in next line bc integrand=/2delta^2 self._JZ= 2./nu.pi*nu.sqrt(2.)*self._delta \ @@ -667,7 +678,7 @@ def Jz(self,**kwargs): self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot), - n=10, + n=order, **kwargs)[0] else: # factor in next line bc integrand=/2delta^2 diff --git a/galpy/actionAngle_src/actionAngleStaeckel_c.py b/galpy/actionAngle_src/actionAngleStaeckel_c.py index 0066d5dc1..51e18d8d2 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel_c.py +++ b/galpy/actionAngle_src/actionAngleStaeckel_c.py @@ -37,7 +37,7 @@ else: _ext_loaded= True -def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): +def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None,order=10): """ NAME: actionAngleStaeckel_c @@ -47,6 +47,8 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz - coordinates (arrays) + u0= (None) if set, u0 to use + order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,err) jr,jz : array, shape (len(R)) @@ -83,6 +85,7 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] @@ -118,6 +121,7 @@ def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): pot_args, ctypes.c_int(ndelta), delta, + ctypes.c_int(order), jr, jz, ctypes.byref(err)) @@ -203,7 +207,7 @@ def actionAngleStaeckel_calcu0(E,Lz,pot,delta): return (u0,err.value) -def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): +def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None,order=10): """ NAME: actionAngleFreqStaeckel_c @@ -214,6 +218,8 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz - coordinates (arrays) + u0= (None) if set, u0 to use + order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,Omegar,Omegaphi,Omegaz,err) jr,jz,Omegar,Omegaphi,Omegaz : array, shape (len(R)) @@ -253,6 +259,7 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), @@ -295,6 +302,7 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): pot_args, ctypes.c_int(ndelta), delta, + ctypes.c_int(order), jr, jz, Omegar, @@ -313,7 +321,8 @@ def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): return (jr,jz,Omegar,Omegaphi,Omegaz,err.value) -def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): +def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi, + u0=None,order=10): """ NAME: actionAngleFreqAngleStaeckel_c @@ -324,6 +333,8 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz, phi - coordinates (arrays) + u0= (None) if set, u0 to use + order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,Omegar,Omegaphi,Omegaz,Angler,Anglephi,Anglez,err) jr,jz,Omegar,Omegaphi,Omegaz,Angler,Anglephi,Anglez : array, shape (len(R)) @@ -366,6 +377,7 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), + ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), @@ -415,6 +427,7 @@ def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi,u0=None): pot_args, ctypes.c_int(ndelta), delta, + ctypes.c_int(order), jr, jz, Omegar, diff --git a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c index 852ae5e7e..bd715110f 100644 --- a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c +++ b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c @@ -94,17 +94,17 @@ void actionAngleStaeckel_uminUmaxVmin(int,double *,double *,double *,double *, double *,double *,int *); void actionAngleStaeckel_actions(int,double *,double *,double *,double *, double *,double *,int,int *,double *,int, - double *,double *,double *,int *); + double *,int,double *,double *,int *); void actionAngleStaeckel_actionsFreqsAngles(int,double *,double *,double *, double *,double *,double *, int,int *,double *, - int,double *,double *,double *, + int,double *,int,double *,double *, double *,double *,double *, double *,double *,double *,int *); void actionAngleStaeckel_actionsFreqs(int,double *,double *,double *,double *, double *,double *,int,int *,double *, - int,double *,double *,double *,double *, - double *,double *,int *); + int,double *,int,double *,double *, + double *,double *,double *,int *); void calcAnglesStaeckel(int,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, @@ -402,6 +402,7 @@ void actionAngleStaeckel_actions(int ndata, double * pot_args, int ndelta, double * delta, + int order, double *jr, double *jz, int * err){ @@ -478,9 +479,9 @@ void actionAngleStaeckel_actions(int ndata, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs,10); + potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, - potupi2,npot,actionAngleArgs,10); + potupi2,npot,actionAngleArgs,order); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); @@ -655,6 +656,7 @@ void actionAngleStaeckel_actionsFreqs(int ndata, double * pot_args, int ndelta, double * delta, + int order, double *jr, double *jz, double *Omegar, @@ -734,9 +736,9 @@ void actionAngleStaeckel_actionsFreqs(int ndata, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs,10); + potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, - potupi2,npot,actionAngleArgs,10); + potupi2,npot,actionAngleArgs,order); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); @@ -747,10 +749,10 @@ void actionAngleStaeckel_actionsFreqs(int ndata, double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs,10); + potu0v0,npot,actionAngleArgs,order); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, - potupi2,npot,actionAngleArgs,10); + potupi2,npot,actionAngleArgs,order); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3); @@ -798,6 +800,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, double * pot_args, int ndelta, double * delta, + int order, double *jr, double *jz, double *Omegar, @@ -880,9 +883,9 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs,10); + potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, - potupi2,npot,actionAngleArgs,10); + potupi2,npot,actionAngleArgs,order); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); @@ -893,10 +896,10 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, - potu0v0,npot,actionAngleArgs,10); + potu0v0,npot,actionAngleArgs,order); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, - potupi2,npot,actionAngleArgs,10); + potupi2,npot,actionAngleArgs,order); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3); @@ -913,7 +916,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0, vmin,I3V,cosh2u0,potupi2, - npot,actionAngleArgs,10); + npot,actionAngleArgs,order); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); From 7e16fe8f574b1c7f9199e69b9ee5eb326a938c94 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 29 Dec 2017 20:09:50 -0500 Subject: [PATCH 54/62] Test that the accuracy of actionAngleStaeckel increases with increasing Gauss-Legendre order --- tests/test_actionAngle.py | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index f4531201f..1d2d5b254 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -976,6 +976,37 @@ def test_actionAngleStaeckel_zerolz_actions_c(): assert numpy.fabs(js[0]-js2[0]) < 10.**-6., 'Orbit with zero angular momentum does not have the correct Jr' return None +# Check that precision increases with increasing Gauss-Legendre order +def test_actionAngleStaeckel_actions_order(): + from galpy.potential import KuzminKutuzovStaeckelPotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + kksp= KuzminKutuzovStaeckelPotential(normalize=1.,ac=4.,Delta=1.4) + o= Orbit([1.,0.5,1.1,0.2,-0.3,0.4]) + aAS= actionAngleStaeckel(pot=kksp,delta=kksp._Delta,c=False) + # We'll assume that order=10000 is the truth, so 50 should be better than 5 + jrt,jpt,jzt= aAS(o,order=10000,fixed_quad=True) + jr1,jp1,jz1= aAS(o,order=5,fixed_quad=True) + jr2,jp2,jz2= aAS(o,order=50,fixed_quad=True) + assert numpy.fabs(jr1-jrt) > numpy.fabs(jr2-jrt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(jz1-jzt) > numpy.fabs(jz2-jzt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + return None + +def test_actionAngleStaeckel_actions_order_c(): + from galpy.potential import KuzminKutuzovStaeckelPotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + kksp= KuzminKutuzovStaeckelPotential(normalize=1.,ac=4.,Delta=1.4) + o= Orbit([1.,0.5,1.1,0.2,-0.3,0.4]) + aAS= actionAngleStaeckel(pot=kksp,delta=kksp._Delta,c=True) + # We'll assume that order=10000 is the truth, so 50 should be better than 5 + jrt,jpt,jzt= aAS(o,order=10000) + jr1,jp1,jz1= aAS(o,order=5) + jr2,jp2,jz2= aAS(o,order=50) + assert numpy.fabs(jr1-jrt) > numpy.fabs(jr2-jrt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(jz1-jzt) > numpy.fabs(jz2-jzt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + return None + #Basic sanity checking of the actionAngleStaeckel frequencies def test_actionAngleStaeckel_basic_freqs_c(): from galpy.actionAngle import actionAngleStaeckel @@ -1069,6 +1100,25 @@ def test_actionAngleStaeckel_unboundr_freqs_c(): assert js[5] > 1000., 'Unbound in R orbit in the MWPotential does not have large Oz' return None +# Check that precision increases with increasing Gauss-Legendre order +def test_actionAngleStaeckel_freqs_order_c(): + from galpy.potential import KuzminKutuzovStaeckelPotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + kksp= KuzminKutuzovStaeckelPotential(normalize=1.,ac=4.,Delta=1.4) + o= Orbit([1.,0.5,1.1,0.2,-0.3,0.4]) + aAS= actionAngleStaeckel(pot=kksp,delta=kksp._Delta,c=True) + # We'll assume that order=10000 is the truth, so 50 should be better than 5 + jrt,jpt,jzt,ort,opt,ozt= aAS.actionsFreqs(o,order=10000) + jr1,jp1,jz1,or1,op1,oz1= aAS.actionsFreqs(o,order=5) + jr2,jp2,jz2,or2,op2,oz2= aAS.actionsFreqs(o,order=50) + assert numpy.fabs(jr1-jrt) > numpy.fabs(jr2-jrt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(jz1-jzt) > numpy.fabs(jz2-jzt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(or1-ort) > numpy.fabs(or2-ort), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(op1-opt) > numpy.fabs(op2-opt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(oz1-ozt) > numpy.fabs(oz2-ozt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + return None + #Basic sanity checking of the actionAngleStaeckel actions, unbound def test_actionAngleStaeckel_unboundr_angles_c(): from galpy.actionAngle import actionAngleStaeckel @@ -1095,6 +1145,28 @@ def test_actionAngleStaeckel_circular_angles_c(): assert numpy.fabs(js[8]) < 10.**-8., 'Circular orbit does not have zero angles' return None +# Check that precision increases with increasing Gauss-Legendre order +def test_actionAngleStaeckel_angles_order_c(): + from galpy.potential import KuzminKutuzovStaeckelPotential + from galpy.orbit import Orbit + from galpy.actionAngle import actionAngleStaeckel + kksp= KuzminKutuzovStaeckelPotential(normalize=1.,ac=4.,Delta=1.4) + o= Orbit([1.,0.5,1.1,0.2,-0.3,0.4]) + aAS= actionAngleStaeckel(pot=kksp,delta=kksp._Delta,c=True) + # We'll assume that order=10000 is the truth, so 50 should be better than 5 + jrt,jpt,jzt,ort,opt,ozt,art,apt,azt= aAS.actionsFreqsAngles(o,order=10000) + jr1,jp1,jz1,or1,op1,oz1,ar1,ap1,az1= aAS.actionsFreqsAngles(o,order=5) + jr2,jp2,jz2,or2,op2,oz2,ar2,ap2,az2= aAS.actionsFreqsAngles(o,order=50) + assert numpy.fabs(jr1-jrt) > numpy.fabs(jr2-jrt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(jz1-jzt) > numpy.fabs(jz2-jzt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(or1-ort) > numpy.fabs(or2-ort), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(op1-opt) > numpy.fabs(op2-opt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(oz1-ozt) > numpy.fabs(oz2-ozt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(ar1-art) > numpy.fabs(ar2-art), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(ap1-apt) > numpy.fabs(ap2-apt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + assert numpy.fabs(az1-azt) > numpy.fabs(az2-azt), 'Accuracy of actionAngleStaeckel does not increase with increasing order of integration' + return None + #Basic sanity checking of the actionAngleStaeckel ecc, zmax, rperi, rap calc. def test_actionAngleStaeckel_basic_EccZmaxRperiRap(): from galpy.actionAngle import actionAngleStaeckel From f533b6dc7d68216fd50d0c97697fe7dcc64fa1a3 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 29 Dec 2017 20:11:30 -0500 Subject: [PATCH 55/62] Entry in HISTORY for flexibility in Gauss-Legendre order in actionAngleStaeckel --- HISTORY.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.txt b/HISTORY.txt index 734610913..0806fa826 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -63,7 +63,10 @@ v1.3 (2017-XX-XX) automatically-estimated delta parameter. - Generalized actionAngleStaeckel to allow for different focal lengths - delta for different phase-space points. + delta for different phase-space points. Also allowed the order of + the Gauss-Legendre integration to be specified (default: 10, which + is good enough when using actionAngleStaeckel to compute approximate + actions etc. for an axisymmetric potential). - Allow transformations of (ra,dec) and (pmra,pmdec) to custom coordinate systems. From 549508ae7b1aeabf5f38212d1cec38a9117982b1 Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Mon, 1 Jan 2018 15:53:38 -0500 Subject: [PATCH 56/62] Also free dI3dLz in actionAngle_actionsFreqsAngles; fixes #328 --- galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c index bd715110f..675e419d4 100644 --- a/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c +++ b/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c @@ -950,6 +950,7 @@ void actionAngleStaeckel_actionsFreqsAngles(int ndata, free(detA); free(dI3dJR); free(dI3dJz); + free(dI3dLz); } void calcFreqsFromDerivsStaeckel(int ndata, double * Omegar, From ed9688a6714f90aa95392c5d81da1f634991e1e3 Mon Sep 17 00:00:00 2001 From: Ted Mackereth Date: Wed, 3 Jan 2018 21:46:04 +0000 Subject: [PATCH 57/62] added option for estimateDeltaStaeckel to return all calculated values (rather than median) --- doc/source/orbit.rst | 14 ++++++++++++++ galpy/actionAngle_src/actionAngleStaeckel.py | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/source/orbit.rst b/doc/source/orbit.rst index e292a5ece..5cfd00a2a 100644 --- a/doc/source/orbit.rst +++ b/doc/source/orbit.rst @@ -346,6 +346,20 @@ behavior .. image:: images/lp-orbit-integration-EzJz.png +Fast orbit characterization +----------------------- + +It is also possible to use galpy for the fast estimation of orbit parameters without +performing any orbit integration via the Staeckel approximation. These methods use +the estimated geometry of the (assumed) filled orbital tori to analytically calculate +the orbit parameters. After initialising an ``Orbit`` instance, this is done (as in the +previous section) specifying ``analytic=True`` and selecting ``type='staeckel'`` (default +in vX.X of galpy). + +>>> o.e(analytic=True, type='staeckel') + + + Accessing the raw orbit ----------------------- diff --git a/galpy/actionAngle_src/actionAngleStaeckel.py b/galpy/actionAngle_src/actionAngleStaeckel.py index f34aaefb5..ab651c813 100644 --- a/galpy/actionAngle_src/actionAngleStaeckel.py +++ b/galpy/actionAngle_src/actionAngleStaeckel.py @@ -1045,7 +1045,7 @@ def _vminFindStart(v,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, @potential_physical_input @physical_conversion('position',pop=True) -def estimateDeltaStaeckel(pot,R,z): +def estimateDeltaStaeckel(pot,R,z, no_median=False): """ NAME: estimateDeltaStaeckel @@ -1054,6 +1054,8 @@ def estimateDeltaStaeckel(pot,R,z): INPUT: pot - Potential instance or list thereof R,z- coordinates (if these are arrays, the median estimated delta is returned, i.e., if this is an orbit) + no_median - (False) if True, and input is array, return all calculated values of delta (useful for quickly + estimating delta for many phase space points) OUTPUT: delta HISTORY: @@ -1070,7 +1072,8 @@ def estimateDeltaStaeckel(pot,R,z): use_physical=False)))/evaluateRzderivs(pot,R[ii],z[ii],use_physical=False)) for ii in range(len(R))]) indx= (delta2 < 0.)*(delta2 > -10.**-10.) delta2[indx]= 0. - delta2= nu.median(delta2[True^nu.isnan(delta2)]) + if not no_median: + delta2= nu.median(delta2[True^nu.isnan(delta2)]) else: delta2= (z**2.-R**2. #eqn. (9) has a sign error +(3.*R*_evaluatezforces(pot,R,z) From 9b9217f94ead1c12170e3c34bf832eac8b1b2674 Mon Sep 17 00:00:00 2001 From: Ted Mackereth Date: Thu, 4 Jan 2018 11:41:11 +0000 Subject: [PATCH 58/62] Added test for estimateDeltaStaeckel no_median option --- tests/test_actionAngle.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 1d2d5b254..84865c2c2 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -78,7 +78,7 @@ def test_actionAngleIsochrone_EccZmaxRperiRap_againstOrbit(): assert numpy.fabs(rperi-o.rperi()) < 1e-10, 'Analytically calculated rperi does not agree with numerically calculated one for an IsochronePotential' assert numpy.fabs(rap-o.rap()) < 1e-10, 'Analytically calculated rap does not agree with numerically calculated one for an IsochronePotential' return None - + # Test that EccZmaxRperiRap for an IsochronePotential are correctly computed # by comparing to a numerical orbit integration for a Kepler potential def test_actionAngleIsochrone_EccZmaxRperiRap_againstOrbit_kepler(): @@ -97,6 +97,7 @@ def test_actionAngleIsochrone_EccZmaxRperiRap_againstOrbit_kepler(): assert numpy.fabs(rap-o.rap()) < 1e-10, 'Analytically calculated rap does not agree with numerically calculated one for an IsochronePotential' return None + #Test the actions of an actionAngleIsochrone def test_actionAngleIsochrone_conserved_actions(): from galpy.potential import IsochronePotential @@ -1257,6 +1258,24 @@ def test_actionAngleStaeckel_indivdelta_actions(): assert numpy.fabs(jz0[0]-jzi[0]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' assert numpy.fabs(jz1[1]-jzi[1]) < 1e-10, 'Vertical action computed with invidual delta does not agree with that computed using the fixed orbit-wide default' return None + +# Test that no_median option for estimateDeltaStaeckel returns the same results as when +# individual values are calculated separately +def test_estimateDeltaStaeckel_no_median(): + from galpy.potential import MWPotential2014 + from galpy.orbit import Orbit + from galpy.actionAngle import estimateDeltaStaeckel + # Briefly integrate orbit to get multiple points + o= Orbit([1.,0.1,1.1,0.001,0.25,1.]) + ts= numpy.linspace(0.,1.,101) + o.integrate(ts,MWPotential2014) + #generate no_median deltas + nomed = estimateDeltaStaeckel(MWPotential2014, o.R(ts[:10]), o.z(ts[:10]), no_median=True) + #and the individual ones + indiv = numpy.array([estimateDeltaStaeckel(MWPotential2014, o.R(ts[i]), o.z(ts[i])) for i in range(10)]) + #check that values agree + assert (numpy.fabs(nomed-indiv) < 1e10).all(), 'no_median option returns different values to individual Delta estimation' + return None def test_actionAngleStaeckel_indivdelta_actions_c(): from galpy.potential import MWPotential2014 From c1bb5bd9ab8c47845c9f34c50a93a102d4f53641 Mon Sep 17 00:00:00 2001 From: Ted Mackereth Date: Thu, 4 Jan 2018 17:07:14 +0000 Subject: [PATCH 59/62] added documentation for orbit parameter estimation under Fast Orbit Characterization, and updated the Dierickx et al. example --- doc/source/actionAngle.rst | 2 + .../examples/dierickx_eccentricities.py | 192 ++++++++++++++++++ doc/source/images/dierickx-analyticee.png | Bin 0 -> 64468 bytes .../images/dierickx-integratedeanalytice.png | Bin 0 -> 36261 bytes doc/source/images/dierickx-integratedee.png | Bin 0 -> 65136 bytes .../images/dierickx-integratedehist.png | Bin 0 -> 18984 bytes doc/source/orbit.rst | 116 ++++++++--- 7 files changed, 287 insertions(+), 23 deletions(-) create mode 100644 doc/source/examples/dierickx_eccentricities.py create mode 100644 doc/source/images/dierickx-analyticee.png create mode 100644 doc/source/images/dierickx-integratedeanalytice.png create mode 100644 doc/source/images/dierickx-integratedee.png create mode 100644 doc/source/images/dierickx-integratedehist.png diff --git a/doc/source/actionAngle.rst b/doc/source/actionAngle.rst index bf6c5d4be..365ecee74 100644 --- a/doc/source/actionAngle.rst +++ b/doc/source/actionAngle.rst @@ -1,3 +1,5 @@ +.. _actionangle: + Action-angle coordinates ========================= diff --git a/doc/source/examples/dierickx_eccentricities.py b/doc/source/examples/dierickx_eccentricities.py new file mode 100644 index 000000000..b35834680 --- /dev/null +++ b/doc/source/examples/dierickx_eccentricities.py @@ -0,0 +1,192 @@ +import os, os.path +import pexpect +import subprocess +from astropy.io import fits, ascii +from astropy import units +import numpy as np +from optparse import OptionParser +import sys +import tempfile +from ftplib import FTP +import shutil +import pickle +from tqdm import tqdm +from galpy.potential import LogarithmicHaloPotential +from galpy.potential import evaluatePotentials as evalPot +from galpy.orbit import Orbit +from galpy.actionAngle import estimateDeltaStaeckel, actionAngleStaeckel, UnboundError +from galpy.util import bovy_coords +import matplotlib.pyplot as plt +import numpy + +_ERASESTR= " " + +def calc_eccentricity(args, options): + table = os.path.join(args[0],'table2.dat') + readme = os.path.join(args[0],'ReadMe') + dierickx = ascii.read(table, readme=readme) + vxvv = np.dstack([dierickx['RAdeg'], dierickx['DEdeg'], dierickx['Dist']/1e3, dierickx['pmRA'], dierickx['pmDE'], dierickx['HRV']])[0] + ro, vo, zo = 8., 220., 0.025 + ra, dec= vxvv[:,0], vxvv[:,1] + lb= bovy_coords.radec_to_lb(ra,dec,degree=True) + pmra, pmdec= vxvv[:,3], vxvv[:,4] + pmllpmbb= bovy_coords.pmrapmdec_to_pmllpmbb(pmra,pmdec,ra,dec,degree=True) + d, vlos= vxvv[:,2], vxvv[:,5] + rectgal= bovy_coords.sphergal_to_rectgal(lb[:,0],lb[:,1],d,vlos,pmllpmbb[:,0], pmllpmbb[:,1],degree=True) + vsolar= np.array([-10.1,4.0,6.7]) + vsun= np.array([0.,1.,0.,])+vsolar/vo + X = rectgal[:,0]/ro + Y = rectgal[:,1]/ro + Z = rectgal[:,2]/ro + vx = rectgal[:,3]/vo + vy = rectgal[:,4]/vo + vz = rectgal[:,5]/vo + vsun= np.array([0.,1.,0.,])+vsolar/vo + Rphiz= bovy_coords.XYZ_to_galcencyl(X,Y,Z,Zsun=zo/ro) + vRvTvz= bovy_coords.vxvyvz_to_galcencyl(vx,vy,vz,Rphiz[:,0],Rphiz[:,1],Rphiz[:,2],vsun=vsun,Xsun=1.,Zsun=zo/ro,galcen=True) + #do the integration and individual analytic estimate for each object + ts= np.linspace(0.,20.,10000) + lp= LogarithmicHaloPotential(normalize=1.) + e_ana = numpy.zeros(len(vxvv)) + e_int = numpy.zeros(len(vxvv)) + print('Performing orbit integration and analytic parameter estimates for Dierickx et al. sample...') + for i in tqdm(range(len(vxvv))): + try: + orbit = Orbit(vxvv[i], radec=True, vo=220., ro=8.) + e_ana[i] = orbit.e(analytic=True, pot=lp, c=True) + except UnboundError: + e_ana[i] = np.nan + orbit.integrate(ts, lp) + e_int[i] = orbit.e(analytic=False) + fig = plt.figure() + fig.set_size_inches(1.5*columnwidth, 1.5*columnwidth) + plt.scatter(e_int, e_ana, s=1, color='Black', lw=0.) + plt.xlabel(r'$\mathrm{galpy\ integrated}\ e$') + plt.ylabel(r'$\mathrm{galpy\ analytic}\ e$') + plt.xlim(0.,1.) + plt.ylim(0.,1.) + fig.tight_layout() + plt.savefig(os.path.join(args[0],'dierickx-integratedeanalytice.png'), format='png', dpi=200) + fig = plt.figure() + fig.set_size_inches(1.5*columnwidth, 1.5*columnwidth) + plt.hist(e_int, bins=30) + plt.xlim(0.,1.) + plt.xlabel(r'$\mathrm{galpy}\ e$') + fig.tight_layout() + plt.savefig(os.path.join(args[0], 'dierickx-integratedehist.png'), format='png', dpi=200) + fig = plt.figure() + fig.set_size_inches(1.5*columnwidth, 1.5*columnwidth) + plt.scatter(dierickx['e'], e_int, s=1, color='Black', lw=0.) + plt.xlabel(r'$\mathrm{Dierickx\ et\ al.}\ e$') + plt.ylabel(r'$\mathrm{galpy\ integrated}\ e$') + plt.xlim(0.,1.) + plt.ylim(0.,1.) + fig.tight_layout() + plt.savefig(os.path.join(args[0],'dierickx-integratedee.png'), format='png', dpi=200) + fig = plt.figure() + fig.set_size_inches(1.5*columnwidth, 1.5*columnwidth) + plt.scatter(dierickx['e'], e_ana, s=1, color='Black', lw=0.) + plt.xlabel(r'$\mathrm{Dierickx\ et\ al.}\ e$') + plt.ylabel(r'$\mathrm{galpy\ estimated}\ e$') + plt.xlim(0.,1.) + plt.ylim(0.,1.) + fig.tight_layout() + plt.savefig(os.path.join(args[0],'dierickx-analyticee.png'), format='png', dpi=200) + arr = numpy.recarray(len(e_ana), dtype=[('analytic_e', float), ('integrated_e', float)]) + arr['analytic_e'] = e_ana + arr['integrated_e'] = e_int + with open(os.path.join(args[0],'eccentricities.dat'), 'w') as file: + pickle.dump(arr, file) + file.close() + +def get_table(args,options): + cat = 'J/ApJ/725/L186/' + tab2name = 'table2.dat.gz' + tab2readme = 'ReadMe' + out = args[0] + ensure_dir(os.path.join(out,tab2name)) + vizier(cat, os.path.join(out,tab2name), os.path.join(out,tab2readme), catalogname=tab2name, readmename=tab2readme) + subprocess.call(['gunzip', os.path.join(out,tab2name)]) + +def vizier(cat,filePath,ReadMePath, + catalogname='catalog.dat',readmename='ReadMe'): + """ + NAME: + vizier + PURPOSE: + download a catalog and its associated ReadMe from Vizier + INPUT: + cat - name of the catalog (e.g., 'III/272' for RAVE, or J/A+A/... for journal-specific catalogs) + filePath - path of the file where you want to store the catalog (note: you need to keep the name of the file the same as the catalogname to be able to read the file with astropy.io.ascii) + ReadMePath - path of the file where you want to store the ReadMe file + catalogname= (catalog.dat) name of the catalog on the Vizier server + readmename= (ReadMe) name of the ReadMe file on the Vizier server + OUTPUT: + (nothing, just downloads) + HISTORY: + 2016-09-12 - Written - Bovy (UofT) + """ + _download_file_vizier(cat,filePath,catalogname=catalogname) + _download_file_vizier(cat,ReadMePath,catalogname=readmename) + return None + + +def _download_file_vizier(cat,filePath,catalogname='catalog.dat'): + ''' + Stolen from Jo Bovy's gaia_tools package! + ''' + sys.stdout.write('\r'+"Downloading file %s ...\r" \ + % (os.path.basename(filePath))) + sys.stdout.flush() + try: + # make all intermediate directories + os.makedirs(os.path.dirname(filePath)) + except OSError: pass + # Safe way of downloading + downloading= True + interrupted= False + file, tmp_savefilename= tempfile.mkstemp() + os.close(file) #Easier this way + ntries= 1 + while downloading: + try: + ftp= FTP('cdsarc.u-strasbg.fr') + ftp.login('anonymous', 'test') + ftp.cwd(os.path.join('pub','cats',cat)) + with open(tmp_savefilename,'wb') as savefile: + ftp.retrbinary('RETR %s' % catalogname,savefile.write) + shutil.move(tmp_savefilename,filePath) + downloading= False + if interrupted: + raise KeyboardInterrupt + except: + raise + if not downloading: #Assume KeyboardInterrupt + raise + elif ntries > _MAX_NTRIES: + raise IOError('File %s does not appear to exist on the server ...' % (os.path.basename(filePath))) + finally: + if os.path.exists(tmp_savefilename): + os.remove(tmp_savefilename) + ntries+= 1 + sys.stdout.write('\r'+_ERASESTR+'\r') + sys.stdout.flush() + return None + +def ensure_dir(f): + """ Ensure a a file exists and if not make the relevant path """ + d = os.path.dirname(f) + if not os.path.exists(d): + os.makedirs(d) + +def get_options(): + #no options yet - probably none needed? + usage = "usage: %prog [options] " + parser = OptionParser(usage=usage) + return parser + +if __name__ == '__main__': + parser = get_options() + options, args= parser.parse_args() + get_table(args,options) + calc_eccentricity(args, options) \ No newline at end of file diff --git a/doc/source/images/dierickx-analyticee.png b/doc/source/images/dierickx-analyticee.png new file mode 100644 index 0000000000000000000000000000000000000000..72417c05de0a7b54d09438807091a8be697877c5 GIT binary patch literal 64468 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 V;t1D1?Q!EGZSv&I*1KA- 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 literal 0 HcmV?d00001 diff --git a/doc/source/images/dierickx-integratedehist.png b/doc/source/images/dierickx-integratedehist.png new file mode 100644 index 0000000000000000000000000000000000000000..c2987a5255a7f3c19fefb8b49ae0e73c4741e5c0 GIT binary patch literal 18984 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 +for the appoximation of actions in axisymmetric potentials), without performing any orbit integration. +The method uses the geometry of the orbit tori to estimate the orbit parameters. After initialising +an ``Orbit`` instance, this is done (as in the previous section) specifying ``analytic=True`` and +selecting ``type='staeckel'`` (default in vX.X of galpy). >>> o.e(analytic=True, type='staeckel') +if running the above without integrating the orbit, the potential should also be specified +in the usual way + +>>> o.e(analytic=True, type='staeckel', pot=mp) + +again, where ``mp`` is the Miyamoto-Nagai potential of :ref:`Introduction: +Rotation curves `. This interface automatically computes the necessary Delta +parameter based on the initial condition of the ``Orbit`` object. + +While this is useful and fast for individual ``Orbit`` objects, it is likely that users will +want to rapidly evaluate the orbit parameters of large numbers of objects. It is possible +to perform the orbital parameter estimation above through the :ref:`actionAngle ` +interface. To do this, we need arrays of the phase-space points ``R``, ``vR``, ``vT``, ``z``, ``vz``, and +``phi`` for the objects. We can then optionally estimate the individual Delta parameter +for these phase-space points using +>>> from galpy.actionAngle import estimateDeltaStaeckel +>>> deltas = estimateDeltaStaeckel(mp, R, z, no_median=True) + +where ``no_median=True`` specifies that the function return the delta parameter at each given point +rather than the median of the calculated deltas. The orbit parameters are then calculated by first +specifying an ``actionAngle`` instance (with an arbitrary delta parameter), then using the +``EccZmaxRperiRap`` method with the data points and the estimated delta array: + +>>> aAS = actionAngleStaeckel(pot=mp, delta=0.4) +>>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi, delta=deltas) + +Here, delta can alternatively be either a single scalar value for all points, negating the need for +the estimation at each point. This method is also applicable in the ``actionAngleIsochrone``, +``actionAngleSpherical``, and ``actionAngleAdiabatic`` modules. This method returns parameters at +speeds as fast as 3 microseconds per object. Accessing the raw orbit @@ -521,32 +553,70 @@ a set of thick disk stars. We start by downloading the sample of SDSS SEGUE (`2009AJ....137.4377Y `_) thick disk stars compiled by Dierickx et al. (`2010arXiv1009.1616D -`_) at - -http://www.mpia-hd.mpg.de/homes/rix/Data/Dierickx-etal-tab2.txt +`_) from CDS at `this +link `_. +Downloading the table and the ReadMe will allow you to read in the data using ``astropy.io.ascii`` +like so + +>>> from astropy.io import ascii +>>> dierickx = ascii.read('table2.dat', readme='ReadMe') +>>> vxvv = numpy.dstack([dierickx['RAdeg'], dierickx['DEdeg'], dierickx['Dist']/1e3, dierickx['pmRA'], dierickx['pmDE'], dierickx['HRV']])[0] After reading in the data (RA,Dec,distance,pmRA,pmDec,vlos; see above) as a vector ``vxvv`` with dimensions [6,ndata] we (a) define the potential in which we want to integrate the orbits, and (b) integrate -each orbit and save its eccentricity (running this for all 30,000-ish +each orbit and save its eccentricity as calculated analytically following the :ref:`Staeckel +approximation method ` and by orbit integration (running this for all 30,000-ish stars will take about half an hour) +>>> from galpy.actionAngle import UnboundError +>>> ts= np.linspace(0.,20.,10000) >>> lp= LogarithmicHaloPotential(normalize=1.) ->>> ts= nu.linspace(0.,20.,10000) ->>> mye= nu.zeros(ndata) ->>> for ii in range(len(e)): -... o= Orbit(vxvv[ii,:],radec=True,vo=220.,ro=8.) #Initialize -... o.integrate(ts,lp) #Integrate -... mye[ii]= o.e() #Calculate eccentricity +>>> e_ana = numpy.zeros(len(vxvv)) +>>> e_int = numpy.zeros(len(vxvv)) +>>> for i in range(len(vxvv)): +... #calculate analytic e estimate, catch any 'unbound' orbits +... try: +... orbit = Orbit(vxvv[i], radec=True, vo=220., ro=8.) +... e_ana[i] = orbit.e(analytic=True, pot=lp, c=True) +... except UnboundError: +... #parameters cannot be estimated analytically +... e_ana[i] = np.nan +... #integrate the orbit and return the numerical e value +... orbit.integrate(ts, lp) +... e_int[i] = orbit.e(analytic=False) + +We then find the following eccentricity distribution (from the numerical eccentricities) + +.. image:: images/dierickx-integratedehist.png + +The eccentricity calculated by integration in galpy compare well with those +calculated by Dierickx et al., except for a few objects -We then find the following eccentricity distribution +.. image:: images/dierickx-integratedee.png -.. image:: images/dierickx-myehist.png +and the analytical estimates are equally as good: -The eccentricity calculated by galpy compare well with those -calculated by Dierickx et al., except for a few objects +.. image:: images/dierickx-analyticee.png + +In comparing the analytic and integrated eccentricity estimates - one can see that in this case +the estimation is almost exact, due to the spherical symmetry of the chosen potential: + +.. image:: images/dierickx-integratedeanalytice.png + +A script that calculates and plots everything can be downloaded +:download:`here `. To generate the plots just run:: + + python dierickx_eccentricities.py ../path/to/folder + +specifiying the location you want to put the plots and data. + +Alternatively - one can transform the observed coordinates into spherical coordinates and perform +the estimations in one batch using the ``actionAngle`` interface, which takes considerably less time: -.. image:: images/dierickx-myee.png +>>> from galpy import actionAngle +>>> deltas = actionAngle.estimateDeltaStaeckel(lp, Rphiz[:,0], Rphiz[:,2], no_median=True) +>>> aAS = actionAngleStaeckel(pot=lp, delta=0.) +>>> par = aAS.EccZmaxRperiRap(Rphiz[:,0], vRvTvz[:,0], vRvTvz[:,1], Rphiz[:,2], vRvTvz[:,2], Rphiz[:,1], delta=deltas) -The script that calculates and plots everything can be downloaded -:download:`here `. +The above code calculates the parameters in roughly 100ms on a single core. From d8c66697c1ca18af176aa7f722fb974f97d1089d Mon Sep 17 00:00:00 2001 From: Ted Mackereth Date: Fri, 5 Jan 2018 19:02:58 +0000 Subject: [PATCH 60/62] Updates to fast orbit characterization docs --- doc/source/images/lp-orbit-integration-et.png | Bin 0 -> 177999 bytes doc/source/orbit.rst | 71 +++++++++++++----- 2 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 doc/source/images/lp-orbit-integration-et.png diff --git a/doc/source/images/lp-orbit-integration-et.png b/doc/source/images/lp-orbit-integration-et.png new file mode 100644 index 0000000000000000000000000000000000000000..54cb59d7cfaee069708de8150ff7d396411e41e6 GIT binary patch literal 177999 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
F}LN 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
J9*)#~Xd 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 +It is also possible to use galpy for the fast estimation of orbit parameters as demonstrated +in Mackereth & Bovy (2018, in prep.) via the Staeckel approximation (originally used by `Binney (2012) `_ for the appoximation of actions in axisymmetric potentials), without performing any orbit integration. The method uses the geometry of the orbit tori to estimate the orbit parameters. After initialising -an ``Orbit`` instance, this is done (as in the previous section) specifying ``analytic=True`` and -selecting ``type='staeckel'`` (default in vX.X of galpy). +an ``Orbit`` instance, the method is applied by specifying ``analytic=True`` and +selecting ``type='staeckel'``. >>> o.e(analytic=True, type='staeckel') @@ -365,32 +365,59 @@ in the usual way >>> o.e(analytic=True, type='staeckel', pot=mp) -again, where ``mp`` is the Miyamoto-Nagai potential of :ref:`Introduction: -Rotation curves `. This interface automatically computes the necessary Delta -parameter based on the initial condition of the ``Orbit`` object. +This interface automatically estimates the necessary delta parameter based on the initial +condition of the ``Orbit`` object. While this is useful and fast for individual ``Orbit`` objects, it is likely that users will want to rapidly evaluate the orbit parameters of large numbers of objects. It is possible to perform the orbital parameter estimation above through the :ref:`actionAngle ` interface. To do this, we need arrays of the phase-space points ``R``, ``vR``, ``vT``, ``z``, ``vz``, and -``phi`` for the objects. We can then optionally estimate the individual Delta parameter -for these phase-space points using +``phi`` for the objects. The orbit parameters are then calculated by first +specifying an ``actionAngle`` instance (with an arbitrary delta parameter), then using the +``EccZmaxRperiRap`` method with the data points and the estimated delta array: + +>>> aAS = actionAngleStaeckel(pot=mp, delta=0.4) +>>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi, delta=delta) + +Here, one can either specify a single scalar value for ``delta`` (applied to all the objects), or alternatively +give an array of estimates for ``delta`` at each phase space point using >>> from galpy.actionAngle import estimateDeltaStaeckel ->>> deltas = estimateDeltaStaeckel(mp, R, z, no_median=True) +>>> delta = estimateDeltaStaeckel(mp, R, z, no_median=True) where ``no_median=True`` specifies that the function return the delta parameter at each given point -rather than the median of the calculated deltas. The orbit parameters are then calculated by first -specifying an ``actionAngle`` instance (with an arbitrary delta parameter), then using the -``EccZmaxRperiRap`` method with the data points and the estimated delta array: +rather than the median of the calculated deltas. This method is also applicable in the ``actionAngleIsochrone``, +``actionAngleSpherical``, and ``actionAngleAdiabatic`` modules. + +We can test the speed of this method in iPython by finding the parameters at 100000 steps +along an orbit in MWPotential2014, like this + +>>> o= Orbit(vxvv=[1.,0.1,1.1,0.,0.1,0.]) +>>> ts = numpy.linspace(0,100,100000) +>>> o.integrate(ts,MWPotential2014) +>>> aAS = actionAngleStaeckel(pot=MWPotential2014,delta=0.3) +>>> R, vR, vT, z, vz, phi = o.getOrbit().T +>>> delta = estimateDeltaStaeckel(MWPotential2014, R, z, no_median=True) +>>> %timeit -n 10 es, zms, rps, ras = aAS.EccZmaxRperiRap(R,vR,vT,z,vz,phi,delta=delta) +#10 loops, best of 3: 899 ms per loop + +you can see that in this potential, each phase space point is calculated in roughly 9µs. +further speed-ups can be gained by using the ``actionAngleStaeckelGrid`` module, which first +calculates the parameters using a grid-based interpolation + +>>> from galpy.actionAngle import actionAngleStaeckelGrid +>>> aASG= actionAngleStaeckelGrid(pot=mp,delta=0.4,nE=51,npsi=51,nLz=61,c=True,interpecc=True) +>>> %timeit -n 10 es, zms, rps, ras = aASG.EccZmaxRperiRap(R,vR,vT,z,vz,phi) +#10 loops, best of 3: 587 ms per loop + +where ``interpecc=True`` is required to perform the interpolation of the orbit parameter grid. +Looking at how the eccentricity estimation varies along the orbit, and comparing to the calculation +using the orbit integration, we see that the estimation good job + +.. image:: images/lp-orbit-integration-et.png + :scale: 40 % ->>> aAS = actionAngleStaeckel(pot=mp, delta=0.4) ->>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi, delta=deltas) -Here, delta can alternatively be either a single scalar value for all points, negating the need for -the estimation at each point. This method is also applicable in the ``actionAngleIsochrone``, -``actionAngleSpherical``, and ``actionAngleAdiabatic`` modules. This method returns parameters at -speeds as fast as 3 microseconds per object. Accessing the raw orbit @@ -589,20 +616,24 @@ stars will take about half an hour) We then find the following eccentricity distribution (from the numerical eccentricities) .. image:: images/dierickx-integratedehist.png + :scale: 40 % The eccentricity calculated by integration in galpy compare well with those calculated by Dierickx et al., except for a few objects .. image:: images/dierickx-integratedee.png + :scale: 40 % and the analytical estimates are equally as good: .. image:: images/dierickx-analyticee.png + :scale: 40 % In comparing the analytic and integrated eccentricity estimates - one can see that in this case the estimation is almost exact, due to the spherical symmetry of the chosen potential: .. image:: images/dierickx-integratedeanalytice.png + :scale: 40 % A script that calculates and plots everything can be downloaded :download:`here `. To generate the plots just run:: From 9b541b9f33d006550954cc8e562d383a740502a8 Mon Sep 17 00:00:00 2001 From: Ted Mackereth Date: Fri, 5 Jan 2018 19:04:07 +0000 Subject: [PATCH 61/62] modifications to tests for estimateDeltaStaeckel and removed import statement from dierickx_eccentricities --- doc/source/examples/dierickx_eccentricities.py | 1 - tests/test_actionAngle.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/examples/dierickx_eccentricities.py b/doc/source/examples/dierickx_eccentricities.py index b35834680..4e3cf62df 100644 --- a/doc/source/examples/dierickx_eccentricities.py +++ b/doc/source/examples/dierickx_eccentricities.py @@ -1,5 +1,4 @@ import os, os.path -import pexpect import subprocess from astropy.io import fits, ascii from astropy import units diff --git a/tests/test_actionAngle.py b/tests/test_actionAngle.py index 84865c2c2..dbbfc7e42 100644 --- a/tests/test_actionAngle.py +++ b/tests/test_actionAngle.py @@ -1274,7 +1274,7 @@ def test_estimateDeltaStaeckel_no_median(): #and the individual ones indiv = numpy.array([estimateDeltaStaeckel(MWPotential2014, o.R(ts[i]), o.z(ts[i])) for i in range(10)]) #check that values agree - assert (numpy.fabs(nomed-indiv) < 1e10).all(), 'no_median option returns different values to individual Delta estimation' + assert (numpy.fabs(nomed-indiv) < 1e-10).all(), 'no_median option returns different values to individual Delta estimation' return None def test_actionAngleStaeckel_indivdelta_actions_c(): From 8ea05702b93be3c7394892c3035509fed98c28ca Mon Sep 17 00:00:00 2001 From: Jo Bovy Date: Fri, 5 Jan 2018 16:47:17 -0500 Subject: [PATCH 62/62] Slight tweaks to the documentation [ci skip] --- doc/source/actionAngle.rst | 2 ++ doc/source/orbit.rst | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/doc/source/actionAngle.rst b/doc/source/actionAngle.rst index 365ecee74..a3e8a1d1f 100644 --- a/doc/source/actionAngle.rst +++ b/doc/source/actionAngle.rst @@ -381,6 +381,8 @@ vertical action to approximately five percent. .. WARNING:: Frequencies and angles using the adiabatic approximation are not implemented at this time. +.. _actionanglestaeckel: + Action-angle coordinates using the Staeckel approximation ----------------------------------------------------------- diff --git a/doc/source/orbit.rst b/doc/source/orbit.rst index 35e9e151c..6768f41fa 100644 --- a/doc/source/orbit.rst +++ b/doc/source/orbit.rst @@ -348,8 +348,8 @@ behavior .. _fastchar: -Fast orbit characterization ----------------------------- +**NEW in v1.3** Fast orbit characterization +-------------------------------------------- It is also possible to use galpy for the fast estimation of orbit parameters as demonstrated in Mackereth & Bovy (2018, in prep.) via the Staeckel approximation (originally used by `Binney (2012) `_ @@ -373,20 +373,23 @@ want to rapidly evaluate the orbit parameters of large numbers of objects. It is to perform the orbital parameter estimation above through the :ref:`actionAngle ` interface. To do this, we need arrays of the phase-space points ``R``, ``vR``, ``vT``, ``z``, ``vz``, and ``phi`` for the objects. The orbit parameters are then calculated by first -specifying an ``actionAngle`` instance (with an arbitrary delta parameter), then using the -``EccZmaxRperiRap`` method with the data points and the estimated delta array: +specifying an ``actionAngleStaeckel`` instance (this requires a single ``delta`` focal-length parameter, see :ref:`the documentation of the actionAngleStaeckel class `), then using the +``EccZmaxRperiRap`` method with the data points: >>> aAS = actionAngleStaeckel(pot=mp, delta=0.4) ->>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi, delta=delta) +>>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi) -Here, one can either specify a single scalar value for ``delta`` (applied to all the objects), or alternatively -give an array of estimates for ``delta`` at each phase space point using +Alternatively, you can specify an array for ``delta`` when calling ``aAS.EccZmaxRperiRap``, for example by first estimating good ``delta`` parameters as follows: >>> from galpy.actionAngle import estimateDeltaStaeckel >>> delta = estimateDeltaStaeckel(mp, R, z, no_median=True) where ``no_median=True`` specifies that the function return the delta parameter at each given point -rather than the median of the calculated deltas. This method is also applicable in the ``actionAngleIsochrone``, +rather than the median of the calculated deltas (which is the default option). Then one can compute the eccetrncity etc. using individual delta values as: + +>>> e, Zmax, rperi, rap = aAS.EccZmaxRperiRap(R, vR, vT, z, vz, phi, delta=delta) + +Th ``EccZmaxRperiRap`` method also exists for the ``actionAngleIsochrone``, ``actionAngleSpherical``, and ``actionAngleAdiabatic`` modules. We can test the speed of this method in iPython by finding the parameters at 100000 steps @@ -417,9 +420,6 @@ using the orbit integration, we see that the estimation good job .. image:: images/lp-orbit-integration-et.png :scale: 40 % - - - Accessing the raw orbit -----------------------

lNiny-1T+GKebV&g`>`;f`GL`#%RQ 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“S*EcU|SGweCj>P3-1$(c^73~`!uc?tn5bL&;j!5o$4~7 z48^YcUO8i;rwDR)cf;te%;Ch3t9q@=AWOuNi40f*@(B$0EJP2sCZc7#%|j2^!*~_u zviiG1>7g_6;?&`uk+xR9WoK#L5!#T^>=-H)|MM zf+$eMf`t|z&RRx0%kj%vp;jT;PHYH(XJ#rx~~GLhoT?%zPfc{c4mAI zYm%of;&XU~k`BeMIj?6S(2bTPs0+>Gr$4bA>giVU1c%bAZdfE+X&>(O?HvESwdW3N zEDYMt|A=flBj4B9pRBxPZ~=?{4%@W1j;MU9sddC#qO*9NiYb^rE*RM|EKhwRkep~! zX(7z0d@mZ?U0WIHn>xc(fbA3FcN%fu%+&aS{%cJYKXSXlYnWaQ-0b(3^y^MYeu=P1 zcWytIIXqRgei)GD$DB@2%r|b0J?#9CFUo49Tm+R2fAD2`q6lgCli2nhEV)nudIp$$ zqi5?M`wL}bLsa{O?izgRf`p`lj(8g8_Ky2vxI<6;cqn4X zGHAU4gfh1!lpoJ~J$1#`dUV_DAecTXjZ3rcRyLH5Ja$LXX`jnIXOdsq)(hN^QGKp)039=VqV+$ zwTf`vBO}Lh3~PWunBA~a#}c_`dVfidEdeIWHyE75^`$tLrw}qYZyP;`)i;|PEh7B2 z#Oi^J6#Par#Z08L)F@y!z0M-DR8FXzzQ}of{UZrVF(FN&iY|ZYJ+^WT0IP3CsC