problemreductions/solvers/ilp/solver.rs
1//! ILP solver implementation using HiGHS.
2
3use crate::models::algebraic::{Comparison, ObjectiveSense, VariableDomain, ILP};
4use crate::rules::{ReduceTo, ReductionResult};
5use good_lp::{default_solver, variable, ProblemVariables, Solution, SolverModel, Variable};
6
7/// An ILP solver using the HiGHS backend.
8///
9/// This solver solves Integer Linear Programming problems directly using the HiGHS solver.
10///
11/// # Example
12///
13/// ```rust,ignore
14/// use problemreductions::models::algebraic::{ILP, LinearConstraint, ObjectiveSense};
15/// use problemreductions::solvers::ILPSolver;
16///
17/// // Create a simple binary ILP: maximize x0 + 2*x1 subject to x0 + x1 <= 1
18/// let ilp = ILP::<bool>::new(
19/// 2,
20/// vec![LinearConstraint::le(vec![(0, 1.0), (1, 1.0)], 1.0)],
21/// vec![(0, 1.0), (1, 2.0)],
22/// ObjectiveSense::Maximize,
23/// );
24///
25/// let solver = ILPSolver::new();
26/// if let Some(solution) = solver.solve(&ilp) {
27/// println!("Solution: {:?}", solution);
28/// }
29/// ```
30#[derive(Debug, Clone, Default)]
31pub struct ILPSolver {
32 /// Time limit in seconds (None = no limit).
33 pub time_limit: Option<f64>,
34}
35
36impl ILPSolver {
37 /// Create a new ILP solver with default settings.
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 /// Create an ILP solver with a time limit.
43 pub fn with_time_limit(seconds: f64) -> Self {
44 Self {
45 time_limit: Some(seconds),
46 }
47 }
48
49 /// Solve an ILP problem directly.
50 ///
51 /// Returns `None` if the problem is infeasible or the solver fails.
52 /// The returned solution is a configuration vector where each element
53 /// is the variable value (config index = value).
54 pub fn solve<V: VariableDomain>(&self, problem: &ILP<V>) -> Option<Vec<usize>> {
55 let n = problem.num_vars;
56 if n == 0 {
57 return Some(vec![]);
58 }
59
60 // Create integer variables with bounds from variable domain
61 let mut vars_builder = ProblemVariables::new();
62 let vars: Vec<Variable> = (0..n)
63 .map(|_| {
64 let mut v = variable().integer();
65 v = v.min(0.0);
66 v = v.max((V::DIMS_PER_VAR - 1) as f64);
67 vars_builder.add(v)
68 })
69 .collect();
70
71 // Build objective expression
72 let objective: good_lp::Expression = problem
73 .objective
74 .iter()
75 .map(|&(var_idx, coef)| coef * vars[var_idx])
76 .sum();
77
78 // Build the model with objective
79 let unsolved = match problem.sense {
80 ObjectiveSense::Maximize => vars_builder.maximise(&objective),
81 ObjectiveSense::Minimize => vars_builder.minimise(&objective),
82 };
83
84 // Create the solver model
85 let mut model = unsolved.using(default_solver);
86
87 // Add constraints
88 for constraint in &problem.constraints {
89 // Build left-hand side expression
90 let lhs: good_lp::Expression = constraint
91 .terms
92 .iter()
93 .map(|&(var_idx, coef)| coef * vars[var_idx])
94 .sum();
95
96 // Create the constraint based on comparison type
97 let good_lp_constraint = match constraint.cmp {
98 Comparison::Le => lhs.leq(constraint.rhs),
99 Comparison::Ge => lhs.geq(constraint.rhs),
100 Comparison::Eq => lhs.eq(constraint.rhs),
101 };
102
103 model = model.with(good_lp_constraint);
104 }
105
106 // Solve
107 let solution = model.solve().ok()?;
108
109 // Extract solution: config index = value (no lower bound offset)
110 let result: Vec<usize> = vars
111 .iter()
112 .map(|v| {
113 let val = solution.value(*v);
114 val.round().max(0.0) as usize
115 })
116 .collect();
117
118 Some(result)
119 }
120
121 /// Solve any problem that reduces to `ILP<bool>`.
122 ///
123 /// This method first reduces the problem to a binary ILP, solves the ILP,
124 /// and then extracts the solution back to the original problem space.
125 ///
126 /// # Example
127 ///
128 /// ```no_run
129 /// use problemreductions::prelude::*;
130 /// use problemreductions::solvers::ILPSolver;
131 ///
132 /// // Create a problem that reduces directly to ILP.
133 /// let problem = MaximumSetPacking::<i32>::new(vec![
134 /// vec![0, 1],
135 /// vec![1, 2],
136 /// vec![3, 4],
137 /// ]);
138 ///
139 /// // Solve using ILP solver
140 /// let solver = ILPSolver::new();
141 /// if let Some(solution) = solver.solve_reduced(&problem) {
142 /// println!("Solution: {:?}", solution);
143 /// }
144 /// ```
145 pub fn solve_reduced<P>(&self, problem: &P) -> Option<Vec<usize>>
146 where
147 P: ReduceTo<ILP<bool>>,
148 {
149 let reduction = problem.reduce_to();
150 let ilp_solution = self.solve(reduction.target_problem())?;
151 Some(reduction.extract_solution(&ilp_solution))
152 }
153}
154
155#[cfg(test)]
156#[path = "../../unit_tests/solvers/ilp/solver.rs"]
157mod tests;