Files
StarPilot/openpilot/starpilot/curve_speed/tests/test_learning.py
T
firestar5683 3e9f070c70 Curve Speed
Resolve curve speed and smoothing through a dedicated learning and preference owner.

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

92 lines
4.2 KiB
Python

import copy
import math
import unittest
from openpilot.starpilot.curve_speed.learning import CURVATURES, GRID, KEYS, MAX_COUNT, LearnedCurve, bucket_index, monotone_fit
class TestLearning(unittest.TestCase):
def test_sparse_conflicting_samples_use_weighted_monotone_fit(self):
for actual, expected in zip(monotone_fit([3.0, 1.0, 4.0], [1, 9, 2]), (1.2, 1.2, 4.0), strict=True):
self.assertAlmostEqual(actual, expected)
result = LearnedCurve.load({KEYS[5]: {'average': 3.0, 'count': 1000}, KEYS[6]: {'average': 1.2, 'count': 1}})
assert result.valid
values = [result.curve.comfort(k) for k in CURVATURES]
assert all((a <= b for a, b in zip(values, values[1:], strict=False)))
assert values[5] > LearnedCurve().comfort(CURVATURES[5])
def test_legacy_duplicate_buckets_merge_without_mutating_or_dirtying_saved_input(self):
document = {'0.001': {'average': 2.0, 'count': 3}, '-0.0010001': {'average': 3.0, 'count': 1}}
original = copy.deepcopy(document)
loaded = LearnedCurve.load(document)
assert loaded.valid and loaded.migrated and (not loaded.curve.dirty)
assert document == original
assert loaded.curve.document()['buckets'] == {KEYS[bucket_index(0.001)]: {'average': 2.25, 'count': 4}}
assert LearnedCurve.load(loaded.curve.document()).curve.document() == loaded.curve.document()
def test_bad_documents_cannot_be_partially_rewritten(self):
for document in [
[],
{'version': True, 'buckets': {}},
{'version': 2, 'buckets': {}},
{'version': 1, 'buckets': {}, 'extra': 0},
{'nan': {'average': 2.0, 'count': 1}},
{'0.01': {'average': math.inf, 'count': 1}},
{'0.01': {'average': 2.0, 'count': math.inf}},
{'0.01': {'average': 2.0, 'count': 1.5}},
{'0.01': {'average': 2.0, 'count': True}},
{'0.01': {'average': 2.0, 'count': MAX_COUNT + 1}},
{'0.01': {'average': -1.0, 'count': 1}},
{'0.01': {'average': 2.0, 'count': 0}},
{str(i): {'average': 2.0, 'count': 1} for i in range(257)},
{'0.001': {'average': 2.0, 'count': MAX_COUNT}, '-0.001': {'average': 2.0, 'count': 1}},
]:
with self.subTest(document=document):
result = LearnedCurve.load(document)
assert not result.valid
assert not result.curve.dirty
assert result.curve.document() == {'version': 1, 'buckets': {}}
def test_learning_caps_historic_weight_but_preserves_progress_count(self):
loaded = LearnedCurve.load({KEYS[10]: {'average': 2.0, 'count': 10000}})
loaded.curve.observe(GRID[10], 3.0)
sample = loaded.curve.document()['buckets'][KEYS[10]]
self.assertAlmostEqual(sample['average'], (600 * 2.0 + 3.0) / 601)
self.assertEqual(sample['count'], 10001)
self.assertAlmostEqual(loaded.curve.progress, 100 / 24)
def test_nudge_is_bounded_and_uses_separate_weight(self):
curve = LearnedCurve()
curve.nudge(0.01, 100.0)
assert curve.document()['buckets'][KEYS[bucket_index(0.01)]] == {'average': 3.2, 'count': 20}
curve.nudge(0.01, -1.0)
sample = curve.document()['buckets'][KEYS[bucket_index(0.01)]]
self.assertAlmostEqual(sample['average'], 2.2)
self.assertEqual(sample['count'], 40)
def test_write_acknowledgement_does_not_lose_newer_samples(self):
curve = LearnedCurve()
curve.observe(0.001, 1.7)
writing_revision = curve.revision
curve.observe(0.002, 1.9)
curve.acknowledge_saved(writing_revision)
assert curve.dirty
curve.acknowledge_saved(curve.revision)
assert not curve.dirty
with self.assertRaises(ValueError):
curve.acknowledge_saved(writing_revision)
def test_invalid_live_observation_does_not_poison_prior(self):
for value in [math.nan, math.inf, True, 10**1000]:
with self.subTest(value=value):
curve = LearnedCurve()
before = curve.document()
with self.assertRaises(ValueError):
curve.observe(value, 2.0)
assert curve.document() == before and (not curve.dirty)
def test_calibration_saturates_independently_of_mean(self):
curve = LearnedCurve.load({key: {'average': 2.0, 'count': 1000} for key in KEYS}).curve
assert curve.progress == 100
assert 1.2 <= curve.average_comfort <= 3.2
assert LearnedCurve().average_comfort == 2.0