use nalgebra::{Matrix3, SMatrix, SVector, Vector3};
use crate::frames::{Body, Ned};
use crate::math::skew;
use crate::state::{ErrorState, STATES, State};
use crate::units::{AngularRate, Position, PositionNoise, Velocity, VelocityNoise};
use crate::update::Observation;
fn arm(state: &State, antenna: Position<Body>) -> (Vector3<f32>, Matrix3<f32>) {
let r = state
.attitude
.quaternion()
.to_rotation_matrix()
.into_inner();
let arm = antenna.vector();
(r * arm, -r * skew(arm))
}
pub(crate) fn horizontal_jacobian() -> SMatrix<f32, 2, STATES> {
let mut h = SMatrix::<f32, 2, STATES>::zeros();
h[(0, ErrorState::PositionNorth.index())] = 1.0;
h[(1, ErrorState::PositionEast.index())] = 1.0;
h
}
pub(crate) fn height_jacobian() -> SMatrix<f32, 1, STATES> {
let mut h = SMatrix::<f32, 1, STATES>::zeros();
h[(0, ErrorState::PositionDown.index())] = 1.0;
h
}
pub(crate) fn horizontal_observation(
state: &State,
fix: Position<Ned>,
noise: PositionNoise<Ned>,
antenna: Position<Body>,
) -> Observation<2> {
let (offset, attitude) = arm(state, antenna);
let y = fix.vector() - (state.position.vector() + offset);
let r_m = noise.variance();
let r_m = SVector::<f32, 2>::new(r_m[0], r_m[1]);
let mut h = horizontal_jacobian();
h.fixed_view_mut::<2, 3>(0, ErrorState::AttitudeX.index())
.copy_from(&attitude.fixed_rows::<2>(0));
Observation {
y: SVector::<f32, 2>::new(y[0], y[1]),
h,
h_b: SVector::<f32, 2>::zeros(),
r_m,
r_gain: r_m,
}
}
pub(crate) fn height_observation(
state: &State,
fix: Position<Ned>,
noise: PositionNoise<Ned>,
antenna: Position<Body>,
) -> Observation<1> {
let (offset, attitude) = arm(state, antenna);
let r_m = SVector::<f32, 1>::new(noise.variance()[2]);
let mut h = height_jacobian();
h.fixed_view_mut::<1, 3>(0, ErrorState::AttitudeX.index())
.copy_from(&attitude.fixed_rows::<1>(2));
Observation {
y: SVector::<f32, 1>::new(fix.vector()[2] - (state.position.vector()[2] + offset[2])),
h,
h_b: SVector::<f32, 1>::zeros(),
r_m,
r_gain: r_m,
}
}
pub(crate) fn velocity_jacobian() -> SMatrix<f32, 3, STATES> {
let mut h = SMatrix::<f32, 3, STATES>::zeros();
h.fixed_view_mut::<3, 3>(0, ErrorState::VelocityNorth.index())
.copy_from(&Matrix3::identity());
h
}
pub(crate) fn velocity_observation(
state: &State,
solution: Velocity<Ned>,
noise: VelocityNoise<Ned>,
antenna: Position<Body>,
omega: AngularRate<Body>,
) -> Observation<3> {
let r = state
.attitude
.quaternion()
.to_rotation_matrix()
.into_inner();
let (arm, turning) = (antenna.vector(), omega.vector().cross(&antenna.vector()));
let mut h = velocity_jacobian();
h.fixed_view_mut::<3, 3>(0, ErrorState::AttitudeX.index())
.copy_from(&(-r * skew(turning)));
h.fixed_view_mut::<3, 3>(0, ErrorState::GyroBiasX.index())
.copy_from(&(r * skew(arm)));
Observation {
y: solution.vector() - (state.velocity.vector() + r * turning),
h,
h_b: SVector::<f32, 3>::zeros(),
r_m: noise.variance().into(),
r_gain: noise.variance().into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{Vector2, Vector3};
#[test]
fn the_two_halves_select_position_and_nothing_else() {
let dx = SVector::<f32, STATES>::from_fn(|i, _| i as f32 + 1.0);
assert_eq!(horizontal_jacobian() * dx, Vector2::new(1.0, 2.0));
assert_eq!((height_jacobian() * dx)[0], 3.0);
}
#[test]
fn the_velocity_jacobian_selects_velocity_and_nothing_else() {
let dx = SVector::<f32, STATES>::from_fn(|i, _| i as f32 + 1.0);
assert_eq!(velocity_jacobian() * dx, Vector3::new(4.0, 5.0, 6.0));
}
#[test]
fn the_innovation_is_the_solution_less_the_estimated_velocity() {
let state = State {
velocity: Velocity::ned(3.0, -1.0, 0.5),
..State::default()
};
let observation = velocity_observation(
&state,
Velocity::ned(3.5, -1.0, 0.0),
VelocityNoise::from_sigma(0.5, 0.25, 2.0),
Position::zero(),
AngularRate::body(0.3, -0.2, 0.1),
);
assert_eq!(observation.y, Vector3::new(0.5, 0.0, -0.5));
assert_eq!(observation.r_m, Vector3::new(0.25, 0.0625, 4.0));
}
#[test]
fn the_innovation_is_the_fix_less_the_estimate() {
let state = State {
position: Position::ned(10.0, -5.0, 2.0),
..State::default()
};
let (fix, noise) = (
Position::ned(11.0, -5.5, 2.5),
PositionNoise::from_sigma(1.0, 2.0, 3.0),
);
let horizontal = horizontal_observation(&state, fix, noise, Position::zero());
assert_eq!(horizontal.y, Vector2::new(1.0, -0.5));
assert_eq!(horizontal.r_m, Vector2::new(1.0, 4.0));
let height = height_observation(&state, fix, noise, Position::zero());
assert_eq!(height.y[0], 0.5);
assert_eq!(height.r_m[0], 9.0);
}
fn tilted() -> State {
State {
attitude: crate::units::Attitude::from_quaternion(
nalgebra::UnitQuaternion::from_euler_angles(0.3, -0.2, 1.1),
),
gyro_bias: AngularRate::body(0.01, -0.02, 0.005),
..State::default()
}
}
fn antenna_at(state: &State, arm: Vector3<f32>, omega: Vector3<f32>) -> [Vector3<f32>; 2] {
let r = state
.attitude
.quaternion()
.to_rotation_matrix()
.into_inner();
[
state.position.vector() + r * arm,
state.velocity.vector() + r * omega.cross(&arm),
]
}
#[test]
fn the_lever_arm_jacobians_match_a_numerical_derivative() {
let state = tilted();
let (arm, measured) = (Vector3::new(0.4, -0.3, -0.5), Vector3::new(0.8, -0.5, 1.2));
let omega = measured - state.gyro_bias.vector();
let antenna = Position::<Body>::from_vector(arm);
let noise = PositionNoise::from_sigma(1.0, 1.0, 1.0);
let at = |state: &State| {
let omega = measured - state.gyro_bias.vector();
antenna_at(state, arm, omega)
};
let (horizontal, height) = (
horizontal_observation(&state, Position::zero(), noise, antenna),
height_observation(&state, Position::zero(), noise, antenna),
);
let velocity = velocity_observation(
&state,
Velocity::zero(),
VelocityNoise::from_sigma(1.0, 1.0, 1.0),
antenna,
AngularRate::from_vector(omega),
);
let step = 1e-3;
for axis in 0..3 {
let turn = |sign: f32| {
let mut e = Vector3::zeros();
e[axis] = sign * step;
State {
attitude: crate::units::Attitude::from_quaternion(
state.attitude.quaternion() * crate::math::exp_quat(e),
),
..state
}
};
let bias = |sign: f32| {
let mut e = Vector3::zeros();
e[axis] = sign * step;
State {
gyro_bias: AngularRate::from_vector(state.gyro_bias.vector() + e),
..state
}
};
let [p_plus, v_plus] = at(&turn(1.0));
let [p_minus, v_minus] = at(&turn(-1.0));
let d_position = (p_plus - p_minus) / (2.0 * step);
let d_velocity = (v_plus - v_minus) / (2.0 * step);
let [_, vb_plus] = at(&bias(1.0));
let [_, vb_minus] = at(&bias(-1.0));
let d_bias = (vb_plus - vb_minus) / (2.0 * step);
let theta = ErrorState::AttitudeX.index() + axis;
let beta = ErrorState::GyroBiasX.index() + axis;
for row in 0..2 {
assert!((horizontal.h[(row, theta)] - d_position[row]).abs() < 1e-3);
}
assert!((height.h[(0, theta)] - d_position[2]).abs() < 1e-3);
for row in 0..3 {
assert!((velocity.h[(row, theta)] - d_velocity[row]).abs() < 1e-3);
assert!((velocity.h[(row, beta)] - d_bias[row]).abs() < 1e-3);
}
}
let [p, v] = antenna_at(&state, arm, omega);
assert!((horizontal.y - Vector2::new(-p[0], -p[1])).norm() < 1e-6);
assert!((height.y[0] + p[2]).abs() < 1e-6);
assert!((velocity.y + v).norm() < 1e-6);
}
#[test]
fn a_zero_arm_is_equations_28_and_29_exactly() {
let state = tilted();
let noise = PositionNoise::from_sigma(1.0, 1.0, 1.0);
let fix = Position::ned(1.0, 2.0, 3.0);
assert_eq!(
horizontal_observation(&state, fix, noise, Position::zero()).h,
horizontal_jacobian()
);
assert_eq!(
height_observation(&state, fix, noise, Position::zero()).h,
height_jacobian()
);
let velocity = velocity_observation(
&state,
Velocity::ned(1.0, 0.0, 0.0),
VelocityNoise::from_sigma(1.0, 1.0, 1.0),
Position::zero(),
AngularRate::body(1.0, 2.0, 3.0),
);
assert_eq!(velocity.h, velocity_jacobian());
assert_eq!(velocity.y, Vector3::new(1.0, 0.0, 0.0));
}
}