clophfit.fitting.grid ===================== .. py:module:: clophfit.fitting.grid .. autoapi-nested-parse:: Machinery for expanding a grid of fit specifications. A grid search over fitting options is generic: something describes which knobs exist, a product is taken over configuration blocks, each combination becomes a flat specification, and specifications that describe the same model must not appear twice under different names. Only the *vocabulary* - which knobs a given project exposes and what it calls them - is project-specific. This module owns the machinery. The caller supplies the knobs. The separation matters because of how it failed before. When a project kept the knob descriptions, the expansion, and the checks in three places that had to agree, six knobs were configured and silently never reached the model: runs succeeded, diagnostics looked normal, and trace ids claimed distinctions the fitted models did not have. Keeping the mechanism here means a project describes each knob once and gets expansion, signatures and duplicate detection from it. Classes ------- .. autoapisummary:: clophfit.fitting.grid.Knob Functions --------- .. autoapisummary:: clophfit.fitting.grid.direct_kwargs clophfit.fitting.grid.apply_blocks clophfit.fitting.grid.model_signature clophfit.fitting.grid.check_unique_signature clophfit.fitting.grid.inert_knobs Module Contents --------------- .. py:class:: Knob One configurable quantity, from configuration block to model kwarg. :param name: Key this knob occupies in an expanded specification. :type name: str :param block: Name of the configuration block it is read from. :type block: str :param source_key: Key within that block. Differs from *name* only where the configuration spelling and the specification spelling diverge. :type source_key: str :param default: Value used when the block omits the key. :type default: typing.Any :param target: Model-kwarg this knob is passed as, when the pass-through is a plain rename with no coercion or conditional construction. Empty means the caller places it, which is the case for anything that lands inside a config object or needs a type conversion. :type target: str :param reader: How to read the resulting value back out of the built model kwargs. ``None`` marks a knob that is consumed on the way through and has no one corresponding kwarg, so it cannot be verified directly. :type reader: Callable[[dict[str, typing.Any]], typing.Any] | None .. py:property:: key :type: str The key to read from the source block. .. py:function:: direct_kwargs(cfg, knobs) Return the model kwargs that are plain pass-throughs of a knob. :param cfg: An expanded specification. :type cfg: Mapping[str, typing.Any] :param knobs: Knobs to consider; those without a *target* are skipped. :type knobs: Iterable[Knob] :returns: Target name to value. :rtype: dict[str, typing.Any] .. rubric:: Notes Only knobs whose journey to the model is a rename belong here. A knob that is coerced, made conditional, or folded into a config object stays with the caller: expressing those generically would need a coercion language whose mistakes are exactly as silent as the ones this registry exists to prevent. .. py:function:: apply_blocks(cfg, knobs, blocks) Copy every knob from its source block into *cfg*. :param cfg: Specification being built; mutated in place. :type cfg: dict[str, typing.Any] :param knobs: Knobs to copy. :type knobs: Iterable[Knob] :param blocks: Source block per block name. :type blocks: Mapping[str, Mapping[str, typing.Any]] :raises KeyError: If a knob names a block that was not supplied, which would otherwise surface much later as a missing value in a fitted model. .. py:function:: model_signature(cfg, knobs, extra_fields = ()) Return a digest of everything in *cfg* that changes the fitted model. :param cfg: An expanded specification. :type cfg: Mapping[str, typing.Any] :param knobs: Knobs whose resolved values determine the model. :type knobs: Iterable[Knob] :param extra_fields: Further specification keys that also determine the model but are not knobs, such as a dataset variant or a sampling width. :type extra_fields: Sequence[str] :returns: Short hex digest over the resolved values. :rtype: str .. rubric:: Notes Identifiers built from the *names* of configuration blocks distinguish two blocks that are named differently whether or not they describe different models. This digest is taken from resolved values instead, so specifications that agree as models agree here. .. py:function:: check_unique_signature(seen, signature, identifier) Record a specification's signature, rejecting a duplicate model. :param seen: Signature to identifier, accumulated across one grid; mutated in place. :type seen: dict[str, str] :param signature: Signature of the specification being added. :type signature: str :param identifier: Human-readable identifier, used in the error message. :type identifier: str :raises ValueError: If a different identifier already claimed this signature, meaning two cells of one grid would fit identical models under different names. .. py:function:: inert_knobs(rows, knobs) Return the knobs that never arrived at the model or never varied. :param rows: One mapping of knob name to arrived value per specification, with ``""`` where a knob produced no kwarg. :type rows: Sequence[Mapping[str, typing.Any]] :param knobs: Knobs the grid is supposed to vary. :type knobs: Sequence[Knob] :returns: Knob name to the reason it is inert; empty when every knob varies. :rtype: dict[str, str] .. rubric:: Notes The constancy check applies only to a grid of more than one specification. A single-specification grid pins its knobs by construction, so a constant value there is the intended configuration rather than evidence a knob went missing.