Skip to main content

solverforge_solver/heuristic/selector/
list_reverse.rs

1/* List reverse move selector for 2-opt optimization.
2
3Generates `ListReverseMove`s that reverse contiguous segments within a single
4list. This is the fundamental 2-opt move for TSP and VRP: reversing a segment
5of the tour can eliminate crossing edges and reduce total distance.
6
7For VRP, 2-opt is applied independently within each route (intra-route 2-opt).
8Cross-route 2-opt would require inter-entity reversal, which is a different
9operation modeled by `SublistSwapMove` with same-size segments.
10
11# Complexity
12
13For n entities with average route length m:
14O(n * m²) — all (start, end) pairs per entity where end > start + 1.
15
16# Example
17
18```
19use solverforge_solver::heuristic::selector::list_reverse::ListReverseMoveSelector;
20use solverforge_solver::heuristic::selector::entity::FromSolutionEntitySelector;
21use solverforge_solver::heuristic::selector::MoveSelector;
22use solverforge_core::domain::PlanningSolution;
23use solverforge_core::score::SoftScore;
24
25#[derive(Clone, Debug)]
26struct Vehicle { visits: Vec<i32> }
27
28#[derive(Clone, Debug)]
29struct Solution { vehicles: Vec<Vehicle>, score: Option<SoftScore> }
30
31impl PlanningSolution for Solution {
32type Score = SoftScore;
33fn score(&self) -> Option<Self::Score> { self.score }
34fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
35}
36
37fn list_len(s: &Solution, entity_idx: usize) -> usize {
38s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
39}
40fn list_reverse(s: &mut Solution, entity_idx: usize, start: usize, end: usize) {
41if let Some(v) = s.vehicles.get_mut(entity_idx) {
42v.visits[start..end].reverse();
43}
44}
45
46let selector = ListReverseMoveSelector::<Solution, i32, _>::new(
47FromSolutionEntitySelector::new(0),
48list_len,
49list_reverse,
50"visits",
510,
52);
53```
54*/
55
56use std::fmt::Debug;
57use std::marker::PhantomData;
58
59use solverforge_core::domain::PlanningSolution;
60use solverforge_scoring::Director;
61
62use crate::heuristic::r#move::ListReverseMove;
63
64use super::entity::EntitySelector;
65use super::list_support::ordered_index;
66use super::move_selector::{
67    CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
68};
69
70/// A move selector that generates 2-opt segment reversal moves.
71///
72/// For each entity, enumerates all valid (start, end) pairs where
73/// `end > start + 1` (at least 2 elements in the reversed segment).
74///
75/// # Type Parameters
76/// * `S` - The solution type
77/// * `V` - The list element type (phantom — only used for type safety)
78/// * `ES` - The entity selector type
79pub struct ListReverseMoveSelector<S, V, ES> {
80    entity_selector: ES,
81    list_len: fn(&S, usize) -> usize,
82    list_get: fn(&S, usize, usize) -> Option<V>,
83    list_reverse: fn(&mut S, usize, usize, usize),
84    variable_name: &'static str,
85    descriptor_index: usize,
86    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
87}
88
89pub struct ListReverseMoveCursor<S, V>
90where
91    S: PlanningSolution,
92    V: Clone + Send + Sync + Debug + 'static,
93{
94    store: CandidateStore<S, ListReverseMove<S, V>>,
95    entities: Vec<usize>,
96    route_lens: Vec<usize>,
97    context: MoveStreamContext,
98    entity_idx: usize,
99    start_offset: usize,
100    end_offset: usize,
101    list_len: fn(&S, usize) -> usize,
102    list_get: fn(&S, usize, usize) -> Option<V>,
103    list_reverse: fn(&mut S, usize, usize, usize),
104    variable_name: &'static str,
105    descriptor_index: usize,
106}
107
108impl<S, V> ListReverseMoveCursor<S, V>
109where
110    S: PlanningSolution,
111    V: Clone + Send + Sync + Debug + 'static,
112{
113    #[allow(clippy::too_many_arguments)]
114    fn new(
115        entities: Vec<usize>,
116        route_lens: Vec<usize>,
117        context: MoveStreamContext,
118        list_len: fn(&S, usize) -> usize,
119        list_get: fn(&S, usize, usize) -> Option<V>,
120        list_reverse: fn(&mut S, usize, usize, usize),
121        variable_name: &'static str,
122        descriptor_index: usize,
123    ) -> Self {
124        Self {
125            store: CandidateStore::new(),
126            entities,
127            route_lens,
128            context,
129            entity_idx: 0,
130            start_offset: 0,
131            end_offset: 0,
132            list_len,
133            list_get,
134            list_reverse,
135            variable_name,
136            descriptor_index,
137        }
138    }
139
140    fn push_move(&mut self, entity: usize, start: usize, end: usize) -> CandidateId {
141        self.store.push(ListReverseMove::new(
142            entity,
143            start,
144            end,
145            self.list_len,
146            self.list_get,
147            self.list_reverse,
148            self.variable_name,
149            self.descriptor_index,
150        ))
151    }
152}
153
154impl<S, V> MoveCursor<S, ListReverseMove<S, V>> for ListReverseMoveCursor<S, V>
155where
156    S: PlanningSolution,
157    V: Clone + Send + Sync + Debug + 'static,
158{
159    fn next_candidate(&mut self) -> Option<CandidateId> {
160        loop {
161            let entity = *self.entities.get(self.entity_idx)?;
162            let len = self.route_lens[self.entity_idx];
163            if len < 2 {
164                self.entity_idx += 1;
165                self.start_offset = 0;
166                self.end_offset = 0;
167                continue;
168            }
169
170            while self.start_offset < len {
171                let start = ordered_index(
172                    self.start_offset,
173                    len,
174                    self.context,
175                    0x1157_2A07_0000_0002 ^ entity as u64 ^ self.descriptor_index as u64,
176                );
177                let end_count = len.saturating_sub(start + 1);
178                if self.end_offset < end_count {
179                    let end = start
180                        + 2
181                        + ordered_index(
182                            self.end_offset,
183                            end_count,
184                            self.context,
185                            0x1157_2A07_0000_0003 ^ entity as u64 ^ start as u64,
186                        );
187                    self.end_offset += 1;
188                    return Some(self.push_move(entity, start, end));
189                }
190                self.start_offset += 1;
191                self.end_offset = 0;
192            }
193
194            self.entity_idx += 1;
195            self.start_offset = 0;
196            self.end_offset = 0;
197        }
198    }
199
200    fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, ListReverseMove<S, V>>> {
201        self.store.candidate(id)
202    }
203
204    fn take_candidate(&mut self, id: CandidateId) -> ListReverseMove<S, V> {
205        self.store.take_candidate(id)
206    }
207}
208
209impl<S, V> Iterator for ListReverseMoveCursor<S, V>
210where
211    S: PlanningSolution,
212    V: Clone + Send + Sync + Debug + 'static,
213{
214    type Item = ListReverseMove<S, V>;
215
216    fn next(&mut self) -> Option<Self::Item> {
217        let id = self.next_candidate()?;
218        Some(self.take_candidate(id))
219    }
220}
221
222impl<S, V: Debug, ES: Debug> Debug for ListReverseMoveSelector<S, V, ES> {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        f.debug_struct("ListReverseMoveSelector")
225            .field("entity_selector", &self.entity_selector)
226            .field("variable_name", &self.variable_name)
227            .field("descriptor_index", &self.descriptor_index)
228            .finish()
229    }
230}
231
232impl<S, V, ES> ListReverseMoveSelector<S, V, ES> {
233    /// Creates a new list reverse move selector.
234    ///
235    /// # Arguments
236    /// * `entity_selector` - Selects entities (routes) to apply 2-opt to
237    /// * `list_len` - Function to get route length
238    /// * `list_reverse` - Function to reverse `[start, end)` in-place
239    /// * `variable_name` - Name of the list variable
240    /// * `descriptor_index` - Entity descriptor index
241    pub fn new(
242        entity_selector: ES,
243        list_len: fn(&S, usize) -> usize,
244        list_get: fn(&S, usize, usize) -> Option<V>,
245        list_reverse: fn(&mut S, usize, usize, usize),
246        variable_name: &'static str,
247        descriptor_index: usize,
248    ) -> Self {
249        Self {
250            entity_selector,
251            list_len,
252            list_get,
253            list_reverse,
254            variable_name,
255            descriptor_index,
256            _phantom: PhantomData,
257        }
258    }
259}
260
261impl<S, V, ES> MoveSelector<S, ListReverseMove<S, V>> for ListReverseMoveSelector<S, V, ES>
262where
263    S: PlanningSolution,
264    V: Clone + Send + Sync + Debug + 'static,
265    ES: EntitySelector<S>,
266{
267    type Cursor<'a>
268        = ListReverseMoveCursor<S, V>
269    where
270        Self: 'a;
271
272    fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
273        self.open_cursor_with_context(score_director, MoveStreamContext::default())
274    }
275
276    fn open_cursor_with_context<'a, D: Director<S>>(
277        &'a self,
278        score_director: &D,
279        context: MoveStreamContext,
280    ) -> Self::Cursor<'a> {
281        let mut entities: Vec<usize> = self
282            .entity_selector
283            .iter(score_director)
284            .map(|r| r.entity_index)
285            .collect();
286        let entity_start = context.start_offset(
287            entities.len(),
288            0x1157_2A07_0000_0001 ^ self.descriptor_index as u64,
289        );
290        entities.rotate_left(entity_start);
291
292        let solution = score_director.working_solution();
293        let route_lens = entities
294            .iter()
295            .map(|&entity| (self.list_len)(solution, entity))
296            .collect();
297
298        ListReverseMoveCursor::new(
299            entities,
300            route_lens,
301            context,
302            self.list_len,
303            self.list_get,
304            self.list_reverse,
305            self.variable_name,
306            self.descriptor_index,
307        )
308    }
309
310    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
311        let solution = score_director.working_solution();
312        let list_len = self.list_len;
313
314        self.entity_selector
315            .iter(score_director)
316            .map(|r| {
317                let m = list_len(solution, r.entity_index);
318                // Number of valid (start, end) pairs: m*(m-1)/2 - m = m*(m-1)/2 - m
319                // For start in 0..m, end in start+2..=m: sum = m*(m-1)/2
320                if m >= 2 {
321                    m * (m - 1) / 2
322                } else {
323                    0
324                }
325            })
326            .sum()
327    }
328}