A match-stick production line

Posted in math on Sunday, May 17 2015

I am reading The Goal and in one chapter there is a toy model of a factory, in which boy scouts move matches from one bowl to the next trying to move matches to the end of the line, how many they can move from their bowl determined by a roll of a dice. Something about the model struck a chord with me, and now I want to make a toy example in Python.

First off I want to create a Producer class that receives material from the previous machine in the production line (or boy scout) into its inventory and then moves some random amount down to the next machine in the line. The whole system receives 3.5 units/time step at the head of the production line, and inventory just falls off the end.

import numpy as np

class Producer(object):
    def __init__(self, name):
        self.name = name
        self.inventory = 0
        self.performance = 0.0
        self.next = None

    def __repr__(self):
        return "Id {:d}, Inventory: {:d}, Performance: {:0.1f} ".format(self.name, self.inventory, self.performance)

    def receive(self, amount):
        self.inventory += amount

    def move(self):
        if self.name < 1:
            amount = 3 + np.random.randint(low=0, high=1)
            self.receive(amount)

        can_make = np.random.randint(low=1, high=6)
        if can_make > self.inventory:
            does_make = self.inventory
        else:
            does_make = can_make

        self.inventory -= does_make
        self.performance += does_make - 3.5

        if self.next != None:
            self.next.receive(does_make)

The ProductionLine class is an interator, each time the production line is iterated it walks through the list of producers advancing one timestep. This is somewhat dangerous as it retains a memory from one iteration to the next, each step through it changes in some random way.

class ProductionLine(object):
    def __init__(self):
        # initialize the production line, there nothing here though
        self.head = None
        self.tail = None
        self.cur = None

    def __iter__(self):
        # return the cursor to the head of the line
        self.cur = self.head
        return self

    def __next__(self):
        # walk down the line, advancing one time step at a time
        if self.cur != None:
            self.cur.move()
            if self.cur.next!=None:
                self.cur = self.cur.next
                return self.cur
            else:
                raise StopIteration

    def append(self, producer):
        # Add a new producer to the end of the line
        if self.head == None:
            self.head = producer

        if self.tail != None:
            self.tail.next = producer

        self.tail = producer

Now I initiate a production line and populate it with 19 producers,

production = ProductionLine()

for i in range(20):
    p = Producer(i)
    production.append(p)

I can make an animation showing how inventory at each work station fluctuates and what happens to the cumulative production in the plant using matplotlib.

production line

This doesn't look so good, what's going on? Well we have a ratchet happening. The performance of any producer in any given time step has an upper bound given by the previous producer. If the guy before you doesn't give you anything to work on, you can't exactly have a high output right? On the flipside the performance can be as low as possible at any given time. The net effect of this is that positive fluctuations are stymied by upstream variability, whereas negative fluctuations remain.

If there was only one producer we would expect the cumulative performance to fluctuate around the mean (the cumulative performane is mean centered, so zero), but since there is a chain of dependency it ratchets lower and lower.

Related to this inventory piles up in front of some producers who happened to slow down while other producers are starved for inventory when they go faster than their predecesors.

As usual the ipython notebook is on github