Skip to main content

papilo_rs/
solver.rs

1use crate::ffi;
2use crate::problem::Problem;
3use papilo_sys::PAPILO_SOLVE_RESULT;
4
5/// A struct representing a solver instance in the PaPILO library.
6pub struct Solver {
7    raw: *mut ffi::Papilo_Solver,
8    problem: Option<Problem>,
9}
10
11impl Solver {
12    /// Returns a raw pointer to the underlying `Papilo_Solver`.
13    pub fn raw(&self) -> *mut ffi::Papilo_Solver {
14        self.raw
15    }
16
17    /// Creates a new instance of the `Solver`.
18    pub fn new() -> Self {
19        let raw = unsafe { ffi::papilo_solver_create() };
20        if raw.is_null() {
21            panic!("Failed to create a new Solver instance");
22        }
23        Self { raw, problem: None }
24    }
25
26    /// Loads a problem into the solver.
27    pub fn load_problem(&mut self, problem: Problem) {
28        unsafe {
29            ffi::papilo_solver_load_problem(self.raw, problem.raw());
30        }
31        self.problem = Some(problem);
32    }
33
34    /// Starts the solver with the loaded problem.
35    pub fn start(&mut self) -> SolveInfo {
36        SolveInfo {
37            raw: unsafe { *ffi::papilo_solver_start(self.raw) },
38        }
39    }
40}
41
42impl Drop for Solver {
43    fn drop(&mut self) {
44        unsafe {
45            ffi::papilo_solver_free(self.raw);
46        }
47    }
48}
49
50
51/// A struct representing the solving information returned by the solver.
52#[derive(Debug)]
53pub struct SolveInfo {
54    raw: ffi::PAPILO_SOLVING_INFO,
55}
56
57impl SolveInfo {
58    /// Returns the dual bound of the solution.
59    pub fn dualbound(&self) -> f64 {
60        self.raw.dualbound
61    }
62
63    /// Returns the solving time.
64    pub fn solving_time(&self) -> f64 {
65        self.raw.solvingtime
66    }
67
68    /// Returns the presolve time.
69    pub fn presolve_time(&self) -> f64 {
70        self.raw.presolvetime
71    }
72
73    /// Returns the best solution objective value.
74    pub fn best_solution_objective(&self) -> f64 {
75        self.raw.bestsol_obj
76    }
77
78    /// Returns the best solution integer violation.
79    pub fn best_solution_integer_violation(&self) -> f64 {
80        self.raw.bestsol_intviol
81    }
82
83    /// Returns the best solution bound violation.
84    pub fn best_solution_bound_violation(&self) -> f64 {
85        self.raw.bestsol_boundviol
86    }
87
88    /// Returns the best solution constraint violation.
89    pub fn best_solution_constraint_violation(&self) -> f64 {
90        self.raw.bestsol_consviol
91    }
92
93    /// Returns the solve result.
94    pub fn solve_result(&self) -> SolveResult {
95        self.raw.solve_result.into()
96    }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100/// An enum representing the possible results of a solve operation.
101pub enum SolveResult {
102    /// The problem was solved to optimality.
103    Optimal,
104    /// The solving stopped early with a feasible solution due to limits or interrupts.
105    Feasible,
106    /// The solving stopped early and without a solution due to limits or interrupts.
107    Stopped,
108    /// The problem was detected to be unbounded or infeasible.
109    UnboundedOrInfeasible,
110    /// The problem was detected to be unbounded.
111    Unbounded,
112    /// The problem was detected to be infeasible.
113    Infeasible,
114}
115
116impl From<PAPILO_SOLVE_RESULT> for SolveResult {
117    fn from(result: PAPILO_SOLVE_RESULT) -> Self {
118        match result {
119            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_OPTIMAL => SolveResult::Optimal,
120            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_FEASIBLE => SolveResult::Feasible,
121            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_STOPPED => SolveResult::Stopped,
122            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_UNBND_OR_INFEAS => {
123                SolveResult::UnboundedOrInfeasible
124            }
125            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_UNBOUNDED => SolveResult::Unbounded,
126            ffi::Papilo_SolveResult_PAPILO_SOLVE_RESULT_INFEASIBLE => SolveResult::Infeasible,
127            _ => panic!("Unknown solve result: {}", result),
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn solver_creation() {
138        let solver = Solver::new();
139        assert!(!solver.raw.is_null(), "Solver instance should not be null");
140        // The drop method will be called automatically at the end of the test
141    }
142
143    #[test]
144    fn empty_problem_load() {
145        let problem = Problem::new();
146        let mut solver = Solver::new();
147        solver.load_problem(problem);
148        assert!(
149            !solver.raw.is_null(),
150            "Solver instance should not be null after loading problem"
151        );
152        let res = solver.start();
153        println!("Solving info: {:?}", res);
154    }
155
156    #[test]
157    fn one_cont_column() {
158        let mut problem = Problem::new();
159        problem.add_col(1.5, 20.0, false, 10.0, "x2");
160
161        let mut solver = Solver::new();
162        solver.load_problem(problem);
163        assert!(
164            !solver.raw.is_null(),
165            "Solver instance should not be null after loading problem with columns"
166        );
167        let res = solver.start();
168        assert_eq!(res.dualbound(), 15.0);
169    }
170
171    #[test]
172    fn one_int_column() {
173        let mut problem = Problem::new();
174        problem.add_col(1.5, 10.0, true, 10.0, "x1");
175
176        let mut solver = Solver::new();
177        solver.load_problem(problem);
178        assert!(
179            !solver.raw.is_null(),
180            "Solver instance should not be null after loading problem with integer columns"
181        );
182        let res = solver.start();
183        assert_eq!(res.dualbound(), 20.0);
184    }
185
186    #[test]
187    fn one_col_one_row() {
188        let mut problem = Problem::new();
189        let x = problem.add_col(1.0, 10.0, true, 10.0, "x1");
190        problem.add_row("r1", &[(x, 1.0)], 2.5, f64::INFINITY);
191
192        let mut solver = Solver::new();
193        solver.load_problem(problem);
194        assert!(
195            !solver.raw.is_null(),
196            "Solver instance should not be null after loading problem with integer columns"
197        );
198        let res = solver.start();
199        assert_eq!(res.solve_result(), SolveResult::Optimal);
200        assert_eq!(res.best_solution_objective(), 30.0);
201        assert_eq!(res.dualbound(), 30.0);
202    }
203}