Skip to main content

solverforge_solver/heuristic/move/
list_reverse.rs

1//! ListReverseMove - reverses a segment within a list variable.
2//!
3//! This move reverses the order of elements in a range. Essential for
4//! TSP 2-opt optimization where reversing a tour segment can reduce distance.
5//!
6//! # Zero-Erasure Design
7//!
8//! Uses typed function pointers for list operations. No `dyn Any`, no downcasting.
9
10use std::fmt::Debug;
11use std::marker::PhantomData;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18/// A move that reverses a segment within a list.
19///
20/// This is the fundamental 2-opt move for TSP. Reversing a segment of the tour
21/// can significantly reduce total distance by eliminating crossing edges.
22///
23/// # Type Parameters
24/// * `S` - The planning solution type
25/// * `V` - The list element value type
26///
27/// # Example
28///
29/// ```
30/// use solverforge_solver::heuristic::r#move::ListReverseMove;
31/// use solverforge_core::domain::PlanningSolution;
32/// use solverforge_core::score::SimpleScore;
33///
34/// #[derive(Clone, Debug)]
35/// struct Tour { cities: Vec<i32>, score: Option<SimpleScore> }
36///
37/// impl PlanningSolution for Tour {
38///     type Score = SimpleScore;
39///     fn score(&self) -> Option<Self::Score> { self.score }
40///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
41/// }
42///
43/// fn list_len(s: &Tour, _: usize) -> usize { s.cities.len() }
44/// fn list_reverse(s: &mut Tour, _: usize, start: usize, end: usize) {
45///     s.cities[start..end].reverse();
46/// }
47///
48/// // Reverse segment [1..4) in tour: [A, B, C, D, E] -> [A, D, C, B, E]
49/// let m = ListReverseMove::<Tour, i32>::new(
50///     0, 1, 4,
51///     list_len, list_reverse,
52///     "cities", 0,
53/// );
54/// ```
55pub struct ListReverseMove<S, V> {
56    /// Entity index
57    entity_index: usize,
58    /// Start of range to reverse (inclusive)
59    start: usize,
60    /// End of range to reverse (exclusive)
61    end: usize,
62    /// Get list length for an entity
63    list_len: fn(&S, usize) -> usize,
64    /// Reverse elements in range [start, end)
65    list_reverse: fn(&mut S, usize, usize, usize),
66    variable_name: &'static str,
67    descriptor_index: usize,
68    _phantom: PhantomData<fn() -> V>,
69}
70
71impl<S, V> Clone for ListReverseMove<S, V> {
72    fn clone(&self) -> Self {
73        *self
74    }
75}
76
77impl<S, V> Copy for ListReverseMove<S, V> {}
78
79impl<S, V: Debug> Debug for ListReverseMove<S, V> {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.debug_struct("ListReverseMove")
82            .field("entity", &self.entity_index)
83            .field("range", &(self.start..self.end))
84            .field("variable_name", &self.variable_name)
85            .finish()
86    }
87}
88
89impl<S, V> ListReverseMove<S, V> {
90    /// Creates a new list reverse move with typed function pointers.
91    ///
92    /// # Arguments
93    /// * `entity_index` - Entity index
94    /// * `start` - Start of range (inclusive)
95    /// * `end` - End of range (exclusive)
96    /// * `list_len` - Function to get list length
97    /// * `list_reverse` - Function to reverse elements in range
98    /// * `variable_name` - Name of the list variable
99    /// * `descriptor_index` - Entity descriptor index
100    #[allow(clippy::too_many_arguments)]
101    pub fn new(
102        entity_index: usize,
103        start: usize,
104        end: usize,
105        list_len: fn(&S, usize) -> usize,
106        list_reverse: fn(&mut S, usize, usize, usize),
107        variable_name: &'static str,
108        descriptor_index: usize,
109    ) -> Self {
110        Self {
111            entity_index,
112            start,
113            end,
114            list_len,
115            list_reverse,
116            variable_name,
117            descriptor_index,
118            _phantom: PhantomData,
119        }
120    }
121
122    /// Returns the entity index.
123    pub fn entity_index(&self) -> usize {
124        self.entity_index
125    }
126
127    /// Returns the range start.
128    pub fn start(&self) -> usize {
129        self.start
130    }
131
132    /// Returns the range end.
133    pub fn end(&self) -> usize {
134        self.end
135    }
136
137    /// Returns the segment length.
138    pub fn segment_len(&self) -> usize {
139        self.end.saturating_sub(self.start)
140    }
141}
142
143impl<S, V> Move<S> for ListReverseMove<S, V>
144where
145    S: PlanningSolution,
146    V: Clone + Send + Sync + Debug + 'static,
147{
148    fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
149        let solution = score_director.working_solution();
150
151        // Range must have at least 2 elements to be meaningful
152        if self.end <= self.start + 1 {
153            return false;
154        }
155
156        // Check range is within bounds
157        let len = (self.list_len)(solution, self.entity_index);
158        if self.end > len {
159            return false;
160        }
161
162        true
163    }
164
165    fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
166        // Notify before change
167        score_director.before_variable_changed(
168            self.descriptor_index,
169            self.entity_index,
170            self.variable_name,
171        );
172
173        // Reverse the segment
174        (self.list_reverse)(
175            score_director.working_solution_mut(),
176            self.entity_index,
177            self.start,
178            self.end,
179        );
180
181        // Notify after change
182        score_director.after_variable_changed(
183            self.descriptor_index,
184            self.entity_index,
185            self.variable_name,
186        );
187
188        // Register undo - reversing twice restores original
189        let list_reverse = self.list_reverse;
190        let entity = self.entity_index;
191        let start = self.start;
192        let end = self.end;
193
194        score_director.register_undo(Box::new(move |s: &mut S| {
195            list_reverse(s, entity, start, end);
196        }));
197    }
198
199    fn descriptor_index(&self) -> usize {
200        self.descriptor_index
201    }
202
203    fn entity_indices(&self) -> &[usize] {
204        std::slice::from_ref(&self.entity_index)
205    }
206
207    fn variable_name(&self) -> &str {
208        self.variable_name
209    }
210}