Skip to main content

kinavis_ins/
lib.rs

1//! Strapdown inertial navigation over the KINAVIS kernel.
2//!
3//! Integrating IMU rate and specific force gives attitude, velocity and
4//! position without external aids, but sensor biases integrate too: 1°/h of
5//! gyro drift becomes about a mile of position error in half an hour. The
6//! system therefore has two parts:
7//!
8//! 1. **Mechanisation**, [`Strapdown`]: NED integration including Earth rate,
9//!    transport rate, gravity and Coriolis.
10//! 2. **Error-state filter**, [`InsFilter`]: 15 states (position, velocity,
11//!    attitude, gyro bias, accelerometer bias errors) estimated from available
12//!    aiding — GNSS position or velocity, heading, zero-velocity — and fed back
13//!    into the mechanisation to bound drift.
14//!
15//! Outputs are kernel types: [`Position`], a [`Vector3`] of [`Speed`], an
16//! [`Attitude`] with yaw as [`TrueCourse`], plus sigmas and the error ellipse;
17//! never the covariance matrix. [`InsMotion`] is a process model for the
18//! six-state estimator in `kinavis`, propagating position between fixes with
19//! the INS velocity.
20//!
21//! No allocation, no panics on any input; builds for bare-metal targets and CI
22//! checks the strict-profile build for panic paths. Filter consistency is
23//! verified by Monte Carlo tests: over a passage with a turn, NEES (15 states)
24//! and NIS of position fixes fall within their χ² intervals.
25//!
26//! ```rust
27//! use core::time::Duration;
28//! use kinavis_ins::{
29//!     gravity_down, GatingPolicy, ImuNoise, ImuSample, InsFilter, InsPriors, Quaternion,
30//!     Strapdown, EARTH_RATE,
31//! };
32//! use kinavis_kernel::{
33//!     Angle, Distance, GeodeticPoint, Height, Instant, Ned, Speed, TrueCourse, Utc, Vector3,
34//! };
35//!
36//! // Alongside at 50°45.3'N, heading 037° by the gyrocompass, level.
37//! let start = Instant::<Utc>::from_unix_seconds(1_789_000_000);
38//! let berth = GeodeticPoint::new(
39//!     "50°45.3'N 001°20.0'W".parse()?,
40//!     Height::above_ellipsoid(Distance::ZERO),
41//! );
42//! let attitude = Quaternion::from_euler(Angle::ZERO, Angle::ZERO, TrueCourse::new(37.0)?);
43//! let still = Vector3::<Ned, Speed>::new(Speed::ZERO, Speed::ZERO, Speed::ZERO);
44//! let mut ins = InsFilter::new(
45//!     Strapdown::new(start, berth, still, attitude)?,
46//!     ImuNoise::mems(),
47//!     &InsPriors::standard(),
48//! );
49//!
50//! // What the IMU reads at rest: the Earth turning, and minus gravity.
51//! let latitude = 50.755_f64.to_radians();
52//! let earth = [EARTH_RATE * latitude.cos(), 0.0, -EARTH_RATE * latitude.sin()];
53//! let gravity = [0.0, 0.0, -gravity_down(latitude.sin(), 0.0)];
54//! let sample = ImuSample::new(
55//!     attitude.rotate_back(earth),
56//!     attitude.rotate_back(gravity),
57//!     Duration::from_millis(100),
58//! )?;
59//!
60//! // A second of samples; then the gyrocompass says 037.2°, and the
61//! // mooring lines say the vessel is not moving.
62//! for _ in 0..10 {
63//!     ins.predict(&sample)?;
64//! }
65//! ins.update_heading(TrueCourse::new(37.2)?, Angle::from_degrees(0.5)?, GatingPolicy::none())?;
66//! ins.update_zero_velocity(Speed::from_metres_per_second(0.02)?, GatingPolicy::none())?;
67//!
68//! assert!((ins.attitude().yaw.degrees() - 37.2).abs() < 0.1);
69//! assert!(ins.heading_sigma().degrees() < 0.5);
70//! assert!(ins.velocity().magnitude().metres_per_second() < 0.02);
71//! # Ok::<(), Box<dyn std::error::Error>>(())
72//! ```
73//!
74//! [`Position`]: kinavis_kernel::Position
75//! [`Vector3`]: kinavis_kernel::Vector3
76//! [`Speed`]: kinavis_kernel::Speed
77//! [`TrueCourse`]: kinavis_kernel::TrueCourse
78//!
79//! # Not implemented
80//!
81//! - Initial alignment (levelling on gravity, gyrocompassing on Earth rate):
82//!   the caller supplies the initial attitude (gyrocompass and level, or a
83//!   previous run) and the filter refines it.
84//! - IMU-to-antenna lever arm.
85//! - Coning and sculling corrections: a marine IMU at tens of hertz on a slow
86//!   vessel is within tolerance without them.
87//!
88//! # Feature flags
89//!
90//! - `std` *(default)* — standard library maths in the kernel.
91//! - `libm` — for `no_std` targets: `--no-default-features --features libm`.
92//! - `serde` — serialisation of the value types.
93
94#![cfg_attr(not(feature = "std"), no_std)]
95
96#[cfg(test)]
97extern crate std;
98
99mod attitude;
100mod filter;
101mod imu;
102mod mechanisation;
103mod motion;
104
105pub use attitude::{Attitude, Quaternion};
106pub use filter::{InsFilter, InsPriors, InsUpdate, ERROR_STATE_DIM};
107pub use imu::{ImuNoise, ImuSample};
108pub use mechanisation::{gravity_down, Strapdown, EARTH_RATE};
109pub use motion::InsMotion;
110
111// Re-exported so callers need not depend on the kernel for the gate type.
112pub use kinavis_kernel::estimation::GatingPolicy;
113
114/// Runs the `README.md` example as a doctest.
115#[cfg(doctest)]
116#[doc = include_str!("../README.md")]
117pub struct ReadmeExamples;