Skip to main content

solverforge_solver/phase/exhaustive/
decider.rs

1/* Exhaustive search decider for node expansion.
2
3The decider is responsible for expanding nodes and generating
4child nodes in the search tree.
5*/
6
7use std::fmt::Debug;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12use super::bounder::ScoreBounder;
13use super::node::ExhaustiveSearchNode;
14
15/// Decides how to expand nodes in the exhaustive search.
16///
17/// The decider is responsible for:
18/// - Finding the next entity to assign
19/// - Generating all possible value assignments
20/// - Creating child nodes for each assignment
21pub trait ExhaustiveSearchDecider<S: PlanningSolution, D: Director<S>>: Send + Debug {
22    /* Expands a node by generating all child nodes.
23
24    Returns a vector of child nodes, one for each possible assignment.
25    */
26    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
40/// A simple value-based decider that works with any value type.
41///
42/// Uses concrete setter for zero-erasure variable assignment.
43///
44/// # Type Parameters
45/// * `S` - The planning solution type
46/// * `V` - The value type to assign
47/// * `B` - The bounder type (use `Option<B>` for optional bounding)
48pub struct SimpleDecider<S: PlanningSolution, V: Clone + Send + Sync + 'static, B = ()> {
49    // Descriptor index of the entity collection.
50    descriptor_index: usize,
51    // Variable name to assign.
52    variable_name: String,
53    // Variable index within the descriptor.
54    variable_index: usize,
55    // Possible values to try.
56    values: Vec<V>,
57    // Score bounder for optimistic bounds (None = no bounding).
58    bounder: Option<B>,
59    // Concrete setter for zero-erasure variable assignment.
60    setter: fn(&mut S, usize, Option<V>),
61}
62
63impl<S: PlanningSolution, V: Clone + Send + Sync + 'static> SimpleDecider<S, V, ()> {
64    /// Creates a new simple decider with concrete setter and no bounder.
65    ///
66    /// # Arguments
67    /// * `descriptor_index` - Index of the entity descriptor
68    /// * `variable_name` - Name of the variable being assigned
69    /// * `values` - Possible values to try
70    /// * `setter` - Concrete setter function `fn(&mut S, entity_index, value)`
71    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        // Check if we've assigned all entities
136        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            // Apply assignment using concrete setter
159            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            // Calculate score for this assignment
170            let score = score_director.calculate_score();
171
172            // Create child node
173            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            // Calculate optimistic bound if bounder is available
184            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            // Undo the assignment for the next iteration
193            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
260// Implement ScoreBounder for () to allow SimpleDecider<S, V> (no bounder)
261impl<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;