Skip to main content

solverforge_solver/heuristic/move/
change.rs

1/* ChangeMove - assigns a value to a planning variable.
2
3This is the most fundamental move type. It takes a value and assigns
4it to a planning variable on an entity.
5
6# Zero-Erasure Design
7
8This move stores concrete function pointers that operate directly on
9the solution. No `Arc<dyn>`, no `Box<dyn Any>`, no `downcast_ref`.
10*/
11
12use std::borrow::Cow;
13use std::fmt::Debug;
14
15use smallvec::smallvec;
16use solverforge_core::domain::PlanningSolution;
17use solverforge_scoring::Director;
18
19use super::metadata::{encode_option_debug, encode_usize, hash_str, MoveTabuScope};
20use super::{Move, MoveTabuSignature};
21
22/// A move that assigns a value to an entity's variable.
23///
24/// Stores concrete function pointers for zero-erasure execution.
25/// No trait objects, no boxing - all operations are fully monomorphized at compile time.
26///
27/// # Type Parameters
28/// * `S` - The planning solution type
29/// * `V` - The variable value type
30pub struct ChangeMove<S, V> {
31    entity_index: usize,
32    to_value: Option<V>,
33    getter: fn(&S, usize, usize) -> Option<V>,
34    setter: fn(&mut S, usize, usize, Option<V>),
35    variable_index: usize,
36    variable_name: &'static str,
37    descriptor_index: usize,
38}
39
40impl<S, V: Clone> Clone for ChangeMove<S, V> {
41    fn clone(&self) -> Self {
42        Self {
43            entity_index: self.entity_index,
44            to_value: self.to_value.clone(),
45            getter: self.getter,
46            setter: self.setter,
47            variable_index: self.variable_index,
48            variable_name: self.variable_name,
49            descriptor_index: self.descriptor_index,
50        }
51    }
52}
53
54impl<S, V: Copy> Copy for ChangeMove<S, V> {}
55
56impl<S, V: Debug> Debug for ChangeMove<S, V> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.debug_struct("ChangeMove")
59            .field("entity_index", &self.entity_index)
60            .field("descriptor_index", &self.descriptor_index)
61            .field("variable_index", &self.variable_index)
62            .field("variable_name", &self.variable_name)
63            .field("to_value", &self.to_value)
64            .finish()
65    }
66}
67
68impl<S, V> ChangeMove<S, V> {
69    /// Creates a new change move with concrete function pointers.
70    ///
71    /// # Arguments
72    /// * `entity_index` - Index of the entity in its collection
73    /// * `to_value` - The value to assign (None to unassign)
74    /// * `getter` - Function pointer to get current value from solution
75    /// * `setter` - Function pointer to set value on solution
76    /// * `variable_name` - Name of the variable (for debugging)
77    /// * `descriptor_index` - Index of the entity descriptor
78    pub fn new(
79        entity_index: usize,
80        to_value: Option<V>,
81        getter: fn(&S, usize, usize) -> Option<V>,
82        setter: fn(&mut S, usize, usize, Option<V>),
83        variable_index: usize,
84        variable_name: &'static str,
85        descriptor_index: usize,
86    ) -> Self {
87        Self {
88            entity_index,
89            to_value,
90            getter,
91            setter,
92            variable_index,
93            variable_name,
94            descriptor_index,
95        }
96    }
97
98    pub fn entity_index(&self) -> usize {
99        self.entity_index
100    }
101
102    pub fn to_value(&self) -> Option<&V> {
103        self.to_value.as_ref()
104    }
105
106    pub fn getter(&self) -> fn(&S, usize, usize) -> Option<V> {
107        self.getter
108    }
109
110    pub fn setter(&self) -> fn(&mut S, usize, usize, Option<V>) {
111        self.setter
112    }
113
114    pub fn variable_index(&self) -> usize {
115        self.variable_index
116    }
117}
118
119impl<S, V> Move<S> for ChangeMove<S, V>
120where
121    S: PlanningSolution,
122    V: Clone + PartialEq + Send + Sync + Debug + 'static,
123{
124    type Undo = Option<V>;
125
126    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
127        // Get current value using concrete getter - no boxing, no downcast
128        let current = (self.getter)(
129            score_director.working_solution(),
130            self.entity_index,
131            self.variable_index,
132        );
133
134        // Compare directly - fully monomorphized comparison
135        match (&current, &self.to_value) {
136            (None, None) => false,                      // Both unassigned
137            (Some(cur), Some(target)) => cur != target, // Different values
138            _ => true,                                  // One assigned, one not
139        }
140    }
141
142    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
143        // Capture old value using concrete getter - zero erasure
144        let old_value = (self.getter)(
145            score_director.working_solution(),
146            self.entity_index,
147            self.variable_index,
148        );
149
150        // Notify before change
151        score_director.before_variable_changed(self.descriptor_index, self.entity_index);
152
153        // Set value using concrete setter - no boxing
154        (self.setter)(
155            score_director.working_solution_mut(),
156            self.entity_index,
157            self.variable_index,
158            self.to_value.clone(),
159        );
160
161        // Notify after change
162        score_director.after_variable_changed(self.descriptor_index, self.entity_index);
163
164        old_value
165    }
166
167    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
168        score_director.before_variable_changed(self.descriptor_index, self.entity_index);
169        (self.setter)(
170            score_director.working_solution_mut(),
171            self.entity_index,
172            self.variable_index,
173            undo,
174        );
175        score_director.after_variable_changed(self.descriptor_index, self.entity_index);
176    }
177
178    fn descriptor_index(&self) -> usize {
179        self.descriptor_index
180    }
181
182    fn entity_indices(&self) -> &[usize] {
183        std::slice::from_ref(&self.entity_index)
184    }
185
186    fn variable_name(&self) -> &str {
187        self.variable_name
188    }
189
190    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> Cow<'_, MoveTabuSignature> {
191        let current = (self.getter)(
192            score_director.working_solution(),
193            self.entity_index,
194            self.variable_index,
195        );
196        let from_id = encode_option_debug(current.as_ref());
197        let to_id = encode_option_debug(self.to_value.as_ref());
198        let entity_id = encode_usize(self.entity_index);
199        let variable_id = hash_str(self.variable_name);
200        let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
201
202        Cow::Owned(
203            MoveTabuSignature::new(
204                scope,
205                smallvec![
206                    encode_usize(self.descriptor_index),
207                    variable_id,
208                    entity_id,
209                    from_id,
210                    to_id
211                ],
212                smallvec![
213                    encode_usize(self.descriptor_index),
214                    variable_id,
215                    entity_id,
216                    to_id,
217                    from_id
218                ],
219            )
220            .with_entity_tokens([scope.entity_token(entity_id)])
221            .with_destination_value_tokens([scope.value_token(to_id)]),
222        )
223    }
224}