Source code for yaw.config.binning

from __future__ import annotations

import warnings
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

import numpy as np

from yaw.config import DEFAULT, OPTIONS
from yaw.config.abc import BaseConfig
from yaw.config.utils import ConfigError
from yaw.core.cosmology import BinFactory, TypeCosmology
from yaw.core.docs import Parameter
from yaw.core.math import array_equal

if TYPE_CHECKING:  # pragma: no cover
    from numpy.typing import NDArray

__all__ = ["BinningConfig"]


[docs] @dataclass(frozen=True) class BinningConfig(BaseConfig): """TODO""" zbins: NDArray[np.float64] = field( metadata=Parameter( type=float, nargs="*", help="list of custom redshift bin edges, if method is set to 'manual'", ) ) """Edges of redshift bins.""" method: str = field( default=DEFAULT.Binning.method, metadata=Parameter( type=str, choices=OPTIONS.binning, help="redshift binning method, 'logspace' means equal size in log(1+z)", default_text="(default: %(default)s)", ), ) """Method used to create redshift binning (defaults to 'manual' if `zbins` are provided), see :obj:`~yaw.config.options.Options.binning` for options.""" zmin: float = field( init=False, metadata=Parameter( type=float, help="lower redshift limit", default_text="(default: %(default)s)", ), ) """Lowest redshift bin edge.""" zmax: float = field( init=False, metadata=Parameter( type=float, help="upper redshift limit", default_text="(default: %(default)s)", ), ) """Highest redshift bin edge.""" zbin_num: int = field( default=DEFAULT.Binning.zbin_num, init=False, metadata=Parameter( type=int, help="number of redshift bins", default_text="(default: %(default)s)", ), ) """Number of redshift bins.""" def __post_init__(self) -> None: if len(self.zbins) < 2: raise ConfigError("'zbins' must have at least two edges") object.__setattr__(self, "zbins", np.asarray(self.zbins)) BinFactory.check(self.zbins) object.__setattr__(self, "zmin", float(self.zbins[0])) object.__setattr__(self, "zmax", float(self.zbins[-1])) object.__setattr__(self, "zbin_num", len(self.zbins) - 1) def __eq__(self, other) -> bool: if not isinstance(other, self.__class__): return False if self.method != other.method or not array_equal(self.zbins, other.zbins): return False return True def __repr__(self) -> str: name = self.__class__.__name__ zbin_num = self.zbin_num z = f"{self.zmin:.3f}...{self.zmax:.3f}" method = self.method return f"{name}({zbin_num=}, {z=}, {method=})" @property def is_manual(self) -> bool: """Whether the redshift bins are set manually.""" return self.method == "manual"
[docs] @classmethod def create( cls, *, zbins: NDArray[np.float64] | None = None, zmin: float | None = None, zmax: float | None = None, zbin_num: int = DEFAULT.Binning.zbin_num, method: str = DEFAULT.Binning.method, cosmology: TypeCosmology | str | None = None, ) -> BinningConfig: """Create a new redshift binning configuration. If redshift bins (``zbins``) are provided, ``method`` is set to ``"manual"`` and the bins edges are used. If ``zmin`` and ``zmax`` are provided, instead generates a specified number of bins between this lower and upper redshift limit. The spacing of the bins depends the generation method, the default is a linear spacing. .. Note:: The ``cosmology`` parameter is only used when generating binnings that require cosmological distance computations. Args: zbins (:obj:`NDArray[np.float64]`): Monotonically increasing redshift bin edges, including the upper edge (ignored if ``zmin`` and ``zmax`` are provided). zmin (:obj:`float`): Minimum redshift, lowest redshift edge. zmax (:obj:`float`): Maximum redshift, highest redshift edge. zbin_num (:obj:`int`, optional): Number of redshift bins to generate. method (:obj:`str`, optional): Method used to create redshift binning, for a list of valid options and their description see :obj:`~yaw.config.options.Options.binning`. cosmology (:obj:`astropy.cosmology.FLRW`, :obj:`~yaw.core.cosmology.CustomCosmology`, optional): Cosmological model used for distance calculations. For a custom model, refer to :mod:`yaw.core.cosmology`. Returns: :obj:`BinningConfig` """ auto_args_set = (zmin is not None, zmax is not None) manual_args_set = (zbins is not None,) if not all(manual_args_set) and not all(auto_args_set): raise ConfigError("either 'zbins' or 'zmin' and 'zmax' are required") elif all(auto_args_set): # generate zbins if all(manual_args_set): warnings.warn( "'zbins' set but ignored since 'zmin' and 'zmax' are provided" ) if not isinstance(method, str): raise ValueError("'method' must a string") zbins = BinFactory(zmin, zmax, zbin_num, cosmology).get(method) else: # use provided zbins method = "manual" return cls(zbins=zbins, method=method)
[docs] def modify( self, zbins: NDArray[np.float64] | None = DEFAULT.NotSet, zmin: float | None = DEFAULT.NotSet, zmax: float | None = DEFAULT.NotSet, zbin_num: int = DEFAULT.NotSet, method: str = DEFAULT.NotSet, cosmology: TypeCosmology | str | None = None, ) -> BinningConfig: """Create a copy of the current configuration with updated parameter values. The method arguments are identical to :meth:`create`. Values that should not be modified are by default represented by the special value :obj:`~yaw.config.default.NotSet`. .. Note:: The ``cosmology`` parameter is only used when generating binnings that require cosmological distance computations. """ if zbins is not DEFAULT.NotSet: kwargs = dict(zbins=zbins) else: inputs = dict(zmin=zmin, zmax=zmax, zbin_num=zbin_num, method=method) mods = {k: v for k, v in inputs.items() if v is not DEFAULT.NotSet} if self.is_manual: # use every input as parameter kwargs = mods else: # keep the existing parameters and update with inputs kwargs = self.to_dict() kwargs.update(mods) return self.__class__.create(**kwargs, cosmology=cosmology)
[docs] def to_dict(self) -> dict[str, Any]: if self.is_manual: return dict(method=self.method, zbins=self.zbins.tolist()) else: return dict( zmin=self.zmin, zmax=self.zmax, zbin_num=self.zbin_num, method=self.method, )
[docs] @classmethod def from_dict( cls, the_dict: dict[str, Any], cosmology: TypeCosmology | None = None ) -> BinningConfig: """Create a class instance from a dictionary representation of the minimally required data. Args: the_dict (:obj:`dict`): Dictionary containing the data. cosmology (:obj:`astropy.cosmology.FLRW`, :obj:`~yaw.core.cosmology.CustomCosmology`, optional): Cosmological model used for distance calculations. For a custom model, refer to :mod:`yaw.core.cosmology`. """ return cls.create(**the_dict, cosmology=cosmology)