1use crate::prelude::*;
24#[cfg(test)]
25use crate::simplex_solver::{Constraint, ConstraintKind, big_rat};
26use crate::simplex_solver::{SimplexError, SimplexSolver, SolveStatus};
27use num_rational::BigRational;
28use num_traits::{One, Zero};
29
30pub type VarId = usize;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ParametricType {
36 Objective,
38 Rhs,
40}
41
42#[derive(Debug, Clone)]
44pub struct Breakpoint {
45 pub lambda: BigRational,
47 pub optimal_value: BigRational,
49 pub basis: Vec<VarId>,
51}
52
53#[derive(Debug, Clone)]
55pub struct ParametricInterval {
56 pub lambda_min: BigRational,
58 pub lambda_max: BigRational,
60 pub value_slope: BigRational,
62 pub value_intercept: BigRational,
64 pub basis: Vec<VarId>,
66}
67
68#[derive(Debug, Clone)]
70pub struct ParametricSimplexConfig {
71 pub param_type: ParametricType,
73 pub max_breakpoints: usize,
75 pub lambda_min: BigRational,
77 pub lambda_max: BigRational,
79}
80
81impl Default for ParametricSimplexConfig {
82 fn default() -> Self {
83 Self {
84 param_type: ParametricType::Objective,
85 max_breakpoints: 1000,
86 lambda_min: BigRational::zero(),
87 lambda_max: BigRational::from_integer(100.into()),
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default)]
94pub struct ParametricSimplexStats {
95 pub breakpoints: u64,
97 pub intervals: u64,
99 pub iterations: u64,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum ParametricSimplexResult {
106 Success,
108 Infeasible,
110 Unbounded,
112 BreakpointLimit,
114}
115
116#[derive(Debug)]
118pub struct ParametricSimplexSolver {
119 config: ParametricSimplexConfig,
121 simplex: SimplexSolver,
123 parametric_coeffs: FxHashMap<usize, BigRational>,
125 breakpoints: Vec<Breakpoint>,
127 intervals: Vec<ParametricInterval>,
129 stats: ParametricSimplexStats,
131}
132
133impl ParametricSimplexSolver {
134 pub fn new(config: ParametricSimplexConfig, simplex: SimplexSolver) -> Self {
139 Self {
140 config,
141 simplex,
142 parametric_coeffs: FxHashMap::default(),
143 breakpoints: Vec::new(),
144 intervals: Vec::new(),
145 stats: ParametricSimplexStats::default(),
146 }
147 }
148
149 pub fn default_config() -> Self {
151 let empty_solver = SimplexSolver::new(Vec::new(), Vec::new());
152 Self::new(ParametricSimplexConfig::default(), empty_solver)
153 }
154
155 pub fn set_simplex(&mut self, simplex: SimplexSolver) {
157 self.simplex = simplex;
158 }
159
160 pub fn set_parametric_coeff(&mut self, id: usize, coeff: BigRational) {
162 self.parametric_coeffs.insert(id, coeff);
163 }
164
165 pub fn solve(&mut self) -> ParametricSimplexResult {
178 self.breakpoints.clear();
179 self.intervals.clear();
180
181 let mut current_lambda = self.config.lambda_min.clone();
183
184 if let Err(e) = self.update_simplex_at_lambda(¤t_lambda) {
186 let _ = e; return ParametricSimplexResult::Infeasible;
190 }
191
192 let initial_result = match self.simplex.solve() {
194 Ok(r) => r,
195 Err(_) => return ParametricSimplexResult::Infeasible,
196 };
197
198 if initial_result.status != SolveStatus::Optimal {
199 return ParametricSimplexResult::Infeasible;
200 }
201
202 let mut current_basis = self.get_current_basis();
204
205 let initial_obj = initial_result.objective.clone();
207 let initial_breakpoint = Breakpoint {
208 lambda: current_lambda.clone(),
209 optimal_value: initial_obj,
210 basis: current_basis.clone(),
211 };
212 self.breakpoints.push(initial_breakpoint);
213 self.stats.breakpoints += 1;
214
215 while current_lambda < self.config.lambda_max {
217 if self.breakpoints.len() >= self.config.max_breakpoints {
218 return ParametricSimplexResult::BreakpointLimit;
219 }
220
221 let next_lambda = self.compute_next_breakpoint(¤t_lambda);
223
224 if next_lambda >= self.config.lambda_max {
225 let interval = self.create_interval(
227 current_lambda.clone(),
228 self.config.lambda_max.clone(),
229 ¤t_basis,
230 );
231 self.intervals.push(interval);
232 self.stats.intervals += 1;
233 break;
234 }
235
236 let interval =
238 self.create_interval(current_lambda.clone(), next_lambda.clone(), ¤t_basis);
239 self.intervals.push(interval);
240 self.stats.intervals += 1;
241
242 current_lambda = next_lambda.clone();
244
245 if let Err(e) = self.update_simplex_at_lambda(¤t_lambda) {
246 let _ = e;
247 return ParametricSimplexResult::Infeasible;
248 }
249
250 let step_result = match self.simplex.solve() {
251 Ok(r) => r,
252 Err(_) => return ParametricSimplexResult::Infeasible,
253 };
254
255 if step_result.status != SolveStatus::Optimal {
256 return ParametricSimplexResult::Infeasible;
257 }
258
259 current_basis = self.get_current_basis();
260
261 let breakpoint = Breakpoint {
262 lambda: current_lambda.clone(),
263 optimal_value: step_result.objective.clone(),
264 basis: current_basis.clone(),
265 };
266 self.breakpoints.push(breakpoint);
267 self.stats.breakpoints += 1;
268 self.stats.iterations += 1;
269 }
270
271 ParametricSimplexResult::Success
272 }
273
274 fn update_simplex_at_lambda(&mut self, lambda: &BigRational) -> Result<(), SimplexError> {
280 match self.config.param_type {
281 ParametricType::Objective => {
282 let updates: Vec<(usize, BigRational)> = self
284 .parametric_coeffs
285 .iter()
286 .map(|(&var, d_j)| {
287 let base_coeff = self
288 .simplex
289 .get_objective_coefficient(var)
290 .cloned()
291 .unwrap_or_else(|_| BigRational::zero());
292 let new_coeff = base_coeff + lambda * d_j;
293 (var, new_coeff)
294 })
295 .collect();
296 for (var, new_coeff) in updates {
297 self.simplex.set_objective_coefficient(var, new_coeff)?;
298 }
299 }
300 ParametricType::Rhs => {
301 let updates: Vec<(usize, BigRational)> = self
302 .parametric_coeffs
303 .iter()
304 .map(|(&constraint, e_i)| {
305 let base_rhs = self
306 .simplex
307 .get_rhs(constraint)
308 .cloned()
309 .unwrap_or_else(|_| BigRational::zero());
310 let new_rhs = base_rhs + lambda * e_i;
311 (constraint, new_rhs)
312 })
313 .collect();
314 for (constraint, new_rhs) in updates {
315 self.simplex.set_rhs(constraint, new_rhs)?;
316 }
317 }
318 }
319 Ok(())
320 }
321
322 fn compute_next_breakpoint(&self, current_lambda: &BigRational) -> BigRational {
330 current_lambda.clone() + BigRational::one()
332 }
333
334 fn get_current_basis(&self) -> Vec<VarId> {
337 match self.simplex.last_result() {
338 Some(r) if r.status == SolveStatus::Optimal => {
339 let zero = BigRational::zero();
340 r.values
341 .iter()
342 .enumerate()
343 .filter(|(_, v)| *v != &zero)
344 .map(|(i, _)| i)
345 .collect()
346 }
347 _ => Vec::new(),
348 }
349 }
350
351 fn create_interval(
357 &self,
358 lambda_min: BigRational,
359 lambda_max: BigRational,
360 basis: &[VarId],
361 ) -> ParametricInterval {
362 let intercept = self
364 .simplex
365 .objective_value()
366 .unwrap_or_else(BigRational::zero);
367
368 let slope = self.compute_objective_slope(&lambda_min, basis);
376
377 ParametricInterval {
378 lambda_min,
379 lambda_max,
380 value_slope: slope,
381 value_intercept: intercept,
382 basis: basis.to_vec(),
383 }
384 }
385
386 fn compute_objective_slope(&self, _lambda: &BigRational, _basis: &[VarId]) -> BigRational {
388 match self.config.param_type {
389 ParametricType::Objective => {
390 let values = match self.simplex.last_result() {
393 Some(r) if r.status == SolveStatus::Optimal => r.values.clone(),
394 _ => return BigRational::zero(),
395 };
396 self.parametric_coeffs
397 .iter()
398 .fold(BigRational::zero(), |acc, (&j, d_j)| {
399 let x_j = values.get(j).cloned().unwrap_or_else(BigRational::zero);
400 acc + d_j * &x_j
401 })
402 }
403 ParametricType::Rhs => {
404 self.parametric_coeffs
406 .iter()
407 .fold(BigRational::zero(), |acc, (&i, e_i)| {
408 let pi_i = self
409 .simplex
410 .shadow_price(i)
411 .unwrap_or_else(|_| BigRational::zero());
412 acc + e_i * &pi_i
413 })
414 }
415 }
416 }
417
418 pub fn breakpoints(&self) -> &[Breakpoint] {
420 &self.breakpoints
421 }
422
423 pub fn intervals(&self) -> &[ParametricInterval] {
425 &self.intervals
426 }
427
428 pub fn evaluate(&self, lambda: &BigRational) -> Option<BigRational> {
430 for interval in &self.intervals {
432 if lambda >= &interval.lambda_min && lambda < &interval.lambda_max {
433 let value = interval.value_intercept.clone() + &interval.value_slope * lambda;
435 return Some(value);
436 }
437 }
438
439 None
440 }
441
442 pub fn stats(&self) -> &ParametricSimplexStats {
444 &self.stats
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 fn simple_lp(c: i64, b: i64) -> SimplexSolver {
454 SimplexSolver::new(
455 vec![big_rat(c, 1)],
456 vec![Constraint {
457 coefficients: vec![big_rat(1, 1)],
458 rhs: big_rat(b, 1),
459 kind: ConstraintKind::Le,
460 }],
461 )
462 }
463
464 #[test]
465 fn test_parametric_creation() {
466 let solver = ParametricSimplexSolver::default_config();
467 assert_eq!(solver.breakpoints().len(), 0);
468 }
469
470 #[test]
471 fn test_set_parametric_coeff() {
472 let mut solver = ParametricSimplexSolver::default_config();
473 solver.set_parametric_coeff(0, BigRational::new(2.into(), 1.into()));
474 assert!(solver.parametric_coeffs.contains_key(&0));
475 }
476
477 #[test]
478 fn test_parametric_solve_with_simplex() {
479 let lp = simple_lp(1, 5);
483 let config = ParametricSimplexConfig {
484 param_type: ParametricType::Objective,
485 max_breakpoints: 10,
486 lambda_min: BigRational::zero(),
487 lambda_max: BigRational::from_integer(3.into()),
488 };
489 let mut solver = ParametricSimplexSolver::new(config, lp);
490 solver.set_parametric_coeff(0, BigRational::new(1.into(), 1.into()));
492 let result = solver.solve();
493 assert!(
495 result == ParametricSimplexResult::Success
496 || result == ParametricSimplexResult::BreakpointLimit
497 );
498 assert!(!solver.breakpoints().is_empty());
500 }
501
502 #[test]
503 fn test_evaluate_after_solve() {
504 let lp = simple_lp(-1, 5);
505 let config = ParametricSimplexConfig {
506 param_type: ParametricType::Rhs,
507 max_breakpoints: 5,
508 lambda_min: BigRational::zero(),
509 lambda_max: BigRational::from_integer(3.into()),
510 };
511 let mut solver = ParametricSimplexSolver::new(config, lp);
512 solver.set_parametric_coeff(0, BigRational::new(1.into(), 1.into()));
514 let result = solver.solve();
515 let _ = result; if !solver.intervals().is_empty() {
518 let val = solver.evaluate(&BigRational::new(1.into(), 1.into()));
519 assert!(val.is_some());
520 }
521 }
522}