README.md

July 14, 2026 · View on GitHub

LuPNT — Lunar Positioning, Navigation and Timing

MacOS Ubuntu Style Install Python Examples Documentation Status codecov License: MIT

LuPNT is an open-source C++/Python library for Lunar Positioning, Navigation, and Timing (PNT) research. It provides high-fidelity astrodynamics, signal propagation models, and navigation algorithms tailored for cislunar missions. While designed for lunar PNT analysis, its config-driven, agent-based framework is general-purpose: it is equally suited to spacecraft orbit-determination and navigation research for other mission scenarios — from Earth orbit (e.g., LEO and GNSS) to interplanetary (e.g., Mars) — as several of the examples demonstrate. This project is a product of the Stanford NAV Lab.

If using this project in your own work please cite the following:

@inproceedings{IiyamaCasadesus2023,
  title = {LuPNT: Open-Source Simulator for Lunar Positioning, Navigation, and Timing},
  author={Iiyama, Keidai and Casadesus Vila, Guillem and Gao, Grace},
  booktitle={Proceedings of the Institute of Navigation Gnss+ conference (ION Gnss+ 2023)},
  institution = {Stanford University},
  year = {2023},
  url = {https://github.com/Stanford-NavLab/LuPNT},
}

Features

LuPNT is organized as a config-driven, agent-based simulation framework. Agents (satellites, ground stations, rovers, landers, surface stations, constellations) host Applications — the mission and navigation-filter logic — and run together on a shared, event-scheduled Simulation. Agents, their devices, dynamics, and applications are all assembled from YAML configuration, so new scenarios are described in config rather than code.

ModuleDescription
Agents & ApplicationsConfig-driven agents (satellite, ground station, rover, lander, surface station, constellation) that host navigation/mission Applications — ODTS, ISL, surface-rover and lander navigation, ephemeris/almanac generation — built from YAML via an asset factory
SimulationsEvent-scheduled Simulation engine plus end-to-end scenario drivers (ground-station & inter-satellite ODTS, lunar GNSS ODTS, lander/surface navigation, ephemeris fitting)
Devices & sensorsClocks, IMUs, cameras, GNSS receivers, and communication devices attached to agents
DynamicsTwo-body, N-body, and high-fidelity force models (gravity, drag, SRP) for Earth, lunar, and arbitrary central-body orbits
EnvironmentGravity fields, atmosphere, solar system bodies, occultation, and plasma/ionosphere models
PlasmaGCPM v2.4 plasmasphere + IRI ionosphere electron density; GNSS ray-tracing with TEC and signal delay for cislunar links (via integrated pecsim)
GNSSGPS/GNSS signal generation, SP3/ANTEX-backed constellations, yaw-steering/attitude models, and space-user ranging
MeasurementsPseudorange, Doppler, and carrier-phase measurement models with light-time, Shapiro, and optional plasma corrections
Filters & StatesExtended Kalman Filter (EKF), Unscented Kalman Filter (UKF), square-root information filter (SRIF)/smoother, and batch least-squares estimators over composable state/joint-state abstractions
InterfacesData loaders and I/O: SP3, ANTEX, RINEX nav, TLE, SPICE kernels, EOP/TAI-UTC, LOLA DEM and crater data, plus Cesium and Matplotlib export
ConversionsReference frame transformations (ECI, ECEF, LVLH, Moon-centered, generic body-fixed/inertial) and time system utilities
NumericsNumerical integration (RK4, Euler), Nelder-Mead optimizer, and matrix utilities
VisualizationMatplotlib/Plotly plotting plus interactive 3-D CesiumJS scenes of constellations and surface assets (pnt.plot.CesiumScene)
Python bindings & authoringFull pylupnt Python package exposing the C++ library via pybind11 — including subclassing Application, Agent, and Measurement in pure Python and registering them into a YAML-driven Simulation (see Example 17)

Repository Structure

LuPNT/
├── cpp/
│   ├── lupnt/                  # C++ library source
│   │   ├── agents/             # Agents: satellite, ground station, rover, lander, surface station, constellations
│   │   ├── applications/       # Per-agent mission/nav logic (ODTS, ISL, lander/rover nav, ephemeris/almanac gen)
│   │   ├── simulations/        # Event-scheduled Simulation engine + scenario drivers
│   │   ├── devices/            # Agent devices: clock, IMU, camera, GNSS receiver, comms
│   │   ├── dynamics/           # Orbital dynamics and propagators
│   │   ├── environment/        # Gravity, atmosphere, solar system, plasma
│   │   │   └── plasma/         # GCPM v2.4 + IRI + ray-tracer (pecsim)
│   │   ├── measurements/       # Measurement models
│   │   ├── attitude/           # GNSS transmitter attitude / yaw-steering laws
│   │   ├── states/             # State / joint-state / parameter abstractions
│   │   ├── transmission/       # Signal transmission modeling
│   │   ├── interfaces/         # Data loaders & I/O (SP3, ANTEX, RINEX, TLE, SPICE, EOP, DEM, Cesium, matplotlib)
│   │   ├── conversions/        # Frame and unit conversions
│   │   ├── core/               # Logging, constants, config, math utils
│   │   └── numerics/           # Numerical methods
│   └── examples/               # C++ example programs
│       └── tutorials/          # C++ counterparts to the Python notebooks (ex1–ex16)
├── configs/                    # YAML scenario configuration (agents, applications, dynamics, environments, datasets)
├── python/
│   ├── pylupnt/                # Python package (installed in-place)
│   │   ├── plasma/             # pylupnt.plasma sub-module
│   │   ├── plot/               # Plotting utilities
│   │   ├── interfaces/         # Python-side data interfaces
│   │   └── _pylupnt.so         # Compiled pybind11 extension
│   ├── examples/               # Tutorial notebooks (ex1–ex17) — see python/examples/README.md
│   └── bindings/               # pybind11 binding source files
├── projects/                   # Research project notebooks and scripts
│   ├── Plasma_Examples/        # GCPM, IRI, and ray-tracing Jupyter notebooks
│   └── …
├── data/
│   └── LuPNT_data/             # Runtime data (ephemeris, TLE, plasma coefficients)
│       ├── ephemeris/
│       ├── plasma/             # IRI coefficient files, Kp index data
│       └── tle/
└── pixi.toml                   # Reproducible environment and task definitions

Tutorials & Examples

A progressive set of Jupyter notebooks in python/examples/ teaches the pylupnt API and reproduces LuPNT's core navigation workflows — from single-orbit propagation up to full orbit-determination filters, constellation design, optical navigation, and non-lunar (Mars / LEO) PNT. See the examples README for the full index and per-notebook details.

#ExampleTheme
1Propagating an ELFO orbitFundamentals — elements, frames, force models
2Relativistic time conversionsFundamentals — TT/TCG/TCB/TDB/TCL/LT
3GNSS interface tutorialEarth-GNSS data — TLEs, antenna gain, SP3 vs. broadcast
4Plasmasphere & ionosphereGCPM v2.4 electron density, TEC, ray-traced signal delay
5GNSS measurement simulationLunar-receiver visibility and C/N₀ from precise ephemerides
6Sidelobe pseudorange/Doppler/TDCP ODTSWeak-signal EKF for position, velocity, clock, and SRP
7Ground-station orbit determinationDSN batch least squares + SRIF/smoother
8Distributed ISL ODTS5 parallel consider-state EKFs + surface-station timing
9Ephemeris & almanac fittingCompressing trajectories into broadcast navigation models
10Surface rover navigationIMU + LCRNS pseudoranges + DEM error-state EKF
11Lunar lander navigationPowered-descent MEKF: IMU, altimeter, craters, LunaNet
12Constellation designELFO Walker layout, PDOP/coverage/EIRP, phasing optimization
13Cesium visualizationInteractive 3-D CesiumJS scenes of constellations & stations
14Optical navigationLunar-horizon image processing → EKF position fixes
15Mars PNTBeyond the Moon — Mars gravity, frames, Walker constellation
16LEO PNTEarth LEO constellation with Harris-Priester atmospheric drag
17New simulation in PythonAuthoring a new Application + Measurement in pure Python (angles-only OD)

C++ tutorial counterparts live in cpp/examples/tutorials/.


Development

Installation is from source only — LuPNT is no longer distributed on PyPI. The legacy pylupnt wheels (last published for 0.0.4) predate the current C++/Fortran dependency stack (OpenCV, Matplot++, Fortran plasma models, HDF5) and are not maintained. Build from source with pixi as described below. See docs/roadmap.md for the packaging status and what restoring a PyPI/conda-forge distribution would take.

Prerequisites

  1. Install pixi (manages the compiler toolchain, Python, and all C++ dependencies via conda-forge — no manual dependency installation needed):
curl -fsSL https://pixi.sh/install.sh | bash
  1. NASA Earthdata Login is required. Several C++ tests (pixi run test-cpp) and GNSS examples depend on live GNSS/EOP products (SP3 precise orbits, RINEX nav, ANTEX) fetched from CDDIS, which requires authentication. Set this up before running pixi run test-cpp or any of the GNSS download tasks:

    1. Create an Earthdata Login account at urs.earthdata.nasa.gov: click "Register for a profile", fill in the required fields, and verify your email address.

    2. Create a ~/.netrc file with your credentials (run inside WSL on Windows, not PowerShell/cmd — see Setting up on Windows (via WSL) below):

    touch ~/.netrc
    chmod 0600 ~/.netrc
    echo "machine urs.earthdata.nasa.gov login <your_username> password <your_password>" >> ~/.netrc
    

    Replace <your_username> and <your_password> with your actual credentials. The chmod 0600 step is required — curl/wget refuse to use a .netrc with broader permissions. Verify with cat ~/.netrc. Never share or commit this file (it stays outside the repo, at ~/.netrc, so there's no risk of it being checked into git).

Setup

  1. Install the environment (downloads all dependencies into an isolated conda environment)
pixi install
  1. Build the C++ library
pixi run build
  1. Build and deploy the Python bindings
pixi run build-py

Note: data/LuPNT_data (ephemeris, GNSS antenna/clock products, plasma coefficients, TLEs) is downloaded and extracted automatically the first time you run step 2 or 3 above — no manual setup needed. It's a ~650MB one-time download (see cmake/FetchLuPNTData.cmake); subsequent builds skip it since the data is already present. To disable this (e.g. air-gapped builds), pass -DLUPNT_FETCH_DATA=OFF to the underlying cmake configure call, or place the data manually at data/LuPNT_data beforehand.

Setting up on Windows (via WSL)

LuPNT does not build natively on Windows; Windows users should develop inside WSL (Windows Subsystem for Linux), which runs a real Linux environment and follows the same setup as native Linux/macOS above.

  1. Install WSL (PowerShell, run as Administrator). This installs the default Ubuntu distribution:
wsl --install

Reboot if prompted, then launch "Ubuntu" from the Start menu and create a Unix username/password when asked.

  1. Update packages inside the WSL Ubuntu shell
sudo apt update && sudo apt upgrade -y
  1. Clone the repository inside the WSL filesystem (not under /mnt/c/..., which is much slower and can break symlinks/permissions):
cd ~
git clone https://github.com/Stanford-NavLab/LuPNT.git
cd LuPNT
  1. Continue with the regular Prerequisites and Setup steps above (curl -fsSL https://pixi.sh/install.sh | bash, then pixi install, pixi run build, pixi run build-py) from inside the WSL shell.

  2. Editing files: you can edit the cloned repo from Windows tools (e.g. VS Code with the "WSL" extension, or code . from inside the WSL shell) while all builds/tests run inside WSL.

Once set up, every command in the rest of this README (pixi shell, pixi run test, etc.) works identically inside the WSL terminal.

Build modes

Three CMake build types are available, each using its own build directory so you can switch between them without reconfiguring:

Pixi taskCMake typeBuild directoryUse case
pixi run buildReleasebuild/Production — full optimisation, no debug symbols
pixi run build-reldbgRelWithDebInfobuild-reldbg/Profiling — optimised with debug symbols
pixi run build-debugDebugbuild-debug/Development — no optimisation, full debug symbols

Corresponding Python-binding tasks copy the compiled .so into python/pylupnt/:

pixi run build-py          # Release bindings
pixi run build-py-reldbg   # RelWithDebInfo bindings
pixi run build-py-debug    # Debug bindings

Daily workflow

Activate the pixi environment in a shell (sets LUPNT_DATA_PATH, LUPNT_OUTPUT_PATH, PECSIMPY_BASE_PATH, and PYTHONPATH automatically):

pixi shell

Or run a single command without entering a shell:

pixi run <command>

Running C++ examples

After building, the C++ tutorial programs (one per source file in cpp/examples/tutorials/, the counterparts to the Python notebooks) are in build/examples/. Run any example with:

# From inside pixi shell
./build/examples/ex1_propagate_orbit
./build/examples/ex4_plasmasphere

Environment variables are set automatically inside pixi shell. Outside of it, set them explicitly:

LUPNT_DATA_PATH=$PWD/data/LuPNT_data \
PECSIMPY_BASE_PATH=$PWD/data/LuPNT_data/plasma \
./build/examples/ex4_plasmasphere

Jupyter notebooks

First register the Jupyter kernel (once per machine, and after moving the repo):

pixi run install-kernel

Then select the LuPNT (pixi) kernel in Jupyter / VS Code. install-kernel bakes PYTHONPATH, LUPNT_DATA_PATH, LUPNT_OUTPUT_PATH, and PECSIMPY_BASE_PATH into the kernelspec (derived from the repo layout, so it is correct on both macOS and Linux/WSL) — VS Code and Jupyter launch the kernel's interpreter without pixi activation, so these must live in the kernelspec itself for import pylupnt and the projects/ notebooks to work out of the box. If the picker only shows the bare pixi interpreter (no env vars), re-run the task.

Interactive Cesium visualization

pnt.plot.CesiumScene renders constellations, relay satellites, and surface stations as an interactive 3-D CesiumJS scene — inline in Jupyter and as a standalone .html you can open in any browser or host to share. See python/examples/ex13_cesium.ipynb for a full tutorial (GNSS + lunar relay constellations + surface stations).

import pylupnt as pnt
from datetime import datetime

scene = pnt.plot.CesiumScene(body="MOON", name="Lunar relays", epoch=datetime(2027, 3, 1))
scene.add_trajectory("SV-1", t_tdb, rv_mci, frame_in=pnt.MOON_CI)  # inertial -> Moon-fixed
scene.add_station("Shackleton", lat=-89.9, lon=0.0)                # degrees
scene.show()                                                       # writes ./cesium_scenes/*.html

Do I need a Cesium ion account? No. Scenes are fully self-contained: the central body (Earth or Moon) is drawn as an ellipsoid textured with LuPNT's own surface imagery, embedded directly in the generated HTML — no ion access token, login, or billing. The only thing fetched at view time is the CesiumJS library from a public CDN (cdn.jsdelivr.net), so an internet connection is needed to view a scene. (If you already have an ion token and want streamed high-resolution terrain/imagery, you can edit the generated .html, but nothing here requires it.)

Running tests

pixi run test-cpp includes tests (data.eop_latest_iers, devices.space_comms, agents.gnss_constellation.setup_from_files, interfaces.antex_loader, interfaces.rinex_nav_loader, interfaces.sp3_loader, measurements.gnss_measurements.visibility, states.tle) that need live GNSS/EOP products (SP3, RINEX nav, ANTEX) from CDDIS — this is why NASA Earthdata Login (see Prerequisites) is required. Some of these tests (interfaces.sp3_loader, interfaces.rinex_nav_loader, agents.gnss_constellation.setup_from_files) additionally expect specific SP3/BRDC files to already exist on disk rather than downloading them on the fly (see GnssFilesDir() in cpp/test/interfaces/test_sp3_loader.cc), so fetch those once before running the suite:

pixi run download-gnss-test-data   # one-time; requires ~/.netrc (see Prerequisites)
pixi run test-cpp                  # Build and run C++ Catch2/CTest tests
pixi run test-py                   # Build Python bindings, then run pytest
pixi run test                      # Run both suites

If you don't have Earthdata Login set up, use pixi run test-cpp-ci instead — it's the same command CI runs and excludes the tests above.

More details are in cpp/test/README.md.

Optional: Orekit / GMAT cross-validation (developers)

The C++ tests under cpp/test/orekit/ and cpp/test/gmat/ cross-check LuPNT's time-scale conversions, sidereal angles, frame conversions (Earth and Moon), Sun/Moon ephemerides, and orbit propagation against reference values from Orekit (a JVM-based astrodynamics library) and GMAT (NASA's General Mission Analysis Tool). To keep LuPNT lightweight for normal users, these tests do not depend on Orekit/Java or GMAT at all:

  • The reference values were computed once with each tool and checked into cpp/test/orekit/data/orekit_reference.json and cpp/test/gmat/data/gmat_reference.json by the developer-only generators gen_orekit_reference.py and gen_gmat_reference.py.
  • The *_orekit_reference/*_gmat_reference tests load those JSON fixtures and compare them against LuPNT's own computations, with no runtime Orekit/GMAT dependency. They run automatically as part of pixi run test-cpp.
  • Orekit is only declared as a dependency of the optional dev pixi environment, so pixi install (the default environment) never downloads it. GMAT is a separately installed desktop application and is not, and will not be, a pixi/conda dependency.

The detailed comparison results -- what is compared, the measured differences per category, and the known modeling differences the comparison uncovered -- are documented in docs/pages/cross_validation.rst.

If you want to regenerate the reference vectors (e.g. to add new cross-validation cases or update them after an intentional algorithm change):

# Orekit (installs the optional dev environment with the orekit package)
pixi install -e dev
pixi run -e dev python cpp/test/orekit/gen_orekit_reference.py

# GMAT (requires a local GMAT install; GMAT_HOME also works, and common
# default install locations are searched automatically)
GMAT_CONSOLE=/Applications/GMAT_R2022a/bin/GmatConsole-R2022a \
    python3 cpp/test/gmat/gen_gmat_reference.py

Each generator overwrites its fixture; review the diff and re-run pixi run test-cpp to confirm the comparison tests still pass. See cpp/test/orekit/README.md and cpp/test/gmat/README.md for the full data dictionaries, tool-specific caveats, and tolerance rationale.

Building documentation

pixi run build-docs

This generates the Sphinx/Doxygen documentation site from the existing headers and Python binding, then copies the GitHub Pages-ready HTML into build/docs/. If python/pylupnt/_pylupnt*.so is missing, run pixi run build-py first. The generated folder includes .nojekyll so Pages serves Sphinx assets as-is. More details are in docs/builddocs.rst.

To preview the docs page locally, run the following command

python -m http.server 8000 --directory build/docs

and open

http://localhost:8000

Pre-commit

Pre-commit hooks run automatically on git commit. To run manually:

pixi run pre-commit run --all-files

Third-party components

LuPNT's plasma module includes the following scientific models, which are freely available for scientific use with attribution. The MIT license for LuPNT applies to LuPNT's own source code only; these components retain their original terms.

ComponentDescriptionSource
IRI2007 / IRI2020International Reference Ionosphere electron density modelirimodel.org
GCPM v2.4Global Core Plasma Model (D.L. Gallagher, NASA MSFC)plasmasphere.nasa.gov
xformCoordinate transformation utilitiesplasmasphere.nasa.gov
IGRF14International Geomagnetic Reference Field (IAGA Working Group V-MOD / BGS)ngdc.noaa.gov

Publications using these components should cite the relevant papers:

  • IRI2020: Bilitza, D., et al. (2022). The International Reference Ionosphere model: A review and description of an ionospheric benchmark. Reviews of Geophysics, 60, e2022RG000792. https://doi.org/10.1029/2022RG000792
  • GCPM: Gallagher, D. L., et al. (2000). An empirical model of the Earth's plasmasphere. Advances in Space Research, 25(12), 2421–2430.
  • IGRF14: Alken, P., et al. (2021). International Geomagnetic Reference Field: the thirteenth generation. Earth, Planets and Space, 73, 49. https://doi.org/10.1186/s40623-020-01288-x