Skip to content

math_spec.piecewise

Expand piecewise: blocks into plain variables and constraints.

A block becomes ordinary affine declarations before anything reads the model, under names prefixed with the block's own; what each method emits is tabled in docs/reference/language/piecewise.md. A link expression is judged before expansion, so a refusal names the link the file wrote rather than an emitted constraint.

declaration_of(expanded) #

The facts of one expanded block, as a program carries them.

A curve has an x-axis only where two links tie it, so the increasing condition — and the shape it is checked with — exist only there; lp alone needs a segment to state a line for; a mask must be one run.

Source code in src/math_spec/piecewise.py
def declaration_of(expanded: ExpandedPiecewise) -> PiecewiseDeclaration:
    """The facts of one expanded block, as a program carries them.

    A curve has an x-axis only where two links tie it, so the increasing
    condition — and the shape it is checked with — exist only there; ``lp``
    alone needs a segment to state a line for; a mask must be one run.
    """
    pw = expanded.block
    checks: list[Check] = []
    curvature = _curvature_required(pw)
    if curvature is not None:
        x, y = pw.curve
        checks.append(Increasing(x.values, pw.over))
        checks.append(Curved(x.values, y.values, pw.over, curvature))
    if pw.method == 'lp':
        checks.append(AtLeastTwo(pw.over, expanded.points))
    if expanded.points is not None:
        checks.append(Contiguous(expanded.points, _nominated(pw)))
    return PiecewiseDeclaration(
        over=pw.over,
        method=pw.method,
        breakpoints=tuple(link.values for link in pw.links),
        checks=tuple(checks),
    )

derivations_of(block, expanded) #

How each parameter block's expansion emitted is filled, by name.

Everything emitted hangs off the mask, so a block masking nothing emits nothing for the caller to be told about.

Source code in src/math_spec/piecewise.py
def derivations_of(block: str, expanded: ExpandedPiecewise) -> dict[str, Derivation]:
    """How each parameter *block*'s expansion emitted is filled, by name.

    Everything emitted hangs off the mask, so a block masking nothing emits
    nothing for the caller to be told about.
    """
    if (mask := expanded.points) is None:
        return {}
    derivations: dict[str, Derivation] = {}
    if (values := _nominated(expanded.block)) is not None:
        derivations[mask] = MaskOf(block, values)
    if expanded.starts is not None:
        derivations[expanded.starts] = FirstOf(block, mask)
    if expanded.ends is not None:
        derivations[expanded.ends] = LastOf(block, mask)
    return derivations

expand_piecewise(schema) #

Return schema as a :class:_ExpandedSpec — every piecewise: block expanded away.

Memoised on schema.

RAISES DESCRIPTION
PiecewiseExpansionError

A block naming something that does not exist, or emitting a name the file already declares.

Source code in src/math_spec/piecewise.py
def expand_piecewise(schema: Spec) -> _ExpandedSpec:
    """Return *schema* as a :class:`_ExpandedSpec` — every ``piecewise:`` block expanded away.

    Memoised on *schema*.

    Raises:
        PiecewiseExpansionError: A block naming something that does not exist,
            or emitting a name the file already declares.
    """
    if isinstance(schema, _ExpandedSpec):
        return schema
    if schema._expansion is not None:
        return schema._expansion
    if not schema.piecewise:
        schema._expansion = _ExpandedSpec.model_construct(**dict(schema))
        return schema._expansion

    raw = schema.model_dump()
    raw.setdefault('variables', {})
    raw.setdefault('constraints', {})
    raw['expanded_piecewise'] = {name: _Block(schema, raw, name, pw).expand() for name, pw in schema.piecewise.items()}
    raw['piecewise'].clear()
    expanded = _ExpandedSpec.model_validate(raw)
    schema._expansion = expanded
    return expanded