Skip to content

Commit 614a4ab

Browse files
committed
discrete event simulation
1 parent 197b405 commit 614a4ab

17 files changed

+385
-1
lines changed

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ greenlet
33
kaleido
44
pandas
55
plotly
6+
prettytable
7+
py-des-lib
68
pydot
79
pyfakefs
810
pytest

src/eventsim/Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
TARGETS=\
2+
multiprocess.out \
3+
pc_stats.out \
4+
pc_stats_multi.out \
5+
wait_and_work.out \
6+
wait_event.out \
7+
wait_work_record.out
8+
9+
include ../../examples.mk
10+
11+
pc_stats.out: pc_stats.py
12+
python $< 4219384 1000 10 > $@
13+
14+
pc_stats_multi.out: pc_stats_multi.py
15+
python $< 4219384 1000 10 3 > $@

src/eventsim/index.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
---
2-
title: "Discrete Event Simulator"
2+
title: "Discrete Event Simulation"
33
abstract: >
44
FIXME
55
syllabus:
66
- FIXME
77
---
88

99
[%fixme "create discrete event simulator" %] [%issue 56 %]
10+
11+
- Basic ideas of discrete event simulation
12+
- `wait_and_work.py`: work a fixed time, wait a fixed time
13+
- `wait_work_record.py`: record events and display table of results
14+
- `multiprocess.py`: two independent processes
15+
- `wait_event.py`: one waits for the other to finish
16+
- But events happen once
17+
- `producer_consumer.py`: eventually producer can only run as fast as consumer

src/eventsim/multiprocess.out

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| time | component | message |
2+
|----: | :---------| :----------|
3+
| 0 | Worker.0 | start work |
4+
| 0 | Worker.1 | start work |
5+
| 2 | Worker.0 | start work |
6+
| 2 | Worker.1 | start work |
7+
| 4 | Worker.0 | start work |
8+
| 4 | Worker.1 | start work |
9+
| 6 | Worker.0 | finished |
10+
| 6 | Worker.1 | finished |

src/eventsim/multiprocess.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from pydes import Component, Simulator
2+
from util import format_log
3+
4+
5+
class Worker(Component):
6+
NUM_STEPS = 3
7+
WORK_TIME = 2
8+
9+
def __init__(self, sim):
10+
self._sim = sim
11+
12+
def main(self):
13+
for i in range(self.NUM_STEPS):
14+
self._sim.record(self, "start work")
15+
self._sim.sleep(self.WORK_TIME)
16+
self._sim.record(self, "finished")
17+
18+
19+
def simulate():
20+
sim = Simulator(trace=False)
21+
sim.schedule(Worker(sim))
22+
sim.schedule(Worker(sim))
23+
sim.run()
24+
print(format_log(sim.records()))
25+
26+
27+
if __name__ == "__main__":
28+
simulate()

src/eventsim/pc_stats.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
produce 5.73 consume 5.75

src/eventsim/pc_stats.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from pydes import Component, Queue, Simulator
2+
import random
3+
import sys
4+
5+
6+
class Producer(Component):
7+
WORK_TIME = (1, 10)
8+
9+
def __init__(self, sim, queue, num_items):
10+
self._sim = sim
11+
self._queue = queue
12+
self._num_items = num_items
13+
14+
def main(self):
15+
for i in range(self._num_items):
16+
delay = random.randint(*self.WORK_TIME)
17+
self._sim.sleep(delay)
18+
self._sim.record(self, "produce")
19+
self._queue.put(i)
20+
21+
22+
class Consumer(Component):
23+
WORK_TIME = (1, 10)
24+
25+
def __init__(self, sim, queue, num_items):
26+
self._sim = sim
27+
self._queue = queue
28+
self._num_items = num_items
29+
30+
def main(self):
31+
for i in range(self._num_items):
32+
self._queue.get()
33+
self._sim.record(self, "consume")
34+
delay = random.randint(*self.WORK_TIME)
35+
self._sim.sleep(delay)
36+
37+
38+
def simulate(num_items, queue_size):
39+
sim = Simulator(trace=False)
40+
queue = Queue(sim, queue_size)
41+
sim.schedule(Producer(sim, queue, num_items))
42+
sim.schedule(Consumer(sim, queue, num_items))
43+
sim.run()
44+
45+
records = sim.records()
46+
produced = mean_diff(records, "produce")
47+
consumed = mean_diff(records, "consume")
48+
print(f"produce {produced:.2f} consume {consumed:.2f}")
49+
50+
51+
def mean_diff(records, which):
52+
subset = [r.time for r in records if r.value == which]
53+
diffs = [subset[i] - subset[i-1] for i in range(1, len(subset))]
54+
return sum(diffs) / len(diffs)
55+
56+
57+
if __name__ == "__main__":
58+
random.seed(int(sys.argv[1]))
59+
num_items = int(sys.argv[2])
60+
queue_size = int(sys.argv[3])
61+
simulate(num_items, queue_size)

src/eventsim/pc_stats_multi.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
produce 16.29 consume 5.46

src/eventsim/pc_stats_multi.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from pydes import Component, Queue, Simulator
2+
import random
3+
import sys
4+
5+
6+
class Lag:
7+
def __init__(self, sim, owner, which):
8+
self._sim = sim
9+
self._owner = owner
10+
self._which = which
11+
self._previous = None
12+
13+
def update(self):
14+
current = self._sim.now()
15+
if self._previous is None:
16+
self._previous = current
17+
else:
18+
self._sim.record(self._owner, current - self._previous, self._which)
19+
self._previous = current
20+
21+
22+
class Producer(Component):
23+
WORK_TIME = (1, 10)
24+
25+
def __init__(self, sim, queue, num_items):
26+
self._sim = sim
27+
self._queue = queue
28+
self._num_items = num_items
29+
self._lag = Lag(sim, self, "produce")
30+
31+
def main(self):
32+
current = None
33+
for i in range(self._num_items):
34+
delay = random.randint(*self.WORK_TIME)
35+
self._sim.sleep(delay)
36+
self._lag.update()
37+
self._queue.put(i)
38+
39+
40+
class Consumer(Component):
41+
WORK_TIME = (1, 10)
42+
43+
def __init__(self, sim, queue, num_items):
44+
self._sim = sim
45+
self._queue = queue
46+
self._num_items = num_items
47+
self._lag = Lag(sim, self, "consume")
48+
49+
def main(self):
50+
current = None
51+
for i in range(self._num_items):
52+
self._queue.get()
53+
self._lag.update()
54+
delay = random.randint(*self.WORK_TIME)
55+
self._sim.sleep(delay)
56+
57+
58+
def simulate(num_items, queue_size, num_producers):
59+
sim = Simulator(trace=False)
60+
queue = Queue(sim, queue_size)
61+
for i in range(num_producers):
62+
sim.schedule(Producer(sim, queue, num_items))
63+
sim.schedule(Consumer(sim, queue, num_items * num_producers))
64+
sim.run()
65+
66+
records = sim.records()
67+
produced = mean_diff(records, "produce")
68+
consumed = mean_diff(records, "consume")
69+
print(f"produce {produced:.2f} consume {consumed:.2f}")
70+
71+
72+
def mean_diff(records, which):
73+
diffs = [r.value for r in records if r.description == which]
74+
return sum(diffs) / len(diffs)
75+
76+
77+
if __name__ == "__main__":
78+
random.seed(int(sys.argv[1]))
79+
num_items = int(sys.argv[2])
80+
queue_size = int(sys.argv[3])
81+
num_producers = int(sys.argv[4])
82+
simulate(num_items, queue_size, num_producers)

src/eventsim/producer_consumer.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from pydes import Component, Queue, Simulator
2+
from util import format_log
3+
4+
5+
NUM_ITEMS = 10
6+
QUEUE_SIZE = 3
7+
8+
9+
class Producer(Component):
10+
WORK_TIME = 2
11+
12+
def __init__(self, sim, queue):
13+
self._sim = sim
14+
self._queue = queue
15+
16+
def main(self):
17+
for i in range(NUM_ITEMS):
18+
self._sim.sleep(self.WORK_TIME)
19+
self._sim.record(self, f"produce={i} queue@{self._queue.size()}")
20+
self._queue.put(i)
21+
22+
23+
class Consumer(Component):
24+
WORK_TIME = 4
25+
26+
def __init__(self, sim, queue):
27+
self._sim = sim
28+
self._queue = queue
29+
30+
def main(self):
31+
for i in range(NUM_ITEMS):
32+
value = self._queue.get()
33+
self._sim.record(self, f"consume={i}")
34+
self._sim.sleep(self.WORK_TIME)
35+
36+
37+
def simulate():
38+
sim = Simulator(trace=False)
39+
queue = Queue(sim, QUEUE_SIZE)
40+
sim.schedule(Producer(sim, queue))
41+
sim.schedule(Consumer(sim, queue))
42+
sim.run()
43+
print(format_log(sim.records()))
44+
45+
46+
if __name__ == "__main__":
47+
simulate()

src/eventsim/util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from prettytable import MARKDOWN, PrettyTable
2+
3+
4+
FIELDS = {
5+
"time": "r",
6+
"component": "l",
7+
"message": "l",
8+
}
9+
10+
11+
def format_log(records):
12+
table = PrettyTable(field_names=FIELDS.keys())
13+
table.set_style(MARKDOWN)
14+
for field, align in FIELDS.items():
15+
table.align[field] = align
16+
for rec in records:
17+
table.add_row([rec.time, str(rec.component), rec.value])
18+
return table

src/eventsim/wait_and_work.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
start to work at 0
2+
start to work at 2
3+
start to work at 4
4+
finished working at 6

src/eventsim/wait_and_work.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from pydes import Component, Simulator
2+
3+
4+
class Worker(Component):
5+
NUM_STEPS = 3
6+
WORK_TIME = 2
7+
8+
def __init__(self, sim):
9+
self._sim = sim
10+
11+
def main(self):
12+
for i in range(self.NUM_STEPS):
13+
print(f"start to work at {self._sim.now()}")
14+
self._sim.sleep(self.WORK_TIME)
15+
print(f"finished working at {self._sim.now()}")
16+
17+
18+
def simulate():
19+
sim = Simulator()
20+
worker = Worker(sim)
21+
sim.schedule(worker)
22+
sim.run()
23+
24+
25+
if __name__ == "__main__":
26+
simulate()

src/eventsim/wait_event.out

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
| time | component | message |
2+
|----: | :---------| :----------|
3+
| 0 | Worker.0 | start work |
4+
| 0 | Worker.1 | start work |
5+
| 2 | Worker.1 | start work |
6+
| 4 | Worker.1 | start work |
7+
| 6 | Worker.1 | finished |
8+
| 6 | Worker.0 | start work |
9+
| 8 | Worker.0 | start work |
10+
| 10 | Worker.0 | start work |
11+
| 12 | Worker.0 | finished |

src/eventsim/wait_event.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from pydes import Component, Event, Simulator
2+
from util import format_log
3+
4+
5+
class Worker(Component):
6+
NUM_STEPS = 3
7+
WORK_TIME = 2
8+
9+
def __init__(self, sim, event, wait_at_start):
10+
self._sim = sim
11+
self._event = event
12+
self._wait_at_start = wait_at_start
13+
14+
def main(self):
15+
if self._wait_at_start:
16+
self._sim.record(self, "start work")
17+
self._event.wait()
18+
for i in range(self.NUM_STEPS):
19+
self._sim.record(self, "start work")
20+
self._sim.sleep(self.WORK_TIME)
21+
self._sim.record(self, "finished")
22+
if not self._wait_at_start:
23+
self._event.set()
24+
25+
26+
def simulate():
27+
sim = Simulator(trace=False)
28+
event = Event(sim)
29+
sim.schedule(Worker(sim, event, True))
30+
sim.schedule(Worker(sim, event, False))
31+
sim.run()
32+
print(format_log(sim.records()))
33+
34+
35+
if __name__ == "__main__":
36+
simulate()

src/eventsim/wait_work_record.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| time | component | message |
2+
|----: | :---------| :----------|
3+
| 0 | Worker.0 | start work |
4+
| 2 | Worker.0 | start work |
5+
| 4 | Worker.0 | start work |
6+
| 6 | Worker.0 | finished |

0 commit comments

Comments
 (0)