Skip to main content

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;