Source code for yaw.config.binning

"""
Implements a class that stores the configuration redshift bins used to split the
catalogs when measuring angular correlations.
"""

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING

from yaw.binning import Binning
from yaw.config.base import BaseConfig, ConfigError, Immutable, Parameter, ParamSpec
from yaw.cosmology import RedshiftBinningFactory, TypeCosmology
from yaw.options import BinMethod, Closed, NotSet, get_options

if TYPE_CHECKING:
    from collections.abc import Iterable
    from typing import Any

    from numpy.typing import NDArray

__all__ = [
    "BinningConfig",
]


[docs] class BinningConfig(BaseConfig, Immutable): """ Configuration of the redshift binning for correlation function measurements. Correlations are measured in bins of redshift, which determines the redshift-resolution of the clustering redshift estimate. This configuration class offers three automatic methods to generate these bins between a minimum and maximum redshift: - ``linear`` (default): bin edges spaced linearly in redshift :math:`z`, - ``comoving``: bin edges spaced linearly in comoving distance :math:`\\chi(z)`, and - ``logspace``: bin edges spaced linearly in :math:`1+\\ln(z)`. Alternatively, custom bin edges may be provided. .. note:: The preferred way to create a new configuration instance is using the :meth:`create()` constructor. All configuration objects are immutable. To modify an existing configuration, create a new instance with updated values by using the :meth:`modify()` method. The bin edges are recomputed when necessary. """ binning: Binning """Container for the redshift bins.""" method: BinMethod """Method used to generate the bin edges, see :obj:`~yaw.options.BinMethod` for valid options.""" def __init__( self, binning: Binning, method: BinMethod | str = BinMethod.linear, ) -> None: if not isinstance(binning, Binning): raise TypeError(f"'binning' must be of type '{type(binning)}'") object.__setattr__(self, "binning", binning) object.__setattr__(self, "method", BinMethod(method)) @property def edges(self) -> NDArray: """Array of redshift bin edges.""" return self.binning.edges @property def zmin(self) -> float: """Lowest redshift bin edge.""" return float(self.binning.edges[0]) @property def zmax(self) -> float: """Highest redshift bin edge.""" return float(self.binning.edges[-1]) @property def num_bins(self) -> int: """Number of redshift bins.""" return len(self.binning) @property def closed(self) -> Closed: """Indicating which side of the bin edges is a closed interval, see :obj:`~yaw.options.Closed` for valid options.""" return self.binning.closed @property def is_custom(self) -> bool: """Whether the bin edges are provided by the user.""" return self.method == "custom"
[docs] @classmethod def from_dict( cls, the_dict: dict[str, Any], cosmology: TypeCosmology | None = None ) -> BinningConfig: """ Restore the class instance from a python dictionary. Args: the_dict: Dictionary containing all required data attributes to restore the instance, see also :meth:`to_dict()`. cosmology: Optional, cosmological model to use for distance computations. Returns: Restored class instance. .. caution:: This cosmology object is not stored with this instance, but should be managed by the top level :obj:`~yaw.Configuration` class. """ is_custom = ( the_dict.get("method", "") == BinMethod.custom or the_dict.get("edges", None) is not None ) if is_custom: edges = the_dict.pop("edges") closed = the_dict.pop("closed") binning = Binning(edges, closed=closed) return cls(binning, **the_dict) return cls.create(**the_dict, cosmology=cosmology)
[docs] def to_dict(self) -> dict[str, Any]: the_dict = dict(method=str(self.method)) if self.is_custom: the_dict.update( dict( zmin=None, zmax=None, num_bins=None, edges=self.binning.edges.tolist(), ) ) else: the_dict.update( dict( zmin=self.zmin, zmax=self.zmax, num_bins=self.num_bins, edges=None, ) ) the_dict["closed"] = str(self.closed) return the_dict
def __eq__(self, other) -> bool: if not isinstance(other, type(self)): return False return self.method == other.method and self.binning == other.binning
[docs] @classmethod def get_paramspec(cls) -> ParamSpec: params = [ Parameter( name="zmin", help="Lowest redshift bin edge to generate.", type=float, ), Parameter( name="zmax", help="Highest redshift bin edge to generate.", type=float, ), Parameter( name="num_bins", help="Number of redshift bins to generate.", type=int, default=30, ), Parameter( name="method", help="Method used to generate the bin edges, must be either of ``linear``, ``comoving``, ``logspace``, or ``custom``.", type=str, choices=get_options(BinMethod), default=str(BinMethod.linear), ), Parameter( name="edges", help="Use these custom bin edges instead of generating them.", type=float, is_sequence=True, default=None, ), Parameter( name="closed", help="String indicating if the bin edges are closed on the ``left`` or the ``right`` side.", type=str, choices=get_options(Closed), default=str(Closed.right), ), ] return ParamSpec(params)
[docs] @classmethod def create( cls, *, zmin: float | None = None, zmax: float | None = None, num_bins: int = 30, method: BinMethod | str = BinMethod.linear, edges: Iterable[float] | None = None, closed: Closed | str = Closed.right, cosmology: TypeCosmology | None = None, ) -> BinningConfig: """ Create a new instance with the given parameters. Keyword Args: zmin: Lowest redshift bin edge to generate. zmax: Highest redshift bin edge to generate. num_bins: Number of redshift bins to generate. method: Method used to generate the bin edges, see :obj:`~yaw.options.BinMethod` for valid options. edges: Use these custom bin edges instead of generating them. closed: Indicating which side of the bin edges is a closed interval, see :obj:`~yaw.options.Closed` for valid options. cosmology: Optional, cosmological model to use for distance computations. Returns: New configuration instance. .. note:: All function parameters are optional, but either ``zmin`` and ``zmax`` (generate bin edges), or ``edges`` (custom bin edges) must be provided. .. caution:: This cosmology object is not stored with this instance, but should be managed by the top level :obj:`~yaw.Configuration` class. """ auto_args_set = (zmin is not None, zmax is not None) custom_args_set = (edges is not None,) if not all(custom_args_set) and not all(auto_args_set): raise ConfigError("either 'edges' or 'zmin' and 'zmax' are required") closed = Closed(closed) if all(auto_args_set): # generate bin edges if all(custom_args_set): warnings.warn( "'zbins' set but ignored since 'zmin' and 'zmax' are provided" ) method = BinMethod(method) bin_func = RedshiftBinningFactory(cosmology).get_method(method) binning = bin_func(zmin, zmax, num_bins, closed=closed) else: # use provided bin edges method = BinMethod.custom binning = Binning(edges, closed=closed) return cls(binning, method=method)
[docs] def modify( self, *, zmin: float | NotSet = NotSet, zmax: float | NotSet = NotSet, num_bins: int | NotSet = NotSet, method: BinMethod | str | NotSet = NotSet, edges: Iterable[float] | NotSet = NotSet, closed: Closed | str | NotSet = NotSet, cosmology: TypeCosmology | None | NotSet = NotSet, ) -> BinningConfig: """ Create a new configuration instance with updated parameter values. Parameter values are only updated if they are provided as inputs to this function, otherwise they are retained from the original instance. Keyword Args: zmin: Lowest redshift bin edge to generate. zmax: Highest redshift bin edge to generate. num_bins: Number of redshift bins to generate. method: Method used to generate the bin edges, see :obj:`~yaw.options.BinMethod` for valid options. edges: Use these custom bin edges instead of generating them. closed: Indicating which side of the bin edges is a closed interval, see :obj:`~yaw.options.Closed` for valid options. cosmology: Optional, cosmological model to use for distance computations. Returns: New instance with updated redshift bins. .. caution:: This cosmology object is not stored with this instance, but should be managed by the top level :obj:`~yaw.Configuration` class. """ if edges is NotSet: if method == "custom": raise ConfigError("'method' is 'custom' but no bin edges provided") the_dict = dict() the_dict["zmin"] = self.zmin if zmin is NotSet else zmin the_dict["zmax"] = self.zmax if zmax is NotSet else zmax the_dict["num_bins"] = self.num_bins if num_bins is NotSet else num_bins the_dict["method"] = self.method if method is NotSet else BinMethod(method) else: the_dict = dict(edges=edges) the_dict["method"] = BinMethod.custom the_dict["method"] = str(the_dict["method"]) the_dict["closed"] = str(self.closed if closed is NotSet else Closed(closed)) return type(self).from_dict(the_dict, cosmology=cosmology)