use tara::atmosphere;
use tara::bridge;
use tara::classification::{self, LuminosityClass};
use tara::evolution;
use tara::luminosity;
use tara::nucleosynthesis;
use tara::spectral;
use tara::sse;
use tara::star::{SpectralClass, Star};
#[test]
fn crate_loads() {
let _ = std::any::type_name::<tara::error::TaraError>();
}
#[test]
fn sun_end_to_end() {
let sun = Star::sun().unwrap();
assert_eq!(sun.spectral_class, SpectralClass::G);
assert_eq!(sun.spectral_subclass, 2);
assert_eq!(sun.luminosity_class, LuminosityClass::V);
let mk = classification::format_classification(sun.temperature_k, sun.luminosity_class);
assert_eq!(mk, "G2V");
let l = luminosity::luminosity_solar(sun.radius_solar, sun.temperature_k);
assert!((l - 1.0).abs() < 0.02, "Solar luminosity: {l}");
let m_bol = luminosity::absolute_bolometric_magnitude(l);
assert!((m_bol - 4.74).abs() < 0.1, "Solar M_bol: {m_bol}");
let phase = evolution::evolutionary_phase(sun.mass_solar, sun.age_years);
assert_eq!(phase, evolution::EvolutionaryPhase::MainSequence);
assert_eq!(
evolution::remnant_type(sun.mass_solar),
evolution::RemnantType::WhiteDwarf
);
}
#[test]
fn massive_star_cross_module() {
let star = Star::builder(30.0, 10.0, 35_000.0, 200_000.0, 3e6)
.build()
.unwrap();
assert_eq!(star.spectral_class, SpectralClass::O);
let lifetime = evolution::main_sequence_lifetime(star.mass_solar);
assert!(
lifetime < 1e8,
"O-star lifetime should be < 100 Myr: {lifetime:.2e}"
);
assert_eq!(
evolution::remnant_type(star.mass_solar),
evolution::RemnantType::BlackHole
);
let region = classification::hr_region(star.temperature_k, star.luminosity_solar);
assert_eq!(region, classification::HrRegion::Supergiant);
assert_eq!(
nucleosynthesis::dominant_process(30e6),
nucleosynthesis::FusionProcess::CnoCycle
);
}
#[test]
fn bridge_round_trips() {
let mu = bridge::stellar_mass_solar_to_mu(1.0);
assert!(mu > 1e20, "Solar mu: {mu}");
let peak = bridge::temperature_to_peak_wavelength_nm(5772.0);
assert!((peak - 502.0).abs() < 5.0, "Solar peak: {peak} nm");
let log_g = atmosphere::log_surface_gravity(1.0, 1.0);
let u = bridge::surface_gravity_to_limb_darkening(log_g);
assert!(u > 0.4 && u < 0.7, "Solar limb darkening: {u}");
let scale = bridge::composition_to_lifetime_scale(0.74);
assert!(
(scale - 1.0).abs() < 0.01,
"Solar composition scale: {scale}"
);
let cno = bridge::core_temp_to_cno_fraction(15.7e6);
assert!(cno < 0.5, "Solar core should be pp-dominated: {cno}");
let m = bridge::luminosity_class_to_abs_magnitude(LuminosityClass::V);
assert!((m - 5.0).abs() < 0.1);
}
#[test]
fn spectral_pipeline() {
let h_alpha_nm = 656.3;
let radiance = spectral::planck_radiance_nm(h_alpha_nm, 5772.0);
assert!(radiance > 0.0, "H-alpha radiance: {radiance}");
let shifted = spectral::doppler_shift(h_alpha_nm, 1e5);
assert!(shifted > h_alpha_nm, "Redshifted: {shifted}");
let sigma = spectral::thermal_broadening(656.3e-9, 5772.0, tara::constants::AMU);
assert!(sigma > 0.0 && sigma < 1e-9, "Broadening sigma: {sigma}");
let v = spectral::pseudo_voigt_profile(h_alpha_nm, h_alpha_nm, 0.01, 0.005);
assert!(v > 0.0, "Voigt at center: {v}");
}
#[test]
fn atmosphere_chain() {
let t_eff = atmosphere::effective_temperature(tara::constants::L_SUN, tara::constants::R_SUN);
assert!((t_eff - 5772.0).abs() < 10.0, "Effective temp: {t_eff}");
let log_g = atmosphere::log_surface_gravity(1.0, 1.0);
assert!((log_g - 4.44).abs() < 0.02, "log(g): {log_g}");
let t_23 = atmosphere::grey_atmosphere_temperature(t_eff, 2.0 / 3.0);
assert!((t_23 - t_eff).abs() < 1.0, "Grey T at τ=2/3: {t_23}");
let kappa_es = atmosphere::electron_scattering_opacity(0.74);
assert!((kappa_es - 0.348).abs() < 0.01, "kappa_es: {kappa_es}");
let g_cgs = 10.0_f64.powf(log_g); let g_si = g_cgs / 100.0; let h = atmosphere::scale_height(t_eff, 1.3, g_si);
assert!(h > 1e5 && h < 3e5, "Scale height: {h} m");
}
#[test]
fn magnitude_distance_consistency() {
let m_abs = luminosity::absolute_bolometric_magnitude(1.0);
let m_app = luminosity::apparent_magnitude(m_abs, 10.0);
assert!(
(m_app - m_abs).abs() < f64::EPSILON,
"At 10 pc, m should equal M"
);
let m_100 = luminosity::apparent_magnitude(m_abs, 100.0);
assert!(
(m_100 - m_abs - 5.0).abs() < 1e-10,
"100 pc distance modulus: {}",
m_100 - m_abs
);
let l = luminosity::luminosity_from_abs_bol_mag(m_abs);
let m_back = luminosity::absolute_bolometric_magnitude(l);
assert!(
(m_back - m_abs).abs() < 1e-10,
"Magnitude roundtrip: {m_abs} → L={l} → {m_back}"
);
}
#[test]
fn sse_solar_evolution() {
let z = sse::Z_SUN;
let zams = sse::zams_properties(1.0, z);
assert!(zams.luminosity_solar > 0.5 && zams.luminosity_solar < 1.0);
assert!(zams.radius_solar > 0.7 && zams.radius_solar < 1.2);
assert!(zams.temperature_k > 5000.0 && zams.temperature_k < 6500.0);
let t_ms = sse::ms_lifetime(1.0, z);
assert!(t_ms > 8e9 && t_ms < 13e9, "Solar t_MS: {t_ms:.2e}");
let mid = sse::ms_properties(1.0, z, t_ms * 0.5);
assert!(mid.luminosity_solar >= zams.luminosity_solar);
assert!(mid.luminosity_solar <= sse::tms_luminosity(1.0, z));
let l_tms = sse::tms_luminosity(1.0, z);
let r_tms = sse::tms_radius(1.0, z);
assert!(l_tms > zams.luminosity_solar);
assert!(r_tms >= zams.radius_solar);
}
#[test]
fn sse_metallicity_consistency() {
for mass in [0.5, 1.0, 5.0, 10.0] {
let l_low = sse::zams_luminosity(mass, 0.001);
let l_high = sse::zams_luminosity(mass, 0.02);
assert!(
l_low > l_high,
"M={mass}: low-Z L={l_low} should exceed high-Z L={l_high}"
);
}
let t_low = sse::ms_lifetime(1.0, 0.001);
let t_high = sse::ms_lifetime(1.0, 0.02);
assert!(
t_low < t_high,
"Low-Z lifetime {t_low:.2e} < high-Z {t_high:.2e}"
);
}
#[test]
fn sse_mass_ordering() {
let masses = [0.5, 1.0, 2.0, 5.0, 10.0, 20.0];
let z = sse::Z_SUN;
for w in masses.windows(2) {
let (m_lo, m_hi) = (w[0], w[1]);
assert!(sse::zams_luminosity(m_hi, z) > sse::zams_luminosity(m_lo, z));
assert!(sse::tms_luminosity(m_hi, z) > sse::tms_luminosity(m_lo, z));
assert!(sse::ms_lifetime(m_hi, z) < sse::ms_lifetime(m_lo, z));
}
}
#[test]
fn sse_evolve_lifecycle() {
let z = sse::Z_SUN;
let t_ms = sse::ms_lifetime(1.0, z);
let t_bgb = sse::t_bgb(1.0, z) * 1e6;
let s = sse::evolve(1.0, z, t_ms * 0.1);
assert_eq!(s.phase, sse::SsePhase::MainSequence);
assert!(s.luminosity_solar > 0.0);
let s = sse::evolve(1.0, z, t_ms * 0.5);
assert_eq!(s.phase, sse::SsePhase::MainSequence);
assert!(s.luminosity_solar >= sse::zams_luminosity(1.0, z));
assert!(s.luminosity_solar <= sse::tms_luminosity(1.0, z));
let s = sse::evolve(1.0, z, t_ms + (t_bgb - t_ms) * 0.5);
assert_eq!(s.phase, sse::SsePhase::HertzsprungGap);
assert!(s.luminosity_solar > sse::tms_luminosity(1.0, z));
let s = sse::evolve(1.0, z, t_bgb * 1.01);
assert_eq!(s.phase, sse::SsePhase::RedGiantBranch);
assert!(s.radius_solar > 1.5, "RGB radius: {}", s.radius_solar);
let s = sse::evolve(1.0, z, 1e11);
assert_eq!(s.phase, sse::SsePhase::WhiteDwarf);
let s = sse::evolve(30.0, z, 1e10);
assert_eq!(s.phase, sse::SsePhase::BlackHole);
let s = sse::evolve(12.0, z, 1e10);
assert_eq!(s.phase, sse::SsePhase::NeutronStar);
let l_ms = sse::evolve(1.0, z, t_ms * 0.99).luminosity_solar;
let l_hg = sse::evolve(1.0, z, t_ms + (t_bgb - t_ms) * 0.99).luminosity_solar;
let l_rgb = sse::evolve(1.0, z, t_bgb * 1.5).luminosity_solar;
assert!(l_hg > l_ms, "L_HG > L_MS: {l_hg} vs {l_ms}");
assert!(l_rgb > l_hg, "L_RGB > L_HG: {l_rgb} vs {l_hg}");
}