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},
}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.
| Module | Description |
|---|---|
| Agents & Applications | Config-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 |
| Simulations | Event-scheduled Simulation engine plus end-to-end scenario drivers (ground-station & inter-satellite ODTS, lunar GNSS ODTS, lander/surface navigation, ephemeris fitting) |
| Devices & sensors | Clocks, IMUs, cameras, GNSS receivers, and communication devices attached to agents |
| Dynamics | Two-body, N-body, and high-fidelity force models (gravity, drag, SRP) for Earth, lunar, and arbitrary central-body orbits |
| Environment | Gravity fields, atmosphere, solar system bodies, occultation, and plasma/ionosphere models |
| Plasma | GCPM v2.4 plasmasphere + IRI ionosphere electron density; GNSS ray-tracing with TEC and signal delay for cislunar links (via integrated pecsim) |
| GNSS | GPS/GNSS signal generation, SP3/ANTEX-backed constellations, yaw-steering/attitude models, and space-user ranging |
| Measurements | Pseudorange, Doppler, and carrier-phase measurement models with light-time, Shapiro, and optional plasma corrections |
| Filters & States | Extended Kalman Filter (EKF), Unscented Kalman Filter (UKF), square-root information filter (SRIF)/smoother, and batch least-squares estimators over composable state/joint-state abstractions |
| Interfaces | Data loaders and I/O: SP3, ANTEX, RINEX nav, TLE, SPICE kernels, EOP/TAI-UTC, LOLA DEM and crater data, plus Cesium and Matplotlib export |
| Conversions | Reference frame transformations (ECI, ECEF, LVLH, Moon-centered, generic body-fixed/inertial) and time system utilities |
| Numerics | Numerical integration (RK4, Euler), Nelder-Mead optimizer, and matrix utilities |
| Visualization | Matplotlib/Plotly plotting plus interactive 3-D CesiumJS scenes of constellations and surface assets (pnt.plot.CesiumScene) |
| Python bindings & authoring | Full 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) |
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
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.
| # | Example | Theme |
|---|---|---|
| 1 | Propagating an ELFO orbit | Fundamentals — elements, frames, force models |
| 2 | Relativistic time conversions | Fundamentals — TT/TCG/TCB/TDB/TCL/LT |
| 3 | GNSS interface tutorial | Earth-GNSS data — TLEs, antenna gain, SP3 vs. broadcast |
| 4 | Plasmasphere & ionosphere | GCPM v2.4 electron density, TEC, ray-traced signal delay |
| 5 | GNSS measurement simulation | Lunar-receiver visibility and C/N₀ from precise ephemerides |
| 6 | Sidelobe pseudorange/Doppler/TDCP ODTS | Weak-signal EKF for position, velocity, clock, and SRP |
| 7 | Ground-station orbit determination | DSN batch least squares + SRIF/smoother |
| 8 | Distributed ISL ODTS | 5 parallel consider-state EKFs + surface-station timing |
| 9 | Ephemeris & almanac fitting | Compressing trajectories into broadcast navigation models |
| 10 | Surface rover navigation | IMU + LCRNS pseudoranges + DEM error-state EKF |
| 11 | Lunar lander navigation | Powered-descent MEKF: IMU, altimeter, craters, LunaNet |
| 12 | Constellation design | ELFO Walker layout, PDOP/coverage/EIRP, phasing optimization |
| 13 | Cesium visualization | Interactive 3-D CesiumJS scenes of constellations & stations |
| 14 | Optical navigation | Lunar-horizon image processing → EKF position fixes |
| 15 | Mars PNT | Beyond the Moon — Mars gravity, frames, Walker constellation |
| 16 | LEO PNT | Earth LEO constellation with Harris-Priester atmospheric drag |
| 17 | New simulation in Python | Authoring a new Application + Measurement in pure Python (angles-only OD) |
C++ tutorial counterparts live in cpp/examples/tutorials/.
Installation is from source only — LuPNT is no longer distributed on PyPI. The legacy
pylupntwheels (last published for0.0.4) predate the current C++/Fortran dependency stack (OpenCV, Matplot++, Fortran plasma models, HDF5) and are not maintained. Build from source withpixias described below. Seedocs/roadmap.mdfor the packaging status and what restoring a PyPI/conda-forge distribution would take.
- 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-
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 runningpixi run test-cppor any of the GNSS download tasks:-
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.
-
Create a
~/.netrcfile 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. Thechmod 0600step is required — curl/wget refuse to use a.netrcwith broader permissions. Verify withcat ~/.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). -
- Install the environment (downloads all dependencies into an isolated conda environment)
pixi install- Build the C++ library
pixi run build- Build and deploy the Python bindings
pixi run build-pyNote:
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=OFFto the underlyingcmakeconfigure call, or place the data manually atdata/LuPNT_databeforehand.
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.
- Install WSL (PowerShell, run as Administrator). This installs the default Ubuntu distribution:
wsl --installReboot if prompted, then launch "Ubuntu" from the Start menu and create a Unix username/password when asked.
- Update packages inside the WSL Ubuntu shell
sudo apt update && sudo apt upgrade -y- 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-
Continue with the regular Prerequisites and Setup steps above (
curl -fsSL https://pixi.sh/install.sh | bash, thenpixi install,pixi run build,pixi run build-py) from inside the WSL shell. -
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.
Three CMake build types are available, each using its own build directory so you can switch between them without reconfiguring:
| Pixi task | CMake type | Build directory | Use case |
|---|---|---|---|
pixi run build |
Release |
build/ |
Production — full optimisation, no debug symbols |
pixi run build-reldbg |
RelWithDebInfo |
build-reldbg/ |
Profiling — optimised with debug symbols |
pixi run build-debug |
Debug |
build-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 bindingsActivate the pixi environment in a shell (sets LUPNT_DATA_PATH, LUPNT_OUTPUT_PATH, PECSIMPY_BASE_PATH, and PYTHONPATH automatically):
pixi shellOr run a single command without entering a shell:
pixi run <command>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_plasmasphereEnvironment 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_plasmasphereFirst register the Jupyter kernel (once per machine, and after moving the repo):
pixi run install-kernelThen 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.
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/*.htmlDo 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.)
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 suitesIf 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.
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.jsonandcpp/test/gmat/data/gmat_reference.jsonby the developer-only generatorsgen_orekit_reference.pyandgen_gmat_reference.py. - The
*_orekit_reference/*_gmat_referencetests load those JSON fixtures and compare them against LuPNT's own computations, with no runtime Orekit/GMAT dependency. They run automatically as part ofpixi run test-cpp. - Orekit is only declared as a dependency of the optional
devpixi environment, sopixi 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.pyEach 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.
pixi run build-docsThis 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/docsand open
http://localhost:8000
Pre-commit hooks run automatically on git commit. To run manually:
pixi run pre-commit run --all-filesLuPNT'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.
| Component | Description | Source |
|---|---|---|
| IRI2007 / IRI2020 | International Reference Ionosphere electron density model | irimodel.org |
| GCPM v2.4 | Global Core Plasma Model (D.L. Gallagher, NASA MSFC) | plasmasphere.nasa.gov |
| xform | Coordinate transformation utilities | plasmasphere.nasa.gov |
| IGRF14 | International 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