Skip to content

Quadratic Conditional interval ​

QuadraticIfInFunction is the quadratic-input counterpart of the linear conditional interval. The input is a bounded QuadraticPolynomial<V>; the result is a binary variable that is 1 when the input value lies in the closed interval [lower,upper] and 0 when it is safely outside. The symbol composes the linear IfInFunction through the shared QuadraticFunctionSymbol<V> base, so the input is first bound to a bridge variable by an exact equality and the linear interval rows are applied to the bridged affine input.

Define the two differences against the input polynomial value x=p(t):

dlower=p(t)−lower,dupper=upper−p(t).

Both differences are classified with the shared GE relation. For strict boundary g:

Position of p(t)Lower sideUpper sideResult
p(t)≤lower−gFalseTrue or Undefined0
lower−g<p(t)<lowerUndefinedTrueUndefined
lower≤p(t)≤upperTrueTrue1
upper<p(t)<upper+gTrueUndefinedUndefined
p(t)≥upper+gTrue or UndefinedFalse0

At either endpoint, both closed-interval comparisons are true.

Contract ​

  • Input: input: QuadraticPolynomial<V> plus the scalar endpoints lower: V and upper: V (lower <= upper is required).
  • An input containing quadratic monomials is bound to one bridge variable ${name}_input_0 by an exact quadratic equality; an affine input passes through unchanged with no bridge variable.
  • Bridge ranges are tightened to the input's finite bounds; a bound that is not float-representable is widened only to the next representable solver value (Math.nextUp/nextDown).
  • Registration validates every input has finite, un-widened bounds; widening a captured bound is rejected, tightening is safe.
  • helperVariables = the bridge variables plus the IfInFunction helpers (${name}_ifin, ${name}_ge, and ${name}_le, all binary).
  • The result polynomial is the unit-coefficient polynomial of ${name}_ifin, lifted to a quadratic polynomial.
  • Semantics inherit the linear conditional interval: closed-interval membership through two GE indicators, strictBoundary defaulting to NONZERO_TOLERANCE = 1e-10, delta defaulting to strictBoundary, and the three-valued gap behavior above.
  • Generic values require V : RealNumber<V>, V : NumberField<V> and an IntoValue<V> converter.

Solver mathematical model ​

Registration first validates the captured input bounds, then submits one exact quadratic equality for the quadratic input:

p(t)−bridge0=0,

named ${name}_input_0, with the bridge's range tightened to the input's finite bounds. The linear IfInFunction is constructed over the bridged affine input, forming ql=bridge0−lower and qu=upper−bridge0. For each side j∈{l,u}, with finite Lj≤qj≤Uj, true threshold Tj, false threshold Fj, and binary aj, it submits the two indicator rows

qj+(Lj−Tj)aj≥Lj,qj+(Fj−Uj)aj≤Fj

and the AND result rows for y = ${name}_ifin:

y≥al+au−1,y≤al,y≤au.

All rows are promoted to quadratic constraints on the same model. See Conditional interval for the threshold derivation, the fold behavior when a side is decidable from the declared range, and the full row set. Because the bridge equality is quadratic, the composed model is generally a nonconvex MIQCP and requires a solver with nonconvex quadratic constraint support.

Current API ​

Kotlin ​

Source: QuadraticIfIn.kt (QuadraticIfInFunction)

kotlin
QuadraticIfInFunction(
    input: QuadraticPolynomial<V>,
    lower: V,
    upper: V,
    strictBoundary: V? = null,
    delta: V? = null,
    converter: IntoValue<V>,
    name: String = "quadratic_ifin",
    displayName: String? = null
)

The class extends QuadraticFunctionSymbol<V> and delegates to IfInFunction(x = inputs[0], lower, upper, ...) after the input has been bound.

Rust ​

Rust now provides a same-named wrapper in quadratic_function.rs: QuadraticIfInFunction<V>. It bridges the quadratic input to a linear expression — one bridge variable pinned by an exact quadratic equality for a genuinely-quadratic input, pass-through for affine inputs — and wraps the same building blocks described below, preserving their three-valued gap semantics and their explicit-bounds Big-M policy.

rust
QuadraticIfInFunction::new(
    id: u64,
    name: &str,
    input: Quadratic<V>,
    lower: V,
    upper: V,
    strict_boundary: V,
    input_bounds: ConditionBounds<V>,
) -> Result<QuadraticIfInFunction<V>>
QuadraticIfInFunction::with_declared_dependencies(
    self,
    dependency_ids: Vec<u64>,
) -> Self

The bridge is named {name}_bridge, and the wrapper wraps a RegisterableIfInRangeFunction whose two sides are x−lower≥0 and upper−x≥0 (both GreaterEqual), so the result is 1 iff lower≤x≤upper. The interval ordering (lower <= upper) and the single-variable side-condition checks happen at construction. result_variable() returns the inner binary result variable, and lower(), upper(), strict_boundary(), and input_bounds() expose the stored configuration; Big-M comes only from the explicit input_bounds — token bounds are never read.

Internally, the wrapper bridges the quadratic input to a linear expression first (with QuadraticLinearFunction, which registers p(t)−bridge=0), then describes the closed interval with two ConditionalIfFunction sides and registers it as one range-driven indicator pair plus an AND result:

Source: if_in.rs, conditional.rs

rust
ConditionalIfFunction::new(
    condition: Linear<V>,
    relation: ConditionRelation,
    strict_boundary: V,
    bounds: ConditionBounds<V>,
) -> Result<ConditionalIfFunction<V>>
IfInRangeFunction::new(
    lower: ConditionalIfFunction<V>,
    upper: ConditionalIfFunction<V>,
) -> Result<IfInRangeFunction<V>>
IfInRangeFunction::registerable(
    self,
    id: u64,
    name: impl AsRef<str>,
) -> Result<RegisterableIfInRangeFunction<V>>

The interval validator requires GreaterEqual side relations, opposite-signed one-variable conditions, ordered endpoints, and finite bounds. RegisterableIfInRangeFunction creates one ConditionalIndicatorFunction per side and combines them with three linear AND rows.

Evaluate versus solver ​

The direct evaluator resolves the original input, writes the computed value into the bridge slot, and delegates to the linear IfInFunction's classify/evaluate. The three-valued interval semantics therefore match the linear page exactly: both sides true maps to 1, either side false maps to 0, and a value inside a boundary band maps to null. The Rust QuadraticIfInFunction skips the bridge bookkeeping and classifies the two interval differences of the original quadratic input directly with the same three-valued semantics (None inside a boundary band, 0 when zero_if_none is set).

Note the evaluation entry point: there is no single-map evaluate(values) on these classes. The overload is evaluate(values, tokenTable, converter, zeroIfNone), for example f.evaluate(mapOf(t to Flt64(1.0)), null, IntoValue.Identity, false). prepare(values, tokenTable, converter) delegates to the same path with zeroIfNone = false.

The solver uses range-driven indicator constraints; if the declared input range intersects an undefined boundary band, the resulting model may be infeasible. The evaluator classifies one supplied value and can return null; it does not need bounds, while registration does.

Minimal example ​

kotlin
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.QuadraticIfInFunction
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.QuadraticMonomial
import fuookami.ospf.kotlin.math.symbol.polynomial.QuadraticPolynomial

val t = RealVar("t").also {
    it.range.geq(Flt64(-2.0))
    it.range.leq(Flt64(2.0))
}
val input = QuadraticPolynomial(
    monomials = listOf(QuadraticMonomial.quadratic(Flt64.one, t, t)),
    constant = Flt64(-1.0)
)
val function = QuadraticIfInFunction(
    input = input,
    lower = Flt64.zero,
    upper = Flt64(2.0),
    strictBoundary = Flt64(0.5),
    converter = IntoValue.Identity
)

fun value(values: Map<Symbol, Flt64>): Flt64? =
    function.evaluate(
        values = values,
        tokenTable = null,
        converter = IntoValue.Identity,
        zeroIfNone = false
    )

check(value(mapOf(t to Flt64.one)) == Flt64.one)    // t^2 - 1 = 0, inside [0, 2]
check(value(mapOf(t to Flt64(2.0))) == Flt64.zero)  // t^2 - 1 = 3, outside (>= 2.5)
check(value(mapOf(t to Flt64(0.9))) == null)        // t^2 - 1 = -0.19, boundary band
rust
use ospf_rust_core::symbol::FunctionSymbol;
use ospf_rust_core::symbol::flatten::{Quadratic, QuadraticMonomial};
use ospf_rust_core::symbol::function::{ConditionBounds, QuadraticIfInFunction};
use ospf_rust_core::token::{MutableTokenList, Token, VecTokenList};
use ospf_rust_core::variable::{ContinuousVariableItem, VariableId};

// Input x^2 with x in [0, 2] => input range [0, 4], closed interval [1, 4]
let input = Quadratic::new(vec![QuadraticMonomial::new_quadratic(1.0, 0, 0)], 0.0);
let qifin = QuadraticIfInFunction::new(
    1,
    "qifin",
    input,
    1.0,
    4.0,
    0.5,
    ConditionBounds { lower: 0.0, upper: 4.0 },
)
.expect("valid quadratic input");

let tokens_for = |value: f64| {
    let x = ContinuousVariableItem::create(VariableId::standalone(0), "x");
    let mut tokens = VecTokenList::<f64>::new();
    let tx = Token::from_generic(x, 0);
    tx.set_result(value);
    tokens.add_token(tx);
    tokens
};

assert_eq!(
    <QuadraticIfInFunction as FunctionSymbol>::calculate_value(&qifin, &tokens_for(1.5), false),
    Some(1.0) // x^2 = 2.25, inside [1, 4]
);
assert_eq!(
    <QuadraticIfInFunction as FunctionSymbol>::calculate_value(&qifin, &tokens_for(2.0), false),
    Some(1.0) // x^2 = 4, the closed upper endpoint
);
assert_eq!(
    <QuadraticIfInFunction as FunctionSymbol>::calculate_value(&qifin, &tokens_for(0.5), false),
    Some(0.0) // x^2 = 0.25 < 1, outside
);

Tests and references ​