Help setting up simulation

158 views
Skip to first unread message

Chris Farr

unread,
Jul 10, 2020, 7:33:23 PM7/10/20
to python-simpy
Looking to simulate how many jobs can be completed by a manufacturing process. A job requires both a tool and a worker (a person) to be completed. There are a fixed number of tools and workers. A tool can undergo planned maintenance and will be unavailable during that time. Each tool has their own maintenance schedule. Workers take breaks during the day (e.g. a lunch break) and will also be unavailable for work during that time.

Can someone point me in the right direction to set this up in SimPy? I did not see any examples on how to set up schedules when resources would be unavailable. Also, didn’t see examples of a two-resource requirement (tool and worker).

Thanks in advance for any help.

Chris

Dale Frakes

unread,
Jul 10, 2020, 7:45:53 PM7/10/20
to python...@googlegroups.com
Chris,

I'm fairly new to Simpy myself and struggled with how to impose a
schedule on a resource (because it's not clear from any of the
documentation).  My hacky work-around was to use a priority queue for
the items to be worked on, then add scheduled higher-priority "downtime"
items that tie up the resource until it's scheduled re-start time.  So
if the process should be "down" at 6PM, I add "downtime" items to the
queue for every 6PM the simulation runs.

This can fail if your normal process time takes a while... let's say the
tool and worker start a new job at 5:59PM and it takes about an hour to
do, then you'll end up with them working until almost 7PM. So if your
machine doesn't go down at precisely 6PM, you have to calculate the
difference in time between when the "downtime order" is taken and when
you want the process to resume.

I hope there's a better way because this doesn't scale well and isn't
super reliable.  It also doesn't work if you're not relying on a queue
to determine the jobs to be done.

Dale
--
Dale Frakes
Adjunct Instructor, PhD Candidate
PSU Systems Science
dfr...@pdx.edu - http://web.pdx.edu/~dfrakes/

Chris Farr

unread,
Jul 10, 2020, 10:03:27 PM7/10/20
to python-simpy
Hi Dale,

Thanks much for your response! I had thought of treating planned maintenance as just another job, as you outline, but wasn’t sure how to assign the maintenance “job” to a specific tool (or instance of a resource I guess in SimPy-speak).

Do you know if you can model the fact that each job must have a free tool AND worker? We’d like to model out different tool/worker combinations to better understand our throughput. And, possibly modeling out the productivity of individual workers as well, so important to have this notion of two resources.

Lastly, you mentioned a prioritized queue. Is that something you did custom?

Thanks again.

Chris

Dale Frakes

unread,
Jul 10, 2020, 11:33:39 PM7/10/20
to python...@googlegroups.com
Hi Chris,

I got my example of how to do a priority queue from the repairman in the sample code found here: https://simpy.readthedocs.io/en/latest/examples/machine_shop.html. They call it a "preemptive resource".

I haven't figured out how to do something like pairing a worker and tool.  I think you'd have to pick one to be the resource, and then the other as maybe a store.  E.g... the machine is a resource and gets a worker from the store; or the worker is the resource and gets a machine from the store.  I haven't done this, so I don't know how to make that work yet.  For example, if the machine comes from the store, I'm not sure how to put it back when the work is done.  

There could also be something useful in the gas station example: https://simpy.readthedocs.io/en/latest/examples/gas_station_refuel.html

And I say that because it looks like a car arrives to be serviced and it needs both a pump and that the station has enough fuel.  Otherwise it waits until a fuel truck comes and refills the stations.  It's not clear in the model, but I think there are 2 pumps... but I don't see yet now to tell that.

Dale

Chris Farr

unread,
Jul 11, 2020, 10:05:17 AM7/11/20
to python-simpy
Hi Dale,

Thanks again. Will think more about your suggestions. I'm starting to think that SimPy cannot handle my simulation without some type of extension to its core capabilities. Which surprises me, as I thought my simulation was pretty basic. It could also be that I'm a newbie at SimPy I guess ...

Anyway, will keep thinking about this. Thanks for your help.

Chris

Timothy J. Miller

unread,
Jul 11, 2020, 4:39:36 PM7/11/20
to python...@googlegroups.com

Chris Farr writes:

> Hi Dale,
>
> Thanks again. Will think more about your suggestions. I'm starting to think
> that SimPy cannot handle my simulation without some type of extension to
> its core capabilities. Which surprises me, as I thought my simulation was
> pretty basic. It could also be that I'm a newbie at SimPy I guess ...

What's wrong with a semaphore and a queue?

```
from collections import namedtuple
import random

import simpy


env = simpy.Environment()

# Resouce is a semaphore, has capacity
toolshed = simpy.Resource(env, capacity=3)

Job = namedtuple("Job", ["size"])

# Stores are queeus
job_queue = simpy.Store(env)

def job_producer(env, queue):
"""produces random size jobs and puts them in the queue"""
while True:
yield env.timeout(1)
job = Job(random.randint(100,500))
yield queue.put(job)
print(f"{env.now} : {job}")

def worker_process(env, work_rate=100, id=1):
"""consumes a job and a tool to do work"""
while True:
print(f"{env.now} : worker {id} waiting on job")
job = yield job_queue.get()
print(f"{env.now} : worker {id} got job")
print(f"{env.now} : worker {id} waiting for tool")
# set the event, then yield, so it can be released later
tool = toolshed.request()
yield tool
print(f"{env.now} : worker {id} got tool")
# do the work
yield env.timeout(job.size / work_rate)
print(f"{env.now} : worker {id} done")
# release the semaphore
toolshed.release(tool)
print(f"{env.now} : worker {id} releasing tool")

# spawn the processes
producer = env.process(job_producer(env, job_queue))
workers = [env.process(worker_process(env, 100, i)) for i in range(10)]

# run the sim
env.run(until=50)
```

Adding a maintenance cycle would be simple with an interrupt in
worker_process, with a try:except: that pauses until the cycle is
complete, then starts a new job timeout, either for the full job or the
remaining time.

Sample output follows:

```
0 : worker 0 waiting on job
0 : worker 1 waiting on job
0 : worker 2 waiting on job
0 : worker 3 waiting on job
0 : worker 4 waiting on job
0 : worker 5 waiting on job
0 : worker 6 waiting on job
0 : worker 7 waiting on job
0 : worker 8 waiting on job
0 : worker 9 waiting on job
1 : Job(size=364)
1 : worker 0 got job
1 : worker 0 waiting for tool
1 : worker 0 got tool
2 : Job(size=423)
2 : worker 1 got job
2 : worker 1 waiting for tool
2 : worker 1 got tool
3 : Job(size=190)
3 : worker 2 got job
3 : worker 2 waiting for tool
3 : worker 2 got tool
4 : Job(size=399)
4 : worker 3 got job
4 : worker 3 waiting for tool
4.640000000000001 : worker 0 done
4.640000000000001 : worker 0 releasing tool
4.640000000000001 : worker 0 waiting on job
4.640000000000001 : worker 3 got tool
4.9 : worker 2 done
4.9 : worker 2 releasing tool
4.9 : worker 2 waiting on job
5 : Job(size=217)
5 : worker 4 got job
5 : worker 4 waiting for tool
5 : worker 4 got tool
6 : Job(size=440)
6 : worker 5 got job
6 : worker 5 waiting for tool
6.23 : worker 1 done
6.23 : worker 1 releasing tool
6.23 : worker 1 waiting on job
6.23 : worker 5 got tool
7 : Job(size=231)
7 : worker 6 got job
7 : worker 6 waiting for tool
7.17 : worker 4 done
7.17 : worker 4 releasing tool
7.17 : worker 4 waiting on job
7.17 : worker 6 got tool
8 : Job(size=342)
8 : worker 7 got job
8 : worker 7 waiting for tool
8.63 : worker 3 done
8.63 : worker 3 releasing tool
8.63 : worker 3 waiting on job
8.63 : worker 7 got tool
9 : Job(size=385)
9 : worker 8 got job
9 : worker 8 waiting for tool
9.48 : worker 6 done
9.48 : worker 6 releasing tool
9.48 : worker 6 waiting on job
9.48 : worker 8 got tool
10 : Job(size=328)
10 : worker 9 got job
10 : worker 9 waiting for tool
10.63 : worker 5 done
10.63 : worker 5 releasing tool
10.63 : worker 5 waiting on job
10.63 : worker 9 got tool
11 : Job(size=181)
11 : worker 0 got job
11 : worker 0 waiting for tool
12 : Job(size=258)
12 : worker 2 got job
12 : worker 2 waiting for tool
12.05 : worker 7 done
12.05 : worker 7 releasing tool
12.05 : worker 7 waiting on job
12.05 : worker 0 got tool
13 : Job(size=494)
13 : worker 1 got job
13 : worker 1 waiting for tool
13.33 : worker 8 done
13.33 : worker 8 releasing tool
13.33 : worker 8 waiting on job
13.33 : worker 2 got tool
13.860000000000001 : worker 0 done
13.860000000000001 : worker 0 releasing tool
13.860000000000001 : worker 0 waiting on job
13.860000000000001 : worker 1 got tool
13.91 : worker 9 done
13.91 : worker 9 releasing tool
13.91 : worker 9 waiting on job
14 : Job(size=485)
14 : worker 4 got job
14 : worker 4 waiting for tool
14 : worker 4 got tool
15 : Job(size=264)
15 : worker 3 got job
15 : worker 3 waiting for tool
15.91 : worker 2 done
15.91 : worker 2 releasing tool
15.91 : worker 2 waiting on job
15.91 : worker 3 got tool
16 : Job(size=392)
16 : worker 6 got job
16 : worker 6 waiting for tool
17 : Job(size=204)
17 : worker 5 got job
17 : worker 5 waiting for tool
18 : Job(size=493)
18 : worker 7 got job
18 : worker 7 waiting for tool
18.55 : worker 3 done
18.55 : worker 3 releasing tool
18.55 : worker 3 waiting on job
18.55 : worker 6 got tool
18.8 : worker 1 done
18.8 : worker 1 releasing tool
18.8 : worker 1 waiting on job
18.8 : worker 5 got tool
18.85 : worker 4 done
18.85 : worker 4 releasing tool
18.85 : worker 4 waiting on job
18.85 : worker 7 got tool
19 : Job(size=283)
19 : worker 8 got job
19 : worker 8 waiting for tool
20 : Job(size=357)
20 : worker 0 got job
20 : worker 0 waiting for tool
20.84 : worker 5 done
20.84 : worker 5 releasing tool
20.84 : worker 5 waiting on job
20.84 : worker 8 got tool
21 : Job(size=191)
21 : worker 9 got job
21 : worker 9 waiting for tool
22 : Job(size=307)
22 : worker 2 got job
22 : worker 2 waiting for tool
22.47 : worker 6 done
22.47 : worker 6 releasing tool
22.47 : worker 6 waiting on job
22.47 : worker 0 got tool
23 : Job(size=429)
23 : worker 3 got job
23 : worker 3 waiting for tool
23.67 : worker 8 done
23.67 : worker 8 releasing tool
23.67 : worker 8 waiting on job
23.67 : worker 9 got tool
23.78 : worker 7 done
23.78 : worker 7 releasing tool
23.78 : worker 7 waiting on job
23.78 : worker 2 got tool
24 : Job(size=347)
24 : worker 1 got job
24 : worker 1 waiting for tool
25 : Job(size=319)
25 : worker 4 got job
25 : worker 4 waiting for tool
25.580000000000002 : worker 9 done
25.580000000000002 : worker 9 releasing tool
25.580000000000002 : worker 9 waiting on job
25.580000000000002 : worker 3 got tool
26 : Job(size=161)
26 : worker 5 got job
26 : worker 5 waiting for tool
26.04 : worker 0 done
26.04 : worker 0 releasing tool
26.04 : worker 0 waiting on job
26.04 : worker 1 got tool
26.85 : worker 2 done
26.85 : worker 2 releasing tool
26.85 : worker 2 waiting on job
26.85 : worker 4 got tool
27 : Job(size=107)
27 : worker 6 got job
27 : worker 6 waiting for tool
28 : Job(size=407)
28 : worker 8 got job
28 : worker 8 waiting for tool
29 : Job(size=216)
29 : worker 7 got job
29 : worker 7 waiting for tool
29.509999999999998 : worker 1 done
29.509999999999998 : worker 1 releasing tool
29.509999999999998 : worker 1 waiting on job
29.509999999999998 : worker 5 got tool
29.87 : worker 3 done
29.87 : worker 3 releasing tool
29.87 : worker 3 waiting on job
29.87 : worker 6 got tool
30 : Job(size=182)
30 : worker 9 got job
30 : worker 9 waiting for tool
30.040000000000003 : worker 4 done
30.040000000000003 : worker 4 releasing tool
30.040000000000003 : worker 4 waiting on job
30.040000000000003 : worker 8 got tool
30.94 : worker 6 done
30.94 : worker 6 releasing tool
30.94 : worker 6 waiting on job
30.94 : worker 7 got tool
31 : Job(size=335)
31 : worker 0 got job
31 : worker 0 waiting for tool
31.119999999999997 : worker 5 done
31.119999999999997 : worker 5 releasing tool
31.119999999999997 : worker 5 waiting on job
31.119999999999997 : worker 9 got tool
32 : Job(size=229)
32 : worker 2 got job
32 : worker 2 waiting for tool
32.94 : worker 9 done
32.94 : worker 9 releasing tool
32.94 : worker 9 waiting on job
32.94 : worker 0 got tool
33 : Job(size=421)
33 : worker 1 got job
33 : worker 1 waiting for tool
33.1 : worker 7 done
33.1 : worker 7 releasing tool
33.1 : worker 7 waiting on job
33.1 : worker 2 got tool
34 : Job(size=392)
34 : worker 3 got job
34 : worker 3 waiting for tool
34.11 : worker 8 done
34.11 : worker 8 releasing tool
34.11 : worker 8 waiting on job
34.11 : worker 1 got tool
35 : Job(size=303)
35 : worker 4 got job
35 : worker 4 waiting for tool
35.39 : worker 2 done
35.39 : worker 2 releasing tool
35.39 : worker 2 waiting on job
35.39 : worker 3 got tool
36 : Job(size=232)
36 : worker 6 got job
36 : worker 6 waiting for tool
36.29 : worker 0 done
36.29 : worker 0 releasing tool
36.29 : worker 0 waiting on job
36.29 : worker 4 got tool
37 : Job(size=253)
37 : worker 5 got job
37 : worker 5 waiting for tool
38 : Job(size=151)
38 : worker 9 got job
38 : worker 9 waiting for tool
38.32 : worker 1 done
38.32 : worker 1 releasing tool
38.32 : worker 1 waiting on job
38.32 : worker 6 got tool
39 : Job(size=226)
39 : worker 7 got job
39 : worker 7 waiting for tool
39.31 : worker 3 done
39.31 : worker 3 releasing tool
39.31 : worker 3 waiting on job
39.31 : worker 5 got tool
39.32 : worker 4 done
39.32 : worker 4 releasing tool
39.32 : worker 4 waiting on job
39.32 : worker 9 got tool
40 : Job(size=339)
40 : worker 8 got job
40 : worker 8 waiting for tool
40.64 : worker 6 done
40.64 : worker 6 releasing tool
40.64 : worker 6 waiting on job
40.64 : worker 7 got tool
40.83 : worker 9 done
40.83 : worker 9 releasing tool
40.83 : worker 9 waiting on job
40.83 : worker 8 got tool
41 : Job(size=359)
41 : worker 2 got job
41 : worker 2 waiting for tool
41.84 : worker 5 done
41.84 : worker 5 releasing tool
41.84 : worker 5 waiting on job
41.84 : worker 2 got tool
42 : Job(size=313)
42 : worker 0 got job
42 : worker 0 waiting for tool
42.9 : worker 7 done
42.9 : worker 7 releasing tool
42.9 : worker 7 waiting on job
42.9 : worker 0 got tool
43 : Job(size=205)
43 : worker 1 got job
43 : worker 1 waiting for tool
44 : Job(size=146)
44 : worker 3 got job
44 : worker 3 waiting for tool
44.22 : worker 8 done
44.22 : worker 8 releasing tool
44.22 : worker 8 waiting on job
44.22 : worker 1 got tool
45 : Job(size=260)
45 : worker 4 got job
45 : worker 4 waiting for tool
45.43000000000001 : worker 2 done
45.43000000000001 : worker 2 releasing tool
45.43000000000001 : worker 2 waiting on job
45.43000000000001 : worker 3 got tool
46 : Job(size=212)
46 : worker 6 got job
46 : worker 6 waiting for tool
46.03 : worker 0 done
46.03 : worker 0 releasing tool
46.03 : worker 0 waiting on job
46.03 : worker 4 got tool
46.269999999999996 : worker 1 done
46.269999999999996 : worker 1 releasing tool
46.269999999999996 : worker 1 waiting on job
46.269999999999996 : worker 6 got tool
46.89000000000001 : worker 3 done
46.89000000000001 : worker 3 releasing tool
46.89000000000001 : worker 3 waiting on job
47 : Job(size=294)
47 : worker 9 got job
47 : worker 9 waiting for tool
47 : worker 9 got tool
48 : Job(size=199)
48 : worker 5 got job
48 : worker 5 waiting for tool
48.38999999999999 : worker 6 done
48.38999999999999 : worker 6 releasing tool
48.38999999999999 : worker 6 waiting on job
48.38999999999999 : worker 5 got tool
48.63 : worker 4 done
48.63 : worker 4 releasing tool
48.63 : worker 4 waiting on job
49 : Job(size=212)
49 : worker 7 got job
49 : worker 7 waiting for tool
49 : worker 7 got tool
49.94 : worker 9 done
49.94 : worker 9 releasing tool
49.94 : worker 9 waiting on job
>>>
```

-- C

Timothy Miller

unread,
Jul 11, 2020, 4:52:53 PM7/11/20
to python-simpy
Actually, I take that back.  You can model maintenance with a maintenance_worker(), a process that waits until the maintenance time, then gets the resource and hangs on to it when the maintenance is done.  If the Resources are fungible, that's as far as you need go.  

If they aren't, and maintenance cycles are unique, then I'd create a Tool() object and create toolshed with a FilterStore instead of a Resource.  worker_process() then has `tool = yield toolshed.get()`, and has to put() it back instead of the calling release().  maintenance_worker() then gets assigned a tool identifier, and calls toolshed.get(lambda x: some tool id filter) to get the correct tool, and also releases it with toolshed.put().

If you want to *interrupt* a worker with the tool, then maintenance_worker() needs to query the worker processes to find it, so I'd start looking at a Worker() class instead.

-- T 

Chris Farr

unread,
Jul 12, 2020, 11:00:53 AM7/12/20
to python-simpy
Timothy,

Wow! I truly appreciate the time you took in your response. Thanks so much. I will review in depth.

Chris

Timothy J. Miller

unread,
Jul 12, 2020, 12:01:52 PM7/12/20
to python...@googlegroups.com

Chris Farr writes:

> Wow! I truly appreciate the time you took in your response. Thanks so much.
> I will review in depth.

Discrete event simulation is very very flexible, but you have to keep
straight the difference between passive participants (resources) and
active participants (processes), things will get clearer [1]. In your
case, I think you're stuck because you're thinking of "maintenance" as
an active process *of the tool*. Once you realize that "maintenance" is
an activity of a separate worker with the tool as a passive participant,
things should go easier.

This also opens up new areas for investigation, since now you can have a
limited pool of maintenance workers and examinw how that can impact
throughput.

-- T

[1] That's not to say that there aren't times when a resource may also
have a process--in fact, I have a network sim kernel writton on top of
SimPy where resources (queues) have processes--but you typically want to
start with maximum separation, and only add that kind of complexity as
the need arises.

Chris Farr

unread,
Jul 13, 2020, 10:09:34 AM7/13/20
to Timothy J. Miller, python...@googlegroups.com
Timothy,

Thanks again!

Chris

--
You received this message because you are subscribed to a topic in the Google Groups "python-simpy" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-simpy/s2la5ZShypI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-simpy...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python-simpy/87lfjog1sh.fsf%40penguin.
Reply all
Reply to author
Forward
0 new messages