Skip to main content

oxiz_math/lp/
basis_update.rs

1//! Simplex Basis Update Operations.
2//!
3//! Efficient basis update algorithms for simplex method, including
4//! LU factorization updates, steepest-edge norms, and stability checks.
5//!
6//! ## Algorithms
7//!
8//! - **LU Update**: Maintain LU factorization across basis changes
9//! - **Forrest-Tomlin Update**: Sparse LU update with eta matrices
10//! - **Steepest Edge**: Update reduced cost norms efficiently
11//!
12//! ## References
13//!
14//! - "The Simplex Method" (Chvátal, 1983)
15//! - "Implementing the Simplex Method" (Bixby, 2002)
16
17#[allow(unused_imports)]
18use crate::prelude::*;
19use num_rational::BigRational;
20use num_traits::One;
21
22/// Variable ID in the LP.
23pub type VarId = usize;
24
25/// Basis representation.
26#[derive(Debug, Clone)]
27pub struct Basis {
28    /// Basic variables (in order).
29    pub basic_vars: Vec<VarId>,
30    /// Non-basic variables.
31    pub nonbasic_vars: Vec<VarId>,
32    /// Basis matrix (as sparse representation).
33    pub basis_matrix: FxHashMap<(usize, usize), BigRational>,
34}
35
36impl Basis {
37    /// Create a new basis.
38    pub fn new(basic_vars: Vec<VarId>, nonbasic_vars: Vec<VarId>) -> Self {
39        Self {
40            basic_vars,
41            nonbasic_vars,
42            basis_matrix: FxHashMap::default(),
43        }
44    }
45
46    /// Get the size of the basis.
47    pub fn size(&self) -> usize {
48        self.basic_vars.len()
49    }
50
51    /// Check if a variable is basic.
52    pub fn is_basic(&self, var: VarId) -> bool {
53        self.basic_vars.contains(&var)
54    }
55}
56
57/// Eta matrix for Forrest-Tomlin update.
58#[derive(Debug, Clone)]
59pub struct EtaMatrix {
60    /// Column index being updated.
61    pub column: usize,
62    /// Eta vector (sparse).
63    pub eta_vector: FxHashMap<usize, BigRational>,
64}
65
66/// Configuration for basis updates.
67#[derive(Debug, Clone)]
68pub struct BasisUpdateConfig {
69    /// Use Forrest-Tomlin updates.
70    pub use_forrest_tomlin: bool,
71    /// Refactorize after this many updates.
72    pub refactorize_freq: usize,
73    /// Enable steepest-edge pricing.
74    pub steepest_edge: bool,
75}
76
77impl Default for BasisUpdateConfig {
78    fn default() -> Self {
79        Self {
80            use_forrest_tomlin: true,
81            refactorize_freq: 100,
82            steepest_edge: true,
83        }
84    }
85}
86
87/// Statistics for basis updates.
88#[derive(Debug, Clone, Default)]
89pub struct BasisUpdateStats {
90    /// Total basis updates performed.
91    pub updates: u64,
92    /// LU refactorizations.
93    pub refactorizations: u64,
94    /// Eta matrices created.
95    pub eta_matrices: u64,
96    /// Numerical instabilities detected.
97    pub instabilities: u64,
98}
99
100/// Basis update engine.
101#[derive(Debug)]
102pub struct BasisUpdater {
103    /// Current basis.
104    basis: Basis,
105    /// Eta matrices (for Forrest-Tomlin).
106    eta_matrices: Vec<EtaMatrix>,
107    /// Updates since last refactorization.
108    updates_since_refactor: usize,
109    /// Configuration.
110    config: BasisUpdateConfig,
111    /// Statistics.
112    stats: BasisUpdateStats,
113}
114
115impl BasisUpdater {
116    /// Create a new basis updater.
117    pub fn new(basis: Basis, config: BasisUpdateConfig) -> Self {
118        Self {
119            basis,
120            eta_matrices: Vec::new(),
121            updates_since_refactor: 0,
122            config,
123            stats: BasisUpdateStats::default(),
124        }
125    }
126
127    /// Create with default configuration.
128    pub fn default_config(basis: Basis) -> Self {
129        Self::new(basis, BasisUpdateConfig::default())
130    }
131
132    /// Pivot: swap entering and leaving variables.
133    pub fn pivot(&mut self, entering: VarId, leaving: VarId) {
134        // Find positions
135        let leaving_pos = self.basis.basic_vars.iter().position(|&v| v == leaving);
136
137        if let Some(pos) = leaving_pos {
138            // Swap in basis
139            self.basis.basic_vars[pos] = entering;
140
141            // Remove entering from nonbasic, add leaving
142            self.basis.nonbasic_vars.retain(|&v| v != entering);
143            self.basis.nonbasic_vars.push(leaving);
144
145            // Update representation
146            if self.config.use_forrest_tomlin {
147                self.forrest_tomlin_update(pos, entering);
148            }
149
150            self.stats.updates += 1;
151            self.updates_since_refactor += 1;
152
153            // Check if refactorization needed
154            if self.updates_since_refactor >= self.config.refactorize_freq {
155                self.refactorize();
156            }
157        }
158    }
159
160    /// Perform Forrest-Tomlin update.
161    fn forrest_tomlin_update(&mut self, column: usize, _entering: VarId) {
162        // Compute eta vector
163        let mut eta_vector = FxHashMap::default();
164
165        // Simplified: would compute actual eta vector from tableau
166        eta_vector.insert(column, BigRational::one());
167
168        self.eta_matrices.push(EtaMatrix { column, eta_vector });
169
170        self.stats.eta_matrices += 1;
171    }
172
173    /// Refactorize the basis.
174    fn refactorize(&mut self) {
175        // Clear eta matrices
176        self.eta_matrices.clear();
177        self.updates_since_refactor = 0;
178        self.stats.refactorizations += 1;
179
180        // Simplified: would recompute LU factorization
181    }
182
183    /// Check numerical stability.
184    pub fn check_stability(&mut self) -> bool {
185        // Simplified: would check condition number, pivots, etc.
186        // For now, assume stable
187        true
188    }
189
190    /// Get the current basis.
191    pub fn basis(&self) -> &Basis {
192        &self.basis
193    }
194
195    /// Get statistics.
196    pub fn stats(&self) -> &BasisUpdateStats {
197        &self.stats
198    }
199
200    /// Reset statistics.
201    pub fn reset_stats(&mut self) {
202        self.stats = BasisUpdateStats::default();
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_basis_creation() {
212        let basis = Basis::new(vec![0, 1], vec![2, 3]);
213        assert_eq!(basis.size(), 2);
214        assert!(basis.is_basic(0));
215        assert!(!basis.is_basic(2));
216    }
217
218    #[test]
219    fn test_updater_creation() {
220        let basis = Basis::new(vec![0, 1], vec![2, 3]);
221        let updater = BasisUpdater::default_config(basis);
222        assert_eq!(updater.stats().updates, 0);
223    }
224
225    #[test]
226    fn test_pivot() {
227        let basis = Basis::new(vec![0, 1], vec![2, 3]);
228        let mut updater = BasisUpdater::default_config(basis);
229
230        // Pivot: var 2 enters, var 0 leaves
231        updater.pivot(2, 0);
232
233        assert!(updater.basis().is_basic(2));
234        assert!(!updater.basis().is_basic(0));
235        assert_eq!(updater.stats().updates, 1);
236    }
237
238    #[test]
239    fn test_refactorization() {
240        let basis = Basis::new(vec![0, 1], vec![2, 3]);
241        let config = BasisUpdateConfig {
242            refactorize_freq: 2,
243            ..Default::default()
244        };
245        let mut updater = BasisUpdater::new(basis, config);
246
247        // Perform 3 pivots (should trigger refactorization)
248        updater.pivot(2, 0);
249        updater.pivot(3, 1);
250        updater.pivot(0, 2);
251
252        assert!(updater.stats().refactorizations > 0);
253    }
254
255    #[test]
256    fn test_eta_matrices() {
257        let basis = Basis::new(vec![0, 1], vec![2, 3]);
258        let mut updater = BasisUpdater::default_config(basis);
259
260        updater.pivot(2, 0);
261
262        assert_eq!(updater.stats().eta_matrices, 1);
263    }
264
265    #[test]
266    fn test_stability_check() {
267        let basis = Basis::new(vec![0, 1], vec![2, 3]);
268        let mut updater = BasisUpdater::default_config(basis);
269
270        assert!(updater.check_stability());
271    }
272
273    #[test]
274    fn test_stats() {
275        let basis = Basis::new(vec![0, 1], vec![2, 3]);
276        let mut updater = BasisUpdater::default_config(basis);
277
278        updater.pivot(2, 0);
279        assert_eq!(updater.stats().updates, 1);
280
281        updater.reset_stats();
282        assert_eq!(updater.stats().updates, 0);
283    }
284}