Help to understand how AnyOf() works

433 views
Skip to first unread message

Adrian Mc

unread,
Jun 15, 2021, 10:16:58 AM6/15/21
to python-simpy
Hi,

I have set up 10 machines as resources. Each machine has slightly different processing times. Each machine has 4 load ports, which can be loaded with material at the same time or any time as long as a port is free.

Boxes of material (we call them 'lots') can queue for the machines and should get loaded onto a free load port. When material is finished on a load port, it can be removed, independently of the other load ports on that machine.

I was trying the AnyOf functionality to determine if any machine has a free port, so I can then load material onto it, but I've been struggling with the code for a day at this stage and I'm thinking maybe I should try some other method of doing this.

Here's some of the code I've been playing with:


import sys
import os
import random
from random import expovariate, uniform, randint
import simpy
from simpy.events import AnyOf, AllOf


class Machine(object):
    def __init__(self, env, name, load_ports):
        self.env = env
        self.name = name
        self.machine = simpy.Resource(env, load_ports)


def lot(env, idnumber, tools):
    requests = [tool.machine.request() for tool in tools]
    request = AnyOf(env, requests)
    yield request
    # I only need 1 load port. Do I need to release all the other load ports?
    env.timeout(expovariate(1/45.0)
    # How do I figure out which machine had the free load port so I can record metrics on it?
    # How do I release the resource now that I am finished with it?



def setup(env):
    step64 = Machine(env, '64LGROUP', 4)
    step65 = Machine(env, '65LGROUP', 4)
    step66 = Machine(env, '66LGROUP', 4)
    step67 = Machine(env, '67LGROUP', 4)
    step68 = Machine(env, '68LGROUP', 4)
    step69 = Machine(env, '69LGROUP', 4)
    step610 = Machine(env, '610LGRP', 4)
    step611 = Machine(env, '611LGRP', 4)
    step612 = Machine(env, '612LGRP', 4)
    step613 = Machine(env, '613LGRP', 4)
    tools = [step64, step65, step66, step67, step68, step69, step610, step611, step612, step613]
   
    lots = []
    for i in range(100):
        lots.append(env.process(lot(env, idnumber=i, tools=tools)))
        yield env.timeout(5.0)


if __name__ == '__main__':
    env = simpy.Environment()
    env.process(setup(env))
    env.run(until=1440 * 1)



My questions are:
1. How do I know which machines (resources) I have gotten hold of?
2. Is it possible that I could get hold of all 10 resources, even though I only want one?
3. If I have hold of multiple resources, how do I release them?


Thanks,
Adrian.







jpgr...@gmail.com

unread,
Jun 15, 2021, 4:30:03 PM6/15/21
to python-simpy
If your resources are perfectly homogeneous, then you might model them with Resource(capacity=N).

If your resources are not perfectly homogeneous (i.e. different name or other characteristics), then you might model your resource pool as a Store of objects. If you're not okay with a FIFO acquisition strategy, you might use FilterStore.

I'm not sure you want or need AnyOf for this application. But two notes nonetheless:

1. You normally use AnyOf via env.anyof().
2. If you are not careful, AnyOf can cause resources to be acquired that you may not actually use. AllOf is safer in this regard.

Hope this helps.

Pete

Adrian Mc

unread,
Jun 17, 2021, 4:44:29 AM6/17/21
to python-simpy
Thanks for the guidance Pete. I switched to FilterStore and I think it's working for me that way.




Regards,
Adrian.

Yannick Portier

unread,
Feb 18, 2022, 4:07:39 PM2/18/22
to python-simpy
Hello,

For what it's worth, here's my take on your problem:

class Machine(simpy.Resource):

    def __init__(self, env, name, load_ports):
        super().__init__(env, capacity=load_ports)
        self.name = name

def lot(env, idnumber, tools):
    requests = [machine.request() for machine in tools]
    condition = AnyOf(env, requests)
    yield condition
    available = list(condition.value)  # all successful requests (to available resources)
    occupied = [rq for rq in requests if rq not in available]  # all pending requests (to occupied resources)
    ok = available.pop(0)  # take the first available
    print(f'id {idnumber} got port from {ok.resource.name} at {env.now}')
    for other in available:
        yield other.resource.release(other)  # release all requests to available resources
    for other in occupied:
        other.cancel()  # cancel all requests to occupied resources
    yield env.timeout(int(expovariate(1 / 45)))
    print(f'id {idnumber} released port from {ok.resource.name} at {env.now}')
    yield ok.resource.release(ok)  # release

def setup(env):
    m1 = Machine(env, 'M-1', 2)
    m2 = Machine(env, 'M-2', 3)
    m3 = Machine(env, 'M-3', 4)
    tools = [m1, m2, m3]
    for i in range(100):
        env.process(lot(env, idnumber=i, tools=tools))
        yield env.timeout(5)


Regards,
Yannick

Message has been deleted
Message has been deleted

Michael Gibbs

unread,
Feb 18, 2022, 7:56:19 PM2/18/22
to python-simpy
When AnyOf fires it returns a list of all events that fired
So will need to release all these events.
You will also need to cancel or reuse in next anyOf all the events that did not fire

I think a better design might be to put all your lots into a store and have your load ports get lots from the store.  

However, to be closer to your original design I put this together
In this I made the load ports objects and then put all the load ports from all the machines into one store.  The lots then get a load port from the store.  The advantage here is you have one store instead of 10 resource pool.  The trick is the load port puts itself back into the store when it is ready/available to process another lot.  


"""
    simulate loading multi load port machines with lots to be processed

    programmer: Michael R. Gibbs
"""

# import sys
# import os
# import random
# from random import expovariate, uniform, randint
import simpy
# from simpy.events import AnyOf, AllOf


class Machine(object):
    """
        machine with a collection of load port that consumes lots
    """

    class Load_Port():
        """
        Load port that comsumes lots

        Make the port a object so it can be put into a store
        Make it a sub class so it can access parent machine variables
        """

        def __init__(self, env, port_name, load_rate, store):
            self.env = env
            self.port_name = port_name
            self.load_rate = load_rate
            self.store = store

        def load(self, lot_size):
            """
                process a lot
                then return to load store to be assign next lot
            """

            print(f'{self.env.now} {self.port_name} loading a lot')

            load_time = lot_size / self.load_rate
            yield self.env.timeout(load_time)

            print(f'{self.env.now} {self.port_name} finished a lot')

            yield self.store.put(self)
               


    def __init__(self, env, name, load_ports, load_rate, load_store):
        self.env = env
        self.name = name
        self.ports = [self.Load_Port(env, f'{name}_port_{i+1}', load_rate, load_store) for i in range(load_ports)]
        load_store.items.extend(self.ports)

        #self.machine = simpy.Resource(env, load_ports)


def log_gen():
    """
        generate a stream of lots of various sizes
    """
    while True:
        yield 10
        yield 20
        yield 30


def setup(env):
    """
        Creates a set of machines with various processing rates
    """
    store = simpy.Store(env)
    step64 = Machine(env, '64LGROUP', 4, 3, store)
    step65 = Machine(env, '65LGROUP', 4, 4, store)
    step66 = Machine(env, '66LGROUP', 4, 5, store)
    step67 = Machine(env, '67LGROUP', 4, 3, store)
    step68 = Machine(env, '68LGROUP', 4, 4, store)
    step69 = Machine(env, '69LGROUP', 4, 5, store)
    step610 = Machine(env, '610LGRP', 4, 3, store)
    step611 = Machine(env, '611LGRP', 4, 4, store)
    step612 = Machine(env, '612LGRP', 4, 5, store)
    step613 = Machine(env, '613LGRP', 4, 3, store)
    tools = [step64, step65, step66, step67, step68, step69, step610, step611, step612, step613]
   
   # generate a stream of lots and assing them
   # to availaiable load ports
    gen = log_gen()
    for i in range(100):
       
        print(f'{env.now} assining lot {i+1}')

        # wait for next avaialable load port
        port = yield store.get()

        # start load processing
        env.process(port.load(next(gen)))


if __name__ == '__main__':
    env = simpy.Environment()
    env.process(setup(env))
    env.run(until=1440 * 1)

Reply all
Reply to author
Forward
0 new messages