use std::f64::consts::PI;
const C: f64 = 2.997_924_58e8;
const H_PLANCK: f64 = 6.626_070_15e-34;
pub const EARTH_ROTATION_RATE_RAD_S: f64 = 7.292_115e-5;
#[derive(Debug, Clone)]
pub struct FiberOpticGyroscope {
pub coil_radius_m: f64,
pub fiber_length_m: f64,
pub wavelength: f64,
pub n_turns: usize,
pub photodetector_noise: f64,
}
impl FiberOpticGyroscope {
pub fn new(coil_radius: f64, n_turns: usize, fiber_length: f64, wavelength: f64) -> Self {
let computed_length = if fiber_length <= 0.0 {
2.0 * PI * coil_radius * n_turns as f64
} else {
fiber_length
};
Self {
coil_radius_m: coil_radius,
fiber_length_m: computed_length,
wavelength,
n_turns,
photodetector_noise: 1e-12,
}
}
fn coil_area_m2(&self) -> f64 {
PI * self.coil_radius_m * self.coil_radius_m * self.n_turns as f64
}
pub fn sagnac_phase(&self, rotation_rate_rad_s: f64) -> f64 {
4.0 * PI * self.coil_area_m2() * rotation_rate_rad_s / (self.wavelength * C)
}
pub fn scale_factor(&self) -> f64 {
4.0 * PI * self.coil_area_m2() / (self.wavelength * C)
}
pub fn rotation_rate(&self, delta_phase_rad: f64) -> f64 {
let s = self.scale_factor();
if s.abs() < f64::EPSILON {
return 0.0;
}
delta_phase_rad / s
}
pub fn noise_equivalent_rotation(&self, optical_power_w: f64) -> f64 {
if optical_power_w <= 0.0 {
return f64::INFINITY;
}
let freq = C / self.wavelength;
let h_nu = H_PLANCK * freq;
let delta_phi = (2.0 * h_nu / optical_power_w).sqrt();
let s = self.scale_factor();
if s.abs() < f64::EPSILON {
return f64::INFINITY;
}
delta_phi / s
}
pub fn bias_stability_deg_per_hr(&self) -> f64 {
0.001_f64 * (1000.0 / self.fiber_length_m).sqrt()
}
pub fn earth_rotation_measurable(&self, power_w: f64) -> bool {
self.noise_equivalent_rotation(power_w) < EARTH_ROTATION_RATE_RAD_S
}
pub fn lock_in_threshold_rad_s(&self) -> f64 {
let typical_backscatter_phase = 1e-7_f64; let s = self.scale_factor();
if s.abs() < f64::EPSILON {
return 0.0;
}
typical_backscatter_phase / s
}
}
#[derive(Debug, Clone)]
pub struct RingLaserGyroscope {
pub cavity_area_m2: f64,
pub perimeter_m: f64,
pub wavelength: f64,
pub q_factor: f64,
}
impl RingLaserGyroscope {
pub fn new(side_length_m: f64, wavelength: f64) -> Self {
Self {
cavity_area_m2: side_length_m * side_length_m,
perimeter_m: 4.0 * side_length_m,
wavelength,
q_factor: 1e12,
}
}
pub fn beat_frequency_hz(&self, rotation_rate_rad_s: f64) -> f64 {
4.0 * self.cavity_area_m2 * rotation_rate_rad_s / (self.wavelength * self.perimeter_m)
}
pub fn scale_factor(&self) -> f64 {
4.0 * self.cavity_area_m2 / (self.wavelength * self.perimeter_m)
}
pub fn lock_in_rate_rad_s(&self) -> f64 {
let tau_rt = self.perimeter_m / C; self.perimeter_m / (4.0 * self.cavity_area_m2 * tau_rt * self.q_factor * 2.0 * PI)
}
pub fn sensitivity_rad_s_per_sqrthz(&self, power_w: f64) -> f64 {
if power_w <= 0.0 {
return f64::INFINITY;
}
let freq = C / self.wavelength;
let h_nu = H_PLANCK * freq;
let k = self.scale_factor();
if k.abs() < f64::EPSILON {
return f64::INFINITY;
}
(h_nu / power_w).sqrt() / k
}
}
#[derive(Debug, Clone)]
pub struct IntegratedGyroscope {
pub waveguide_length_m: f64,
pub coil_radius_mm: f64,
pub n_eff: f64,
pub propagation_loss_db_per_cm: f64,
pub wavelength: f64,
}
impl IntegratedGyroscope {
pub fn new(
length_m: f64,
radius_mm: f64,
n_eff: f64,
loss_db_cm: f64,
wavelength: f64,
) -> Self {
Self {
waveguide_length_m: length_m,
coil_radius_mm: radius_mm,
n_eff,
propagation_loss_db_per_cm: loss_db_cm,
wavelength,
}
}
fn n_turns(&self) -> f64 {
let radius_m = self.coil_radius_mm * 1e-3;
if radius_m <= 0.0 {
return 0.0;
}
self.waveguide_length_m / (2.0 * PI * radius_m)
}
fn area_m2(&self) -> f64 {
let radius_m = self.coil_radius_mm * 1e-3;
PI * radius_m * radius_m * self.n_turns()
}
pub fn sagnac_phase(&self, omega: f64) -> f64 {
4.0 * PI * self.area_m2() * self.n_eff * omega / (self.wavelength * C)
}
pub fn scale_factor(&self) -> f64 {
4.0 * PI * self.area_m2() * self.n_eff / (self.wavelength * C)
}
pub fn minimum_detectable_rotation(&self, source_power_w: f64) -> f64 {
if source_power_w <= 0.0 {
return f64::INFINITY;
}
let loss_db = self.propagation_loss_penalty_db();
let power_after_loss = source_power_w * 10.0_f64.powf(-loss_db / 10.0);
let freq = C / self.wavelength;
let h_nu = H_PLANCK * freq;
let s = self.scale_factor();
if s.abs() < f64::EPSILON || power_after_loss <= 0.0 {
return f64::INFINITY;
}
(2.0 * h_nu / power_after_loss).sqrt() / s
}
pub fn propagation_loss_penalty_db(&self) -> f64 {
self.propagation_loss_db_per_cm * (self.waveguide_length_m * 100.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn fog_sagnac_phase_earth_rotation() {
let fog = FiberOpticGyroscope::new(0.10, 1592, 1000.0, 1550e-9);
let phi = fog.sagnac_phase(EARTH_ROTATION_RATE_RAD_S);
assert!(
phi > 1e-6 && phi < 1.0,
"Sagnac phase out of expected range: {}",
phi
);
}
#[test]
fn fog_scale_factor_roundtrip() {
let fog = FiberOpticGyroscope::new(0.15, 1000, 942.5, 1310e-9);
let omega = 0.01_f64; let phi = fog.sagnac_phase(omega);
let omega_recovered = fog.rotation_rate(phi);
assert_abs_diff_eq!(omega_recovered, omega, epsilon = 1e-12);
}
#[test]
fn fog_earth_rotation_detectable_high_power() {
let fog = FiberOpticGyroscope::new(0.15, 2000, 1884.0, 1550e-9);
assert!(
fog.earth_rotation_measurable(1e-3),
"Should detect Earth rotation at 1 mW"
);
}
#[test]
fn rlg_beat_frequency_scaling() {
let rlg = RingLaserGyroscope::new(0.10, 633e-9);
let f = rlg.beat_frequency_hz(EARTH_ROTATION_RATE_RAD_S);
assert!(
f > 0.0 && f < 1000.0,
"Beat frequency out of range: {} Hz",
f
);
}
#[test]
fn rlg_scale_factor_beat_consistency() {
let rlg = RingLaserGyroscope::new(0.05, 1550e-9);
let k = rlg.scale_factor();
let omega = 1.0_f64;
assert_abs_diff_eq!(rlg.beat_frequency_hz(omega), k * omega, epsilon = 1e-10);
}
#[test]
fn integrated_gyro_loss_penalty() {
let ig = IntegratedGyroscope::new(1.0, 5.0, 1.5, 1.0, 1550e-9);
assert_abs_diff_eq!(ig.propagation_loss_penalty_db(), 100.0, epsilon = 1e-6);
}
#[test]
fn integrated_gyro_sagnac_sign() {
let ig = IntegratedGyroscope::new(0.1, 2.0, 1.5, 0.5, 1550e-9);
let phi_pos = ig.sagnac_phase(1.0);
let phi_neg = ig.sagnac_phase(-1.0);
assert!(phi_pos > 0.0 && phi_neg < 0.0);
assert_abs_diff_eq!(phi_pos, -phi_neg, epsilon = 1e-20);
}
}