use arika::epoch::Epoch;
use super::noise::NoiseModel;
use crate::SpacecraftState;
use crate::plugin::tick_input::AngularVelocityBody;
pub struct Gyroscope {
noise: Vec<Box<dyn NoiseModel>>,
}
impl Gyroscope {
pub fn new() -> Self {
Self { noise: Vec::new() }
}
pub fn with_noise(mut self, noise: impl NoiseModel + 'static) -> Self {
self.noise.push(Box::new(noise));
self
}
pub fn measure(&mut self, state: &SpacecraftState, _epoch: &Epoch) -> AngularVelocityBody {
let mut omega = state.attitude.angular_velocity;
for n in &mut self.noise {
omega = n.apply(omega);
}
AngularVelocityBody::new(arika::frame::Vec3::from_raw(omega))
}
}
impl Default for Gyroscope {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attitude::AttitudeState;
use crate::orbital::OrbitalState;
use crate::sensor::noise::{BiasRandomWalk, GaussianNoise};
use nalgebra::{Vector3, Vector4};
fn make_state(omega: Vector3<f64>) -> SpacecraftState {
SpacecraftState {
orbit: OrbitalState::new(Vector3::new(7000.0, 0.0, 0.0), Vector3::new(0.0, 7.5, 0.0)),
attitude: AttitudeState {
quaternion: Vector4::new(1.0, 0.0, 0.0, 0.0),
angular_velocity: omega,
},
mass: 50.0,
}
}
#[test]
fn ideal_gyroscope_returns_true_angular_velocity() {
let mut gyro = Gyroscope::new();
let omega = Vector3::new(0.1, 0.05, -0.03);
let state = make_state(omega);
let epoch = Epoch::j2000();
assert_eq!(
gyro.measure(&state, &epoch).into_inner().into_inner(),
omega
);
}
#[test]
fn noisy_gyroscope_differs_from_true() {
let mut gyro = Gyroscope::new().with_noise(GaussianNoise::isotropic(1e-3, 42));
let omega = Vector3::new(0.1, 0.05, -0.03);
let state = make_state(omega);
let epoch = Epoch::j2000();
let measured = gyro.measure(&state, &epoch).into_inner().into_inner();
assert!((measured - omega).magnitude() > 0.0);
assert!((measured - omega).magnitude() < 0.1);
}
#[test]
fn chained_noise_models() {
let mut gyro = Gyroscope::new()
.with_noise(GaussianNoise::isotropic(1e-4, 42))
.with_noise(BiasRandomWalk::isotropic(1e-5, 1.0, 99));
let omega = Vector3::new(0.1, 0.05, -0.03);
let state = make_state(omega);
let epoch = Epoch::j2000();
let m1 = gyro.measure(&state, &epoch).into_inner();
let m2 = gyro.measure(&state, &epoch).into_inner();
assert_ne!(m1, m2);
}
#[test]
fn noisy_gyroscope_is_deterministic() {
let omega = Vector3::new(0.1, 0.05, -0.03);
let state = make_state(omega);
let epoch = Epoch::j2000();
let mut g1 = Gyroscope::new().with_noise(GaussianNoise::isotropic(1e-3, 42));
let mut g2 = Gyroscope::new().with_noise(GaussianNoise::isotropic(1e-3, 42));
assert_eq!(g1.measure(&state, &epoch), g2.measure(&state, &epoch));
}
}