Thermostat Simulation

A thermostat simulation showing balancing feedback and time delays.

Level: Beginner

controltime-delaybalancing-loop

  • Stocks: indoor_temp
  • Flows: heat_gain, heat_loss
  • Feedback Loops: controller chasing set-point (balancing), high gain overshoot (reinforcing)
  • Probes: indoor_temp, heater_on

Delays

Learn about delays in systems, how they create oscillations, and their impact on system behavior and decision-making.

Explore Delays | Take the Quiz

Feedback Loops

Understand the balancing and reinforcing feedback loops that drive system behavior and create complex dynamics in systems thinking.

Explore Feedback Loops | Take the Quiz

simulation.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)

indoor_temp

indoor_temp chartCSV
Samples241 @ 0.00–120.00
Valuesmin 18.00, mean 19.66, median 19.78, max 20.56, σ 0.68

heater_on

heater_on chartCSV
Samples241 @ 0.00–120.00
Valuesmin 0.00, mean 0.92, median 1.00, max 1.00, σ 0.27
Final Results (Default)
MetricValue
final_temp18.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.