Skip to content

math_spec.composition

A base and its patches into one model, before any of them is validated.

What a framework ships and a project extends. The patch says only what it changes, because declarations are laid over a field at a time::

constraints:
  ramp: {dims: [snapshot, generator, investment_period]}

A patch is not a :class:~math_spec.model.Spec. It is read before validation, so it may carry null where a declaration would go and may name what only its base declares. Nothing here resolves a name or checks a dim: the laid mapping goes through :func:~math_spec.validation.to_spec like any other file, and every rule the language has applies to it there and nowhere else.

The verb is designed to collide, so what keeps it predictable is that a collision is refused everywhere the caller did not ask for one:

  • A partial entry edits, and a whole one creates. An entry that does not validate as a declaration on its own has to land on one the base declares, named with the near miss. A mistyped name then refuses instead of quietly inventing a declaration nothing refers to.
  • Sibling patches are disjoint. Two patches writing one field is refused, both named, so the order they are given in never decides a model. Layering is written out — override(override(base, …), …) — where it is on the page rather than in an argument's position.
  • A patch adjusts the math, not the axes. A dimensions or relations entry may be added or restated exactly; changing one under the expressions already written over it is refused.

A declaration the patch sets to null is removed, which is the one thing an ordered list of files cannot say for itself: a declaration a patch does not mention is left alone, so without a marker a deletion has no spelling. The marker is positional and means nothing deeper down — constraints: {ramp: null} removes the constraint, where variables: {p: {where: null}} sets that variable's mask to none, which is a value the schema already takes.

IRREGULAR = {'piecewise': 'piecewise curve', 'sos': 'special-ordered set', 'objective': 'objective'} module-attribute #

OWNED_SECTIONS = ('parameters', 'variables', 'constraints', 'expressions', 'macros', 'piecewise', 'sos') module-attribute #

SECTIONS = (*SHARED_SECTIONS, *OWNED_SECTIONS) module-attribute #

SHARED_SECTIONS = ('dimensions', 'relations') module-attribute #

override(base, patches) #

base with each patch laid over it, and nothing laid over another patch.

PARAMETER DESCRIPTION
base

The model being extended — whatever every other verb takes.

TYPE: str | Path | dict[str, Any] | Spec

patches

What each patch is called, to the patch. The name is what an error calls it, so it is the patch's own name rather than a path. The patches must write disjoint fields, which is why the order they are given in cannot change the result.

TYPE: Mapping[str, str | Path | dict[str, Any] | Spec]

RETURNS DESCRIPTION
dict[str, Any]

One mapping, ready for :func:~math_spec.validation.to_spec. Nothing

dict[str, Any]

in it has been resolved, name-checked or lowered.

RAISES DESCRIPTION
LanguageError

A patch edits or removes a declaration its base does not declare; a patch creates one that is not whole; a patch redeclares a dimension or a relation as something else; or two patches write one field.

FileNotFoundError

A str with no newline that names no file.

Source code in src/math_spec/composition.py
def override(
    base: str | Path | dict[str, Any] | Spec,
    patches: Mapping[str, str | Path | dict[str, Any] | Spec],
) -> dict[str, Any]:
    """*base* with each patch laid over it, and nothing laid over another patch.

    Args:
        base: The model being extended — whatever every other verb takes.
        patches: What each patch is called, to the patch. The name is what an
            error calls it, so it is the patch's own name rather than a path.
            The patches must write disjoint fields, which is why the order they
            are given in cannot change the result.

    Returns:
        One mapping, ready for :func:`~math_spec.validation.to_spec`. Nothing
        in it has been resolved, name-checked or lowered.

    Raises:
        LanguageError: A patch edits or removes a declaration its base does not
            declare; a patch creates one that is not whole; a patch redeclares
            a dimension or a relation as something else; or two patches write
            one field.
        FileNotFoundError: A ``str`` with no newline that names no file.
    """
    read = {name: _declarations(patch) for name, patch in patches.items()}
    _disjoint(read)

    result = deepcopy(_declarations(base))
    for name, patch in read.items():
        result = _lay_over(result, deepcopy(patch), name)
    return result