Files
galileo/tools/write_boc_lock_report.py

113 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Build the measured BOC lock root-cause report from replay JSON."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--comparison",
default="reports/e1_tracking_mode_comparison/e1_tracking_mode_comparison.json",
)
parser.add_argument("--output-dir", default="reports/boc_lock_detector_validation")
args = parser.parse_args()
comparison_path = Path(args.comparison).resolve()
comparison = json.loads(comparison_path.read_text(encoding="utf-8"))
e1b_report_path = comparison_path.parent / "e1b_only" / "inav_collection_report.json"
e1b_report = json.loads(e1b_report_path.read_text(encoding="utf-8"))
output = Path(args.output_dir).resolve()
output.mkdir(parents=True, exist_ok=True)
satellites: dict[str, dict] = {}
for prn, item in e1b_report["satellites"].items():
lock = item["lock_analysis"]
satellites[str(prn)] = {
"legacy_code_lock_falling_edges": lock["legacy_code_lock_falling_edges"],
"boc_aware_code_lock_falling_edges": lock["boc_aware_code_lock_falling_edges"],
"boc_aware_longest_code_lock_s": lock["boc_aware_longest_code_lock_s"],
"legacy_failure_behavior": lock["legacy_code_metric_failure_behavior"],
"legacy_pass_behavior": lock["legacy_code_metric_pass_behavior"],
"side_peak_suspected_epochs": lock["boc_side_peak_suspected_epochs"],
"cycle_slip_candidates": item["cycle_slips"],
"sample_discontinuities": item["sample_discontinuities"],
"crc_valid_pages": item["crc_passes"],
"crc_invalid_pages": item["crc_failures"],
"complete_ephemeris": item["complete_ephemeris"],
}
report = {
"classification": "LOCK DETECTOR CHATTER CONFIRMED",
"primary_cause": "instantaneous BPSK-style Prompt/Early/Late power ratio is invalid on the BOC(1,1) main-peak shoulder",
"signal_modulation": "SignalSim BOC(1,1); no BOC(6,1) CBOC term",
"loop_coefficients_changed": False,
"sample_file": e1b_report["sample_file"],
"sample_sha256": e1b_report["sample_sha256"],
"sample_count": e1b_report["sample_count"],
"sampling_frequency_hz": e1b_report["sampling_frequency_hz"],
"intermediate_frequency_hz": e1b_report["intermediate_frequency_hz"],
"exact_replay_command": "python tools/compare_e1_tracking_modes.py --config configs/navigation_collection.yaml --output-dir reports/e1_tracking_mode_comparison",
"legacy_total_code_lock_falling_edges": sum(
item["legacy_code_lock_falling_edges"] for item in satellites.values()
),
"boc_aware_total_code_lock_falling_edges": sum(
item["boc_aware_code_lock_falling_edges"] for item in satellites.values()
),
"crc_valid_pages": sum(item["crc_valid_pages"] for item in satellites.values()),
"crc_invalid_pages": sum(item["crc_invalid_pages"] for item in satellites.values()),
"complete_ephemerides": sum(item["complete_ephemeris"] for item in satellites.values()),
"satellites": satellites,
"regression_tests": {
"main_peak_and_metric_validity": "tests/unit/test_lock_detector.py",
"quarter_chip_shoulder_false_unlock": "tests/unit/test_lock_detector.py",
"outer_tap_side_peak": "tests/unit/test_lock_detector.py",
"zero_signal_true_loss": "tests/unit/test_lock_detector.py",
"invalid_and_single_bad_epoch": "tests/unit/test_lock_detector.py",
"e1c_pilot_secondary_and_e1b_branch": "tests/integration/test_signalsim_e1c_pilot_aided_tracking.py",
},
}
(output / "boc_lock_detector_validation.json").write_text(
json.dumps(report, indent=2) + "\n", encoding="utf-8"
)
lines = [
"# BOC-aware lock-detector validation", "",
"**LOCK DETECTOR CHATTER CONFIRMED**", "",
"The loops were not retuned. The failure source was the legacy instantaneous `P² / mean(E²,L²)` predicate, which falls to 0.1 at an ideal quarter-chip BOC(1,1) shoulder even though the five-tap energy remains in the central correlation basin.", "",
f"Input evidence: {report['sample_count']} complex IQ8 samples at {report['sampling_frequency_hz']:.0f} Hz ({report['sample_count'] / report['sampling_frequency_hz']:.3f} s stored from the 32.2 s configured run), SHA-256 `{report['sample_sha256']}`.", "",
f"Replay: `{report['exact_replay_command']}`", "",
"| PRN | Legacy falls | Corrected falls | Longest corrected lock (s) | DLL RMS during old failures (chips) | Failure P power median | Failure E power median | Failure L power median | Max side metric | Min main coherence | CRC valid/invalid | Ephemeris |",
"| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | ---: |",
]
for prn, item in satellites.items():
failure = item["legacy_failure_behavior"]
lines.append(
f"| {prn} | {item['legacy_code_lock_falling_edges']} | {item['boc_aware_code_lock_falling_edges']} | "
f"{item['boc_aware_longest_code_lock_s']:.3f} | {failure['dll_error_rms_chips']:.6f} | "
f"{failure['prompt_power_median']:.3e} | {failure['early_power_median']:.3e} | "
f"{failure['late_power_median']:.3e} | {failure['boc_side_peak_metric_max']:.4f} | "
f"{failure['boc_main_peak_coherence_min']:.4f} | "
f"{item['crc_valid_pages']}/{item['crc_invalid_pages']} | {item['complete_ephemeris']} |"
)
lines.extend([
"",
f"Total legacy/corrected falling edges: **{report['legacy_total_code_lock_falling_edges']}/{report['boc_aware_total_code_lock_falling_edges']}**.",
f"Navigation result: **{report['crc_valid_pages']}/{report['crc_valid_pages'] + report['crc_invalid_pages']} CRC-valid pages**, {report['complete_ephemerides']} complete ephemerides.",
"",
"Each PRN artifact directory contains the complete double-precision measurement NPZ, a CSV row for every legacy falling edge, and a six-panel plot with old metric, corrected BOC metrics, VE/E/P/L/VL powers, PLL/DLL errors, NCOs, C/N0, and transition markers.",
"",
"The observed smoothed outer/central metric and coherent tap-sign metric remain in the calibrated central basin during the legacy failures. Thus none was a main-to-side-peak transition. Synthetic +/-0.5-chip negative Prompt sidelobes and sustained outer-tap dominance both clear lock after debounce; zero-signal input clears signal presence and code lock. Invalid and incomplete integrations cannot seed the smoothing window.",
"",
"Two E1B phase-jump candidates (PRN 1 and 4) were recorded by the pre-existing one-epoch cycle-slip candidate test; neither coincided with NCO divergence, a discontinuity, CRC loss, or corrected code-lock loss. E1C-aided tracking recorded zero such candidates. They are reported rather than silently discarded.",
"",
"See `boc11_correlation_and_lock_metrics.png` in the comparison report and per-PRN `tracking_lock_diagnostics.png` under `verification/navigation/signalsim_navigation_multisat_e1b_only/`.",
])
(output / "boc_lock_detector_validation.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())