Skip to main content

native/sidereon_nif/src/consts.rs

//! Rustler boundary that mirrors the canonical `sidereon-core` physical
//! constants and solver defaults.
//!
//! Pure glue: every value below is read straight from a `sidereon_core` public
//! constant, never redefined here. The Elixir side keeps literal copies for
//! compile-time ergonomics (module attributes, defaults), and a drift test
//! asserts each literal is bit-equal to the value these NIFs return, so a future
//! edit cannot silently diverge a binding constant or default from the core.

use rustler::{Encoder, Env, Term};
use sidereon_core::astro::constants::earth::{GM_EARTH_KM3_S2, J2_EARTH};
use sidereon_core::astro::math::robust::HUBER_K;
use sidereon_core::constants::{C_M_S, AU_KM, OMEGA_E_DOT_RAD_S, WGS84_A_KM, WGS84_E2, WGS84_F};
use sidereon_core::positioning::{
    SurfaceMet, DEFAULT_ROBUST_MAX_OUTER, DEFAULT_ROBUST_OUTER_TOL_M, DEFAULT_ROBUST_SCALE_FLOOR_M,
};
use sidereon_core::precise_positioning::defaults as ppp_defaults;
use sidereon_core::rtk_filter::defaults as rtk_defaults;
use sidereon_core::rtk_filter::defaults::{CODE_SIGMA_M, MAX_ITERATIONS, PHASE_SIGMA_M};

fn put<'a>(env: Env<'a>, map: Term<'a>, key: &str, value: impl Encoder) -> Term<'a> {
    let atom = rustler::types::atom::Atom::from_str(env, key).expect("atom key");
    map.map_put(atom.to_term(env), value).expect("map_put")
}

/// The physical constants the Elixir `Sidereon.Constants` module mirrors, read
/// from their canonical `sidereon_core` homes.
#[rustler::nif]
fn core_constants(env: Env<'_>) -> Term<'_> {
    let m = Term::map_new(env);
    let m = put(env, m, "speed_of_light_m_s", C_M_S);
    let m = put(env, m, "gm_earth_km3_s2", GM_EARTH_KM3_S2);
    let m = put(env, m, "wgs84_a_km", WGS84_A_KM);
    let m = put(env, m, "wgs84_f", WGS84_F);
    let m = put(env, m, "wgs84_e2", WGS84_E2);
    let m = put(env, m, "j2_earth", J2_EARTH);
    let m = put(env, m, "omega_e_dot_rad_s", OMEGA_E_DOT_RAD_S);
    put(env, m, "au_km", AU_KM)
}

/// The solver defaults the Elixir binding mirrors, read from their canonical
/// `sidereon_core` homes. Covers the RTK measurement/iteration/convergence
/// defaults (`rtk_filter::defaults`), the static-PPP convergence defaults
/// (`precise_positioning::defaults`), the robust SPP IRLS defaults
/// (`positioning::DEFAULT_ROBUST_*`), the standard-atmosphere surface
/// meteorology defaults (`positioning::SurfaceMet::default()`), and the publicly
/// exported Huber constant (`astro::math::robust::HUBER_K`).
#[rustler::nif]
fn core_defaults(env: Env<'_>) -> Term<'_> {
    let m = Term::map_new(env);

    // RTK measurement / iteration / convergence defaults.
    let m = put(env, m, "rtk_code_sigma_m", CODE_SIGMA_M);
    let m = put(env, m, "rtk_phase_sigma_m", PHASE_SIGMA_M);
    let m = put(env, m, "rtk_max_iterations", MAX_ITERATIONS as i64);
    let m = put(env, m, "rtk_position_tol_m", rtk_defaults::POSITION_TOL_M);
    let m = put(env, m, "rtk_ambiguity_tol_m", rtk_defaults::AMBIGUITY_TOL_M);
    let m = put(env, m, "rtk_ratio_threshold", rtk_defaults::RATIO_THRESHOLD);
    let m = put(
        env,
        m,
        "rtk_partial_min_ambiguities",
        rtk_defaults::PARTIAL_MIN_AMBIGUITIES as i64,
    );

    // Static-PPP convergence / iteration / integer defaults.
    let m = put(env, m, "ppp_position_tol_m", ppp_defaults::POSITION_TOLERANCE_M);
    let m = put(env, m, "ppp_clock_tol_m", ppp_defaults::CLOCK_TOLERANCE_M);
    let m = put(
        env,
        m,
        "ppp_ambiguity_tol_m",
        ppp_defaults::AMBIGUITY_TOLERANCE_M,
    );
    let m = put(env, m, "ppp_ztd_tol_m", ppp_defaults::ZTD_TOLERANCE_M);
    let m = put(env, m, "ppp_max_iterations", ppp_defaults::MAX_ITERATIONS as i64);
    let m = put(env, m, "ppp_ratio_threshold", ppp_defaults::RATIO_THRESHOLD);

    // Robust SPP IRLS defaults.
    let m = put(env, m, "robust_scale_floor_m", DEFAULT_ROBUST_SCALE_FLOOR_M);
    let m = put(env, m, "robust_max_outer", DEFAULT_ROBUST_MAX_OUTER as i64);
    let m = put(env, m, "robust_outer_tol_m", DEFAULT_ROBUST_OUTER_TOL_M);

    // Standard-atmosphere surface meteorology defaults, read from
    // `SurfaceMet::default()` so the troposphere term's fallback pressure /
    // temperature / humidity in the binding cannot diverge from the core.
    let met = SurfaceMet::default();
    let m = put(env, m, "surface_met_pressure_hpa", met.pressure_hpa);
    let m = put(env, m, "surface_met_temperature_k", met.temperature_k);
    let m = put(
        env,
        m,
        "surface_met_relative_humidity",
        met.relative_humidity,
    );

    put(env, m, "huber_k", HUBER_K)
}