nx_chem_mass_test.nx source
↩ module page · 251 lines · 11101 B
1// nx_chem_mass_test.nx -- C2.8a KAT for monoisotopic mass + ion m/z.
2//
3// All reference values from NIST WebBook (https://webbook.nist.gov/)
4// and standard MS textbook references.
5//
6// expect_exit: 0
7//
8// license_tier: ORIGINAL
9
10import "nx_chem.nx"
11import "nx_chem_molecule.nx"
12import "nx_chem_smiles.nx"
13import "nx_chem_periodic.nx"
14import "nx_chem_valence.nx"
15import "nx_chem_mass.nx"
16
17// Helper: parse + populate implicit-H
18func parse_with_h(src: *u8, n: nx_int) -> *MolGraph {
19 let m: *MolGraph = nx_chem_parse_smiles(src, n)
20 let _f: nx_int = nx_chem_compute_implicit_h(m)
21 return m
22}
23
24// =================================================================
25// A -- water "O" monoisotopic mass = 18.01056 Da
26// ¹⁶O exact = 15.99491; ¹H exact = 1.00783
27// H2O = 15.99491 + 2 * 1.00783 = 18.01057
28// In Q4 micro-AMU: 159949 + 2*10078 = 180105 (matches NIST 18.01056 within rounding)
29// =================================================================
30func a_water() -> nx_int {
31 let ad: *AtomicData = nx_chem_atomic_data_table_118()
32 let s: *u8 = sys_mmap(8); s[0] = 0x4F
33 let m: *MolGraph = parse_with_h(s, 1)
34 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
35 if mass_q4 != 180105 { return 11 }
36 return 0
37}
38
39// =================================================================
40// B -- methane "C" monoisotopic mass = 16.0313 Da
41// ¹²C exact = 12.00000 (by definition); ¹H = 1.00783
42// CH4 = 12.00000 + 4 * 1.00783 = 16.03132
43// In Q4: 120000 + 40312 = 160312
44// =================================================================
45func b_methane() -> nx_int {
46 let ad: *AtomicData = nx_chem_atomic_data_table_118()
47 let s: *u8 = sys_mmap(8); s[0] = 0x43
48 let m: *MolGraph = parse_with_h(s, 1)
49 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
50 if mass_q4 != 160312 { return 21 }
51 return 0
52}
53
54// =================================================================
55// C -- ethanol "CCO" monoisotopic mass = 46.04187 Da
56// C2H6O = 2*12 + 6*1.00783 + 15.99491 = 24 + 6.04698 + 15.99491 = 46.04189
57// In Q4: 240000 + 60469 + 159949 = 460418 (rounding tolerant ±1)
58// =================================================================
59func c_ethanol() -> nx_int {
60 let ad: *AtomicData = nx_chem_atomic_data_table_118()
61 let s: *u8 = sys_mmap(8)
62 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
63 let m: *MolGraph = parse_with_h(s, 3)
64 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
65 // Allow ±1 Q4 unit (0.0001 Da) for rounding
66 var diff: nx_int = mass_q4 - 460417
67 if diff < 0 { diff = 0 - diff }
68 if diff > 2 { return 31 }
69 return 0
70}
71
72// =================================================================
73// D -- benzene "c1ccccc1" monoisotopic mass = 78.04695 Da
74// C6H6 = 6*12 + 6*1.00783 = 72 + 6.04698 = 78.04698
75// Aromatic carbons don't have h_count populated by our valence pass
76// (honest C2.3a gap: aromatic non-carbon h_count is -1; aromatic
77// carbon gets h_count from compute_implicit_h since z=6). Each
78// aromatic c in benzene gets 1 H per the rule. In Q4: 720000 + 60469 = 780469.
79// =================================================================
80func d_benzene() -> nx_int {
81 let ad: *AtomicData = nx_chem_atomic_data_table_118()
82 let s: *u8 = sys_mmap(16)
83 s[0] = 0x63; s[1] = 0x31
84 s[2] = 0x63; s[3] = 0x63
85 s[4] = 0x63; s[5] = 0x63
86 s[6] = 0x63; s[7] = 0x31
87 let m: *MolGraph = parse_with_h(s, 8)
88 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
89 var diff: nx_int = mass_q4 - 780469
90 if diff < 0 { diff = 0 - diff }
91 if diff > 2 { return 41 }
92 return 0
93}
94
95// =================================================================
96// E -- [M+H]+ ion m/z for ethanol = 46.04187 + 1.00728 = 47.04915
97// In Q4: 460418 + 10073 = 470491 (±2 tolerance)
98// =================================================================
99func e_ethanol_mh_plus() -> nx_int {
100 let ad: *AtomicData = nx_chem_atomic_data_table_118()
101 let s: *u8 = sys_mmap(8)
102 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
103 let m: *MolGraph = parse_with_h(s, 3)
104 let mz: nx_int = nx_chem_mol_mh_plus_q4(m, ad)
105 var diff: nx_int = mz - 470490
106 if diff < 0 { diff = 0 - diff }
107 if diff > 2 { return 51 }
108 return 0
109}
110
111// =================================================================
112// F -- [M-H]- ion m/z for ethanol = 46.04187 - 1.00728 = 45.03459
113// In Q4: 460418 - 10073 = 450345
114// =================================================================
115func f_ethanol_mh_minus() -> nx_int {
116 let ad: *AtomicData = nx_chem_atomic_data_table_118()
117 let s: *u8 = sys_mmap(8)
118 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
119 let m: *MolGraph = parse_with_h(s, 3)
120 let mz: nx_int = nx_chem_mol_mh_minus_q4(m, ad)
121 var diff: nx_int = mz - 450345
122 if diff < 0 { diff = 0 - diff }
123 if diff > 2 { return 61 }
124 return 0
125}
126
127// =================================================================
128// G -- [M+Na]+ adduct for ethanol = 46.04187 + 21.98244 (Na - electron)
129// In Q4: 460418 + 219825 = 680243 (±2)
130// =================================================================
131func g_ethanol_mna_plus() -> nx_int {
132 let ad: *AtomicData = nx_chem_atomic_data_table_118()
133 let s: *u8 = sys_mmap(8)
134 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
135 let m: *MolGraph = parse_with_h(s, 3)
136 let mz: nx_int = nx_chem_mol_mna_plus_q4(m, ad)
137 var diff: nx_int = mz - 680243
138 if diff < 0 { diff = 0 - diff }
139 if diff > 2 { return 71 }
140 return 0
141}
142
143// =================================================================
144// H -- multi-charge [M+2H]2+ for benzene: (78.04698 + 2*1.00728) / 2 = 40.03077
145// In Q4: (780469 + 20146) / 2 = 400307 (integer truncation; ±2)
146// =================================================================
147func h_benzene_2h_plus() -> nx_int {
148 let ad: *AtomicData = nx_chem_atomic_data_table_118()
149 let s: *u8 = sys_mmap(16)
150 s[0] = 0x63; s[1] = 0x31
151 s[2] = 0x63; s[3] = 0x63
152 s[4] = 0x63; s[5] = 0x63
153 s[6] = 0x63; s[7] = 0x31
154 let m: *MolGraph = parse_with_h(s, 8)
155 let mz: nx_int = nx_chem_mol_multi_charge_mh_q4(m, ad, 2)
156 var diff: nx_int = mz - 400307
157 if diff < 0 { diff = 0 - diff }
158 if diff > 2 { return 81 }
159 return 0
160}
161
162// =================================================================
163// I -- supplement-arc heavy-metal "[Pb+2]" monoisotopic mass.
164// ²⁰⁸Pb exact = 207.97665 Da (most abundant); h_count = 0 (bracket explicit).
165// In Q4: 2079767. Direct from C1 AtomicData (no implicit H added).
166// =================================================================
167func i_lead() -> nx_int {
168 let ad: *AtomicData = nx_chem_atomic_data_table_118()
169 let s: *u8 = sys_mmap(8)
170 s[0] = 0x5B; s[1] = 0x50; s[2] = 0x62; s[3] = 0x2B; s[4] = 0x32; s[5] = 0x5D
171 let m: *MolGraph = parse_with_h(s, 6)
172 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
173 if mass_q4 != 2079767 { return 91 }
174 return 0
175}
176
177// =================================================================
178// J -- supplement-arc adulterant marker sodium chloride "[Na+].[Cl-]".
179// ²³Na = 22.98977; ³⁵Cl = 34.96885 (most abundant). Multi-component;
180// monoisotopic mass = 22.98977 + 34.96885 = 57.95862.
181// In Q4: 229898 + 349689 = 579587.
182// (Bracket atoms have h_count=0 by parser; no implicit-H added.)
183// =================================================================
184func j_nacl_monoisotopic() -> nx_int {
185 let ad: *AtomicData = nx_chem_atomic_data_table_118()
186 let s: *u8 = sys_mmap(16)
187 s[0] = 0x5B; s[1] = 0x4E; s[2] = 0x61; s[3] = 0x2B; s[4] = 0x5D
188 s[5] = 0x2E
189 s[6] = 0x5B; s[7] = 0x43; s[8] = 0x6C; s[9] = 0x2D; s[10] = 0x5D
190 let m: *MolGraph = parse_with_h(s, 11)
191 let mass_q4: nx_int = nx_chem_mol_monoisotopic_mass_q4(m, ad)
192 if mass_q4 != 579587 { return 101 }
193 return 0
194}
195
196func main() -> nx_exit {
197 println("=== nx_chem_mass -- C2.8a KAT: monoisotopic mass + ion m/z ===" as *u8)
198
199 let ra: nx_int = a_water()
200 if ra != 0 { println("A water FAIL" as *u8); return ra }
201 println("A water PASS H2O monoisotopic 18.0105 Da (Q4=180105)" as *u8)
202
203 let rb: nx_int = b_methane()
204 if rb != 0 { println("B methane FAIL" as *u8); return rb }
205 println("B methane PASS CH4 monoisotopic 16.0313 Da (Q4=160312)" as *u8)
206
207 let rc: nx_int = c_ethanol()
208 if rc != 0 { println("C ethanol FAIL" as *u8); return rc }
209 println("C ethanol PASS C2H6O monoisotopic 46.0419 Da (Q4=460418 +/- 2)" as *u8)
210
211 let rd: nx_int = d_benzene()
212 if rd != 0 { println("D benzene FAIL" as *u8); return rd }
213 println("D benzene PASS C6H6 monoisotopic 78.0470 Da (Q4=780469 +/- 2)" as *u8)
214
215 let re: nx_int = e_ethanol_mh_plus()
216 if re != 0 { println("E ethanol_mh_plus FAIL" as *u8); return re }
217 println("E ethanol_mh_plus PASS [M+H]+ m/z 47.0491 (Q4=470491; LC-MS ESI+ target)" as *u8)
218
219 let rf: nx_int = f_ethanol_mh_minus()
220 if rf != 0 { println("F ethanol_mh_minus FAIL" as *u8); return rf }
221 println("F ethanol_mh_minus PASS [M-H]- m/z 45.0346 (Q4=450345; LC-MS ESI- target)" as *u8)
222
223 let rg: nx_int = g_ethanol_mna_plus()
224 if rg != 0 { println("G ethanol_mna_plus FAIL" as *u8); return rg }
225 println("G ethanol_mna_plus PASS [M+Na]+ m/z 68.0243 (Q4=680243; ESI+ with Na buffer)" as *u8)
226
227 let rh: nx_int = h_benzene_2h_plus()
228 if rh != 0 { println("H benzene_2h_plus FAIL" as *u8); return rh }
229 println("H benzene_2h_plus PASS [M+2H]2+ m/z 40.0308 (Q4=400307; multi-charge)" as *u8)
230
231 let ri: nx_int = i_lead()
232 if ri != 0 { println("I lead FAIL" as *u8); return ri }
233 println("I lead PASS [Pb+2] monoisotopic 207.97665 (Q4=2079767; supplement Pb adulterant)" as *u8)
234
235 let rj: nx_int = j_nacl_monoisotopic()
236 if rj != 0 { println("J nacl_monoisotopic FAIL" as *u8); return rj }
237 println("J nacl_monoisotopic PASS [Na+].[Cl-] = 57.9586 Da (Q4=579587; multi-component sum)" as *u8)
238
239 println("" as *u8)
240 println("=== C2.8a substrate milestone PASS ===" as *u8)
241 println(" monoisotopic mass : Q4 micro-AMU exact-isotope mass using AtomicData.iso_mass_q4" as *u8)
242 println(" (composes C1 AtomicData first non-trivial use of iso_mass_q4 field)" as *u8)
243 println(" ion m/z predictors : [M+H]+ / [M-H]- / [M+Na]+ / [M+NH4]+ / [M+nH]n+ multi-charge" as *u8)
244 println(" supplement-arc value: predicted LC-MS m/z compared to observed peaks for adulterant" as *u8)
245 println(" detection (sildenafil 475.2, sibutramine 280.1, DMBA 116.1, etc.)" as *u8)
246 println(" honest gaps : isotopologue pattern (M+1, M+2) for natural-abundance distribution," as *u8)
247 println(" composes AtomicData.iso_abund_ppm; C2.8b" as *u8)
248 println(" electron-mass correction (~0.0005 Da; sub-Q4 precision; not yet)" as *u8)
249 println(" next : C2.8 -- logP (Crippen-Wildman) + TPSA (Ertl) atom-contribution tables" as *u8)
250 return 0
251}