Files
galileo/tools/generate_signalsim_vectors.py
T

111 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Regenerate deterministic SignalSim vectors without modifying SignalSim."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
import sys
from copy import deepcopy
import yaml
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--signalsim-path", required=True, type=Path)
parser.add_argument("--executable", type=Path)
vector_names = [
"high_cn0_single_prn",
"nominal_cn0_single_prn",
"negative_doppler",
"multiple_prns",
"navigation_single_prn",
"navigation_multisat",
]
parser.add_argument("--vector", choices=[*vector_names, "all"], default="high_cn0_single_prn")
parser.add_argument("--project-root", type=Path, default=Path(__file__).resolve().parents[1])
args = parser.parse_args()
source = args.signalsim_path.resolve()
root = args.project_root.resolve()
executable = args.executable.resolve() if args.executable else root / "build" / "signalsim" / "IFdataGen"
if not executable.is_file():
subprocess.run([str(root / "tools" / "build_signalsim.sh"), str(source)], check=True)
template_path = root / "verification" / "vectors" / "signalsim" / "high_cn0_single_prn" / "config.json"
template = json.loads(template_path.read_text(encoding="utf-8"))
rinex = source / "EphData" / "BRDC00IGS_R_20211700000_01D_MN.rnx"
if not rinex.is_file():
raise FileNotFoundError(f"bundled RINEX input is missing: {rinex}")
commit = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
variants = {
"high_cn0_single_prn": {"duration_s": 2.2, "cn0_db_hz": 55.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}},
"nominal_cn0_single_prn": {"duration_s": 0.6, "cn0_db_hz": 50.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}},
"negative_doppler": {"duration_s": 2.2, "cn0_db_hz": 55.0, "visible_prns": [27], "expected_doppler_hz": {27: -2637.0}},
"multiple_prns": {"duration_s": 0.12, "cn0_db_hz": 55.0, "visible_prns": [1, 4, 19, 21, 27], "expected_doppler_hz": {1: 1828.0, 4: 2171.0, 19: -367.0, 21: -712.0, 27: -2637.0}},
"navigation_single_prn": {"duration_s": 4.2, "cn0_db_hz": 55.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}, "sample_frequency_mhz": 4.092, "center_frequency_mhz": 1574.397},
"navigation_multisat": {"duration_s": 32.2, "cn0_db_hz": 55.0, "visible_prns": [1, 4, 19, 21, 27], "expected_doppler_hz": {1: 1828.0, 4: 2171.0, 19: -367.0, 21: -712.0, 27: -2637.0}, "sample_frequency_mhz": 4.092, "center_frequency_mhz": 1574.397},
}
selected = vector_names if args.vector == "all" else [args.vector]
for vector_name in selected:
parameters = variants[vector_name]
vector_dir = root / "verification" / "vectors" / "signalsim" / vector_name
vector_dir.mkdir(parents=True, exist_ok=True)
config = deepcopy(template)
config["description"] = f"SignalSim validation vector: {vector_name}"
config["ephemeris"]["name"] = str(rinex)
config["trajectory"]["trajectoryList"][0]["time"] = parameters["duration_s"]
config["power"]["initPower"]["value"] = parameters["cn0_db_hz"]
config["output"]["sampleFreq"] = parameters.get("sample_frequency_mhz", 8.184)
config["output"]["centerFreq"] = parameters.get("center_frequency_mhz", 1573.374)
visible = set(parameters["visible_prns"])
config["output"]["config"]["maskOut"] = [{"system": "Galileo", "svid": [prn for prn in range(1, 37) if prn not in visible]}]
output_name = f"{vector_name}_iq8.bin"
config["output"]["name"] = output_name
generated_config = vector_dir / "generated_config.json"
generated_config.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
output = vector_dir / output_name
command = [str(executable), "-c", str(generated_config), "-o", str(output), "-t", "-st"]
completed = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(vector_dir / "generation.log").write_text(completed.stdout, encoding="utf-8")
metadata = {
"name": vector_name,
"signalsim_path": str(source),
"signalsim_commit": commit,
"command": command,
"output": str(output.relative_to(root)),
"size_bytes": output.stat().st_size,
"sha256": sha256(output),
"complex_sample_count": output.stat().st_size // 2,
"sampling_frequency_hz": round(float(config["output"]["sampleFreq"]) * 1e6, 6),
"intermediate_frequency_hz": round(
(1575.42 - float(config["output"]["centerFreq"])) * 1e6, 6
),
"sample_format": "IQ8_interleaved_signed_I_then_Q",
"duration_s": parameters["duration_s"],
"prns": parameters["visible_prns"],
"components": ["E1B", "E1C"],
"expected_doppler_hz": parameters["expected_doppler_hz"],
"expected_code_delay": None,
"navigation_truth": "INavBit generated from bundled RINEX; no sample-side truth file",
"secondary_truth": "PilotBit.cpp SecondaryCodeE1, phase must be synchronized",
}
(vector_dir / "metadata.yaml").write_text(yaml.safe_dump(metadata, sort_keys=False), encoding="utf-8")
print(completed.stdout, end="")
print(yaml.safe_dump(metadata, sort_keys=False), end="")
return 0
if __name__ == "__main__":
sys.exit(main())