Skip to content

Conditional If-Then ​

Contract ​

IfThenFunction<V> gates one linear polynomial with a linear condition. When the condition is true, the result equals thenPoly; when it is false, the result is zero. A value in the condition gap is Undefined and evaluates to null.

The condition and the then expression are both LinearPolynomial<V>. The function is generic over V : RealNumber<V> & NumberField<V>; it does not accept a quadratic polynomial directly.

Definition and three-valued condition ​

Let d be the supplied condition difference and q be thenPoly. Let g be strictBoundary:

RelationTrue branchFalse branchUndefined gap
GTd≥gd≤00<d<g
GEd≥0d≤−g−g<d<0
LTd≤−gd≥0−g<d<0
LEd≤0d≥g0<d<g

The gated result is:

y={q,true branch0,false branchundefined,inside the gap

Boundary, tolerance, and Undefined ​

classify(values) uses the shared TruthValue classifier. evaluate() returns thenPoly.evaluateWith(values) on True, the converter's zero on False, and null on Undefined, missing condition input, or failed evaluation.

strictBoundary defaults to tolerance, whose default is NONZERO_TOLERANCE = 1e-10. delta defaults to strictBoundary. Only GT, GE, LT, and LE are supported.

Constraint registration needs finite closed bounds for both the condition and thenPoly. They can be supplied as conditionBounds/bounds and thenBounds, or inferred from the corresponding polynomials. The legacy bigM argument cannot replace either range.

Current API ​

Kotlin ​

Source: IfThen.kt (IfThenFunction)

kotlin
IfThenFunction(
    condition: LinearPolynomial<V>,
    thenPoly: LinearPolynomial<V>,
    converter: IntoValue<V>,
    bigM: V? = null,
    tolerance: V? = null,
    strictBoundary: V? = null,
    name: String = "ifthen",
    displayName: String? = null,
    relation: Comparison = Comparison.GT,
    conditionBounds: ConditionBounds<V>? = null,
    thenBounds: ConditionBounds<V>? = null,
    bounds: ConditionBounds<V>? = null,
    delta: V? = null
)

The companion invoke accepts the same parameters. IfThenFunction.from accepts a LinearConstraintInput<V>; it extracts the flattened condition and can use a constant-one thenPoly by default, returning a LinearFunctionSymbolAdapter<V>.

Rust ​

Source: if_then.rs

Rust keeps two distinct APIs. ConditionalThenFunction is the closest match to Kotlin's conditional value gate: it accepts a ConditionalIfFunction, a Linear<V> then expression, and explicit finite then bounds. The legacy IfThenFunction instead models implication between two LinearInequality<V> objects and returns a binary implication result; it is not a one-to-one replacement for Kotlin's thenPoly output. Undefined conditions remain None in ConditionalThenFunction; they are not silently treated as false.

rust
ConditionalThenFunction::from_parts_with_bounds(
    condition: Linear<V>,
    relation: ConditionRelation,
    strict_boundary: V,
    condition_bounds: ConditionBounds<V>,
    then_poly: Linear<V>,
    then_bounds: ConditionBounds<V>,
) -> Result<ConditionalThenFunction<V>>
ConditionalThenFunction::named(
    name: impl AsRef<str>,
    condition: ConditionalIfFunction<V>,
    then_poly: Linear<V>,
    then_bounds: ConditionBounds<V>,
) -> Result<Self>
IfThenFunction::new(
    id: u64,
    name: &str,
    premise: LinearInequality<V>,
    consequence: LinearInequality<V>,
    big_m: V,
) -> Self

Solver mathematical model ​

For name, the implementation creates name_ind as a binary condition indicator and name_y as a real result variable. Both are in helperVariables; resultPolynomial is the unit-coefficient polynomial of name_y.

After adding the helper variables, registerConstraints normalizes the relation over the condition range. It adds two range-driven indicator inequalities, then gates thenPoly with the indicator. For then bounds L≤q≤U, the four gating inequalities are:

For normalized condition c∈[Lc,Uc], true threshold T, and false threshold F, the two condition rows are

c+(Lc−T)i≥Lc,c+(F−Uc)i≤F.

The four result rows are

y≤Ui,y≥Li,y−q≤−L(1−i),y−q≥−U(1−i).

Together these rows enforce i=0⇒y=0 and i=1⇒y=q. If the condition range proves one branch, Kotlin folds the indicator and result instead. Rust's conditional counterpart uses the same indicator-plus-gating construction.

evaluate() versus the solver model ​

The evaluator can return a then value, zero, or null. The solver model requires both finite ranges and has no assignment for a condition value in the gap; such a value can make the model infeasible. An undefined condition is not silently treated as false.

Minimal current example ​

kotlin
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.ConditionBounds
import fuookami.ospf.kotlin.core.symbol.function.IfThenFunction
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.inequality.Comparison
import fuookami.ospf.kotlin.math.symbol.monomial.LinearMonomial
import fuookami.ospf.kotlin.math.symbol.polynomial.LinearPolynomial

fun main() {
    val x = RealVar("x")
    val condition = LinearPolynomial(
        monomials = listOf(LinearMonomial(Flt64.one, x)),
        constant = Flt64(-2.0)
    )
    val thenPoly = LinearPolynomial<Flt64>(emptyList(), Flt64(5.0))
    val function = IfThenFunction(
        condition = condition,
        thenPoly = thenPoly,
        converter = IntoValue.Identity,
        relation = Comparison.GT,
        strictBoundary = Flt64(0.1),
        conditionBounds = ConditionBounds(Flt64(-2.0), Flt64(3.0)),
        thenBounds = ConditionBounds(Flt64(5.0), Flt64(5.0)),
        name = "ifthen"
    )

    check(function.evaluate(mapOf<Symbol, Flt64>(x to Flt64(3.0))) == Flt64(5.0))
    check(function.evaluate(mapOf<Symbol, Flt64>(x to Flt64(1.0))) == Flt64.zero)
}
rust
use ospf_rust_core::symbol::flatten::Linear;
use ospf_rust_core::symbol::function::{
    ConditionBounds, ConditionRelation, ConditionalThenFunction,
};

let function = ConditionalThenFunction::from_parts_with_bounds(
    Linear::new(vec![], 1.0),
    ConditionRelation::GreaterEqual,
    0.1,
    ConditionBounds {
        lower: -1.0,
        upper: 2.0,
    },
    Linear::new(vec![], 5.0),
    ConditionBounds {
        lower: 5.0,
        upper: 5.0,
    },
)
.expect("valid conditional-then function");
let value = function.evaluate(&1.0, &5.0).expect("classifiable condition");
assert_eq!(value, Some(5.0));

Source and core tests ​