code wiki / (root) / nx_chem_mass_test.nx

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}