• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LSSTDESC / CCL / 8752929359

19 Apr 2024 11:37AM UTC coverage: 97.484% (+0.02%) from 97.464%
8752929359

Pull #1161

github

web-flow
Merge c41e943e5 into 9ce2f6d40
Pull Request #1161: Nikosarcevic ccl v3 paper plots

6547 of 6716 relevant lines covered (97.48%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.52
/pyccl/cosmology.py
1
"""The core functionality of CCL, including the core data types lives in this
1✔
2
module. Its focus is the :class:`Cosmology` class, which plays a central role,
3
carrying the information on cosmological parameters and derived quantities
4
needed in most of the calculations carried out by CCL.
5

6
.. note::
7
    All of the standalone functions in other modules, which take `cosmo` as
8
    their first argument, are methods of :class:`~Cosmology`.
9

10
Some important CCL parameters, governing for example the precision and speed
11
of some calculations, are independent of the :class:`~Cosmology` objects,
12
and instead can be accessed at a global level. You can do so as e.g.
13
``pyccl.gsl_params['ODE_GROWTH_EPSREL']``, ``pyccl.spline_params['K_MIN']``,
14
``pyccl.physical_constants['CLIGHT']``, or
15
``pyccl.gsl_params.ODE_GROWTH_EPSREL``, ``pyccl.spline_params.K_MIN``,
16
``pyccl.physical_constants.CLIGHT``.
17
"""
18
__all__ = ("TransferFunctions", "MatterPowerSpectra",
1✔
19
           "Cosmology", "CosmologyVanillaLCDM", "CosmologyCalculator",)
20

21
import yaml
1✔
22
from copy import deepcopy
1✔
23
from enum import Enum
1✔
24
from inspect import getmembers, isfunction, signature
1✔
25
from numbers import Real
1✔
26
from typing import Iterable
1✔
27
from dataclasses import dataclass
1✔
28
from scipy.interpolate import Akima1DInterpolator
1✔
29

30
import numpy as np
1✔
31

32
from . import (
1✔
33
    CCLError, CCLObject, CCLParameters, CosmologyParams,
34
    DEFAULT_POWER_SPECTRUM, DefaultParams, Pk2D, check, lib,
35
    unlock_instance, emulators, baryons, modified_gravity)
36
from . import physical_constants as const
1✔
37

38

39
class TransferFunctions(Enum):
1✔
40
    BBKS = "bbks"
1✔
41
    EISENSTEIN_HU = "eisenstein_hu"
1✔
42
    EISENSTEIN_HU_NOWIGGLES = "eisenstein_hu_nowiggles"
1✔
43
    BOLTZMANN_CLASS = "boltzmann_class"
1✔
44
    BOLTZMANN_CAMB = "boltzmann_camb"
1✔
45
    BOLTZMANN_ISITGR = "boltzmann_isitgr"
1✔
46
    CALCULATOR = "calculator"
1✔
47
    EMULATOR_LINPK = "emulator"
1✔
48

49

50
class MatterPowerSpectra(Enum):
1✔
51
    LINEAR = "linear"
1✔
52
    HALOFIT = "halofit"
1✔
53
    CAMB = "camb"
1✔
54
    CALCULATOR = "calculator"
1✔
55
    EMULATOR_NLPK = "emulator"
1✔
56

57

58
# Configuration types
59
transfer_function_types = {
1✔
60
    'eisenstein_hu': lib.eisenstein_hu,
61
    'eisenstein_hu_nowiggles': lib.eisenstein_hu_nowiggles,
62
    'bbks': lib.bbks,
63
    'boltzmann_class': lib.boltzmann_class,
64
    'boltzmann_camb': lib.boltzmann_camb,
65
    'boltzmann_isitgr': lib.boltzmann_isitgr,
66
    'calculator': lib.pklin_from_input,
67
    'emulator': lib.emulator_linpk
68
}
69

70

71
matter_power_spectrum_types = {
1✔
72
    'halo_model': lib.halo_model,
73
    'halofit': lib.halofit,
74
    'linear': lib.linear,
75
    'calculator': lib.pknl_from_input,
76
    'camb': lib.pknl_from_boltzman,
77
    'emulator': lib.emulator_nlpk
78
}
79

80
_TOP_LEVEL_MODULES = ("",)
1✔
81

82

83
def _make_methods(cls=None, *, modules=_TOP_LEVEL_MODULES, name=None):
1✔
84
    """Assign all functions in ``modules`` which take ``name`` as their
85
    first argument as methods of the class ``cls``.
86
    """
87
    import functools
1✔
88
    from importlib import import_module
1✔
89

90
    if cls is None:
1✔
91
        # called with parentheses
92
        return functools.partial(_make_methods, modules=modules)
1✔
93

94
    pkg = __name__.rsplit(".")[0]
1✔
95
    modules = [import_module(f".{module}", pkg) for module in modules]
1✔
96
    funcs = [getmembers(module, isfunction) for module in modules]
1✔
97
    funcs = [func for sublist in funcs for func in sublist]
1✔
98

99
    for name, func in funcs:
1✔
100
        pars = signature(func).parameters
1✔
101
        if pars and list(pars)[0] == "cosmo":
1✔
102
            setattr(cls, name, func)
1✔
103

104
    return cls
1✔
105

106

107
@dataclass
1✔
108
class _CosmologyBackgroundData:
1✔
109
    """
1✔
110
    This private class stores values calculated in the python level
111
    as we are porting background calculators from C to python.
112
    """
113
    lookback: Akima1DInterpolator = None
1✔
114
    age0: float = None
1✔
115

116

117
def _make_yaml_friendly(d):
1✔
118
    """Turn python objects into yaml types where possible."""
119

120
    d = deepcopy(d)
1✔
121
    for k, v in d.items():
1✔
122
        if isinstance(v, tuple):
1✔
123
            d[k] = list(v)
1✔
124
        elif isinstance(v, np.ndarray):
1✔
125
            d[k] = v.tolist()
1✔
126
        elif isinstance(v, dict):
1✔
127
            d[k] = _make_yaml_friendly(v)
1✔
128
        elif not (isinstance(v, (int, float, str, list)) or v is None):
1✔
129
            raise ValueError(f"{k}={v} cannot be serialised to YAML.")
1✔
130

131
    return d
1✔
132

133

134
@_make_methods(modules=("", "halos", "nl_pt",), name="cosmo")
1✔
135
class Cosmology(CCLObject):
1✔
136
    """Stores information about cosmological parameters and associated data
1✔
137
    (e.g. distances, power spectra).
138

139
    The values of cosmological parameters may be looked up by name
140
    (e.g. ``cosmo["sigma8"]``). Note that some of the parameters accessible
141
    this way are not contained in the signature of :class:`~Cosmology`, but
142
    are derived during initialization.
143

144
    .. note:: Although some arguments default to `None`, they will raise a
145
              ValueError inside this function if not specified, so they are not
146
              optional.
147

148
    .. note:: The parameter ``Omega_g`` can be used to set the radiation density
149
              (not including relativistic neutrinos) to zero. Doing this will
150
              give you a model that is physically inconsistent since the
151
              temperature of the CMB will still be non-zero.
152

153
    Args:
154
        Omega_c (:obj:`float`): Cold dark matter density fraction.
155
        Omega_b (:obj:`float`): Baryonic matter density fraction.
156
        h (:obj:`float`): Hubble constant divided by 100 km/s/Mpc; unitless.
157
        A_s (:obj:`float`): Power spectrum normalization. Exactly one of A_s
158
            and sigma_8 is required.
159
        sigma8 (:obj:`float`): Variance of matter density perturbations at
160
            an 8 Mpc/h scale. Exactly one of A_s and sigma_8 is required.
161
            Note that, if a value of `sigma8` is passed, CCL will enforce
162
            the linear matter power spectrum to be correctly normalised to
163
            this value of :math:`\\sigma_8`, even in the presence of other
164
            parameters (e.g. modified gravity parameters) that might affect
165
            the overall power spectrum normalization.
166
        n_s (:obj:`float`): Primordial scalar perturbation spectral index.
167
        Omega_k (:obj:`float`): Curvature density fraction.
168
            Defaults to 0.
169
        Omega_g (:obj:`float`): Density in relativistic species
170
            except massless neutrinos. The default of `None` corresponds
171
            to setting this from the CMB temperature. Note that if a non-`None`
172
            value is given, this may result in a physically inconsistent model
173
            because the CMB temperature will still be non-zero in the
174
            parameters.
175
        Neff (:obj:`float`): Effective number of massless
176
            neutrinos present. Defaults to 3.044.
177
        m_nu (:obj:`float` or `array`):
178
            Mass in eV of the massive neutrinos present. Defaults to 0.
179
            If a sequence is passed, it is assumed that the elements of the
180
            sequence represent the individual neutrino masses.
181
        mass_split (:obj:`str`): Type of massive neutrinos. Should
182
            be one of 'single', 'equal', 'normal', 'inverted'. 'single' treats
183
            the mass as being held by one massive neutrino. The other options
184
            split the mass into 3 massive neutrinos. Ignored if a sequence is
185
            passed in ``m_nu``. Default is 'normal'.
186
        w0 (:obj:`float`): First order term of dark energy equation
187
            of state. Defaults to -1.
188
        wa (:obj:`float`): Second order term of dark energy equation
189
            of state. Defaults to 0.
190
        T_CMB (:obj:`float`): The CMB temperature today. The default value
191
            is 2.7255.
192
        T_ncdm (:obj:`float`): Non-CDM temperature in units of photon
193
            temperature. The default is 0.71611.
194
        transfer_function (:obj:`str` or :class:`~pyccl.emulators.emu_base.EmulatorPk`):
195
            The transfer function to use. Defaults to 'boltzmann_camb'.
196
        matter_power_spectrum (:obj:`str` or :class:`~pyccl.emulators.emu_base.EmulatorPk`):
197
            The matter power spectrum to use. Defaults to 'halofit'.
198
        baryonic_effects (:class:`~pyccl.baryons.baryons_base.Baryons` or :obj:`None`):
199
            The baryonic effects model to use. Options are `None` (no baryonic effects), or
200
            a :class:`~pyccl.baryons.baryons_base.Baryons` object.
201
        mg_parametrization (:class:`~pyccl.modified_gravity.modified_gravity_base.ModifiedGravity` or `None`):
202
            The modified gravity parametrization to use. Options are `None`
203
            (no MG), or a :class:`~pyccl.modified_gravity.modified_gravity_base.ModifiedGravity`
204
            object. Currently, only :class:`~pyccl.modified_gravity.mu_Sigma.MuSigmaMG`
205
            is supported.
206
        extra_parameters (:obj:`dict`): Dictionary holding extra
207
            parameters. Currently supports extra parameters for CAMB.
208
            Details described below. Defaults to None.
209

210
    Currently supported extra parameters for CAMB are:
211

212
        * `halofit_version`
213
        * `HMCode_A_baryon`
214
        * `HMCode_eta_baryon`
215
        * `HMCode_logT_AGN`
216
        * `kmax`
217
        * `lmax`
218
        * `dark_energy_model`
219

220
    Consult the CAMB documentation for their usage. These parameters are passed
221
    in a :obj:`dict` to `extra_parameters` as::
222

223
        extra_parameters = {"camb": {"halofit_version": "mead2020_feedback",
224
                                     "HMCode_logT_AGN": 7.8}}
225

226
    .. note :: If using camb to compute the non-linear power spectrum with HMCode
227
               to include baryonic effects, you should not include any extra
228
               baryonic effects (i.e. set `baryonic_effects=None`).
229
    """ # noqa
230
    from ._core.repr_ import build_string_Cosmology as __repr__
1✔
231
    __eq_attrs__ = ("_params_init_kwargs", "_config_init_kwargs",
1✔
232
                    "_accuracy_params", "lin_pk_emu", 'nl_pk_emu',
233
                    "baryons", "mg_parametrization")
234

235
    def __init__(
1✔
236
            self, *, Omega_c=None, Omega_b=None, h=None, n_s=None,
237
            sigma8=None, A_s=None, Omega_k=0., Omega_g=None,
238
            Neff=None, m_nu=0., mass_split='normal', w0=-1., wa=0.,
239
            T_CMB=DefaultParams.T_CMB,
240
            T_ncdm=DefaultParams.T_ncdm,
241
            transfer_function='boltzmann_camb',
242
            matter_power_spectrum='halofit',
243
            baryonic_effects=None,
244
            mg_parametrization=None,
245
            extra_parameters=None):
246

247
        if Neff is None:
1✔
248
            Neff = 3.044
1✔
249

250
        extra_parameters = extra_parameters or {}
1✔
251

252
        # initialise linear Pk emulators if needed
253
        self.lin_pk_emu = None
1✔
254
        if isinstance(transfer_function, emulators.EmulatorPk):
1✔
255
            self.lin_pk_emu = transfer_function
1✔
256
            self.transfer_function_type = "emulator"
1✔
257
        elif isinstance(transfer_function, str):
1✔
258
            self.transfer_function_type = transfer_function
1✔
259
        else:
260
            raise ValueError(f"transfer_function={transfer_function} not "
1✔
261
                             f"supported.")
262

263
        # initialise nonlinear Pk emulators if needed
264
        self.nl_pk_emu = None
1✔
265
        if isinstance(matter_power_spectrum, emulators.EmulatorPk):
1✔
266
            self.nl_pk_emu = matter_power_spectrum
1✔
267
            self.matter_power_spectrum_type = "emulator"
1✔
268
        elif isinstance(matter_power_spectrum, str):
1✔
269
            self.matter_power_spectrum_type = matter_power_spectrum
1✔
270
        else:
271
            raise ValueError(f"matter_power_spectrum={matter_power_spectrum} "
1✔
272
                             f"not supported.")
273

274
        self.baryons = baryonic_effects
1✔
275
        if not isinstance(self.baryons, baryons.Baryons):
1✔
276
            if self.baryons is not None:
1✔
277
                raise ValueError("`baryonic_effects` must be `None` "
1✔
278
                                 "or a `Baryons` instance.")
279

280
        self.mg_parametrization = mg_parametrization
1✔
281
        if self.mg_parametrization is not None and not isinstance(
1✔
282
                self.mg_parametrization,
283
                modified_gravity.ModifiedGravity):
284
            raise ValueError("`mg_parametrization` must be `None` "
1✔
285
                             "or a `ModifiedGravity` instance.")
286

287
        if self.mg_parametrization is None:
1✔
288
            # Internally, CCL still relies exclusively on the mu-Sigma
289
            # parametrization, so we fill that in for now unless something
290
            # else is provided.
291
            self.mg_parametrization = modified_gravity.MuSigmaMG()
1✔
292
        if not isinstance(
1✔
293
                self.mg_parametrization,
294
                modified_gravity.MuSigmaMG):
295
            raise NotImplementedError("`mg_parametrization` only supports the "
1✔
296
                                      "mu-Sigma parametrization at this point")
297

298
        # going to save these for later
299
        self._params_init_kwargs = dict(
1✔
300
            Omega_c=Omega_c, Omega_b=Omega_b, h=h, n_s=n_s, sigma8=sigma8,
301
            A_s=A_s, Omega_k=Omega_k, Omega_g=Omega_g, Neff=Neff, m_nu=m_nu,
302
            mass_split=mass_split, w0=w0, wa=wa, T_CMB=T_CMB, T_ncdm=T_ncdm,
303
            extra_parameters=extra_parameters)
304

305
        self._config_init_kwargs = dict(
1✔
306
            transfer_function=transfer_function,
307
            matter_power_spectrum=matter_power_spectrum,
308
            baryonic_effects=baryonic_effects,
309
            mg_parametrization=mg_parametrization,
310
            extra_parameters=extra_parameters)
311

312
        self._build_cosmo()
1✔
313

314
        self._pk_lin = {}
1✔
315
        self._pk_nl = {}
1✔
316

317
    def _build_cosmo(self):
1✔
318
        """Assemble all of the input data into a valid ccl_cosmology object."""
319
        # We have to make all of the C stuff that goes into a cosmology
320
        # and then we make the cosmology.
321
        self._build_parameters(**self._params_init_kwargs)
1✔
322
        self._build_config(**self._config_init_kwargs)
1✔
323
        self.cosmo = lib.cosmology_create(self._params, self._config)
1✔
324
        self.data = _CosmologyBackgroundData()
1✔
325
        self._spline_params = CCLParameters.get_params_dict("spline_params")
1✔
326
        self._gsl_params = CCLParameters.get_params_dict("gsl_params")
1✔
327
        self._accuracy_params = {**self._spline_params, **self._gsl_params}
1✔
328

329
        if self.cosmo.status != 0:
1✔
330
            raise CCLError(f"{self.cosmo.status}: {self.cosmo.status_message}")
×
331

332
    def to_dict(self):
1✔
333
        """Returns a dictionary of the arguments used to create the Cosmology
334
        object such that ``cosmo == pyccl.Cosmology(**cosmo.to_dict())``
335
        is ``True``."""
336
        return {**self._params_init_kwargs, **self._config_init_kwargs}
1✔
337

338
    def write_yaml(self, filename, *, sort_keys=False):
1✔
339
        """Write a YAML representation of the parameters to file.
340

341
        Args:
342
            filename (:obj:`str`): file name, file pointer, or stream to write
343
                parameters to.
344
        """
345
        params = _make_yaml_friendly(self.to_dict())
1✔
346

347
        if isinstance(filename, str):
1✔
348
            with open(filename, "w") as fp:
1✔
349
                return yaml.dump(params, fp, sort_keys=sort_keys)
1✔
350
        return yaml.dump(params, filename, sort_keys=sort_keys)
1✔
351

352
    @classmethod
1✔
353
    def read_yaml(cls, filename, **kwargs):
1✔
354
        """Read the parameters from a YAML file.
355

356
        Args:
357
            filename (:obj:`str`): file name, file pointer, or stream to read
358
                parameters from.
359
            **kwargs (:obj:`dict`): additional keywords that supersede
360
                file contents
361
        """
362
        loader = yaml.Loader
1✔
363
        if isinstance(filename, str):
1✔
364
            with open(filename, 'r') as fp:
1✔
365
                params = yaml.load(fp, Loader=loader)
1✔
366
        else:
367
            params = yaml.load(filename, Loader=loader)
1✔
368
        return cls(**{**params, **kwargs})
1✔
369

370
    def _build_config(
1✔
371
            self, *, transfer_function=None, matter_power_spectrum=None,
372
            **kwargs):
373
        """Build a ccl_configuration struct.
374

375
        This function builds C ccl_configuration struct. This structure
376
        controls which various approximations are used for the transfer
377
        function, matter power spectrum, and baryonic effect in the matter
378
        power spectrum.
379

380
        It also does some error checking on the inputs to make sure they
381
        are valid and physically consistent.
382
        """
383
        if (matter_power_spectrum == "camb"
1✔
384
                and transfer_function != "boltzmann_camb"):
385
            raise CCLError(
1✔
386
                "To compute the non-linear matter power spectrum with CAMB "
387
                "the transfer function should be 'boltzmann_camb'.")
388

389
        config = lib.configuration()
1✔
390
        tf = transfer_function_types[self.transfer_function_type]
1✔
391
        config.transfer_function_method = tf
1✔
392
        mps = matter_power_spectrum_types[self.matter_power_spectrum_type]
1✔
393
        config.matter_power_spectrum_method = mps
1✔
394

395
        # Store ccl_configuration for later access
396
        self._config = config
1✔
397

398
    def _build_parameters(
1✔
399
            self, Omega_c=None, Omega_b=None, h=None, n_s=None, sigma8=None,
400
            A_s=None, Omega_k=None, Neff=None, m_nu=None, mass_split=None,
401
            w0=None, wa=None, T_CMB=None, T_ncdm=None,
402
            Omega_g=None, extra_parameters=None):
403
        """Build a ccl_parameters struct"""
404
        # Fill-in defaults (SWIG converts `numpy.nan` to `NAN`)
405
        A_s = np.nan if A_s is None else A_s
1✔
406
        sigma8 = np.nan if sigma8 is None else sigma8
1✔
407
        Omega_g = np.nan if Omega_g is None else Omega_g
1✔
408

409
        # Check to make sure Omega_k is within reasonable bounds.
410
        if Omega_k < -1.0135:
1✔
411
            raise ValueError("Omega_k must be more than -1.0135.")
1✔
412

413
        # Check to make sure specified amplitude parameter is consistent.
414
        if [A_s, sigma8].count(np.nan) != 1:
1✔
415
            raise ValueError("Set either A_s or sigma8 but not both.")
1✔
416

417
        # Check if any compulsory parameters are not set.
418
        compul = {"Omega_c": Omega_c, "Omega_b": Omega_b, "h": h, "n_s": n_s}
1✔
419
        for param, value in compul.items():
1✔
420
            if value is None:
1✔
421
                raise ValueError(f"Must set parameter {param}.")
1✔
422

423
        # Make sure the neutrino parameters are consistent.
424
        if not isinstance(m_nu, (Real, Iterable)):
1✔
425
            raise ValueError("m_nu must be float or sequence")
1✔
426

427
        c = const
1✔
428
        # Initialize curvature.
429
        k_sign = -np.sign(Omega_k) if np.abs(Omega_k) > 1e-6 else 0
1✔
430
        sqrtk = np.sqrt(np.abs(Omega_k)) * h / c.CLIGHT_HMPC
1✔
431

432
        # Initialize radiation.
433
        rho_g = 4 * c.STBOLTZ / c.CLIGHT**3 * T_CMB**4
1✔
434
        rho_crit = c.RHO_CRITICAL * c.SOLAR_MASS / c.MPC_TO_METER**3 * h**2
1✔
435

436
        # Initialize neutrinos.
437
        g = (4/11)**(1/3)
1✔
438
        T_nu = g * T_CMB
1✔
439
        massless_limit = T_nu * c.KBOLTZ / c.EV_IN_J
1✔
440

441
        from .neutrinos import nu_masses
1✔
442
        mnu_list = nu_masses(m_nu=m_nu, mass_split=mass_split)
1✔
443
        nu_mass = mnu_list[mnu_list > massless_limit]
1✔
444
        N_nu_mass = len(nu_mass)
1✔
445
        N_nu_rel = Neff - N_nu_mass * (T_ncdm/g)**4
1✔
446

447
        rho_nu_rel = N_nu_rel * (7/8) * 4 * c.STBOLTZ / c.CLIGHT**3 * T_nu**4
1✔
448
        Omega_nu_rel = rho_nu_rel / rho_crit
1✔
449
        Omega_nu_mass = self._OmNuh2(nu_mass, N_nu_mass, T_CMB, T_ncdm) / h**2
1✔
450

451
        if N_nu_rel < 0:
1✔
452
            raise ValueError("Unphysical Neff and m_nu combination results to "
1✔
453
                             "negative number of relativistic neutrinos.")
454

455
        # Initialize matter & dark energy.
456
        Omega_m = Omega_b + Omega_c + Omega_nu_mass
1✔
457
        Omega_l = 1 - Omega_m - rho_g/rho_crit - Omega_nu_rel - Omega_k
1✔
458
        if np.isnan(Omega_g):
1✔
459
            # No value passed for Omega_g
460
            Omega_g = rho_g/rho_crit
1✔
461
        else:
462
            # Omega_g was passed - modify Omega_l
463
            Omega_l += rho_g/rho_crit - Omega_g
1✔
464

465
        # Take the mu-Sigma parameters from the modified_gravity container
466
        # object. This is the only supported MG parametrization at this time.
467
        assert isinstance(self.mg_parametrization, modified_gravity.MuSigmaMG)
1✔
468
        mu_0 = self.mg_parametrization.mu_0
1✔
469
        sigma_0 = self.mg_parametrization.sigma_0
1✔
470
        c1_mg = self.mg_parametrization.c1_mg
1✔
471
        c2_mg = self.mg_parametrization.c2_mg
1✔
472
        lambda_mg = self.mg_parametrization.lambda_mg
1✔
473

474
        self._fill_params(
1✔
475
            m_nu=nu_mass, sum_nu_masses=sum(nu_mass), N_nu_mass=N_nu_mass,
476
            N_nu_rel=N_nu_rel, Neff=Neff, Omega_nu_mass=Omega_nu_mass,
477
            Omega_nu_rel=Omega_nu_rel, Omega_m=Omega_m, Omega_c=Omega_c,
478
            Omega_b=Omega_b, Omega_k=Omega_k, sqrtk=sqrtk, k_sign=int(k_sign),
479
            T_CMB=T_CMB, T_ncdm=T_ncdm, Omega_g=Omega_g, w0=w0, wa=wa,
480
            Omega_l=Omega_l, h=h, H0=h*100, A_s=A_s, sigma8=sigma8, n_s=n_s,
481
            mu_0=mu_0, sigma_0=sigma_0, c1_mg=c1_mg, c2_mg=c2_mg,
482
            lambda_mg=lambda_mg)
483

484
    def _OmNuh2(self, m_nu, N_nu_mass, T_CMB, T_ncdm):
1✔
485
        # Compute OmNuh2 today.
486
        ret, st = lib.Omeganuh2_vec(N_nu_mass, T_CMB, T_ncdm, [1], m_nu, 1, 0)
1✔
487
        check(st)
1✔
488
        return ret[0]
1✔
489

490
    def _fill_params(self, **kwargs):
1✔
491
        if not hasattr(self, "_params"):
1✔
492
            self._params = CosmologyParams()
1✔
493
        [setattr(self._params, par, val) for par, val in kwargs.items()]
1✔
494

495
    def __getitem__(self, key):
1✔
496
        if key == 'extra_parameters':
1✔
497
            return self._params_init_kwargs["extra_parameters"]
1✔
498
        return getattr(self._params, key)
1✔
499

500
    def __del__(self):
1✔
501
        """Free the C memory this object is managing as it is being garbage
502
        collected (hopefully)."""
503
        if hasattr(self, "cosmo"):
1✔
504
            lib.cosmology_free(self.cosmo)
1✔
505
            delattr(self, "cosmo")
1✔
506
        if hasattr(self, "_params"):
1✔
507
            lib.parameters_free(self._params)
1✔
508
            delattr(self, "_params")
1✔
509

510
    def __enter__(self):
1✔
511
        return self
1✔
512

513
    def __exit__(self, type, value, traceback):
1✔
514
        """Free the C memory this object is managing when the context manager
515
        exits."""
516
        self.__del__()
1✔
517

518
    def __getstate__(self):
1✔
519
        # we are removing any C data before pickling so that the
520
        # is pure python when pickled.
521
        state = self.__dict__.copy()
1✔
522
        state.pop('cosmo', None)
1✔
523
        state.pop('_params', None)
1✔
524
        state.pop('_config', None)
1✔
525
        return state
1✔
526

527
    def __setstate__(self, state):
1✔
528
        # This will create a new `Cosmology` object so we create another lock.
529
        state["_object_lock"] = type(state.pop("_object_lock"))()
1✔
530
        self.__dict__ = state
1✔
531
        # we removed the C data when it was pickled, so now we unpickle
532
        # and rebuild the C data
533
        self._build_cosmo()
1✔
534
        self._object_lock.lock()  # Lock on exit.
1✔
535

536
    def compute_growth(self):
1✔
537
        """Compute the growth function."""
538
        if self.has_growth:
1✔
539
            return
1✔
540
        status = 0
1✔
541
        status = lib.cosmology_compute_growth(self.cosmo, status)
1✔
542
        check(status, self)
1✔
543

544
    def _compute_linear_power(self):
1✔
545
        """Return the linear power spectrum."""
546
        self.compute_growth()
1✔
547

548
        # Populate power spectrum splines
549
        trf = self.transfer_function_type
1✔
550
        pk = None
1✔
551
        rescale_s8 = True
1✔
552
        rescale_mg = True
1✔
553
        if trf == "boltzmann_camb":
1✔
554
            rescale_s8 = False
1✔
555
            # For MG, the input sigma8 includes the effects of MG, while the
556
            # sigma8 that CAMB uses is the GR definition. So we need to rescale
557
            # sigma8 afterwards.
558
            if self.mg_parametrization.mu_0 != 0:
1✔
559
                rescale_s8 = True
1✔
560
        elif trf == 'boltzmann_class':
1✔
561
            pk = self.get_class_pk_lin()
1✔
562
        elif trf == 'boltzmann_isitgr':
1✔
563
            rescale_mg = False
1✔
564
            pk = self.get_isitgr_pk_lin()
1✔
565
        elif trf in ['bbks', 'eisenstein_hu', 'eisenstein_hu_nowiggles']:
1✔
566
            rescale_s8 = False
1✔
567
            rescale_mg = False
1✔
568
            pk = Pk2D.from_model(self, model=trf)
1✔
569
        elif trf == 'emulator':
1✔
570
            rescale_s8 = False
1✔
571
            pk = self.lin_pk_emu.get_pk2d(self)
1✔
572

573
        # Compute the CAMB nonlin power spectrum if needed,
574
        # to avoid repeating the code in `compute_nonlin_power`.
575
        # Because CAMB power spectra come in pairs with pkl always computed,
576
        # we set the nonlin power spectrum first, but keep the linear via a
577
        # status variable to use it later if the transfer function is CAMB too.
578
        pkl = None
1✔
579
        if self.matter_power_spectrum_type == "camb":
1✔
580
            rescale_mg = False
1✔
581
            if self.mg_parametrization.mu_0 != 0:
1✔
582
                raise ValueError("Can't rescale non-linear power spectrum "
1✔
583
                                 "from CAMB for mu-Sigma MG.")
584
            name = "delta_matter:delta_matter"
1✔
585
            pkl, self._pk_nl[name] = self.get_camb_pk_lin(nonlin=True)
1✔
586

587
        if trf == "boltzmann_camb":
1✔
588
            pk = pkl if pkl is not None else self.get_camb_pk_lin()
1✔
589

590
        # Rescale by sigma8/mu-sigma if needed
591
        if pk:
1✔
592
            status = 0
1✔
593
            status = lib.rescale_linpower(self.cosmo, pk.psp,
1✔
594
                                          int(rescale_mg),
595
                                          int(rescale_s8),
596
                                          status)
597
            check(status, self)
1✔
598

599
        return pk
1✔
600

601
    @unlock_instance(mutate=False)
1✔
602
    def compute_linear_power(self):
1✔
603
        """Compute the linear power spectrum."""
604
        if self.has_linear_power:
1✔
605
            return
1✔
606
        self._pk_lin[DEFAULT_POWER_SPECTRUM] = self._compute_linear_power()
1✔
607

608
    def _compute_nonlin_power(self):
1✔
609
        """Return the non-linear power spectrum."""
610
        self.compute_distances()
1✔
611

612
        # Populate power spectrum splines
613
        mps = self.matter_power_spectrum_type
1✔
614
        # needed for halofit, and linear options
615
        if (mps not in ['emulator']) and (mps is not None):
1✔
616
            self.compute_linear_power()
1✔
617

618
        if mps == "camb" and self.has_nonlin_power:
1✔
619
            # Already computed
620
            return self._pk_nl[DEFAULT_POWER_SPECTRUM]
1✔
621

622
        if mps == 'halofit':
1✔
623
            pkl = self._pk_lin[DEFAULT_POWER_SPECTRUM]
1✔
624
            pk = pkl.apply_halofit(self)
1✔
625
        elif mps == 'linear':
1✔
626
            pk = self._pk_lin[DEFAULT_POWER_SPECTRUM]
1✔
627
        elif mps == 'emulator':
1✔
628
            pk = self.nl_pk_emu.get_pk2d(self)
1✔
629

630
        # Include baryonic effects
631
        if self.baryons is not None:
1✔
632
            pk = self.baryons.include_baryonic_effects(self, pk)
1✔
633
        return pk
1✔
634

635
    @unlock_instance(mutate=False)
1✔
636
    def compute_nonlin_power(self):
1✔
637
        """Compute the non-linear power spectrum."""
638
        if self.has_nonlin_power:
1✔
639
            return
1✔
640
        self._pk_nl[DEFAULT_POWER_SPECTRUM] = self._compute_nonlin_power()
1✔
641

642
    def compute_sigma(self):
1✔
643
        """Compute the sigma(M) spline."""
644
        if self.has_sigma:
1✔
645
            return
1✔
646

647
        pk = self.get_linear_power()
1✔
648
        status = 0
1✔
649
        status = lib.cosmology_compute_sigma(self.cosmo, pk.psp, status)
1✔
650
        check(status, self)
1✔
651

652
    def get_linear_power(self, name=DEFAULT_POWER_SPECTRUM):
1✔
653
        """Get the :class:`~pyccl.pk2d.Pk2D` object associated with
654
        the linear power spectrum with name ``name``.
655

656
        Args:
657
            name (:obj:`str` or :obj:`None`): name of the power spectrum to
658
                return.
659

660
        Returns:
661
            :class:`~pyccl.pk2d.Pk2D` object containing the linear
662
            power spectrum with name `name`.
663
        """
664
        if name == DEFAULT_POWER_SPECTRUM:
1✔
665
            self.compute_linear_power()
1✔
666
        pk = self._pk_lin.get(name)
1✔
667
        if pk is None:
1✔
668
            raise KeyError(f"Power spectrum {name} does not exist.")
1✔
669
        return pk
1✔
670

671
    def get_nonlin_power(self, name=DEFAULT_POWER_SPECTRUM):
1✔
672
        """Get the :class:`~pyccl.pk2d.Pk2D` object associated with
673
        the non-linear power spectrum with name ``name``.
674

675
        Args:
676
            name (:obj:`str` or :obj:`None`): name of the power spectrum to
677
                return.
678

679
        Returns:
680
            :class:`~pyccl.pk2d.Pk2D` object containing the non-linear
681
            power spectrum with name ``name``.
682
        """
683
        if name == DEFAULT_POWER_SPECTRUM:
1✔
684
            self.compute_nonlin_power()
1✔
685
        pk = self._pk_nl.get(name)
1✔
686
        if pk is None:
1✔
687
            raise KeyError(f"Power spectrum {name} does not exist.")
1✔
688
        return pk
1✔
689

690
    @property
1✔
691
    def has_distances(self):
1✔
692
        """Checks if the distances have been precomputed."""
693
        return bool(self.cosmo.computed_distances)
1✔
694

695
    @property
1✔
696
    def has_growth(self):
1✔
697
        """Checks if the growth function has been precomputed."""
698
        return bool(self.cosmo.computed_growth)
1✔
699

700
    @property
1✔
701
    def has_linear_power(self):
1✔
702
        """Checks if the linear power spectra have been precomputed."""
703
        return DEFAULT_POWER_SPECTRUM in self._pk_lin
1✔
704

705
    @property
1✔
706
    def has_nonlin_power(self):
1✔
707
        """Checks if the non-linear power spectra have been precomputed."""
708
        return DEFAULT_POWER_SPECTRUM in self._pk_nl
1✔
709

710
    @property
1✔
711
    def has_sigma(self):
1✔
712
        """Checks if sigma(M) is precomputed."""
713
        return bool(self.cosmo.computed_sigma)
1✔
714

715

716
def CosmologyVanillaLCDM(**kwargs):
1✔
717
    """A cosmology with typical flat Lambda-CDM parameters (`Omega_c=0.25`,
718
    `Omega_b = 0.05`, `Omega_k = 0`, `sigma8 = 0.81`, `n_s = 0.96`, `h = 0.67`,
719
    no massive neutrinos) for quick instantiation.
720

721
    Arguments:
722
        **kwargs (:obj:`dict`): a dictionary of parameters passed as arguments
723
            to the :class:`Cosmology` constructor. It should not contain any of
724
            the :math:`\\Lambda`-CDM parameters (`"Omega_c"`, `"Omega_b"`,
725
            `"n_s"`, `"sigma8"`, `"A_s"`, `"h"`), since these are fixed.
726
    """
727
    p = {'Omega_c': 0.25,
1✔
728
         'Omega_b': 0.05,
729
         'h': 0.67,
730
         'n_s': 0.96,
731
         'sigma8': 0.81,
732
         'A_s': None}
733
    if set(p).intersection(set(kwargs)):
1✔
734
        raise ValueError(
1✔
735
            f"You cannot change the ΛCDM parameters: {list(p.keys())}.")
736
    # TODO py39+: dictionary union operator `(p | kwargs)`.
737
    return Cosmology(**{**p, **kwargs})
1✔
738

739

740
class CosmologyCalculator(Cosmology):
1✔
741
    """A "calculator-mode" CCL :class:`~Cosmology` object.
1✔
742
    This allows users to build a cosmology from a set of arrays
743
    describing the background expansion, linear growth factor and
744
    linear and non-linear power spectra, which can then be used
745
    to compute more complex observables (e.g. angular power
746
    spectra or halo-model quantities). These are stored in
747
    ``background``, ``growth``, ``pk_linear`` and ``pk_nonlin``.
748

749
    .. note:: Although in principle these arrays should suffice
750
              to compute most observable quantities some
751
              calculations implemented in CCL (e.g. the halo
752
              mass function) requires knowledge of basic
753
              cosmological parameters such as :math:`\\Omega_M`.
754
              For this reason, users must pass a minimal set
755
              of :math:`\\Lambda`-CDM cosmological parameters.
756

757
    Args:
758
        Omega_c (:obj:`float`): Cold dark matter density fraction.
759
        Omega_b (:obj:`float`): Baryonic matter density fraction.
760
        h (:obj:`float`): Hubble constant divided by 100 km/s/Mpc; unitless.
761
        A_s (:obj:`float`): Power spectrum normalization. Exactly one of A_s
762
            and sigma_8 is required.
763
        sigma8 (:obj:`float`): Variance of matter density perturbations at
764
            an 8 Mpc/h scale. Exactly one of A_s and sigma_8 is required.
765
        n_s (:obj:`float`): Primordial scalar perturbation spectral index.
766
        Omega_k (:obj:`float`): Curvature density fraction.
767
            Defaults to 0.
768
        Omega_g (:obj:`float`): Density in relativistic species
769
            except massless neutrinos. The default of `None` corresponds
770
            to setting this from the CMB temperature. Note that if a non-`None`
771
            value is given, this may result in a physically inconsistent model
772
            because the CMB temperature will still be non-zero in the
773
            parameters.
774
        Neff (:obj:`float`): Effective number of massless
775
            neutrinos present. Defaults to 3.044.
776
        m_nu (:obj:`float` or `array`):
777
            Mass in eV of the massive neutrinos present. Defaults to 0.
778
            If a sequence is passed, it is assumed that the elements of the
779
            sequence represent the individual neutrino masses.
780
        mass_split (:obj:`str`): Type of massive neutrinos. Should
781
            be one of 'single', 'equal', 'normal', 'inverted'. 'single' treats
782
            the mass as being held by one massive neutrino. The other options
783
            split the mass into 3 massive neutrinos. Ignored if a sequence is
784
            passed in ``m_nu``. Default is 'normal'.
785
        w0 (:obj:`float`): First order term of dark energy equation
786
            of state. Defaults to -1.
787
        wa (:obj:`float`): Second order term of dark energy equation
788
            of state. Defaults to 0.
789
        T_CMB (:obj:`float`): The CMB temperature today. The default value
790
            is 2.7255.
791
        T_ncdm (:obj:`float`): Non-CDM temperature in units of photon
792
            temperature. The default is the same as in the base class
793
        mg_parametrization (:class:`~pyccl.modified_gravity.modified_gravity_base.ModifiedGravity` or `None`):
794
            The modified gravity parametrization to use. Options are `None`
795
            (no MG), or a :class:`~pyccl.modified_gravity.modified_gravity_base.ModifiedGravity`
796
            object. Currently, only :class:`~pyccl.modified_gravity.mu_Sigma.MuSigmaMG`
797
            is supported.
798
        background (:obj:`dict`): a dictionary describing the background
799
            expansion. It must contain three mandatory entries: ``'a'``: an
800
            array of monotonically ascending scale-factor values. ``'chi'``:
801
            an array containing the values of the comoving radial distance
802
            (in units of Mpc) at the scale factor values stored in ``a``.
803
            '``h_over_h0``': an array containing the Hubble expansion rate at
804
            the scale factor values stored in ``a``, divided by its value
805
            today (at ``a=1``).
806
        growth (:obj:`dict`): a dictionary describing the linear growth of
807
            matter fluctuations. It must contain three mandatory entries:
808
            ``'a'``: an array of monotonically ascending scale-factor
809
            values. ``'growth_factor'``: an array containing the values of
810
            the linear growth factor :math:`D(a)` at the scale factor
811
            values stored in ``a``. '``growth_rate``': an array containing the
812
            growth rate :math:`f(a)\\equiv d\\log D/d\\log a` at the scale
813
            factor values stored in ``a``.
814
        pk_linear (:obj:`dict`): a dictionary containing linear power
815
            spectra. It must contain the following mandatory entries:
816
            ``'a'``: an array of scale factor values. ``'k'``: an array of
817
            comoving wavenumbers in units of inverse Mpc.
818
            ``'delta_matter:delta_matter'``: a 2D array of shape
819
            ``(n_a, n_k)``, where ``n_a`` and ``n_k`` are the lengths of
820
            ``'a'`` and ``'k'`` respectively, containing the linear matter
821
            power spectrum :math:`P(k,a)`. This dictionary may also
822
            contain other entries with keys of the form ``'q1:q2'``,
823
            containing other cross-power spectra between quantities
824
            ``'q1'`` and ``'q2'``.
825
        pk_nonlin (:obj:`dict`): a dictionary containing non-linear
826
            power spectra. It must contain the following mandatory
827
            entries: ``'a'``: an array of scale factor values.
828
            ``'k'``: an array of comoving wavenumbers in units of
829
            inverse Mpc. If ``nonlinear_model`` is ``None``, it should also
830
            contain ``'delta_matter:delta_matter'``: a 2D array of
831
            shape ``(n_a, n_k)``, where ``n_a`` and ``n_k`` are the lengths
832
            of ``'a'`` and ``'k'`` respectively, containing the non-linear
833
            matter power spectrum :math:`P(k,a)`. This dictionary may
834
            also contain other entries with keys of the form ``'q1:q2'``,
835
            containing other cross-power spectra between quantities
836
            ``'q1'`` and ``'q2'``.
837
        nonlinear_model (:obj:`str`, :obj:`dict` or :obj:`None`): model to
838
            compute non-linear power spectra. If a string, the associated
839
            non-linear model will be applied to all entries in ``pk_linear``
840
            which do not appear in ``pk_nonlin``. If a dictionary, it should
841
            contain entries of the form ``'q1:q2': model``, where ``model``
842
            is a string designating the non-linear model to apply to the
843
            ``'q1:q2'`` power spectrum, which must also be present in
844
            ``pk_linear``. If ``model`` is ``None``, this non-linear power
845
            spectrum will not be calculated. If ``nonlinear_model`` is
846
            ``None``, no additional non-linear power spectra will be
847
            computed. The only non-linear model supported is ``'halofit'``,
848
            corresponding to the "HALOFIT" transformation of
849
            `Takahashi et al. 2012 <https://arxiv.org/abs/1208.2701>`_.
850
    """ # noqa
851
    __eq_attrs__ = ("_params_init_kwargs", "_config_init_kwargs",
1✔
852
                    "_accuracy_params", "_input_arrays",)
853

854
    def __init__(
1✔
855
            self, *, Omega_c=None, Omega_b=None, h=None, n_s=None,
856
            sigma8=None, A_s=None, Omega_k=0., Omega_g=None,
857
            Neff=None, m_nu=0., mass_split="normal", w0=-1., wa=0.,
858
            T_CMB=DefaultParams.T_CMB, T_ncdm=DefaultParams.T_ncdm,
859
            mg_parametrization=None, background=None, growth=None,
860
            pk_linear=None, pk_nonlin=None, nonlinear_model=None):
861

862
        super().__init__(
1✔
863
            Omega_c=Omega_c, Omega_b=Omega_b, h=h, n_s=n_s, sigma8=sigma8,
864
            A_s=A_s, Omega_k=Omega_k, Omega_g=Omega_g, Neff=Neff, m_nu=m_nu,
865
            mass_split=mass_split, w0=w0, wa=wa, T_CMB=T_CMB, T_ncdm=T_ncdm,
866
            mg_parametrization=mg_parametrization,
867
            transfer_function="calculator", matter_power_spectrum="calculator")
868

869
        self._input_arrays = {"background": background, "growth": growth,
1✔
870
                              "pk_linear": pk_linear, "pk_nonlin": pk_nonlin,
871
                              "nonlinear_model": nonlinear_model}
872

873
        if background is not None:
1✔
874
            self._init_background(background)
1✔
875
        if growth is not None:
1✔
876
            self._init_growth(growth)
1✔
877
        if pk_linear is not None:
1✔
878
            self._init_pk_linear(pk_linear)
1✔
879
        if pk_nonlin is not None:
1✔
880
            self._init_pk_nonlinear(pk_nonlin, nonlinear_model)
1✔
881
        self._apply_nonlinear_model(nonlinear_model)
1✔
882

883
    def _check_scale_factor(self, a):
1✔
884
        if not (np.diff(a) > 0).all():
1✔
885
            raise ValueError("Scale factor not monotonically increasing.")
1✔
886
        if np.abs(a[-1] - 1) > 1e-5:
1✔
887
            raise ValueError("Scale factor should end at 1.")
1✔
888

889
    def _check_input(self, a, arr1, arr2):
1✔
890
        self._check_scale_factor(a)
1✔
891
        if not a.shape == arr1.shape == arr2.shape:
1✔
892
            raise ValueError("Shape mismatch of input arrays.")
1✔
893

894
    def _check_label(self, name):
1✔
895
        if len(name.split(":")) != 2:
1✔
896
            raise ValueError(f"Could not parse power spectrum {name}. "
×
897
                             "Label must be of the form 'q1:q2'.")
898

899
    def _init_background(self, background):
1✔
900
        a, chi, E = background["a"], background["chi"], background["h_over_h0"]
1✔
901
        self._check_input(a, chi, E)
1✔
902
        status = 0
1✔
903
        status = lib.cosmology_distances_from_input(self.cosmo, a, chi, E,
1✔
904
                                                    status)
905
        check(status, self)
1✔
906

907
    def _init_growth(self, growth):
1✔
908
        a, gz, fz = growth["a"], growth["growth_factor"], growth["growth_rate"]
1✔
909
        self._check_input(a, gz, fz)
1✔
910
        status = 0
1✔
911
        status = lib.cosmology_growth_from_input(self.cosmo, a, gz, fz, status)
1✔
912
        check(status, self)
1✔
913

914
    def _init_pk_linear(self, pk_linear):
1✔
915
        a, lk = pk_linear["a"], np.log(pk_linear["k"])
1✔
916
        self._check_scale_factor(a)
1✔
917
        self.compute_growth()  # needed for high-z extrapolation
1✔
918
        na, nk = a.size, lk.size
1✔
919

920
        if DEFAULT_POWER_SPECTRUM not in pk_linear:
1✔
921
            raise ValueError("pk_linear does not contain "
1✔
922
                             f"{DEFAULT_POWER_SPECTRUM}")
923

924
        pk_names = set(pk_linear.keys()) - set(["a", "k"])
1✔
925
        for name in pk_names:
1✔
926
            self._check_label(name)
1✔
927
            pk = pk_linear[name]
1✔
928
            if pk.shape != (na, nk):
1✔
929
                raise ValueError("Power spectrum shape mismatch. "
1✔
930
                                 f"Expected {(na, nk)}. Received {pk.shape}.")
931
            # Spline in log-space if the P(k) is positive-definite
932
            use_log = (pk > 0).all()
1✔
933
            if use_log:
1✔
934
                pk = np.log(pk)
1✔
935
            self._pk_lin[name] = Pk2D(a_arr=a, lk_arr=lk, pk_arr=pk,
1✔
936
                                      is_logp=use_log)
937

938
    def _init_pk_nonlinear(self, pk_nonlin, nonlinear_model):
1✔
939
        a, lk = pk_nonlin["a"], np.log(pk_nonlin["k"])
1✔
940
        self._check_scale_factor(a)
1✔
941
        na, nk = a.size, lk.size
1✔
942

943
        if DEFAULT_POWER_SPECTRUM not in pk_nonlin and nonlinear_model is None:
1✔
944
            raise ValueError(f"{DEFAULT_POWER_SPECTRUM} not specified in "
1✔
945
                             "`pk_nonlin` and `nonlinear_model` is None")
946

947
        pk_names = set(pk_nonlin.keys()) - set(["a", "k"])
1✔
948
        for name in pk_names:
1✔
949
            self._check_label(name)
1✔
950
            pk = pk_nonlin[name]
1✔
951
            if pk.shape != (na, nk):
1✔
952
                raise ValueError("Power spectrum shape mismatch. "
1✔
953
                                 f"Expected {(na, nk)}. Received {pk.shape}.")
954
            # Spline in log-space if the P(k) is positive-definite
955
            use_log = (pk > 0).all()
1✔
956
            if use_log:
1✔
957
                pk = np.log(pk)
1✔
958
            self._pk_nl[name] = Pk2D(a_arr=a, lk_arr=lk, pk_arr=pk,
1✔
959
                                     is_logp=use_log)
960

961
    def _apply_nonlinear_model(self, nonlin_model):
1✔
962
        if nonlin_model is None:
1✔
963
            return
1✔
964

965
        if not isinstance(nonlin_model, (str, dict)):
1✔
966
            raise ValueError("`nonlin_model` must be str, dict, or None.")
1✔
967
        if isinstance(nonlin_model, str) and not self.has_linear_power:
1✔
968
            raise ValueError("Linear power spectrum is empty.")
1✔
969

970
        if isinstance(nonlin_model, str):
1✔
971
            nonlin_model = {name: nonlin_model for name in self._pk_lin}
1✔
972

973
        for name, model in nonlin_model.items():
1✔
974
            if name in self._pk_nl:
1✔
975
                continue
1✔
976

977
            if name not in self._pk_lin:
1✔
978
                raise KeyError(f"Linear power spectrum {name} does not exist.")
1✔
979
            if model not in [None, "halofit"]:
1✔
980
                raise KeyError(f"{model} is not a valid non-linear model.")
1✔
981
            if name == DEFAULT_POWER_SPECTRUM and model is None:
1✔
982
                raise ValueError("The non-linear matter power spectrum must "
1✔
983
                                 "not be None.")
984

985
            if model == 'halofit':
1✔
986
                pkl = self._pk_lin[name]
1✔
987
                self._pk_nl[name] = pkl.apply_halofit(self)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc