Skip to content

Commit

Permalink
Merge branch 'master' into action
Browse files Browse the repository at this point in the history
  • Loading branch information
cmbant authored Nov 11, 2024
2 parents 0aa57be + 94d4fe7 commit b98a6e2
Show file tree
Hide file tree
Showing 23 changed files with 379 additions and 70 deletions.
38 changes: 29 additions & 9 deletions cobaya/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
from inspect import cleandoc
from packaging import version
from importlib import import_module, resources
from typing import Optional, Union, List, Set
from typing import Optional, Union, List, Set, get_type_hints

from cobaya.log import HasLogger, LoggedError, get_logger
from cobaya.typing import Any, InfoDict, InfoDictIn, empty_dict
from cobaya.typing import Any, InfoDict, InfoDictIn, empty_dict, validate_type
from cobaya.tools import resolve_packages_path, load_module, get_base_classes, \
get_internal_class_component_name, deepcopy_where_possible, VersionCheckError
from cobaya.conventions import kinds, cobaya_package, reserved_attributes
from cobaya.yaml import yaml_load_file, yaml_dump, yaml_load
from cobaya.mpi import is_main_process
import cobaya


class Timer:
Expand Down Expand Up @@ -278,7 +279,7 @@ def get_defaults(cls, return_yaml=False, yaml_expand_defaults=True,
"(type declarations without values are fine "
"with yaml file as well).",
cls.get_qualified_class_name(), list(both))
options |= yaml_options
options.update(yaml_options)
yaml_text = None
if return_yaml and not yaml_expand_defaults:
return yaml_text or ""
Expand Down Expand Up @@ -315,6 +316,7 @@ def get_annotations(cls) -> InfoDict:
if issubclass(base, HasDefaults) and base is not HasDefaults:
d.update(base.get_annotations())

# from Python 10 should use just cls.__annotations__
d.update({k: v for k, v in cls.__dict__.get("__annotations__", {}).items()
if not k.startswith('_')})
return d
Expand All @@ -331,6 +333,8 @@ class CobayaComponent(HasLogger, HasDefaults):
_at_resume_prefer_new: List[str] = ["version"]
_at_resume_prefer_old: List[str] = []

_enforce_types: bool = False

def __init__(self, info: InfoDictIn = empty_dict,
name: Optional[str] = None,
timing: Optional[bool] = None,
Expand All @@ -355,6 +359,8 @@ def __init__(self, info: InfoDictIn = empty_dict,
except AttributeError:
raise AttributeError("Cannot set {} attribute for {}!".format(k, self))
self.set_logger(name=self._name)
self.validate_attributes(annotations)

self.set_timing_on(timing)
try:
if initialize:
Expand Down Expand Up @@ -412,21 +418,35 @@ def has_version(self):
"""
return True

def validate_info(self, k: str, value: Any, annotations: dict):
def validate_info(self, name: str, value: Any, annotations: dict):
"""
Does any validation on parameter k read from an input dictionary or yaml file,
before setting the corresponding class attribute.
You could enforce consistency with annotations here, but does not by default.
This check is always done, even if _enforce_types is not set.
:param k: name of parameter
:param name: name of parameter
:param value: value
:param annotations: resolved inherited dictionary of attributes for this class
"""

# by default just test booleans, e.g. for typos of "false" which evaluate true
if annotations.get(k) is bool and value and isinstance(value, str):
if annotations.get(name) is bool and value and isinstance(value, str):
raise AttributeError("Class '%s' parameter '%s' should be True "
"or False, got '%s'" % (self, k, value))
"or False, got '%s'" % (self, name, value))

def validate_attributes(self, annotations: dict):
"""
If _enforce_types or cobaya.typing.enforce_type_checking is set, this
checks all class attributes against the annotation types
:param annotations: resolved inherited dictionary of attributes for this class
:raises: TypeError if any attribute does not match the annotation type
"""
check = cobaya.typing.enforce_type_checking
if check or self._enforce_types and check is not False:
hints = get_type_hints(self.__class__) # resolve any deferred attributes
for name in annotations:
validate_type(hints[name], getattr(self, name, None),
self.get_name() + ':' + name)

@classmethod
def get_kind(cls):
Expand Down
42 changes: 33 additions & 9 deletions cobaya/cosmo_input/input_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,8 @@

like_cmb: InfoDict = {
none: {},
"planck_NPIPE": {
"desc": "Planck NPIPE (native; polarized NPIPE CMB + lensing)",
"planck_NPIPE_CamSpec": {
"desc": "Planck NPIPE CamSpec (native; polarized NPIPE CMB + lensing)",
"sampler": cmb_sampler_recommended,
"theory": {theo: {"extra_args": cmb_precision[theo]}
for theo in ["camb", "classy"]},
Expand All @@ -427,6 +427,22 @@
"planckpr4lensing":
{'package_install': {'github_repository': 'carronj/planck_PR4_lensing',
'min_version': '1.0.2'}}}},
"planck_NPIPE_Hillipop": {
"desc": "Planck NPIPE Hillipop+Lollipop (polarized NPIPE CMB + lensing)",
"sampler": cmb_sampler_recommended,
"theory": {theo: {"extra_args": cmb_precision[theo]}
for theo in ["camb", "classy"]},
"likelihood": {
"planck_2018_lowl.TT": None,
"planck_2020_lollipop.lowlE":
{'package_install': {'pip': 'planck-npipe/lollipop',
'min_version': '4.1.1'}},
"planck_2020_hillipop.TTTEEE":
{'package_install': {'pip': 'planck-npipe/hillipop',
'min_version': '4.2.2'}},
"planckpr4lensing":
{'package_install': {'github_repository': 'carronj/planck_PR4_lensing',
'min_version': '1.0.2'}}}},
"planck_2018": {
"desc": "Planck 2018 (Polarized CMB + lensing)",
"sampler": cmb_sampler_recommended,
Expand Down Expand Up @@ -610,14 +626,22 @@
preset: InfoDict = dict([
(none, {"desc": "(No preset chosen)"}),
# Pure CMB #######################################################
("planck_NPIPE_camb", {
"desc": "Planck NPIPE with CAMB (all native Python)",
("planck_NPIPE_CamSpec_camb", {
"desc": "Planck NPIPE CamSpec with CAMB (all native Python)",
"theory": "camb",
"like_cmb": "planck_NPIPE_CamSpec"}),
("planck_NPIPE_CamSpec_classy", {
"desc": "Planck NPIPE CamSpec with CLASS (all native Python)",
"theory": "classy",
"like_cmb": "planck_NPIPE_CamSpec"}),
("planck_NPIPE_Hillipop_camb", {
"desc": "Planck NPIPE Hillipop+Lollipop with CAMB (all native Python)",
"theory": "camb",
"like_cmb": "planck_NPIPE"}),
("planck_NPIPE_classy", {
"desc": "Planck NPIPE with CLASS (all native Python)",
"like_cmb": "planck_NPIPE_Hillipop"}),
("planck_NPIPE_Hillipop_classy", {
"desc": "Planck NPIPE Hillipop+Lollipop with CLASS (all native Python)",
"theory": "classy",
"like_cmb": "planck_NPIPE"}),
"like_cmb": "planck_NPIPE_Hillipop"}),
("planck_2018_camb", {
"desc": "Planck 2018 with CAMB",
"theory": "camb",
Expand Down Expand Up @@ -745,7 +769,7 @@
# BASIC INSTALLATION #####################################################################
install_basic: InfoDict = {
"theory": theory,
"likelihood": dict(like_cmb["planck_NPIPE"]["likelihood"], **{
"likelihood": dict(like_cmb["planck_NPIPE_CamSpec"]["likelihood"], **{
# 2018 lensing ensured covmat database also installed
"planck_2018_lensing.native": None,
"sn.pantheon": None,
Expand Down
3 changes: 3 additions & 0 deletions cobaya/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from cobaya.log import LoggedError, get_logger
from cobaya.parameterization import expand_info_param
from cobaya import mpi
import cobaya.typing

# Logger
logger = get_logger(__name__)
Expand Down Expand Up @@ -141,6 +142,8 @@ def load_info_overrides(*infos_or_yaml_or_files, **flags) -> InputDict:
for flag, value in flags.items():
if value is not None:
info[flag] = value
if cobaya.typing.enforce_type_checking:
cobaya.typing.validate_type(InputDict, info)
return info


Expand Down
2 changes: 1 addition & 1 deletion cobaya/likelihood.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def calculate(self, state, want_derived=True, **params_values_dict):
derived: Optional[ParamValuesDict] = {} if want_derived else None
state["logp"] = -np.inf # in case of exception
state["logp"] = self.logp(_derived=derived, **params_values_dict)
self.log.debug("Computed log-likelihood = %r", state["logp"])
self.log.debug("Computed log-likelihood = %s", state["logp"])
if derived is not None:
state["derived"] = derived.copy()

Expand Down
6 changes: 4 additions & 2 deletions cobaya/likelihoods/base_classes/planck_2018_CamSpec_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ class Planck2018CamSpecPython(DataSetLikelihood):

@classmethod
def get_bibtex(cls):
from cobaya.likelihoods.base_classes import Planck2018Clik
return Planck2018Clik.get_bibtex()
if not (res := super().get_bibtex()):
from cobaya.likelihoods.base_classes import Planck2018Clik
return Planck2018Clik.get_bibtex()
return res

def read_normalized(self, filename, pivot=None):
# arrays all based at L=0, in L(L+1)/2pi units
Expand Down
8 changes: 8 additions & 0 deletions cobaya/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import platform
import traceback
import functools
import numpy as np
from random import shuffle, choice

# Local
Expand Down Expand Up @@ -230,6 +231,7 @@ def format(self, record):
"%(message)s")
self._style._fmt = fmt
return super().format(record)

# Configure stdout handler
handle_stdout = logging.StreamHandler(sys.stdout)
handle_stdout.setLevel(level)
Expand Down Expand Up @@ -300,3 +302,9 @@ def mpi_info(self, msg, *args, **kwargs):
@mpi.root_only
def mpi_debug(self, msg, *args, **kwargs):
self.log.debug(msg, *args, **kwargs)

def param_dict_debug(self, msg, dic: dict):
"""Removes numpy2 np.float64 for consistent output"""
if self.log.getEffectiveLevel() <= logging.DEBUG:
self.log.debug(msg, {k: float(v) if isinstance(v, np.number) else v
for k, v in dic.items()})
19 changes: 9 additions & 10 deletions cobaya/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class LogPosterior:
A consistency check will be performed if initialized simultaneously with
log-posterior, log-priors and log-likelihoods, so, for faster initialisation,
you may prefer to pass log-priors and log-likelhoods only, and only pass all three
you may prefer to pass log-priors and log-likelihoods only, and only pass all three
(so that the test is performed) only when e.g. loading from an old sample.
If ``finite=True`` (default: False), it will try to represent infinities as the
Expand Down Expand Up @@ -368,7 +368,7 @@ def _loglikes_input_params(
outpar_dict: ParamValuesDict = {}
compute_success = True
self.provider.set_current_input_params(input_params)
self.log.debug("Got input parameters: %r", input_params)
self.param_dict_debug("Got input parameters: %r", input_params)
loglikes = np.zeros(len(self.likelihood))
need_derived = self.requires_derived or return_derived or return_output_params
for (component, like_index), param_dep in zip(self._component_order.items(),
Expand All @@ -391,7 +391,7 @@ def _loglikes_input_params(
raise LoggedError(
self.log,
"Likelihood %s has not returned a valid log-likelihood, "
"but %r instead.", component,
"but %s instead.", component,
component.current_logp) from type_excpt
if make_finite:
loglikes = np.nan_to_num(loglikes)
Expand All @@ -413,7 +413,7 @@ def _loglikes_input_params(
else list(outpar_dict.values()))
else: # explicitly derived, instead of output params
derived_dict = self.parameterization.to_derived(outpar_dict)
self.log.debug("Computed derived parameters: %s", derived_dict)
self.param_dict_debug("Computed derived parameters: %s", derived_dict)
return_params = (derived_dict if as_dict
else list(derived_dict.values()))
return return_likes, return_params
Expand Down Expand Up @@ -537,12 +537,12 @@ def logposterior(self,
self.log.debug(
"Posterior to be computed for parameters %s",
dict(zip(self.parameterization.sampled_params(),
params_values_array)))
params_values_array.astype(float))))
if not np.all(np.isfinite(params_values_array)):
raise LoggedError(
self.log, "Got non-finite parameter values: %r",
dict(zip(self.parameterization.sampled_params(),
params_values_array)))
params_values_array.astype(float))))
# Notice that we don't use the make_finite in the prior call,
# to correctly check if we have to compute the likelihood
logpriors_1d = self.prior.logps_internal(params_values_array)
Expand Down Expand Up @@ -591,8 +591,8 @@ def logpost(self,

def get_valid_point(self, max_tries: int, ignore_fixed_ref: bool = False,
logposterior_as_dict: bool = False, random_state=None,
) -> Union[Tuple[np.ndarray, LogPosterior],
Tuple[np.ndarray, dict]]:
) \
-> Union[Tuple[np.ndarray, LogPosterior], Tuple[np.ndarray, dict]]:
"""
Finds a point with finite posterior, sampled from the reference pdf.
Expand Down Expand Up @@ -1087,8 +1087,7 @@ def _assign_params(self, info_likelihood, info_theory=None,
# Update infos! (helper theory parameters stored in yaml with host)
inf = (info_likelihood if component in self.likelihood.values() else
info_theory)
inf = inf.get(component.get_name())
if inf:
if inf := inf.get(component.get_name()):
inf.pop("params", None)
inf[option] = component.get_attr_list_with_helpers(option)
if self.is_debug_and_mpi_root():
Expand Down
8 changes: 7 additions & 1 deletion cobaya/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@ def __init__(self, prefix, resume=resume_default, force=False, infix=None):
self.log.info("Output to be read-from/written-into folder '%s', with prefix '%s'",
self.folder, self.prefix)
self._resuming = False
if os.path.isfile(self.file_updated):
self._has_old_updated_info = os.path.isfile(self.file_updated)
if self._has_old_updated_info:
self.log.info(
"Found existing info files with the requested output prefix: '%s'",
prefix)
Expand All @@ -388,6 +389,11 @@ def __init__(self, prefix, resume=resume_default, force=False, infix=None):
# Only in this case we can be sure that we are actually resuming
self._resuming = True
self.log.info("Let's try to resume/load.")
else:
self.log.debug(
"There was old updated info, but no resume or force requested. "
"Behavior will be handled by sampler."
)

@mpi.root_only
def create_folder(self, folder):
Expand Down
2 changes: 1 addition & 1 deletion cobaya/prior.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ def logps_internal(self, x: np.ndarray) -> float:
if len(self._non_uniform_indices) else 0)
else:
logps = -np.inf
self.log.debug("Got logpriors (internal) = %r", logps)
self.log.debug("Got logpriors (internal) = %s", logps)
return logps

def logps_external(self, input_params) -> List[float]:
Expand Down
14 changes: 7 additions & 7 deletions cobaya/samplers/mcmc/mcmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ def initialize(self):
# If resuming but no existing chains, assume failed run and ignore blocking
# if speeds measurement requested
if (
self.output.is_resuming() and not existing_chains_any_process and
self.measure_speeds
self.output.is_resuming() and not existing_chains_any_process and
self.measure_speeds
):
self.blocking = None
if self.measure_speeds and self.blocking:
Expand Down Expand Up @@ -255,7 +255,7 @@ def n_fast(self):
"""Number of parameters which are considered fast, in binary fast/slow splits."""
return len(self.fast_params)

def get_acceptance_rate(self, first=0, last=None):
def get_acceptance_rate(self, first=0, last=None) -> np.floating:
"""
Computes the current acceptance rate, optionally only for ``[first:last]``
subchain.
Expand Down Expand Up @@ -698,12 +698,12 @@ def check_convergence_and_learn_proposal(self):
acceptance_rate = (np.average(acceptance_rates, weights=Ns)
if acceptance_rates is not None else acceptance_rate)
if self.oversample_thin > 1:
weights_multi_str = (" = 1/avg(%r)" % list(acceptance_rates)
weights_multi_str = (" = 1/avg(%r)" % acceptance_rates.tolist()
if acceptance_rates is not None else "")
self.log.info(" - Average thinned weight: %.3f%s", 1 / acceptance_rate,
weights_multi_str)
else:
accpt_multi_str = (" = avg(%r)" % list(acceptance_rates)
accpt_multi_str = (" = avg(%r)" % acceptance_rates.tolist()
if acceptance_rates is not None else "")
self.log.info(" - Acceptance rate: %.3f%s", acceptance_rate,
accpt_multi_str)
Expand Down Expand Up @@ -746,8 +746,8 @@ def check_convergence_and_learn_proposal(self):
condition_number = Rminus1 / min(np.abs(eigvals))
self.log.debug(" - Condition number = %g", condition_number)
self.log.debug(" - Eigenvalues = %r", eigvals)
accpt_multi_str = \
" = sum(%r)" % list(Ns) if more_than_one_process() else ""
accpt_multi_str = " = sum(%r)" % Ns.astype(int).tolist() \
if more_than_one_process() else ""
self.log.info(
" - Convergence of means: R-1 = %f after %d accepted steps%s",
Rminus1, sum(Ns), accpt_multi_str)
Expand Down
2 changes: 1 addition & 1 deletion cobaya/samplers/polychord/polychord.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class polychord(Sampler):

# variables from yaml
do_clustering: bool
num_repeats: int
num_repeats: Union[int, str]
confidence_for_unbounded: float
callback_function: Callable
blocking: Any
Expand Down
Loading

0 comments on commit b98a6e2

Please sign in to comment.