Simulating Grocery Store Orders with Scheduled Labor

309 views
Skip to first unread message

Joshua Smitherman

unread,
Apr 18, 2021, 10:41:59 PM4/18/21
to python-simpy
Hi Team:
  Developing a simulator using simpy 4 to create a one store one day, oder picking process with priority using scheduled labor. Example, you could have 4 pickers between the hours of 8 to 12 and 2 between the hours of 12 to close, etc. Orders that drop into the queue at 9 would need to utilize the 4 where orders that dropped at 4pm would unitize 2 pickers. 

I'm using PriorityResourcing  as certain orders need to be "staged" before others as the are dropped into the queue throughout the day. 

I've looked at several areas but didn't seem to find any that helps solve this issue for me. Any ideas on approaches I could take here?

Regards,
Josh

jpgr...@gmail.com

unread,
Apr 19, 2021, 9:33:25 AM4/19/21
to python-simpy
Hi Josh,

A lot of the simpy examples are oriented around modeling using Resources. However, it often seems the case that we want to model an intelligent or semi-intelligent actor with non-trivial behavior. In these cases, modeling such an actor using a Resource (or PriorityResource) becomes difficult-to-impossible.

An alternative approach that I use regularly is to model intelligent actors as Processes which consumer and/or generate work units from/to a Store or Container (or a Queue from the desmod package). With a Process, I can usually express the behavior of my actors directly.

In this case, I suggest modeling the grocery pickers as processes that consume orders from a queue (simpy.Store). The shift times of the pickers can thus be modeled directly. Like this:

import random
import simpy

ORDER_PROCESSING_TIME = 15

def pick(env, queue, ident):
    while True:
        try:
            order = yield queue.get()
        except simpy.Interrupt:
            break
        else:
            print(f'{env.now} {ident} begin order')

        start_time = env.now
        try:
            yield env.timeout(ORDER_PROCESSING_TIME)
        except simpy.Interrupt:
            yield env.timeout(ORDER_PROCESSING_TIME - (env.now - start_time))
        print(f'{env.now} {ident} finish order')


def picker(env, queue, ident, start_hour, shift_hours):
    yield env.timeout(start_hour * 60)
    print(f'{env.now} {ident} start shift')
    pick_process = env.process(pick(env, queue, ident))
    yield env.timeout(shift_hours * 60)
    pick_process.interrupt()
    print(f'{env.now} {ident} end shift')


def order_generator(env, queue):
    for n in range(50):
        yield env.timeout(random.randint(1, 20))
        yield queue.put(n)
        print(f'{env.now} put order')


if __name__ == '__main__':
    env = simpy.Environment()
    queue = simpy.Store(env)
    env.process(picker(env, queue, ident=0, start_hour=0, shift_hours=12))
    env.process(picker(env, queue, ident=1, start_hour=0, shift_hours=12))
    env.process(picker(env, queue, ident=2, start_hour=0, shift_hours=4))
    env.process(picker(env, queue, ident=3, start_hour=0, shift_hours=4))
    env.process(order_generator(env, queue))
    env.run(12 * 60)


Hopefully this gives you some ideas on how to proceed with your model.

Cheers,
Pete

Joshua Smitherman

unread,
Apr 19, 2021, 9:59:09 AM4/19/21
to python-simpy
this is great Pete. So are the ident specifying how many employees are available during that shift? or would we specify some kind of max number queue = simpy.Store(env,  capacity = max_pickers) then remove and add from there?

jpgr...@gmail.com

unread,
Apr 19, 2021, 10:18:48 AM4/19/21
to python-simpy
Well, in this example, the picker processes were established statically before the start of simulation. To model different configurations of picker employees, you would write code to instantiate picker processes to start/stop at the times you want to model. In my example, the pickers use a Store as their source of work units (orders), but the pickers themselves are not put into a Store, so a Store(capacity=max_pickers) does not make sense in the context of this model. The 'ident' is just an identifier for each picker so that they can be distinguished in the logging output--the identifiers are not strictly necessary and could alternatively be strings ('alice', 'bob', 'carol') or something else.

I suggest taking another look at the pick() and picker() functions and how interrupt() is used to terminate a picker's shift. It seems like you may not yet see how the picker process' lifetimes work.

Pete

Joshua Smitherman

unread,
Apr 19, 2021, 10:58:40 AM4/19/21
to python-simpy
ok, i see now. so to remove and then reset I would need some add/remove logic once the shift ends. Thanks for the help!
Reply all
Reply to author
Forward
0 new messages