use serde::Serialize;
use crate::inertial::quantum_imu::{CaiAccelerometer, QuantumNavBudget, RB87_D2_WAVELENGTH_M};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum BracketLevel {
Best,
Nominal,
Conservative,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct CitedBracket {
pub low: f64,
pub nominal: f64,
pub high: f64,
pub unit: String,
pub higher_is_better: bool,
pub source: String,
pub needs_source_confirmation: bool,
pub note: String,
}
impl CitedBracket {
pub fn is_valid(&self) -> bool {
self.low.is_finite()
&& self.nominal.is_finite()
&& self.high.is_finite()
&& self.low >= 0.0
&& self.low <= self.nominal
&& self.nominal <= self.high
&& !self.source.trim().is_empty()
}
pub fn at(&self, level: BracketLevel) -> f64 {
match (level, self.higher_is_better) {
(BracketLevel::Nominal, _) => self.nominal,
(BracketLevel::Best, true) => self.high,
(BracketLevel::Best, false) => self.low,
(BracketLevel::Conservative, true) => self.low,
(BracketLevel::Conservative, false) => self.high,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct CaiParameterSheet {
pub bias_instability: CitedBracket,
pub velocity_random_walk: CitedBracket,
pub scale_factor_stability: CitedBracket,
pub sample_rate: CitedBracket,
pub dynamic_range: CitedBracket,
pub angle_random_walk: CitedBracket,
pub caveat: String,
}
impl CaiParameterSheet {
pub fn literature_survey() -> Self {
let confirm = true;
CaiParameterSheet {
bias_instability: CitedBracket {
low: 1.0e-7,
nominal: 1.0e-6,
high: 1.0e-5,
unit: "m/s^2".into(),
higher_is_better: false,
source: "Bongs et al., Nat. Rev. Phys. 1, 731 (2019); Geiger et al., \
Nat. Commun. 2, 474 (2011) (airborne CAI ~few µg); Exail/Muquans \
AQG instrument class"
.into(),
needs_source_confirmation: confirm,
note: "≈0.01–1 µg-class residual bias after calibration; long-tau lab \
instruments reach the low end, airborne/field the high end"
.into(),
},
velocity_random_walk: CitedBracket {
low: 1.0e-8,
nominal: 1.0e-7,
high: 1.0e-6,
unit: "m/s^2/sqrt(Hz)".into(),
higher_is_better: false,
source: "Bongs et al., Nat. Rev. Phys. 1, 731 (2019); Geiger et al., \
Nat. Commun. 2, 474 (2011); shot-noise-limited CAI sensitivity"
.into(),
needs_source_confirmation: confirm,
note: "acceleration ASD ≈ tens of ng/√Hz (lab) to ~µg/√Hz (field); \
cross-checked against CaiAccelerometer::accel_asd physics"
.into(),
},
scale_factor_stability: CitedBracket {
low: 1.0e-3,
nominal: 1.0e-2,
high: 1.0e-1,
unit: "ppm".into(),
higher_is_better: false,
source: "CAI scale factor = k_eff·T² set by the atomic transition / \
optical frequency reference (ppb-class); Le Gouët et al., \
Appl. Phys. B 92, 133 (2008); Bongs et al. (2019)"
.into(),
needs_source_confirmation: confirm,
note: "≈1–100 ppb (0.001–0.1 ppm); the headline CAI advantage over \
MEMS/FOG — scale factor traces to fundamental constants"
.into(),
},
sample_rate: CitedBracket {
low: 0.5,
nominal: 2.0,
high: 10.0,
unit: "Hz".into(),
higher_is_better: true,
source: "interrogation-time/cycle-time limit (T_c = prepare+interrogate+detect); \
interleaved/joint-interrogation extends rate, e.g. Savoie et al., \
Sci. Adv. 4, eaau7948 (2018)"
.into(),
needs_source_confirmation: confirm,
note: "long interrogation T → low rate (≥0.5 Hz); short-T / interleaved \
operation → ~10 Hz; dead time is the core limitation"
.into(),
},
dynamic_range: CitedBracket {
low: 1.0e-3,
nominal: 1.0,
high: 50.0,
unit: "m/s^2".into(),
higher_is_better: true,
source: "raw single-sensor range = fringe-ambiguity limit 2π/(k_eff·T²) \
(computed, see raw_fringe_ambiguity_accel); extended by hybrid \
CAI+classical operation, e.g. Lautier et al., APL 105, 144102 \
(2014); Cheiney et al., PRApplied 10, 034030 (2018)"
.into(),
needs_source_confirmation: confirm,
note: "raw fringe ambiguity is sub-mg per fringe → CAI must be \
hybridised with a classical accelerometer to reach navigation \
dynamic ranges (~several g)"
.into(),
},
angle_random_walk: CitedBracket {
low: 1.0e-9,
nominal: 1.0e-8,
high: 1.0e-7,
unit: "rad/s/sqrt(Hz)".into(),
higher_is_better: false,
source: "atom Sagnac gyroscopes: Gauguet et al., PRA 80, 063604 (2009); \
Stockton et al., PRL 107, 133001 (2011); Durfee et al., PRL 97, \
240801 (2006)"
.into(),
needs_source_confirmation: confirm,
note: "rotation-rate sensitivity ≈ 1e-9–1e-7 rad/s/√Hz; informational \
(the consumed budget here is the accelerometer path)"
.into(),
},
caveat: "MODELLED literature-survey parameter sheet. Every bracket is an \
order-of-magnitude range distilled from published atom-interferometry \
sources and is flagged needs_source_confirmation=true: confirm exact \
values against the named primary source before bid submission. \
Performance-level only — no hardware is modelled, no device validated, \
no flight heritage implied. Does NOT borrow the SGP4/Allan/IGS \
external-validation halo."
.into(),
}
}
pub fn is_valid(&self) -> bool {
let brackets = [
&self.bias_instability,
&self.velocity_random_walk,
&self.scale_factor_stability,
&self.sample_rate,
&self.dynamic_range,
&self.angle_random_walk,
];
brackets
.iter()
.all(|b| b.is_valid() && b.needs_source_confirmation)
}
pub fn reference_cai(&self, level: BracketLevel) -> CaiAccelerometer {
let rate = self.sample_rate.at(level).max(1.0e-6);
CaiAccelerometer {
wavelength_m: RB87_D2_WAVELENGTH_M,
pulse_sep_t: 0.05,
atom_number: 1.0e6,
contrast: 0.5,
cycle_time_s: 1.0 / rate,
}
}
pub fn to_nav_budget(
&self,
level: BracketLevel,
ref_accel_m_s2: f64,
tau_stability_s: f64,
) -> QuantumNavBudget {
QuantumNavBudget {
cai: self.reference_cai(level),
bias_m_s2: self.bias_instability.at(level),
scale_factor_ppm: self.scale_factor_stability.at(level),
ref_accel_m_s2,
tau_stability_s,
}
}
pub fn raw_fringe_ambiguity_accel(&self, level: BracketLevel) -> f64 {
let cai = self.reference_cai(level);
let scale = cai.scale_factor(); if scale == 0.0 {
return f64::INFINITY;
}
2.0 * std::f64::consts::PI / scale
}
pub fn vrw_consistency(&self, level: BracketLevel) -> (f64, f64, f64, bool) {
let physics = self.reference_cai(level).accel_asd();
let lo = self.velocity_random_walk.low;
let hi = self.velocity_random_walk.high;
(physics, lo, hi, physics >= lo && physics <= hi)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn literature_survey_is_valid_and_flagged_for_confirmation() {
let s = CaiParameterSheet::literature_survey();
assert!(s.is_valid(), "the curated sheet must validate");
for b in [
&s.bias_instability,
&s.velocity_random_walk,
&s.scale_factor_stability,
&s.sample_rate,
&s.dynamic_range,
&s.angle_random_walk,
] {
assert!(!b.source.trim().is_empty(), "bracket without a source");
assert!(
b.needs_source_confirmation,
"bracket not flagged for confirmation"
);
}
}
#[test]
fn bracket_level_selection_respects_performance_direction() {
let s = CaiParameterSheet::literature_survey();
assert_eq!(
s.bias_instability.at(BracketLevel::Best),
s.bias_instability.low
);
assert_eq!(
s.bias_instability.at(BracketLevel::Conservative),
s.bias_instability.high
);
assert_eq!(s.sample_rate.at(BracketLevel::Best), s.sample_rate.high);
assert_eq!(
s.sample_rate.at(BracketLevel::Conservative),
s.sample_rate.low
);
assert_eq!(
s.bias_instability.at(BracketLevel::Nominal),
s.bias_instability.nominal
);
assert_eq!(
s.sample_rate.at(BracketLevel::Nominal),
s.sample_rate.nominal
);
}
#[test]
fn nav_budget_consumes_the_cited_brackets() {
let s = CaiParameterSheet::literature_survey();
let b = s.to_nav_budget(BracketLevel::Nominal, 1.0, 0.0);
assert_eq!(b.bias_m_s2, s.bias_instability.at(BracketLevel::Nominal));
assert_eq!(
b.scale_factor_ppm,
s.scale_factor_stability.at(BracketLevel::Nominal)
);
}
#[test]
fn conservative_budget_drifts_more_than_best_budget() {
let s = CaiParameterSheet::literature_survey();
let best = s.to_nav_budget(BracketLevel::Best, 1.0, 0.0);
let cons = s.to_nav_budget(BracketLevel::Conservative, 1.0, 0.0);
let t = 100.0;
assert!(
cons.position_drift_1sigma(t) > best.position_drift_1sigma(t),
"conservative {} should exceed best {}",
cons.position_drift_1sigma(t),
best.position_drift_1sigma(t)
);
}
#[test]
fn physics_vrw_lands_within_the_cited_vrw_bracket() {
let s = CaiParameterSheet::literature_survey();
for level in [
BracketLevel::Best,
BracketLevel::Nominal,
BracketLevel::Conservative,
] {
let (physics, lo, hi, within) = s.vrw_consistency(level);
assert!(
within,
"physics VRW {physics:.3e} fell outside cited bracket [{lo:.3e}, {hi:.3e}] at {level:?}"
);
}
}
#[test]
fn raw_fringe_ambiguity_is_computed_and_far_below_the_hybrid_range() {
let s = CaiParameterSheet::literature_survey();
let raw = s.raw_fringe_ambiguity_accel(BracketLevel::Nominal);
assert!(
(raw - 1.56e-4).abs() < 2.0e-5,
"raw fringe-ambiguity accel was {raw:.3e}, expected ≈1.56e-4"
);
assert!(
raw < s.dynamic_range.high,
"hybridisation must extend the range well beyond the raw fringe limit"
);
}
#[test]
fn invalid_brackets_are_rejected() {
let mut bad = CaiParameterSheet::literature_survey();
bad.bias_instability.low = 1.0;
bad.bias_instability.nominal = 0.5;
assert!(
!bad.is_valid(),
"disordered bracket must invalidate the sheet"
);
let mut unsourced = CaiParameterSheet::literature_survey();
unsourced.sample_rate.source = " ".into();
assert!(
!unsourced.is_valid(),
"unsourced bracket must invalidate the sheet"
);
}
#[test]
fn serializes_with_sources_caveat_and_confirmation_flag() {
let s = CaiParameterSheet::literature_survey();
let json = serde_json::to_string(&s).unwrap();
assert!(json.contains("source"));
assert!(json.contains("needs_source_confirmation"));
assert!(json.contains("MODELLED"));
assert!(
json.contains("Bongs"),
"the umbrella citation should be on the artifact"
);
assert!(
json.contains("\"low\"") && json.contains("\"nominal\"") && json.contains("\"high\"")
);
}
}