Skip to main content

otspot_core/
lib.rs

1//! # otspot — 数理最適化ソルバー
2//!
3//! 線形計画法(LP)・二次計画法(QP)と混合整数問題(MILP / MIQP)を解く Rust ソルバークレート。
4//! LP は改訂単体法(Revised Simplex)、QP は内点法(IPM / IP-PMM)を核とし、
5//! 実行不可能・非有界の判定と完全な主双対情報の出力に対応する。
6//!
7//! ## 主要モジュール
8//!
9//! | モジュール | 役割 |
10//! |-----------|------|
11//! | [`sparse`] | CSC 形式の疎行列・疎ベクトル演算 |
12//! | [`problem`] | 問題定義(`LpProblem` / `QpProblem`、`SolveStatus`、`SolverResult`) |
13//! | [`lp`] | LP 求解エントリポイント(`solve_lp_with`) |
14//! | [`qp`] | 内点法ソルバー(QP、IPM / IP-PMM) |
15//! | [`mip`] | 混合整数ソルバー(MILP / MIQP、branch-and-bound) |
16//! | [`options`] | `SolverOptions`、`Tolerance` |
17//!
18//! ## 使用例
19//!
20//! MPS ファイルから LP 問題を読み込んで解く (via the `otspot` facade):
21//!
22//! ```rust,ignore
23//! use std::path::Path;
24//! use otspot::io::mps;
25//!
26//! let prob = mps::parse_mps_file(Path::new("problem.mps")).expect("MPS読み込み失敗");
27//! let result = otspot_core::solve(&prob);
28//! println!("最適値: {:?}", result);
29//! ```
30
31pub mod error;
32pub use error::SolverError;
33pub use error::MpsError;
34#[doc(hidden)]
35pub mod presolve;
36pub mod sparse;
37pub mod problem;
38pub(crate) mod simplex;
39// Internal parsers compiled only under cfg(test), used by otspot-core's own
40// integration-style tests (e.g. qp::ipm_solver diagnostics). These are
41// source-duplicates of otspot-io's canonical, published parsers and are not
42// part of the production library. Full removal is tracked separately (the
43// ipm_solver tests depend on crate-internal IPM diagnostics).
44#[cfg(test)]
45#[allow(dead_code)]
46pub(crate) mod io;
47pub(crate) mod basis;
48pub mod tolerances;
49pub mod options;
50pub use options::{
51    BranchingStrategy, DualPricing, GlobalOptimizationConfig, LpWarmStart, MipBranching, MipConfig,
52    SolverOptions, Tolerance, WarmStartBasis,
53};
54pub mod qp;
55pub mod mip;
56pub mod lp;
57#[doc(hidden)]
58pub mod linalg;
59
60#[cfg(test)]
61pub(crate) mod test_kkt;
62
63/// Thread-local peak-allocation tracker for memory sentinel tests.
64///
65/// Wraps the system allocator and records per-thread net live bytes.
66/// `TrackingAlloc` is wired as the `#[global_allocator]` in test builds so
67/// that any future sentinel test can read allocation deltas via `update`.
68#[cfg(test)]
69pub(crate) mod peak_alloc {
70    use std::alloc::{GlobalAlloc, Layout, System};
71    use std::cell::Cell;
72
73    thread_local! {
74        static CURRENT: Cell<isize> = const { Cell::new(0) };
75    }
76
77    #[inline]
78    fn update(delta: isize) {
79        CURRENT.with(|c| c.set(c.get() + delta));
80    }
81
82    pub struct TrackingAlloc;
83
84    unsafe impl GlobalAlloc for TrackingAlloc {
85        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
86            let ptr = System.alloc(layout);
87            if !ptr.is_null() {
88                update(layout.size() as isize);
89            }
90            ptr
91        }
92        unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
93            let ptr = System.alloc_zeroed(layout);
94            if !ptr.is_null() {
95                update(layout.size() as isize);
96            }
97            ptr
98        }
99        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
100            System.dealloc(ptr, layout);
101            update(-(layout.size() as isize));
102        }
103        unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
104            let new_ptr = System.realloc(ptr, layout, new_size);
105            if !new_ptr.is_null() {
106                update(new_size as isize - layout.size() as isize);
107            }
108            new_ptr
109        }
110    }
111}
112
113#[cfg(test)]
114#[global_allocator]
115static TEST_ALLOC: peak_alloc::TrackingAlloc = peak_alloc::TrackingAlloc;
116
117// --- re-export: ユーザーが最も使う型を最短パスで ---
118pub use sparse::CscMatrix;
119pub use problem::{SolveRoute, SolveStats, SolveStatus};
120pub use problem::certificate::{
121    BoundGapCertificate, FarkasCertificate, IncompleteReason, NotProven, OptimalCertificate,
122    SolveOutcome, UnboundedRayCertificate,
123};
124pub use qp::certificate::prove_optimal;
125pub use qp::{solve_qp, solve_qp_global, solve_qp_with, QpProblem, SolverResult, QpWarmStart};
126pub use mip::{
127    solve_milp, solve_milp_with_stats, solve_miqp, solve_miqp_with_stats, MilpProblem,
128    MipProblemError, MipStats, MiqpProblem,
129};
130pub use lp::solve_lp_with;
131pub use simplex::{solve, solve_with};
132
133/// Internal BFRT (Bound-Flipping Ratio Test) primitives for integration tests.
134/// Deferred for removal until typed pipeline restructures the simplex tree.
135#[doc(hidden)]
136pub mod bound_flip {
137    pub use crate::simplex::dual_advanced::bound_flip::{
138        bfrt_flip_invocations, bfrt_select_entering, reset_bfrt_flip_invocations,
139        BfrtResult, ColBound,
140    };
141}
142pub use qp::{diagnose, DiagnosticReport, DiagnosticWarning, DiagnosticCode, Severity, ProblemInfo};
143
144/// RAII guard that disables a production sentinel for the duration of its lifetime.
145///
146/// On construction: calls `enable` to disable the sentinel.
147/// On drop: calls `restore` to re-enable the sentinel.
148/// Panic-safe: `restore` runs even if the guarded closure panics.
149#[cfg(test)]
150pub(crate) struct ScopedDisable<D: Fn()> {
151    restore: D,
152}
153
154#[cfg(test)]
155impl<D: Fn()> ScopedDisable<D> {
156    pub(crate) fn new<E: Fn()>(enable: E, restore: D) -> Self {
157        enable();
158        ScopedDisable { restore }
159    }
160}
161
162#[cfg(test)]
163impl<D: Fn()> Drop for ScopedDisable<D> {
164    fn drop(&mut self) {
165        (self.restore)();
166    }
167}
168
169/// Apply the LP KKT optimality guard to a solver result.
170///
171/// Exposed for integration-test sentinel load-bearing proofs. Runs full
172/// KKT+dual_sign verification via `prove_optimal_lp`; demotes false-Optimal
173/// to `SuboptimalSolution`. Non-Optimal results pass through unchanged.
174#[doc(hidden)]
175pub fn apply_lp_primal_guard(
176    result: crate::problem::SolverResult,
177    problem: &crate::problem::LpProblem,
178) -> crate::problem::SolverResult {
179    crate::qp::certificate::guard_lp_optimal(result, problem)
180}