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}