nx_chem_smiles_emit_test.nx source
↩ module page · 289 lines · 12404 B
1// nx_chem_smiles_emit_test.nx -- C2.3b KAT for SMILES emit.
2//
3// Round-trip test: SMILES -> MolGraph -> SMILES' -> MolGraph' compares
4// structurally equal. Output SMILES may not be byte-identical to input
5// (canonical ranking is C2.3c), but it must parse back to the same graph.
6//
7// expect_exit: 0
8//
9// license_tier: ORIGINAL
10
11import "nx_chem_molecule.nx"
12import "nx_chem_smiles.nx"
13import "nx_chem_smiles_emit.nx"
14
15// Helper: compare two MolGraphs structurally.
16// Returns 0 if equal, non-zero error code otherwise.
17func cmp_graphs(m1: *MolGraph, m2: *MolGraph) -> nx_int {
18 if m1.n_atoms != m2.n_atoms { return 1 }
19 if m1.n_bonds != m2.n_bonds { return 2 }
20 // Compare atom Z + charge (in input order; this is a weak structural
21 // check but adequate when emit preserves atom order)
22 var i: nx_int = 0
23 while i < m1.n_atoms {
24 let a1: *Atom = nx_chem_mol_atom(m1, i)
25 let a2: *Atom = nx_chem_mol_atom(m2, i)
26 if a1.z != a2.z { return 10 + i }
27 if a1.charge != a2.charge { return 100 + i }
28 if a1.isotope != a2.isotope { return 200 + i }
29 i = i + 1
30 }
31 // Compare bond orders (in input order)
32 var j: nx_int = 0
33 while j < m1.n_bonds {
34 let b1: *Bond = nx_chem_mol_bond(m1, j)
35 let b2: *Bond = nx_chem_mol_bond(m2, j)
36 if b1.order != b2.order { return 300 + j }
37 j = j + 1
38 }
39 return 0
40}
41
42// Helper: parse-emit-parse round-trip on a SMILES string.
43// Returns 0 if round-trip preserves graph structure.
44func roundtrip_check(src: *u8, n: nx_int, base_err: nx_int) -> nx_int {
45 let m1: *MolGraph = nx_chem_parse_smiles(src, n)
46 if m1.is_valid != 1 { return base_err + 1 }
47 let out: *u8 = (sys_mmap(256)) as *u8
48 let len_io: *nx_int = (sys_mmap(8)) as *nx_int
49 let r: nx_int = nx_chem_emit_smiles(m1, out, 256, len_io)
50 if r != 0 { return base_err + 2 }
51 if len_io[0] == 0 { return base_err + 3 }
52 let m2: *MolGraph = nx_chem_parse_smiles(out, len_io[0])
53 if m2.is_valid != 1 { return base_err + 4 }
54 let c: nx_int = cmp_graphs(m1, m2)
55 if c != 0 { return base_err + 10 + c }
56 return 0
57}
58
59// =================================================================
60// A -- water "O" round-trip.
61// =================================================================
62func a_water() -> nx_int {
63 let s: *u8 = sys_mmap(8); s[0] = 0x4F
64 return roundtrip_check(s, 1, 100)
65}
66
67// =================================================================
68// B -- methane "C" round-trip.
69// =================================================================
70func b_methane() -> nx_int {
71 let s: *u8 = sys_mmap(8); s[0] = 0x43
72 return roundtrip_check(s, 1, 200)
73}
74
75// =================================================================
76// C -- ethanol "CCO" round-trip.
77// =================================================================
78func c_ethanol() -> nx_int {
79 let s: *u8 = sys_mmap(8)
80 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F
81 return roundtrip_check(s, 3, 300)
82}
83
84// =================================================================
85// D -- ethene "C=C" round-trip.
86// =================================================================
87func d_ethene() -> nx_int {
88 let s: *u8 = sys_mmap(8)
89 s[0] = 0x43; s[1] = 0x3D; s[2] = 0x43
90 return roundtrip_check(s, 3, 400)
91}
92
93// =================================================================
94// E -- ethyne "C#C" round-trip.
95// =================================================================
96func e_ethyne() -> nx_int {
97 let s: *u8 = sys_mmap(8)
98 s[0] = 0x43; s[1] = 0x23; s[2] = 0x43
99 return roundtrip_check(s, 3, 500)
100}
101
102// =================================================================
103// F -- isobutane "CC(C)C" round-trip (branch).
104// =================================================================
105func f_isobutane() -> nx_int {
106 let s: *u8 = sys_mmap(8)
107 s[0] = 0x43; s[1] = 0x43; s[2] = 0x28; s[3] = 0x43; s[4] = 0x29; s[5] = 0x43
108 return roundtrip_check(s, 6, 600)
109}
110
111// =================================================================
112// G -- cyclohexane "C1CCCCC1" round-trip (ring closure).
113// =================================================================
114func g_cyclohexane() -> nx_int {
115 let s: *u8 = sys_mmap(16)
116 s[0] = 0x43; s[1] = 0x31
117 s[2] = 0x43; s[3] = 0x43
118 s[4] = 0x43; s[5] = 0x43
119 s[6] = 0x43; s[7] = 0x31
120 return roundtrip_check(s, 8, 700)
121}
122
123// =================================================================
124// H -- benzene "c1ccccc1" round-trip (aromatic ring).
125// =================================================================
126func h_benzene() -> nx_int {
127 let s: *u8 = sys_mmap(16)
128 s[0] = 0x63; s[1] = 0x31
129 s[2] = 0x63; s[3] = 0x63
130 s[4] = 0x63; s[5] = 0x63
131 s[6] = 0x63; s[7] = 0x31
132 return roundtrip_check(s, 8, 800)
133}
134
135// =================================================================
136// I -- bracket atoms "[Na+].[Cl-]" round-trip (multi-component + charge).
137// =================================================================
138func i_nacl() -> nx_int {
139 let s: *u8 = sys_mmap(16)
140 s[0] = 0x5B; s[1] = 0x4E; s[2] = 0x61; s[3] = 0x2B; s[4] = 0x5D
141 s[5] = 0x2E
142 s[6] = 0x5B; s[7] = 0x43; s[8] = 0x6C; s[9] = 0x2D; s[10] = 0x5D
143 return roundtrip_check(s, 11, 900)
144}
145
146// =================================================================
147// J -- supplement-arc adulterant "[Pb+2]" round-trip (heavy metal).
148// =================================================================
149func j_lead() -> nx_int {
150 let s: *u8 = sys_mmap(8)
151 s[0] = 0x5B; s[1] = 0x50; s[2] = 0x62; s[3] = 0x2B; s[4] = 0x32; s[5] = 0x5D
152 return roundtrip_check(s, 6, 1000)
153}
154
155// =================================================================
156// K -- diethyl ether "CCOCC" round-trip.
157// =================================================================
158func k_diethyl_ether() -> nx_int {
159 let s: *u8 = sys_mmap(8)
160 s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F; s[3] = 0x43; s[4] = 0x43
161 return roundtrip_check(s, 5, 1100)
162}
163
164// =================================================================
165// L -- direct emit check: "O" should emit as "O" (1 byte).
166// =================================================================
167func l_emit_water_direct() -> nx_int {
168 let s: *u8 = sys_mmap(8); s[0] = 0x4F
169 let m: *MolGraph = nx_chem_parse_smiles(s, 1)
170 let out: *u8 = (sys_mmap(16)) as *u8
171 let len_io: *nx_int = (sys_mmap(8)) as *nx_int
172 let r: nx_int = nx_chem_emit_smiles(m, out, 16, len_io)
173 if r != 0 { return 1201 }
174 if len_io[0] != 1 { return 1202 }
175 if (out[0] & 0xff) != 0x4F { return 1203 } // 'O'
176 return 0
177}
178
179// =================================================================
180// M -- direct emit: "C=C" should emit "C=C" exactly.
181// =================================================================
182func m_emit_ethene_direct() -> nx_int {
183 let s: *u8 = sys_mmap(8)
184 s[0] = 0x43; s[1] = 0x3D; s[2] = 0x43
185 let m: *MolGraph = nx_chem_parse_smiles(s, 3)
186 let out: *u8 = (sys_mmap(16)) as *u8
187 let len_io: *nx_int = (sys_mmap(8)) as *nx_int
188 let r: nx_int = nx_chem_emit_smiles(m, out, 16, len_io)
189 if r != 0 { return 1301 }
190 if len_io[0] != 3 { return 1302 }
191 if (out[0] & 0xff) != 0x43 { return 1303 }
192 if (out[1] & 0xff) != 0x3D { return 1304 }
193 if (out[2] & 0xff) != 0x43 { return 1305 }
194 return 0
195}
196
197// =================================================================
198// N -- direct emit: "[Pb+2]" emits exactly as "[Pb+2]" (round-trip
199// byte-identical for bracket atoms with no free-form ambiguity).
200// =================================================================
201func n_emit_lead_direct() -> nx_int {
202 let s: *u8 = sys_mmap(8)
203 s[0] = 0x5B; s[1] = 0x50; s[2] = 0x62; s[3] = 0x2B; s[4] = 0x32; s[5] = 0x5D
204 let m: *MolGraph = nx_chem_parse_smiles(s, 6)
205 let out: *u8 = (sys_mmap(16)) as *u8
206 let len_io: *nx_int = (sys_mmap(8)) as *nx_int
207 let r: nx_int = nx_chem_emit_smiles(m, out, 16, len_io)
208 if r != 0 { return 1401 }
209 if len_io[0] != 6 { return 1402 }
210 if (out[0] & 0xff) != 0x5B { return 1403 } // [
211 if (out[1] & 0xff) != 0x50 { return 1404 } // P
212 if (out[2] & 0xff) != 0x62 { return 1405 } // b
213 if (out[3] & 0xff) != 0x2B { return 1406 } // +
214 if (out[4] & 0xff) != 0x32 { return 1407 } // 2
215 if (out[5] & 0xff) != 0x5D { return 1408 } // ]
216 return 0
217}
218
219func main() -> nx_exit {
220 println("=== nx_chem_smiles_emit -- C2.3b KAT: MolGraph -> SMILES emit ===" as *u8)
221
222 let ra: nx_int = a_water()
223 if ra != 0 { println("A water round-trip FAIL" as *u8); return ra }
224 println("A water_roundtrip PASS O -> emit -> parse same graph" as *u8)
225
226 let rb: nx_int = b_methane()
227 if rb != 0 { println("B methane round-trip FAIL" as *u8); return rb }
228 println("B methane_roundtrip PASS C -> emit -> parse same graph" as *u8)
229
230 let rc: nx_int = c_ethanol()
231 if rc != 0 { println("C ethanol round-trip FAIL" as *u8); return rc }
232 println("C ethanol_roundtrip PASS CCO -> emit -> parse same 3 atoms / 2 single bonds" as *u8)
233
234 let rd: nx_int = d_ethene()
235 if rd != 0 { println("D ethene round-trip FAIL" as *u8); return rd }
236 println("D ethene_roundtrip PASS C=C -> emit -> parse same double bond" as *u8)
237
238 let re: nx_int = e_ethyne()
239 if re != 0 { println("E ethyne round-trip FAIL" as *u8); return re }
240 println("E ethyne_roundtrip PASS C#C -> emit -> parse same triple bond" as *u8)
241
242 let rf: nx_int = f_isobutane()
243 if rf != 0 { println("F isobutane round-trip FAIL" as *u8); return rf }
244 println("F isobutane_roundtrip PASS CC(C)C -> emit (with branch) -> parse same 4 atoms" as *u8)
245
246 let rg: nx_int = g_cyclohexane()
247 if rg != 0 { println("G cyclohexane round-trip FAIL" as *u8); return rg }
248 println("G cyclohexane_roundtrip PASS C1CCCCC1 -> emit (with ring digit) -> parse same 6-atom ring" as *u8)
249
250 let rh: nx_int = h_benzene()
251 if rh != 0 { println("H benzene round-trip FAIL" as *u8); return rh }
252 println("H benzene_roundtrip PASS c1ccccc1 -> emit -> parse same aromatic 6-ring" as *u8)
253
254 let ri: nx_int = i_nacl()
255 if ri != 0 { println("I nacl round-trip FAIL" as *u8); return ri }
256 println("I nacl_roundtrip PASS [Na+].[Cl-] -> emit (multi-component) -> parse same 2 charged atoms" as *u8)
257
258 let rj: nx_int = j_lead()
259 if rj != 0 { println("J lead round-trip FAIL" as *u8); return rj }
260 println("J lead_roundtrip PASS [Pb+2] -> emit -> parse same Pb+2 (supplement-arc heavy metal)" as *u8)
261
262 let rk: nx_int = k_diethyl_ether()
263 if rk != 0 { println("K diethyl_ether round-trip FAIL" as *u8); return rk }
264 println("K diethyl_ether_roundtrip PASS CCOCC -> emit -> parse same 5 atoms / 4 bonds" as *u8)
265
266 let rl: nx_int = l_emit_water_direct()
267 if rl != 0 { println("L emit_water_direct FAIL" as *u8); return rl }
268 println("L emit_water_direct PASS O -> emit length=1 byte 'O' (byte-identical for atom-only)" as *u8)
269
270 let rm: nx_int = m_emit_ethene_direct()
271 if rm != 0 { println("M emit_ethene_direct FAIL" as *u8); return rm }
272 println("M emit_ethene_direct PASS C=C -> emit byte-identical 'C=C'" as *u8)
273
274 let rn: nx_int = n_emit_lead_direct()
275 if rn != 0 { println("N emit_lead_direct FAIL" as *u8); return rn }
276 println("N emit_lead_direct PASS [Pb+2] -> emit byte-identical '[Pb+2]'" as *u8)
277
278 println("" as *u8)
279 println("=== C2.3b substrate milestone PASS ===" as *u8)
280 println(" emit : DFS-based + ring numbering + branches + brackets-when-needed" as *u8)
281 println(" round-trip : 11 reference molecules parse -> emit -> parse same graph" as *u8)
282 println(" byte-identical : single atoms + simple multi-bonds + bracket atoms emit byte-stable" as *u8)
283 println(" honest gaps : Morgan canonical ranking (C2.3c) -- two different SMILES of same molecule" as *u8)
284 println(" do NOT yet produce byte-identical output; stereo emit (C2.3c after canonical);" as *u8)
285 println(" CIP-rule E/Z resolution (C2.3c)" as *u8)
286 println(" next : C2.3c -- Morgan canonical ranking + stereo emit -> unlocks E1 EXCEED axis" as *u8)
287 println(" (bit-reproducible canonical form across versions/platforms forever)" as *u8)
288 return 0
289}