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