"""
Implements the central configuration class for `yet_another_wizz`. This stores
the configuration of measurement scales, redshift binning and cosmological model
to use for correlation fuction measurements.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, get_args
import astropy.cosmology
from yaw.config.base import BaseConfig, ConfigError, Immutable, Parameter, ParamSpec
from yaw.config.binning import BinningConfig
from yaw.config.scales import ScalesConfig
from yaw.cosmology import (
CustomCosmology,
TypeCosmology,
cosmology_is_equal,
get_default_cosmology,
)
from yaw.options import BinMethod, Closed, NotSet, Unit
from yaw.utils import parallel
if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path
from typing import Any
__all__ = [
"Configuration",
]
logger = logging.getLogger(__name__)
def cosmology_to_yaml(cosmology: TypeCosmology) -> str:
"""
Attempt to serialise a cosmology instance to YAML.
.. caution::
This currently works only for named astropy cosmologies.
Args:
cosmology:
A cosmology class instance, either a custom or astropy cosmology.
Returns:
A YAML string.
"""
if isinstance(cosmology, CustomCosmology):
raise ConfigError("cannot serialise custom cosmologies to YAML")
elif not isinstance(cosmology, astropy.cosmology.FLRW):
raise TypeError(f"invalid type '{type(cosmology)}' for cosmology")
if cosmology.name not in astropy.cosmology.available:
raise ConfigError("can only serialise predefined astropy cosmologies to YAML")
return cosmology.name
def yaml_to_cosmology(cosmo_name: str) -> TypeCosmology:
"""Restore a cosmology class instance from a YAML string."""
if cosmo_name not in astropy.cosmology.available:
raise ConfigError(
"unknown cosmology, for available options see 'astropy.cosmology.available'"
)
return getattr(astropy.cosmology, cosmo_name)
def parse_cosmology(cosmology: TypeCosmology | str | None) -> TypeCosmology:
"""
Parse and verify that the provided cosmology is supported by
yet_another_wizz.
Parses any YAML string or replaces None with the default cosmology.
Args:
cosmology:
A cosmology class instance, either a custom or astropy cosmology, or
``None`` or a cosmology serialised to YAML.
Returns:
A cosmology class instance, either a custom or astropy cosmology.
Raises:
ConfigError:
If the cosmology cannot be parsed.
"""
if cosmology is None:
return get_default_cosmology()
elif isinstance(cosmology, str):
return yaml_to_cosmology(cosmology)
elif not isinstance(cosmology, get_args(TypeCosmology)):
which = ", ".join(str(c) for c in get_args(TypeCosmology))
raise ConfigError(f"'cosmology' must be instance of: {which}")
return cosmology
default_cosmology = get_default_cosmology().name
[docs]
class Configuration(BaseConfig, Immutable):
"""
Configuration for correlation function measurements.
This is the top-level configuration class for `yet_another_wizz`, defining
correlation scales, redshift binning, and the cosmological model used for
distance calculations.
.. 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.
"""
scales: ScalesConfig
"""Organises the configuration of correlation scales."""
binning: BinningConfig
"""Organises the configuration of redshift bins."""
cosmology: TypeCosmology | str
"""Optional cosmological model to use for distance computations."""
max_workers: int | None
"""Limit the number of workers for parallel operations (all by default)."""
def __init__(
self,
scales: ScalesConfig,
binning: BinningConfig,
cosmology: TypeCosmology | str | None = None,
max_workers: int | None = None,
) -> None:
if not isinstance(scales, ScalesConfig):
raise TypeError(f"'scales' must be of type '{type(ScalesConfig)}'")
object.__setattr__(self, "scales", scales)
if not isinstance(binning, BinningConfig):
raise TypeError(f"'binning' must be of type '{type(BinningConfig)}'")
object.__setattr__(self, "binning", binning)
object.__setattr__(self, "cosmology", parse_cosmology(cosmology))
object.__setattr__(
self, "max_workers", int(max_workers) if max_workers else None
)
[docs]
@classmethod
def from_dict(cls, the_dict: dict[str, Any], **kwargs) -> Configuration:
the_dict = the_dict.copy()
cosmology = parse_cosmology(the_dict.pop("cosmology", default_cosmology))
max_workers = the_dict.pop("max_workers", None)
try: # scales
scales_dict = the_dict.pop("scales")
scales = ScalesConfig.from_dict(scales_dict)
except (TypeError, KeyError) as err:
raise ConfigError("failed parsing the 'scales' section") from err
try: # binning
binning_dict = the_dict.pop("binning")
binning = BinningConfig.from_dict(binning_dict, cosmology=cosmology)
except (TypeError, KeyError) as err:
raise ConfigError("failed parsing the 'binning' section") from err
if len(the_dict) > 0:
key = next(iter(the_dict))
raise ConfigError(f"encountered unknown configuration entry '{key}'")
return cls(
scales=scales, binning=binning, cosmology=cosmology, max_workers=max_workers
)
[docs]
def to_dict(self) -> dict[str, Any]:
return dict(
scales=self.scales.to_dict(),
binning=self.binning.to_dict(),
cosmology=cosmology_to_yaml(self.cosmology),
max_workers=self.max_workers,
)
[docs]
@classmethod
def from_file(cls, path: Path | str) -> Configuration:
new = None
if parallel.on_root():
logger.info("reading configuration file: %s", path)
new = super().from_file(path)
return parallel.COMM.bcast(new, root=0)
[docs]
def to_file(self, path: Path | str) -> None:
if parallel.on_root():
logger.info("writing configuration file: %s", path)
super().to_file(path)
parallel.COMM.Barrier()
def __eq__(self, other) -> bool:
if not isinstance(other, type(self)):
return False
return (
self.binning == other.binning
and self.scales == other.scales
and cosmology_is_equal(self.cosmology, other.cosmology)
)
[docs]
@classmethod
def get_paramspec(cls) -> ParamSpec:
params = [
Parameter(
name="cosmology",
help="Optional cosmological model to use for distance computations.",
type=str,
default=default_cosmology,
),
]
return ParamSpec(params)
[docs]
@classmethod
def create(
cls,
*,
# ScalesConfig
rmin: Iterable[float] | float,
rmax: Iterable[float] | float,
unit: Unit = Unit.kpc,
rweight: float | None = None,
resolution: int | None = None,
# BinningConfig
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,
# uncategorized
cosmology: TypeCosmology | str | None = default_cosmology,
max_workers: int | None = None,
) -> Configuration:
"""
Create a new instance with the given parameters.
Keyword Args:
rmin:
Single or multiple lower scale limits in kpc (angular diameter
distance).
rmax:
Single or multiple upper scale limits in kpc (angular diameter
distance).
unit:
String describing the angular, physical, or comoving unit of
correlation scales, see :obj:`~yaw.options.Unit` for valid
options (default: kpc).
rweight:
Optional power-law exponent :math:`\\alpha` used to weight pairs
by their separation.
resolution:
Optional number of radial logarithmic bin used to approximate
the weighting by separation.
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.
max_workers:
Limit the number of parallel workers for this operation (all by
default).
Returns:
New configuration instance.
.. note::
Although the function parameters are optional, either ``zmin`` and
``zmax`` (generate bin edges), or ``edges`` (custom bin edges) must
be provided.
"""
cosmology = parse_cosmology(cosmology)
scales = ScalesConfig.create(
rmin=rmin, rmax=rmax, unit=unit, rweight=rweight, resolution=resolution
)
binning = BinningConfig.create(
zmin=zmin,
zmax=zmax,
num_bins=num_bins,
method=method,
edges=edges,
closed=closed,
cosmology=cosmology,
)
return cls(
scales=scales, binning=binning, cosmology=cosmology, max_workers=max_workers
)
[docs]
def modify(
self,
*,
# ScalesConfig
rmin: Iterable[float] | float | NotSet = NotSet,
rmax: Iterable[float] | float | NotSet = NotSet,
unit: Unit | NotSet = NotSet,
rweight: float | None | NotSet = NotSet,
resolution: int | None | NotSet = NotSet,
# BinningConfig
zmin: float | NotSet = NotSet,
zmax: float | NotSet = NotSet,
num_bins: int | NotSet = NotSet,
method: BinMethod | str | NotSet = NotSet,
edges: Iterable[float] | None = NotSet,
closed: Closed | str | NotSet = NotSet,
# uncategorized
cosmology: TypeCosmology | str | None | NotSet = NotSet,
max_workers: int | None | NotSet = NotSet,
) -> Configuration:
"""
Create a new configuration instance with updated parameter values.
Parameter values are only updated if they are provided as inputs to this
function and retained from the original instance otherwise.
Keyword Args:
rmin:
Single or multiple lower scale limits in kpc (angular diameter
distance).
rmax:
Single or multiple upper scale limits in kpc (angular diameter
distance).
unit:
String describing the angular, physical, or comoving unit of
correlation scales, see :obj:`~yaw.options.Unit` for valid
options (default: kpc).
rweight:
Optional power-law exponent :math:`\\alpha` used to weight pairs
by their separation.
resolution:
Optional number of radial logarithmic bin used to approximate
the weighting by separation.
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.
max_workers:
Limit the number of parallel workers for this operation (all by
default).
Returns:
New instance with updated parameter values.
"""
scales = self.scales.modify(
rmin=rmin, rmax=rmax, unit=unit, rweight=rweight, resolution=resolution
)
binning = self.binning.modify(
zmin=zmin,
zmax=zmax,
num_bins=num_bins,
method=method,
edges=edges,
closed=closed,
cosmology=cosmology,
)
cosmology = (
self.cosmology if cosmology is NotSet else parse_cosmology(cosmology)
)
max_workers = self.max_workers if max_workers is NotSet else max_workers
return type(self)(
scales=scales, binning=binning, cosmology=cosmology, max_workers=max_workers
)