code wiki / (root) / nx_chem_periodic_test.nx

nx_chem_periodic_test.nx source

↩ module page · 334 lines · 16231 B

1// nx_chem_periodic_test.nx -- C1 KAT for the full 1..118 periodic table 2// + auxiliary atomic data (isotope mass-number, ionization energy, 3// electron affinity, electronegativity, common-valence bitfield). 4// 5// All atomic masses are IUPAC 2021 abridged standard atomic weights 6// (CIAAW); ionization energies + electron affinities from NIST Atomic 7// Spectra Database; Pauling electronegativity from the 1960 revision 8// (Allred-Rochow values used where Pauling undefined). 9// 10// expect_exit: 0 11// 12// license_tier: ORIGINAL 13 14import "nx_chem.nx" 15import "nx_chem_periodic.nx" 16 17// ============================================================ 18// Section A -- closure: every Z in [1, 118] returns non-null 19// element with z == Z. Bounds: z=0 + z=119 return null. 20// ============================================================ 21func a_closure_1_118(table: *Element) -> nx_int { 22 var z: nx_int = 1 23 while z <= 118 { 24 let e: *Element = nx_chem_element_by_z_118(table, z) 25 if (e as nx_int) == 0 { return 100 + z } // null where we need data 26 if e.z != z { return 200 + z } // wrong Z stored 27 if e.mass_q3 <= 0 { return 300 + z } // mass must be positive 28 z = z + 1 29 } 30 let below: *Element = nx_chem_element_by_z_118(table, 0) 31 if (below as nx_int) != 0 { return 401 } // z=0 must be null 32 let neg: *Element = nx_chem_element_by_z_118(table, -1) 33 if (neg as nx_int) != 0 { return 402 } // z<0 must be null 34 let above: *Element = nx_chem_element_by_z_118(table, 119) 35 if (above as nx_int) != 0 { return 403 } // z>118 must be null 36 return 0 37} 38 39// ============================================================ 40// Section B -- IUPAC 2021 standard atomic weights for the 41// supplement-critical elements (Q3 milli-AMU). Hand-verified 42// against IUPAC CIAAW 2021 abridged table. 43// ============================================================ 44func b_iupac_masses(table: *Element) -> nx_int { 45 let h: *Element = nx_chem_element_by_z_118(table, 1) 46 if h.mass_q3 != 1008 { return 501 } 47 let c: *Element = nx_chem_element_by_z_118(table, 6) 48 if c.mass_q3 != 12011 { return 506 } 49 let n: *Element = nx_chem_element_by_z_118(table, 7) 50 if n.mass_q3 != 14007 { return 507 } 51 let o: *Element = nx_chem_element_by_z_118(table, 8) 52 if o.mass_q3 != 15999 { return 508 } 53 let f: *Element = nx_chem_element_by_z_118(table, 9) 54 if f.mass_q3 != 18998 { return 509 } 55 let na: *Element = nx_chem_element_by_z_118(table, 11) 56 if na.mass_q3 != 22990 { return 511 } 57 let mg: *Element = nx_chem_element_by_z_118(table, 12) 58 if mg.mass_q3 != 24305 { return 512 } 59 let p: *Element = nx_chem_element_by_z_118(table, 15) 60 if p.mass_q3 != 30974 { return 515 } 61 let s: *Element = nx_chem_element_by_z_118(table, 16) 62 if s.mass_q3 != 32066 { return 516 } 63 let cl: *Element = nx_chem_element_by_z_118(table, 17) 64 if cl.mass_q3 != 35453 { return 517 } 65 let k: *Element = nx_chem_element_by_z_118(table, 19) 66 if k.mass_q3 != 39098 { return 519 } 67 let ca: *Element = nx_chem_element_by_z_118(table, 20) 68 if ca.mass_q3 != 40078 { return 520 } 69 let fe: *Element = nx_chem_element_by_z_118(table, 26) 70 if fe.mass_q3 != 55845 { return 526 } 71 let cu: *Element = nx_chem_element_by_z_118(table, 29) 72 if cu.mass_q3 != 63546 { return 529 } 73 let zn: *Element = nx_chem_element_by_z_118(table, 30) 74 if zn.mass_q3 != 65380 { return 530 } 75 let se: *Element = nx_chem_element_by_z_118(table, 34) 76 if se.mass_q3 != 78971 { return 534 } 77 let i: *Element = nx_chem_element_by_z_118(table, 53) 78 if i.mass_q3 != 126904 { return 553 } 79 let hg: *Element = nx_chem_element_by_z_118(table, 80) 80 if hg.mass_q3 != 200592 { return 580 } 81 let pb: *Element = nx_chem_element_by_z_118(table, 82) 82 if pb.mass_q3 != 207200 { return 582 } 83 return 0 84} 85 86// ============================================================ 87// Section C -- IUPAC 2021 masses for period 5-7 + lanthanides 88// (Q3 milli-AMU). Spot check. 89// ============================================================ 90func c_iupac_heavy(table: *Element) -> nx_int { 91 let rb: *Element = nx_chem_element_by_z_118(table, 37) 92 if rb.mass_q3 != 85468 { return 637 } 93 let mo: *Element = nx_chem_element_by_z_118(table, 42) 94 if mo.mass_q3 != 95950 { return 642 } 95 let ag: *Element = nx_chem_element_by_z_118(table, 47) 96 if ag.mass_q3 != 107868 { return 647 } 97 let cs: *Element = nx_chem_element_by_z_118(table, 55) 98 if cs.mass_q3 != 132905 { return 655 } 99 let nd: *Element = nx_chem_element_by_z_118(table, 60) 100 if nd.mass_q3 != 144242 { return 660 } 101 let w: *Element = nx_chem_element_by_z_118(table, 74) 102 if w.mass_q3 != 183840 { return 674 } 103 let au: *Element = nx_chem_element_by_z_118(table, 79) 104 if au.mass_q3 != 196967 { return 679 } 105 let u: *Element = nx_chem_element_by_z_118(table, 92) 106 if u.mass_q3 != 238029 { return 692 } 107 let og: *Element = nx_chem_element_by_z_118(table, 118) 108 if og.mass_q3 != 294000 { return 6118 } 109 return 0 110} 111 112// ============================================================ 113// Section D -- atomic-data table closure + first-ionization 114// energy (Q3 milli-eV) for reference elements from NIST DB. 115// ============================================================ 116func d_first_ionization(ad: *AtomicData) -> nx_int { 117 let h: *AtomicData = nx_chem_atomic_data_by_z(ad, 1) 118 if h.ie1_meV != 13598 { return 701 } // H 13.598 eV 119 let he: *AtomicData = nx_chem_atomic_data_by_z(ad, 2) 120 if he.ie1_meV != 24587 { return 702 } // He 24.587 eV (highest) 121 let li: *AtomicData = nx_chem_atomic_data_by_z(ad, 3) 122 if li.ie1_meV != 5392 { return 703 } // Li 5.392 eV 123 let c: *AtomicData = nx_chem_atomic_data_by_z(ad, 6) 124 if c.ie1_meV != 11260 { return 706 } // C 11.260 eV 125 let o: *AtomicData = nx_chem_atomic_data_by_z(ad, 8) 126 if o.ie1_meV != 13618 { return 708 } // O 13.618 eV 127 let f: *AtomicData = nx_chem_atomic_data_by_z(ad, 9) 128 if f.ie1_meV != 17423 { return 709 } // F 17.423 eV 129 let na: *AtomicData = nx_chem_atomic_data_by_z(ad, 11) 130 if na.ie1_meV != 5139 { return 711 } // Na 5.139 eV (lowest of common metals) 131 let cl: *AtomicData = nx_chem_atomic_data_by_z(ad, 17) 132 if cl.ie1_meV != 12968 { return 717 } // Cl 12.968 eV 133 let cs: *AtomicData = nx_chem_atomic_data_by_z(ad, 55) 134 if cs.ie1_meV != 3894 { return 755 } // Cs 3.894 eV (lowest of stable elements) 135 return 0 136} 137 138// ============================================================ 139// Section E -- electron affinity (Q3 milli-eV). Negative 140// values indicate exothermic (anion stable); positive means 141// repulsive (noble gases / Be / N etc). Sentinel = 0 means 142// "undefined / not reported" for elements where EA is not 143// well-characterized. 144// ============================================================ 145func e_electron_affinity(ad: *AtomicData) -> nx_int { 146 let h: *AtomicData = nx_chem_atomic_data_by_z(ad, 1) 147 if h.ea_meV != 754 { return 801 } // H 0.754 eV 148 let f: *AtomicData = nx_chem_atomic_data_by_z(ad, 9) 149 if f.ea_meV != 3401 { return 809 } // F 3.401 eV 150 let cl: *AtomicData = nx_chem_atomic_data_by_z(ad, 17) 151 if cl.ea_meV != 3613 { return 817 } // Cl 3.613 eV (highest of stable) 152 let o: *AtomicData = nx_chem_atomic_data_by_z(ad, 8) 153 if o.ea_meV != 1461 { return 808 } // O 1.461 eV 154 return 0 155} 156 157// ============================================================ 158// Section F -- Pauling electronegativity (Q2, ×100). 159// Sentinel = 0 means "undefined" for noble gases. 160// ============================================================ 161func f_electronegativity(ad: *AtomicData) -> nx_int { 162 let h: *AtomicData = nx_chem_atomic_data_by_z(ad, 1) 163 if h.en_pauling_q2 != 220 { return 901 } // H 2.20 164 let li: *AtomicData = nx_chem_atomic_data_by_z(ad, 3) 165 if li.en_pauling_q2 != 98 { return 903 } // Li 0.98 166 let c: *AtomicData = nx_chem_atomic_data_by_z(ad, 6) 167 if c.en_pauling_q2 != 255 { return 906 } // C 2.55 168 let n: *AtomicData = nx_chem_atomic_data_by_z(ad, 7) 169 if n.en_pauling_q2 != 304 { return 907 } // N 3.04 170 let o: *AtomicData = nx_chem_atomic_data_by_z(ad, 8) 171 if o.en_pauling_q2 != 344 { return 908 } // O 3.44 172 let f: *AtomicData = nx_chem_atomic_data_by_z(ad, 9) 173 if f.en_pauling_q2 != 398 { return 909 } // F 3.98 (highest) 174 let na: *AtomicData = nx_chem_atomic_data_by_z(ad, 11) 175 if na.en_pauling_q2 != 93 { return 911 } // Na 0.93 176 let cl: *AtomicData = nx_chem_atomic_data_by_z(ad, 17) 177 if cl.en_pauling_q2 != 316 { return 917 } // Cl 3.16 178 let fr: *AtomicData = nx_chem_atomic_data_by_z(ad, 87) 179 if fr.en_pauling_q2 != 70 { return 987 } // Fr 0.70 (lowest) 180 let he: *AtomicData = nx_chem_atomic_data_by_z(ad, 2) 181 if he.en_pauling_q2 != 0 { return 902 } // He undefined (sentinel 0) 182 return 0 183} 184 185// ============================================================ 186// Section G -- most-abundant isotope mass number (Q0). Used 187// for monoisotopic mass calculations in MS. 188// ============================================================ 189func g_abundant_isotope(ad: *AtomicData) -> nx_int { 190 let h: *AtomicData = nx_chem_atomic_data_by_z(ad, 1) 191 if h.iso_a_q0 != 1 { return 1001 } // ¹H 99.985% 192 let c: *AtomicData = nx_chem_atomic_data_by_z(ad, 6) 193 if c.iso_a_q0 != 12 { return 1006 } // ¹²C 98.93% 194 let n: *AtomicData = nx_chem_atomic_data_by_z(ad, 7) 195 if n.iso_a_q0 != 14 { return 1007 } // ¹⁴N 99.636% 196 let o: *AtomicData = nx_chem_atomic_data_by_z(ad, 8) 197 if o.iso_a_q0 != 16 { return 1008 } // ¹⁶O 99.757% 198 let cl: *AtomicData = nx_chem_atomic_data_by_z(ad, 17) 199 if cl.iso_a_q0 != 35 { return 1017 } // ³⁵Cl 75.78% 200 let fe: *AtomicData = nx_chem_atomic_data_by_z(ad, 26) 201 if fe.iso_a_q0 != 56 { return 1026 } // ⁵⁶Fe 91.75% 202 let pb: *AtomicData = nx_chem_atomic_data_by_z(ad, 82) 203 if pb.iso_a_q0 != 208 { return 1082 } // ²⁰⁸Pb 52.4% 204 return 0 205} 206 207// ============================================================ 208// Section H -- common-oxidation-state bitfield. Bit (15 + ox) 209// is set if that oxidation state is common. Supports -15..+15. 210// ============================================================ 211func h_valence_bits(ad: *AtomicData) -> nx_int { 212 // Helper: bit at offset (15 + ox) 213 let h: *AtomicData = nx_chem_atomic_data_by_z(ad, 1) 214 // H: -1 (hydride) and +1 (proton) -> bits at 14 and 16 215 let h_expected: nx_int = (1 << 14) | (1 << 16) 216 if h.valence_bits != h_expected { return 1101 } 217 218 let na: *AtomicData = nx_chem_atomic_data_by_z(ad, 11) 219 // Na: only +1 -> bit at 16 220 if na.valence_bits != (1 << 16) { return 1111 } 221 222 let o: *AtomicData = nx_chem_atomic_data_by_z(ad, 8) 223 // O: -2 (most common) -> bit at 13 224 if o.valence_bits != (1 << 13) { return 1108 } 225 226 let fe: *AtomicData = nx_chem_atomic_data_by_z(ad, 26) 227 // Fe: +2 and +3 -> bits at 17 and 18 228 let fe_expected: nx_int = (1 << 17) | (1 << 18) 229 if fe.valence_bits != fe_expected { return 1126 } 230 231 let pb: *AtomicData = nx_chem_atomic_data_by_z(ad, 82) 232 // Pb: +2 (more common) and +4 -> bits at 17 and 19 233 let pb_expected: nx_int = (1 << 17) | (1 << 19) 234 if pb.valence_bits != pb_expected { return 1182 } 235 236 let s: *AtomicData = nx_chem_atomic_data_by_z(ad, 16) 237 // S: -2 (sulfide), +4 (sulfite), +6 (sulfate) -> bits 13, 19, 21 238 let s_expected: nx_int = (1 << 13) | (1 << 19) | (1 << 21) 239 if s.valence_bits != s_expected { return 1116 } 240 241 return 0 242} 243 244// ============================================================ 245// Section I -- atomic-data closure + bounds. 246// ============================================================ 247func i_atomic_data_closure(ad: *AtomicData) -> nx_int { 248 var z: nx_int = 1 249 while z <= 118 { 250 let d: *AtomicData = nx_chem_atomic_data_by_z(ad, z) 251 if (d as nx_int) == 0 { return 1200 + z } 252 if d.z != z { return 1400 + z } 253 z = z + 1 254 } 255 let below: *AtomicData = nx_chem_atomic_data_by_z(ad, 0) 256 if (below as nx_int) != 0 { return 1601 } 257 let above: *AtomicData = nx_chem_atomic_data_by_z(ad, 119) 258 if (above as nx_int) != 0 { return 1602 } 259 return 0 260} 261 262// ============================================================ 263// Section J -- backward-compatible: nx_chem_periodic_table_36 264// (the existing C0 floor) still produces correct results. 265// nx_chem_periodic_table_118 must MATCH on 1..36. 266// ============================================================ 267func j_backward_compatible(t36: *Element, t118: *Element) -> nx_int { 268 var z: nx_int = 1 269 while z <= 36 { 270 let e36: *Element = nx_chem_element_by_z_36(t36, z) 271 let e118: *Element = nx_chem_element_by_z_118(t118, z) 272 if e36.z != e118.z { return 1700 + z } 273 if e36.mass_q3 != e118.mass_q3 { return 1800 + z } 274 z = z + 1 275 } 276 return 0 277} 278 279func main() -> nx_exit { 280 println("=== nx_chem_periodic -- C1 KAT: periodic 1..118 + atomic data ===" as *u8) 281 282 let table: *Element = nx_chem_periodic_table_118() 283 let ad: *AtomicData = nx_chem_atomic_data_table_118() 284 let t36: *Element = nx_chem_periodic_table_36() 285 286 let ra: nx_int = a_closure_1_118(table) 287 if ra != 0 { println("A closure_1_118 FAIL" as *u8); return ra } 288 println("A closure_1_118 PASS every Z in [1,118] returns non-null + correct z + positive mass; bounds reject" as *u8) 289 290 let rb: nx_int = b_iupac_masses(table) 291 if rb != 0 { println("B iupac_masses FAIL" as *u8); return rb } 292 println("B iupac_masses PASS IUPAC 2021 abridged for supplement-critical: H/C/N/O/F/Na/Mg/P/S/Cl/K/Ca/Fe/Cu/Zn/Se/I/Hg/Pb" as *u8) 293 294 let rc: nx_int = c_iupac_heavy(table) 295 if rc != 0 { println("C iupac_heavy FAIL" as *u8); return rc } 296 println("C iupac_heavy PASS IUPAC 2021 period-5/6/7 + lanthanides: Rb/Mo/Ag/Cs/Nd/W/Au/U/Og" as *u8) 297 298 let rd: nx_int = d_first_ionization(ad) 299 if rd != 0 { println("D first_ionization FAIL" as *u8); return rd } 300 println("D first_ionization PASS NIST atomic-spectra DB: H/He/Li/C/O/F/Na/Cl/Cs" as *u8) 301 302 let re: nx_int = e_electron_affinity(ad) 303 if re != 0 { println("E electron_affinity FAIL" as *u8); return re } 304 println("E electron_affinity PASS NIST: H/O/F/Cl" as *u8) 305 306 let rf: nx_int = f_electronegativity(ad) 307 if rf != 0 { println("F electronegativity FAIL" as *u8); return rf } 308 println("F electronegativity PASS Pauling 1960: H/Li/C/N/O/F/Na/Cl/Fr + He sentinel" as *u8) 309 310 let rg: nx_int = g_abundant_isotope(ad) 311 if rg != 0 { println("G abundant_isotope FAIL" as *u8); return rg } 312 println("G abundant_isotope PASS most-abundant mass number: 1H/12C/14N/16O/35Cl/56Fe/208Pb" as *u8) 313 314 let rh: nx_int = h_valence_bits(ad) 315 if rh != 0 { println("H valence_bits FAIL" as *u8); return rh } 316 println("H valence_bits PASS common oxidation states: H(-1,+1)/Na(+1)/O(-2)/Fe(+2,+3)/Pb(+2,+4)/S(-2,+4,+6)" as *u8) 317 318 let ri: nx_int = i_atomic_data_closure(ad) 319 if ri != 0 { println("I atomic_data_closure FAIL" as *u8); return ri } 320 println("I atomic_data_closure PASS AtomicData table covers [1,118]; bounds reject" as *u8) 321 322 let rj: nx_int = j_backward_compatible(t36, table) 323 if rj != 0 { println("J backward_compatible FAIL" as *u8); return rj } 324 println("J backward_compatible PASS nx_chem_periodic_table_118 matches _36 on Z in [1,36]" as *u8) 325 326 println("" as *u8) 327 println("=== C1 substrate milestone PASS ===" as *u8) 328 println(" periodic_table : 1..118 with IUPAC 2021 standard atomic weights (Q3 milli-AMU)" as *u8) 329 println(" atomic_data : isotope-A + ionization-energy + electron-affinity +" as *u8) 330 println(" Pauling-electronegativity + common-valence bitfield" as *u8) 331 println(" composes : existing nx_chem.nx (Element struct) + nx_chem_extended.nx (1..36)" as *u8) 332 println(" next : C2 -- nx_chem_molecule + nx_chem_smiles bits-up" as *u8) 333 return 0 334}