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