Function

Function#

class Function#

A scalar expression used in optimization objectives and constraint bodies.

Function refers to variables by ID, without owning their names, kinds, or bounds. It can be evaluated independently of a solver. Use evaluate() when variable domains and constraints must also be checked, and InstanceClass to inspect a solver-input representation. Being evaluable as a Function does not imply that an Adapter accepts it.

Construction and polynomial forms

Construct a Function from numbers, decision variables, parameters, polynomial objects, or another Function. Linear stores a linear polynomial, Quadratic a polynomial of degree at most two, and Polynomial an arbitrary-degree polynomial. Function is the common wrapper for these forms and for composed scalar expressions.

>>> from ommx import DecisionVariable, Function, Linear
>>> x = DecisionVariable.continuous(0)
>>> y = DecisionVariable.continuous(1)
>>> constant = Function(3)
>>> f = Function(x + 2 * y + 3)
>>> by_coefficients = Function(Linear(terms={0: 1, 1: 2}, constant=3))
>>> assert f.almost_equal(by_coefficients)
>>> assert f.evaluate({0: 1, 1: 0}) == 4.0

Addition, subtraction, multiplication, and negation of stored polynomials are normalized into polynomial form. Terms that become exactly zero through cancellation or floating-point underflow are removed. Finite nonzero coefficients are retained regardless of magnitude; the evaluation tolerance does not prune them. This describes Function arithmetic, not the numerical behavior of a downstream coefficient-format conversion.

Use degree() and num_terms() for polynomial metadata, and terms, linear_terms, quadratic_terms, and constant_term for coefficients. Composed expressions return None for degree/term count and raise TypeError for coefficient properties; these accessors do not expand an expression. almost_equal() compares coefficients or expression structure, not global mathematical equivalence.

Composed scalar operations

Use Python arithmetic and abs(f) to construct expressions, together with signum(), minimum(), maximum(), and powi(). These calls construct expressions rather than supplying variable values. For pointwise minimum and maximum, use the methods rather than Python’s built-in min and max.

>>> fx, fy = Function(x), Function(y)
>>> assert abs(fx - 3).evaluate({0: 1}) == 2.0
>>> assert fx.minimum(fy).evaluate({0: 1, 1: 0}) == 0.0
>>> assert (fx / (fy + 1)).evaluate({0: 1, 1: 1}) == 0.5
>>> assert (fx ** 2).evaluate({0: 1}) == 1.0

Division remains a composed expression even with a constant divisor. Except for identity operations and finite constant folding, integer powers also remain composed, including nonnegative powers. To construct an expanded polynomial square, use g * g rather than g ** 2. See powi() for the supported exponent types and domain rules.

Evaluation, substitution, and stored order

evaluate() supplies the point-evaluation contract, including tolerance-based zero tests, undefined operations, and non-finite values. partial_evaluate() returns an expression with selected values substituted and does not fix the tolerance for a later evaluation. evaluate_bound() returns an enclosing interval, which need not be the tightest possible range.

Composed expressions retain parentheses and operand order. They evaluate left to right, combining two values per operator application rather than automatically reassociating expressions such as (abs(a) + abs(b)) + abs(c) and abs(a) + (abs(b) + abs(c)). A domain or overflow error can depend on that order.

The wire representation uses a flat reverse-Polish-notation (RPN) instruction sequence rather than a recursively nested tree. Its encoding belongs to the OMMX Message specification; constructing a Python Function does not require creating RPN instructions.

__abs__() → Function#

Absolute value operator

__add__(rhs: ToFunction) → Function#

Addition

__copy__() → Function#
__deepcopy__(_memo: Any) → Function#
__eq__(other: ToFunction) → Constraint#

Create an equality constraint: self == other → Constraint with EqualToZero

Returns a Constraint where (self - other) == 0. Note: This does NOT return bool, it creates a Constraint object.

__ge__(other: ToFunction) → Constraint#

Create a greater-than-or-equal constraint: self >= other → Constraint with LessThanOrEqualToZero

Returns a Constraint where (other - self) <= 0.

__iadd__(rhs: ToFunction) → Function#
__le__(other: ToFunction) → Constraint#

Create a less-than-or-equal constraint: self <= other → Constraint with LessThanOrEqualToZero

Returns a Constraint where (self - other) <= 0.

__mul__(rhs: ToFunction) → Function#

Multiplication

__neg__() → Function#

Negation operator

__new__(inner: ToFunction) → Function#

Create a Function from various types.

Accepts:

  • int or float: creates a constant function

  • DecisionVariable: creates a linear function with single term

  • AttachedDecisionVariable: creates a linear function with single term (only the id is used; no host borrow is taken)

  • Parameter: creates a linear function with single term

  • Linear: creates a linear function

  • Quadratic: creates a quadratic function

  • Polynomial: creates a polynomial function

  • Function: returns a copy

__pow__(exponent: int, modulo: None = None) → Function#
__radd__(lhs: ToFunction) → Function#

Reverse addition (lhs + self)

__repr__() → str#
__rmul__(lhs: ToFunction) → Function#

Reverse multiplication (lhs * self)

__rsub__(lhs: ToFunction) → Function#

Reverse subtraction (lhs - self)

__rtruediv__(lhs: ToFunction) → Function#

Reverse division (lhs / self)

__sub__(rhs: ToFunction) → Function#

Subtraction

__truediv__(rhs: ToFunction) → Function#

Construct division by another function, retaining a composed expression.

This also applies to a constant divisor. The zero-domain check is made by evaluate() with its atol; constructing the expression does not establish that all inputs lie in its domain.

add_assign(rhs: ToFunction) → None#
add_linear(linear: Linear) → Function#
add_polynomial(polynomial: Polynomial) → Function#
add_quadratic(quadratic: Quadratic) → Function#
add_scalar(scalar: float) → Function#
almost_equal(other: ToFunction, atol: float = 1e-06) → bool#

Compare polynomial coefficients or composed-expression structure within atol.

For two stored polynomials, compares the coefficient of each monomial; an absent monomial has coefficient zero. Different polynomial storage variants may compare equal. For two composed expressions, operators and evaluation order must match, and polynomial operands are compared with the same coefficient tolerance. A polynomial and a composed expression do not compare equal merely because they evaluate to the same values. This is not a test of global mathematical equivalence.

  • other: Function to compare with.

  • atol: Positive absolute coefficient tolerance.

>>> from ommx import DecisionVariable, Function
>>> x = Function(DecisionVariable.continuous(0))
>>> assert (x * x).almost_equal(x * x)
>>> assert not (x ** 2).almost_equal(x * x)
as_linear() → Optional[Linear]#

Try to convert this function to a linear function.

Returns Some(Linear) if the function can be represented as linear, None otherwise. This is useful for checking if a function is suitable for linear programming solvers.

as_quadratic() → Optional[Quadratic]#

Try to convert this function to a quadratic function.

Returns Some(Quadratic) if the function can be represented as quadratic, None otherwise.

content_factor() → float#

Return the minimal positive factor that makes all coefficients integers.

Raises TypeError for a composed, non-polynomial Function.

degree() → Optional[int]#

Return the degree of the stored polynomial, or None for a composed expression.

Returns the highest degree of any term in a polynomial function. Zero function has degree 0, constant function has degree 0, linear function has degree 1, quadratic function has degree 2, etc. This inspects the representation; it does not expand an expression to determine its mathematical degree. For the variable expression g = Function(DecisionVariable.continuous(0)), g ** 2 returns None while the explicitly multiplied g * g has degree 2.

evaluate(state: ToState, *, atol: Optional[float] = None) → float#

Evaluate this expression at the supplied variable values.

  • state: A State or mapping from variable IDs to values. All IDs required by the expression must be supplied.

  • atol: Positive absolute tolerance for zero-sensitive operations. If omitted, get_default_atol() supplies it.

Returns the scalar value of the expression.

A Function references IDs but does not own variable kinds, bounds, or constraints. This method does not test candidate feasibility or round discrete-variable values; use evaluate() for that.

abs(v) <= atol is treated as zero, including the boundary. This gives signum(v) == 0 and makes a denominator or a negative-power base in that interval undefined. 0 ** 0 is 1, and integer powers of negative bases are supported. Polynomial coefficients are not pruned using this tolerance.

Composed expressions evaluate operands from left to right and combine two values at each operator application. Parentheses and operation order are retained rather than reassociated. Overflow and domain failures can therefore depend on the represented order. Simplification does not erase the domain of a partial function: 0 * (1 / f) remains undefined where f is treated as zero.

Raises ValueError for a missing required variable value, undefined point operation, or non-finite operand/intermediate result.

>>> from ommx import Function, Linear
>>> f = Function(Linear(terms={0: 1, 1: 2}, constant=3))
>>> assert f.evaluate({0: 1, 1: 0}) == 4.0
evaluate_bound(bounds: Mapping[int, Bound], *, atol: Optional[float] = None) → Bound#

Compute an interval bound of this function given variable bounds.

Missing IDs in bounds are treated as unbounded (Bound.unbounded()).

Args:

  • bounds: Mapping from variable ID to its Bound.

  • atol: Absolute tolerance used by operations whose semantics depend on whether a value is zero. If omitted, get_default_atol() supplies it. Use the same tolerance when point-evaluating this function.

Returns: A Bound that contains \([\inf f, \sup f]\) over the given variable bounds.

Tightness: Polynomial leaves are bounded term by term (monomial-wise), and composed expression operations combine their operand bounds with interval arithmetic. The result is a sound over-approximation of the true range \([\inf f, \sup f]\) but is not guaranteed to be tight, because it ignores dependencies between terms or operands that share variables. For example, \(f = x^2 - x\) with \(x \in [0, 1]\) has true range \([-1/4, 0]\) (minimum at \(x = 1/2\)), but term-wise evaluation yields \([0, 1] + (-[0, 1]) = [-1, 1]\).

Raises: RuntimeError when an interval contains a value treated as zero by atol in a denominator or as the base of a negative integer power. Raises ValueError if valid bound endpoints cannot be constructed after numeric overflow.

Examples#

>>> from ommx import Function, Linear, Bound
>>> f = Function(Linear(terms={1: 2}, constant=3))  # 2*x1 + 3
>>> b = f.evaluate_bound({1: Bound(0.0, 2.0)})
>>> b.lower <= 3.0 and b.upper >= 7.0
True
from_linear(linear: Linear) → Function#
from_polynomial(polynomial: Polynomial) → Function#
from_quadratic(quadratic: Quadratic) → Function#
from_scalar(scalar: float) → Function#
maximum(rhs: ToFunction) → Function#

Construct the pointwise maximum of two functions.

Use this method instead of Python’s built-in max.

minimum(rhs: ToFunction) → Function#

Construct the pointwise minimum of two functions.

Use this method instead of Python’s built-in min, which compares objects rather than constructing an optimization expression.

mul_linear(linear: Linear) → Function#
mul_polynomial(polynomial: Polynomial) → Function#
mul_quadratic(quadratic: Quadratic) → Function#
mul_scalar(scalar: float) → Function#
num_terms() → Optional[int]#

Get the number of terms in this function.

Zero function has 0 terms, constant function has 1 term, and polynomial functions have the number of non-zero coefficient terms. Returns None for composed expressions, even when their mathematical value could be represented by a polynomial. No expansion is performed.

partial_evaluate(state: ToState, *, atol: Optional[float] = None) → Function#

Substitute supplied variable values and return the remaining Function.

The original Function is unchanged. Values may cover only some of the required IDs; unresolved variables remain in the returned expression. The concrete Linear, Quadratic, and Polynomial partial-evaluation methods preserve their respective Python types; this method returns a Function.

Closed subexpressions are folded only when the result is independent of the later evaluation tolerance. signum, division, and negative integer powers remain composed operations even after all their variables are replaced. Construction, substitution, and partial evaluation do not capture a default or supplied atol for these zero-domain decisions; the subsequent evaluate() call determines their value or domain.

  • state: Variable-ID/value mapping or State to substitute.

  • atol: A positive tolerance; zero-sensitive composed operations are retained rather than resolved with this value.

>>> from ommx import Function, Linear
>>> f = Function(Linear(terms={0: 1, 1: 2}, constant=3))
>>> remaining = f.partial_evaluate({0: 1})
>>> assert remaining.evaluate({1: 0}) == 4.0
>>> x = Function(Linear(terms={0: 1}))
>>> sign = x.signum().partial_evaluate({0: 1e-8}, atol=1e-6)
>>> assert sign.evaluate({}, atol=1e-6) == 0.0
>>> assert sign.evaluate({}, atol=1e-9) == 1.0
powi(exponent: int) → Function#

Raise this function to a signed 32-bit integer power.

f.powi(n) and f ** n construct the same operation. Floating-point or function-valued exponents, reversed exponentiation such as 2 ** f, and modular exponentiation are not supported.

Except for identity operations and finite constant folding, powers remain composed expressions, including nonnegative powers. They are not automatically expanded into polynomial coefficients. Use explicit multiplication, such as g * g, when an expanded polynomial square is required by an input format.

During evaluate(), a negative power is undefined when the base has absolute value at most atol. Negative bases are supported because the exponent is integral; zero to the zero power is 1. The operand is still evaluated, so raising an undefined expression to zero does not erase its domain restriction.

>>> from ommx import DecisionVariable, Function
>>> g = Function(DecisionVariable.continuous(0))
>>> assert (g ** 2).degree() is None
>>> assert (g * g).degree() == 2
>>> assert (g ** 0).evaluate({0: 0}) == 1.0
random(rng: Rng, num_terms: int = 5, max_degree: int = 3, max_id: int = 10) → Function#
reduce_binary_power(binary_ids: set[int]) → bool#

Reduce binary powers in the function.

For binary variables, \(x^n = x\) for any \(n \geq 1\), so we can reduce higher powers to linear terms.

Args:

  • binary_ids: Set of binary variable IDs to reduce powers for

Returns: True if any reduction was performed, False otherwise

required_ids() → set[int]#
signum() → Function#

Construct the pointwise sign of this function.

Evaluation returns 0 when abs(value) <= atol, -1 below that interval, and 1 above it. The tolerance is supplied to evaluate(), not fixed when this expression is constructed or partially evaluated.

property constant_term: float#

Read-only property.

Get the constant term of the function.

Returns the constant term. Returns 0.0 if function has no constant term. Works for all polynomial functions by filtering the degree-0 term. Raises TypeError if this is a non-polynomial expression function.

property linear_terms: dict[int, float]#

Read-only property.

Get linear terms as a dictionary mapping variable id to coefficient.

Returns dictionary mapping variable IDs to their linear coefficients. Returns empty dict if function has no linear terms. Works for all polynomial functions by filtering only degree-1 terms. Raises TypeError if this is a non-polynomial expression function.

property quadratic_terms: dict[tuple[int, int], float]#

Read-only property.

Get quadratic terms as a dictionary mapping (row, col) to coefficient.

Returns dictionary mapping variable ID pairs to their quadratic coefficients. Returns empty dict if function has no quadratic terms. Works for all polynomial functions by filtering only degree-2 terms. Raises TypeError if this is a non-polynomial expression function.

property terms: dict#

Read-only property.

Get all polynomial terms as a dictionary mapping monomial tuples to coefficients.

The empty tuple denotes the constant term. Repeated IDs denote powers, for example (0, 0) for the square of variable 0. The dictionary is a snapshot and does not change the Function when edited. Finite nonzero coefficients are not discarded because of their size or the evaluation tolerance; exact cancellation or floating-point underflow may produce zeros during polynomial arithmetic. Raises TypeError for a composed expression. This property does not expand the expression, including a retained integer power.

property type_name: str#

Read-only property.