use crate::{Angle, GeoCollection, Geonum};
const RYDBERG: f64 = 13.6;
const ALPHA: f64 = 1.0 / 137.035_999_084;
fn bohr(n: usize) -> f64 {
1.0 / n as f64
}
#[derive(Clone, Copy)]
pub enum Lattice {
Canonical,
Custom {
spin: Angle,
radial: fn(usize) -> f64,
q: Angle,
},
}
impl Lattice {
fn constants(self) -> (Angle, fn(usize) -> f64, Angle) {
match self {
Lattice::Canonical => (Angle::new(1.0, 3.0), bohr, Angle::new(1.0, 4.0)),
Lattice::Custom { spin, radial, q } => (spin, radial, q),
}
}
}
fn grade_positions(base: Angle, l: usize, spread: Angle, spin: Angle) -> Vec<Angle> {
let n_orb = 2 * l + 1;
let orbital_step = spread / n_orb as f64;
let mut pos = Vec::new();
let mut angle = base;
for _ in 0..n_orb {
pos.push(angle);
pos.push(angle + spin);
angle = angle + orbital_step;
}
pos
}
fn last_filled(z: usize) -> usize {
let mut placed = 0;
let mut n = 1;
for (nn, l) in Geonum::madelung_order(6) {
if placed >= z {
break;
}
n = nn;
placed += (2 * (2 * l + 1)).min(z - placed);
}
n
}
fn affinity_binding(z: usize, lattice: Lattice) -> f64 {
let screened = Geonum::new(1.0, 0.0, 1.0);
let marginal = Geonum::electron_wave(z + 1, lattice) - Geonum::electron_wave(z, lattice);
marginal.ionization_projection(screened, Geonum::valence_shell(z + 1) as f64, lattice)
}
pub trait Chemistry: Sized {
fn madelung_order(max_n: usize) -> Vec<(usize, usize)>;
fn electron_shell(z: usize, lattice: Lattice) -> GeoCollection;
fn electron_wave(z: usize, lattice: Lattice) -> Self;
fn valence_shell(z: usize) -> usize;
fn relativistic_valence_shell(z: usize) -> f64;
fn ionization_projection(&self, nuclear: Self, n: f64, lattice: Lattice) -> f64;
fn ionization_energy(z: usize, electrons: usize, lattice: Lattice) -> f64;
fn electron_affinity(z: usize, lattice: Lattice) -> f64;
fn electronegativity(z: usize, lattice: Lattice) -> f64;
}
impl Chemistry for Geonum {
fn madelung_order(max_n: usize) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let mut tier = Geonum::new(1.0, 1.0, 2.0); while tier.angle.blade() < 2 * max_n {
let t = tier.angle.blade(); let l_start = (t - 1) / 2; let mut n = t - l_start;
let mut l = l_start;
loop {
if n <= max_n {
out.push((n, l));
}
if l == 0 {
break;
}
n += 1; l -= 1;
}
tier = tier.increment_blade(); }
out
}
fn electron_shell(z: usize, lattice: Lattice) -> GeoCollection {
let (spin, radial, _) = lattice.constants();
let spread = Angle::new(1.0, 2.0); let mut electrons = Vec::new();
let mut placed = 0;
for (n, l) in Geonum::madelung_order(6) {
if placed >= z {
break;
}
let mut base = Angle::new(1.0, 1.0); for _ in 0..l {
base = base + spread;
}
let positions = grade_positions(base, l, spread, spin);
let to_fill = positions.len().min(z - placed);
let mag = radial(n);
for &p in positions.iter().take(to_fill) {
electrons.push(Geonum::new_with_angle(mag, p));
}
placed += to_fill;
}
GeoCollection::from(electrons)
}
fn electron_wave(z: usize, lattice: Lattice) -> Geonum {
Geonum::electron_shell(z, lattice).wave_sum()
}
fn valence_shell(z: usize) -> usize {
let mut placed = 0;
let mut n = 1;
for (nn, l) in Geonum::madelung_order(6) {
if placed >= z {
break;
}
if nn > n {
n = nn; }
placed += (2 * (2 * l + 1)).min(z - placed);
}
n
}
fn relativistic_valence_shell(z: usize) -> f64 {
let n_max = Geonum::valence_shell(z) as f64;
let n_last = last_filled(z) as f64;
let lorentz = (z as f64 * ALPHA).powi(2);
let periods_since_inversion = (n_max - 4.0).max(0.0);
n_max - lorentz * periods_since_inversion * (n_max - n_last)
}
fn ionization_projection(&self, nuclear: Self, n: f64, lattice: Lattice) -> f64 {
let (_, _, q) = lattice.constants();
let p = nuclear * *self; let ref0 = Geonum::new(1.0, 0.0, 1.0);
let ref_q = Geonum::new_with_angle(1.0, Angle::new(1.0, 2.0));
let adj = p.project(&ref0);
let opp = p.project(&ref_q);
RYDBERG * (adj.mag + q.grade_angle() * opp.mag) / (n * n)
}
fn ionization_energy(z: usize, electrons: usize, lattice: Lattice) -> f64 {
let nucleus = Geonum::new(z as f64, 0.0, 1.0);
let exposed = Geonum::new((z - (electrons - 1)) as f64, 0.0, 1.0);
let marginal = Geonum::electron_wave(electrons, lattice)
- Geonum::electron_wave(electrons - 1, lattice);
marginal.ionization_projection(
nucleus.geo(&exposed),
Geonum::valence_shell(electrons) as f64,
lattice,
)
}
fn electron_affinity(z: usize, lattice: Lattice) -> f64 {
let marginal = Geonum::electron_wave(z + 1, lattice) - Geonum::electron_wave(z, lattice);
let bind = affinity_binding(z, lattice);
if marginal.angle.grade() == 2 {
-bind } else {
bind
}
}
fn electronegativity(z: usize, lattice: Lattice) -> f64 {
(Geonum::ionization_energy(z, z, lattice) + affinity_binding(z, lattice)) / 2.0
}
}