1

I want to implement a discrete-event maintenance scheduling simulation in which some maintenance activities must happen whenever another one happens.

For example, if walls are repainted every 5 years, and dry-lining is replaced every 14 years, then walls must be repainted whenever the dry-lining is replaced and the clock restarted.

yr 5:  paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls
...

I'm not sure whether I should implement each activity as a process which refers to the dependent process, or if "wall maintenance" should be a process with the logic handled internally, or some other way of.

The code I have has each activity as a process with the dependent process stored as an attribute but I feel like I'm probably missing the correct way of doing this as I'm seeing events happen twice in the same year.

Jamie Bull
  • 12,889
  • 15
  • 77
  • 116

2 Answers2

1

You should always start with a very simple (and wrong) implementation just to get a better understanding of your use-case and a feeling how everything works, e.g.:

import simpy


def paint_walls(env, interval):
    while True:
        yield env.timeout(interval)
        print('yr %2d: paint walls' % env.now)


def replace_dry_lining(env, interval):
    while True:
        yield env.timeout(interval)
        print('yr %d: replace dry-lining' % env.now)


env = simpy.Environment()
env.process(paint_walls(env, interval=5))
env.process(replace_dry_lining(env, interval=14))
env.run(until=20)

Output:

yr  5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 15: paint walls

Then you can start extending/modifying your simulation. Here are two possibilities how your problem can be modeled:

Variant A

We keep using two separate processes but need a way to exchange the event "dry-lining replaced" between them, so that we can also paint the wall:

import simpy


class Maintenance:
    PAINT_WALL_INTERVAL = 5
    REPLACE_DRY_LINING_INTERVAL= 14

    def __init__(self, env):
        self.env = env
        self.dry_lining_replaced = env.event()

        self.p_paint_walls = env.process(self.paint_walls())
        self.p_replace_dry_lining = env.process(self.replace_dry_lining())

    def paint_walls(self):
        timeout = self.PAINT_WALL_INTERVAL
        while True:
            yield self.env.timeout(timeout) | self.dry_lining_replaced
            print('yr %2d: paint walls' % self.env.now)

    def replace_dry_lining(self):
        timeout = self.REPLACE_DRY_LINING_INTERVAL
        while True:
            yield self.env.timeout(timeout)
            print('yr %2d: replace dry-lining' % self.env.now)
            self.dry_lining_replaced.succeed()
            self.dry_lining_replaced = self.env.event()


env = simpy.Environment()
m = Maintenance(env)
env.run(until=20)

Output:

yr  5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls

Variant B

We can also model it with just one process that waits for either a "paint walls" or a "replace dry-lining" event:

import simpy


def maintenance(env):
    PAINT_WALL_INTERVAL = 5
    REPLACE_DRY_LINING_INTERVAL = 14

    paint_wall = env.timeout(PAINT_WALL_INTERVAL)
    replace_dry_lining = env.timeout(REPLACE_DRY_LINING_INTERVAL)

    while True:
        results = yield paint_wall | replace_dry_lining
        do_paint = paint_wall in results
        do_replace = replace_dry_lining in results

        if do_replace:
            print('yr %2d: replace dry-lining' % env.now)
            replace_dry_lining = env.timeout(REPLACE_DRY_LINING_INTERVAL)

        if do_paint or do_replace:
            print('yr %2d: paint walls' % env.now)
            paint_wall = env.timeout(PAINT_WALL_INTERVAL)


env = simpy.Environment()
env.process(maintenance(env))
env.run(until=20)

Output:

yr  5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls
Stefan Scherfke
  • 3,012
  • 1
  • 19
  • 28
  • Thanks for this. Is there any reason why the answer I came to is incorrect? The reason I ended up with that is so that I can "chain" several dependent activities together with each one only needing to know the next one in the chain. – Jamie Bull Apr 07 '16 at 08:12
  • True! I've added a fuller example now. – Jamie Bull Apr 07 '16 at 09:27
0

This is the approach I took in the end:

import simpy
from simpy.events import Interrupt


class Construction(object):

    def __init__(self, name, components):
        self.name = name
        self.components = components
        self.link_components()

    def link_components(self):
        """Link each component to the next outermost component      
        """
        for i, component in enumerate(self.components):
            try:
                component.dependent = self.components[i+1]
            except IndexError:
                component.dependent = None                

class Component(object):

    def __init__(self, env, name, lifespan):
        """Represents a component used in a construction.

        """
        self.env = env
        self.name = name
        self.lifespan = lifespan    
        self.action = env.process(self.run())

    def run(self):
        while True:
            try:
                yield self.env.timeout(self.lifespan)
                self.replace()
            except Interrupt:  # don't replace
                pass

    def replace(self):
        print "yr %d:  replace %s" % (env.now, self.name)
        if self.dependent:
            self.dependent.action.interrupt()  # stop the dependent process
            self.dependent.replace()  # replace the dependent component


env = simpy.Environment()
components = [Component(env, 'structure', 60),
              Component(env, 'dry-lining', 14),
              Component(env, 'paint', 5)]
wall = Construction('wall', components)
env.run(until=65)
Jamie Bull
  • 12,889
  • 15
  • 77
  • 116