#![cfg(all(feature = "alpha-dft", feature = "alpha-obara-saika"))]
use sci_form::dft::grid::{GridQuality, MolecularGrid};
use sci_form::dft::ks_fock::{solve_ks_dft, DftConfig, DftMethod};
const _HARTREE_TO_EV: f64 = 27.211_386;
#[test]
fn h2_svwn_total_energy_reasonable() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Fine,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
assert!(result.converged, "SCF must converge for H₂");
assert!(
result.total_energy < 0.0,
"H₂ total energy ({:.4} Ha) should be negative",
result.total_energy
);
assert!(
result.total_energy.is_finite(),
"H₂ total energy should be finite"
);
assert_eq!(result.n_electrons, 2);
assert_eq!(result.n_basis, 2);
}
#[test]
fn h2_svwn_homo_lumo_gap_positive() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Medium,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
assert!(result.converged);
assert!(
result.gap > 0.0,
"HOMO-LUMO gap ({:.3} eV) should be positive for H₂",
result.gap
);
assert!(result.gap.is_finite(), "HOMO-LUMO gap should be finite");
}
#[test]
fn h2_pbe_converges_and_energy_reasonable() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Pbe,
grid_quality: GridQuality::Fine,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
assert!(result.converged, "PBE SCF must converge for H₂");
assert!(
result.total_energy < 0.0 && result.total_energy.is_finite(),
"PBE H₂ energy ({:.4} Ha) should be negative and finite",
result.total_energy
);
}
#[test]
fn h2_svwn_vs_pbe_methods_differ() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let svwn = solve_ks_dft(
&elements,
&positions,
&DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Fine,
..Default::default()
},
)
.unwrap();
let pbe = solve_ks_dft(
&elements,
&positions,
&DftConfig {
method: DftMethod::Pbe,
grid_quality: GridQuality::Fine,
..Default::default()
},
)
.unwrap();
assert!(svwn.converged && pbe.converged);
assert!(
(svwn.total_energy - pbe.total_energy).abs() > 1e-4,
"SVWN ({:.6}) and PBE ({:.6}) should give different total energies",
svwn.total_energy,
pbe.total_energy
);
}
#[test]
fn h2_svwn_energy_increases_with_stretch() {
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Medium,
..Default::default()
};
let elements = [1u8, 1];
let e_equil = solve_ks_dft(&elements, &[[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]], &config).unwrap();
let e_stretched =
solve_ks_dft(&elements, &[[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]], &config).unwrap();
assert!(e_equil.converged && e_stretched.converged);
assert!(
e_equil.total_energy < e_stretched.total_energy,
"Equilibrium ({:.4}) should be lower than stretched ({:.4})",
e_equil.total_energy,
e_stretched.total_energy
);
}
#[test]
fn h2_svwn_mulliken_charges_sum_to_zero() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Medium,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
let sum: f64 = result.mulliken_charges.iter().sum();
assert!(
sum.abs() < 0.1,
"Mulliken charges should sum to ~0 for neutral H₂, got {:.4}",
sum
);
}
#[test]
fn h2_svwn_mulliken_charges_symmetric() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Fine,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
let diff = (result.mulliken_charges[0] - result.mulliken_charges[1]).abs();
assert!(
diff < 0.05,
"Symmetric H₂ should have equal charges, diff = {:.4}",
diff
);
}
#[test]
fn grid_quality_ordering_affects_points() {
let elements = [1u8, 1];
let positions_bohr = [[0.0, 0.0, 0.0], [1.4, 0.0, 0.0]];
let coarse = MolecularGrid::build(&elements, &positions_bohr, GridQuality::Coarse);
let fine = MolecularGrid::build(&elements, &positions_bohr, GridQuality::Fine);
assert!(
fine.n_points > coarse.n_points,
"Fine grid ({}) should have more points than Coarse ({})",
fine.n_points,
coarse.n_points
);
}
#[test]
fn h2_svwn_xc_energy_negative() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig {
method: DftMethod::Svwn,
grid_quality: GridQuality::Medium,
..Default::default()
};
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
assert!(result.converged);
assert!(
result.xc_energy < 0.0,
"XC energy ({:.6} Ha) should be negative",
result.xc_energy
);
}
#[test]
fn h2_nuclear_repulsion_positive() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let config = DftConfig::default();
let result = solve_ks_dft(&elements, &positions, &config).unwrap();
assert!(
result.nuclear_repulsion > 0.0,
"Nuclear repulsion ({:.6}) should be positive",
result.nuclear_repulsion
);
let expected_nuc = 1.0 / (0.74 * 1.889_726_124_6);
assert!(
(result.nuclear_repulsion - expected_nuc).abs() / expected_nuc < 0.01,
"Nuclear repulsion ({:.6}) should be near {:.6}",
result.nuclear_repulsion,
expected_nuc
);
}