mercurys 0.0.3

Mercury celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use mercurys::physics::collisions::{
    Impactor, MEAN_IMPACT_SPEED_KM_S, MERCURY_SURFACE_DENSITY, caloris_basin_diameter,
    caloris_impactor, simple_complex_transition_m, typical_microimpact,
};
use mercurys::physics::orbit::{ECCENTRICITY, MERCURY_MASS, MERCURY_RADIUS, MercuryOrbit};
use mercurys::physics::rotation::MercuryRotation;
use mercurys::physics::tides::{
    SolarTide, bulge_variation, perihelion_aphelion_tide_ratio, spin_orbit_resonance,
    tidal_dissipation_rate, tidal_locking_timescale,
};

// ── Orbit ──

#[test]
fn orbital_period_about_88_days() {
    let o = MercuryOrbit::new();
    let days = o.orbital_period_days();
    assert!((days - 87.969).abs() < 1.0);
}

#[test]
fn perihelion_lt_aphelion() {
    let o = MercuryOrbit::new();
    assert!(o.perihelion_m() < o.aphelion_m());
}

#[test]
fn eccentricity_about_0_206() {
    assert!((ECCENTRICITY - 0.2056).abs() < 0.01);
}

#[test]
fn escape_velocity_about_4250() {
    let v = MercuryOrbit::escape_velocity_at_surface();
    assert!((v - 4250.0).abs() < 200.0);
}

#[test]
fn mean_motion_positive() {
    let o = MercuryOrbit::new();
    assert!(o.mean_motion() > 0.0);
}

#[test]
fn specific_orbital_energy_negative() {
    let o = MercuryOrbit::new();
    assert!(o.specific_orbital_energy() < 0.0);
}

#[test]
fn specific_angular_momentum_positive() {
    let o = MercuryOrbit::new();
    assert!(o.specific_angular_momentum() > 0.0);
}

#[test]
fn mean_orbital_velocity_about_47km() {
    let o = MercuryOrbit::new();
    let v = o.mean_orbital_velocity();
    assert!((v - 47360.0).abs() < 1000.0);
}

#[test]
fn velocity_higher_at_perihelion() {
    let o = MercuryOrbit::new();
    let vp = o.velocity_at_distance(o.perihelion_m());
    let va = o.velocity_at_distance(o.aphelion_m());
    assert!(vp > va);
}

#[test]
fn gravitational_force_sun_positive() {
    let o = MercuryOrbit::new();
    assert!(o.gravitational_force_sun() > 0.0);
}

#[test]
fn position_tuple_finite() {
    let o = MercuryOrbit::new();
    let (x, y, z) = o.position();
    assert!(x.is_finite() && y.is_finite() && z.is_finite());
}

#[test]
fn position_distance_matches_radius() {
    let o = MercuryOrbit::new();
    let (x, y, z) = o.position();
    let dist = (x * x + y * y + z * z).sqrt();
    let radius = o.current_radius();
    assert!((dist - radius).abs() / radius < 0.01);
}

#[test]
fn solar_irradiance_positive() {
    let o = MercuryOrbit::new();
    assert!(o.solar_irradiance() > 0.0);
}

#[test]
fn gr_precession_positive() {
    let o = MercuryOrbit::new();
    assert!(o.gr_perihelion_precession_rad_per_orbit() > 0.0);
}

#[test]
fn step_advances_anomaly() {
    let mut o = MercuryOrbit::new();
    let ma_before = o.mean_anomaly_rad;
    o.step(86400.0);
    assert!(o.mean_anomaly_rad != ma_before);
}

#[test]
fn at_mean_anomaly_sets_true_anomaly() {
    let o = MercuryOrbit::at_mean_anomaly(1.0);
    assert!(o.true_anomaly_rad.is_finite());
    assert!(o.true_anomaly_deg().is_finite());
}

#[test]
fn mercury_mass_about_3e23() {
    assert!((MERCURY_MASS - 3.3011e23).abs() < 1.0e22);
}

#[test]
fn mercury_radius_about_2440km() {
    assert!((MERCURY_RADIUS - 2_439_700.0).abs() < 10_000.0);
}

// ── Rotation ──

#[test]
fn spin_orbit_3_to_2() {
    let (n, d) = MercuryRotation::spin_orbit_ratio();
    assert_eq!((n, d), (3, 2));
}

#[test]
fn surface_velocity_at_equator_positive() {
    let r = MercuryRotation::new();
    assert!(r.surface_velocity_at_latitude(0.0) > 0.0);
}

#[test]
fn surface_velocity_decreases_with_lat() {
    let r = MercuryRotation::new();
    assert!(r.surface_velocity_at_latitude(0.0) > r.surface_velocity_at_latitude(60.0));
}

#[test]
fn coriolis_parameter_finite() {
    let r = MercuryRotation::new();
    assert!(r.coriolis_parameter(45.0).is_finite());
}

#[test]
fn centripetal_acceleration_positive() {
    let r = MercuryRotation::new();
    assert!(r.centripetal_acceleration_at_latitude(0.0) > 0.0);
}

#[test]
fn moment_of_inertia_positive() {
    let r = MercuryRotation::new();
    assert!(r.moment_of_inertia() > 0.0);
}

#[test]
fn rotational_ke_positive() {
    let r = MercuryRotation::new();
    assert!(r.rotational_kinetic_energy() > 0.0);
}

#[test]
fn angular_momentum_positive() {
    let r = MercuryRotation::new();
    assert!(r.angular_momentum() > 0.0);
}

#[test]
fn libration_amplitude_about_38() {
    let amp = MercuryRotation::libration_amplitude_arcsec();
    assert!((amp - 38.5).abs() < 5.0);
}

#[test]
fn libration_amplitude_rad_positive() {
    assert!(MercuryRotation::libration_amplitude_rad() > 0.0);
}

#[test]
fn libration_angle_oscillates() {
    let r = MercuryRotation::new();
    let a1 = r.libration_angle_rad(0.0);
    let a2 = r.libration_angle_rad(std::f64::consts::PI);
    assert!((a1 - a2).abs() > 0.0 || a1 == 0.0);
}

#[test]
fn subsolar_longitude_finite() {
    let r = MercuryRotation::new();
    let lon = r.subsolar_longitude_deg(0.5, 1_000_000.0);
    assert!(lon.is_finite());
}

#[test]
fn solar_day_about_176_earth_days() {
    let sd = MercuryRotation::solar_day_s();
    let earth_days = sd / 86400.0;
    assert!((earth_days - 175.94).abs() < 2.0);
}

#[test]
fn sidereal_day_about_58_earth_days() {
    let sd = MercuryRotation::sidereal_day_s();
    let earth_days = sd / 86400.0;
    assert!((earth_days - 58.646).abs() < 1.0);
}

#[test]
fn day_length_hours_finite() {
    let r = MercuryRotation::new();
    let h = r.day_length_hours(30.0);
    assert!(h.is_finite() && h > 0.0);
}

// ── Tides ──

#[test]
fn solar_tide_at_mean_distance() {
    let t = SolarTide::at_mean_distance();
    assert!(t.tidal_acceleration() > 0.0);
}

#[test]
fn perihelion_tide_stronger() {
    let peri = SolarTide::at_perihelion();
    let aph = SolarTide::at_aphelion();
    assert!(peri.tidal_acceleration() > aph.tidal_acceleration());
}

#[test]
fn tidal_bulge_height_positive() {
    let t = SolarTide::at_perihelion();
    assert!(t.tidal_bulge_height() > 0.0);
}

#[test]
fn tidal_potential_finite() {
    let t = SolarTide::at_mean_distance();
    assert!(t.tidal_potential(0.0).is_finite());
}

#[test]
fn gravitational_attraction_positive() {
    let t = SolarTide::at_mean_distance();
    assert!(t.gravitational_attraction() > 0.0);
}

#[test]
fn spin_orbit_resonance_3_2() {
    let (n, d) = spin_orbit_resonance();
    assert_eq!((n, d), (3, 2));
}

#[test]
fn tidal_dissipation_positive() {
    let d = tidal_dissipation_rate(mercurys::SEMI_MAJOR_AXIS_M);
    assert!(d > 0.0);
}

#[test]
fn bulge_variation_ordered() {
    let (peri, aph) = bulge_variation();
    assert!(peri > aph, "perihelion bulge should exceed aphelion");
}

#[test]
fn tidal_locking_timescale_positive() {
    let t = tidal_locking_timescale(1.0e11);
    assert!(t > 0.0);
}

#[test]
fn perihelion_aphelion_tide_ratio_gt_1() {
    assert!(perihelion_aphelion_tide_ratio() > 1.0);
}

// ── Collisions ──

#[test]
fn caloris_impactor_energy_huge() {
    let imp = caloris_impactor();
    assert!(imp.kinetic_energy_j() > 1.0e25);
}

#[test]
fn caloris_basin_diameter_about_1550km() {
    let d = caloris_basin_diameter();
    assert!((d - 1_550_000.0).abs() < 100_000.0);
}

#[test]
fn asteroid_crater_diameter_positive() {
    let imp = Impactor::asteroid(1000.0, 42.5);
    assert!(imp.crater_diameter_m(MERCURY_SURFACE_DENSITY) > 0.0);
}

#[test]
fn iron_asteroid_heavier() {
    let stony = Impactor::asteroid(100.0, 42.5);
    let iron = Impactor::iron_asteroid(100.0, 42.5);
    assert!(iron.kinetic_energy_j() > stony.kinetic_energy_j());
}

#[test]
fn with_angle_modifies() {
    let imp = Impactor::asteroid(100.0, 42.5).with_angle(30.0);
    assert!((imp.angle_deg - 30.0).abs() < 0.01);
}

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

#[test]
fn ejecta_volume_positive() {
    let imp = Impactor::asteroid(100.0, 42.5);
    assert!(imp.ejecta_volume_m3(MERCURY_SURFACE_DENSITY) > 0.0);
}

#[test]
fn ejecta_escape_fraction_between_0_and_1() {
    let imp = Impactor::asteroid(100.0, 42.5);
    let f = imp.ejecta_escape_fraction();
    assert!((0.0..=1.0).contains(&f));
}

#[test]
fn kinetic_energy_mt_positive() {
    let imp = Impactor::asteroid(100.0, 42.5);
    assert!(imp.kinetic_energy_mt() > 0.0);
}

#[test]
fn impact_velocity_matches() {
    let imp = Impactor::asteroid(100.0, 42.5);
    assert!(
        imp.impact_velocity() > 42500.0,
        "impact velocity includes escape velocity"
    );
}

#[test]
fn typical_microimpact_small() {
    let imp = typical_microimpact();
    assert!(imp.mass_kg > 0.0 && imp.mass_kg.is_finite());
}

#[test]
fn simple_complex_transition_positive() {
    assert!(simple_complex_transition_m() > 0.0);
}

#[test]
fn mean_impact_speed_about_42() {
    assert!((MEAN_IMPACT_SPEED_KM_S - 42.5).abs() < 5.0);
}