planetsfactory 0.0.4

Planet factory — classify, build and catalogue planets for any star system: Solar System, TRAPPIST-1, Kepler-90, Proxima Centauri, or fully custom worlds.
Documentation
use planetsfactory::config::parameters::*;
use planetsfactory::engine::evolution::*;
use planetsfactory::engine::formation::*;
use planetsfactory::engine::generator::*;
use planetsfactory::engine::orbits::*;
use planetsfactory::observables::photometry::*;
use planetsfactory::observables::radial_velocity::*;
use planetsfactory::observables::transits::*;

#[test]
fn kepler_solver_circular() {
    let ea = solve_kepler(1.0, 0.0, 1e-14);
    assert!((ea - 1.0).abs() < 1e-12);
}

#[test]
fn kepler_solver_eccentric() {
    let ea = solve_kepler(1.0, 0.5, 1e-14);
    let check = ea - 0.5 * ea.sin();
    assert!((check - 1.0).abs() < 1e-12);
}

#[test]
fn true_anomaly_at_periapsis() {
    let nu = true_from_eccentric(0.0, 0.5);
    assert!(nu.abs() < 1e-12);
}

#[test]
fn elements_to_state_earth_distance() {
    let earth = OrbitalElements::from_au_deg(1.0, 0.0167, 0.0, 0.0, 0.0, 0.0);
    let (pos, _vel) = elements_to_state(&earth, SOLAR_MASS);
    let r = (pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2]).sqrt();
    assert!((r / AU - 1.0).abs() < 0.02);
}

#[test]
fn vis_viva_circular() {
    let v = vis_viva(G * SOLAR_MASS, AU, AU);
    let expected = (G * SOLAR_MASS / AU).sqrt();
    assert!((v - expected).abs() / expected < 1e-10);
}

#[test]
fn hohmann_delta_v_positive() {
    let (dv1, dv2) = hohmann_delta_v(G * SOLAR_MASS, AU, 1.524 * AU);
    assert!(dv1 > 0.0 && dv2 > 0.0);
}

#[test]
fn orbital_period_consistent() {
    let elem = OrbitalElements::from_au_deg(1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    let p = elem.period(G * SOLAR_MASS);
    assert!((p / YEAR - 1.0).abs() < 0.01);
}

#[test]
fn formation_new_isolation_mass_positive() {
    let f = FormationModel::new(
        FormationChannel::CoreAccretion,
        0.01 * SOLAR_MASS,
        SOLAR_MASS,
        0.02,
        2.7,
    );
    let m = f.isolation_mass(1.0);
    assert!(m > 0.0);
}

#[test]
fn formation_solar_like_snow_line_in_range() {
    let f = FormationModel::solar_like();
    assert!(f.snow_line_au > 2.0 && f.snow_line_au < 4.0);
}

#[test]
fn formation_critical_core_mass() {
    let f = FormationModel::solar_like();
    assert!(f.critical_core_mass() > 5.0 * EARTH_MASS);
}

#[test]
fn formation_gas_accretion_zero_below_critical() {
    let f = FormationModel::solar_like();
    assert!(f.runaway_gas_accretion_rate(EARTH_MASS) == 0.0);
}

#[test]
fn formation_gas_accretion_positive_above_critical() {
    let f = FormationModel::solar_like();
    assert!(f.runaway_gas_accretion_rate(20.0 * EARTH_MASS) > 0.0);
}

#[test]
fn formation_pebble_accretion_positive() {
    let f = FormationModel::new(
        FormationChannel::PebbleAccretion,
        0.01 * SOLAR_MASS,
        SOLAR_MASS,
        0.02,
        2.7,
    );
    assert!(f.pebble_accretion_rate(EARTH_MASS, 1.0) > 0.0);
}

#[test]
fn formation_timescale_positive() {
    let f = FormationModel::solar_like();
    assert!(f.formation_timescale(1.0) > 0.0);
}

#[test]
fn evolution_cooling_luminosity_positive() {
    let e = PlanetEvolution::new(JUPITER_MASS, JUPITER_RADIUS, 5.2 * AU, SOLAR_LUMINOSITY);
    assert!(e.cooling_luminosity() > 0.0);
}

#[test]
fn evolution_xuv_loss_positive() {
    let e = PlanetEvolution::new(EARTH_MASS, EARTH_RADIUS, AU, SOLAR_LUMINOSITY);
    assert!(e.xuv_mass_loss_rate(1e-3) > 0.0);
}

#[test]
fn evolution_impact_erosion_zero_below_escape() {
    let f = PlanetEvolution::impact_erosion_fraction(5e3, 1.1e4);
    assert!(f == 0.0);
}

#[test]
fn evolution_radius_at_age_gas_giant() {
    let e = PlanetEvolution::new(JUPITER_MASS, JUPITER_RADIUS, 5.2 * AU, SOLAR_LUMINOSITY);
    let r_young = e.radius_at_age(1e8 * YEAR);
    let r_old = e.radius_at_age(5e9 * YEAR);
    assert!(r_young > r_old);
}

#[test]
fn generator_solar_like_system() {
    let config = SystemGeneratorConfig::solar_like(8);
    let planets = generate_system(&config);
    assert!(planets.len() == 8);
}

#[test]
fn generator_m_dwarf_system() {
    let config = SystemGeneratorConfig::m_dwarf(5);
    let planets = generate_system(&config);
    assert!(planets.len() == 5);
}

#[test]
fn generator_hot_star_system() {
    let config = SystemGeneratorConfig::hot_star(6);
    let planets = generate_system(&config);
    assert!(planets.len() == 6);
}

#[test]
fn generator_single_planet() {
    let config = SystemGeneratorConfig::solar_like(1);
    let planets = generate_system(&config);
    assert!(planets.len() == 1);
}

#[test]
fn generator_custom_system() {
    let config = SystemGeneratorConfig {
        star_name: String::from("Proxima"),
        star_mass: 0.12 * SOLAR_MASS,
        star_temperature: 3042.0,
        star_radius: 0.154 * SOLAR_RADIUS,
        n_planets: 3,
        inner_limit_au: 0.01,
        outer_limit_au: 0.5,
        frost_line_au: 0.05,
        metallicity: 0.02,
        seed: 123,
    };
    let planets = generate_system(&config);
    assert!(planets.len() == 3);
}

#[test]
fn generator_produces_diverse_types() {
    let config = SystemGeneratorConfig::solar_like(12);
    let planets = generate_system(&config);
    let mut has_terrestrial = false;
    let mut has_giant = false;
    let mut has_ice = false;
    for p in &planets {
        match p {
            GeneratedPlanet::Terrestrial(_)
            | GeneratedPlanet::SuperEarth(_)
            | GeneratedPlanet::LavaWorld(_)
            | GeneratedPlanet::OceanWorld(_) => has_terrestrial = true,
            GeneratedPlanet::GasGiant(_) => has_giant = true,
            GeneratedPlanet::IceGiant(_) | GeneratedPlanet::SubNeptune(_) => has_ice = true,
            GeneratedPlanet::Rogue(_) => {}
        }
    }
    assert!(has_terrestrial);
    assert!(has_giant || has_ice);
}

#[test]
fn generator_rogue_planets() {
    let rogues = generate_rogue_planets(5, 42);
    assert!(rogues.len() == 5);
    for p in &rogues {
        match p {
            GeneratedPlanet::Rogue(r) => {
                assert!(r.mass > 0.0);
                assert!(r.velocity > 0.0);
            }
            _ => panic!("expected rogue planet"),
        }
    }
}

#[test]
fn transit_depth_jupiter_like() {
    let d = transit_depth(JUPITER_RADIUS, SOLAR_RADIUS);
    assert!(d > 0.01 && d < 0.012);
}

#[test]
fn transit_probability_earth_like() {
    let p = transit_probability(SOLAR_RADIUS, AU);
    assert!(p > 0.004 && p < 0.005);
}

#[test]
fn rv_semi_amplitude_jupiter_like() {
    let k = rv_semi_amplitude(
        JUPITER_MASS,
        SOLAR_MASS,
        5.2 * AU,
        0.048,
        std::f64::consts::FRAC_PI_2,
    );
    assert!(k > 10.0 && k < 15.0);
}

#[test]
fn rv_minimum_mass_positive() {
    let m = minimum_mass(0.09, SOLAR_MASS, YEAR, 0.017);
    assert!(m > 0.0);
}

#[test]
fn photometry_flux_ratio_small() {
    let r = planet_flux_ratio(JUPITER_RADIUS, 5.2 * AU, 0.34);
    assert!(r > 0.0 && r < 1e-6);
}