from pathlib import Path
import numpy as np
import kshana
REPO_ROOT = Path(__file__).resolve().parents[2]
SCENARIO = (REPO_ROOT / "scenarios" / "clock-holdover.toml").read_text()
CSAC_SIGMA_Y_1S = 3.0e-10
ADEV_REL_TOL = 0.25
SHORT_TAU_S = 320.0
def _run_data():
return kshana.run_typed(SCENARIO).data()
def test_data_arrays_are_numpy_convertible_and_finite():
curve = _run_data()["classical"]["adev_curve"]
assert curve, "adev_curve is empty"
adev = np.asarray([p["adev"] for p in curve], dtype=float)
tau = np.asarray([p["tau_s"] for p in curve], dtype=float)
assert adev.dtype == np.float64
assert tau.dtype == np.float64
assert adev.ndim == 1 and tau.ndim == 1
assert adev.shape == tau.shape
assert np.all(np.isfinite(adev))
assert np.all(np.isfinite(tau))
def test_series_error_column_is_a_numeric_ndarray():
series = _run_data()["classical"]["series"]
err = np.asarray([s["error_ns"] for s in series])
assert err.dtype.kind == "f"
assert len(err) == 721
assert np.isfinite(err).all()
def test_classical_adev_matches_white_fm_power_law():
curve = _run_data()["classical"]["adev_curve"]
taus = np.asarray([p["tau_s"] for p in curve], dtype=float)
adev = np.asarray([p["adev"] for p in curve], dtype=float)
mask = taus <= SHORT_TAU_S
assert mask.sum() >= 3, "need several short-tau points for a meaningful band check"
analytic = CSAC_SIGMA_Y_1S / np.sqrt(taus[mask])
rel_err = np.abs(adev[mask] - analytic) / analytic
assert np.all(rel_err < ADEV_REL_TOL), f"max rel-err {rel_err.max():.3f} exceeds {ADEV_REL_TOL}"
slope = np.polyfit(np.log10(taus[mask]), np.log10(adev[mask]), 1)[0]
assert -0.65 < slope < -0.35, f"log-log slope {slope:.3f} not consistent with white-FM -1/2"
def test_quantum_holds_over_at_least_as_long_as_classical():
data = _run_data()
q = data["quantum"]["fom"]["holdover_s"]
c = data["classical"]["fom"]["holdover_s"]
assert q >= c