pumpkin_core/optimisation/
linear_unsat_sat.rs1use std::num::NonZero;
2
3use super::solution_callback::SolutionCallback;
4use super::OptimisationProcedure;
5use crate::branching::Brancher;
6use crate::optimisation::OptimisationDirection;
7use crate::predicate;
8use crate::proof::ConstraintTag;
9use crate::results::OptimisationResult;
10use crate::results::ProblemSolution;
11use crate::results::SatisfactionResult;
12use crate::results::SatisfactionResultUnderAssumptions;
13use crate::results::Solution;
14use crate::termination::TerminationCondition;
15use crate::variables::IntegerVariable;
16use crate::Solver;
17
18#[derive(Debug, Clone, Copy)]
20pub struct LinearUnsatSat<Var, Callback> {
21 direction: OptimisationDirection,
22 objective: Var,
23 solution_callback: Callback,
24}
25
26impl<Var, Callback> LinearUnsatSat<Var, Callback> {
27 pub fn new(
29 direction: OptimisationDirection,
30 objective: Var,
31 solution_callback: Callback,
32 ) -> Self {
33 Self {
34 direction,
35 objective,
36 solution_callback,
37 }
38 }
39}
40
41impl<Var, B, Callback> OptimisationProcedure<B, Callback> for LinearUnsatSat<Var, Callback>
42where
43 Var: IntegerVariable,
44 B: Brancher,
45 Callback: SolutionCallback<B>,
46{
47 fn optimise(
48 &mut self,
49 brancher: &mut B,
50 termination: &mut impl TerminationCondition,
51 solver: &mut Solver,
52 ) -> OptimisationResult {
53 let objective = match self.direction {
54 OptimisationDirection::Maximise => self.objective.scaled(-1),
55 OptimisationDirection::Minimise => self.objective.scaled(1),
56 };
57
58 let primal_solution: Solution = match solver.satisfy(brancher, termination) {
60 SatisfactionResult::Satisfiable(satisfiable) => satisfiable.solution().into(),
61 SatisfactionResult::Unsatisfiable(_, _) => return OptimisationResult::Unsatisfiable,
62 SatisfactionResult::Unknown(_, _) => return OptimisationResult::Unknown,
63 };
64
65 self.solution_callback.on_solution_callback(
66 solver,
67 primal_solution.as_reference(),
68 brancher,
69 );
70
71 let primal_objective = primal_solution.get_integer_value(objective.clone());
72
73 let mut objective_lower_bound = solver.lower_bound(&objective);
85 let mut proven_lower_bound = objective_lower_bound;
86
87 while objective_lower_bound < primal_objective {
88 let conclusion = {
89 let solve_result = solver.satisfy_under_assumptions(
90 brancher,
91 termination,
92 &[predicate![objective <= objective_lower_bound]],
93 );
94
95 match solve_result {
96 SatisfactionResultUnderAssumptions::Satisfiable(satisfiable) => {
97 Some(OptimisationResult::Optimal(satisfiable.solution().into()))
98 }
99 SatisfactionResultUnderAssumptions::UnsatisfiableUnderAssumptions(
100 _,
101 ) => {
102 None
103 }
104 SatisfactionResultUnderAssumptions::Unsatisfiable(_) =>
105 unreachable!("If the problem is unsatisfiable here, it would have been unsatisifable in the initial solve."),
106 SatisfactionResultUnderAssumptions::Unknown(_) => Some(OptimisationResult::Unknown),
107 }
108 };
109
110 match conclusion {
111 Some(OptimisationResult::Optimal(solution)) => {
112 self.solution_callback.on_solution_callback(
113 solver,
114 primal_solution.as_reference(),
115 brancher,
116 );
117
118 solver
119 .conclude_proof_dual_bound(predicate![objective >= objective_lower_bound]);
120 return OptimisationResult::Optimal(solution);
121 }
122 Some(OptimisationResult::Unknown) => {
123 solver.conclude_proof_dual_bound(predicate![objective >= proven_lower_bound]);
124 return OptimisationResult::Satisfiable(primal_solution);
125 }
126 Some(result) => return result,
127 None => {}
128 }
129
130 solver
131 .add_clause(
132 [predicate![objective >= objective_lower_bound + 1]],
133 ConstraintTag::from_non_zero(NonZero::<u32>::MAX),
134 )
135 .expect("this should always be valid given the previous solves");
136
137 proven_lower_bound = objective_lower_bound + 1;
138 objective_lower_bound = solver.lower_bound(&objective);
139 }
140
141 solver.conclude_proof_dual_bound(predicate![objective >= primal_objective]);
142 OptimisationResult::Optimal(primal_solution)
143 }
144}