Fix shadow observer lifecycle, learning and prediction correctness
This commit is contained in:
1 parent
2b9a10979b
commit
c434f3995d
10 files changed
+594
-33
No files matched your search
@@ -60,6 +60,55 @@ class ModelTests(unittest.TestCase):
|
||||
m.optimize({'r': 20}, 10, 0, 22, {'r': 0}, {'r': m.Learner()},
|
||||
False, 1000, horizon_minutes=60)
|
||||
|
||||
def test_short_horizon_does_not_claim_60_minute_prediction(self):
|
||||
out = m.optimize({'r': 20}, 5, 0, 22, {'r': 1}, {'r': m.Learner()},
|
||||
False, 3600, horizon_minutes=30)
|
||||
self.assertEqual(out['predicted_60m'], {})
|
||||
self.assertIn('r', out['predicted_end'])
|
||||
|
||||
def test_prediction_matches_replay_at_60_minutes(self):
|
||||
learners = {'r': m.Learner()}
|
||||
out = m.optimize({'r': 20}, 5, .4, 22, {'r': 1}, learners, False, 3600,
|
||||
horizon_minutes=90)
|
||||
temps, lag = {'r': 20}, .4
|
||||
for index, bit in enumerate(out['plan_15min']):
|
||||
for _ in range(3):
|
||||
temps, lag = m.simulate_step(temps, 5, lag, bit, learners)
|
||||
if index == 3:
|
||||
self.assertEqual(out['predicted_60m']['r'], round(temps['r'], 3))
|
||||
self.assertEqual(out['predicted_end']['r'], round(temps['r'], 3))
|
||||
|
||||
def test_jitter_normalized_and_rejection_counted(self):
|
||||
nominal, delayed = m.Learner(), m.Learner()
|
||||
self.assertTrue(nominal.learn(20, 20.1, 5, .5, command=True))
|
||||
self.assertTrue(delayed.learn(20, 20.11, 5, .5, 330, True))
|
||||
for a, b in zip(nominal.theta, delayed.theta):
|
||||
self.assertAlmostEqual(a, b)
|
||||
self.assertFalse(delayed.learn(20, 25, 5, .5, command=False))
|
||||
self.assertEqual((delayed.count, delayed.on_count, delayed.off_count), (1, 1, 0))
|
||||
|
||||
def test_corrupt_model_restore_is_atomic(self):
|
||||
for changes in ({'theta': [-1, .1, 0]}, {'p': [[-1, 0, 0], [0, 1, 0], [0, 0, 1]]},
|
||||
{'count': float('inf')}, {'count': '5'}, {'error_sum': float('nan')},
|
||||
{'off_count': 1}, {'theta': [float('nan'), 0, 0]}, {'p': None}):
|
||||
with self.subTest(changes=changes):
|
||||
data = m.Learner().as_dict()
|
||||
data.update(changes)
|
||||
self.assertEqual(m.Learner.from_dict(data), m.Learner())
|
||||
for bad in (None, [], 'broken'):
|
||||
self.assertEqual(m.Learner.from_dict(bad), m.Learner())
|
||||
|
||||
def test_nonfinite_optimizer_inputs_rejected(self):
|
||||
for key, value in (('weights', {'r': float('nan')}), ('effective_heat', float('nan')),
|
||||
('switching_cost', float('inf')), ('seconds_since_change', -1),
|
||||
('minimum_on_minutes', -1), ('comfort_band', float('nan'))):
|
||||
args = dict(temps={'r': 20}, outside=5, effective_heat=0, target=22,
|
||||
weights={'r': 1}, learners={'r': m.Learner()}, is_on=False,
|
||||
seconds_since_change=3600, horizon_minutes=60)
|
||||
args[key] = value
|
||||
with self.subTest(key=key), self.assertRaises(ValueError):
|
||||
m.optimize(**args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user