use crate::clock_state::ClockClass;
use crate::holdover::QuantumClockClass;
use crate::representativeness::{Gap, Representativeness};
#[derive(Clone, Debug, serde::Serialize)]
pub struct DeviceCard {
pub name: String,
pub key_spec: String,
pub representativeness: Representativeness,
}
fn clock_gap() -> Gap {
Gap::new(
"flight clock hardware in the space thermal/radiation environment",
"Phase B2 engineering model + environmental test",
)
}
pub fn quantum_clock_card(class: QuantumClockClass) -> DeviceCard {
let (name, refr) = match class {
QuantumClockClass::OpticalLattice => {
("optical-lattice clock (Sr/Yb)", "Ludlow et al. 2015")
}
QuantumClockClass::TrappedIon => ("trapped-ion optical clock (Al+)", "Brewer et al. 2019"),
QuantumClockClass::MercuryIon => {
("space mercury-ion clock (DSAC-class)", "Burt et al. 2021")
}
};
let adev = class.adev_1s();
DeviceCard {
name: name.to_string(),
key_spec: format!("sigma_y(1 s) = {adev:.0e}"),
representativeness: Representativeness::modelled(name, (3, 4))
.with_assumption(&format!("sigma_y(1 s) = {adev:.0e} from {refr}"))
.with_assumption("long-tau red-noise floor synthesised from the class default")
.with_gap(clock_gap()),
}
}
pub fn classical_clock_card(class: ClockClass) -> DeviceCard {
let (name, refr) = match class {
ClockClass::Csac => (
"chip-scale atomic clock (CSAC)",
"Microsemi SA.45s datasheet",
),
ClockClass::Uso => ("ultra-stable oscillator (USO)", "space USO class default"),
ClockClass::Dsac => ("deep-space atomic clock (DSAC)", "Burt et al. 2021"),
};
let adev = class.adev_1s();
DeviceCard {
name: name.to_string(),
key_spec: format!("sigma_y(1 s) = {adev:.0e}"),
representativeness: Representativeness::modelled(name, (3, 4))
.with_assumption(&format!("sigma_y(1 s) = {adev:.0e} from {refr}"))
.with_gap(clock_gap()),
}
}
#[derive(Clone, Copy, Debug, serde::Serialize)]
pub struct EntanglementTimeLink {
pub single_photon_jitter_s: f64,
pub source_pair_rate_hz: f64,
pub eta_a: f64,
pub eta_b: f64,
pub link_loss_db: f64,
pub dark_rate_hz: f64,
pub systematic_floor_s: f64,
}
impl Default for EntanglementTimeLink {
fn default() -> Self {
EntanglementTimeLink {
single_photon_jitter_s: 50e-12, source_pair_rate_hz: 1.0e7, eta_a: 0.7,
eta_b: 0.7,
link_loss_db: 30.0, dark_rate_hz: 100.0,
systematic_floor_s: 1e-12, }
}
}
impl EntanglementTimeLink {
pub fn detected_coincidence_rate_hz(&self) -> f64 {
self.source_pair_rate_hz * self.eta_a * self.eta_b * 10f64.powf(-self.link_loss_db / 10.0)
}
pub fn timing_precision_s(&self, integration_s: f64) -> f64 {
let rc = self.detected_coincidence_rate_hz().max(1e-12);
let n = (rc * integration_s.max(0.0)).max(1e-12);
let shot = self.single_photon_jitter_s / n.sqrt();
let dark_penalty = (1.0 + self.dark_rate_hz / rc).sqrt();
((shot * dark_penalty).powi(2) + self.systematic_floor_s.powi(2)).sqrt()
}
pub fn card(&self, integration_s: f64) -> DeviceCard {
let prec = self.timing_precision_s(integration_s);
DeviceCard {
name: "entanglement time-transfer link".to_string(),
key_spec: format!(
"sigma_t({integration_s:.0e} s) = {prec:.2e} s; R_c = {:.2e} Hz",
self.detected_coincidence_rate_hz()
),
representativeness: Representativeness::modelled(
"entanglement / single-photon time transfer",
(2, 4),
)
.with_assumption(
"shot-limited precision ~ jitter/sqrt(R*tau) with dark-count penalty; \
pair-rate/efficiency/loss are illustrative public-source values",
)
.with_gap(Gap::new(
"real entangled-photon source + space optical channel + SNSPD detectors",
"Phase B2 hardware + link demonstration",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clock_cards_are_honest_and_ordered() {
let opt = quantum_clock_card(QuantumClockClass::OpticalLattice);
let merc = quantum_clock_card(QuantumClockClass::MercuryIon);
let csac = classical_clock_card(ClockClass::Csac);
for c in [&opt, &merc, &csac] {
assert!(
c.representativeness.is_valid(),
"{} invalid: {:?}",
c.name,
c.representativeness.check()
);
}
assert!(
QuantumClockClass::OpticalLattice.adev_1s() < QuantumClockClass::MercuryIon.adev_1s()
);
assert!(QuantumClockClass::MercuryIon.adev_1s() < ClockClass::Csac.adev_1s());
}
#[test]
fn entanglement_precision_improves_as_inverse_sqrt_integration() {
let link = EntanglementTimeLink {
dark_rate_hz: 0.0,
systematic_floor_s: 0.0,
..Default::default()
};
let s1 = link.timing_precision_s(1.0);
let s4 = link.timing_precision_s(4.0);
assert!((s1 / s4 - 2.0).abs() < 1e-6, "expected 2x, got {}", s1 / s4);
}
#[test]
fn detected_rate_falls_10x_per_10db() {
let a = EntanglementTimeLink {
link_loss_db: 20.0,
..Default::default()
};
let b = EntanglementTimeLink {
link_loss_db: 30.0,
..Default::default()
};
let ratio = a.detected_coincidence_rate_hz() / b.detected_coincidence_rate_hz();
assert!((ratio - 10.0).abs() < 1e-6, "expected 10x, got {ratio}");
}
#[test]
fn dark_counts_degrade_precision() {
let clean = EntanglementTimeLink {
dark_rate_hz: 0.0,
..Default::default()
};
let noisy = EntanglementTimeLink {
dark_rate_hz: 1e5,
..Default::default()
};
assert!(noisy.timing_precision_s(1.0) > clean.timing_precision_s(1.0));
}
#[test]
fn systematic_floor_bounds_below() {
let link = EntanglementTimeLink {
systematic_floor_s: 1e-12,
..Default::default()
};
let s = link.timing_precision_s(1e12);
assert!((1e-12 - 1e-18..1.1e-12).contains(&s), "got {s}");
}
#[test]
fn entanglement_card_is_modelled_and_valid() {
let card = EntanglementTimeLink::default().card(1.0);
assert!(card.representativeness.is_valid());
assert!(card.key_spec.contains("sigma_t"));
let j = serde_json::to_string(&card).unwrap();
assert!(j.contains("entanglement"));
}
}