kshana 0.27.1

Open, reproducible PNT-resilience simulator with quantum-sensor performance models
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
// SPDX-License-Identifier: AGPL-3.0-only
//! **Trusted quantum timing — end-to-end time-transfer chain, secure dissemination
//! and a quantum-vs-classical trade.**
//!
//! One scenario (`kind = "quantum-time-transfer"`) that composes the timing chain a
//! quantum-PNT demonstrator needs and reports it as honest, reproducible evidence:
//!
//! 1. **End-to-end chain.** clock → link → dissemination → user. The user-time 1σ is
//!    the quadrature sum of the link timing precision and the reference clock's coast
//!    error over the dissemination interval. The *quantum* chain pairs an
//!    optical-lattice clock with an entanglement/single-photon link
//!    ([`crate::quantum_devices::EntanglementTimeLink`]); the *classical* chain pairs
//!    a chip-scale atomic clock with a two-way RF link.
//! 2. **Secure dissemination + integrity.** A timing protection level
//!    ([`crate::tpl`], reused) bounds the undetected time error, and a delay/replay
//!    attack detector ([`crate::detection`], reused) gives a security figure of merit
//!    `1 − P_md` at a stated false-alarm rate.
//! 3. **Timing anomaly detection.** A clock-fault detection probability + a CUSUM
//!    change-detection latency ([`crate::tpl::cusum_latency_s`], reused).
//! 4. **Trade.** The quantum-vs-classical comparison is emitted as a
//!    [`crate::qtrade::TradeEvidence`] carrying a [`crate::representativeness`] record.
//!
//! Everything is **MODELLED**: clock/link parameters are illustrative, public-source,
//! and the chain composes published-coefficient device models with closed-form
//! budgets. It models the *class* of system; no TRL/flight/certification is claimed.

use crate::detection::{analytic_pd, detection_boundary};
use crate::holdover::{coast_phase_sigma, QuantumClockClass};
use crate::qtrade::{TradeEvidence, TradeFom, TradeFrame};
use crate::quantum_devices::EntanglementTimeLink;
use crate::representativeness::{Gap, Representativeness};
use crate::tpl::{cusum_latency_s, timing_protection_level_ns, TplInputs};
use crate::verification::VerificationStatus;

fn d_integration_s() -> f64 {
    1.0
}
fn d_dissemination_interval_s() -> f64 {
    100.0
}
fn d_link_loss_db() -> f64 {
    30.0
}
fn d_classical_link_sigma_s() -> f64 {
    1.0e-9 // ~1 ns two-way RF time transfer
}
fn d_monitor_pfa() -> f64 {
    1.0e-3
}
fn d_attack_delay_s() -> f64 {
    5.0e-9 // a 5 ns delay/replay attack
}
fn d_clock_fault_sigma() -> f64 {
    4.0 // a clock anomaly at 4σ of the monitor noise
}

/// Coast (holdover) phase 1σ over an interval for a clock given its PSDs (s).
fn clock_coast_sigma_s(psds: (f64, f64, f64), interval_s: f64) -> f64 {
    coast_phase_sigma(psds.0, psds.1, psds.2, interval_s)
}

/// A runnable trusted-quantum-timing scenario.
#[derive(Clone, Copy, Debug, serde::Deserialize)]
pub struct QuantumTimeTransferScenario {
    /// Link integration time per measurement (s).
    #[serde(default = "d_integration_s")]
    pub integration_s: f64,
    /// Time-dissemination interval the clock must coast across (s).
    #[serde(default = "d_dissemination_interval_s")]
    pub dissemination_interval_s: f64,
    /// Quantum (entanglement) link total channel loss (dB).
    #[serde(default = "d_link_loss_db")]
    pub link_loss_db: f64,
    /// Classical two-way link timing 1σ (s).
    #[serde(default = "d_classical_link_sigma_s")]
    pub classical_link_sigma_s: f64,
    /// Integrity monitor false-alarm probability.
    #[serde(default = "d_monitor_pfa")]
    pub monitor_pfa: f64,
    /// Delay/replay attack offset to detect (s).
    #[serde(default = "d_attack_delay_s")]
    pub attack_delay_s: f64,
    /// Clock-anomaly magnitude, in units of the monitor noise σ.
    #[serde(default = "d_clock_fault_sigma")]
    pub clock_fault_sigma: f64,
}

impl Default for QuantumTimeTransferScenario {
    fn default() -> Self {
        QuantumTimeTransferScenario {
            integration_s: d_integration_s(),
            dissemination_interval_s: d_dissemination_interval_s(),
            link_loss_db: d_link_loss_db(),
            classical_link_sigma_s: d_classical_link_sigma_s(),
            monitor_pfa: d_monitor_pfa(),
            attack_delay_s: d_attack_delay_s(),
            clock_fault_sigma: d_clock_fault_sigma(),
        }
    }
}

/// The trusted-quantum-timing report.
#[derive(Clone, Debug, serde::Serialize)]
pub struct QuantumTimeTransferReport {
    /// Quantum chain end-to-end user-time 1σ (s).
    pub quantum_chain_sigma_s: f64,
    /// Classical chain end-to-end user-time 1σ (s).
    pub classical_chain_sigma_s: f64,
    /// Quantum-link detected coincidence rate (Hz).
    pub quantum_link_rate_hz: f64,
    /// Timing protection level for the disseminated time (ns).
    pub protection_level_ns: f64,
    /// Security FoM 1 − P_md against the delay/replay attack.
    pub security_pd: f64,
    /// Assumed monitor false-alarm probability.
    pub monitor_pfa: f64,
    /// Detection probability for the injected clock anomaly.
    pub anomaly_pd: f64,
    /// CUSUM change-detection latency for the anomaly (s).
    pub anomaly_cusum_latency_s: f64,
    /// The quantum-vs-classical trade evidence.
    pub trade: TradeEvidence,
}

/// Unit and provenance class for every numeric field the `quantum-time-transfer`
/// report emits.
pub const UNITS: &[crate::field_schema::FieldUnit] = {
    use crate::field_schema::{FieldUnit, ProvenanceClass::*};
    &[
        FieldUnit {
            path: "quantum_chain_sigma_s",
            unit: "s",
            provenance: Computed,
            definition: "end-to-end user-time 1 sigma of the quantum chain: the quadrature \
                         sum of the entanglement link's timing precision at the integration \
                         time and the optical-lattice clock's coast phase error over the \
                         dissemination interval",
        },
        FieldUnit {
            path: "classical_chain_sigma_s",
            unit: "s",
            provenance: Computed,
            definition: "end-to-end user-time 1 sigma of the classical chain: the quadrature \
                         sum of the two-way RF link timing 1 sigma and the CSAC coast phase \
                         error over the dissemination interval",
        },
        FieldUnit {
            path: "quantum_link_rate_hz",
            unit: "Hz",
            provenance: Computed,
            definition: "detected two-fold coincidence rate of the entanglement time link, \
                         source_pair_rate * eta_a * eta_b * 10^(-link_loss_db/10)",
        },
        FieldUnit {
            path: "protection_level_ns",
            unit: "ns",
            provenance: Computed,
            definition: "timing protection level bounding the undetected time error of the \
                         disseminated time, from the reused k = 5 TPL bound over the quantum \
                         clock's noise parameters",
        },
        FieldUnit {
            path: "security_pd",
            unit: "1",
            provenance: Computed,
            definition: "security figure of merit 1 - P_md: the probability the delay/replay \
                         attack offset is detected by the k-sigma gate set for the monitor \
                         false-alarm rate",
        },
        FieldUnit {
            path: "monitor_pfa",
            unit: "1",
            provenance: Input,
            definition: "assumed integrity-monitor false-alarm probability, which sets the \
                         detection gate the security and anomaly detection probabilities are \
                         evaluated at",
        },
        FieldUnit {
            path: "anomaly_pd",
            unit: "1",
            provenance: Computed,
            definition: "probability that the injected clock anomaly, of magnitude \
                         clock_fault_sigma times the monitor noise, is detected at that same \
                         false-alarm gate",
        },
        FieldUnit {
            path: "anomaly_cusum_latency_s",
            unit: "s",
            provenance: Computed,
            definition: "CUSUM change-detection latency for the standardized fault, \
                         (floor(h / (z - kref)) + 1) samples at the integration-time cadence",
        },
        FieldUnit {
            path: "trade.frame.seed",
            unit: "1",
            provenance: Constant,
            definition: "the RNG seed stamped into the trade's comparison frame so the \
                         comparison is reproducible; a dimensionless integer label, not a \
                         measured quantity, and fixed at 0 because this pack draws no random \
                         numbers",
        },
        FieldUnit {
            path: "trade.foms[].quantum",
            unit: "see the sibling `unit` field",
            provenance: Computed,
            definition: "the quantum candidate's score on that row's figure of merit; the \
                         unit is data, differing row by row (s for the end-to-end precision, \
                         dimensionless fractional frequency for the reference clock's \
                         sigma_y(1 s)), so the row's own `unit` string names it",
        },
        FieldUnit {
            path: "trade.foms[].classical",
            unit: "see the sibling `unit` field",
            provenance: Computed,
            definition: "the classical baseline's score on that row's figure of merit, in the \
                         same data-dependent unit as the row's quantum value",
        },
        FieldUnit {
            path: "trade.representativeness.trl_band[]",
            unit: "1",
            provenance: Modelled,
            definition: "the (lo, hi) endpoints of the technology-readiness band this \
                         modelled demonstration is claimed to be representative for; a TRL is \
                         an ordinal readiness index on the 1-9 scale, not a physical quantity, \
                         so it is dimensionless and its differences are not metric",
        },
    ]
};

impl QuantumTimeTransferScenario {
    /// Run the scenario.
    pub fn run(&self) -> QuantumTimeTransferReport {
        // --- 1. End-to-end chain budgets (quantum vs classical) -----------------
        // Quantum: optical-lattice clock + entanglement link.
        let q_clock = QuantumClockClass::OpticalLattice;
        let q_link = EntanglementTimeLink {
            link_loss_db: self.link_loss_db,
            ..Default::default()
        };
        let q_link_sigma = q_link.timing_precision_s(self.integration_s);
        let q_clock_coast = clock_coast_sigma_s(q_clock.psds(), self.dissemination_interval_s);
        let quantum_chain_sigma_s = (q_link_sigma.powi(2) + q_clock_coast.powi(2)).sqrt();

        // Classical: CSAC + two-way RF link.
        let c_psds = crate::clock_state::ClockClass::Csac.psds();
        let c_clock_coast = clock_coast_sigma_s(c_psds, self.dissemination_interval_s);
        let classical_chain_sigma_s =
            (self.classical_link_sigma_s.powi(2) + c_clock_coast.powi(2)).sqrt();

        // --- 2. Secure dissemination: protection level + attack detection -------
        // Protection level (reuse the certified TPL bound) over the quantum clock.
        let (q_wf, q_rw, q_drift) = q_clock.psds();
        let tpl = TplInputs {
            q_wf,
            q_rw,
            q_drift,
            r: quantum_chain_sigma_s.max(1e-15),
            tau: self.integration_s.max(1e-3),
            samples: (self.dissemination_interval_s / self.integration_s.max(1e-3)).max(1.0),
            k: 5.0,
            detection_latency_s: self.integration_s.max(1e-3),
        };
        let protection_level_ns = timing_protection_level_ns(&tpl);

        // Delay/replay attack detector: alarm if the offset exceeds a k-sigma gate set
        // for the monitor false-alarm rate. Security FoM = 1 - P_md = P_d.
        let sigma_mon = quantum_chain_sigma_s.max(1e-15);
        let gamma = detection_boundary(sigma_mon, self.monitor_pfa);
        let security_pd = analytic_pd(self.attack_delay_s, sigma_mon, gamma);

        // --- 3. Timing anomaly detection ----------------------------------------
        // A clock anomaly at clock_fault_sigma * monitor noise.
        let fault_mu = self.clock_fault_sigma * sigma_mon;
        let anomaly_pd = analytic_pd(fault_mu, sigma_mon, gamma);
        // CUSUM change-detection latency for the standardized fault magnitude.
        let anomaly_cusum_latency_s = cusum_latency_s(
            0.5,
            5.0,
            self.clock_fault_sigma,
            self.integration_s.max(1e-3),
        );

        // --- 4. Quantum-vs-classical trade evidence -----------------------------
        let rep =
            Representativeness::modelled("quantum vs classical end-to-end time transfer", (3, 4))
                .with_assumption(
                    "optical-lattice clock + entanglement link vs CSAC + RF two-way link",
                )
                .with_assumption(
                    "illustrative, public-source device/link parameters; seeded synthetic",
                )
                .with_gap(Gap::new(
                    "real clock + optical link hardware and a space channel demonstration",
                    "Phase B2 hardware-in-the-loop",
                ));
        let trade = TradeEvidence::new(TradeFrame::new("quantum-time-transfer", 0), rep)
            .with_fom(TradeFom {
                name: "end-to-end time-transfer precision".into(),
                unit: "s".into(),
                quantum: quantum_chain_sigma_s,
                classical: classical_chain_sigma_s,
                higher_is_better: false,
                ci95: None,
                status: VerificationStatus::Modelled,
            })
            .with_fom(TradeFom {
                name: "reference clock 1 s stability".into(),
                unit: "sigma_y(1 s)".into(),
                quantum: q_clock.adev_1s(),
                classical: crate::clock_state::ClockClass::Csac.adev_1s(),
                higher_is_better: false,
                ci95: None,
                status: VerificationStatus::Modelled,
            });

        QuantumTimeTransferReport {
            quantum_chain_sigma_s,
            classical_chain_sigma_s,
            quantum_link_rate_hz: q_link.detected_coincidence_rate_hz(),
            protection_level_ns,
            security_pd,
            monitor_pfa: self.monitor_pfa,
            anomaly_pd,
            anomaly_cusum_latency_s,
            trade,
        }
    }
}

/// A minimal accumulated-budget SVG.
pub fn to_svg(r: &QuantumTimeTransferReport) -> String {
    let q = r.quantum_chain_sigma_s;
    let c = r.classical_chain_sigma_s;
    let max = q.max(c).max(1e-18);
    let qh = (q / max * 180.0).min(180.0);
    let ch = (c / max * 180.0).min(180.0);
    format!(
        "<svg xmlns='http://www.w3.org/2000/svg' width='320' height='220'>\
         <rect width='320' height='220' fill='white'/>\
         <text x='10' y='20' font-size='12'>quantum-time-transfer (MODELLED)</text>\
         <rect x='60' y='{}' width='60' height='{:.1}' fill='#3a6'/>\
         <text x='62' y='210' font-size='10'>quantum</text>\
         <rect x='180' y='{}' width='60' height='{:.1}' fill='#c44'/>\
         <text x='182' y='210' font-size='10'>classical</text></svg>",
        200.0 - qh,
        qh,
        200.0 - ch,
        ch
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn quantum_precision_improves_with_integration() {
        let short = QuantumTimeTransferScenario {
            integration_s: 1.0,
            ..Default::default()
        };
        let long = QuantumTimeTransferScenario {
            integration_s: 100.0,
            ..Default::default()
        };
        // Longer integration -> tighter quantum link -> tighter (or equal) chain.
        assert!(long.run().quantum_chain_sigma_s <= short.run().quantum_chain_sigma_s);
    }

    #[test]
    fn quantum_can_win_and_can_lose() {
        // Favourable link: quantum wins the precision FoM.
        let good = QuantumTimeTransferScenario {
            link_loss_db: 10.0,
            ..Default::default()
        }
        .run();
        assert!(good.quantum_chain_sigma_s < good.classical_chain_sigma_s);
        // A very lossy quantum link: classical wins (honest — not a universal win).
        let bad = QuantumTimeTransferScenario {
            link_loss_db: 80.0,
            ..Default::default()
        }
        .run();
        assert!(bad.quantum_chain_sigma_s > bad.classical_chain_sigma_s);
    }

    #[test]
    fn protection_level_is_finite_positive() {
        let r = QuantumTimeTransferScenario::default().run();
        assert!(r.protection_level_ns.is_finite() && r.protection_level_ns > 0.0);
    }

    #[test]
    fn security_fom_in_range_and_grows_with_attack_delay() {
        let small = QuantumTimeTransferScenario {
            attack_delay_s: 1e-9,
            ..Default::default()
        }
        .run();
        let large = QuantumTimeTransferScenario {
            attack_delay_s: 20e-9,
            ..Default::default()
        }
        .run();
        for v in [small.security_pd, large.security_pd] {
            assert!((0.0..=1.0).contains(&v), "Pd out of range: {v}");
        }
        assert!(
            large.security_pd >= small.security_pd,
            "larger attack must be at least as detectable"
        );
    }

    #[test]
    fn anomaly_detection_is_consistent() {
        let r = QuantumTimeTransferScenario::default().run();
        assert!((0.0..=1.0).contains(&r.anomaly_pd));
        assert!(r.anomaly_cusum_latency_s.is_finite() && r.anomaly_cusum_latency_s > 0.0);
        // A bigger fault is detected at least as well.
        let big = QuantumTimeTransferScenario {
            clock_fault_sigma: 8.0,
            ..Default::default()
        }
        .run();
        assert!(big.anomaly_pd >= r.anomaly_pd);
    }

    #[test]
    fn trade_is_honest() {
        let r = QuantumTimeTransferScenario::default().run();
        assert!(
            r.trade.is_honest(),
            "violations: {:?}",
            r.trade.honesty_violations()
        );
        assert!(r.trade.representativeness.is_valid());
    }
}