use crate::constants;
#[must_use]
#[inline]
pub fn effective_temperature(luminosity_w: f64, radius_m: f64) -> f64 {
if luminosity_w <= 0.0 || radius_m <= 0.0 {
tracing::warn!(
luminosity_w,
radius_m,
"non-positive input to effective_temperature — returning 0"
);
return 0.0;
}
let flux = luminosity_w / (4.0 * std::f64::consts::PI * radius_m * radius_m);
(flux / constants::SIGMA_SB).powf(0.25)
}
#[must_use]
#[inline]
pub fn log_surface_gravity(mass_solar: f64, radius_solar: f64) -> f64 {
if mass_solar <= 0.0 || radius_solar <= 0.0 {
tracing::warn!(
mass_solar,
radius_solar,
"non-positive input to log_surface_gravity — returning 0"
);
return 0.0;
}
let m_kg = mass_solar * constants::M_SUN;
let r_m = radius_solar * constants::R_SUN;
let g_si = constants::G * m_kg / (r_m * r_m);
(g_si * 100.0).log10()
}
#[must_use]
#[inline]
pub fn grey_atmosphere_temperature(t_eff: f64, optical_depth: f64) -> f64 {
(0.75 * t_eff.powi(4) * (optical_depth + 2.0 / 3.0)).powf(0.25)
}
#[must_use]
#[inline]
pub fn kramers_opacity(density_gcc: f64, temperature_k: f64, x: f64, z: f64) -> f64 {
if temperature_k <= 0.0 {
return 0.0;
}
4.34e25 * (1.0 + x) * z * density_gcc * temperature_k.powf(-3.5)
}
#[must_use]
#[inline]
pub fn electron_scattering_opacity(hydrogen_fraction: f64) -> f64 {
0.2 * (1.0 + hydrogen_fraction)
}
#[must_use]
#[inline]
pub fn scale_height(temperature_k: f64, mean_molecular_weight: f64, gravity_ms2: f64) -> f64 {
if mean_molecular_weight <= 0.0 || gravity_ms2 <= 0.0 {
return 0.0;
}
constants::K_B * temperature_k / (mean_molecular_weight * constants::AMU * gravity_ms2)
}
#[must_use]
#[inline]
pub fn limb_darkening_linear(mu: f64, u: f64) -> f64 {
(1.0 - u * (1.0 - mu)).clamp(0.0, 1.0)
}
#[must_use]
#[inline]
pub fn limb_darkening_quadratic(mu: f64, a: f64, b: f64) -> f64 {
let omu = 1.0 - mu;
(1.0 - a * omu - b * omu * omu).clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn solar_effective_temperature() {
let t = effective_temperature(constants::L_SUN, constants::R_SUN);
assert!(
(t - constants::T_SUN).abs() < 10.0,
"Solar T_eff: {t} K, expected ~{}",
constants::T_SUN
);
}
#[test]
fn solar_log_g() {
let lg = log_surface_gravity(1.0, 1.0);
assert!(
(lg - 4.44).abs() < 0.02,
"Solar log(g): {lg}, expected ~4.44"
);
}
#[test]
fn grey_atmosphere_surface() {
let t = grey_atmosphere_temperature(5772.0, 2.0 / 3.0);
let expected = (0.75 * 5772.0_f64.powi(4) * (2.0 / 3.0 + 2.0 / 3.0)).powf(0.25);
assert!(
(t - expected).abs() < 1.0,
"Grey atmosphere at τ=2/3: {t}, expected {expected}"
);
}
#[test]
fn grey_atmosphere_deep() {
let t_surface = grey_atmosphere_temperature(5772.0, 0.0);
let t_deep = grey_atmosphere_temperature(5772.0, 10.0);
assert!(t_deep > t_surface);
}
#[test]
fn electron_scattering_solar() {
let k = electron_scattering_opacity(0.74);
assert!((k - 0.348).abs() < 0.01, "Solar electron scattering: {k}");
}
#[test]
fn limb_darkening_center() {
assert!((limb_darkening_linear(1.0, 0.6) - 1.0).abs() < f64::EPSILON);
assert!((limb_darkening_quadratic(1.0, 0.4, 0.2) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn limb_darkening_edge() {
let i = limb_darkening_linear(0.0, 0.6);
assert!((i - 0.4).abs() < f64::EPSILON, "Limb at μ=0: {i}");
}
#[test]
fn scale_height_positive() {
let h = scale_height(5772.0, 1.3, 274.0);
assert!(h > 1e5 && h < 3e5, "Solar scale height: {h} m");
}
#[test]
fn zero_inputs_return_zero() {
assert!(effective_temperature(0.0, 1.0).abs() < f64::EPSILON);
assert!(log_surface_gravity(0.0, 1.0).abs() < f64::EPSILON);
assert!(kramers_opacity(1.0, 0.0, 0.74, 0.02).abs() < f64::EPSILON);
assert!(scale_height(5772.0, 0.0, 274.0).abs() < f64::EPSILON);
}
}