satellitesfactory 0.0.2

Satellite factory — classify, build and catalogue natural satellites for any planetary system: Solar System moons (Moon, Galileans, Titan, Triton…) or custom configurations.
Documentation
use satellitesfactory::config::parameters::*;
use satellitesfactory::physics::gravitation::*;
use satellitesfactory::physics::interiors::*;
use satellitesfactory::physics::tidal_dynamics::*;

// ====================== Gravitation ======================

#[test]
fn satellite_gravity_surface_acceleration() {
    let g = SatelliteGravity::new(MOON_MASS, MOON_RADIUS);
    let acc = g.surface_acceleration();
    assert!(acc > 1.6 && acc < 1.7);
}

#[test]
fn satellite_gravity_escape_velocity() {
    let g = SatelliteGravity::new(MOON_MASS, MOON_RADIUS);
    let v = g.escape_velocity();
    assert!(v > 2300.0 && v < 2400.0);
}

#[test]
fn satellite_gravity_orbital_velocity() {
    let g = SatelliteGravity::new(EARTH_MASS, EARTH_RADIUS);
    let v = g.orbital_velocity(384_400.0e3);
    assert!(v > 1000.0 && v < 1100.0);
}

#[test]
fn satellite_gravity_orbital_period() {
    let g = SatelliteGravity::new(EARTH_MASS, EARTH_RADIUS);
    let p_days = g.orbital_period(384_400.0e3) / 86400.0;
    assert!((p_days - 27.3).abs() < 0.5);
}

#[test]
fn satellite_gravity_synchronous_orbit() {
    let g = SatelliteGravity::new(EARTH_MASS, EARTH_RADIUS);
    let r = g.synchronous_orbit_radius(86400.0);
    assert!(r > 4.0e7 && r < 4.5e7);
}

#[test]
fn satellite_gravity_sphere_of_influence() {
    let g = SatelliteGravity::new(MOON_MASS, MOON_RADIUS);
    let soi = g.sphere_of_influence(EARTH_MASS, 384_400.0e3);
    assert!(soi > 5.0e7 && soi < 7.0e7);
}

#[test]
fn two_body_force_earth_moon() {
    let f = two_body_force(EARTH_MASS, MOON_MASS, 384_400.0e3);
    assert!(f > 1.9e20 && f < 2.1e20);
}

#[test]
fn hill_sphere_moon() {
    let h = hill_sphere(384_400.0e3, MOON_MASS, EARTH_MASS);
    assert!(h > 5.0e7 && h < 7.0e7);
}

#[test]
fn roche_limit_fluid() {
    let rl = satellitesfactory::physics::gravitation::roche_limit(SATURN_RADIUS, 687.0, 917.0);
    assert!(rl > SATURN_RADIUS);
    assert!(rl < 3.0 * SATURN_RADIUS);
}

#[test]
fn roche_limit_rigid_less_than_fluid() {
    let r_fluid = satellitesfactory::physics::gravitation::roche_limit(SATURN_RADIUS, 687.0, 917.0);
    let r_rigid = roche_limit_rigid(SATURN_RADIUS, 687.0, 917.0);
    assert!(r_rigid < r_fluid);
}

#[test]
fn gravitational_binding_energy_positive() {
    let e = gravitational_binding_energy(MOON_MASS, MOON_RADIUS);
    assert!(e > 0.0);
}

#[test]
fn mutual_hill_positive() {
    let mh = mutual_hill_radius(421_700.0e3, IO_MASS, 671_100.0e3, EUROPA_MASS, JUPITER_MASS);
    assert!(mh > 0.0);
}

#[test]
fn tisserand_parameter_value() {
    let tp = tisserand_parameter(1.0e9, 5.2 * AU, 0.05, 0.1);
    assert!(tp > 0.0 && tp.is_finite());
}

// ====================== Tidal Dynamics ======================

#[test]
fn io_tidal_heating_rate() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let power = tidal.tidal_heating_rate();
    assert!(power > 1e10);
}

#[test]
fn io_tidal_heating_flux_positive() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let flux = tidal.tidal_heating_flux();
    assert!(flux > 0.0);
}

#[test]
fn io_equilibrium_temperature_positive() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let t = tidal.equilibrium_temperature_from_tidal();
    assert!(t > 0.0);
}

#[test]
fn europa_less_heating_than_io() {
    let io = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1).with_tidal(
        JUPITER_RADIUS,
        100.0,
        0.015,
    );
    let europa = TidalSystem::new(
        EUROPA_MASS,
        EUROPA_RADIUS,
        JUPITER_MASS,
        671_100.0e3,
        0.009_0,
    )
    .with_tidal(JUPITER_RADIUS, 100.0, 0.025);
    assert!(europa.tidal_heating_rate() < io.tidal_heating_rate());
}

#[test]
fn enceladus_tidal_heating() {
    let enc = TidalSystem::new(
        ENCELADUS_MASS,
        ENCELADUS_RADIUS,
        SATURN_MASS,
        238_042.0e3,
        0.004_7,
    )
    .with_tidal(SATURN_RADIUS, 20.0, 0.01);
    assert!(enc.tidal_heating_rate() > 0.0);
    assert!(enc.tidal_heating_flux() > 0.0);
}

#[test]
fn moon_earth_less_than_io() {
    let me = TidalSystem::new(MOON_MASS, MOON_RADIUS, EARTH_MASS, 384_400.0e3, 0.054_9).with_tidal(
        EARTH_RADIUS,
        27.0,
        0.025,
    );
    let io = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1).with_tidal(
        JUPITER_RADIUS,
        100.0,
        0.015,
    );
    assert!(me.tidal_heating_rate() < io.tidal_heating_rate());
}

#[test]
fn tidal_locking_timescale_positive() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let t = tidal.tidal_locking_timescale();
    assert!(t > 0.0 && t.is_finite());
}

#[test]
fn orbital_decay_rate_negative() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let rate = tidal.orbital_decay_rate();
    assert!(rate < 0.0);
}

#[test]
fn circularization_timescale_positive() {
    let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
        .with_tidal(JUPITER_RADIUS, 100.0, 0.015);
    let t = tidal.circularization_timescale();
    assert!(t > 0.0 && t.is_finite());
}

#[test]
fn tidal_bulge_height_positive() {
    let h = tidal_bulge_height(JUPITER_MASS, IO_RADIUS, 421_700.0e3);
    assert!(h > 0.0);
}

#[test]
fn dissipation_function_inverse_q() {
    let d = dissipation_function(100.0);
    assert!((d - 0.01).abs() < 1e-10);
}

#[test]
fn love_number_homogeneous_range() {
    let k2 = love_number_homogeneous(4e9, 1.315, EUROPA_RADIUS, 3013.0);
    assert!(k2 > 0.0 && k2 < 1.5);
}

#[test]
fn resonance_width_positive() {
    let rw = resonance_width(IO_MASS, EUROPA_MASS, JUPITER_MASS, 550_000.0e3);
    assert!(rw > 0.0);
}

// ====================== Interiors ======================

#[test]
fn moon_interior_no_ocean() {
    let mi = SatelliteInterior::new(MOON_MASS, MOON_RADIUS, 0.0, 0.83);
    assert!(mi.ocean_depth_estimate() == 0.0);
}

#[test]
fn europa_interior_has_ocean() {
    let ei = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
    assert!(ei.ocean_depth_estimate() > 0.0);
}

#[test]
fn ganymede_interior_has_ocean() {
    let gi = SatelliteInterior::new(GANYMEDE_MASS, GANYMEDE_RADIUS, 0.40, 0.55);
    assert!(gi.ocean_depth_estimate() > 0.0);
}

#[test]
fn titan_interior_has_ocean() {
    let ti = SatelliteInterior::new(TITAN_MASS, TITAN_RADIUS, 0.45, 0.50);
    assert!(ti.ocean_depth_estimate() > 0.0);
}

#[test]
fn io_interior_no_ocean() {
    let ii = SatelliteInterior::new(IO_MASS, IO_RADIUS, 0.0, 0.94);
    assert!(ii.ocean_depth_estimate() == 0.0);
}

#[test]
fn enceladus_interior_has_ocean() {
    let ei = SatelliteInterior::new(ENCELADUS_MASS, ENCELADUS_RADIUS, 0.57, 0.40);
    assert!(ei.ocean_depth_estimate() > 0.0);
}

#[test]
fn interior_mean_density_consistent() {
    let ei = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
    let rho = ei.mean_density();
    assert!(rho > 2500.0 && rho < 3500.0);
}

#[test]
fn interior_core_radius_less_than_total() {
    let ei = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
    assert!(ei.core_radius() < ei.radius);
}

#[test]
fn interior_mantle_plus_core_equals_radius() {
    let ei = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
    let sum = ei.core_radius() + ei.mantle_thickness();
    assert!((sum - ei.radius).abs() / ei.radius < 1e-12);
}

#[test]
fn interior_central_pressure_positive() {
    let gi = SatelliteInterior::new(GANYMEDE_MASS, GANYMEDE_RADIUS, 0.40, 0.55);
    assert!(gi.central_pressure() > 0.0);
}

#[test]
fn interior_moment_of_inertia() {
    let gi = SatelliteInterior::new(GANYMEDE_MASS, GANYMEDE_RADIUS, 0.40, 0.55);
    let moi = gi.moment_of_inertia_factor();
    assert!(moi > 0.2 && moi < 0.4);
}

#[test]
fn interior_binding_energy_positive() {
    let ti = SatelliteInterior::new(TITAN_MASS, TITAN_RADIUS, 0.45, 0.50);
    assert!(ti.gravitational_binding_energy() > 0.0);
}

#[test]
fn io_densest_then_europa_then_ganymede() {
    let io = SatelliteInterior::new(IO_MASS, IO_RADIUS, 0.0, 0.94);
    let eu = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
    let ga = SatelliteInterior::new(GANYMEDE_MASS, GANYMEDE_RADIUS, 0.40, 0.55);
    assert!(io.mean_density() > eu.mean_density());
    assert!(eu.mean_density() > ga.mean_density());
}

#[test]
fn differentiation_energy_positive() {
    let e = differentiation_energy(GANYMEDE_MASS, GANYMEDE_RADIUS, 0.3);
    assert!(e > 0.0);
}

#[test]
fn ice_melting_depth_finite() {
    let d = ice_melting_depth(100.0, 0.05);
    assert!(d > 0.0 && d.is_finite());
}

#[test]
fn ice_melting_depth_zero_when_warm() {
    let d = ice_melting_depth(300.0, 0.05);
    assert!(d == 0.0);
}

#[test]
fn ice_melting_depth_infinite_no_flux() {
    let d = ice_melting_depth(100.0, 0.0);
    assert!(d.is_infinite());
}

#[test]
fn convective_vigor_positive() {
    let ra = convective_vigor(0.05, 1e14, 1.6e-4, 100_000.0);
    assert!(ra > 0.0);
}