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}