solverforge_solver/phase/exhaustive/
decider.rs1use std::fmt::Debug;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12use super::bounder::ScoreBounder;
13use super::node::ExhaustiveSearchNode;
14
15pub trait ExhaustiveSearchDecider<S: PlanningSolution, D: Director<S>>: Send + Debug {
22 fn expand(
27 &self,
28 parent_index: usize,
29 parent: &ExhaustiveSearchNode<S>,
30 score_director: &mut D,
31 ) -> Vec<ExhaustiveSearchNode<S>>;
32
33 fn reset_assignments(&self, score_director: &mut D);
34
35 fn apply_assignment(&self, node: &ExhaustiveSearchNode<S>, score_director: &mut D);
36
37 fn total_entities(&self, score_director: &D) -> usize;
38}
39
40pub struct SimpleDecider<S: PlanningSolution, V: Clone + Send + Sync + 'static, B = ()> {
49 descriptor_index: usize,
51 variable_name: String,
53 variable_index: usize,
55 values: Vec<V>,
57 bounder: Option<B>,
59 setter: fn(&mut S, usize, Option<V>),
61}
62
63impl<S: PlanningSolution, V: Clone + Send + Sync + 'static> SimpleDecider<S, V, ()> {
64 pub fn new(
72 descriptor_index: usize,
73 variable_name: impl Into<String>,
74 values: Vec<V>,
75 setter: fn(&mut S, usize, Option<V>),
76 ) -> Self {
77 Self {
78 descriptor_index,
79 variable_name: variable_name.into(),
80 variable_index: 0,
81 values,
82 bounder: None,
83 setter,
84 }
85 }
86}
87
88impl<S: PlanningSolution, V: Clone + Send + Sync + 'static, B> SimpleDecider<S, V, B> {
89 pub fn with_bounder<B2>(self, bounder: B2) -> SimpleDecider<S, V, B2> {
90 SimpleDecider {
91 descriptor_index: self.descriptor_index,
92 variable_name: self.variable_name,
93 variable_index: self.variable_index,
94 values: self.values,
95 bounder: Some(bounder),
96 setter: self.setter,
97 }
98 }
99
100 pub fn with_variable_index(mut self, variable_index: usize) -> Self {
101 self.variable_index = variable_index;
102 self
103 }
104}
105
106impl<S: PlanningSolution, V: Clone + Send + Sync + Debug + 'static, B: Debug> Debug
107 for SimpleDecider<S, V, B>
108{
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("SimpleDecider")
111 .field("descriptor_index", &self.descriptor_index)
112 .field("variable_name", &self.variable_name)
113 .field("variable_index", &self.variable_index)
114 .field("value_count", &self.values.len())
115 .finish()
116 }
117}
118
119impl<S, V, B, D> ExhaustiveSearchDecider<S, D> for SimpleDecider<S, V, B>
120where
121 S: PlanningSolution,
122 V: Clone + Send + Sync + Debug + 'static,
123 B: ScoreBounder<S, D>,
124 D: Director<S>,
125{
126 fn expand(
127 &self,
128 parent_index: usize,
129 parent: &ExhaustiveSearchNode<S>,
130 score_director: &mut D,
131 ) -> Vec<ExhaustiveSearchNode<S>> {
132 let entity_index = parent.depth();
133 let new_depth = parent.depth() + 1;
134
135 let total = self.total_entities(score_director);
137 if entity_index >= total {
138 return Vec::new();
139 }
140
141 if crate::pinning::entity_is_pinned(score_director, self.descriptor_index, entity_index) {
142 let mut child = ExhaustiveSearchNode::pinned_child(
143 parent_index,
144 new_depth,
145 score_director.calculate_score(),
146 );
147 if let Some(ref bounder) = self.bounder {
148 if let Some(bound) = bounder.calculate_optimistic_bound(score_director) {
149 child.set_optimistic_bound(bound);
150 }
151 }
152 return vec![child];
153 }
154
155 let mut children = Vec::with_capacity(self.values.len());
156
157 for (value_index, value) in self.values.iter().enumerate() {
158 score_director.before_variable_changed(self.descriptor_index, entity_index);
160
161 (self.setter)(
162 score_director.working_solution_mut(),
163 entity_index,
164 Some(value.clone()),
165 );
166
167 score_director.after_variable_changed(self.descriptor_index, entity_index);
168
169 let score = score_director.calculate_score();
171
172 let mut child = ExhaustiveSearchNode::child(
174 parent_index,
175 new_depth,
176 score,
177 self.descriptor_index,
178 self.variable_index,
179 entity_index,
180 value_index,
181 );
182
183 if let Some(ref bounder) = self.bounder {
185 if let Some(bound) = bounder.calculate_optimistic_bound(score_director) {
186 child.set_optimistic_bound(bound);
187 }
188 }
189
190 children.push(child);
191
192 score_director.before_variable_changed(self.descriptor_index, entity_index);
194
195 (self.setter)(score_director.working_solution_mut(), entity_index, None);
196
197 score_director.after_variable_changed(self.descriptor_index, entity_index);
198 }
199
200 children
201 }
202
203 fn reset_assignments(&self, score_director: &mut D) {
204 for entity_index in 0..self.total_entities(score_director) {
205 if crate::pinning::entity_is_pinned(score_director, self.descriptor_index, entity_index)
206 {
207 continue;
208 }
209 score_director.before_variable_changed(self.descriptor_index, entity_index);
210 (self.setter)(score_director.working_solution_mut(), entity_index, None);
211 score_director.after_variable_changed(self.descriptor_index, entity_index);
212 }
213 }
214
215 fn apply_assignment(&self, node: &ExhaustiveSearchNode<S>, score_director: &mut D) {
216 let Some(descriptor_index) = node.descriptor_index() else {
217 return;
218 };
219 let Some(variable_index) = node.variable_index() else {
220 return;
221 };
222 let Some(entity_index) = node.entity_index() else {
223 return;
224 };
225 let Some(candidate_value_index) = node.candidate_value_index() else {
226 return;
227 };
228
229 if crate::pinning::entity_is_pinned(score_director, self.descriptor_index, entity_index) {
230 return;
231 }
232
233 assert_eq!(descriptor_index, self.descriptor_index);
234 assert_eq!(variable_index, self.variable_index);
235
236 let value = self
237 .values
238 .get(candidate_value_index)
239 .unwrap_or_else(|| {
240 panic!("candidate value index {candidate_value_index} is out of range")
241 })
242 .clone();
243
244 score_director.before_variable_changed(self.descriptor_index, entity_index);
245 (self.setter)(
246 score_director.working_solution_mut(),
247 entity_index,
248 Some(value),
249 );
250 score_director.after_variable_changed(self.descriptor_index, entity_index);
251 }
252
253 fn total_entities(&self, score_director: &D) -> usize {
254 score_director
255 .entity_count(self.descriptor_index)
256 .unwrap_or(0)
257 }
258}
259
260impl<S: PlanningSolution, D: Director<S>> ScoreBounder<S, D> for () {
262 fn calculate_optimistic_bound(&self, _score_director: &D) -> Option<S::Score> {
263 None
264 }
265}
266
267#[cfg(test)]
268#[path = "decider_tests.rs"]
269mod tests;