1use crate::config::{Csp, OptimizationMode, PropagationStrategy, Pruning, SolveConfig, SolveStats};
10use crate::constraint::VarId;
11use crate::domain::{self, Domain};
12use crate::solver::search::{self, SearchParams};
13use crate::solver::{self, Solution, optimize};
14
15impl<D: Domain> Csp<D> {
16 pub fn propagate(&mut self) -> Result<(), Unsatisfiable>
18 where
19 D::Value: PartialEq + 'static,
20 {
21 self.propagate_with(PropagationStrategy::Auto)
22 }
23
24 pub fn propagate_with(&mut self, strategy: PropagationStrategy) -> Result<(), Unsatisfiable>
26 where
27 D::Value: PartialEq + 'static,
28 {
29 match strategy {
30 PropagationStrategy::Auto => {
31 if self.adjacency.is_some() {
32 self.propagate_with(PropagationStrategy::Ac3)
33 } else {
34 self.propagate_with(PropagationStrategy::Sweep)
35 }
36 }
37 PropagationStrategy::Ac3 => {
38 let adjacency = self.adjacency.as_ref().ok_or(Unsatisfiable)?;
40 let mut worklist = solver::ac3::BitsetWorklist::new(self.constraints.len());
42 solver::ac3::ac3_full(
43 &mut self.variables,
44 &self.constraints,
45 adjacency,
46 &mut self.stats,
47 &mut worklist,
48 search::PERMANENT_DEPTH,
49 )
50 }
51 PropagationStrategy::Sweep => solver::monotonic::propagate_monotonic(
52 &mut self.variables,
53 &self.constraints,
54 &mut self.stats,
55 ),
56 }
57 }
58
59 pub fn solve(&mut self, config: &SolveConfig) -> Vec<Solution<D>>
65 where
66 D::Value: PartialEq + 'static,
67 {
68 assert!(self.adjacency.is_some(), "call finalize() before solve()");
69
70 self.stats = SolveStats::default();
71
72 for v in &mut self.variables {
74 v.reset();
75 }
76
77 if config.pruning == Pruning::Ac3 {
85 let adjacency = self.adjacency.as_ref().unwrap();
86 let mut worklist = solver::ac3::BitsetWorklist::new(self.constraints.len());
87 let _ = solver::ac3::ac3_full(
88 &mut self.variables,
89 &self.constraints,
90 adjacency,
91 &mut self.stats,
92 &mut worklist,
93 search::PERMANENT_DEPTH,
94 );
95 }
96
97 let params = SearchParams {
98 pruning: config.pruning,
99 ordering: config.ordering,
100 max_solutions: config.max_solutions,
101 node_budget: config.node_budget,
102 cancel: config.cancel.clone(),
103 };
104 let adjacency = self.adjacency.as_ref().unwrap();
105
106 match config.optimization_mode {
107 OptimizationMode::Feasibility => search::feasibility_search(
108 &mut self.variables,
109 &self.constraints,
110 adjacency,
111 &mut self.constraint_weights,
112 &self.var_constraint_ids,
113 ¶ms,
114 &mut self.stats,
115 None,
116 ),
117 mode @ (OptimizationMode::MinimizeCost | OptimizationMode::MaximizeCost) => {
118 search::branch_and_bound(
121 &mut self.variables,
122 &self.constraints,
123 adjacency,
124 &mut self.constraint_weights,
125 &self.var_constraint_ids,
126 ¶ms,
127 &mut self.stats,
128 mode == OptimizationMode::MaximizeCost,
129 &optimize::ZeroCost,
130 )
131 }
132 }
133 }
134
135 pub fn solve_with_given(
140 &mut self,
141 config: &SolveConfig,
142 given: &[(VarId, D::Value)],
143 ) -> Vec<Solution<D>>
144 where
145 D::Value: PartialEq + 'static,
146 {
147 assert!(
148 self.adjacency.is_some(),
149 "call finalize() before solve_with_given()"
150 );
151
152 self.stats = SolveStats::default();
153
154 for v in &mut self.variables {
156 v.reset();
157 }
158
159 for (var, val) in given {
164 let _ = self.variables[*var as usize].domain.restrict_to(val);
165 }
166
167 {
170 let adjacency = self.adjacency.as_ref().unwrap();
171 for (var, val) in given {
172 for &neighbor in adjacency.neighbors_of_var(*var) {
173 let is_given = given.iter().any(|(gv, _)| *gv == neighbor);
174 if !is_given {
175 self.variables[neighbor as usize].domain.remove(val);
176 }
177 }
178 }
179 }
180
181 {
187 let adjacency = self.adjacency.as_ref().unwrap();
188 let mut worklist = solver::ac3::BitsetWorklist::new(self.constraints.len());
189 let _ = solver::ac3::ac3_full(
190 &mut self.variables,
191 &self.constraints,
192 adjacency,
193 &mut self.stats,
194 &mut worklist,
195 search::PERMANENT_DEPTH,
196 );
197 }
198
199 let params = SearchParams {
200 pruning: config.pruning,
201 ordering: config.ordering,
202 max_solutions: config.max_solutions,
203 node_budget: config.node_budget,
204 cancel: config.cancel.clone(),
205 };
206 let adjacency = self.adjacency.as_ref().unwrap();
207
208 search::feasibility_search(
209 &mut self.variables,
210 &self.constraints,
211 adjacency,
212 &mut self.constraint_weights,
213 &self.var_constraint_ids,
214 ¶ms,
215 &mut self.stats,
216 Some(given),
217 )
218 }
219
220 pub fn solve_with_cost_eval(
228 &mut self,
229 config: &SolveConfig,
230 cost_eval: &dyn optimize::DomainCostEval<D>,
231 ) -> Vec<Solution<D>>
232 where
233 D::Value: PartialEq + 'static,
234 {
235 assert!(
236 self.adjacency.is_some(),
237 "call finalize() before solve_with_cost_eval()"
238 );
239
240 self.stats = SolveStats::default();
241 for v in &mut self.variables {
242 v.reset();
243 }
244
245 let params = SearchParams {
246 pruning: config.pruning,
247 ordering: config.ordering,
248 max_solutions: config.max_solutions,
249 node_budget: config.node_budget,
250 cancel: config.cancel.clone(),
251 };
252 let adjacency = self.adjacency.as_ref().unwrap();
253 search::branch_and_bound(
254 &mut self.variables,
255 &self.constraints,
256 adjacency,
257 &mut self.constraint_weights,
258 &self.var_constraint_ids,
259 ¶ms,
260 &mut self.stats,
261 config.optimization_mode == OptimizationMode::MaximizeCost,
262 cost_eval,
263 )
264 }
265
266 pub fn stats(&self) -> &SolveStats {
268 &self.stats
269 }
270}
271
272impl<D: Domain> Default for Csp<D> {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl<D: domain::CostDomain> Csp<D> {
279 pub fn solve_optimized(&mut self, config: &SolveConfig) -> Vec<Solution<D>>
283 where
284 D::Value: PartialEq + 'static,
285 {
286 self.solve_with_cost_eval(config, &optimize::CostDomainEval)
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct Unsatisfiable;
293
294impl std::fmt::Display for Unsatisfiable {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 write!(f, "CSP is unsatisfiable")
297 }
298}
299
300impl std::error::Error for Unsatisfiable {}