Skip to main content

solverforge_solver/phase/exhaustive/
decider.rs

1//! Exhaustive search decider for node expansion.
2//!
3//! The decider is responsible for expanding nodes and generating
4//! child nodes in the search tree.
5
6use std::fmt::Debug;
7
8use solverforge_core::domain::PlanningSolution;
9use solverforge_scoring::ScoreDirector;
10
11use super::bounder::ScoreBounder;
12use super::node::ExhaustiveSearchNode;
13
14/// Decides how to expand nodes in the exhaustive search.
15///
16/// The decider is responsible for:
17/// - Finding the next entity to assign
18/// - Generating all possible value assignments
19/// - Creating child nodes for each assignment
20pub trait ExhaustiveSearchDecider<S: PlanningSolution, D: ScoreDirector<S>>: Send + Debug {
21    /// Expands a node by generating all child nodes.
22    ///
23    /// Returns a vector of child nodes, one for each possible assignment.
24    fn expand(
25        &self,
26        parent_index: usize,
27        parent: &ExhaustiveSearchNode<S>,
28        score_director: &mut D,
29    ) -> Vec<ExhaustiveSearchNode<S>>;
30
31    /// Returns the total number of entities to assign.
32    fn total_entities(&self, score_director: &D) -> usize;
33}
34
35/// A simple value-based decider that works with any value type.
36///
37/// Uses typed setter for zero-erasure variable assignment.
38///
39/// # Type Parameters
40/// * `S` - The planning solution type
41/// * `V` - The value type to assign
42/// * `B` - The bounder type (use `Option<B>` for optional bounding)
43pub struct SimpleDecider<S: PlanningSolution, V: Clone + Send + Sync + 'static, B = ()> {
44    /// Descriptor index of the entity collection.
45    descriptor_index: usize,
46    /// Variable name to assign.
47    variable_name: String,
48    /// Possible values to try.
49    values: Vec<V>,
50    /// Score bounder for optimistic bounds (None = no bounding).
51    bounder: Option<B>,
52    /// Typed setter for zero-erasure variable assignment.
53    setter: fn(&mut S, usize, Option<V>),
54}
55
56impl<S: PlanningSolution, V: Clone + Send + Sync + 'static> SimpleDecider<S, V, ()> {
57    /// Creates a new simple decider with typed setter and no bounder.
58    ///
59    /// # Arguments
60    /// * `descriptor_index` - Index of the entity descriptor
61    /// * `variable_name` - Name of the variable being assigned
62    /// * `values` - Possible values to try
63    /// * `setter` - Typed setter function `fn(&mut S, entity_index, value)`
64    pub fn new(
65        descriptor_index: usize,
66        variable_name: impl Into<String>,
67        values: Vec<V>,
68        setter: fn(&mut S, usize, Option<V>),
69    ) -> Self {
70        Self {
71            descriptor_index,
72            variable_name: variable_name.into(),
73            values,
74            bounder: None,
75            setter,
76        }
77    }
78}
79
80impl<S: PlanningSolution, V: Clone + Send + Sync + 'static, B> SimpleDecider<S, V, B> {
81    /// Sets the bounder for optimistic bound calculation.
82    pub fn with_bounder<B2>(self, bounder: B2) -> SimpleDecider<S, V, B2> {
83        SimpleDecider {
84            descriptor_index: self.descriptor_index,
85            variable_name: self.variable_name,
86            values: self.values,
87            bounder: Some(bounder),
88            setter: self.setter,
89        }
90    }
91}
92
93impl<S: PlanningSolution, V: Clone + Send + Sync + Debug + 'static, B: Debug> Debug
94    for SimpleDecider<S, V, B>
95{
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("SimpleDecider")
98            .field("descriptor_index", &self.descriptor_index)
99            .field("variable_name", &self.variable_name)
100            .field("value_count", &self.values.len())
101            .finish()
102    }
103}
104
105impl<S, V, B, D> ExhaustiveSearchDecider<S, D> for SimpleDecider<S, V, B>
106where
107    S: PlanningSolution,
108    V: Clone + Send + Sync + Debug + 'static,
109    B: ScoreBounder<S, D>,
110    D: ScoreDirector<S>,
111{
112    fn expand(
113        &self,
114        parent_index: usize,
115        parent: &ExhaustiveSearchNode<S>,
116        score_director: &mut D,
117    ) -> Vec<ExhaustiveSearchNode<S>> {
118        let entity_index = parent.depth();
119        let new_depth = parent.depth() + 1;
120
121        // Check if we've assigned all entities
122        let total = self.total_entities(score_director);
123        if entity_index >= total {
124            return Vec::new();
125        }
126
127        let mut children = Vec::with_capacity(self.values.len());
128
129        for (value_index, value) in self.values.iter().enumerate() {
130            // Apply assignment using typed setter
131            score_director.before_variable_changed(
132                self.descriptor_index,
133                entity_index,
134                &self.variable_name,
135            );
136
137            (self.setter)(
138                score_director.working_solution_mut(),
139                entity_index,
140                Some(value.clone()),
141            );
142
143            score_director.after_variable_changed(
144                self.descriptor_index,
145                entity_index,
146                &self.variable_name,
147            );
148
149            // Calculate score for this assignment
150            let score = score_director.calculate_score();
151
152            // Create child node
153            let mut child = ExhaustiveSearchNode::child(
154                parent_index,
155                new_depth,
156                score,
157                entity_index,
158                value_index,
159            );
160
161            // Calculate optimistic bound if bounder is available
162            if let Some(ref bounder) = self.bounder {
163                if let Some(bound) = bounder.calculate_optimistic_bound(score_director) {
164                    child.set_optimistic_bound(bound);
165                }
166            }
167
168            children.push(child);
169
170            // Undo the assignment for the next iteration
171            score_director.before_variable_changed(
172                self.descriptor_index,
173                entity_index,
174                &self.variable_name,
175            );
176
177            (self.setter)(score_director.working_solution_mut(), entity_index, None);
178
179            score_director.after_variable_changed(
180                self.descriptor_index,
181                entity_index,
182                &self.variable_name,
183            );
184        }
185
186        children
187    }
188
189    fn total_entities(&self, score_director: &D) -> usize {
190        score_director
191            .entity_count(self.descriptor_index)
192            .unwrap_or(0)
193    }
194}
195
196// Implement ScoreBounder for () to allow SimpleDecider<S, V> (no bounder)
197impl<S: PlanningSolution, D: ScoreDirector<S>> ScoreBounder<S, D> for () {
198    fn calculate_optimistic_bound(&self, _score_director: &D) -> Option<S::Score> {
199        None
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use solverforge_core::score::SimpleScore;
207
208    #[derive(Clone, Debug)]
209    struct TestSolution {
210        score: Option<SimpleScore>,
211    }
212
213    impl PlanningSolution for TestSolution {
214        type Score = SimpleScore;
215
216        fn score(&self) -> Option<Self::Score> {
217            self.score
218        }
219
220        fn set_score(&mut self, score: Option<Self::Score>) {
221            self.score = score;
222        }
223    }
224
225    // Dummy setter for tests
226    fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {
227        // No-op for this minimal test
228    }
229
230    #[test]
231    fn test_simple_decider_creation() {
232        let decider: SimpleDecider<TestSolution, i32> =
233            SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
234
235        let debug = format!("{:?}", decider);
236        assert!(debug.contains("SimpleDecider"));
237        assert!(debug.contains("value_count: 4"));
238    }
239}