Skip to main content

solverforge_solver/heuristic/move/
pillar_swap.rs

1/* PillarSwapMove - exchanges values between two pillars.
2
3A pillar is a group of entities that share the same variable value.
4This move swaps the values between two pillars atomically.
5
6# Zero-Erasure Design
7
8PillarSwapMove uses typed function pointers instead of `dyn Any` for complete
9compile-time type safety. No runtime type checks or downcasting.
10*/
11
12use std::fmt::Debug;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::Move;
18
19/// A move that swaps values between two pillars.
20///
21/// Stores pillar indices and typed function pointers for zero-erasure access.
22/// Undo is handled by `RecordingDirector`, not by this move.
23///
24/// # Type Parameters
25/// * `S` - The planning solution type
26/// * `V` - The variable value type
27pub struct PillarSwapMove<S, V> {
28    left_indices: Vec<usize>,
29    right_indices: Vec<usize>,
30    descriptor_index: usize,
31    variable_name: &'static str,
32    // Typed getter function pointer - zero erasure.
33    getter: fn(&S, usize) -> Option<V>,
34    // Typed setter function pointer - zero erasure.
35    setter: fn(&mut S, usize, Option<V>),
36}
37
38impl<S, V: Clone> Clone for PillarSwapMove<S, V> {
39    fn clone(&self) -> Self {
40        Self {
41            left_indices: self.left_indices.clone(),
42            right_indices: self.right_indices.clone(),
43            descriptor_index: self.descriptor_index,
44            variable_name: self.variable_name,
45            getter: self.getter,
46            setter: self.setter,
47        }
48    }
49}
50
51impl<S, V: Debug> Debug for PillarSwapMove<S, V> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("PillarSwapMove")
54            .field("left_indices", &self.left_indices)
55            .field("right_indices", &self.right_indices)
56            .field("descriptor_index", &self.descriptor_index)
57            .field("variable_name", &self.variable_name)
58            .finish()
59    }
60}
61
62impl<S, V> PillarSwapMove<S, V> {
63    /// Creates a new pillar swap move with typed function pointers.
64    ///
65    /// # Arguments
66    /// * `left_indices` - Indices of entities in the left pillar
67    /// * `right_indices` - Indices of entities in the right pillar
68    /// * `getter` - Typed getter function pointer
69    /// * `setter` - Typed setter function pointer
70    /// * `variable_name` - Name of the variable being swapped
71    /// * `descriptor_index` - Index in the entity descriptor
72    pub fn new(
73        left_indices: Vec<usize>,
74        right_indices: Vec<usize>,
75        getter: fn(&S, usize) -> Option<V>,
76        setter: fn(&mut S, usize, Option<V>),
77        variable_name: &'static str,
78        descriptor_index: usize,
79    ) -> Self {
80        Self {
81            left_indices,
82            right_indices,
83            descriptor_index,
84            variable_name,
85            getter,
86            setter,
87        }
88    }
89
90    pub fn left_indices(&self) -> &[usize] {
91        &self.left_indices
92    }
93
94    pub fn right_indices(&self) -> &[usize] {
95        &self.right_indices
96    }
97}
98
99impl<S, V> Move<S> for PillarSwapMove<S, V>
100where
101    S: PlanningSolution,
102    V: Clone + PartialEq + Send + Sync + Debug + 'static,
103{
104    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
105        if self.left_indices.is_empty() || self.right_indices.is_empty() {
106            return false;
107        }
108
109        let count = score_director.entity_count(self.descriptor_index);
110        let max = count.unwrap_or(0);
111
112        // Check all indices valid
113        for &idx in self.left_indices.iter().chain(&self.right_indices) {
114            if idx >= max {
115                return false;
116            }
117        }
118
119        // Get representative values using typed getter - zero erasure
120        let left_val = self
121            .left_indices
122            .first()
123            .map(|&idx| (self.getter)(score_director.working_solution(), idx));
124        let right_val = self
125            .right_indices
126            .first()
127            .map(|&idx| (self.getter)(score_director.working_solution(), idx));
128
129        left_val != right_val
130    }
131
132    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
133        // Capture all old values using typed getter - zero erasure
134        let left_old: Vec<(usize, Option<V>)> = self
135            .left_indices
136            .iter()
137            .map(|&idx| (idx, (self.getter)(score_director.working_solution(), idx)))
138            .collect();
139        let right_old: Vec<(usize, Option<V>)> = self
140            .right_indices
141            .iter()
142            .map(|&idx| (idx, (self.getter)(score_director.working_solution(), idx)))
143            .collect();
144
145        // Get representative values for the swap
146        let left_value = left_old.first().and_then(|(_, v)| v.clone());
147        let right_value = right_old.first().and_then(|(_, v)| v.clone());
148
149        // Notify before changes for all entities
150        for &idx in self.left_indices.iter().chain(&self.right_indices) {
151            score_director.before_variable_changed(self.descriptor_index, idx);
152        }
153
154        // Swap: left gets right's value using typed setter - zero erasure
155        for &idx in &self.left_indices {
156            (self.setter)(
157                score_director.working_solution_mut(),
158                idx,
159                right_value.clone(),
160            );
161        }
162        // Right gets left's value
163        for &idx in &self.right_indices {
164            (self.setter)(
165                score_director.working_solution_mut(),
166                idx,
167                left_value.clone(),
168            );
169        }
170
171        // Notify after changes
172        for &idx in self.left_indices.iter().chain(&self.right_indices) {
173            score_director.after_variable_changed(self.descriptor_index, idx);
174        }
175
176        // Register typed undo closure - restore all original values
177        let setter = self.setter;
178        score_director.register_undo(Box::new(move |s: &mut S| {
179            for (idx, old_value) in left_old {
180                setter(s, idx, old_value);
181            }
182            for (idx, old_value) in right_old {
183                setter(s, idx, old_value);
184            }
185        }));
186    }
187
188    fn descriptor_index(&self) -> usize {
189        self.descriptor_index
190    }
191
192    fn entity_indices(&self) -> &[usize] {
193        // Return left indices as primary; caller can use left_indices/right_indices for full info
194        &self.left_indices
195    }
196
197    fn variable_name(&self) -> &str {
198        self.variable_name
199    }
200}