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::move_selector::{ArenaMoveCursor, MoveSelector};
66
67/// A move selector that generates 2-opt segment reversal moves.
68///
69/// For each entity, enumerates all valid (start, end) pairs where
70/// `end > start + 1` (at least 2 elements in the reversed segment).
71///
72/// # Type Parameters
73/// * `S` - The solution type
74/// * `V` - The list element type (phantom — only used for type safety)
75/// * `ES` - The entity selector type
76pub struct ListReverseMoveSelector<S, V, ES> {
77    entity_selector: ES,
78    list_len: fn(&S, usize) -> usize,
79    list_get: fn(&S, usize, usize) -> Option<V>,
80    list_reverse: fn(&mut S, usize, usize, usize),
81    variable_name: &'static str,
82    descriptor_index: usize,
83    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
84}
85
86impl<S, V: Debug, ES: Debug> Debug for ListReverseMoveSelector<S, V, ES> {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("ListReverseMoveSelector")
89            .field("entity_selector", &self.entity_selector)
90            .field("variable_name", &self.variable_name)
91            .field("descriptor_index", &self.descriptor_index)
92            .finish()
93    }
94}
95
96impl<S, V, ES> ListReverseMoveSelector<S, V, ES> {
97    /// Creates a new list reverse move selector.
98    ///
99    /// # Arguments
100    /// * `entity_selector` - Selects entities (routes) to apply 2-opt to
101    /// * `list_len` - Function to get route length
102    /// * `list_reverse` - Function to reverse `[start, end)` in-place
103    /// * `variable_name` - Name of the list variable
104    /// * `descriptor_index` - Entity descriptor index
105    pub fn new(
106        entity_selector: ES,
107        list_len: fn(&S, usize) -> usize,
108        list_get: fn(&S, usize, usize) -> Option<V>,
109        list_reverse: fn(&mut S, usize, usize, usize),
110        variable_name: &'static str,
111        descriptor_index: usize,
112    ) -> Self {
113        Self {
114            entity_selector,
115            list_len,
116            list_get,
117            list_reverse,
118            variable_name,
119            descriptor_index,
120            _phantom: PhantomData,
121        }
122    }
123}
124
125impl<S, V, ES> MoveSelector<S, ListReverseMove<S, V>> for ListReverseMoveSelector<S, V, ES>
126where
127    S: PlanningSolution,
128    V: Clone + Send + Sync + Debug + 'static,
129    ES: EntitySelector<S>,
130{
131    type Cursor<'a>
132        = ArenaMoveCursor<S, ListReverseMove<S, V>>
133    where
134        Self: 'a;
135
136    fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
137        let solution = score_director.working_solution();
138        let list_len = self.list_len;
139        let list_get = self.list_get;
140        let list_reverse = self.list_reverse;
141        let variable_name = self.variable_name;
142        let descriptor_index = self.descriptor_index;
143
144        let entities: Vec<usize> = self
145            .entity_selector
146            .iter(score_director)
147            .map(|r| r.entity_index)
148            .collect();
149
150        let mut moves = Vec::new();
151
152        for &entity in &entities {
153            let len = list_len(solution, entity);
154            if len < 2 {
155                continue;
156            }
157
158            // Enumerate all (start, end) pairs where end > start + 1
159            // This covers all 2-opt reversals within this entity's list
160            for start in 0..len {
161                // end is exclusive; minimum valid end = start + 2
162                for end in (start + 2)..=len {
163                    moves.push(ListReverseMove::new(
164                        entity,
165                        start,
166                        end,
167                        list_len,
168                        list_get,
169                        list_reverse,
170                        variable_name,
171                        descriptor_index,
172                    ));
173                }
174            }
175        }
176
177        ArenaMoveCursor::from_moves(moves)
178    }
179
180    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
181        let solution = score_director.working_solution();
182        let list_len = self.list_len;
183
184        self.entity_selector
185            .iter(score_director)
186            .map(|r| {
187                let m = list_len(solution, r.entity_index);
188                // Number of valid (start, end) pairs: m*(m-1)/2 - m = m*(m-1)/2 - m
189                // For start in 0..m, end in start+2..=m: sum = m*(m-1)/2
190                if m >= 2 {
191                    m * (m - 1) / 2
192                } else {
193                    0
194                }
195            })
196            .sum()
197    }
198}