Sleep Debt Simulation

Models sleep patterns, caffeine intake, and sleep debt accumulation over time with reinforcing and balancing feedback.

Level:Intermediate

sleepcaffeinedebtenergycircadian

  • Stocks:sleep_debt_hours
  • Feedback Loops:coffee reduces sleep (reinforcing), circadian pressure triggers sleep (balancing)
  • Probes:sleep_debt, subjective_energy

Dynamic Behavior Patterns

Explore common system behaviors: exponential growth, goal-seeking decay, overshoot-and-collapse, and S-curve saturation.

Explore Dynamic Behavior Patterns
simulation.py

Sleep debt and the lure of caffeine

This simulation keeps tabs on lost sleep while caffeine tries to hide the fatigue. It's a fun way to explore habits and half-life effects.


import random
from tys import probe, progress

Simulate how sleep debt accumulates and caffeine masks fatigue.

def simulate(cfg: dict):

    import simpy
    env = simpy.Environment()

Parameters

    sleep_debt_hours = cfg["initial_debt"]    # current sleep debt (h)
    days            = cfg["sim_days"]        # simulation length (d)
    sleep_need      = cfg["sleep_need"]      # ideal nightly sleep (h)
    coffee_hit  = cfg["coffee_mg"]           # mg caffeine per cup
    half_life   = cfg["half_life"]           # caffeine half-life (h)
    cups_per_3h     = cfg["cups_per_3h"]     # cups every 3h when sleepy
    drink_threshold = cfg["debt_to_drink"]   # start coffee above this debt
    nap_repay       = cfg["nap_repay"]       # hours debt repaid by 20-min nap
    weekend_nap     = cfg["weekend_nap"]     # take Saturday nap? (bool)

    caffeine_mg = 0.0                        # mg in bloodstream
    rng         = random.Random(cfg["seed"])
    hours       = 24 * days
    done        = env.event()

Record current sleep debt and perceived energy.

    def recorder():
        while True:
            energy = max(0, 1 - sleep_debt_hours/10) + caffeine_mg/400
            probe("sleep_debt", env.now,     sleep_debt_hours)
            probe("energy_level", env.now,   energy)
            yield env.timeout(1)
    env.process(recorder())

Simulate caffeine intake and nightly sleep.

    def cycle():
        nonlocal sleep_debt_hours, caffeine_mg
        for h in range(int(hours)):
            day = h // 24
            hour_in_day = h % 24

            caffeine_mg *= 0.5 ** (1/half_life)

            if 7 <= hour_in_day < 23:
                if sleep_debt_hours > drink_threshold and rng.random() < cups_per_3h:  # sleepy binge
                    caffeine_mg += coffee_hit
                    progress(int(100*h/hours),
                             f"☕ {day=}, hour {hour_in_day}: another coffee")

            if hour_in_day == 23:
                sleep_loss = min(4, caffeine_mg/200)          # jittery!
                sleep_hours = max(4, sleep_need - sleep_debt_hours - sleep_loss)   # can't always repay
                sleep_debt_hours = max(0, sleep_debt_hours - sleep_hours + sleep_need)  # debt update

                if weekend_nap and hour_in_day == 23 and day % 7 == 5:
                    sleep_debt_hours = max(0, sleep_debt_hours - nap_repay)
                    progress(int(100*h/hours),
                             f"🛌 Weekend nap repaid {nap_repay:.1f}h debt")

                if sleep_debt_hours > 20:
                    progress(int(100*h/hours),
                             "⚠︎ Burnout spiral — consider an intervention!")

            yield env.timeout(1)

        progress(100)
        done.succeed({"final_debt": sleep_debt_hours, "avg_caffeine": caffeine_mg / hours})

    env.process(cycle())
    env.run(until=done)
    return done.value


def requirements():
    return {
        "builtin": ["micropip", "pyyaml"],
        "external": ["simpy==4.1.1"],
    }
config.yaml
initial_debt: 6
sim_days: 30
sleep_need: 8
coffee_mg: 100
half_life: 5
cups_per_3h: 0.3
debt_to_drink: 5
nap_repay: 1.5
weekend_nap: true
seed: 42