Skip to main content

solverforge_solver/heuristic/move/
sublist_change.rs

1/* SublistChangeMove - relocates a contiguous sublist within or between list variables.
2
3This move removes a range of elements from one position and inserts them at another.
4Essential for vehicle routing where multiple consecutive stops need relocation.
5
6# Zero-Erasure Design
7
8Uses concrete function pointers for list operations. No `dyn Any`, no downcasting.
9*/
10
11use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::list_kernel::{
18    sublist_change_candidate_trace_identity, sublist_change_do_move, sublist_change_is_doable,
19    sublist_change_tabu_signature, sublist_change_undo_move, StaticListWindowAccess,
20};
21use super::segment_layout::SegmentRelocationCoords;
22use super::{Move, MoveTabuSignature};
23
24/// A move that relocates a contiguous sublist from one position to another.
25///
26/// Supports both intra-list moves (within same entity) and inter-list moves
27/// (between different entities). Uses concrete function pointers for zero-erasure.
28///
29/// # Type Parameters
30/// * `S` - The planning solution type
31/// * `V` - The list element value type
32///
33/// # Example
34///
35/// ```
36/// use solverforge_solver::heuristic::r#move::SublistChangeMove;
37/// use solverforge_core::domain::PlanningSolution;
38/// use solverforge_core::score::SoftScore;
39///
40/// #[derive(Clone, Debug)]
41/// struct Vehicle { id: usize, visits: Vec<i32> }
42///
43/// #[derive(Clone, Debug)]
44/// struct Solution { vehicles: Vec<Vehicle>, score: Option<SoftScore> }
45///
46/// impl PlanningSolution for Solution {
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 list_len(s: &Solution, entity_idx: usize) -> usize {
53///     s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
54/// }
55/// fn list_get(s: &Solution, entity_idx: usize, pos: usize) -> Option<i32> {
56///     s.vehicles
57///         .get(entity_idx)
58///         .and_then(|v| v.visits.get(pos))
59///         .copied()
60/// }
61/// fn sublist_remove(s: &mut Solution, entity_idx: usize, start: usize, end: usize) -> Vec<i32> {
62///     s.vehicles.get_mut(entity_idx)
63///         .map(|v| v.visits.drain(start..end).collect())
64///         .unwrap_or_default()
65/// }
66/// fn sublist_insert(s: &mut Solution, entity_idx: usize, pos: usize, items: Vec<i32>) {
67///     if let Some(v) = s.vehicles.get_mut(entity_idx) {
68///         for (i, item) in items.into_iter().enumerate() {
69///             v.visits.insert(pos + i, item);
70///         }
71///     }
72/// }
73///
74/// // Move elements [1..3) from vehicle 0 to vehicle 1 at position 0
75/// let m = SublistChangeMove::<Solution, i32>::new(
76///     0, 1, 3,  // source: entity 0, range [1, 3)
77///     1, 0,     // dest: entity 1, position 0
78///     list_len, list_get, sublist_remove, sublist_insert,
79///     "visits", 0,
80/// );
81/// ```
82pub struct SublistChangeMove<S, V> {
83    // Source entity index
84    source_entity_index: usize,
85    // Start of range in source list (inclusive)
86    source_start: usize,
87    // End of range in source list (exclusive)
88    source_end: usize,
89    // Destination entity index
90    dest_entity_index: usize,
91    // Position in destination list to insert at
92    dest_position: usize,
93    list_len: fn(&S, usize) -> usize,
94    list_get: fn(&S, usize, usize) -> Option<V>,
95    // Remove sublist [start, end), returns removed elements
96    sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
97    // Insert elements at position
98    sublist_insert: fn(&mut S, usize, usize, Vec<V>),
99    variable_name: &'static str,
100    descriptor_index: usize,
101    // Store indices for entity_indices()
102    indices: [usize; 2],
103    _phantom: PhantomData<fn() -> V>,
104}
105
106impl<S, V> Clone for SublistChangeMove<S, V> {
107    fn clone(&self) -> Self {
108        *self
109    }
110}
111
112impl<S, V> Copy for SublistChangeMove<S, V> {}
113
114impl<S, V: Debug> Debug for SublistChangeMove<S, V> {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("SublistChangeMove")
117            .field("source_entity", &self.source_entity_index)
118            .field("source_range", &(self.source_start..self.source_end))
119            .field("dest_entity", &self.dest_entity_index)
120            .field("dest_position", &self.dest_position)
121            .field("variable_name", &self.variable_name)
122            .finish()
123    }
124}
125
126impl<S, V> SublistChangeMove<S, V> {
127    /* Creates a new sublist change move with concrete function pointers.
128
129    # Arguments
130    * `source_entity_index` - Entity index to remove from
131    * `source_start` - Start of range (inclusive)
132    * `source_end` - End of range (exclusive)
133    * `dest_entity_index` - Entity index to insert into
134    * `dest_position` - Position in destination list
135    * `list_len` - Function to get list length
136    * `sublist_remove` - Function to remove range [start, end)
137    * `sublist_insert` - Function to insert elements at position
138    * `variable_name` - Name of the list variable
139    * `descriptor_index` - Entity descriptor index
140    */
141    #[allow(clippy::too_many_arguments)]
142    pub fn new(
143        source_entity_index: usize,
144        source_start: usize,
145        source_end: usize,
146        dest_entity_index: usize,
147        dest_position: usize,
148        list_len: fn(&S, usize) -> usize,
149        list_get: fn(&S, usize, usize) -> Option<V>,
150        sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
151        sublist_insert: fn(&mut S, usize, usize, Vec<V>),
152        variable_name: &'static str,
153        descriptor_index: usize,
154    ) -> Self {
155        Self {
156            source_entity_index,
157            source_start,
158            source_end,
159            dest_entity_index,
160            dest_position,
161            list_len,
162            list_get,
163            sublist_remove,
164            sublist_insert,
165            variable_name,
166            descriptor_index,
167            indices: [source_entity_index, dest_entity_index],
168            _phantom: PhantomData,
169        }
170    }
171
172    pub fn source_entity_index(&self) -> usize {
173        self.source_entity_index
174    }
175
176    pub fn source_start(&self) -> usize {
177        self.source_start
178    }
179
180    pub fn source_end(&self) -> usize {
181        self.source_end
182    }
183
184    pub fn sublist_len(&self) -> usize {
185        self.source_end.saturating_sub(self.source_start)
186    }
187
188    pub fn dest_entity_index(&self) -> usize {
189        self.dest_entity_index
190    }
191
192    pub fn dest_position(&self) -> usize {
193        self.dest_position
194    }
195
196    pub fn is_intra_list(&self) -> bool {
197        self.source_entity_index == self.dest_entity_index
198    }
199
200    fn access(&self) -> StaticListWindowAccess<S, V> {
201        StaticListWindowAccess {
202            list_len: self.list_len,
203            list_get: self.list_get,
204            sublist_remove: self.sublist_remove,
205            sublist_insert: self.sublist_insert,
206            variable_name: self.variable_name,
207            descriptor_index: self.descriptor_index,
208        }
209    }
210
211    fn coordinates(&self) -> SegmentRelocationCoords {
212        SegmentRelocationCoords::new(
213            self.source_entity_index,
214            self.source_start,
215            self.source_end,
216            self.dest_entity_index,
217            self.dest_position,
218        )
219    }
220}
221
222impl<S, V> Move<S> for SublistChangeMove<S, V>
223where
224    S: PlanningSolution,
225    V: Clone + Send + Sync + Debug + 'static,
226{
227    type Undo = ();
228
229    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
230        sublist_change_is_doable(&self.access(), self.coordinates(), score_director)
231    }
232
233    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
234        sublist_change_do_move(&self.access(), self.coordinates(), score_director);
235    }
236
237    fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
238        sublist_change_undo_move(&self.access(), self.coordinates(), score_director);
239    }
240
241    fn descriptor_index(&self) -> usize {
242        self.descriptor_index
243    }
244
245    fn entity_indices(&self) -> &[usize] {
246        if self.is_intra_list() {
247            &self.indices[0..1]
248        } else {
249            &self.indices
250        }
251    }
252
253    fn variable_name(&self) -> &str {
254        self.variable_name
255    }
256
257    fn telemetry_label(&self) -> &'static str {
258        "sublist_change"
259    }
260
261    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
262        sublist_change_tabu_signature(&self.access(), self.coordinates(), score_director)
263    }
264
265    fn candidate_trace_identity(&self) -> Option<crate::stats::CandidateTraceIdentity> {
266        Some(sublist_change_candidate_trace_identity(
267            &self.access(),
268            self.coordinates(),
269        ))
270    }
271}