nx_align_minimizer_test.nx source
↩ module page · 135 lines · 5276 B
1// nx_align_minimizer_test.nx -- KAT for minimizer extraction.
2//
3// Fixture "ACGTACGTAC" (10 bases) with k=4 w=3. Hand-traced
4// canonical k-mers at base positions 0..6:
5// pos 0: ACGT canon = 0x1B (palindrome)
6// pos 1: CGTA canon = 0x6C (vs rc TACG = 0xC6)
7// pos 2: GTAC canon = 0xB1 (palindrome)
8// pos 3: TACG canon = 0x6C (vs rc CGTA = 0xC6; min = 0x6C)
9// pos 4: ACGT canon = 0x1B
10// pos 5: CGTA canon = 0x6C
11// pos 6: GTAC canon = 0xB1
12//
13// Number of windows = (n - k + 1) - w + 1 = 7 - 3 + 1 = 5.
14// Per-window minimizer (leftmost min of w consecutive canon values):
15// w0 [pos 0,1,2] -> (0x1B, 0)
16// w1 [pos 1,2,3] -> (0x6C, 1) tie at pos 1 + 3 -> leftmost = 1
17// w2 [pos 2,3,4] -> (0x1B, 4)
18// w3 [pos 3,4,5] -> (0x1B, 4) persists
19// w4 [pos 4,5,6] -> (0x1B, 4) persists
20// Deduplicated emission stream:
21// (0x1B, 0), (0x6C, 1), (0x1B, 4) -- count = 3.
22//
23// expect_exit: 0
24//
25// license_tier: ORIGINAL
26
27import "nx_syscalls.nx"
28import "nx_sequence.nx"
29import "nx_align_minimizer.nx"
30
31func main() -> i64 {
32
33 // ============================================================
34 // Section A -- window counting on canonical fixture.
35 // ============================================================
36
37 if minimizer_count_windows(10, 4, 3) != 5 { return 1 }
38 if minimizer_count_windows( 4, 2, 3) != 1 { return 2 }
39 if minimizer_count_windows( 3, 2, 3) != 0 { return 3 } // too short
40 if minimizer_count_windows(10, 4, 1) != 7 { return 4 } // w=1 -> per-kmer
41 if minimizer_count_windows( 0, 4, 3) != 0 { return 5 }
42
43 // ============================================================
44 // Section B -- "ACGTACGTAC" / k=4 / w=3 main KAT.
45 // ============================================================
46
47 let bases: *u8 = sys_mmap(8)
48 bases[0] = 0x1B // ACGT
49 bases[1] = 0x1B // ACGT
50 bases[2] = 0x10 // AC + padding zeros (high nibble = 00 01, low = 00 00)
51
52 let vals: *i64 = sys_mmap(128) as *i64
53 let pos: *i64 = sys_mmap(128) as *i64
54
55 let n_emit: i64 = minimizer_extract(bases, 10, 4, 3, vals, pos, 16)
56 if n_emit != 3 { return 10 }
57
58 if vals[0] != 0x1B { return 11 }
59 if pos[0] != 0 { return 12 }
60 if vals[1] != 0x6C { return 13 }
61 if pos[1] != 1 { return 14 }
62 if vals[2] != 0x1B { return 15 }
63 if pos[2] != 4 { return 16 }
64
65 // ============================================================
66 // Section C -- "AAAA" / k=2 / w=2.
67 // All k-mers canonicalise to 0; leftmost-tie means each window's
68 // min sits at its left edge, so emission stream is:
69 // w0 -> (0, 0)
70 // w1 -> (0, 1)
71 // Count = 2.
72 // ============================================================
73
74 let bases_a: *u8 = sys_mmap(4)
75 bases_a[0] = 0x00 // AAAA
76
77 let vals_a: *i64 = sys_mmap(64) as *i64
78 let pos_a: *i64 = sys_mmap(64) as *i64
79
80 let n_a: i64 = minimizer_extract(bases_a, 4, 2, 2, vals_a, pos_a, 16)
81 if n_a != 2 { return 20 }
82 if vals_a[0] != 0 { return 21 }
83 if pos_a[0] != 0 { return 22 }
84 if vals_a[1] != 0 { return 23 }
85 if pos_a[1] != 1 { return 24 }
86
87 // ============================================================
88 // Section D -- "ACGT" / k=2 / w=3.
89 // Canonical k-mers at pos 0..2:
90 // pos 0: AC = 0x1, rc GT = 0xB -> canon 1
91 // pos 1: CG = 0x6, rc CG palindrome -> canon 6
92 // pos 2: GT = 0xB, rc AC = 0x1 -> canon 1
93 // 1 window covering pos [0,1,2]: min = 1 (tie at 0 and 2 -> leftmost = 0).
94 // Emit (1, 0). Count = 1.
95 // ============================================================
96
97 let bases_b: *u8 = sys_mmap(4)
98 bases_b[0] = 0x1B // ACGT
99
100 let vals_b: *i64 = sys_mmap(64) as *i64
101 let pos_b: *i64 = sys_mmap(64) as *i64
102
103 let n_b: i64 = minimizer_extract(bases_b, 4, 2, 3, vals_b, pos_b, 16)
104 if n_b != 1 { return 30 }
105 if vals_b[0] != 1 { return 31 }
106 if pos_b[0] != 0 { return 32 }
107
108 // ============================================================
109 // Section E -- too-short input: n < k + w - 1 yields zero emit.
110 // ============================================================
111
112 let n_e: i64 = minimizer_extract(bases_b, 3, 2, 3, vals_b, pos_b, 16)
113 if n_e != 0 { return 40 }
114
115 // ============================================================
116 // Section F -- bad parameter rejection.
117 // ============================================================
118
119 if minimizer_extract(bases, 10, 0, 3, vals, pos, 16) != -1 { return 50 } // k=0
120 if minimizer_extract(bases, 10, 33, 3, vals, pos, 16) != -1 { return 51 } // k>32
121 if minimizer_extract(bases, 10, 4, 0, vals, pos, 16) != -1 { return 52 } // w=0
122 if minimizer_extract(bases, 10, 4, 3, vals, pos, 0) != -1 { return 53 } // max_out=0
123
124 // ============================================================
125 // Section G -- capacity overflow surfaces as -1.
126 // Same canonical fixture but max_out=2 cannot fit the 3 emissions.
127 // ============================================================
128
129 let vals_g: *i64 = sys_mmap(64) as *i64
130 let pos_g: *i64 = sys_mmap(64) as *i64
131 let n_g: i64 = minimizer_extract(bases, 10, 4, 3, vals_g, pos_g, 2)
132 if n_g != -1 { return 60 }
133
134 return 0
135}