code wiki / (root) / nx_chem_smiles_emit_test.nx

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}