113 lines
7.4 KiB
Python
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())
|