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