Skip to main content

pounce_common/
types.rs

1//! Fundamental scalar types.
2//!
3//! Mirrors `Common/IpTypes.h` and `Common/IpTypes.hpp`. We commit to
4//! `f64` and `i32` for v1.0 because Ipopt's MUMPS/HSL ABI is built
5//! around those widths; widening here would force us off the
6//! bit-equivalence path.
7
8/// Floating-point scalar — `Number` in Ipopt.
9pub type Number = f64;
10
11/// Signed index — `Index` in Ipopt. Held at 32 bits for ABI parity
12/// with MUMPS, MA27, etc.
13pub type Index = i32;
14
15/// Sentinel used by Ipopt for "no bound" in TNLP get_bounds_info.
16/// Value `1e19` is hard-coded throughout upstream; we match it.
17pub const NLP_LOWER_BOUND_INF: Number = -1e19;
18pub const NLP_UPPER_BOUND_INF: Number = 1e19;
19
20/// Is this *lower* bound a real bound, or the absent-bound sentinel?
21///
22/// The sentinel convention is **directional**, and reading it as a magnitude
23/// is a bug this codebase has now hit four separate times (#396, #398, #401,
24/// #402). A lower bound is absent only at or below [`NLP_LOWER_BOUND_INF`];
25/// `-5e20` is an ordinary finite lower bound, not "beyond infinity", and a
26/// symmetric `|b| < 1e19` test silently discards it.
27///
28/// Pair with [`upper_bound_present`] and decide presence *before* comparing a
29/// pair: `lo > hi` means nothing until both sides are known to be real, and
30/// neither does `lo == hi` (an "equality" at the sentinel is a one-sided row).
31///
32/// Callers that override the thresholds via `nlp_lower_bound_inf` /
33/// `nlp_upper_bound_inf` must test against their own values instead — these
34/// helpers hard-code the defaults.
35#[inline]
36pub fn lower_bound_present(lo: Number) -> bool {
37    lo.is_finite() && lo > NLP_LOWER_BOUND_INF
38}
39
40/// Is this *upper* bound a real bound, or the absent-bound sentinel?
41/// See [`lower_bound_present`] — an upper bound is absent only at or above
42/// [`NLP_UPPER_BOUND_INF`].
43#[inline]
44pub fn upper_bound_present(hi: Number) -> bool {
45    hi.is_finite() && hi < NLP_UPPER_BOUND_INF
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn presence_is_directional_not_symmetric() {
54        // Sentinels: absent on their own side.
55        assert!(!lower_bound_present(NLP_LOWER_BOUND_INF));
56        assert!(!upper_bound_present(NLP_UPPER_BOUND_INF));
57        assert!(!lower_bound_present(-2.0e19));
58        assert!(!upper_bound_present(2.0e19));
59
60        // Past the *opposite* sentinel: an ordinary bound, not an absent one.
61        // A symmetric `|b| < 1e19` test gets both of these wrong.
62        assert!(upper_bound_present(-5.0e20));
63        assert!(lower_bound_present(5.0e20));
64
65        // True infinities are absent on either side.
66        assert!(!lower_bound_present(Number::NEG_INFINITY));
67        assert!(!upper_bound_present(Number::INFINITY));
68        assert!(!lower_bound_present(Number::NAN));
69        assert!(!upper_bound_present(Number::NAN));
70
71        // Ordinary bounds.
72        assert!(lower_bound_present(0.0));
73        assert!(upper_bound_present(0.0));
74    }
75}