use std::f64::consts::PI;
use crate::constants::physical::{
BOHR_RADIUS, BOLTZMANN, ELECTRON_MASS, ELEMENTARY_CHARGE, FINE_STRUCTURE, PLANCK,
REDUCED_PLANCK, RYDBERG, SPEED_OF_LIGHT,
};
use super::error::{PhysicsError, PhysicsResult};
pub fn de_broglie_wavelength(mass: f64, velocity: f64) -> PhysicsResult<f64> {
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
if velocity <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "velocity",
reason: format!("velocity must be positive, got {velocity}"),
});
}
Ok(PLANCK / (mass * velocity))
}
pub fn de_broglie_wavelength_from_energy(mass: f64, kinetic_energy: f64) -> PhysicsResult<f64> {
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
if kinetic_energy <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "kinetic_energy",
reason: format!("kinetic energy must be positive, got {kinetic_energy}"),
});
}
Ok(PLANCK / (2.0 * mass * kinetic_energy).sqrt())
}
pub fn heisenberg_uncertainty(delta_x: f64) -> PhysicsResult<f64> {
if delta_x <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "delta_x",
reason: format!("position uncertainty must be positive, got {delta_x}"),
});
}
Ok(REDUCED_PLANCK / (2.0 * delta_x))
}
pub fn energy_time_uncertainty(delta_t: f64) -> PhysicsResult<f64> {
if delta_t <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "delta_t",
reason: format!("time uncertainty must be positive, got {delta_t}"),
});
}
Ok(REDUCED_PLANCK / (2.0 * delta_t))
}
pub fn hydrogen_energy_level(n: usize) -> PhysicsResult<f64> {
if n == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"principal quantum number n must be ≥ 1".to_string(),
));
}
let rydberg_energy = PLANCK * SPEED_OF_LIGHT * RYDBERG;
Ok(-rydberg_energy / (n * n) as f64)
}
pub fn hydrogen_orbit_radius(n: usize) -> PhysicsResult<f64> {
if n == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"principal quantum number n must be ≥ 1".to_string(),
));
}
Ok((n * n) as f64 * BOHR_RADIUS)
}
pub fn hydrogen_transition_energy(n_initial: usize, n_final: usize) -> PhysicsResult<f64> {
if n_initial == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"n_initial must be ≥ 1".to_string(),
));
}
if n_final == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"n_final must be ≥ 1".to_string(),
));
}
if n_initial == n_final {
return Err(PhysicsError::DomainError(
"n_initial and n_final must differ for a spectral transition".to_string(),
));
}
let e_i = hydrogen_energy_level(n_initial)?;
let e_f = hydrogen_energy_level(n_final)?;
Ok(e_i - e_f) }
pub fn particle_in_box_energy(n: usize, length: f64, mass: f64) -> PhysicsResult<f64> {
if n == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"quantum number n must be ≥ 1".to_string(),
));
}
if length <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "length",
reason: format!("box length must be positive, got {length}"),
});
}
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
let n_sq = (n * n) as f64;
Ok(n_sq * PI * PI * REDUCED_PLANCK * REDUCED_PLANCK / (2.0 * mass * length * length))
}
pub fn particle_in_box_wavefunction(
n: usize,
length: f64,
x: f64,
mass: f64,
) -> PhysicsResult<f64> {
if n == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"quantum number n must be ≥ 1".to_string(),
));
}
if length <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "length",
reason: format!("box length must be positive, got {length}"),
});
}
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
if x < 0.0 || x > length {
return Ok(0.0); }
Ok((2.0 / length).sqrt() * (n as f64 * PI * x / length).sin())
}
pub fn tunnel_transmission_wkb(
mass: f64,
energy: f64,
barrier_height: f64,
barrier_width: f64,
) -> PhysicsResult<f64> {
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
if energy <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "energy",
reason: format!("particle energy must be positive, got {energy}"),
});
}
if barrier_width <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "barrier_width",
reason: format!("barrier width must be positive, got {barrier_width}"),
});
}
if energy >= barrier_height {
return Err(PhysicsError::DomainError(format!(
"particle energy ({energy:.4e} J) must be less than barrier height ({barrier_height:.4e} J) \
for sub-barrier tunnelling"
)));
}
let kappa = (2.0 * mass * (barrier_height - energy)).sqrt() / REDUCED_PLANCK;
Ok((-2.0 * kappa * barrier_width).exp())
}
pub fn photon_energy(wavelength: f64) -> PhysicsResult<f64> {
if wavelength <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "wavelength",
reason: format!("wavelength must be positive, got {wavelength}"),
});
}
Ok(PLANCK * SPEED_OF_LIGHT / wavelength)
}
pub fn photon_wavelength(energy: f64) -> PhysicsResult<f64> {
if energy <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "energy",
reason: format!("photon energy must be positive, got {energy}"),
});
}
Ok(PLANCK * SPEED_OF_LIGHT / energy)
}
pub fn photon_frequency(energy: f64) -> PhysicsResult<f64> {
if energy <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "energy",
reason: format!("photon energy must be positive, got {energy}"),
});
}
Ok(energy / PLANCK)
}
pub fn qho_energy(n: usize, angular_freq: f64) -> PhysicsResult<f64> {
if angular_freq <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "angular_freq",
reason: format!("angular frequency must be positive, got {angular_freq}"),
});
}
Ok(REDUCED_PLANCK * angular_freq * (n as f64 + 0.5))
}
pub fn photoelectric_kinetic_energy(frequency: f64, work_function: f64) -> PhysicsResult<f64> {
if frequency <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "frequency",
reason: format!("frequency must be positive, got {frequency}"),
});
}
if work_function <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "work_function",
reason: format!("work function must be positive, got {work_function}"),
});
}
let photon_e = PLANCK * frequency;
if photon_e < work_function {
return Err(PhysicsError::DomainError(format!(
"photon energy ({photon_e:.4e} J) < work function ({work_function:.4e} J): \
no photoelectric emission"
)));
}
Ok(photon_e - work_function)
}
pub fn spin_angular_momentum(spin_twice: usize) -> PhysicsResult<f64> {
if spin_twice == 0 {
return Err(PhysicsError::QuantumNumberOutOfRange(
"spin_twice must be ≥ 1 (representing spin ≥ 1/2)".to_string(),
));
}
let s = spin_twice as f64 / 2.0;
Ok(REDUCED_PLANCK * (s * (s + 1.0)).sqrt())
}
#[must_use]
pub fn bohr_magneton() -> f64 {
ELEMENTARY_CHARGE * REDUCED_PLANCK / (2.0 * ELECTRON_MASS)
}
#[must_use]
pub fn fine_structure_constant() -> f64 {
FINE_STRUCTURE
}
pub fn thermal_wavelength(mass: f64, temperature: f64) -> PhysicsResult<f64> {
if mass <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "mass",
reason: format!("mass must be positive, got {mass}"),
});
}
if temperature <= 0.0 {
return Err(PhysicsError::InvalidParameter {
param: "temperature",
reason: format!("temperature must be positive (K), got {temperature}"),
});
}
Ok(PLANCK / (2.0 * PI * mass * BOLTZMANN * temperature).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-30;
#[test]
fn test_de_broglie_electron_thermal() {
let ke = 1.5 * BOLTZMANN * 300.0; let lambda = de_broglie_wavelength_from_energy(ELECTRON_MASS, ke).expect("should succeed");
assert!(lambda > 1e-9 && lambda < 20e-9, "λ = {lambda:.4e} m");
}
#[test]
fn test_de_broglie_invalid() {
assert!(de_broglie_wavelength(0.0, 1.0).is_err());
assert!(de_broglie_wavelength(1.0, 0.0).is_err());
assert!(de_broglie_wavelength_from_energy(0.0, 1.0).is_err());
assert!(de_broglie_wavelength_from_energy(1.0, 0.0).is_err());
}
#[test]
fn test_heisenberg_uncertainty_bohr_radius() {
let dp = heisenberg_uncertainty(BOHR_RADIUS).expect("should succeed");
let expected = REDUCED_PLANCK / (2.0 * BOHR_RADIUS);
assert!((dp - expected).abs() < 1e-35);
}
#[test]
fn test_heisenberg_uncertainty_invalid() {
assert!(heisenberg_uncertainty(0.0).is_err());
assert!(heisenberg_uncertainty(-1.0).is_err());
}
#[test]
fn test_energy_time_uncertainty() {
let de = energy_time_uncertainty(1e-9).expect("should succeed");
let expected = REDUCED_PLANCK / (2.0 * 1e-9);
assert!((de - expected).abs() < 1e-40);
}
#[test]
fn test_hydrogen_ground_state_energy() {
let e1 = hydrogen_energy_level(1).expect("should succeed");
let ev = crate::constants::physical::ELEMENTARY_CHARGE; let expected_ev = -13.606; let actual_ev = e1 / ev;
assert!(
(actual_ev - expected_ev).abs() < 0.01,
"E_1 = {actual_ev:.3} eV"
);
}
#[test]
fn test_hydrogen_energy_levels_ordering() {
let e1 = hydrogen_energy_level(1).expect("should succeed");
let e2 = hydrogen_energy_level(2).expect("should succeed");
let e3 = hydrogen_energy_level(3).expect("should succeed");
assert!(e1 < e2 && e2 < e3, "E_1 < E_2 < E_3");
}
#[test]
fn test_hydrogen_quantum_number_zero() {
assert!(hydrogen_energy_level(0).is_err());
}
#[test]
fn test_hydrogen_transition_lyman_alpha() {
let de = hydrogen_transition_energy(2, 1).expect("should succeed");
let ev = ELEMENTARY_CHARGE;
let de_ev = de / ev;
assert!((de_ev - 10.2).abs() < 0.1, "Lyman α = {de_ev:.3} eV");
}
#[test]
fn test_hydrogen_orbit_radius() {
let r1 = hydrogen_orbit_radius(1).expect("should succeed");
let r2 = hydrogen_orbit_radius(2).expect("should succeed");
let r3 = hydrogen_orbit_radius(3).expect("should succeed");
assert!((r1 - BOHR_RADIUS).abs() < 1e-20);
assert!((r2 - 4.0 * BOHR_RADIUS).abs() < 1e-20);
assert!((r3 - 9.0 * BOHR_RADIUS).abs() < 1e-20);
}
#[test]
fn test_particle_in_box_energy_ratio() {
let e1 = particle_in_box_energy(1, 1e-9, ELECTRON_MASS).expect("should succeed");
let e2 = particle_in_box_energy(2, 1e-9, ELECTRON_MASS).expect("should succeed");
let e3 = particle_in_box_energy(3, 1e-9, ELECTRON_MASS).expect("should succeed");
assert!((e2 / e1 - 4.0).abs() < 1e-12, "E_2/E_1 = {}", e2 / e1);
assert!((e3 / e1 - 9.0).abs() < 1e-12, "E_3/E_1 = {}", e3 / e1);
}
#[test]
fn test_particle_in_box_wavefunction_boundary() {
let l = 1e-9_f64;
let psi_0 = particle_in_box_wavefunction(1, l, 0.0, ELECTRON_MASS).expect("should succeed");
let psi_l = particle_in_box_wavefunction(1, l, l, ELECTRON_MASS).expect("should succeed");
assert!(psi_0.abs() < 1e-20, "ψ(0) = {psi_0}");
let norm = (2.0 / l).sqrt(); assert!(psi_l.abs() < norm * 2e-15, "ψ(L) = {psi_l:.4e}");
}
#[test]
fn test_particle_in_box_wavefunction_outside() {
let psi =
particle_in_box_wavefunction(1, 1e-9, 2e-9, ELECTRON_MASS).expect("should succeed");
assert_eq!(psi, 0.0);
}
#[test]
fn test_particle_in_box_invalid() {
assert!(particle_in_box_energy(0, 1e-9, ELECTRON_MASS).is_err());
assert!(particle_in_box_energy(1, 0.0, ELECTRON_MASS).is_err());
assert!(particle_in_box_energy(1, 1e-9, 0.0).is_err());
}
#[test]
fn test_tunnel_transmission_small_barrier() {
let t = tunnel_transmission_wkb(ELECTRON_MASS, 1e-20, 2e-20, 1e-9).expect("should succeed");
assert!(t > 0.0 && t < 1.0, "T = {t:.4e}");
}
#[test]
fn test_tunnel_transmission_thin_barrier() {
let t_wide =
tunnel_transmission_wkb(ELECTRON_MASS, 1e-19, 2e-19, 1e-10).expect("should succeed");
let t_thin =
tunnel_transmission_wkb(ELECTRON_MASS, 1e-19, 2e-19, 1e-11).expect("should succeed");
assert!(t_thin > t_wide, "Thinner barrier should transmit more");
}
#[test]
fn test_tunnel_transmission_above_barrier_fails() {
assert!(tunnel_transmission_wkb(ELECTRON_MASS, 3e-19, 2e-19, 1e-10).is_err());
}
#[test]
fn test_photon_energy_visible_light() {
let e = photon_energy(532e-9).expect("should succeed");
assert!((e - 3.73e-19).abs() < 0.05e-19, "E = {e:.4e} J");
}
#[test]
fn test_photon_energy_wavelength_roundtrip() {
let lambda_in = 500e-9_f64;
let e = photon_energy(lambda_in).expect("should succeed");
let lambda_out = photon_wavelength(e).expect("should succeed");
assert!((lambda_in - lambda_out).abs() < 1e-25);
}
#[test]
fn test_qho_ground_state_zero_point() {
let omega = 1e14_f64;
let e0 = qho_energy(0, omega).expect("should succeed");
let expected = 0.5 * REDUCED_PLANCK * omega;
assert!((e0 - expected).abs() < 1e-40);
}
#[test]
fn test_qho_energy_spacing() {
let omega = 1e12_f64;
let e0 = qho_energy(0, omega).expect("should succeed");
let e1 = qho_energy(1, omega).expect("should succeed");
let spacing = e1 - e0;
let expected = REDUCED_PLANCK * omega;
let rel_err = (spacing - expected).abs() / expected;
assert!(rel_err < 1e-12, "ΔE rel error = {rel_err:.4e}");
}
#[test]
fn test_bohr_magneton_matches_constant() {
let mu_b = bohr_magneton();
let expected = crate::constants::physical::BOHR_MAGNETON;
assert!(
(mu_b - expected).abs() / expected < 1e-6,
"μ_B = {mu_b:.6e} J/T"
);
}
#[test]
fn test_spin_half_angular_momentum() {
let s = spin_angular_momentum(1).expect("should succeed"); let expected = REDUCED_PLANCK * (3.0_f64).sqrt() / 2.0;
assert!((s - expected).abs() < 1e-50);
}
#[test]
fn test_fine_structure_constant() {
let alpha = fine_structure_constant();
assert!((alpha - 1.0 / 137.036).abs() < 1e-5, "α = {alpha:.8}");
}
}