Search code examples
pythonsimpy

How to use Filter Store by several items


I am using simpy to model of manufacturing equipment. The equipment need to make a product using some specified materials. I tried to model this using FilterStore as shown in the following code.

import simpy


class Material:
    def __init__(self,name):
        self.name = name

class Machine(object):
    def __init__(self,env):
        self.env = env
        self.inputs  = simpy.FilterStore(env)

    def run(self):
        mat = [ self.inputs.get(lambda i: i.name == itemname ) for itemname in ["mat1","mat2","mat4"] ]
        res = yield self.env.all_of(mat)
        print([res.events[i]._value.name for i in range(len(res.events))])


def input_materials(env,m):
    for i in range(5):
        m.inputs.put(Material( "mat"+str(i) ))
        m.inputs.put(Material( "mat"+str(i) ))
        m.inputs.put(Material( "mat"+str(i) ))
        yield env.timeout(1)


env = simpy.Environment()
machine = Machine(env)

env.process(machine.run())
env.process(input_materials(env,machine))
env.run()

The above code is output as follows.

['mat4', 'mat4', 'mat4']

I want to get [mat1,mat2,mat4], but the above code all gets mat4. We have confirmed that the results are as expected if I do not use variable, itemname, and code it separately as follows.

    def run(self):
        m1 =  self.inputs.get(lambda i: i.name == "mat1" )
        m2 =  self.inputs.get(lambda i: i.name == "mat2" )
        m4 =  self.inputs.get(lambda i: i.name == "mat4" )
        res = yield self.env.all_of([m1,m2,m4])
        print([res.events[i]._value.name for i in range(len(res.events))])

How should I code this? Some assistance would be greatly appreciated.


Solution

  • This is subtle.
    The itemname in you lambda is the same itemname used in the for statement but, the lambda does not get executed till after for statement is done, meanning when the lambdas finally do run they are all using the same itemname variable from the for statement with the for statement's last for loop assignment of mat4. This is also know as a enclosure which can be quite useful

    To fix this your lambda needs a local itemname variable

    update

    mat = [ self.inputs.get(lambda i: i.name == itemname ) for itemname in ["mat1","mat2","mat4"] ]
    

    to

    mat = [ self.inputs.get(lambda i,itemname=itemname: i.name == itemname ) for itemname in ["mat1","mat2","mat4"] ]