Skip to main content

solverforge_solver/heuristic/selector/
list_swap.rs

1/* List swap move selector for element exchange.
2
3Generates `ListSwapMove`s that swap elements within or between list variables.
4Useful for inter-route rebalancing in vehicle routing problems.
5
6# Complexity
7
8For n entities with average route length m:
9- Intra-entity swaps: O(n * m * (m-1) / 2)
10- Inter-entity swaps: O(n² * m²)
11- Total: O(n² * m²) worst case (triangular optimization halves constant)
12
13# Example
14
15```
16use solverforge_solver::heuristic::selector::list_swap::ListSwapMoveSelector;
17use solverforge_solver::heuristic::selector::entity::FromSolutionEntitySelector;
18use solverforge_solver::heuristic::selector::MoveSelector;
19use solverforge_core::domain::PlanningSolution;
20use solverforge_core::score::SoftScore;
21
22#[derive(Clone, Debug)]
23struct Vehicle { visits: Vec<i32> }
24
25#[derive(Clone, Debug)]
26struct Solution { vehicles: Vec<Vehicle>, score: Option<SoftScore> }
27
28impl PlanningSolution for Solution {
29type Score = SoftScore;
30fn score(&self) -> Option<Self::Score> { self.score }
31fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
32}
33
34fn list_len(s: &Solution, entity_idx: usize) -> usize {
35s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
36}
37fn list_get(s: &Solution, entity_idx: usize, pos: usize) -> Option<i32> {
38s.vehicles.get(entity_idx).and_then(|v| v.visits.get(pos).copied())
39}
40fn list_set(s: &mut Solution, entity_idx: usize, pos: usize, val: i32) {
41if let Some(v) = s.vehicles.get_mut(entity_idx) {
42if let Some(elem) = v.visits.get_mut(pos) { *elem = val; }
43}
44}
45
46let selector = ListSwapMoveSelector::<Solution, i32, _>::new(
47FromSolutionEntitySelector::new(0),
48list_len,
49list_get,
50list_set,
51"visits",
520,
53);
54```
55*/
56
57use std::fmt::Debug;
58use std::marker::PhantomData;
59
60use solverforge_core::domain::PlanningSolution;
61use solverforge_scoring::Director;
62
63use crate::heuristic::r#move::ListSwapMove;
64
65use super::entity::EntitySelector;
66use super::list_support::{collect_selected_entities, ordered_index};
67use super::move_selector::{
68    CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
69};
70
71/// A move selector that generates list swap moves.
72///
73/// Enumerates all valid (entity_a, pos_a, entity_b, pos_b) pairs for swapping
74/// elements within or between list variables. Intra-entity swaps use a
75/// triangular iteration to avoid duplicate pairs.
76///
77/// # Type Parameters
78/// * `S` - The solution type
79/// * `V` - The list element type
80/// * `ES` - The entity selector type
81pub struct ListSwapMoveSelector<S, V, ES> {
82    entity_selector: ES,
83    list_len: fn(&S, usize) -> usize,
84    list_get: fn(&S, usize, usize) -> Option<V>,
85    list_set: fn(&mut S, usize, usize, V),
86    variable_name: &'static str,
87    descriptor_index: usize,
88    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
89}
90
91enum ListSwapStage {
92    Intra,
93    Inter,
94}
95
96pub struct ListSwapMoveCursor<S, V>
97where
98    S: PlanningSolution,
99    V: Clone + PartialEq + Send + Sync + Debug + 'static,
100{
101    store: CandidateStore<S, ListSwapMove<S, V>>,
102    entities: Vec<usize>,
103    route_lens: Vec<usize>,
104    context: MoveStreamContext,
105    entity_idx: usize,
106    stage: ListSwapStage,
107    pos_a_offset: usize,
108    pos_b_offset: usize,
109    dst_idx: usize,
110    inter_pos_a_offset: usize,
111    inter_pos_b_offset: usize,
112    list_len: fn(&S, usize) -> usize,
113    list_get: fn(&S, usize, usize) -> Option<V>,
114    list_set: fn(&mut S, usize, usize, V),
115    variable_name: &'static str,
116    descriptor_index: usize,
117}
118
119impl<S, V> ListSwapMoveCursor<S, V>
120where
121    S: PlanningSolution,
122    V: Clone + PartialEq + Send + Sync + Debug + 'static,
123{
124    #[allow(clippy::too_many_arguments)]
125    fn new(
126        entities: Vec<usize>,
127        route_lens: Vec<usize>,
128        context: MoveStreamContext,
129        list_len: fn(&S, usize) -> usize,
130        list_get: fn(&S, usize, usize) -> Option<V>,
131        list_set: fn(&mut S, usize, usize, V),
132        variable_name: &'static str,
133        descriptor_index: usize,
134    ) -> Self {
135        Self {
136            store: CandidateStore::new(),
137            entities,
138            route_lens,
139            context,
140            entity_idx: 0,
141            stage: ListSwapStage::Intra,
142            pos_a_offset: 0,
143            pos_b_offset: 0,
144            dst_idx: 1,
145            inter_pos_a_offset: 0,
146            inter_pos_b_offset: 0,
147            list_len,
148            list_get,
149            list_set,
150            variable_name,
151            descriptor_index,
152        }
153    }
154
155    fn push_move(
156        &mut self,
157        entity_a: usize,
158        pos_a: usize,
159        entity_b: usize,
160        pos_b: usize,
161    ) -> CandidateId {
162        self.store.push(ListSwapMove::new(
163            entity_a,
164            pos_a,
165            entity_b,
166            pos_b,
167            self.list_len,
168            self.list_get,
169            self.list_set,
170            self.variable_name,
171            self.descriptor_index,
172        ))
173    }
174
175    fn advance_entity(&mut self) {
176        self.entity_idx += 1;
177        self.stage = ListSwapStage::Intra;
178        self.pos_a_offset = 0;
179        self.pos_b_offset = 0;
180        self.dst_idx = self.entity_idx + 1;
181        self.inter_pos_a_offset = 0;
182        self.inter_pos_b_offset = 0;
183    }
184}
185
186impl<S, V> MoveCursor<S, ListSwapMove<S, V>> for ListSwapMoveCursor<S, V>
187where
188    S: PlanningSolution,
189    V: Clone + PartialEq + Send + Sync + Debug + 'static,
190{
191    fn next_candidate(&mut self) -> Option<CandidateId> {
192        loop {
193            if self.entity_idx >= self.entities.len() {
194                return None;
195            }
196
197            let entity_a = self.entities[self.entity_idx];
198            let len_a = self.route_lens[self.entity_idx];
199            if len_a == 0 {
200                self.advance_entity();
201                continue;
202            }
203
204            match self.stage {
205                ListSwapStage::Intra => {
206                    while self.pos_a_offset < len_a {
207                        let pos_a = ordered_index(
208                            self.pos_a_offset,
209                            len_a,
210                            self.context,
211                            0x1157_5A09_0000_0002 ^ entity_a as u64 ^ self.descriptor_index as u64,
212                        );
213                        let pos_b_count = len_a.saturating_sub(pos_a + 1);
214                        if self.pos_b_offset < pos_b_count {
215                            let pos_b = pos_a
216                                + 1
217                                + ordered_index(
218                                    self.pos_b_offset,
219                                    pos_b_count,
220                                    self.context,
221                                    0x1157_5A09_0000_0003 ^ entity_a as u64 ^ pos_a as u64,
222                                );
223                            self.pos_b_offset += 1;
224                            return Some(self.push_move(entity_a, pos_a, entity_a, pos_b));
225                        }
226                        self.pos_a_offset += 1;
227                        self.pos_b_offset = 0;
228                    }
229                    self.stage = ListSwapStage::Inter;
230                    self.dst_idx = self.entity_idx + 1;
231                    self.inter_pos_a_offset = 0;
232                    self.inter_pos_b_offset = 0;
233                }
234                ListSwapStage::Inter => {
235                    while self.dst_idx < self.entities.len() {
236                        let entity_b = self.entities[self.dst_idx];
237                        let len_b = self.route_lens[self.dst_idx];
238                        if len_b == 0 {
239                            self.dst_idx += 1;
240                            continue;
241                        }
242
243                        while self.inter_pos_a_offset < len_a {
244                            let pos_a = ordered_index(
245                                self.inter_pos_a_offset,
246                                len_a,
247                                self.context,
248                                0x1157_5A09_0000_0004 ^ entity_a as u64 ^ entity_b as u64,
249                            );
250                            if self.inter_pos_b_offset < len_b {
251                                let pos_b = ordered_index(
252                                    self.inter_pos_b_offset,
253                                    len_b,
254                                    self.context,
255                                    0x1157_5A09_0000_0005
256                                        ^ entity_a as u64
257                                        ^ entity_b as u64
258                                        ^ pos_a as u64,
259                                );
260                                self.inter_pos_b_offset += 1;
261                                return Some(self.push_move(entity_a, pos_a, entity_b, pos_b));
262                            }
263                            self.inter_pos_a_offset += 1;
264                            self.inter_pos_b_offset = 0;
265                        }
266                        self.dst_idx += 1;
267                        self.inter_pos_a_offset = 0;
268                        self.inter_pos_b_offset = 0;
269                    }
270                    self.advance_entity();
271                }
272            }
273        }
274    }
275
276    fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, ListSwapMove<S, V>>> {
277        self.store.candidate(id)
278    }
279
280    fn take_candidate(&mut self, id: CandidateId) -> ListSwapMove<S, V> {
281        self.store.take_candidate(id)
282    }
283}
284
285impl<S, V> Iterator for ListSwapMoveCursor<S, V>
286where
287    S: PlanningSolution,
288    V: Clone + PartialEq + Send + Sync + Debug + 'static,
289{
290    type Item = ListSwapMove<S, V>;
291
292    fn next(&mut self) -> Option<Self::Item> {
293        let id = self.next_candidate()?;
294        Some(self.take_candidate(id))
295    }
296}
297
298impl<S, V: Debug, ES: Debug> Debug for ListSwapMoveSelector<S, V, ES> {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        f.debug_struct("ListSwapMoveSelector")
301            .field("entity_selector", &self.entity_selector)
302            .field("variable_name", &self.variable_name)
303            .field("descriptor_index", &self.descriptor_index)
304            .finish()
305    }
306}
307
308impl<S, V, ES> ListSwapMoveSelector<S, V, ES> {
309    /// Creates a new list swap move selector.
310    ///
311    /// # Arguments
312    /// * `entity_selector` - Selects entities to consider for swaps
313    /// * `list_len` - Function to get list length for an entity
314    /// * `list_get` - Function to get element at position
315    /// * `list_set` - Function to set element at position
316    /// * `variable_name` - Name of the list variable
317    /// * `descriptor_index` - Entity descriptor index
318    pub fn new(
319        entity_selector: ES,
320        list_len: fn(&S, usize) -> usize,
321        list_get: fn(&S, usize, usize) -> Option<V>,
322        list_set: fn(&mut S, usize, usize, V),
323        variable_name: &'static str,
324        descriptor_index: usize,
325    ) -> Self {
326        Self {
327            entity_selector,
328            list_len,
329            list_get,
330            list_set,
331            variable_name,
332            descriptor_index,
333            _phantom: PhantomData,
334        }
335    }
336}
337
338impl<S, V, ES> MoveSelector<S, ListSwapMove<S, V>> for ListSwapMoveSelector<S, V, ES>
339where
340    S: PlanningSolution,
341    V: Clone + PartialEq + Send + Sync + Debug + 'static,
342    ES: EntitySelector<S>,
343{
344    type Cursor<'a>
345        = ListSwapMoveCursor<S, V>
346    where
347        Self: 'a;
348
349    fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
350        self.open_cursor_with_context(score_director, MoveStreamContext::default())
351    }
352
353    fn open_cursor_with_context<'a, D: Director<S>>(
354        &'a self,
355        score_director: &D,
356        context: MoveStreamContext,
357    ) -> Self::Cursor<'a> {
358        let mut selected =
359            collect_selected_entities(&self.entity_selector, score_director, self.list_len);
360        selected.apply_stream_order(
361            context,
362            0x1157_5A09_0000_0001 ^ self.descriptor_index as u64,
363        );
364        ListSwapMoveCursor::new(
365            selected.entities,
366            selected.route_lens,
367            context,
368            self.list_len,
369            self.list_get,
370            self.list_set,
371            self.variable_name,
372            self.descriptor_index,
373        )
374    }
375
376    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
377        collect_selected_entities(&self.entity_selector, score_director, self.list_len)
378            .list_swap_move_capacity()
379    }
380}