Skip to content

Logical OR ​

Contract ​

OrFunction<V> accepts one or more linear polynomials and exposes a binary result. The result is 1 when at least one input polynomial is nonzero, and 0 only when every input is zero. The API is generic over V : RealNumber<V> & NumberField<V>.

This operation tests nonzero values; it does not require the input polynomials themselves to be binary variables.

Definition and truth table ​

For input polynomials p1,…,pn, let ai be the nonzero indicators:

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

For two inputs:

p1 nonzerop2 nonzeroy
nono0
noyes1
yesno1
yesyes1

The constructor requires at least one input polynomial.

Boundary, tolerance, and Undefined ​

evaluate() uses exact v != 0 for the first nonzero input and returns null if an input cannot be evaluated. It does not expose an Undefined value.

The solver's shared nonzero indicators use two numerical bands. Indicator 0 denotes |pi|≤t, where tolerance is t. Indicator 1 requires one of pi≥g or pi≤−g, where strictBoundary is g. The open gap t<|pi|<g has no indicator assignment and can make registration or solving infeasible.

The source constants are NONZERO_TOLERANCE = 1e-10 and STRICT_BOUNDARY = NONZERO_TOLERANCE * 16 + 16 * 2^-52. The default bigM is inferred per polynomial from finite bounds and otherwise falls back to BIG_M_DEFAULT = 1e6.

Current API ​

Kotlin ​

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

The companion invoke accepts polynomials, converter, bigM, name, and displayName. Unlike the primary constructor, that convenience overload does not expose tolerance or strictBoundary.

Rust ​

Rust exposes OrFunction:

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

named and auto are also available. result_variable(), indicator_variables(), and side_variables() expose the generated binary variables. The Rust constructor has no Kotlin-style converter, bigM, tolerance, or strict-boundary arguments; it uses the shared nonzero-indicator policy and infers Big-M from registered bounds when possible.

Solver mathematical model ​

For name, the implementation creates name_or as the result, name_or_nz{i} as one nonzero indicator per input, and name_or_side{i} as one sign-side helper per input. All are returned by helperVariables. When every input polynomial is exactly a unit-coefficient binary variable, only the result is registered and the indicator block below is skipped.

For each input, the four-row Big-M block represents

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

After expanding these implications into linear inequalities, registration appends the OR rows:

∑iai≥y,y≥ai(1≤i≤n).

In that all-binary case the indicator rows are replaced by the direct rows y≥bi for every input plus ∑ibi≤y in Kotlin; Rust instead registers the exact two-sided hull with ∑ibi≥y.

The public resultPolynomial is the unit-coefficient polynomial of name_or. The symbol registers against AbstractLinearMechanismModel.

Rust registers the same nonzero-indicator block followed by the same OR rows, with Rust's own fixed threshold and bound inference. For a direct all-binary input, Rust registers the exact hull y≥bi and ∑ibi≥y.

evaluate() versus the solver model ​

The evaluator treats every exact nonzero value as true, including a value smaller than strictBoundary. The solver encoding intentionally leaves the tolerance-to-boundary gap unclassified. For a robust model, choose a strict boundary that is separated from the values your variables can actually attain.

Examples and tests ​

kotlin
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.OrFunction
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 = OrFunction(
        polynomials = listOf(xPoly, yPoly),
        converter = IntoValue.Identity,
        name = "or"
    )

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

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 or = OrFunction::new(1, "or", vec![x, y]);
assert_eq!(or.indicator_variables().len(), 2);
let _result = or.result_variable();

Source and core tests:

Rust source and parity coverage: and.rs and gurobi_linear_function_kotlin_parity.rs.