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 an expanded model into a :class:~math_spec.program.Program.

A domain: binary variable lowers with fixed 0/1 bounds.

RAISES DESCRIPTION
LanguageError

A construct outside the language, named with its rewrite.

Source code in src/math_spec/lowering.py
def lower_program(expanded: _ExpandedSpec) -> program.Program:
    """Compile an expanded model into a :class:`~math_spec.program.Program`.

    A ``domain: binary`` variable lowers with fixed 0/1 bounds.

    Raises:
        LanguageError: A construct outside the language, named with its
            rewrite.
    """
    resolved = expanded.resolved
    derivations = {
        name: how
        for block, ex in expanded.expanded_piecewise.items()
        for name, how in derivations_of(block, ex).items()
    }
    parameters = {
        name: program.ParameterDeclaration(tuple(pdef.dims), pdef.dtype, derivations.get(name))
        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=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=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,
            big_m=sdef.big_m,
        )
        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
        )
    return program.Program(
        parameters=parameters,
        variables=variables,
        constraints=constraints,
        objective=objective,
        dimensions=dimensions,
        relations=resolved.relations,
        sos=sos,
        piecewise={name: declaration_of(ex) for name, ex in expanded.expanded_piecewise.items()},
        expressions=expressions,
    )

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; :func:~math_spec.piecewise.expand_piecewise 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; :func:`~math_spec.piecewise.expand_piecewise` 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(expand_piecewise(to_spec(spec)))