1

New python user here and first post on this great website. I haven't been able to find an answer to my question so hopefully it is unique.

Using simpy I am trying to create a train subway/metro simulation with failures and repairs periodically built into the system. These failures happen to the train but also to signals on sections of track and on plaforms. I have read and applied the official Machine Shop example (which you can see resemblance of in the attached code) and have thus managed to model random failures and repairs to the train by interrupting its 'journey time'.

However I have not figured out how to model failures of signals on the routes which the trains follow. I am currently just specifying a time for a trip from A to B, which does get interrupted but only due to train failure.

Is it possible to define each trip as its own process i.e. a separate process for sections A_to_B and B_to_C, and separate platforms as pA, pB and pC. Each one with a single resource (to allow only one train on it at a time) and to incorporate random failures and repairs for these section and platform processes? I would also need to perhaps have several sections between two platforms, any of which could experience a failure.

Any help would be greatly appreciated.

Here's my code so far:

import random
import simpy
import numpy

RANDOM_SEED = 1234

T_MEAN_A = 240.0 # mean journey time
T_MEAN_EXPO_A = 1/T_MEAN_A # for exponential distribution

T_MEAN_B = 240.0 # mean journey time
T_MEAN_EXPO_B = 1/T_MEAN_B # for exponential distribution

DWELL_TIME = 30.0 # amount of time train sits at platform for passengers
DWELL_TIME_EXPO = 1/DWELL_TIME

MTTF = 3600.0  # mean time to failure (seconds)
TTF_MEAN = 1/MTTF # for exponential distribution

REPAIR_TIME = 240.0
REPAIR_TIME_EXPO = 1/REPAIR_TIME

NUM_TRAINS = 1
SIM_TIME_DAYS = 100
SIM_TIME = 3600 * 18 * SIM_TIME_DAYS
SIM_TIME_HOURS = SIM_TIME/3600


# Defining the times for processes
def A_B(): # returns processing time for journey A to B
    return random.expovariate(T_MEAN_EXPO_A) + random.expovariate(DWELL_TIME_EXPO)

def B_C(): # returns processing time for journey B to C
    return random.expovariate(T_MEAN_EXPO_B) + random.expovariate(DWELL_TIME_EXPO)

def time_to_failure(): # returns time until next failure
    return random.expovariate(TTF_MEAN)


# Defining the train
class Train(object):

    def __init__(self, env, name, repair):
        self.env = env
        self.name = name
        self.trips_complete = 0
        self.broken = False

    # Start "travelling" and "break_train" processes for the train
        self.process = env.process(self.running(repair))
        env.process(self.break_train())

    def running(self, repair):
        while True:
        # start trip A_B
            done_in = A_B()
            while done_in:
                try:
                    # going on the trip
                    start = self.env.now
                    yield self.env.timeout(done_in)
                    done_in = 0 # Set to 0 to exit while loop

                except simpy.Interrupt:
                    self.broken = True
                    done_in -= self.env.now - start # How much time left?
                    with repair.request(priority = 1) as req:
                        yield req
                        yield self.env.timeout(random.expovariate(REPAIR_TIME_EXPO))
                    self.broken = False
        # Trip is finished
            self.trips_complete += 1

        # start trip B_C
            done_in = B_C()
            while done_in:
                try:
                    # going on the trip
                    start = self.env.now
                    yield self.env.timeout(done_in)
                    done_in = 0 # Set to 0 to exit while loop

                except simpy.Interrupt:
                    self.broken = True
                    done_in -= self.env.now - start # How much time left?
                    with repair.request(priority = 1) as req:
                        yield req
                        yield self.env.timeout(random.expovariate(REPAIR_TIME_EXPO))
                    self.broken = False
        # Trip is finished
            self.trips_complete += 1


    # Defining the failure      
    def break_train(self):
        while True:
            yield self.env.timeout(time_to_failure())
            if not self.broken:
            # Only break the train if it is currently working
                self.process.interrupt()


# Setup and start the simulation
print('Train trip simulator')
random.seed(RANDOM_SEED) # Helps with reproduction

# Create an environment and start setup process
env = simpy.Environment()
repair = simpy.PreemptiveResource(env, capacity = 1)
trains = [Train(env, 'Train %d' % i, repair)
    for i in range(NUM_TRAINS)]

# Execute
env.run(until = SIM_TIME)


# Analysis
trips = []
print('Train trips after %s hours of simulation' % SIM_TIME_HOURS)
for train in trains:
    print('%s completed %d trips.' % (train.name, train.trips_complete))
    trips.append(train.trips_complete)

mean_trips = numpy.mean(trips)
std_trips = numpy.std(trips)
print "mean trips: %d" % mean_trips
print "standard deviation trips: %d" % std_trips
Harry Munro
  • 304
  • 2
  • 12

1 Answers1

1

it looks like you are using Python 2, which is a bit unfortunate, because Python 3.3 and above give you some more flexibility with Python generators. But your problem should be solveable in Python 2 nonetheless.

you can use sub processes within in a process:

def sub(env):
    print('I am a sub process')
    yield env.timeout(1)
    # return 23  # Only works in py3.3 and above
    env.exit(23)  # Workaround for older python versions

def main(env):
    print('I am the main process')
    retval = yield env.process(sub(env))
    print('Sub returned', retval)

As you can see, you can use Process instances returned by Environment.process() like normal events. You can even use return values in your sub proceses.

If you use Python 3.3 or newer, you don’t have to explicitly start a new sub-process but can use sub() as a sub routine instead and just forward the events it yields:

def sub(env):
    print('I am a sub routine')
    yield env.timeout(1)
    return 23

def main(env):
    print('I am the main process')
    retval = yield from sub(env)
    print('Sub returned', retval)

You may also be able to model signals as resources that may either be used by failure process or by a train. If the failure process requests the signal at first, the train has to wait in front of the signal until the failure process releases the signal resource. If the train is aleady passing the signal (and thus has the resource), the signal cannot break. I don’t think that’s a problem be cause the train can’t stop anyway. If it should be a problem, just use a PreemptiveResource.

I hope this helps. Please feel welcome to join our mailing list for more discussions.

Stefan Scherfke
  • 3,012
  • 1
  • 19
  • 28