mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 17:13:54 +08:00
Complete FLM learning pipeline
This commit is contained in:
@@ -5,35 +5,37 @@ RECORDED_DEBT = 'recorded-debt-v1'
|
||||
STATE_ALIGNED_DEBT = 'candidate-state-debt-v1'
|
||||
STATE_ALIGNED_RELIEF = 'candidate-state-relief-v2'
|
||||
EXACT_EMISSION_QUALITY = 'candidate-exact-emission-v3'
|
||||
ZERO_REFERENCE_QUALITY = 'candidate-zero-reference-exact-v4'
|
||||
QUALIFIED_EVENT_CLIPPING_QUALITY = EXACT_EMISSION_QUALITY
|
||||
STRICT_IDENTIFICATION = 'strict-final-quality-v1'
|
||||
NATIVE_SHADOW_IDENTIFICATION = 'native-limited-shadow-v1'
|
||||
FIXED_MAP_IDENTIFICATION = 'fixed-map-no-identification-v1'
|
||||
RESTORE_ACTIVATION_VERSION = 'fixed-eight-whole-map-restore-v1'
|
||||
RESTORE_AGGREGATE_ACTIVATION_VERSION = 'fixed-eight-whole-map-restore-support-v2'
|
||||
RESTORE_ZERO_REFERENCE_VERSION = 'fixed-eight-whole-map-restore-zero-reference-v3'
|
||||
|
||||
|
||||
def clipping_quality(value):
|
||||
if type(value) is not str or value not in (RECORDED_DEBT, STATE_ALIGNED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY):
|
||||
if type(value) is not str or value not in (RECORDED_DEBT, STATE_ALIGNED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY):
|
||||
raise ValueError('unknown clipping quality contract')
|
||||
return value
|
||||
|
||||
|
||||
def event_clipping_quality(payload):
|
||||
if type(payload.get('event_version')) is int and payload['event_version'] in (4, 5):
|
||||
if type(payload.get('event_version')) is int and payload['event_version'] in (4, 5, 6):
|
||||
if (payload.get('event_operation') != 'restore' or
|
||||
payload.get('event_restore_policy') != {4: RESTORE_ACTIVATION_VERSION, 5: RESTORE_AGGREGATE_ACTIVATION_VERSION}[payload['event_version']] or
|
||||
payload.get('event_clipping_quality') != QUALIFIED_EVENT_CLIPPING_QUALITY or
|
||||
payload.get('event_restore_policy') != restore_policy(payload['event_version']) or
|
||||
payload.get('event_clipping_quality') != restore_quality(payload['event_version']) or
|
||||
payload.get('event_identification_policy') != FIXED_MAP_IDENTIFICATION):
|
||||
raise ValueError('explicit whole-map restore policy/operation')
|
||||
return QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
return restore_quality(payload['event_version'])
|
||||
if 'event_restore_policy' in payload:
|
||||
raise ValueError('whole-map restore policy requires version4/5')
|
||||
raise ValueError('whole-map restore policy requires version4/5/6')
|
||||
if 'event_clipping_quality' in payload and payload.get('event_version') != 3:
|
||||
raise ValueError('explicit clipping quality requires event version3')
|
||||
contract = clipping_quality(payload.get('event_clipping_quality', RECORDED_DEBT))
|
||||
expected = identification_for_quality(contract)
|
||||
if contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY) and payload.get('event_identification_policy') != expected:
|
||||
if contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) and payload.get('event_identification_policy') != expected:
|
||||
raise ValueError('combined event experiment requires explicit native shadow identification')
|
||||
if 'event_identification_policy' in payload and (payload.get('event_version') != 3 or
|
||||
identification_policy(payload['event_identification_policy']) != expected):
|
||||
@@ -88,4 +90,36 @@ def identification_policy(value):
|
||||
|
||||
def identification_for_quality(contract):
|
||||
clipping_quality(contract)
|
||||
return NATIVE_SHADOW_IDENTIFICATION if contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY) else STRICT_IDENTIFICATION
|
||||
return NATIVE_SHADOW_IDENTIFICATION if contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) else STRICT_IDENTIFICATION
|
||||
|
||||
|
||||
def restore_policy(version):
|
||||
if type(version) is not int or version not in (4, 5, 6):
|
||||
raise ValueError('explicit whole-map restore version')
|
||||
return {4: RESTORE_ACTIVATION_VERSION, 5: RESTORE_AGGREGATE_ACTIVATION_VERSION, 6: RESTORE_ZERO_REFERENCE_VERSION}[version]
|
||||
|
||||
|
||||
def restore_quality(version):
|
||||
restore_policy(version)
|
||||
return ZERO_REFERENCE_QUALITY if version == 6 else QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
|
||||
|
||||
def zero_reference_relief_is_admitted(recorded_request, reference_request, candidate_request,
|
||||
recorded_output, reference_output, candidate_output,
|
||||
recorded_debt, reference_debt, candidate_debt):
|
||||
"""Exact paired-output boundary of UNWIND; never native or physical authority.
|
||||
|
||||
Callers must additionally certify original LIMITED context, audited codec,
|
||||
exact candidate/reference raw integer equality and full native projection.
|
||||
Honda supplies shaped normalized requests and decoded physical-domain output.
|
||||
"""
|
||||
requests = recorded_request, reference_request, candidate_request
|
||||
outputs = recorded_output, reference_output, candidate_output
|
||||
values = requests + outputs + (recorded_debt, reference_debt, candidate_debt)
|
||||
if any(type(value) not in (int, float) or not math.isfinite(value) for value in values):
|
||||
return False
|
||||
sign = 1 if recorded_request > 0 else -1
|
||||
return bool(reference_debt == 0 and all(sign * value > 0 for value in requests + outputs) and
|
||||
recorded_debt * recorded_request < 0 and candidate_debt * candidate_request < 0 and
|
||||
abs(candidate_request) < min(abs(recorded_request), abs(reference_request)) and
|
||||
candidate_output == reference_output and abs(candidate_output) <= abs(recorded_output))
|
||||
|
||||
@@ -52,6 +52,18 @@ def _numeric(value):
|
||||
|
||||
def _tree(values, path, value):
|
||||
"""Container shape markers prevent stale factory shape from dropping gains."""
|
||||
kind = type(value)
|
||||
if kind is float or kind is int or kind is bool:
|
||||
# Exact builtin leaves only. Preserve the original coercion, Float64 bits,
|
||||
# nonfinite error and post-append cardinality guard. Subclasses/NumPy/native
|
||||
# containers continue through the original generic traversal below.
|
||||
number = float(value)
|
||||
if not math.isfinite(number):
|
||||
raise ValueError('snapshot requires finite numeric state')
|
||||
values.append((path, number))
|
||||
if len(values) > MAX_SNAPSHOT_VALUES:
|
||||
raise ValueError('snapshot exceeds bounded primitive budget')
|
||||
return
|
||||
if type(value).__module__.startswith('capnp') and 'List' in type(value).__name__:
|
||||
# Native PID gains contain Cap'n Proto list readers. Only scalar contents
|
||||
# cross the wire; reconstructing a builtin list preserves interpolation.
|
||||
@@ -388,15 +400,57 @@ class SelectedControllerFactory:
|
||||
return controller, VehicleModel(self.cp)
|
||||
|
||||
|
||||
def _run(controller, vm, inputs, objective, coordinates, torque_values=(), centre=None, actual_stage_residual=None, effect_trial=None):
|
||||
from opendbc.car import structs
|
||||
@dataclass(frozen=True)
|
||||
class _PreparedCarState:
|
||||
inputs: tuple
|
||||
reader: object
|
||||
|
||||
|
||||
def _car_state(values, *, first_segment_words=None):
|
||||
# Keep the original Capnp conversions, including Float32 fields and enum.
|
||||
from opendbc.car import structs
|
||||
cs = (structs.CarState.new_message() if first_segment_words is None else
|
||||
structs.CarState.new_message(num_first_segment_words=first_segment_words))
|
||||
for name in _CS_FIELDS:
|
||||
setattr(cs, name, bool(values[name]) if name in ('steeringPressed', 'standstill', 'steerFaultTemporary', 'steerFaultPermanent') else values[name])
|
||||
cs.gearShifter = int(values['gearShifter'])
|
||||
return cs
|
||||
|
||||
|
||||
class _PreparedCarStates:
|
||||
"""One child job only: immutable input tuple refs, never frame-object refs."""
|
||||
def __init__(self):
|
||||
self.__entries = {}
|
||||
|
||||
def get(self, inputs):
|
||||
saved = self.__entries.get(id(inputs))
|
||||
if saved is not None and saved.inputs is inputs:
|
||||
return saved
|
||||
if (len(self.__entries) >= 12000 or type(inputs) is not tuple or
|
||||
any(type(row) is not tuple or len(row) != 2 or type(row[0]) is not str or
|
||||
type(row[1]) not in (bool, int, float, str) for row in inputs)):
|
||||
return None
|
||||
# Strongly retain the deeply immutable tuple: identity cannot be recycled.
|
||||
prepared = _PreparedCarState(inputs, _car_state(dict(inputs), first_segment_words=32).as_reader())
|
||||
self.__entries[id(inputs)] = prepared
|
||||
return prepared
|
||||
|
||||
|
||||
def _replay_car_state(inputs, values, prepared):
|
||||
if type(prepared) is _PreparedCarState and prepared.inputs is inputs:
|
||||
return prepared.reader
|
||||
return _car_state(values)
|
||||
|
||||
|
||||
def _run(controller, vm, inputs, objective, coordinates, torque_values=(), centre=None, actual_stage_residual=None, effect_trial=None,
|
||||
_prepared_car_state=None):
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
if type(controller) is LatControlPID:
|
||||
from openpilot.starpilot.flm.pid_controller_replay import run_pid
|
||||
if torque_values or any(coordinates) or centre is not None and any(centre.values):
|
||||
raise ValueError('PID cannot consume torque tuning or legacy residual coordinates')
|
||||
return run_pid(controller, vm, inputs, effect_trial=effect_trial)
|
||||
kwargs = {} if _prepared_car_state is None else {'_prepared_car_state': _prepared_car_state}
|
||||
return run_pid(controller, vm, inputs, effect_trial=effect_trial, **kwargs)
|
||||
values = dict(inputs)
|
||||
if torque_values:
|
||||
tune = dict(torque_values)
|
||||
@@ -410,10 +464,7 @@ def _run(controller, vm, inputs, objective, coordinates, torque_values=(), centr
|
||||
controller.torque_params.latAccelOffset = tune['offset']
|
||||
controller.torque_params.friction = tune['friction']
|
||||
controller.pid.set_limits(tune['pid_pos_limit'], tune['pid_neg_limit'])
|
||||
cs = structs.CarState.new_message()
|
||||
for name in _CS_FIELDS:
|
||||
setattr(cs, name, bool(values[name]) if name in ('steeringPressed', 'standstill', 'steerFaultTemporary', 'steerFaultPermanent') else values[name])
|
||||
cs.gearShifter = int(values['gearShifter'])
|
||||
cs = _replay_car_state(inputs, values, _prepared_car_state)
|
||||
for name in _VM_FIELDS:
|
||||
setattr(vm, name, values['vm.' + name])
|
||||
params = SimpleNamespace(angleOffsetDeg=values['angleOffsetDeg'], roll=values['roll'])
|
||||
@@ -588,6 +639,40 @@ class ControllerReplay:
|
||||
return bool(frame.healthy and not frame.manual and
|
||||
(not frame.limited or self.raw_mode and replay_context(frame)))
|
||||
|
||||
def _recorded_cp_digest(self):
|
||||
"""One successful canonical CP digest; reread ALL native bytes each lookup.
|
||||
|
||||
Only owned registered CarParams readers share this replay-local derived
|
||||
value. No object identity shortcut, payload flag, global/current cache or
|
||||
cached error. Unknown schemas/objects keep the original canonical function.
|
||||
"""
|
||||
from capnp.lib.capnp import _DynamicStructReader
|
||||
from opendbc.car import structs
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import car_params_digest
|
||||
|
||||
cp = self.factory.cp
|
||||
raw = None
|
||||
try:
|
||||
if ((type(self.factory) is SelectedControllerFactory or type(self.factory) is PidControllerFactory) and
|
||||
type(cp) is _DynamicStructReader and cp.schema == structs.CarParams.schema):
|
||||
raw = cp.as_builder().to_bytes()
|
||||
except Exception:
|
||||
pass # Original digest below retains its own errors and unsupported behavior.
|
||||
if type(raw) is not bytes:
|
||||
return car_params_digest(cp)
|
||||
saved = self.__dict__.get('_ControllerReplay__recorded_cp_digest_cache')
|
||||
if type(saved) is tuple and len(saved) == 3 and saved[0] == raw and saved[2] is car_params_digest:
|
||||
return saved[1]
|
||||
value = car_params_digest(cp)
|
||||
try:
|
||||
unchanged = cp.as_builder().to_bytes() == raw
|
||||
except Exception:
|
||||
unchanged = False
|
||||
if unchanged and type(value) is str and len(value) == 64 and all(char in '0123456789abcdef' for char in value):
|
||||
self.__dict__['_ControllerReplay__recorded_cp_digest_cache'] = (raw, value, car_params_digest)
|
||||
return value
|
||||
|
||||
def normalize(self, frame, *, epsilon=0.0001, _verify_only=False):
|
||||
if (
|
||||
self.failed
|
||||
@@ -615,10 +700,9 @@ class ControllerReplay:
|
||||
recorded_effect = None
|
||||
if frame.effect_trace is not None:
|
||||
from openpilot.starpilot.flm.selected_effects import EffectReplayRecordedSource
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import car_params_digest
|
||||
if (frame.effect_trace.control_ns != frame.effect_prepare_ns or
|
||||
frame.effect_trace.envelope.selected_baseline_revision != self.baseline_revision or
|
||||
frame.effect_trace.envelope.cp_sha256 != car_params_digest(self.factory.cp)):
|
||||
frame.effect_trace.envelope.cp_sha256 != self._recorded_cp_digest()):
|
||||
raise ValueError('recorded full30 trace source/prepare clock mismatch')
|
||||
recorded_effect = EffectReplayRecordedSource(frame.effect_trace)
|
||||
_verify_only = True
|
||||
|
||||
@@ -6,15 +6,18 @@ import math
|
||||
|
||||
from openpilot.starpilot.flm.physical_response import PhysicalModel
|
||||
from openpilot.starpilot.flm.clipping_quality import (
|
||||
RECORDED_DEBT, STATE_ALIGNED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY,
|
||||
RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, QUALIFIED_EVENT_CLIPPING_QUALITY, clipping_quality, identification_for_quality,
|
||||
RECORDED_DEBT, STATE_ALIGNED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY,
|
||||
RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, RESTORE_ZERO_REFERENCE_VERSION,
|
||||
QUALIFIED_EVENT_CLIPPING_QUALITY, clipping_quality, identification_for_quality,
|
||||
)
|
||||
|
||||
ACTIVATION_VERSION = 'fixed-eight-owner-v1'
|
||||
STATE_ALIGNED_ACTIVATION_VERSION = 'fixed-eight-owner-state-aligned-v2'
|
||||
COMBINED_ACTIVATION_VERSION = 'fixed-eight-shadow-relief-v3'
|
||||
EXACT_EMISSION_ACTIVATION_VERSION = 'fixed-eight-shadow-exact-emission-v4'
|
||||
ACTIVATION_VERSIONS = (ACTIVATION_VERSION, STATE_ALIGNED_ACTIVATION_VERSION, COMBINED_ACTIVATION_VERSION, EXACT_EMISSION_ACTIVATION_VERSION)
|
||||
ZERO_REFERENCE_ACTIVATION_VERSION = 'fixed-eight-shadow-native-feasible-v6'
|
||||
ACTIVATION_VERSIONS = (ACTIVATION_VERSION, STATE_ALIGNED_ACTIVATION_VERSION, COMBINED_ACTIVATION_VERSION,
|
||||
EXACT_EMISSION_ACTIVATION_VERSION, ZERO_REFERENCE_ACTIVATION_VERSION)
|
||||
CALIBRATION_EVENTS = 8
|
||||
|
||||
|
||||
@@ -76,7 +79,8 @@ class EventActivationPlan:
|
||||
def clipping_quality(self):
|
||||
self.validate()
|
||||
return {ACTIVATION_VERSION: RECORDED_DEBT, STATE_ALIGNED_ACTIVATION_VERSION: STATE_ALIGNED_DEBT,
|
||||
COMBINED_ACTIVATION_VERSION: STATE_ALIGNED_RELIEF, EXACT_EMISSION_ACTIVATION_VERSION: EXACT_EMISSION_QUALITY}[self.version]
|
||||
COMBINED_ACTIVATION_VERSION: STATE_ALIGNED_RELIEF, EXACT_EMISSION_ACTIVATION_VERSION: EXACT_EMISSION_QUALITY,
|
||||
ZERO_REFERENCE_ACTIVATION_VERSION: ZERO_REFERENCE_QUALITY}[self.version]
|
||||
|
||||
@property
|
||||
def identification_policy(self):
|
||||
@@ -100,7 +104,8 @@ def build_activation_plan(engine, *, model, cutoff_ns, stage_events, physical_co
|
||||
"""No trial coordinates, rollout results, desired gains or cutoff search here."""
|
||||
clipping_quality(quality_contract)
|
||||
version = {RECORDED_DEBT: ACTIVATION_VERSION, STATE_ALIGNED_DEBT: STATE_ALIGNED_ACTIVATION_VERSION,
|
||||
STATE_ALIGNED_RELIEF: COMBINED_ACTIVATION_VERSION, EXACT_EMISSION_QUALITY: EXACT_EMISSION_ACTIVATION_VERSION}[quality_contract]
|
||||
STATE_ALIGNED_RELIEF: COMBINED_ACTIVATION_VERSION, EXACT_EMISSION_QUALITY: EXACT_EMISSION_ACTIVATION_VERSION,
|
||||
ZERO_REFERENCE_QUALITY: ZERO_REFERENCE_ACTIVATION_VERSION}[quality_contract]
|
||||
frames = {frame.control_ns: frame for frame in engine.records}
|
||||
windows = tuple(engine.replay_windows)
|
||||
if (not stage_events or any(min(point['control_ns'], point['apply_ns'], point['mono_ns']) <= cutoff_ns
|
||||
@@ -154,7 +159,8 @@ def activation_model_from_dict(value):
|
||||
if 'activation_version' not in copied:
|
||||
return PhysicalModel(**copied)
|
||||
model = ActivatedPhysicalModel(**copied)
|
||||
if (model.activation_version not in (*ACTIVATION_VERSIONS, RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION) or
|
||||
restore_versions = (RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, RESTORE_ZERO_REFERENCE_VERSION)
|
||||
if (model.activation_version not in (*ACTIVATION_VERSIONS, *restore_versions) or
|
||||
not _hex64(model.activation_plan_sha256) or
|
||||
model.domain != 'normalized_torque' or
|
||||
any(type(value) not in (int, float) or not math.isfinite(value) for value in
|
||||
@@ -174,10 +180,13 @@ def activation_matches_model(plan, model):
|
||||
|
||||
def model_clipping_quality(model):
|
||||
if type(model) is ActivatedPhysicalModel:
|
||||
if model.activation_version == RESTORE_ZERO_REFERENCE_VERSION:
|
||||
return ZERO_REFERENCE_QUALITY
|
||||
if model.activation_version in (RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION):
|
||||
return QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
if model.activation_version not in ACTIVATION_VERSIONS:
|
||||
raise ValueError('unknown activated physical proof version')
|
||||
return {ACTIVATION_VERSION: RECORDED_DEBT, STATE_ALIGNED_ACTIVATION_VERSION: STATE_ALIGNED_DEBT,
|
||||
COMBINED_ACTIVATION_VERSION: STATE_ALIGNED_RELIEF, EXACT_EMISSION_ACTIVATION_VERSION: EXACT_EMISSION_QUALITY}[model.activation_version]
|
||||
COMBINED_ACTIVATION_VERSION: STATE_ALIGNED_RELIEF, EXACT_EMISSION_ACTIVATION_VERSION: EXACT_EMISSION_QUALITY,
|
||||
ZERO_REFERENCE_ACTIVATION_VERSION: ZERO_REFERENCE_QUALITY}[model.activation_version]
|
||||
return RECORDED_DEBT
|
||||
|
||||
@@ -47,9 +47,9 @@ class EventCollectionAdapter(TorqueCollectionAdapter):
|
||||
self.clipping_quality = validate_clipping_quality(clipping_quality)
|
||||
if replay_version != 3 and self.clipping_quality != RECORDED_DEBT:
|
||||
raise ValueError('state-aligned quality requires explicit event version3')
|
||||
from openpilot.starpilot.flm.clipping_quality import QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
if type(restore_version) is not int or restore_version not in (2, 4, 5) or (restore_version in (4, 5) and
|
||||
(replay_version != 3 or self.clipping_quality != QUALIFIED_EVENT_CLIPPING_QUALITY)):
|
||||
from openpilot.starpilot.flm.clipping_quality import restore_quality
|
||||
if type(restore_version) is not int or restore_version not in (2, 4, 5, 6) or (restore_version in (4, 5, 6) and
|
||||
(replay_version != 3 or self.clipping_quality != restore_quality(restore_version))):
|
||||
raise ValueError('explicit whole-map restore producer contract')
|
||||
self.restore_version = restore_version
|
||||
self.replay_version = replay_version
|
||||
@@ -212,14 +212,14 @@ class EventCollectionAdapter(TorqueCollectionAdapter):
|
||||
envelope = self.envelope.for_trial(self.event_objective.names).restore_from_history(self.optimizer_prior)
|
||||
envelope.validate()
|
||||
operation = 'restore'
|
||||
from openpilot.starpilot.flm.clipping_quality import FIXED_MAP_IDENTIFICATION, RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION
|
||||
from openpilot.starpilot.flm.clipping_quality import FIXED_MAP_IDENTIFICATION, restore_policy
|
||||
payload = {'version': 1, 'family': self.command_family,
|
||||
'event_version': self.replay_version if operation == 'trial' else self.restore_version, 'event_operation': operation,
|
||||
**({'event_clipping_quality': self.clipping_quality, 'event_identification_policy': identification_for_quality(self.clipping_quality)}
|
||||
if self.replay_version == 3 and operation == 'trial' else {}),
|
||||
**({'event_clipping_quality': self.clipping_quality, 'event_identification_policy': FIXED_MAP_IDENTIFICATION,
|
||||
'event_restore_policy': {4: RESTORE_ACTIVATION_VERSION, 5: RESTORE_AGGREGATE_ACTIVATION_VERSION}[self.restore_version]}
|
||||
if operation == 'restore' and self.restore_version in (4, 5) else {}),
|
||||
'event_restore_policy': restore_policy(self.restore_version)}
|
||||
if operation == 'restore' and self.restore_version in (4, 5, 6) else {}),
|
||||
'event_objective': asdict(self.event_objective),
|
||||
'effect_envelope': asdict(envelope), 'objective': asdict(self.objective),
|
||||
'now_ns': now_ns, 'sequence': sequence, 'prior': tuple(dict(envelope.prior_coordinates)[name] for name in self.event_objective.names),
|
||||
@@ -265,12 +265,13 @@ class EventCollectionAdapter(TorqueCollectionAdapter):
|
||||
not set(result.profile.train_ids + result.profile.heldout_ids).issubset(plan.stage_blocks) or
|
||||
not activation_matches_model(plan, model)):
|
||||
return False
|
||||
elif report_version in (4, 5):
|
||||
elif report_version in (4, 5, 6):
|
||||
from openpilot.starpilot.flm.restore_activation import restore_plan_from_dict, restore_plan_matches
|
||||
from openpilot.starpilot.flm.clipping_quality import RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION
|
||||
from openpilot.starpilot.flm.clipping_quality import restore_policy
|
||||
plan = restore_plan_from_dict(report['restore_activation_plan'])
|
||||
expected_policy = {4: RESTORE_ACTIVATION_VERSION, 5: RESTORE_AGGREGATE_ACTIVATION_VERSION}[report_version]
|
||||
expected_policy = restore_policy(report_version)
|
||||
if (self.restore_version != report_version or plan.version != expected_policy or
|
||||
report_version == 6 and plan.clipping_quality != self.clipping_quality or
|
||||
envelope.operation != 'restore' or 'activation_plan' in report or
|
||||
plan.identity != self.identity or plan.baseline_revision != self.baseline_revision or
|
||||
plan.source_epoch != self.source_epoch or plan.cutoff_ns != report['model_cutoff_ns'] or
|
||||
@@ -289,7 +290,7 @@ class EventCollectionAdapter(TorqueCollectionAdapter):
|
||||
physical_model_revision(model) != validation.physical_model_revision or validation.baseline_revision != self.baseline_revision):
|
||||
return False
|
||||
self.event_objective = EffectObjective(self.key[1], envelope.trial_names, envelope.trial_anchor, self.source_epoch)
|
||||
if report_version in (3, 4, 5) and self.model != model:
|
||||
if report_version in (3, 4, 5, 6) and self.model != model:
|
||||
self.current_physical_valid = False
|
||||
self.last_physical_ns = 0
|
||||
self.model, self.model_cutoff_ns = model, report['model_cutoff_ns']
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Child-only canonical effect trials through complete selected controller/CAN history."""
|
||||
from collections.abc import Mapping
|
||||
import math
|
||||
|
||||
from openpilot.starpilot.flm.clipping_quality import NATIVE_SHADOW_IDENTIFICATION
|
||||
|
||||
from openpilot.starpilot.flm.adaptive_profile import ReplayValidation
|
||||
from openpilot.starpilot.flm.controller_replay import MAX_FRAME_GAP_NS, _run, final_command, restore_snapshot, same_final_command, same_float
|
||||
from openpilot.starpilot.flm.controller_replay import (
|
||||
MAX_FRAME_GAP_NS, _run, capture_snapshot, final_command, restore_snapshot, same_final_command, same_float,
|
||||
)
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import canonical_physical_context, physical_model_revision
|
||||
from openpilot.starpilot.flm.torque_eligibility import exact_unclipped_pair
|
||||
|
||||
@@ -12,6 +15,48 @@ POLICY = 'event_effect_coordinate_v2'
|
||||
RESTORE_POLICY = 'event_effect_restore_v1'
|
||||
|
||||
|
||||
class _LazyControllerRequests(Mapping):
|
||||
"""Only original native replay may pull this child-local controller iterator.
|
||||
|
||||
Values are cached by their exact control timestamp. Native apply-order stays
|
||||
owned by EmissionHistory.rollout, including duplicate/older requested stamps.
|
||||
No result is successful until finish() exhausts all original controller guards.
|
||||
"""
|
||||
def __init__(self, values, frames):
|
||||
self.values, self.frames = values, frames
|
||||
self.finished = False
|
||||
|
||||
def __contains__(self, stamp):
|
||||
while stamp not in self.values and not self.finished:
|
||||
try:
|
||||
next(self.frames)
|
||||
except StopIteration:
|
||||
self.finished = True
|
||||
return stamp in self.values
|
||||
|
||||
def __getitem__(self, stamp):
|
||||
if stamp not in self:
|
||||
raise KeyError(stamp)
|
||||
return self.values[stamp]
|
||||
|
||||
def __iter__(self):
|
||||
self.finish()
|
||||
return iter(self.values)
|
||||
|
||||
def __len__(self):
|
||||
self.finish()
|
||||
return len(self.values)
|
||||
|
||||
def close(self):
|
||||
self.frames.close()
|
||||
|
||||
def finish(self):
|
||||
if not self.finished:
|
||||
for _ in self.frames:
|
||||
pass
|
||||
self.finished = True
|
||||
|
||||
|
||||
class EventControllerReplay:
|
||||
"""Reuse original source/native/encoder proofs; replace only trial coordinates."""
|
||||
def __init__(self, engine, objective, original_anchor, *, restore_envelope=None, activation_plan=None):
|
||||
@@ -22,6 +67,11 @@ class EventControllerReplay:
|
||||
self.restore_envelope = restore_envelope
|
||||
self.activation_plan = activation_plan
|
||||
self.__identification_token = object()
|
||||
self.__strict_rollouts = None
|
||||
self.__causal_features = None
|
||||
self.__prepared_car_states = None
|
||||
self.__controller_prefix = None
|
||||
self.__pending_controller_prefix = None
|
||||
if activation_plan is not None:
|
||||
from openpilot.starpilot.flm.restore_activation import WholeMapRestorePlan, restore_plan_matches
|
||||
activation_plan.validate()
|
||||
@@ -59,6 +109,77 @@ class EventControllerReplay:
|
||||
return False
|
||||
|
||||
def _rollout(self, coordinates, *, restore_target=False, restore_omit_group=None, _identification_token=None):
|
||||
"""At most baseline+last successful strict trial result, never authority.
|
||||
|
||||
Every hit rebinds exact owned input bytes. No errors, shadow probes, restore
|
||||
paths or current admissions are memoized. Final validate still checks its
|
||||
current source/model/sample/support/heldout predicates independently.
|
||||
"""
|
||||
from openpilot.starpilot.flm.event_activation import EventActivationPlan, ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
import pickle
|
||||
from types import MappingProxyType
|
||||
|
||||
plan = self.__dict__.get('activation_plan')
|
||||
eligible = (type(plan) is EventActivationPlan and plan.version == ZERO_REFERENCE_ACTIVATION_VERSION and
|
||||
self.restore_envelope is None and restore_target is False and restore_omit_group is None and _identification_token is None and
|
||||
type(coordinates) is tuple and all(type(value) in (int, float) and math.isfinite(value) for value in coordinates))
|
||||
if not eligible:
|
||||
# The controller's ORIGINAL prefix is independent of native strict/shadow
|
||||
# policy. Never reuse native output or a native-policy verdict here.
|
||||
prefix_key = None
|
||||
if (_identification_token is not None and
|
||||
_identification_token is self.__dict__.get('_EventControllerReplay__identification_token') and
|
||||
self.__dict__.get('_EventControllerReplay__controller_prefix') is not None and
|
||||
type(plan) is EventActivationPlan and plan.version == ZERO_REFERENCE_ACTIVATION_VERSION and
|
||||
self.restore_envelope is None and restore_target is False and restore_omit_group is None):
|
||||
prefix_key = self._causal_input_key(.01)
|
||||
if prefix_key is None:
|
||||
return self._rollout_uncached(coordinates, restore_target=restore_target,
|
||||
restore_omit_group=restore_omit_group, _identification_token=_identification_token)
|
||||
result = self._rollout_uncached(coordinates, restore_target=restore_target,
|
||||
restore_omit_group=restore_omit_group, _identification_token=_identification_token,
|
||||
_prefix_context=(prefix_key, False))
|
||||
if self._causal_input_key(.01) != prefix_key:
|
||||
self.__controller_prefix = None
|
||||
raise ValueError('controller prefix changed owned input during execution')
|
||||
return result
|
||||
source_key = self._causal_input_key(.01)
|
||||
if source_key is None:
|
||||
self.__strict_rollouts = None
|
||||
self.__controller_prefix = None
|
||||
return self._rollout_uncached(coordinates)
|
||||
if self.__strict_rollouts is None or self.__strict_rollouts[0] != source_key:
|
||||
self.__strict_rollouts = source_key, {}
|
||||
slots = self.__strict_rollouts[1]
|
||||
coordinate_key = pickle.dumps(coordinates, protocol=5) # Type/bit exact, including signed zero.
|
||||
slot = 'baseline' if coordinate_key == pickle.dumps(self.reference, protocol=5) else 'candidate'
|
||||
key = plan.version, 'strict-trial', coordinate_key
|
||||
cached = slots.get(slot)
|
||||
if cached is not None and cached[0] == key:
|
||||
return tuple(dict(part) for part in cached[1])
|
||||
from openpilot.starpilot.flm.controller_replay import ControllerReplay
|
||||
from openpilot.starpilot.flm.torque_emission_replay import EmissionHistory
|
||||
prefix_scoped = (type(self) is EventControllerReplay and type(self.engine) is ControllerReplay and
|
||||
type(self.emissions) is EmissionHistory)
|
||||
build_prefix = slot == 'baseline' and prefix_scoped
|
||||
self.__pending_controller_prefix = None
|
||||
if prefix_scoped:
|
||||
result = self._rollout_uncached(coordinates, _prefix_context=(source_key, build_prefix))
|
||||
else:
|
||||
result = self._rollout_uncached(coordinates)
|
||||
if self._causal_input_key(.01) != source_key:
|
||||
self.__strict_rollouts = None
|
||||
self.__controller_prefix = self.__pending_controller_prefix = None
|
||||
raise ValueError('strict rollout changed owned input during execution')
|
||||
if build_prefix:
|
||||
self.__controller_prefix = self.__pending_controller_prefix
|
||||
self.__pending_controller_prefix = None
|
||||
frozen = tuple(MappingProxyType(dict(part)) for part in result)
|
||||
slots[slot] = key, frozen
|
||||
return tuple(dict(part) for part in frozen)
|
||||
|
||||
def _rollout_uncached(self, coordinates, *, restore_target=False, restore_omit_group=None, _identification_token=None,
|
||||
_prefix_context=None):
|
||||
from openpilot.starpilot.flm.selected_effects import (EffectTrial, EffectReplayTransition, EffectReplayRecordedSource,
|
||||
EffectRestoreTrial, EffectRestoreAblationTrial)
|
||||
|
||||
@@ -98,79 +219,324 @@ class EventControllerReplay:
|
||||
if any(a > b or i and a <= windows[i - 1][1] for i, (a, b) in enumerate(windows)):
|
||||
raise ValueError('event windows overlap')
|
||||
controller, vm = self.factory.create()
|
||||
# Parent/default/current replay never allocates this child-only reader cache.
|
||||
from openpilot.starpilot.flm.controller_replay import SelectedControllerFactory, _PreparedCarStates
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
supported = (len(frames) <= 12000 and type(self.factory) in (SelectedControllerFactory, PidControllerFactory) and
|
||||
type(controller) in (LatControlTorque, LatControlPID))
|
||||
prepared_states = self.__dict__.get('_EventControllerReplay__prepared_car_states') if supported else None
|
||||
if supported and type(prepared_states) is not _PreparedCarStates:
|
||||
prepared_states = self.__prepared_car_states = _PreparedCarStates()
|
||||
transition = None if trial is None or restore_target else EffectReplayTransition(trial)
|
||||
checkpoints, requests, saturated = dict(self.checkpoints), {}, {}
|
||||
prior_ns = prior_sequence = index = 0
|
||||
neutral = not restore_target and (self.restore_envelope is not None or tuple(coordinates) == self.reference)
|
||||
for frame in frames:
|
||||
while index < len(windows) and frame.control_ns > windows[index][1]:
|
||||
index += 1
|
||||
if index >= len(windows) or not windows[index][0] <= frame.control_ns <= windows[index][1]:
|
||||
raise ValueError('event frame outside original window')
|
||||
if frame.control_ns == windows[index][0]:
|
||||
if frame.control_ns not in checkpoints:
|
||||
raise ValueError('event actual pre-maneuver state missing')
|
||||
restore_snapshot(controller, vm, checkpoints[frame.control_ns])
|
||||
if transition is not None:
|
||||
transition.reset()
|
||||
prior_ns = prior_sequence = 0
|
||||
elif not prior_ns:
|
||||
raise ValueError('event window beginning omitted')
|
||||
if (not self.engine._frame_context(frame) or not self._prior_matches(frame) or any(frame.centre_values) or
|
||||
frame.actual_stage_residual is not None and any(frame.actual_stage_residual) or
|
||||
prior_ns and not 0 < frame.control_ns - prior_ns <= MAX_FRAME_GAP_NS or
|
||||
prior_sequence and frame.frame_sequence != prior_sequence + 1):
|
||||
raise ValueError('event source/history mismatch')
|
||||
trace = getattr(frame, 'effect_trace', None)
|
||||
prepare_ns = getattr(frame, 'effect_prepare_ns', 0)
|
||||
activation = None if self.activation_plan is None else self.activation_plan.activation_for(*windows[index])
|
||||
if self.activation_plan is not None and activation is None:
|
||||
raise ValueError('event activation window omitted')
|
||||
original_prefix = bool(activation is not None and (activation[0] == 0 or frame.control_ns < activation[0]))
|
||||
activation_tick = bool(activation is not None and frame.control_ns == activation[0])
|
||||
if activation_tick and prepare_ns != activation[1]:
|
||||
raise ValueError('event activation original prepare clock changed')
|
||||
if self.activation_plan is not None and restore_target and not original_prefix and not 0 < prepare_ns <= frame.control_ns:
|
||||
raise ValueError('restore activation original owner clock unavailable')
|
||||
if neutral or original_prefix:
|
||||
source = None if trace is None else EffectReplayRecordedSource(trace)
|
||||
elif restore_target:
|
||||
# Whole-map endpoint qualification is independent from the separately
|
||||
# replayed/probed gradual owner transition. Never reduce this map to the
|
||||
# protocol's <=4 marker coordinates or infer neutral from zero markers.
|
||||
source = trial
|
||||
elif prepare_ns > 0:
|
||||
source = transition.source_for_frame(prepare_ns, trace, new_owner=activation_tick)
|
||||
elif self.activation_plan is not None:
|
||||
raise ValueError('event activation post-treatment owner clock missing')
|
||||
elif trace is None and not any(self.prior_full.values()):
|
||||
# Historical zero-effect data may qualify a stationary shape proposal;
|
||||
# it contains no new owner prepare stamp and confers no slew admission.
|
||||
# Parent current_effect_probe separately requires fresh exact prepare_ns.
|
||||
source = trial
|
||||
else:
|
||||
raise ValueError('event exact owner transition clock unavailable')
|
||||
output, log = _run(controller, vm, frame.inputs, self.engine.objective, (0.0, 0.0), frame.torque_values,
|
||||
actual_stage_residual=frame.actual_stage_residual, effect_trial=source)
|
||||
if (neutral or original_prefix) and (not same_final_command(output, frame.selected_command) or
|
||||
frame.actual_lac_command is not None and not same_float(output, frame.actual_lac_command)):
|
||||
raise ValueError('event neutral selected trajectory changed')
|
||||
output = final_command(output)
|
||||
if not math.isfinite(output) or abs(output) > 1.000001:
|
||||
raise ValueError('event selected command envelope')
|
||||
requests[frame.control_ns], saturated[frame.control_ns] = output, bool(log.saturated)
|
||||
prior_ns, prior_sequence = frame.control_ns, frame.frame_sequence
|
||||
if index != len(windows) - 1 or prior_ns != windows[-1][1]:
|
||||
raise ValueError('event last actual tick missing')
|
||||
# This replays every consumed native encoding, including clipped unscored
|
||||
# context. The encoder independently refuses greater/opposite clipping.
|
||||
quality = {} if self.activation_plan is None else {'quality_contract': self.activation_plan.clipping_quality}
|
||||
if shadow:
|
||||
quality['identification_policy'] = NATIVE_SHADOW_IDENTIFICATION
|
||||
encoded = self.emissions.rollout(requests, windows[0][0], windows[-1][1], windows, **quality)
|
||||
return requests, encoded, saturated
|
||||
# Only the wrapper supplies a freshly hashed owned-input context. A new
|
||||
# certificate is published there AFTER full strict success and rehashing.
|
||||
from openpilot.starpilot.flm.event_activation import EventActivationPlan, ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
from types import MappingProxyType
|
||||
from openpilot.starpilot.flm.torque_emission_replay import EmissionHistory
|
||||
from openpilot.starpilot.lateral.torque_extension import TorqueExtension
|
||||
from openpilot.starpilot.lateral.ioniq6_policy import Ioniq6TorquePolicy
|
||||
extension = getattr(controller, 'starpilot_extension', None)
|
||||
prefix_supported = (type(controller) is LatControlPID or type(controller) is LatControlTorque and
|
||||
(extension is None or type(extension) is TorqueExtension and type(extension.policy) is Ioniq6TorquePolicy))
|
||||
prefix_eligible = (supported and prefix_supported and type(self.emissions) is EmissionHistory and
|
||||
type(self.activation_plan) is EventActivationPlan and
|
||||
self.activation_plan.version == ZERO_REFERENCE_ACTIVATION_VERSION and self.restore_envelope is None and
|
||||
not restore_target and restore_omit_group is None and _prefix_context is not None)
|
||||
build_prefix = bool(prefix_eligible and _prefix_context[1] and not shadow and tuple(coordinates) == self.reference)
|
||||
certificate = self.__dict__.get('_EventControllerReplay__controller_prefix') if prefix_eligible and not build_prefix else None
|
||||
if certificate is not None and certificate[0] != _prefix_context[0]:
|
||||
certificate = None
|
||||
prefix_requests, prefix_saturated, prefix_states = {}, {}, {}
|
||||
|
||||
def controller_frames():
|
||||
nonlocal build_prefix
|
||||
prior_ns = prior_sequence = index = 0
|
||||
neutral = not restore_target and (self.restore_envelope is not None or tuple(coordinates) == self.reference)
|
||||
for frame in frames:
|
||||
while index < len(windows) and frame.control_ns > windows[index][1]:
|
||||
index += 1
|
||||
if index >= len(windows) or not windows[index][0] <= frame.control_ns <= windows[index][1]:
|
||||
raise ValueError('event frame outside original window')
|
||||
if frame.control_ns == windows[index][0]:
|
||||
if frame.control_ns not in checkpoints:
|
||||
raise ValueError('event actual pre-maneuver state missing')
|
||||
restore_snapshot(controller, vm, checkpoints[frame.control_ns])
|
||||
if transition is not None:
|
||||
transition.reset()
|
||||
prior_ns = prior_sequence = 0
|
||||
elif not prior_ns:
|
||||
raise ValueError('event window beginning omitted')
|
||||
if (not self.engine._frame_context(frame) or not self._prior_matches(frame) or any(frame.centre_values) or
|
||||
frame.actual_stage_residual is not None and any(frame.actual_stage_residual) or
|
||||
prior_ns and not 0 < frame.control_ns - prior_ns <= MAX_FRAME_GAP_NS or
|
||||
prior_sequence and frame.frame_sequence != prior_sequence + 1):
|
||||
raise ValueError('event source/history mismatch')
|
||||
trace = getattr(frame, 'effect_trace', None)
|
||||
prepare_ns = getattr(frame, 'effect_prepare_ns', 0)
|
||||
activation = None if self.activation_plan is None else self.activation_plan.activation_for(*windows[index])
|
||||
if self.activation_plan is not None and activation is None:
|
||||
raise ValueError('event activation window omitted')
|
||||
original_prefix = bool(activation is not None and (activation[0] == 0 or frame.control_ns < activation[0]))
|
||||
activation_tick = bool(activation is not None and frame.control_ns == activation[0])
|
||||
if activation_tick and prepare_ns != activation[1]:
|
||||
raise ValueError('event activation original prepare clock changed')
|
||||
reusable_prefix = bool(original_prefix and activation[0] > windows[index][0])
|
||||
if activation_tick:
|
||||
if build_prefix and activation[0] > windows[index][0]:
|
||||
try:
|
||||
prefix_states[windows[index]] = capture_snapshot(controller, vm)
|
||||
except (AttributeError, TypeError, ValueError, KeyError, OverflowError):
|
||||
# An unavailable optimization checkpoint must NOT reject an
|
||||
# otherwise ordinary replay. Never publish a partial certificate.
|
||||
build_prefix = False
|
||||
prefix_requests.clear()
|
||||
prefix_saturated.clear()
|
||||
prefix_states.clear()
|
||||
elif certificate is not None and windows[index] in certificate[3]:
|
||||
restore_snapshot(controller, vm, certificate[3][windows[index]])
|
||||
if self.activation_plan is not None and restore_target and not original_prefix and not 0 < prepare_ns <= frame.control_ns:
|
||||
raise ValueError('restore activation original owner clock unavailable')
|
||||
if neutral or original_prefix:
|
||||
source = None if trace is None else EffectReplayRecordedSource(trace)
|
||||
elif restore_target:
|
||||
# Whole-map endpoint qualification is independent from the separately
|
||||
# replayed/probed gradual owner transition. Never reduce this map to the
|
||||
# protocol's <=4 marker coordinates or infer neutral from zero markers.
|
||||
source = trial
|
||||
elif prepare_ns > 0:
|
||||
source = transition.source_for_frame(prepare_ns, trace, new_owner=activation_tick)
|
||||
elif self.activation_plan is not None:
|
||||
raise ValueError('event activation post-treatment owner clock missing')
|
||||
elif trace is None and not any(self.prior_full.values()):
|
||||
# Historical zero-effect data may qualify a stationary shape proposal;
|
||||
# it contains no new owner prepare stamp and confers no slew admission.
|
||||
# Parent current_effect_probe separately requires fresh exact prepare_ns.
|
||||
source = trial
|
||||
else:
|
||||
raise ValueError('event exact owner transition clock unavailable')
|
||||
if certificate is not None and reusable_prefix and windows[index] in certificate[3]:
|
||||
# Retain every source/history/clock/prepare/recorded-source check above.
|
||||
# Later state is restored ONLY at the exact declared activation tick.
|
||||
output = certificate[1][frame.control_ns]
|
||||
is_saturated = certificate[2][frame.control_ns]
|
||||
else:
|
||||
prepared = None if prepared_states is None else prepared_states.get(frame.inputs)
|
||||
kwargs = {} if prepared is None else {'_prepared_car_state': prepared}
|
||||
output, log = _run(controller, vm, frame.inputs, self.engine.objective, (0.0, 0.0), frame.torque_values,
|
||||
actual_stage_residual=frame.actual_stage_residual, effect_trial=source, **kwargs)
|
||||
is_saturated = bool(log.saturated)
|
||||
if (neutral or original_prefix) and (not same_final_command(output, frame.selected_command) or
|
||||
frame.actual_lac_command is not None and not same_float(output, frame.actual_lac_command)):
|
||||
raise ValueError('event neutral selected trajectory changed')
|
||||
raw_output = output # Preserve raw Float64 for the original actual_lac guard.
|
||||
output = final_command(output)
|
||||
if not math.isfinite(output) or abs(output) > 1.000001:
|
||||
raise ValueError('event selected command envelope')
|
||||
requests[frame.control_ns], saturated[frame.control_ns] = output, is_saturated
|
||||
if build_prefix and reusable_prefix:
|
||||
prefix_requests[frame.control_ns], prefix_saturated[frame.control_ns] = raw_output, is_saturated
|
||||
prior_ns, prior_sequence = frame.control_ns, frame.frame_sequence
|
||||
yield frame.control_ns
|
||||
if index != len(windows) - 1 or prior_ns != windows[-1][1]:
|
||||
raise ValueError('event last actual tick missing')
|
||||
|
||||
lazy_requests = _LazyControllerRequests(requests, controller_frames())
|
||||
try:
|
||||
from openpilot.starpilot.flm.torque_emission_replay import EmissionHistory
|
||||
native_requests = lazy_requests
|
||||
if not supported or type(self.emissions) is not EmissionHistory:
|
||||
lazy_requests.finish() # Unknown/legacy shells retain original eager order.
|
||||
native_requests = requests # Preserve their original plain-dict API too.
|
||||
# Original native replay owns every emission, including unscored context.
|
||||
quality = {} if self.activation_plan is None else {'quality_contract': self.activation_plan.clipping_quality}
|
||||
if shadow:
|
||||
quality['identification_policy'] = NATIVE_SHADOW_IDENTIFICATION
|
||||
encoded = self.emissions.rollout(native_requests, windows[0][0], windows[-1][1], windows, **quality)
|
||||
# Native success is insufficient: late controller-only frames and the exact
|
||||
# original final-window/tick guard must ALSO finish before any result exists.
|
||||
lazy_requests.finish()
|
||||
if build_prefix and prefix_requests and prefix_states:
|
||||
self.__pending_controller_prefix = (_prefix_context[0], MappingProxyType(prefix_requests),
|
||||
MappingProxyType(prefix_saturated), MappingProxyType(prefix_states))
|
||||
return requests, encoded, saturated
|
||||
finally:
|
||||
# Release suspended controller/native traceback resources on rejection.
|
||||
# close() NEVER substitutes for the required successful finish() above.
|
||||
lazy_requests.close()
|
||||
|
||||
def _causal_input_key(self, epsilon):
|
||||
"""Exact owned input bytes, not equality of floats or a caller-ready flag.
|
||||
|
||||
Includes every original frame/native witness/checkpoint, full-map anchor,
|
||||
selected factory configuration, objective, epsilon and declared plan.
|
||||
Pickle is only serialized+hashed here; no cache bytes are ever unpickled.
|
||||
"""
|
||||
import hashlib
|
||||
import pickle
|
||||
|
||||
if 'engine' not in self.__dict__:
|
||||
return None # A legacy/test shell has no owned input history to cache.
|
||||
try:
|
||||
from openpilot.starpilot.flm.controller_replay import SelectedControllerFactory
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
factory = self.factory
|
||||
if type(factory) not in (SelectedControllerFactory, PidControllerFactory):
|
||||
return None
|
||||
policy = ('torque', str(factory.policy)) if type(factory) is SelectedControllerFactory else ('stock-pid', None)
|
||||
selected = (type(factory).__module__, type(factory).__qualname__,
|
||||
factory.cp.as_builder().to_bytes(), str(factory.mode), policy, factory.turn_assist,
|
||||
factory.manual_baseline, factory.manual_source_epoch,
|
||||
factory.interface_class.__module__, factory.interface_class.__qualname__)
|
||||
native = (self.emissions.cp.as_builder().to_bytes(), self.emissions.identity, self.emissions.baseline_revision,
|
||||
tuple(self.emissions.events.items()), tuple(self.emissions.carriers.items()))
|
||||
data = (epsilon, self.identity, self.baseline_revision, self.engine.model_revision,
|
||||
self.engine.failed, self.engine.raw_mode, selected, self.engine.objective,
|
||||
self.objective, self.reference, self.prior_full, self.original_anchor,
|
||||
self.activation_plan, self.restore_envelope, tuple(self.records), tuple(self.checkpoints),
|
||||
tuple(self.replay_windows), native)
|
||||
return hashlib.sha256(pickle.dumps(data, protocol=5)).digest()
|
||||
except (AttributeError, TypeError, ValueError, OverflowError, pickle.PicklingError):
|
||||
return None # Unsupported cache key means full replay, never readiness.
|
||||
|
||||
def reuse_subset_causal_features(self, original, *, epsilon=.01):
|
||||
"""Project a verified full FD matrix only for the identical owned subtrial.
|
||||
|
||||
This transfers numeric columns, never a native verdict, shadow token,
|
||||
profile, validation, lease, or optimizer choice. Any unknown case rebuilds.
|
||||
"""
|
||||
import pickle
|
||||
import struct
|
||||
from types import MappingProxyType
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import NAMES, parameters_for
|
||||
from openpilot.starpilot.flm.event_activation import EventActivationPlan, ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.selected_effects import EffectTrial, EffectReplayTransition
|
||||
|
||||
try:
|
||||
if (type(self) is not EventControllerReplay or type(original) is not EventControllerReplay or self is original or
|
||||
'engine' not in self.__dict__ or 'engine' not in original.__dict__):
|
||||
return False
|
||||
if (type(epsilon) is not float or not math.isfinite(epsilon) or not 0 < epsilon <= .03 or
|
||||
self.restore_envelope is not None or original.restore_envelope is not None or
|
||||
self.engine is not original.engine or
|
||||
self.failed or original.failed or not self.raw_mode or not original.raw_mode or
|
||||
any(getattr(self, name) is not getattr(original, name) for name in
|
||||
('factory', 'records', 'checkpoints', 'replay_windows', 'emissions', 'activation_plan')) or
|
||||
type(self.objective) is not EffectObjective or type(original.objective) is not EffectObjective or
|
||||
self.objective.group != original.objective.group or self.objective.context_key != original.objective.context_key):
|
||||
return False
|
||||
if self.activation_plan is not None and (type(self.activation_plan) is not EventActivationPlan or
|
||||
self.activation_plan.version != ZERO_REFERENCE_ACTIVATION_VERSION):
|
||||
return False
|
||||
names, full_names = self.objective.names, original.objective.names
|
||||
if (not set(names) < set(full_names) or self.objective.parameters != parameters_for(names) or
|
||||
original.objective.parameters != parameters_for(full_names)):
|
||||
return False
|
||||
if (self.__causal_features is not None or self.projected or
|
||||
pickle.dumps((self.identity, self.baseline_revision), protocol=5) !=
|
||||
pickle.dumps((original.identity, original.baseline_revision), protocol=5)):
|
||||
return False
|
||||
saved = original.__causal_features
|
||||
old_key = original._causal_input_key(epsilon)
|
||||
if (old_key is None or type(saved) is not tuple or len(saved) != 2 or saved[0] != old_key or
|
||||
type(saved[1]) is not MappingProxyType):
|
||||
return False
|
||||
|
||||
def bits(values):
|
||||
if type(values) is not dict or tuple(values) != NAMES or any(type(values[n]) is not float for n in NAMES):
|
||||
raise ValueError('subset requires exact builtin full30 float prior')
|
||||
return struct.pack('!30d', *(values[n] for n in NAMES))
|
||||
|
||||
before = EffectTrial(original.objective, original.reference, original.baseline_revision, original.original_anchor)
|
||||
after = EffectTrial(self.objective, self.reference, self.baseline_revision, self.original_anchor)
|
||||
_, _, old_prior = before.basis()
|
||||
_, _, new_prior = after.basis()
|
||||
prior_bits = bits(old_prior)
|
||||
if any(bits(value) != prior_bits for value in (new_prior, original.prior_full, self.prior_full)):
|
||||
return False
|
||||
for engine in (original, self):
|
||||
reference = tuple(engine.prior_full[name] for name in engine.objective.names)
|
||||
if (type(engine.reference) is not tuple or any(type(value) is not float for value in engine.reference) or
|
||||
struct.pack('!' + 'd' * len(reference), *engine.reference) != struct.pack('!' + 'd' * len(reference), *reference)):
|
||||
return False
|
||||
# The only allowed anchor-payload difference declares the smaller names.
|
||||
old_anchor, new_anchor = dict(original.original_anchor), dict(self.original_anchor)
|
||||
old_anchor.pop('anchor_digest')
|
||||
new_anchor.pop('anchor_digest')
|
||||
if pickle.dumps(old_anchor, protocol=5) != pickle.dumps(new_anchor, protocol=5):
|
||||
return False
|
||||
indices = tuple(full_names.index(name) for name in names)
|
||||
# Independently validate BOTH bases/targets/static full30 source geometry
|
||||
# for each exact clamped probe. Same original trace, plan, checkpoint and
|
||||
# prepare clocks then give identical step_full paths by induction.
|
||||
for index, parameter in enumerate(self.objective.parameters):
|
||||
for direction in (-1, 1):
|
||||
full = list(original.reference)
|
||||
subset = list(self.reference)
|
||||
probe = max(parameter.low, min(parameter.high, self.reference[index] + direction * epsilon))
|
||||
full[indices[index]], subset[index] = probe, probe
|
||||
left = EffectTrial(original.objective, tuple(full), self.baseline_revision, original.original_anchor)
|
||||
right = EffectTrial(self.objective, tuple(subset), self.baseline_revision, self.original_anchor)
|
||||
left_transition, right_transition = EffectReplayTransition(left), EffectReplayTransition(right)
|
||||
left_source, right_source = left.source(), right.source()
|
||||
if (bits(left_transition.original_target) != bits(right_transition.original_target) or
|
||||
bits(left_transition.target) != bits(right_transition.target) or
|
||||
bits(left_source.values) != bits(right_source.values) or
|
||||
left_source.full_profile_sha256 != right_source.full_profile_sha256 or
|
||||
left_source.original_effect_anchor != original.objective.anchor_digest or
|
||||
right_source.original_effect_anchor != self.objective.anchor_digest):
|
||||
return False
|
||||
copied = {}
|
||||
for stamp, (baseline, features) in saved[1].items():
|
||||
if (type(stamp) is not int or type(features) is not tuple or len(features) != len(full_names) or
|
||||
any(type(value) is not float or not math.isfinite(value) for value in (baseline, *features))):
|
||||
return False
|
||||
copied[stamp] = baseline, tuple(features[index] for index in indices)
|
||||
if not copied or original._causal_input_key(epsilon) != old_key:
|
||||
return False
|
||||
new_key = self._causal_input_key(epsilon)
|
||||
if new_key is None:
|
||||
return False
|
||||
self.__causal_features = new_key, MappingProxyType(copied)
|
||||
self.projected = dict(copied)
|
||||
# ONLY after every original subset/full30/source proof succeeded, rebind
|
||||
# the controller-only ORIGINAL prefix. _run consumes the common base
|
||||
# engine objective, recorded source and inputs before activation; trial
|
||||
# names/coordinates/transition are unused there. Native replay stays fresh.
|
||||
# Prefix keys ALWAYS use .01, independently from an FD caller's epsilon.
|
||||
from openpilot.starpilot.flm.controller_replay import ControllerReplay, Snapshot
|
||||
from openpilot.starpilot.flm.torque_emission_replay import EmissionHistory
|
||||
prefix = original.__dict__.get('_EventControllerReplay__controller_prefix')
|
||||
if (epsilon == .01 and type(self.engine) is ControllerReplay and type(self.emissions) is EmissionHistory and
|
||||
type(self.activation_plan) is EventActivationPlan and self.activation_plan.version == ZERO_REFERENCE_ACTIVATION_VERSION and
|
||||
self.__dict__.get('_EventControllerReplay__controller_prefix') is None and
|
||||
type(prefix) is tuple and len(prefix) == 4 and prefix[0] == old_key and
|
||||
all(type(part) is MappingProxyType for part in prefix[1:]) and
|
||||
prefix[1].keys() == prefix[2].keys() and
|
||||
all(type(stamp) is int and type(value) is float and math.isfinite(value) for stamp, value in prefix[1].items()) and
|
||||
all(type(value) is bool for value in prefix[2].values()) and
|
||||
all(window in self.replay_windows and type(value) is Snapshot for window, value in prefix[3].items())):
|
||||
self.__controller_prefix = (new_key, *(MappingProxyType(dict(part)) for part in prefix[1:]))
|
||||
return True
|
||||
except (AttributeError, TypeError, ValueError, KeyError, OverflowError, pickle.PicklingError):
|
||||
return False
|
||||
|
||||
def ensure_causal_features(self, *, epsilon=.01):
|
||||
"""Reuse only this engine's successful original full rebuild; never IPC flags."""
|
||||
if self.restore_envelope is not None:
|
||||
return self.rebuild_causal_features(epsilon=epsilon)
|
||||
key = self._causal_input_key(epsilon)
|
||||
if key is None or self.__causal_features is None or self.__causal_features[0] != key:
|
||||
return self.rebuild_causal_features(epsilon=epsilon)
|
||||
# Restore only the readonly derived result, never a caller-edited projection.
|
||||
self.projected = dict(self.__causal_features[1])
|
||||
return len(self.projected)
|
||||
|
||||
def rebuild_causal_features(self, *, epsilon=.01):
|
||||
self.__causal_features = None
|
||||
if self.restore_envelope is not None:
|
||||
raise ValueError('restoration requires joint full-map/group-off proof, not marker finite differences')
|
||||
if not math.isfinite(epsilon) or not 0 < epsilon <= .03:
|
||||
@@ -219,8 +585,35 @@ class EventControllerReplay:
|
||||
continue
|
||||
sensitivities.append({stamp: (high[stamp] - low[stamp]) / span for stamp in baseline})
|
||||
self.projected = {stamp: (value, tuple(feature[stamp] for feature in sensitivities)) for stamp, value in baseline.items()}
|
||||
from types import MappingProxyType
|
||||
key = self._causal_input_key(epsilon)
|
||||
if key is not None:
|
||||
self.__causal_features = key, MappingProxyType(dict(self.projected))
|
||||
return len(self.projected)
|
||||
|
||||
def native_feasible(self, coordinates):
|
||||
"""Child trial-only feasibility, without losses, physical targets or gains.
|
||||
|
||||
This is not a ReplayValidation and never grants application authority.
|
||||
Full validate() repeats all guards and measures heldout benefit separately.
|
||||
"""
|
||||
from openpilot.starpilot.flm.event_activation import ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
if (self.activation_plan is None or self.activation_plan.version != ZERO_REFERENCE_ACTIVATION_VERSION or
|
||||
self.restore_envelope is not None):
|
||||
raise ValueError('native feasibility requires declared trial plan')
|
||||
try:
|
||||
# The strict rollout cache performs full owned-input binding. Do not keep
|
||||
# a second, weaker plan/reference-only baseline memo here.
|
||||
# A rejected candidate cannot grant feasibility; avoid an unnecessary
|
||||
# baseline lookup/hash before that strict candidate rejection.
|
||||
trial_requests, proposed, trial_saturated = self._rollout(coordinates)
|
||||
base_requests, baseline, base_saturated = self._rollout(self.reference)
|
||||
return bool(set(proposed) == set(baseline) and
|
||||
not any(trial_saturated[stamp] and not base_saturated[stamp] for stamp in base_requests) and
|
||||
self._slew_ok(base_requests, trial_requests, encoded=False) and self._slew_ok(baseline, proposed, encoded=True))
|
||||
except (ValueError, TypeError, KeyError, AttributeError, OverflowError):
|
||||
return False
|
||||
|
||||
def validate(self, profile, control, output, physical_context, blocks, model):
|
||||
if (self.failed or profile.policy != (RESTORE_POLICY if self.restore_envelope is not None else POLICY) or
|
||||
self.restore_envelope is not None and profile.effect_envelope != self.restore_envelope or profile.identity != self.identity or
|
||||
|
||||
@@ -6,6 +6,7 @@ from openpilot.starpilot.flm.adaptive_profile import Result, evaluate, loss
|
||||
from openpilot.starpilot.flm.event_controller_replay import POLICY
|
||||
|
||||
TRIAL_STEP = .005
|
||||
NATIVE_FEASIBILITY_SCALES = (1., .5, .25, .125, .0625, .03125, .015625, .0078125)
|
||||
|
||||
|
||||
def _lower(values):
|
||||
@@ -17,7 +18,7 @@ def _lower(values):
|
||||
|
||||
|
||||
def evaluate_event(identity, parameters, blocks, *, episodes, now_ns, required_cohorts, response_revision,
|
||||
sequence=1, ridge=.01, prior=None, effect_envelope=None):
|
||||
sequence=1, ridge=.01, prior=None, effect_envelope=None, native_feasible=None):
|
||||
"""Actual fit boundary; caller reconstructs episodes from exact child trace.
|
||||
|
||||
An episode crossing train/heldout boundaries is rejected, not counted twice.
|
||||
@@ -68,6 +69,26 @@ def evaluate_event(identity, parameters, blocks, *, episodes, now_ns, required_c
|
||||
for old, target in zip(reference, fitted.profile.coefficients, strict=True))
|
||||
if proposal == reference:
|
||||
return Result('collecting', 'no_identifiable_event_trial')
|
||||
if native_feasible is not None:
|
||||
# The direction was fitted ONCE on train. Existing ridge-heldout acceptance
|
||||
# above remains a conservative preliminary gate, never a direction selector.
|
||||
# Select ONLY by original whole native/source/quality/slew feasibility; no
|
||||
# target, sample loss or heldout gain is supplied to this callback.
|
||||
delta = tuple(target - old for old, target in zip(reference, proposal, strict=True))
|
||||
for scale in NATIVE_FEASIBILITY_SCALES:
|
||||
candidate = tuple(old + scale * step for old, step in zip(reference, delta, strict=True))
|
||||
if candidate == reference:
|
||||
continue # Finite grid underflow cannot manufacture a neutral profile.
|
||||
feasible = native_feasible(candidate)
|
||||
if type(feasible) is not bool:
|
||||
return Result('unavailable', 'event_native_feasibility_contract')
|
||||
if feasible:
|
||||
proposal = candidate
|
||||
break
|
||||
else:
|
||||
return Result('collecting', 'event_no_native_feasible_scale')
|
||||
# This selected proposal is now frozen. ALL following tiny-loss/heldout
|
||||
# checks run once; failure MUST NOT retry a smaller scale.
|
||||
heldout = blocks[split:]
|
||||
improvements = []
|
||||
for cohort in sorted(required_cohorts):
|
||||
|
||||
@@ -46,11 +46,11 @@ def _identified(engine, raw):
|
||||
|
||||
|
||||
def fit_event_snapshot(payload, factory, replay_payload):
|
||||
if type(payload.get('event_version')) is not int or payload['event_version'] not in (2, 3, 4, 5):
|
||||
if type(payload.get('event_version')) is not int or payload['event_version'] not in (2, 3, 4, 5, 6):
|
||||
raise ValueError('explicit event snapshot version')
|
||||
event_clipping_quality(payload)
|
||||
envelope = envelope_from_dict(payload['effect_envelope'])
|
||||
if payload['event_version'] in (4, 5) and (payload.get('event_operation') != 'restore' or envelope.operation != 'restore'):
|
||||
if payload['event_version'] in (4, 5, 6) and (payload.get('event_operation') != 'restore' or envelope.operation != 'restore'):
|
||||
raise ValueError('whole-map activation is restore-only')
|
||||
if payload['event_version'] == 3 and (payload.get('event_operation') != 'trial' or envelope.operation != 'trial'):
|
||||
raise ValueError('declared activation requires original trial operation')
|
||||
@@ -63,13 +63,13 @@ def fit_event_snapshot(payload, factory, replay_payload):
|
||||
replay_payload['objective'] = asdict(legacy)
|
||||
actual = ControllerReplay.from_snapshot(factory, legacy, pack_primitive_snapshot(replay_payload), rebuild=False)
|
||||
if envelope.operation == 'restore':
|
||||
if payload.get('event_version') not in (2, 4, 5):
|
||||
if payload.get('event_version') not in (2, 4, 5, 6):
|
||||
raise ValueError('declared activation cannot replace full-map restore contract')
|
||||
if payload.get('event_operation') != 'restore':
|
||||
raise ValueError('explicit historical restore job required')
|
||||
from openpilot.starpilot.flm.event_restore_fit import fit_event_restore, prepare_fixed_restore_calibration
|
||||
engine = EventControllerReplay(actual, objective, envelope.replay_anchor(), restore_envelope=envelope)
|
||||
if payload['event_version'] in (4, 5):
|
||||
if payload['event_version'] in (4, 5, 6):
|
||||
from openpilot.starpilot.flm.restore_activation import build_restore_plan
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import canonical_physical_context
|
||||
prepared = prepare_fixed_restore_calibration(payload, engine, objective)
|
||||
@@ -103,8 +103,10 @@ def fit_event_snapshot(payload, factory, replay_payload):
|
||||
if selected != objective.names:
|
||||
envelope = envelope.for_trial(selected)
|
||||
objective = EffectObjective(objective.group, selected, envelope.trial_anchor, objective.context_key)
|
||||
previous = engine
|
||||
engine = EventControllerReplay(actual, objective, envelope.replay_anchor(), activation_plan=activation_plan)
|
||||
engine.rebuild_causal_features()
|
||||
if payload.get('event_version') != 3 or engine.reuse_subset_causal_features(previous) is not True:
|
||||
engine.rebuild_causal_features()
|
||||
from openpilot.starpilot.flm.event_raw_fit import fit_event_torque
|
||||
payload = {**payload, 'effect_envelope': asdict(envelope),
|
||||
'event_objective': asdict(objective),
|
||||
|
||||
@@ -143,7 +143,7 @@ def fit_event_torque(payload, engine, objective):
|
||||
break
|
||||
if model is None:
|
||||
return _unavailable('event_independent_physical_model', wholeBlocks=len(events), modelPoints=len(rows))
|
||||
engine.rebuild_causal_features()
|
||||
engine.ensure_causal_features()
|
||||
blocks, stage_events = [], []
|
||||
unexcited_stage_blocks = 0
|
||||
previous = cutoff
|
||||
@@ -176,9 +176,13 @@ def fit_event_torque(payload, engine, objective):
|
||||
return _unavailable('event_disjoint_stage_support', wholeBlocks=len(events), stageBlocks=len(blocks), unexcitedStageBlocks=unexcited_stage_blocks)
|
||||
episodes = tracking_episodes(engine, tuple(stage_events), model, objective)
|
||||
prior = None if payload.get('prior') is None else tuple(payload['prior'])
|
||||
from openpilot.starpilot.flm.event_activation import ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
native_feasible = (engine.native_feasible if engine.activation_plan is not None and
|
||||
engine.activation_plan.version == ZERO_REFERENCE_ACTIVATION_VERSION else None)
|
||||
result = evaluate_event(engine.identity, objective.parameters, tuple(blocks), episodes=episodes,
|
||||
now_ns=payload['now_ns'], required_cohorts=objective.required_cohorts,
|
||||
response_revision='event-independent-physical-v2', sequence=payload['sequence'], prior=prior, effect_envelope=envelope)
|
||||
response_revision='event-independent-physical-v2', sequence=payload['sequence'], prior=prior,
|
||||
effect_envelope=envelope, native_feasible=native_feasible)
|
||||
validation = None
|
||||
if result.profile is not None:
|
||||
current, applied = control_from_dict(payload['control']), output_from_dict(payload['output'])
|
||||
|
||||
@@ -66,8 +66,8 @@ def _restore_original_evidence(payload, engine, objective):
|
||||
|
||||
def prepare_fixed_restore_calibration(payload, engine, objective):
|
||||
from openpilot.starpilot.flm.clipping_quality import event_clipping_quality
|
||||
if type(payload.get('event_version')) is not int or payload['event_version'] not in (4, 5):
|
||||
raise ValueError('explicit fixed-map restore version4/5')
|
||||
if type(payload.get('event_version')) is not int or payload['event_version'] not in (4, 5, 6):
|
||||
raise ValueError('explicit fixed-map restore version4/5/6')
|
||||
event_clipping_quality(payload)
|
||||
envelope, context, packets, events = _restore_original_evidence(payload, engine, objective)
|
||||
_control_frame_index(engine.records) # reject ambiguous original activation clocks
|
||||
@@ -94,7 +94,7 @@ def _fixed_restore_rollout(payload, engine, coordinates, *, restore_target=False
|
||||
try:
|
||||
return engine._rollout(coordinates, restore_target=restore_target, restore_omit_group=restore_omit_group)
|
||||
except ValueError as error:
|
||||
if payload.get('event_version') not in (4, 5):
|
||||
if payload.get('event_version') not in (4, 5, 6):
|
||||
raise
|
||||
return unavailable('restore_actual_full_map_controller_replay', status='rejected', event_version=payload['event_version'],
|
||||
replay_phase='group-off' if restore_omit_group is not None else 'full-map' if restore_target else 'baseline',
|
||||
@@ -106,7 +106,7 @@ def fit_event_restore(payload, engine, objective):
|
||||
frames = _control_frame_index(engine.records)
|
||||
if len(events) < 16:
|
||||
return unavailable('restore_whole_physical_event_support', wholeBlocks=len(events), requiredWholeBlocks=16)
|
||||
declared = payload.get('event_version') in (4, 5)
|
||||
declared = payload.get('event_version') in (4, 5, 6)
|
||||
if declared:
|
||||
from openpilot.starpilot.flm.event_activation import bind_activation_model
|
||||
from openpilot.starpilot.flm.restore_activation import build_restore_plan
|
||||
@@ -195,14 +195,14 @@ def fit_event_restore(payload, engine, objective):
|
||||
for group in effects:
|
||||
# Joint group impact must be present in this actual supported trajectory.
|
||||
# No unobservable saved low-speed/window coordinate restores on faith.
|
||||
if payload.get('event_version') == 5:
|
||||
if payload.get('event_version') in (5, 6):
|
||||
group_gains[group].append(sum(group_errors[group]) / len(samples) - after_loss)
|
||||
elif sum(value > 1e-8 for value in signals[group]) < 40:
|
||||
group_gains[group].append(None)
|
||||
else:
|
||||
group_gains[group].append(sum(group_errors[group]) / len(samples) - after_loss)
|
||||
middle = len(blocks) // 2
|
||||
if payload.get('event_version') == 5:
|
||||
if payload.get('event_version') in (5, 6):
|
||||
from openpilot.starpilot.flm.restore_support import capped_group_credits, independent_half_supported, all_event_lower
|
||||
for group, trace in effects.items():
|
||||
evidence = tuple((event['block_id'], tuple((point['control_ns'], point['apply_ns'],
|
||||
@@ -217,7 +217,7 @@ def fit_event_restore(payload, engine, objective):
|
||||
support_credits=half, required_credits=80, required_effectful_events=2)
|
||||
for group, gains in group_gains.items():
|
||||
for cohort in (gains[:middle], gains[middle:]):
|
||||
if payload.get('event_version') == 5:
|
||||
if payload.get('event_version') in (5, 6):
|
||||
if all_event_lower(cohort, _lower) is None:
|
||||
return unavailable('restore_group_independent_heldout_support', group=group)
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Fresh bounded numerical/replay actor. Invoked only by optional learner owner."""
|
||||
|
||||
import gc
|
||||
import json
|
||||
import os
|
||||
import resource
|
||||
@@ -33,7 +34,17 @@ def main():
|
||||
snapshot = json.loads(raw)
|
||||
from openpilot.starpilot.flm.learner_factory import fit_snapshot
|
||||
|
||||
result = fit_snapshot(snapshot)
|
||||
# One isolated, address-space/CPU-bounded job. Reference counting remains
|
||||
# active; avoid repeated cycle scans of the same large replay input graph.
|
||||
was_enabled = gc.isenabled()
|
||||
try:
|
||||
gc.disable()
|
||||
result = fit_snapshot(snapshot)
|
||||
finally:
|
||||
if was_enabled:
|
||||
gc.enable()
|
||||
else:
|
||||
gc.disable()
|
||||
encoded = json.dumps(result, separators=(',', ':'), allow_nan=False).encode()
|
||||
if len(encoded) > MAX_OUTPUT:
|
||||
raise ValueError('fit result bound')
|
||||
|
||||
@@ -447,7 +447,7 @@ def fit_snapshot(snapshot):
|
||||
if payload.get('version') != 1 or payload.get('family') != snapshot.get('family'):
|
||||
raise ValueError('fit family')
|
||||
event_version = payload.get('event_version')
|
||||
event_job = type(event_version) is int and event_version in (2, 3, 4, 5)
|
||||
event_job = type(event_version) is int and event_version in (2, 3, 4, 5, 6)
|
||||
if 'event_version' in payload and not event_job:
|
||||
raise ValueError('unknown explicit torque event evidence version')
|
||||
from openpilot.starpilot.flm.clipping_quality import event_clipping_quality
|
||||
|
||||
@@ -87,15 +87,11 @@ class PidControllerFactory:
|
||||
return controller, VehicleModel(self.cp)
|
||||
|
||||
|
||||
def run_pid(controller, vm, inputs, *, effect_trial=None):
|
||||
from opendbc.car import structs
|
||||
from openpilot.starpilot.flm.controller_replay import _CS_FIELDS, _VM_FIELDS
|
||||
def run_pid(controller, vm, inputs, *, effect_trial=None, _prepared_car_state=None):
|
||||
from openpilot.starpilot.flm.controller_replay import _VM_FIELDS, _replay_car_state
|
||||
from openpilot.starpilot.flm.selected_effects import EffectReplaySource
|
||||
values = dict(inputs)
|
||||
cs = structs.CarState.new_message()
|
||||
for name in _CS_FIELDS:
|
||||
setattr(cs, name, bool(values[name]) if name in ('steeringPressed', 'standstill', 'steerFaultTemporary', 'steerFaultPermanent') else values[name])
|
||||
cs.gearShifter = int(values['gearShifter'])
|
||||
cs = _replay_car_state(inputs, values, _prepared_car_state)
|
||||
for name in _VM_FIELDS:
|
||||
setattr(vm, name, values['vm.' + name])
|
||||
params = SimpleNamespace(angleOffsetDeg=values['angleOffsetDeg'], roll=values['roll'])
|
||||
|
||||
@@ -4,7 +4,8 @@ import hashlib
|
||||
import json
|
||||
|
||||
from openpilot.starpilot.flm.clipping_quality import (
|
||||
FIXED_MAP_IDENTIFICATION, RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, QUALIFIED_EVENT_CLIPPING_QUALITY,
|
||||
FIXED_MAP_IDENTIFICATION, RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, RESTORE_ZERO_REFERENCE_VERSION,
|
||||
restore_policy, restore_quality,
|
||||
)
|
||||
from openpilot.starpilot.flm.event_activation import EventActivationPlan, EXACT_EMISSION_ACTIVATION_VERSION, build_activation_plan
|
||||
from openpilot.starpilot.flm.full_surface_profile import FullSurfaceEnvelope
|
||||
@@ -22,7 +23,8 @@ class WholeMapRestorePlan(EventActivationPlan):
|
||||
restore_envelope_sha256: str
|
||||
|
||||
def validate(self):
|
||||
if (self.version not in (RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION) or type(self.restore_envelope_sha256) is not str or
|
||||
versions = (RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, RESTORE_ZERO_REFERENCE_VERSION)
|
||||
if (self.version not in versions or type(self.restore_envelope_sha256) is not str or
|
||||
len(self.restore_envelope_sha256) != 64 or any(c not in '0123456789abcdef' for c in self.restore_envelope_sha256)):
|
||||
raise ValueError('whole-map restore plan version/envelope')
|
||||
# Reuse only the chronology/lineage validator; never decode this as a trial.
|
||||
@@ -33,7 +35,7 @@ class WholeMapRestorePlan(EventActivationPlan):
|
||||
@property
|
||||
def clipping_quality(self):
|
||||
self.validate()
|
||||
return QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
return restore_quality(6 if self.version == RESTORE_ZERO_REFERENCE_VERSION else 4)
|
||||
|
||||
@property
|
||||
def identification_policy(self):
|
||||
@@ -58,13 +60,13 @@ def restore_plan_matches(plan, envelope):
|
||||
|
||||
|
||||
def build_restore_plan(engine, *, model, cutoff_ns, stage_events, physical_context, envelope, restore_version=4):
|
||||
if type(restore_version) is not int or restore_version not in (4, 5):
|
||||
if type(restore_version) is not int or restore_version not in (4, 5, 6):
|
||||
raise ValueError('explicit whole-map restore support version')
|
||||
policy = {4: RESTORE_ACTIVATION_VERSION, 5: RESTORE_AGGREGATE_ACTIVATION_VERSION}[restore_version]
|
||||
policy = restore_policy(restore_version)
|
||||
digest = restore_envelope_digest(envelope)
|
||||
if engine.restore_envelope is None or restore_envelope_digest(engine.restore_envelope) != digest:
|
||||
raise ValueError('whole-map restore declaration changed transaction')
|
||||
original = build_activation_plan(engine, model=model, cutoff_ns=cutoff_ns, stage_events=stage_events,
|
||||
physical_context=physical_context, quality_contract=QUALIFIED_EVENT_CLIPPING_QUALITY)
|
||||
physical_context=physical_context, quality_contract=restore_quality(restore_version))
|
||||
return WholeMapRestorePlan(**{**asdict(original), 'version': policy},
|
||||
restore_envelope_sha256=digest).validate()
|
||||
|
||||
@@ -439,11 +439,13 @@ class EffectReplayTransition:
|
||||
self.target.update(zip(self.names, trial.coordinates, strict=True))
|
||||
self.applied = None
|
||||
self.previous_ns = 0
|
||||
self.__source_key = self.__source = None
|
||||
|
||||
def reset(self):
|
||||
self.applied = None
|
||||
self.previous_ns = 0
|
||||
self.recorded_effects_seen = False
|
||||
self.__source_key = self.__source = None
|
||||
|
||||
def source_for_frame(self, control_ns, trace=None, *, new_owner=False):
|
||||
from openpilot.starpilot.flm.effect_trace import step_full
|
||||
@@ -478,8 +480,17 @@ class EffectReplayTransition:
|
||||
self.applied = step_full(self.applied, self.target, now_ns=control_ns, previous_ns=self.previous_ns, qualified=qualified)
|
||||
self.previous_ns = control_ns
|
||||
source = EffectReplaySource.__new__(EffectReplaySource)
|
||||
group = {n: self.applied[n] for n in self.names}
|
||||
source.source = CanonicalSurfaceEffects(group, self.trial.selected_baseline_revision, accumulated=self.applied)
|
||||
# Per-transition geometry only: trace validation and exact step_full above
|
||||
# still run EVERY frame. Packed doubles preserve signed zero and full30.
|
||||
import struct
|
||||
key = self.trial.selected_baseline_revision, self.names, struct.pack('!30d', *(self.applied[n] for n in NAMES))
|
||||
if key != self.__source_key:
|
||||
group = {n: self.applied[n] for n in self.names}
|
||||
self.__source = CanonicalSurfaceEffects(group, self.trial.selected_baseline_revision, accumulated=self.applied)
|
||||
self.__source_key = key
|
||||
source.source = self.__source
|
||||
# The ordinary _run -> begin_frame path still resets ALL call counters and
|
||||
# enforces the real frame clock. No mutable frame state crosses transitions.
|
||||
source.original_effect_anchor = source.source.original_effect_anchor
|
||||
source.applied_coordinates = source.source.applied_coordinates
|
||||
return source
|
||||
|
||||
@@ -25,15 +25,15 @@ def production_adapter_for_evidence(cp, evidence, key, *, source):
|
||||
"""Adopt the qualified event contract only in the optional live producer.
|
||||
|
||||
Legacy/reference callers retain version2. The live event producer explicitly
|
||||
selects the separate fixed-map version5 restore declaration, never FD shadow.
|
||||
selects the separate fixed-map version6 restore declaration, never FD shadow.
|
||||
"""
|
||||
from openpilot.starpilot.flm.clipping_quality import QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
from openpilot.starpilot.flm.clipping_quality import ZERO_REFERENCE_QUALITY
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import SourceState, adapter_for_evidence
|
||||
|
||||
if not isinstance(source, SourceState) or not source.enabled or source.manual:
|
||||
return None
|
||||
return adapter_for_evidence(cp, evidence, key, source=source,
|
||||
event_replay_version=3, event_clipping_quality=QUALIFIED_EVENT_CLIPPING_QUALITY, event_restore_version=5)
|
||||
event_replay_version=3, event_clipping_quality=ZERO_REFERENCE_QUALITY, event_restore_version=6)
|
||||
|
||||
|
||||
def run(
|
||||
|
||||
@@ -5,6 +5,7 @@ from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.hyundai.values import CAR as HKG
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.starpilot.flm.learner_factory import from_evidence
|
||||
from openpilot.starpilot.flm.clipping_quality import ZERO_REFERENCE_QUALITY, QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
from openpilot.starpilot.flm.runtime_adapter_registry import baseline_values
|
||||
from openpilot.starpilot.flm.steerlearnerd import production_adapter_for_evidence
|
||||
from openpilot.starpilot.flm.tests.test_production_event_contract import capture
|
||||
@@ -20,9 +21,14 @@ def test_actual_registered_producer_explicit_restore_version_and_legacy_default(
|
||||
before = controls.CP.as_builder().to_bytes(), baseline_values(controls.LaC)
|
||||
live = production_adapter_for_evidence(controls.CP, evidence, ('event', 'center'), source=source)
|
||||
old = from_evidence(controls.CP, evidence, ('event', 'center'), source=source)
|
||||
assert live is not None and live.replay_version == 3 and live.restore_version == 5
|
||||
assert live is not None and live.replay_version == 3 and live.restore_version == 6
|
||||
assert live.clipping_quality == ZERO_REFERENCE_QUALITY
|
||||
for version in (4, 5):
|
||||
legacy = from_evidence(controls.CP, evidence, ('event', 'center'), source=source,
|
||||
event_replay_version=3, event_clipping_quality=QUALIFIED_EVENT_CLIPPING_QUALITY, event_restore_version=version)
|
||||
assert legacy is not None and legacy.restore_version == version and legacy.clipping_quality == QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
assert old is not None and old.replay_version == 2 and old.restore_version == 2
|
||||
assert from_evidence(controls.CP, evidence, ('event', 'center'), source=source, event_restore_version=4) is None
|
||||
live.select_objective(('event', 'deadband.high'))
|
||||
assert live.restore_version == 5
|
||||
assert live.restore_version == 6
|
||||
assert (controls.CP.as_builder().to_bytes(), baseline_values(controls.LaC)) == before
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
"""Controller-only preactivation reuse: actual selected controllers and native CAN."""
|
||||
from dataclasses import replace
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from unittest import mock
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm.controller_replay import (
|
||||
ControllerReplay, ReplayFrame, SelectedControllerFactory, _run, capture_snapshot, final_command, frame_input,
|
||||
)
|
||||
from openpilot.starpilot.flm.effect_trace import FullEffectTrace
|
||||
from openpilot.starpilot.flm.event_activation import EventActivationPlan, ZERO_REFERENCE_ACTIVATION_VERSION
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
from openpilot.starpilot.flm.selected_effects import EffectReplayRecordedSource
|
||||
from openpilot.starpilot.flm.torque_adapter import Objective
|
||||
from openpilot.starpilot.flm.torque_emission_replay import Emission
|
||||
from openpilot.starpilot.flm.tests.test_full_surface_profile import base
|
||||
from openpilot.starpilot.flm.tests.test_event_lazy_native import (
|
||||
TOYOTA, HYUNDAI, HONDA, make_interface, install_observer, settle, control, apply, BASE_NS,
|
||||
)
|
||||
|
||||
|
||||
def fixture(domain='torque', nonzero=False, two_windows=False):
|
||||
platform = {'torque': TOYOTA.TOYOTA_COROLLA_TSS2,
|
||||
'starpilot': HYUNDAI.HYUNDAI_IONIQ_6, 'pid': HONDA.HONDA_CIVIC}[domain]
|
||||
ci = make_interface(platform)
|
||||
assert install_observer(ci)
|
||||
settle(ci, control(0.))
|
||||
factory = PidControllerFactory(ci.CP, 'native') if domain == 'pid' else SelectedControllerFactory(
|
||||
ci.CP, 'starpilot' if domain == 'starpilot' else 'standard')
|
||||
objective = Objective('feedforward', 'high', 'steady')
|
||||
original = ControllerReplay(factory, objective, identity='f' * 64, baseline_revision='a' * 64)
|
||||
actual, vm = factory.create()
|
||||
original.raw_mode = True
|
||||
first = BASE_NS + ci.CC.frame * 10_000_000
|
||||
original.checkpoint(first, capture_snapshot(actual, vm))
|
||||
envelope = base()
|
||||
if nonzero:
|
||||
envelope = base(prior=base().with_proposal({'ff_gain_left': .002}))
|
||||
rows = []
|
||||
for tick in range(60):
|
||||
stamp = first + tick * 10_000_000
|
||||
if two_windows and tick == 30:
|
||||
original.checkpoint(stamp, capture_snapshot(actual, vm))
|
||||
trace = FullEffectTrace(1, envelope, stamp, stamp - 10_000_000, True,
|
||||
envelope.coordinates, envelope.coordinates, envelope.coordinates, envelope.full_profile_sha256).validate()
|
||||
inputs = frame_input(ci.CS.out, vm, SimpleNamespace(angleOffsetDeg=.01, roll=.002), active=True,
|
||||
safety_limited=False, desired_curvature=.00003, curvature_limited=False, lat_delay=.2)
|
||||
inputs += (('control_frame', float(tick + 1)), ('raw_model_curvature', .00003))
|
||||
value, _ = _run(actual, vm, inputs, objective, (0., 0.), effect_trial=EffectReplayRecordedSource(trace))
|
||||
rows.append(ReplayFrame(stamp, inputs, final_command(value), (0., 0.), frame_sequence=tick + 1,
|
||||
actual_lac_command=value, controller_domain='torque_pid' if domain == 'pid' else 'torque',
|
||||
effect_trace=trace, effect_prepare_ns=stamp))
|
||||
_, packets, witness = apply(ci, control(value))
|
||||
if witness is not None:
|
||||
assert (witness.can_address, witness.can_data, witness.can_bus) in packets
|
||||
clipped = witness.unlimited_raw is None or witness.unlimited_raw != witness.final_raw
|
||||
event = Emission(stamp, stamp, stamp + 1_000_000, witness, 'f' * 64, 'e' * 64, 'a' * 64,
|
||||
True, True, False, False, clipped, False, False, 0, 0, '')
|
||||
original.emissions.events[event.apply_ns] = event
|
||||
original.emissions.by_control[event.control_ns] = event
|
||||
original.records.extend(rows)
|
||||
original.replay_windows = (((rows[0].control_ns, rows[29].control_ns), (rows[30].control_ns, rows[-1].control_ns))
|
||||
if two_windows else ((rows[0].control_ns, rows[-1].control_ns),))
|
||||
selected = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
activation = rows[45 if two_windows else 30].control_ns
|
||||
windows = (((rows[0].control_ns, rows[29].control_ns, 0, 0),
|
||||
(rows[30].control_ns, rows[-1].control_ns, activation, activation)) if two_windows else
|
||||
((first, rows[-1].control_ns, activation, activation),))
|
||||
plan = EventActivationPlan(ZERO_REFERENCE_ACTIVATION_VERSION, original.identity, original.baseline_revision,
|
||||
selected.context_key, 'b' * 64, tuple('train-' + str(i) for i in range(4)),
|
||||
tuple('held-' + str(i) for i in range(4)), activation - 10_000_000,
|
||||
tuple('stage-' + str(i) for i in range(9)), windows).validate()
|
||||
return EventControllerReplay(original, selected, envelope.replay_anchor(), activation_plan=plan)
|
||||
|
||||
|
||||
def state(value, seen=None):
|
||||
"""Independent vars/containers audit, not capture_snapshot against itself."""
|
||||
seen = set() if seen is None else seen
|
||||
if value is None or type(value) in (bool, int, float, str, bytes):
|
||||
return value
|
||||
label = type(value).__module__ + '.' + type(value).__qualname__
|
||||
# Constructor bindings are identities, not mutable numerical state objects.
|
||||
# In particular, memoryview.tolist on the CLASS is an unbound descriptor.
|
||||
if isinstance(value, ModuleType):
|
||||
return ('module', value.__name__, getattr(value, '__file__', None))
|
||||
if isinstance(value, type):
|
||||
return ('class', value.__module__, value.__qualname__, getattr(sys.modules.get(value.__module__), '__file__', None))
|
||||
if callable(value):
|
||||
return ('callable', getattr(value, '__module__', None), getattr(value, '__qualname__', None), label,
|
||||
getattr(sys.modules.get(getattr(value, '__module__', None)), '__file__', None))
|
||||
if id(value) in seen:
|
||||
return ('reference', label)
|
||||
seen.add(id(value))
|
||||
if isinstance(value, dict):
|
||||
return label, tuple((state(k, seen), state(v, seen)) for k, v in value.items())
|
||||
if isinstance(value, (tuple, list)) or label == 'collections.deque':
|
||||
return label, tuple(state(v, seen) for v in value), getattr(value, 'maxlen', None)
|
||||
if hasattr(value, 'to_dict'):
|
||||
return label, value.to_dict()
|
||||
if hasattr(value, 'tolist'):
|
||||
return label, value.tolist()
|
||||
if hasattr(value, '__dict__'):
|
||||
return label, tuple((k, state(v, seen)) for k, v in vars(value).items())
|
||||
return label, str(value)
|
||||
|
||||
|
||||
def observed(engine, coordinates, shadow=True):
|
||||
calls, native = [], []
|
||||
original_native = engine.emissions.rollout
|
||||
def update(*args, **kwargs):
|
||||
result = _run(*args, **kwargs)
|
||||
calls.append((args[2], result[0], result[1].to_dict(), state(args[0]), state(args[1])))
|
||||
return result
|
||||
def emit(requests, *args, **kwargs):
|
||||
result = original_native(requests, *args, **kwargs)
|
||||
native.append((dict(requests), result, kwargs))
|
||||
return result
|
||||
token = {'_identification_token': engine._EventControllerReplay__identification_token} if shadow else {}
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay._run', side_effect=update), \
|
||||
mock.patch.object(engine.emissions, 'rollout', side_effect=emit):
|
||||
result = engine._rollout(coordinates, **token)
|
||||
return result, calls, native
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
@pytest.mark.parametrize('nonzero', (False, True))
|
||||
@pytest.mark.parametrize('shadow, delta', ((False, .000000001), (True, .000000001), (True, .01), (True, -.01)))
|
||||
def test_actual_prefix_reuse_full_tail_state_logs_requests_and_native_maps_exact(domain, nonzero, shadow, delta):
|
||||
cached = fixture(domain, nonzero)
|
||||
# Original full strict baseline must succeed before a certificate can exist.
|
||||
baseline = cached._rollout(cached.reference)
|
||||
certificate = cached._EventControllerReplay__controller_prefix
|
||||
assert certificate is not None and len(certificate[1]) == 30
|
||||
# The original guard checks raw Float64, NOT the rounded native request map.
|
||||
assert all(pickle.dumps(certificate[1][row.control_ns], protocol=5) ==
|
||||
pickle.dumps(row.actual_lac_command, protocol=5) for row in list(cached.records)[:30])
|
||||
with pytest.raises(TypeError):
|
||||
certificate[1][0] = 0.
|
||||
eager = EventControllerReplay(cached.engine, cached.objective, cached.original_anchor, activation_plan=cached.activation_plan)
|
||||
coordinates = (cached.reference[0] + delta,)
|
||||
expected, before, native_before = observed(eager, coordinates, shadow=shadow)
|
||||
actual, after, native_after = observed(cached, coordinates, shadow=shadow)
|
||||
assert pickle.dumps(actual, protocol=5) == pickle.dumps(expected, protocol=5)
|
||||
assert len(before) == 60 and len(after) == 30
|
||||
assert after == before[30:] # Every actual treated log + independent vars(controller/VM).
|
||||
assert native_before == native_after and len(native_after[0][0]) == 60
|
||||
assert set(actual[0]) == set(baseline[0])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ('input', 'checkpoint', 'plan', 'prior_bits', 'native', 'context', 'late_fault'))
|
||||
def test_mutated_owned_inputs_never_use_stale_prefix(change):
|
||||
engine = fixture()
|
||||
engine._rollout(engine.reference)
|
||||
before = engine._EventControllerReplay__controller_prefix
|
||||
if change == 'input':
|
||||
row = engine.records[0]
|
||||
engine.records[0] = replace(row, inputs=row.inputs + (('extra', 1.),))
|
||||
elif change == 'checkpoint':
|
||||
stamp, snapshot = engine.checkpoints[0]
|
||||
engine.checkpoints[0] = (stamp, replace(snapshot, values=snapshot.values + (('unknown', 1.),)))
|
||||
elif change == 'plan':
|
||||
engine.activation_plan = replace(engine.activation_plan, physical_context='c' * 64)
|
||||
elif change == 'prior_bits':
|
||||
engine.prior_full['ff_gain_right'] = -0.
|
||||
elif change == 'native':
|
||||
stamp, event = next(iter(engine.emissions.events.items()))
|
||||
engine.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
elif change == 'context':
|
||||
engine.factory.manual_source_epoch = 'changed'
|
||||
else:
|
||||
engine.records[-1] = replace(engine.records[-1], healthy=False)
|
||||
assert engine._causal_input_key(.01) != before[0]
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay._run', wraps=_run) as run:
|
||||
try:
|
||||
engine._rollout((.00001,), _identification_token=engine._EventControllerReplay__identification_token)
|
||||
except (ValueError, KeyError, TypeError):
|
||||
pass
|
||||
assert run.call_count > 0 or change in ('checkpoint', 'context')
|
||||
|
||||
|
||||
def test_failure_unknown_operation_and_bad_shadow_token_never_create_certificate():
|
||||
engine = fixture()
|
||||
stamp, event = next(iter(engine.emissions.events.items()))
|
||||
engine.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
with pytest.raises(ValueError):
|
||||
engine._rollout(engine.reference)
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
engine = fixture()
|
||||
with pytest.raises(ValueError, match='identification scope'):
|
||||
engine._rollout((.00001,), _identification_token=object())
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
# Old/default operation has no new certificate even after a complete baseline.
|
||||
engine.activation_plan = None
|
||||
engine._rollout(engine.reference)
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
|
||||
|
||||
def test_every_original_prefix_context_guard_and_late_tail_guard_still_runs():
|
||||
engine = fixture()
|
||||
engine._rollout(engine.reference)
|
||||
calls = []
|
||||
original = engine.engine._frame_context
|
||||
def context(frame):
|
||||
calls.append(frame.control_ns)
|
||||
return original(frame)
|
||||
with mock.patch.object(engine.engine, '_frame_context', side_effect=context):
|
||||
engine._rollout((.00001,), _identification_token=engine._EventControllerReplay__identification_token)
|
||||
assert calls == [frame.control_ns for frame in engine.records]
|
||||
engine.records[-1] = replace(engine.records[-1], healthy=False)
|
||||
with pytest.raises(ValueError, match='source/history'):
|
||||
engine._rollout((.00001,), _identification_token=engine._EventControllerReplay__identification_token)
|
||||
|
||||
|
||||
def test_baseline_source_changed_during_success_never_publishes_pending_certificate():
|
||||
engine = fixture()
|
||||
with mock.patch.object(engine, '_causal_input_key', side_effect=(b'first', b'changed')):
|
||||
with pytest.raises(ValueError, match='changed owned input'):
|
||||
engine._rollout(engine.reference)
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
assert engine._EventControllerReplay__pending_controller_prefix is None
|
||||
|
||||
|
||||
def test_shadow_prefix_source_changed_during_success_rejects_and_discards_certificate():
|
||||
engine = fixture()
|
||||
engine._rollout(engine.reference)
|
||||
key = engine._EventControllerReplay__controller_prefix[0]
|
||||
with mock.patch.object(engine, '_causal_input_key', side_effect=(key, b'changed')):
|
||||
with pytest.raises(ValueError, match='changed owned input'):
|
||||
engine._rollout((.000000001,), _identification_token=engine._EventControllerReplay__identification_token)
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
|
||||
|
||||
def test_restore_endpoint_never_uses_trial_prefix_certificate():
|
||||
engine = fixture()
|
||||
engine._rollout(engine.reference)
|
||||
with pytest.raises(ValueError, match='restore endpoint'):
|
||||
engine._rollout(engine.reference, restore_target=True)
|
||||
|
||||
|
||||
def test_unavailable_activation_snapshot_keeps_original_baseline_and_creates_no_certificate():
|
||||
engine = fixture()
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay.capture_snapshot',
|
||||
side_effect=ValueError('unsupported transient state')):
|
||||
result = engine._rollout(engine.reference)
|
||||
assert len(result[0]) == 60
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
|
||||
|
||||
def test_unsupported_exact_controller_policy_keeps_original_replay_without_certificate():
|
||||
engine = fixture('starpilot')
|
||||
original_create = engine.factory.create
|
||||
def create():
|
||||
controller, vm = original_create()
|
||||
# Inherit all actual methods/state but leave the audited exact-class set.
|
||||
original = type(controller.starpilot_extension)
|
||||
controller.starpilot_extension.__class__ = type('UnknownExtension', (original,), {})
|
||||
return controller, vm
|
||||
with mock.patch.object(engine.factory, 'create', side_effect=create):
|
||||
result = engine._rollout(engine.reference)
|
||||
assert len(result[0]) == 60
|
||||
assert engine._EventControllerReplay__controller_prefix is None
|
||||
|
||||
|
||||
def test_actual_raw_double_prefix_guard_preserves_non_float32_exact_command():
|
||||
engine = fixture()
|
||||
assert any(row.actual_lac_command != final_command(row.actual_lac_command) for row in list(engine.records)[:30])
|
||||
engine._rollout(engine.reference)
|
||||
certificate = engine._EventControllerReplay__controller_prefix
|
||||
assert any(value != final_command(value) for value in certificate[1].values())
|
||||
result, tail, _ = observed(engine, (.000000001,))
|
||||
assert len(result[0]) == 60 and len(tail) == 30
|
||||
|
||||
|
||||
def test_legacy_shell_without_new_private_fields_keeps_original_argument_api():
|
||||
from openpilot.starpilot.flm.tests.test_event_strict_rollout_cache import engine, numeric
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
value._rollout(value.reference)
|
||||
assert original.call_args.kwargs == {}
|
||||
value.activation_plan = None
|
||||
value._rollout((.01,))
|
||||
assert original.call_args.kwargs == {'restore_target': False, 'restore_omit_group': None, '_identification_token': None}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_actual_second_window_checkpoint_reset_and_native_partition_remain_exact(domain):
|
||||
cached = fixture(domain, nonzero=True, two_windows=True)
|
||||
cached._rollout(cached.reference)
|
||||
certificate = cached._EventControllerReplay__controller_prefix
|
||||
assert certificate is not None and len(certificate[1]) == 15 and len(certificate[3]) == 1
|
||||
eager = EventControllerReplay(cached.engine, cached.objective, cached.original_anchor, activation_plan=cached.activation_plan)
|
||||
expected, before, native_before = observed(eager, (cached.reference[0] + .01,))
|
||||
actual, after, native_after = observed(cached, (cached.reference[0] + .01,))
|
||||
assert pickle.dumps(actual, protocol=5) == pickle.dumps(expected, protocol=5)
|
||||
assert len(before) == 60 and len(after) == 45
|
||||
assert after == before[:30] + before[45:]
|
||||
assert native_after == native_before
|
||||
|
||||
|
||||
def test_state_observer_handles_module_class_and_memoryview_instance_without_dropping_values():
|
||||
import math
|
||||
assert state(memoryview) == ('class', 'builtins', 'memoryview', None)
|
||||
assert state(math) == ('module', 'math', getattr(math, '__file__', None))
|
||||
assert state(memoryview(b'ab')) == ('builtins.memoryview', [97, 98])
|
||||
assert state(SimpleNamespace(number=3., mutable=[1., 2.], constructor=memoryview)) != \
|
||||
state(SimpleNamespace(number=3., mutable=[1., 4.], constructor=memoryview))
|
||||
@@ -49,7 +49,7 @@ def test_actual_fit_missing_stage_frame_is_fail_closed():
|
||||
'control': {'stamp': index * 1_000_000_000}, 'output': {'stamp': index * 1_000_000_000 + 1},
|
||||
'physical': {'lateral_accel': .02}},)} for index in range(1, 17))
|
||||
first_stage = events[8]['samples'][0]
|
||||
engine = SimpleNamespace(records=(), identity=model.identity, projected={}, rebuild_causal_features=lambda: None)
|
||||
engine = SimpleNamespace(records=(), identity=model.identity, projected={}, ensure_causal_features=lambda: None)
|
||||
objective = SimpleNamespace(names=('center',), required_cohorts=('center',))
|
||||
envelope = SimpleNamespace(prior_coordinates=(('center', 0.),))
|
||||
with mock.patch('openpilot.starpilot.flm.event_raw_fit.original_event_evidence',
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
"""Exact selected-controller/CAN replay units, never native/profile authority."""
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.starpilot.flm.controller_replay import (
|
||||
ControllerReplay, ReplayFrame, SelectedControllerFactory, _run, capture_snapshot, final_command, frame_input,
|
||||
)
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay, _LazyControllerRequests
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
from openpilot.starpilot.flm.torque_adapter import Objective
|
||||
from openpilot.starpilot.flm.torque_emission_replay import Emission
|
||||
from openpilot.starpilot.flm.tests.test_full_surface_profile import base
|
||||
from openpilot.starpilot.flm.tests.test_steering_projection import make_interface, install_observer, settle, control, apply, BASE_NS
|
||||
|
||||
|
||||
class EagerRequests(_LazyControllerRequests):
|
||||
"""Test-only original ordering: ALL controller work before native rollout."""
|
||||
def __init__(self, values, frames):
|
||||
super().__init__(values, frames)
|
||||
self.finish()
|
||||
|
||||
|
||||
def replay_fixture(domain):
|
||||
platform = {'torque': TOYOTA.TOYOTA_COROLLA_TSS2,
|
||||
'starpilot': HYUNDAI.HYUNDAI_IONIQ_6, 'pid': HONDA.HONDA_CIVIC}[domain]
|
||||
ci = make_interface(platform)
|
||||
assert install_observer(ci)
|
||||
settle(ci, control(0.))
|
||||
factory = PidControllerFactory(ci.CP, 'native') if domain == 'pid' else SelectedControllerFactory(
|
||||
ci.CP, 'starpilot' if domain == 'starpilot' else 'standard')
|
||||
objective = Objective('feedforward', 'high', 'steady')
|
||||
original = ControllerReplay(factory, objective, identity='f' * 64, baseline_revision='a' * 64)
|
||||
actual, vm = factory.create()
|
||||
original.raw_mode = True
|
||||
first = BASE_NS + ci.CC.frame * 10_000_000
|
||||
original.checkpoint(first, capture_snapshot(actual, vm))
|
||||
rows = []
|
||||
for tick in range(44):
|
||||
stamp = first + tick * 10_000_000
|
||||
inputs = frame_input(ci.CS.out, vm, SimpleNamespace(angleOffsetDeg=0., roll=0.), active=True,
|
||||
safety_limited=False, desired_curvature=.00003, curvature_limited=False, lat_delay=.2)
|
||||
inputs += (('control_frame', float(tick + 1)), ('raw_model_curvature', .00003))
|
||||
value, _ = _run(actual, vm, inputs, objective, (0., 0.))
|
||||
rows.append(ReplayFrame(stamp, inputs, final_command(value), (0., 0.), frame_sequence=tick + 1,
|
||||
actual_lac_command=value, controller_domain='torque_pid' if domain == 'pid' else 'torque'))
|
||||
if tick >= 40:
|
||||
continue # Controller-only tail MUST still run after native success.
|
||||
_, packets, witness = apply(ci, control(value))
|
||||
if witness is not None:
|
||||
assert (witness.can_address, witness.can_data, witness.can_bus) in packets
|
||||
clipped = witness.unlimited_raw is None or witness.unlimited_raw != witness.final_raw
|
||||
event = Emission(stamp, stamp, stamp + 1_000_000, witness, 'f' * 64, 'e' * 64, 'a' * 64,
|
||||
True, True, False, False, clipped, False, False, 0, 0, '')
|
||||
# As in registered codec units, native flags grant NO current authority.
|
||||
# We exercise original decode/EncoderBranch with actual captured bytes.
|
||||
original.emissions.events[event.apply_ns] = event
|
||||
original.emissions.by_control[event.control_ns] = event
|
||||
original.records.extend(rows)
|
||||
original.replay_windows = ((rows[0].control_ns, rows[-1].control_ns),)
|
||||
envelope = base()
|
||||
selected = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
return EventControllerReplay(original, selected, envelope.replay_anchor())
|
||||
|
||||
|
||||
def run_observed(engine, eager=False, coordinates=(0.,)):
|
||||
trace = []
|
||||
engine.emissions.decoder = None # Both paths independently decode real bytes.
|
||||
def update(*args, **kwargs):
|
||||
result = _run(*args, **kwargs)
|
||||
trace.append((args[2], result[0], result[1].to_dict(), capture_snapshot(args[0], args[1])))
|
||||
return result
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay._run', side_effect=update), \
|
||||
mock.patch('openpilot.starpilot.flm.event_controller_replay._LazyControllerRequests',
|
||||
EagerRequests if eager else _LazyControllerRequests):
|
||||
try:
|
||||
return ('success', engine._rollout_uncached(coordinates)), trace
|
||||
except ValueError as error:
|
||||
return ('rejected', str(error)), trace
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
@pytest.mark.parametrize('coordinates', ((0.,), (.00001,)))
|
||||
def test_actual_selected_and_native_success_outputs_and_full_controller_trace_equal(domain, coordinates):
|
||||
engine = replay_fixture(domain)
|
||||
eager, before = run_observed(engine, eager=True, coordinates=coordinates)
|
||||
lazy, after = run_observed(engine, coordinates=coordinates)
|
||||
assert lazy == eager
|
||||
if lazy[0] == 'success':
|
||||
assert after == before and len(after) == len(engine.records)
|
||||
assert set(lazy[1][0]) == {row.control_ns for row in engine.records}
|
||||
if coordinates == (0.,):
|
||||
assert lazy[0] == 'success'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_real_first_native_byte_failure_rejects_without_unneeded_controller_tail(domain):
|
||||
engine = replay_fixture(domain)
|
||||
stamp, event = next(iter(engine.emissions.events.items()))
|
||||
engine.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
eager, before = run_observed(engine, eager=True)
|
||||
lazy, after = run_observed(engine)
|
||||
assert eager == lazy and lazy[0] == 'rejected' and 'actual steering CAN bytes mismatch' in lazy[1]
|
||||
assert 0 < len(after) < len(before) == len(engine.records)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('defect', ('late_controller', 'missing_last_tick', 'missing_consumed_request'))
|
||||
def test_native_success_never_skips_late_controller_or_exact_endpoint_guards(defect):
|
||||
engine = replay_fixture('torque')
|
||||
if defect == 'late_controller':
|
||||
engine.records[-1] = replace(engine.records[-1], healthy=False)
|
||||
elif defect == 'missing_last_tick':
|
||||
first, last = engine.replay_windows[0]
|
||||
engine.engine.replay_windows = ((first, last + 10_000_000),)
|
||||
else:
|
||||
stamp, event = next(iter(engine.emissions.events.items()))
|
||||
engine.emissions.events[stamp] = replace(event, control_ns=event.control_ns + 5_000_000)
|
||||
eager, _ = run_observed(engine, eager=True)
|
||||
lazy, _ = run_observed(engine)
|
||||
assert lazy == eager and lazy[0] == 'rejected'
|
||||
|
||||
|
||||
def test_lazy_mapping_repeated_older_keys_missing_key_and_explicit_finish():
|
||||
values, generated = {}, []
|
||||
def frames():
|
||||
for stamp in (10, 20, 30):
|
||||
generated.append(stamp)
|
||||
values[stamp] = stamp / 100
|
||||
yield stamp
|
||||
lazy = _LazyControllerRequests(values, frames())
|
||||
assert 20 in lazy and generated == [10, 20]
|
||||
assert lazy[10] == .1 and lazy[20] == .2 and generated == [10, 20]
|
||||
lazy.finish()
|
||||
assert generated == [10, 20, 30] and dict(lazy) == values
|
||||
assert 99 not in lazy
|
||||
with pytest.raises(KeyError):
|
||||
lazy[99]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('defect', ('success', 'native_failure', 'tail_failure'))
|
||||
def test_controller_iterator_is_closed_on_every_exit_without_bypassing_finish(defect):
|
||||
engine = replay_fixture('torque')
|
||||
if defect == 'native_failure':
|
||||
stamp, event = next(iter(engine.emissions.events.items()))
|
||||
engine.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
elif defect == 'tail_failure':
|
||||
engine.records[-1] = replace(engine.records[-1], healthy=False)
|
||||
closed = []
|
||||
original_close = _LazyControllerRequests.close
|
||||
def close(value):
|
||||
original_close(value)
|
||||
closed.append(value.frames.gi_frame is None)
|
||||
with mock.patch.object(_LazyControllerRequests, 'close', close):
|
||||
result, trace = run_observed(engine)
|
||||
assert closed == [True]
|
||||
assert result[0] == ('success' if defect == 'success' else 'rejected')
|
||||
if defect == 'success':
|
||||
assert len(trace) == len(engine.records)
|
||||
|
||||
|
||||
def test_unknown_legacy_receiver_keeps_plain_dictionary_and_eager_order():
|
||||
from openpilot.starpilot.flm.tests.test_declared_restore import replay_fixture as legacy_fixture
|
||||
engine, restored, frames, emissions = legacy_fixture()
|
||||
coordinates = tuple(dict(restored.coordinates)[name] for name in engine.objective.names)
|
||||
seen = []
|
||||
def update(*args, **kwargs):
|
||||
seen.append(args[2])
|
||||
return 0., SimpleNamespace(saturated=False)
|
||||
def native(requests, *args, **kwargs):
|
||||
assert type(requests) is dict and len(requests) == len(frames) and len(seen) == len(frames)
|
||||
return {}
|
||||
emissions.rollout.side_effect = native
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay._run', side_effect=update), \
|
||||
mock.patch('openpilot.starpilot.flm.event_controller_replay.restore_snapshot'):
|
||||
engine._rollout_uncached(coordinates, restore_target=True)
|
||||
@@ -0,0 +1,143 @@
|
||||
"""Deterministic native selection, no heldout search or authority fabrication."""
|
||||
from dataclasses import asdict, replace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm.adaptive_profile import Block, Sample, loss
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import parameters_for
|
||||
from openpilot.starpilot.flm.event_evaluate import evaluate_event, NATIVE_FEASIBILITY_SCALES
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay
|
||||
from openpilot.starpilot.flm.event_activation import (
|
||||
ZERO_REFERENCE_ACTIVATION_VERSION, EXACT_EMISSION_ACTIVATION_VERSION, activation_from_dict,
|
||||
activation_model_from_dict, activation_matches_model, bind_activation_model,
|
||||
)
|
||||
from openpilot.starpilot.flm.tests.test_full_surface_profile import base
|
||||
from openpilot.starpilot.flm.tests.test_event_activation import primitive_plan, fitted_model
|
||||
from openpilot.starpilot.flm.tests.test_declared_restore import envelope, plan_for
|
||||
|
||||
|
||||
def problem():
|
||||
envelope = base()
|
||||
identity, cohort, response = 'b' * 64, 'cohort', 'measured-response'
|
||||
blocks, episodes = [], []
|
||||
for episode in range(9):
|
||||
samples = tuple(Sample(1_000_000 * (episode * 40 + row + 1) + 2,
|
||||
1_000_000 * (episode * 40 + row + 1), 1_000_000 * (episode * 40 + row + 1) + 1,
|
||||
(1.,), .02, 1.) for row in range(40))
|
||||
block = Block(identity, str(episode), cohort, 'torque-selected-effect-v2', response, samples)
|
||||
blocks.append(block)
|
||||
episodes.append({'id': f'{episode:064x}', 'kind': 'subtle', 'start_ns': samples[0].control_ns,
|
||||
'end_ns': samples[-1].mono_ns, 'block_ids': (block.block_id,)})
|
||||
kwargs = {'episodes': tuple(episodes), 'now_ns': 1_000_000_000, 'required_cohorts': (cohort,), 'response_revision': response,
|
||||
'prior': (0.,), 'effect_envelope': envelope}
|
||||
return (identity, parameters_for(envelope.trial_names), tuple(blocks)), kwargs
|
||||
|
||||
|
||||
def test_first_native_feasible_scale_selected_before_tiny_heldout_losses_exact_metrics():
|
||||
args, kwargs = problem()
|
||||
calls = []
|
||||
def native(coordinates):
|
||||
calls.append(coordinates)
|
||||
return len(calls) == 3
|
||||
def exact_loss(samples, coordinates):
|
||||
assert len(calls) == 3 # All event tiny-heldout losses are AFTER frozen selection.
|
||||
return loss(samples, coordinates)
|
||||
with mock.patch('openpilot.starpilot.flm.event_evaluate.loss', side_effect=exact_loss):
|
||||
result = evaluate_event(*args, **kwargs, native_feasible=native)
|
||||
assert NATIVE_FEASIBILITY_SCALES == (1., .5, .25, .125, .0625, .03125, .015625, .0078125)
|
||||
assert calls == [(.005,), (.0025,), (.00125,)]
|
||||
profile = result.profile
|
||||
assert profile is not None and profile.coefficients == calls[-1]
|
||||
rows = tuple(row for block in args[2][4:] for row in block.samples)
|
||||
assert profile.baseline_loss == loss(rows, (0.,))
|
||||
assert profile.candidate_loss == loss(rows, calls[-1])
|
||||
assert profile.candidate_loss != loss(rows, calls[0]) * .25 # Never scale fake losses.
|
||||
assert dict(profile.effect_envelope.coordinates)['ff_gain_left'] == calls[-1][0]
|
||||
assert profile.train_ids == ('0', '1', '2', '3') and profile.heldout_ids == ('4', '5', '6', '7', '8')
|
||||
|
||||
|
||||
def test_all_fixed_native_scales_fail_without_losses_neutral_or_profile():
|
||||
args, kwargs = problem()
|
||||
native = mock.Mock(return_value=False)
|
||||
with mock.patch('openpilot.starpilot.flm.event_evaluate.loss') as tiny_loss:
|
||||
result = evaluate_event(*args, **kwargs, native_feasible=native)
|
||||
assert result.profile is None and result.reason == 'event_no_native_feasible_scale'
|
||||
assert [call.args[0] for call in native.call_args_list] == [(.005 * scale,) for scale in NATIVE_FEASIBILITY_SCALES]
|
||||
tiny_loss.assert_not_called()
|
||||
|
||||
|
||||
def test_selected_tiny_heldout_failure_never_falls_back_or_retries():
|
||||
args, kwargs = problem()
|
||||
native = mock.Mock(side_effect=(False, True, AssertionError('must not retry heldout rejection')))
|
||||
with mock.patch('openpilot.starpilot.flm.event_evaluate._lower', return_value=-1.):
|
||||
result = evaluate_event(*args, **kwargs, native_feasible=native)
|
||||
assert result.profile is None and result.reason == 'event_tiny_trial_heldout_regression_or_noise'
|
||||
assert native.call_count == 2
|
||||
|
||||
|
||||
def test_preliminary_gate_failure_cannot_select_direction_and_legacy_has_no_callback():
|
||||
args, kwargs = problem()
|
||||
native = mock.Mock(return_value=True)
|
||||
# Real contradictory heldout data fails the existing preliminary ridge gate.
|
||||
blocks = tuple(replace(block, samples=tuple(replace(row, residual_target=-.02) for row in block.samples))
|
||||
if index >= 4 else block for index, block in enumerate(args[2]))
|
||||
result = evaluate_event(args[0], args[1], blocks, **kwargs, native_feasible=native)
|
||||
assert result.profile is None and 'heldout' in result.reason
|
||||
native.assert_not_called()
|
||||
ordinary = evaluate_event(*args, **kwargs)
|
||||
assert ordinary.profile is not None and ordinary.profile.coefficients == (.005,)
|
||||
assert evaluate_event(*args, **kwargs, native_feasible=lambda _: 1).reason == 'event_native_feasibility_contract'
|
||||
|
||||
|
||||
def replay():
|
||||
plan = replace(primitive_plan(fitted_model()), version=ZERO_REFERENCE_ACTIVATION_VERSION).validate()
|
||||
engine = EventControllerReplay.__new__(EventControllerReplay)
|
||||
engine.activation_plan, engine.restore_envelope, engine.reference = plan, None, (0.,)
|
||||
engine._EventControllerReplay__strict_rollouts = None
|
||||
return engine
|
||||
|
||||
|
||||
def test_native_selector_has_no_loss_inputs_and_unknown_source_runs_baseline_each_time():
|
||||
engine = replay()
|
||||
native = ({1: 0., 2: 0.}, {1: 0., 2: 0.}, {1: False, 2: False})
|
||||
seen = []
|
||||
def rollout(coordinates):
|
||||
seen.append(coordinates)
|
||||
return native
|
||||
with mock.patch.object(engine, '_rollout', side_effect=rollout), \
|
||||
mock.patch.object(engine, '_slew_ok', return_value=True) as slew:
|
||||
assert engine.native_feasible((.005,)) and engine.native_feasible((.0025,))
|
||||
assert seen == [(.005,), (0.,), (.0025,), (0.,)]
|
||||
assert slew.call_count == 4
|
||||
# This symbolic shell has no owned native history and cannot gain cache
|
||||
# readiness. Actual Torque/PID cache keys and immutable maps are tested in
|
||||
# test_event_strict_rollout_cache, using their registered factories.
|
||||
assert engine._EventControllerReplay__strict_rollouts is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('defect', ('clipping', 'saturation', 'request_slew', 'encoded_slew', 'missing'))
|
||||
def test_native_selector_preserves_every_strict_feasibility_failure(defect):
|
||||
engine = replay()
|
||||
baseline = ({1: 0.}, {1: 0.}, {1: False})
|
||||
candidate = ({1: .01}, {1: .01}, {1: defect == 'saturation'})
|
||||
if defect == 'missing':
|
||||
candidate = ({1: .01}, {}, {1: False})
|
||||
sides = [ValueError('original unclip candidate clipping') if defect == 'clipping' else candidate, baseline]
|
||||
with mock.patch.object(engine, '_rollout', side_effect=sides), \
|
||||
mock.patch.object(engine, '_slew_ok', side_effect=lambda *_, encoded: not (
|
||||
defect == 'request_slew' and not encoded or defect == 'encoded_slew' and encoded)):
|
||||
assert not engine.native_feasible((.005,))
|
||||
|
||||
|
||||
def test_new_unpublished_plan_rejects_abandoned_string_restore_and_legacy_no_backtrack():
|
||||
engine = replay()
|
||||
model = bind_activation_model(fitted_model(), engine.activation_plan)
|
||||
assert activation_matches_model(engine.activation_plan, activation_model_from_dict(asdict(model)))
|
||||
abandoned = {**asdict(engine.activation_plan), 'version': 'fixed-eight-shadow-zero-reference-v5'}
|
||||
with pytest.raises(ValueError):
|
||||
activation_from_dict(abandoned)
|
||||
for plan in (replace(engine.activation_plan, version=EXACT_EMISSION_ACTIVATION_VERSION), plan_for(envelope())):
|
||||
engine.activation_plan = plan
|
||||
with pytest.raises(ValueError, match='declared trial plan'):
|
||||
engine.native_feasible((.005,))
|
||||
@@ -0,0 +1,187 @@
|
||||
"""Per-job deterministic reuse only; no manufactured physical/native acceptance."""
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import parameters_for, NAMES
|
||||
from openpilot.starpilot.flm.controller_replay import SelectedControllerFactory
|
||||
from openpilot.starpilot.flm.pid_controller_replay import PidControllerFactory
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.selected_effects import EffectTrial, EffectReplayTransition, CanonicalSurfaceEffects
|
||||
from openpilot.starpilot.flm.tests.test_full_surface_profile import base
|
||||
|
||||
|
||||
def engine_for(domain):
|
||||
if domain == 'pid':
|
||||
cp = interfaces[HONDA.HONDA_CIVIC].get_non_essential_params(HONDA.HONDA_CIVIC)
|
||||
factory = PidControllerFactory(cp, 'native')
|
||||
else:
|
||||
cp = interfaces[TOYOTA.TOYOTA_RAV4_TSS2].get_non_essential_params(TOYOTA.TOYOTA_RAV4_TSS2)
|
||||
factory = SelectedControllerFactory(cp, 'standard')
|
||||
frame = SimpleNamespace(control_ns=1, inputs=(('safety_limited', False),))
|
||||
actual = SimpleNamespace(factory=factory, identity='a' * 64, baseline_revision='b' * 64,
|
||||
model_revision='c' * 64, failed=False, raw_mode=True, objective=('original',), records=[frame],
|
||||
checkpoints=((1, ('exact-original-state',)),), replay_windows=((1, 2),),
|
||||
emissions=SimpleNamespace(cp=factory.cp, identity='a' * 64, baseline_revision='b' * 64,
|
||||
events={1: ('original-native-event',)}, carriers={1: (1, 1)}))
|
||||
engine = EventControllerReplay.__new__(EventControllerReplay)
|
||||
engine.engine = actual
|
||||
engine.objective = SimpleNamespace(names=('ff_gain_left',), parameters=parameters_for(('ff_gain_left',)))
|
||||
engine.reference, engine.prior_full, engine.original_anchor = (0.,), {'ff_gain_left': 0.}, {'full30': (0.,)}
|
||||
engine.activation_plan = engine.restore_envelope = None
|
||||
engine.projected = {}
|
||||
engine._EventControllerReplay__causal_features = None
|
||||
return engine
|
||||
|
||||
|
||||
def rollout(coordinates):
|
||||
# Symbolic deterministic native trace for cache behavior, NEVER a validation.
|
||||
return {1: coordinates[0]}, {1: coordinates[0]}, {1: False}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'pid'))
|
||||
def test_real_registered_factory_first_builds_then_reuses_exact_readonly_derived_features(domain):
|
||||
engine = engine_for(domain)
|
||||
assert type(engine._causal_input_key(.01)) is bytes
|
||||
with mock.patch.object(engine, '_rollout', side_effect=rollout) as native:
|
||||
assert engine.ensure_causal_features() == 1 and native.call_count == 3
|
||||
original = dict(engine.projected)
|
||||
engine.projected[1] = ('untrusted-public-edit',)
|
||||
assert engine.ensure_causal_features() == 1 and engine.projected == original
|
||||
assert native.call_count == 3
|
||||
_, cached = engine._EventControllerReplay__causal_features
|
||||
with pytest.raises(TypeError):
|
||||
cached[1] = (0., (9.,))
|
||||
assert engine.ensure_causal_features(epsilon=.02) == 1 and native.call_count == 6
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field', ('frame', 'native', 'checkpoint', 'anchor', 'objective', 'plan', 'source', 'factory'))
|
||||
def test_changed_exact_owned_input_does_not_reuse_previous_feature_result(field):
|
||||
engine = engine_for('torque')
|
||||
with mock.patch.object(engine, '_rollout', side_effect=rollout) as native:
|
||||
engine.ensure_causal_features()
|
||||
if field == 'frame':
|
||||
engine.records[0].inputs = (('safety_limited', True),)
|
||||
elif field == 'native':
|
||||
engine.emissions.events[1] = ('changed-native-event',)
|
||||
elif field == 'checkpoint':
|
||||
engine.engine.checkpoints = ((1, ('changed-checkpoint',)),)
|
||||
elif field == 'anchor':
|
||||
engine.original_anchor['full30'] = (-0.,) # Bit-sensitive, unlike dataclass equality.
|
||||
elif field == 'objective':
|
||||
engine.objective.parameters = tuple(replace(p, excitation_floor=.02) for p in engine.objective.parameters)
|
||||
elif field == 'plan':
|
||||
engine.activation_plan = SimpleNamespace(identification_policy='strict-final-quality-v1')
|
||||
elif field == 'source':
|
||||
engine.engine.baseline_revision = 'd' * 64
|
||||
else:
|
||||
engine.factory.manual_source_epoch = 'changed-source'
|
||||
engine.ensure_causal_features()
|
||||
assert native.call_count == 6
|
||||
|
||||
|
||||
def test_boolean_integer_type_and_missing_factory_cache_keys_never_alias_readiness():
|
||||
engine = engine_for('pid')
|
||||
before = engine._causal_input_key(.01)
|
||||
engine.records[0].inputs = (('safety_limited', 0),)
|
||||
assert engine._causal_input_key(.01) != before
|
||||
engine.engine.factory = SimpleNamespace()
|
||||
assert engine._causal_input_key(.01) is None
|
||||
with mock.patch.object(engine, '_rollout', side_effect=rollout) as native:
|
||||
engine.ensure_causal_features()
|
||||
engine.ensure_causal_features()
|
||||
assert native.call_count == 6
|
||||
|
||||
|
||||
def test_failed_full_build_cannot_create_ready_result_and_restore_still_rejects():
|
||||
engine = engine_for('pid')
|
||||
with mock.patch.object(engine, '_rollout', side_effect=ValueError('native/source guard')):
|
||||
with pytest.raises(ValueError):
|
||||
engine.ensure_causal_features()
|
||||
assert engine._EventControllerReplay__causal_features is None
|
||||
engine.restore_envelope = object()
|
||||
with pytest.raises(ValueError, match='restoration requires'):
|
||||
engine.ensure_causal_features()
|
||||
|
||||
|
||||
def transition(coordinate=.005):
|
||||
envelope = base(('ff_gain_left',))
|
||||
objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
return EffectReplayTransition(EffectTrial(objective, (coordinate,), envelope.selected_baseline_revision, envelope.replay_anchor()))
|
||||
|
||||
|
||||
def test_transition_reuses_only_exact_plateau_geometry_but_resets_each_real_frame_counter():
|
||||
owner = transition()
|
||||
with mock.patch('openpilot.starpilot.flm.selected_effects.CanonicalSurfaceEffects', wraps=CanonicalSurfaceEffects) as constructor:
|
||||
last = None
|
||||
for tick in range(110):
|
||||
current = owner.source_for_frame(1_000_000_000 + tick * 10_000_000)
|
||||
current.begin_frame(1_000_000_000 + tick * 10_000_000)
|
||||
current.selected_friction(.1, original_anchor=current.original_effect_anchor)
|
||||
assert current.friction_calls == 1
|
||||
with pytest.raises(ValueError):
|
||||
current.selected_friction(.1, original_anchor=current.original_effect_anchor)
|
||||
if tick >= 102:
|
||||
assert current.source is last.source
|
||||
last = current
|
||||
assert constructor.call_count <= 101 # Ramp configs change; plateau configs do not.
|
||||
previous = last.source
|
||||
owner.reset()
|
||||
fresh = owner.source_for_frame(3_000_000_000)
|
||||
assert fresh.source is not previous
|
||||
|
||||
|
||||
def test_cached_and_fresh_geometry_fields_are_bit_exact_and_candidates_do_not_share_state():
|
||||
owner = transition(0.)
|
||||
first = owner.source_for_frame(1_000_000_000)
|
||||
first.begin_frame(1_000_000_000)
|
||||
second = owner.source_for_frame(1_010_000_000)
|
||||
second.begin_frame(1_010_000_000)
|
||||
assert second.source is first.source
|
||||
values = dict(owner.applied)
|
||||
fresh = CanonicalSurfaceEffects({'ff_gain_left': values['ff_gain_left']}, owner.trial.selected_baseline_revision, accumulated=values)
|
||||
for name in ('values', 'names', 'applied_coordinates', 'original_effect_anchor', 'full_original_anchor', 'full_profile_sha256'):
|
||||
assert getattr(second.source, name) == getattr(fresh, name)
|
||||
assert second.source.surface.knobs == fresh.surface.knobs
|
||||
other = transition(0.).source_for_frame(1_010_000_000)
|
||||
assert other.source is not second.source
|
||||
with pytest.raises(ValueError, match='nonmonotonic'):
|
||||
second.begin_frame(1_010_000_000)
|
||||
assert tuple(values) == NAMES
|
||||
|
||||
|
||||
def test_plateau_cache_keeps_per_frame_trace_validation_and_exact_stage_outputs():
|
||||
from openpilot.starpilot.flm.tests.test_canonical_surface_adapter import CanonicalSurfaceTest
|
||||
owner = transition(.005)
|
||||
for tick in range(105):
|
||||
source = owner.source_for_frame(1_000_000_000 + tick * 10_000_000)
|
||||
source.begin_frame(1_000_000_000 + tick * 10_000_000)
|
||||
values = dict(owner.applied)
|
||||
fresh = CanonicalSurfaceEffects({'ff_gain_left': values['ff_gain_left']}, owner.trial.selected_baseline_revision, accumulated=values)
|
||||
fresh.begin_frame(2_040_000_000)
|
||||
for speed, setpoint in ((8., .2), (30., -.2)):
|
||||
frame = CanonicalSurfaceTest().frame(speed=speed, setpoint=setpoint)
|
||||
assert source.selected_stage(frame, original_anchor=source.original_effect_anchor) == fresh.selected_stage(
|
||||
frame, original_anchor=fresh.original_effect_anchor)
|
||||
source.begin_frame(source.source.frame_ns + 1)
|
||||
fresh.begin_frame(fresh.frame_ns + 1)
|
||||
invalid = SimpleNamespace(validate=mock.Mock(side_effect=ValueError('invalid current trace')))
|
||||
with pytest.raises(ValueError, match='invalid current trace'):
|
||||
owner.source_for_frame(2_100_000_000, invalid)
|
||||
invalid.validate.assert_called_once()
|
||||
|
||||
|
||||
def test_uninitialized_legacy_shell_never_recurses_or_mints_cache_readiness():
|
||||
from openpilot.starpilot.flm.tests.test_event_fd_boundary import engine as legacy_engine
|
||||
engine = legacy_engine()
|
||||
assert engine._causal_input_key(.01) is None
|
||||
with mock.patch.object(engine, '_rollout', side_effect=rollout) as native:
|
||||
assert engine.ensure_causal_features() == 1
|
||||
assert engine.ensure_causal_features() == 1
|
||||
assert native.call_count == 6
|
||||
assert engine._EventControllerReplay__causal_features is None
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Bounded deterministic memo tests, not controller/native/physical qualification."""
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm.event_activation import ZERO_REFERENCE_ACTIVATION_VERSION, EXACT_EMISSION_ACTIVATION_VERSION
|
||||
from openpilot.starpilot.flm.event_controller_replay import POLICY
|
||||
from openpilot.starpilot.flm.tests.test_event_activation import fitted_model, primitive_plan
|
||||
from openpilot.starpilot.flm.tests.test_event_replay_cost import engine_for, rollout
|
||||
|
||||
|
||||
def numeric(coordinates, **_kwargs):
|
||||
return rollout(coordinates)
|
||||
|
||||
|
||||
def engine(domain='torque'):
|
||||
result = engine_for(domain)
|
||||
result.activation_plan = replace(primitive_plan(fitted_model()), version=ZERO_REFERENCE_ACTIVATION_VERSION).validate()
|
||||
result._EventControllerReplay__strict_rollouts = None
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'pid'))
|
||||
def test_exact_owned_registered_factory_baseline_and_last_candidate_only_readonly_copyout(domain):
|
||||
value = engine(domain)
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
before = value._rollout(value.reference)
|
||||
candidate = value._rollout((.01,))
|
||||
assert original.call_count == 2
|
||||
before[0][1] = 99.
|
||||
candidate[1][1] = 99.
|
||||
assert value._rollout(value.reference) == rollout(value.reference)
|
||||
assert value._rollout((.01,)) == rollout((.01,))
|
||||
assert original.call_count == 2
|
||||
_, slots = value._EventControllerReplay__strict_rollouts
|
||||
assert set(slots) == {'baseline', 'candidate'}
|
||||
with pytest.raises(TypeError):
|
||||
slots['baseline'][1][0][1] = 1.
|
||||
value._rollout((.02,))
|
||||
value._rollout((.01,)) # Only the last successful candidate is retained.
|
||||
assert original.call_count == 4 and len(slots) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ('frame', 'native', 'checkpoint', 'anchor', 'objective', 'plan', 'failed', 'factory', 'reference'))
|
||||
def test_every_changed_owned_input_misses_and_replaces_all_slots(change):
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
value._rollout(value.reference)
|
||||
value._rollout((.01,))
|
||||
if change == 'frame':
|
||||
value.records[0].inputs = (('safety_limited', 0),) # False and0 must not alias.
|
||||
elif change == 'native':
|
||||
value.emissions.events[1] = ('different-native-source',)
|
||||
elif change == 'checkpoint':
|
||||
value.engine.checkpoints = ((1, ('different-checkpoint',)),)
|
||||
elif change == 'anchor':
|
||||
value.original_anchor['full30'] = (-0.,)
|
||||
elif change == 'objective':
|
||||
value.objective.names = ('ff_gain_right',)
|
||||
elif change == 'plan':
|
||||
value.activation_plan = replace(value.activation_plan, stage_blocks=tuple(reversed(value.activation_plan.stage_blocks)))
|
||||
elif change == 'failed':
|
||||
value.engine.failed = True
|
||||
elif change == 'factory':
|
||||
value.factory.manual_source_epoch = 'different-source'
|
||||
else:
|
||||
value.reference = (-0.,)
|
||||
value._rollout((.01,))
|
||||
assert original.call_count == 3
|
||||
assert set(value._EventControllerReplay__strict_rollouts[1]) == {'candidate'}
|
||||
|
||||
|
||||
def test_exact_coordinate_types_signed_zero_unknown_key_and_errors_never_create_alias():
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
for coordinates in ((0.,), (0,), (-0.,)):
|
||||
value._rollout(coordinates)
|
||||
assert original.call_count == 3
|
||||
with mock.patch.object(value, '_causal_input_key', return_value=None):
|
||||
value._rollout((.01,))
|
||||
value._rollout((.01,))
|
||||
assert original.call_count == 5 and value._EventControllerReplay__strict_rollouts is None
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=ValueError('strict native failure')) as failure:
|
||||
for _ in range(2):
|
||||
with pytest.raises(ValueError, match='strict native failure'):
|
||||
value._rollout((.01,))
|
||||
assert failure.call_count == 2
|
||||
assert not value._EventControllerReplay__strict_rollouts[1]
|
||||
|
||||
|
||||
def test_shadow_restore_ablation_old_plan_and_unsupported_types_always_execute_original_path():
|
||||
for kwargs in ({'_identification_token': object()}, {'restore_target': True}, {'restore_omit_group': ('ff_gain_left',)}):
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
value._rollout(value.reference)
|
||||
slots = dict(value._EventControllerReplay__strict_rollouts[1])
|
||||
value._rollout((.01,), **kwargs)
|
||||
value._rollout((.01,), **kwargs)
|
||||
assert original.call_count == 3 and value._EventControllerReplay__strict_rollouts[1] == slots
|
||||
assert original.call_args.kwargs == {'restore_target': kwargs.get('restore_target', False),
|
||||
'restore_omit_group': kwargs.get('restore_omit_group'), '_identification_token': kwargs.get('_identification_token')}
|
||||
value = engine()
|
||||
value.activation_plan = replace(value.activation_plan, version=EXACT_EMISSION_ACTIVATION_VERSION)
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original:
|
||||
value._rollout((.01,))
|
||||
value._rollout((.01,))
|
||||
assert original.call_count == 2 and value._EventControllerReplay__strict_rollouts is None
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=TypeError('original type rejection')) as original:
|
||||
for coordinates in ((False,), ['x'], object()):
|
||||
with pytest.raises(TypeError, match='original type rejection'):
|
||||
value._rollout(coordinates)
|
||||
assert original.call_count == 3 and value._EventControllerReplay__strict_rollouts is None
|
||||
|
||||
|
||||
def test_midexecution_input_mutation_cannot_be_stored_as_success():
|
||||
value = engine()
|
||||
def changing(coordinates):
|
||||
value.records[0].inputs = (('changed-during-controller-run', True),)
|
||||
return rollout(coordinates)
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=changing), pytest.raises(ValueError, match='changed owned input'):
|
||||
value._rollout((.01,))
|
||||
assert value._EventControllerReplay__strict_rollouts is None
|
||||
|
||||
|
||||
def test_native_feasibility_and_final_current_guard_are_not_cached_admission():
|
||||
value = engine()
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric) as original, \
|
||||
mock.patch.object(value, '_slew_ok', return_value=True):
|
||||
assert value.native_feasible((.01,))
|
||||
assert value.native_feasible((.01,))
|
||||
assert original.call_count == 2
|
||||
# This negative supplies no ReplayValidation: a cached numeric trace cannot
|
||||
# override the ordinary current-output limiter veto in final validate.
|
||||
profile = SimpleNamespace(policy=POLICY, identity=value.identity, parameters=value.objective.parameters, coefficients=(.01,))
|
||||
model = SimpleNamespace(identity=value.identity, domain='normalized_torque', context='unused')
|
||||
control = SimpleNamespace(identity=value.identity, baseline_revision=value.baseline_revision)
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay.canonical_physical_context', return_value='unused'):
|
||||
assert value.validate(profile, control, SimpleNamespace(limited=True), (), (), model) is None
|
||||
assert original.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'pid'))
|
||||
def test_failed_candidate_skips_baseline_lookup_but_retains_exact_owned_key(domain):
|
||||
value = engine(domain)
|
||||
with mock.patch.object(value, '_rollout_uncached', side_effect=numeric):
|
||||
value._rollout(value.reference)
|
||||
slots = value._EventControllerReplay__strict_rollouts[1]
|
||||
baseline = slots['baseline']
|
||||
with mock.patch.object(value, '_causal_input_key', wraps=value._causal_input_key) as source_key, \
|
||||
mock.patch.object(value, '_rollout_uncached', side_effect=ValueError('strict candidate clipping')) as original, \
|
||||
mock.patch.object(value, '_slew_ok') as slew:
|
||||
assert not value.native_feasible((.01,))
|
||||
assert original.call_args_list == [mock.call((.01,))]
|
||||
assert source_key.call_args_list == [mock.call(.01)]
|
||||
slew.assert_not_called()
|
||||
assert slots == {'baseline': baseline} # No failed candidate was stored.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'pid'))
|
||||
def test_successful_candidate_still_requires_original_baseline_and_all_source_keys(domain):
|
||||
value = engine(domain)
|
||||
with mock.patch.object(value, '_causal_input_key', wraps=value._causal_input_key) as source_key, \
|
||||
mock.patch.object(value, '_rollout_uncached',
|
||||
side_effect=[rollout((.01,)), ValueError('invalid baseline')]) as original, \
|
||||
mock.patch.object(value, '_slew_ok') as slew:
|
||||
assert not value.native_feasible((.01,))
|
||||
assert original.call_args_list == [mock.call((.01,)), mock.call(value.reference)]
|
||||
assert source_key.call_args_list == [mock.call(.01)] * 3
|
||||
slew.assert_not_called()
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Subset FD equivalence; no manufactured fit, model or current authority."""
|
||||
from dataclasses import asdict, replace
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import GROUPS, NAMES
|
||||
from openpilot.starpilot.flm.clipping_quality import ZERO_REFERENCE_QUALITY, NATIVE_SHADOW_IDENTIFICATION
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay
|
||||
from openpilot.starpilot.flm.event_fit_factory import fit_event_snapshot
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.selected_effects import EffectTrial, EffectReplayTransition
|
||||
from openpilot.starpilot.flm.tests.test_full_surface_profile import base
|
||||
from openpilot.starpilot.flm.tests.test_event_lazy_native import replay_fixture
|
||||
from openpilot.starpilot.flm.torque_adapter import Objective
|
||||
|
||||
|
||||
def full_and_subset(domain='torque'):
|
||||
original = replay_fixture(domain).engine
|
||||
envelope = base(names=GROUPS['ff.left'])
|
||||
objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
full = EventControllerReplay(original, objective, envelope.replay_anchor())
|
||||
full.rebuild_causal_features()
|
||||
envelope = envelope.for_trial(('ff_gain_left',))
|
||||
objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
return full, EventControllerReplay(original, objective, envelope.replay_anchor())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_actual_full_fd_columns_equal_independent_selected_controller_and_native_rebuild(domain):
|
||||
full, subset = full_and_subset(domain)
|
||||
with mock.patch.object(subset, '_rollout', side_effect=AssertionError('transfer cannot replay or mint a native result')):
|
||||
assert subset.reuse_subset_causal_features(full)
|
||||
copied = dict(subset.projected)
|
||||
assert copied == {stamp: (row[0], (row[1][0],)) for stamp, row in full.projected.items()}
|
||||
assert subset._EventControllerReplay__strict_rollouts is None
|
||||
frozen = subset._EventControllerReplay__causal_features[1]
|
||||
with pytest.raises(TypeError):
|
||||
frozen[0] = (0., (1.,))
|
||||
subset.projected.clear()
|
||||
with mock.patch.object(subset, 'rebuild_causal_features', side_effect=AssertionError('fresh bound key must hit')):
|
||||
assert subset.ensure_causal_features() == len(copied)
|
||||
assert subset.projected == copied
|
||||
independent = EventControllerReplay(subset.engine, subset.objective, subset.original_anchor)
|
||||
independent.rebuild_causal_features()
|
||||
assert independent.projected == copied
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ('context', 'frame', 'native', 'prior_bits', 'mutable_anchor', 'plan',
|
||||
'factory', 'engine', 'epsilon', 'missing_cache', 'nonfloat_prior'))
|
||||
def test_changed_or_unknown_owned_input_cannot_rebind_full_feature_proof(change):
|
||||
full, subset = full_and_subset()
|
||||
epsilon = .01
|
||||
if change == 'context':
|
||||
subset.objective = replace(subset.objective, context_key='1' * 64)
|
||||
elif change == 'frame':
|
||||
full.records[0] = replace(full.records[0], selected_command=-0.)
|
||||
elif change == 'native':
|
||||
stamp, event = next(iter(full.emissions.events.items()))
|
||||
full.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
elif change == 'prior_bits':
|
||||
subset.prior_full['ff_gain_left'] = -0.
|
||||
elif change == 'mutable_anchor':
|
||||
subset.original_anchor['accumulated'] = list(subset.original_anchor['accumulated'])
|
||||
elif change == 'plan':
|
||||
subset.activation_plan = SimpleNamespace(version='unknown')
|
||||
elif change == 'factory':
|
||||
full.factory.manual_source_epoch = 'changed-factory'
|
||||
elif change == 'engine':
|
||||
subset.engine = SimpleNamespace(**vars(full.engine))
|
||||
elif change == 'epsilon':
|
||||
epsilon = .02
|
||||
elif change == 'missing_cache':
|
||||
full._EventControllerReplay__causal_features = None
|
||||
else:
|
||||
full.prior_full['ff_gain_left'] = 0
|
||||
assert not subset.reuse_subset_causal_features(full, epsilon=epsilon)
|
||||
assert subset._EventControllerReplay__causal_features is None and not subset.projected
|
||||
|
||||
|
||||
def test_nonzero_float_prior_full30_targets_and_source_geometry_are_subset_invariant():
|
||||
prior = dict(base(names=GROUPS['ff.left']).prior_coordinates)
|
||||
prior['turn_in_boost_left'] = .002
|
||||
prior['center_taper_max'] = .003
|
||||
prior['ff_gain_right'] = -0.
|
||||
envelope = base(names=GROUPS['ff.left'])
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import full_profile_sha256
|
||||
coordinates = tuple((name, prior[name]) for name in NAMES)
|
||||
digest = full_profile_sha256(envelope.selected_baseline_revision, coordinates)
|
||||
envelope = replace(envelope, prior_coordinates=coordinates, coordinates=coordinates,
|
||||
prior_full_profile_sha256=digest, full_profile_sha256=digest).validate()
|
||||
for direction in (-1, 1):
|
||||
small = envelope.for_trial(('ff_gain_left',))
|
||||
left_objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
right_objective = EffectObjective('ff.left', small.trial_names, small.trial_anchor, small.context_key)
|
||||
full = tuple(prior[name] + direction * .01 if name == 'ff_gain_left' else prior[name] for name in envelope.trial_names)
|
||||
left = EffectTrial(left_objective, full, envelope.selected_baseline_revision, envelope.replay_anchor())
|
||||
right = EffectTrial(right_objective, (direction * .01,), envelope.selected_baseline_revision, small.replay_anchor())
|
||||
assert EffectReplayTransition(left).target == EffectReplayTransition(right).target
|
||||
assert left.source().values == right.source().values
|
||||
assert left.source().full_profile_sha256 == right.source().full_profile_sha256
|
||||
|
||||
|
||||
@pytest.mark.parametrize('reuse', (True, False, 'untrusted-true'))
|
||||
def test_actual_factory_uses_transfer_only_for_selected_subset_and_falls_back_exactly(reuse):
|
||||
envelope = base(names=GROUPS['ff.left'])
|
||||
objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
payload = {'event_version': 3, 'event_operation': 'trial', 'event_clipping_quality': ZERO_REFERENCE_QUALITY,
|
||||
'event_identification_policy': NATIVE_SHADOW_IDENTIFICATION, 'effect_envelope': asdict(envelope),
|
||||
'event_objective': asdict(objective), 'objective': asdict(Objective('feedforward', 'high', 'steady')),
|
||||
'physical_context': ('unit-boundary',)}
|
||||
# Boundary stubs expose routing only; they return no model/profile/validation.
|
||||
first, full, subset = mock.Mock(), mock.Mock(), mock.Mock()
|
||||
subset.reuse_subset_causal_features.return_value = reuse
|
||||
expected = {'status': 'collecting', 'reason': 'unit-boundary-only'}
|
||||
with mock.patch('openpilot.starpilot.flm.event_fit_factory.ControllerReplay.from_snapshot') as original, \
|
||||
mock.patch('openpilot.starpilot.flm.event_fit_factory.EventControllerReplay', side_effect=(first, full, subset)), \
|
||||
mock.patch('openpilot.starpilot.flm.event_raw_fit.prepare_fixed_event_calibration', return_value=(object(), 1, (), ())), \
|
||||
mock.patch('openpilot.starpilot.flm.event_activation.build_activation_plan', return_value=object()), \
|
||||
mock.patch('openpilot.starpilot.flm.runtime_adapter_registry.canonical_physical_context', return_value='unit'), \
|
||||
mock.patch('openpilot.starpilot.flm.event_fit_factory._identified', return_value=('ff_gain_left',)), \
|
||||
mock.patch('openpilot.starpilot.flm.event_raw_fit.fit_event_torque', return_value=expected) as fit:
|
||||
assert fit_event_snapshot(payload, object(), {'baseline_revision': envelope.selected_baseline_revision}) == expected
|
||||
subset.reuse_subset_causal_features.assert_called_once_with(full)
|
||||
assert subset.rebuild_causal_features.call_count == (0 if reuse is True else 1)
|
||||
assert full.rebuild_causal_features.call_count == 1
|
||||
assert fit.call_args.args[1] is subset and original.call_args.kwargs == {'rebuild': False}
|
||||
|
||||
|
||||
def test_unowned_legacy_shells_do_not_proxy_recurse_or_gain_readiness():
|
||||
source = EventControllerReplay.__new__(EventControllerReplay)
|
||||
destination = EventControllerReplay.__new__(EventControllerReplay)
|
||||
assert not destination.reuse_subset_causal_features(source)
|
||||
assert not destination.reuse_subset_causal_features(object())
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Subset controller prefix only: no native-result/token/admission transfer."""
|
||||
from dataclasses import replace
|
||||
import pickle
|
||||
from types import MappingProxyType
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm.canonical_surface_adapter import GROUPS
|
||||
from openpilot.starpilot.flm.controller_replay import _run
|
||||
from openpilot.starpilot.flm.event_controller_replay import EventControllerReplay
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.tests.test_event_controller_prefix import fixture, observed
|
||||
|
||||
|
||||
def full_and_subset(domain='torque', nonzero=True, epsilon=.01):
|
||||
original = fixture(domain, nonzero=nonzero)
|
||||
envelope = original.records[0].effect_trace.envelope.for_trial(GROUPS['ff.left'])
|
||||
objective = EffectObjective('ff.left', envelope.trial_names, envelope.trial_anchor, envelope.context_key)
|
||||
full = EventControllerReplay(original.engine, objective, envelope.replay_anchor(), activation_plan=original.activation_plan)
|
||||
full.rebuild_causal_features(epsilon=epsilon)
|
||||
small = envelope.for_trial(('ff_gain_left',))
|
||||
selected = EffectObjective('ff.left', small.trial_names, small.trial_anchor, small.context_key)
|
||||
subset = EventControllerReplay(original.engine, selected, small.replay_anchor(), activation_plan=original.activation_plan)
|
||||
return full, subset
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
@pytest.mark.parametrize('nonzero', (False, True))
|
||||
@pytest.mark.parametrize('shadow,delta', ((False, .000000001), (True, .01), (True, -.01)))
|
||||
def test_actual_full_to_subset_prefix_matches_independent_tail_state_logs_maps_and_native(domain, nonzero, shadow, delta):
|
||||
full, subset = full_and_subset(domain, nonzero=nonzero)
|
||||
source = full._EventControllerReplay__controller_prefix
|
||||
assert source is not None and len(source[1]) == 30
|
||||
independent = EventControllerReplay(subset.engine, subset.objective, subset.original_anchor, activation_plan=subset.activation_plan)
|
||||
assert subset.reuse_subset_causal_features(full) is True
|
||||
rebound = subset._EventControllerReplay__controller_prefix
|
||||
assert rebound is not None and rebound[0] == subset._causal_input_key(.01)
|
||||
assert rebound[0] != source[0] and rebound[1:] == source[1:]
|
||||
assert all(type(part) is MappingProxyType and part is not old for part, old in zip(rebound[1:], source[1:], strict=True))
|
||||
assert subset._EventControllerReplay__strict_rollouts is None
|
||||
assert subset._EventControllerReplay__identification_token is not full._EventControllerReplay__identification_token
|
||||
with pytest.raises(TypeError):
|
||||
rebound[1][0] = 1.
|
||||
coordinates = (subset.reference[0] + delta,)
|
||||
expected, before, native_before = observed(independent, coordinates, shadow=shadow)
|
||||
actual, after, native_after = observed(subset, coordinates, shadow=shadow)
|
||||
assert pickle.dumps(actual, protocol=5) == pickle.dumps(expected, protocol=5)
|
||||
assert len(before) == 60 and len(after) == 30 and after == before[30:]
|
||||
assert native_after == native_before
|
||||
|
||||
|
||||
@pytest.mark.parametrize('defect', ('missing', 'stale', 'mutable', 'bad_raw_type', 'bad_snapshot', 'nondefault_epsilon'))
|
||||
def test_missing_stale_unknown_prefix_does_not_change_valid_fd_transfer(defect):
|
||||
epsilon = .02 if defect == 'nondefault_epsilon' else .01
|
||||
full, subset = full_and_subset(epsilon=epsilon)
|
||||
source = full._EventControllerReplay__controller_prefix
|
||||
if defect == 'missing':
|
||||
full._EventControllerReplay__controller_prefix = None
|
||||
elif defect == 'stale':
|
||||
full._EventControllerReplay__controller_prefix = (b'not-current', *source[1:])
|
||||
elif defect == 'mutable':
|
||||
full._EventControllerReplay__controller_prefix = (source[0], dict(source[1]), *source[2:])
|
||||
elif defect == 'bad_raw_type':
|
||||
raw = dict(source[1])
|
||||
raw[next(iter(raw))] = 0 # Must not alias raw Float64 output.
|
||||
full._EventControllerReplay__controller_prefix = (source[0], MappingProxyType(raw), *source[2:])
|
||||
elif defect == 'bad_snapshot':
|
||||
snapshots = dict(source[3])
|
||||
snapshots[next(iter(snapshots))] = object()
|
||||
full._EventControllerReplay__controller_prefix = (*source[:3], MappingProxyType(snapshots))
|
||||
assert subset.reuse_subset_causal_features(full, epsilon=epsilon) is True
|
||||
assert subset._EventControllerReplay__controller_prefix is None
|
||||
assert subset.projected and subset._EventControllerReplay__strict_rollouts is None
|
||||
with mock.patch('openpilot.starpilot.flm.event_controller_replay._run', wraps=_run) as run:
|
||||
subset._rollout((subset.reference[0] + .000000001,),
|
||||
_identification_token=subset._EventControllerReplay__identification_token)
|
||||
assert run.call_count == 60
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ('prior', 'foreign_engine', 'plan', 'late_fault', 'native'))
|
||||
def test_existing_fd_guard_failure_never_transfers_controller_certificate(change):
|
||||
full, subset = full_and_subset()
|
||||
if change == 'prior':
|
||||
subset.prior_full['ff_gain_right'] = .002
|
||||
elif change == 'foreign_engine':
|
||||
other, _ = full_and_subset()
|
||||
subset.engine = other.engine
|
||||
elif change == 'plan':
|
||||
subset.activation_plan = replace(subset.activation_plan, physical_context='c' * 64)
|
||||
elif change == 'late_fault':
|
||||
full.records[-1] = replace(full.records[-1], healthy=False)
|
||||
else:
|
||||
stamp, event = next(iter(full.emissions.events.items()))
|
||||
full.emissions.events[stamp] = replace(event, witness=event.witness._replace(final_raw=event.witness.final_raw + 1))
|
||||
assert subset.reuse_subset_causal_features(full) is False
|
||||
assert subset._EventControllerReplay__controller_prefix is None
|
||||
assert subset._EventControllerReplay__strict_rollouts is None
|
||||
|
||||
|
||||
def test_source_shadow_token_cannot_become_destination_permission():
|
||||
full, subset = full_and_subset()
|
||||
assert subset.reuse_subset_causal_features(full)
|
||||
with pytest.raises(ValueError, match='identification scope'):
|
||||
subset._rollout((.01,), _identification_token=full._EventControllerReplay__identification_token)
|
||||
@@ -0,0 +1,103 @@
|
||||
"""One-shot worker lifecycle tests, not fabricated replay qualification."""
|
||||
import gc
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm import fit_worker
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def worker_io(monkeypatch):
|
||||
prior = gc.isenabled()
|
||||
incoming, outgoing = io.BytesIO(b'{"test":true}'), io.BytesIO()
|
||||
monkeypatch.setattr(fit_worker, 'sys', SimpleNamespace(platform='darwin',
|
||||
stdin=SimpleNamespace(buffer=incoming), stdout=SimpleNamespace(buffer=outgoing)))
|
||||
monkeypatch.setattr(fit_worker.os, 'nice', lambda _: None)
|
||||
monkeypatch.setattr(fit_worker.os, 'sched_setscheduler', lambda *args: None, raising=False)
|
||||
limits = []
|
||||
monkeypatch.setattr(fit_worker.resource, 'getrlimit', lambda kind: (-1, -1))
|
||||
monkeypatch.setattr(fit_worker.resource, 'setrlimit', lambda *args: limits.append(args))
|
||||
yield incoming, outgoing, limits
|
||||
if prior:
|
||||
gc.enable()
|
||||
else:
|
||||
gc.disable()
|
||||
|
||||
|
||||
def install_fit(monkeypatch, call):
|
||||
monkeypatch.setitem(sys.modules, 'openpilot.starpilot.flm.learner_factory', SimpleNamespace(fit_snapshot=call))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('enabled', (True, False))
|
||||
def test_success_restores_exact_gc_state_and_original_bounds(monkeypatch, worker_io, enabled):
|
||||
incoming, outgoing, limits = worker_io
|
||||
(gc.enable if enabled else gc.disable)()
|
||||
calls = []
|
||||
|
||||
def fit(snapshot):
|
||||
calls.append(snapshot)
|
||||
assert not gc.isenabled()
|
||||
# Even a helper toggling GC cannot alter the caller's final state.
|
||||
gc.enable()
|
||||
return {'status': 'collecting'}
|
||||
|
||||
original_dumps = json.dumps
|
||||
|
||||
def encode(*args, **kwargs):
|
||||
assert gc.isenabled() is enabled
|
||||
return original_dumps(*args, **kwargs)
|
||||
|
||||
install_fit(monkeypatch, fit)
|
||||
monkeypatch.setattr(fit_worker.json, 'dumps', encode)
|
||||
assert fit_worker.main() == 0
|
||||
assert gc.isenabled() is enabled
|
||||
assert calls == [{'test': True}]
|
||||
assert outgoing.getvalue() == b'{"status":"collecting"}'
|
||||
assert limits == [(fit_worker.resource.RLIMIT_AS, (2 * 1024**3, -1)),
|
||||
(fit_worker.resource.RLIMIT_CPU, (20, -1))]
|
||||
assert fit_worker.MAX_INPUT == 8 * 1024**2 and fit_worker.MAX_OUTPUT == 65536
|
||||
assert incoming.tell() == len(b'{"test":true}')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('enabled', (True, False))
|
||||
@pytest.mark.parametrize('failure', (ValueError, MemoryError))
|
||||
def test_fit_failure_restores_prior_gc_and_emits_nothing(monkeypatch, worker_io, enabled, failure):
|
||||
_, outgoing, _ = worker_io
|
||||
(gc.enable if enabled else gc.disable)()
|
||||
|
||||
def fit(_):
|
||||
assert not gc.isenabled()
|
||||
raise failure('optional fit failure')
|
||||
|
||||
install_fit(monkeypatch, fit)
|
||||
assert fit_worker.main() == 1
|
||||
assert gc.isenabled() is enabled
|
||||
assert outgoing.getvalue() == b''
|
||||
|
||||
|
||||
def test_oversized_input_never_enters_fit_or_changes_gc(monkeypatch, worker_io):
|
||||
incoming, outgoing, _ = worker_io
|
||||
gc.enable()
|
||||
incoming.seek(0)
|
||||
incoming.write(b'x' * (fit_worker.MAX_INPUT + 1))
|
||||
incoming.seek(0)
|
||||
install_fit(monkeypatch, lambda _: pytest.fail('oversized input reached fitter'))
|
||||
assert fit_worker.main() == 1
|
||||
assert gc.isenabled() and outgoing.getvalue() == b''
|
||||
|
||||
|
||||
def test_output_bound_remains_after_gc_restoration(monkeypatch, worker_io):
|
||||
_, outgoing, _ = worker_io
|
||||
gc.enable()
|
||||
|
||||
def fit(_):
|
||||
assert not gc.isenabled()
|
||||
return {'data': 'x' * fit_worker.MAX_OUTPUT}
|
||||
|
||||
install_fit(monkeypatch, fit)
|
||||
assert fit_worker.main() == 1
|
||||
assert gc.isenabled() and outgoing.getvalue() == b''
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Actual selected-controller reader equivalence; no fit/application authority."""
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI
|
||||
from openpilot.starpilot.flm.controller_replay import (
|
||||
SelectedControllerFactory, _PreparedCarStates, _car_state, _replay_car_state, _run, capture_snapshot, frame_input,
|
||||
)
|
||||
from openpilot.starpilot.flm.tests.test_event_replay_cost import engine_for
|
||||
from openpilot.starpilot.flm.torque_adapter import Objective
|
||||
|
||||
|
||||
def inputs_for(vm, tick=0):
|
||||
cs = structs.CarState.new_message(vEgo=23.123456789, steeringAngleDeg=.1 * math.sin(tick / 20),
|
||||
steeringRateDeg=.00003, gearShifter='drive')
|
||||
values = frame_input(cs, vm, SimpleNamespace(angleOffsetDeg=.02, roll=.001), active=True,
|
||||
safety_limited=False, desired_curvature=.0001 * math.cos(tick / 60), curvature_limited=False, lat_delay=.2)
|
||||
return values + (('control_frame', float(tick + 1)), ('raw_model_curvature', .0001))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_full_selected_updates_and_every_captured_state_match_fresh_builder(domain):
|
||||
if domain == 'starpilot':
|
||||
fingerprint = HYUNDAI.HYUNDAI_IONIQ_6
|
||||
cp = interfaces[fingerprint].get_non_essential_params(fingerprint)
|
||||
factory = SelectedControllerFactory(cp, 'starpilot')
|
||||
else:
|
||||
factory = engine_for(domain).factory
|
||||
actual, vm = factory.create()
|
||||
cached, cached_vm = factory.create()
|
||||
cache = _PreparedCarStates()
|
||||
objective = Objective('feedforward', 'high', 'steady')
|
||||
original_cp = factory.cp.as_builder().to_bytes()
|
||||
for tick in range(220):
|
||||
inputs = inputs_for(vm, tick)
|
||||
prepared = cache.get(inputs)
|
||||
assert prepared is not None and prepared.inputs is inputs
|
||||
expected, original_log = _run(actual, vm, inputs, objective, (0., 0.))
|
||||
observed, cached_log = _run(cached, cached_vm, inputs, objective, (0., 0.), _prepared_car_state=prepared)
|
||||
assert observed == expected and cached_log.to_dict() == original_log.to_dict()
|
||||
assert capture_snapshot(cached, cached_vm) == capture_snapshot(actual, vm)
|
||||
assert cache.get(inputs) is prepared
|
||||
assert prepared.reader.as_builder().to_bytes() == _car_state(dict(inputs)).to_bytes()
|
||||
assert factory.cp.as_builder().to_bytes() == original_cp
|
||||
|
||||
|
||||
def test_exact_capnp_fields_enum_float32_reader_immutability_and_replacement():
|
||||
_, vm = engine_for('torque').factory.create()
|
||||
inputs = inputs_for(vm)
|
||||
cache = _PreparedCarStates()
|
||||
prepared = cache.get(inputs)
|
||||
fresh = _car_state(dict(inputs))
|
||||
assert prepared.reader.to_dict() == fresh.to_dict()
|
||||
assert prepared.reader.as_builder().to_bytes() == fresh.to_bytes()
|
||||
assert prepared.reader.total_size.word_count <= 32
|
||||
assert prepared.reader.vEgo == fresh.vEgo
|
||||
assert prepared.reader.gearShifter == fresh.gearShifter
|
||||
assert prepared.reader.total_size.word_count == fresh.total_size.word_count
|
||||
with pytest.raises(AttributeError):
|
||||
prepared.reader.vEgo = 0.
|
||||
replaced = (*inputs[:-1], ('raw_model_curvature', -.0001))
|
||||
assert cache.get(replaced) is not prepared
|
||||
assert _replay_car_state(replaced, dict(replaced), prepared).to_dict() == _car_state(dict(replaced)).to_dict()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ([], [('vEgo', 20.)], (['vEgo', 20.],), (('vEgo', []),)))
|
||||
def test_mutable_or_unknown_input_shapes_take_original_path(value):
|
||||
assert _PreparedCarStates().get(value) is None
|
||||
|
||||
|
||||
def test_subclasses_fall_back_and_duplicate_key_semantics_are_unchanged():
|
||||
class Scalar(float):
|
||||
pass
|
||||
_, vm = engine_for('torque').factory.create()
|
||||
inputs = inputs_for(vm)
|
||||
cache = _PreparedCarStates()
|
||||
assert cache.get((*inputs, ('extra', Scalar(1.)))) is None
|
||||
duplicate = (*inputs, ('vEgo', 21.125))
|
||||
assert cache.get(duplicate).reader.vEgo == _car_state(dict(duplicate)).vEgo == 21.125
|
||||
|
||||
|
||||
def test_cache_is_strongly_retained_and_bounded_to_twelve_thousand_inputs():
|
||||
cache = _PreparedCarStates()
|
||||
with mock.patch('openpilot.starpilot.flm.controller_replay._car_state') as build:
|
||||
for tick in range(12000):
|
||||
inputs = (('tick', tick),)
|
||||
assert cache.get(inputs).inputs is inputs
|
||||
assert cache.get((('tick', 12000),)) is None
|
||||
assert build.call_count == 12000
|
||||
assert len(cache._PreparedCarStates__entries) == 12000
|
||||
|
||||
|
||||
def test_default_call_still_constructs_fresh_car_state_every_time():
|
||||
_, vm = engine_for('torque').factory.create()
|
||||
inputs = inputs_for(vm)
|
||||
with mock.patch('openpilot.starpilot.flm.controller_replay._car_state', wraps=_car_state) as build:
|
||||
a = _replay_car_state(inputs, dict(inputs), None)
|
||||
b = _replay_car_state(inputs, dict(inputs), None)
|
||||
assert build.call_count == 2 and a is not b and a.to_dict() == b.to_dict()
|
||||
@@ -8,7 +8,7 @@ from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.hyundai.values import CAR as HKG
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.starpilot.flm.clipping_quality import RECORDED_DEBT, STATE_ALIGNED_DEBT, QUALIFIED_EVENT_CLIPPING_QUALITY, event_clipping_quality
|
||||
from openpilot.starpilot.flm.clipping_quality import RECORDED_DEBT, STATE_ALIGNED_DEBT, ZERO_REFERENCE_QUALITY, event_clipping_quality
|
||||
from openpilot.starpilot.flm.event_fit_factory import fit_event_snapshot
|
||||
from openpilot.starpilot.flm.event_objective import EffectObjective
|
||||
from openpilot.starpilot.flm.learner_factory import from_evidence
|
||||
@@ -62,10 +62,10 @@ def test_actual_live_factory_adopts_both_domains_without_changing_legacy_default
|
||||
old = from_evidence(controls.CP, evidence, key, source=source)
|
||||
assert live is not None and old is not None
|
||||
assert controls.global_learning.descriptor.domain == domain
|
||||
assert live.replay_version == 3 and live.clipping_quality == QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
assert live.replay_version == 3 and live.clipping_quality == ZERO_REFERENCE_QUALITY
|
||||
assert old.replay_version == 2 and old.clipping_quality == RECORDED_DEBT
|
||||
live.select_objective(('event', 'deadband.high'))
|
||||
assert live.replay_version == 3 and live.clipping_quality == QUALIFIED_EVENT_CLIPPING_QUALITY
|
||||
assert live.replay_version == 3 and live.clipping_quality == ZERO_REFERENCE_QUALITY
|
||||
assert production_adapter_for_evidence(controls.CP, evidence, key, source=None) is None
|
||||
assert production_adapter_for_evidence(controls.CP, evidence, key, source=replace(source, enabled=False)) is None
|
||||
assert production_adapter_for_evidence(controls.CP, evidence, key, source=replace(source, manual=True)) is None
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
"""Replay-local canonical CP reuse rereads native bytes; no live/global authority."""
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from openpilot.starpilot.flm import runtime_adapter_registry as registry
|
||||
from openpilot.starpilot.flm.tests.test_event_controller_prefix import fixture
|
||||
|
||||
|
||||
def changed(engine, update):
|
||||
cp = engine.factory.cp.as_builder()
|
||||
update(cp)
|
||||
engine.factory.cp = cp.as_reader()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_actual_owned_cp_hits_only_one_entry_and_every_lookup_reads_bytes(domain):
|
||||
engine = fixture(domain).engine
|
||||
original = registry.car_params_digest
|
||||
with mock.patch.object(registry, 'car_params_digest', wraps=original) as canonical:
|
||||
assert engine._recorded_cp_digest() == original(engine.factory.cp)
|
||||
saved = engine.__dict__['_ControllerReplay__recorded_cp_digest_cache']
|
||||
for _ in range(5):
|
||||
assert engine._recorded_cp_digest() == saved[1]
|
||||
assert canonical.call_count == 1
|
||||
changed(engine, lambda cp: setattr(cp, 'carVin', '1HGBH41JXMN109186'))
|
||||
assert engine._recorded_cp_digest() == original(engine.factory.cp)
|
||||
assert canonical.call_count == 2
|
||||
assert engine.__dict__['_ControllerReplay__recorded_cp_digest_cache'][0] != saved[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', (('mass', 0.), ('mass', -0.), ('steerLimitTimer', float('inf')),
|
||||
('steerLimitTimer', -float('inf')), ('carVin', 'new-source')))
|
||||
def test_actual_scalar_bits_infinity_and_source_changes_match_original(field, value):
|
||||
engine = fixture().engine
|
||||
before = engine._recorded_cp_digest()
|
||||
changed(engine, lambda cp: setattr(cp, field, value))
|
||||
assert engine._recorded_cp_digest() == registry.car_params_digest(engine.factory.cp)
|
||||
assert engine._recorded_cp_digest() != before
|
||||
if field == 'mass':
|
||||
plus = engine._recorded_cp_digest()
|
||||
changed(engine, lambda cp: setattr(cp, 'mass', -value))
|
||||
# Test both directions of native Float32 signed zero identity.
|
||||
assert engine._recorded_cp_digest() != plus
|
||||
|
||||
|
||||
def test_actual_safety_config_native_bits_are_not_hidden_by_cache():
|
||||
engine = fixture().engine
|
||||
before = engine._recorded_cp_digest()
|
||||
cp = engine.factory.cp.as_builder()
|
||||
assert len(cp.safetyConfigs)
|
||||
cp.safetyConfigs[0].safetyParam ^= 1
|
||||
engine.factory.cp = cp.as_reader()
|
||||
assert engine._recorded_cp_digest() == registry.car_params_digest(engine.factory.cp)
|
||||
assert engine._recorded_cp_digest() != before
|
||||
|
||||
|
||||
@pytest.mark.parametrize('wrong', ('schema', 'object', 'factory'))
|
||||
def test_unknown_or_wrong_schema_keeps_original_digest_each_time(wrong):
|
||||
engine = fixture().engine
|
||||
if wrong == 'schema':
|
||||
engine.factory.cp = structs.CarState.new_message().as_reader()
|
||||
elif wrong == 'object':
|
||||
engine.factory.cp = SimpleNamespace(to_dict=lambda: {'negativeZero': -0., 'sentinel': float('inf')})
|
||||
else:
|
||||
engine.factory = SimpleNamespace(cp=engine.factory.cp)
|
||||
original = registry.car_params_digest
|
||||
with mock.patch.object(registry, 'car_params_digest', wraps=original) as canonical:
|
||||
expected = original(engine.factory.cp)
|
||||
assert engine._recorded_cp_digest() == engine._recorded_cp_digest() == expected
|
||||
assert canonical.call_count == 2
|
||||
assert '_ControllerReplay__recorded_cp_digest_cache' not in engine.__dict__
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure', ('serialization', 'changed_during_digest', 'canonical_error'))
|
||||
def test_serialization_failure_and_unstable_pair_never_cache_errors_or_retry_original(failure):
|
||||
engine = fixture().engine
|
||||
original = registry.car_params_digest
|
||||
real = engine.factory.cp
|
||||
class Reader:
|
||||
schema = structs.CarParams.schema
|
||||
def __init__(self):
|
||||
self.calls = 0
|
||||
def to_dict(self):
|
||||
return real.to_dict()
|
||||
def as_builder(self):
|
||||
self.calls += 1
|
||||
if failure == 'serialization':
|
||||
raise ValueError('serializer unavailable')
|
||||
raw = b'before' if self.calls == 1 else b'after'
|
||||
return SimpleNamespace(to_bytes=lambda: raw)
|
||||
engine.factory.cp = Reader()
|
||||
with mock.patch('capnp.lib.capnp._DynamicStructReader', Reader):
|
||||
if failure == 'canonical_error':
|
||||
with mock.patch.object(registry, 'car_params_digest', side_effect=ValueError('original digest error')) as canonical:
|
||||
with pytest.raises(ValueError, match='original digest error'):
|
||||
engine._recorded_cp_digest()
|
||||
assert canonical.call_count == 1
|
||||
else:
|
||||
with mock.patch.object(registry, 'car_params_digest', wraps=original) as canonical:
|
||||
assert engine._recorded_cp_digest() == original(real)
|
||||
assert canonical.call_count == 1
|
||||
assert '_ControllerReplay__recorded_cp_digest_cache' not in engine.__dict__
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_actual_normalize_trace_cp_binding_uses_cache_without_losing_any_recent_states(domain):
|
||||
engine = fixture(domain).engine
|
||||
cp_digest = registry.car_params_digest(engine.factory.cp)
|
||||
frames = tuple(engine.records)
|
||||
engine.records.clear()
|
||||
original = registry.car_params_digest
|
||||
with mock.patch.object(registry, 'car_params_digest', wraps=original) as canonical:
|
||||
for frame in frames:
|
||||
trace = frame.effect_trace
|
||||
trace = replace(trace, envelope=replace(trace.envelope, cp_sha256=cp_digest).validate()).validate()
|
||||
assert engine.normalize(replace(frame, effect_trace=trace), _verify_only=True) is not None
|
||||
assert canonical.call_count == 1
|
||||
assert len(engine.recent_states) == len(frames) == len(engine.records) == 60
|
||||
assert not engine.failed
|
||||
|
||||
|
||||
def test_mutation_through_builder_changes_same_reader_identity_and_recomputes():
|
||||
engine = fixture().engine
|
||||
builder = engine.factory.cp.as_builder()
|
||||
reader = builder.as_reader()
|
||||
engine.factory.cp = reader
|
||||
before = engine._recorded_cp_digest()
|
||||
builder.carVin = 'same-reader-new-native-bytes'
|
||||
assert engine.factory.cp is reader
|
||||
assert engine._recorded_cp_digest() == registry.car_params_digest(reader)
|
||||
assert engine._recorded_cp_digest() != before
|
||||
|
||||
|
||||
def test_same_schema_node_id_without_full_schema_equality_does_not_cache():
|
||||
engine = fixture().engine
|
||||
real = engine.factory.cp
|
||||
class Schema:
|
||||
node = SimpleNamespace(id=structs.CarParams.schema.node.id)
|
||||
def __eq__(self, other):
|
||||
return False
|
||||
class Reader:
|
||||
schema = Schema()
|
||||
def to_dict(self):
|
||||
return real.to_dict()
|
||||
def as_builder(self):
|
||||
raise AssertionError('wrong schema must not be serialized for a cache hit')
|
||||
engine.factory.cp = Reader()
|
||||
original = registry.car_params_digest
|
||||
with mock.patch('capnp.lib.capnp._DynamicStructReader', Reader), \
|
||||
mock.patch.object(registry, 'car_params_digest', wraps=original) as canonical:
|
||||
assert engine._recorded_cp_digest() == engine._recorded_cp_digest() == original(real)
|
||||
assert canonical.call_count == 2
|
||||
assert '_ControllerReplay__recorded_cp_digest_cache' not in engine.__dict__
|
||||
|
||||
|
||||
def test_malformed_original_result_is_not_cached_as_a_success():
|
||||
engine = fixture().engine
|
||||
with mock.patch.object(registry, 'car_params_digest', return_value=None) as canonical:
|
||||
assert engine._recorded_cp_digest() is None and engine._recorded_cp_digest() is None
|
||||
assert canonical.call_count == 2
|
||||
assert '_ControllerReplay__recorded_cp_digest_cache' not in engine.__dict__
|
||||
|
||||
|
||||
def test_replacing_canonical_function_invalidates_cache_without_any_data_identity_shortcut():
|
||||
engine = fixture().engine
|
||||
original = registry.car_params_digest
|
||||
assert engine._recorded_cp_digest() == original(engine.factory.cp)
|
||||
with mock.patch.object(registry, 'car_params_digest', wraps=original) as replacement:
|
||||
assert engine._recorded_cp_digest() == original(engine.factory.cp)
|
||||
assert engine._recorded_cp_digest() == original(engine.factory.cp)
|
||||
assert replacement.call_count == 1
|
||||
saved = engine.__dict__['_ControllerReplay__recorded_cp_digest_cache']
|
||||
assert saved[2] is replacement
|
||||
engine._recorded_cp_digest()
|
||||
assert engine.__dict__['_ControllerReplay__recorded_cp_digest_cache'][2] is original
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Exact leaf speedup equivalence, including all original generic fallbacks."""
|
||||
from collections import deque
|
||||
import math
|
||||
import pickle
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.flm import controller_replay as replay
|
||||
from openpilot.starpilot.flm.selected_effects import EffectReplayRecordedSource
|
||||
from openpilot.starpilot.flm.tests.test_event_controller_prefix import fixture
|
||||
|
||||
|
||||
def original_tree(values, path, value):
|
||||
# Original complete recursive traversal, without calling the proposed tree.
|
||||
if type(value).__module__.startswith('capnp') and 'List' in type(value).__name__:
|
||||
value = list(value)
|
||||
if isinstance(value, (tuple, list, deque)):
|
||||
values.append((path + '.kind', float(3 if isinstance(value, deque) else 2 if isinstance(value, tuple) else 1)))
|
||||
values.append((path + '.length', float(len(value))))
|
||||
if isinstance(value, deque):
|
||||
values.append((path + '.maxlen', float(value.maxlen or 0)))
|
||||
for i, item in enumerate(value):
|
||||
original_tree(values, path + '.' + str(i), item)
|
||||
else:
|
||||
values.append((path, replay._numeric(value)))
|
||||
if len(values) > replay.MAX_SNAPSHOT_VALUES:
|
||||
raise ValueError('snapshot exceeds bounded primitive budget')
|
||||
|
||||
|
||||
def outcome(function, value, initial=()):
|
||||
values = list(initial)
|
||||
error = None
|
||||
try:
|
||||
function(values, 'state', value)
|
||||
except (TypeError, ValueError, OverflowError, AttributeError) as exc:
|
||||
error = (type(exc), str(exc))
|
||||
return pickle.dumps(values, protocol=5), error
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', (0., -0., .12345678901234567, 1, True, False, -(2 ** 100), 2 ** 10000,
|
||||
float('nan'), float('inf'), -float('inf'), 'invalid', None,
|
||||
(1., [False, -0., .3]), deque([1., 2., (3.,)], maxlen=6),
|
||||
np.float64(.123), np.float32(-.5), np.int64(3), np.bool_(True), np.float64('nan')))
|
||||
@pytest.mark.parametrize('size', (0, 1023, 1024))
|
||||
def test_every_leaf_fallback_order_floatbits_error_and_max_bound_match_original(value, size):
|
||||
initial = tuple((str(i), 1.) for i in range(size))
|
||||
assert outcome(replay._tree, value, initial) == outcome(original_tree, value, initial)
|
||||
|
||||
|
||||
def test_subclass_coercion_and_native_capnp_pid_list_stay_original_fallback():
|
||||
class Float(float):
|
||||
calls = 0
|
||||
def __float__(self):
|
||||
type(self).calls += 1
|
||||
return .25
|
||||
value = Float(1.)
|
||||
expected = outcome(original_tree, value)
|
||||
assert Float.calls == 1
|
||||
Float.calls = 0
|
||||
assert outcome(replay._tree, value) == expected and Float.calls == 1
|
||||
class Tuple(tuple):
|
||||
pass
|
||||
assert outcome(replay._tree, Tuple((.1, -0.))) == outcome(original_tree, Tuple((.1, -0.)))
|
||||
from opendbc.car import structs
|
||||
cp = structs.CarParams.new_message()
|
||||
pid = cp.lateralTuning.init('pid')
|
||||
pid.kpBP = [0., 5., 20.]
|
||||
assert outcome(replay._tree, pid.kpBP) == outcome(original_tree, pid.kpBP)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot', 'pid'))
|
||||
def test_actual_warm_selected_snapshot_every_value_and_hidden_invalid_state_match_original(domain):
|
||||
engine = fixture(domain, nonzero=True)
|
||||
controller, vm = engine.factory.create()
|
||||
replay.restore_snapshot(controller, vm, engine.checkpoints[0][1])
|
||||
for frame in engine.records:
|
||||
value, _ = replay._run(controller, vm, frame.inputs, engine.engine.objective, (0., 0.), frame.torque_values,
|
||||
actual_stage_residual=frame.actual_stage_residual, effect_trial=EffectReplayRecordedSource(frame.effect_trace))
|
||||
assert replay.same_float(value, frame.actual_lac_command)
|
||||
proposed = replay.capture_snapshot(controller, vm)
|
||||
with mock.patch.object(replay, '_tree', original_tree):
|
||||
before = replay.capture_snapshot(controller, vm)
|
||||
assert pickle.dumps(proposed, protocol=5) == pickle.dumps(before, protocol=5)
|
||||
controller.pid.i = math.nan
|
||||
for function in (replay._tree, original_tree):
|
||||
with mock.patch.object(replay, '_tree', function), pytest.raises(ValueError, match='finite numeric'):
|
||||
replay.capture_snapshot(controller, vm)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', ('torque', 'starpilot'))
|
||||
def test_actual_manual_surface_and_duplicate_paths_guards_are_unchanged(domain):
|
||||
engine = fixture(domain)
|
||||
controller, vm = engine.factory.create()
|
||||
controller.flm_surface = object()
|
||||
for function in (replay._tree, original_tree):
|
||||
with mock.patch.object(replay, '_tree', function), pytest.raises(ValueError, match='manual surface'):
|
||||
replay.capture_snapshot(controller, vm)
|
||||
controller.flm_surface = None
|
||||
controller.pid.i = None
|
||||
setattr(controller.pid, 'i.none', 1.)
|
||||
for function in (replay._tree, original_tree):
|
||||
with mock.patch.object(replay, '_tree', function), pytest.raises(ValueError, match='cardinality'):
|
||||
replay.capture_snapshot(controller, vm)
|
||||
|
||||
|
||||
def test_custom_metaclass_equality_cannot_impersonate_exact_builtin_leaf():
|
||||
class EqualFloat(type):
|
||||
def __eq__(cls, other):
|
||||
return other is float
|
||||
__hash__ = type.__hash__
|
||||
class NotNumeric(metaclass=EqualFloat):
|
||||
def __float__(self):
|
||||
return .25
|
||||
value = NotNumeric()
|
||||
assert type(value) == float # noqa: E721 - exercise adversarial class equality, not type identity.
|
||||
assert type(value) is not float
|
||||
assert outcome(replay._tree, value) == outcome(original_tree, value)
|
||||
assert outcome(replay._tree, value)[1] == (ValueError, 'snapshot requires finite numeric state')
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Exact native zero-reference boundary; no fit, native authority or plant result."""
|
||||
from dataclasses import asdict, replace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car import DT_CTRL, rate_limit
|
||||
from opendbc.car.gm.values import CAR as GM
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.steering_projection import normalized_applied_command, _shaped_input, float32
|
||||
from openpilot.starpilot.flm.clipping_quality import (
|
||||
ZERO_REFERENCE_QUALITY, EXACT_EMISSION_QUALITY, QUALIFIED_EVENT_CLIPPING_QUALITY,
|
||||
NATIVE_SHADOW_IDENTIFICATION, FIXED_MAP_IDENTIFICATION, RESTORE_ZERO_REFERENCE_VERSION,
|
||||
RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION,
|
||||
zero_reference_relief_is_admitted, native_relief_is_admitted, event_clipping_quality, restore_policy, restore_quality,
|
||||
)
|
||||
from openpilot.starpilot.flm.event_activation import (
|
||||
ZERO_REFERENCE_ACTIVATION_VERSION, bind_activation_model, activation_matches_model, activation_model_from_dict, model_clipping_quality,
|
||||
)
|
||||
from openpilot.starpilot.flm.restore_activation import restore_plan_from_dict
|
||||
from openpilot.starpilot.flm.torque_emission_replay import EncoderBranch
|
||||
from openpilot.starpilot.flm.tests.test_event_activation import fitted_model, primitive_plan
|
||||
from openpilot.starpilot.flm.tests.test_state_aligned_encoder import captured_event
|
||||
from openpilot.starpilot.flm.tests.test_steering_projection import make_interface, control, apply, settle, install_observer
|
||||
from openpilot.starpilot.flm.tests.test_declared_restore import envelope, plan_for, payload
|
||||
|
||||
|
||||
def test_exact_actual_v9_integer_witness_and_all_sign_mirrors():
|
||||
values = (-86, -86, -85, -87, -86, -86, 1, 0, 1)
|
||||
for sign in (-1, 1):
|
||||
mirrored = tuple(sign * value for value in values)
|
||||
assert not native_relief_is_admitted(*mirrored)
|
||||
assert zero_reference_relief_is_admitted(*mirrored)
|
||||
# This admits no nonzero reference debt and no generalized <= comparator.
|
||||
changes = ((6, 0), (7, 1), (7, -1), (8, 0), (2, -86), (2, -87),
|
||||
(2, 0), (2, 1), (4, -85), (5, -85), (5, -87), (3, -85),
|
||||
(4, 0), (6, -1), (8, -1), (0, True), (2, float('nan')), (5, float('inf')))
|
||||
for index, value in changes:
|
||||
changed = list(values)
|
||||
changed[index] = value
|
||||
assert not zero_reference_relief_is_admitted(*changed), (index, value)
|
||||
|
||||
|
||||
def test_partial_order_boundary_property_has_no_tolerance_or_zero_generalization():
|
||||
for original in range(3, 15):
|
||||
for reduction in range(1, original):
|
||||
for sign in (-1, 1):
|
||||
args = tuple(sign * v for v in (original, original, original - reduction,
|
||||
original + 2, original, original, -2, 0, -reduction))
|
||||
assert zero_reference_relief_is_admitted(*args)
|
||||
for index in range(9):
|
||||
changed = list(args)
|
||||
if index == 7:
|
||||
changed[index] = sign * 1e-12
|
||||
else:
|
||||
changed[index] = 0
|
||||
assert not zero_reference_relief_is_admitted(*changed)
|
||||
|
||||
|
||||
def unwind(platform):
|
||||
ci = make_interface(platform)
|
||||
assert install_observer(ci)
|
||||
settle(ci, control(.1))
|
||||
for _ in range(4):
|
||||
_, _, witness = apply(ci, control(.01))
|
||||
if witness is not None:
|
||||
break
|
||||
assert witness is not None and 1 < witness.unlimited_raw < witness.final_raw
|
||||
return replace(captured_event(platform), witness=witness, limited=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('platform', (TOYOTA.TOYOTA_COROLLA_TSS2, GM.GMC_ACADIA))
|
||||
def test_registered_native_boundary_old_rejects_new_exact_reference_admits(platform):
|
||||
event = unwind(platform)
|
||||
witness = event.witness
|
||||
candidate = (witness.unlimited_raw - 1) / witness.effective_max
|
||||
previous = witness.unlimited_raw + dict(witness.limits)['STEER_DELTA_DOWN']
|
||||
old = EncoderBranch(quality_contract=EXACT_EMISSION_QUALITY)
|
||||
old.previous = previous
|
||||
with pytest.raises(ValueError, match='clipping regression'):
|
||||
old.step(event, candidate)
|
||||
branch = EncoderBranch(quality_contract=ZERO_REFERENCE_QUALITY)
|
||||
branch.previous = previous
|
||||
delivered = branch.step(event, candidate)
|
||||
assert branch.previous == witness.unlimited_raw
|
||||
assert delivered == normalized_applied_command(witness._replace(final_raw=witness.unlimited_raw))
|
||||
assert 0 < branch.previous < witness.final_raw
|
||||
# Future frame retains strict original-unclipped and own-state native checks.
|
||||
future = replace(event, control_ns=event.control_ns + 100_000_000,
|
||||
state_ns=event.state_ns + 100_000_000, apply_ns=event.apply_ns + 100_000_000, limited=False)
|
||||
with pytest.raises(ValueError):
|
||||
branch.step(future, 1.)
|
||||
for changed in (replace(event, limited=False), replace(event, witness=witness._replace(steer_requested=False)),
|
||||
replace(event, witness=witness._replace(codec_id='volkswagen.HCA_01'))):
|
||||
denied = EncoderBranch(quality_contract=ZERO_REFERENCE_QUALITY)
|
||||
denied.previous = previous
|
||||
with pytest.raises(ValueError):
|
||||
denied.step(changed, candidate)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('modified', (False, True))
|
||||
def test_honda_zero_reference_remains_in_shaped_normalized_and_decoded_domain(modified):
|
||||
platform = HONDA.HONDA_CIVIC_BOSCH if modified else HONDA.HONDA_CIVIC
|
||||
# Original production witness is a real normalized-rate unwind, inverse CAN sign.
|
||||
from opendbc.car import structs
|
||||
firmware = (structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x18DA30F1, subAddress=0,
|
||||
fwVersion=b'39990-TGG,A020\x00\x00', brand='honda'),) if modified else ()
|
||||
ci = make_interface(platform, firmware)
|
||||
assert install_observer(ci)
|
||||
settle(ci, control(.8))
|
||||
for _ in range(4):
|
||||
_, _, witness = apply(ci, control(.01))
|
||||
if witness is not None:
|
||||
break
|
||||
event = replace(captured_event(platform, modified=modified), witness=witness, limited=True)
|
||||
shape = tuple(witness.shape) if witness.shape else ()
|
||||
reference = _shaped_input(witness, float32(witness.requested_torque), shape)
|
||||
down = dict(witness.limits)['STEER_DELTA_DOWN'] * DT_CTRL
|
||||
previous = reference + down
|
||||
assert rate_limit(reference, previous, -down, dict(witness.limits)['STEER_DELTA_UP'] * DT_CTRL) == reference
|
||||
# Candidate shaping and the full actual native function remain unstubbed.
|
||||
old = EncoderBranch(quality_contract=EXACT_EMISSION_QUALITY)
|
||||
old.previous = previous
|
||||
with pytest.raises(ValueError, match='clipping regression'):
|
||||
old.step(event, .005)
|
||||
branch = EncoderBranch(quality_contract=ZERO_REFERENCE_QUALITY)
|
||||
branch.previous = previous
|
||||
delivered = branch.step(event, .005)
|
||||
assert 0 < delivered <= normalized_applied_command(witness)
|
||||
assert branch.previous == reference and witness.final_raw < 0
|
||||
if modified:
|
||||
assert branch.civic is not None and branch.civic != tuple(witness.shape[:4])
|
||||
# Both original-paired and candidate override decisions still run before quality.
|
||||
if modified:
|
||||
pressed = replace(event, witness=witness._replace(shape=tuple(witness.shape[:4]) + (18., True, -1.)))
|
||||
veto = EncoderBranch(quality_contract=ZERO_REFERENCE_QUALITY)
|
||||
veto.previous = previous
|
||||
veto.civic = (1., -.2, .095, False)
|
||||
with pytest.raises(ValueError, match='driver override'):
|
||||
veto.step(pressed, .005)
|
||||
|
||||
|
||||
def test_new_policy_trial_restore_model_proof_and_old_restore_mappings_are_distinct():
|
||||
assert QUALIFIED_EVENT_CLIPPING_QUALITY == EXACT_EMISSION_QUALITY
|
||||
assert tuple(restore_policy(v) for v in (4, 5, 6)) == (
|
||||
RESTORE_ACTIVATION_VERSION, RESTORE_AGGREGATE_ACTIVATION_VERSION, RESTORE_ZERO_REFERENCE_VERSION)
|
||||
assert restore_quality(4) == restore_quality(5) == EXACT_EMISSION_QUALITY
|
||||
assert restore_quality(6) == ZERO_REFERENCE_QUALITY
|
||||
row = {'event_version': 3, 'event_operation': 'trial', 'event_clipping_quality': ZERO_REFERENCE_QUALITY,
|
||||
'event_identification_policy': NATIVE_SHADOW_IDENTIFICATION}
|
||||
assert event_clipping_quality(row) == ZERO_REFERENCE_QUALITY
|
||||
trial = replace(primitive_plan(fitted_model()), version=ZERO_REFERENCE_ACTIVATION_VERSION).validate()
|
||||
assert trial.clipping_quality == ZERO_REFERENCE_QUALITY
|
||||
bound = bind_activation_model(fitted_model(), trial)
|
||||
assert model_clipping_quality(bound) == ZERO_REFERENCE_QUALITY
|
||||
assert activation_matches_model(trial, activation_model_from_dict(asdict(bound)))
|
||||
restore = replace(plan_for(envelope()), version=RESTORE_ZERO_REFERENCE_VERSION).validate()
|
||||
assert restore.identification_policy == FIXED_MAP_IDENTIFICATION and restore.clipping_quality == ZERO_REFERENCE_QUALITY
|
||||
model = bind_activation_model(fitted_model(), restore)
|
||||
assert model_clipping_quality(model) == ZERO_REFERENCE_QUALITY
|
||||
assert activation_matches_model(restore, activation_model_from_dict(asdict(model)))
|
||||
assert not activation_matches_model(replace(restore, version=RESTORE_AGGREGATE_ACTIVATION_VERSION), model)
|
||||
assert restore_plan_from_dict(asdict(restore)) == restore
|
||||
row = {**payload(), 'event_version': 6, 'event_restore_policy': RESTORE_ZERO_REFERENCE_VERSION,
|
||||
'event_clipping_quality': ZERO_REFERENCE_QUALITY}
|
||||
assert event_clipping_quality(row) == ZERO_REFERENCE_QUALITY
|
||||
for change in ({'event_version': 5}, {'event_restore_policy': RESTORE_AGGREGATE_ACTIVATION_VERSION},
|
||||
{'event_clipping_quality': EXACT_EMISSION_QUALITY}, {'event_operation': 'trial'},
|
||||
{'event_identification_policy': NATIVE_SHADOW_IDENTIFICATION}):
|
||||
with pytest.raises(ValueError):
|
||||
event_clipping_quality({**row, **change})
|
||||
for name in ('event_restore_policy', 'event_clipping_quality', 'event_identification_policy'):
|
||||
with pytest.raises(ValueError):
|
||||
event_clipping_quality({k: v for k, v in row.items() if k != name})
|
||||
|
||||
|
||||
def test_actual_outer_factory_v6_dispatch_and_parent_wrong_version_rejections():
|
||||
from openpilot.starpilot.flm.tests import test_declared_restore as existing
|
||||
row = {**existing.payload(), 'event_version': 6, 'event_restore_policy': RESTORE_ZERO_REFERENCE_VERSION,
|
||||
'event_clipping_quality': ZERO_REFERENCE_QUALITY}
|
||||
with mock.patch.object(existing, 'payload', return_value=row):
|
||||
existing.test_actual_child_and_outer_factory_dispatch_restore_v4_without_fd()
|
||||
for configured, reported in ((6, 5), (5, 6), (6, 4), (4, 6)):
|
||||
existing.test_actual_parent_rejects_restore_report_from_other_configured_version(configured, reported)
|
||||
@@ -11,7 +11,8 @@ import math
|
||||
|
||||
from openpilot.starpilot.flm.torque_eligibility import bounded_context_output
|
||||
from openpilot.starpilot.flm.clipping_quality import (
|
||||
RECORDED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, clipping_quality, debt_is_admitted, native_relief_is_admitted,
|
||||
RECORDED_DEBT, STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY,
|
||||
clipping_quality, debt_is_admitted, native_relief_is_admitted, zero_reference_relief_is_admitted,
|
||||
STRICT_IDENTIFICATION, NATIVE_SHADOW_IDENTIFICATION, identification_policy as validate_identification_policy,
|
||||
)
|
||||
|
||||
@@ -102,7 +103,8 @@ class EncoderBranch:
|
||||
def __init__(self, *, quality_contract=RECORDED_DEBT, identification_policy=STRICT_IDENTIFICATION):
|
||||
self.quality_contract = clipping_quality(quality_contract)
|
||||
self.identification_policy = validate_identification_policy(identification_policy)
|
||||
if self.identification_policy == NATIVE_SHADOW_IDENTIFICATION and self.quality_contract not in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY):
|
||||
if (self.identification_policy == NATIVE_SHADOW_IDENTIFICATION and
|
||||
self.quality_contract not in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY)):
|
||||
raise ValueError('shadow identification requires combined quality proof')
|
||||
self.previous = None
|
||||
self.civic = None
|
||||
@@ -173,7 +175,7 @@ class EncoderBranch:
|
||||
candidate_demand = source - honda_limited_source
|
||||
raw = int(np.interp(-honda_limited_source * witness.effective_max, witness.lookup_x, witness.lookup_y))
|
||||
admitted = debt_is_admitted(original_demand, reference_demand, candidate_demand, self.quality_contract)
|
||||
if not admitted and self.quality_contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY) and not shadow and event.limited:
|
||||
if not admitted and self.quality_contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) and not shadow and event.limited:
|
||||
reference_raw = int(np.interp(-reference_limited * witness.effective_max, witness.lookup_x, witness.lookup_y))
|
||||
recorded_output = normalized_applied_command(witness)
|
||||
reference_output = normalized_applied_command(witness._replace(final_raw=reference_raw))
|
||||
@@ -184,7 +186,13 @@ class EncoderBranch:
|
||||
admitted = (requested * witness.requested_torque > 0 and abs(requested) < abs(witness.requested_torque) and
|
||||
native_relief_is_admitted(witness.codec_input_torque, reference_source, source,
|
||||
recorded_output, reference_output, candidate_output, original_demand, reference_demand, candidate_demand))
|
||||
if not admitted and self.quality_contract == EXACT_EMISSION_QUALITY and not actual and not shadow and event.limited:
|
||||
if not admitted and self.quality_contract == ZERO_REFERENCE_QUALITY and not actual and not shadow and event.limited:
|
||||
admitted = (witness.codec_id in EXACT_EMISSION_CODECS and witness.steer_requested is True and
|
||||
type(raw) is int and type(reference_raw) is int and raw == reference_raw and
|
||||
requested * witness.requested_torque > 0 and abs(requested) < abs(witness.requested_torque) and
|
||||
zero_reference_relief_is_admitted(witness.codec_input_torque, reference_source, source,
|
||||
recorded_output, reference_output, candidate_output, original_demand, reference_demand, candidate_demand))
|
||||
if not admitted and self.quality_contract in (EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) and not actual and not shadow and event.limited:
|
||||
admitted = exact_local_emission(witness, reference_raw, raw, original_demand, reference_demand, candidate_demand)
|
||||
if not shadow and (not original_demand or not actual and not admitted):
|
||||
raise ValueError('candidate encoder clipping regression')
|
||||
@@ -213,10 +221,15 @@ class EncoderBranch:
|
||||
reference_demand = reference_request - reference_raw
|
||||
candidate_demand = requested_raw - raw
|
||||
admitted = debt_is_admitted(original_demand, reference_demand, candidate_demand, self.quality_contract)
|
||||
if not admitted and self.quality_contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY) and not shadow and event.limited:
|
||||
if not admitted and self.quality_contract in (STATE_ALIGNED_RELIEF, EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) and not shadow and event.limited:
|
||||
admitted = native_relief_is_admitted(witness.unlimited_raw, reference_request, requested_raw,
|
||||
witness.final_raw, reference_raw, raw, original_demand, reference_demand, candidate_demand)
|
||||
if not admitted and self.quality_contract == EXACT_EMISSION_QUALITY and not actual and not shadow and event.limited:
|
||||
if not admitted and self.quality_contract == ZERO_REFERENCE_QUALITY and not actual and not shadow and event.limited:
|
||||
admitted = (witness.codec_id in EXACT_EMISSION_CODECS and witness.steer_requested is True and
|
||||
type(raw) is int and type(reference_raw) is int and raw == reference_raw and
|
||||
zero_reference_relief_is_admitted(witness.unlimited_raw, reference_request, requested_raw,
|
||||
witness.final_raw, reference_raw, raw, original_demand, reference_demand, candidate_demand))
|
||||
if not admitted and self.quality_contract in (EXACT_EMISSION_QUALITY, ZERO_REFERENCE_QUALITY) and not actual and not shadow and event.limited:
|
||||
admitted = exact_local_emission(witness, reference_raw, raw, original_demand, reference_demand, candidate_demand)
|
||||
if not actual and not shadow and not admitted:
|
||||
raise ValueError('candidate encoder clipping regression')
|
||||
|
||||
Reference in New Issue
Block a user