Example 9: Ephemeris and LansAlmanac Fitting

Study the accuracy-vs-broadcast-datasize trade-off of two lunar-satellite orbit models fit to a numerically propagated truth trajectory: a precise, short-validity LansEphemeris (Chebyshev) and a coarse, long-validity LansAlmanac (Algorithm 2 of Iiyama & Gao). The scenario is built from configs/ephemeris.yaml (a thin EphemerisManager agent hosting an EphemerisApp) and run with Simulation; the app fits both models over a sweep of fitting-window lengths, reporting RMS / 95th-percentile RTN position and velocity error and the minimum broadcast bit budget for a target position accuracy.

Mirrors Example 9: Ephemeris and LansAlmanac Fitting.

// Example 9: Ephemeris and LansAlmanac Fitting
// ------------------------------------------------------------------------------
// C++ counterpart of python/examples/ex9_ephemeris.ipynb.
//
// Studies the accuracy-vs-broadcast-datasize trade-off of two lunar-satellite
// orbit models fit to a numerically propagated truth trajectory:
//
//   * LansEphemeris -- a precise, short-validity Chebyshev model (the lunar
//     analogue of a GNSS broadcast ephemeris record).
//   * LansAlmanac            -- a coarse, long-validity model (Algorithm 2 of
//     Iiyama & Gao) for whole-constellation acquisition.
//
// The scenario is built from a single YAML file: a thin `EphemerisManager`
// coordinator agent hosts an `EphemerisApp`, and the shared environment (frame +
// a truth force model) lives in the top-level `world:` block. `Simulation` runs
// the event loop; the app's single scheduled step propagates the truth trajectory
// and, for each fitting-window length, fits both models over several windows,
// evaluating RMS / 95th-percentile RTN position and velocity error and the minimum
// broadcast bit budget needed to stay within a position tolerance. The per-window
// results are read off the app's `GetResults()`.

#include <iomanip>
#include <iostream>

#include "lupnt/lupnt.h"

using namespace lupnt;

static void PrintTable(const std::string& title, const std::vector<EphemerisWindowResult>& rows) {
  std::cout << "\n" << title << "\n";
  std::cout << "  window[min]  params  bits   pos_rms[m]  pos_p95[m]  vel_rms[mm/s]\n";
  std::cout << std::fixed;
  for (const EphemerisWindowResult& r : rows) {
    std::cout << "  " << std::setw(9) << std::setprecision(1) << r.fit_window_min << "  "
              << std::setw(6) << r.num_params << "  " << std::setw(5) << r.total_bits << "  "
              << std::setw(9) << std::setprecision(4) << r.pos_rms_m << "  " << std::setw(9)
              << r.pos_p95_m << "  " << std::setw(11) << std::setprecision(4) << r.vel_rms_mps * 1e3
              << "\n";
  }
}

int main(int argc, char** argv) {
  const std::string config_path = argc > 1 ? argv[1] : "configs/ephemeris.yaml";

  Config cfg = YAML::LoadFile(config_path);
  Simulation sim(cfg);
  sim.Run();
  auto* app = dynamic_cast<EphemerisApp*>(sim.GetAgent("EphemerisManager")->GetApplication().get());
  LUPNT_CHECK(app, "EphemerisManager agent has no EphemerisApp", "ex9");
  const EphemerisResults& res = app->GetResults();

  std::cout << "Ephemeris/LansAlmanac fit study over " << app->GetConfig().duration_days
            << " days, "
            << "output frame " << enum_name(app->GetConfig().output_frame) << "\n";
  PrintTable("LansEphemeris (precise, short-validity):", res.cartesian_results);
  PrintTable("LansAlmanac (coarse, long-validity):", res.almanac_results);

  return 0;
}
pixi run build-examples
./build-examples-pixi/ex9_ephemeris