Skip to content

Logical AND ​

Contract ​

AndFunction<V> accepts one or more linear polynomials and exposes a binary result. The result is 1 exactly when every input polynomial is nonzero; it is 0 when at least one input is zero. The current API is generic over V : RealNumber<V> & NumberField<V> and is a linear function symbol.

This is a nonzero test, not a Boolean-variable-only operation. A polynomial may be continuous or may contain intermediate symbols.

Definition and truth table ​

For input polynomials p1,…,pn, let ai denote the nonzero indicator:

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

For two inputs, the truth table is:

p1 nonzerop2 nonzeroy
nono0
noyes0
yesno0
yesyes1

The constructor requires at least one input polynomial.

Boundary, tolerance, and Undefined ​

evaluate() compares each evaluated value with exact zero. A missing polynomial input makes the result null; it does not return a separate Undefined value.

Solver registration uses the shared nonzero-indicator construction. With tolerance t, indicator 0 represents the zero band |pi|≤t. With strict boundary g, indicator 1 represents either pi≥g or pi≤−g. Values in the gap t<|pi|<g are not assigned to either branch and can make the model infeasible.

The constants in the current source are NONZERO_TOLERANCE = 1e-10 and STRICT_BOUNDARY = NONZERO_TOLERANCE * 16 + 16 * 2^-52. They are not 1e-6 or 0.5. bigM is inferred from each polynomial's finite range when omitted; the range-based helper falls back to BIG_M_DEFAULT = 1e6 when no usable range is available.

Current API ​

Kotlin ​

The primary constructor is:

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

The companion invoke accepts polynomials, converter, bigM, name, and displayName. The additional fromLinearPolynomials factory accepts List<ToLinearPolynomial<V>> and returns a LinearFunctionSymbolAdapter<V>.

Rust ​

Source: and.rs

Rust accepts flattened inputs and provides AndFunction::new(id, name, polynomials), AndFunction::named(name, polynomials), and AndFunction::auto(polynomials). The public variables are available through result_variable(), indicator_variables(), and side_variables(). Rust has no public tolerance parameter; its evaluator uses an epsilon-level nonzero test, while mechanism Big-M is inferred from token bounds or falls back to the core default.

rust
AndFunction::new(id: u64, name: &str, polynomials: Vec<Linear<V>>) -> Self
AndFunction::named(name: impl AsRef<str>, polynomials: Vec<Linear<V>>) -> Self
AndFunction::auto(polynomials: Vec<Linear<V>>) -> Self

Solver mathematical model ​

For a function named name, the current implementation creates:

  • name_and: the binary result;
  • name_and_nz{i}: one nonzero indicator for each input;
  • name_and_side{i}: one sign-side helper for each nonzero indicator.

helperVariables contains the result, all nonzero indicators, and all side helpers; when every input polynomial is exactly a unit-coefficient binary variable, only the result is registered and the indicator block below is skipped. Registration first adds these variables through registerAuxiliaryTokens; registerConstraints adds the shared four-inequality nonzero test for every input, then adds:

For each pi, with nonzero flag ai, side flag si, zero tolerance t, and strict boundary g, that shared block is

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

The implementation expands these implications into four Big-M linear inequalities before appending the AND rows:

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

In the all-binary-input case the indicator block is replaced by the direct rows y≤zi for each binary input zi, plus ∑izi≥ny+1−n.

The public resultPolynomial is the unit-coefficient polynomial of name_and. The implementation is in And.kt and uses AbstractLinearMechanismModel registration.

Rust registers the same nonzero-indicator block followed by the same AND rows; its numerical threshold and Big-M are selected by the Rust mechanism rather than Kotlin constructor arguments.

evaluate() versus the solver model ​

The direct evaluator uses exact v == 0/v != 0 semantics. The solver model deliberately separates a zero band from a strict nonzero branch, so a value that is numerically nonzero but lies between tolerance and strict boundary is accepted by evaluate() but has no solver branch. Choose tolerance and strictBoundary consistently with the value lattice of the model.

Minimal current example ​

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

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

let function = AndFunction::named(
    "and",
    vec![Linear::new(vec![], 1.0), Linear::new(vec![], 2.0)],
);
let value = <AndFunction as FunctionSymbol>::calculate_value(
    &function,
    &VecTokenList::<f64>::new(),
    false,
);
assert_eq!(value, Some(1.0));

Source and core tests ​