cometsfactory 0.0.2

Comet factory — classify, build and catalogue comets of any type: short-period, long-period, Halley-type, sungrazer, interstellar, main-belt comet, centaur-transition, and extinct.
Documentation
use cometsfactory::types::extinct::DormancyState;
use cometsfactory::types::sungrazer::SungrazerGroup;
use cometsfactory::types::*;

// ── ShortPeriod ──────────────────────────────────────────────

#[test]
fn short_period_builder() {
    let c = ShortPeriod::new(500.0, 3.5, 0.6)
        .with_density(600.0)
        .with_albedo(0.05)
        .with_inclination(12.0)
        .with_dust_to_gas_ratio(2.0)
        .with_active_fraction(0.03);
    assert_eq!(c.nucleus_radius, 500.0);
    assert!((c.semi_major_axis - 3.5).abs() < 1e-6);
    assert!((c.eccentricity - 0.6).abs() < 1e-6);
    assert!((c.density - 600.0).abs() < 1e-6);
    assert!((c.inclination - 12.0).abs() < 1e-6);
}

#[test]
fn short_period_mass_positive() {
    let c = ShortPeriod::new(1000.0, 4.0, 0.5);
    assert!(c.mass() > 0.0);
}

#[test]
fn short_period_perihelion_less_than_a() {
    let c = ShortPeriod::new(500.0, 4.0, 0.7);
    assert!(c.perihelion() < c.semi_major_axis);
}

#[test]
fn short_period_aphelion_greater_than_a() {
    let c = ShortPeriod::new(500.0, 4.0, 0.7);
    assert!(c.aphelion() > c.semi_major_axis);
}

#[test]
fn short_period_orbital_period_positive() {
    let c = ShortPeriod::new(500.0, 4.0, 0.5);
    assert!(c.orbital_period_years() > 0.0);
}

#[test]
fn short_period_tisserand_range() {
    let c = ShortPeriod::new(500.0, 3.5, 0.6).with_inclination(10.0);
    let tj = c.tisserand_jupiter();
    assert!(tj > 1.0 && tj < 4.0);
}

#[test]
fn short_period_jupiter_family() {
    let c = ShortPeriod::new(500.0, 3.5, 0.6).with_inclination(10.0);
    assert!(c.is_jupiter_family());
}

// ── LongPeriod ───────────────────────────────────────────────

#[test]
fn long_period_builder() {
    let c = LongPeriod::new(2000.0, 20000.0, 0.999)
        .with_density(400.0)
        .with_inclination(120.0)
        .with_dynamically_new(true);
    assert!(c.nucleus_radius >= 10.0);
    assert!(c.semi_major_axis >= 50.0);
    assert!(c.is_dynamically_new);
}

#[test]
fn long_period_oort_cloud() {
    let c = LongPeriod::new(5000.0, 50000.0, 0.9999);
    assert!(c.is_oort_cloud_origin());
}

#[test]
fn long_period_not_oort() {
    let c = LongPeriod::new(5000.0, 500.0, 0.99);
    assert!(!c.is_oort_cloud_origin());
}

#[test]
fn long_period_mass_positive() {
    let c = LongPeriod::new(3000.0, 10000.0, 0.999);
    assert!(c.mass() > 0.0);
}

#[test]
fn long_period_period_large() {
    let c = LongPeriod::new(3000.0, 10000.0, 0.999);
    assert!(c.orbital_period_years() > 200.0);
}

// ── HalleyType ───────────────────────────────────────────────

#[test]
fn halley_type_builder() {
    let c = HalleyType::new(4000.0, 18.0, 0.97).with_inclination(162.0);
    assert!(c.is_retrograde);
}

#[test]
fn halley_type_tisserand_lt2() {
    let c = HalleyType::new(4000.0, 18.0, 0.97).with_inclination(162.0);
    assert!(c.tisserand_jupiter() < 2.0);
}

#[test]
fn halley_type_period_range() {
    let c = HalleyType::new(4000.0, 18.0, 0.97);
    let p = c.orbital_period_years();
    assert!(p > 20.0 && p < 200.0);
}

#[test]
fn halley_type_mass() {
    let c = HalleyType::new(4000.0, 18.0, 0.97);
    assert!(c.mass() > 0.0);
}

// ── Sungrazer ────────────────────────────────────────────────

#[test]
fn sungrazer_builder() {
    let c = Sungrazer::new(50.0, 100.0, 0.9999).with_group(SungrazerGroup::Marsden);
    assert_eq!(c.group, SungrazerGroup::Marsden);
}

#[test]
fn sungrazer_tidal_disrupt_small_perihelion() {
    let c = Sungrazer::new(50.0, 100.0, 0.99999);
    assert!(c.will_tidally_disrupt());
}

#[test]
fn sungrazer_perihelion_temperature_high() {
    let c = Sungrazer::new(50.0, 100.0, 0.99999);
    assert!(c.perihelion_temperature() > 1000.0);
}

#[test]
fn sungrazer_mass() {
    let c = Sungrazer::new(50.0, 100.0, 0.9999);
    assert!(c.mass() > 0.0);
}

// ── Interstellar ─────────────────────────────────────────────

#[test]
fn interstellar_builder() {
    let c = Interstellar::new(100.0, 26.0, 2.0)
        .with_density(400.0)
        .with_inclination(45.0);
    assert!(c.is_unbound());
    assert!((c.excess_velocity - 26.0).abs() < 1e-6);
}

#[test]
fn interstellar_perihelion_velocity_gt_vinf() {
    let c = Interstellar::new(100.0, 26.0, 2.0);
    assert!(c.perihelion_velocity() > c.velocity_at_infinity_kms() * 1000.0);
}

#[test]
fn interstellar_temperature() {
    let c = Interstellar::new(100.0, 26.0, 2.0);
    assert!(c.perihelion_temperature() > 0.0);
}

// ── MainBeltComet ────────────────────────────────────────────

#[test]
fn mbc_builder() {
    let c = MainBeltComet::new(500.0, 3.0, 0.15)
        .with_ice_fraction(0.03)
        .with_recurrence(2);
    assert_eq!(c.recurrence_interval_orbits, 2);
    assert!((c.ice_fraction - 0.03).abs() < 1e-6);
}

#[test]
fn mbc_asteroidal_orbit() {
    let c = MainBeltComet::new(500.0, 3.0, 0.15).with_inclination(5.0);
    assert!(c.has_asteroidal_orbit());
}

#[test]
fn mbc_ice_mass_positive() {
    let c = MainBeltComet::new(500.0, 3.0, 0.15).with_ice_fraction(0.05);
    assert!(c.ice_mass() > 0.0);
}

#[test]
fn mbc_period_reasonable() {
    let c = MainBeltComet::new(500.0, 3.0, 0.15);
    let p = c.orbital_period_years();
    assert!(p > 3.0 && p < 10.0);
}

// ── CentaurTransition ────────────────────────────────────────

#[test]
fn centaur_transition_builder() {
    let c = CentaurTransition::new(5000.0, 12.0, 0.3)
        .with_co_driven(true)
        .with_outburst_magnitude(6.0);
    assert!(c.co_driven);
    assert!((c.outburst_magnitude - 6.0).abs() < 1e-6);
}

#[test]
fn centaur_transition_co_active() {
    let c = CentaurTransition::new(5000.0, 12.0, 0.3);
    assert!(c.is_co_active_distance());
}

#[test]
fn centaur_dynamical_lifetime() {
    let c = CentaurTransition::new(5000.0, 8.0, 0.3);
    assert!(c.dynamical_lifetime_myr() <= 10.0);
    assert!(c.dynamical_lifetime_myr() >= 1.0);
}

#[test]
fn centaur_water_ice_line() {
    let c = CentaurTransition::new(5000.0, 6.0, 0.7);
    assert!(c.crosses_water_ice_line());
}

// ── Extinct ──────────────────────────────────────────────────

#[test]
fn extinct_builder() {
    let c = Extinct::new(500.0, 3.0, 0.5)
        .with_mantle_thickness(5.0)
        .with_residual_ice(0.0);
    assert!((c.mantle_thickness - 5.0).abs() < 1e-6);
    assert_eq!(c.state, DormancyState::Extinct);
}

#[test]
fn extinct_becomes_dormant() {
    let c = Extinct::new(500.0, 3.0, 0.5).with_residual_ice(0.05);
    assert_eq!(c.state, DormancyState::Dormant);
    assert!(c.can_reactivate());
}

#[test]
fn extinct_neo_like() {
    let c = Extinct::new(500.0, 2.0, 0.6);
    assert!(c.is_neo_like());
}

#[test]
fn extinct_not_neo() {
    let c = Extinct::new(500.0, 5.0, 0.2);
    assert!(!c.is_neo_like());
}

#[test]
fn extinct_tisserand() {
    let c = Extinct::new(500.0, 3.5, 0.6).with_inclination(10.0);
    let tj = c.tisserand_jupiter();
    assert!(tj > 0.0);
}

// ── GeneratedComet enum ──────────────────────────────────────

#[test]
fn generated_comet_nucleus_radius() {
    let comets: Vec<GeneratedComet> = vec![
        GeneratedComet::ShortPeriod(ShortPeriod::new(500.0, 3.5, 0.6)),
        GeneratedComet::LongPeriod(LongPeriod::new(2000.0, 20000.0, 0.999)),
        GeneratedComet::HalleyType(HalleyType::new(4000.0, 18.0, 0.97)),
        GeneratedComet::Sungrazer(Sungrazer::new(50.0, 100.0, 0.9999)),
        GeneratedComet::Interstellar(Interstellar::new(100.0, 26.0, 2.0)),
        GeneratedComet::MainBeltComet(MainBeltComet::new(500.0, 3.0, 0.15)),
        GeneratedComet::CentaurTransition(CentaurTransition::new(5000.0, 12.0, 0.3)),
        GeneratedComet::Extinct(Extinct::new(500.0, 3.0, 0.5)),
    ];
    let all_positive = comets.iter().all(|c| c.nucleus_radius() > 0.0);
    assert!(all_positive);
    assert_eq!(comets.len(), 8);
}

#[test]
fn generated_comet_eccentricity() {
    let comets: Vec<GeneratedComet> = vec![
        GeneratedComet::ShortPeriod(ShortPeriod::new(500.0, 3.5, 0.6)),
        GeneratedComet::LongPeriod(LongPeriod::new(2000.0, 20000.0, 0.999)),
        GeneratedComet::HalleyType(HalleyType::new(4000.0, 18.0, 0.97)),
        GeneratedComet::Sungrazer(Sungrazer::new(50.0, 100.0, 0.9999)),
        GeneratedComet::Interstellar(Interstellar::new(100.0, 26.0, 2.0)),
        GeneratedComet::MainBeltComet(MainBeltComet::new(500.0, 3.0, 0.15)),
        GeneratedComet::CentaurTransition(CentaurTransition::new(5000.0, 12.0, 0.3)),
        GeneratedComet::Extinct(Extinct::new(500.0, 3.0, 0.5)),
    ];
    let bound_count = comets.iter().filter(|c| c.eccentricity() < 1.0).count();
    assert!(bound_count >= 7);
}