Skip to content

math_spec.lowering

Lower a validated model to a :class:~math_spec.program.Program.

One lowering, on the language side: it reads the typed AST and emits declarations with names resolved and shapes fixed, and reaches no consumer. A construct with no lowering raises :class:~math_spec.errors.LanguageError naming its rewrite.

lower_program(expanded) #

Compile a model whose curves are written out into a :class:~math_spec.program.Program.

A domain: binary variable lowers with fixed 0/1 bounds. A sos: block lowers as itself — a program carries a set, and :meth:~math_spec.model.Spec.expand is what states one as binaries instead.

PARAMETER DESCRIPTION
expanded

A model with no piecewise: block left, which :meth:~math_spec.model.Spec.expand returns.

TYPE: Spec

RAISES DESCRIPTION
LanguageError

A construct outside the language, named with its rewrite.

Source code in src/math_spec/lowering.py
def lower_program(expanded: Spec) -> program.Program:
    """Compile a model whose curves are written out into a :class:`~math_spec.program.Program`.

    A ``domain: binary`` variable lowers with fixed 0/1 bounds. A ``sos:``
    block lowers as itself — a program carries a set, and
    :meth:`~math_spec.model.Spec.expand` is what states one as binaries
    instead.

    Args:
        expanded: A model with no ``piecewise:`` block left, which
            :meth:`~math_spec.model.Spec.expand` returns.

    Raises:
        LanguageError: A construct outside the language, named with its
            rewrite.
    """
    assert not expanded.piecewise, "a curve states rows, and lowering reads them: pass spec.expand('piecewise')"
    resolved = expanded.resolved
    parameters = {
        name: program.ParameterDeclaration(tuple(pdef.dims), pdef.dtype) for name, pdef in expanded.parameters.items()
    }

    variables = {}
    for vname, vdef in expanded.variables.items():
        domain = vdef.domain
        if domain == 'binary':
            lower, upper = program.Constant(0.0), program.Constant(1.0)
        else:
            lower, upper = _bound_expression(vdef.bounds.lower), _bound_expression(vdef.bounds.upper)
        variables[vname] = program.VariableDeclaration(
            tuple(vdef.dims),
            where=_Lowering(expanded, f"variable '{vname}'").mask(resolved.variables[vname]),
            lower=lower,
            upper=upper,
            domain=domain,
            absence=vdef.absence,
        )

    constraints = {}
    for cname, cdef in expanded.constraints.items():
        expression, where = resolved.constraints[cname]
        lowering = _Lowering(expanded, f"constraint '{cname}'")
        constraints[cname] = program.ConstraintDeclaration(
            tuple(cdef.dims),
            lhs=lowering.expr(expression.left),
            sense=expression.op,
            rhs=lowering.expr(expression.right),
            where=lowering.mask(where),
        )

    objective = None
    if (odef := expanded.objective) is not None:
        assert resolved.objective is not None, 'validation resolves the objective the file declares'
        objective = program.ObjectiveDeclaration(
            odef.sense,
            _Lowering(expanded, 'the objective').expr(resolved.objective),
        )

    dimensions = {dname: program.DimensionDeclaration(ddef.dtype) for dname, ddef in expanded.dimensions.items()}
    sos = {
        sname: program.SosDeclaration(
            sdef.variable,
            sdef.over,
            sos_type=sdef.type,
        )
        for sname, sdef in expanded.sos.items()
    }
    expressions: dict[str, program.ExpressionDeclaration] = {}
    for name, ast in resolved.expressions.items():
        expressions[name] = program.ExpressionDeclaration(
            _Lowering(expanded, f"named expression '{name}'").expr(ast), in_math=name in resolved.read_by_the_math
        )
    given = program.GivenTargets(
        variables={name: program.GivenDeclaration(tuple(g.dims)) for name, g in expanded.given.variables.items()},
        constraints={name: program.GivenDeclaration(tuple(g.dims)) for name, g in expanded.given.constraints.items()},
    )
    return program.Program(
        parameters=parameters,
        variables=variables,
        constraints=constraints,
        objective=objective,
        dimensions=dimensions,
        relations=resolved.relations,
        sos=sos,
        piecewise={name: declaration_of(pw) for name, pw in expanded._expanded_piecewise.items()},
        assumptions=_assumptions(expanded),
        expressions=expressions,
        given=given,
    )

to_program(spec) #

spec as a :class:~math_spec.program.Program — the public door.

Takes whatever you have: a YAML path, the YAML itself, a mapping, a loaded model, or a program already. Idempotent, so a caller that does not know which it holds can call this and be sure.

Not memoised; :meth:~math_spec.model.Spec.expand is.

PARAMETER DESCRIPTION
spec

What to read the declarations from.

TYPE: str | Path | Mapping[str, object] | Spec | Program

RETURNS DESCRIPTION
Program

Every declaration the file makes, with names resolved and shapes

Program

fixed.

RAISES DESCRIPTION
SchemaError

The file is not a valid model.

LanguageError

A construct outside the language, named with its rewrite.

Source code in src/math_spec/lowering.py
def to_program(spec: str | Path | Mapping[str, object] | Spec | program.Program) -> program.Program:
    """*spec* as a :class:`~math_spec.program.Program` — the public door.

    Takes whatever you have: a YAML path, the YAML itself, a mapping, a loaded
    model, or a program already. Idempotent, so a caller that does not know
    which it holds can call this and be sure.

    Not memoised; :meth:`~math_spec.model.Spec.expand` is.

    Args:
        spec: What to read the declarations from.

    Returns:
        Every declaration the file makes, with names resolved and shapes
        fixed.

    Raises:
        SchemaError: The file is not a valid model.
        LanguageError: A construct outside the language, named with its
            rewrite.
    """
    if isinstance(spec, program.Program):
        return spec
    return lower_program(to_spec(spec).expand('piecewise'))