diff --git a/0.10.0/fitk.html b/0.10.0/fitk.html index f2ca455..5ebc08f 100644 --- a/0.10.0/fitk.html +++ b/0.10.0/fitk.html @@ -99,8 +99,8 @@

Submodules

+ - diff --git a/0.10.0/fitk/graphics.html b/0.10.0/fitk/graphics.html index f66762c..a136c72 100644 --- a/0.10.0/fitk/graphics.html +++ b/0.10.0/fitk/graphics.html @@ -139,8 +139,8 @@

API Documentation

+ - diff --git a/0.10.0/fitk/interfaces/camb_interfaces.html b/0.10.0/fitk/interfaces/camb_interfaces.html index 56de695..7018788 100644 --- a/0.10.0/fitk/interfaces/camb_interfaces.html +++ b/0.10.0/fitk/interfaces/camb_interfaces.html @@ -97,8 +97,8 @@

API Documentation

+ - diff --git a/0.10.0/fitk/interfaces/coffe_interfaces.html b/0.10.0/fitk/interfaces/coffe_interfaces.html index da1c9e8..fff23cb 100644 --- a/0.10.0/fitk/interfaces/coffe_interfaces.html +++ b/0.10.0/fitk/interfaces/coffe_interfaces.html @@ -139,8 +139,8 @@

API Documentation

+ - diff --git a/0.10.0/fitk/operations.html b/0.10.0/fitk/operations.html index c334c03..b7e988e 100644 --- a/0.10.0/fitk/operations.html +++ b/0.10.0/fitk/operations.html @@ -70,8 +70,8 @@

API Documentation

+ - diff --git a/0.10.0/fitk/utilities.html b/0.10.0/fitk/utilities.html index 35e79a4..7d8ae0e 100644 --- a/0.10.0/fitk/utilities.html +++ b/0.10.0/fitk/utilities.html @@ -184,8 +184,8 @@

API Documentation

+ - diff --git a/0.10.1/fitk/derivatives.html b/0.10.1/fitk/derivatives.html index 3b7b50e..8e5c96b 100644 --- a/0.10.1/fitk/derivatives.html +++ b/0.10.1/fitk/derivatives.html @@ -127,8 +127,8 @@

API Documentation

+ - diff --git a/0.10.1/fitk/interfaces.html b/0.10.1/fitk/interfaces.html index 298cbc5..1c7f7af 100644 --- a/0.10.1/fitk/interfaces.html +++ b/0.10.1/fitk/interfaces.html @@ -63,8 +63,8 @@

Submodules

+ - diff --git a/0.10.1/fitk/interfaces/classy_interfaces.html b/0.10.1/fitk/interfaces/classy_interfaces.html index b7f05df..24ec215 100644 --- a/0.10.1/fitk/interfaces/classy_interfaces.html +++ b/0.10.1/fitk/interfaces/classy_interfaces.html @@ -103,8 +103,8 @@

API Documentation

+ - diff --git a/0.10.1/fitk/interfaces/misc_interfaces.html b/0.10.1/fitk/interfaces/misc_interfaces.html index 1eda3a7..2612d8e 100644 --- a/0.10.1/fitk/interfaces/misc_interfaces.html +++ b/0.10.1/fitk/interfaces/misc_interfaces.html @@ -79,8 +79,8 @@

API Documentation

+ - diff --git a/0.10.1/fitk/tensors.html b/0.10.1/fitk/tensors.html index 6226117..acb1616 100644 --- a/0.10.1/fitk/tensors.html +++ b/0.10.1/fitk/tensors.html @@ -175,8 +175,8 @@

API Documentation

+ - diff --git a/0.10.2/fitk.html b/0.10.2/fitk.html index 810c642..0ccd28e 100644 --- a/0.10.2/fitk.html +++ b/0.10.2/fitk.html @@ -99,8 +99,8 @@

Submodules

+ - diff --git a/0.10.2/fitk/graphics.html b/0.10.2/fitk/graphics.html index 4cb23c7..a3aeb3d 100644 --- a/0.10.2/fitk/graphics.html +++ b/0.10.2/fitk/graphics.html @@ -139,8 +139,8 @@

API Documentation

+ - diff --git a/0.10.2/fitk/interfaces/camb_interfaces.html b/0.10.2/fitk/interfaces/camb_interfaces.html index 56de695..7018788 100644 --- a/0.10.2/fitk/interfaces/camb_interfaces.html +++ b/0.10.2/fitk/interfaces/camb_interfaces.html @@ -97,8 +97,8 @@

API Documentation

+ - diff --git a/0.10.2/fitk/interfaces/coffe_interfaces.html b/0.10.2/fitk/interfaces/coffe_interfaces.html index da1c9e8..fff23cb 100644 --- a/0.10.2/fitk/interfaces/coffe_interfaces.html +++ b/0.10.2/fitk/interfaces/coffe_interfaces.html @@ -139,8 +139,8 @@

API Documentation

+ - diff --git a/0.10.2/fitk/operations.html b/0.10.2/fitk/operations.html index c334c03..b7e988e 100644 --- a/0.10.2/fitk/operations.html +++ b/0.10.2/fitk/operations.html @@ -70,8 +70,8 @@

API Documentation

+ - diff --git a/0.10.2/fitk/utilities.html b/0.10.2/fitk/utilities.html index 35e79a4..7d8ae0e 100644 --- a/0.10.2/fitk/utilities.html +++ b/0.10.2/fitk/utilities.html @@ -184,8 +184,8 @@

API Documentation

+ - diff --git a/0.10.3/fitk/derivatives.html b/0.10.3/fitk/derivatives.html index 4d34b10..f104ae3 100644 --- a/0.10.3/fitk/derivatives.html +++ b/0.10.3/fitk/derivatives.html @@ -127,8 +127,8 @@

API Documentation

+ - diff --git a/0.10.3/fitk/interfaces.html b/0.10.3/fitk/interfaces.html index 06efac6..f9e6c77 100644 --- a/0.10.3/fitk/interfaces.html +++ b/0.10.3/fitk/interfaces.html @@ -63,8 +63,8 @@

Submodules

+ - diff --git a/0.10.3/fitk/interfaces/classy_interfaces.html b/0.10.3/fitk/interfaces/classy_interfaces.html index e0f96d8..e8256a6 100644 --- a/0.10.3/fitk/interfaces/classy_interfaces.html +++ b/0.10.3/fitk/interfaces/classy_interfaces.html @@ -103,8 +103,8 @@

API Documentation

+ - diff --git a/0.10.3/fitk/interfaces/misc_interfaces.html b/0.10.3/fitk/interfaces/misc_interfaces.html index 6d34d22..841e4aa 100644 --- a/0.10.3/fitk/interfaces/misc_interfaces.html +++ b/0.10.3/fitk/interfaces/misc_interfaces.html @@ -79,8 +79,8 @@

API Documentation

+ - diff --git a/0.10.3/fitk/tensors.html b/0.10.3/fitk/tensors.html index 07a7614..1e3d212 100644 --- a/0.10.3/fitk/tensors.html +++ b/0.10.3/fitk/tensors.html @@ -175,8 +175,8 @@

API Documentation

+ - diff --git a/0.10.4/fitk.html b/0.10.4/fitk.html index a9ffca8..0cda8eb 100644 --- a/0.10.4/fitk.html +++ b/0.10.4/fitk.html @@ -99,8 +99,8 @@

Submodules

+ - diff --git a/0.10.4/fitk/graphics.html b/0.10.4/fitk/graphics.html index 31dc76d..99f090a 100644 --- a/0.10.4/fitk/graphics.html +++ b/0.10.4/fitk/graphics.html @@ -220,8 +220,8 @@

API Documentation

+ - diff --git a/0.10.4/fitk/interfaces/camb_interfaces.html b/0.10.4/fitk/interfaces/camb_interfaces.html index ad576e6..0758e97 100644 --- a/0.10.4/fitk/interfaces/camb_interfaces.html +++ b/0.10.4/fitk/interfaces/camb_interfaces.html @@ -97,8 +97,8 @@

API Documentation

+ - diff --git a/0.10.4/fitk/interfaces/coffe_interfaces.html b/0.10.4/fitk/interfaces/coffe_interfaces.html index c23bc50..8312d68 100644 --- a/0.10.4/fitk/interfaces/coffe_interfaces.html +++ b/0.10.4/fitk/interfaces/coffe_interfaces.html @@ -139,8 +139,8 @@

API Documentation

+ - diff --git a/0.10.4/fitk/operations.html b/0.10.4/fitk/operations.html index 5569a64..b6ccb9d 100644 --- a/0.10.4/fitk/operations.html +++ b/0.10.4/fitk/operations.html @@ -70,8 +70,8 @@

API Documentation

+ - diff --git a/0.10.4/fitk/utilities.html b/0.10.4/fitk/utilities.html index 8e85e01..ca6aadb 100644 --- a/0.10.4/fitk/utilities.html +++ b/0.10.4/fitk/utilities.html @@ -184,8 +184,8 @@

API Documentation

+ - diff --git a/0.11.0/fitk/derivatives.html b/0.11.0/fitk/derivatives.html index bf169b4..8e575fa 100644 --- a/0.11.0/fitk/derivatives.html +++ b/0.11.0/fitk/derivatives.html @@ -127,8 +127,8 @@

API Documentation

+ - diff --git a/0.11.0/fitk/interfaces.html b/0.11.0/fitk/interfaces.html index eeac90b..9ff3bab 100644 --- a/0.11.0/fitk/interfaces.html +++ b/0.11.0/fitk/interfaces.html @@ -63,8 +63,8 @@

Submodules

+ - diff --git a/0.11.0/fitk/interfaces/classy_interfaces.html b/0.11.0/fitk/interfaces/classy_interfaces.html index d9f76e9..1e1df3b 100644 --- a/0.11.0/fitk/interfaces/classy_interfaces.html +++ b/0.11.0/fitk/interfaces/classy_interfaces.html @@ -118,8 +118,8 @@

API Documentation

+ - diff --git a/0.11.0/fitk/interfaces/misc_interfaces.html b/0.11.0/fitk/interfaces/misc_interfaces.html index a57a443..10f4b20 100644 --- a/0.11.0/fitk/interfaces/misc_interfaces.html +++ b/0.11.0/fitk/interfaces/misc_interfaces.html @@ -79,8 +79,8 @@

API Documentation

+ - diff --git a/0.11.0/fitk/tensors.html b/0.11.0/fitk/tensors.html index 1dcfb80..bf01f96 100644 --- a/0.11.0/fitk/tensors.html +++ b/0.11.0/fitk/tensors.html @@ -175,8 +175,8 @@

API Documentation

+ - diff --git a/0.11.dev1/fitk.html b/0.11.1/fitk.html similarity index 99% rename from 0.11.dev1/fitk.html rename to 0.11.1/fitk.html index 899a597..38ed313 100644 --- a/0.11.dev1/fitk.html +++ b/0.11.1/fitk.html @@ -99,8 +99,8 @@

Submodules

+ - diff --git a/0.11.dev1/fitk/graphics.html b/0.11.1/fitk/graphics.html similarity index 99% rename from 0.11.dev1/fitk/graphics.html rename to 0.11.1/fitk/graphics.html index 31dc76d..99f090a 100644 --- a/0.11.dev1/fitk/graphics.html +++ b/0.11.1/fitk/graphics.html @@ -220,8 +220,8 @@

API Documentation

+ - @@ -163,6 +163,13 @@

fitk.interfaces.coffe_interfaces +

CAMB

+ + +

Computation of custom derivatives

To define a new interface for computing derivatives, one should define a class @@ -265,68 +272,73 @@

Computation of custom derivatives

29 target="_blank" rel="noopener noreferrer">here</a>) 30- for documentation of available interfaces, see `fitk.interfaces.coffe_interfaces` 31 -32### Computation of custom derivatives -33To define a new interface for computing derivatives, one should define a class -34that inherits from `fitk.derivatives.FisherDerivative`, and implements -35either the `signal` or the `covariance` methods (or both); below outlines the -36steps to create an interface of your own using a minimal amount of code: -37 -38```python -39from __future__ import annotations # required for Python 3.7 -40import numpy as np -41from fitk import FisherDerivative +32#### <a href="https://github.com/cmbant/CAMB" target="_blank" rel="noopener noreferrer">CAMB</a> +33 +34- installable with `pip install fitk[camb]` +35- for documentation of available interfaces, see `fitk.interfaces.camb_interfaces` +36 +37### Computation of custom derivatives +38To define a new interface for computing derivatives, one should define a class +39that inherits from `fitk.derivatives.FisherDerivative`, and implements +40either the `signal` or the `covariance` methods (or both); below outlines the +41steps to create an interface of your own using a minimal amount of code: 42 -43# optional: if your code has a Python interface, you should import it here -44import mycode -45 -46class MyFisher(FisherDerivative): -47 # define a signal function, so you can compute -48 # derivatives w.r.t. it -49 def signal( -50 self, -51 *args: tuple[str, float], -52 **kwargs, -53 ): -54 for name, value in args: -55 # go through the parameters and set them -56 ... -57 # do the calculation using the external module, or some other means -58 ... -59 # the returned result _must_ be a 1-dimensional numpy array -60 return np.array([...]) -61 -62 # define a covariance function, so you can compute -63 # derivatives w.r.t. it -64 def covariance( -65 self, -66 *args: tuple[str, float], -67 **kwargs, -68 ): -69 for name, value in args: -70 # go through the parameters and set them -71 ... -72 # do the computation -73 ... -74 # the returned result _must_ be a 2-dimensional, square, numpy array, -75 # with the same number of elements as the output of the `signal` -76 # method -77 return np.array([...]) -78``` -79 -80The following are some general recommendations when creating a new interface: -81 -82- for ease of use, any additional parameters specifying the configuration for -83 the interface should be passed to the constructor (i.e. the ``__init__`` -84 method) -85- extra information (methods, members, custom parameters) should be documented -86 accordingly -87- if the external module could not be imported, rather than directly raising an -88 exception, it is preferable that instantiating a class from that module -89 raises an ``ImportError`` instead. A simple way to accomplish this is to wrap -90 the import in a ``try...except ImportError``, pass the success result (perhaps -91 stored as a boolean) to the constructor of the class, and then only raise the -92 ``ImportError`` there -93""" +43```python +44from __future__ import annotations # required for Python 3.7 +45import numpy as np +46from fitk import FisherDerivative +47 +48# optional: if your code has a Python interface, you should import it here +49import mycode +50 +51class MyFisher(FisherDerivative): +52 # define a signal function, so you can compute +53 # derivatives w.r.t. it +54 def signal( +55 self, +56 *args: tuple[str, float], +57 **kwargs, +58 ): +59 for name, value in args: +60 # go through the parameters and set them +61 ... +62 # do the calculation using the external module, or some other means +63 ... +64 # the returned result _must_ be a 1-dimensional numpy array +65 return np.array([...]) +66 +67 # define a covariance function, so you can compute +68 # derivatives w.r.t. it +69 def covariance( +70 self, +71 *args: tuple[str, float], +72 **kwargs, +73 ): +74 for name, value in args: +75 # go through the parameters and set them +76 ... +77 # do the computation +78 ... +79 # the returned result _must_ be a 2-dimensional, square, numpy array, +80 # with the same number of elements as the output of the `signal` +81 # method +82 return np.array([...]) +83``` +84 +85The following are some general recommendations when creating a new interface: +86 +87- for ease of use, any additional parameters specifying the configuration for +88 the interface should be passed to the constructor (i.e. the ``__init__`` +89 method) +90- extra information (methods, members, custom parameters) should be documented +91 accordingly +92- if the external module could not be imported, rather than directly raising an +93 exception, it is preferable that instantiating a class from that module +94 raises an ``ImportError`` instead. A simple way to accomplish this is to wrap +95 the import in a ``try...except ImportError``, pass the success result (perhaps +96 stored as a boolean) to the constructor of the class, and then only raise the +97 ``ImportError`` there +98""" diff --git a/0.11.dev1/fitk/interfaces/camb_interfaces.html b/0.11.1/fitk/interfaces/camb_interfaces.html similarity index 99% rename from 0.11.dev1/fitk/interfaces/camb_interfaces.html rename to 0.11.1/fitk/interfaces/camb_interfaces.html index ad576e6..0758e97 100644 --- a/0.11.dev1/fitk/interfaces/camb_interfaces.html +++ b/0.11.1/fitk/interfaces/camb_interfaces.html @@ -97,8 +97,8 @@

API Documentation

+ - @@ -231,324 +246,532 @@
Notes
30from abc import ABC 31from configparser import ConfigParser 32from functools import lru_cache - 33from pathlib import Path - 34from typing import Optional, Union - 35 - 36# third party imports - 37import numpy as np - 38from scipy.linalg import block_diag - 39 - 40try: - 41 import classy - 42except ImportError: - 43 IMPORT_SUCCESS = False - 44else: - 45 IMPORT_SUCCESS = True - 46 - 47# first party imports - 48from fitk.derivatives import FisherDerivative - 49 + 33from itertools import product + 34from pathlib import Path + 35from typing import Optional, Union + 36 + 37# third party imports + 38import numpy as np + 39from scipy.linalg import block_diag + 40 + 41try: + 42 import classy + 43except ImportError: + 44 IMPORT_SUCCESS = False + 45else: + 46 IMPORT_SUCCESS = True + 47 + 48# first party imports + 49from fitk.derivatives import FisherDerivative 50 - 51class ClassyBaseDerivative(ABC, FisherDerivative): - 52 r"""Base interface for CLASS.""" - 53 - 54 software_names = "classy" - 55 urls = dict(github="https://github.com/lesgourg/class_public") - 56 version = "1.0.0" - 57 authors = [ - 58 dict( - 59 name="Goran Jelic-Cizmek", - 60 email="goran.jelic-cizmek@unige.ch", - 61 ) - 62 ] - 63 __imported__ = IMPORT_SUCCESS - 64 - 65 def __init__( - 66 self, - 67 *args, - 68 config: Optional[dict] = None, - 69 **kwargs, - 70 ): - 71 """ - 72 Create an instance. - 73 - 74 Parameters - 75 ---------- - 76 config : dict, optional - 77 the CLASS configuration to use. All parameters are accepted. - 78 """ - 79 if not self.__imported__: - 80 raise ImportError( - 81 f"Unable to import the `{self.software_names}` module, " - 82 "please make sure it is installed; " - 83 f"for additional help, please consult one of the following URL(s): {self.urls}" - 84 ) - 85 - 86 self._config = config if config is not None else {} - 87 super().__init__(*args, **kwargs) - 88 self._run_classy = lru_cache(maxsize=None)(self._run_classy) # type: ignore - 89 - 90 @classmethod - 91 def from_file(cls, path: Union[str, Path]): - 92 r""" - 93 Load a CLASS configuration from a file. - 94 - 95 Parameters - 96 ---------- - 97 path : str or Path - 98 the path to the configuration file - 99 """ -100 contents = Path(path).read_text(encoding="utf-8").split("\n") -101 config = ConfigParser(inline_comment_prefixes=("#", ";")) -102 config.optionxform = lambda option: option # type: ignore -103 section = "default" -104 config.read_string(f"[{section}]" + "\n".join(contents)) -105 -106 final_config = { -107 key: value for key, value in dict(config[section]).items() if value -108 } -109 -110 return cls(config=final_config) -111 -112 @property -113 def config(self): -114 """ -115 Return the current configuration used for running CLASS. -116 -117 .. versionchanged:: 0.10.0 -118 member is now read-only -119 """ -120 return self._config -121 -122 def _parse_outputs(self): -123 raw_output = self.config.get("output", "") -124 -125 if isinstance(raw_output, (tuple, list, np.ndarray)): -126 outputs = raw_output -127 else: -128 outputs = {item.strip() for item in raw_output.split(",")} -129 -130 return { -131 "temperature": "tCl" in outputs, -132 "polarization": "pCl" in outputs, -133 } -134 -135 def _run_classy(self, *args): # pylint: disable=method-hidden -136 r""" -137 Run classy and returns the instance of it after computation. -138 -139 Notes -140 ----- -141 The method is cached using <a -142 href="https://docs.python.org/3/library/functools.html#functools.lru_cache" -143 target="_blank" rel="noreferrer noopener">``functools.lru_cache``</a> -144 """ -145 cosmo = classy.Class() # pylint: disable=c-extension-no-member -146 final_kwargs = {**self.config} -147 for name, value in args: -148 final_kwargs[name] = value -149 cosmo.set(final_kwargs) -150 cosmo.compute() -151 -152 return cosmo + 51 + 52class ClassyBaseDerivative(ABC, FisherDerivative): + 53 r"""Base interface for CLASS.""" + 54 + 55 software_names = "classy" + 56 urls = dict(github="https://github.com/lesgourg/class_public") + 57 version = "1.0.0" + 58 authors = [ + 59 dict( + 60 name="Goran Jelic-Cizmek", + 61 email="goran.jelic-cizmek@unige.ch", + 62 ) + 63 ] + 64 __imported__ = IMPORT_SUCCESS + 65 + 66 def __init__( + 67 self, + 68 *args, + 69 config: Optional[dict] = None, + 70 **kwargs, + 71 ): + 72 """ + 73 Create an instance. + 74 + 75 Parameters + 76 ---------- + 77 config : dict, optional + 78 the CLASS configuration to use. All parameters are accepted. + 79 """ + 80 if not self.__imported__: + 81 raise ImportError( + 82 f"Unable to import the `{self.software_names}` module, " + 83 "please make sure it is installed; " + 84 f"for additional help, please consult one of the following URL(s): {self.urls}" + 85 ) + 86 + 87 self._config = config if config is not None else {} + 88 super().__init__(*args, **kwargs) + 89 self._run_classy = lru_cache(maxsize=None)(self._run_classy) # type: ignore + 90 + 91 @classmethod + 92 def from_file(cls, path: Union[str, Path]): + 93 r""" + 94 Load a CLASS configuration from a file. + 95 + 96 Parameters + 97 ---------- + 98 path : str or Path + 99 the path to the configuration file +100 """ +101 contents = Path(path).read_text(encoding="utf-8").split("\n") +102 config = ConfigParser(inline_comment_prefixes=("#", ";")) +103 config.optionxform = lambda option: option # type: ignore +104 section = "default" +105 config.read_string(f"[{section}]" + "\n".join(contents)) +106 +107 final_config = { +108 key: value for key, value in dict(config[section]).items() if value +109 } +110 +111 return cls(config=final_config) +112 +113 @property +114 def config(self): +115 """ +116 Return the current configuration used for running CLASS. +117 +118 .. versionchanged:: 0.10.0 +119 member is now read-only +120 """ +121 return self._config +122 +123 def _parse_outputs(self): +124 raw_output = self.config.get("output", "") +125 +126 if isinstance(raw_output, (tuple, list, np.ndarray)): +127 outputs = raw_output +128 else: +129 outputs = {item.strip() for item in raw_output.split(",")} +130 +131 return { +132 "temperature": "tCl" in outputs, +133 "polarization": "pCl" in outputs, +134 "galaxy_counts": "nCl" in outputs or "dCl" in outputs, +135 } +136 +137 def _run_classy(self, *args): # pylint: disable=method-hidden +138 r""" +139 Run classy and returns the instance of it after computation. +140 +141 Notes +142 ----- +143 The method is cached using <a +144 href="https://docs.python.org/3/library/functools.html#functools.lru_cache" +145 target="_blank" rel="noreferrer noopener">``functools.lru_cache``</a> +146 """ +147 cosmo = classy.Class() # pylint: disable=c-extension-no-member +148 final_kwargs = {**self.config} +149 for name, value in args: +150 final_kwargs[name] = value +151 cosmo.set(final_kwargs) +152 cosmo.compute() 153 -154 def _parse_covariance_kwargs(self, **kwargs): -155 """Parse any keyword arguments for the covariance.""" -156 final_kwargs = {} -157 final_kwargs["fsky"] = float(kwargs.pop("fsky", 1)) -158 final_kwargs["delta_ell"] = int( -159 kwargs.pop("delta_ell", 2 / final_kwargs["fsky"]) -160 ) -161 -162 return final_kwargs +154 return cosmo +155 +156 def _parse_covariance_kwargs(self, **kwargs): +157 """Parse any keyword arguments for the covariance.""" +158 final_kwargs = {} +159 final_kwargs["fsky"] = float(kwargs.pop("fsky", 1)) +160 final_kwargs["delta_ell"] = round( +161 kwargs.pop("delta_ell", 2 / final_kwargs["fsky"]) +162 ) 163 -164 -165class ClassyCMBDerivative(ClassyBaseDerivative): -166 """ -167 Interface for CMB quantities. -168 -169 Interface for computing derivatives using the CMB signal and covariance -170 (temperature, polarization, or both). -171 -172 .. versionadded:: 0.9.2 -173 """ -174 -175 def __init__( -176 self, -177 *args, -178 config: Optional[dict] = None, -179 **kwargs, -180 ): -181 """ -182 Create an instance. -183 -184 Parameters -185 ---------- -186 config : dict, optional -187 the CLASS configuration to use. All parameters are accepted. -188 If not specified, defaults to ``{'output' : 'tCl'}``. -189 If the key ``output`` is missing, it is inserted with a default value -190 ``tCl``. -191 """ -192 super().__init__(*args, config=config, **kwargs) -193 self._config = config if config is not None else {"output": "tCl"} -194 if not self._config.get("output"): -195 self._config["output"] = "tCl" -196 -197 def signal( -198 self, -199 *args: tuple[str, float], -200 **kwargs, -201 ): -202 r""" -203 Compute the CMB :math:`C_\ell` s. -204 -205 Parameters -206 ---------- -207 *args -208 the name(s) and value(s) of the parameter(s) for which we want to -209 compute the derivative -210 -211 Returns -212 ------- -213 result : array_like of float -214 the signal as a numpy array -215 -216 Notes -217 ----- -218 The signal is composed of the following contributions, in this order: -219 -220 - temperature :math:`C_\ell` s if ``output`` contains ``tCl`` -221 - E-mode :math:`C_\ell` s if ``output`` contains ``pCl`` -222 - cross-correlations between T and E-modes if ``output`` contains ``tCl`` -223 and ``pCl`` -224 - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero* -225 -226 Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose -227 :math:`\ell_\mathrm{min} = 2`. -228 -229 The output is ordered as follows: +164 return final_kwargs +165 +166 +167class ClassyCMBDerivative(ClassyBaseDerivative): +168 """ +169 Interface for CMB quantities. +170 +171 Interface for computing derivatives using the CMB signal and covariance +172 (temperature, polarization, or both). +173 +174 .. versionadded:: 0.9.2 +175 """ +176 +177 def __init__( +178 self, +179 *args, +180 config: Optional[dict] = None, +181 **kwargs, +182 ): +183 """ +184 Create an instance. +185 +186 Parameters +187 ---------- +188 config : dict, optional +189 the CLASS configuration to use. All parameters are accepted. +190 If not specified, defaults to ``{'output' : 'tCl'}``. +191 If the key ``output`` is missing, it is inserted with a default value +192 ``tCl``. +193 """ +194 super().__init__(*args, config=config, **kwargs) +195 self._config = config if config is not None else {"output": "tCl"} +196 if not self._config.get("output"): +197 self._config["output"] = "tCl" +198 +199 def signal( +200 self, +201 *args: tuple[str, float], +202 **kwargs, +203 ): +204 r""" +205 Compute the CMB :math:`C_\ell` s. +206 +207 Parameters +208 ---------- +209 *args +210 the name(s) and value(s) of the parameter(s) for which we want to +211 compute the derivative +212 +213 Returns +214 ------- +215 result : array_like of float +216 the signal as a numpy array +217 +218 Notes +219 ----- +220 The signal is composed of the following contributions, in this order: +221 +222 - temperature :math:`C_\ell` s if ``output`` contains ``tCl`` +223 - E-mode :math:`C_\ell` s if ``output`` contains ``pCl`` +224 - cross-correlations between T and E-modes if ``output`` contains ``tCl`` +225 and ``pCl`` +226 - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero* +227 +228 Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose +229 :math:`\ell_\mathrm{min} = 2`. 230 -231 .. math:: -232 \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\} -233 """ -234 cosmo = self._run_classy(*args) -235 -236 outputs = self._parse_outputs() +231 The output is ordered as follows: +232 +233 .. math:: +234 \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\} +235 """ +236 cosmo = self._run_classy(*args) 237 -238 result = [] -239 if outputs["temperature"]: -240 result.extend(cosmo.raw_cl()["tt"][2:]) -241 if outputs["polarization"]: -242 result.extend(cosmo.raw_cl()["ee"][2:]) -243 if outputs["temperature"] and outputs["polarization"]: -244 result.extend(cosmo.raw_cl()["te"][2:]) -245 if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]): -246 result.extend(cosmo.raw_cl()["bb"][2:]) -247 -248 if result: -249 return np.array(result) -250 -251 return NotImplemented +238 outputs = self._parse_outputs() +239 +240 result = [] +241 if outputs["temperature"]: +242 result.extend(cosmo.raw_cl()["tt"][2:]) +243 if outputs["polarization"]: +244 result.extend(cosmo.raw_cl()["ee"][2:]) +245 if outputs["temperature"] and outputs["polarization"]: +246 result.extend(cosmo.raw_cl()["te"][2:]) +247 if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]): +248 result.extend(cosmo.raw_cl()["bb"][2:]) +249 +250 if result: +251 return np.array(result) 252 -253 def _prefactor_covariance(self, size: int): -254 l_max = int(self.config.get("l_max_scalars", 2500)) -255 matrix = np.diag([2 / (2 * ell + 1) for ell in range(2, l_max + 1)]) -256 return np.tile(matrix, (size, size)) -257 -258 def covariance( -259 self, -260 *args: tuple[str, float], -261 **kwargs, -262 ): -263 r""" -264 Compute the covariance of CMB :math:`C_\ell` s. -265 -266 Parameters -267 ---------- -268 *args -269 the name(s) and fiducial value(s) of the parameter(s) for which we want to -270 compute the covariance -271 -272 **kwargs -273 keyword arguments for the covariance. Supported values are: -274 -275 - ``fsky``: the sky fraction of the survey (default: 1) +253 return NotImplemented +254 +255 def _prefactor_covariance(self, size: int): +256 l_max = int(self.config.get("l_max_scalars", 2500)) +257 matrix = np.diag([2 / (2 * ell + 1) for ell in range(2, l_max + 1)]) +258 return np.tile(matrix, (size, size)) +259 +260 def covariance( +261 self, +262 *args: tuple[str, float], +263 **kwargs, +264 ): +265 r""" +266 Compute the covariance of CMB :math:`C_\ell` s. +267 +268 Parameters +269 ---------- +270 *args +271 the name(s) and fiducial value(s) of the parameter(s) for which we want to +272 compute the covariance +273 +274 **kwargs +275 keyword arguments for the covariance. Supported values are: 276 -277 Returns -278 ------- -279 result : array_like of float -280 the signal as a numpy array -281 -282 Notes -283 ----- -284 The covariance is the following block-matrix (with the notation :math:`X = C_\ell`): -285 -286 .. math:: -287 \frac{2}{2 \ell + 1} -288 \begin{pmatrix} -289 (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\ -290 (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\ -291 X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\ -292 0 & 0 & 0 & (X^{BB})^2 -293 \end{pmatrix} -294 -295 See the notes of the ``signal`` method about the order of the outputs in -296 the matrix. -297 -298 The covariance has been taken from <a -299 href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer -300 noopener">arXiv:0911.3105</a>, eq. (27). -301 """ -302 cosmo = self._run_classy(*args) -303 -304 outputs = self._parse_outputs() +277 - ``fsky``: the sky fraction of the survey (default: 1) +278 +279 Returns +280 ------- +281 result : array_like of float +282 the signal as a numpy array +283 +284 Notes +285 ----- +286 The covariance is the following block-matrix (with the notation :math:`X = C_\ell`): +287 +288 .. math:: +289 \frac{2}{2 \ell + 1} +290 \begin{pmatrix} +291 (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\ +292 (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\ +293 X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\ +294 0 & 0 & 0 & (X^{BB})^2 +295 \end{pmatrix} +296 +297 See the notes of the ``signal`` method about the order of the outputs in +298 the matrix. +299 +300 The covariance has been taken from <a +301 href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer +302 noopener">arXiv:0911.3105</a>, eq. (27). +303 """ +304 cosmo = self._run_classy(*args) 305 -306 c_ell = cosmo.raw_cl() -307 c_tt = c_ell.get("tt", [])[2:] -308 c_ee = c_ell.get("ee", [])[2:] -309 c_te = c_ell.get("te", [])[2:] -310 c_bb = c_ell.get("bb", [])[2:] -311 -312 # CMB C_ells -313 if outputs["temperature"] and outputs["polarization"]: -314 result = np.block( -315 [ -316 [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)], -317 [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)], -318 [ -319 np.diag(c_tt * c_te), -320 np.diag(c_ee * c_te), -321 np.diag(c_te**2 + c_tt * c_ee) / 2, -322 ], -323 ] -324 ) -325 -326 if np.any(c_bb): -327 result = self._prefactor_covariance(4) * block_diag( -328 result, -329 np.diag(c_bb**2), -330 ) -331 else: -332 result = self._prefactor_covariance(3) * result -333 -334 elif outputs["temperature"]: -335 result = self._prefactor_covariance(1) * np.diag(c_tt**2) -336 -337 elif outputs["polarization"]: -338 if np.any(c_bb): -339 result = self._prefactor_covariance(2) * block_diag( -340 np.diag(c_ee**2), np.diag(c_bb**2) -341 ) -342 else: -343 result = self._prefactor_covariance(1) * np.diag(c_ee**2) -344 -345 else: -346 return NotImplemented -347 -348 final_kwargs = self._parse_covariance_kwargs(**kwargs) +306 outputs = self._parse_outputs() +307 +308 c_ell = cosmo.raw_cl() +309 c_tt = c_ell.get("tt", [])[2:] +310 c_ee = c_ell.get("ee", [])[2:] +311 c_te = c_ell.get("te", [])[2:] +312 c_bb = c_ell.get("bb", [])[2:] +313 +314 # CMB C_ells +315 if outputs["temperature"] and outputs["polarization"]: +316 result = np.block( +317 [ +318 [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)], +319 [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)], +320 [ +321 np.diag(c_tt * c_te), +322 np.diag(c_ee * c_te), +323 np.diag(c_te**2 + c_tt * c_ee) / 2, +324 ], +325 ] +326 ) +327 +328 if np.any(c_bb): +329 result = self._prefactor_covariance(4) * block_diag( +330 result, +331 np.diag(c_bb**2), +332 ) +333 else: +334 result = self._prefactor_covariance(3) * result +335 +336 elif outputs["temperature"]: +337 result = self._prefactor_covariance(1) * np.diag(c_tt**2) +338 +339 elif outputs["polarization"]: +340 if np.any(c_bb): +341 result = self._prefactor_covariance(2) * block_diag( +342 np.diag(c_ee**2), np.diag(c_bb**2) +343 ) +344 else: +345 result = self._prefactor_covariance(1) * np.diag(c_ee**2) +346 +347 else: +348 return NotImplemented 349 -350 return result / final_kwargs["fsky"] +350 final_kwargs = self._parse_covariance_kwargs(**kwargs) +351 +352 return result / final_kwargs["fsky"] +353 +354 +355class ClassyGalaxyCountsDerivative(ClassyBaseDerivative): +356 r""" +357 Interface for galaxy number count quantities. +358 +359 Interface for computing derivatives using the galaxy number count signal +360 and covariance. +361 """ +362 +363 def __init__( +364 self, +365 *args, +366 config: Optional[dict] = None, +367 **kwargs, +368 ): +369 """ +370 Create an instance. +371 +372 Parameters +373 ---------- +374 config : dict, optional +375 the CLASS configuration to use. All parameters are accepted. +376 If not specified, defaults to `{'output' : 'nCl'}`. +377 If the key `output` is missing, it is inserted with a default value +378 `nCl`. +379 """ +380 super().__init__(*args, config=config, **kwargs) +381 self._config = config if config is not None else {"output": "nCl"} +382 if not self._config.get("output"): +383 self._config["output"] = "nCl" +384 +385 def _parse_redshifts(self): +386 r""" +387 Parse the redshifts from the configuration and return them. +388 """ +389 raw_redshifts = self.config.get("selection_mean", "") +390 +391 if isinstance(raw_redshifts, (tuple, list, np.ndarray)): +392 redshifts = raw_redshifts if raw_redshifts else [1.0] +393 else: +394 redshifts = ( +395 [item.strip() for item in raw_redshifts.split(",")] +396 if raw_redshifts +397 else [1.0] +398 ) +399 +400 return redshifts +401 +402 def _cross_correlations(self) -> int: +403 r""" +404 Return which cross-correlations the output contains. +405 """ +406 return int(self.config.get("non_diagonal", 0)) +407 +408 def _compute_angular_power_spectrum(self, *args): +409 r""" +410 Compute the $C_\ell$s. +411 +412 Computes the angular power spectrum (the $C_\ell$s) and returns the +413 number of angular power spectra, the number of redshift bins, and the +414 angular power spectra as a dictionary with keys `(ell, index_z1, +415 index_z2)` +416 """ +417 cosmo = self._run_classy(*args) +418 +419 outputs = self._parse_outputs() +420 +421 z_size = len(self._parse_redshifts()) +422 +423 if outputs["galaxy_counts"]: +424 # the output from CLASS +425 c_ells = cosmo.density_cl()["dd"] +426 +427 # how many angular power spectra do we have +428 ell_size = len(c_ells[0]) +429 +430 # the angular power spectra as a dictionary +431 c_ells_dict = {} +432 +433 # if we have cross-correlations, we need to handle them specially +434 if self._cross_correlations() >= 1 and z_size > 1: +435 for ell in range(2, ell_size): +436 counter = 0 +437 for i in range(z_size): +438 for j in range(i, z_size): +439 c_ells_dict[(ell, i, j)] = c_ells_dict[ +440 (ell, j, i) +441 ] = c_ells[counter][ell] +442 counter += 1 +443 else: +444 c_ells_dict = { +445 (ell, i, i): c_ells[i][ell] +446 for i in range(z_size) +447 for ell in range(2, ell_size) +448 } +449 +450 return ell_size, z_size, c_ells_dict +451 +452 return NotImplemented +453 +454 def signal( +455 self, +456 *args: tuple[str, float], +457 **kwargs, +458 ): +459 r""" +460 Compute the signal ($C_\ell$s) of galaxy number counts. +461 +462 Parameters +463 ---------- +464 *args +465 the name(s) and fiducial value(s) of the parameter(s) for which we +466 want to compute the covariance +467 +468 Notes +469 ----- +470 The coordinates used are $(z_1, z_2, \ell)$, in that increasing order. +471 Note that $\ell = \\{0, 1\\}$ are not part of the output, i.e. we +472 impose $\ell_\mathrm{min} = 2$. +473 """ +474 *_, c_ells = self._compute_angular_power_spectrum(*args) +475 +476 if c_ells is NotImplemented: +477 return c_ells +478 +479 return np.array([c_ells[key] for key in sorted(c_ells)]) +480 +481 def covariance( +482 self, +483 *args: tuple[str, float], +484 **kwargs, +485 ): +486 r""" +487 Compute the covariance of the $C_\ell$s of galaxy number counts. +488 +489 Parameters +490 ---------- +491 *args +492 the name(s) and fiducial value(s) of the parameter(s) for which we +493 want to compute the covariance +494 +495 **kwargs +496 keyword arguments for the covariance. Supported values are: +497 - `fsky`: the sky coverage of the survey (default: 1) +498 - `delta_ell`: the bin width in multipole space (default: 2 / `fsky`) +499 +500 Notes +501 ----- +502 The covariance is computed as: +503 +504 $$ +505 \mathsf{C}[(ij), (pq), \ell, \ell'] = \delta_{\ell, \ell'} +506 \frac{ +507 C_\ell(i, p) C_\ell(j, q) + C_\ell(i, q) C_\ell(j, p) +508 } +509 { +510 f_\mathrm{sky} \Delta \ell (2 \ell + 1) +511 } +512 $$ +513 +514 The covariance is block diagonal in $\ell$, that is: +515 $$ +516 \begin{pmatrix} +517 \mathsf{C}[(ij), (pq), \ell = 2] & 0 & \ldots & 0\\\ +518 0 & \mathsf{C}[(ij), (pq), \ell = 3] & \ldots & 0\\\ +519 \vdots & \vdots & \ddots & \vdots\\\ +520 0 & 0 & \ldots & \mathsf{C}[(ij), (pq), \ell = \ell_\mathrm{max}] +521 \end{pmatrix} +522 $$ +523 +524 Note that the covariance may be singular if the cross-correlations +525 between redshift bins are not zero (i.e. if using a non-zero value for +526 the `non_diagonal` parameter). +527 """ +528 ell_size, z_size, c_ells = self._compute_angular_power_spectrum(*args) +529 +530 if c_ells is NotImplemented: +531 return c_ells +532 +533 if self._cross_correlations(): +534 blocks = [] +535 # we skip ell=0 and ell=1 as CLASS sets them to zero anyway +536 for ell in range(2, ell_size): +537 # array containing the covariance for fixed ell +538 covariance_fixed_ell = np.zeros([z_size] * 4) +539 for i1, i2, j1, j2 in product( # pylint: disable=invalid-name +540 range(z_size), repeat=4 +541 ): +542 covariance_fixed_ell[i1, i2, j1, j2] = ( +543 c_ells[(ell, i1, j1)] * c_ells[(ell, i2, j2)] +544 + c_ells[(ell, i1, j2)] * c_ells[(ell, i2, j1)] +545 ) +546 blocks.append( +547 np.reshape(covariance_fixed_ell, (z_size * z_size, z_size * z_size)) +548 / (2 * ell + 1) +549 ) +550 +551 result = block_diag(*blocks) +552 +553 else: +554 result = np.diag(np.array([2 * c_ells[key] ** 2 for key in sorted(c_ells)])) +555 +556 final_kwargs = self._parse_covariance_kwargs(**kwargs) +557 +558 return result / final_kwargs["fsky"] / final_kwargs["delta_ell"] @@ -564,118 +787,119 @@
Notes
-
 52class ClassyBaseDerivative(ABC, FisherDerivative):
- 53    r"""Base interface for CLASS."""
- 54
- 55    software_names = "classy"
- 56    urls = dict(github="https://github.com/lesgourg/class_public")
- 57    version = "1.0.0"
- 58    authors = [
- 59        dict(
- 60            name="Goran Jelic-Cizmek",
- 61            email="goran.jelic-cizmek@unige.ch",
- 62        )
- 63    ]
- 64    __imported__ = IMPORT_SUCCESS
- 65
- 66    def __init__(
- 67        self,
- 68        *args,
- 69        config: Optional[dict] = None,
- 70        **kwargs,
- 71    ):
- 72        """
- 73        Create an instance.
- 74
- 75        Parameters
- 76        ----------
- 77        config : dict, optional
- 78            the CLASS configuration to use. All parameters are accepted.
- 79        """
- 80        if not self.__imported__:
- 81            raise ImportError(
- 82                f"Unable to import the `{self.software_names}` module, "
- 83                "please make sure it is installed; "
- 84                f"for additional help, please consult one of the following URL(s): {self.urls}"
- 85            )
- 86
- 87        self._config = config if config is not None else {}
- 88        super().__init__(*args, **kwargs)
- 89        self._run_classy = lru_cache(maxsize=None)(self._run_classy)  # type: ignore
- 90
- 91    @classmethod
- 92    def from_file(cls, path: Union[str, Path]):
- 93        r"""
- 94        Load a CLASS configuration from a file.
- 95
- 96        Parameters
- 97        ----------
- 98        path : str or Path
- 99            the path to the configuration file
-100        """
-101        contents = Path(path).read_text(encoding="utf-8").split("\n")
-102        config = ConfigParser(inline_comment_prefixes=("#", ";"))
-103        config.optionxform = lambda option: option  # type: ignore
-104        section = "default"
-105        config.read_string(f"[{section}]" + "\n".join(contents))
-106
-107        final_config = {
-108            key: value for key, value in dict(config[section]).items() if value
-109        }
-110
-111        return cls(config=final_config)
-112
-113    @property
-114    def config(self):
-115        """
-116        Return the current configuration used for running CLASS.
-117
-118        .. versionchanged:: 0.10.0
-119            member is now read-only
-120        """
-121        return self._config
-122
-123    def _parse_outputs(self):
-124        raw_output = self.config.get("output", "")
-125
-126        if isinstance(raw_output, (tuple, list, np.ndarray)):
-127            outputs = raw_output
-128        else:
-129            outputs = {item.strip() for item in raw_output.split(",")}
-130
-131        return {
-132            "temperature": "tCl" in outputs,
-133            "polarization": "pCl" in outputs,
-134        }
-135
-136    def _run_classy(self, *args):  # pylint: disable=method-hidden
-137        r"""
-138        Run classy and returns the instance of it after computation.
-139
-140        Notes
-141        -----
-142        The method is cached using <a
-143        href="https://docs.python.org/3/library/functools.html#functools.lru_cache"
-144        target="_blank" rel="noreferrer noopener">``functools.lru_cache``</a>
-145        """
-146        cosmo = classy.Class()  # pylint: disable=c-extension-no-member
-147        final_kwargs = {**self.config}
-148        for name, value in args:
-149            final_kwargs[name] = value
-150        cosmo.set(final_kwargs)
-151        cosmo.compute()
-152
-153        return cosmo
+            
 53class ClassyBaseDerivative(ABC, FisherDerivative):
+ 54    r"""Base interface for CLASS."""
+ 55
+ 56    software_names = "classy"
+ 57    urls = dict(github="https://github.com/lesgourg/class_public")
+ 58    version = "1.0.0"
+ 59    authors = [
+ 60        dict(
+ 61            name="Goran Jelic-Cizmek",
+ 62            email="goran.jelic-cizmek@unige.ch",
+ 63        )
+ 64    ]
+ 65    __imported__ = IMPORT_SUCCESS
+ 66
+ 67    def __init__(
+ 68        self,
+ 69        *args,
+ 70        config: Optional[dict] = None,
+ 71        **kwargs,
+ 72    ):
+ 73        """
+ 74        Create an instance.
+ 75
+ 76        Parameters
+ 77        ----------
+ 78        config : dict, optional
+ 79            the CLASS configuration to use. All parameters are accepted.
+ 80        """
+ 81        if not self.__imported__:
+ 82            raise ImportError(
+ 83                f"Unable to import the `{self.software_names}` module, "
+ 84                "please make sure it is installed; "
+ 85                f"for additional help, please consult one of the following URL(s): {self.urls}"
+ 86            )
+ 87
+ 88        self._config = config if config is not None else {}
+ 89        super().__init__(*args, **kwargs)
+ 90        self._run_classy = lru_cache(maxsize=None)(self._run_classy)  # type: ignore
+ 91
+ 92    @classmethod
+ 93    def from_file(cls, path: Union[str, Path]):
+ 94        r"""
+ 95        Load a CLASS configuration from a file.
+ 96
+ 97        Parameters
+ 98        ----------
+ 99        path : str or Path
+100            the path to the configuration file
+101        """
+102        contents = Path(path).read_text(encoding="utf-8").split("\n")
+103        config = ConfigParser(inline_comment_prefixes=("#", ";"))
+104        config.optionxform = lambda option: option  # type: ignore
+105        section = "default"
+106        config.read_string(f"[{section}]" + "\n".join(contents))
+107
+108        final_config = {
+109            key: value for key, value in dict(config[section]).items() if value
+110        }
+111
+112        return cls(config=final_config)
+113
+114    @property
+115    def config(self):
+116        """
+117        Return the current configuration used for running CLASS.
+118
+119        .. versionchanged:: 0.10.0
+120            member is now read-only
+121        """
+122        return self._config
+123
+124    def _parse_outputs(self):
+125        raw_output = self.config.get("output", "")
+126
+127        if isinstance(raw_output, (tuple, list, np.ndarray)):
+128            outputs = raw_output
+129        else:
+130            outputs = {item.strip() for item in raw_output.split(",")}
+131
+132        return {
+133            "temperature": "tCl" in outputs,
+134            "polarization": "pCl" in outputs,
+135            "galaxy_counts": "nCl" in outputs or "dCl" in outputs,
+136        }
+137
+138    def _run_classy(self, *args):  # pylint: disable=method-hidden
+139        r"""
+140        Run classy and returns the instance of it after computation.
+141
+142        Notes
+143        -----
+144        The method is cached using <a
+145        href="https://docs.python.org/3/library/functools.html#functools.lru_cache"
+146        target="_blank" rel="noreferrer noopener">``functools.lru_cache``</a>
+147        """
+148        cosmo = classy.Class()  # pylint: disable=c-extension-no-member
+149        final_kwargs = {**self.config}
+150        for name, value in args:
+151            final_kwargs[name] = value
+152        cosmo.set(final_kwargs)
+153        cosmo.compute()
 154
-155    def _parse_covariance_kwargs(self, **kwargs):
-156        """Parse any keyword arguments for the covariance."""
-157        final_kwargs = {}
-158        final_kwargs["fsky"] = float(kwargs.pop("fsky", 1))
-159        final_kwargs["delta_ell"] = int(
-160            kwargs.pop("delta_ell", 2 / final_kwargs["fsky"])
-161        )
-162
-163        return final_kwargs
+155        return cosmo
+156
+157    def _parse_covariance_kwargs(self, **kwargs):
+158        """Parse any keyword arguments for the covariance."""
+159        final_kwargs = {}
+160        final_kwargs["fsky"] = float(kwargs.pop("fsky", 1))
+161        final_kwargs["delta_ell"] = round(
+162            kwargs.pop("delta_ell", 2 / final_kwargs["fsky"])
+163        )
+164
+165        return final_kwargs
 
@@ -693,30 +917,30 @@
Notes
-
66    def __init__(
-67        self,
-68        *args,
-69        config: Optional[dict] = None,
-70        **kwargs,
-71    ):
-72        """
-73        Create an instance.
-74
-75        Parameters
-76        ----------
-77        config : dict, optional
-78            the CLASS configuration to use. All parameters are accepted.
-79        """
-80        if not self.__imported__:
-81            raise ImportError(
-82                f"Unable to import the `{self.software_names}` module, "
-83                "please make sure it is installed; "
-84                f"for additional help, please consult one of the following URL(s): {self.urls}"
-85            )
-86
-87        self._config = config if config is not None else {}
-88        super().__init__(*args, **kwargs)
-89        self._run_classy = lru_cache(maxsize=None)(self._run_classy)  # type: ignore
+            
67    def __init__(
+68        self,
+69        *args,
+70        config: Optional[dict] = None,
+71        **kwargs,
+72    ):
+73        """
+74        Create an instance.
+75
+76        Parameters
+77        ----------
+78        config : dict, optional
+79            the CLASS configuration to use. All parameters are accepted.
+80        """
+81        if not self.__imported__:
+82            raise ImportError(
+83                f"Unable to import the `{self.software_names}` module, "
+84                "please make sure it is installed; "
+85                f"for additional help, please consult one of the following URL(s): {self.urls}"
+86            )
+87
+88        self._config = config if config is not None else {}
+89        super().__init__(*args, **kwargs)
+90        self._run_classy = lru_cache(maxsize=None)(self._run_classy)  # type: ignore
 
@@ -812,27 +1036,27 @@
Parameters
-
 91    @classmethod
- 92    def from_file(cls, path: Union[str, Path]):
- 93        r"""
- 94        Load a CLASS configuration from a file.
- 95
- 96        Parameters
- 97        ----------
- 98        path : str or Path
- 99            the path to the configuration file
-100        """
-101        contents = Path(path).read_text(encoding="utf-8").split("\n")
-102        config = ConfigParser(inline_comment_prefixes=("#", ";"))
-103        config.optionxform = lambda option: option  # type: ignore
-104        section = "default"
-105        config.read_string(f"[{section}]" + "\n".join(contents))
-106
-107        final_config = {
-108            key: value for key, value in dict(config[section]).items() if value
-109        }
-110
-111        return cls(config=final_config)
+            
 92    @classmethod
+ 93    def from_file(cls, path: Union[str, Path]):
+ 94        r"""
+ 95        Load a CLASS configuration from a file.
+ 96
+ 97        Parameters
+ 98        ----------
+ 99        path : str or Path
+100            the path to the configuration file
+101        """
+102        contents = Path(path).read_text(encoding="utf-8").split("\n")
+103        config = ConfigParser(inline_comment_prefixes=("#", ";"))
+104        config.optionxform = lambda option: option  # type: ignore
+105        section = "default"
+106        config.read_string(f"[{section}]" + "\n".join(contents))
+107
+108        final_config = {
+109            key: value for key, value in dict(config[section]).items() if value
+110        }
+111
+112        return cls(config=final_config)
 
@@ -889,192 +1113,192 @@
Inherited Members
-
166class ClassyCMBDerivative(ClassyBaseDerivative):
-167    """
-168    Interface for CMB quantities.
-169
-170    Interface for computing derivatives using the CMB signal and covariance
-171    (temperature, polarization, or both).
-172
-173    .. versionadded:: 0.9.2
-174    """
-175
-176    def __init__(
-177        self,
-178        *args,
-179        config: Optional[dict] = None,
-180        **kwargs,
-181    ):
-182        """
-183        Create an instance.
-184
-185        Parameters
-186        ----------
-187        config : dict, optional
-188            the CLASS configuration to use. All parameters are accepted.
-189            If not specified, defaults to ``{'output' : 'tCl'}``.
-190            If the key ``output`` is missing, it is inserted with a default value
-191            ``tCl``.
-192        """
-193        super().__init__(*args, config=config, **kwargs)
-194        self._config = config if config is not None else {"output": "tCl"}
-195        if not self._config.get("output"):
-196            self._config["output"] = "tCl"
-197
-198    def signal(
-199        self,
-200        *args: tuple[str, float],
-201        **kwargs,
-202    ):
-203        r"""
-204        Compute the CMB :math:`C_\ell` s.
-205
-206        Parameters
-207        ----------
-208        *args
-209            the name(s) and value(s) of the parameter(s) for which we want to
-210            compute the derivative
-211
-212        Returns
-213        -------
-214        result : array_like of float
-215            the signal as a numpy array
-216
-217        Notes
-218        -----
-219        The signal is composed of the following contributions, in this order:
-220
-221        - temperature :math:`C_\ell` s if ``output`` contains ``tCl``
-222        - E-mode :math:`C_\ell` s if ``output`` contains ``pCl``
-223        - cross-correlations between T and E-modes if ``output`` contains ``tCl``
-224          and ``pCl``
-225        - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero*
-226
-227        Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose
-228        :math:`\ell_\mathrm{min} = 2`.
-229
-230        The output is ordered as follows:
+            
168class ClassyCMBDerivative(ClassyBaseDerivative):
+169    """
+170    Interface for CMB quantities.
+171
+172    Interface for computing derivatives using the CMB signal and covariance
+173    (temperature, polarization, or both).
+174
+175    .. versionadded:: 0.9.2
+176    """
+177
+178    def __init__(
+179        self,
+180        *args,
+181        config: Optional[dict] = None,
+182        **kwargs,
+183    ):
+184        """
+185        Create an instance.
+186
+187        Parameters
+188        ----------
+189        config : dict, optional
+190            the CLASS configuration to use. All parameters are accepted.
+191            If not specified, defaults to ``{'output' : 'tCl'}``.
+192            If the key ``output`` is missing, it is inserted with a default value
+193            ``tCl``.
+194        """
+195        super().__init__(*args, config=config, **kwargs)
+196        self._config = config if config is not None else {"output": "tCl"}
+197        if not self._config.get("output"):
+198            self._config["output"] = "tCl"
+199
+200    def signal(
+201        self,
+202        *args: tuple[str, float],
+203        **kwargs,
+204    ):
+205        r"""
+206        Compute the CMB :math:`C_\ell` s.
+207
+208        Parameters
+209        ----------
+210        *args
+211            the name(s) and value(s) of the parameter(s) for which we want to
+212            compute the derivative
+213
+214        Returns
+215        -------
+216        result : array_like of float
+217            the signal as a numpy array
+218
+219        Notes
+220        -----
+221        The signal is composed of the following contributions, in this order:
+222
+223        - temperature :math:`C_\ell` s if ``output`` contains ``tCl``
+224        - E-mode :math:`C_\ell` s if ``output`` contains ``pCl``
+225        - cross-correlations between T and E-modes if ``output`` contains ``tCl``
+226          and ``pCl``
+227        - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero*
+228
+229        Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose
+230        :math:`\ell_\mathrm{min} = 2`.
 231
-232        .. math::
-233            \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\}
-234        """
-235        cosmo = self._run_classy(*args)
-236
-237        outputs = self._parse_outputs()
+232        The output is ordered as follows:
+233
+234        .. math::
+235            \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\}
+236        """
+237        cosmo = self._run_classy(*args)
 238
-239        result = []
-240        if outputs["temperature"]:
-241            result.extend(cosmo.raw_cl()["tt"][2:])
-242        if outputs["polarization"]:
-243            result.extend(cosmo.raw_cl()["ee"][2:])
-244        if outputs["temperature"] and outputs["polarization"]:
-245            result.extend(cosmo.raw_cl()["te"][2:])
-246        if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]):
-247            result.extend(cosmo.raw_cl()["bb"][2:])
-248
-249        if result:
-250            return np.array(result)
-251
-252        return NotImplemented
+239        outputs = self._parse_outputs()
+240
+241        result = []
+242        if outputs["temperature"]:
+243            result.extend(cosmo.raw_cl()["tt"][2:])
+244        if outputs["polarization"]:
+245            result.extend(cosmo.raw_cl()["ee"][2:])
+246        if outputs["temperature"] and outputs["polarization"]:
+247            result.extend(cosmo.raw_cl()["te"][2:])
+248        if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]):
+249            result.extend(cosmo.raw_cl()["bb"][2:])
+250
+251        if result:
+252            return np.array(result)
 253
-254    def _prefactor_covariance(self, size: int):
-255        l_max = int(self.config.get("l_max_scalars", 2500))
-256        matrix = np.diag([2 / (2 * ell + 1) for ell in range(2, l_max + 1)])
-257        return np.tile(matrix, (size, size))
-258
-259    def covariance(
-260        self,
-261        *args: tuple[str, float],
-262        **kwargs,
-263    ):
-264        r"""
-265        Compute the covariance of CMB :math:`C_\ell` s.
-266
-267        Parameters
-268        ----------
-269        *args
-270            the name(s) and fiducial value(s) of the parameter(s) for which we want to
-271            compute the covariance
-272
-273        **kwargs
-274            keyword arguments for the covariance. Supported values are:
-275
-276            - ``fsky``: the sky fraction of the survey (default: 1)
+254        return NotImplemented
+255
+256    def _prefactor_covariance(self, size: int):
+257        l_max = int(self.config.get("l_max_scalars", 2500))
+258        matrix = np.diag([2 / (2 * ell + 1) for ell in range(2, l_max + 1)])
+259        return np.tile(matrix, (size, size))
+260
+261    def covariance(
+262        self,
+263        *args: tuple[str, float],
+264        **kwargs,
+265    ):
+266        r"""
+267        Compute the covariance of CMB :math:`C_\ell` s.
+268
+269        Parameters
+270        ----------
+271        *args
+272            the name(s) and fiducial value(s) of the parameter(s) for which we want to
+273            compute the covariance
+274
+275        **kwargs
+276            keyword arguments for the covariance. Supported values are:
 277
-278        Returns
-279        -------
-280        result : array_like of float
-281            the signal as a numpy array
-282
-283        Notes
-284        -----
-285        The covariance is the following block-matrix (with the notation :math:`X = C_\ell`):
-286
-287        .. math::
-288            \frac{2}{2 \ell + 1}
-289            \begin{pmatrix}
-290            (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\
-291            (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\
-292            X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\
-293            0 & 0 & 0 & (X^{BB})^2
-294            \end{pmatrix}
-295
-296        See the notes of the ``signal`` method about the order of the outputs in
-297        the matrix.
-298
-299        The covariance has been taken from <a
-300        href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer
-301        noopener">arXiv:0911.3105</a>, eq. (27).
-302        """
-303        cosmo = self._run_classy(*args)
-304
-305        outputs = self._parse_outputs()
+278            - ``fsky``: the sky fraction of the survey (default: 1)
+279
+280        Returns
+281        -------
+282        result : array_like of float
+283            the signal as a numpy array
+284
+285        Notes
+286        -----
+287        The covariance is the following block-matrix (with the notation :math:`X = C_\ell`):
+288
+289        .. math::
+290            \frac{2}{2 \ell + 1}
+291            \begin{pmatrix}
+292            (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\
+293            (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\
+294            X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\
+295            0 & 0 & 0 & (X^{BB})^2
+296            \end{pmatrix}
+297
+298        See the notes of the ``signal`` method about the order of the outputs in
+299        the matrix.
+300
+301        The covariance has been taken from <a
+302        href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer
+303        noopener">arXiv:0911.3105</a>, eq. (27).
+304        """
+305        cosmo = self._run_classy(*args)
 306
-307        c_ell = cosmo.raw_cl()
-308        c_tt = c_ell.get("tt", [])[2:]
-309        c_ee = c_ell.get("ee", [])[2:]
-310        c_te = c_ell.get("te", [])[2:]
-311        c_bb = c_ell.get("bb", [])[2:]
-312
-313        # CMB C_ells
-314        if outputs["temperature"] and outputs["polarization"]:
-315            result = np.block(
-316                [
-317                    [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)],
-318                    [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)],
-319                    [
-320                        np.diag(c_tt * c_te),
-321                        np.diag(c_ee * c_te),
-322                        np.diag(c_te**2 + c_tt * c_ee) / 2,
-323                    ],
-324                ]
-325            )
-326
-327            if np.any(c_bb):
-328                result = self._prefactor_covariance(4) * block_diag(
-329                    result,
-330                    np.diag(c_bb**2),
-331                )
-332            else:
-333                result = self._prefactor_covariance(3) * result
-334
-335        elif outputs["temperature"]:
-336            result = self._prefactor_covariance(1) * np.diag(c_tt**2)
-337
-338        elif outputs["polarization"]:
-339            if np.any(c_bb):
-340                result = self._prefactor_covariance(2) * block_diag(
-341                    np.diag(c_ee**2), np.diag(c_bb**2)
-342                )
-343            else:
-344                result = self._prefactor_covariance(1) * np.diag(c_ee**2)
-345
-346        else:
-347            return NotImplemented
-348
-349        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+307        outputs = self._parse_outputs()
+308
+309        c_ell = cosmo.raw_cl()
+310        c_tt = c_ell.get("tt", [])[2:]
+311        c_ee = c_ell.get("ee", [])[2:]
+312        c_te = c_ell.get("te", [])[2:]
+313        c_bb = c_ell.get("bb", [])[2:]
+314
+315        # CMB C_ells
+316        if outputs["temperature"] and outputs["polarization"]:
+317            result = np.block(
+318                [
+319                    [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)],
+320                    [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)],
+321                    [
+322                        np.diag(c_tt * c_te),
+323                        np.diag(c_ee * c_te),
+324                        np.diag(c_te**2 + c_tt * c_ee) / 2,
+325                    ],
+326                ]
+327            )
+328
+329            if np.any(c_bb):
+330                result = self._prefactor_covariance(4) * block_diag(
+331                    result,
+332                    np.diag(c_bb**2),
+333                )
+334            else:
+335                result = self._prefactor_covariance(3) * result
+336
+337        elif outputs["temperature"]:
+338            result = self._prefactor_covariance(1) * np.diag(c_tt**2)
+339
+340        elif outputs["polarization"]:
+341            if np.any(c_bb):
+342                result = self._prefactor_covariance(2) * block_diag(
+343                    np.diag(c_ee**2), np.diag(c_bb**2)
+344                )
+345            else:
+346                result = self._prefactor_covariance(1) * np.diag(c_ee**2)
+347
+348        else:
+349            return NotImplemented
 350
-351        return result / final_kwargs["fsky"]
+351        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+352
+353        return result / final_kwargs["fsky"]
 
@@ -1097,27 +1321,27 @@
Inherited Members
-
176    def __init__(
-177        self,
-178        *args,
-179        config: Optional[dict] = None,
-180        **kwargs,
-181    ):
-182        """
-183        Create an instance.
-184
-185        Parameters
-186        ----------
-187        config : dict, optional
-188            the CLASS configuration to use. All parameters are accepted.
-189            If not specified, defaults to ``{'output' : 'tCl'}``.
-190            If the key ``output`` is missing, it is inserted with a default value
-191            ``tCl``.
-192        """
-193        super().__init__(*args, config=config, **kwargs)
-194        self._config = config if config is not None else {"output": "tCl"}
-195        if not self._config.get("output"):
-196            self._config["output"] = "tCl"
+            
178    def __init__(
+179        self,
+180        *args,
+181        config: Optional[dict] = None,
+182        **kwargs,
+183    ):
+184        """
+185        Create an instance.
+186
+187        Parameters
+188        ----------
+189        config : dict, optional
+190            the CLASS configuration to use. All parameters are accepted.
+191            If not specified, defaults to ``{'output' : 'tCl'}``.
+192            If the key ``output`` is missing, it is inserted with a default value
+193            ``tCl``.
+194        """
+195        super().__init__(*args, config=config, **kwargs)
+196        self._config = config if config is not None else {"output": "tCl"}
+197        if not self._config.get("output"):
+198            self._config["output"] = "tCl"
 
@@ -1147,61 +1371,61 @@
Parameters
-
198    def signal(
-199        self,
-200        *args: tuple[str, float],
-201        **kwargs,
-202    ):
-203        r"""
-204        Compute the CMB :math:`C_\ell` s.
-205
-206        Parameters
-207        ----------
-208        *args
-209            the name(s) and value(s) of the parameter(s) for which we want to
-210            compute the derivative
-211
-212        Returns
-213        -------
-214        result : array_like of float
-215            the signal as a numpy array
-216
-217        Notes
-218        -----
-219        The signal is composed of the following contributions, in this order:
-220
-221        - temperature :math:`C_\ell` s if ``output`` contains ``tCl``
-222        - E-mode :math:`C_\ell` s if ``output`` contains ``pCl``
-223        - cross-correlations between T and E-modes if ``output`` contains ``tCl``
-224          and ``pCl``
-225        - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero*
-226
-227        Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose
-228        :math:`\ell_\mathrm{min} = 2`.
-229
-230        The output is ordered as follows:
+            
200    def signal(
+201        self,
+202        *args: tuple[str, float],
+203        **kwargs,
+204    ):
+205        r"""
+206        Compute the CMB :math:`C_\ell` s.
+207
+208        Parameters
+209        ----------
+210        *args
+211            the name(s) and value(s) of the parameter(s) for which we want to
+212            compute the derivative
+213
+214        Returns
+215        -------
+216        result : array_like of float
+217            the signal as a numpy array
+218
+219        Notes
+220        -----
+221        The signal is composed of the following contributions, in this order:
+222
+223        - temperature :math:`C_\ell` s if ``output`` contains ``tCl``
+224        - E-mode :math:`C_\ell` s if ``output`` contains ``pCl``
+225        - cross-correlations between T and E-modes if ``output`` contains ``tCl``
+226          and ``pCl``
+227        - B-mode :math:`C_\ell` s if ``output`` contains ``pCl`` *and they are non-zero*
+228
+229        Note that :math:`\ell = \\{0, 1\\}` are not part of the output, i.e. we impose
+230        :math:`\ell_\mathrm{min} = 2`.
 231
-232        .. math::
-233            \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\}
-234        """
-235        cosmo = self._run_classy(*args)
-236
-237        outputs = self._parse_outputs()
+232        The output is ordered as follows:
+233
+234        .. math::
+235            \mathbf{S} = \\{C_\ell^{TT}, C_\ell^{EE}, C_\ell^{TE}, C_\ell^{BB} \\}
+236        """
+237        cosmo = self._run_classy(*args)
 238
-239        result = []
-240        if outputs["temperature"]:
-241            result.extend(cosmo.raw_cl()["tt"][2:])
-242        if outputs["polarization"]:
-243            result.extend(cosmo.raw_cl()["ee"][2:])
-244        if outputs["temperature"] and outputs["polarization"]:
-245            result.extend(cosmo.raw_cl()["te"][2:])
-246        if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]):
-247            result.extend(cosmo.raw_cl()["bb"][2:])
-248
-249        if result:
-250            return np.array(result)
-251
-252        return NotImplemented
+239        outputs = self._parse_outputs()
+240
+241        result = []
+242        if outputs["temperature"]:
+243            result.extend(cosmo.raw_cl()["tt"][2:])
+244        if outputs["polarization"]:
+245            result.extend(cosmo.raw_cl()["ee"][2:])
+246        if outputs["temperature"] and outputs["polarization"]:
+247            result.extend(cosmo.raw_cl()["te"][2:])
+248        if outputs["polarization"] and np.any(cosmo.raw_cl()["bb"]):
+249            result.extend(cosmo.raw_cl()["bb"][2:])
+250
+251        if result:
+252            return np.array(result)
+253
+254        return NotImplemented
 
@@ -1254,99 +1478,99 @@
Notes
-
259    def covariance(
-260        self,
-261        *args: tuple[str, float],
-262        **kwargs,
-263    ):
-264        r"""
-265        Compute the covariance of CMB :math:`C_\ell` s.
-266
-267        Parameters
-268        ----------
-269        *args
-270            the name(s) and fiducial value(s) of the parameter(s) for which we want to
-271            compute the covariance
-272
-273        **kwargs
-274            keyword arguments for the covariance. Supported values are:
-275
-276            - ``fsky``: the sky fraction of the survey (default: 1)
+            
261    def covariance(
+262        self,
+263        *args: tuple[str, float],
+264        **kwargs,
+265    ):
+266        r"""
+267        Compute the covariance of CMB :math:`C_\ell` s.
+268
+269        Parameters
+270        ----------
+271        *args
+272            the name(s) and fiducial value(s) of the parameter(s) for which we want to
+273            compute the covariance
+274
+275        **kwargs
+276            keyword arguments for the covariance. Supported values are:
 277
-278        Returns
-279        -------
-280        result : array_like of float
-281            the signal as a numpy array
-282
-283        Notes
-284        -----
-285        The covariance is the following block-matrix (with the notation :math:`X = C_\ell`):
-286
-287        .. math::
-288            \frac{2}{2 \ell + 1}
-289            \begin{pmatrix}
-290            (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\
-291            (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\
-292            X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\
-293            0 & 0 & 0 & (X^{BB})^2
-294            \end{pmatrix}
-295
-296        See the notes of the ``signal`` method about the order of the outputs in
-297        the matrix.
-298
-299        The covariance has been taken from <a
-300        href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer
-301        noopener">arXiv:0911.3105</a>, eq. (27).
-302        """
-303        cosmo = self._run_classy(*args)
-304
-305        outputs = self._parse_outputs()
+278            - ``fsky``: the sky fraction of the survey (default: 1)
+279
+280        Returns
+281        -------
+282        result : array_like of float
+283            the signal as a numpy array
+284
+285        Notes
+286        -----
+287        The covariance is the following block-matrix (with the notation :math:`X = C_\ell`):
+288
+289        .. math::
+290            \frac{2}{2 \ell + 1}
+291            \begin{pmatrix}
+292            (X^{TT})^2 & (X^{TE})^2 & X^{TT} X^{TE} & 0 \\\\
+293            (X^{TE})^2 & (X^{EE})^2 & X^{EE} X^{TE} & 0 \\\\
+294            X^{TT} X^{TE} & X^{EE} X^{TE} & [(X^{TE})^2 + X^{TT} X^{EE}] / 2 & 0 \\\\
+295            0 & 0 & 0 & (X^{BB})^2
+296            \end{pmatrix}
+297
+298        See the notes of the ``signal`` method about the order of the outputs in
+299        the matrix.
+300
+301        The covariance has been taken from <a
+302        href="https://arxiv.org/abs/0911.3105" target="_blank" rel="noreferrer
+303        noopener">arXiv:0911.3105</a>, eq. (27).
+304        """
+305        cosmo = self._run_classy(*args)
 306
-307        c_ell = cosmo.raw_cl()
-308        c_tt = c_ell.get("tt", [])[2:]
-309        c_ee = c_ell.get("ee", [])[2:]
-310        c_te = c_ell.get("te", [])[2:]
-311        c_bb = c_ell.get("bb", [])[2:]
-312
-313        # CMB C_ells
-314        if outputs["temperature"] and outputs["polarization"]:
-315            result = np.block(
-316                [
-317                    [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)],
-318                    [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)],
-319                    [
-320                        np.diag(c_tt * c_te),
-321                        np.diag(c_ee * c_te),
-322                        np.diag(c_te**2 + c_tt * c_ee) / 2,
-323                    ],
-324                ]
-325            )
-326
-327            if np.any(c_bb):
-328                result = self._prefactor_covariance(4) * block_diag(
-329                    result,
-330                    np.diag(c_bb**2),
-331                )
-332            else:
-333                result = self._prefactor_covariance(3) * result
-334
-335        elif outputs["temperature"]:
-336            result = self._prefactor_covariance(1) * np.diag(c_tt**2)
-337
-338        elif outputs["polarization"]:
-339            if np.any(c_bb):
-340                result = self._prefactor_covariance(2) * block_diag(
-341                    np.diag(c_ee**2), np.diag(c_bb**2)
-342                )
-343            else:
-344                result = self._prefactor_covariance(1) * np.diag(c_ee**2)
-345
-346        else:
-347            return NotImplemented
-348
-349        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+307        outputs = self._parse_outputs()
+308
+309        c_ell = cosmo.raw_cl()
+310        c_tt = c_ell.get("tt", [])[2:]
+311        c_ee = c_ell.get("ee", [])[2:]
+312        c_te = c_ell.get("te", [])[2:]
+313        c_bb = c_ell.get("bb", [])[2:]
+314
+315        # CMB C_ells
+316        if outputs["temperature"] and outputs["polarization"]:
+317            result = np.block(
+318                [
+319                    [np.diag(c_tt**2), np.diag(c_te**2), np.diag(c_tt * c_te)],
+320                    [np.diag(c_te**2), np.diag(c_ee**2), np.diag(c_ee * c_te)],
+321                    [
+322                        np.diag(c_tt * c_te),
+323                        np.diag(c_ee * c_te),
+324                        np.diag(c_te**2 + c_tt * c_ee) / 2,
+325                    ],
+326                ]
+327            )
+328
+329            if np.any(c_bb):
+330                result = self._prefactor_covariance(4) * block_diag(
+331                    result,
+332                    np.diag(c_bb**2),
+333                )
+334            else:
+335                result = self._prefactor_covariance(3) * result
+336
+337        elif outputs["temperature"]:
+338            result = self._prefactor_covariance(1) * np.diag(c_tt**2)
+339
+340        elif outputs["polarization"]:
+341            if np.any(c_bb):
+342                result = self._prefactor_covariance(2) * block_diag(
+343                    np.diag(c_ee**2), np.diag(c_bb**2)
+344                )
+345            else:
+346                result = self._prefactor_covariance(1) * np.diag(c_ee**2)
+347
+348        else:
+349            return NotImplemented
 350
-351        return result / final_kwargs["fsky"]
+351        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+352
+353        return result / final_kwargs["fsky"]
 
@@ -1414,6 +1638,496 @@
Inherited Members
+
+ +
+ + class + ClassyGalaxyCountsDerivative(ClassyBaseDerivative): + + + +
+ +
356class ClassyGalaxyCountsDerivative(ClassyBaseDerivative):
+357    r"""
+358    Interface for galaxy number count quantities.
+359
+360    Interface for computing derivatives using the galaxy number count signal
+361    and covariance.
+362    """
+363
+364    def __init__(
+365        self,
+366        *args,
+367        config: Optional[dict] = None,
+368        **kwargs,
+369    ):
+370        """
+371        Create an instance.
+372
+373        Parameters
+374        ----------
+375        config : dict, optional
+376            the CLASS configuration to use. All parameters are accepted.
+377            If not specified, defaults to `{'output' : 'nCl'}`.
+378            If the key `output` is missing, it is inserted with a default value
+379            `nCl`.
+380        """
+381        super().__init__(*args, config=config, **kwargs)
+382        self._config = config if config is not None else {"output": "nCl"}
+383        if not self._config.get("output"):
+384            self._config["output"] = "nCl"
+385
+386    def _parse_redshifts(self):
+387        r"""
+388        Parse the redshifts from the configuration and return them.
+389        """
+390        raw_redshifts = self.config.get("selection_mean", "")
+391
+392        if isinstance(raw_redshifts, (tuple, list, np.ndarray)):
+393            redshifts = raw_redshifts if raw_redshifts else [1.0]
+394        else:
+395            redshifts = (
+396                [item.strip() for item in raw_redshifts.split(",")]
+397                if raw_redshifts
+398                else [1.0]
+399            )
+400
+401        return redshifts
+402
+403    def _cross_correlations(self) -> int:
+404        r"""
+405        Return which cross-correlations the output contains.
+406        """
+407        return int(self.config.get("non_diagonal", 0))
+408
+409    def _compute_angular_power_spectrum(self, *args):
+410        r"""
+411        Compute the $C_\ell$s.
+412
+413        Computes the angular power spectrum (the $C_\ell$s) and returns the
+414        number of angular power spectra, the number of redshift bins, and the
+415        angular power spectra as a dictionary with keys `(ell, index_z1,
+416        index_z2)`
+417        """
+418        cosmo = self._run_classy(*args)
+419
+420        outputs = self._parse_outputs()
+421
+422        z_size = len(self._parse_redshifts())
+423
+424        if outputs["galaxy_counts"]:
+425            # the output from CLASS
+426            c_ells = cosmo.density_cl()["dd"]
+427
+428            # how many angular power spectra do we have
+429            ell_size = len(c_ells[0])
+430
+431            # the angular power spectra as a dictionary
+432            c_ells_dict = {}
+433
+434            # if we have cross-correlations, we need to handle them specially
+435            if self._cross_correlations() >= 1 and z_size > 1:
+436                for ell in range(2, ell_size):
+437                    counter = 0
+438                    for i in range(z_size):
+439                        for j in range(i, z_size):
+440                            c_ells_dict[(ell, i, j)] = c_ells_dict[
+441                                (ell, j, i)
+442                            ] = c_ells[counter][ell]
+443                            counter += 1
+444            else:
+445                c_ells_dict = {
+446                    (ell, i, i): c_ells[i][ell]
+447                    for i in range(z_size)
+448                    for ell in range(2, ell_size)
+449                }
+450
+451            return ell_size, z_size, c_ells_dict
+452
+453        return NotImplemented
+454
+455    def signal(
+456        self,
+457        *args: tuple[str, float],
+458        **kwargs,
+459    ):
+460        r"""
+461        Compute the signal ($C_\ell$s) of galaxy number counts.
+462
+463        Parameters
+464        ----------
+465        *args
+466            the name(s) and fiducial value(s) of the parameter(s) for which we
+467            want to compute the covariance
+468
+469        Notes
+470        -----
+471        The coordinates used are $(z_1, z_2, \ell)$, in that increasing order.
+472        Note that $\ell = \\{0, 1\\}$ are not part of the output, i.e. we
+473        impose $\ell_\mathrm{min} = 2$.
+474        """
+475        *_, c_ells = self._compute_angular_power_spectrum(*args)
+476
+477        if c_ells is NotImplemented:
+478            return c_ells
+479
+480        return np.array([c_ells[key] for key in sorted(c_ells)])
+481
+482    def covariance(
+483        self,
+484        *args: tuple[str, float],
+485        **kwargs,
+486    ):
+487        r"""
+488        Compute the covariance of the $C_\ell$s of galaxy number counts.
+489
+490        Parameters
+491        ----------
+492        *args
+493            the name(s) and fiducial value(s) of the parameter(s) for which we
+494            want to compute the covariance
+495
+496        **kwargs
+497            keyword arguments for the covariance. Supported values are:
+498            - `fsky`: the sky coverage of the survey (default: 1)
+499            - `delta_ell`: the bin width in multipole space (default: 2 / `fsky`)
+500
+501        Notes
+502        -----
+503        The covariance is computed as:
+504
+505        $$
+506            \mathsf{C}[(ij), (pq), \ell, \ell'] = \delta_{\ell, \ell'}
+507            \frac{
+508                C_\ell(i, p) C_\ell(j, q) + C_\ell(i, q) C_\ell(j, p)
+509            }
+510            {
+511                f_\mathrm{sky} \Delta \ell (2 \ell + 1)
+512            }
+513        $$
+514
+515        The covariance is block diagonal in $\ell$, that is:
+516        $$
+517            \begin{pmatrix}
+518            \mathsf{C}[(ij), (pq), \ell = 2] & 0 & \ldots & 0\\\
+519            0 & \mathsf{C}[(ij), (pq), \ell = 3] & \ldots & 0\\\
+520            \vdots & \vdots & \ddots & \vdots\\\
+521            0 & 0 & \ldots & \mathsf{C}[(ij), (pq), \ell = \ell_\mathrm{max}]
+522            \end{pmatrix}
+523        $$
+524
+525        Note that the covariance may be singular if the cross-correlations
+526        between redshift bins are not zero (i.e. if using a non-zero value for
+527        the `non_diagonal` parameter).
+528        """
+529        ell_size, z_size, c_ells = self._compute_angular_power_spectrum(*args)
+530
+531        if c_ells is NotImplemented:
+532            return c_ells
+533
+534        if self._cross_correlations():
+535            blocks = []
+536            # we skip ell=0 and ell=1 as CLASS sets them to zero anyway
+537            for ell in range(2, ell_size):
+538                # array containing the covariance for fixed ell
+539                covariance_fixed_ell = np.zeros([z_size] * 4)
+540                for i1, i2, j1, j2 in product(  # pylint: disable=invalid-name
+541                    range(z_size), repeat=4
+542                ):
+543                    covariance_fixed_ell[i1, i2, j1, j2] = (
+544                        c_ells[(ell, i1, j1)] * c_ells[(ell, i2, j2)]
+545                        + c_ells[(ell, i1, j2)] * c_ells[(ell, i2, j1)]
+546                    )
+547                blocks.append(
+548                    np.reshape(covariance_fixed_ell, (z_size * z_size, z_size * z_size))
+549                    / (2 * ell + 1)
+550                )
+551
+552            result = block_diag(*blocks)
+553
+554        else:
+555            result = np.diag(np.array([2 * c_ells[key] ** 2 for key in sorted(c_ells)]))
+556
+557        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+558
+559        return result / final_kwargs["fsky"] / final_kwargs["delta_ell"]
+
+ + +

Interface for galaxy number count quantities.

+ +

Interface for computing derivatives using the galaxy number count signal +and covariance.

+
+ + +
+ +
+ + ClassyGalaxyCountsDerivative(*args, config: Optional[dict] = None, **kwargs) + + + +
+ +
364    def __init__(
+365        self,
+366        *args,
+367        config: Optional[dict] = None,
+368        **kwargs,
+369    ):
+370        """
+371        Create an instance.
+372
+373        Parameters
+374        ----------
+375        config : dict, optional
+376            the CLASS configuration to use. All parameters are accepted.
+377            If not specified, defaults to `{'output' : 'nCl'}`.
+378            If the key `output` is missing, it is inserted with a default value
+379            `nCl`.
+380        """
+381        super().__init__(*args, config=config, **kwargs)
+382        self._config = config if config is not None else {"output": "nCl"}
+383        if not self._config.get("output"):
+384            self._config["output"] = "nCl"
+
+ + +

Create an instance.

+ +
Parameters
+ +
    +
  • config (dict, optional): +the CLASS configuration to use. All parameters are accepted. +If not specified, defaults to {'output' : 'nCl'}. +If the key output is missing, it is inserted with a default value +nCl.
  • +
+
+ + +
+
+ +
+ + def + signal(self, *args: tuple[str, float], **kwargs): + + + +
+ +
455    def signal(
+456        self,
+457        *args: tuple[str, float],
+458        **kwargs,
+459    ):
+460        r"""
+461        Compute the signal ($C_\ell$s) of galaxy number counts.
+462
+463        Parameters
+464        ----------
+465        *args
+466            the name(s) and fiducial value(s) of the parameter(s) for which we
+467            want to compute the covariance
+468
+469        Notes
+470        -----
+471        The coordinates used are $(z_1, z_2, \ell)$, in that increasing order.
+472        Note that $\ell = \\{0, 1\\}$ are not part of the output, i.e. we
+473        impose $\ell_\mathrm{min} = 2$.
+474        """
+475        *_, c_ells = self._compute_angular_power_spectrum(*args)
+476
+477        if c_ells is NotImplemented:
+478            return c_ells
+479
+480        return np.array([c_ells[key] for key in sorted(c_ells)])
+
+ + +

Compute the signal ($C_\ell$s) of galaxy number counts.

+ +
Parameters
+ +
    +
  • *args: the name(s) and fiducial value(s) of the parameter(s) for which we +want to compute the covariance
  • +
+ +
Notes
+ +

The coordinates used are $(z_1, z_2, \ell)$, in that increasing order. +Note that $\ell = \{0, 1\}$ are not part of the output, i.e. we +impose $\ell_\mathrm{min} = 2$.

+
+ + +
+
+ +
+ + def + covariance(self, *args: tuple[str, float], **kwargs): + + + +
+ +
482    def covariance(
+483        self,
+484        *args: tuple[str, float],
+485        **kwargs,
+486    ):
+487        r"""
+488        Compute the covariance of the $C_\ell$s of galaxy number counts.
+489
+490        Parameters
+491        ----------
+492        *args
+493            the name(s) and fiducial value(s) of the parameter(s) for which we
+494            want to compute the covariance
+495
+496        **kwargs
+497            keyword arguments for the covariance. Supported values are:
+498            - `fsky`: the sky coverage of the survey (default: 1)
+499            - `delta_ell`: the bin width in multipole space (default: 2 / `fsky`)
+500
+501        Notes
+502        -----
+503        The covariance is computed as:
+504
+505        $$
+506            \mathsf{C}[(ij), (pq), \ell, \ell'] = \delta_{\ell, \ell'}
+507            \frac{
+508                C_\ell(i, p) C_\ell(j, q) + C_\ell(i, q) C_\ell(j, p)
+509            }
+510            {
+511                f_\mathrm{sky} \Delta \ell (2 \ell + 1)
+512            }
+513        $$
+514
+515        The covariance is block diagonal in $\ell$, that is:
+516        $$
+517            \begin{pmatrix}
+518            \mathsf{C}[(ij), (pq), \ell = 2] & 0 & \ldots & 0\\\
+519            0 & \mathsf{C}[(ij), (pq), \ell = 3] & \ldots & 0\\\
+520            \vdots & \vdots & \ddots & \vdots\\\
+521            0 & 0 & \ldots & \mathsf{C}[(ij), (pq), \ell = \ell_\mathrm{max}]
+522            \end{pmatrix}
+523        $$
+524
+525        Note that the covariance may be singular if the cross-correlations
+526        between redshift bins are not zero (i.e. if using a non-zero value for
+527        the `non_diagonal` parameter).
+528        """
+529        ell_size, z_size, c_ells = self._compute_angular_power_spectrum(*args)
+530
+531        if c_ells is NotImplemented:
+532            return c_ells
+533
+534        if self._cross_correlations():
+535            blocks = []
+536            # we skip ell=0 and ell=1 as CLASS sets them to zero anyway
+537            for ell in range(2, ell_size):
+538                # array containing the covariance for fixed ell
+539                covariance_fixed_ell = np.zeros([z_size] * 4)
+540                for i1, i2, j1, j2 in product(  # pylint: disable=invalid-name
+541                    range(z_size), repeat=4
+542                ):
+543                    covariance_fixed_ell[i1, i2, j1, j2] = (
+544                        c_ells[(ell, i1, j1)] * c_ells[(ell, i2, j2)]
+545                        + c_ells[(ell, i1, j2)] * c_ells[(ell, i2, j1)]
+546                    )
+547                blocks.append(
+548                    np.reshape(covariance_fixed_ell, (z_size * z_size, z_size * z_size))
+549                    / (2 * ell + 1)
+550                )
+551
+552            result = block_diag(*blocks)
+553
+554        else:
+555            result = np.diag(np.array([2 * c_ells[key] ** 2 for key in sorted(c_ells)]))
+556
+557        final_kwargs = self._parse_covariance_kwargs(**kwargs)
+558
+559        return result / final_kwargs["fsky"] / final_kwargs["delta_ell"]
+
+ + +

Compute the covariance of the $C_\ell$s of galaxy number counts.

+ +
Parameters
+ +
    +
  • *args: the name(s) and fiducial value(s) of the parameter(s) for which we +want to compute the covariance
  • +
  • **kwargs: keyword arguments for the covariance. Supported values are: +
      +
    • fsky: the sky coverage of the survey (default: 1)
    • +
    • delta_ell: the bin width in multipole space (default: 2 / fsky)
    • +
  • +
+ +
Notes
+ +

The covariance is computed as:

+ +

$$ + \mathsf{C}[(ij), (pq), \ell, \ell'] = \delta_{\ell, \ell'} + \frac{ + C_\ell(i, p) C_\ell(j, q) + C_\ell(i, q) C_\ell(j, p) + } + { + f_\mathrm{sky} \Delta \ell (2 \ell + 1) + } +$$

+ +

The covariance is block diagonal in $\ell$, that is: +$$ + \begin{pmatrix} + \mathsf{C}[(ij), (pq), \ell = 2] & 0 & \ldots & 0\\ + 0 & \mathsf{C}[(ij), (pq), \ell = 3] & \ldots & 0\\ + \vdots & \vdots & \ddots & \vdots\\ + 0 & 0 & \ldots & \mathsf{C}[(ij), (pq), \ell = \ell_\mathrm{max}] + \end{pmatrix} +$$

+ +

Note that the covariance may be singular if the cross-correlations +between redshift bins are not zero (i.e. if using a non-zero value for +the non_diagonal parameter).

+
+ + +
+
+
Inherited Members
+
+
ClassyBaseDerivative
+
software_names
+
urls
+
version
+
authors
+
from_file
+
config
+ +
+
fitk.derivatives.FisherDerivative
+
validate_parameter
+
derivative
+
fisher_matrix
+ +
+
+
+