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.
38pub struct SimpleDecider<S: PlanningSolution, V: Clone + Send + Sync + 'static> {
39    /// Descriptor index of the entity collection.
40    descriptor_index: usize,
41    /// Variable name to assign.
42    variable_name: String,
43    /// Possible values to try.
44    values: Vec<V>,
45    /// Score bounder for optimistic bounds.
46    bounder: Option<Box<dyn ScoreBounder<S>>>,
47    /// Typed setter for zero-erasure variable assignment.
48    setter: fn(&mut S, usize, Option<V>),
49}
50
51impl<S: PlanningSolution, V: Clone + Send + Sync + 'static> SimpleDecider<S, V> {
52    /// Creates a new simple decider with typed setter.
53    ///
54    /// # Arguments
55    /// * `descriptor_index` - Index of the entity descriptor
56    /// * `variable_name` - Name of the variable being assigned
57    /// * `values` - Possible values to try
58    /// * `setter` - Typed setter function `fn(&mut S, entity_index, value)`
59    pub fn new(
60        descriptor_index: usize,
61        variable_name: impl Into<String>,
62        values: Vec<V>,
63        setter: fn(&mut S, usize, Option<V>),
64    ) -> Self {
65        Self {
66            descriptor_index,
67            variable_name: variable_name.into(),
68            values,
69            bounder: None,
70            setter,
71        }
72    }
73
74    /// Sets the bounder for optimistic bound calculation.
75    pub fn with_bounder(mut self, bounder: Box<dyn ScoreBounder<S>>) -> Self {
76        self.bounder = Some(bounder);
77        self
78    }
79}
80
81impl<S: PlanningSolution, V: Clone + Send + Sync + Debug + 'static> Debug for SimpleDecider<S, V> {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("SimpleDecider")
84            .field("descriptor_index", &self.descriptor_index)
85            .field("variable_name", &self.variable_name)
86            .field("value_count", &self.values.len())
87            .finish()
88    }
89}
90
91impl<S: PlanningSolution, V: Clone + Send + Sync + Debug + 'static> ExhaustiveSearchDecider<S>
92    for SimpleDecider<S, V>
93{
94    fn expand(
95        &self,
96        parent_index: usize,
97        parent: &ExhaustiveSearchNode<S>,
98        score_director: &mut dyn ScoreDirector<S>,
99    ) -> Vec<ExhaustiveSearchNode<S>> {
100        let entity_index = parent.depth();
101        let new_depth = parent.depth() + 1;
102
103        // Check if we've assigned all entities
104        let total = self.total_entities(score_director);
105        if entity_index >= total {
106            return Vec::new();
107        }
108
109        let mut children = Vec::with_capacity(self.values.len());
110
111        for (value_index, value) in self.values.iter().enumerate() {
112            // Apply assignment using typed setter
113            score_director.before_variable_changed(
114                self.descriptor_index,
115                entity_index,
116                &self.variable_name,
117            );
118
119            (self.setter)(
120                score_director.working_solution_mut(),
121                entity_index,
122                Some(value.clone()),
123            );
124
125            score_director.after_variable_changed(
126                self.descriptor_index,
127                entity_index,
128                &self.variable_name,
129            );
130
131            // Calculate score for this assignment
132            let score = score_director.calculate_score();
133
134            // Create child node
135            let mut child = ExhaustiveSearchNode::child(
136                parent_index,
137                new_depth,
138                score.clone(),
139                entity_index,
140                value_index,
141            );
142
143            // Calculate optimistic bound if bounder is available
144            if let Some(ref bounder) = self.bounder {
145                if let Some(bound) = bounder.calculate_optimistic_bound(score_director) {
146                    child.set_optimistic_bound(bound);
147                }
148            }
149
150            children.push(child);
151
152            // Undo the assignment for the next iteration
153            score_director.before_variable_changed(
154                self.descriptor_index,
155                entity_index,
156                &self.variable_name,
157            );
158
159            (self.setter)(score_director.working_solution_mut(), entity_index, None);
160
161            score_director.after_variable_changed(
162                self.descriptor_index,
163                entity_index,
164                &self.variable_name,
165            );
166        }
167
168        children
169    }
170
171    fn total_entities(&self, score_director: &dyn ScoreDirector<S>) -> usize {
172        score_director
173            .entity_count(self.descriptor_index)
174            .unwrap_or(0)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use solverforge_core::score::SimpleScore;
182
183    #[derive(Clone, Debug)]
184    struct TestSolution {
185        score: Option<SimpleScore>,
186    }
187
188    impl PlanningSolution for TestSolution {
189        type Score = SimpleScore;
190
191        fn score(&self) -> Option<Self::Score> {
192            self.score
193        }
194
195        fn set_score(&mut self, score: Option<Self::Score>) {
196            self.score = score;
197        }
198    }
199
200    // Dummy setter for tests
201    fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {
202        // No-op for this minimal test
203    }
204
205    #[test]
206    fn test_simple_decider_creation() {
207        let decider: SimpleDecider<TestSolution, i32> =
208            SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
209
210        let debug = format!("{:?}", decider);
211        assert!(debug.contains("SimpleDecider"));
212        assert!(debug.contains("value_count: 4"));
213    }
214}