Skip to main content

solverforge_solver/heuristic/move/
ruin.rs

1//! RuinMove - unassigns a subset of entities for Large Neighborhood Search.
2//!
3//! This move "ruins" (unassigns) selected entities, allowing a construction
4//! heuristic to reassign them. This is the fundamental building block for
5//! Large Neighborhood Search (LNS) algorithms.
6//!
7//! # Zero-Erasure Design
8//!
9//! Uses typed function pointers for variable access. No `dyn Any`, no downcasting.
10
11use std::fmt::Debug;
12
13use smallvec::SmallVec;
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::ScoreDirector;
16
17use super::Move;
18
19/// A move that unassigns multiple entities for Large Neighborhood Search.
20///
21/// This move sets the planning variable to `None` for a set of entities,
22/// creating "gaps" that a construction heuristic can fill. Combined with
23/// construction, this enables exploring distant regions of the search space.
24///
25/// # Type Parameters
26/// * `S` - The planning solution type
27/// * `V` - The variable value type
28///
29/// # Example
30///
31/// ```
32/// use solverforge_solver::heuristic::r#move::RuinMove;
33/// use solverforge_core::domain::PlanningSolution;
34/// use solverforge_core::score::SimpleScore;
35///
36/// #[derive(Clone, Debug)]
37/// struct Task { assigned_to: Option<i32>, score: Option<SimpleScore> }
38/// #[derive(Clone, Debug)]
39/// struct Schedule { tasks: Vec<Task>, score: Option<SimpleScore> }
40///
41/// impl PlanningSolution for Schedule {
42///     type Score = SimpleScore;
43///     fn score(&self) -> Option<Self::Score> { self.score }
44///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
45/// }
46///
47/// fn get_task(s: &Schedule, idx: usize) -> Option<i32> {
48///     s.tasks.get(idx).and_then(|t| t.assigned_to)
49/// }
50/// fn set_task(s: &mut Schedule, idx: usize, v: Option<i32>) {
51///     if let Some(t) = s.tasks.get_mut(idx) { t.assigned_to = v; }
52/// }
53///
54/// // Ruin entities 0, 2, and 4
55/// let m = RuinMove::<Schedule, i32>::new(
56///     &[0, 2, 4],
57///     get_task, set_task,
58///     "assigned_to", 0,
59/// );
60/// ```
61pub struct RuinMove<S, V> {
62    /// Indices of entities to unassign
63    entity_indices: SmallVec<[usize; 8]>,
64    /// Get current value for an entity
65    getter: fn(&S, usize) -> Option<V>,
66    /// Set value for an entity
67    setter: fn(&mut S, usize, Option<V>),
68    variable_name: &'static str,
69    descriptor_index: usize,
70}
71
72impl<S, V> Clone for RuinMove<S, V> {
73    fn clone(&self) -> Self {
74        Self {
75            entity_indices: self.entity_indices.clone(),
76            getter: self.getter,
77            setter: self.setter,
78            variable_name: self.variable_name,
79            descriptor_index: self.descriptor_index,
80        }
81    }
82}
83
84impl<S, V: Debug> Debug for RuinMove<S, V> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        f.debug_struct("RuinMove")
87            .field("entities", &self.entity_indices.as_slice())
88            .field("variable_name", &self.variable_name)
89            .finish()
90    }
91}
92
93impl<S, V> RuinMove<S, V> {
94    /// Creates a new ruin move with typed function pointers.
95    ///
96    /// # Arguments
97    /// * `entity_indices` - Indices of entities to unassign
98    /// * `getter` - Function to get current value
99    /// * `setter` - Function to set value
100    /// * `variable_name` - Name of the planning variable
101    /// * `descriptor_index` - Entity descriptor index
102    pub fn new(
103        entity_indices: &[usize],
104        getter: fn(&S, usize) -> Option<V>,
105        setter: fn(&mut S, usize, Option<V>),
106        variable_name: &'static str,
107        descriptor_index: usize,
108    ) -> Self {
109        Self {
110            entity_indices: SmallVec::from_slice(entity_indices),
111            getter,
112            setter,
113            variable_name,
114            descriptor_index,
115        }
116    }
117
118    /// Returns the entity indices being ruined.
119    pub fn entity_indices_slice(&self) -> &[usize] {
120        &self.entity_indices
121    }
122
123    /// Returns the number of entities being ruined.
124    pub fn ruin_count(&self) -> usize {
125        self.entity_indices.len()
126    }
127}
128
129impl<S, V> Move<S> for RuinMove<S, V>
130where
131    S: PlanningSolution,
132    V: Clone + Send + Sync + Debug + 'static,
133{
134    fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
135        // At least one entity must be currently assigned
136        let solution = score_director.working_solution();
137        self.entity_indices
138            .iter()
139            .any(|&idx| (self.getter)(solution, idx).is_some())
140    }
141
142    fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
143        let getter = self.getter;
144        let setter = self.setter;
145        let descriptor = self.descriptor_index;
146        let variable_name = self.variable_name;
147
148        // Collect old values for undo
149        let old_values: SmallVec<[(usize, Option<V>); 8]> = self
150            .entity_indices
151            .iter()
152            .map(|&idx| {
153                let old = getter(score_director.working_solution(), idx);
154                (idx, old)
155            })
156            .collect();
157
158        // Unassign all entities
159        for &idx in &self.entity_indices {
160            score_director.before_variable_changed(descriptor, idx, variable_name);
161            setter(score_director.working_solution_mut(), idx, None);
162            score_director.after_variable_changed(descriptor, idx, variable_name);
163        }
164
165        // Register undo to restore old values
166        score_director.register_undo(Box::new(move |s: &mut S| {
167            for (idx, old_value) in old_values {
168                setter(s, idx, old_value);
169            }
170        }));
171    }
172
173    fn descriptor_index(&self) -> usize {
174        self.descriptor_index
175    }
176
177    fn entity_indices(&self) -> &[usize] {
178        &self.entity_indices
179    }
180
181    fn variable_name(&self) -> &str {
182        self.variable_name
183    }
184}