Thermostat Simulation
A thermostat simulation showing balancing feedback and time delays.
Level: Beginner
Delays
Learn about delays in systems, how they create oscillations, and their impact on system behavior and decision-making.
Take the QuizFeedback Loops
Understand the balancing and reinforcing feedback loops that drive system behavior and create complex dynamics in systems thinking.
Take the Quizsimulation.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 {
"external": ["simpy==4.1.1"],
}
Default.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
Charts (Default)
Final Results (Default)
Metric | Value |
---|---|
final_temp | 18.83 |
overshoot | -2.17 |
FAQ
- Why does the system overshoot the setpoint?
- The heater uses a delayed sensor reading, so decisions lag behind the true temperature and overshoot occurs.
- How is the heater turned on or off?
- The controller compares the sensed error against gain * band and toggles the heater accordingly.
- What does sensor_delay represent?
- It is the number of ticks between the actual temperature and the value the controller reads.