Discrete Event Simulation

Terms defined: dataclass, exponential random distribution, generator, log-normal random distribution, median, parameter sweeping, serialize

Our First Simulation

from simpy import Environment

T_SIM = 30
T_WAIT = 8


def coder(env):
    while True:
        print(f"{env.now}: Is there any work?")
        yield env.timeout(T_WAIT)


if __name__ == "__main__":
    env = Environment()
    env.process(coder(env))
    env.run(until=T_SIM)
0: Is there any work?
8: Is there any work?
16: Is there any work?
24: Is there any work?

Interaction

T_CREATE = 6
T_JOB = 8
T_SIM = 20
from itertools import count

class Job:
    _next_id = count()

    def __init__(self):
        self.id = next(Job._next_id)
        self.duration = T_JOB

    def __str__(self):
        return f"job-{self.id}"
def manager(env, queue):
    while True:
        job = Job()
        print(f"manager creates {job} at {env.now}")
        yield queue.put(job)
        yield env.timeout(T_CREATE)
def coder(env, queue):
    while True:
        print(f"coder waits at {env.now}")
        job = yield queue.get()
        print(f"coder gets {job} at {env.now}")
        yield env.timeout(job.duration)
        print(f"code completes {job} at {env.now}")
if __name__ == "__main__":
    env = Environment()
    queue = Store(env)
    env.process(manager(env, queue))
    env.process(coder(env, queue))
    env.run(until=T_SIM)
manager creates job-0 at 0
coder waits at 0
coder gets job-0 at 0
manager creates job-1 at 6
code completes job-0 at 8
coder waits at 8
coder gets job-1 at 8
manager creates job-2 at 12
code completes job-1 at 16
coder waits at 16
coder gets job-2 at 16
manager creates job-3 at 18
manager creates job-0 at 0
manager creates job-1 at 6
manager creates job-2 at 12
manager creates job-3 at 18
coder waits at 0
coder gets job-0 at 0
coder waits at 8
coder gets job-1 at 8
coder waits at 16
coder gets job-2 at 16

Uniform Rates

RNG_SEED = 98765
T_CREATE = (6, 10)
T_JOB = (8, 12)
T_SIM = 20
class Job:
    def __init__(self):
        self.id = next(Job._next_id)
        self.duration = random.uniform(*T_JOB)
def manager(env, queue):
    while True:
        job = Job()
        print(f"manager creates {job} at {env.now:.2f}")
        yield queue.put(job)
        yield env.timeout(random.uniform(*T_CREATE))
if __name__ == "__main__":
    random.seed(RNG_SEED)
    # …as before…
manager creates job-0 at 0.00
manager creates job-1 at 8.36
manager creates job-2 at 14.73
coder waits at 0.00
coder gets job-0 at 0.00
coder waits at 8.52
coder gets job-1 at 8.52

Better Random Distributions

exponential distribution
log-normal distribution

Better Random Interaction

other parameters as before
T_JOB_ARRIVAL = 2.0
T_JOB_MEAN = 0.5
T_JOB_STD = 0.6

def rand_job_arrival():
    return random.expovariate(1.0 / T_JOB_ARRIVAL)

def rand_job_duration():
    return random.lognormvariate(T_JOB_MEAN, T_JOB_STD)
class Job:
    def __init__(self):
        self.id = next(Job._next_id)
        self.duration = rand_job_duration()

def manager(env, queue):
    while True:
        job = Job()
        t_delay = rand_job_arrival()
        print(f"manager creates {job} at {env.now:.2f} waits for {t_delay:.2f}")
        yield queue.put(job)
        yield env.timeout(t_delay)
manager creates job-0 at 0.00 waits for 7.96
manager creates job-1 at 7.96 waits for 0.60
manager creates job-2 at 8.56 waits for 3.70
coder waits at 0.00
coder gets job-0 at 0.00
coder waits at 0.65
coder gets job-1 at 7.96
coder waits at 8.75
coder gets job-2 at 8.75

Introduce Structure

@dataclass_json
@dataclass
class Params:
    """Simulation parameters."""

    n_seed: int = 13579
    t_sim: float = 30
    t_wait: float = 8
class Simulation(Environment):
    """Complete simulation."""

    def __init__(self):
        super().__init__()
        self.params = Params()
        self.log = []

    def result(self):
        return {"log": self.log}
def coder(sim):
    """Simulate a single coder."""

    while True:
        sim.log.append(f"{sim.now}: Is there any work?")
        yield sim.timeout(sim.params.t_wait)
class Simulation
    def simulate(self):
        self.process(coder(self))
        self.run(until=self.params.t_sim)
if __name__ == "__main__":
    args, results = util.run(Params, Simulation)
    if args.json:
        json.dump(results, sys.stdout, indent=2)
    else:
        results = util.as_frames(results)
        for key, frame in results.items():
            print(f"## {key}")
            print(frame)
python introduce_structure.py --json t_wait=12,20 t_sim=20,30
{
  "results": [
    {
      "params": {"n_seed": 13579, "t_sim": 20, "t_wait": 12},
      "log": [
        {"time": 0, "message": "loop 0"},
        {"time": 12, "message": "loop 1"}
      ]
    },
    {
      "params": {"n_seed": 13579, "t_sim": 30, "t_wait": 12},
      "log": [
        {"time": 0, "message": "loop 0"},
        {"time": 12, "message": "loop 1"},
        {"time": 24, "message": "loop 2"}
      ]
    },
    {
      "params": {"n_seed": 13579, "t_sim": 20, "t_wait": 20},
      "log": [
        {"time": 0, "message": "loop 0"}
      ]
    },
    {
      "params": {"n_seed": 13579, "t_sim": 30, "t_wait": 20},
      "log": [
        {"time": 0, "message": "loop 0"},
        {"time": 20, "message": "loop 1"}
      ]
    }
  ]
}
python introduce_structure.py --tables t_wait=12,20 t_sim=20,30
## log
shape: (8, 5)
## log
shape: (8, 6)
┌──────┬─────────┬─────┬────────┬───────┬────────┐
│ time ┆ message ┆ id  ┆ n_seed ┆ t_sim ┆ t_wait │
│ ---  ┆ ---     ┆ --- ┆ ---    ┆ ---   ┆ ---    │
│ i64  ┆ str     ┆ i32 ┆ i32    ┆ i32   ┆ i32    │
╞══════╪═════════╪═════╪════════╪═══════╪════════╡
│ 0    ┆ loop 0  ┆ 0   ┆ 13579  ┆ 20    ┆ 12     │
│ 12   ┆ loop 1  ┆ 0   ┆ 13579  ┆ 20    ┆ 12     │
│ 0    ┆ loop 0  ┆ 1   ┆ 13579  ┆ 30    ┆ 12     │
│ 12   ┆ loop 1  ┆ 1   ┆ 13579  ┆ 30    ┆ 12     │
│ 24   ┆ loop 2  ┆ 1   ┆ 13579  ┆ 30    ┆ 12     │
│ 0    ┆ loop 0  ┆ 2   ┆ 13579  ┆ 20    ┆ 20     │
│ 0    ┆ loop 0  ┆ 3   ┆ 13579  ┆ 30    ┆ 20     │
│ 20   ┆ loop 1  ┆ 3   ┆ 13579  ┆ 30    ┆ 20     │
└──────┴─────────┴─────┴────────┴───────┴────────┘