Spaces:
Running
Running
File size: 4,920 Bytes
a464cc6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | """POST /api/what-if-replay deterministic re-projection (Phase 4 task 4.M3b).
Spec at docs/wave-41-backend-spec-handoff.md L91-150.
Determinism contract:
- Same (baseline_fixture_id, mutation_key) MUST produce byte-identical
replayed_violation_log per violations.py to_text() output.
- Backend MUST use the same V2 cvxpylayers projector instance + the
same friction-ellipse coefficients as /api/forecast.
The mutation catalogue is the minimum frontend the wave-41 spec
references; new mutations land here as new keys + a `.apply()` function.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Final
import numpy as np
from apex.physics.projection import CvxpyLayersProjector
from apex.shared.contracts import (
CHANNEL_COUNT,
HORIZON,
PhysicsViolationLog,
PROTOCOL_VERSION,
SCHEMA_VERSION,
channel_index,
)
class UnknownFixtureError(ValueError):
"""400 surface: baseline_fixture_id not in BASELINE_FIXTURES."""
class UnknownMutationError(ValueError):
"""400 surface: mutation_key not in MUTATIONS."""
def _build_jerk_bound_fixture() -> np.ndarray:
"""Minimal fixture producing a friction-ellipse violation under V2.
Mirrors the C14-04 jerk-bound fixture the frontend stub references.
The actual jerk-bound check is Tier-8 kinematic; for V2 projector
re-projection we surface the friction-ellipse hit as the visible
log line.
"""
f = np.zeros((HORIZON, CHANNEL_COUNT), dtype=np.float32)
f[10, channel_index("long_g")] = 1.5
f[10, channel_index("speed_mps")] = 40.0
f[10, channel_index("coa_overlap_flag")] = 1.0
return f
BASELINE_FIXTURES: Final[dict[str, dict[str, Any]]] = {
"C14-04-jerk-bound": {
"id": "C14-04-jerk-bound",
"label": "Convergence-14 jerk-bound fixture (C14-04)",
"build_forecast": _build_jerk_bound_fixture,
},
}
def _mutation_coa_overlap_invert(forecast: np.ndarray) -> np.ndarray:
"""Invert the COA simultaneity channel from 1.0 -> 0.0 (or vice versa).
Produces a counterfactual "what if the COA did not permit overlap"
scenario; the validator will then flag any brake+throttle overlap
that survives the re-projection.
"""
mutated = forecast.copy()
idx = channel_index("coa_overlap_flag")
mutated[:, idx] = 1.0 - mutated[:, idx]
return mutated
MUTATIONS: Final[dict[str, Callable[[np.ndarray], np.ndarray]]] = {
"MUTATION_COA_OVERLAP_INVERT": _mutation_coa_overlap_invert,
}
@dataclass(frozen=True)
class ReplayResult:
mutated_fixture: dict[str, Any]
replayed_violation_log: PhysicsViolationLog
schema_version: str
protocol_version: str
# Module-level projector. Single instance per process so the
# cvxpylayers DPP-compiled problem is reused across calls (matches the
# /api/forecast determinism contract per spec L130-138).
_projector_singleton: CvxpyLayersProjector | None = None
def _get_projector() -> CvxpyLayersProjector:
global _projector_singleton
if _projector_singleton is None:
_projector_singleton = CvxpyLayersProjector()
return _projector_singleton
def run_what_if_replay(
*,
baseline_fixture_id: str,
mutation_key: str,
) -> ReplayResult:
"""Run the V2 projector over the mutated fixture; return the
re-projected violation log.
Determinism: caller may call this function any number of times
with the same arguments and receive byte-identical
`replayed_violation_log.to_text()` output. The cvxpylayers solve
is itself deterministic given the same DPP-compiled problem +
same input tensor; the singleton + fixed-fixture path guarantees
those invariants.
"""
if baseline_fixture_id not in BASELINE_FIXTURES:
raise UnknownFixtureError(
f"baseline_fixture_id {baseline_fixture_id!r} not in "
f"BASELINE_FIXTURES; known keys: {sorted(BASELINE_FIXTURES.keys())}"
)
if mutation_key not in MUTATIONS:
raise UnknownMutationError(
f"mutation_key {mutation_key!r} not in MUTATIONS; known keys: "
f"{sorted(MUTATIONS.keys())}"
)
import torch
fixture = BASELINE_FIXTURES[baseline_fixture_id]
baseline = fixture["build_forecast"]()
mutated = MUTATIONS[mutation_key](baseline)
tensor = torch.from_numpy(mutated).unsqueeze(0).float()
result = _get_projector().project(tensor)
return ReplayResult(
mutated_fixture={
"id": baseline_fixture_id,
"mutation": mutation_key,
"shape": list(mutated.shape),
},
replayed_violation_log=result.violation_log,
schema_version=SCHEMA_VERSION,
protocol_version=PROTOCOL_VERSION,
)
__all__ = [
"BASELINE_FIXTURES",
"MUTATIONS",
"ReplayResult",
"UnknownFixtureError",
"UnknownMutationError",
"run_what_if_replay",
]
|