Skip to main content

problemreductions/rules/unitdiskmapping/ksg/
gadgets.rs

1//! KSG unweighted square lattice gadgets for resolving crossings.
2//!
3//! This module contains all gadget implementations for the King's SubGraph (KSG)
4//! unweighted mapping: KsgCross, KsgTurn, KsgWTurn, KsgBranch, KsgBranchFix, KsgTCon,
5//! KsgTrivialTurn, KsgEndTurn, KsgBranchFixB, KsgDanglingLeg, and their rotated/reflected variants.
6
7use super::super::grid::{CellState, MappingGrid};
8use super::super::traits::{apply_gadget, pattern_matches, Pattern, PatternCell};
9use crate::rules::unitdiskmapping::{mapping_integer_overflow, mapping_invalid};
10use crate::rules::ReductionError;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// Type alias for pattern factory function used in crossing gadget matching.
15type PatternFactory = Box<dyn Fn() -> Box<dyn KsgPatternBoxed>>;
16
17/// Type alias for source graph representation: (locations, pin_edges, source_pins).
18pub type SourceGraph = (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>);
19
20// ============================================================================
21// Crossing Gadgets - matching Julia's gadgets.jl exactly
22// ============================================================================
23
24/// Crossing gadget for resolving two crossing copy-lines.
25///
26/// `KsgCross<true>`: connected crossing (edges share a vertex), size (3,3)
27/// `KsgCross<false>`: disconnected crossing, size (4,5)
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub struct KsgCross<const CON: bool>;
30
31impl Pattern for KsgCross<true> {
32    fn size(&self) -> (usize, usize) {
33        (3, 3)
34    }
35
36    fn cross_location(&self) -> (usize, usize) {
37        (2, 2)
38    }
39
40    fn is_connected(&self) -> bool {
41        true
42    }
43
44    fn is_cross_gadget(&self) -> bool {
45        true
46    }
47
48    fn connected_nodes(&self) -> Vec<usize> {
49        vec![0, 5]
50    }
51
52    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
53        let locs = vec![(2, 1), (2, 2), (2, 3), (1, 2), (2, 2), (3, 2)];
54        let edges = vec![(0, 1), (1, 2), (3, 4), (4, 5), (0, 5)];
55        let pins = vec![0, 3, 5, 2];
56        (locs, edges, pins)
57    }
58
59    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
60        let locs = vec![(2, 1), (2, 2), (2, 3), (1, 2), (3, 2)];
61        let pins = vec![0, 3, 4, 2];
62        (locs, pins)
63    }
64
65    fn mis_overhead(&self) -> i64 {
66        -1
67    }
68
69    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
70        [
71            (5, 5),
72            (12, 12),
73            (8, 0),
74            (1, 0),
75            (0, 0),
76            (6, 6),
77            (11, 11),
78            (9, 9),
79            (14, 14),
80            (3, 3),
81            (7, 7),
82            (4, 0),
83            (13, 13),
84            (15, 15),
85            (2, 0),
86            (10, 10),
87        ]
88        .into_iter()
89        .collect()
90    }
91
92    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
93        let mut map = HashMap::new();
94        map.insert(0, vec![vec![false, true, false, false, true, false]]);
95        map.insert(1, vec![vec![true, false, false, false, true, false]]);
96        map.insert(3, vec![vec![true, false, false, true, false, false]]);
97        map.insert(4, vec![vec![false, true, false, false, false, true]]);
98        map.insert(6, vec![vec![false, true, false, true, false, true]]);
99        map.insert(8, vec![vec![false, false, true, false, true, false]]);
100        map.insert(9, vec![vec![true, false, true, false, true, false]]);
101        map.insert(10, vec![vec![false, false, true, true, false, false]]);
102        map.insert(11, vec![vec![true, false, true, true, false, false]]);
103        map.insert(12, vec![vec![false, false, true, false, false, true]]);
104        map.insert(14, vec![vec![false, false, true, true, false, true]]);
105        map.insert(5, vec![]);
106        map.insert(7, vec![]);
107        map.insert(13, vec![]);
108        map.insert(15, vec![]);
109        map.insert(2, vec![vec![false, true, false, true, false, false]]);
110        map
111    }
112}
113
114impl Pattern for KsgCross<false> {
115    fn size(&self) -> (usize, usize) {
116        (4, 5)
117    }
118
119    fn cross_location(&self) -> (usize, usize) {
120        (2, 3)
121    }
122
123    fn is_connected(&self) -> bool {
124        false
125    }
126
127    fn is_cross_gadget(&self) -> bool {
128        true
129    }
130
131    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
132        let locs = vec![
133            (2, 1),
134            (2, 2),
135            (2, 3),
136            (2, 4),
137            (2, 5),
138            (1, 3),
139            (2, 3),
140            (3, 3),
141            (4, 3),
142        ];
143        let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4), (5, 6), (6, 7), (7, 8)];
144        let pins = vec![0, 5, 8, 4];
145        (locs, edges, pins)
146    }
147
148    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
149        let locs = vec![
150            (2, 1),
151            (2, 2),
152            (2, 3),
153            (2, 4),
154            (2, 5),
155            (1, 3),
156            (3, 3),
157            (4, 3),
158            (3, 2),
159            (3, 4),
160        ];
161        let pins = vec![0, 5, 7, 4];
162        (locs, pins)
163    }
164
165    fn mis_overhead(&self) -> i64 {
166        -1
167    }
168
169    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
170        [
171            (5, 4),
172            (12, 4),
173            (8, 0),
174            (1, 0),
175            (0, 0),
176            (6, 0),
177            (11, 11),
178            (9, 9),
179            (14, 2),
180            (3, 2),
181            (7, 2),
182            (4, 4),
183            (13, 13),
184            (15, 11),
185            (2, 2),
186            (10, 2),
187        ]
188        .into_iter()
189        .collect()
190    }
191
192    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
193        let mut map = HashMap::new();
194        map.insert(
195            0,
196            vec![
197                vec![false, true, false, true, false, false, false, true, false],
198                vec![false, true, false, true, false, false, true, false, false],
199            ],
200        );
201        map.insert(
202            2,
203            vec![vec![
204                false, true, false, true, false, true, false, true, false,
205            ]],
206        );
207        map.insert(
208            4,
209            vec![vec![
210                false, true, false, true, false, false, true, false, true,
211            ]],
212        );
213        map.insert(
214            9,
215            vec![
216                vec![true, false, true, false, true, false, false, true, false],
217                vec![true, false, true, false, true, false, true, false, false],
218            ],
219        );
220        map.insert(
221            11,
222            vec![vec![
223                true, false, true, false, true, true, false, true, false,
224            ]],
225        );
226        map.insert(
227            13,
228            vec![vec![
229                true, false, true, false, true, false, true, false, true,
230            ]],
231        );
232        for i in [1, 3, 5, 6, 7, 8, 10, 12, 14, 15] {
233            map.entry(i).or_insert_with(Vec::new);
234        }
235        map
236    }
237}
238
239/// Turn gadget for 90-degree turns in copy-lines.
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241pub struct KsgTurn;
242
243impl Pattern for KsgTurn {
244    fn size(&self) -> (usize, usize) {
245        (4, 4)
246    }
247    fn cross_location(&self) -> (usize, usize) {
248        (3, 2)
249    }
250    fn is_connected(&self) -> bool {
251        false
252    }
253
254    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
255        let locs = vec![(1, 2), (2, 2), (3, 2), (3, 3), (3, 4)];
256        let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4)];
257        let pins = vec![0, 4];
258        (locs, edges, pins)
259    }
260
261    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
262        let locs = vec![(1, 2), (2, 3), (3, 4)];
263        let pins = vec![0, 2];
264        (locs, pins)
265    }
266
267    fn mis_overhead(&self) -> i64 {
268        -1
269    }
270
271    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
272        [(0, 0), (2, 0), (3, 3), (1, 0)].into_iter().collect()
273    }
274
275    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
276        let mut map = HashMap::new();
277        map.insert(0, vec![vec![false, true, false, true, false]]);
278        map.insert(
279            1,
280            vec![
281                vec![true, false, true, false, false],
282                vec![true, false, false, true, false],
283            ],
284        );
285        map.insert(
286            2,
287            vec![
288                vec![false, true, false, false, true],
289                vec![false, false, true, false, true],
290            ],
291        );
292        map.insert(3, vec![vec![true, false, true, false, true]]);
293        map
294    }
295}
296
297/// W-shaped turn gadget.
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299pub struct KsgWTurn;
300
301impl Pattern for KsgWTurn {
302    fn size(&self) -> (usize, usize) {
303        (4, 4)
304    }
305    fn cross_location(&self) -> (usize, usize) {
306        (2, 2)
307    }
308    fn is_connected(&self) -> bool {
309        false
310    }
311
312    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
313        let locs = vec![(2, 3), (2, 4), (3, 2), (3, 3), (4, 2)];
314        let edges = vec![(0, 1), (0, 3), (2, 3), (2, 4)];
315        let pins = vec![1, 4];
316        (locs, edges, pins)
317    }
318
319    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
320        let locs = vec![(2, 4), (3, 3), (4, 2)];
321        let pins = vec![0, 2];
322        (locs, pins)
323    }
324
325    fn mis_overhead(&self) -> i64 {
326        -1
327    }
328
329    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
330        [(0, 0), (2, 0), (3, 3), (1, 0)].into_iter().collect()
331    }
332
333    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
334        let mut map = HashMap::new();
335        map.insert(0, vec![vec![true, false, true, false, false]]);
336        map.insert(
337            1,
338            vec![
339                vec![false, true, false, true, false],
340                vec![false, true, true, false, false],
341            ],
342        );
343        map.insert(
344            2,
345            vec![
346                vec![false, false, false, true, true],
347                vec![true, false, false, false, true],
348            ],
349        );
350        map.insert(3, vec![vec![false, true, false, true, true]]);
351        map
352    }
353}
354
355/// Branch gadget for T-junctions.
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
357pub struct KsgBranch;
358
359impl Pattern for KsgBranch {
360    fn size(&self) -> (usize, usize) {
361        (5, 4)
362    }
363    fn cross_location(&self) -> (usize, usize) {
364        (3, 2)
365    }
366    fn is_connected(&self) -> bool {
367        false
368    }
369
370    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
371        let locs = vec![
372            (1, 2),
373            (2, 2),
374            (3, 2),
375            (3, 3),
376            (3, 4),
377            (4, 3),
378            (4, 2),
379            (5, 2),
380        ];
381        let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4), (3, 5), (5, 6), (6, 7)];
382        let pins = vec![0, 4, 7];
383        (locs, edges, pins)
384    }
385
386    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
387        let locs = vec![(1, 2), (2, 3), (3, 2), (3, 4), (4, 3), (5, 2)];
388        let pins = vec![0, 3, 5];
389        (locs, pins)
390    }
391
392    fn mis_overhead(&self) -> i64 {
393        -1
394    }
395
396    // Julia: sw[[4]] .= 3 (node 4 = 0-indexed 3 has weight 3)
397    fn source_weights(&self) -> Vec<i64> {
398        vec![2, 2, 2, 3, 2, 2, 2, 2]
399    }
400    // Julia: mw[[2]] .= 3 (mapped node 2 = 0-indexed 1 has weight 3)
401    fn mapped_weights(&self) -> Vec<i64> {
402        vec![2, 3, 2, 2, 2, 2]
403    }
404
405    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
406        [
407            (0, 0),
408            (4, 0),
409            (5, 5),
410            (6, 6),
411            (2, 0),
412            (7, 7),
413            (3, 3),
414            (1, 0),
415        ]
416        .into_iter()
417        .collect()
418    }
419
420    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
421        let mut map = HashMap::new();
422        map.insert(
423            0,
424            vec![vec![false, true, false, true, false, false, true, false]],
425        );
426        map.insert(
427            3,
428            vec![
429                vec![true, false, true, false, true, false, true, false],
430                vec![true, false, true, false, true, true, false, false],
431            ],
432        );
433        map.insert(
434            5,
435            vec![vec![true, false, true, false, false, true, false, true]],
436        );
437        map.insert(
438            6,
439            vec![
440                vec![false, false, true, false, true, true, false, true],
441                vec![false, true, false, false, true, true, false, true],
442            ],
443        );
444        map.insert(
445            7,
446            vec![vec![true, false, true, false, true, true, false, true]],
447        );
448        for i in [1, 2, 4] {
449            map.insert(i, vec![]);
450        }
451        map
452    }
453}
454
455/// Branch fix gadget for simplifying branches.
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
457pub struct KsgBranchFix;
458
459impl Pattern for KsgBranchFix {
460    fn size(&self) -> (usize, usize) {
461        (4, 4)
462    }
463    fn cross_location(&self) -> (usize, usize) {
464        (2, 2)
465    }
466    fn is_connected(&self) -> bool {
467        false
468    }
469
470    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
471        let locs = vec![(1, 2), (2, 2), (2, 3), (3, 3), (3, 2), (4, 2)];
472        let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)];
473        let pins = vec![0, 5];
474        (locs, edges, pins)
475    }
476
477    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
478        let locs = vec![(1, 2), (2, 2), (3, 2), (4, 2)];
479        let pins = vec![0, 3];
480        (locs, pins)
481    }
482
483    fn mis_overhead(&self) -> i64 {
484        -1
485    }
486
487    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
488        [(0, 0), (2, 2), (3, 1), (1, 1)].into_iter().collect()
489    }
490
491    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
492        let mut map = HashMap::new();
493        map.insert(
494            0,
495            vec![
496                vec![false, true, false, true, false, false],
497                vec![false, true, false, false, true, false],
498                vec![false, false, true, false, true, false],
499            ],
500        );
501        map.insert(1, vec![vec![true, false, true, false, true, false]]);
502        map.insert(2, vec![vec![false, true, false, true, false, true]]);
503        map.insert(
504            3,
505            vec![
506                vec![true, false, false, true, false, true],
507                vec![true, false, true, false, false, true],
508            ],
509        );
510        map
511    }
512}
513
514/// T-connection gadget.
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
516pub struct KsgTCon;
517
518impl Pattern for KsgTCon {
519    fn size(&self) -> (usize, usize) {
520        (3, 4)
521    }
522    fn cross_location(&self) -> (usize, usize) {
523        (2, 2)
524    }
525    fn is_connected(&self) -> bool {
526        true
527    }
528    fn connected_nodes(&self) -> Vec<usize> {
529        vec![0, 1]
530    }
531
532    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
533        let locs = vec![(1, 2), (2, 1), (2, 2), (3, 2)];
534        let edges = vec![(0, 1), (0, 2), (2, 3)];
535        let pins = vec![0, 1, 3];
536        (locs, edges, pins)
537    }
538
539    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
540        let locs = vec![(1, 2), (2, 1), (2, 3), (3, 2)];
541        let pins = vec![0, 1, 3];
542        (locs, pins)
543    }
544
545    fn mis_overhead(&self) -> i64 {
546        0
547    }
548
549    // Julia: sw[[2]] .= 1 (node 2 = 0-indexed 1 has weight 1)
550    fn source_weights(&self) -> Vec<i64> {
551        vec![2, 1, 2, 2]
552    }
553    // Julia: mw[[2]] .= 1 (mapped node 2 = 0-indexed 1 has weight 1)
554    fn mapped_weights(&self) -> Vec<i64> {
555        vec![2, 1, 2, 2]
556    }
557
558    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
559        [
560            (0, 0),
561            (4, 0),
562            (5, 5),
563            (6, 6),
564            (2, 2),
565            (7, 7),
566            (3, 3),
567            (1, 0),
568        ]
569        .into_iter()
570        .collect()
571    }
572
573    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
574        let mut map = HashMap::new();
575        map.insert(0, vec![vec![false, false, true, false]]);
576        map.insert(1, vec![vec![true, false, false, false]]);
577        map.insert(2, vec![vec![false, true, true, false]]);
578        map.insert(4, vec![vec![false, false, false, true]]);
579        map.insert(5, vec![vec![true, false, false, true]]);
580        map.insert(6, vec![vec![false, true, false, true]]);
581        map.insert(3, vec![]);
582        map.insert(7, vec![]);
583        map
584    }
585}
586
587/// Trivial turn gadget for simple diagonal turns.
588#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
589pub struct KsgTrivialTurn;
590
591impl Pattern for KsgTrivialTurn {
592    fn size(&self) -> (usize, usize) {
593        (2, 2)
594    }
595    fn cross_location(&self) -> (usize, usize) {
596        (2, 2)
597    }
598    fn is_connected(&self) -> bool {
599        true
600    }
601    fn connected_nodes(&self) -> Vec<usize> {
602        vec![0, 1]
603    }
604
605    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
606        let locs = vec![(1, 2), (2, 1)];
607        let edges = vec![(0, 1)];
608        let pins = vec![0, 1];
609        (locs, edges, pins)
610    }
611
612    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
613        let locs = vec![(1, 2), (2, 1)];
614        let pins = vec![0, 1];
615        (locs, pins)
616    }
617
618    fn mis_overhead(&self) -> i64 {
619        0
620    }
621
622    // Julia: sw[[1,2]] .= 1 (nodes 1,2 have weight 1)
623    fn source_weights(&self) -> Vec<i64> {
624        vec![1, 1]
625    }
626    // Julia: mw[[1,2]] .= 1 (mapped nodes 1,2 have weight 1)
627    fn mapped_weights(&self) -> Vec<i64> {
628        vec![1, 1]
629    }
630
631    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
632        [(0, 0), (2, 2), (3, 3), (1, 1)].into_iter().collect()
633    }
634
635    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
636        let mut map = HashMap::new();
637        map.insert(0, vec![vec![false, false]]);
638        map.insert(1, vec![vec![true, false]]);
639        map.insert(2, vec![vec![false, true]]);
640        map.insert(3, vec![]);
641        map
642    }
643}
644
645/// End turn gadget for line terminations.
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647pub struct KsgEndTurn;
648
649impl Pattern for KsgEndTurn {
650    fn size(&self) -> (usize, usize) {
651        (3, 4)
652    }
653    fn cross_location(&self) -> (usize, usize) {
654        (2, 2)
655    }
656    fn is_connected(&self) -> bool {
657        false
658    }
659
660    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
661        let locs = vec![(1, 2), (2, 2), (2, 3)];
662        let edges = vec![(0, 1), (1, 2)];
663        let pins = vec![0];
664        (locs, edges, pins)
665    }
666
667    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
668        let locs = vec![(1, 2)];
669        let pins = vec![0];
670        (locs, pins)
671    }
672
673    fn mis_overhead(&self) -> i64 {
674        -1
675    }
676
677    // Julia: sw[[3]] .= 1 (node 3 = 0-indexed 2 has weight 1)
678    fn source_weights(&self) -> Vec<i64> {
679        vec![2, 2, 1]
680    }
681    // Julia: mw[[1]] .= 1 (mapped node 1 = 0-indexed 0 has weight 1)
682    fn mapped_weights(&self) -> Vec<i64> {
683        vec![1]
684    }
685
686    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
687        [(0, 0), (1, 1)].into_iter().collect()
688    }
689
690    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
691        let mut map = HashMap::new();
692        map.insert(0, vec![vec![false, false, true], vec![false, true, false]]);
693        map.insert(1, vec![vec![true, false, true]]);
694        map
695    }
696}
697
698/// Alternate branch fix gadget.
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
700pub struct KsgBranchFixB;
701
702impl Pattern for KsgBranchFixB {
703    fn size(&self) -> (usize, usize) {
704        (4, 4)
705    }
706    fn cross_location(&self) -> (usize, usize) {
707        (2, 2)
708    }
709    fn is_connected(&self) -> bool {
710        false
711    }
712
713    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
714        let locs = vec![(2, 3), (3, 2), (3, 3), (4, 2)];
715        let edges = vec![(0, 2), (1, 2), (1, 3)];
716        let pins = vec![0, 3];
717        (locs, edges, pins)
718    }
719
720    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
721        let locs = vec![(3, 2), (4, 2)];
722        let pins = vec![0, 1];
723        (locs, pins)
724    }
725
726    fn mis_overhead(&self) -> i64 {
727        -1
728    }
729
730    // Julia: sw[[1]] .= 1 (node 1 = 0-indexed 0 has weight 1)
731    fn source_weights(&self) -> Vec<i64> {
732        vec![1, 2, 2, 2]
733    }
734    // Julia: mw[[1]] .= 1 (mapped node 1 = 0-indexed 0 has weight 1)
735    fn mapped_weights(&self) -> Vec<i64> {
736        vec![1, 2]
737    }
738
739    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
740        [(0, 0), (2, 2), (3, 3), (1, 1)].into_iter().collect()
741    }
742
743    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
744        let mut map = HashMap::new();
745        map.insert(
746            0,
747            vec![
748                vec![false, false, true, false],
749                vec![false, true, false, false],
750            ],
751        );
752        map.insert(1, vec![vec![true, true, false, false]]);
753        map.insert(2, vec![vec![false, false, true, true]]);
754        map.insert(3, vec![vec![true, false, false, true]]);
755        map
756    }
757}
758
759// ============================================================================
760// Rotated and Reflected Gadgets
761// ============================================================================
762
763/// A rotated version of a gadget.
764#[derive(Debug, Clone)]
765pub struct KsgRotatedGadget<G: Pattern> {
766    pub gadget: G,
767    /// Number of 90-degree clockwise rotations (0-3).
768    pub n: usize,
769}
770
771impl<G: Pattern> KsgRotatedGadget<G> {
772    pub fn new(gadget: G, n: usize) -> Self {
773        Self { gadget, n: n % 4 }
774    }
775}
776
777fn rotate90(loc: (i64, i64)) -> (i64, i64) {
778    (-loc.1, loc.0)
779}
780
781fn rotate_around_center(loc: (usize, usize), center: (usize, usize), n: usize) -> (i64, i64) {
782    let center = (
783        i64::try_from(center.0).expect("gadget coordinates fit i64"),
784        i64::try_from(center.1).expect("gadget coordinates fit i64"),
785    );
786    let mut dx = i64::try_from(loc.0).expect("gadget coordinates fit i64") - center.0;
787    let mut dy = i64::try_from(loc.1).expect("gadget coordinates fit i64") - center.1;
788    for _ in 0..n {
789        let (nx, ny) = rotate90((dx, dy));
790        dx = nx;
791        dy = ny;
792    }
793    (center.0 + dx, center.1 + dy)
794}
795
796impl<G: Pattern> Pattern for KsgRotatedGadget<G> {
797    fn size(&self) -> (usize, usize) {
798        let (m, n) = self.gadget.size();
799        if self.n.is_multiple_of(2) {
800            (m, n)
801        } else {
802            (n, m)
803        }
804    }
805
806    fn cross_location(&self) -> (usize, usize) {
807        let center = self.gadget.cross_location();
808        let (m, n) = self.gadget.size();
809        let rotated = rotate_around_center(center, center, self.n);
810        let corners = [(1, 1), (m, n)];
811        let rotated_corners: Vec<_> = corners
812            .iter()
813            .map(|&c| rotate_around_center(c, center, self.n))
814            .collect();
815        let min_r = rotated_corners.iter().map(|c| c.0).min().unwrap();
816        let min_c = rotated_corners.iter().map(|c| c.1).min().unwrap();
817        let offset_r = 1 - min_r;
818        let offset_c = 1 - min_c;
819        (
820            (rotated.0 + offset_r) as usize,
821            (rotated.1 + offset_c) as usize,
822        )
823    }
824
825    fn is_connected(&self) -> bool {
826        self.gadget.is_connected()
827    }
828    fn is_cross_gadget(&self) -> bool {
829        self.gadget.is_cross_gadget()
830    }
831    fn connected_nodes(&self) -> Vec<usize> {
832        self.gadget.connected_nodes()
833    }
834
835    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
836        let (locs, edges, pins) = self.gadget.source_graph();
837        let center = self.gadget.cross_location();
838        let (m, n) = self.gadget.size();
839        let corners = [(1usize, 1usize), (m, n)];
840        let rotated_corners: Vec<_> = corners
841            .iter()
842            .map(|&c| rotate_around_center(c, center, self.n))
843            .collect();
844        let min_r = rotated_corners.iter().map(|c| c.0).min().unwrap();
845        let min_c = rotated_corners.iter().map(|c| c.1).min().unwrap();
846        let offset_r = 1 - min_r;
847        let offset_c = 1 - min_c;
848        let new_locs: Vec<_> = locs
849            .into_iter()
850            .map(|loc| {
851                let rotated = rotate_around_center(loc, center, self.n);
852                (
853                    (rotated.0 + offset_r) as usize,
854                    (rotated.1 + offset_c) as usize,
855                )
856            })
857            .collect();
858        (new_locs, edges, pins)
859    }
860
861    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
862        let (locs, pins) = self.gadget.mapped_graph();
863        let center = self.gadget.cross_location();
864        let (m, n) = self.gadget.size();
865        let corners = [(1usize, 1usize), (m, n)];
866        let rotated_corners: Vec<_> = corners
867            .iter()
868            .map(|&c| rotate_around_center(c, center, self.n))
869            .collect();
870        let min_r = rotated_corners.iter().map(|c| c.0).min().unwrap();
871        let min_c = rotated_corners.iter().map(|c| c.1).min().unwrap();
872        let offset_r = 1 - min_r;
873        let offset_c = 1 - min_c;
874        let new_locs: Vec<_> = locs
875            .into_iter()
876            .map(|loc| {
877                let rotated = rotate_around_center(loc, center, self.n);
878                (
879                    (rotated.0 + offset_r) as usize,
880                    (rotated.1 + offset_c) as usize,
881                )
882            })
883            .collect();
884        (new_locs, pins)
885    }
886
887    fn mis_overhead(&self) -> i64 {
888        self.gadget.mis_overhead()
889    }
890    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
891        self.gadget.mapped_entry_to_compact()
892    }
893    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
894        self.gadget.source_entry_to_configs()
895    }
896
897    // Weights don't change with rotation - delegate to inner gadget
898    fn source_weights(&self) -> Vec<i64> {
899        self.gadget.source_weights()
900    }
901    fn mapped_weights(&self) -> Vec<i64> {
902        self.gadget.mapped_weights()
903    }
904}
905
906/// Mirror axis for reflection.
907#[derive(Debug, Clone, Copy, PartialEq, Eq)]
908pub enum Mirror {
909    X,
910    Y,
911    Diag,
912    OffDiag,
913}
914
915/// A reflected version of a gadget.
916#[derive(Debug, Clone)]
917pub struct KsgReflectedGadget<G: Pattern> {
918    pub gadget: G,
919    pub mirror: Mirror,
920}
921
922impl<G: Pattern> KsgReflectedGadget<G> {
923    pub fn new(gadget: G, mirror: Mirror) -> Self {
924        Self { gadget, mirror }
925    }
926}
927
928fn reflect(loc: (i64, i64), mirror: Mirror) -> (i64, i64) {
929    match mirror {
930        Mirror::X => (loc.0, -loc.1),
931        Mirror::Y => (-loc.0, loc.1),
932        Mirror::Diag => (-loc.1, -loc.0),
933        Mirror::OffDiag => (loc.1, loc.0),
934    }
935}
936
937fn reflect_around_center(
938    loc: (usize, usize),
939    center: (usize, usize),
940    mirror: Mirror,
941) -> (i64, i64) {
942    let center = (
943        i64::try_from(center.0).expect("gadget coordinates fit i64"),
944        i64::try_from(center.1).expect("gadget coordinates fit i64"),
945    );
946    let dx = i64::try_from(loc.0).expect("gadget coordinates fit i64") - center.0;
947    let dy = i64::try_from(loc.1).expect("gadget coordinates fit i64") - center.1;
948    let (nx, ny) = reflect((dx, dy), mirror);
949    (center.0 + nx, center.1 + ny)
950}
951
952impl<G: Pattern> Pattern for KsgReflectedGadget<G> {
953    fn size(&self) -> (usize, usize) {
954        let (m, n) = self.gadget.size();
955        match self.mirror {
956            Mirror::X | Mirror::Y => (m, n),
957            Mirror::Diag | Mirror::OffDiag => (n, m),
958        }
959    }
960
961    fn cross_location(&self) -> (usize, usize) {
962        let center = self.gadget.cross_location();
963        let (m, n) = self.gadget.size();
964        let reflected = reflect_around_center(center, center, self.mirror);
965        let corners = [(1, 1), (m, n)];
966        let reflected_corners: Vec<_> = corners
967            .iter()
968            .map(|&c| reflect_around_center(c, center, self.mirror))
969            .collect();
970        let min_r = reflected_corners.iter().map(|c| c.0).min().unwrap();
971        let min_c = reflected_corners.iter().map(|c| c.1).min().unwrap();
972        let offset_r = 1 - min_r;
973        let offset_c = 1 - min_c;
974        (
975            (reflected.0 + offset_r) as usize,
976            (reflected.1 + offset_c) as usize,
977        )
978    }
979
980    fn is_connected(&self) -> bool {
981        self.gadget.is_connected()
982    }
983    fn is_cross_gadget(&self) -> bool {
984        self.gadget.is_cross_gadget()
985    }
986    fn connected_nodes(&self) -> Vec<usize> {
987        self.gadget.connected_nodes()
988    }
989
990    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
991        let (locs, edges, pins) = self.gadget.source_graph();
992        let center = self.gadget.cross_location();
993        let (m, n) = self.gadget.size();
994        let corners = [(1usize, 1usize), (m, n)];
995        let reflected_corners: Vec<_> = corners
996            .iter()
997            .map(|&c| reflect_around_center(c, center, self.mirror))
998            .collect();
999        let min_r = reflected_corners.iter().map(|c| c.0).min().unwrap();
1000        let min_c = reflected_corners.iter().map(|c| c.1).min().unwrap();
1001        let offset_r = 1 - min_r;
1002        let offset_c = 1 - min_c;
1003        let new_locs: Vec<_> = locs
1004            .into_iter()
1005            .map(|loc| {
1006                let reflected = reflect_around_center(loc, center, self.mirror);
1007                (
1008                    (reflected.0 + offset_r) as usize,
1009                    (reflected.1 + offset_c) as usize,
1010                )
1011            })
1012            .collect();
1013        (new_locs, edges, pins)
1014    }
1015
1016    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
1017        let (locs, pins) = self.gadget.mapped_graph();
1018        let center = self.gadget.cross_location();
1019        let (m, n) = self.gadget.size();
1020        let corners = [(1usize, 1usize), (m, n)];
1021        let reflected_corners: Vec<_> = corners
1022            .iter()
1023            .map(|&c| reflect_around_center(c, center, self.mirror))
1024            .collect();
1025        let min_r = reflected_corners.iter().map(|c| c.0).min().unwrap();
1026        let min_c = reflected_corners.iter().map(|c| c.1).min().unwrap();
1027        let offset_r = 1 - min_r;
1028        let offset_c = 1 - min_c;
1029        let new_locs: Vec<_> = locs
1030            .into_iter()
1031            .map(|loc| {
1032                let reflected = reflect_around_center(loc, center, self.mirror);
1033                (
1034                    (reflected.0 + offset_r) as usize,
1035                    (reflected.1 + offset_c) as usize,
1036                )
1037            })
1038            .collect();
1039        (new_locs, pins)
1040    }
1041
1042    fn mis_overhead(&self) -> i64 {
1043        self.gadget.mis_overhead()
1044    }
1045    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
1046        self.gadget.mapped_entry_to_compact()
1047    }
1048    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
1049        self.gadget.source_entry_to_configs()
1050    }
1051
1052    // Weights don't change with reflection - delegate to inner gadget
1053    fn source_weights(&self) -> Vec<i64> {
1054        self.gadget.source_weights()
1055    }
1056    fn mapped_weights(&self) -> Vec<i64> {
1057        self.gadget.mapped_weights()
1058    }
1059}
1060
1061// ============================================================================
1062// Simplifier Patterns
1063// ============================================================================
1064
1065/// Dangling leg simplifier pattern.
1066///
1067/// Julia pattern:
1068/// ```text
1069/// Source:       Mapped:
1070/// . . .         . . .
1071/// . X .    =>   . . .
1072/// . X .         . . .
1073/// . X .         . X .
1074/// ```
1075/// Removes 2 nodes from a dangling chain, keeping only the endpoint.
1076#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1077pub struct KsgDanglingLeg;
1078
1079impl Pattern for KsgDanglingLeg {
1080    fn size(&self) -> (usize, usize) {
1081        (4, 3)
1082    }
1083    // Julia: cross_location = size ./ 2 = (4/2, 3/2) = (2, 1)
1084    fn cross_location(&self) -> (usize, usize) {
1085        (2, 1)
1086    }
1087    fn is_connected(&self) -> bool {
1088        false
1089    }
1090
1091    fn source_graph(&self) -> (Vec<(usize, usize)>, Vec<(usize, usize)>, Vec<usize>) {
1092        // Julia: 3 nodes at (2,2), (3,2), (4,2) - vertical chain in column 2
1093        let locs = vec![(2, 2), (3, 2), (4, 2)];
1094        let edges = vec![(0, 1), (1, 2)];
1095        // Boundary node: only (4,2) is on boundary (row 4 = m for 4x3 pattern)
1096        let pins = vec![2];
1097        (locs, edges, pins)
1098    }
1099
1100    fn mapped_graph(&self) -> (Vec<(usize, usize)>, Vec<usize>) {
1101        // Julia: 1 node at (4,2) - the bottom endpoint
1102        let locs = vec![(4, 2)];
1103        let pins = vec![0];
1104        (locs, pins)
1105    }
1106
1107    fn mis_overhead(&self) -> i64 {
1108        -1
1109    }
1110
1111    // Julia: sw[[1]] .= 1 (node 1 = 0-indexed 0 has weight 1)
1112    fn source_weights(&self) -> Vec<i64> {
1113        vec![1, 2, 2]
1114    }
1115    // Julia: mw[[1]] .= 1 (mapped node 1 = 0-indexed 0 has weight 1)
1116    fn mapped_weights(&self) -> Vec<i64> {
1117        vec![1]
1118    }
1119
1120    fn mapped_entry_to_compact(&self) -> HashMap<usize, usize> {
1121        // Julia: Dict([0 => 0, 1 => 1])
1122        [(0, 0), (1, 1)].into_iter().collect()
1123    }
1124
1125    fn source_entry_to_configs(&self) -> HashMap<usize, Vec<Vec<bool>>> {
1126        // Julia: 0 => [[1,0,0], [0,1,0]], 1 => [[1,0,1]]
1127        // Entry 0 (mapped node not selected): select node 0 OR node 1
1128        // Entry 1 (mapped node selected): select nodes 0 and 2
1129        let mut map = HashMap::new();
1130        map.insert(0, vec![vec![true, false, false], vec![false, true, false]]);
1131        map.insert(1, vec![vec![true, false, true]]);
1132        map
1133    }
1134}
1135
1136// ============================================================================
1137// KsgPattern Enum for Dynamic Dispatch
1138// ============================================================================
1139
1140/// Enum wrapping all KSG square lattice patterns for dynamic dispatch during unapply.
1141#[derive(Debug, Clone)]
1142pub enum KsgPattern {
1143    CrossFalse(KsgCross<false>),
1144    CrossTrue(KsgCross<true>),
1145    Turn(KsgTurn),
1146    WTurn(KsgWTurn),
1147    Branch(KsgBranch),
1148    BranchFix(KsgBranchFix),
1149    TCon(KsgTCon),
1150    TrivialTurn(KsgTrivialTurn),
1151    EndTurn(KsgEndTurn),
1152    BranchFixB(KsgBranchFixB),
1153    DanglingLeg(KsgDanglingLeg),
1154    RotatedTCon1(KsgRotatedGadget<KsgTCon>),
1155    ReflectedCrossTrue(KsgReflectedGadget<KsgCross<true>>),
1156    ReflectedTrivialTurn(KsgReflectedGadget<KsgTrivialTurn>),
1157    ReflectedRotatedTCon1(KsgReflectedGadget<KsgRotatedGadget<KsgTCon>>),
1158    DanglingLegRot1(KsgRotatedGadget<KsgDanglingLeg>),
1159    DanglingLegRot2(KsgRotatedGadget<KsgRotatedGadget<KsgDanglingLeg>>),
1160    DanglingLegRot3(KsgRotatedGadget<KsgRotatedGadget<KsgRotatedGadget<KsgDanglingLeg>>>),
1161    DanglingLegReflX(KsgReflectedGadget<KsgDanglingLeg>),
1162    DanglingLegReflY(KsgReflectedGadget<KsgDanglingLeg>),
1163}
1164
1165impl KsgPattern {
1166    /// Get pattern from tape index.
1167    pub fn from_tape_idx(idx: usize) -> Option<Self> {
1168        match idx {
1169            0 => Some(Self::CrossFalse(KsgCross::<false>)),
1170            1 => Some(Self::Turn(KsgTurn)),
1171            2 => Some(Self::WTurn(KsgWTurn)),
1172            3 => Some(Self::Branch(KsgBranch)),
1173            4 => Some(Self::BranchFix(KsgBranchFix)),
1174            5 => Some(Self::TCon(KsgTCon)),
1175            6 => Some(Self::TrivialTurn(KsgTrivialTurn)),
1176            7 => Some(Self::RotatedTCon1(KsgRotatedGadget::new(KsgTCon, 1))),
1177            8 => Some(Self::ReflectedCrossTrue(KsgReflectedGadget::new(
1178                KsgCross::<true>,
1179                Mirror::Y,
1180            ))),
1181            9 => Some(Self::ReflectedTrivialTurn(KsgReflectedGadget::new(
1182                KsgTrivialTurn,
1183                Mirror::Y,
1184            ))),
1185            10 => Some(Self::BranchFixB(KsgBranchFixB)),
1186            11 => Some(Self::EndTurn(KsgEndTurn)),
1187            12 => Some(Self::ReflectedRotatedTCon1(KsgReflectedGadget::new(
1188                KsgRotatedGadget::new(KsgTCon, 1),
1189                Mirror::Y,
1190            ))),
1191            100 => Some(Self::DanglingLeg(KsgDanglingLeg)),
1192            101 => Some(Self::DanglingLegRot1(KsgRotatedGadget::new(
1193                KsgDanglingLeg,
1194                1,
1195            ))),
1196            102 => Some(Self::DanglingLegRot2(KsgRotatedGadget::new(
1197                KsgRotatedGadget::new(KsgDanglingLeg, 1),
1198                1,
1199            ))),
1200            103 => Some(Self::DanglingLegRot3(KsgRotatedGadget::new(
1201                KsgRotatedGadget::new(KsgRotatedGadget::new(KsgDanglingLeg, 1), 1),
1202                1,
1203            ))),
1204            104 => Some(Self::DanglingLegReflX(KsgReflectedGadget::new(
1205                KsgDanglingLeg,
1206                Mirror::X,
1207            ))),
1208            105 => Some(Self::DanglingLegReflY(KsgReflectedGadget::new(
1209                KsgDanglingLeg,
1210                Mirror::Y,
1211            ))),
1212            _ => None,
1213        }
1214    }
1215
1216    /// Apply map_config_back_pattern for this pattern.
1217    pub(crate) fn map_config_back(
1218        &self,
1219        gi: usize,
1220        gj: usize,
1221        config: &mut [Vec<usize>],
1222    ) -> Result<(), ReductionError> {
1223        match self {
1224            Self::CrossFalse(p) => map_config_back_pattern(p, gi, gj, config),
1225            Self::CrossTrue(p) => map_config_back_pattern(p, gi, gj, config),
1226            Self::Turn(p) => map_config_back_pattern(p, gi, gj, config),
1227            Self::WTurn(p) => map_config_back_pattern(p, gi, gj, config),
1228            Self::Branch(p) => map_config_back_pattern(p, gi, gj, config),
1229            Self::BranchFix(p) => map_config_back_pattern(p, gi, gj, config),
1230            Self::TCon(p) => map_config_back_pattern(p, gi, gj, config),
1231            Self::TrivialTurn(p) => map_config_back_pattern(p, gi, gj, config),
1232            Self::EndTurn(p) => map_config_back_pattern(p, gi, gj, config),
1233            Self::BranchFixB(p) => map_config_back_pattern(p, gi, gj, config),
1234            Self::DanglingLeg(p) => map_config_back_pattern(p, gi, gj, config),
1235            Self::RotatedTCon1(p) => map_config_back_pattern(p, gi, gj, config),
1236            Self::ReflectedCrossTrue(p) => map_config_back_pattern(p, gi, gj, config),
1237            Self::ReflectedTrivialTurn(p) => map_config_back_pattern(p, gi, gj, config),
1238            Self::ReflectedRotatedTCon1(p) => map_config_back_pattern(p, gi, gj, config),
1239            Self::DanglingLegRot1(p) => map_config_back_pattern(p, gi, gj, config),
1240            Self::DanglingLegRot2(p) => map_config_back_pattern(p, gi, gj, config),
1241            Self::DanglingLegRot3(p) => map_config_back_pattern(p, gi, gj, config),
1242            Self::DanglingLegReflX(p) => map_config_back_pattern(p, gi, gj, config),
1243            Self::DanglingLegReflY(p) => map_config_back_pattern(p, gi, gj, config),
1244        }
1245    }
1246}
1247
1248// ============================================================================
1249// Crossing ruleset and apply functions
1250// ============================================================================
1251
1252/// A tape entry recording a gadget application.
1253#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1254pub struct KsgTapeEntry {
1255    pub pattern_idx: usize,
1256    pub row: usize,
1257    pub col: usize,
1258}
1259
1260/// Calculate MIS overhead for a tape entry.
1261pub fn tape_entry_mis_overhead(entry: &KsgTapeEntry) -> Result<i64, ReductionError> {
1262    Ok(match entry.pattern_idx {
1263        0 => KsgCross::<false>.mis_overhead(),
1264        1 => KsgTurn.mis_overhead(),
1265        2 => KsgWTurn.mis_overhead(),
1266        3 => KsgBranch.mis_overhead(),
1267        4 => KsgBranchFix.mis_overhead(),
1268        5 => KsgTCon.mis_overhead(),
1269        6 => KsgTrivialTurn.mis_overhead(),
1270        7 => KsgRotatedGadget::new(KsgTCon, 1).mis_overhead(),
1271        8 => KsgReflectedGadget::new(KsgCross::<true>, Mirror::Y).mis_overhead(),
1272        9 => KsgReflectedGadget::new(KsgTrivialTurn, Mirror::Y).mis_overhead(),
1273        10 => KsgBranchFixB.mis_overhead(),
1274        11 => KsgEndTurn.mis_overhead(),
1275        12 => KsgReflectedGadget::new(KsgRotatedGadget::new(KsgTCon, 1), Mirror::Y).mis_overhead(),
1276        100..=105 => KsgDanglingLeg.mis_overhead(),
1277        _ => {
1278            return Err(mapping_invalid(
1279                "tape contains an unknown unweighted KSG gadget index",
1280            ))
1281        }
1282    })
1283}
1284
1285/// The default crossing ruleset for KSG square lattice.
1286#[allow(dead_code)]
1287pub fn crossing_ruleset_indices() -> Vec<usize> {
1288    (0..13).collect()
1289}
1290
1291/// Apply all crossing gadgets to the grid.
1292/// Follows Julia's algorithm: iterate over all (i,j) pairs and try all patterns.
1293/// Note: Unlike the previous version, we don't skip based on crossat position
1294/// because different (i,j) pairs with the same crossat can match different patterns
1295/// at different positions (since each pattern has a different cross_location).
1296pub fn apply_crossing_gadgets(
1297    grid: &mut MappingGrid,
1298    copylines: &[super::super::copyline::CopyLine],
1299) -> Vec<KsgTapeEntry> {
1300    let mut tape = Vec::new();
1301    let n = copylines.len();
1302
1303    for j in 0..n {
1304        for i in 0..n {
1305            let (cross_row, cross_col) = crossat(grid, copylines, i, j);
1306            if let Some((pattern_idx, row, col)) =
1307                try_match_and_apply_crossing(grid, cross_row, cross_col)
1308            {
1309                tape.push(KsgTapeEntry {
1310                    pattern_idx,
1311                    row,
1312                    col,
1313                });
1314            }
1315        }
1316    }
1317    tape
1318}
1319
1320/// Calculate crossing point for two copylines.
1321/// Uses grid.cross_at() which implements Julia's crossat formula.
1322fn crossat(
1323    grid: &MappingGrid,
1324    copylines: &[super::super::copyline::CopyLine],
1325    v: usize,
1326    w: usize,
1327) -> (usize, usize) {
1328    let line_v = copylines.get(v);
1329    let line_w = copylines.get(w);
1330
1331    match (line_v, line_w) {
1332        (Some(lv), Some(lw)) => {
1333            let (line_first, line_second) = if lv.vslot < lw.vslot {
1334                (lv, lw)
1335            } else {
1336                (lw, lv)
1337            };
1338            // Delegate to grid.cross_at() - single source of truth for crossat formula
1339            grid.cross_at(line_first.vslot, line_second.vslot, line_first.hslot)
1340        }
1341        _ => (0, 0),
1342    }
1343}
1344
1345fn try_match_and_apply_crossing(
1346    grid: &mut MappingGrid,
1347    cross_row: usize,
1348    cross_col: usize,
1349) -> Option<(usize, usize, usize)> {
1350    // Try each pattern in order
1351    let patterns: Vec<(usize, PatternFactory)> = vec![
1352        (0, Box::new(|| Box::new(KsgCross::<false>))),
1353        (1, Box::new(|| Box::new(KsgTurn))),
1354        (2, Box::new(|| Box::new(KsgWTurn))),
1355        (3, Box::new(|| Box::new(KsgBranch))),
1356        (4, Box::new(|| Box::new(KsgBranchFix))),
1357        (5, Box::new(|| Box::new(KsgTCon))),
1358        (6, Box::new(|| Box::new(KsgTrivialTurn))),
1359        (7, Box::new(|| Box::new(KsgRotatedGadget::new(KsgTCon, 1)))),
1360        (
1361            8,
1362            Box::new(|| Box::new(KsgReflectedGadget::new(KsgCross::<true>, Mirror::Y))),
1363        ),
1364        (
1365            9,
1366            Box::new(|| Box::new(KsgReflectedGadget::new(KsgTrivialTurn, Mirror::Y))),
1367        ),
1368        (10, Box::new(|| Box::new(KsgBranchFixB))),
1369        (11, Box::new(|| Box::new(KsgEndTurn))),
1370        (
1371            12,
1372            Box::new(|| {
1373                Box::new(KsgReflectedGadget::new(
1374                    KsgRotatedGadget::new(KsgTCon, 1),
1375                    Mirror::Y,
1376                ))
1377            }),
1378        ),
1379    ];
1380
1381    for (idx, make_pattern) in patterns {
1382        let pattern = make_pattern();
1383        let cl = pattern.cross_location();
1384        // cross_row/cross_col are 0-indexed, cl is 1-indexed within gadget
1385        // x = cross_row - (cl.0 - 1) = cross_row + 1 - cl.0, needs x >= 0
1386        if cross_row + 1 >= cl.0 && cross_col + 1 >= cl.1 {
1387            let x = cross_row + 1 - cl.0;
1388            let y = cross_col + 1 - cl.1;
1389            if pattern.pattern_matches_boxed(grid, x, y) {
1390                pattern.apply_gadget_boxed(grid, x, y);
1391                return Some((idx, x, y));
1392            }
1393        }
1394    }
1395    None
1396}
1397
1398/// Apply crossing gadgets with proper weights for weighted mode.
1399/// Uses apply_weighted_gadget which respects mapped_weights() for each gadget.
1400pub fn apply_weighted_crossing_gadgets(
1401    grid: &mut MappingGrid,
1402    copylines: &[super::super::copyline::CopyLine],
1403) -> Vec<KsgTapeEntry> {
1404    let mut tape = Vec::new();
1405    let n = copylines.len();
1406
1407    for j in 0..n {
1408        for i in 0..n {
1409            let (cross_row, cross_col) = crossat(grid, copylines, i, j);
1410            if let Some((pattern_idx, row, col)) =
1411                try_match_and_apply_weighted_crossing(grid, cross_row, cross_col)
1412            {
1413                tape.push(KsgTapeEntry {
1414                    pattern_idx,
1415                    row,
1416                    col,
1417                });
1418            }
1419        }
1420    }
1421    tape
1422}
1423
1424fn try_match_and_apply_weighted_crossing(
1425    grid: &mut MappingGrid,
1426    cross_row: usize,
1427    cross_col: usize,
1428) -> Option<(usize, usize, usize)> {
1429    // Try each pattern in order - same order as try_match_and_apply_crossing
1430    let patterns: Vec<(usize, PatternFactory)> = vec![
1431        (0, Box::new(|| Box::new(KsgCross::<false>))),
1432        (1, Box::new(|| Box::new(KsgTurn))),
1433        (2, Box::new(|| Box::new(KsgWTurn))),
1434        (3, Box::new(|| Box::new(KsgBranch))),
1435        (4, Box::new(|| Box::new(KsgBranchFix))),
1436        (5, Box::new(|| Box::new(KsgTCon))),
1437        (6, Box::new(|| Box::new(KsgTrivialTurn))),
1438        (7, Box::new(|| Box::new(KsgRotatedGadget::new(KsgTCon, 1)))),
1439        (
1440            8,
1441            Box::new(|| Box::new(KsgReflectedGadget::new(KsgCross::<true>, Mirror::Y))),
1442        ),
1443        (
1444            9,
1445            Box::new(|| Box::new(KsgReflectedGadget::new(KsgTrivialTurn, Mirror::Y))),
1446        ),
1447        (10, Box::new(|| Box::new(KsgBranchFixB))),
1448        (11, Box::new(|| Box::new(KsgEndTurn))),
1449        (
1450            12,
1451            Box::new(|| {
1452                Box::new(KsgReflectedGadget::new(
1453                    KsgRotatedGadget::new(KsgTCon, 1),
1454                    Mirror::Y,
1455                ))
1456            }),
1457        ),
1458    ];
1459
1460    for (idx, make_pattern) in patterns {
1461        let pattern = make_pattern();
1462        let cl = pattern.cross_location();
1463        if cross_row + 1 >= cl.0 && cross_col + 1 >= cl.1 {
1464            let x = cross_row + 1 - cl.0;
1465            let y = cross_col + 1 - cl.1;
1466            let matches = pattern.pattern_matches_boxed(grid, x, y);
1467            if matches {
1468                pattern.apply_weighted_gadget_boxed(grid, x, y);
1469                return Some((idx, x, y));
1470            }
1471        }
1472    }
1473    None
1474}
1475
1476/// Apply simplifier gadgets (KsgDanglingLeg variants).
1477/// `nrepeat` specifies the number of simplification passes.
1478pub fn apply_simplifier_gadgets(grid: &mut MappingGrid, nrepeat: usize) -> Vec<KsgTapeEntry> {
1479    let mut tape = Vec::new();
1480    let (rows, cols) = grid.size();
1481
1482    // Get all rotations and reflections of KsgDanglingLeg
1483    let patterns = rotated_and_reflected_danglinleg();
1484
1485    for _ in 0..nrepeat {
1486        for (pattern_idx, pattern) in patterns.iter().enumerate() {
1487            for j in 0..cols {
1488                for i in 0..rows {
1489                    if pattern_matches_boxed(pattern.as_ref(), grid, i, j) {
1490                        apply_gadget_boxed(pattern.as_ref(), grid, i, j);
1491                        tape.push(KsgTapeEntry {
1492                            pattern_idx: 100 + pattern_idx, // Offset to distinguish from crossing gadgets
1493                            row: i,
1494                            col: j,
1495                        });
1496                    }
1497                }
1498            }
1499        }
1500    }
1501
1502    tape
1503}
1504
1505/// Apply weighted simplifier gadgets (KsgDanglingLeg variants with weight checking).
1506/// For weighted mode, KsgDanglingLeg requires the center node to have weight 1.
1507/// Julia's WeightedGadget{DanglingLeg}: source_centers = [(2,2)] means node at (2,2) has weight 1.
1508pub fn apply_weighted_simplifier_gadgets(
1509    grid: &mut MappingGrid,
1510    nrepeat: usize,
1511) -> Vec<KsgTapeEntry> {
1512    let mut tape = Vec::new();
1513    let (rows, cols) = grid.size();
1514
1515    let patterns = rotated_and_reflected_danglinleg();
1516
1517    for _ in 0..nrepeat {
1518        for (pattern_idx, pattern) in patterns.iter().enumerate() {
1519            for j in 0..cols {
1520                for i in 0..rows {
1521                    if pattern_matches_weighted(pattern.as_ref(), grid, i, j) {
1522                        pattern.apply_weighted_gadget_boxed(grid, i, j);
1523                        tape.push(KsgTapeEntry {
1524                            pattern_idx: 100 + pattern_idx,
1525                            row: i,
1526                            col: j,
1527                        });
1528                    }
1529                }
1530            }
1531        }
1532    }
1533
1534    tape
1535}
1536
1537/// Check if a weighted KsgDanglingLeg pattern matches.
1538/// For weighted mode, the center node (at source_centers position) must have weight 1,
1539/// and other nodes must have weight 2.
1540fn pattern_matches_weighted(
1541    pattern: &dyn KsgPatternBoxed,
1542    grid: &MappingGrid,
1543    i: usize,
1544    j: usize,
1545) -> bool {
1546    // First check basic pattern match
1547    if !pattern_matches_boxed(pattern, grid, i, j) {
1548        return false;
1549    }
1550
1551    // For weighted KsgDanglingLeg, check that the center node has weight 1
1552    // KsgDanglingLeg source_centers = [(2,2)] (1-indexed), which is (1,1) 0-indexed in 4x3 pattern
1553    // After rotation/reflection, the center position changes
1554    let (locs, _, _) = pattern.source_graph_boxed();
1555    // The first node in source_graph is at (2,2), which should have weight 1
1556    // Node positions in source_graph are 1-indexed, convert to 0-indexed and add to (i,j)
1557    if let Some((loc_r, loc_c)) = locs.first() {
1558        let grid_r = i + loc_r - 1;
1559        let grid_c = j + loc_c - 1;
1560        if let Some(cell) = grid.get(grid_r, grid_c) {
1561            // Center node must have weight 1
1562            if cell.weight() != 1 {
1563                return false;
1564            }
1565        }
1566    }
1567
1568    // Check other nodes have weight 2
1569    for (_idx, (loc_r, loc_c)) in locs.iter().enumerate().skip(1) {
1570        let grid_r = i + loc_r - 1;
1571        let grid_c = j + loc_c - 1;
1572        if let Some(cell) = grid.get(grid_r, grid_c) {
1573            if cell.weight() != 2 {
1574                return false;
1575            }
1576        }
1577    }
1578
1579    true
1580}
1581
1582fn rotated_and_reflected_danglinleg() -> Vec<Box<dyn KsgPatternBoxed>> {
1583    vec![
1584        Box::new(KsgDanglingLeg),
1585        Box::new(KsgRotatedGadget::new(KsgDanglingLeg, 1)),
1586        Box::new(KsgRotatedGadget::new(KsgDanglingLeg, 2)),
1587        Box::new(KsgRotatedGadget::new(KsgDanglingLeg, 3)),
1588        Box::new(KsgReflectedGadget::new(KsgDanglingLeg, Mirror::X)),
1589        Box::new(KsgReflectedGadget::new(KsgDanglingLeg, Mirror::Y)),
1590    ]
1591}
1592
1593/// Check if a boxed pattern matches at position (i, j) in the grid.
1594#[allow(clippy::needless_range_loop)]
1595fn pattern_matches_boxed(
1596    pattern: &dyn KsgPatternBoxed,
1597    grid: &MappingGrid,
1598    i: usize,
1599    j: usize,
1600) -> bool {
1601    let source = pattern.source_matrix();
1602    let (m, n) = pattern.size_boxed();
1603
1604    for r in 0..m {
1605        for c in 0..n {
1606            let grid_r = i + r;
1607            let grid_c = j + c;
1608
1609            let expected = source[r][c];
1610            let actual = safe_get_pattern_cell(grid, grid_r, grid_c);
1611
1612            // Connected cells in pattern match both Connected and Occupied in grid
1613            // (Connected is just a marker for edge connection points)
1614            let matches = match (expected, actual) {
1615                (a, b) if a == b => true,
1616                (PatternCell::Connected, PatternCell::Occupied) => true,
1617                (PatternCell::Occupied, PatternCell::Connected) => true,
1618                _ => false,
1619            };
1620            if !matches {
1621                return false;
1622            }
1623        }
1624    }
1625    true
1626}
1627
1628fn safe_get_pattern_cell(grid: &MappingGrid, row: usize, col: usize) -> PatternCell {
1629    let (rows, cols) = grid.size();
1630    if row >= rows || col >= cols {
1631        return PatternCell::Empty;
1632    }
1633    match grid.get(row, col) {
1634        Some(CellState::Empty) => PatternCell::Empty,
1635        Some(CellState::Occupied { .. }) => PatternCell::Occupied,
1636        Some(CellState::Doubled { .. }) => PatternCell::Doubled,
1637        Some(CellState::Connected { .. }) => PatternCell::Connected,
1638        None => PatternCell::Empty,
1639    }
1640}
1641
1642/// Apply a boxed gadget pattern at position (i, j).
1643#[allow(clippy::needless_range_loop)]
1644fn apply_gadget_boxed(pattern: &dyn KsgPatternBoxed, grid: &mut MappingGrid, i: usize, j: usize) {
1645    let mapped = pattern.mapped_matrix();
1646    let (m, n) = pattern.size_boxed();
1647
1648    for r in 0..m {
1649        for c in 0..n {
1650            let grid_r = i + r;
1651            let grid_c = j + c;
1652
1653            let cell = mapped[r][c];
1654            let state = match cell {
1655                PatternCell::Empty => CellState::Empty,
1656                PatternCell::Occupied => CellState::Occupied { weight: 1 },
1657                PatternCell::Doubled => CellState::Doubled { weight: 1 },
1658                PatternCell::Connected => CellState::Connected { weight: 1 },
1659            };
1660            grid.set(grid_r, grid_c, state);
1661        }
1662    }
1663}
1664
1665/// Apply a boxed gadget pattern at position (i, j) with proper weights.
1666#[allow(dead_code)]
1667fn apply_weighted_gadget_boxed_fn(
1668    pattern: &dyn KsgPatternBoxed,
1669    grid: &mut MappingGrid,
1670    i: usize,
1671    j: usize,
1672) {
1673    pattern.apply_weighted_gadget_boxed(grid, i, j);
1674}
1675
1676/// Trait for boxed pattern operations.
1677pub trait KsgPatternBoxed {
1678    fn size_boxed(&self) -> (usize, usize);
1679    fn cross_location(&self) -> (usize, usize);
1680    fn source_matrix(&self) -> Vec<Vec<PatternCell>>;
1681    fn mapped_matrix(&self) -> Vec<Vec<PatternCell>>;
1682    fn source_graph_boxed(&self) -> SourceGraph;
1683    fn pattern_matches_boxed(&self, grid: &MappingGrid, i: usize, j: usize) -> bool;
1684    fn apply_gadget_boxed(&self, grid: &mut MappingGrid, i: usize, j: usize);
1685    fn apply_weighted_gadget_boxed(&self, grid: &mut MappingGrid, i: usize, j: usize);
1686}
1687
1688impl<P: Pattern> KsgPatternBoxed for P {
1689    fn size_boxed(&self) -> (usize, usize) {
1690        self.size()
1691    }
1692    fn cross_location(&self) -> (usize, usize) {
1693        Pattern::cross_location(self)
1694    }
1695    fn source_matrix(&self) -> Vec<Vec<PatternCell>> {
1696        Pattern::source_matrix(self)
1697    }
1698    fn mapped_matrix(&self) -> Vec<Vec<PatternCell>> {
1699        Pattern::mapped_matrix(self)
1700    }
1701    fn source_graph_boxed(&self) -> SourceGraph {
1702        Pattern::source_graph(self)
1703    }
1704    fn pattern_matches_boxed(&self, grid: &MappingGrid, i: usize, j: usize) -> bool {
1705        pattern_matches(self, grid, i, j)
1706    }
1707    fn apply_gadget_boxed(&self, grid: &mut MappingGrid, i: usize, j: usize) {
1708        apply_gadget(self, grid, i, j);
1709    }
1710    fn apply_weighted_gadget_boxed(&self, grid: &mut MappingGrid, i: usize, j: usize) {
1711        apply_weighted_gadget(self, grid, i, j);
1712    }
1713}
1714
1715/// Apply a weighted gadget pattern at position (i, j) with proper weights.
1716/// Uses mapped_graph locations and mapped_weights for each node.
1717#[allow(clippy::needless_range_loop)]
1718pub fn apply_weighted_gadget<P: Pattern>(pattern: &P, grid: &mut MappingGrid, i: usize, j: usize) {
1719    let (m, n) = pattern.size();
1720    let (mapped_locs, _) = pattern.mapped_graph();
1721    let mapped_weights = pattern.mapped_weights();
1722
1723    // First clear the gadget area
1724    for r in 0..m {
1725        for c in 0..n {
1726            let grid_r = i + r;
1727            let grid_c = j + c;
1728            grid.set(grid_r, grid_c, CellState::Empty);
1729        }
1730    }
1731
1732    // Build a map of (row, col) -> accumulated weight for doubled nodes
1733    let mut weight_map: HashMap<(usize, usize), i64> = HashMap::new();
1734    for (idx, &(r, c)) in mapped_locs.iter().enumerate() {
1735        let weight = mapped_weights[idx];
1736        *weight_map.entry((r, c)).or_insert(0) += weight;
1737    }
1738
1739    // Count occurrences to detect doubled nodes
1740    let mut count_map: HashMap<(usize, usize), usize> = HashMap::new();
1741    for &(r, c) in &mapped_locs {
1742        *count_map.entry((r, c)).or_insert(0) += 1;
1743    }
1744
1745    // Set cells with proper weights
1746    for (&(r, c), &total_weight) in &weight_map {
1747        let grid_r = i + r - 1; // Convert 1-indexed to 0-indexed
1748        let grid_c = j + c - 1;
1749        let count = count_map[&(r, c)];
1750
1751        let state = if count > 1 {
1752            CellState::Doubled {
1753                weight: total_weight,
1754            }
1755        } else {
1756            CellState::Occupied {
1757                weight: total_weight,
1758            }
1759        };
1760        grid.set(grid_r, grid_c, state);
1761    }
1762}
1763
1764/// Map configuration back through a single gadget.
1765pub(crate) fn map_config_back_pattern<P: Pattern>(
1766    pattern: &P,
1767    gi: usize,
1768    gj: usize,
1769    config: &mut [Vec<usize>],
1770) -> Result<(), ReductionError> {
1771    let (m, n) = pattern.size();
1772    let (mapped_locs, mapped_pins) = pattern.mapped_graph();
1773    let (source_locs, _, _) = pattern.source_graph();
1774
1775    // Step 1: Extract config at mapped locations
1776    let mapped_config: Vec<usize> = mapped_locs
1777        .iter()
1778        .map(|&(r, c)| {
1779            let row = gi + r - 1;
1780            let col = gj + c - 1;
1781            config
1782                .get(row)
1783                .and_then(|row_vec| row_vec.get(col))
1784                .copied()
1785                .ok_or(mapping_invalid(
1786                    "unweighted KSG gadget lies outside the configuration grid",
1787                ))
1788        })
1789        .collect::<Result<_, _>>()?;
1790
1791    // Step 2: Compute boundary config
1792    let bc = {
1793        let mut result = 0usize;
1794        for (i, &pin_idx) in mapped_pins.iter().enumerate() {
1795            if *mapped_config.get(pin_idx).ok_or(mapping_invalid(
1796                "unweighted KSG gadget contains an invalid mapped pin index",
1797            ))? > 0
1798            {
1799                result |= 1 << i;
1800            }
1801        }
1802        result
1803    };
1804
1805    // Step 3: Look up source config
1806    let d1 = pattern.mapped_entry_to_compact();
1807    let d2 = pattern.source_entry_to_configs();
1808
1809    let compact = d1.get(&bc).copied().ok_or(mapping_invalid(
1810        "unweighted KSG boundary configuration has no source equivalent",
1811    ))?;
1812    let new_config =
1813        d2.get(&compact)
1814            .and_then(|configs| configs.first())
1815            .ok_or(mapping_invalid(
1816                "unweighted KSG compact state has no source configuration",
1817            ))?;
1818    if new_config.len() != source_locs.len() {
1819        return Err(mapping_invalid(
1820            "unweighted KSG source configuration has the wrong length",
1821        ));
1822    }
1823
1824    // Step 4: Clear gadget area
1825    for row in gi..gi + m {
1826        for col in gj..gj + n {
1827            *config
1828                .get_mut(row)
1829                .and_then(|row_vec| row_vec.get_mut(col))
1830                .ok_or(mapping_invalid(
1831                    "unweighted KSG gadget lies outside the configuration grid",
1832                ))? = 0;
1833        }
1834    }
1835
1836    // Step 5: Write source config
1837    for (k, &(r, c)) in source_locs.iter().enumerate() {
1838        let row = gi + r - 1;
1839        let col = gj + c - 1;
1840        let cell = config
1841            .get_mut(row)
1842            .and_then(|row_vec| row_vec.get_mut(col))
1843            .ok_or(mapping_invalid(
1844                "unweighted KSG source position lies outside the configuration grid",
1845            ))?;
1846        *cell = cell
1847            .checked_add(usize::from(new_config[k]))
1848            .ok_or(mapping_integer_overflow(
1849                "accumulating an unweighted KSG source configuration",
1850            ))?;
1851    }
1852
1853    Ok(())
1854}