Source code for cosmofit.core.info
"""
The input: a dict, or the YAML file it was written as.
Top-level blocks::
theory: {name: options} # what computes predictions
likelihood: {name: options} # what compares them with data
params: {name: declaration} # see core.parameters
sampler: {name: options} # exactly one
output: path prefix # optional (used by later phases)
debug, force, resume # optional flags
Options may be ``null`` (``desi:`` on its own line) for "defaults".
"""
from __future__ import annotations
import copy
from pathlib import Path
__all__ = ["load_info", "validate_info", "TOP_LEVEL_KEYS"]
TOP_LEVEL_KEYS = {
"theory", "likelihood", "params", "sampler", "output",
"debug", "force", "resume",
}
[docs]
def load_info(source) -> dict:
"""
An input as a dict: a dict (copied), a path to a ``.yaml``/``.yml``
file, or YAML text.
"""
if isinstance(source, dict):
return copy.deepcopy(source)
import yaml
if isinstance(source, Path) or (
isinstance(source, str) and "\n" not in source
and source.endswith((".yaml", ".yml"))
):
with open(source, encoding="utf-8") as handle:
data = yaml.safe_load(handle)
elif isinstance(source, str):
data = yaml.safe_load(source)
else:
raise TypeError(f"Cannot read an input from {type(source).__name__}.")
if not isinstance(data, dict):
raise ValueError("An input must be a mapping of blocks at the top level.")
return data
[docs]
def validate_info(info: dict) -> dict:
"""
Check an input's shape and normalize it: every block a dict, every
component's options a dict (``None`` becomes ``{}``).
Raises
------
ValueError
Naming the first thing that is wrong.
"""
unknown = set(info) - TOP_LEVEL_KEYS
if unknown:
raise ValueError(
f"Unknown top-level block(s) {sorted(unknown)}; an input has "
f"{sorted(TOP_LEVEL_KEYS)}."
)
out = dict(info)
for block in ("theory", "likelihood", "sampler"):
entries = out.get(block) or {}
if isinstance(entries, (list, tuple)):
entries = {name: None for name in entries}
if not isinstance(entries, dict):
raise ValueError(
f"'{block}' must map names to options, got "
f"{type(entries).__name__}."
)
normalized = {}
for name, options in entries.items():
if options is None:
options = {}
if not isinstance(options, dict):
raise ValueError(
f"Options of {block} {name!r} must be a dict or empty."
)
normalized[str(name)] = dict(options)
out[block] = normalized
if not out["likelihood"]:
raise ValueError("An input needs at least one likelihood.")
if len(out["sampler"]) > 1:
raise ValueError(
f"Exactly one sampler, got {sorted(out['sampler'])}."
)
params = out.get("params") or {}
if not isinstance(params, dict):
raise ValueError("'params' must map names to declarations.")
out["params"] = dict(params)
return out