nx_chem_adulterant_charge_test.nx source
↩ module page · 131 lines · 7883 B
1// nx_chem_adulterant_charge_test.nx -- gate for the seq651 fix: charge-state
2// screening in nx_chem_adulterant_db.
3//
4// The decisive test is T4/T5: take a REAL catalog entry, synthesise the peak
5// an electrospray source would actually produce for it at z=2 and z=3, and
6// show the OLD single-charge lookup returns ZERO while the new charge-aware
7// lookup identifies it. That is the defect reproduced and then closed, not
8// a claim that it was.
9// expect_exit: 0 license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_chem.nx"
12import "nx_chem_molecule.nx"
13import "nx_chem_periodic.nx"
14import "nx_chem_mass.nx"
15import "nx_chem_adulterant_db.nx"
16
17func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
18func t_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, bb, k); return 0 }
19
20// The m/z an ESI source gives for a neutral mass M at charge z:
21// (M + z*proton) / z
22func esi_mz(mh_plus_q4: i64, z: i64) -> i64 {
23 let neutral: i64 = mh_plus_q4 - NX_PROTON_MASS_Q4
24 let protons: i64 = z * NX_PROTON_MASS_Q4
25 let tot: i64 = neutral + protons
26 return tot / z
27}
28
29func main() -> i64 {
30 var pass: i64 = 0
31 var total: i64 = 0
32
33 let db: *AdulterantDB = nx_chem_adulterant_db_seed()
34 let out: *MatchResult = (sys_mmap(NX_MATCH_RESULT_BYTES * 32)) as *MatchResult
35
36 // --- T1 the seed DB is populated and entry 0 has a real m/z ---
37 total = total + 1
38 let e0: *AdulterantEntry = nx_chem_adulterant_entry_by_id(db, 0)
39 let mh0: i64 = e0.mh_plus_q4
40 t_puts("T1 seed entries=" as *u8); t_putn(db.n); t_puts(" entry0 [M+H]+ Q4=" as *u8); t_putn(mh0); t_puts(": " as *u8)
41 if db.n > 0 { if mh0 > 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
42
43 // --- T2 the change of variable is EXACT: translate back and forth ---
44 total = total + 1
45 let mz2: i64 = esi_mz(mh0, 2)
46 let mz3: i64 = esi_mz(mh0, 3)
47 let back2: i64 = nx_chem_adulterant_mz_to_mh_plus(mz2, 2)
48 let back3: i64 = nx_chem_adulterant_mz_to_mh_plus(mz3, 3)
49 t_puts("T2 z=2 peak " as *u8); t_putn(mz2); t_puts(" -> " as *u8); t_putn(back2); t_puts(" ; z=3 peak " as *u8); t_putn(mz3); t_puts(" -> " as *u8); t_putn(back3); t_puts(" want " as *u8); t_putn(mh0); t_puts(" (+/-3): " as *u8)
50 var d2: i64 = back2 - mh0
51 if d2 < 0 { d2 = 0 - d2 }
52 var d3: i64 = back3 - mh0
53 if d3 < 0 { d3 = 0 - d3 }
54 if d2 <= 3 { if d3 <= 3 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
55
56 // --- T3 singly-charged peaks still match (no regression) ---
57 total = total + 1
58 let n1_old: i64 = nx_chem_adulterant_db_lookup_with_rt(db, mh0, 0, 20, out, 32)
59 let n1_new: i64 = nx_chem_adulterant_db_lookup_charges(db, mh0, 0, 20, 4, out, 32)
60 t_puts("T3 [M+H]+ peak: old lookup=" as *u8); t_putn(n1_old); t_puts(" charge-aware=" as *u8); t_putn(n1_new); t_puts(" (both >=1): " as *u8)
61 if n1_old >= 1 { if n1_new >= 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
62
63 // --- T4 THE DEFECT, REPRODUCED: a z=2 peak is INVISIBLE to the old path ---
64 total = total + 1
65 let n2_old: i64 = nx_chem_adulterant_db_lookup_with_rt(db, mz2, 0, 20, out, 32)
66 let n2_new: i64 = nx_chem_adulterant_db_lookup_charges(db, mz2, 0, 20, 4, out, 32)
67 t_puts("T4 z=2 peak: old lookup=" as *u8); t_putn(n2_old); t_puts(" (blind) charge-aware=" as *u8); t_putn(n2_new); t_puts(" (finds it): " as *u8)
68 if n2_old == 0 { if n2_new >= 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
69
70 // --- T5 same at z=3, and it resolves to the RIGHT compound ---
71 total = total + 1
72 let n3_old: i64 = nx_chem_adulterant_db_lookup_with_rt(db, mz3, 0, 20, out, 32)
73 let n3_new: i64 = nx_chem_adulterant_db_lookup_charges(db, mz3, 0, 20, 4, out, 32)
74 var id3: i64 = 0 - 1
75 if n3_new >= 1 { id3 = out.entry_id }
76 t_puts("T5 z=3 peak: old=" as *u8); t_putn(n3_old); t_puts(" charge-aware=" as *u8); t_putn(n3_new); t_puts(" identified entry_id=" as *u8); t_putn(id3); t_puts(" want 0/>=1/0: " as *u8)
77 var ok5: i64 = 1
78 if n3_old != 0 { ok5 = 0 }
79 if n3_new < 1 { ok5 = 0 }
80 if id3 != 0 { ok5 = 0 }
81 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
82
83 // --- T6 ppm tolerance SURVIVES the translation ---
84 // A peak 10 ppm off at z=2 must still match at a 20 ppm window, and must
85 // NOT match at 5 ppm. If the translation broke the ratio, one of these
86 // would flip.
87 total = total + 1
88 let off: i64 = mz2 / 100000
89 let near2: i64 = mz2 + off
90 let loose: i64 = nx_chem_adulterant_db_lookup_charges(db, near2, 0, 20, 4, out, 32)
91 let tight: i64 = nx_chem_adulterant_db_lookup_charges(db, near2, 0, 5, 4, out, 32)
92 t_puts("T6 z=2 peak +10ppm: at 20ppm window=" as *u8); t_putn(loose); t_puts(" at 5ppm window=" as *u8); t_putn(tight); t_puts(" want >=1/0: " as *u8)
93 if loose >= 1 { if tight == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
94
95 // --- T7 max_charge=0 degrades to old behaviour, never to nothing ---
96 total = total + 1
97 let z0: i64 = nx_chem_adulterant_db_lookup_charges(db, mh0, 0, 20, 0, out, 32)
98 let zneg: i64 = nx_chem_adulterant_db_lookup_charges(db, mh0, 0, 20, 0 - 5, out, 32)
99 t_puts("T7 max_charge 0/-5 on a [M+H]+ peak = " as *u8); t_putn(z0); t_puts("/" as *u8); t_putn(zneg); t_puts(" (both >=1, degrade to z=1): " as *u8)
100 if z0 >= 1 { if zneg >= 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
101
102 // --- T8 non-physical inputs refuse rather than fabricate ---
103 total = total + 1
104 let bad_z: i64 = nx_chem_adulterant_mz_to_mh_plus(mz2, 0)
105 let bad_mz: i64 = nx_chem_adulterant_mz_to_mh_plus(0, 2)
106 let bad_neg: i64 = nx_chem_adulterant_mz_to_mh_plus(0 - 100, 2)
107 t_puts("T8 z=0 -> " as *u8); t_putn(bad_z); t_puts(" mz=0 -> " as *u8); t_putn(bad_mz); t_puts(" mz<0 -> " as *u8); t_putn(bad_neg); t_puts(" (all 0): " as *u8)
108 var ok8: i64 = 1
109 if bad_z != 0 { ok8 = 0 }
110 if bad_mz != 0 { ok8 = 0 }
111 if bad_neg != 0 { ok8 = 0 }
112 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
113
114 // --- T9 an unrelated peak matches at NO charge state (neg control) ---
115 // Guards against the change of variable turning the screen into a sieve
116 // that hits on anything.
117 total = total + 1
118 let junk: i64 = nx_chem_adulterant_db_lookup_charges(db, 3333333, 0, 20, 4, out, 32)
119 t_puts("T9 unrelated peak m/z 333.33 across z=1..4 matches=" as *u8); t_putn(junk); t_puts(" want 0: " as *u8)
120 if junk == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
121
122 // --- T10 the output cap is respected (no overrun past max_out) ---
123 total = total + 1
124 let capped: i64 = nx_chem_adulterant_db_lookup_charges(db, mh0, 0, 20, 4, out, 1)
125 t_puts("T10 max_out=1 returns " as *u8); t_putn(capped); t_puts(" (<=1): " as *u8)
126 if capped <= 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
127
128 t_puts("ADULTERANT-CHARGE-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total)
129 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
130 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
131}