feat(pps): add adaptive disciplined timing loop
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Monte-Carlo sweep for noisy/missing WLS adaptive PPS scenarios."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import random
|
||||
import statistics
|
||||
|
||||
try:
|
||||
from .adaptive_pps import Observation, Profile, QualityMetrics, run_observations
|
||||
except ImportError:
|
||||
from adaptive_pps import Observation, Profile, QualityMetrics, run_observations
|
||||
|
||||
|
||||
def metrics(profile: Profile) -> QualityMetrics:
|
||||
if profile == Profile.PRECISION:
|
||||
return QualityMetrics(1.7, 3.2, 18.0, 38.0, 27.0, 0.35, 0.25, 7)
|
||||
if profile == Profile.ROBUST:
|
||||
return QualityMetrics(5.0, 8.0, 65.0, 27.0, 18.0, 0.85, 1.8, 5)
|
||||
return QualityMetrics(12.0, 18.0, 180.0, 18.0, 10.0, 2.0, 4.0, 4)
|
||||
|
||||
|
||||
def scenario(seed: int, duration_s: float = 180.0) -> list[Observation]:
|
||||
rng = random.Random(seed)
|
||||
result = []
|
||||
for update in range(round(duration_s / 0.2)):
|
||||
time_s = update * 0.2
|
||||
if 70.0 <= time_s < 82.0:
|
||||
continue
|
||||
profile = Profile.PRECISION
|
||||
if 35.0 <= time_s < 45.0 or 110.0 <= time_s < 125.0:
|
||||
profile = Profile.ROBUST
|
||||
noise_sigma = 320.0e-9 if profile == Profile.PRECISION else 650.0e-9
|
||||
noise_s = rng.gauss(0.0, noise_sigma)
|
||||
if update % 41 == 0:
|
||||
noise_s += rng.choice((-1.0, 1.0)) * 900.0e-9
|
||||
result.append(
|
||||
Observation(
|
||||
time_s,
|
||||
noise_s,
|
||||
rng.gauss(0.0, 0.3),
|
||||
metrics(profile),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def summarize(name: str, traces) -> None:
|
||||
first_locks = [
|
||||
trace.first_lock_time()
|
||||
for trace in traces
|
||||
if trace.first_lock_time() is not None
|
||||
]
|
||||
tail_rms_ns = []
|
||||
jitter_rms_ns = []
|
||||
for trace in traces:
|
||||
tail = trace.phase_error_s[len(trace.phase_error_s) // 2 :]
|
||||
if tail:
|
||||
tail_rms_ns.append(
|
||||
math.sqrt(sum(value * value for value in tail) / len(tail))
|
||||
* 1.0e9
|
||||
)
|
||||
jitter = trace.one_second_jitter_rms(after_s=90.0)
|
||||
if math.isfinite(jitter):
|
||||
jitter_rms_ns.append(jitter * 1.0e9)
|
||||
first_lock_p95 = (
|
||||
statistics.quantiles(first_locks, n=20)[18]
|
||||
if len(first_locks) >= 2
|
||||
else (first_locks[0] if first_locks else math.nan)
|
||||
)
|
||||
tail_rms_median = (
|
||||
statistics.median(tail_rms_ns) if tail_rms_ns else math.nan
|
||||
)
|
||||
jitter_rms_median = (
|
||||
statistics.median(jitter_rms_ns) if jitter_rms_ns else math.nan
|
||||
)
|
||||
print(
|
||||
f"{name}: lock_success={len(first_locks)}/{len(traces)} "
|
||||
f"first_lock_p95={first_lock_p95:.2f}s "
|
||||
f"tail_rms_median={tail_rms_median:.2f}ns "
|
||||
f"one_second_jitter_median={jitter_rms_median:.2f}ns "
|
||||
f"unlock_total={sum(trace.unlock_events for trace in traces)}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
seeds = range(1, 51)
|
||||
raw = [
|
||||
run_observations(
|
||||
scenario(seed),
|
||||
finish_time_s=180.0,
|
||||
adaptive=False,
|
||||
software_filter=False,
|
||||
)
|
||||
for seed in seeds
|
||||
]
|
||||
adaptive = [
|
||||
run_observations(scenario(seed), finish_time_s=180.0)
|
||||
for seed in seeds
|
||||
]
|
||||
summarize("raw fixed PI", raw)
|
||||
summarize("adaptive fixed-point PPS", adaptive)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user