problemreductions/solvers/
decision_search.rs1use crate::models::decision::{Decision, DecisionProblemMeta};
4use crate::solvers::BruteForce;
5use crate::traits::Problem;
6use crate::types::{Max, Min, OptimizationValue, Or};
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9use std::fmt;
10
11fn is_satisfiable<P>(problem: &P) -> Result<bool, crate::solvers::SolveError>
13where
14 P: Problem<Value = Or> + 'static,
15 P::Solution: 'static,
16{
17 Ok(BruteForce::new().solve(problem)?.is_some())
18}
19
20fn solve_via_decision_min<P>(
21 problem: &P,
22 lower: i64,
23 upper: i64,
24) -> Result<Option<i64>, crate::solvers::SolveError>
25where
26 P: DecisionProblemMeta + Problem<Value = Min<i64>> + Clone + 'static,
27 P::Solution: 'static,
28{
29 if lower > upper {
30 return Err(crate::solvers::SolveError::InvalidSearchInterval { lower, upper });
31 }
32
33 if !is_satisfiable(&Decision::new(problem.clone(), upper))? {
34 if upper != i64::MAX && is_satisfiable(&Decision::new(problem.clone(), i64::MAX))? {
35 return Err(crate::solvers::SolveError::OptimumOutsideSearchInterval { lower, upper });
36 }
37 return Ok(None);
38 }
39 if let Some(bound) = lower.checked_sub(1) {
40 if is_satisfiable(&Decision::new(problem.clone(), bound))? {
41 return Err(crate::solvers::SolveError::OptimumOutsideSearchInterval { lower, upper });
42 }
43 }
44
45 let mut lo = lower;
46 let mut hi = upper;
47 while lo < hi {
48 let mid = i64::try_from((i128::from(lo) + i128::from(hi)).div_euclid(2))
49 .expect("midpoint lies within the i64 interval");
50 if is_satisfiable(&Decision::new(problem.clone(), mid))? {
51 hi = mid;
52 } else {
53 lo = mid + 1;
54 }
55 }
56
57 Ok(Some(lo))
58}
59
60fn solve_via_decision_max<P>(
61 problem: &P,
62 lower: i64,
63 upper: i64,
64) -> Result<Option<i64>, crate::solvers::SolveError>
65where
66 P: DecisionProblemMeta + Problem<Value = Max<i64>> + Clone + 'static,
67 P::Solution: 'static,
68{
69 if lower > upper {
70 return Err(crate::solvers::SolveError::InvalidSearchInterval { lower, upper });
71 }
72
73 if !is_satisfiable(&Decision::new(problem.clone(), lower))? {
74 if lower != i64::MIN && is_satisfiable(&Decision::new(problem.clone(), i64::MIN))? {
75 return Err(crate::solvers::SolveError::OptimumOutsideSearchInterval { lower, upper });
76 }
77 return Ok(None);
78 }
79 if let Some(bound) = upper.checked_add(1) {
80 if is_satisfiable(&Decision::new(problem.clone(), bound))? {
81 return Err(crate::solvers::SolveError::OptimumOutsideSearchInterval { lower, upper });
82 }
83 }
84
85 let mut lo = lower;
86 let mut hi = upper;
87 while lo < hi {
88 let mid = i64::try_from((i128::from(lo) + i128::from(hi)).div_euclid(2) + 1)
89 .expect("midpoint lies within the i64 interval");
90 if is_satisfiable(&Decision::new(problem.clone(), mid))? {
91 lo = mid;
92 } else {
93 hi = mid - 1;
94 }
95 }
96
97 Ok(Some(lo))
98}
99
100#[doc(hidden)]
101pub trait DecisionSearchValue:
102 OptimizationValue<Inner = i64> + Clone + fmt::Debug + Serialize + DeserializeOwned
103{
104 fn solve_problem<P>(
105 problem: &P,
106 lower: i64,
107 upper: i64,
108 ) -> Result<Option<i64>, crate::solvers::SolveError>
109 where
110 P: DecisionProblemMeta + Problem<Value = Self> + Clone + 'static,
111 P::Solution: 'static;
112}
113
114impl DecisionSearchValue for Min<i64> {
115 fn solve_problem<P>(
116 problem: &P,
117 lower: i64,
118 upper: i64,
119 ) -> Result<Option<i64>, crate::solvers::SolveError>
120 where
121 P: DecisionProblemMeta + Problem<Value = Self> + Clone + 'static,
122 P::Solution: 'static,
123 {
124 solve_via_decision_min(problem, lower, upper)
125 }
126}
127
128impl DecisionSearchValue for Max<i64> {
129 fn solve_problem<P>(
130 problem: &P,
131 lower: i64,
132 upper: i64,
133 ) -> Result<Option<i64>, crate::solvers::SolveError>
134 where
135 P: DecisionProblemMeta + Problem<Value = Self> + Clone + 'static,
136 P::Solution: 'static,
137 {
138 solve_via_decision_max(problem, lower, upper)
139 }
140}
141
142pub fn solve_via_decision<P>(
147 problem: &P,
148 lower: i64,
149 upper: i64,
150) -> Result<Option<i64>, crate::solvers::SolveError>
151where
152 P: DecisionProblemMeta + Clone + 'static,
153 P::Solution: 'static,
154 P::Value: DecisionSearchValue,
155{
156 <P::Value as DecisionSearchValue>::solve_problem(problem, lower, upper)
157}
158
159#[cfg(test)]
160#[path = "../unit_tests/solvers/decision_search.rs"]
161mod tests;