mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
86 lines
4.2 KiB
Python
86 lines
4.2 KiB
Python
"""Independent whole-episode response gates, separate from legacy sign blocks."""
|
|
from dataclasses import replace
|
|
import math
|
|
|
|
from openpilot.starpilot.flm.event_physical import fit_event_physical
|
|
from openpilot.starpilot.flm.physical_response import PhysicalRow, fit_physical
|
|
|
|
BASE = 1_000_000_000
|
|
|
|
|
|
def episodes():
|
|
result = []
|
|
for episode in range(8):
|
|
for sample in range(80):
|
|
stamp = BASE + episode * 5_000_000_000 + sample * 50_000_000
|
|
command = .15 * math.sin(2 * math.pi * (sample + .25) / 40)
|
|
result.append(PhysicalRow(str(episode), stamp, stamp + 50_000_000,
|
|
command, 1.98 * command + .002, 'physical-context', 'left' if command > 0 else 'right', True, False, False))
|
|
return tuple(result)
|
|
|
|
|
|
def test_whole_mixed_sign_episodes_are_not_split_into_fake_independent_turns():
|
|
rows = episodes()
|
|
now = rows[-1].response_ns
|
|
legacy = fit_physical('a' * 64, 'normalized_torque', 'physical-context', rows,
|
|
lag_ns=50_000_000, min_excitation=.05, max_rmse=.05, now_ns=now)
|
|
assert legacy is None # Legacy one-sign block policy remains unchanged.
|
|
model = fit_event_physical('a' * 64, 'physical-context', rows, lag_ns=50_000_000, now_ns=now)
|
|
assert model is not None and abs(model.slope - 1.98) < 1e-12
|
|
assert model.train_blocks == ('0', '1', '2', '3')
|
|
assert model.heldout_blocks == ('4', '5', '6', '7')
|
|
assert model.heldout_rmse < 1e-12
|
|
assert len({row.block_id for row in rows}) == 8
|
|
|
|
|
|
def test_whole_episode_model_preserves_fault_clip_context_and_latency_gates():
|
|
rows = episodes()
|
|
for changed in (
|
|
replace(rows[25], saturated=True), replace(rows[25], healthy=False),
|
|
replace(rows[25], overridden=True), replace(rows[25], context='foreign'),
|
|
replace(rows[25], response_ns=rows[25].response_ns + 30_000_001),
|
|
):
|
|
altered = rows[:25] + (changed,) + rows[26:]
|
|
assert fit_event_physical('a' * 64, 'physical-context', altered, lag_ns=50_000_000, now_ns=rows[-1].response_ns) is None
|
|
|
|
|
|
def test_one_whole_episode_cannot_be_relabelled_as_eight_independent_episodes():
|
|
rows = tuple(replace(row, block_id='same-turn') for row in episodes())
|
|
assert fit_event_physical('a' * 64, 'physical-context', rows, lag_ns=50_000_000, now_ns=rows[-1].response_ns) is None
|
|
|
|
|
|
def test_local_model_does_not_extrapolate_or_accept_quiet_unidentified_input():
|
|
rows = episodes()
|
|
quiet = tuple(replace(row, command=row.command / 100, physical_response=row.physical_response / 100) for row in rows)
|
|
assert fit_event_physical('a' * 64, 'physical-context', quiet, lag_ns=50_000_000, now_ns=quiet[-1].response_ns) is None
|
|
unsupported = tuple(replace(row, command=row.command * 2, physical_response=row.physical_response * 2)
|
|
if row.block_id in ('4', '5', '6', '7') else row for row in rows)
|
|
assert fit_event_physical('a' * 64, 'physical-context', unsupported, lag_ns=50_000_000, now_ns=unsupported[-1].response_ns) is None
|
|
|
|
|
|
def test_zero_and_negative_zero_do_not_supply_negative_direction_support():
|
|
for zero in (0.0, -0.0):
|
|
for change_train in (True, False):
|
|
rows = tuple(replace(row, command=zero, physical_response=.002, direction='right')
|
|
if row.command < 0 and (change_train or row.block_id in ('4', '5', '6', '7')) else row
|
|
for row in episodes())
|
|
assert fit_event_physical('a' * 64, 'physical-context', rows,
|
|
lag_ns=50_000_000, now_ns=rows[-1].response_ns) is None
|
|
|
|
|
|
def test_tiny_positive_has_no_negative_direction_support_and_no_epsilon_is_used():
|
|
for tiny, expected in ((1e-15, False), (-1e-15, True)):
|
|
rows = tuple(replace(row, command=tiny, physical_response=1.98 * tiny + .002,
|
|
direction='left' if tiny > 0 else 'right') if row.command < 0 else row
|
|
for row in episodes())
|
|
model = fit_event_physical('a' * 64, 'physical-context', rows,
|
|
lag_ns=50_000_000, now_ns=rows[-1].response_ns)
|
|
assert (model is not None) is expected
|
|
|
|
|
|
def test_true_bilateral_original_control_still_qualifies():
|
|
rows = episodes()
|
|
assert any(row.command > 0 for row in rows) and any(row.command < 0 for row in rows)
|
|
assert fit_event_physical('a' * 64, 'physical-context', rows,
|
|
lag_ns=50_000_000, now_ns=rows[-1].response_ns) is not None
|