Skip to content

One-of constraint ​

Contract ​

OneOfFunction<V> accepts one or more linear polynomials. Its direct evaluator returns 1 exactly when one input is nonzero and 0 otherwise. During solver registration it is stronger than a free Boolean indicator: it imposes that exactly one input is nonzero and fixes the result to 1.

This function selects no branch value and does not implement the old branch/payload API. It counts nonzero input polynomials.

Definition and truth table ​

For inputs p1,…,pn, let:

ai={1,pi≠00,pi=0,y={1,∑i=1nai=10,∑i=1nai≠1

The registered model additionally requires:

∑i=1nai=1,y=1.

For two inputs, the evaluator table is:

p1 nonzerop2 nonzeroy
nono0
noyes1
yesno1
yesyes0

The constructor requires at least one input polynomial.

Boundary, tolerance, and Undefined ​

evaluate() uses exact v != 0; a missing input value returns null. It does not return Undefined.

Each solver nonzero indicator uses tolerance t as its zero band |pi|≤t and strict boundary g as its nonzero band pi≥g or pi≤−g. The gap t<|pi|<g has no valid indicator assignment and can make the model infeasible. The current defaults are NONZERO_TOLERANCE = 1e-10 and STRICT_BOUNDARY = NONZERO_TOLERANCE * 16 + 16 * 2^-52; omitted bigM is inferred from finite input bounds and otherwise falls back to BIG_M_DEFAULT = 1e6.

Current API ​

Kotlin ​

kotlin
OneOfFunction(
    polynomials: List<LinearPolynomial<V>>,
    bigM: V? = null,
    tolerance: V? = null,
    strictBoundary: V? = null,
    converter: IntoValue<V>,
    name: String = "oneof",
    displayName: String? = null
)

The companion invoke accepts polynomials, bigM, converter, name, and displayName; use the primary constructor to set tolerance or strictBoundary.

Rust ​

Rust's OneOfFunction has a different contract from Kotlin's exactly-one nonzero test:

rust
OneOfFunction::new(id: u64, name: &str, polynomials: Vec<Linear<V>>) -> OneOfFunction<V>

It creates selection_variables() and returns the selected weighted sum in a continuous result_variable(). The selectors are model variables; Rust does not inspect whether each candidate polynomial is zero. There is therefore no one-to-one Rust API for Kotlin's OneOfFunction truth table; use XorFunction or SatisfiedAmountFunction when counting nonzero/binary indicators is the intended meaning.

Solver mathematical model ​

For name, the implementation creates name_oneof as the result, name_oneof_nz{i} as one nonzero indicator per input, and name_oneof_side{i} as one sign-side helper per input. All are in helperVariables; resultPolynomial is the unit-coefficient polynomial of name_oneof.

For every input, the shared four-row Big-M block represents

ai=0⇒−t≤pi≤t,(ai,si)=(1,1)⇒pi≥g,(ai,si)=(1,0)⇒pi≤−g.

The implementation expands those implications and then passes

∑iai=1,y=1.

Rust's same-named selection function instead passes ∑izi=1 and y=∑izipi; it does not create nonzero flags.

evaluate() versus the solver model ​

Before registration, evaluate() is a total exactly-one indicator (or null for missing input). After registration, any assignment with zero or multiple solver nonzero indicators is infeasible rather than merely producing result 0. This distinction is intentional in the current implementation and should be stated wherever the function is used as a model constraint.

Examples and tests ​

kotlin
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.OneOfFunction
import fuookami.ospf.kotlin.core.variable.RealVar
import fuookami.ospf.kotlin.math.algebra.number.Flt64
import fuookami.ospf.kotlin.math.symbol.Symbol
import fuookami.ospf.kotlin.math.symbol.monomial.LinearMonomial
import fuookami.ospf.kotlin.math.symbol.polynomial.LinearPolynomial

fun main() {
    val x = RealVar("x")
    val y = RealVar("y")
    val xPoly = LinearPolynomial(
        monomials = listOf(LinearMonomial(Flt64.one, x)),
        constant = Flt64.zero
    )
    val yPoly = LinearPolynomial(
        monomials = listOf(LinearMonomial(Flt64.one, y)),
        constant = Flt64.zero
    )
    val function = OneOfFunction(
        polynomials = listOf(xPoly, yPoly),
        converter = IntoValue.Identity,
        name = "oneof"
    )

    check(function.evaluate(mapOf<Symbol, Flt64>(x to Flt64.one, y to Flt64.zero)) == Flt64.one)
    check(function.evaluate(mapOf<Symbol, Flt64>(x to Flt64.one, y to Flt64(2.0))) == Flt64.zero)
}
rust
use ospf_rust_core::symbol::flatten::{Linear, LinearMonomial};
use ospf_rust_core::symbol::function::OneOfFunction;

let x = Linear::new(vec![LinearMonomial::new(1.0, 0)], 0.0);
let y = Linear::new(vec![LinearMonomial::new(1.0, 1)], 0.0);
let one_of = OneOfFunction::new(1, "one_of", vec![x, y]);
assert_eq!(one_of.selection_variables().len(), 2);
let _result = one_of.result_variable();

Source and core tests:

Rust source: one_of.rs.