This document describes the mathematical models, filter algorithms, and software
architecture of the layered inertial navigation estimator implemented in
sim/estimation/. It is intended as a self-contained reference for anyone who
needs to understand what the code is doing, tune noise parameters, or extend the
estimator with new sensor models.
The estimator reconstructs a rocket's full 15-degree-of-freedom kinematic state (orientation, gyro bias, position, velocity, accelerometer bias) from noisy sensor measurements. It is organized as two decoupled Kalman filters that run in series at every timestep:
Raw IMU (accel + gyro) + dt
│
▼
┌─────────────────────┐
│ Attitude ESKF │ 6 DoF error-state filter on SO(3)
│ q, b_g │ corrected by gravity alignment
└────────┬────────────┘
│ extract R_b→i (body-to-inertial rotation)
▼
┌─────────────────────┐
│ Navigation EKF │ 9 DoF Euclidean filter
│ p, v, b_a │ corrected by GNSS + barometer
└────────┬────────────┘
│
▼
Final state + covariance + diagnostics
Key design rule: The generic filter engines (GenericEKF, GenericESKF)
know nothing about rockets, flight phases, or sensor hardware. All
domain-specific logic lives in the adapter layer (sim/estimation/adapters/).
The attitude filter tracks orientation and gyroscope bias.
| Component | Symbol | Dimension | Units |
|---|---|---|---|
| Quaternion (scalar-first) | 4 | — | |
| Gyro bias | 3 | rad/s |
Because quaternions live on the
where
The navigation filter is a conventional Euclidean EKF:
| Component | Symbol | Dimension | Units |
|---|---|---|---|
| Position | 3 | m | |
| Velocity | 3 | m/s | |
| Accel bias | 3 | m/s² |
This layer receives the rotation matrix
At every IMU sample, two prediction steps execute in sequence.
Given gyroscope measurement
Quaternion propagation (right-multiplicative):
where
Bias propagation (random walk):
Error dynamics Jacobian (continuous-time, then discretized
where
Process noise Jacobian:
Process noise covariance (continuous-time white noise scaled by
| Parameter | Symbol | Default | Units |
|---|---|---|---|
| Gyro noise density | rad/s | ||
| Gyro bias random walk | rad/s |
Covariance propagation:
Given accelerometer measurement
Inertial acceleration:
where
Kinematic integration:
State transition Jacobian:
Process noise covariance (continuous-time noise model integrating position-velocity correlation):
| Parameter | Symbol | Default | Units |
|---|---|---|---|
| Accel noise density | m/s² | ||
| Accel bias random walk | m/s² |
All measurement updates share the same generic update equations. The difference between the EKF and ESKF is what happens after the Kalman gain is applied.
For a measurement
Innovation:
Innovation covariance:
Kalman gain (computed via linear solve for numerical stability):
State correction:
or in the ESKF case,
Covariance update (Joseph form):
The Joseph form is algebraically equivalent to the textbook
Before applying any update, the filter computes the Mahalanobis distance:
If
After the attitude ESKF computes an error-state correction
The covariance is then reset to account for the manifold geometry after injection:
where the reset Jacobian is:
This accounts for the first-order change of coordinates on
Uses the accelerometer to observe the direction of gravity in the body frame.
Predicted measurement:
Innovation:
Measurement Jacobian (3 × 6):
The skew-symmetric matrix
Noise:
Important: This measurement is only valid when the accelerometer reads primarily gravity. During powered flight (motor burning), thrust dominates the reading and the measurement is suppressed by the flight-phase policy.
Predicted measurement:
Measurement Jacobian (3 × 9):
Noise:
Geodetic GNSS coordinates (lat, lon, alt) are converted to a local East-North-Up (ENU) frame anchored at the first GNSS fix before being passed to the filter.
GNSS velocity is derived by finite-differencing consecutive GNSS position fixes. This is an approximation, but provides useful velocity observability between direct velocity measurements.
Predicted measurement:
Measurement Jacobian (3 × 9):
Noise:
Barometric pressure is converted to altitude using the ISA troposphere model:
where
Predicted measurement:
Measurement Jacobian (1 × 9):
Noise:
The flight-phase detector is an adapter-layer finite state machine that runs outside the generic estimator. Its only effect is gating which measurements are submitted to the attitude filter.
ON_PAD ──────────────────────► POWERED_ASCENT
|a| − g > 15 m/s² │
│ |a| − g < 5 m/s²
▼ (hysteresis)
COAST ◄──────────────┐
│ |a|−g > 15 │
│ (re-ignition) │
│ │
│ vz < −2 m/s
▼
DESCENT (terminal)
| Transition | Condition | Rationale |
|---|---|---|
| PAD → POWERED | Accel excess > 15 m/s² | Conservative; sounding rockets produce >50 m/s² excess |
| POWERED → COAST | Accel excess < 5 m/s² | Hysteresis prevents chatter near burnout |
| COAST → DESCENT | Downward velocity > 2 m/s | Confirms rocket is past apogee |
| Phase | Submit gravity update? | Reason |
|---|---|---|
| ON_PAD | Yes | Accelerometer reads pure gravity |
| POWERED_ASCENT | No | Thrust dominates; would corrupt attitude |
| COAST | Yes | Only gravity + drag (aero loads small vs. gravity) |
| DESCENT | Yes | Gravity-dominated again |
The LayeredNavigationStack in sim/estimation/stacks/layered_navigation.py
wires the two filters together. The layers are block-diagonal — there is
no cross-covariance between attitude and navigation. The only coupling is the
rotation matrix passed from attitude to navigation at prediction time.
where
| State | Initial |
|---|---|
| Attitude |
|
| Gyro bias |
|
| Position |
|
| Velocity |
|
| Accel bias |
|
These are intentionally conservative. The filter is designed to converge from wide initial uncertainty rather than requiring precise initialization.
# 1. Attitude predict: integrate gyroscope
attitude_eskf.predict(gyroscope, dt)
# 2. Extract rotation for navigation
R_b2i = quaternion_to_rotation_matrix(attitude_state.quaternion)
# 3. Navigation predict: strapdown integration with corrected accel
navigation_ekf.predict(accelerometer, R_b2i, dt)
# 4. Attitude update: gravity alignment (if flight phase allows)
if policy.submit_update:
attitude_eskf.update(gravity_alignment_model, accelerometer)
# 5. Navigation updates: barometer, GNSS position, GNSS velocity
navigation_ekf.update(baro_model, baro_altitude)
navigation_ekf.update(gps_position_model, gnss_position)
navigation_ekf.update(gps_velocity_model, gnss_velocity)The implementation is organized into a strict layered file structure where dependencies only flow inward (adapters → stacks → models/measurements → core → math):
sim/estimation/
core/ Generic filter engines (EKF, ESKF, Gaussian utilities)
ekf.py GenericEKF — predict/update for Euclidean states
eskf.py GenericESKF — predict/inject/reset for manifold states
gaussian.py Joseph update, Mahalanobis gating, covariance utilities
base.py Protocol definitions (ProcessModel, MeasurementModel)
types.py Type aliases (StateT, Vector, Matrix)
math/ Rotation and manifold helpers
quaternion.py Multiply, normalize, inverse, exp map, q→R conversion
rotation.py Skew-symmetric matrix
jacobians.py Finite-difference Jacobian (test utility only)
models/ Process models (state propagation + Jacobians)
strapdown.py Combined 9-DoF strapdown inertial model
attitude.py 6-DoF attitude ESKF model (quaternion + gyro bias)
navigation.py 9-DoF navigation EKF model (position + velocity + accel bias)
measurements/ Sensor measurement models (h(x), H, R)
imu_gravity.py Gravity alignment from accelerometer
gps_position.py GNSS 3D position observation
gps_velocity.py GNSS 3D velocity observation
baro_altitude.py Barometric altitude from pressure sensor
policies/ Optional external gating and scheduling
gating.py Mahalanobis-distance measurement gate
stacks/ Composition layers
layered_navigation.py Wires attitude ESKF + navigation EKF
adapters/ RocketPy-specific (not imported by core)
rocketpy_replay.py Telemetry CSV replay, geodetic conversion,
barometer conversion, auto-tuning
rocket_flight_phase.py Flight-phase FSM, gravity-update policy
Boundary rule: The core/, math/, models/, measurements/, policies/,
and stacks/ packages must be importable and usable without loading anything
from adapters/. This is enforced by a test that imports the core stack in a
subprocess and asserts that no adapter module appears in sys.modules.
| Want to... | Adjust | Direction |
|---|---|---|
| Track GNSS more tightly | gnss_position_std_m |
Decrease |
| Trust IMU integration more | accel_noise_density / gyro_noise_density |
Decrease |
| Allow faster bias convergence | accel_bias_random_walk / gyro_bias_random_walk |
Increase |
| Smooth through GNSS dropouts | gnss_position_std_m |
Increase |
| Reduce attitude drift | gyro_bias_random_walk |
Increase (allows faster bias tracking) |
| Trust barometer more for altitude | baro_altitude_std_m |
Decrease |
| Parameter | Default | Effect |
|---|---|---|
powered_accel_excess_mps2 |
15 m/s² | Higher → delays POWERED detection (more conservative) |
burnout_accel_hysteresis_mps2 |
5 m/s² | Lower → faster COAST detection after burnout |
descent_velocity_threshold_mps |
2 m/s | Higher → delays DESCENT detection |
| Symbol | Meaning |
|---|---|
| Unit quaternion (scalar-first: |
|
| Quaternion multiplication | |
| Rotation vector → quaternion via exponential map | |
| Rotation matrix (body-to-inertial) from quaternion | |
|
|
|
| Small-angle rotation error vector (tangent space of $SO(3)$) | |
| State error covariance matrix | |
| Kalman gain | |
| Innovation covariance | |
| Measurement Jacobian ( |
|
| State transition Jacobian (discrete) | |
| Process noise input Jacobian | |
| Process noise covariance | |
| Measurement noise covariance |