Beispiel #1
0
    def __init__(self,
                 kwargs_model,
                 kwargs_cosmo,
                 kwargs_numerics=None,
                 analytic_kinematics=False):
        """

        :param kwargs_model: keyword arguments describing the model components
        :param kwargs_cosmo: keyword arguments that define the cosmology in terms of the angular diameter distances involved
        :param kwargs_numerics: numerics keyword arguments
        :param analytic_kinematics: bool, if True uses the analytic kinematic model
        """
        if kwargs_numerics is None:
            kwargs_numerics = {
                'interpol_grid_num':
                200,  # numerical interpolation, should converge -> infinity
                'log_integration': True,
                # log or linear interpolation of surface brightness and mass models
                'max_integrate': 100,
                'min_integrate': 0.001
            }  # lower/upper bound of numerical integrals
        if analytic_kinematics is True:
            anisotropy_model = kwargs_model.get('anisotropy_model')
            if not anisotropy_model == 'OM':
                raise ValueError(
                    'analytic kinematics only available for OsipkovMerritt ("OM") anisotropy model.'
                )
            self.numerics = AnalyticKinematics(kwargs_cosmo=kwargs_cosmo,
                                               **kwargs_numerics)
        else:
            self.numerics = NumericKinematics(kwargs_model=kwargs_model,
                                              kwargs_cosmo=kwargs_cosmo,
                                              **kwargs_numerics)
        self._analytic_kinematics = analytic_kinematics
    def test_sigma_s2(self):
        """
        test LOS projected velocity dispersion at 3d ratios (numerical Jeans equation solution vs analytic one)
        """
        light_profile_list = ['HERNQUIST']
        r_eff = 0.5
        Rs = 0.551 * r_eff
        kwargs_light = [{
            'Rs': Rs,
            'amp': 1.
        }]  # effective half light radius (2d projected) in arcsec
        # 0.551 *
        # mass profile
        mass_profile_list = ['SPP']
        theta_E = 1.2
        gamma = 1.95
        kwargs_mass = [{
            'theta_E': theta_E,
            'gamma': gamma
        }]  # Einstein radius (arcsec) and power-law slope

        # anisotropy profile
        anisotropy_type = 'OM'
        r_ani = 0.5
        kwargs_anisotropy = {'r_ani': r_ani}  # anisotropy radius [arcsec]

        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
        kwargs_numerics = {
            'interpol_grid_num': 2000,
            'log_integration': True,
            'max_integrate': 4000,
            'min_integrate': 0.001
        }

        kwargs_model = {
            'mass_profile_list': mass_profile_list,
            'light_profile_list': light_profile_list,
            'anisotropy_model': anisotropy_type
        }
        analytic_kin = AnalyticKinematics(kwargs_cosmo, **kwargs_numerics)
        numeric_kin = NumericKinematics(kwargs_model, kwargs_cosmo,
                                        **kwargs_numerics)
        r_list = np.logspace(-2, 1, 10)
        for r in r_list:
            for R in np.linspace(start=0, stop=r, num=5):
                sigma_s2_analytic, I_R = analytic_kin.sigma_s2(
                    r, R, {
                        'theta_E': theta_E,
                        'gamma': gamma
                    }, {'r_eff': r_eff}, kwargs_anisotropy)
                sigma_s2_full_num = numeric_kin.sigma_s2_r(
                    r, R, kwargs_mass, kwargs_light, kwargs_anisotropy)
                npt.assert_almost_equal(sigma_s2_full_num / sigma_s2_analytic,
                                        1,
                                        decimal=2)
    def test_I_R_sigma_s2(self):
        light_profile_list = ['HERNQUIST']
        r_eff = 1
        Rs = 0.551 * r_eff
        kwargs_light = [{
            'Rs': Rs,
            'amp': 1.
        }]  # effective half light radius (2d projected) in arcsec
        # 0.551 *
        # mass profile
        mass_profile_list = ['SPP']
        theta_E = 1.2
        gamma = 1.95
        kwargs_mass = [{
            'theta_E': theta_E,
            'gamma': gamma
        }]  # Einstein radius (arcsec) and power-law slope

        # anisotropy profile
        anisotropy_type = 'OM'
        r_ani = 0.5
        kwargs_anisotropy = {'r_ani': r_ani}  # anisotropy radius [arcsec]

        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
        kwargs_numerics = {
            'interpol_grid_num': 4000,
            'log_integration': True,
            'max_integrate': 100,
            'min_integrate': 0.0001,
            'max_light_draw': 50
        }

        kwargs_model = {
            'mass_profile_list': mass_profile_list,
            'light_profile_list': light_profile_list,
            'anisotropy_model': anisotropy_type
        }
        numeric_kin = NumericKinematics(kwargs_model, kwargs_cosmo,
                                        **kwargs_numerics)

        # check whether projected light integral is the same as analytic expression
        R = 1
        I_R_sigma2, I_R = numeric_kin._I_R_sigma2(R, kwargs_mass, kwargs_light,
                                                  kwargs_anisotropy)
        out = integrate.quad(
            lambda x: numeric_kin.lightProfile.light_3d(
                np.sqrt(R**2 + x**2), kwargs_light),
            kwargs_numerics['min_integrate'],
            np.sqrt(kwargs_numerics['max_integrate']**2 - R**2))
        l_R_quad = out[0] * 2
        npt.assert_almost_equal(l_R_quad / I_R, 1, decimal=2)

        l_R = numeric_kin.lightProfile.light_2d(R, kwargs_light)
        npt.assert_almost_equal(l_R / I_R, 1, decimal=2)
 def test_mass_3d(self):
     kwargs_model = {
         'mass_profile_list': ['HERNQUIST'],
         'light_profile_list': ['HERNQUIST'],
         'anisotropy_model': 'isotropic'
     }
     massProfile = NumericKinematics(kwargs_model=kwargs_model,
                                     kwargs_cosmo={
                                         'd_d': 1.,
                                         'd_s': 2.,
                                         'd_ds': 1.
                                     })
     r = 0.3
     kwargs_profile = [{'sigma0': 1., 'Rs': 0.5}]
     mass_3d = massProfile._mass_3d_interp(r, kwargs_profile)
     mass_3d_exact = massProfile.mass_3d(r, kwargs_profile)
     npt.assert_almost_equal(mass_3d / mass_3d_exact, 1., decimal=3)
 def test_delete_cache(self):
     kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
     kwargs_numerics = {
         'interpol_grid_num': 2000,
         'log_integration': True,
         'max_integrate': 1000,
         'min_integrate': 0.0001
     }
     kwargs_model = {
         'mass_profile_list': [],
         'light_profile_list': [],
         'anisotropy_model': 'const'
     }
     numeric_kin = NumericKinematics(kwargs_model, kwargs_cosmo,
                                     **kwargs_numerics)
     numeric_kin._interp_jeans_integral = 1
     numeric_kin._log_mass_3d = 2
     numeric_kin.delete_cache()
     assert hasattr(numeric_kin, '_log_mass_3d') is False
     assert hasattr(numeric_kin, '_interp_jeans_integral') is False
    def test_power_law_test(self):
        # tests a isotropic velocity anisotropy on a singular isothermal sphere with the same tracer particle distribution
        # This should result in a constant velocity dispersion as a function of radius, analytically known

        # set up power-law light profile
        light_model = ['POWER_LAW']
        kwargs_light = [{'gamma': 2, 'amp': 1, 'e1': 0, 'e2': 0}]

        lens_model = ['SIS']
        kwargs_mass = [{'theta_E': 1}]

        anisotropy_type = 'isotropic'
        kwargs_anisotropy = {}
        kwargs_model = {
            'mass_profile_list': lens_model,
            'light_profile_list': light_model,
            'anisotropy_model': anisotropy_type
        }
        kwargs_numerics = {
            'interpol_grid_num': 2000,
            'log_integration': True,
            'max_integrate': 1000,
            'min_integrate': 0.0001
        }
        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}

        # compute analytic velocity dispersion of SIS profile

        v_sigma_c2 = kwargs_mass[0]['theta_E'] * const.arcsec / (
            4 * np.pi) * kwargs_cosmo['d_s'] / kwargs_cosmo['d_ds']
        v_sigma_true = np.sqrt(v_sigma_c2) * const.c / 1000

        numerics = NumericKinematics(kwargs_model=kwargs_model,
                                     kwargs_cosmo=kwargs_cosmo,
                                     **kwargs_numerics)
        R = 2
        I_R_sigma2, I_R = numerics._I_R_sigma2(R,
                                               kwargs_mass,
                                               kwargs_light,
                                               kwargs_anisotropy={})
        sigma_v = np.sqrt(I_R_sigma2 / I_R) / 1000
        print(sigma_v, v_sigma_true)
        npt.assert_almost_equal(sigma_v / v_sigma_true, 1, decimal=2)

        # plot as radial distance of projected dispersion
        r_array = np.logspace(start=-2, stop=1, num=100)

        sigma_array = np.zeros_like(r_array)
        for i, R in enumerate(r_array):
            I_R_sigma2, I_R = numerics._I_R_sigma2(R, kwargs_mass,
                                                   kwargs_light,
                                                   kwargs_anisotropy)
            sigma_array[i] = np.sqrt(I_R_sigma2 / I_R) / 1000

        #import matplotlib.pyplot as plt
        #plt.semilogx(r_array, sigma_array)
        #plt.hlines(v_sigma_true, xmin=r_array[0], xmax=r_array[-1])
        #plt.show()

        npt.assert_almost_equal(sigma_array / v_sigma_true, 1, decimal=2)

        # and here we test the 3d radial velocity dispersion solution
        sigma_array = np.zeros_like(r_array)
        R_test = 0
        for i, r in enumerate(r_array):
            sigma_s2 = numerics.sigma_s2_r(r, R_test, kwargs_mass,
                                           kwargs_light, kwargs_anisotropy)
            sigma_array[i] = np.sqrt(sigma_s2) / 1000
        npt.assert_almost_equal(sigma_array / v_sigma_true, 1, decimal=2)
    def test_sigma_r2(self):
        """
        tests the solution of the Jeans equation for sigma**2(r), where r is the 3d radius.
        Test is compared to analytic OM solution with power-law and Hernquist light profile

        :return:
        """
        light_profile_list = ['HERNQUIST']
        r_eff = 0.5
        Rs = 0.551 * r_eff
        kwargs_light = [{
            'Rs': Rs,
            'amp': 1.
        }]  # effective half light radius (2d projected) in arcsec
        # 0.551 *
        # mass profile
        mass_profile_list = ['SPP']
        theta_E = 1.2
        gamma = 1.95
        kwargs_mass = [{
            'theta_E': theta_E,
            'gamma': gamma
        }]  # Einstein radius (arcsec) and power-law slope

        # anisotropy profile
        anisotropy_type = 'OM'
        r_ani = 0.5
        kwargs_anisotropy = {'r_ani': r_ani}  # anisotropy radius [arcsec]

        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
        kwargs_numerics = {
            'interpol_grid_num': 2000,
            'log_integration': True,
            'max_integrate': 4000,
            'min_integrate': 0.001
        }

        kwargs_model = {
            'mass_profile_list': mass_profile_list,
            'light_profile_list': light_profile_list,
            'anisotropy_model': anisotropy_type
        }
        analytic_kin = AnalyticKinematics(kwargs_cosmo, **kwargs_numerics)
        numeric_kin = NumericKinematics(kwargs_model, kwargs_cosmo,
                                        **kwargs_numerics)
        rho0_r0_gamma = analytic_kin._rho0_r0_gamma(theta_E, gamma)
        r_array = np.logspace(-2.9, 2.9, 100)
        sigma_r_analytic_array = []
        sigma_r_num_array = []
        for r in r_array:
            sigma_r2_analytic = analytic_kin._sigma_r2(
                r=r,
                a=Rs,
                gamma=gamma,
                r_ani=r_ani,
                rho0_r0_gamma=rho0_r0_gamma)
            sigma_r2_num = numeric_kin.sigma_r2(r, kwargs_mass, kwargs_light,
                                                kwargs_anisotropy)
            sigma_r_analytic = np.sqrt(sigma_r2_analytic) / 1000
            sigma_r_num = np.sqrt(sigma_r2_num) / 1000
            sigma_r_num_array.append(sigma_r_num)
            sigma_r_analytic_array.append(sigma_r_analytic)

        npt.assert_almost_equal(sigma_r_num_array,
                                sigma_r_analytic_array,
                                decimal=-2)
        npt.assert_almost_equal(np.array(sigma_r_num_array) /
                                np.array(sigma_r_analytic_array),
                                1,
                                decimal=-2)
        print(np.array(sigma_r_num_array) / np.array(sigma_r_analytic_array))
    def test_I_R_sigma(self):
        """
        test numerical integral against quad integrator
        :return:
        """
        light_profile_list = ['HERNQUIST']
        Rs = .5
        kwargs_light = [{
            'Rs': Rs,
            'amp': 1.
        }]  # effective half light radius (2d projected) in arcsec
        # 0.551 *
        # mass profile
        mass_profile_list = ['SPP']
        theta_E = 1.2
        gamma = 2.
        kwargs_profile = [{
            'theta_E': theta_E,
            'gamma': gamma
        }]  # Einstein radius (arcsec) and power-law slope

        # anisotropy profile
        anisotropy_type = 'OM'
        r_ani = 2.
        kwargs_anisotropy = {'r_ani': r_ani}  # anisotropy radius [arcsec]

        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
        kwargs_numerics = {
            'interpol_grid_num': 2000,
            'log_integration': True,
            'max_integrate': 1000,
            'min_integrate': 0.0001
        }

        kwargs_model = {
            'mass_profile_list': mass_profile_list,
            'light_profile_list': light_profile_list,
            'anisotropy_model': anisotropy_type
        }

        numerics = NumericKinematics(kwargs_model=kwargs_model,
                                     kwargs_cosmo=kwargs_cosmo,
                                     **kwargs_numerics)

        R = 0.1
        out = integrate.quad(
            lambda x: numerics._integrand_A15(
                x, R, kwargs_profile, kwargs_light, kwargs_anisotropy), R,
            kwargs_numerics['max_integrate'])

        I_R_sigma_quad = out[0] * 2 * const.G / (
            const.arcsec * kwargs_cosmo['d_d'] * const.Mpc)
        I_R_sigma_numerics_log, _ = numerics._I_R_sigma2(
            R, kwargs_profile, kwargs_light, kwargs_anisotropy)

        kwargs_numerics_lin = {
            'interpol_grid_num': 2000,
            'log_integration': False,
            'max_integrate': 1000,
            'min_integrate': 0.0001
        }
        numerics_lin = NumericKinematics(kwargs_model=kwargs_model,
                                         kwargs_cosmo=kwargs_cosmo,
                                         **kwargs_numerics_lin)
        I_R_simga_numerics_lin, _ = numerics_lin._I_R_sigma2(
            R, kwargs_profile, kwargs_light, kwargs_anisotropy)
        npt.assert_almost_equal(I_R_sigma_numerics_log / I_R_sigma_quad,
                                1,
                                decimal=2)

        # We do not test the linear integral as it is not as accurate!!!
        #npt.assert_almost_equal(I_R_simga_numerics_lin / I_R_simga_quad, 1, decimal=2)

        # here we test the interpolation
        numerics = NumericKinematics(kwargs_model=kwargs_model,
                                     kwargs_cosmo=kwargs_cosmo,
                                     **kwargs_numerics)
        R = 1
        I_R_sigma2_interp, I_R_interp = numerics._I_R_sigma2_interp(
            R, kwargs_profile, kwargs_light, kwargs_anisotropy)
        I_R_sigma2, I_R = numerics._I_R_sigma2(R, kwargs_profile, kwargs_light,
                                               kwargs_anisotropy)
        npt.assert_almost_equal(I_R_sigma2_interp / I_R_sigma2, 1, decimal=3)
        npt.assert_almost_equal(I_R_interp / I_R, 1, decimal=3)
    def test_log_linear_integral(self):
        # light profile
        light_profile_list = ['HERNQUIST']
        Rs = .5
        kwargs_light = [{
            'Rs': Rs,
            'amp': 1.
        }]  # effective half light radius (2d projected) in arcsec
        # 0.551 *
        # mass profile
        mass_profile_list = ['SPP']
        theta_E = 1.2
        gamma = 2.
        kwargs_profile = [{
            'theta_E': theta_E,
            'gamma': gamma
        }]  # Einstein radius (arcsec) and power-law slope

        # anisotropy profile
        anisotropy_type = 'OM'
        r_ani = 2.
        kwargs_anisotropy = {'r_ani': r_ani}  # anisotropy radius [arcsec]

        kwargs_cosmo = {'d_d': 1000, 'd_s': 1500, 'd_ds': 800}
        kwargs_numerics_linear = {
            'interpol_grid_num': 2000,
            'log_integration': False,
            'max_integrate': 10,
            'min_integrate': 0.001
        }
        kwargs_numerics_log = {
            'interpol_grid_num': 1000,
            'log_integration': True,
            'max_integrate': 10,
            'min_integrate': 0.001
        }
        kwargs_model = {
            'mass_profile_list': mass_profile_list,
            'light_profile_list': light_profile_list,
            'anisotropy_model': anisotropy_type
        }

        numerics_linear = NumericKinematics(kwargs_model=kwargs_model,
                                            kwargs_cosmo=kwargs_cosmo,
                                            **kwargs_numerics_linear)
        numerics_log = NumericKinematics(kwargs_model=kwargs_model,
                                         kwargs_cosmo=kwargs_cosmo,
                                         **kwargs_numerics_log)
        R = np.logspace(-2, 0, 100)

        lin_I_R = np.zeros_like(R)
        log_I_R = np.zeros_like(R)
        for i in range(len(R)):
            lin_I_R[i], _ = numerics_linear._I_R_sigma2(
                R[i], kwargs_profile, kwargs_light, kwargs_anisotropy)
            log_I_R[i], _ = numerics_log._I_R_sigma2(R[i], kwargs_profile,
                                                     kwargs_light,
                                                     kwargs_anisotropy)

        #import matplotlib.pyplot as plt
        #plt.semilogx(R, lin_I_R / log_I_R, 'r', label='lin /log integrate')
        #plt.legend()
        #plt.show()

        R_ = 1
        r_array = np.logspace(start=np.log10(R_ + 0.001), stop=1, num=100)
        integrad_a15 = numerics_linear._integrand_A15(r_array, R_,
                                                      kwargs_profile,
                                                      kwargs_light,
                                                      kwargs_anisotropy)
        #plt.loglog(r_array, integrad_a15)
        #plt.show()

        for i in range(len(R)):
            npt.assert_almost_equal(log_I_R[i] / lin_I_R[i], 1, decimal=2)
Beispiel #10
0
class GalkinModel(object):
    """
    this class handles all the kinematic modeling aspects of Galkin
    Excluded are observational conditions (seeing, aperture etc)
    Major class to compute velocity dispersion measurements given light and mass models

    The class supports any mass and light distribution (and superposition thereof) that has a 3d correspondance in their
    2d lens model distribution. For models that do not have this correspondence, you may want to apply a
    Multi-Gaussian Expansion (MGE) on their models and use the MGE to be de-projected to 3d.

    The computation follows Mamon&Lokas 2005.

    The class supports various types of anisotropy models (see Anisotropy class).
    Solving the Jeans Equation requires a numerical integral over the 3d light and mass profile (see Mamon&Lokas 2005).
    This class (as well as the dedicated LightModel and MassModel classes) perform those integral numerically with an
    interpolated grid.

    The cosmology assumed to compute the physical mass and distances are set via the kwargs_cosmo keyword arguments.
        d_d: Angular diameter distance to the deflector (in Mpc)
        d_s: Angular diameter distance to the source (in Mpc)
        d_ds: Angular diameter distance from the deflector to the source (in Mpc)

    The numerical options can be chosen through the kwargs_numerics keywords

        interpol_grid_num: number of interpolation points in the light and mass profile (radially). This number should
        be chosen high enough to accurately describe the true light profile underneath.
        log_integration: bool, if True, performs the interpolation and numerical integration in log-scale.

        max_integrate: maximum 3d radius to where the numerical integration of the Jeans Equation solver is made.
        This value should be large enough to contain most of the light and to lead to a converged result.
        min_integrate: minimal integration value. This value should be very close to zero but some mass and light
        profiles are diverging and a numerically stable value should be chosen.

    These numerical options should be chosen to allow for a converged result (within your tolerance) but not too
    conservative to impact too much the computational cost. Reasonable values might depend on the specific problem.

    """
    def __init__(self,
                 kwargs_model,
                 kwargs_cosmo,
                 kwargs_numerics=None,
                 analytic_kinematics=False):
        """

        :param kwargs_model: keyword arguments describing the model components
        :param kwargs_cosmo: keyword arguments that define the cosmology in terms of the angular diameter distances involved
        :param kwargs_numerics: numerics keyword arguments
        :param analytic_kinematics: bool, if True uses the analytic kinematic model
        """
        if kwargs_numerics is None:
            kwargs_numerics = {
                'interpol_grid_num':
                200,  # numerical interpolation, should converge -> infinity
                'log_integration': True,
                # log or linear interpolation of surface brightness and mass models
                'max_integrate': 100,
                'min_integrate': 0.001
            }  # lower/upper bound of numerical integrals
        if analytic_kinematics is True:
            anisotropy_model = kwargs_model.get('anisotropy_model')
            if not anisotropy_model == 'OM':
                raise ValueError(
                    'analytic kinematics only available for OsipkovMerritt ("OM") anisotropy model.'
                )
            self.numerics = AnalyticKinematics(kwargs_cosmo=kwargs_cosmo,
                                               **kwargs_numerics)
        else:
            self.numerics = NumericKinematics(kwargs_model=kwargs_model,
                                              kwargs_cosmo=kwargs_cosmo,
                                              **kwargs_numerics)
        self._analytic_kinematics = analytic_kinematics

    def check_df(self, r, kwargs_mass, kwargs_light, kwargs_anisotropy):
        """
        checks whether the phase space distribution function of a given anisotropy model is positive.
        Currently this is implemented by the relation provided by Ciotti and Morganti 2010 equation (10)
        https://arxiv.org/pdf/1006.2344.pdf

        :param r: 3d radius to check slope-anisotropy constraint
        :param theta_E: Einstein radius in arc seconds
        :param gamma: power-law slope
        :param a_ani: scaled transition radius of the OM anisotropy distribution
        :param r_eff: half-light radius in arc seconds
        :return: equation (10) >= 0 for physical interpretation
        """
        dr = 0.01  # finite differential in radial direction
        r_dr = r + dr

        sigmar2 = self.numerics.sigma_r2(r, kwargs_mass, kwargs_light,
                                         kwargs_anisotropy)
        sigmar2_dr = self.numerics.sigma_r2(r_dr, kwargs_mass, kwargs_light,
                                            kwargs_anisotropy)
        grav_pot = self.numerics.grav_potential(r, kwargs_mass)
        grav_pot_dr = self.numerics.grav_potential(r_dr, kwargs_mass)
        self.numerics.delete_cache()
        return r * (sigmar2_dr - sigmar2 - grav_pot + grav_pot_dr) / dr