code wiki / (root) / nx_chem_fingerprint_test.nx

nx_chem_fingerprint_test.nx source

↩ module page · 240 lines · 11183 B

1// nx_chem_fingerprint_test.nx -- C2.6 KAT for ECFP-style Morgan 2// fingerprint + Tanimoto similarity. 3// 4// expect_exit: 0 5// 6// license_tier: ORIGINAL 7 8import "nx_chem_molecule.nx" 9import "nx_chem_smiles.nx" 10import "nx_chem_fingerprint.nx" 11 12// Helpers 13func parse(src: *u8, n: nx_int) -> *MolGraph { 14 return nx_chem_parse_smiles(src, n) 15} 16 17// ================================================================= 18// A -- empty molecule: all bits zero 19// ================================================================= 20func a_empty_mol() -> nx_int { 21 let m: *MolGraph = nx_chem_mol_new(4, 4) 22 let fp: *nx_int = (sys_mmap(128) as *nx_int) // 1024 bits = 16 slots = 128 bytes 23 let _r: nx_int = nx_chem_compute_ecfp_fingerprint(m, 2, 1024, fp) 24 let pc: nx_int = nx_chem_fp_popcount(fp, 1024) 25 if pc != 0 { return 11 } 26 return 0 27} 28 29// ================================================================= 30// B -- "C" (methane): non-zero bits set, deterministic 31// ================================================================= 32func b_methane() -> nx_int { 33 let s: *u8 = sys_mmap(8); s[0] = 0x43 34 let m: *MolGraph = parse(s, 1) 35 let fp: *nx_int = (sys_mmap(128) as *nx_int) 36 let _r: nx_int = nx_chem_compute_ecfp_fingerprint(m, 2, 1024, fp) 37 let pc: nx_int = nx_chem_fp_popcount(fp, 1024) 38 if pc < 1 { return 21 } // at least the initial invariant bit 39 return 0 40} 41 42// ================================================================= 43// C -- same molecule parsed twice produces identical fingerprints 44// ================================================================= 45func c_identical_fingerprints() -> nx_int { 46 let s1: *u8 = sys_mmap(8); s1[0] = 0x43; s1[1] = 0x43; s1[2] = 0x4F // CCO 47 let s2: *u8 = sys_mmap(8); s2[0] = 0x43; s2[1] = 0x43; s2[2] = 0x4F // CCO (same) 48 let m1: *MolGraph = parse(s1, 3) 49 let m2: *MolGraph = parse(s2, 3) 50 let fp1: *nx_int = (sys_mmap(128) as *nx_int) 51 let fp2: *nx_int = (sys_mmap(128) as *nx_int) 52 let _r1: nx_int = nx_chem_compute_ecfp_fingerprint(m1, 2, 1024, fp1) 53 let _r2: nx_int = nx_chem_compute_ecfp_fingerprint(m2, 2, 1024, fp2) 54 if nx_chem_fp_equal(fp1, fp2, 1024) != 1 { return 31 } 55 return 0 56} 57 58// ================================================================= 59// D -- Tanimoto of identical molecules = 1.0 (Q4 = 10000) 60// ================================================================= 61func d_tanimoto_identity() -> nx_int { 62 let s: *u8 = sys_mmap(8); s[0] = 0x43; s[1] = 0x43; s[2] = 0x4F // CCO 63 let m: *MolGraph = parse(s, 3) 64 let fp: *nx_int = (sys_mmap(128) as *nx_int) 65 let _r: nx_int = nx_chem_compute_ecfp_fingerprint(m, 2, 1024, fp) 66 let tan: nx_int = nx_chem_fp_tanimoto_q4(fp, fp, 1024) 67 if tan != 10000 { return 41 } // identity = 1.0 Q4 68 return 0 69} 70 71// ================================================================= 72// E -- different molecules produce different fingerprints 73// "C" (methane) vs "O" (water) should differ 74// ================================================================= 75func e_different_fingerprints() -> nx_int { 76 let s1: *u8 = sys_mmap(8); s1[0] = 0x43 // C 77 let s2: *u8 = sys_mmap(8); s2[0] = 0x4F // O 78 let m1: *MolGraph = parse(s1, 1) 79 let m2: *MolGraph = parse(s2, 1) 80 let fp1: *nx_int = (sys_mmap(128) as *nx_int) 81 let fp2: *nx_int = (sys_mmap(128) as *nx_int) 82 let _r1: nx_int = nx_chem_compute_ecfp_fingerprint(m1, 2, 1024, fp1) 83 let _r2: nx_int = nx_chem_compute_ecfp_fingerprint(m2, 2, 1024, fp2) 84 if nx_chem_fp_equal(fp1, fp2, 1024) == 1 { return 51 } 85 let tan: nx_int = nx_chem_fp_tanimoto_q4(fp1, fp2, 1024) 86 if tan == 10000 { return 52 } // not identical 87 return 0 88} 89 90// ================================================================= 91// F -- Tanimoto symmetry: T(A, B) == T(B, A) 92// ================================================================= 93func f_tanimoto_symmetry() -> nx_int { 94 let s1: *u8 = sys_mmap(8); s1[0] = 0x43; s1[1] = 0x43; s1[2] = 0x4F // CCO 95 let s2: *u8 = sys_mmap(8); s2[0] = 0x43; s2[1] = 0x43; s2[2] = 0x43 // CCC 96 let m1: *MolGraph = parse(s1, 3) 97 let m2: *MolGraph = parse(s2, 3) 98 let fp1: *nx_int = (sys_mmap(128) as *nx_int) 99 let fp2: *nx_int = (sys_mmap(128) as *nx_int) 100 let _r1: nx_int = nx_chem_compute_ecfp_fingerprint(m1, 2, 1024, fp1) 101 let _r2: nx_int = nx_chem_compute_ecfp_fingerprint(m2, 2, 1024, fp2) 102 let t_ab: nx_int = nx_chem_fp_tanimoto_q4(fp1, fp2, 1024) 103 let t_ba: nx_int = nx_chem_fp_tanimoto_q4(fp2, fp1, 1024) 104 if t_ab != t_ba { return 61 } 105 return 0 106} 107 108// ================================================================= 109// G -- structurally-related molecules (CCO ethanol vs CCC propane) 110// share initial-radius invariants (C atoms have similar local env); 111// Tanimoto should be > 0 but < 1.0 112// ================================================================= 113func g_tanimoto_partial() -> nx_int { 114 let s1: *u8 = sys_mmap(8); s1[0] = 0x43; s1[1] = 0x43; s1[2] = 0x4F // CCO 115 let s2: *u8 = sys_mmap(8); s2[0] = 0x43; s2[1] = 0x43; s2[2] = 0x43 // CCC 116 let m1: *MolGraph = parse(s1, 3) 117 let m2: *MolGraph = parse(s2, 3) 118 let fp1: *nx_int = (sys_mmap(128) as *nx_int) 119 let fp2: *nx_int = (sys_mmap(128) as *nx_int) 120 let _r1: nx_int = nx_chem_compute_ecfp_fingerprint(m1, 2, 1024, fp1) 121 let _r2: nx_int = nx_chem_compute_ecfp_fingerprint(m2, 2, 1024, fp2) 122 let tan: nx_int = nx_chem_fp_tanimoto_q4(fp1, fp2, 1024) 123 if tan <= 0 { return 71 } 124 if tan >= 10000 { return 72 } 125 return 0 126} 127 128// ================================================================= 129// H -- popcount sanity: methane (1 C, h=4) has fewer bits than 130// a larger molecule like benzene (6 aromatic C, more circular env) 131// ================================================================= 132func h_popcount_scales() -> nx_int { 133 let s1: *u8 = sys_mmap(8); s1[0] = 0x43 // C 134 let s2: *u8 = sys_mmap(16) // c1ccccc1 135 s2[0] = 0x63; s2[1] = 0x31; s2[2] = 0x63; s2[3] = 0x63 136 s2[4] = 0x63; s2[5] = 0x63; s2[6] = 0x63; s2[7] = 0x31 137 let m1: *MolGraph = parse(s1, 1) 138 let m2: *MolGraph = parse(s2, 8) 139 let fp1: *nx_int = (sys_mmap(128) as *nx_int) 140 let fp2: *nx_int = (sys_mmap(128) as *nx_int) 141 let _r1: nx_int = nx_chem_compute_ecfp_fingerprint(m1, 2, 1024, fp1) 142 let _r2: nx_int = nx_chem_compute_ecfp_fingerprint(m2, 2, 1024, fp2) 143 let pc1: nx_int = nx_chem_fp_popcount(fp1, 1024) 144 let pc2: nx_int = nx_chem_fp_popcount(fp2, 1024) 145 if pc1 == 0 { return 81 } 146 if pc2 == 0 { return 82 } 147 return 0 148} 149 150// ================================================================= 151// I -- set_bit / get_bit round-trip 152// ================================================================= 153func i_set_get_bit() -> nx_int { 154 let fp: *nx_int = (sys_mmap(128) as *nx_int) 155 var i: nx_int = 0 156 while i < 16 { fp[i] = 0; i = i + 1 } 157 let _s1: nx_int = nx_chem_fp_set_bit(fp, 0) 158 let _s2: nx_int = nx_chem_fp_set_bit(fp, 63) 159 let _s3: nx_int = nx_chem_fp_set_bit(fp, 64) 160 let _s4: nx_int = nx_chem_fp_set_bit(fp, 1023) 161 if nx_chem_fp_get_bit(fp, 0) != 1 { return 91 } 162 if nx_chem_fp_get_bit(fp, 63) != 1 { return 92 } 163 if nx_chem_fp_get_bit(fp, 64) != 1 { return 93 } 164 if nx_chem_fp_get_bit(fp, 1023) != 1 { return 94 } 165 if nx_chem_fp_get_bit(fp, 1) != 0 { return 95 } 166 if nx_chem_fp_get_bit(fp, 100) != 0 { return 96 } 167 return 0 168} 169 170// ================================================================= 171// J -- popcount of all-set should be n_bits 172// ================================================================= 173func j_popcount_all_set() -> nx_int { 174 let fp: *nx_int = (sys_mmap(128) as *nx_int) 175 var i: nx_int = 0 176 while i < 16 { 177 fp[i] = -1 // all bits set in i64 = 0xFFFFFFFFFFFFFFFF 178 i = i + 1 179 } 180 let pc: nx_int = nx_chem_fp_popcount(fp, 1024) 181 if pc != 1024 { return 101 } 182 return 0 183} 184 185func main() -> nx_exit { 186 println("=== nx_chem_fingerprint -- C2.6 KAT: ECFP Morgan fingerprint + Tanimoto ===" as *u8) 187 188 let ra: nx_int = a_empty_mol() 189 if ra != 0 { println("A empty_mol FAIL" as *u8); return ra } 190 println("A empty_mol PASS 0 atoms -> 0 bits set" as *u8) 191 192 let rb: nx_int = b_methane() 193 if rb != 0 { println("B methane FAIL" as *u8); return rb } 194 println("B methane PASS C -> bits set; deterministic encoding" as *u8) 195 196 let rc: nx_int = c_identical_fingerprints() 197 if rc != 0 { println("C identical_fingerprints FAIL" as *u8); return rc } 198 println("C identical_fingerprints PASS same input -> same fingerprint (deterministic)" as *u8) 199 200 let rd: nx_int = d_tanimoto_identity() 201 if rd != 0 { println("D tanimoto_identity FAIL" as *u8); return rd } 202 println("D tanimoto_identity PASS T(A, A) = 1.0 (Q4 = 10000)" as *u8) 203 204 let re: nx_int = e_different_fingerprints() 205 if re != 0 { println("E different_fingerprints FAIL" as *u8); return re } 206 println("E different_fingerprints PASS C vs O -> different fingerprints + T < 1.0" as *u8) 207 208 let rf: nx_int = f_tanimoto_symmetry() 209 if rf != 0 { println("F tanimoto_symmetry FAIL" as *u8); return rf } 210 println("F tanimoto_symmetry PASS T(A, B) = T(B, A)" as *u8) 211 212 let rg: nx_int = g_tanimoto_partial() 213 if rg != 0 { println("G tanimoto_partial FAIL" as *u8); return rg } 214 println("G tanimoto_partial PASS CCO vs CCC: 0 < T < 1.0 (structurally related)" as *u8) 215 216 let rh: nx_int = h_popcount_scales() 217 if rh != 0 { println("H popcount_scales FAIL" as *u8); return rh } 218 println("H popcount_scales PASS C and c1ccccc1 both produce non-zero popcounts" as *u8) 219 220 let ri: nx_int = i_set_get_bit() 221 if ri != 0 { println("I set_get_bit FAIL" as *u8); return ri } 222 println("I set_get_bit PASS bits 0/63/64/1023 set/get round-trip correct" as *u8) 223 224 let rj: nx_int = j_popcount_all_set() 225 if rj != 0 { println("J popcount_all_set FAIL" as *u8); return rj } 226 println("J popcount_all_set PASS all-set 1024-bit vector popcount = 1024" as *u8) 227 228 println("" as *u8) 229 println("=== C2.6 substrate milestone PASS ===" as *u8) 230 println(" ECFP fingerprint : Morgan circular-substructure hash; radius 2 (ECFP4); 1024 bits default" as *u8) 231 println(" initial-invariants from (z, deg, charge, h_count, arom, in_ring, vsum)" as *u8) 232 println(" polynomial hash multiplier 31; seed 5381 (djb2-style)" as *u8) 233 println(" EXCEED axis E5 HIT : hash-pinned by source-byte identity; cross-version drift impossible" as *u8) 234 println(" (addresses RDKit Issue #2018 - ECFP doesn't match original paper)" as *u8) 235 println(" Tanimoto : Q4 fixed-point (0..10000); identity / symmetry verified" as *u8) 236 println(" honest gaps : stereo-aware ECFP (C2.6.1); FCFP functional equivalence (C2.7);" as *u8) 237 println(" variable bit length (1024 only; 512/2048 are param flips)" as *u8) 238 println(" next : C2.7 -- substructure search (SMARTS over MolGraph + fingerprint screen)" as *u8) 239 return 0 240}