Skip to main content

solverforge_solver/heuristic/selector/
sublist_swap.rs

1//! Sublist swap move selector for segment exchange.
2//!
3//! Generates `SubListSwapMove`s that swap contiguous segments within or between
4//! list variables. Useful for balanced inter-route segment exchanges in VRP.
5//!
6//! # Complexity
7//!
8//! For n entities with average route length m and max segment size k:
9//! - Intra-entity pairs: O(n * m² * k²) — triangular over non-overlapping segments
10//! - Inter-entity pairs: O(n² * m² * k²) — all pairs across entities
11//!
12//! Use a forager that quits early for large instances.
13//!
14//! # Example
15//!
16//! ```
17//! use solverforge_solver::heuristic::selector::sublist_swap::SubListSwapMoveSelector;
18//! use solverforge_solver::heuristic::selector::entity::FromSolutionEntitySelector;
19//! use solverforge_solver::heuristic::selector::MoveSelector;
20//! use solverforge_core::domain::PlanningSolution;
21//! use solverforge_core::score::SimpleScore;
22//!
23//! #[derive(Clone, Debug)]
24//! struct Vehicle { visits: Vec<i32> }
25//!
26//! #[derive(Clone, Debug)]
27//! struct Solution { vehicles: Vec<Vehicle>, score: Option<SimpleScore> }
28//!
29//! impl PlanningSolution for Solution {
30//!     type Score = SimpleScore;
31//!     fn score(&self) -> Option<Self::Score> { self.score }
32//!     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
33//! }
34//!
35//! fn list_len(s: &Solution, entity_idx: usize) -> usize {
36//!     s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
37//! }
38//! fn sublist_remove(s: &mut Solution, entity_idx: usize, start: usize, end: usize) -> Vec<i32> {
39//!     s.vehicles.get_mut(entity_idx)
40//!         .map(|v| v.visits.drain(start..end).collect())
41//!         .unwrap_or_default()
42//! }
43//! fn sublist_insert(s: &mut Solution, entity_idx: usize, pos: usize, items: Vec<i32>) {
44//!     if let Some(v) = s.vehicles.get_mut(entity_idx) {
45//!         for (i, item) in items.into_iter().enumerate() {
46//!             v.visits.insert(pos + i, item);
47//!         }
48//!     }
49//! }
50//!
51//! // Swap segments of size 1..=3 between routes
52//! let selector = SubListSwapMoveSelector::<Solution, i32, _>::new(
53//!     FromSolutionEntitySelector::new(0),
54//!     1, 3,
55//!     list_len,
56//!     sublist_remove,
57//!     sublist_insert,
58//!     "visits",
59//!     0,
60//! );
61//! ```
62
63use std::fmt::Debug;
64use std::marker::PhantomData;
65
66use solverforge_core::domain::PlanningSolution;
67use solverforge_scoring::ScoreDirector;
68
69use crate::heuristic::r#move::{ListMoveImpl, SubListSwapMove};
70
71use super::entity::EntitySelector;
72use super::typed_move_selector::MoveSelector;
73
74/// A move selector that generates sublist swap moves.
75///
76/// For each pair of segments (which may span different entities), generates
77/// a swap move. Intra-entity swaps require non-overlapping segments.
78///
79/// # Type Parameters
80/// * `S` - The solution type
81/// * `V` - The list element type
82/// * `ES` - The entity selector type
83pub struct SubListSwapMoveSelector<S, V, ES> {
84    entity_selector: ES,
85    /// Minimum segment size (inclusive).
86    min_sublist_size: usize,
87    /// Maximum segment size (inclusive).
88    max_sublist_size: usize,
89    list_len: fn(&S, usize) -> usize,
90    sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
91    sublist_insert: fn(&mut S, usize, usize, Vec<V>),
92    variable_name: &'static str,
93    descriptor_index: usize,
94    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
95}
96
97impl<S, V: Debug, ES: Debug> Debug for SubListSwapMoveSelector<S, V, ES> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("SubListSwapMoveSelector")
100            .field("entity_selector", &self.entity_selector)
101            .field("min_sublist_size", &self.min_sublist_size)
102            .field("max_sublist_size", &self.max_sublist_size)
103            .field("variable_name", &self.variable_name)
104            .field("descriptor_index", &self.descriptor_index)
105            .finish()
106    }
107}
108
109impl<S, V, ES> SubListSwapMoveSelector<S, V, ES> {
110    /// Creates a new sublist swap move selector.
111    ///
112    /// # Arguments
113    /// * `entity_selector` - Selects entities to consider for swaps
114    /// * `min_sublist_size` - Minimum segment length (must be ≥ 1)
115    /// * `max_sublist_size` - Maximum segment length
116    /// * `list_len` - Function to get list length for an entity
117    /// * `sublist_remove` - Function to drain range `[start, end)`, returning elements
118    /// * `sublist_insert` - Function to insert elements at a position
119    /// * `variable_name` - Name of the list variable
120    /// * `descriptor_index` - Entity descriptor index
121    ///
122    /// # Panics
123    /// Panics if `min_sublist_size == 0` or `max_sublist_size < min_sublist_size`.
124    #[allow(clippy::too_many_arguments)]
125    pub fn new(
126        entity_selector: ES,
127        min_sublist_size: usize,
128        max_sublist_size: usize,
129        list_len: fn(&S, usize) -> usize,
130        sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
131        sublist_insert: fn(&mut S, usize, usize, Vec<V>),
132        variable_name: &'static str,
133        descriptor_index: usize,
134    ) -> Self {
135        assert!(min_sublist_size >= 1, "min_sublist_size must be at least 1");
136        assert!(
137            max_sublist_size >= min_sublist_size,
138            "max_sublist_size must be >= min_sublist_size"
139        );
140        Self {
141            entity_selector,
142            min_sublist_size,
143            max_sublist_size,
144            list_len,
145            sublist_remove,
146            sublist_insert,
147            variable_name,
148            descriptor_index,
149            _phantom: PhantomData,
150        }
151    }
152}
153
154impl<S, V, ES> MoveSelector<S, SubListSwapMove<S, V>> for SubListSwapMoveSelector<S, V, ES>
155where
156    S: PlanningSolution,
157    V: Clone + PartialEq + Send + Sync + Debug + 'static,
158    ES: EntitySelector<S>,
159{
160    fn iter_moves<'a, D: ScoreDirector<S>>(
161        &'a self,
162        score_director: &'a D,
163    ) -> impl Iterator<Item = SubListSwapMove<S, V>> + 'a {
164        let solution = score_director.working_solution();
165        let list_len = self.list_len;
166        let sublist_remove = self.sublist_remove;
167        let sublist_insert = self.sublist_insert;
168        let variable_name = self.variable_name;
169        let descriptor_index = self.descriptor_index;
170        let min_seg = self.min_sublist_size;
171        let max_seg = self.max_sublist_size;
172
173        let entities: Vec<usize> = self
174            .entity_selector
175            .iter(score_director)
176            .map(|r| r.entity_index)
177            .collect();
178
179        let route_lens: Vec<usize> = entities.iter().map(|&e| list_len(solution, e)).collect();
180
181        let mut moves = Vec::new();
182
183        for (i, &entity_a) in entities.iter().enumerate() {
184            let len_a = route_lens[i];
185
186            // Intra-entity: pairs of non-overlapping segments in entity_a
187            // Enumerate all valid first segments, then all non-overlapping second segments
188            for first_start in 0..len_a {
189                for first_size in min_seg..=max_seg {
190                    let first_end = first_start + first_size;
191                    if first_end > len_a {
192                        break;
193                    }
194
195                    // Second segment must not overlap: second_start >= first_end
196                    for second_start in first_end..len_a {
197                        for second_size in min_seg..=max_seg {
198                            let second_end = second_start + second_size;
199                            if second_end > len_a {
200                                break;
201                            }
202                            moves.push(SubListSwapMove::new(
203                                entity_a,
204                                first_start,
205                                first_end,
206                                entity_a,
207                                second_start,
208                                second_end,
209                                list_len,
210                                sublist_remove,
211                                sublist_insert,
212                                variable_name,
213                                descriptor_index,
214                            ));
215                        }
216                    }
217                }
218            }
219
220            // Inter-entity: all segment pairs between entity_a and entity_b (b > a)
221            for (j, &entity_b) in entities.iter().enumerate() {
222                if j <= i {
223                    continue;
224                }
225                let len_b = route_lens[j];
226                if len_b == 0 {
227                    continue;
228                }
229
230                for first_start in 0..len_a {
231                    for first_size in min_seg..=max_seg {
232                        let first_end = first_start + first_size;
233                        if first_end > len_a {
234                            break;
235                        }
236
237                        for second_start in 0..len_b {
238                            for second_size in min_seg..=max_seg {
239                                let second_end = second_start + second_size;
240                                if second_end > len_b {
241                                    break;
242                                }
243                                moves.push(SubListSwapMove::new(
244                                    entity_a,
245                                    first_start,
246                                    first_end,
247                                    entity_b,
248                                    second_start,
249                                    second_end,
250                                    list_len,
251                                    sublist_remove,
252                                    sublist_insert,
253                                    variable_name,
254                                    descriptor_index,
255                                ));
256                            }
257                        }
258                    }
259                }
260            }
261        }
262
263        moves.into_iter()
264    }
265
266    fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
267        let solution = score_director.working_solution();
268        let list_len = self.list_len;
269
270        let entities: Vec<usize> = self
271            .entity_selector
272            .iter(score_director)
273            .map(|r| r.entity_index)
274            .collect();
275
276        let route_lens: Vec<usize> = entities.iter().map(|&e| list_len(solution, e)).collect();
277        let n = entities.len();
278        if n == 0 {
279            return 0;
280        }
281
282        let k_range = self.max_sublist_size - self.min_sublist_size + 1;
283        let total_elements: usize = route_lens.iter().sum();
284        let avg_len = total_elements / n;
285        // Rough estimate: n * avg_len * k * (avg_len * k + (n-1) * avg_len * k) / 2
286        n * avg_len * k_range * avg_len.max(1) * k_range * (n + 1) / 2
287    }
288}
289
290/// Wraps a `SubListSwapMoveSelector` to yield `ListMoveImpl::SubListSwap`.
291pub struct ListMoveSubListSwapSelector<S, V, ES> {
292    inner: SubListSwapMoveSelector<S, V, ES>,
293}
294
295impl<S, V: Debug, ES: Debug> Debug for ListMoveSubListSwapSelector<S, V, ES> {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        f.debug_struct("ListMoveSubListSwapSelector")
298            .field("inner", &self.inner)
299            .finish()
300    }
301}
302
303impl<S, V, ES> ListMoveSubListSwapSelector<S, V, ES> {
304    /// Wraps an existing [`SubListSwapMoveSelector`].
305    pub fn new(inner: SubListSwapMoveSelector<S, V, ES>) -> Self {
306        Self { inner }
307    }
308}
309
310impl<S, V, ES> MoveSelector<S, ListMoveImpl<S, V>> for ListMoveSubListSwapSelector<S, V, ES>
311where
312    S: PlanningSolution,
313    V: Clone + PartialEq + Send + Sync + Debug + 'static,
314    ES: EntitySelector<S>,
315{
316    fn iter_moves<'a, D: ScoreDirector<S>>(
317        &'a self,
318        score_director: &'a D,
319    ) -> impl Iterator<Item = ListMoveImpl<S, V>> + 'a {
320        self.inner
321            .iter_moves(score_director)
322            .map(ListMoveImpl::SubListSwap)
323    }
324
325    fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
326        self.inner.size(score_director)
327    }
328}