Skip to content

Commit

Permalink
Implement separate __eq__ methods (#1033)
Browse files Browse the repository at this point in the history
* first commit

* remove warning in the first spin of CCL

* kernels chk ok, transfers almost done

* more tests

* finished tracer eq tests

* coverage for _repr.py

* more coverage

* more coverage

* super comprehensive tests & full coverage

* add a final extra test

* comments

* more efficient

* reinstate True if all checks pass

* use np.any for array comparison

* remove cm from FoF masses

* zero tolerance for array eq checking

* array_equal

* stray cM

* direct type comparison instead of hook

* comments

* fallback to id-checking
  • Loading branch information
nikfilippas committed Apr 17, 2023
1 parent 4e36b13 commit 1a351df
Show file tree
Hide file tree
Showing 22 changed files with 539 additions and 210 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ cosmo = ccl.Cosmology(
transfer_function='bbks')

# Define a simple binned galaxy number density curve as a function of redshift
z_n = np.linspace(0., 1., 200)
z_n = np.linspace(0., 1., 500)
n = np.ones(z_n.shape)

# Create objects to represent tracers of the weak lensing signal with this
Expand Down
20 changes: 18 additions & 2 deletions pyccl/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,15 @@ def Funlock(cls, cl, name, mutate: bool):
unlock_instance = UnlockInstance.unlock_instance


def is_equal(this, other):
"""Powerful helper for equivalence checking."""
try:
np.testing.assert_equal(this, other)
return True
except AssertionError:
return False


class FancyRepr:
"""Controls the usage of fancy ``__repr__` for ``CCLObjects."""
_enabled: bool = True
Expand Down Expand Up @@ -590,9 +599,16 @@ def __hash__(self):

def __eq__(self, other):
# Two same-type objects are equal if their representations are equal.
if self.__class__ is not other.__class__:
if type(self) is not type(other):
return False
return repr(self) == repr(other)
# Compare the attributes listed in `__eq_attrs__`.
if hasattr(self, "__eq_attrs__"):
for attr in self.__eq_attrs__:
if not is_equal(getattr(self, attr), getattr(other, attr)):
return False
return True
# Fall back to default Python comparison.
return id(self) == id(other)


class CCLHalosObject(CCLObject):
Expand Down
57 changes: 38 additions & 19 deletions pyccl/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,36 @@
}


def _methods_of_cosmology(cls=None, *, modules=[]):
"""Assign all functions in ``modules`` which take ``cosmo`` as their
first argument as methods of the class ``cls``.
"""
import functools
from importlib import import_module

if cls is None:
# called with parentheses
return functools.partial(_methods_of_cosmology, modules=modules)

pkg = __name__.rsplit(".")[0]
modules = [import_module(f".{module}", pkg) for module in modules]
funcs = [getmembers(module, isfunction) for module in modules]
funcs = [func for sublist in funcs for func in sublist]

for name, func in funcs:
pars = signature(func).parameters
if pars and list(pars)[0] == "cosmo":
setattr(cls, name, func)

return cls


_modules = ["background", "bcm", "boltzmann", "cls", "correlations",
"covariances", "neutrinos", "pk2d", "power", "pyutils",
"tk3d", "tracers", "halos", "nl_pt"]


@_methods_of_cosmology(modules=_modules)
class Cosmology(CCLObject):
"""A cosmology including parameters and associated data.
Expand Down Expand Up @@ -198,25 +228,8 @@ class Cosmology(CCLObject):
"""
from ._repr import _build_string_Cosmology as __repr__

# Go through all functions in the main package and the subpackages
# and make every function that takes `cosmo` as its first argument
# an attribute of this class.
from . import (background, bcm, boltzmann, cls,
correlations, covariances, neutrinos,
pk2d, power, pyutils, tk3d, tracers, halos, nl_pt)
subs = [background, boltzmann, bcm, cls, correlations, covariances,
neutrinos, pk2d, power, pyutils, tk3d, tracers, halos, nl_pt]
funcs = [getmembers(sub, isfunction) for sub in subs]
funcs = [func for sub in funcs for func in sub]
for name, func in funcs:
pars = list(signature(func).parameters)
if pars and pars[0] == "cosmo":
vars()[name] = func
# clear unnecessary locals
del (background, boltzmann, bcm, cls, correlations, covariances,
neutrinos, pk2d, power, pyutils, tk3d, tracers, halos, nl_pt,
subs, funcs, func, name, pars)
__eq_attrs__ = ("_params_init_kwargs", "_config_init_kwargs",
"_accuracy_params",)

def __init__(
self, Omega_c=None, Omega_b=None, h=None, n_s=None,
Expand Down Expand Up @@ -1167,6 +1180,9 @@ class CosmologyCalculator(Cosmology):
corresponding to the "HALOFIT" transformation of
Takahashi et al. 2012 (arXiv:1208.2701).
"""
__eq_attrs__ = ("_params_init_kwargs", "_config_init_kwargs",
"_accuracy_params", "_input_arrays",)

def __init__(
self, Omega_c=None, Omega_b=None, h=None, n_s=None,
sigma8=None, A_s=None, Omega_k=0., Omega_g=None,
Expand Down Expand Up @@ -1199,6 +1215,9 @@ def __init__(
has_pknl = pk_nonlin is not None
has_nonlin_model = nonlinear_model is not None

self._input_arrays = {"background": background, "growth": growth,
"pk_linear": pk_linear, "pk_nonlin": pk_nonlin,
"nonlinear_model": nonlinear_model}
if has_bg:
self._init_bg(background)
if has_dz:
Expand Down
6 changes: 3 additions & 3 deletions pyccl/halos/concentration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Concentration(CCLHalosObject):
object that fixes the mass definition used by this c(M)
parametrization.
"""
__repr_attrs__ = ("mdef",)
__repr_attrs__ = __eq_attrs__ = ("mdef",)

def __init__(self, mass_def=None):
if mass_def is not None:
Expand Down Expand Up @@ -395,7 +395,7 @@ class ConcentrationIshiyama21(Concentration):
method. Otherwise, use the concentration found with profile
fitting. The default is False.
"""
__repr_attrs__ = ("mdef", "relaxed", "Vmax",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "relaxed", "Vmax",)
name = 'Ishiyama21'

def __init__(self, mdef=None, relaxed=False, Vmax=False):
Expand Down Expand Up @@ -555,7 +555,7 @@ class ConcentrationConstant(Concentration):
the mass definition used by this c(M)
parametrization. In this case it's arbitrary.
"""
__repr_attrs__ = ("mdef", "c",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "c",)
name = 'Constant'

def __init__(self, c=1, mdef=None):
Expand Down
2 changes: 1 addition & 1 deletion pyccl/halos/halo_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class HMCalculator(CCLHalosObject):
determines what is considered a "very large" scale.
Default: 1E-5.
"""
__repr_attrs__ = ("_massfunc", "_hbias", "_mdef", "_prec",)
__repr_attrs__ = __eq_attrs__ = ("_massfunc", "_hbias", "_mdef", "_prec",)

def __init__(self, cosmo, massfunc, hbias, mass_def,
log10M_min=8., log10M_max=16.,
Expand Down
5 changes: 3 additions & 2 deletions pyccl/halos/hbias.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HaloBias(CCLHalosObject):
mass_def_strict (bool): if False, consistency of the mass
definition will be ignored.
"""
__repr_attrs__ = ("mdef", "mass_def_strict",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict",)

def __init__(self, cosmo, mass_def=None, mass_def_strict=True):
cosmo.compute_sigma()
Expand Down Expand Up @@ -190,7 +190,8 @@ class HaloBiasSheth99(HaloBias):
the fit of Nakamura & Suto 1997. Otherwise use
delta_crit = 1.68647.
"""
__repr_attrs__ = ("mdef", "mass_def_strict", "use_delta_c_fit",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict",
"use_delta_c_fit",)
name = "Sheth99"

def __init__(self, cosmo, mass_def=None,
Expand Down
11 changes: 6 additions & 5 deletions pyccl/halos/hmfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class MassFunc(CCLHalosObject):
mass_def_strict (bool): if False, consistency of the mass
definition will be ignored.
"""
__repr_attrs__ = ("mdef", "mass_def_strict",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict",)

def __init__(self, cosmo, mass_def=None, mass_def_strict=True):
# Initialize sigma(M) splines if needed
Expand Down Expand Up @@ -251,7 +251,8 @@ class MassFuncSheth99(MassFunc):
the fit of Nakamura & Suto 1997. Otherwise use
delta_crit = 1.68647.
"""
__repr_attrs__ = ("mdef", "mass_def_strict", "use_delta_c_fit",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict",
"use_delta_c_fit",)
name = 'Sheth99'

def __init__(self, cosmo, mass_def=None, mass_def_strict=True,
Expand Down Expand Up @@ -393,7 +394,7 @@ class MassFuncDespali16(MassFunc):
mass_def_strict (bool): if False, consistency of the mass
definition will be ignored.
"""
__repr_attrs__ = ("mdef", "mass_def_strict", "ellipsoidal",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict", "ellipsoidal",)
name = 'Despali16'

def __init__(self, cosmo, mass_def=None, mass_def_strict=True,
Expand Down Expand Up @@ -456,7 +457,7 @@ class MassFuncTinker10(MassFunc):
norm_all_z (bool): should we normalize the mass function
at z=0 or at all z?
"""
__repr_attrs__ = ("mdef", "mass_def_strict", "norm_all_z",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict", "norm_all_z",)
name = 'Tinker10'

def __init__(self, cosmo, mass_def=None, mass_def_strict=True,
Expand Down Expand Up @@ -539,7 +540,7 @@ class MassFuncBocquet16(MassFunc):
using dark-matter-only simulations. Otherwise, include
baryonic effects (default).
"""
__repr_attrs__ = ("mdef", "mass_def_strict", "hydro",)
__repr_attrs__ = __eq_attrs__ = ("mdef", "mass_def_strict", "hydro",)
name = 'Bocquet16'

def __init__(self, cosmo, mass_def=None, mass_def_strict=True,
Expand Down
14 changes: 12 additions & 2 deletions pyccl/halos/massdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ..background import species_types, rho_x, omega_x
from ..base import CCLHalosObject
import numpy as np
from functools import cached_property


def mass2radius_lagrangian(cosmo, M):
Expand Down Expand Up @@ -84,7 +85,7 @@ class MassDef(CCLHalosObject):
If `None`, no c(M) relation will be attached to this mass
definition (and hence one can't translate into other definitions).
"""
__repr_attrs__ = ("name",)
__repr_attrs__ = __eq_attrs__ = ("name",)

def __init__(self, Delta, rho_type, c_m_relation=None):
# Check it makes sense
Expand All @@ -106,7 +107,7 @@ def __init__(self, Delta, rho_type, c_m_relation=None):
else:
self._concentration_init(c_m_relation)

@property
@cached_property
def name(self):
"""Give a name to this mass definition."""
if isinstance(self.Delta, (int, float)):
Expand Down Expand Up @@ -286,3 +287,12 @@ def MassDefVir(c_m='Klypin11'):
c_m (string): concentration-mass relation.
"""
return MassDef('vir', 'critical', c_m_relation=c_m)


def MassDefFof():
r""":math:`\Delta = \rm FoF` mass definition.
Args:
c_m (string): concentration-mass relation.
"""
return MassDef('fof', 'matter')
36 changes: 18 additions & 18 deletions pyccl/halos/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ def __init__(self):
'plaw_fourier': -1.5,
'plaw_projected': -1.}

__eq__ = object.__eq__

__hash__ = object.__hash__ # TODO: remove once __eq__ is replaced.

@unlock_instance(mutate=True)
def update_precision_fftlog(self, **kwargs):
""" Update any of the precision parameters used by
Expand Down Expand Up @@ -535,7 +531,7 @@ class HaloProfileGaussian(HaloProfile):
rho0 (:obj:`function`): the amplitude of the profile.
It should have the same signature as `r_scale`.
"""
__repr_attrs__ = ("r_s", "rho_0", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = ("r_s", "rho_0", "precision_fftlog",)
name = 'Gaussian'

def __init__(self, r_scale, rho0):
Expand Down Expand Up @@ -582,7 +578,7 @@ class HaloProfilePowerLaw(HaloProfile):
profile. The signature of this function should
be `f(cosmo, a)`.
"""
__repr_attrs__ = ("r_s", "tilt", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = ("r_s", "tilt", "precision_fftlog",)
name = 'PowerLaw'

def __init__(self, r_scale, tilt):
Expand Down Expand Up @@ -654,8 +650,9 @@ class HaloProfileNFW(HaloProfile):
truncated at :math:`r = R_\\Delta` (i.e. zero at larger
radii.
"""
__repr_attrs__ = ("cM", "fourier_analytic", "projected_analytic",
"cumul2d_analytic", "truncated", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = (
"fourier_analytic", "projected_analytic",
"cumul2d_analytic", "truncated", "cM", "precision_fftlog",)
name = 'NFW'

def __init__(self, c_M_relation,
Expand Down Expand Up @@ -851,7 +848,8 @@ class HaloProfileEinasto(HaloProfile):
alpha (float, 'cosmo'): Set the Einasto alpha parameter or set to
'cosmo' to calculate the value from cosmology. Default: 'cosmo'
"""
__repr_attrs__ = ("cM", "truncated", "alpha", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = ("truncated", "alpha",
"cM", "precision_fftlog",)
name = 'Einasto'

def __init__(self, c_M_relation, truncated=True, alpha='cosmo'):
Expand Down Expand Up @@ -961,8 +959,9 @@ class HaloProfileHernquist(HaloProfile):
truncated at :math:`r = R_\\Delta` (i.e. zero at larger
radii.
"""
__repr_attrs__ = ("cM", "fourier_analytic", "projected_analytic",
"cumul2d_analytic", "truncated", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = (
"fourier_analytic", "projected_analytic", "cumul2d_analytic",
"truncated", "cM", "precision_fftlog",)
name = 'Hernquist'

def __init__(self, c_M_relation,
Expand Down Expand Up @@ -1193,9 +1192,9 @@ class HaloProfilePressureGNFW(HaloProfile):
Profile threshold, in units of :math:`R_{\\mathrm{500c}}`.
Defaults to :math:`+\\infty`.
"""
__repr_attrs__ = ("mass_bias", "P0", "c500", "alpha", "alpha_P", "beta",
"gamma", "P0_hexp", "qrange", "nq", "x_out",
"precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = (
"mass_bias", "P0", "c500", "alpha", "alpha_P", "beta", "gamma",
"P0_hexp", "qrange", "nq", "x_out", "precision_fftlog",)
name = 'GNFW'

def __init__(self, mass_bias=0.8, P0=6.41,
Expand Down Expand Up @@ -1472,10 +1471,11 @@ class HaloProfileHOD(HaloProfile):
ns_independent (bool): drop requirement to only form
satellites when centrals are present.
"""
__repr_attrs__ = ("cM", "lMmin_0", "lMmin_p", "siglM_0", "siglM_p",
"lM0_0", "lM0_p", "lM1_0", "lM1_p", "alpha_0", "alpha_p",
"fc_0", "fc_p", "bg_0", "bg_p", "bmax_0", "bmax_p",
"a_pivot", "ns_independent", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = (
"lMmin_0", "lMmin_p", "siglM_0", "siglM_p", "lM0_0", "lM0_p", "lM1_0",
"lM1_p", "alpha_0", "alpha_p", "fc_0", "fc_p", "bg_0", "bg_p",
"bmax_0", "bmax_p", "a_pivot", "ns_independent",
"cM", "precision_fftlog",)
name = 'HOD'
is_number_counts = True

Expand Down
6 changes: 1 addition & 5 deletions pyccl/halos/profiles_2pt.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@ class Profile2pt(CCLHalosObject):
Defaults to ``r_corr=0``, returning simply the product
of the fourier profiles.
"""
__repr_attrs__ = ("r_corr",)
__repr_attrs__ = __eq_attrs__ = ("r_corr",)

def __init__(self, r_corr=0.):
self.r_corr = r_corr

__eq__ = object.__eq__

__hash__ = object.__hash__ # TODO: remove once __eq__ is replaced.

def update_parameters(self, r_corr=None):
""" Update any of the parameters associated with this 1-halo
2-point correlator. Any parameter set to `None` won't be updated.
Expand Down
5 changes: 3 additions & 2 deletions pyccl/halos/profiles_cib.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ class HaloProfileCIBShang12(HaloProfile):
L0 (float): luminosity scale (in
:math:`{\\rm Jy}\\,{\\rm Mpc}^2\\,M_\\odot^{-1}`).
"""
__repr_attrs__ = ("cM", "nu", "alpha", "T0", "beta", "gamma", "s_z",
"l10meff", "sigLM", "Mmin", "L0", "precision_fftlog",)
__repr_attrs__ = __eq_attrs__ = (
"nu", "alpha", "T0", "beta", "gamma", "s_z",
"l10meff", "sigLM", "Mmin", "L0", "cM", "precision_fftlog",)
name = 'CIBShang12'
_one_over_4pi = 0.07957747154

Expand Down

0 comments on commit 1a351df

Please sign in to comment.