Thermostat Simulation
A temperature control system demonstrating balancing feedback loops and time delays in a heating system.
Level:Beginner
Delays
Learn about delays in systems, how they create oscillations, and their impact on system behavior and decision-making.
Explore DelaysFeedback Loops
Understand the balancing and reinforcing feedback loops that drive system behavior and create complex dynamics in systems thinking.
Explore Feedback Loopssimulation.py
Thermostat control with delayed sensing
Here a basic on/off controller tries to keep the room comfortable, but the temperature sensor lags behind reality. That delay causes overshoot and a gentle oscillation around the setpoint.
from tys import probe, progress
Simulate a thermostat with delayed temperature sensing.
def simulate(cfg: dict):
import simpy
env = simpy.Environment()
Parameters
indoor_temp = cfg["initial_temp"] # indoor temp (°C)
outside_temp = cfg["outside_temp"] # fixed outside temp
setpoint = cfg["setpoint"] # desired indoor temp
band = cfg["comfort_band"] # ±°C accepted dead-band
loss_rate = cfg["loss_coeff"] # heat-loss constant
heater_kw = cfg["heater_power"] # °C per tick when on
control_gain = cfg["gain"] # controller aggressiveness
sensor_delay = cfg["sensor_delay"] # perception delay (ticks)
sim_time = cfg["sim_time"]
sensor_history = [indoor_temp] * sensor_delay # delay line for sensed temp
heater_on = False
Recorder Log the indoor temperature and heater state.
def recorder():
while True:
probe("indoor_temp", env.now, indoor_temp)
probe("heater_on", env.now, 1 if heater_on else 0)
yield env.timeout(0.5)
env.process(recorder())
done = env.event()
Dynamics Update temperature and controller every tick.
def dynamics():
nonlocal indoor_temp, heater_on
for t in range(sim_time):
sensor_T = sensor_history[0]
error = setpoint - sensor_T
heater_on = error > control_gain * band # bang-bang with gain
heat_gain = heater_kw if heater_on else 0
heat_loss = loss_rate * (indoor_temp - outside_temp)
indoor_temp += heat_gain - heat_loss
sensor_history.append(indoor_temp)
sensor_history.pop(0)
if abs(error) < band / 2 and t % 5 == 0:
progress(int(100 * t / sim_time), "😌 In comfort zone")
if abs(error) > 3 * band and heater_on:
progress(int(100 * t / sim_time), "⚠︎ Big overshoot, heater still ON")
yield env.timeout(1)
progress(100)
done.succeed({
"final_temp": indoor_temp,
"overshoot": max(sensor_history) - setpoint,
})
env.process(dynamics())
env.run(until=done)
return done.value
def requirements():
return {
"builtin": ["micropip", "pyyaml"],
"external": ["simpy==4.1.1"],
}
config.yaml
initial_temp: 18
outside_temp: 5
setpoint: 21
comfort_band: 0.5
loss_coeff: 0.05
heater_power: 0.8
gain: 1.0
sensor_delay: 3
sim_time: 120