affn 0.6.0

Affine geometry primitives: strongly-typed coordinate systems, reference frames, and centers for scientific computing.
Documentation
use affn::conic::{
    ClassifiedPeriapsisParam, ConicOrientation, ConicShape, ConicValidationError,
    EllipticPeriapsis, EllipticSemiMajorAxis, HyperbolicPeriapsis, HyperbolicSemiMajorAxis,
    OrientedConic, ParabolicPeriapsis, PeriapsisParam, SemiMajorAxisParam,
};
use affn::frames::ReferenceFrame;
use qtty::*;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Inertial;

impl ReferenceFrame for Inertial {
    fn frame_name() -> &'static str {
        "Inertial"
    }
}

fn main() {
    erased_runtime_shapes();
    classify_all_kinds();
    oriented_conics_and_aliases();
}

fn erased_runtime_shapes() {
    section("Erased Runtime Shapes");

    let periapsis = PeriapsisParam::try_new(7_000.0 * M, 0.42).expect("valid periapsis parameter");
    println!(
        "PeriapsisParam: q = {}, e = {}, kind = {:?}",
        periapsis.periapsis_distance(),
        periapsis.eccentricity(),
        periapsis.kind()
    );

    let semi_major_axis = periapsis
        .to_semi_major_axis()
        .expect("elliptic periapsis converts to semi-major axis");
    println!(
        "Converted to SemiMajorAxisParam: a = {}, kind = {:?}",
        semi_major_axis.semi_major_axis(),
        semi_major_axis.kind()
    );

    let hyperbolic = SemiMajorAxisParam::try_new(2.0 * M, 1.5).expect("valid hyperbolic shape");
    println!(
        "SemiMajorAxisParam: a = {}, e = {}, kind = {:?}",
        hyperbolic.semi_major_axis(),
        hyperbolic.eccentricity(),
        hyperbolic.kind()
    );

    match SemiMajorAxisParam::try_new(2.0 * M, 1.0) {
        Err(ConicValidationError::ParabolicSemiMajorAxis) => {
            println!("SemiMajorAxisParam rejects parabolic e == 1 at construction");
        }
        other => panic!("unexpected semi-major-axis classification result: {other:?}"),
    }
}

fn classify_all_kinds() {
    section("Classification To Typed Kinds");

    for eccentricity in [0.2, 1.0, 1.4] {
        let shape =
            PeriapsisParam::try_new(7_000.0 * M, eccentricity).expect("valid periapsis shape");

        match shape.classify() {
            ClassifiedPeriapsisParam::Elliptic(typed) => {
                let sma = typed
                    .to_semi_major_axis()
                    .expect("elliptic periapsis converts to semi-major axis");
                println!(
                    "Elliptic branch: q = {}, e = {}, a = {}",
                    typed.periapsis_distance(),
                    typed.eccentricity(),
                    sma.semi_major_axis()
                );
            }
            ClassifiedPeriapsisParam::Parabolic(typed) => {
                println!(
                    "Parabolic branch: q = {}, e = {} (no semi-major axis form)",
                    typed.periapsis_distance(),
                    typed.eccentricity()
                );
            }
            ClassifiedPeriapsisParam::Hyperbolic(typed) => {
                let sma = typed
                    .to_semi_major_axis()
                    .expect("hyperbolic periapsis converts to semi-major axis");
                println!(
                    "Hyperbolic branch: q = {}, e = {}, a = {}",
                    typed.periapsis_distance(),
                    typed.eccentricity(),
                    sma.semi_major_axis()
                );
            }
        }
    }
}

fn oriented_conics_and_aliases() {
    section("Oriented Conics And Aliases");

    let orientation = ConicOrientation::<Inertial>::try_new(28.5 * DEG, 40.0 * DEG, 15.0 * DEG)
        .expect("valid conic orientation");

    let elliptic_shape = PeriapsisParam::try_new(7_000.0 * M, 0.42).expect("valid elliptic shape");
    let ClassifiedPeriapsisParam::Elliptic(elliptic_typed) = elliptic_shape.classify() else {
        unreachable!("expected elliptic classification")
    };
    let elliptic: EllipticPeriapsis<_, Inertial> = OrientedConic::new(elliptic_typed, orientation);
    let elliptic_sma: EllipticSemiMajorAxis<_, Inertial> = elliptic
        .to_semi_major_axis()
        .expect("elliptic oriented conic converts to semi-major axis");

    println!(
        "EllipticPeriapsis: kind = {:?}, q = {}, i = {}",
        elliptic.kind(),
        elliptic.shape().periapsis_distance(),
        elliptic.orientation().inclination()
    );
    println!(
        "EllipticSemiMajorAxis: a = {}, same orientation = {}",
        elliptic_sma.shape().semi_major_axis(),
        elliptic_sma.orientation() == elliptic.orientation()
    );

    let parabolic_shape = PeriapsisParam::try_new(7_000.0 * M, 1.0).expect("valid parabolic shape");
    let ClassifiedPeriapsisParam::Parabolic(parabolic_typed) = parabolic_shape.classify() else {
        unreachable!("expected parabolic classification")
    };
    let parabolic: ParabolicPeriapsis<_, Inertial> =
        OrientedConic::new(parabolic_typed, orientation);

    println!(
        "ParabolicPeriapsis: kind = {:?}, q = {}, convertible to SMA = no",
        parabolic.kind(),
        parabolic.shape().periapsis_distance()
    );

    let hyperbolic_shape =
        PeriapsisParam::try_new(7_000.0 * M, 1.4).expect("valid hyperbolic shape");
    let ClassifiedPeriapsisParam::Hyperbolic(hyperbolic_typed) = hyperbolic_shape.classify() else {
        unreachable!("expected hyperbolic classification")
    };
    let hyperbolic: HyperbolicPeriapsis<_, Inertial> =
        OrientedConic::new(hyperbolic_typed, orientation);
    let hyperbolic_sma: HyperbolicSemiMajorAxis<_, Inertial> = hyperbolic
        .to_semi_major_axis()
        .expect("hyperbolic oriented conic converts to semi-major axis");

    println!(
        "HyperbolicPeriapsis: q = {}, node = {}",
        hyperbolic.shape().periapsis_distance(),
        hyperbolic.orientation().longitude_of_ascending_node()
    );
    println!(
        "HyperbolicSemiMajorAxis: a = {}, arg periapsis = {}",
        hyperbolic_sma.shape().semi_major_axis(),
        hyperbolic_sma.orientation().argument_of_periapsis()
    );
}

fn section(title: &str) {
    println!("\n== {title} ==");
}