import numpy as np
import scipy.special as special
from lenstronomy.GalKin import velocity_util
from scipy.interpolate import interp1d
from lenstronomy.Util.package_util import exporter
export, __all__ = exporter()
[docs]@export
class Anisotropy(object):
"""
class that handles the kinematic anisotropy
sources: Mamon & Lokas 2005
https://arxiv.org/pdf/astro-ph/0405491.pdf
Agnello et al. 2014
https://arxiv.org/pdf/1401.4462.pdf
"""
def __init__(self, anisotropy_type):
"""
:param anisotropy_type: string, anisotropy model type
"""
self._type = anisotropy_type
if self._type == 'const':
self._model = Const()
elif self._type == 'radial':
self._model = Radial()
elif self._type == 'isotropic':
self._model = Isotropic()
elif self._type == 'OM':
self._model = OsipkovMerritt()
elif self._type == 'GOM':
self._model = GeneralizedOM()
elif self._type == 'Colin':
self._model = Colin()
else:
raise ValueError('anisotropy type %s not supported!' % self._type)
[docs] def beta_r(self, r, **kwargs):
"""
returns the anisotropy parameter at a given radius
:param r: 3d radius
:param kwargs: parameters of the specified anisotropy model
:return: beta(r)
"""
return self._model.beta_r(r, **kwargs)
[docs] def K(self, r, R, **kwargs):
"""
equation A16 im Mamon & Lokas for Osipkov&Merrit anisotropy
:param r: 3d radius
:param R: projected 2d radius
:param kwargs: parameters of the specified anisotropy model
:return: K(r, R)
"""
return self._model.K(r, R, **kwargs)
[docs] def anisotropy_solution(self, r, **kwargs):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
:param r: 3d radius
:param kwargs: parameters of the specified anisotropy model
:return: f(r)
"""
return self._model.anisotropy_solution(r, **kwargs)
[docs] def delete_anisotropy_cache(self):
"""
deletes cached interpolations for a fixed anisotropy model
:return:
"""
if hasattr(self._model, 'delete_cache'):
self._model.delete_cache()
[docs]@export
class Const(object):
"""
constant anisotropy model class
See Mamon & Lokas 2005 for details
"""
def __init__(self):
pass
[docs] @staticmethod
def K(r, R, beta):
"""
equation A16 im Mamon & Lokas for constant anisotropy
:param r: 3d radius
:param R: projected 2d radius
:param beta: anisotropy
:return: K(r, R, beta)
"""
u = r / R
k = np.sqrt(1 - 1. / u ** 2) / (1. - 2 * beta) + np.sqrt(np.pi) / 2 * special.gamma(
beta - 1. / 2) / special.gamma(beta) \
* (3. / 2 - beta) * u ** (2 * beta - 1.) * (1 - special.betainc(beta + 1. / 2, 1. / 2, 1. / u ** 2))
return k
[docs] @staticmethod
def beta_r(r, beta):
"""
anisotropy as a function of radius
:param r: 3d radius
:param beta: anisotropy
:return: beta
"""
return beta
[docs] def anisotropy_solution(self, r, **kwargs):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
:param r: 3d radius
:param kwargs: parameters of the specified anisotropy model
:return: f(r)
"""
raise ValueError('routine not supported yet for constant anisotropy model!')
[docs]@export
class Isotropic(object):
"""
class for isotropic (beta=0) stellar orbits
See Mamon & Lokas 2005 for details
"""
def __init__(self):
pass
[docs] @staticmethod
def K(r, R):
"""
equation A16 im Mamon & Lokas for constant anisotropy
:param r: 3d radius
:param R: projected 2d radius
:return: K(r, R)
"""
u = r / R
k = np.sqrt(1 - 1. / u ** 2)
return k
[docs] @staticmethod
def beta_r(r):
"""
anisotropy as a function of radius
:param r: 3d radius
:return: beta
"""
return 0.
[docs] def anisotropy_solution(self, r, **kwargs):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
See e.g. A3 in Mamon & Lokas
:param r: 3d radius
:param kwargs: parameters of the specified anisotropy model
:return: f(r)
"""
return 1
[docs]@export
class Radial(object):
"""
class for radial (beta=1) stellar orbits
See Mamon & Lokas 2005 for details
"""
def __init__(self):
pass
[docs] def K(self, r, R):
"""
equation A16 im Mamon & Lokas for constant anisotropy
:param r: 3d radius
:param R: projected 2d radius
:return: K(r, R)
"""
u = r / R
k = np.pi / 4 * u - 1. / 2 * np.sqrt(1 - 1. / u ** 2) - u / 2. * np.arcsin(1. / u)
return k
[docs] @staticmethod
def beta_r(r):
"""
anisotropy as a function of radius
:param r: 3d radius
:return: beta
"""
return 1.
[docs] def anisotropy_solution(self, r):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
See e.g. A4 in Mamon & Lokas
:param r: 3d radius
:return: f(r)
"""
return r**2
[docs]@export
class OsipkovMerritt(object):
"""
class for Osipkov&Merrit stellar orbits
See Mamon & Lokas 2005 for details
"""
def __init__(self):
pass
[docs] def K(self, r, R, r_ani):
"""
equation A16 im Mamon & Lokas 2005 for Osipkov&Merrit anisotropy
:param r: 3d radius
:param R: projected 2d radius
:param r_ani: anisotropy radius
:return: K(r, R)
"""
u = r / R
ua = r_ani / R
k = (ua ** 2 + 1. / 2) / (ua ** 2 + 1) ** (3. / 2) * (u ** 2 + ua ** 2) / u * np.arctan(
np.sqrt((u ** 2 - 1) / (ua ** 2 + 1))) - 1. / 2 / (ua ** 2 + 1) * np.sqrt(1 - 1. / u ** 2)
return k
[docs] @staticmethod
def beta_r(r, r_ani):
"""
anisotropy as a function of radius
:param r: 3d radius
:param r_ani: anisotropy radius
:return: beta
"""
return r**2/(r_ani**2 + r**2)
[docs] @staticmethod
def anisotropy_solution(r, r_ani):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
See e.g. A5 in Mamon & Lokas
:param r: 3d radius
:param r_ani: anisotropy radius
:return: f(r)
"""
return r**2 + r_ani**2
[docs]@export
class GeneralizedOM(object):
"""
generalized Osipkov&Merrit profile
see Agnello et al. 2014 https://arxiv.org/pdf/1401.4462.pdf
b(r) = beta_inf * r^2 / (r^2 + r_ani^2)
"""
def __init__(self):
self._z_interp = np.append(-np.flip(np.logspace(-1, 3, 200)**2), 0)
#self._z_interp = -np.linspace(-200, 0, 200)**2 # z = (R**2 - r**2) / (r_ani**2 + R**2)
[docs] @staticmethod
def beta_r(r, r_ani, beta_inf):
"""
anisotropy as a function of radius
:param r: 3d radius
:param r_ani: anisotropy radius
:param beta_inf: anisotropy at infinity
:return: beta
"""
return beta_inf * r**2/(r_ani**2 + r**2)
[docs] def K(self, r, R, r_ani, beta_inf):
"""
equation19 in Agnello et al. 2014 for k_beta(R, r) such that
K(R, r) = (sqrt(r^2 - R^2) + k_beta(R, r)) / r
:param r: 3d radius
:param R: projected 2d radius
:param r_ani: anisotropy radius
:param beta_inf: anisotropy at infinity
:return: K(r, R)
"""
return (np.sqrt(r**2 - R**2) + self._k_beta(r, R, r_ani, beta_inf)) / r
[docs] def anisotropy_solution(self, r, r_ani, beta_inf):
"""
the solution to
d ln(f)/ d ln(r) = 2 beta(r)
See e.g. A5 in Mamon & Lokas with a scaling (nominator of Agnello et al. 2014 Equation (12)
:param r: 3d radius
:param r_ani: anisotropy radius
:param beta_inf: anisotropy at infinity
:return: f(r)
"""
return (r**2 + r_ani**2) ** beta_inf
[docs] def delete_cache(self):
"""
deletes the interpolation function of the hypergeometic function for a specific beta_inf
:return: deleted self variables
"""
if hasattr(self, '_f_12_interp'):
del self._f_12_interp
if hasattr(self, '_f_32_interp'):
del self._f_32_interp
def _k_beta(self, r, R, r_ani, beta_inf):
"""
equation19 in Agnello et al. 2014 for k_beta(R, r) such that
K(R, r) = (sqrt(r^2 - R^2) + k_beta(R, r)) / r
:param r: 3d radius
:param R: projected 2d radius
:param r_ani: anisotropy radius
:param beta_inf: anisotropy at infinity
:return: k_beta(r, R)
"""
z = (R**2 - r**2) / (r_ani**2 + R**2)
# ((r**2 + r_ani**2) / (R**2 + r_ani**2)) ** beta_inf
return - self.beta_r(R, r_ani, beta_inf) * self._j_beta(R, r, r_ani, beta_inf) *\
np.sqrt(r**2 - R**2) * (self._F_12(z, beta_inf) + 2. * (1 - r**2/R**2) / 3 * self._F_32(z, beta_inf))
def _F_12(self, z, beta_inf):
"""
:param z: (R**2 - r**2) / (r_ani**2 + R**2)
:param beta_inf: anisotropy at infinity
:return: _F(1/2, z, beta_inf)
"""
if not hasattr(self, '_f_12_interp'):
f_12_interp = self._F(1 / 2., self._z_interp, beta_inf)
self._f_12_interp = interp1d(self._z_interp, f_12_interp, kind='cubic', fill_value="extrapolate")
return self._f_12_interp(z)
def _F_32(self, z, beta_inf):
"""
:param z: (R**2 - r**2) / (r_ani**2 + R**2)
:param beta_inf: anisotropy at infinity
:return: _F(3/2, z, beta_inf)
"""
if not hasattr(self, '_f_32_interp'):
f_32_interp = self._F(3 / 2., self._z_interp, beta_inf)
self._f_32_interp = interp1d(self._z_interp, f_32_interp, kind='cubic', fill_value="extrapolate")
return self._f_32_interp(z)
def _j_beta(self, r, s, r_ani, beta_inf):
"""
equation (12) in Agnello et al. 2014
:param r:
:param s:
:param r_ani:
:param beta_inf
:return:
"""
return ((s**2 + r_ani**2) / (r**2 + r_ani**2)) ** beta_inf
def _F(self, a, z, beta_inf):
"""
the hypergeometric function 2F1 (a, 1 +beta_inf, a + 1, z)
:param a:
:param z:
:return:
"""
if isinstance(z, int) or isinstance(z, float):
return velocity_util.hyp_2F1(a=a, b=1+beta_inf, c=a+1, z=z)
else:
_F_array = []
for z_i in z:
_F_array.append(velocity_util.hyp_2F1(a=a, b=1+beta_inf, c=a+1, z=z_i))
return np.array(_F_array, dtype=float)
[docs]@export
class Colin(object):
"""
class for stellar orbits anisotropy parameter based on Colin et al. (2000)
See Mamon & Lokas 2005 for details
"""
def __init__(self):
pass
[docs] def K(self, r, R, r_ani):
"""
equation A16 im Mamon & Lokas for Osipkov&Merrit anisotropy
:param r: 3d radius
:param R: projected 2d radius
:param r_ani: anisotropy radius
:return: K(r, R)
"""
u = r / R
if np.min(u) < 1:
raise ValueError("3d radius is smaller than projected radius! Does not make sense.")
ua = r_ani / R
if ua == 1:
k = (1 + 1. / u) * np.arccosh(u) - 1. / 6 * (8. / u + 7) * np.sqrt((u - 1.) / (u + 1.))
elif ua > 1:
k = 0.5 / (ua ** 2 - 1) * np.sqrt(1 - 1. / u ** 2) + (1. + ua / u) * np.arccosh(u) - np.sign(ua - 1) * ua * \
(ua ** 2 - 0.5) / np.abs(ua ** 2 - 1) ** (3. / 2) * (1. + ua / u) * np.arccosh((ua * u + 1) / (u + ua))
else: # ua < 1
k = 0.5 / (ua ** 2 - 1) * np.sqrt(1 - 1. / u ** 2) + (1. + ua / u) * np.arccosh(u) - np.sign(ua - 1) * ua * \
(ua ** 2 - 0.5) / np.abs(ua ** 2 - 1) ** (3. / 2) * (1. + ua / u) * np.arccos((ua * u + 1) / (u + ua))
return k
[docs] @staticmethod
def beta_r(r, r_ani):
"""
anisotropy as a function of radius
:param r: 3d radius
:param r_ani: anisotropy radius
:return: beta
"""
return 1./2 * r / (r + r_ani)