Thermostat Simulation

A temperature control system demonstrating balancing feedback loops and time delays in a heating system.

Level:Beginner

controltemperaturefeedbackdelay

  • 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

Feedback Loops

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

Explore Feedback Loops
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 {
        "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