otspot_core/qp/ipm_solver/
outcome.rs1use crate::sparse::CscMatrix;
4
5#[derive(Clone, Debug)]
8pub struct IpmOutcome {
9 pub solution: Vec<f64>,
10 pub dual_solution: Vec<f64>,
11 pub bound_duals: Vec<f64>,
13 pub objective: f64,
14 pub iterations: usize,
15 pub kkt_residual_rel: f64,
17 pub primal_residual_rel: f64,
19 pub bound_violation: f64,
21 pub complementarity_residual_rel: f64,
23 pub duality_gap_rel: f64,
25 pub numerical_failure: bool,
26 pub infeasibility_status: Option<crate::problem::SolveStatus>,
28 pub is_locally_optimal: bool,
30 pub postsolve_krylov_ir_skipped: bool,
34 pub timing: Option<crate::problem::TimingBreakdown>,
36}
37
38impl IpmOutcome {
39 pub fn empty() -> Self {
40 Self {
41 solution: Vec::new(),
42 dual_solution: Vec::new(),
43 bound_duals: Vec::new(),
44 objective: f64::INFINITY,
45 iterations: 0,
46 kkt_residual_rel: f64::INFINITY,
47 primal_residual_rel: f64::INFINITY,
48 bound_violation: f64::INFINITY,
49 complementarity_residual_rel: f64::INFINITY,
50 duality_gap_rel: f64::INFINITY,
51 numerical_failure: false,
52 infeasibility_status: None,
53 is_locally_optimal: false,
54 postsolve_krylov_ir_skipped: false,
55 timing: None,
56 }
57 }
58
59 pub fn infeasibility(status: crate::problem::SolveStatus) -> Self {
61 debug_assert!(
62 matches!(
63 status,
64 crate::problem::SolveStatus::Infeasible
65 | crate::problem::SolveStatus::Unbounded
66 | crate::problem::SolveStatus::NonConvex(_)
67 ),
68 "infeasibility outcome must be Infeasible / Unbounded / NonConvex, got {:?}",
69 status
70 );
71 Self {
72 infeasibility_status: Some(status),
73 ..Self::empty()
74 }
75 }
76
77 pub const PROMOTION_GAP_TOL: f64 = 1e-1;
79
80 pub fn satisfies_eps(&self, eps: f64) -> bool {
81 !self.solution.is_empty()
82 && !self.numerical_failure
83 && self.kkt_residual_rel <= eps
84 && self.primal_residual_rel <= eps
85 && self.bound_violation <= eps
86 && self.complementarity_residual_rel <= eps
87 && self.duality_gap_rel < Self::PROMOTION_GAP_TOL
88 }
89
90 pub fn quality_score(&self) -> f64 {
92 if self.solution.is_empty() || self.numerical_failure {
93 return f64::INFINITY;
94 }
95 self.kkt_residual_rel
96 .max(self.primal_residual_rel)
97 .max(self.bound_violation)
98 .max(self.complementarity_residual_rel)
99 }
100}
101
102pub struct ProblemView<'a> {
109 pub q: &'a CscMatrix,
110 pub a: &'a CscMatrix,
111 pub c: &'a [f64],
112 pub b: &'a [f64],
113 pub bounds: &'a [(f64, f64)],
114 pub constraint_types: &'a [crate::problem::ConstraintType],
115 pub eliminated_cols: &'a [bool],
116}
117
118impl<'a> ProblemView<'a> {
119 pub fn from_problem(problem: &'a crate::qp::problem::QpProblem) -> Self {
121 Self {
122 q: &problem.q,
123 a: &problem.a,
124 c: &problem.c,
125 b: &problem.b,
126 bounds: &problem.bounds,
127 constraint_types: &problem.constraint_types,
128 eliminated_cols: &[],
129 }
130 }
131}