Introduction

Welcome to the Strapdown-rs documentation! This guide will help you understand and use the Strapdown-rs library, a high-performance Rust implementation of strapdown inertial navigation system (INS) algorithms.

Crates.io Documentation License

What is Strapdown-rs?

Strapdown-rs is a straightforward strapdown inertial navigation system (INS) implementation in Rust. It is designed to be simple, performant, and easy to understand, making it an excellent starting point for those interested in learning about or implementing strapdown INS algorithms.

The project provides:

  1. A source library of strapdown INS algorithms and utilities (strapdown-core)
  2. A simulation program for simulating INS performance in various GNSS conditions (strapdown-sim)
  3. Experimental geophysical navigation capabilities for GNSS-denied environments (strapdown-geonav)

Key Features

  • Modern Rust Implementation: Memory-safe, high-performance code with cross-platform support
  • Multiple Navigation Filters: Extended Kalman Filter (EKF), Unscented Kalman Filter (UKF), and Particle Filters
  • GNSS Degradation Scenarios: Simulate dropouts, reduced update rates, and measurement corruption
  • Geophysical Navigation: Experimental work on gravity and magnetic anomaly navigation
  • Comprehensive Documentation: Well-documented code with examples and tutorials
  • Research-Focused: Designed for research, teaching, and development purposes

Target Audience

This library is intended for:

  • Researchers working on navigation systems and sensor fusion
  • Engineers developing autonomous systems, robotics, or aerospace applications
  • Students learning about inertial navigation and state estimation
  • Developers needing a high-performance INS implementation in Rust

Project Status

Strapdown-rs is under active development as part of ongoing PhD research. The project prioritizes correctness, numerical stability, and performance while maintaining extensibility for researchers and engineers.

Getting Help

Citation

If you use Strapdown-rs in your research, please cite:

JOSS Paper

License

Strapdown-rs is licensed under the MIT License. See the LICENSE file for details.

Installation

This page provides detailed instructions for installing Strapdown-rs on your system.

Prerequisites

Before installing Strapdown-rs, ensure you have the following:

  • Rust: Version 1.70 or higher (install from rustup.rs)
  • System Libraries: Required for building certain dependencies

System Dependencies

Strapdown-rs requires several system libraries for HDF5 and NetCDF support:

Ubuntu/Debian

sudo apt update
sudo apt install -y pkg-config \
  libhdf5-dev \
  libhdf5-openmpi-dev \
  libnetcdf-dev \
  zlib1g-dev

Fedora/RHEL

sudo dnf install -y pkg-config \
  hdf5-devel \
  hdf5-openmpi-devel \
  netcdf-devel \
  zlib-devel

macOS

brew install pkg-config hdf5 netcdf

Windows

For Windows users, we recommend using vcpkg to install dependencies:

vcpkg install hdf5 netcdf zlib

Installation Methods

The easiest way to use Strapdown-rs is to add it as a dependency in your project:

cargo add strapdown-core

Or add manually to your Cargo.toml:

[dependencies]
strapdown-core = "0.1"

To install the simulation binary:

cargo install strapdown-sim

Method 2: Build from Source

Clone the repository and build locally:

# Clone the repository
git clone https://github.com/jbrodovsky/strapdown-rs.git
cd strapdown-rs

# Build the entire workspace
cargo build --workspace --all-features --release

# Install the simulation binary
cargo install --path sim

# Optionally, install the geonav binary
cargo install --path geonav

Method 3: Using Pixi (Experimental)

The project includes a pixi.toml for environment management:

# Install pixi
curl -fsSL https://pixi.sh/install.sh | bash

# Activate the environment
pixi install
pixi shell

# Build and run
cargo build --release

Verifying Installation

After installation, verify everything works:

# Check strapdown-sim version
strapdown-sim --version

# Run a simple test
cargo test -p strapdown-core

Troubleshooting

HDF5/NetCDF Linking Issues

If you encounter linking errors:

  1. Ensure pkg-config can find the libraries:

    pkg-config --modversion hdf5
    pkg-config --modversion netcdf
    
  2. Set environment variables if needed:

    export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
    

Rust Version Issues

Ensure you're using a recent Rust version:

rustc --version
rustup update stable

Next Steps

System Requirements

This page outlines the system requirements for running Strapdown-rs.

Hardware Requirements

Minimum

  • CPU: Any modern 64-bit processor (x86_64 or ARM64)
  • RAM: 2 GB
  • Storage: 100 MB for binaries, plus space for data files
  • CPU: Multi-core processor (4+ cores) for parallel processing
  • RAM: 8 GB or more for large simulations
  • Storage: SSD with sufficient space for trajectory data and results

Software Requirements

Operating Systems

Strapdown-rs supports all major operating systems:

  • Linux: Ubuntu 20.04+, Debian 11+, Fedora 35+, or equivalent
  • macOS: 11.0 (Big Sur) or later
  • Windows: Windows 10/11 with MSVC or MinGW

Rust Toolchain

  • Minimum Rust version: 1.70.0
  • Recommended: Latest stable release
  • Required components: rustc, cargo, clippy, rustfmt

Install via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

System Libraries

Required Libraries

  • pkg-config: For library detection
  • HDF5: Version 1.10 or later
  • NetCDF: Version 4.7 or later (for geophysical navigation)
  • zlib: Standard compression library

Optional Libraries

  • OpenMPI: For parallel HDF5 support (optional but recommended)

Platform-Specific Notes

Linux

Most distributions include the required libraries in their package repositories. Use your package manager to install dependencies.

Performance Note: For best performance, consider building with native CPU optimizations:

RUSTFLAGS="-C target-cpu=native" cargo build --release

macOS

Apple Silicon (M1/M2/M3) is fully supported. The ARM64 architecture provides excellent performance for navigation algorithms.

Note: You may need to set additional environment variables for Homebrew-installed libraries:

export CPATH=/opt/homebrew/include
export LIBRARY_PATH=/opt/homebrew/lib

Windows

Windows support is available but requires additional setup for HDF5 and NetCDF. Using vcpkg is recommended for managing C/C++ dependencies.

Alternatively, use WSL2 (Windows Subsystem for Linux) for a native Linux environment.

Development Requirements

If you plan to contribute to Strapdown-rs development:

  • Git: Version control
  • clippy: Rust linter (rustup component add clippy)
  • rustfmt: Code formatter (rustup component add rustfmt)
  • cargo-edit: For managing dependencies (cargo install cargo-edit)

Testing Your Environment

After installing dependencies, verify your environment:

# Check Rust version
rustc --version

# Verify pkg-config
pkg-config --version

# Check for HDF5
pkg-config --modversion hdf5

# Check for NetCDF
pkg-config --modversion netcdf

# Test compilation
cargo build --workspace

Next Steps

Installing from Crates.io

Detailed instructions for installing from Crates.io.

Content coming soon.

Building from Source

Detailed instructions for building from source.

Content coming soon.

Quick Start

This guide will get you up and running with Strapdown-rs in minutes.

Installation

Add Strapdown-rs to your Cargo.toml:

cargo add strapdown-core

Or install the simulation binary:

cargo install strapdown-sim

Running Your First Simulation

1. Prepare Input Data

Strapdown-sim expects CSV files with IMU and GNSS data. The format follows the Sensor Logger app convention:

timestamp,gyro_x,gyro_y,gyro_z,accel_x,accel_y,accel_z,latitude,longitude,altitude
1234567890.0,0.01,-0.02,0.03,0.5,-0.2,9.81,45.5,-122.6,100.0
...

2. Run a Simulation

Dead Reckoning (Open-Loop):

strapdown-sim open-loop -i data/input.csv -o results/output.csv

Closed-Loop with EKF:

strapdown-sim closed-loop -i data/input.csv -o results/output.csv --filter ekf

Closed-Loop with UKF:

strapdown-sim closed-loop -i data/input.csv -o results/output.csv --filter ukf

Particle Filter:

strapdown-sim particle-filter -i data/input.csv -o results/output.csv --particles 100

3. View Results

The output CSV contains the estimated navigation state:

timestamp,latitude,longitude,altitude,velocity_north,velocity_east,velocity_down,roll,pitch,yaw
1234567890.0,45.50001,-122.59999,100.1,1.2,0.5,-0.1,0.01,0.02,90.5
...

Using Configuration Files

For more complex scenarios, use TOML configuration files:

# config.toml
[simulation]
mode = "closed-loop"
filter = "ekf"

[data]
input_file = "data/trajectory.csv"
output_file = "results/navigation.csv"

[gnss]
enabled = true
dropout_probability = 0.1
update_rate = 1.0

[filter]
use_biases = true
initial_position_uncertainty = 10.0
initial_velocity_uncertainty = 1.0
initial_attitude_uncertainty = 0.1

Run with:

strapdown-sim --config config.toml

Next Steps

User Guide Overview

Welcome to the Strapdown-rs User Guide! This section provides comprehensive information on using the library and simulation tools.

What You'll Learn

This guide covers:

  1. Core Concepts: Understanding strapdown INS, coordinate frames, and state representation
  2. Running Simulations: How to use the strapdown-sim binary
  3. Data Formats: Preparing and formatting your input data
  4. Configuration: Setting up simulations with TOML config files
  5. Logging and Debugging: Monitoring simulation progress

Quick Navigation

For Beginners

If you're new to strapdown INS or this library:

  1. Start with Core Concepts to understand the fundamentals
  2. Learn about Coordinate Frames used in the library
  3. Review State Representation to understand the 9-state and 15-state models
  4. Try the Quick Start tutorial

For Experienced Users

If you're familiar with INS and want to jump in:

  1. Check Input Data Format to prepare your data
  2. Review Configuration Files for advanced options
  3. Explore Running Simulations for different modes
  4. See Logging for debugging and monitoring

Simulation Modes

Strapdown-rs supports three main simulation modes:

Open-Loop (Dead Reckoning)

Pure inertial navigation without corrections. Useful for:

  • Understanding INS error growth
  • Baseline comparisons
  • Testing IMU data quality

See: Open-Loop Mode

Closed-Loop (Kalman Filtering)

INS with GNSS corrections using EKF or UKF. Best for:

  • Realistic navigation scenarios
  • GNSS degradation studies
  • Production-like simulations

See: Closed-Loop Mode

Particle Filter

Non-parametric Bayesian filtering for non-Gaussian distributions. Useful for:

  • Multimodal uncertainty
  • Highly nonlinear scenarios
  • Research applications

See: Particle Filter Mode

The library provides multiple filter implementations:

  • Extended Kalman Filter (EKF): Fast, efficient, works well for mildly nonlinear systems
  • Unscented Kalman Filter (UKF): Better accuracy for nonlinear systems, 2-3x slower
  • Particle Filter: Handles non-Gaussian distributions, computationally intensive
  • Rao-Blackwellized Particle Filter (RBPF): Hybrid approach combining particles and EKF

Learn more: Navigation Filters

State Models

9-State Model

The basic navigation-only model:

  • Position: latitude, longitude, altitude
  • Velocity: north, east, down
  • Attitude: roll, pitch, yaw

15-State Model

Extended model with IMU bias estimation:

  • 9 navigation states (as above)
  • Accelerometer biases: 3 states
  • Gyroscope biases: 3 states

The 15-state model provides better long-term accuracy by estimating and correcting sensor biases.

Typical Workflow

  1. Collect or prepare IMU/GNSS data in CSV format
  2. Create a configuration file specifying simulation parameters
  3. Run the simulation using strapdown-sim
  4. Analyze results from the output CSV
  5. Iterate by adjusting parameters as needed

Common Use Cases

Research and Development

  • Testing new navigation algorithms
  • Comparing filter performance
  • Studying error characteristics
  • Publishing research results

Education

  • Teaching INS fundamentals
  • Demonstrating sensor fusion
  • Illustrating error sources
  • Hands-on learning

System Development

  • Prototyping navigation systems
  • Evaluating sensor requirements
  • Testing GNSS-denied scenarios
  • Performance benchmarking

Getting Help

Next Steps

Choose your path:

Core Concepts

Strapdown Mechanization

Coordinate Frames

State Representation

Running Simulations

Open-Loop Mode

Closed-Loop Mode

Particle Filter Mode

Input Data Format

Overview

The strapdown-rs project uses CSV data files for simulation and testing. The primary data format is compatible with the Sensor Logger mobile application, which records IMU, GNSS, and other sensor measurements.

CSV Column Descriptions

The data files contain the following columns (not all columns may be present in every file):

Timestamp

  • time - ISO UTC timestamp of the form YYYY-MM-DD HH:mm:ss.ssss+HH:MM (where the +HH:MM is the timezone offset)

GNSS Measurements

  • latitude - Latitude measurement in degrees
  • longitude - Longitude measurement in degrees
  • altitude - Altitude measurement in meters
  • speed - Speed measurement in meters per second
  • bearing - Bearing measurement in degrees

IMU Measurements (Body Frame)

  • acc_x - Acceleration in the X direction (meters per second squared)
  • acc_y - Acceleration in the Y direction (meters per second squared)
  • acc_z - Acceleration in the Z direction (meters per second squared)
  • gyro_x - Angular velocity around the X axis (radians per second)
  • gyro_y - Angular velocity around the Y axis (radians per second)
  • gyro_z - Angular velocity around the Z axis (radians per second)

Attitude (Orientation)

  • roll - Roll angle in degrees
  • pitch - Pitch angle in degrees
  • yaw - Yaw angle in degrees
  • qw - Quaternion scalar component
  • qx - Quaternion X component
  • qy - Quaternion Y component
  • qz - Quaternion Z component

Magnetometer

  • mag_x - Magnetic field strength in the X direction (micro teslas)
  • mag_y - Magnetic field strength in the Y direction (micro teslas)
  • mag_z - Magnetic field strength in the Z direction (micro teslas)

Barometric Pressure

  • pressure - Atmospheric pressure measurement (milli bar)
  • relativeAltitude - Relative altitude measurement (meters)

Gravity Vector (Computed)

  • grav_x - Gravitational acceleration in the X direction (meters per second squared)
  • grav_y - Gravitational acceleration in the Y direction (meters per second squared)
  • grav_z - Gravitational acceleration in the Z direction (meters per second squared)

Data Directory Structure

The project organizes data files into several directories based on processing type and GNSS condition:

Input Data

  • input/: Contains pre-processed input files ready for processing, along with route visualization images. Raw recordings are available upon request but not stored in version control due to size.

Ground Truth

  • truth/: Contains ground truth data files of processed trajectories used for validation and testing. These files serve as reference data for comparing alternative processing methods and navigation algorithms.

GNSS Degradation Scenarios

The following directories contain processed trajectory data that simulate various degraded GNSS conditions:

  • degraded/: Simulates degraded GPS conditions with reduced accuracy
  • spoofed/: Simulates GPS spoofing with fixed position offsets to mislead the navigation system
  • intermittent/: Simulates intermittent GPS with periodic outages
  • combo/: Simulates a combination of degraded, spoofed, and intermittent GPS conditions

Usage

Loading Data

The strapdown-core library provides functions to load CSV data:

#![allow(unused)]
fn main() {
use strapdown_core::sim::load_test_data;

let data = load_test_data("path/to/data.csv")?;
}

Testing with Degraded GNSS

To test alternative processing methods and navigation algorithms under degraded conditions:

  1. Design your navigation algorithm to leverage the available data in the input files
  2. Configure your experiment's GnssDegradationConfiguration to match the specific characteristics of the degraded condition you want to simulate
  3. Process your experiment accordingly

Example Usage

# Run closed-loop simulation with input data
strapdown-sim closed-loop -i data/input/trajectory.csv -o results/output.csv

# Test with degraded GNSS
strapdown-sim closed-loop -i data/degraded/trajectory.csv -o results/degraded_output.csv

Data Collection

Data can be collected using the Sensor Logger mobile application:

  • Available for iOS and Android
  • Records synchronized IMU and GNSS data
  • Exports in CSV format compatible with this project
  • Website: https://www.tszheichoi.com/sensorlogger

Notes

  • Not all columns need to be present in every file
  • Missing values are typically represented as empty strings or NaN
  • The simulation functions will skip rows with invalid or missing critical data (e.g., IMU measurements)
  • Timestamps should be monotonically increasing
  • IMU data is typically recorded at ~100 Hz
  • GNSS data is typically recorded at ~1 Hz

Configuration Files

Logging Guide

This project uses the Rust log crate with env_logger as the backend, providing a Python-like logging experience.

Usage

Command-Line Options

Both strapdown-sim and geonav-sim executables support the following logging options:

  • --log-level <LEVEL>: Set the log level (off, error, warn, info, debug, trace)
    • Default: info
  • --log-file <PATH>: Write logs to a file instead of stderr
    • If not specified, logs are written to stderr

Examples

Basic usage with default settings (info level to stderr):

strapdown-sim closed-loop -i input.csv -o output.csv

Set log level to debug:

strapdown-sim closed-loop -i input.csv -o output.csv --log-level debug

Write logs to a file:

strapdown-sim closed-loop -i input.csv -o output.csv --log-file simulation.log

Combine log level and file output:

strapdown-sim closed-loop -i input.csv -o output.csv --log-level debug --log-file debug.log

Disable logging:

strapdown-sim closed-loop -i input.csv -o output.csv --log-level off

Log Levels

The following log levels are available, from least to most verbose:

  • off: No logging output
  • error: Only errors that prevent operation
  • warn: Warnings about potentially problematic situations
  • info: General informational messages (default)
  • debug: Detailed information useful for debugging
  • trace: Very detailed trace information

Log Format

Log messages are formatted as:

YYYY-MM-DD HH:MM:SS.mmm [LEVEL] - message

Example:

2025-12-01 14:30:45.123 [INFO] - Read 1000 records from data/input.csv
2025-12-01 14:30:45.456 [INFO] - Running particle filter with 100 particles
2025-12-01 14:30:50.789 [INFO] - Results written to output.csv

Environment Variable Override

You can also control logging using the RUST_LOG environment variable, which follows the env_logger syntax. Command-line options take precedence over environment variables.

# Set log level via environment variable
RUST_LOG=debug strapdown-sim closed-loop -i input.csv -o output.csv

# Module-specific logging
RUST_LOG=strapdown=debug,strapdown_sim=trace strapdown-sim closed-loop -i input.csv -o output.csv

Using Logging in Code

For developers extending the project, use the logging macros from the log crate:

#![allow(unused)]
fn main() {
use log::{trace, debug, info, warn, error};

// Informational messages
info!("Processing {} records", count);

// Warnings
warn!("Skipping row {} due to parse error", row_num);

// Errors
error!("Failed to read config file: {}", err);

// Debug information
debug!("UKF state: {:?}", ukf.get_mean());

// Detailed traces
trace!("Entering function with params: {:?}", params);
}

Python Logger Comparison

This logging system provides a similar experience to Python's logging module:

PythonRust
logging.info("message")info!("message")
logging.warning("message")warn!("message")
logging.error("message")error!("message")
logging.debug("message")debug!("message")
--log-level info--log-level info
Writing to file with FileHandler--log-file path/to/file.log

Notes

  • Log files are opened in append mode, so multiple runs will append to the same log file
  • Timestamps use the local system timezone
  • The logger is initialized once at program startup
  • Log messages from the core library (strapdown-core) are also captured and formatted consistently

Kalman Filters

Extended Kalman Filter (EKF)

The Extended Kalman Filter (EKF) is one of the primary navigation filters available in Strapdown-rs. It provides efficient state estimation for the INS through linearization of nonlinear system and measurement models.

Overview

The EKF linearizes the nonlinear navigation equations using Jacobian matrices (first-order Taylor series expansion). While this approximation is less accurate than the UKF's unscented transform for highly nonlinear systems, it is computationally more efficient and works well for most practical navigation scenarios.

Mathematical Foundation

State Vector

The EKF supports two state configurations:

9-State Model (Navigation Only):

  • Latitude, longitude, altitude
  • North, east, down velocities
  • Roll, pitch, yaw angles

15-State Model (Navigation + Biases):

  • 9 navigation states (as above)
  • Accelerometer biases (3)
  • Gyroscope biases (3)

Prediction Step

The prediction step propagates the state and covariance forward using IMU measurements using the strapdown mechanization and computed Jacobian matrices.

Update Step

When GNSS or other measurements are available, the Kalman gain is computed and the state is updated with the measurement innovation.

Features

Advantages

  1. Computational Efficiency: 3-5x faster than UKF
  2. Memory Efficient: Stores only mean and covariance
  3. Well-Understood: Extensive literature and proven track record
  4. Analytic Jacobians: Uses pre-computed derivatives for accuracy

Limitations

  1. Linearization Error: Less accurate for highly nonlinear systems
  2. First-Order Approximation: May miss higher-order effects
  3. Gaussian Assumption: Cannot handle multimodal distributions

Usage

Basic Initialization

#![allow(unused)]
fn main() {
use strapdown::kalman::{ExtendedKalmanFilter, InitialState, NavigationFilter};
use nalgebra::{DMatrix, DVector};

// Define initial state
let initial_state = InitialState {
    latitude: 45.0,
    longitude: -122.0,
    altitude: 100.0,
    northward_velocity: 0.0,
    eastward_velocity: 0.0,
    vertical_velocity: 0.0,
    roll: 0.0,
    pitch: 0.0,
    yaw: 0.0,
    in_degrees: true,
    is_enu: true,
};

// Initialize 9-state EKF (no biases)
let mut ekf = ExtendedKalmanFilter::new(
    initial_state,
    vec![],  // No biases for 9-state
    vec![1e-6; 9],  // Initial covariance diagonal
    DMatrix::from_diagonal(&DVector::from_vec(vec![1e-9; 9])),  // Process noise
    false,  // use_biases = false for 9-state
);
}

15-State with Bias Estimation

#![allow(unused)]
fn main() {
// Initialize 15-state EKF with bias estimation
let mut ekf = ExtendedKalmanFilter::new(
    initial_state,
    vec![0.0; 6],  // Initial bias estimates (3 accel + 3 gyro)
    vec![1e-6; 15],  // Initial covariance diagonal
    DMatrix::from_diagonal(&DVector::from_vec(vec![1e-9; 15])),  // Process noise
    true,  // use_biases = true for 15-state
);
}

Prediction with IMU Data

#![allow(unused)]
fn main() {
use strapdown::IMUData;

let imu_data = IMUData {
    timestamp: 1234567890.0,
    gyro: [0.01, -0.02, 0.03],  // rad/s
    accel: [0.5, -0.2, 9.81],    // m/s²
};

let dt = 0.01;  // Time step in seconds
ekf.predict(&imu_data, dt);
}

Update with GNSS

#![allow(unused)]
fn main() {
use strapdown::measurements::GPSPositionMeasurement;

let gps_measurement = GPSPositionMeasurement {
    latitude: 45.00012,
    longitude: -122.00015,
    altitude: 101.5,
    std_dev: [5.0, 5.0, 10.0],  // Measurement uncertainty
};

ekf.update(&gps_measurement);
}

Performance

Computational Complexity

  • Typical update rate: 10,000-20,000 updates/second on modern hardware
  • 3-5x faster than UKF
  • Memory usage: ~2 KB for 15-state

Comparison with UKF

AspectEKFUKF
SpeedFaster (3-5x)Slower
AccuracyGood for mildly nonlinearBetter for highly nonlinear
ImplementationRequires JacobiansNo Jacobians needed
MemoryLowerHigher
Best ForReal-time systemsResearch/offline processing

See EKF vs UKF Comparison for detailed analysis.

Best Practices

  1. Start with 9-state unless you need bias estimation
  2. Tune conservatively: Start with larger uncertainties and reduce
  3. Monitor innovation: Check measurement residuals for divergence
  4. Use 15-state for long-duration missions or low-quality IMUs
  5. Validate with dead reckoning: Compare against open-loop results

Next Steps

Unscented Kalman Filter (UKF)

EKF vs UKF Comparison

Particle Filters

Overview

This document outlines the design for a particle filter-based Inertial Navigation System (INS) following the architecture established in the UKF implementation (kalman.rs). The design addresses vertical channel instability through two alternative approaches: third-order damping and 2.5D navigation.

Executive Summary: Key Architectural Decisions

1. Custom Forward Propagation Function Required

Critical insight: For 2.5D navigation, we cannot reuse the standard forward() function from lib.rs. A new forward_2_5d() function must be implemented in particle.rs.

Why?

  • Standard strapdown mechanization (Groves Eq 5.54) fully integrates vertical acceleration into vertical velocity
  • This creates deterministic coupling: v_v(t+dt) = v_v(t) + ∫[a_z + g - Coriolis] dt
  • In 2.5D navigation, we want vertical velocity to be a random walk constrained by measurements, not deterministically coupled to accelerometer readings

Solution: forward_2_5d() implements:

  • Full strapdown for: attitude, horizontal velocities (v_n, v_e), horizontal position (lat, lon)
  • Simplified for: vertical velocity (random walk), altitude (integration of v_v only)

2. Vertical Channel Treatment

Two approaches are provided:

ApproachStatesVertical VelocityComplexityRecommended
2.5D Simplified15Random walk with large process noiseLowYes - Start here
Third-Order Damping17Damped feedback controlHighOptional - if 2.5D insufficient

3. Implementation Priority

  1. First: Implement forward_2_5d() - this is the foundation
  2. Second: Implement particle filter with 2.5D mode
  3. Later: Optionally add third-order damping if needed

State Representation

Standard 15-State Model

Following the UKF implementation, the baseline particle filter uses a 15-state model:

Navigation states (9):

  • Position: [latitude, longitude, altitude] (rad, rad, m)
  • Velocity: [v_north, v_east, v_vertical] (m/s)
  • Attitude: [roll, pitch, yaw] (rad) - represented internally as DCM

Bias states (6):

  • Accelerometer biases: [b_ax, b_ay, b_az] (m/s²)
  • Gyroscope biases: [b_gx, b_gy, b_gz] (rad/s)

Extended State for Vertical Channel Damping

For third-order vertical channel damping, add optional states:

  • Altitude error estimate: δh (m)
  • Altitude error rate: δh_dot (m/s)

Total state dimension: 15 + 2 = 17 states (when using damping)

Implementation note: Use Particle.other_states: Option<DVector<f64>> for the damping states.

Vertical Channel Approaches

Approach A: Third-Order Vertical Channel Damping

Motivation: The vertical channel in INS is inherently unstable due to coupling between altitude and vertical velocity errors. Third-order damping provides feedback to stabilize this channel.

State augmentation:

x = [lat, lon, h, v_n, v_e, v_v, φ, θ, ψ, b_a, b_g, δh, δh_dot]
    └─────────────────────────────────────┘  └─────┘  └────────┘
           15 standard states                 biases    damping

Dynamics model:

  • Altitude error dynamics: δh_dot = δv_v
  • Altitude error rate dynamics: δh_ddot = -k₁·δh - k₂·δh_dot + w_h
    • k₁, k₂: Damping coefficients (tunable)
    • w_h: Process noise on altitude error

Feedback mechanism: During propagation (predict step):

  1. Propagate particles through standard strapdown equations
  2. Update altitude using damping feedback:
    h_corrected = h_propagated - k_fb · δh
    v_v_corrected = v_v_propagated - k_fb · δh_dot
    
  3. During measurement update with GPS altitude:
    • Innovation: z - h_expected updates both h and δh
    • Damping states receive weighted update based on particle importance

Pros:

  • Theoretically rigorous approach based on observability analysis
  • Provides smooth vertical channel behavior
  • Well-suited for long GNSS outages

Cons:

  • Additional states increase computational cost
  • Requires careful tuning of damping coefficients
  • Increased complexity in implementation

Approach B: 2.5D Navigation (Simplified Vertical Treatment)

Motivation: Treat vertical acceleration as primarily noise-driven rather than deterministic, simplifying the vertical channel dynamics. This is the recommended approach based on past success.

State representation:

x = [lat, lon, h, v_n, v_e, v_v, φ, θ, ψ, b_a, b_g]
    └────────────────────────────────────────────┘
              Standard 15 states only

Modified dynamics:

  • Horizontal navigation: Full 6-DOF strapdown mechanization for position, velocity, and attitude
  • Vertical channel: Simplified treatment:
    • Altitude propagation: h(t+dt) = h(t) + v_v(t)·dt
    • Vertical velocity: Integrate vertical acceleration with high process noise
    • Vertical acceleration noise: Model as white noise with large variance
      Q_vertical = [Q_h, Q_v_v] where Q_v_v >> Q_v_n, Q_v_e
      

Process noise tuning:

  • Position (lat/lon): 1e-6 (tight)
  • Altitude: 1e-3 to 1e-2 (loose, allows drift)
  • Horizontal velocity: 1e-3
  • Vertical velocity: 1e-2 to 1e-1 (very loose, absorbs vertical uncertainty)
  • Biases: 1e-6 to 1e-8 (standard)

Measurement handling:

  • GPS altitude measurements directly constrain h with measurement noise
  • Barometric altitude (if available) provides additional vertical constraint
  • Vertical velocity measurements (if available) constrain v_v
  • During GNSS outages: Altitude drifts according to high process noise

Pros:

  • Simpler implementation (no additional states)
  • Computationally efficient
  • Leverages particle filter's ability to handle non-Gaussian distributions
  • Works well with intermittent GPS
  • Avoids complex tuning of damping coefficients

Cons:

  • Less theoretically rigorous
  • Vertical channel accumulates uncertainty faster during outages
  • Requires careful process noise tuning to balance stability and responsiveness

Particle Filter Architecture

Core Components

Particle struct (already defined in particle.rs)

#![allow(unused)]
fn main() {
pub struct Particle {
    pub nav_state: StrapdownState,        // 9 nav states
    pub accel_bias: DVector<f64>,         // 3 accel biases
    pub gyro_bias: DVector<f64>,          // 3 gyro biases
    pub other_states: Option<DVector<f64>>, // Optional: damping states
    pub state_size: usize,                // Total dimension
    pub weight: f64,                      // Importance weight
}
}

ParticleFilter struct

#![allow(unused)]
fn main() {
pub struct ParticleFilter {
    particles: Vec<Particle>,
    num_particles: usize,
    process_noise: ProcessNoise,
    resampling_strategy: ResamplingStrategy,
    averaging_strategy: AveragingStrategy,
    effective_particle_threshold: f64,  // e.g., 0.5 * num_particles
    rng: StdRng,  // Seeded RNG for reproducibility
    is_enu: bool,

    // Vertical channel specific
    vertical_channel_mode: VerticalChannelMode,  // ThirdOrder or Simplified
    damping_coefficients: Option<(f64, f64)>,    // (k1, k2) if using damping
}
}

ProcessNoise struct

#![allow(unused)]
fn main() {
pub struct ProcessNoise {
    pub position_std: Vector3<f64>,       // [σ_lat, σ_lon, σ_h]
    pub velocity_std: Vector3<f64>,       // [σ_vn, σ_ve, σ_vv]
    pub attitude_std: Vector3<f64>,       // [σ_φ, σ_θ, σ_ψ]
    pub accel_bias_std: Vector3<f64>,     // [σ_ba_x, σ_ba_y, σ_ba_z]
    pub gyro_bias_std: Vector3<f64>,      // [σ_bg_x, σ_bg_y, σ_bg_z]
    pub damping_states_std: Option<Vector2<f64>>,  // [σ_δh, σ_δh_dot]
}
}

Enums

#![allow(unused)]
fn main() {
pub enum VerticalChannelMode {
    ThirdOrderDamping { k1: f64, k2: f64 },
    Simplified,  // 2.5D navigation
}

pub enum ResamplingStrategy {
    Systematic,
    Stratified,
    Residual,
    Multinomial,
}

pub enum AveragingStrategy {
    WeightedMean,        // Standard weighted average
    MaximumWeight,       // Use highest weight particle
    MeanWithTrimming,    // Remove outliers then average
}
}

Core Algorithm

Initialization

#![allow(unused)]
fn main() {
impl ParticleFilter {
    pub fn new(
        initial_state: InitialState,
        imu_biases: Vec<f64>,
        covariance_diagonal: Vec<f64>,
        process_noise: ProcessNoise,
        num_particles: usize,
        vertical_mode: VerticalChannelMode,
        seed: Option<u64>,
    ) -> Self;
}
}

Initialization steps:

  1. Create num_particles particles from initial state
  2. Sample each particle's state from N(μ₀, P₀) where:
    • μ₀ = initial mean state
    • P₀ = diag(covariance_diagonal)
  3. Initialize all weights to 1.0 / num_particles
  4. Seed RNG for reproducibility

Predict Step

Key architectural decision: For 2.5D navigation, we cannot use the standard forward() function from lib.rs. The standard strapdown mechanization fully integrates vertical acceleration to update vertical velocity (Equation 5.54 from Groves), which creates deterministic coupling we want to avoid in 2.5D.

Why a new forward function is needed:

The standard forward() computes vertical velocity as:

#![allow(unused)]
fn main() {
// velocity_update() in lib.rs, line 532
velocity + (specific_force + gravity - r * (transport_rate + 2.0 * rotation_rate) * velocity) * dt
}

This tightly couples vertical acceleration to vertical velocity. In 2.5D navigation, we want:

  • Horizontal dynamics: Full strapdown (deterministic acceleration → velocity → position)
  • Vertical dynamics: Decoupled or weakly coupled (vertical velocity as random walk, altitude from velocity integration only)

Solution: Implement forward_2_5d() that modifies vertical channel propagation.

Update Step

#![allow(unused)]
fn main() {
impl NavigationFilter for ParticleFilter {
    fn update<M: MeasurementModel + ?Sized>(&mut self, measurement: &M) {
        // 1. Compute weights based on measurement likelihood
        for particle in &mut self.particles {
            let predicted_meas = measurement.get_expected_measurement(
                &particle.to_state_vector()
            );
            let innovation = measurement.get_vector() - predicted_meas;
            let meas_cov = measurement.get_noise();

            // Likelihood: p(z|x) ∝ exp(-0.5 * innovation^T * R^-1 * innovation)
            let likelihood = self.compute_likelihood(&innovation, &meas_cov);
            particle.weight *= likelihood;
        }

        // 2. Normalize weights
        self.normalize_weights();

        // 3. Check effective particle count
        let n_eff = self.effective_particle_count();

        // 4. Resample if needed
        if n_eff < self.effective_particle_threshold {
            self.resample();
        }
    }
}
}

Resampling

Systematic resampling is the recommended strategy:

#![allow(unused)]
fn main() {
fn systematic_resample(&mut self) {
    let n = self.num_particles;
    let mut new_particles = Vec::with_capacity(n);

    // Cumulative sum of weights
    let cumsum: Vec<f64> = self.particles.iter()
        .scan(0.0, |sum, p| { *sum += p.weight; Some(*sum) })
        .collect();

    // Systematic sampling
    let step = 1.0 / n as f64;
    let start = self.rng.gen_range(0.0..step);

    for i in 0..n {
        let u = start + i as f64 * step;
        let idx = cumsum.iter().position(|&cs| cs >= u).unwrap_or(n - 1);
        let mut new_particle = self.particles[idx].clone();
        new_particle.weight = 1.0 / n as f64;
        new_particles.push(new_particle);
    }

    self.particles = new_particles;
}
}

State Estimation

#![allow(unused)]
fn main() {
fn weighted_mean_estimate(&self) -> DVector<f64> {
    let mut mean_state = DVector::zeros(self.particles[0].state_size);

    for particle in &self.particles {
        let state_vec = particle.to_state_vector();
        mean_state += particle.weight * state_vec;
    }

    // Special handling for circular quantities (angles)
    self.wrap_angles(&mut mean_state);

    mean_state
}
}

The forward_2_5d() Function

This function implements modified strapdown mechanization for 2.5D navigation. It should be added to particle.rs as a module-level function.

Mathematical Formulation

Full strapdown components (unchanged from Groves Ch 5.4-5.5):

  1. Attitude update (Equation 5.46):

    C(t+dt) = C(t) * [I + Ω_ib * dt] - [Ω_ie + Ω_el] * C(t) * dt
    
    • Full implementation using gyros, transport rate, Earth rate
  2. Horizontal velocity update (Equation 5.54, horizontal components only):

    v_n(t+dt) = v_n(t) + [f_n + g_n - Coriolis_n - transport_n] * dt
    v_e(t+dt) = v_e(t) + [f_e + g_e - Coriolis_e - transport_e] * dt
    
    • Full mechanization for northward and eastward velocities
  3. Horizontal position update (Equation 5.56, lat/lon only):

    lat(t+dt) = lat(t) + [v_n / (R_N + h)] * dt
    lon(t+dt) = lon(t) + [v_e / ((R_E + h) * cos(lat))] * dt
    

Simplified vertical components (2.5D modification):

  1. Vertical velocity: Random walk model (NOT integrated from acceleration)

    v_v(t+dt) = v_v(t) + w_v * sqrt(dt)
    
    • w_v ~ N(0, σ_vv²) - large process noise
    • No deterministic integration of vertical specific force
    • Vertical velocity drifts according to process noise only
  2. Altitude update: Simple integration of vertical velocity

    h(t+dt) = h(t) + v_v(t) * dt
    
    • Uses trapezoidal rule: h(t+dt) = h(t) + 0.5 * [v_v(t) + v_v(t+dt)] * dt

Implementation Details

#![allow(unused)]
fn main() {
/// Modified forward propagation for 2.5D navigation.
///
/// Implements full strapdown mechanization for horizontal navigation (position,
/// velocity, attitude) while treating the vertical channel with simplified dynamics.
/// Vertical acceleration is NOT integrated into vertical velocity; instead, vertical
/// velocity follows a random walk model constrained by measurements and process noise.
///
/// # Arguments
/// * `state` - Mutable reference to the navigation state
/// * `imu_data` - Bias-corrected IMU measurements
/// * `dt` - Time step in seconds
///
/// # Mathematical Basis
/// - Horizontal: Full Groves Equations 5.46, 5.54, 5.56
/// - Vertical: Simplified dynamics, vertical velocity as random walk
pub fn forward_2_5d(state: &mut StrapdownState, imu_data: IMUData, dt: f64) {
    // See full implementation in source code
}
}

Key Differences from Standard forward()

ComponentStandard forward()forward_2_5d()
AttitudeFull mechanization (Eq 5.46)Same - Full mechanization
Specific force transformFull (Eq 5.47)Same - Full transform
Horizontal velocityFull mechanization (Eq 5.54)Same - Full mechanization
Vertical velocityIntegrated from vertical accelNOT integrated - random walk
Horizontal positionFull mechanization (Eq 5.56)Same - Full mechanization
AltitudeIntegrated from vertical velocitySame - Simple integration

Critical insight: The ONLY difference is that v_v(t+dt) ≈ v_v(t) in the propagation step. Changes to vertical velocity come entirely from:

  1. Process noise injection (large σ_vv)
  2. Measurement updates (GPS altitude/velocity)

This makes vertical velocity behave like a "free parameter" that is constrained by measurements rather than deterministic dynamics.

Integration with Existing Codebase

Mirroring UKF Structure

The particle filter implements the same NavigationFilter trait as UKF:

#![allow(unused)]
fn main() {
pub trait NavigationFilter {
    fn predict(&mut self, imu_data: IMUData, dt: f64);
    fn update<M: MeasurementModel + ?Sized>(&mut self, measurement: &M);
    fn get_estimate(&self) -> DVector<f64>;
    fn get_certainty(&self) -> DMatrix<f64>;  // Empirical covariance from particles
}
}

Simulation Integration

In sim.rs, add particle filter option to closed_loop function:

#![allow(unused)]
fn main() {
pub enum FilterType {
    UKF { alpha: f64, beta: f64, kappa: f64 },
    ParticleFilter {
        num_particles: usize,
        vertical_mode: VerticalChannelMode,
        resampling: ResamplingStrategy,
    },
}
}

Testing Strategy

Unit Tests

  • Test particle initialization from InitialState
  • Test process noise injection
  • Test resampling algorithms (verify particle diversity)
  • Test weight computation and normalization
  • Test vertical channel damping (if used)
  • Test state estimation with different averaging strategies

Integration Tests

  • Compare PF vs UKF on same trajectory
  • Test with GNSS dropout scenarios
  • Test vertical channel stability (hover, climb, descent scenarios)
  • Verify deterministic results with seeded RNG

Vertical Channel Tests

Create tests similar to UKF tests in kalman.rs:

  • pf_free_fall_motion() - vertical dynamics with no support
  • pf_hover_motion() - stationary altitude
  • pf_climb_descent() - vertical motion profile
  • pf_altitude_drift_during_outage() - verify controlled drift with 2.5D
  1. ✅ Define Particle struct (already done)
  2. Implement forward_2_5d() function in particle.rs
    • Copy attitude_update() from lib.rs or make it public
    • Implement velocity_update_horizontal() helper
    • Reuse position_update() from lib.rs (make public if needed)
  3. Implement ProcessNoise struct
  4. Implement ParticleFilter struct with basic initialization
  5. Implement predict() using forward_2_5d()
  6. Implement update() with systematic resampling
  7. Implement get_estimate() with weighted mean
  8. Write unit tests for each component
  9. Integration with sim.rs
  10. Test on real datasets
  11. (Optional) Implement third-order damping if 2.5D is insufficient

Tuning Guidance

Number of Particles

  • Start with 500-1000 particles for smartphone IMU
  • Increase to 2000-5000 for high-precision applications
  • Monitor computational cost vs. accuracy tradeoff

Process Noise (2.5D)

Conservative (high uncertainty, smooth vertical):

  • σ_h = 0.1 m, σ_vv = 0.2 m/s

Aggressive (low uncertainty, responsive vertical):

  • σ_h = 0.01 m, σ_vv = 0.05 m/s

Recommended starting point:

  • σ_h = 0.05 m, σ_vv = 0.1 m/s

Resampling Threshold

  • Effective particle threshold: 0.5 * num_particles
  • Resample more frequently if seeing particle degeneracy
  • Monitor n_eff over time to diagnose issues

Expected Performance

Advantages over UKF

  • Better handling of non-Gaussian distributions
  • More robust to outliers in measurements
  • Can represent multimodal posteriors (e.g., during ambiguous scenarios)
  • 2.5D approach naturally handles vertical channel uncertainty

Computational Cost

  • PF with N=1000 particles ≈ 10-50× slower than UKF
  • Use Rayon for parallel particle propagation if needed
  • Profile and optimize hot paths (propagation, resampling)

Memory Requirements

  • Each particle: ~200 bytes (15 states × 8 bytes + overhead)
  • 1000 particles ≈ 200 KB (negligible for modern systems)

References

  • Groves, P. D. (2013). Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems, 2nd Edition. Chapters 14-15 (INS error models, vertical channel instability).
  • Gustafsson, F., et al. (2002). "Particle filters for positioning, navigation, and tracking." IEEE Transactions on Signal Processing.
  • Existing kalman.rs implementation for architectural patterns.

Rao-Blackwellized Particle Filter

Measurement Models

Geophysical Navigation Overview

Gravity Anomaly Navigation

Magnetic Anomaly Navigation

Data Sources and Maps

Fault Simulation

Dropout Scenarios

Reduced Update Rates

Measurement Corruption

API Reference

earth Module

kalman Module

measurements Module

particles Module

sim Module

strapdown-sim Binary

strapdown-geonav

Example Configurations

Tutorial: Basic INS Simulation

Tutorial: GPS Degradation

Tutorial: Particle Filter

Contributing

Building and Testing

Building the Project

System Dependencies

The project requires the following system libraries:

  • pkg-config
  • libhdf5-dev and libhdf5-openmpi-dev (for HDF5 support)
  • libnetcdf-dev (for NetCDF geophysical data)
  • zlib1g-dev (for compression support)

On Ubuntu/Debian systems:

sudo apt update
sudo apt install -y pkg-config libhdf5-dev libhdf5-openmpi-dev libnetcdf-dev zlib1g-dev

Rust Toolchain

  • Minimum Rust version: 1.70+ (stable channel)
  • Required components: clippy, rustfmt

Building

Build the entire workspace:

cargo build --workspace --all-features

Build a specific crate:

cargo build -p strapdown-core
cargo build -p strapdown-sim
cargo build -p strapdown-geonav

Build with optimizations (release mode):

cargo build --workspace --all-features --release

Installing Binaries

Install the simulation binaries to your system:

# Install strapdown-sim
cargo install --path sim

# Install geonav-sim
cargo install --path geonav

Testing

Overview

The strapdown-rs project includes comprehensive unit tests, integration tests, and module-specific tests to validate the correctness of the navigation algorithms.

Running Tests

Run all tests in the workspace:

cargo test --workspace --all-features --verbose

Run tests for a specific crate:

cargo test -p strapdown-core
cargo test -p strapdown-sim
cargo test -p strapdown-geonav

Run with output visible:

cargo test -- --nocapture

Run tests sequentially (single-threaded):

cargo test -- --test-threads=1

Test Organization

Tests are organized into:

  • Unit tests: Inline in source files using #[cfg(test)] modules
  • Integration tests: Located in core/tests/integration_tests.rs
  • Module tests: Specific to individual modules

Integration Tests for INS Filters

The integration tests validate the entire INS filter pipeline using real sensor data. These tests are comprehensive and take longer to run than unit tests.

Test Data

The integration tests use real data collected from the Sensor Logger mobile application:

  • IMU measurements (accelerometer and gyroscope) at ~100 Hz
  • GNSS position and velocity measurements at ~1 Hz
  • Approximately 90 minutes of data with ~5366 samples
  • Dataset location: sim/data/test_data.csv

Error Metrics

Horizontal Position Error
  • Metric: Haversine distance between estimated and GNSS positions (meters)
  • Formula: Great-circle distance on Earth's surface
  • Purpose: Measures planar navigation accuracy
Altitude Error
  • Metric: Simple absolute difference (meters)
  • Purpose: Measures vertical navigation accuracy
Velocity Error
  • Metric: Component-wise absolute differences for north, east, and down velocities (m/s)
  • Purpose: Measures velocity estimation accuracy

Test Suite

1. test_dead_reckoning_on_real_data

Purpose: Establish baseline performance for pure INS dead reckoning without GNSS corrections.

What it tests:

  • Dead reckoning completes without crashes or errors
  • Navigation solution remains finite (no NaN or Inf values)
  • Provides baseline drift metrics for comparison

Expected behavior:

  • Significant drift over time (this is normal for MEMS IMUs)
  • All state values remain finite
  • Errors grow unbounded (no error thresholds enforced)

Typical results:

  • RMS horizontal error: ~7,000 km (expected drift for 90 minutes without corrections)
  • Test serves as a baseline to demonstrate the value of GNSS-aided navigation
2. test_ukf_closed_loop_on_real_data

Purpose: Validate UKF performance with full-rate GNSS measurements.

What it tests:

  • Closed-loop UKF completes successfully
  • Position errors remain bounded
  • Velocity and attitude estimates are stable
  • All values remain finite

Error thresholds:

  • RMS horizontal error < 30 m
  • RMS altitude error < 20 m
  • Maximum horizontal error < 100 m

Typical results:

  • RMS horizontal error: ~24 m
  • RMS altitude error: ~4 m
  • Mean velocity errors: <1 m/s

These are realistic values for consumer-grade MEMS IMU with smartphone GNSS.

3. test_ukf_with_degraded_gnss

Purpose: Validate UKF performance under degraded GNSS conditions (reduced update rate).

What it tests:

  • UKF handles reduced GNSS update rate (5-second intervals)
  • Errors are higher than full-rate but still bounded
  • Filter doesn't diverge between GNSS updates

Error thresholds:

  • RMS horizontal error < 50 m
  • Maximum horizontal error < 600 m

Typical results:

  • RMS horizontal error: ~28 m
  • Maximum horizontal error: ~553 m
  • Errors are larger due to drift between 5-second updates
4. test_ukf_outperforms_dead_reckoning

Purpose: Demonstrate that GNSS-aided UKF provides significant improvement over dead reckoning.

What it tests:

  • UKF with GNSS has lower errors than dead reckoning
  • GNSS corrections effectively bound error growth

Typical results:

  • Dead reckoning RMS error: ~7,100 km
  • UKF RMS error: ~24 m
  • Improvement: >99.99%

Running Integration Tests

Run all integration tests:

cd core
cargo test --test integration_tests

Run a specific test:

cd core
cargo test --test integration_tests test_ukf_closed_loop_on_real_data -- --nocapture

Run with output visible:

cd core  
cargo test --test integration_tests -- --nocapture

Run with single thread (sequential execution):

cd core
cargo test --test integration_tests -- --test-threads=1

Expected Runtime

The integration tests process real sensor data and run complex filters:

  • test_dead_reckoning_on_real_data: ~5 seconds
  • test_ukf_closed_loop_on_real_data: ~60-90 seconds
  • test_ukf_with_degraded_gnss: ~60-90 seconds
  • test_ukf_outperforms_dead_reckoning: ~120-180 seconds

Total runtime: ~4-5 minutes

Implementation Details

Error Metric Calculation

The compute_error_metrics() function:

  1. Matches navigation results to GNSS measurements by timestamp
  2. Skips invalid GNSS data (NaN values)
  3. Computes error for each matched sample
  4. Filters out non-finite errors
  5. Calculates mean, max, and RMS statistics
Filter Initialization

Tests use realistic initialization based on first GNSS measurement:

  • Position: First GNSS lat/lon/alt
  • Velocity: Computed from GNSS speed and bearing
  • Attitude: From phone orientation sensors
  • Covariances: Conservative initial uncertainties
  • Process noise: Tuned for MEMS IMU characteristics
Event Stream Generation

Tests use the build_event_stream() function to create a sequence of IMU propagation and GNSS update events from the raw data, with configurable scheduling and fault injection.

Future Test Enhancements

Potential improvements for the test suite:

  1. Additional test scenarios:

    • GNSS outages (DutyCycle scheduler)
    • Measurement corruption (fault models)
    • Different motion profiles
  2. More filters:

    • Particle filter integration tests
    • Extended Kalman Filter (when implemented)
  3. Performance benchmarks:

    • Execution time metrics
    • Memory usage tracking
    • Scalability tests
  4. Shorter test datasets:

    • Create focused test datasets for faster CI/CD
    • Keep full dataset for comprehensive validation

Linting and Formatting

Running Clippy

Clippy provides lint checks for Rust code:

cargo clippy --workspace --all-features

Address warnings and errors before committing.

Running rustfmt

Format code with rustfmt:

cargo fmt --all

Check formatting without making changes:

cargo fmt --all -- --check

Documentation

Building API Documentation

Generate documentation for all crates:

cargo doc --workspace --all-features --no-deps

Open the documentation in a browser:

cargo doc --workspace --all-features --no-deps --open

Building the Book

This user guide is built using mdBook. Install mdBook:

cargo install mdbook

Build the book:

cd book
mdbook build

Serve the book locally for development:

cd book
mdbook serve

Then open http://localhost:3000 in your browser.

Continuous Integration

The project uses GitHub Actions for continuous integration. See .github/workflows/ for workflow definitions:

  • rust.yml: Builds and tests the project
  • deploy-book.yml: Builds and deploys this documentation to GitHub Pages
  • publish.yml: Publishes crates to crates.io

References

  • Groves, P. D. (2013). Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems, 2nd ed.
  • Sensor Logger app: https://www.tszheichoi.com/sensorlogger

Architecture

Project Structure

Frequently Asked Questions

General Questions

What is Strapdown-rs?

Strapdown-rs is a Rust library for implementing strapdown inertial navigation systems (INS). It provides core functionality for processing IMU data to estimate position, velocity, and orientation.

Who should use Strapdown-rs?

Strapdown-rs is designed for:

  • Researchers working on navigation systems
  • Engineers developing autonomous systems
  • Students learning about inertial navigation
  • Anyone needing a high-performance INS implementation in Rust

What coordinate frames are supported?

The library primarily uses the North-East-Down (NED) local-level frame. The 9-state vector includes:

  • Position: latitude, longitude, altitude
  • Velocity: northward, eastward, downward
  • Attitude: roll, pitch, yaw

Is this production-ready?

Strapdown-rs is primarily intended for research and development. While the code is well-tested and prioritizes correctness, it is still under active development as part of ongoing PhD research.

Installation and Setup

What are the system requirements?

See the System Requirements page for detailed information. In summary:

  • Rust 1.70 or later
  • System libraries: HDF5, NetCDF, zlib
  • Supported on Linux, macOS, and Windows

Why do I need HDF5 and NetCDF?

These libraries are required for:

  • HDF5: Data storage and processing
  • NetCDF: Geophysical map data for geonav features

If you only need the core INS functionality, you can disable these features in your Cargo.toml.

How do I install on Windows?

Windows support requires additional setup. We recommend either:

  1. Using vcpkg to install dependencies
  2. Using WSL2 for a native Linux environment

See Installation for details.

Usage Questions

What data format does strapdown-sim accept?

The simulation expects CSV files with IMU and GNSS data following the Sensor Logger app format. See Input Data Format for details.

Which filter should I use: EKF, UKF, or Particle Filter?

  • EKF: Fastest, works well for mildly nonlinear systems
  • UKF: Better accuracy for highly nonlinear systems, 2-3x slower than EKF
  • Particle Filter: Best for non-Gaussian distributions and multimodal scenarios

See Filter Comparison for detailed analysis.

Can I use my own sensor data?

Yes! You'll need to convert your data to the expected CSV format. The library is designed to work with standard IMU (gyroscope and accelerometer) and GNSS (position) measurements.

How do I simulate GNSS outages?

Use the GNSS fault simulation features in the configuration file:

[gnss]
dropout_probability = 0.1  # 10% chance of dropout
reduced_update_rate = 0.5  # Half the normal rate

See GNSS Degradation Scenarios for more options.

Performance Questions

How fast is Strapdown-rs?

Performance varies by filter type and configuration:

  • EKF: ~10,000-20,000 updates/second
  • UKF: ~3,000-5,000 updates/second
  • Particle Filter: Depends on particle count (100 particles: ~500 updates/second)

These are approximate figures on modern hardware and will vary based on your system.

Can I run simulations in parallel?

The current implementation processes data sequentially as navigation is inherently a sequential process. However, the particle filter implementation can utilize multiple cores for particle processing.

How much memory does it use?

Memory usage is modest:

  • Core library: ~10-50 MB
  • Simulations: Depends on data size and filter configuration
  • Particle filters: Linear with particle count

Development Questions

How can I contribute?

We welcome contributions! Please see the Contributing Guide and reach out to the project maintainer before starting major work.

Where is the API documentation?

Full API documentation is available at docs.rs/strapdown-core. This book focuses on high-level concepts and usage patterns.

Can I use this in my commercial project?

Yes! Strapdown-rs is licensed under the MIT License, which allows commercial use. See the LICENSE file for details.

How do I cite this work?

If you use Strapdown-rs in your research, please cite the JOSS paper:

JOSS

Troubleshooting

I'm getting linking errors during build

This usually means HDF5 or NetCDF libraries aren't found. Ensure:

  1. Libraries are installed: pkg-config --modversion hdf5
  2. PKG_CONFIG_PATH is set correctly
  3. Development headers are installed (-dev packages on Linux)

See Installation Troubleshooting for more help.

My simulation produces NaN values

Common causes:

  • Invalid initial conditions
  • IMU data with unrealistic values
  • Numerical instability in filter

Check your input data and initial state. Enable debug logging with --log-level debug to investigate.

The particle filter is very slow

This is expected with high particle counts. Consider:

  • Reducing the number of particles
  • Using the RBPF (Rao-Blackwellized) variant
  • Using EKF/UKF instead if appropriate

Where can I get help?

  • Check this FAQ and the user guide
  • Search existing GitHub Issues
  • Open a new issue if you've found a bug
  • Contact the maintainer for research collaborations

Additional Questions?

If your question isn't answered here, please:

  1. Check the User Guide
  2. Review the API Reference
  3. Open an issue on GitHub

Publications

External Links

Glossary