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>: 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 dyn ScoreDirector<S>,
29    ) -> Vec<ExhaustiveSearchNode<S>>;
30
31    /// Returns the total number of entities to assign.
32    fn total_entities(&self, score_director: &dyn ScoreDirector<S>) -> 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> ExhaustiveSearchDecider<S> for SimpleDecider<S, V, B>
106where
107    S: PlanningSolution,
108    V: Clone + Send + Sync + Debug + 'static,
109    B: ScoreBounder<S>,
110{
111    fn expand(
112        &self,
113        parent_index: usize,
114        parent: &ExhaustiveSearchNode<S>,
115        score_director: &mut dyn ScoreDirector<S>,
116    ) -> Vec<ExhaustiveSearchNode<S>> {
117        let entity_index = parent.depth();
118        let new_depth = parent.depth() + 1;
119
120        // Check if we've assigned all entities
121        let total = self.total_entities(score_director);
122        if entity_index >= total {
123            return Vec::new();
124        }
125
126        let mut children = Vec::with_capacity(self.values.len());
127
128        for (value_index, value) in self.values.iter().enumerate() {
129            // Apply assignment using typed setter
130            score_director.before_variable_changed(
131                self.descriptor_index,
132                entity_index,
133                &self.variable_name,
134            );
135
136            (self.setter)(
137                score_director.working_solution_mut(),
138                entity_index,
139                Some(value.clone()),
140            );
141
142            score_director.after_variable_changed(
143                self.descriptor_index,
144                entity_index,
145                &self.variable_name,
146            );
147
148            // Calculate score for this assignment
149            let score = score_director.calculate_score();
150
151            // Create child node
152            let mut child = ExhaustiveSearchNode::child(
153                parent_index,
154                new_depth,
155                score,
156                entity_index,
157                value_index,
158            );
159
160            // Calculate optimistic bound if bounder is available
161            if let Some(ref bounder) = self.bounder {
162                if let Some(bound) = bounder.calculate_optimistic_bound(score_director) {
163                    child.set_optimistic_bound(bound);
164                }
165            }
166
167            children.push(child);
168
169            // Undo the assignment for the next iteration
170            score_director.before_variable_changed(
171                self.descriptor_index,
172                entity_index,
173                &self.variable_name,
174            );
175
176            (self.setter)(score_director.working_solution_mut(), entity_index, None);
177
178            score_director.after_variable_changed(
179                self.descriptor_index,
180                entity_index,
181                &self.variable_name,
182            );
183        }
184
185        children
186    }
187
188    fn total_entities(&self, score_director: &dyn ScoreDirector<S>) -> usize {
189        score_director
190            .entity_count(self.descriptor_index)
191            .unwrap_or(0)
192    }
193}
194
195// Implement ScoreBounder for () to allow SimpleDecider<S, V> (no bounder)
196impl<S: PlanningSolution> ScoreBounder<S> for () {
197    fn calculate_optimistic_bound(
198        &self,
199        _score_director: &dyn ScoreDirector<S>,
200    ) -> Option<S::Score> {
201        None
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use solverforge_core::score::SimpleScore;
209
210    #[derive(Clone, Debug)]
211    struct TestSolution {
212        score: Option<SimpleScore>,
213    }
214
215    impl PlanningSolution for TestSolution {
216        type Score = SimpleScore;
217
218        fn score(&self) -> Option<Self::Score> {
219            self.score
220        }
221
222        fn set_score(&mut self, score: Option<Self::Score>) {
223            self.score = score;
224        }
225    }
226
227    // Dummy setter for tests
228    fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {
229        // No-op for this minimal test
230    }
231
232    #[test]
233    fn test_simple_decider_creation() {
234        let decider: SimpleDecider<TestSolution, i32> =
235            SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
236
237        let debug = format!("{:?}", decider);
238        assert!(debug.contains("SimpleDecider"));
239        assert!(debug.contains("value_count: 4"));
240    }
241}