Files
StarPilot/openpilot/starpilot/flm/tests/test_adaptive_profile.py
T
firestar5683 200632aabd StarPilot Auto Learn
Learn bounded feed-forward corrections on eligible torque controllers behind Use StarPilot Auto Learn, preserving installed tunes and native safety limits.
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: whoisdomi <dcantu83@gmail.com>
2026-10-14 20:02:39 -05:00

141 lines
6.7 KiB
Python

from dataclasses import replace
import pytest
from openpilot.starpilot.flm.adaptive_profile import Block, Parameter, ProfileOwner, ReplayValidation, CurrentAdmission, readmit_profile, Sample, evaluate
IDENTITY = 'a' * 64
PARAMETERS = (Parameter('ff_gain', 'stage1', -.03, .03, .03, .005),)
def blocks():
result = []
for index in range(8):
rows = tuple(Sample(1_000_000_000 + (index * 40 + j) * 10_000_000,
990_000_000 + (index * 40 + j) * 10_000_000,
995_000_000 + (index * 40 + j) * 10_000_000,
(1. + j / 40,), .02 * (1. + j / 40), 1.) for j in range(40))
result.append(Block(IDENTITY, str(index), 'left' if index % 2 else 'right', 'stage1', 'plant1', rows))
return tuple(result)
def fit(data=None, parameters=PARAMETERS, **kwargs):
return evaluate(IDENTITY, parameters, blocks() if data is None else data, now_ns=5_000_000_000,
required_cohorts=('left', 'right'), response_revision='plant1', **kwargs)
def test_identified_plant_and_original_expiry():
profile = fit().profile
assert profile is not None
assert 0 < profile.coefficients[0] < .02
assert profile.candidate_loss < profile.baseline_loss
assert profile.expires_ns == profile.observed_ns + 500_000_000
assert set(profile.train_ids).isdisjoint(profile.heldout_ids)
@pytest.mark.parametrize('field', ['override', 'fault', 'saturated', 'model_wobble', 'controller_chatter'])
def test_ineligible_response_never_trains(field):
data = list(blocks())
rows = list(data[0].samples)
rows[0] = replace(rows[0], **{field: True})
data[0] = replace(data[0], samples=tuple(rows))
assert fit(tuple(data)).reason == 'evidence'
def test_wrong_physical_model_and_missing_cohort():
data = tuple(replace(block, response_revision='unqualified') for block in blocks())
assert fit(data).reason == 'provenance'
data = tuple(replace(block, cohort='left') for block in blocks())
assert fit(data).reason == 'independent_cohorts'
def test_heldout_regression_rejected():
data = list(blocks())
for index in range(4, 8):
data[index] = replace(data[index], samples=tuple(replace(row, residual_target=-row.residual_target) for row in data[index].samples))
assert fit(tuple(data)).status == 'rejected'
@pytest.mark.parametrize('epsilon', [0., 1e-9])
def test_confounded_basis_rejected(epsilon):
parameters = PARAMETERS + (replace(PARAMETERS[0], name='friction'),)
data = tuple(replace(block, samples=tuple(replace(row, features=(row.features[0], row.features[0] + epsilon))
for row in block.samples)) for block in blocks())
assert fit(data, parameters).reason == 'confounded_stage'
def test_gradual_adoption_same_sequence_mutation_and_exact_fallback():
profile = fit().profile
assert profile is not None
owner = ProfileOwner(IDENTITY, PARAMETERS, baseline_revision="baseline1", physical_model_revision="plant1")
kwargs = {"active": True, "enabled": True, "manual": False, "healthy": True,
"replay_validation": ReplayValidation(profile, "baseline1", "plant1", True, True, .001)}
first = owner.tick(profile, now_ns=5_000_000_000, **kwargs)
assert first == (.00005,)
changed = replace(profile, coefficients=(.01,))
changed_validation = replace(kwargs["replay_validation"], profile=changed)
assert owner.tick(changed, now_ns=5_010_000_000, **(kwargs | {"replay_validation": changed_validation})) == (0.,)
assert owner.tick(profile, now_ns=5_020_000_000, **kwargs) == (.00005,)
assert owner.tick(profile, now_ns=5_030_000_000, **(kwargs | {'manual': True})) == (0.,)
assert owner.tick(profile, now_ns=5_040_000_000, **(kwargs | {'active': False})) == (0.,)
assert owner.tick(profile, now_ns=5_550_000_001, **kwargs) == (0.,)
def test_live_revalidation_adopts_for_six_seconds_then_stale_withdraws():
fit_profile = fit().profile
assert fit_profile is not None
validation = ReplayValidation(fit_profile, 'baseline', 'physical', True, True, .001)
owner = ProfileOwner(IDENTITY, PARAMETERS, baseline_revision="baseline", physical_model_revision="physical")
profile, replay = fit_profile, validation
for tick in range(601):
now = 5_000_000_000 + tick * 10_000_000
if tick % 25 == 0:
admission = CurrentAdmission(IDENTITY, 'baseline', 'physical', now, True, True, False, True)
renewed = readmit_profile(fit_profile, validation, admission, now_ns=now, sequence=tick + 2)
assert renewed is not None
profile, replay = renewed
value = owner.tick(profile, now_ns=now, active=True, enabled=True, manual=False, healthy=True, replay_validation=replay)
assert value == profile.coefficients
stale = replace(admission, source_ns=now - 100_000_001)
assert readmit_profile(fit_profile, validation, stale, now_ns=now, sequence=1000) is None
assert fit_profile.expires_ns == 5_500_000_000
owner.last_ns = profile.expires_ns - 1
assert owner.tick(profile, now_ns=profile.expires_ns + 1, active=True, enabled=True, manual=False,
healthy=True, replay_validation=replay) == (0.,)
@pytest.mark.parametrize('field', ['baseline_revision', 'physical_model_revision'])
def test_owner_rejects_wrong_current_revision(field):
profile = fit().profile
assert profile is not None
owner = ProfileOwner(IDENTITY, PARAMETERS, baseline_revision='baseline', physical_model_revision='physical')
validation = ReplayValidation(profile, 'baseline', 'physical', True, True, .001)
assert owner.tick(profile, now_ns=5_000_000_000, active=True, enabled=True, manual=False,
healthy=True, replay_validation=replace(validation, **{field: 'different'})) == (0.,)
def test_historical_prior_regularizes_only_fresh_fit():
neutral = fit().profile
informed = fit(prior=(.02,)).profile
assert neutral is not None and informed is not None
assert abs(informed.coefficients[0] - .02) < abs(neutral.coefficients[0] - .02)
assert informed.train_ids == neutral.train_ids
assert informed.heldout_ids == neutral.heldout_ids
assert fit((), prior=(.02,)).profile is None
assert fit(prior=None) == fit()
@pytest.mark.parametrize('prior', [(.04,), (float('nan'),), (), [0.], (True,)])
def test_bad_historical_prior_cannot_admit(prior):
assert fit(prior=prior).reason == 'historical_prior'
def test_prior_cannot_bypass_fresh_override_or_heldout_regression():
data = list(blocks())
data[0] = replace(data[0], samples=tuple(replace(row, override=True) for row in data[0].samples))
assert fit(tuple(data), prior=(.02,)).reason == 'evidence'
data = list(blocks())
for index in range(4, 8):
data[index] = replace(data[index], samples=tuple(replace(row, residual_target=-row.residual_target) for row in data[index].samples))
assert fit(tuple(data), prior=(.02,)).status == 'rejected'