Skip to main content

solverforge_solver/heuristic/move/
ruin.rs

1/* RuinMove - unassigns a subset of entities for Large Neighborhood Search.
2
3This move "ruins" (unassigns) selected entities, allowing a construction
4heuristic to reassign them. This is the fundamental building block for
5Large Neighborhood Search (LNS) algorithms.
6
7# Zero-Erasure Design
8
9Uses concrete function pointers for variable access. No `dyn Any`, no downcasting.
10*/
11
12use std::fmt::Debug;
13
14use smallvec::SmallVec;
15use solverforge_core::domain::PlanningSolution;
16use solverforge_scoring::Director;
17
18use super::metadata::{
19    encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedEntityTabuToken,
20};
21use super::{Move, MoveTabuSignature};
22
23/// A move that unassigns multiple entities for Large Neighborhood Search.
24///
25/// This move sets the planning variable to `None` for a set of entities,
26/// creating "gaps" that a construction heuristic can fill. Combined with
27/// construction, this enables exploring distant regions of the search space.
28///
29/// # Type Parameters
30/// * `S` - The planning solution type
31/// * `V` - The variable value type
32///
33/// # Example
34///
35/// ```
36/// use solverforge_solver::heuristic::r#move::RuinMove;
37/// use solverforge_core::domain::PlanningSolution;
38/// use solverforge_core::score::SoftScore;
39///
40/// #[derive(Clone, Debug)]
41/// struct Task { assigned_to: Option<i32>, score: Option<SoftScore> }
42/// #[derive(Clone, Debug)]
43/// struct Schedule { tasks: Vec<Task>, score: Option<SoftScore> }
44///
45/// impl PlanningSolution for Schedule {
46///     type Score = SoftScore;
47///     fn score(&self) -> Option<Self::Score> { self.score }
48///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
49/// }
50///
51/// fn get_task(s: &Schedule, idx: usize, _var: usize) -> Option<i32> {
52///     s.tasks.get(idx).and_then(|t| t.assigned_to)
53/// }
54/// fn set_task(s: &mut Schedule, idx: usize, _var: usize, v: Option<i32>) {
55///     if let Some(t) = s.tasks.get_mut(idx) { t.assigned_to = v; }
56/// }
57///
58/// // Ruin entities 0, 2, and 4
59/// let m = RuinMove::<Schedule, i32>::new(
60///     &[0, 2, 4],
61///     get_task, set_task,
62///     0, "assigned_to", 0,
63/// );
64/// ```
65pub struct RuinMove<S, V> {
66    // Indices of entities to unassign
67    entity_indices: SmallVec<[usize; 8]>,
68    getter: fn(&S, usize, usize) -> Option<V>,
69    // Set value for an entity
70    setter: fn(&mut S, usize, usize, Option<V>),
71    variable_index: usize,
72    variable_name: &'static str,
73    descriptor_index: usize,
74}
75
76impl<S, V> Clone for RuinMove<S, V> {
77    fn clone(&self) -> Self {
78        Self {
79            entity_indices: self.entity_indices.clone(),
80            getter: self.getter,
81            setter: self.setter,
82            variable_index: self.variable_index,
83            variable_name: self.variable_name,
84            descriptor_index: self.descriptor_index,
85        }
86    }
87}
88
89impl<S, V: Debug> Debug for RuinMove<S, V> {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("RuinMove")
92            .field("entities", &self.entity_indices.as_slice())
93            .field("variable_index", &self.variable_index)
94            .field("variable_name", &self.variable_name)
95            .finish()
96    }
97}
98
99impl<S, V> RuinMove<S, V> {
100    /// Creates a new ruin move with concrete function pointers.
101    ///
102    /// # Arguments
103    /// * `entity_indices` - Indices of entities to unassign
104    /// * `getter` - Function to get current value
105    /// * `setter` - Function to set value
106    /// * `variable_name` - Name of the planning variable
107    /// * `descriptor_index` - Entity descriptor index
108    pub fn new(
109        entity_indices: &[usize],
110        getter: fn(&S, usize, usize) -> Option<V>,
111        setter: fn(&mut S, usize, usize, Option<V>),
112        variable_index: usize,
113        variable_name: &'static str,
114        descriptor_index: usize,
115    ) -> Self {
116        Self::from_indices(
117            SmallVec::from_slice(entity_indices),
118            getter,
119            setter,
120            variable_index,
121            variable_name,
122            descriptor_index,
123        )
124    }
125
126    pub(crate) fn from_indices(
127        entity_indices: SmallVec<[usize; 8]>,
128        getter: fn(&S, usize, usize) -> Option<V>,
129        setter: fn(&mut S, usize, usize, Option<V>),
130        variable_index: usize,
131        variable_name: &'static str,
132        descriptor_index: usize,
133    ) -> Self {
134        Self {
135            entity_indices,
136            getter,
137            setter,
138            variable_index,
139            variable_name,
140            descriptor_index,
141        }
142    }
143
144    pub fn entity_indices_slice(&self) -> &[usize] {
145        &self.entity_indices
146    }
147
148    pub fn ruin_count(&self) -> usize {
149        self.entity_indices.len()
150    }
151}
152
153impl<S, V> Move<S> for RuinMove<S, V>
154where
155    S: PlanningSolution,
156    V: Clone + Send + Sync + Debug + 'static,
157{
158    type Undo = SmallVec<[(usize, Option<V>); 8]>;
159
160    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
161        // At least one entity must be currently assigned
162        let solution = score_director.working_solution();
163        self.entity_indices
164            .iter()
165            .any(|&idx| (self.getter)(solution, idx, self.variable_index).is_some())
166    }
167
168    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
169        let getter = self.getter;
170        let setter = self.setter;
171        let descriptor = self.descriptor_index;
172        let variable_index = self.variable_index;
173
174        // Collect old values for undo
175        let old_values: SmallVec<[(usize, Option<V>); 8]> = self
176            .entity_indices
177            .iter()
178            .map(|&idx| {
179                let old = getter(score_director.working_solution(), idx, variable_index);
180                (idx, old)
181            })
182            .collect();
183
184        // Unassign all entities
185        for &idx in &self.entity_indices {
186            score_director.before_variable_changed(descriptor, idx);
187            setter(
188                score_director.working_solution_mut(),
189                idx,
190                variable_index,
191                None,
192            );
193            score_director.after_variable_changed(descriptor, idx);
194        }
195
196        old_values
197    }
198
199    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
200        for (idx, _) in &undo {
201            score_director.before_variable_changed(self.descriptor_index, *idx);
202        }
203        for (idx, old_value) in undo {
204            (self.setter)(
205                score_director.working_solution_mut(),
206                idx,
207                self.variable_index,
208                old_value,
209            );
210        }
211        for &idx in &self.entity_indices {
212            score_director.after_variable_changed(self.descriptor_index, idx);
213        }
214    }
215
216    fn descriptor_index(&self) -> usize {
217        self.descriptor_index
218    }
219
220    fn entity_indices(&self) -> &[usize] {
221        &self.entity_indices
222    }
223
224    fn variable_name(&self) -> &str {
225        self.variable_name
226    }
227
228    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
229        let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
230        let entity_ids: SmallVec<[u64; 2]> = self
231            .entity_indices
232            .iter()
233            .map(|&idx| encode_usize(idx))
234            .collect();
235        let entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> = entity_ids
236            .iter()
237            .copied()
238            .map(|entity_id| scope.entity_token(entity_id))
239            .collect();
240        let mut value_ids: SmallVec<[u64; 2]> = SmallVec::new();
241        for &idx in &self.entity_indices {
242            let value = (self.getter)(score_director.working_solution(), idx, self.variable_index);
243            value_ids.push(encode_option_debug(value.as_ref()));
244        }
245        let variable_id = hash_str(self.variable_name);
246        let mut move_id = SmallVec::<[u64; 8]>::from_slice(&[
247            encode_usize(self.descriptor_index),
248            variable_id,
249            encode_usize(self.entity_indices.len()),
250        ]);
251        move_id.extend(entity_ids.iter().copied());
252        move_id.extend(value_ids.iter().copied());
253
254        MoveTabuSignature::new(scope, move_id.clone(), move_id).with_entity_tokens(entity_tokens)
255    }
256}