cometsfactory 0.0.3

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 crate::config::parameters::{AU, G, PI, SOLAR_MASS, YEAR};

/// Mean motion (rad/s) for a bound orbit with semi-major axis a (AU).
pub fn mean_motion(semi_major_au: f64) -> f64 {
    let a = semi_major_au * AU;
    if a < 1.0 {
        return 0.0;
    }
    (G * SOLAR_MASS / a.powi(3)).sqrt()
}

/// Orbital period (s) from Kepler's third law.
pub fn orbital_period(semi_major_au: f64) -> f64 {
    let n = mean_motion(semi_major_au);
    if n < 1.0e-30 {
        return f64::INFINITY;
    }
    2.0 * PI / n
}

/// Tisserand parameter w.r.t. a perturber at a_p AU.
pub fn tisserand(semi_major_au: f64, eccentricity: f64, inclination_deg: f64, a_p: f64) -> f64 {
    let ratio = a_p / semi_major_au;
    let inc_rad = inclination_deg.to_radians();
    ratio
        + 2.0 * ((1.0 - eccentricity * eccentricity) * (semi_major_au / a_p)).sqrt() * inc_rad.cos()
}

/// True anomaly from mean anomaly via Newton–Raphson for the Kepler equation.
/// Returns eccentric anomaly (rad).
pub fn eccentric_anomaly(mean_anomaly: f64, eccentricity: f64) -> f64 {
    let mut ea = mean_anomaly;
    for _i in 0..50 {
        let delta = (ea - eccentricity * ea.sin() - mean_anomaly) / (1.0 - eccentricity * ea.cos());
        ea -= delta;
        if delta.abs() < 1.0e-12 {
            break;
        }
    }
    ea
}

/// Heliocentric distance (AU) at a given true anomaly (rad).
pub fn heliocentric_distance(semi_major_au: f64, eccentricity: f64, true_anomaly: f64) -> f64 {
    semi_major_au * (1.0 - eccentricity * eccentricity) / (1.0 + eccentricity * true_anomaly.cos())
}

/// Velocity at a given heliocentric distance r (AU) via vis-viva (m/s).
pub fn velocity_at_distance(semi_major_au: f64, r_au: f64) -> f64 {
    let a = semi_major_au * AU;
    let r = r_au * AU;
    if r < 1.0 || a < 1.0 {
        return 0.0;
    }
    (G * SOLAR_MASS * (2.0 / r - 1.0 / a)).sqrt()
}

/// Synodic period (s) between two objects at semi-major axes a1, a2 (AU).
pub fn synodic_period(a1_au: f64, a2_au: f64) -> f64 {
    let n1 = mean_motion(a1_au);
    let n2 = mean_motion(a2_au);
    let diff = (n1 - n2).abs();
    if diff < 1.0e-30 {
        return f64::INFINITY;
    }
    2.0 * PI / diff
}

/// Time from perihelion to a given true anomaly (s) for an elliptical orbit.
pub fn time_from_perihelion(semi_major_au: f64, eccentricity: f64, true_anomaly: f64) -> f64 {
    let ea = 2.0
        * ((1.0 - eccentricity).sqrt() * (true_anomaly / 2.0).sin())
            .atan2((1.0 + eccentricity).sqrt() * (true_anomaly / 2.0).cos());
    let ma = ea - eccentricity * ea.sin();
    let n = mean_motion(semi_major_au);
    if n < 1.0e-30 {
        return f64::INFINITY;
    }
    ma / n
}

/// Number of perihelion passages in a given duration (years).
pub fn perihelion_passages(semi_major_au: f64, duration_years: f64) -> f64 {
    let period_s = orbital_period(semi_major_au);
    let duration_s = duration_years * YEAR;
    duration_s / period_s
}