const EPS0: f64 = 8.854_187_817e-12;
const MU0: f64 = 1.256_637_061_4e-6;
const C0: f64 = 2.997_924_58e8;
const H_PLANCK: f64 = 6.626_070_15e-34;
#[derive(Debug, Clone)]
pub struct MaxwellStressTensor {
pub t: [[f64; 3]; 3],
}
impl MaxwellStressTensor {
pub fn from_fields(e: [f64; 3], h: [f64; 3]) -> Self {
let e_sq = e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
let h_sq = h[0] * h[0] + h[1] * h[1] + h[2] * h[2];
let mut t = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
let delta = if i == j { 1.0 } else { 0.0 };
let e_term = EPS0 * (e[i] * e[j] - delta * e_sq / 2.0);
let h_term = MU0 * (h[i] * h[j] - delta * h_sq / 2.0);
t[i][j] = e_term + h_term;
}
}
Self { t }
}
pub fn force_on_surface(&self, normal: [f64; 3], area: f64) -> [f64; 3] {
let mut force = [0.0f64; 3];
for (i, f_i) in force.iter_mut().enumerate() {
for (j, &n_j) in normal.iter().enumerate() {
*f_i += self.t[i][j] * n_j;
}
*f_i *= area;
}
force
}
pub fn momentum_density(e: [f64; 3], h: [f64; 3]) -> [f64; 3] {
let s = Self::poynting_vector(e, h);
let c2 = C0 * C0;
[s[0] / c2, s[1] / c2, s[2] / c2]
}
pub fn poynting_vector(e: [f64; 3], h: [f64; 3]) -> [f64; 3] {
[
e[1] * h[2] - e[2] * h[1],
e[2] * h[0] - e[0] * h[2],
e[0] * h[1] - e[1] * h[0],
]
}
pub fn radiation_pressure_mirror(intensity_w_per_m2: f64) -> f64 {
2.0 * intensity_w_per_m2 / C0
}
pub fn radiation_pressure_absorber(intensity_w_per_m2: f64) -> f64 {
intensity_w_per_m2 / C0
}
pub fn total_force(
e_field: &[[f64; 3]],
h_field: &[[f64; 3]],
normals: &[[f64; 3]],
areas: &[f64],
) -> [f64; 3] {
let n = e_field.len();
assert_eq!(h_field.len(), n, "h_field length must match e_field");
assert_eq!(normals.len(), n, "normals length must match e_field");
assert_eq!(areas.len(), n, "areas length must match e_field");
let mut total = [0.0f64; 3];
for idx in 0..n {
let mst = Self::from_fields(e_field[idx], h_field[idx]);
let df = mst.force_on_surface(normals[idx], areas[idx]);
total[0] += df[0];
total[1] += df[1];
total[2] += df[2];
}
total
}
}
pub struct RadiationPressure;
impl RadiationPressure {
pub fn solar_pressure_pa() -> f64 {
let solar_irradiance = 1361.0_f64;
solar_irradiance / C0
}
pub fn mirror_force_n(intensity: f64, area_m2: f64) -> f64 {
2.0 * intensity * area_m2 / C0
}
pub fn sail_force_n(intensity: f64, area_m2: f64, angle_rad: f64) -> f64 {
let cos_theta = angle_rad.cos();
2.0 * intensity * area_m2 * cos_theta * cos_theta / C0
}
pub fn photon_momentum(lambda_nm: f64) -> f64 {
let lambda_m = lambda_nm * 1.0e-9;
H_PLANCK / lambda_m
}
pub fn photon_force_n(power_w: f64, lambda_nm: f64) -> f64 {
let _ = lambda_nm; power_w / C0
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_radiation_pressure_mirror() {
let intensity = 1000.0_f64; let pressure = MaxwellStressTensor::radiation_pressure_mirror(intensity);
let expected = 2.0 * intensity / C0;
assert_abs_diff_eq!(pressure, expected, epsilon = 1.0e-20);
assert!((pressure - 6.671e-6).abs() < 1.0e-9);
}
#[test]
fn test_radiation_pressure_absorber() {
let intensity = 1000.0_f64;
let pressure = MaxwellStressTensor::radiation_pressure_absorber(intensity);
let expected = intensity / C0;
assert_abs_diff_eq!(pressure, expected, epsilon = 1.0e-20);
let mirror_p = MaxwellStressTensor::radiation_pressure_mirror(intensity);
assert_abs_diff_eq!(mirror_p, 2.0 * pressure, epsilon = 1.0e-30);
}
#[test]
fn test_photon_momentum() {
let lambda_nm = 500.0_f64;
let p = RadiationPressure::photon_momentum(lambda_nm);
let lambda_m = lambda_nm * 1.0e-9;
let expected = H_PLANCK / lambda_m;
assert_abs_diff_eq!(p, expected, epsilon = 1.0e-40);
assert!((p - 1.326e-27).abs() < 1.0e-30);
}
#[test]
fn test_photon_force() {
let power = 1.0_f64; let lambda_nm = 532.0_f64; let force = RadiationPressure::photon_force_n(power, lambda_nm);
let expected = power / C0;
assert_abs_diff_eq!(force, expected, epsilon = 1.0e-20);
assert!((force - 3.336e-9).abs() < 1.0e-12);
}
#[test]
fn test_poynting_vector() {
let e = [1.0, 0.0, 0.0_f64];
let h = [0.0, 1.0, 0.0_f64];
let s = MaxwellStressTensor::poynting_vector(e, h);
assert_abs_diff_eq!(s[0], 0.0, epsilon = 1.0e-15);
assert_abs_diff_eq!(s[1], 0.0, epsilon = 1.0e-15);
assert_abs_diff_eq!(s[2], 1.0, epsilon = 1.0e-15);
}
#[test]
fn test_mst_symmetric() {
let e = [1.5e5, 2.3e5, 0.8e5_f64]; let h = [400.0, 150.0, 600.0_f64]; let mst = MaxwellStressTensor::from_fields(e, h);
for i in 0..3 {
for j in 0..3 {
assert_abs_diff_eq!(mst.t[i][j], mst.t[j][i], epsilon = 1.0e-10);
}
}
}
#[test]
fn test_sail_force_normal_incidence() {
let intensity = 1361.0_f64; let area = 100.0_f64; let sail_force = RadiationPressure::sail_force_n(intensity, area, 0.0);
let mirror_force = RadiationPressure::mirror_force_n(intensity, area);
assert_abs_diff_eq!(sail_force, mirror_force, epsilon = 1.0e-20);
}
#[test]
fn test_total_force_single_point() {
let e_amp = 1.0e4_f64; let h_amp = e_amp / (MU0 * C0); let e_field = vec![[e_amp, 0.0, 0.0]];
let h_field = vec![[0.0, h_amp, 0.0]];
let normals = vec![[0.0, 0.0, -1.0]];
let area = 1.0e-6_f64;
let force = MaxwellStressTensor::total_force(&e_field, &h_field, &normals, &[area]);
assert!(
force[2] > 0.0,
"Radiation pressure force must be positive in z, got {}",
force[2]
);
assert!(
force[0].abs() < 1.0e-20,
"No lateral force for normal incidence"
);
assert!(
force[1].abs() < 1.0e-20,
"No lateral force for normal incidence"
);
let intensity = e_amp * h_amp; let expected_force = intensity / C0 * area;
assert_abs_diff_eq!(force[2], expected_force, epsilon = 1.0e-15);
}
#[test]
fn test_momentum_density_direction() {
let e = [1.0e4, 0.0, 0.0_f64];
let h = [0.0, 1.0e3 / 377.0, 0.0_f64]; let s = MaxwellStressTensor::poynting_vector(e, h);
let g = MaxwellStressTensor::momentum_density(e, h);
let c2 = C0 * C0;
assert_abs_diff_eq!(g[0], s[0] / c2, epsilon = 1.0e-20);
assert_abs_diff_eq!(g[1], s[1] / c2, epsilon = 1.0e-20);
assert_abs_diff_eq!(g[2], s[2] / c2, epsilon = 1.0e-20);
}
}