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