1use crate::ffi;
2use crate::problem::Problem;
3use papilo_sys::PAPILO_SOLVE_RESULT;
4
5pub struct Solver {
7 raw: *mut ffi::Papilo_Solver,
8 problem: Option<Problem>,
9}
10
11impl Solver {
12 pub fn raw(&self) -> *mut ffi::Papilo_Solver {
14 self.raw
15 }
16
17 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 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 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#[derive(Debug)]
53pub struct SolveInfo {
54 raw: ffi::PAPILO_SOLVING_INFO,
55}
56
57impl SolveInfo {
58 pub fn dualbound(&self) -> f64 {
60 self.raw.dualbound
61 }
62
63 pub fn solving_time(&self) -> f64 {
65 self.raw.solvingtime
66 }
67
68 pub fn presolve_time(&self) -> f64 {
70 self.raw.presolvetime
71 }
72
73 pub fn best_solution_objective(&self) -> f64 {
75 self.raw.bestsol_obj
76 }
77
78 pub fn best_solution_integer_violation(&self) -> f64 {
80 self.raw.bestsol_intviol
81 }
82
83 pub fn best_solution_bound_violation(&self) -> f64 {
85 self.raw.bestsol_boundviol
86 }
87
88 pub fn best_solution_constraint_violation(&self) -> f64 {
90 self.raw.bestsol_consviol
91 }
92
93 pub fn solve_result(&self) -> SolveResult {
95 self.raw.solve_result.into()
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum SolveResult {
102 Optimal,
104 Feasible,
106 Stopped,
108 UnboundedOrInfeasible,
110 Unbounded,
112 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 }
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}