problemreductions/solvers/ilp/
solver.rs1use super::adapter::HighsAdapter;
4use crate::models::algebraic::{Bounded, ILP};
5use crate::solvers::registry::solver_capability_registry;
6use crate::solvers::ExactProblemKey;
7use crate::traits::Problem;
8
9#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
11pub enum ILPSolveError {
12 #[error("the problem is infeasible")]
14 Infeasible,
15 #[error("the ILP objective is unbounded")]
17 Unbounded,
18 #[error("the ILP solver reached its time limit before proving optimality")]
20 Timeout,
21 #[error("the ILP backend failed: {0}")]
23 BackendFailure(String),
24 #[error("the ILP backend requires bool/i64 variables and i64/f64 coefficients")]
26 UnsupportedProblemType,
27 #[error("no ILP pipeline is registered for {0}")]
29 MissingPipeline(String),
30 #[error("solver capability registry is invalid: {0}")]
32 InvalidRegistry(String),
33 #[error("registered ILP pipeline returned the wrong solution type for {0}")]
35 PipelineTypeMismatch(String),
36 #[error("the ILP backend returned an invalid rounded solution: {0}")]
38 InvalidSolution(String),
39 #[error("the ILP backend cannot represent an exact model integer: {0}")]
41 InexactTransport(#[from] crate::types::ExactI64ToF64Error),
42 #[error(transparent)]
44 Extraction(#[from] crate::rules::ExtractionError),
45 #[error(transparent)]
47 Reduction(#[from] crate::rules::ReductionError),
48}
49
50#[derive(Debug, Clone, Default)]
77pub struct ILPSolver {
78 pub time_limit: Option<f64>,
80}
81
82impl ILPSolver {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn with_time_limit(seconds: f64) -> Self {
90 Self {
91 time_limit: Some(seconds),
92 }
93 }
94
95 pub fn solve<P>(&self, problem: &P) -> Result<P::Solution, ILPSolveError>
100 where
101 P: Problem + 'static,
102 P::Solution: 'static,
103 {
104 let key = ExactProblemKey::new(P::NAME, crate::export::variant_to_map(P::variant()));
105 let registry = solver_capability_registry()
106 .map_err(|error| ILPSolveError::InvalidRegistry(error.to_string()))?;
107 let pipeline = registry
108 .lookup(&key)
109 .ilp
110 .ok_or_else(|| ILPSolveError::MissingPipeline(key.label()))?;
111 let solution = pipeline.solve_typed(problem, self)?;
112 problem
113 .evaluate(&solution)
114 .map_err(|error| ILPSolveError::InvalidSolution(error.to_string()))?;
115 Ok(solution)
116 }
117
118 pub(crate) fn solve_dyn(&self, any: &dyn std::any::Any) -> Result<Vec<i64>, ILPSolveError> {
120 if let Some(ilp) = any.downcast_ref::<ILP<bool, i64>>() {
121 return HighsAdapter::new(self.time_limit).solve(ilp);
122 }
123 if let Some(ilp) = any.downcast_ref::<ILP<i64, i64, Bounded>>() {
124 return HighsAdapter::new(self.time_limit).solve(ilp);
125 }
126 if let Some(ilp) = any.downcast_ref::<ILP<i64, i64>>() {
127 return HighsAdapter::new(self.time_limit).solve(ilp);
128 }
129 if let Some(ilp) = any.downcast_ref::<ILP<bool, f64>>() {
130 return HighsAdapter::new(self.time_limit).solve(ilp);
131 }
132 if let Some(ilp) = any.downcast_ref::<ILP<i64, f64>>() {
133 return HighsAdapter::new(self.time_limit).solve(ilp);
134 }
135 Err(ILPSolveError::UnsupportedProblemType)
136 }
137}
138
139#[cfg(test)]
140#[path = "../../unit_tests/solvers/ilp/solver.rs"]
141mod tests;