def test_temperature_approximation2() -> None: features = { "previous_heater_dc": 17, "time_series_of_temp": [ 44.8125, 43.8125, 43.0, 42.25, 41.5625, 40.875, 40.3125, 39.75, 39.1875, 38.6875, 38.25, 37.8125, 37.375, 37.0, 36.625, 36.25, 35.9375, ], } with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert 38 <= t.approximate_temperature(features) <= 39
def test_changing_temperature_algo_over_mqtt_and_then_update_params() -> None: pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/temperature", None, retain=True, ) with temperature_control.TemperatureController( "silent", unit=unit, experiment=experiment) as algo: assert algo.automation_name == "silent" assert isinstance(algo.automation_job, Silent) pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/automation/set", '{"automation_name": "constant_duty_cycle", "duty_cycle": 25}', ) time.sleep(8) assert algo.automation_name == "constant_duty_cycle" assert isinstance(algo.automation_job, ConstantDutyCycle) assert algo.automation_job.duty_cycle == 25 pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_automation/duty_cycle/set", 30) pause() assert algo.automation_job.duty_cycle == 30
def test_temperature_approximation3() -> None: features = { "previous_heater_dc": 17, "time_series_of_temp": [ 49.875, 47.5, 45.8125, 44.375, 43.1875, 42.0625, 41.125, 40.3125, 39.5625, 38.875, 38.1875, 37.625, 37.125, 36.625, 36.1875, 35.8125, 35.4375, ], } with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert 39 <= t.approximate_temperature(features) <= 40
def test_duty_cycle_is_published_and_not_settable() -> None: dc_msgs = [] def collect(msg) -> None: dc_msgs.append(msg.payload) pubsub.subscribe_and_callback( collect, f"pioreactor/{get_unit_name()}/{get_latest_experiment_name()}/temperature_control/heater_duty_cycle", ) with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment): # change to PID stable pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/automation/set", '{"automation_name": "stable", "target_temperature": 35}', ) pause(3) # should produce an "Unable to set heater_duty_cycle" pubsub.publish( f"pioreactor/{get_unit_name()}/{get_latest_experiment_name()}/temperature_control/heater_duty_cycle/set", 10, ) pause(1) assert len(dc_msgs) > 0
def test_temperature_approximation1() -> None: features = { "previous_heater_dc": 17, "time_series_of_temp": [ 37.8125, 36.625, 35.6875, 35.0, 34.5, 34.0625, 33.6875, 33.4375, 33.1875, 33.0, 32.875, 32.6875, 32.5625, 32.4375, 32.375, 32.25, 32.1875, ], } with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert 32.0 <= t.approximate_temperature(features) <= 33.4
def test_temperature_approximation_if_constant() -> None: features = {"previous_heater_dc": 17, "time_series_of_temp": 30 * [32.0]} with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert abs(32.0 - t.approximate_temperature(features)) < 0.01
def test_temperature_approximation_if_dc_is_nil() -> None: features = { "previous_heater_dc": 0, "time_series_of_temp": [37.8125, 32.1875] } with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert t.approximate_temperature(features) == 32.1875
def test_setting_pid_control_after_startup_will_start_some_heating() -> None: # this test tries to replicate what a user does in the UI with temperature_control.TemperatureController("stable", unit=unit, experiment=experiment, target_temperature=35) as t: pause(3) assert t.automation_job.state == "ready" assert t.heater_duty_cycle > 0
def test_constant_duty_cycle_init() -> None: pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/temperature", None, retain=True, ) dc = 50 with temperature_control.TemperatureController("constant_duty_cycle", unit=unit, experiment=experiment, duty_cycle=dc) as algo: pause() assert algo.heater_duty_cycle == 50
def test_child_cant_update_heater_when_locked() -> None: with temperature_control.TemperatureController( "silent", unit=unit, experiment=experiment, eval_and_publish_immediately=False) as t: assert t.automation_job.update_heater(50) with t.pwm.lock_temporarily(): assert not t.automation_job.update_heater(50) assert not t.update_heater(50) assert t.automation_job.update_heater(50)
def test_heating_is_reduced_when_set_temp_is_exceeded() -> None: with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: t.tmp_driver.get_temperature = lambda *args: t.MAX_TEMP_TO_REDUCE_HEATING + 0.1 pause() t._update_heater(50) pause() assert t.heater_duty_cycle == 50 pause() t.read_external_temperature() pause() assert 0 < t.heater_duty_cycle < 50
def test_heating_stops_when_max_temp_is_exceeded() -> None: with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: # monkey path the driver t.tmp_driver.get_temperature = lambda *args: t.MAX_TEMP_TO_DISABLE_HEATING + 0.1 t._update_heater(50) assert t.heater_duty_cycle == 50 pause() t.read_external_temperature() pause() assert t.heater_duty_cycle == 0
def test_temperature_approximation_even_if_very_large_heat_source() -> None: import numpy as np features = { "previous_heater_dc": 14.5, "time_series_of_temp": list(22 + 3 * np.exp(-0.008 * np.arange(0, 17)) + 20 * np.exp(-0.28 * np.arange(0, 17))), } with temperature_control.TemperatureController("silent", unit=unit, experiment=experiment) as t: assert (24 * np.exp(-0.008 * 17)) < t.approximate_temperature(features) < 25
def test_changing_temperature_algo_over_mqtt() -> None: pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/temperature", None, retain=True, ) with temperature_control.TemperatureController( "silent", unit=unit, experiment=experiment) as algo: assert algo.automation_name == "silent" assert isinstance(algo.automation_job, Silent) pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_control/automation/set", '{"automation_name": "stable", "target_temperature": 20}', ) time.sleep(8) assert algo.automation_name == "stable" assert isinstance(algo.automation_job, Stable) assert algo.automation_job.target_temperature == 20
def test_temperature_control_and_stables_relationship(): with temperature_control.TemperatureController( "stable", unit=unit, experiment=experiment, target_temperature=30) as tc: tc.publish_temperature_timer.pause( ) # pause this for now. we will manually run evaluate_and_publish_temperature pause() pause() assert tc.heater_duty_cycle > 0 initial_dc = tc.heater_duty_cycle stable_automation = tc.automation_job # suppose we want to update target_temperature... stable_automation.set_target_temperature(35) pause() # should have changed the dc immediately. assert tc.heater_duty_cycle != initial_dc assert tc.heater_duty_cycle > 0 pause() # run evaluate_and_publish_temperature, this locks the PWM from anyone updating it directly. thread = threading.Thread(target=tc.evaluate_and_publish_temperature, daemon=True) thread.start() pause() assert tc.heater_duty_cycle == 0 pause() # suppose we want to update target_temperature... stable_automation.set_target_temperature(40) pause() # should still be 0! assert tc.heater_duty_cycle == 0 pause() thread.join()
def test_stable_automation() -> None: with temperature_control.TemperatureController( "stable", target_temperature=50, unit=unit, experiment=experiment) as algo: pause(2) # 55 is too high - clamps to 50 pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_automation/target_temperature/set", 55, ) pause(2) assert algo.automation_job.target_temperature == 50 pubsub.publish( f"pioreactor/{unit}/{experiment}/temperature_automation/target_temperature/set", 35, ) pause(2) assert algo.automation_job.target_temperature == 35