solverforge_solver/heuristic/move/k_opt_reconnection.rs
1/* Reconnection patterns for k-opt moves.
2
3K-opt removes k edges from a tour, creating k+1 segments. These segments
4can be reconnected in different ways, with optional reversal of segments.
5
6# Zero-Erasure Design
7
8All data structures use plain arrays for compile-time construction:
9- `[u8; 6]` for segment order (max 6 segments for 5-opt)
10- `u8` bitmask for reversal flags
11- `const fn` constructors
12
13# Example
14
15```
16use solverforge_solver::heuristic::r#move::k_opt_reconnection::{
17KOptReconnection, THREE_OPT_RECONNECTIONS,
18};
19
20// 3-opt has 7 non-trivial reconnection patterns
21assert_eq!(THREE_OPT_RECONNECTIONS.len(), 7);
22
23// Each pattern specifies segment order and reversals
24let pattern = &THREE_OPT_RECONNECTIONS[0];
25assert_eq!(pattern.k(), 3);
26assert_eq!(pattern.segment_count(), 4);
27```
28*/
29
30/* A reconnection pattern for k-opt.
31
32Specifies how to reconnect segments after removing k edges.
33Uses plain arrays for zero-erasure compile-time construction.
34
35# Fields
36
37- `segment_order`: Permutation of segment indices `[0..segment_count)`.
38Index 0 is always 0 (first segment stays first).
39Unused slots (beyond `len`) are 0.
40
41- `reverse_mask`: Bitmask indicating which segments to reverse.
42Bit i is set if segment i should be reversed.
43
44- `len`: Number of valid segments (k+1 for k-opt).
45
46# Example
47
48```
49use solverforge_solver::heuristic::r#move::k_opt_reconnection::KOptReconnection;
50
51// 3-opt reconnection: swap B and C, reverse both
52// Original: A -> B -> C -> D
53// Result: A -> C' -> B' -> D
54let pattern = KOptReconnection::new([0, 2, 1, 3, 0, 0], 0b0110, 4);
55
56assert_eq!(pattern.k(), 3);
57assert!(pattern.should_reverse(1)); // B reversed
58assert!(pattern.should_reverse(2)); // C reversed
59assert!(!pattern.should_reverse(0)); // A not reversed
60```
61*/
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub struct KOptReconnection {
64 // Segment order as fixed array.
65 segment_order: [u8; 6],
66 // Bitmask of segments to reverse.
67 reverse_mask: u8,
68 // Number of valid segments.
69 len: u8,
70}
71
72impl KOptReconnection {
73 /* Creates a new reconnection pattern (const for compile-time use).
74
75 # Arguments
76
77 * `segment_order` - Permutation of segment indices
78 * `reverse_mask` - Bitmask of segments to reverse
79 * `len` - Number of valid segments (k+1)
80
81 # Example
82
83 ```
84 use solverforge_solver::heuristic::r#move::k_opt_reconnection::KOptReconnection;
85
86 const PATTERN: KOptReconnection = KOptReconnection::new(
87 [0, 2, 1, 3, 0, 0],
88 0b0000,
89 4,
90 );
91 assert_eq!(PATTERN.segment_at(1), 2);
92 ```
93 */
94 #[inline]
95 pub const fn new(segment_order: [u8; 6], reverse_mask: u8, len: u8) -> Self {
96 Self {
97 segment_order,
98 reverse_mask,
99 len,
100 }
101 }
102
103 /* Returns the k value (number of edges removed).
104
105 # Example
106
107 ```
108 use solverforge_solver::heuristic::r#move::k_opt_reconnection::THREE_OPT_RECONNECTIONS;
109
110 assert_eq!(THREE_OPT_RECONNECTIONS[0].k(), 3);
111 ```
112 */
113 #[inline]
114 pub const fn k(&self) -> usize {
115 (self.len - 1) as usize
116 }
117
118 /* Returns the number of segments (k+1).
119
120 # Example
121
122 ```
123 use solverforge_solver::heuristic::r#move::k_opt_reconnection::THREE_OPT_RECONNECTIONS;
124
125 assert_eq!(THREE_OPT_RECONNECTIONS[0].segment_count(), 4);
126 ```
127 */
128 #[inline]
129 pub const fn segment_count(&self) -> usize {
130 self.len as usize
131 }
132
133 /* Returns the segment index at position `pos` in the reconnected order.
134
135 # Example
136
137 ```
138 use solverforge_solver::heuristic::r#move::k_opt_reconnection::KOptReconnection;
139
140 // Pattern swaps segments 1 and 2: [A, C, B, D]
141 let pattern = KOptReconnection::new([0, 2, 1, 3, 0, 0], 0, 4);
142 assert_eq!(pattern.segment_at(0), 0); // A
143 assert_eq!(pattern.segment_at(1), 2); // C (was at position 2)
144 assert_eq!(pattern.segment_at(2), 1); // B (was at position 1)
145 assert_eq!(pattern.segment_at(3), 3); // D
146 ```
147 */
148 #[inline]
149 pub const fn segment_at(&self, pos: usize) -> usize {
150 self.segment_order[pos] as usize
151 }
152
153 /* Returns true if segment at index `idx` should be reversed.
154
155 # Example
156
157 ```
158 use solverforge_solver::heuristic::r#move::k_opt_reconnection::KOptReconnection;
159
160 let pattern = KOptReconnection::new([0, 1, 2, 3, 0, 0], 0b0110, 4);
161 assert!(!pattern.should_reverse(0));
162 assert!(pattern.should_reverse(1));
163 assert!(pattern.should_reverse(2));
164 assert!(!pattern.should_reverse(3));
165 ```
166 */
167 #[inline]
168 pub const fn should_reverse(&self, idx: usize) -> bool {
169 (self.reverse_mask >> idx) & 1 == 1
170 }
171
172 #[inline]
173 pub fn segment_order(&self) -> &[u8] {
174 &self.segment_order[..self.len as usize]
175 }
176
177 // Returns true if this is the identity reconnection (no change).
178 const fn is_identity(&self) -> bool {
179 if self.reverse_mask != 0 {
180 return false;
181 }
182 let mut i = 0;
183 while i < self.len as usize {
184 if self.segment_order[i] != i as u8 {
185 return false;
186 }
187 i += 1;
188 }
189 true
190 }
191}
192
193/// Pre-computed 3-opt reconnection patterns (7 non-trivial patterns).
194///
195/// Given segments `[A, B, C, D]` where edges are cut between each:
196///
197/// | # | Order | Reverse | Description |
198/// |---|------------|---------|--------------------------|
199/// | 0 | A-B'-C-D | B | Reverse B only |
200/// | 1 | A-B-C'-D | C | Reverse C only |
201/// | 2 | A-B'-C'-D | B,C | Reverse both (no swap) |
202/// | 3 | A-C-B-D | - | Swap B and C |
203/// | 4 | A-C'-B-D | C | Swap, reverse C |
204/// | 5 | A-C-B'-D | B | Swap, reverse B |
205/// | 6 | A-C'-B'-D | B,C | Swap, reverse both |
206///
207/// # Example
208///
209/// ```
210/// use solverforge_solver::heuristic::r#move::k_opt_reconnection::THREE_OPT_RECONNECTIONS;
211///
212/// assert_eq!(THREE_OPT_RECONNECTIONS.len(), 7);
213///
214/// // Pattern 3 swaps B and C without reversal
215/// let swap_pattern = &THREE_OPT_RECONNECTIONS[3];
216/// assert_eq!(swap_pattern.segment_at(1), 2); // C moved to position 1
217/// assert_eq!(swap_pattern.segment_at(2), 1); // B moved to position 2
218/// assert!(!swap_pattern.should_reverse(1));
219/// assert!(!swap_pattern.should_reverse(2));
220/// ```
221pub static THREE_OPT_RECONNECTIONS: &[KOptReconnection] = &[
222 // No swap, with reversals
223 KOptReconnection::new([0, 1, 2, 3, 0, 0], 0b0010, 4), // Reverse B
224 KOptReconnection::new([0, 1, 2, 3, 0, 0], 0b0100, 4), // Reverse C
225 KOptReconnection::new([0, 1, 2, 3, 0, 0], 0b0110, 4), // Reverse B and C
226 // Swap B and C
227 KOptReconnection::new([0, 2, 1, 3, 0, 0], 0b0000, 4), // No reversal
228 KOptReconnection::new([0, 2, 1, 3, 0, 0], 0b0010, 4), // Reverse B (now at pos 2)
229 KOptReconnection::new([0, 2, 1, 3, 0, 0], 0b0100, 4), // Reverse C (now at pos 1)
230 KOptReconnection::new([0, 2, 1, 3, 0, 0], 0b0110, 4), // Reverse both
231];
232
233/// Generates all non-trivial reconnection patterns for k-opt.
234///
235/// Enumerates all valid reconnections by:
236/// 1. Generating all permutations of middle segments
237/// 2. For each permutation, generating all reversal combinations
238/// 3. Filtering out the identity reconnection
239///
240/// # Arguments
241///
242/// * `k` - Number of edges to remove (2-5)
243///
244/// # Panics
245///
246/// Panics if k < 2 or k > 5.
247///
248/// # Example
249///
250/// ```
251/// use solverforge_solver::heuristic::r#move::k_opt_reconnection::enumerate_reconnections;
252///
253/// let patterns_2opt = enumerate_reconnections(2);
254/// assert_eq!(patterns_2opt.len(), 1); // Only reverse middle
255///
256/// let patterns_3opt = enumerate_reconnections(3);
257/// assert_eq!(patterns_3opt.len(), 7);
258///
259/// let patterns_4opt = enumerate_reconnections(4);
260/// assert_eq!(patterns_4opt.len(), 47); // 3! * 2^3 - 1
261/// ```
262pub fn enumerate_reconnections(k: usize) -> Vec<KOptReconnection> {
263 assert!((2..=5).contains(&k), "k must be between 2 and 5");
264
265 let num_segments = k + 1;
266 let num_middle = k - 1;
267
268 let mut result = Vec::new();
269
270 // Generate all permutations of middle segment indices [1..k]
271 let middle_indices: Vec<u8> = (1..k as u8).collect();
272 let permutations = generate_permutations(&middle_indices);
273
274 for perm in permutations {
275 // Build full segment order: [0, perm..., k]
276 let mut segment_order = [0u8; 6];
277 segment_order[0] = 0;
278 for (i, &idx) in perm.iter().enumerate() {
279 segment_order[i + 1] = idx;
280 }
281 segment_order[num_segments - 1] = (num_segments - 1) as u8;
282
283 // Generate all reversal combinations for middle segments
284 for mask in 0..(1u8 << num_middle) {
285 // Shift mask left by 1 since segment 0 is never reversed
286 let reverse_mask = mask << 1;
287
288 let reconnection =
289 KOptReconnection::new(segment_order, reverse_mask, num_segments as u8);
290
291 if !reconnection.is_identity() {
292 result.push(reconnection);
293 }
294 }
295 }
296
297 result
298}
299
300// Generates all permutations of a slice.
301fn generate_permutations(items: &[u8]) -> Vec<Vec<u8>> {
302 if items.is_empty() {
303 return vec![vec![]];
304 }
305 if items.len() == 1 {
306 return vec![vec![items[0]]];
307 }
308
309 let mut result = Vec::new();
310 for i in 0..items.len() {
311 let mut rest: Vec<u8> = items.to_vec();
312 let item = rest.remove(i);
313 for mut perm in generate_permutations(&rest) {
314 perm.insert(0, item);
315 result.push(perm);
316 }
317 }
318 result
319}
320
321#[cfg(test)]
322#[path = "k_opt_reconnection_tests.rs"]
323mod tests;