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::{ListMoveImpl, ListReverseMove};
63
64use super::entity::EntitySelector;
65use super::typed_move_selector::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_reverse: fn(&mut S, usize, usize, usize),
80 variable_name: &'static str,
81 descriptor_index: usize,
82 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
83}
84
85impl<S, V: Debug, ES: Debug> Debug for ListReverseMoveSelector<S, V, ES> {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("ListReverseMoveSelector")
88 .field("entity_selector", &self.entity_selector)
89 .field("variable_name", &self.variable_name)
90 .field("descriptor_index", &self.descriptor_index)
91 .finish()
92 }
93}
94
95impl<S, V, ES> ListReverseMoveSelector<S, V, ES> {
96 /// Creates a new list reverse move selector.
97 ///
98 /// # Arguments
99 /// * `entity_selector` - Selects entities (routes) to apply 2-opt to
100 /// * `list_len` - Function to get route length
101 /// * `list_reverse` - Function to reverse `[start, end)` in-place
102 /// * `variable_name` - Name of the list variable
103 /// * `descriptor_index` - Entity descriptor index
104 pub fn new(
105 entity_selector: ES,
106 list_len: fn(&S, usize) -> usize,
107 list_reverse: fn(&mut S, usize, usize, usize),
108 variable_name: &'static str,
109 descriptor_index: usize,
110 ) -> Self {
111 Self {
112 entity_selector,
113 list_len,
114 list_reverse,
115 variable_name,
116 descriptor_index,
117 _phantom: PhantomData,
118 }
119 }
120}
121
122impl<S, V, ES> MoveSelector<S, ListReverseMove<S, V>> for ListReverseMoveSelector<S, V, ES>
123where
124 S: PlanningSolution,
125 V: Clone + Send + Sync + Debug + 'static,
126 ES: EntitySelector<S>,
127{
128 fn iter_moves<'a, D: Director<S>>(
129 &'a self,
130 score_director: &'a D,
131 ) -> impl Iterator<Item = ListReverseMove<S, V>> + 'a {
132 let solution = score_director.working_solution();
133 let list_len = self.list_len;
134 let list_reverse = self.list_reverse;
135 let variable_name = self.variable_name;
136 let descriptor_index = self.descriptor_index;
137
138 let entities: Vec<usize> = self
139 .entity_selector
140 .iter(score_director)
141 .map(|r| r.entity_index)
142 .collect();
143
144 let mut moves = Vec::new();
145
146 for &entity in &entities {
147 let len = list_len(solution, entity);
148 if len < 2 {
149 continue;
150 }
151
152 // Enumerate all (start, end) pairs where end > start + 1
153 // This covers all 2-opt reversals within this entity's list
154 for start in 0..len {
155 // end is exclusive; minimum valid end = start + 2
156 for end in (start + 2)..=len {
157 moves.push(ListReverseMove::new(
158 entity,
159 start,
160 end,
161 list_len,
162 list_reverse,
163 variable_name,
164 descriptor_index,
165 ));
166 }
167 }
168 }
169
170 moves.into_iter()
171 }
172
173 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
174 let solution = score_director.working_solution();
175 let list_len = self.list_len;
176
177 self.entity_selector
178 .iter(score_director)
179 .map(|r| {
180 let m = list_len(solution, r.entity_index);
181 // Number of valid (start, end) pairs: m*(m-1)/2 - m = m*(m-1)/2 - m
182 // For start in 0..m, end in start+2..=m: sum = m*(m-1)/2
183 if m >= 2 {
184 m * (m - 1) / 2
185 } else {
186 0
187 }
188 })
189 .sum()
190 }
191}
192
193/// Wraps a `ListReverseMoveSelector` to yield `ListMoveImpl::ListReverse`.
194pub struct ListMoveListReverseSelector<S, V, ES> {
195 inner: ListReverseMoveSelector<S, V, ES>,
196}
197
198impl<S, V: Debug, ES: Debug> Debug for ListMoveListReverseSelector<S, V, ES> {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 f.debug_struct("ListMoveListReverseSelector")
201 .field("inner", &self.inner)
202 .finish()
203 }
204}
205
206impl<S, V, ES> ListMoveListReverseSelector<S, V, ES> {
207 /// Wraps an existing [`ListReverseMoveSelector`].
208 pub fn new(inner: ListReverseMoveSelector<S, V, ES>) -> Self {
209 Self { inner }
210 }
211}
212
213impl<S, V, ES> MoveSelector<S, ListMoveImpl<S, V>> for ListMoveListReverseSelector<S, V, ES>
214where
215 S: PlanningSolution,
216 V: Clone + PartialEq + Send + Sync + Debug + 'static,
217 ES: EntitySelector<S>,
218{
219 fn iter_moves<'a, D: Director<S>>(
220 &'a self,
221 score_director: &'a D,
222 ) -> impl Iterator<Item = ListMoveImpl<S, V>> + 'a {
223 self.inner
224 .iter_moves(score_director)
225 .map(ListMoveImpl::ListReverse)
226 }
227
228 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
229 self.inner.size(score_director)
230 }
231}