earths 0.0.1

High-fidelity Earth simulation engine — orbit, atmosphere, geology, hydrology, biosphere, terrain, lighting, rendering, satellites, and temporal systems with full scientific coupling
Documentation
use earth::physics::collisions::{Impactor, chicxulub_equivalent, tunguska_equivalent};
use earth::physics::orbit::{
    ARGUMENT_PERIHELION_DEG, ECCENTRICITY, EarthOrbit, INCLINATION_DEG,
    LONGITUDE_ASCENDING_NODE_DEG, SEMI_MAJOR_AXIS,
};
use earth::physics::rotation::{
    AXIAL_TILT_DEG, AXIAL_TILT_RAD, EarthRotation, PRECESSION_PERIOD_YEARS, SIDEREAL_DAY_S,
    SOLAR_DAY_S,
};
use earth::physics::tides::{
    TidalForce, lunar_to_solar_tide_ratio, neap_tide_amplitude, spring_tide_amplitude,
};

#[test]
fn orbital_period_about_365_days() {
    let orbit = EarthOrbit::new();
    let days = orbit.orbital_period_days();
    assert!((days - 365.25).abs() < 1.0);
}

#[test]
fn perihelion_closer_than_aphelion() {
    let orbit = EarthOrbit::new();
    assert!(orbit.perihelion_m() < orbit.aphelion_m());
}

#[test]
fn velocity_higher_at_perihelion() {
    let orbit = EarthOrbit::new();
    let v_peri = orbit.velocity_at_distance(orbit.perihelion_m());
    let v_aph = orbit.velocity_at_distance(orbit.aphelion_m());
    assert!(v_peri > v_aph);
}

#[test]
fn orbital_energy_negative_bound() {
    let orbit = EarthOrbit::new();
    let e = orbit.specific_orbital_energy();
    assert!(e < 0.0);
}

#[test]
fn angular_momentum_positive() {
    let orbit = EarthOrbit::new();
    let l = orbit.specific_angular_momentum();
    assert!(l > 0.0);
}

#[test]
fn escape_velocity_about_11_km_s() {
    let v_esc = EarthOrbit::escape_velocity_at_surface();
    let v_km_s = v_esc / 1000.0;
    assert!((v_km_s - 11.2).abs() < 0.5);
}

#[test]
fn gravitational_force_sun_positive() {
    let orbit = EarthOrbit::new();
    let f = orbit.gravitational_force_sun();
    assert!(f > 3e22);
}

#[test]
fn current_radius_at_zero_anomaly() {
    let orbit = EarthOrbit::new();
    let r = orbit.current_radius();
    let expected = orbit.perihelion_m();
    assert!((r - expected).abs() / expected < 1e-10);
}

#[test]
fn mean_orbital_velocity_about_30_km_s() {
    let orbit = EarthOrbit::new();
    let v = orbit.mean_orbital_velocity() / 1000.0;
    assert!((v - 29.8).abs() < 1.0);
}

#[test]
fn orbital_constants_valid() {
    const { assert!(SEMI_MAJOR_AXIS > 1.49e11 && SEMI_MAJOR_AXIS < 1.50e11) };
    const { assert!(ECCENTRICITY > 0.0 && ECCENTRICITY < 0.1) };
    const { assert!(INCLINATION_DEG < 1.0) };
    const { assert!(LONGITUDE_ASCENDING_NODE_DEG < 360.0) };
    const { assert!(ARGUMENT_PERIHELION_DEG > 0.0 && ARGUMENT_PERIHELION_DEG < 360.0) };
}

#[test]
fn surface_velocity_max_at_equator() {
    let rot = EarthRotation::new();
    let v_eq = rot.surface_velocity_at_latitude(0.0);
    let v_45 = rot.surface_velocity_at_latitude(45.0);
    let v_90 = rot.surface_velocity_at_latitude(90.0);
    assert!(v_eq > v_45);
    assert!(v_45 > v_90);
    assert!((v_eq - 465.0).abs() < 5.0);
}

#[test]
fn centripetal_acceleration_max_at_equator() {
    let rot = EarthRotation::new();
    let a_eq = rot.centripetal_acceleration_at_latitude(0.0);
    let a_45 = rot.centripetal_acceleration_at_latitude(45.0);
    assert!(a_eq > a_45);
    assert!(a_eq > 0.03);
}

#[test]
fn coriolis_parameter_zero_at_equator() {
    let rot = EarthRotation::new();
    let f = rot.coriolis_parameter(0.0);
    assert!(f.abs() < 1e-10);
}

#[test]
fn moment_of_inertia_correct_order() {
    let rot = EarthRotation::new();
    let i = rot.moment_of_inertia();
    assert!(i > 8e37 && i < 9e37);
}

#[test]
fn rotational_kinetic_energy_huge() {
    let rot = EarthRotation::new();
    let ke = rot.rotational_kinetic_energy();
    assert!(ke > 2e29);
}

#[test]
fn angular_momentum_conserved_sign() {
    let rot = EarthRotation::new();
    let l = rot.angular_momentum();
    assert!(l > 0.0);
}

#[test]
fn precession_rate_small() {
    let rot = EarthRotation::new();
    let rate = rot.precession_rate_rad_per_year();
    assert!(rate > 0.0);
    assert!(rate < 0.001);
}

#[test]
fn day_length_summer_longer_than_winter() {
    let rot = EarthRotation::new();
    let summer = rot.day_length_variation_due_to_tilt(172, 50.0);
    let winter = rot.day_length_variation_due_to_tilt(355, 50.0);
    assert!(summer > winter);
    assert!(summer > 12.0);
    assert!(winter < 12.0);
}

#[test]
fn polar_day_24_hours() {
    let rot = EarthRotation::new();
    let arctic_summer = rot.day_length_variation_due_to_tilt(172, 80.0);
    assert!((arctic_summer - 24.0).abs() < 0.1);
}

#[test]
fn rotation_constants_valid() {
    const { assert!(SIDEREAL_DAY_S < SOLAR_DAY_S) };
    assert!((AXIAL_TILT_DEG - 23.44).abs() < 0.1);
    assert!((AXIAL_TILT_RAD - AXIAL_TILT_DEG.to_radians()).abs() < 0.01);
    const { assert!(PRECESSION_PERIOD_YEARS > 25_000.0) };
}

#[test]
fn lunar_tide_larger_than_solar() {
    let moon = TidalForce::from_moon();
    let sun = TidalForce::from_sun();
    assert!(moon.tidal_acceleration() > sun.tidal_acceleration());
}

#[test]
fn tidal_ratio_about_2_2() {
    let ratio = lunar_to_solar_tide_ratio();
    assert!((ratio - 2.2).abs() < 0.5);
}

#[test]
fn spring_tide_larger_than_neap() {
    let spring = spring_tide_amplitude();
    let neap = neap_tide_amplitude();
    assert!(spring > neap);
    assert!(spring > 0.0);
    assert!(neap > 0.0);
}

#[test]
fn tidal_potential_varies_with_angle() {
    let moon = TidalForce::from_moon();
    let pot_0 = moon.tidal_potential(0.0);
    let pot_90 = moon.tidal_potential(std::f64::consts::FRAC_PI_2);
    assert!(pot_0 != pot_90);
}

#[test]
fn tidal_bulge_height_positive() {
    let moon = TidalForce::from_moon();
    let h = moon.tidal_bulge_height();
    assert!(h > 0.0);
    assert!(h < 1.0);
}

#[test]
fn gravitational_attraction_moon_positive() {
    let moon = TidalForce::from_moon();
    let f = moon.gravitational_attraction();
    assert!(f > 1e20);
}

#[test]
fn gravitational_attraction_sun_much_larger() {
    let moon = TidalForce::from_moon();
    let sun = TidalForce::from_sun();
    assert!(sun.gravitational_attraction() > moon.gravitational_attraction());
}

#[test]
fn chicxulub_enormous_energy() {
    let chix = chicxulub_equivalent();
    let energy_mt = chix.kinetic_energy_mt();
    assert!(energy_mt > 1e6);
}

#[test]
fn tunguska_smaller_than_chicxulub() {
    let tung = tunguska_equivalent();
    let chix = chicxulub_equivalent();
    assert!(tung.kinetic_energy_j() < chix.kinetic_energy_j());
}

#[test]
fn impact_velocity_higher_than_approach() {
    let imp = Impactor::asteroid(1000.0, 15.0);
    let v_impact = imp.impact_velocity();
    assert!(v_impact > imp.velocity_m_s);
}

#[test]
fn crater_diameter_increases_with_impactor_size() {
    let small = Impactor::asteroid(100.0, 20.0);
    let large = Impactor::asteroid(10_000.0, 20.0);
    let c_small = small.crater_diameter_m(2700.0);
    let c_large = large.crater_diameter_m(2700.0);
    assert!(c_large > c_small);
}

#[test]
fn fireball_radius_positive() {
    let imp = Impactor::asteroid(500.0, 18.0);
    let r = imp.fireball_radius_m();
    assert!(r > 0.0);
}

#[test]
fn ejecta_volume_positive() {
    let imp = Impactor::asteroid(1000.0, 20.0);
    let vol = imp.ejecta_volume_m3(2700.0);
    assert!(vol > 0.0);
}

#[test]
fn kinetic_energy_megatons_consistent() {
    let imp = Impactor::asteroid(500.0, 15.0);
    let j = imp.kinetic_energy_j();
    let mt = imp.kinetic_energy_mt();
    assert!((mt - j / 4.184e15).abs() < 1.0);
}