use crate::math::constants;
const WIEN_FREQ_CONSTANT: f64 = 5.879e10;
const WIEN_DISPLACEMENT_CONSTANT: f64 = 2.898e-3;
pub fn total_emissive_power(temperature: f64) -> f64 {
constants::SIGMA * temperature.powi(4)
}
pub fn spectral_peak_frequency(temperature: f64) -> f64 {
WIEN_FREQ_CONSTANT * temperature
}
pub fn color_temperature(peak_wavelength: f64) -> f64 {
assert!(peak_wavelength > 0.0, "peak_wavelength must be positive");
WIEN_DISPLACEMENT_CONSTANT / peak_wavelength
}
pub fn brightness_temperature(intensity: f64, frequency: f64) -> f64 {
assert!(frequency > 0.0, "frequency must be positive");
intensity * constants::C * constants::C / (2.0 * constants::K_B * frequency * frequency)
}
pub fn optical_depth(absorption_coeff: f64, path_length: f64) -> f64 {
absorption_coeff * path_length
}
pub fn beer_lambert(initial_intensity: f64, absorption_coeff: f64, path_length: f64) -> f64 {
initial_intensity * (-absorption_coeff * path_length).exp()
}
pub fn mean_free_path_photon(absorption_coeff: f64) -> f64 {
assert!(absorption_coeff > 0.0, "absorption_coeff must be positive");
1.0 / absorption_coeff
}
pub fn radiation_pressure(intensity: f64) -> f64 {
intensity / constants::C
}
pub fn radiation_pressure_reflected(intensity: f64) -> f64 {
2.0 * intensity / constants::C
}
pub fn emissivity_from_absorptivity(absorptivity: f64) -> f64 {
absorptivity
}
pub fn view_factor_parallel_plates(width: f64, height: f64, separation: f64) -> f64 {
assert!(separation > 0.0, "separation must be positive");
let x = width / separation;
let y = height / separation;
let x2 = x * x;
let a = 1.0 + x2;
let b = 1.0 + y * y;
let c = 1.0 + x2 + y * y;
let term_ln = ((a * b / c) as f64).sqrt().ln();
let term_x = x * b.sqrt() * (x / b.sqrt()).atan() - x * x.atan();
let term_y = y * a.sqrt() * (y / a.sqrt()).atan() - y * y.atan();
let factor = 2.0 / (constants::PI * x * y) * (term_ln + term_x + term_y);
factor.clamp(0.0, 1.0)
}
pub fn radiative_exchange(
emissivity1: f64,
emissivity2: f64,
area: f64,
t1: f64,
t2: f64,
) -> f64 {
assert!(emissivity1 > 0.0, "emissivity1 must be positive");
assert!(emissivity2 > 0.0, "emissivity2 must be positive");
let resistance = 1.0 / emissivity1 + 1.0 / emissivity2 - 1.0;
constants::SIGMA * area * (t1.powi(4) - t2.powi(4)) / resistance
}
pub fn intensity_at_distance(luminosity: f64, distance: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
luminosity / (4.0 * constants::PI * distance * distance)
}
pub fn luminosity_from_intensity(intensity: f64, distance: f64) -> f64 {
intensity * 4.0 * constants::PI * distance * distance
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_rel(a: f64, b: f64, tol: f64) -> bool {
if b == 0.0 {
return a.abs() < tol;
}
((a - b) / b).abs() < tol
}
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_total_emissive_power_sun() {
let e = total_emissive_power(5778.0);
assert!(approx_rel(e, 6.32e7, 0.02));
}
#[test]
fn test_total_emissive_power_room_temp() {
let e = total_emissive_power(300.0);
assert!(approx_rel(e, 459.3, 0.02));
}
#[test]
fn test_spectral_peak_frequency_sun() {
let f = spectral_peak_frequency(5778.0);
assert!(approx_rel(f, 3.397e14, 0.01));
}
#[test]
fn test_color_temperature_sun() {
let t = color_temperature(502e-9);
assert!(approx_rel(t, 5773.0, 0.01));
}
#[test]
fn test_color_temperature_inverse_of_wien() {
let temp = 3000.0;
let peak = WIEN_DISPLACEMENT_CONSTANT / temp;
let recovered = color_temperature(peak);
assert!(approx_rel(recovered, temp, 1e-9));
}
#[test]
fn test_brightness_temperature() {
let freq = 1e9; let intensity = 2.0 * constants::K_B * 5000.0 * freq * freq / (constants::C * constants::C);
let tb = brightness_temperature(intensity, freq);
assert!(approx_rel(tb, 5000.0, 1e-6));
}
#[test]
fn test_optical_depth() {
let tau = optical_depth(0.5, 10.0);
assert!(approx(tau, 5.0, 1e-9));
}
#[test]
fn test_beer_lambert_zero_depth() {
let i = beer_lambert(100.0, 0.0, 10.0);
assert!(approx(i, 100.0, 1e-9));
}
#[test]
fn test_beer_lambert_attenuation() {
let i = beer_lambert(1000.0, 1.0, 1.0);
assert!(approx_rel(i, 1000.0 / std::f64::consts::E, 1e-6));
}
#[test]
fn test_beer_lambert_thick_medium() {
let i = beer_lambert(1000.0, 10.0, 10.0);
assert!(i < 1e-30);
}
#[test]
fn test_mean_free_path_photon() {
let l = mean_free_path_photon(0.25);
assert!(approx(l, 4.0, 1e-9));
}
#[test]
fn test_radiation_pressure_absorbed() {
let p = radiation_pressure(1361.0);
assert!(approx_rel(p, 4.54e-6, 0.02));
}
#[test]
fn test_radiation_pressure_reflected() {
let p_abs = radiation_pressure(1361.0);
let p_ref = radiation_pressure_reflected(1361.0);
assert!(approx_rel(p_ref, 2.0 * p_abs, 1e-9));
}
#[test]
fn test_emissivity_from_absorptivity() {
assert!(approx(emissivity_from_absorptivity(0.75), 0.75, 1e-15));
}
#[test]
fn test_view_factor_parallel_plates_bounded() {
let f = view_factor_parallel_plates(1.0, 1.0, 1.0);
assert!(f > 0.0 && f < 1.0);
}
#[test]
fn test_view_factor_parallel_plates_large_separation() {
let f = view_factor_parallel_plates(1.0, 1.0, 1000.0);
assert!(f < 0.01);
}
#[test]
fn test_view_factor_parallel_plates_known_value() {
let f = view_factor_parallel_plates(1.0, 1.0, 1.0);
assert!(approx_rel(f, 0.1998, 0.02));
}
#[test]
fn test_radiative_exchange_blackbodies() {
let q = radiative_exchange(1.0, 1.0, 1.0, 500.0, 300.0);
let expected = 3084.683683936;
assert!(approx_rel(q, expected, 1e-9));
}
#[test]
fn test_radiative_exchange_gray_surfaces() {
let q = radiative_exchange(0.5, 0.8, 2.0, 600.0, 300.0);
let expected = 6124.00437252;
assert!(approx_rel(q, expected, 1e-9));
}
#[test]
fn test_radiative_exchange_equal_temps() {
let q = radiative_exchange(0.9, 0.9, 5.0, 400.0, 400.0);
assert!(approx(q, 0.0, 1e-9));
}
#[test]
fn test_intensity_at_distance_sun() {
let i = intensity_at_distance(3.828e26, 1.496e11);
assert!(approx_rel(i, 1361.0, 0.02));
}
#[test]
fn test_luminosity_from_intensity_roundtrip() {
let luminosity = 1e26;
let distance = 1e10;
let i = intensity_at_distance(luminosity, distance);
let recovered = luminosity_from_intensity(i, distance);
assert!(approx_rel(recovered, luminosity, 1e-9));
}
#[test]
fn test_inverse_square_law_double_distance() {
let i1 = intensity_at_distance(1e26, 1e10);
let i2 = intensity_at_distance(1e26, 2e10);
assert!(approx_rel(i2, i1 / 4.0, 1e-9));
}
#[test]
fn test_approx_rel_zero_b() {
assert!(approx_rel(0.0, 0.0, 1e-6));
assert!(!approx_rel(1.0, 0.0, 0.5));
}
}