Skip to content

首个非零索引 ​

FirstFunction 返回输入多项式中值严格大于 epsilon 的第一个元素的从零开始索引。如果没有输入通过该判定,则返回输入数量。

契约 ​

  • 输入:有序、通常应非空的 List<LinearPolynomial<V>>。
  • 输出:[0,n] 中的数值索引,以线性多项式 result 暴露。
  • 选择条件:p_i > epsilon,不是 abs(p_i) > epsilon;负值永远不会被选中。
  • epsilon 是 Flt64 参数(默认 1e-6),系数和结果通过转换器使用泛型 V。
  • 直接 evaluate 遇到缺失输入值时返回 null。

定义与数学模型 ​

设输入值为 p0,…,pn−1,则

bi=1[pi>ε].

设 yi 为首个命中 one-hot 标志:

yi=1[bi=1∧∑j<iyj=0].

返回索引为

r=∑i=0n−1iyi+n(1−∑i=0n−1yi).

因此全为 false 时返回 n,而不是 null。

求解器数学模型 ​

Kotlin 先通过两约束正值指标模型把 bi∈{0,1} 连接到 pi>0。预期的首个命中变量 hi∈{0,1} 可以写成

hi≤bi,hi≤1−bj(j<i),hi≥bi−∑j<ibj,

公开结果为

r=∑i=0n−1ihi+n(1−∑i=0n−1hi).

当前 Kotlin 注册还会额外传入单调约束 hi≤hi−1。该约束会与“较后位置首次命中”冲突,例如 b=(0,1) 可能变为不可行。这是当前实现缺陷,不属于预期公式。

Rust 不创建 bi,而是接收外部条件二值变量,创建首个激活选择变量,并把连续结果门控到被选候选多项式;没有激活项时,仅在启用 zero_if_none 后把结果固定为零。因此 Rust 返回被选值,不是 Kotlin 的首个索引。

当前 API ​

Kotlin ​

源码:First.kt(FirstFunction)

kotlin
FirstFunction(
    polynomials: List<LinearPolynomial<V>>,
    epsilon: Flt64 = Flt64(1e-6),
    converter: IntoValue<V>,
    name: String,
    displayName: String? = null
)

Rust ​

源码:first.rs

Rust 有同名辅助函数,但不是 Kotlin 阈值函数的一一对应替代。FirstFunction::new 接收候选 Linear<V> 值以及等长的、已经计算好的 Vec<BinaryVariableItem> 条件变量;它没有 epsilon 参数,也不会在内部创建 BinaryzationFunction。公开访问器为 result_variable()、polynomials() 和 condition_variables()。没有任何条件命中时,直接求值返回 None(zero_if_none 为 true 时返回零),机理约束也将结果限定为零;而 Kotlin 返回输入数量 n。若需要 Kotlin 的阈值契约,应为每个输入组合一个 BinaryzationFunction,再传入其指示变量。

rust
FirstFunction::new(
    id: u64,
    name: &str,
    polynomials: Vec<Linear<V>>,
    conditions: Vec<BinaryVariableItem>,
) -> Self
FirstFunction::result_variable(&self) -> &ContinuousVariableItem
FirstFunction::polynomials(&self) -> &[Linear<V>]
FirstFunction::condition_variables(&self) -> &[BinaryVariableItem]

evaluate 与 solver 的差异 ​

直接求值从索引 0 扫描列表并使用调用者的 epsilon。solver 注册先构造二值化函数;其当前约束容差是共享的 NONZERO_TOLERANCE,再连接首个命中数组。因此当 epsilon 非默认值时,阈值附近的直接求值和 solver 判定可能不同。

边界、tolerance 与 Undefined ​

首个命中结果不是布尔值,也不是从 1 开始的索引。由于使用严格 >,等于 epsilon 时不会选中。空列表不是有用的模型输入,调用者应至少提供一个多项式。直接求值遇到缺失符号时返回 null;Big-M 无法推导或多项式值非法时,底层二值化注册失败。

当前最小示例 ​

kotlin
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.FirstFunction
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
import fuookami.ospf.kotlin.core.variable.RealVar

val x0 = RealVar("x0")
val x1 = RealVar("x1")
val first = FirstFunction(
    polynomials = listOf(
        LinearPolynomial(listOf(LinearMonomial(Flt64.one, x0)), Flt64.zero),
        LinearPolynomial(listOf(LinearMonomial(Flt64.one, x1)), Flt64.zero)
    ),
    converter = IntoValue.Identity,
    name = "first"
)
val value = first.evaluate(
    mapOf<Symbol, Flt64>(x0 to Flt64.zero, x1 to Flt64.two)
)
check(value != null && value == Flt64.one)
rust
use ospf_rust_core::symbol::flatten::Linear;
use ospf_rust_core::symbol::function::FirstFunction;
use ospf_rust_core::symbol::FunctionSymbol;
use ospf_rust_core::token::{MutableTokenList, Token, VecTokenList};
use ospf_rust_core::variable::{BinaryVariableItem, VariableId};

let c0 = BinaryVariableItem::create(VariableId::standalone(1), "c0");
let c1 = BinaryVariableItem::create(VariableId::standalone(2), "c1");
let function = FirstFunction::new(
    1,
    "first",
    vec![Linear::new(vec![], 10.0), Linear::new(vec![], 20.0)],
    vec![c0.clone(), c1.clone()],
);
let mut tokens = VecTokenList::new();
let token0 = Token::from_generic(c0.clone(), c0.index());
token0.set_result(0.0);
tokens.add_token(token0);
let token1 = Token::from_generic(c1.clone(), c1.index());
token1.set_result(1.0);
tokens.add_token(token1);
let value = <FirstFunction as FunctionSymbol>::calculate_value(&function, &tokens, false);
assert_eq!(value, Some(20.0));

测试与示例 ​

相关页面 ​