Skip to main content

sci_form/eht/
params.rs

1//! EHT parameter tables: VSIP values and Slater exponents per element/orbital.
2//!
3//! Hoffmann parameters for valence orbitals of common elements.
4
5use serde::{Deserialize, Serialize};
6
7/// Confidence level for the current EHT parameterization.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum SupportLevel {
11    Unsupported,
12    Experimental,
13    Supported,
14}
15
16/// Summary of EHT support for a specific element set.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct EhtSupport {
19    /// Overall support level across the full molecule.
20    pub level: SupportLevel,
21    /// Whether the element set contains transition metals.
22    pub has_transition_metals: bool,
23    /// Supported, validated elements for the current EHT implementation.
24    pub supported_elements: Vec<u8>,
25    /// Supported but still provisional / uncalibrated elements.
26    pub provisional_elements: Vec<u8>,
27    /// Elements with no EHT parameter set.
28    pub unsupported_elements: Vec<u8>,
29    /// Human-readable warnings for callers that want to surface caveats.
30    pub warnings: Vec<String>,
31}
32
33/// Definition of a single atomic orbital for EHT.
34#[derive(Debug, Clone, Serialize)]
35pub struct OrbitalDef {
36    /// Principal quantum number n.
37    pub n: u8,
38    /// Angular momentum quantum number l (0=s, 1=p, 2=d).
39    pub l: u8,
40    /// Label, e.g. "2s", "2p".
41    pub label: &'static str,
42    /// Valence State Ionization Potential (eV), used as H_ii.
43    pub vsip: f64,
44    /// Slater exponent ζ (bohr⁻¹).
45    pub zeta: f64,
46}
47
48/// Full EHT parameter set for one element.
49#[derive(Debug, Clone, Serialize)]
50pub struct EhtParams {
51    /// Atomic number.
52    pub z: u8,
53    /// Element symbol.
54    pub symbol: &'static str,
55    /// Valence orbital definitions.
56    pub orbitals: &'static [OrbitalDef],
57}
58
59/// Wolfsberg-Helmholtz constant (dimensionless).
60pub const DEFAULT_K: f64 = 1.75;
61
62/// Return true if the element belongs to the d-block series currently covered by EHT.
63pub fn is_transition_metal(z: u8) -> bool {
64    matches!(z, 21..=30 | 39..=48 | 72..=80)
65}
66
67/// Return the support level for a single element.
68pub fn support_level_for_element(z: u8) -> SupportLevel {
69    if get_params(z).is_none() {
70        SupportLevel::Unsupported
71    } else if is_transition_metal(z) {
72        // Calibrated against Alvarez/Ammeter/Hoffmann literature tables.
73        SupportLevel::Experimental
74    } else {
75        SupportLevel::Supported
76    }
77}
78
79/// Analyze a molecule-level EHT support profile from atomic numbers.
80pub fn analyze_eht_support(elements: &[u8]) -> EhtSupport {
81    let mut supported_elements = Vec::new();
82    let mut provisional_elements = Vec::new();
83    let mut unsupported_elements = Vec::new();
84
85    for &z in elements {
86        match support_level_for_element(z) {
87            SupportLevel::Supported => {
88                if !supported_elements.contains(&z) {
89                    supported_elements.push(z);
90                }
91            }
92            SupportLevel::Experimental => {
93                if !provisional_elements.contains(&z) {
94                    provisional_elements.push(z);
95                }
96            }
97            SupportLevel::Unsupported => {
98                if !unsupported_elements.contains(&z) {
99                    unsupported_elements.push(z);
100                }
101            }
102        }
103    }
104
105    supported_elements.sort_unstable();
106    provisional_elements.sort_unstable();
107    unsupported_elements.sort_unstable();
108
109    let mut warnings = Vec::new();
110    if !provisional_elements.is_empty() {
111        warnings.push(format!(
112            "Transition-metal EHT parameters are provisional for elements {:?}; results should be treated as experimental until benchmark calibration is completed.",
113            provisional_elements
114        ));
115    }
116    if !unsupported_elements.is_empty() {
117        warnings.push(format!(
118            "No EHT parameters are available for elements {:?}.",
119            unsupported_elements
120        ));
121    }
122
123    let level = if !unsupported_elements.is_empty() {
124        SupportLevel::Unsupported
125    } else if !provisional_elements.is_empty() {
126        SupportLevel::Experimental
127    } else {
128        SupportLevel::Supported
129    };
130
131    EhtSupport {
132        level,
133        has_transition_metals: !provisional_elements.is_empty(),
134        supported_elements,
135        provisional_elements,
136        unsupported_elements,
137        warnings,
138    }
139}
140
141// ─── Parameter tables ────────────────────────────────────────────────────────
142
143static H_ORBITALS: [OrbitalDef; 1] = [OrbitalDef {
144    n: 1,
145    l: 0,
146    label: "1s",
147    vsip: -13.6,
148    zeta: 1.3,
149}];
150
151static C_ORBITALS: [OrbitalDef; 2] = [
152    OrbitalDef {
153        n: 2,
154        l: 0,
155        label: "2s",
156        vsip: -21.4,
157        zeta: 1.625,
158    },
159    OrbitalDef {
160        n: 2,
161        l: 1,
162        label: "2p",
163        vsip: -11.4,
164        zeta: 1.625,
165    },
166];
167
168static N_ORBITALS: [OrbitalDef; 2] = [
169    OrbitalDef {
170        n: 2,
171        l: 0,
172        label: "2s",
173        vsip: -26.0,
174        zeta: 1.950,
175    },
176    OrbitalDef {
177        n: 2,
178        l: 1,
179        label: "2p",
180        vsip: -13.4,
181        zeta: 1.950,
182    },
183];
184
185static O_ORBITALS: [OrbitalDef; 2] = [
186    OrbitalDef {
187        n: 2,
188        l: 0,
189        label: "2s",
190        vsip: -32.3,
191        zeta: 2.275,
192    },
193    OrbitalDef {
194        n: 2,
195        l: 1,
196        label: "2p",
197        vsip: -14.8,
198        zeta: 2.275,
199    },
200];
201
202static F_ORBITALS: [OrbitalDef; 2] = [
203    OrbitalDef {
204        n: 2,
205        l: 0,
206        label: "2s",
207        vsip: -40.0,
208        zeta: 2.425,
209    },
210    OrbitalDef {
211        n: 2,
212        l: 1,
213        label: "2p",
214        vsip: -18.1,
215        zeta: 2.425,
216    },
217];
218
219static CL_ORBITALS: [OrbitalDef; 2] = [
220    OrbitalDef {
221        n: 3,
222        l: 0,
223        label: "3s",
224        vsip: -26.3,
225        zeta: 2.183,
226    },
227    OrbitalDef {
228        n: 3,
229        l: 1,
230        label: "3p",
231        vsip: -14.2,
232        zeta: 1.733,
233    },
234];
235
236static BR_ORBITALS: [OrbitalDef; 2] = [
237    OrbitalDef {
238        n: 4,
239        l: 0,
240        label: "4s",
241        vsip: -22.07,
242        zeta: 2.588,
243    },
244    OrbitalDef {
245        n: 4,
246        l: 1,
247        label: "4p",
248        vsip: -13.1,
249        zeta: 2.131,
250    },
251];
252
253static I_ORBITALS: [OrbitalDef; 2] = [
254    OrbitalDef {
255        n: 5,
256        l: 0,
257        label: "5s",
258        vsip: -18.0,
259        zeta: 2.679,
260    },
261    OrbitalDef {
262        n: 5,
263        l: 1,
264        label: "5p",
265        vsip: -12.7,
266        zeta: 2.322,
267    },
268];
269
270static S_ORBITALS: [OrbitalDef; 2] = [
271    OrbitalDef {
272        n: 3,
273        l: 0,
274        label: "3s",
275        vsip: -20.0,
276        zeta: 1.817,
277    },
278    OrbitalDef {
279        n: 3,
280        l: 1,
281        label: "3p",
282        vsip: -11.0,
283        zeta: 1.817,
284    },
285];
286
287static P_ORBITALS: [OrbitalDef; 2] = [
288    OrbitalDef {
289        n: 3,
290        l: 0,
291        label: "3s",
292        vsip: -18.6,
293        zeta: 1.600,
294    },
295    OrbitalDef {
296        n: 3,
297        l: 1,
298        label: "3p",
299        vsip: -14.0,
300        zeta: 1.600,
301    },
302];
303
304static SI_ORBITALS: [OrbitalDef; 2] = [
305    OrbitalDef {
306        n: 3,
307        l: 0,
308        label: "3s",
309        vsip: -17.3,
310        zeta: 1.383,
311    },
312    OrbitalDef {
313        n: 3,
314        l: 1,
315        label: "3p",
316        vsip: -9.2,
317        zeta: 1.383,
318    },
319];
320
321static B_ORBITALS: [OrbitalDef; 2] = [
322    OrbitalDef {
323        n: 2,
324        l: 0,
325        label: "2s",
326        vsip: -15.2,
327        zeta: 1.300,
328    },
329    OrbitalDef {
330        n: 2,
331        l: 1,
332        label: "2p",
333        vsip: -8.5,
334        zeta: 1.300,
335    },
336];
337
338// ─── First-row transition metals (3d series): 4s + 4p + 3d ──────────────────
339//
340// Literature-calibrated parameters from:
341//   Alvarez, S. "Tables of Parameters for Extended Hückel Calculations" (1993)
342//   Ammeter, J. H. et al. Helv. Chim. Acta 61 (1978): 1.
343//   Hoffmann, R. J. Chem. Phys. 39 (1963): 1397.
344//
345// VSIP values (eV) correspond to valence state ionization potentials.
346// Slater exponents (ζ, bohr⁻¹) are single-zeta values from Alvarez tables.
347// The outer np shell is included per Ammeter convention for proper σ/π metal-ligand bonding.
348static SC_ORBITALS: [OrbitalDef; 3] = [
349    OrbitalDef {
350        n: 4,
351        l: 0,
352        label: "4s",
353        vsip: -8.87,
354        zeta: 1.300,
355    },
356    OrbitalDef {
357        n: 4,
358        l: 1,
359        label: "4p",
360        vsip: -2.75,
361        zeta: 1.300,
362    },
363    OrbitalDef {
364        n: 3,
365        l: 2,
366        label: "3d",
367        vsip: -8.51,
368        zeta: 4.350,
369    },
370];
371static TI_ORBITALS: [OrbitalDef; 3] = [
372    OrbitalDef {
373        n: 4,
374        l: 0,
375        label: "4s",
376        vsip: -8.97,
377        zeta: 1.075,
378    },
379    OrbitalDef {
380        n: 4,
381        l: 1,
382        label: "4p",
383        vsip: -5.44,
384        zeta: 1.075,
385    },
386    OrbitalDef {
387        n: 3,
388        l: 2,
389        label: "3d",
390        vsip: -10.81,
391        zeta: 4.550,
392    },
393];
394static V_ORBITALS: [OrbitalDef; 3] = [
395    OrbitalDef {
396        n: 4,
397        l: 0,
398        label: "4s",
399        vsip: -8.81,
400        zeta: 1.300,
401    },
402    OrbitalDef {
403        n: 4,
404        l: 1,
405        label: "4p",
406        vsip: -5.52,
407        zeta: 1.300,
408    },
409    OrbitalDef {
410        n: 3,
411        l: 2,
412        label: "3d",
413        vsip: -11.00,
414        zeta: 4.750,
415    },
416];
417static CR_ORBITALS: [OrbitalDef; 3] = [
418    OrbitalDef {
419        n: 4,
420        l: 0,
421        label: "4s",
422        vsip: -8.66,
423        zeta: 1.700,
424    },
425    OrbitalDef {
426        n: 4,
427        l: 1,
428        label: "4p",
429        vsip: -5.24,
430        zeta: 1.700,
431    },
432    OrbitalDef {
433        n: 3,
434        l: 2,
435        label: "3d",
436        vsip: -11.22,
437        zeta: 4.950,
438    },
439];
440static MN_ORBITALS: [OrbitalDef; 3] = [
441    OrbitalDef {
442        n: 4,
443        l: 0,
444        label: "4s",
445        vsip: -9.75,
446        zeta: 1.800,
447    },
448    OrbitalDef {
449        n: 4,
450        l: 1,
451        label: "4p",
452        vsip: -5.89,
453        zeta: 1.800,
454    },
455    OrbitalDef {
456        n: 3,
457        l: 2,
458        label: "3d",
459        vsip: -11.67,
460        zeta: 5.150,
461    },
462];
463static FE_ORBITALS: [OrbitalDef; 3] = [
464    OrbitalDef {
465        n: 4,
466        l: 0,
467        label: "4s",
468        vsip: -9.10,
469        zeta: 1.900,
470    },
471    OrbitalDef {
472        n: 4,
473        l: 1,
474        label: "4p",
475        vsip: -5.32,
476        zeta: 1.900,
477    },
478    OrbitalDef {
479        n: 3,
480        l: 2,
481        label: "3d",
482        vsip: -12.60,
483        zeta: 5.350,
484    },
485];
486static CO_ORBITALS: [OrbitalDef; 3] = [
487    OrbitalDef {
488        n: 4,
489        l: 0,
490        label: "4s",
491        vsip: -9.21,
492        zeta: 2.000,
493    },
494    OrbitalDef {
495        n: 4,
496        l: 1,
497        label: "4p",
498        vsip: -5.29,
499        zeta: 2.000,
500    },
501    OrbitalDef {
502        n: 3,
503        l: 2,
504        label: "3d",
505        vsip: -13.18,
506        zeta: 5.550,
507    },
508];
509static NI_ORBITALS: [OrbitalDef; 3] = [
510    OrbitalDef {
511        n: 4,
512        l: 0,
513        label: "4s",
514        vsip: -10.95,
515        zeta: 2.100,
516    },
517    OrbitalDef {
518        n: 4,
519        l: 1,
520        label: "4p",
521        vsip: -6.27,
522        zeta: 2.100,
523    },
524    OrbitalDef {
525        n: 3,
526        l: 2,
527        label: "3d",
528        vsip: -13.49,
529        zeta: 5.750,
530    },
531];
532static CU_ORBITALS: [OrbitalDef; 3] = [
533    OrbitalDef {
534        n: 4,
535        l: 0,
536        label: "4s",
537        vsip: -11.40,
538        zeta: 2.200,
539    },
540    OrbitalDef {
541        n: 4,
542        l: 1,
543        label: "4p",
544        vsip: -6.06,
545        zeta: 2.200,
546    },
547    OrbitalDef {
548        n: 3,
549        l: 2,
550        label: "3d",
551        vsip: -14.00,
552        zeta: 5.950,
553    },
554];
555static ZN_ORBITALS: [OrbitalDef; 3] = [
556    OrbitalDef {
557        n: 4,
558        l: 0,
559        label: "4s",
560        vsip: -12.41,
561        zeta: 2.010,
562    },
563    OrbitalDef {
564        n: 4,
565        l: 1,
566        label: "4p",
567        vsip: -6.53,
568        zeta: 2.010,
569    },
570    OrbitalDef {
571        n: 3,
572        l: 2,
573        label: "3d",
574        vsip: -17.10,
575        zeta: 6.150,
576    },
577];
578
579// ─── Second-row transition metals (4d series): 5s + 5p + 4d ─────────────────
580//
581// Alvarez, S. "Tables of Parameters for Extended Hückel Calculations" (1993).
582static Y_ORBITALS: [OrbitalDef; 3] = [
583    OrbitalDef {
584        n: 5,
585        l: 0,
586        label: "5s",
587        vsip: -7.29,
588        zeta: 1.390,
589    },
590    OrbitalDef {
591        n: 5,
592        l: 1,
593        label: "5p",
594        vsip: -4.37,
595        zeta: 1.390,
596    },
597    OrbitalDef {
598        n: 4,
599        l: 2,
600        label: "4d",
601        vsip: -8.46,
602        zeta: 3.310,
603    },
604];
605static ZR_ORBITALS: [OrbitalDef; 3] = [
606    OrbitalDef {
607        n: 5,
608        l: 0,
609        label: "5s",
610        vsip: -8.12,
611        zeta: 1.520,
612    },
613    OrbitalDef {
614        n: 5,
615        l: 1,
616        label: "5p",
617        vsip: -5.12,
618        zeta: 1.520,
619    },
620    OrbitalDef {
621        n: 4,
622        l: 2,
623        label: "4d",
624        vsip: -10.14,
625        zeta: 3.840,
626    },
627];
628static NB_ORBITALS: [OrbitalDef; 3] = [
629    OrbitalDef {
630        n: 5,
631        l: 0,
632        label: "5s",
633        vsip: -10.10,
634        zeta: 1.640,
635    },
636    OrbitalDef {
637        n: 5,
638        l: 1,
639        label: "5p",
640        vsip: -6.86,
641        zeta: 1.640,
642    },
643    OrbitalDef {
644        n: 4,
645        l: 2,
646        label: "4d",
647        vsip: -12.10,
648        zeta: 4.080,
649    },
650];
651static MO_ORBITALS: [OrbitalDef; 3] = [
652    OrbitalDef {
653        n: 5,
654        l: 0,
655        label: "5s",
656        vsip: -8.34,
657        zeta: 1.730,
658    },
659    OrbitalDef {
660        n: 5,
661        l: 1,
662        label: "5p",
663        vsip: -5.24,
664        zeta: 1.730,
665    },
666    OrbitalDef {
667        n: 4,
668        l: 2,
669        label: "4d",
670        vsip: -10.50,
671        zeta: 4.540,
672    },
673];
674static TC_ORBITALS: [OrbitalDef; 3] = [
675    OrbitalDef {
676        n: 5,
677        l: 0,
678        label: "5s",
679        vsip: -9.00,
680        zeta: 1.820,
681    },
682    OrbitalDef {
683        n: 5,
684        l: 1,
685        label: "5p",
686        vsip: -5.60,
687        zeta: 1.820,
688    },
689    OrbitalDef {
690        n: 4,
691        l: 2,
692        label: "4d",
693        vsip: -11.20,
694        zeta: 4.900,
695    },
696];
697static RU_ORBITALS: [OrbitalDef; 3] = [
698    OrbitalDef {
699        n: 5,
700        l: 0,
701        label: "5s",
702        vsip: -10.40,
703        zeta: 1.900,
704    },
705    OrbitalDef {
706        n: 5,
707        l: 1,
708        label: "5p",
709        vsip: -6.87,
710        zeta: 1.900,
711    },
712    OrbitalDef {
713        n: 4,
714        l: 2,
715        label: "4d",
716        vsip: -14.90,
717        zeta: 5.380,
718    },
719];
720static RH_ORBITALS: [OrbitalDef; 3] = [
721    OrbitalDef {
722        n: 5,
723        l: 0,
724        label: "5s",
725        vsip: -8.09,
726        zeta: 2.135,
727    },
728    OrbitalDef {
729        n: 5,
730        l: 1,
731        label: "5p",
732        vsip: -4.57,
733        zeta: 2.135,
734    },
735    OrbitalDef {
736        n: 4,
737        l: 2,
738        label: "4d",
739        vsip: -12.50,
740        zeta: 4.290,
741    },
742];
743static PD_ORBITALS: [OrbitalDef; 3] = [
744    OrbitalDef {
745        n: 5,
746        l: 0,
747        label: "5s",
748        vsip: -7.32,
749        zeta: 2.190,
750    },
751    OrbitalDef {
752        n: 5,
753        l: 1,
754        label: "5p",
755        vsip: -3.75,
756        zeta: 2.190,
757    },
758    OrbitalDef {
759        n: 4,
760        l: 2,
761        label: "4d",
762        vsip: -12.02,
763        zeta: 5.983,
764    },
765];
766static AG_ORBITALS: [OrbitalDef; 3] = [
767    OrbitalDef {
768        n: 5,
769        l: 0,
770        label: "5s",
771        vsip: -6.27,
772        zeta: 2.242,
773    },
774    OrbitalDef {
775        n: 5,
776        l: 1,
777        label: "5p",
778        vsip: -3.97,
779        zeta: 2.242,
780    },
781    OrbitalDef {
782        n: 4,
783        l: 2,
784        label: "4d",
785        vsip: -14.58,
786        zeta: 6.070,
787    },
788];
789static CD_ORBITALS: [OrbitalDef; 3] = [
790    OrbitalDef {
791        n: 5,
792        l: 0,
793        label: "5s",
794        vsip: -11.79,
795        zeta: 2.300,
796    },
797    OrbitalDef {
798        n: 5,
799        l: 1,
800        label: "5p",
801        vsip: -6.10,
802        zeta: 2.300,
803    },
804    OrbitalDef {
805        n: 4,
806        l: 2,
807        label: "4d",
808        vsip: -17.84,
809        zeta: 6.330,
810    },
811];
812
813// ─── Third-row transition metals (5d series): 6s + 6p + 5d ──────────────────
814//
815// Alvarez, S. "Tables of Parameters for Extended Hückel Calculations" (1993).
816// Pt values from Thorn, D. L.; Hoffmann, R. Inorg. Chem. 17 (1978): 126.
817static HF_ORBITALS: [OrbitalDef; 3] = [
818    OrbitalDef {
819        n: 6,
820        l: 0,
821        label: "6s",
822        vsip: -8.20,
823        zeta: 1.720,
824    },
825    OrbitalDef {
826        n: 6,
827        l: 1,
828        label: "6p",
829        vsip: -4.65,
830        zeta: 1.720,
831    },
832    OrbitalDef {
833        n: 5,
834        l: 2,
835        label: "5d",
836        vsip: -11.18,
837        zeta: 4.360,
838    },
839];
840static TA_ORBITALS: [OrbitalDef; 3] = [
841    OrbitalDef {
842        n: 6,
843        l: 0,
844        label: "6s",
845        vsip: -10.79,
846        zeta: 1.830,
847    },
848    OrbitalDef {
849        n: 6,
850        l: 1,
851        label: "6p",
852        vsip: -6.86,
853        zeta: 1.830,
854    },
855    OrbitalDef {
856        n: 5,
857        l: 2,
858        label: "5d",
859        vsip: -12.10,
860        zeta: 4.762,
861    },
862];
863static W_ORBITALS: [OrbitalDef; 3] = [
864    OrbitalDef {
865        n: 6,
866        l: 0,
867        label: "6s",
868        vsip: -8.26,
869        zeta: 1.890,
870    },
871    OrbitalDef {
872        n: 6,
873        l: 1,
874        label: "6p",
875        vsip: -5.17,
876        zeta: 1.890,
877    },
878    OrbitalDef {
879        n: 5,
880        l: 2,
881        label: "5d",
882        vsip: -10.37,
883        zeta: 4.982,
884    },
885];
886static RE_ORBITALS: [OrbitalDef; 3] = [
887    OrbitalDef {
888        n: 6,
889        l: 0,
890        label: "6s",
891        vsip: -9.36,
892        zeta: 1.980,
893    },
894    OrbitalDef {
895        n: 6,
896        l: 1,
897        label: "6p",
898        vsip: -5.96,
899        zeta: 1.980,
900    },
901    OrbitalDef {
902        n: 5,
903        l: 2,
904        label: "5d",
905        vsip: -12.66,
906        zeta: 5.343,
907    },
908];
909static OS_ORBITALS: [OrbitalDef; 3] = [
910    OrbitalDef {
911        n: 6,
912        l: 0,
913        label: "6s",
914        vsip: -8.17,
915        zeta: 2.070,
916    },
917    OrbitalDef {
918        n: 6,
919        l: 1,
920        label: "6p",
921        vsip: -4.81,
922        zeta: 2.070,
923    },
924    OrbitalDef {
925        n: 5,
926        l: 2,
927        label: "5d",
928        vsip: -11.84,
929        zeta: 5.571,
930    },
931];
932static IR_ORBITALS: [OrbitalDef; 3] = [
933    OrbitalDef {
934        n: 6,
935        l: 0,
936        label: "6s",
937        vsip: -11.36,
938        zeta: 2.200,
939    },
940    OrbitalDef {
941        n: 6,
942        l: 1,
943        label: "6p",
944        vsip: -4.50,
945        zeta: 2.200,
946    },
947    OrbitalDef {
948        n: 5,
949        l: 2,
950        label: "5d",
951        vsip: -12.17,
952        zeta: 5.796,
953    },
954];
955static PT_ORBITALS: [OrbitalDef; 3] = [
956    OrbitalDef {
957        n: 6,
958        l: 0,
959        label: "6s",
960        vsip: -9.077,
961        zeta: 2.554,
962    },
963    OrbitalDef {
964        n: 6,
965        l: 1,
966        label: "6p",
967        vsip: -5.475,
968        zeta: 2.554,
969    },
970    OrbitalDef {
971        n: 5,
972        l: 2,
973        label: "5d",
974        vsip: -12.59,
975        zeta: 6.013,
976    },
977];
978static AU_ORBITALS: [OrbitalDef; 3] = [
979    OrbitalDef {
980        n: 6,
981        l: 0,
982        label: "6s",
983        vsip: -10.92,
984        zeta: 2.602,
985    },
986    OrbitalDef {
987        n: 6,
988        l: 1,
989        label: "6p",
990        vsip: -5.55,
991        zeta: 2.602,
992    },
993    OrbitalDef {
994        n: 5,
995        l: 2,
996        label: "5d",
997        vsip: -15.07,
998        zeta: 6.163,
999    },
1000];
1001static HG_ORBITALS: [OrbitalDef; 3] = [
1002    OrbitalDef {
1003        n: 6,
1004        l: 0,
1005        label: "6s",
1006        vsip: -13.68,
1007        zeta: 2.649,
1008    },
1009    OrbitalDef {
1010        n: 6,
1011        l: 1,
1012        label: "6p",
1013        vsip: -8.47,
1014        zeta: 2.649,
1015    },
1016    OrbitalDef {
1017        n: 5,
1018        l: 2,
1019        label: "5d",
1020        vsip: -17.50,
1021        zeta: 6.350,
1022    },
1023];
1024
1025/// All supported element parameter sets.
1026static ALL_PARAMS: &[EhtParams] = &[
1027    EhtParams {
1028        z: 1,
1029        symbol: "H",
1030        orbitals: &H_ORBITALS,
1031    },
1032    EhtParams {
1033        z: 5,
1034        symbol: "B",
1035        orbitals: &B_ORBITALS,
1036    },
1037    EhtParams {
1038        z: 6,
1039        symbol: "C",
1040        orbitals: &C_ORBITALS,
1041    },
1042    EhtParams {
1043        z: 7,
1044        symbol: "N",
1045        orbitals: &N_ORBITALS,
1046    },
1047    EhtParams {
1048        z: 8,
1049        symbol: "O",
1050        orbitals: &O_ORBITALS,
1051    },
1052    EhtParams {
1053        z: 9,
1054        symbol: "F",
1055        orbitals: &F_ORBITALS,
1056    },
1057    EhtParams {
1058        z: 14,
1059        symbol: "Si",
1060        orbitals: &SI_ORBITALS,
1061    },
1062    EhtParams {
1063        z: 15,
1064        symbol: "P",
1065        orbitals: &P_ORBITALS,
1066    },
1067    EhtParams {
1068        z: 16,
1069        symbol: "S",
1070        orbitals: &S_ORBITALS,
1071    },
1072    EhtParams {
1073        z: 17,
1074        symbol: "Cl",
1075        orbitals: &CL_ORBITALS,
1076    },
1077    EhtParams {
1078        z: 21,
1079        symbol: "Sc",
1080        orbitals: &SC_ORBITALS,
1081    },
1082    EhtParams {
1083        z: 22,
1084        symbol: "Ti",
1085        orbitals: &TI_ORBITALS,
1086    },
1087    EhtParams {
1088        z: 23,
1089        symbol: "V",
1090        orbitals: &V_ORBITALS,
1091    },
1092    EhtParams {
1093        z: 24,
1094        symbol: "Cr",
1095        orbitals: &CR_ORBITALS,
1096    },
1097    EhtParams {
1098        z: 25,
1099        symbol: "Mn",
1100        orbitals: &MN_ORBITALS,
1101    },
1102    EhtParams {
1103        z: 26,
1104        symbol: "Fe",
1105        orbitals: &FE_ORBITALS,
1106    },
1107    EhtParams {
1108        z: 27,
1109        symbol: "Co",
1110        orbitals: &CO_ORBITALS,
1111    },
1112    EhtParams {
1113        z: 28,
1114        symbol: "Ni",
1115        orbitals: &NI_ORBITALS,
1116    },
1117    EhtParams {
1118        z: 29,
1119        symbol: "Cu",
1120        orbitals: &CU_ORBITALS,
1121    },
1122    EhtParams {
1123        z: 30,
1124        symbol: "Zn",
1125        orbitals: &ZN_ORBITALS,
1126    },
1127    EhtParams {
1128        z: 35,
1129        symbol: "Br",
1130        orbitals: &BR_ORBITALS,
1131    },
1132    EhtParams {
1133        z: 39,
1134        symbol: "Y",
1135        orbitals: &Y_ORBITALS,
1136    },
1137    EhtParams {
1138        z: 40,
1139        symbol: "Zr",
1140        orbitals: &ZR_ORBITALS,
1141    },
1142    EhtParams {
1143        z: 41,
1144        symbol: "Nb",
1145        orbitals: &NB_ORBITALS,
1146    },
1147    EhtParams {
1148        z: 42,
1149        symbol: "Mo",
1150        orbitals: &MO_ORBITALS,
1151    },
1152    EhtParams {
1153        z: 43,
1154        symbol: "Tc",
1155        orbitals: &TC_ORBITALS,
1156    },
1157    EhtParams {
1158        z: 44,
1159        symbol: "Ru",
1160        orbitals: &RU_ORBITALS,
1161    },
1162    EhtParams {
1163        z: 45,
1164        symbol: "Rh",
1165        orbitals: &RH_ORBITALS,
1166    },
1167    EhtParams {
1168        z: 46,
1169        symbol: "Pd",
1170        orbitals: &PD_ORBITALS,
1171    },
1172    EhtParams {
1173        z: 47,
1174        symbol: "Ag",
1175        orbitals: &AG_ORBITALS,
1176    },
1177    EhtParams {
1178        z: 48,
1179        symbol: "Cd",
1180        orbitals: &CD_ORBITALS,
1181    },
1182    EhtParams {
1183        z: 53,
1184        symbol: "I",
1185        orbitals: &I_ORBITALS,
1186    },
1187    EhtParams {
1188        z: 72,
1189        symbol: "Hf",
1190        orbitals: &HF_ORBITALS,
1191    },
1192    EhtParams {
1193        z: 73,
1194        symbol: "Ta",
1195        orbitals: &TA_ORBITALS,
1196    },
1197    EhtParams {
1198        z: 74,
1199        symbol: "W",
1200        orbitals: &W_ORBITALS,
1201    },
1202    EhtParams {
1203        z: 75,
1204        symbol: "Re",
1205        orbitals: &RE_ORBITALS,
1206    },
1207    EhtParams {
1208        z: 76,
1209        symbol: "Os",
1210        orbitals: &OS_ORBITALS,
1211    },
1212    EhtParams {
1213        z: 77,
1214        symbol: "Ir",
1215        orbitals: &IR_ORBITALS,
1216    },
1217    EhtParams {
1218        z: 78,
1219        symbol: "Pt",
1220        orbitals: &PT_ORBITALS,
1221    },
1222    EhtParams {
1223        z: 79,
1224        symbol: "Au",
1225        orbitals: &AU_ORBITALS,
1226    },
1227    EhtParams {
1228        z: 80,
1229        symbol: "Hg",
1230        orbitals: &HG_ORBITALS,
1231    },
1232];
1233
1234/// Look up EHT parameters by atomic number.
1235pub fn get_params(z: u8) -> Option<&'static EhtParams> {
1236    ALL_PARAMS.iter().find(|p| p.z == z)
1237}
1238
1239/// Count the total number of valence basis functions for an element.
1240/// s → 1, p → 3, d → 5.
1241pub fn num_basis_functions(z: u8) -> usize {
1242    match get_params(z) {
1243        Some(p) => p
1244            .orbitals
1245            .iter()
1246            .map(|o| match o.l {
1247                0 => 1,
1248                1 => 3,
1249                2 => 5,
1250                _ => 0,
1251            })
1252            .sum(),
1253        None => 0,
1254    }
1255}
1256
1257#[cfg(test)]
1258mod tests {
1259    use super::*;
1260
1261    #[test]
1262    fn test_hydrogen_params() {
1263        let p = get_params(1).expect("H params");
1264        assert_eq!(p.symbol, "H");
1265        assert_eq!(p.orbitals.len(), 1);
1266        assert!((p.orbitals[0].vsip - (-13.6)).abs() < 1e-10);
1267        assert!((p.orbitals[0].zeta - 1.3).abs() < 1e-10);
1268    }
1269
1270    #[test]
1271    fn test_carbon_params() {
1272        let p = get_params(6).expect("C params");
1273        assert_eq!(p.symbol, "C");
1274        assert_eq!(p.orbitals.len(), 2);
1275        // 2s + 2px,2py,2pz = 4 basis functions
1276        assert_eq!(num_basis_functions(6), 4);
1277    }
1278
1279    #[test]
1280    fn test_oxygen_params() {
1281        let p = get_params(8).expect("O params");
1282        assert_eq!(p.symbol, "O");
1283        assert!((p.orbitals[0].vsip - (-32.3)).abs() < 1e-10);
1284        assert!((p.orbitals[1].vsip - (-14.8)).abs() < 1e-10);
1285    }
1286
1287    #[test]
1288    fn test_all_elements_have_params() {
1289        for z in [1, 5, 6, 7, 8, 9, 14, 15, 16, 17, 35, 53] {
1290            assert!(get_params(z).is_some(), "Missing params for Z={}", z);
1291        }
1292    }
1293
1294    #[test]
1295    fn test_unknown_element_returns_none() {
1296        assert!(get_params(118).is_none());
1297    }
1298
1299    #[test]
1300    fn test_basis_function_count() {
1301        assert_eq!(num_basis_functions(1), 1); // H: 1s
1302        assert_eq!(num_basis_functions(6), 4); // C: 2s + 2p(3)
1303        assert_eq!(num_basis_functions(8), 4); // O: 2s + 2p(3)
1304    }
1305
1306    #[test]
1307    fn test_transition_metals_are_experimental() {
1308        assert_eq!(support_level_for_element(26), SupportLevel::Experimental);
1309        assert!(is_transition_metal(26));
1310    }
1311
1312    #[test]
1313    fn test_support_summary_collects_warnings() {
1314        let support = analyze_eht_support(&[8, 26, 118]);
1315        assert_eq!(support.level, SupportLevel::Unsupported);
1316        assert!(support.has_transition_metals);
1317        assert_eq!(support.supported_elements, vec![8]);
1318        assert_eq!(support.provisional_elements, vec![26]);
1319        assert_eq!(support.unsupported_elements, vec![118]);
1320        assert_eq!(support.warnings.len(), 2);
1321    }
1322}