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.
    """
    ns = Namespace.of(expanded)
    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():
        variable_type = vdef.domain
        if variable_type == '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.foreach),
            where=where_of(vdef.where, ns, f"variable '{vname}'", self_variable=vname),
            lower=lower,
            upper=upper,
            variable_type=variable_type,
            absence=vdef.absence,
        )

    constraints = {}
    for cname, cdef in expanded.constraints.items():
        where = where_of(cdef.where, ns, f"constraint '{cname}'")
        ast = expression_of(cdef.expression, expanded, ns, f"constraint '{cname}'")
        assert isinstance(ast, ComparisonNode), 'load-time validation refuses a constraint without a comparison'
        lowering = _Lowering(expanded, f"constraint '{cname}'")
        constraints[cname] = program.ConstraintDeclaration(
            tuple(cdef.foreach),
            lhs=lowering.expr(ast.left),
            sense=ast.op,
            rhs=lowering.expr(ast.right),
            where=where,
        )

    objective = None
    if (odef := expanded.objective) is not None:
        ast = expression_of(odef.expression, expanded, ns, 'the objective')
        assert not isinstance(ast, ComparisonNode), 'load-time validation refuses a comparison in the objective'
        objective = program.ObjectiveDeclaration(
            odef.sense,
            _Lowering(expanded, 'the objective').expr(ast),
        )

    dimensions = {
        dname: program.DimensionDeclaration(
            tuple(
                program.LookupDeclaration(lname, lk.into, lk.dtype)
                for lname, lk in expanded.lookups.items()
                if lk.over == dname
            ),
            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()
    }
    in_math = read_by_the_math(expanded)
    expressions: dict[str, program.ExpressionDeclaration] = {}
    for name in expanded.expressions:
        context = f"named expression '{name}'"
        ast = expression_of(name, expanded, ns, context)
        assert not isinstance(ast, ComparisonNode), 'load-time validation refuses a comparison in a named expression'
        expressions[name] = program.ExpressionDeclaration(
            _Lowering(expanded, context).expr(ast), in_math=name in in_math
        )
    return program.Program(
        parameters=parameters,
        variables=variables,
        constraints=constraints,
        objective=objective,
        dimensions=dimensions,
        sos=sos,
        piecewise={name: declaration_of(ex) for name, ex in expanded.expanded_piecewise.items()},
        named_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 | dict[str, Any] | 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 | dict[str, Any] | 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)))