舍入
RoundingFunction 使用数值实现的 round 操作将线性多项式映射为整数:
由于半整数行为并不相同,本页将直接求值契约与 solver 契约分开说明。
契约
- 输入:
x: LinearPolynomial<V>。 - 输出:
IntVar(resultVar),通过resultPolynomial暴露。 - 输入无法求值时,
evaluate返回null;否则委托给converter.fromValue(x).round()。 bigM控制小数指示变量门控,并规范为至少 1;它必须足以支持该门控,默认值为 1。
数学定义
solver 将输入分解为
然后使用二进制
并注册
因此 solver 在小于 0.5 时取 r = 0,大于等于 0.5 时取 r = 1:半整数向正无穷取整。
适用域与边界
直接求值接受所有有限实数。solver 在整数边界附近使用 NONZERO_TOLERANCE,小数门控使用规范化后的 Big-M。关键语义边界是半整数:
| 路径 | 半整数规则 | 示例 |
|---|---|---|
| solver 注册 | b >= 0.5 向上取整(k + 1) | 2.5 -> 3, -1.5 -> -1 |
Flt32 / Flt64 求值 | 委托 kotlin.math.round,半整数取偶 | 2.5 -> 2, -1.5 -> -2 |
FltX 求值 | BigDecimal HALF_UP | 2.5 -> 3, -1.5 -> -2 |
没有处理这一差异时,不要使用半整数交叉核对 evaluate 与 solver 结果。
当前 API
Kotlin
evaluate 使用的数值行为实现于 Floating.kt(Flt64.round) 与 FltX.round。
RoundingFunction(
x: LinearPolynomial<V>,
bigM: V? = null,
converter: IntoValue<V>,
name: String,
displayName: String? = null
)Rust
Rust 在 RoundingFunction 中显式指定取整类型:
RoundingFunction::new(id: u64, name: &str, input: Linear<V>, kind: RoundingKind) -> RoundingFunction<V>
RoundingFunction::round(id: u64, name: &str, input: Linear<V>) -> RoundingFunction<V>RoundingKind 包含 Floor、Ceil、Round 和 Trunc,同时提供 named_round 等快捷构造器。Rust 保留整数辅助变量,但通过 result_variable() 暴露连续结果;它没有 Kotlin 的 bigM/converter 参数。取整边界由 Rust 自己的 ROUNDING_EPSILON 规则实现,因此不要假定两种语言在半整数处的行为完全一致。
求解器数学模型
Kotlin 引入
Rust 使用所选 RoundingKind 对应的约束组;Round 同样把连续结果连接到整数辅助变量,但使用 Rust 自己的固定边界 epsilon。
evaluate 与 solver 的差异
evaluate 调用转换器的 round,不会创建辅助变量或使用 Big-M。solver 始终使用 floor 加 b >= 0.5 门控。两者在非半整数处一致,但在半整数处可能返回不同值,如上表所示。
示例与测试
import fuookami.ospf.kotlin.core.solver.value.IntoValue
import fuookami.ospf.kotlin.core.symbol.function.RoundingFunction
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.eq
import fuookami.ospf.kotlin.math.symbol.monomial.LinearMonomial
import fuookami.ospf.kotlin.math.symbol.polynomial.LinearPolynomial
val x = RealVar("x")
val xPoly = LinearPolynomial(listOf(LinearMonomial(Flt64.one, x)), Flt64.zero)
val round = RoundingFunction(
x = xPoly,
converter = IntoValue.Identity,
name = "round"
)
val value = round.evaluate(mapOf<Symbol, Flt64>(x to Flt64(1.2)))
check(value != null && (value eq Flt64.one))use ospf_rust_core::symbol::flatten::{Linear, LinearMonomial};
use ospf_rust_core::symbol::function::RoundingFunction;
let input = Linear::new(vec![LinearMonomial::new(1.0, 0)], 0.0);
let round = RoundingFunction::round(1, "round", input);
let _result = round.result_variable();
assert!(round.sign_variable().is_some());完整示例:RoundTest.kt
Core 验证:FunctionSymbolDiscreteGenericEvaluateTest.kt 与 FunctionSymbolRoundingGenericRegistrationTest.kt
Rust 源码与 parity 覆盖:rounding.rs 和 gurobi_linear_function_kotlin_parity.rs。