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}