nx_chem_valence_test.nx source
↩ module page · 361 lines · 15449 B
1// nx_chem_valence_test.nx -- C2.3a KAT for implicit-H + molecular weight.
2//
3// Tests verify implicit-H inference matches Daylight 1988 organic-subset
4// rules; molecular weights are checked against NIST WebBook standard
5// values (atomic weights from IUPAC 2021 abridged).
6//
7// expect_exit: 0
8//
9// license_tier: ORIGINAL
10
11import "nx_chem.nx"
12import "nx_chem_molecule.nx"
13import "nx_chem_smiles.nx"
14import "nx_chem_periodic.nx"
15import "nx_chem_valence.nx"
16
17// =================================================================
18// A -- default valence lookup matches Daylight organic-subset.
19// =================================================================
20func a_default_valence() -> nx_int {
21 if nx_chem_default_valence(1) != 1 { return 11 } // H
22 if nx_chem_default_valence(5) != 3 { return 12 } // B
23 if nx_chem_default_valence(6) != 4 { return 13 } // C
24 if nx_chem_default_valence(7) != 3 { return 14 } // N
25 if nx_chem_default_valence(8) != 2 { return 15 } // O
26 if nx_chem_default_valence(9) != 1 { return 16 } // F
27 if nx_chem_default_valence(15) != 3 { return 17 } // P
28 if nx_chem_default_valence(16) != 2 { return 18 } // S
29 if nx_chem_default_valence(17) != 1 { return 19 } // Cl
30 if nx_chem_default_valence(35) != 1 { return 20 } // Br
31 if nx_chem_default_valence(53) != 1 { return 21 } // I
32 // outside organic subset returns -1
33 if nx_chem_default_valence(26) != -1 { return 22 } // Fe
34 if nx_chem_default_valence(80) != -1 { return 23 } // Hg
35 if nx_chem_default_valence(82) != -1 { return 24 } // Pb
36 return 0
37}
38
39// =================================================================
40// B -- methane "C" -- single C, 0 bonds, h_count = 4.
41// =================================================================
42func b_methane_h() -> nx_int {
43 let s: *u8 = sys_mmap(8); s[0] = 0x43
44 let m: *MolGraph = nx_chem_parse_smiles(s, 1)
45 if m.is_valid != 1 { return 31 }
46 let _f: nx_int = nx_chem_compute_implicit_h(m)
47 let a: *Atom = nx_chem_mol_atom(m, 0)
48 if a.h_count != 4 { return 32 }
49 if nx_chem_mol_h_count_total(m) != 4 { return 33 }
50 return 0
51}
52
53// =================================================================
54// C -- water "O" -- single O, 0 bonds, h_count = 2.
55// =================================================================
56func c_water_h() -> nx_int {
57 let s: *u8 = sys_mmap(8); s[0] = 0x4F
58 let m: *MolGraph = nx_chem_parse_smiles(s, 1)
59 if m.is_valid != 1 { return 41 }
60 let _f: nx_int = nx_chem_compute_implicit_h(m)
61 let a: *Atom = nx_chem_mol_atom(m, 0)
62 if a.h_count != 2 { return 42 }
63 return 0
64}
65
66// =================================================================
67// D -- ethanol "CCO" -- C(3H) - C(2H) - O(1H).
68// =================================================================
69func d_ethanol_h() -> nx_int {
70 let s: *u8 = sys_mmap(8)
71 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
72 let m: *MolGraph = nx_chem_parse_smiles(s, 3)
73 if m.is_valid != 1 { return 51 }
74 let _f: nx_int = nx_chem_compute_implicit_h(m)
75 let c0: *Atom = nx_chem_mol_atom(m, 0)
76 if c0.h_count != 3 { return 52 }
77 let c1: *Atom = nx_chem_mol_atom(m, 1)
78 if c1.h_count != 2 { return 53 }
79 let o: *Atom = nx_chem_mol_atom(m, 2)
80 if o.h_count != 1 { return 54 }
81 if nx_chem_mol_h_count_total(m) != 6 { return 55 }
82 return 0
83}
84
85// =================================================================
86// E -- ethene "C=C" -- each C has 1 double bond -> 4-2 = 2 H.
87// =================================================================
88func e_ethene_h() -> nx_int {
89 let s: *u8 = sys_mmap(8)
90 s[0] = 0x43; s[1] = 0x3D; s[2] = 0x43
91 let m: *MolGraph = nx_chem_parse_smiles(s, 3)
92 let _f: nx_int = nx_chem_compute_implicit_h(m)
93 let c0: *Atom = nx_chem_mol_atom(m, 0)
94 if c0.h_count != 2 { return 61 }
95 let c1: *Atom = nx_chem_mol_atom(m, 1)
96 if c1.h_count != 2 { return 62 }
97 return 0
98}
99
100// =================================================================
101// F -- ethyne "C#C" -- each C has 1 triple bond -> 4-3 = 1 H.
102// =================================================================
103func f_ethyne_h() -> nx_int {
104 let s: *u8 = sys_mmap(8)
105 s[0] = 0x43; s[1] = 0x23; s[2] = 0x43
106 let m: *MolGraph = nx_chem_parse_smiles(s, 3)
107 let _f: nx_int = nx_chem_compute_implicit_h(m)
108 let c0: *Atom = nx_chem_mol_atom(m, 0)
109 if c0.h_count != 1 { return 71 }
110 let c1: *Atom = nx_chem_mol_atom(m, 1)
111 if c1.h_count != 1 { return 72 }
112 return 0
113}
114
115// =================================================================
116// G -- benzene "c1ccccc1" -- each c has 2 aromatic bonds; 4 - 2 - 1 = 1 H.
117// =================================================================
118func g_benzene_h() -> nx_int {
119 let s: *u8 = sys_mmap(16)
120 s[0] = 0x63; s[1] = 0x31
121 s[2] = 0x63; s[3] = 0x63
122 s[4] = 0x63; s[5] = 0x63
123 s[6] = 0x63; s[7] = 0x31
124 let m: *MolGraph = nx_chem_parse_smiles(s, 8)
125 if m.is_valid != 1 { return 81 }
126 let _f: nx_int = nx_chem_compute_implicit_h(m)
127 var i: nx_int = 0
128 while i < 6 {
129 let a: *Atom = nx_chem_mol_atom(m, i)
130 if a.h_count != 1 { return 90 + i }
131 i = i + 1
132 }
133 if nx_chem_mol_h_count_total(m) != 6 { return 99 }
134 return 0
135}
136
137// =================================================================
138// H -- bracket-explicit H is preserved (NOT overwritten).
139// "[CH4]" -> Atom.h_count stays 4 (from bracket).
140// =================================================================
141func h_bracket_preserved() -> nx_int {
142 let s: *u8 = sys_mmap(8)
143 s[0] = 0x5B; s[1] = 0x43; s[2] = 0x48; s[3] = 0x34; s[4] = 0x5D
144 let m: *MolGraph = nx_chem_parse_smiles(s, 5)
145 let _f: nx_int = nx_chem_compute_implicit_h(m)
146 let a: *Atom = nx_chem_mol_atom(m, 0)
147 if a.h_count != 4 { return 101 }
148 return 0
149}
150
151// =================================================================
152// I -- methane molecular weight = 12.011 + 4 * 1.008 = 16.043
153// In Q3 milli-AMU: 12011 + 4032 = 16043.
154// =================================================================
155func i_methane_mw() -> nx_int {
156 let table: *Element = nx_chem_periodic_table_118()
157 let s: *u8 = sys_mmap(8); s[0] = 0x43
158 let m: *MolGraph = nx_chem_parse_smiles(s, 1)
159 let _f: nx_int = nx_chem_compute_implicit_h(m)
160 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
161 if mw != 16043 { return 111 }
162 return 0
163}
164
165// =================================================================
166// J -- water molecular weight = 15.999 + 2 * 1.008 = 18.015
167// In Q3 milli-AMU: 15999 + 2016 = 18015.
168// =================================================================
169func j_water_mw() -> nx_int {
170 let table: *Element = nx_chem_periodic_table_118()
171 let s: *u8 = sys_mmap(8); s[0] = 0x4F
172 let m: *MolGraph = nx_chem_parse_smiles(s, 1)
173 let _f: nx_int = nx_chem_compute_implicit_h(m)
174 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
175 if mw != 18015 { return 121 }
176 return 0
177}
178
179// =================================================================
180// K -- ethanol molecular weight = 2*12.011 + 15.999 + 6*1.008 = 46.069
181// In Q3 milli-AMU: 24022 + 15999 + 6048 = 46069.
182// NIST WebBook: ethanol 46.068.
183// =================================================================
184func k_ethanol_mw() -> nx_int {
185 let table: *Element = nx_chem_periodic_table_118()
186 let s: *u8 = sys_mmap(8)
187 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
188 let m: *MolGraph = nx_chem_parse_smiles(s, 3)
189 let _f: nx_int = nx_chem_compute_implicit_h(m)
190 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
191 if mw != 46069 { return 131 }
192 return 0
193}
194
195// =================================================================
196// L -- benzene molecular weight = 6*12.011 + 6*1.008 = 78.114
197// In Q3 milli-AMU: 72066 + 6048 = 78114.
198// NIST WebBook: benzene 78.114.
199// =================================================================
200func l_benzene_mw() -> nx_int {
201 let table: *Element = nx_chem_periodic_table_118()
202 let s: *u8 = sys_mmap(16)
203 s[0] = 0x63; s[1] = 0x31
204 s[2] = 0x63; s[3] = 0x63
205 s[4] = 0x63; s[5] = 0x63
206 s[6] = 0x63; s[7] = 0x31
207 let m: *MolGraph = nx_chem_parse_smiles(s, 8)
208 let _f: nx_int = nx_chem_compute_implicit_h(m)
209 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
210 if mw != 78114 { return 141 }
211 return 0
212}
213
214// =================================================================
215// M -- vitamin D3 backbone test: cholecalciferol simplified SMILES
216// "C(C(C)C)C(=C)C" -- 2,3-dimethyl-1-butene-class. Just verify
217// the parser + H-count + MW pipeline works on a small branched
218// olefin.
219// 6 C atoms; 1 C=C; rest single.
220// MW = 6*12.011 + 12*1.008 = 84.162 (the 2,3-dimethyl-1-butene)
221// = 72066 + 12096 = 84162 milli-AMU
222// NIST: 2,3-dimethyl-1-butene = 84.16.
223// =================================================================
224func m_branched_olefin_mw() -> nx_int {
225 let table: *Element = nx_chem_periodic_table_118()
226 // 2,3-dimethyl-1-butene: "C=C(C)C(C)C" = CH2=C(CH3)CH(CH3)CH3
227 // 6 C atoms; 5 bonds (1 double + 4 single); 12 H implicit
228 let s: *u8 = sys_mmap(16)
229 s[0] = 0x43 // C
230 s[1] = 0x3D // =
231 s[2] = 0x43 // C
232 s[3] = 0x28 // (
233 s[4] = 0x43 // C
234 s[5] = 0x29 // )
235 s[6] = 0x43 // C
236 s[7] = 0x28 // (
237 s[8] = 0x43 // C
238 s[9] = 0x29 // )
239 s[10] = 0x43 // C
240 let m: *MolGraph = nx_chem_parse_smiles(s, 11)
241 if m.is_valid != 1 { return 151 }
242 if m.n_atoms != 6 { return 152 }
243 let _f: nx_int = nx_chem_compute_implicit_h(m)
244 if nx_chem_mol_heavy_atom_count(m) != 6 { return 153 }
245 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
246 if mw != 84162 { return 154 }
247 return 0
248}
249
250// =================================================================
251// N -- supplement adulterant: chloride salt of sodium "[Na+].[Cl-]"
252// MW = Na (22.990) + Cl (35.453) = 58.443
253// In Q3: 22990 + 35453 = 58443
254// (charge doesn't add to atomic weight here; no Hs)
255// =================================================================
256func n_nacl_mw() -> nx_int {
257 let table: *Element = nx_chem_periodic_table_118()
258 let s: *u8 = sys_mmap(16)
259 s[0] = 0x5B; s[1] = 0x4E; s[2] = 0x61; s[3] = 0x2B; s[4] = 0x5D
260 s[5] = 0x2E
261 s[6] = 0x5B; s[7] = 0x43; s[8] = 0x6C; s[9] = 0x2D; s[10] = 0x5D
262 let m: *MolGraph = nx_chem_parse_smiles(s, 11)
263 if m.is_valid != 1 { return 161 }
264 if m.n_atoms != 2 { return 162 }
265 let _f: nx_int = nx_chem_compute_implicit_h(m)
266 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
267 if mw != 58443 { return 163 }
268 return 0
269}
270
271// =================================================================
272// O -- heavy-metal adulterant: lead acetate Pb(OAc)2 simplified
273// as just "[Pb+2]" alone -- MW = Pb (207.200) = 207200 milli-AMU.
274// =================================================================
275func o_lead_mw() -> nx_int {
276 let table: *Element = nx_chem_periodic_table_118()
277 let s: *u8 = sys_mmap(8)
278 s[0] = 0x5B; s[1] = 0x50; s[2] = 0x62; s[3] = 0x2B; s[4] = 0x32; s[5] = 0x5D
279 let m: *MolGraph = nx_chem_parse_smiles(s, 6)
280 if m.is_valid != 1 { return 171 }
281 let _f: nx_int = nx_chem_compute_implicit_h(m)
282 let mw: nx_int = nx_chem_mol_weight_q3(m, table)
283 if mw != 207200 { return 172 }
284 return 0
285}
286
287func main() -> nx_exit {
288 println("=== nx_chem_valence -- C2.3a KAT: implicit-H + molecular weight ===" as *u8)
289
290 let ra: nx_int = a_default_valence()
291 if ra != 0 { println("A default_valence FAIL" as *u8); return ra }
292 println("A default_valence PASS Daylight 1988 organic subset valences; non-subset returns -1" as *u8)
293
294 let rb: nx_int = b_methane_h()
295 if rb != 0 { println("B methane_h FAIL" as *u8); return rb }
296 println("B methane_h PASS C with 0 bonds -> 4 H implicit" as *u8)
297
298 let rc: nx_int = c_water_h()
299 if rc != 0 { println("C water_h FAIL" as *u8); return rc }
300 println("C water_h PASS O with 0 bonds -> 2 H implicit" as *u8)
301
302 let rd: nx_int = d_ethanol_h()
303 if rd != 0 { println("D ethanol_h FAIL" as *u8); return rd }
304 println("D ethanol_h PASS CCO -> C(3H) + C(2H) + O(1H) = 6 H total" as *u8)
305
306 let re: nx_int = e_ethene_h()
307 if re != 0 { println("E ethene_h FAIL" as *u8); return re }
308 println("E ethene_h PASS C=C -> 2 H each (4 - 2)" as *u8)
309
310 let rf: nx_int = f_ethyne_h()
311 if rf != 0 { println("F ethyne_h FAIL" as *u8); return rf }
312 println("F ethyne_h PASS C#C -> 1 H each (4 - 3)" as *u8)
313
314 let rg: nx_int = g_benzene_h()
315 if rg != 0 { println("G benzene_h FAIL" as *u8); return rg }
316 println("G benzene_h PASS c1ccccc1 -> 1 H per aromatic C (4 - 2 - 1)" as *u8)
317
318 let rh: nx_int = h_bracket_preserved()
319 if rh != 0 { println("H bracket_preserved FAIL" as *u8); return rh }
320 println("H bracket_preserved PASS [CH4] -> h_count stays 4 (NOT overwritten by inference)" as *u8)
321
322 let ri: nx_int = i_methane_mw()
323 if ri != 0 { println("I methane_mw FAIL" as *u8); return ri }
324 println("I methane_mw PASS C -> MW 16.043 (matches NIST WebBook)" as *u8)
325
326 let rj: nx_int = j_water_mw()
327 if rj != 0 { println("J water_mw FAIL" as *u8); return rj }
328 println("J water_mw PASS O -> MW 18.015 (matches NIST WebBook)" as *u8)
329
330 let rk: nx_int = k_ethanol_mw()
331 if rk != 0 { println("K ethanol_mw FAIL" as *u8); return rk }
332 println("K ethanol_mw PASS CCO -> MW 46.069 (matches NIST WebBook)" as *u8)
333
334 let rl: nx_int = l_benzene_mw()
335 if rl != 0 { println("L benzene_mw FAIL" as *u8); return rl }
336 println("L benzene_mw PASS c1ccccc1 -> MW 78.114 (matches NIST WebBook)" as *u8)
337
338 let rm: nx_int = m_branched_olefin_mw()
339 if rm != 0 { println("M branched_olefin_mw FAIL" as *u8); return rm }
340 println("M branched_olefin_mw PASS C(C(C)C)C(=C)C -> MW 84.162 (2,3-dimethyl-1-butene NIST)" as *u8)
341
342 let rn: nx_int = n_nacl_mw()
343 if rn != 0 { println("N nacl_mw FAIL" as *u8); return rn }
344 println("N nacl_mw PASS [Na+].[Cl-] -> MW 58.443 (sodium chloride)" as *u8)
345
346 let ro: nx_int = o_lead_mw()
347 if ro != 0 { println("O lead_mw FAIL" as *u8); return ro }
348 println("O lead_mw PASS [Pb+2] -> MW 207.200 (supplement-arc heavy-metal adulterant)" as *u8)
349
350 println("" as *u8)
351 println("=== C2.3a substrate milestone PASS ===" as *u8)
352 println(" implicit-H : Daylight 1988 organic-subset rules; aromatic-C handled;" as *u8)
353 println(" aromatic non-C honestly deferred (pyrrole-vs-pyridine = C2.5)" as *u8)
354 println(" molecular weight : Q3 milli-AMU from IUPAC 2021 atomic weights + implicit Hs;" as *u8)
355 println(" verified against NIST WebBook for water, methane, ethanol," as *u8)
356 println(" benzene, 2,3-dimethyl-1-butene, NaCl, Pb" as *u8)
357 println(" honest gaps : aromatic non-C h_count (C2.5); hypervalent N/P/S (bracket-required);" as *u8)
358 println(" exact-mass via monoisotope (use AtomicData.iso_mass_q4); canonical SMILES (C2.3b)" as *u8)
359 println(" next : C2.3b -- canonical SMILES emit + Morgan extended-connectivity" as *u8)
360 return 0
361}