Files
symclaw/crates/symclaw-materials/tests/integration.rs
T

189 lines
6.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Integration tests for symclaw-materials.
//!
//! Cross-module scenarios: lattice → groups → structure → bonding pipeline.
use std::collections::HashMap;
use symclaw_materials::bonding::{
BondValence, LennardJones, MadelungStructure, pauling_electronegativity, pauling_ionicity,
};
use symclaw_materials::groups::{PointGroup, SpaceGroup, SymOp};
use symclaw_materials::lattice::{CrystalSystem, LatticeParameters, Miller};
use symclaw_materials::structure::{CrystalStructure, ScatteringFactor, structure_factor};
// ── Lattice → structure pipeline ──────────────────────────────────
#[test]
fn nacl_full_pipeline() {
// Build structure → compute d-spacings → structure factors
let nacl = CrystalStructure::nacl();
let a = nacl.lattice.a;
// (200) reflection allowed in Fm-3m
let m200 = Miller::new(2, 0, 0);
let d200 = m200.d_spacing_cubic(a);
assert!(d200 > 0.0, "d(200) > 0");
// Bragg angle for Cu Kα
let two_theta = Miller::bragg_angle_deg(d200, 1.5406).unwrap();
assert!(
two_theta > 20.0 && two_theta < 40.0,
"NaCl (200) Bragg angle should be 20-40°, got {two_theta:.1}°"
);
}
#[test]
fn silicon_lattice_parameters() {
let si = CrystalStructure::silicon();
let vol = si.lattice.volume();
// Si a=5.4309 Å → V = a³ ≈ 160.2 ų
assert!(
(vol - 160.2).abs() < 1.0,
"Si unit cell V ≈ 160.2 ų, got {vol:.1}"
);
}
#[test]
fn reciprocal_lattice_cubic_orthogonal() {
// For cubic: aᵢ · bⱼ = 2π δᵢⱼ
let p = LatticeParameters::cubic(4.0);
let rv = p.reciprocal_vectors();
let lv = p.lattice_vectors();
let dot = |u: [f64; 3], v: [f64; 3]| u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
assert!((dot(rv[0], lv[0]) - 2.0 * std::f64::consts::PI).abs() < 1e-8);
assert!(dot(rv[0], lv[1]).abs() < 1e-8);
assert!(dot(rv[1], lv[0]).abs() < 1e-8);
}
// ── Point groups → space groups consistency ────────────────────────
#[test]
fn cubic_point_groups_all_centred() {
// Not all cubic PGs are centrosymmetric (23, 432, -43m are not)
let cubic: Vec<PointGroup> = PointGroup::all_32()
.into_iter()
.filter(|g| g.crystal_system == CrystalSystem::Cubic)
.collect();
assert_eq!(cubic.len(), 5, "5 cubic point groups");
let centrosym = cubic.iter().filter(|g| g.centrosymmetric).count();
assert_eq!(
centrosym, 2,
"2 centrosymmetric cubic groups (m-3 and m-3m)"
);
}
#[test]
fn symop_group_closure() {
// C4 × C4 = C2, C2 × C2 = E, C4^4 = E
let c4 = SymOp::c4z();
let c4_2 = c4.compose(&c4); // C4² = C2
let c4_4 = c4_2.compose(&c4_2); // C4⁴ = E
assert_eq!(c4_4.rot, SymOp::identity().rot, "C4⁴ = E");
}
#[test]
fn space_group_numbers_cover_all_230() {
assert!(SpaceGroup::is_valid_number(1));
assert!(SpaceGroup::is_valid_number(115));
assert!(SpaceGroup::is_valid_number(230));
assert!(!SpaceGroup::is_valid_number(231));
}
// ── Structure factor pipeline ──────────────────────────────────────
#[test]
fn silicon_f0_near_14() {
// Si scattering factor at s=0 should be ≈ Z = 14
let sf = ScatteringFactor::silicon();
let f0 = sf.eval(0.0);
assert!((f0 - 14.0).abs() < 0.5, "Si f(0) ≈ 14, got {f0:.2}");
}
#[test]
fn structure_factor_iron_bcc_110() {
// BCC: (110) is present (h+k+l = 2 = even)
let fe = CrystalStructure::bcc_iron();
let m110 = Miller::new(1, 1, 0);
let d = m110.d_spacing_cubic(fe.lattice.a);
let mut sf_map = HashMap::new();
sf_map.insert(
"Fe".to_owned(),
ScatteringFactor {
a: [11.77, 7.068, 3.982, 2.417],
b: [4.761, 0.307, 15.35, 76.88],
c: 1.036,
},
);
let (re, im) = structure_factor(&m110, &fe.sites, &sf_map, d, 1.5406);
let intensity = re * re + im * im;
assert!(
intensity > 1.0,
"Fe BCC (110) should be present, |F|²={intensity:.1}"
);
}
// ── Bonding pipeline ──────────────────────────────────────────────
#[test]
fn lj_argon_well_depth() {
let lj = LennardJones::argon();
// At r_min: V = -ε = -0.0104 eV
let v = lj.potential(lj.r_min());
assert!((v - lj.v_min()).abs() < 1e-12);
assert!(
(v + 0.0104).abs() < 1e-12,
"V(r_min) = -0.0104 eV for Ar, got {v}"
);
}
#[test]
fn madelung_nacl_born_lande_energy() {
// NaCl Born-Landé: ~-787 kJ/mol
let u = MadelungStructure::NaCl.lattice_energy_kj_mol(1.0, 1.0, 2.82, 9.0);
assert!(
u < -700.0 && u > -900.0,
"NaCl lattice energy should be -700 to -900 kJ/mol, got {u:.1}"
);
}
#[test]
fn bvs_silicon_four_coord() {
// Si⁴⁺ in tetrahedral coordination: 4 bonds each contributing BVS = 1
// BVS = n_bonds * exp((R0 - R)/b); target = 4
// For 4 equal bonds: each s = 1, so R = R0 = 1.624 Å
let bv = BondValence::new();
let r0 = 1.624_f64; // Si-O R0
let lengths = vec![r0; 4]; // each bond at R0 gives s=1 per bond
let sum = bv.bvs("Si", "O", &lengths).unwrap();
assert!((sum - 4.0).abs() < 0.01, "BVS(Si,4×O@R0) = 4, got {sum:.4}");
}
#[test]
fn pauling_ionicity_naf_vs_hf() {
// NaF more ionic than HF
let en_na = pauling_electronegativity("Na").unwrap();
let en_f = pauling_electronegativity("F").unwrap();
let en_h = pauling_electronegativity("H").unwrap();
let ion_naf = pauling_ionicity(en_na, en_f);
let ion_hf = pauling_ionicity(en_h, en_f);
assert!(
ion_naf > ion_hf,
"NaF more ionic than HF: {ion_naf:.2} > {ion_hf:.2}"
);
}
// ── Miller indices cross-check ────────────────────────────────────
#[test]
fn miller_zone_law_systematic() {
// [100] zone axis: planes (0kl) only — h=0
for k in -2..=2i32 {
for l in -2..=2i32 {
let m = Miller::new(0, k, l);
assert!(m.in_zone(1, 0, 0), "(0{k}{l}) should be in [100] zone");
}
}
// (100) itself is NOT in its own zone axis [100]
let m100 = Miller::new(1, 0, 0);
assert!(!m100.in_zone(1, 0, 0), "(100) is not in [100] zone");
}