osqp_rust/
status.rs

1use osqp_rust_sys as ffi;
2use std::fmt;
3use std::slice;
4use std::time::Duration;
5
6use crate::{float, Problem};
7
8/// The result of solving a problem.
9#[derive(Clone, Debug)]
10pub enum Status<'a> {
11    Solved(Solution<'a>),
12    SolvedInaccurate(Solution<'a>),
13    MaxIterationsReached(Solution<'a>),
14    TimeLimitReached(Solution<'a>),
15    PrimalInfeasible(PrimalInfeasibilityCertificate<'a>),
16    PrimalInfeasibleInaccurate(PrimalInfeasibilityCertificate<'a>),
17    DualInfeasible(DualInfeasibilityCertificate<'a>),
18    DualInfeasibleInaccurate(DualInfeasibilityCertificate<'a>),
19    NonConvex(Failure<'a>),
20    // Prevent exhaustive enum matching
21    #[doc(hidden)]
22    __Nonexhaustive,
23}
24
25/// A solution to a problem.
26#[derive(Clone)]
27pub struct Solution<'a> {
28    prob: &'a Problem,
29}
30
31/// A proof of primal infeasibility.
32#[derive(Clone)]
33pub struct PrimalInfeasibilityCertificate<'a> {
34    prob: &'a Problem,
35}
36
37/// A proof of dual infeasibility.
38#[derive(Clone)]
39pub struct DualInfeasibilityCertificate<'a> {
40    prob: &'a Problem,
41}
42
43/// A problem that failed to be solved.
44#[derive(Clone)]
45pub struct Failure<'a> {
46    prob: &'a Problem,
47}
48
49/// The status of the polish operation.
50#[derive(Copy, Clone, Debug, Hash, PartialEq)]
51pub enum PolishStatus {
52    Successful,
53    Unsuccessful,
54    Unperformed,
55    // Prevent exhaustive enum matching
56    #[doc(hidden)]
57    __Nonexhaustive,
58}
59
60impl<'a> Status<'a> {
61    pub(crate) fn from_problem(prob: &'a Problem) -> Status<'a> {
62        use std::os::raw::c_int;
63        unsafe {
64            match (*(*prob.workspace).info).status_val as c_int {
65                ffi::src::src::auxil::OSQP_SOLVED => Status::Solved(Solution { prob }),
66                ffi::src::src::auxil::OSQP_SOLVED_INACCURATE => Status::SolvedInaccurate(Solution { prob }),
67                ffi::src::src::auxil::OSQP_MAX_ITER_REACHED => Status::MaxIterationsReached(Solution { prob }),
68                ffi::src::src::auxil::OSQP_TIME_LIMIT_REACHED => Status::TimeLimitReached(Solution { prob }),
69                ffi::src::src::auxil::OSQP_PRIMAL_INFEASIBLE => {
70                    Status::PrimalInfeasible(PrimalInfeasibilityCertificate { prob })
71                }
72                ffi::src::src::auxil::OSQP_PRIMAL_INFEASIBLE_INACCURATE => {
73                    Status::PrimalInfeasibleInaccurate(PrimalInfeasibilityCertificate { prob })
74                }
75                ffi::src::src::auxil::OSQP_DUAL_INFEASIBLE => {
76                    Status::DualInfeasible(DualInfeasibilityCertificate { prob })
77                }
78                ffi::src::src::auxil::OSQP_DUAL_INFEASIBLE_INACCURATE => {
79                    Status::DualInfeasibleInaccurate(DualInfeasibilityCertificate { prob })
80                }
81                ffi::src::src::auxil::OSQP_NON_CVX => Status::NonConvex(Failure { prob }),
82                _ => unreachable!(),
83            }
84        }
85    }
86
87    /// Returns the primal variables at the solution if the problem is `Solved`.
88    pub fn x(&self) -> Option<&'a [float]> {
89        self.solution().map(|s| s.x())
90    }
91
92    /// Returns the solution if the problem is `Solved`.
93    pub fn solution(&self) -> Option<Solution<'a>> {
94        match *self {
95            Status::Solved(ref solution) => Some(solution.clone()),
96            _ => None,
97        }
98    }
99
100    /// Returns the number of iterations taken by the solver.
101    pub fn iter(&self) -> u32 {
102        unsafe {
103            // cast safe as more than 2 billion iterations would be unreasonable
104            (*(*self.prob().workspace).info).iter as u32
105        }
106    }
107
108    /// Returns the time taken for the setup phase.
109    pub fn setup_time(&self) -> Duration {
110        unsafe { secs_to_duration((*(*self.prob().workspace).info).setup_time) }
111    }
112
113    /// Returns the time taken for the solve phase.
114    pub fn solve_time(&self) -> Duration {
115        unsafe { secs_to_duration((*(*self.prob().workspace).info).solve_time) }
116    }
117
118    /// Returns the time taken for the polish phase.
119    pub fn polish_time(&self) -> Duration {
120        unsafe { secs_to_duration((*(*self.prob().workspace).info).polish_time) }
121    }
122
123    /// Returns the total time taken by the solver.
124    ///
125    /// This includes the time taken for the setup phase on the first solve.
126    pub fn run_time(&self) -> Duration {
127        unsafe { secs_to_duration((*(*self.prob().workspace).info).run_time) }
128    }
129
130    /// Returns the number of rho updates.
131    pub fn rho_updates(&self) -> u32 {
132        unsafe {
133            // cast safe as more than 2 billion updates would be unreasonable
134            (*(*self.prob().workspace).info).rho_updates as u32
135        }
136    }
137
138    /// Returns the current best estimate of rho.
139    pub fn rho_estimate(&self) -> float {
140        unsafe { (*(*self.prob().workspace).info).rho_estimate }
141    }
142
143    fn prob(&self) -> &'a Problem {
144        match *self {
145            Status::Solved(ref solution)
146            | Status::SolvedInaccurate(ref solution)
147            | Status::MaxIterationsReached(ref solution)
148            | Status::TimeLimitReached(ref solution) => solution.prob,
149            Status::PrimalInfeasible(ref cert) | Status::PrimalInfeasibleInaccurate(ref cert) => {
150                cert.prob
151            }
152            Status::DualInfeasible(ref cert) | Status::DualInfeasibleInaccurate(ref cert) => {
153                cert.prob
154            }
155            Status::NonConvex(ref failure) => failure.prob,
156            Status::__Nonexhaustive => unreachable!(),
157        }
158    }
159}
160
161impl<'a> Solution<'a> {
162    /// Returns the primal variables at the solution.
163    pub fn x(&self) -> &'a [float] {
164        unsafe { slice::from_raw_parts((*(*self.prob.workspace).solution).x, self.prob.n) }
165    }
166
167    /// Returns the dual variables at the solution.
168    ///
169    /// These are the Lagrange multipliers of the constraints `l <= Ax <= u`.
170    pub fn y(&self) -> &'a [float] {
171        unsafe { slice::from_raw_parts((*(*self.prob.workspace).solution).y, self.prob.m) }
172    }
173
174    /// Returns the status of the polish operation.
175    pub fn polish_status(&self) -> PolishStatus {
176        unsafe {
177            match (*(*self.prob.workspace).info).status_polish {
178                1 => PolishStatus::Successful,
179                -1 => PolishStatus::Unsuccessful,
180                0 => PolishStatus::Unperformed,
181                _ => unreachable!(),
182            }
183        }
184    }
185
186    /// Returns the primal objective value.
187    pub fn obj_val(&self) -> float {
188        unsafe { (*(*self.prob.workspace).info).obj_val }
189    }
190
191    /// Returns the norm of primal residual.
192    pub fn pri_res(&self) -> float {
193        unsafe { (*(*self.prob.workspace).info).pri_res }
194    }
195
196    /// Returns the norm of dual residual.
197    pub fn dua_res(&self) -> float {
198        unsafe { (*(*self.prob.workspace).info).dua_res }
199    }
200}
201
202impl<'a> fmt::Debug for Solution<'a> {
203    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
204        fmt.debug_struct("Solution")
205            .field("x", &self.x())
206            .field("y", &self.y())
207            .field("polish_status", &self.polish_status())
208            .field("obj_val", &self.obj_val())
209            .field("pri_res", &self.pri_res())
210            .field("dua_res", &self.dua_res())
211            .finish()
212    }
213}
214
215impl<'a> PrimalInfeasibilityCertificate<'a> {
216    /// Returns the certificate of primal infeasibility.
217    ///
218    /// For further explanation see [Infeasibility detection in the alternating direction method of
219    /// multipliers for convex
220    /// optimization](http://www.optimization-online.org/DB_HTML/2017/06/6058.html).
221    pub fn delta_y(&self) -> &'a [float] {
222        unsafe { slice::from_raw_parts((*self.prob.workspace).delta_y, self.prob.m) }
223    }
224}
225
226impl<'a> fmt::Debug for PrimalInfeasibilityCertificate<'a> {
227    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
228        fmt.debug_struct("PrimalInfeasibilityCertificate")
229            .field("delta_y", &self.delta_y())
230            .finish()
231    }
232}
233
234impl<'a> DualInfeasibilityCertificate<'a> {
235    /// Returns the certificate of dual infeasibility.
236    ///
237    /// For further explanation see [Infeasibility detection in the alternating direction method of
238    /// multipliers for convex
239    /// optimization](http://www.optimization-online.org/DB_HTML/2017/06/6058.html).
240    pub fn delta_x(&self) -> &'a [float] {
241        unsafe { slice::from_raw_parts((*self.prob.workspace).delta_x, self.prob.n) }
242    }
243}
244
245impl<'a> fmt::Debug for DualInfeasibilityCertificate<'a> {
246    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
247        fmt.debug_struct("DualInfeasibilityCertificate")
248            .field("delta_x", &self.delta_x())
249            .finish()
250    }
251}
252
253impl<'a> fmt::Debug for Failure<'a> {
254    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
255        fmt.debug_struct("Failure").finish()
256    }
257}
258
259fn secs_to_duration(secs: float) -> Duration {
260    let whole_secs = secs.floor() as u64;
261    let nanos = (secs.fract() * 1e9) as u32;
262    Duration::new(whole_secs, nanos)
263}