[AZ-270] [AZ-272] [AZ-279] [AZ-281] [AZ-283] Compose root + FDR schema + 3 Layer-1 helpers

AZ-270: composition root with strategy registry, tier-gated lookup,
topo-order construction, all-or-nothing teardown, StrategyNotLinkedError
payload.
AZ-272: orjson-backed FdrRecord serialise/parse with forward-compat for
unknown payload + top-level fields and canonical overrun-record shape.
AZ-279: pyproj-backed WGS84/ECEF/ENU + OSM slippy-map tile math with
WgsConversionError for shape/range/zoom guards.
AZ-281: strict EngineFilenameSchema build/parse/matches_host with
anchored regex + enum validation; round-trip identity by construction.
AZ-283: dtype-preserving (fp16/fp32) single + batch L2 normaliser with
zero-norm safety and descriptor_metric() source-of-truth.
pyproject.toml pins pyproj>=3.6 and orjson>=3.9 (named-backend deps per
the AZ-272 / AZ-279 contracts). New DTOs LatLonAlt + BoundingBox and
EngineCacheKey + HostCapabilities land in _types/ to back the helper
contracts.
203 unit tests pass (64 new). Review verdict: PASS_WITH_WARNINGS;
findings are perf-NFR deferrals + dep amendment + minor docstring polish.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 02:03:36 +03:00
parent 8e71f6c002
commit 3acc7f33dd
24 changed files with 2381 additions and 97 deletions
@@ -5,6 +5,17 @@ ones that have landed so consumers can depend on a stable public surface
without reaching into the helper modules directly.
"""
from gps_denied_onboard.helpers.descriptor_normaliser import (
ALLOWED_DTYPES,
DescriptorNormaliser,
DescriptorNormaliserError,
)
from gps_denied_onboard.helpers.engine_filename_schema import (
ALLOWED_PRECISIONS,
ENGINE_SUFFIX,
EngineFilenameSchema,
EngineFilenameSchemaError,
)
from gps_denied_onboard.helpers.se3_utils import (
SE3,
Se3InvalidMatrixError,
@@ -20,13 +31,30 @@ from gps_denied_onboard.helpers.sha256_sidecar import (
Sha256Sidecar,
Sha256SidecarError,
)
from gps_denied_onboard.helpers.wgs_converter import (
MAX_ZOOM,
WEB_MERCATOR_MAX_LAT_DEG,
WgsConversionError,
WgsConverter,
)
__all__ = [
"ALLOWED_DTYPES",
"ALLOWED_PRECISIONS",
"ENGINE_SUFFIX",
"MAX_ZOOM",
"SE3",
"SIDECAR_SUFFIX",
"WEB_MERCATOR_MAX_LAT_DEG",
"DescriptorNormaliser",
"DescriptorNormaliserError",
"EngineFilenameSchema",
"EngineFilenameSchemaError",
"Se3InvalidMatrixError",
"Sha256Sidecar",
"Sha256SidecarError",
"WgsConversionError",
"WgsConverter",
"adjoint",
"exp_map",
"is_valid_rotation",
@@ -1,14 +1,97 @@
"""Descriptor-normalisation utility — STUB.
"""L2 descriptor normaliser aligning cosine similarity to FAISS inner-product (AZ-283).
Concrete impl owned by AZ-283. Contract:
`_docs/02_document/common-helpers/08_helper_descriptor_normaliser.md`.
Public surface frozen by
``_docs/02_document/contracts/shared_helpers/descriptor_normaliser.md`` v1.0.0.
Used on both the corpus side (C10 index build) and the query side (C2 runtime
lookup). The two sides MUST go through the same helper so the FAISS HNSW
search returns useful neighbours.
"""
from __future__ import annotations
from typing import Any
from typing import Final
import numpy as np
__all__ = [
"ALLOWED_DTYPES",
"DescriptorNormaliser",
"DescriptorNormaliserError",
]
# Allowed input dtypes; anything else is rejected to keep the FAISS index and
# query path on the same precision.
ALLOWED_DTYPES: Final[tuple[np.dtype, ...]] = (
np.dtype(np.float16),
np.dtype(np.float32),
)
_METRIC_VALUE: Final[str] = "inner_product"
def l2_normalise(descriptors: Any) -> Any:
"""L2-normalise a (N, D) descriptor matrix in-place semantics."""
raise NotImplementedError("descriptor_normaliser concrete impl is AZ-283")
class DescriptorNormaliserError(ValueError):
"""Raised on shape / dtype violations (AZ-283)."""
def _validate_dtype(arr: np.ndarray, label: str) -> None:
if arr.dtype not in ALLOWED_DTYPES:
raise DescriptorNormaliserError(
f"{label}: dtype {arr.dtype} not in allowed set (float16, float32)"
)
class DescriptorNormaliser:
"""Stateless L2-normalisation helper; dtype-preserving; zero-norm safe."""
@staticmethod
def l2_normalise(descriptor: np.ndarray) -> np.ndarray:
if not isinstance(descriptor, np.ndarray):
raise DescriptorNormaliserError(
f"l2_normalise: expected np.ndarray; got {type(descriptor).__name__}"
)
if descriptor.ndim != 1:
raise DescriptorNormaliserError(
f"l2_normalise: expected 1-D shape (D,); got shape {descriptor.shape}"
)
if descriptor.shape[0] < 1:
raise DescriptorNormaliserError(
f"l2_normalise: dimension must be >= 1; got shape {descriptor.shape}"
)
_validate_dtype(descriptor, "l2_normalise")
in_dtype = descriptor.dtype
# Compute norm in float32 to stabilise float16 inputs against overflow /
# underflow; cast back to the caller dtype so we never silently up-cast.
as_f32 = descriptor.astype(np.float32, copy=False)
norm = float(np.linalg.norm(as_f32))
if norm == 0.0:
return np.zeros_like(descriptor)
normalised_f32 = as_f32 / norm
return normalised_f32.astype(in_dtype, copy=False)
@staticmethod
def l2_normalise_batch(descriptors: np.ndarray) -> np.ndarray:
if not isinstance(descriptors, np.ndarray):
raise DescriptorNormaliserError(
f"l2_normalise_batch: expected np.ndarray; got {type(descriptors).__name__}"
)
if descriptors.ndim != 2:
raise DescriptorNormaliserError(
f"l2_normalise_batch: expected 2-D shape (N, D); got shape {descriptors.shape}"
)
if descriptors.shape[0] < 1 or descriptors.shape[1] < 1:
raise DescriptorNormaliserError(
f"l2_normalise_batch: N and D must be >= 1; got shape {descriptors.shape}"
)
_validate_dtype(descriptors, "l2_normalise_batch")
in_dtype = descriptors.dtype
as_f32 = descriptors.astype(np.float32, copy=False)
norms = np.linalg.norm(as_f32, axis=1, keepdims=True)
# Avoid division-by-zero: leave zero rows as zero.
safe = np.where(norms == 0.0, 1.0, norms)
normalised_f32 = np.where(norms == 0.0, 0.0, as_f32 / safe)
return normalised_f32.astype(in_dtype, copy=False)
@staticmethod
def descriptor_metric() -> str:
return _METRIC_VALUE
@@ -1,28 +1,127 @@
"""TensorRT engine filename schema — STUB.
"""Self-describing `.engine` filename schema (AZ-281 / D-C10-7).
D-C10-7 self-describing engine names. Concrete impl owned by AZ-281. Contract:
`_docs/02_document/common-helpers/06_helper_engine_filename_schema.md`.
Public surface frozen by
``_docs/02_document/contracts/shared_helpers/engine_filename_schema.md`` v1.0.0.
Filename format: ``{model}__sm{SM}_jp{JP_dotted}_trt{TRT_dotted}_{precision}.engine``
where ``model`` is ``[a-z0-9_]`` (no ``__``), versions are dotted
``<major>.<minor>``, and ``precision`` is one of ``fp16``, ``int8``, ``mixed``.
"""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Final
from gps_denied_onboard._types.manifests import EngineCacheKey, HostCapabilities
__all__ = [
"ALLOWED_PRECISIONS",
"ENGINE_SUFFIX",
"EngineFilenameSchema",
"EngineFilenameSchemaError",
]
ENGINE_SUFFIX: Final[str] = ".engine"
ALLOWED_PRECISIONS: Final[frozenset[str]] = frozenset({"fp16", "int8", "mixed"})
_MODEL_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z0-9_]+$")
_DOTTED_VERSION_RE: Final[re.Pattern[str]] = re.compile(r"^\d+\.\d+$")
_FILENAME_RE: Final[re.Pattern[str]] = re.compile(
r"^(?P<model>[a-z0-9_]+)__sm(?P<sm>\d+)_jp(?P<jetpack>\d+\.\d+)_trt(?P<trt>\d+\.\d+)_"
r"(?P<precision>fp16|int8|mixed)\.engine$"
)
@dataclass(frozen=True)
class EngineFilename:
"""Parsed parts of a self-describing engine filename."""
class EngineFilenameSchemaError(ValueError):
"""Raised by ``build`` / ``parse`` on validation / format violations (AZ-281)."""
model_name: str
sm_arch: str
jetpack_version: str
tensorrt_version: str
precision: str
content_hash: str
def render(self) -> str:
raise NotImplementedError("engine_filename_schema concrete impl is AZ-281")
class EngineFilenameSchema:
"""Stateless ``.engine`` filename builder / parser / host-match predicate."""
@classmethod
def parse(cls, filename: str) -> EngineFilename:
raise NotImplementedError("engine_filename_schema concrete impl is AZ-281")
@staticmethod
def build(model_name: str, sm: int, jetpack: str, trt: str, precision: str) -> str:
_validate_model_name(model_name)
_validate_sm(sm)
_validate_version(jetpack, "jetpack")
_validate_version(trt, "trt")
_validate_precision(precision)
return f"{model_name}__sm{sm}_jp{jetpack}_trt{trt}_{precision}{ENGINE_SUFFIX}"
@staticmethod
def parse(filename: str) -> EngineCacheKey:
if not isinstance(filename, str):
raise EngineFilenameSchemaError(f"parse expects str; got {type(filename).__name__}")
if not filename.endswith(ENGINE_SUFFIX):
raise EngineFilenameSchemaError(
f"parse: filename must end with {ENGINE_SUFFIX!r}; got {filename!r}"
)
match = _FILENAME_RE.match(filename)
if not match:
raise EngineFilenameSchemaError(
f"parse: filename {filename!r} does not match the engine-schema format "
"'{model}__sm{SM}_jp{JP}_trt{TRT}_{precision}.engine'"
)
model = match.group("model")
if "__" in model:
raise EngineFilenameSchemaError(
f"parse: model segment {model!r} contains reserved separator '__'"
)
return EngineCacheKey(
model_name=model,
sm=int(match.group("sm")),
jetpack=match.group("jetpack"),
trt=match.group("trt"),
precision=match.group("precision"),
)
@staticmethod
def matches_host(filename: str, host_capabilities: HostCapabilities) -> bool:
key = EngineFilenameSchema.parse(filename)
return (
key.sm == host_capabilities.sm
and key.jetpack == host_capabilities.jetpack
and key.trt == host_capabilities.trt
)
def _validate_model_name(model_name: str) -> None:
if not isinstance(model_name, str):
raise EngineFilenameSchemaError(f"model_name must be str; got {type(model_name).__name__}")
if not model_name:
raise EngineFilenameSchemaError("model_name must be a non-empty string")
if "__" in model_name:
raise EngineFilenameSchemaError(
f"model_name {model_name!r} contains reserved separator '__'"
)
if not _MODEL_RE.match(model_name):
raise EngineFilenameSchemaError(
f"model_name {model_name!r} must match [a-z0-9_]+ (lowercase, digits, underscores)"
)
if len(model_name) > 64:
raise EngineFilenameSchemaError(f"model_name {model_name!r} exceeds 64-character limit")
def _validate_sm(sm: int) -> None:
if not isinstance(sm, int) or isinstance(sm, bool):
raise EngineFilenameSchemaError(f"sm must be a non-bool integer; got {sm!r}")
if sm <= 0:
raise EngineFilenameSchemaError(f"sm must be > 0; got {sm}")
def _validate_version(version: str, field_name: str) -> None:
if not isinstance(version, str):
raise EngineFilenameSchemaError(f"{field_name} must be str; got {type(version).__name__}")
if not _DOTTED_VERSION_RE.match(version):
raise EngineFilenameSchemaError(
f"{field_name} {version!r} must match dotted '<major>.<minor>' format"
)
def _validate_precision(precision: str) -> None:
if precision not in ALLOWED_PRECISIONS:
raise EngineFilenameSchemaError(
f"precision {precision!r} not in allowed enum "
f"{{{', '.join(sorted(ALLOWED_PRECISIONS))}}}"
)
+162 -18
View File
@@ -1,26 +1,170 @@
"""WGS84 ↔ local-tangent-plane converter — STUB.
"""WGS84 ↔ ECEF ↔ ENU ↔ slippy-map tile-xy conversions (AZ-279 / E-CC-HELPERS).
Concrete implementation is owned by AZ-279. Contract:
`_docs/02_document/common-helpers/04_helper_wgs_converter.md`.
Public surface frozen by
``_docs/02_document/contracts/shared_helpers/wgs_converter.md`` v1.0.0.
Backed by ``pyproj`` for the geodesy primitives. Slippy-map tile math is hand
rolled to match OSM's `{zoom}/{x}/{y}.jpg` convention exactly so the on-disk
layout produced by ``satellite-provider`` round-trips byte-equal.
"""
from __future__ import annotations
import math
from typing import Final
def wgs84_to_ltp(
lat_deg: float,
lon_deg: float,
alt_m: float,
ref_lat_deg: float,
ref_lon_deg: float,
ref_alt_m: float,
) -> tuple[float, float, float]:
"""Convert a WGS-84 lat/lon/alt to local-tangent-plane east/north/up metres."""
raise NotImplementedError("wgs_converter concrete impl is AZ-279")
import numpy as np
from pyproj import Transformer # type: ignore[import-not-found]
from gps_denied_onboard._types.geo import BoundingBox, LatLonAlt
__all__ = ["MAX_ZOOM", "WEB_MERCATOR_MAX_LAT_DEG", "WgsConversionError", "WgsConverter"]
def ltp_to_wgs84(
e_m: float, n_m: float, u_m: float, ref_lat_deg: float, ref_lon_deg: float, ref_alt_m: float
) -> tuple[float, float, float]:
"""Inverse of wgs84_to_ltp."""
raise NotImplementedError("wgs_converter concrete impl is AZ-279")
WEB_MERCATOR_MAX_LAT_DEG: Final[float] = 85.0511287798066
MAX_ZOOM: Final[int] = 22
class WgsConversionError(ValueError):
"""Raised on shape / range violations in any ``WgsConverter`` static method."""
_ECEF_FROM_LLA: Final[Transformer] = Transformer.from_crs("EPSG:4326", "EPSG:4978", always_xy=True)
_LLA_FROM_ECEF: Final[Transformer] = Transformer.from_crs("EPSG:4978", "EPSG:4326", always_xy=True)
def _validate_finite_latlonalt(p: LatLonAlt, label: str) -> None:
if not (math.isfinite(p.lat_deg) and math.isfinite(p.lon_deg) and math.isfinite(p.alt_m)):
raise WgsConversionError(f"{label}: non-finite component in {p!r}")
if not (-90.0 <= p.lat_deg <= 90.0):
raise WgsConversionError(f"{label}: latitude {p.lat_deg} outside [-90, 90]")
if not (-180.0 <= p.lon_deg <= 180.0):
raise WgsConversionError(f"{label}: longitude {p.lon_deg} outside [-180, 180]")
def _enforce_ecef_shape(arr: np.ndarray, label: str) -> None:
if not isinstance(arr, np.ndarray):
raise WgsConversionError(
f"{label}: expected np.ndarray of shape (3,); got {type(arr).__name__}"
)
if arr.shape != (3,):
raise WgsConversionError(
f"{label}: expected np.ndarray of shape (3,); got shape {arr.shape}"
)
if not np.all(np.isfinite(arr)):
raise WgsConversionError(f"{label}: non-finite component in {arr!r}")
class WgsConverter:
"""Stateless WGS84 / ECEF / ENU / slippy-map-tile converter.
Every method is a pure function of its arguments; no module-level state
other than the cached ``pyproj`` transformer pair.
"""
@staticmethod
def latlonalt_to_ecef(p: LatLonAlt) -> np.ndarray:
_validate_finite_latlonalt(p, "latlonalt_to_ecef")
x, y, z = _ECEF_FROM_LLA.transform(p.lon_deg, p.lat_deg, p.alt_m)
return np.array([x, y, z], dtype=np.float64)
@staticmethod
def ecef_to_latlonalt(p_ecef: np.ndarray) -> LatLonAlt:
_enforce_ecef_shape(p_ecef, "ecef_to_latlonalt")
lon, lat, alt = _LLA_FROM_ECEF.transform(
float(p_ecef[0]), float(p_ecef[1]), float(p_ecef[2])
)
return LatLonAlt(lat_deg=float(lat), lon_deg=float(lon), alt_m=float(alt))
@staticmethod
def latlonalt_to_local_enu(origin: LatLonAlt, p: LatLonAlt) -> np.ndarray:
_validate_finite_latlonalt(origin, "latlonalt_to_local_enu/origin")
_validate_finite_latlonalt(p, "latlonalt_to_local_enu/p")
return _ecef_delta_to_enu(origin, WgsConverter.latlonalt_to_ecef(p))
@staticmethod
def local_enu_to_latlonalt(origin: LatLonAlt, p_enu: np.ndarray) -> LatLonAlt:
_validate_finite_latlonalt(origin, "local_enu_to_latlonalt/origin")
_enforce_ecef_shape(p_enu, "local_enu_to_latlonalt/p_enu")
origin_ecef = WgsConverter.latlonalt_to_ecef(origin)
rotation = _enu_to_ecef_rotation(origin.lat_deg, origin.lon_deg)
delta_ecef = rotation @ p_enu.astype(np.float64)
return WgsConverter.ecef_to_latlonalt(origin_ecef + delta_ecef)
@staticmethod
def latlon_to_tile_xy(zoom: int, lat: float, lon: float) -> tuple[int, int]:
_validate_zoom(zoom)
if not (math.isfinite(lat) and math.isfinite(lon)):
raise WgsConversionError(f"latlon_to_tile_xy: non-finite input (lat={lat}, lon={lon})")
if abs(lat) > WEB_MERCATOR_MAX_LAT_DEG:
raise WgsConversionError(
f"latlon_to_tile_xy: latitude {lat} outside Web-Mercator range "
f"[-{WEB_MERCATOR_MAX_LAT_DEG}, {WEB_MERCATOR_MAX_LAT_DEG}]"
)
if not (-180.0 <= lon <= 180.0):
raise WgsConversionError(f"latlon_to_tile_xy: longitude {lon} outside [-180, 180]")
n = 1 << zoom
lat_rad = math.radians(lat)
x = math.floor((lon + 180.0) / 360.0 * n)
y = math.floor(
(1.0 - math.log(math.tan(lat_rad) + 1.0 / math.cos(lat_rad)) / math.pi) / 2.0 * n
)
x = max(0, min(x, n - 1))
y = max(0, min(y, n - 1))
return x, y
@staticmethod
def tile_xy_to_latlon_bounds(zoom: int, x: int, y: int) -> BoundingBox:
_validate_zoom(zoom)
n = 1 << zoom
if not (0 <= x < n and 0 <= y < n):
raise WgsConversionError(
f"tile_xy_to_latlon_bounds: tile (x={x}, y={y}) outside [0, {n}) at zoom {zoom}"
)
return BoundingBox(
min_lat_deg=_tile_y_to_lat(y + 1, n),
min_lon_deg=_tile_x_to_lon(x, n),
max_lat_deg=_tile_y_to_lat(y, n),
max_lon_deg=_tile_x_to_lon(x + 1, n),
)
def _validate_zoom(zoom: int) -> None:
if not isinstance(zoom, int) or isinstance(zoom, bool):
raise WgsConversionError(f"zoom must be a non-bool integer; got {zoom!r}")
if not (0 <= zoom <= MAX_ZOOM):
raise WgsConversionError(f"zoom {zoom} outside supported range [0, {MAX_ZOOM}]")
def _tile_x_to_lon(x: int, n: int) -> float:
return x / n * 360.0 - 180.0
def _tile_y_to_lat(y: int, n: int) -> float:
t = math.pi * (1.0 - 2.0 * y / n)
return math.degrees(math.atan(math.sinh(t)))
def _enu_to_ecef_rotation(lat_deg: float, lon_deg: float) -> np.ndarray:
"""Rotation matrix mapping local ENU vectors to ECEF deltas at ``(lat, lon)``."""
lat = math.radians(lat_deg)
lon = math.radians(lon_deg)
sin_lat = math.sin(lat)
cos_lat = math.cos(lat)
sin_lon = math.sin(lon)
cos_lon = math.cos(lon)
return np.array(
[
[-sin_lon, -sin_lat * cos_lon, cos_lat * cos_lon],
[cos_lon, -sin_lat * sin_lon, cos_lat * sin_lon],
[0.0, cos_lat, sin_lat],
],
dtype=np.float64,
)
def _ecef_delta_to_enu(origin: LatLonAlt, p_ecef: np.ndarray) -> np.ndarray:
origin_ecef = WgsConverter.latlonalt_to_ecef(origin)
delta = p_ecef - origin_ecef
rotation = _enu_to_ecef_rotation(origin.lat_deg, origin.lon_deg)
return rotation.T @ delta