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::{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}