satellitesfactory 0.0.1

Satellite factory — classify, build and catalogue natural satellites for any planetary system: Solar System moons (Moon, Galileans, Titan, Triton…) or custom configurations.
Documentation
// system_generation.rs — Generate random satellite systems and validate them.

use satellitesfactory::config::parameters::*;
use satellitesfactory::engine::evolution::*;
use satellitesfactory::engine::formation::*;
use satellitesfactory::engine::generator::*;
use satellitesfactory::observables::astrometry::*;

fn main() {
    // Generate a Jovian-like system: 4 regulars, 3 captured, 2 ring moons, 1 volcanic, 1 icy
    let config = SystemGeneratorConfig::new(
        "Jupiter",
        JUPITER_MASS,
        JUPITER_RADIUS,
        2.0 * JUPITER_RADIUS,
        30.0 * JUPITER_RADIUS,
    )
    .with_counts(4, 3, 2, 1, 1)
    .with_exotic(1, 0, 0)
    .with_seed(42);
    let system = generate_system(&config);
    assert!(system.len() == 12);

    let mut n_reg = 0;
    let mut n_cap = 0;
    let mut n_ring = 0;
    let mut n_vol = 0;
    let mut n_icy = 0;
    let mut n_ocean = 0;
    for sat in &system {
        match sat {
            GeneratedSatellite::Regular(s) => {
                n_reg += 1;
                assert!(s.mass > 0.0);
                assert!(s.radius > 0.0);
                assert!(s.surface_gravity() > 0.0);
                let period = s.period(JUPITER_MASS);
                assert!(period > 0.0 && period.is_finite());
            }
            GeneratedSatellite::Captured(s) => {
                n_cap += 1;
                assert!(s.mass > 0.0);
                assert!(s.mean_density() > 0.0);
                assert!(s.capture_energy() > 0.0);
            }
            GeneratedSatellite::Ring(s) => {
                n_ring += 1;
                assert!(s.mass > 0.0);
                assert!(s.ice_fraction > 0.0);
                assert!(s.mean_density() > 0.0);
            }
            GeneratedSatellite::Volcanic(s) => {
                n_vol += 1;
                assert!(s.mass > 0.0);
                assert!(s.mean_density() > 0.0);
            }
            GeneratedSatellite::Icy(s) => {
                n_icy += 1;
                assert!(s.mass > 0.0);
                assert!(s.ice_mass() > 0.0);
            }
            GeneratedSatellite::SubsurfaceOcean(s) => {
                n_ocean += 1;
                assert!(s.mass > 0.0);
                assert!(s.ocean_depth() > 0.0);
            }
            GeneratedSatellite::Exo(_) | GeneratedSatellite::CoOrbital(_) => {}
        }
    }
    assert!(n_reg == 4);
    assert!(n_cap == 3);
    assert!(n_ring == 2);
    assert!(n_vol == 1);
    assert!(n_icy == 1);
    assert!(n_ocean == 1);

    // Generate a Saturnian-like system with all 8 types
    let config2 = SystemGeneratorConfig::new(
        "Saturn",
        SATURN_MASS,
        SATURN_RADIUS,
        1.5 * SATURN_RADIUS,
        25.0 * SATURN_RADIUS,
    )
    .with_counts(5, 2, 4, 1, 2)
    .with_exotic(1, 1, 1)
    .with_seed(123);
    let system2 = generate_system(&config2);
    assert!(system2.len() == 17);

    // Formation model — Jovian circumplanetary disk
    let fm = FormationModel::new(
        JUPITER_MASS,
        JUPITER_RADIUS,
        0.02,
        30.0 * JUPITER_RADIUS,
        400.0,
    );
    let disk_m = fm.disk_mass();
    assert!(disk_m > 0.0 && disk_m < JUPITER_MASS);

    let ice_line = fm.ice_line_radius();
    assert!(ice_line > JUPITER_RADIUS);

    let max_sat = fm.maximum_satellite_mass();
    assert!(max_sat > 0.0);

    // Disk properties at different radii
    let radii = [5.0, 10.0, 20.0, 30.0];
    for &r_factor in &radii {
        let r = r_factor * JUPITER_RADIUS;
        let sigma = fm.disk_surface_density(r);
        let temp = fm.disk_temperature(r);
        let iso = fm.isolation_mass(r);
        let t_form = fm.formation_timescale(r);
        assert!(sigma > 0.0);
        assert!(temp > 0.0 && temp < fm.disk_temperature_1r);
        assert!(iso > 0.0);
        assert!(t_form > 0.0);
    }

    // Saturnian formation — smaller disk
    let fm_sat = FormationModel::new(
        SATURN_MASS,
        SATURN_RADIUS,
        0.01,
        25.0 * SATURN_RADIUS,
        200.0,
    );
    assert!(fm_sat.disk_mass() < fm.disk_mass());

    // Capture probability
    let p_cap = FormationModel::capture_probability(300.0, 1000.0, 0.5);
    assert!(p_cap > 0.0 && p_cap <= 1.0);
    let p_too_fast = FormationModel::capture_probability(1500.0, 1000.0, 0.5);
    assert!(p_too_fast == 0.0);

    // Impact ejecta
    let ejecta = FormationModel::impact_ejecta_fraction(20_000.0, 2_400.0);
    assert!(ejecta > 0.0 && ejecta <= 0.5);
    let no_ejecta = FormationModel::impact_ejecta_fraction(1000.0, 2400.0);
    assert!(no_ejecta == 0.0);

    // Ring spreading
    let t_spread = FormationModel::ring_spreading_timescale(100_000.0e3, 1e6);
    assert!(t_spread > 0.0);

    // Evolution — Io-like
    let evo = SatelliteEvolution::new(
        IO_MASS,
        IO_RADIUS,
        421_700.0e3,
        0.004_1,
        JUPITER_MASS,
        JUPITER_RADIUS,
    );
    let da_dt = evo.tidal_migration_rate(100.0);
    assert!(da_dt > 0.0);

    let circ = evo.circularization_timescale(100.0, 0.015);
    assert!(circ > 0.0);

    let sputter = evo.sputtering_rate(1e12);
    assert!(sputter > 0.0);

    // Triton-like evolution (retrograde capture → spiral in)
    let triton_evo = SatelliteEvolution::new(
        TRITON_MASS,
        TRITON_RADIUS,
        354_759.0e3,
        0.000_016,
        NEPTUNE_MASS,
        NEPTUNE_RADIUS,
    );
    let triton_decay = triton_evo.orbital_decay_timescale(12_000.0);
    assert!(triton_decay > 0.0);

    // Kozai-Lidov
    let emax = kozai_lidov_max_eccentricity(0.7);
    assert!(emax > 0.0 && emax < 1.0);
    let emax_low = kozai_lidov_max_eccentricity(0.1);
    assert!(emax_low <= emax);

    // Astrometric wobble of Jupiter due to Ganymede
    let wobble = astrometric_wobble(GANYMEDE_MASS, JUPITER_MASS, 1_070_400.0e3, 10.0 * 3.086e16);
    assert!(wobble > 0.0);

    // Light travel time to Jupiter
    let ltt = light_travel_time(5.2 * AU);
    assert!(ltt > 2500.0 && ltt < 2700.0);
}