code wiki / (root) / nx_align_minimizer_test.nx

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}