simpylogo LIST OF MODELS using SimPy Classic

SimPy version:2.3
Web-site:https://github.com/SimPyClassic/SimPyClassic
Python-Version:2.7 or later and 3.x except for the GUI ones

These models are examples of SimPy use written by several authors and usually developed for other purposes, such as teaching and consulting. They are in a variety of styles.

Most models are given in two versions, one with the procedural SimPy API and the other (identified by an “_OO” appended to the program name) with the Object Oriented API introduced in SimPy 2.0.

All of these examples come with SimPY in the docs/examples directory.

NOTE: The SimGUI examples do not work for Python 3 as the SimGUI library has not been ported to Python 3.

New Program Structure

M/M/C Queue: MCC.py, MCC_OO.py

M/M/C (multiple server queue model). This demonstrates both the multiple capacity Resource class and the observe method of the Monitor class. Random arrivals, exponential service-times. (TV)

Jobs arrive at random into a c-server queue with exponential service-time distribution. Simulate to determine the average number in the system and the average time jobs spend in the system.

"""MMC.py

An M/M/c queue

Jobs arrive at random into a c-server queue with
exponential service-time distribution. Simulate to
determine the average  number in the system and
the average time jobs spend in the system.

- c = Number of servers = 3
- rate = Arrival rate = 2.0
- stime = mean service time = 1.0

"""
from SimPy.Simulation import *
from random import expovariate, seed

# Model components ------------------------


class Generator(Process):
    """ generates Jobs at random """

    def execute(self, maxNumber, rate, stime):
        ''' generate Jobs at exponential intervals '''
        for i in range(maxNumber):
            L = Job("Job {0} ".format(i))
            activate(L, L.execute(stime), delay=0)
            yield hold, self, expovariate(rate)


class Job(Process):
    ''' Jobs request a gatekeeper and hold
        it for an exponential time '''

    NoInSystem = 0

    def execute(self, stime):
        arrTime = now()
        self.trace("Hello World")
        Job.NoInSystem += 1
        m.observe(Job.NoInSystem)
        yield request, self, server
        self.trace("At last    ")
        t = expovariate(1.0 / stime)
        msT.observe(t)
        yield hold, self, t
        yield release, self, server
        Job.NoInSystem -= 1
        m.observe(Job.NoInSystem)
        mT.observe(now() - arrTime)
        self.trace("Geronimo   ")

    def trace(self, message):
        FMT = "{0:7.4f} {1:6} {2:10} ({3:2d})"
        if TRACING:
            print(FMT.format(now(), self.name, message, Job.NoInSystem))
# Experiment data -------------------------


TRACING = False
c = 3  # number of servers in M/M/c system
stime = 1.0  # mean service time
rate = 2.0  # mean arrival rate
maxNumber = 1000
m = Monitor()  # monitor for the number of jobs
mT = Monitor()  # monitor for the time in system
msT = Monitor()  # monitor for the generated service times

seed(333555777)  # seed for random numbers


server = Resource(capacity=c, name='Gatekeeper')


# Model/Experiment ------------------------------

initialize()
g = Generator('gen')
activate(g, g.execute(maxNumber=maxNumber,
                      rate=rate, stime=stime))
m.observe(0)  # number in system is 0 at the start
simulate(until=3000.0)


# Analysis/output -------------------------

print('MMC')
print("{0:2d} servers, {1:6.4f} arrival rate, "
      "{2:6.4f} mean service time".format(c, rate, stime))
print("Average number in the system is {0:6.4f}".format(m.timeAverage()))
print("Average time in the system is   {0:6.4f}".format(mT.mean()))
print("Actual average service-time is  {0:6.4f}".format(msT.mean()))
MMC
 3 servers, 2.0000 arrival rate, 1.0000 mean service time
Average number in the system is 2.8786
Average time in the system is   1.4350
Actual average service-time is  0.9837
"""MMC_OO.py

An M/M/c queue

Jobs arrive at random into a c-server queue with
exponential service-time distribution. Simulate to
determine the average  number in the system and
the average time jobs spend in the system.

- c = Number of servers = 3
- rate = Arrival rate = 2.0
- stime = mean service time = 1.0

"""
from SimPy.Simulation import *
from random import expovariate, seed

# Model components ------------------------


class Generator(Process):
    """ generates Jobs at random """

    def execute(self, maxNumber, rate, stime):
        ''' generate Jobs at exponential intervals '''
        for i in range(maxNumber):
            L = Job("Job {0}".format(i), sim=self.sim)
            self.sim.activate(L, L.execute(stime), delay=0)
            yield hold, self, expovariate(rate)


class Job(Process):
    ''' Jobs request a gatekeeper and hold
        it for an exponential time '''

    NoInSystem = 0

    def execute(self, stime):
        arrTime = self.sim.now()
        self.trace("Hello World")
        Job.NoInSystem += 1
        self.sim.m.observe(Job.NoInSystem)
        yield request, self, self.sim.server
        self.trace("At last    ")
        t = expovariate(1.0 / stime)
        self.sim.msT.observe(t)
        yield hold, self, t
        yield release, self, self.sim.server
        Job.NoInSystem -= 1
        self.sim.m.observe(Job.NoInSystem)
        self.sim.mT.observe(self.sim.now() - arrTime)
        self.trace("Geronimo   ")

    def trace(self, message):
        FMT = "{0:7.4f} {1:6} {2:10} {(3:2d)}"
        if TRACING:
            print(FMT.format(self.sim.now(), self.name,
                             message, Job.NoInSystem))

# Experiment data -------------------------


TRACING = False
c = 3  # number of servers in M/M/c system
stime = 1.0  # mean service time
rate = 2.0  # mean arrival rate
maxNumber = 1000
seed(333555777)  # seed for random numbers

# Model -----------------------------------


class MMCmodel(Simulation):
    def run(self):
        self.initialize()
        self.m = Monitor(sim=self)  # monitor for the number of jobs
        self.mT = Monitor(sim=self)  # monitor for the time in system
        self.msT = Monitor(sim=self)  # monitor for the generated service times
        self.server = Resource(capacity=c, name='Gatekeeper', sim=self)
        g = Generator(name='gen', sim=self)
        self.activate(g, g.execute(maxNumber=maxNumber,
                                   rate=rate, stime=stime))
        self.m.observe(0)  # number in system is 0 at the start
        self.simulate(until=3000.0)


# Experiment ------------------------------
model = MMCmodel()
model.run()

# Analysis/output -------------------------

print('MMC')
print("{0:2d} servers, {1:6.4f} arrival rate, "
      "{2:6.4f} mean service time".format(c, rate, stime))
print("Average number in the system is {0:6.4f}".format(model.m.timeAverage()))
print("Average time in the system is   {0:6.4f}".format(model.mT.mean()))
print("Actual average service-time is  {0:6.4f}".format(model.msT.mean()))
MMC
 3 servers, 2.0000 arrival rate, 1.0000 mean service time
Average number in the system is 2.8786
Average time in the system is   1.4350
Actual average service-time is  0.9837

BCC: bcc.py, bcc_OO.py

Determine the probability of rejection of random arrivals to a 2-server system with different service-time distributions. No queues allowed, blocked customers are rejected (BCC). Distributions are Erlang, exponential, and hyperexponential. The theoretical probability is also calculated. (TV)

""" bcc.py

  Queue with blocked customers cleared
  Jobs (e.g messages) arrive randomly at rate 1.0 per minute at a
  2-server system.  Mean service time is 0.75 minutes and the
  service-time distribution is (1) exponential, (2) Erlang-5, or (3)
  hyperexponential with p=1/8,m1=2.0, and m2=4/7. However no
  queue is allowed; a job arriving when all the servers are busy is
  rejected.

  Develop and run a simulation program to estimate the probability of
  rejection (which, in steady-state, is the same as p(c)) Measure
  and compare the probability for each service time distribution.
  Though you should test the program with a trace, running just a few
  jobs, the final runs should be of 10000 jobs without a trace. Stop
  the simulation when 10000 jobs have been generated.
"""

from SimPy.Simulation import *
from random import seed, Random, expovariate

# Model components ------------------------

dist = ""


def bcc(lam, mu, s):
    """ bcc - blocked customers cleared model

    - returns p[i], i = 0,1,..s.
    - ps = p[s] = prob of blocking
    - lameff = effective arrival rate = lam*(1-ps)

    See Winston 22.11 for Blocked Customers Cleared Model (Erlang B formula)
    """
    rho = lam / mu
    n = range(s + 1)
    p = [0] * (s + 1)
    p[0] = 1
    sump = 1.0
    for i in n[1:]:
        p[i] = (rho / i) * p[i - 1]
        sump = sump + p[i]
    p0 = 1.0 / sump
    for i in n:
        p[i] = p[i] * p0
    p0 = p[0]
    ps = p[s]
    lameff = lam * (1 - ps)
    L = rho * (1 - ps)
    return {'lambda': lam, 'mu': mu, 's': s,
            'p0': p0, 'p[i]': p, 'ps': ps, 'L': L}


def ErlangVariate(mean, K):
    """ Erlang random variate

    mean = mean
    K = shape parameter
    g = rv to be used
    """
    sum = 0.0
    mu = K / mean
    for i in range(K):
        sum += expovariate(mu)
    return (sum)


def HyperVariate(p, m1, m2):
    """ Hyperexponential random variate

    p = prob of branch 1
    m1 = mean of exponential, branch 1
    m2 = mean of exponential, branch 2
    g = rv to be used
    """
    if random() < p:
        return expovariate(1.0 / m1)
    else:
        return expovariate(1.0 / m2)


def testHyperVariate():
    """ tests the HyerVariate rv generator"""
    ERR = 0
    x = (1.0981, 1.45546, 5.7470156)
    p = 0.0, 1.0, 0.5
    g = Random(1113355)
    for i in range(3):
        x1 = HyperVariate(p[i], 1.0, 10.0, g)
        # print(p[i], x1)
        assert abs(x1 - x[i]) < 0.001, 'HyperVariate error'


def erlangB(rho, c):
    """ Erlang's B formula for probabilities in no-queue

    Returns p[n] list
    see also SPlus and R version in que.q mmcK
    que.py has bcc.
    """
    n = range(c + 1)
    pn = list(range(c + 1))
    term = 1
    pn[0] = 1
    sum = 1
    term = 1.0
    i = 1
    while i < (c + 1):
        term *= rho / i
        pn[i] = term
        sum += pn[i]
        i += 1
    for i in n:
        pn[i] = pn[i] / sum
    return(pn)


class JobGen(Process):
    """ generates a sequence of Jobs
    """

    def execute(self, JobRate, MaxJob, mu):
        global NoInService, Busy
        for i in range(MaxJob):
            j = Job()
            activate(j, j.execute(i, mu), delay=0.0)
            t = expovariate(JobRate)
            MT.tally(t)
            yield hold, self, t
        self.trace("Job generator finished")

    def trace(self, message):
        if JobGenTRACING:
            print("{0:8.4f} \t{1}".format(now(), message))


class Job(Process):
    """ Jobs that are either accepted or rejected
    """

    def execute(self, i, mu):
        """ Job execution, only if accepted"""
        global NoInService, Busy, dist, NoRejected
        if NoInService < c:
            self.trace("Job %2d accepted b=%1d" % (i, Busy))
            NoInService += 1
            if NoInService == c:
                Busy = 1
                try:
                    BM.accum(Busy, now())
                except:
                    "accum error BM=", BM
            # yield hold,self,Job.g.expovariate(self.mu);
            # dist= "Exponential"
            yield hold, self, ErlangVariate(1.0 / mu, 5)
            dist = "Erlang     "
            # yield hold,self,HyperVariate(1.0/8,m1=2.0,m2=4.0/7,g=Job.g);
            # dist= "HyperExpon "
            NoInService -= 1
            Busy = 0
            BM.accum(Busy, now())
            self.trace("Job %2d leaving b=%1d" % (i, Busy))
        else:
            self.trace("Job %2d REJECT  b=%1d" % (i, Busy))
            NoRejected += 1

    def trace(self, message):
        if JobTRACING:
            print("{0:8.4f} \t{1}".format(now(), message))


# Experiment data -------------------------
c = 2
lam = 1.0  # per minute
mu = 1.0 / 0.75  # per minute
p = 1.0 / 8
m1 = 2.0
m2 = 4.0 / 7.0
K = 5
rho = lam / mu

NoRejected = 0
NoInService = 0
Busy = 0

JobRate = lam
JobMax = 10000

JobTRACING = 0
JobGenTRACING = 0


# Model/Experiment ------------------------------

seed(111333)
BM = Monitor()
MT = Monitor()

initialize()
jbg = JobGen()
activate(jbg, jbg.execute(1.0, JobMax, mu), 0.0)
simulate(until=20000.0)

# Analysis/output -------------------------

print('bcc')
print("time at the end = {0}".format(now()))
print("now = {0}\tstartTime = {1}".format(now(), BM.startTime))
print("No Rejected = {0:d}, ratio= {1}".format(
    NoRejected, (1.0 * NoRejected) / JobMax))
print("Busy proportion ({0}) = {1:8.6f}".format(dist, BM.timeAverage()))
print("Erlang pc (th)                = {0:8.6f}".format(erlangB(rho, c)[c]))
bcc
time at the end = 10085.751051963694
now = 10085.751051963694	startTime = 0.0
No Rejected = 1374, ratio= 0.1374
Busy proportion (Erlang     ) = 0.139016
Erlang pc (th)                = 0.138462
""" bcc_OO.py

  Queue with blocked customers cleared
  Jobs (e.g messages) arrive randomly at rate 1.0 per minute at a
  2-server system.  Mean service time is 0.75 minutes and the
  service-time distribution is (1) exponential, (2) Erlang-5, or (3)
  hyperexponential with p=1/8,m1=2.0, and m2=4/7. However no
  queue is allowed; a job arriving when all the servers are busy is
  rejected.

  Develop and run a simulation program to estimate the probability of
  rejection (which, in steady-state, is the same as p(c)) Measure
  and compare the probability for each service time distribution.
  Though you should test the program with a trace, running just a few
  jobs, the final runs should be of 10000 jobs without a trace. Stop
  the simulation when 10000 jobs have been generated.
"""

from SimPy.Simulation import *
from random import seed, Random, expovariate

# Model components ------------------------

dist = ""


def bcc(lam, mu, s):
    """ bcc - blocked customers cleared model

    - returns p[i], i = 0,1,..s.
    - ps = p[s] = prob of blocking
    - lameff = effective arrival rate = lam*(1-ps)

    See Winston 22.11 for Blocked Customers Cleared Model (Erlang B formula)
    """
    rho = lam / mu
    n = range(s + 1)
    p = [0] * (s + 1)
    p[0] = 1
    sump = 1.0
    for i in n[1:]:
        p[i] = (rho / i) * p[i - 1]
        sump = sump + p[i]
    p0 = 1.0 / sump
    for i in n:
        p[i] = p[i] * p0
    p0 = p[0]
    ps = p[s]
    lameff = lam * (1 - ps)
    L = rho * (1 - ps)
    return {'lambda': lam, 'mu': mu, 's': s,
            'p0': p0, 'p[i]': p, 'ps': ps, 'L': L}


def ErlangVariate(mean, K):
    """ Erlang random variate

    mean = mean
    K = shape parameter
    g = rv to be used
    """
    sum = 0.0
    mu = K / mean
    for i in range(K):
        sum += expovariate(mu)
    return (sum)


def HyperVariate(p, m1, m2):
    """ Hyperexponential random variate

    p = prob of branch 1
    m1 = mean of exponential, branch 1
    m2 = mean of exponential, branch 2
    g = rv to be used
    """
    if random() < p:
        return expovariate(1.0 / m1)
    else:
        return expovariate(1.0 / m2)


def testHyperVariate():
    """ tests the HyerVariate rv generator"""
    ERR = 0
    x = (1.0981, 1.45546, 5.7470156)
    p = 0.0, 1.0, 0.5
    g = Random(1113355)
    for i in range(3):
        x1 = HyperVariate(p[i], 1.0, 10.0, g)
        # print(p[i], x1)
        assert abs(x1 - x[i]) < 0.001, 'HyperVariate error'


def erlangB(rho, c):
    """ Erlang's B formula for probabilities in no-queue

    Returns p[n] list
    see also SPlus and R version in que.q mmcK
    que.py has bcc.
    """
    n = range(c + 1)
    pn = list(range(c + 1))
    term = 1
    pn[0] = 1
    sum = 1
    term = 1.0
    i = 1
    while i < (c + 1):
        term *= rho / i
        pn[i] = term
        sum += pn[i]
        i += 1
    for i in n:
        pn[i] = pn[i] / sum
    return(pn)


class JobGen(Process):
    """ generates a sequence of Jobs
    """

    def execute(self, JobRate, MaxJob, mu):
        global NoInService, Busy
        for i in range(MaxJob):
            j = Job(sim=self.sim)
            self.sim.activate(j, j.execute(i, mu), delay=0.0)
            t = expovariate(JobRate)
            MT.tally(t)
            yield hold, self, t
        self.trace("Job generator finished")

    def trace(self, message):
        if JobGenTRACING:
            print("{0:8.4f} \t{1}".format(self.sim.now(), message))


class Job(Process):
    """ Jobs that are either accepted or rejected
    """

    def execute(self, i, mu):
        """ Job execution, only if accepted"""
        global NoInService, Busy, dist, NoRejected
        if NoInService < c:
            self.trace("Job %2d accepted b=%1d" % (i, Busy))
            NoInService += 1
            if NoInService == c:
                Busy = 1
                try:
                    BM.accum(Busy, self.sim.now())
                except:
                    "accum error BM=", BM
            # yield hold,self,Job.g.expovariate(self.mu);
            # dist= "Exponential"
            yield hold, self, ErlangVariate(1.0 / mu, 5)
            dist = "Erlang     "
            # yield hold,self,HyperVariate(1.0/8,m1=2.0,m2=4.0/7,g=Job.g);
            # dist= "HyperExpon "
            NoInService -= 1
            Busy = 0
            BM.accum(Busy, self.sim.now())
            self.trace("Job %2d leaving b=%1d" % (i, Busy))
        else:
            self.trace("Job %2d REJECT  b=%1d" % (i, Busy))
            NoRejected += 1

    def trace(self, message):
        if JobTRACING:
            print("{0:8.4f} \t{1}".format(self.sim.now(), message))


# Experiment data -------------------------
c = 2
lam = 1.0  # per minute
mu = 1.0 / 0.75  # per minute
p = 1.0 / 8
m1 = 2.0
m2 = 4.0 / 7.0
K = 5
rho = lam / mu

NoRejected = 0
NoInService = 0
Busy = 0

JobRate = lam
JobMax = 10000

JobTRACING = 0
JobGenTRACING = 0


# Model/Experiment ------------------------------

seed(111333)
s = Simulation()
BM = Monitor(sim=s)
MT = Monitor(sim=s)

s.initialize()
jbg = JobGen(sim=s)
s.activate(jbg, jbg.execute(1.0, JobMax, mu), 0.0)
s.simulate(until=20000.0)

# Analysis/output -------------------------

print('bcc')
print("time at the end = {0}".format(s.now()))
print("now = {0}\tstartTime = {1}".format(s.now(), BM.startTime))
print("No Rejected = {0:d}, ratio= {1}".format(
    NoRejected, (1.0 * NoRejected) / JobMax))
print("Busy proportion ({0}) = {1:8.6f}".format(dist, BM.timeAverage()))
print("Erlang pc (th)                = {0:8.6f}".format(erlangB(rho, c)[c]))
bcc
time at the end = 10085.751051963694
now = 10085.751051963694	startTime = 0.0
No Rejected = 1374, ratio= 0.1374
Busy proportion (Erlang     ) = 0.139016
Erlang pc (th)                = 0.138462

callCenter.py, callCenter_OO.py

Scenario: A call center runs around the clock. It has a number of agents online with different skills. Calls by clients with different questions arrive at an expected rate of callrate per minute (expo. distribution). An agent only deals with clients with questions in his competence areas. The number of agents online and their skills remain constant – when an agent goes offline, he is replaced by one with the same skills. The expected service time tService[i] per question follows an exponential distribution. Clients are impatient and renege if they don’t get service within time tImpatience.

The model returns the frequency distribution of client waiting times, the percentage of reneging clients, and the load on the agents. This model demonstrates the use of yield get with a filter function. (KGM)

"""callCenter.py
Model shows use of get command with a filter function.

Scenario:
A call center runs around the clock. It has a number of agents online with
different skills/competences.
Calls by clients with different questions arrive at an expected rate of
callrate per minute (expo. distribution). An agent only deals with clients with
questions in his competence areas. The number of agents online and their skills
remain constant --
when an agent goes offline, he is replaced by one withe thesame skills.
The expected service time tService[i] per question
follows an exponential distribution.
Clients are impatient and renege if they don't get service within time
tImpatience.

* Determine the waiting times of clients.
* Determine the percentage renegers
* Determine the percentage load on agents.
"""
from SimPy.Simulation import *
import random as r
# Model components -----------------------------------------------------------


class Client(Process):
    def __init__(self, need):
        Process.__init__(self)
        self.need = need

    def getServed(self, callCenter):
        self.done = SimEvent()
        callsWaiting = callCenter.calls
        self.served = False
        self.tArrive = now()
        yield put, self, callsWaiting, [self]
        yield hold, self, tImpatience
        # get here either after renege or after interrupt of
        # renege==successful call
        if self.interrupted():
            # success, got service
            callCenter.renegeMoni.observe(success)
            # wait for completion of service
            yield waitevent, self, self.done
        else:
            # renege
            callCenter.renegeMoni.observe(renege)
            callsWaiting.theBuffer.remove(self)
            callCenter.waitMoni.observe(now() - self.tArrive)
            if callsWaiting.monitored:
                callsWaiting.bufferMon.observe(y=len(callsWaiting.theBuffer))


class CallGenerator(Process):
    def __init__(self, name, center):
        Process.__init__(self, name)
        self.buffer = center.calls
        self.center = center

    def generate(self):
        while now() <= endTime:
            yield hold, self, r.expovariate(callrate)
            ran = r.random()
            for aNeed in clientNeeds:
                if ran < probNeeds[aNeed]:
                    need = aNeed
                    break
            c = Client(need=need)
            activate(c, c.getServed(callCenter=self.center))


class Agent(Process):
    def __init__(self, name, skills):
        Process.__init__(self, name)
        self.skills = skills
        self.busyMon = Monitor(name="Load on {0}".format(self.name))

    def work(self, callCtr):
        incoming = callCtr.calls

        def mySkills(buffer):
            ret = []
            for client in buffer:
                if client.need in self.skills:
                    ret.append(client)
                    break
            return ret
        self.started = now()
        while True:
            self.busyMon.observe(idle)
            yield get, self, incoming, mySkills
            self.busyMon.observe(busy)
            theClient = self.got[0]
            callCtr.waitMoni.observe(now() - theClient.tArrive)
            self.interrupt(theClient)  # interrupt the timeout renege
            yield hold, self, tService[theClient.need]
            theClient.done.signal()


class Callcenter:
    def __init__(self, name):
        self.calls = Store(name=name, unitName="call", monitored=True)
        self.waitMoni = Monitor("Caller waiting time")
        self.agents = []
        self.renegeMoni = Monitor("Renegers")


renege = 1
success = 0
busy = 1
idle = 0

# Experiment data ------------------------------------------------------------
centerName = "SimCityBank"
clientNeeds = ["loan", "insurance", "credit card", "other"]
aSkills = [["loan"], ["loan", "credit card"],
           ["insurance"], ["insurance", "other"]]
nrAgents = {0: 1, 1: 2, 2: 2, 3: 2}  # skill:nr agents of that skill
probNeeds = {"loan": 0.1, "insurance": 0.2, "credit card": 0.5, "other": 1.0}
tService = {"loan": 3., "insurance": 4.,
            "credit card": 2., "other": 3.}  # minutes
tImpatience = 3       # minutes
callrate = 7. / 10          # Callers per minute
endTime = 10 * 24 * 60    # minutes (10 days)
r.seed(12345)

# Model ----------------------------------------------------------------------


def model():
    initialize()
    callC = Callcenter(name=centerName)
    for i in nrAgents.keys():  # loop over skills
        for j in range(nrAgents[i]):  # loop over nr agents of that skill
            a = Agent(name="Agent type {0}".format(i), skills=aSkills[i])
            callC.agents.append(a)
            activate(a, a.work(callCtr=callC))
    # buffer=callC.calls)
    cg = CallGenerator(name="Call generator", center=callC)
    activate(cg, cg.generate())
    simulate(until=endTime)
    return callC


for tImpatience in (0.5, 1., 2.,):
    # Experiment --------------------------------------------------------------
    callCenter = model()
    # Analysis/output ---------------------------------------------------------
    print("\ntImpatience={0} minutes".format(tImpatience))
    print("==================")
    callCenter.waitMoni.setHistogram(low=0.0, high=float(tImpatience))
    try:
        print(callCenter.waitMoni.printHistogram(fmt="{0:6.1f}"))
    except:
        pass
    renegers = [1 for x in callCenter.renegeMoni.yseries() if x == renege]
    print("\nPercentage reneging callers: {0:4.1f}\n".format(
        100.0 * sum(renegers) / callCenter.renegeMoni.count()))
    for agent in callCenter.agents:
        print("Load on {0} (skills= {1}): {2:4.1f} percent".format(
            agent.name, agent.skills, agent.busyMon.timeAverage() * 100))

tImpatience=0.5 minutes
==================

Percentage reneging callers: 10.1

Load on Agent type 0 (skills= ['loan']): 13.9 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.1 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.1 percent
Load on Agent type 2 (skills= ['insurance']): 13.1 percent
Load on Agent type 2 (skills= ['insurance']): 13.1 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 44.3 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 44.3 percent

tImpatience=1.0 minutes
==================

Percentage reneging callers:  7.6

Load on Agent type 0 (skills= ['loan']): 13.5 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.4 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.4 percent
Load on Agent type 2 (skills= ['insurance']): 12.9 percent
Load on Agent type 2 (skills= ['insurance']): 12.9 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 46.0 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 45.8 percent

tImpatience=2.0 minutes
==================

Percentage reneging callers:  3.3

Load on Agent type 0 (skills= ['loan']): 13.8 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.8 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.8 percent
Load on Agent type 2 (skills= ['insurance']): 13.0 percent
Load on Agent type 2 (skills= ['insurance']): 13.0 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 49.5 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 49.5 percent

Object Oriented version.

"""callCenter_OO.py
Model shows use of get command with a filter function.

Scenario:
A call center runs around the clock. It has a number of agents online with
different skills/competences.
Calls by clients with different questions arrive at an expected rate of
callrate per minute (expo. distribution). An agent only deals with clients with
questions in his competence areas. The number of agents online and their skills
remain
constant --
when an agent goes offline, he is replaced by one withe thesame skills.
The expected service time tService[i] per question
follows an exponential distribution.
Clients are impatient and renege if they don't get service within time
tImpatience.

* Determine the waiting times of clients.
* Determine the percentage renegers
* Determine the percentage load on agents.
"""
from SimPy.Simulation import *
import random as r
# Model components -----------------------------------------------------------


class Client(Process):
    def __init__(self, need, sim):
        Process.__init__(self, sim=sim)
        self.need = need

    def getServed(self, callCenter):
        self.done = SimEvent(sim=self.sim)
        callsWaiting = callCenter.calls
        self.served = False
        self.tArrive = self.sim.now()
        yield put, self, callsWaiting, [self]
        yield hold, self, tImpatience
        # get here either after renege or after interrupt of
        # renege==successful call
        if self.interrupted():
            # success, got service
            callCenter.renegeMoni.observe(success)
            # wait for completion of service
            yield waitevent, self, self.done
        else:
            # renege
            callCenter.renegeMoni.observe(renege)
            callsWaiting.theBuffer.remove(self)
            callCenter.waitMoni.observe(self.sim.now() - self.tArrive)
            if callsWaiting.monitored:
                callsWaiting.bufferMon.observe(y=len(callsWaiting.theBuffer))


class CallGenerator(Process):
    def __init__(self, name, center, sim):
        Process.__init__(self, name=name, sim=sim)
        self.buffer = center.calls
        self.center = center

    def generate(self):
        while self.sim.now() <= endTime:
            yield hold, self, r.expovariate(callrate)
            ran = r.random()
            for aNeed in clientNeeds:
                if ran < probNeeds[aNeed]:
                    need = aNeed
                    break
            c = Client(need=need, sim=self.sim)
            self.sim.activate(c, c.getServed(callCenter=self.center))


class Agent(Process):
    def __init__(self, name, skills, sim):
        Process.__init__(self, name=name, sim=sim)
        self.skills = skills
        self.busyMon = Monitor(
            name="Load on {0}".format(self.name), sim=self.sim)

    def work(self, callCtr):
        incoming = callCtr.calls

        def mySkills(buffer):
            ret = []
            for client in buffer:
                if client.need in self.skills:
                    ret.append(client)
                    break
            return ret
        self.started = self.sim.now()
        while True:
            self.busyMon.observe(idle)
            yield get, self, incoming, mySkills
            self.busyMon.observe(busy)
            theClient = self.got[0]
            callCtr.waitMoni.observe(self.sim.now() - theClient.tArrive)
            self.interrupt(theClient)  # interrupt the timeout renege
            yield hold, self, tService[theClient.need]
            theClient.done.signal()


class Callcenter:
    def __init__(self, name, sim):
        self.calls = Store(name=name, unitName="call", monitored=True, sim=sim)
        self.waitMoni = Monitor("Caller waiting time", sim=sim)
        self.agents = []
        self.renegeMoni = Monitor("Renegers", sim=sim)


renege = 1
success = 0
busy = 1
idle = 0

# Experiment data ------------------------------------------------------------
centerName = "SimCityBank"
clientNeeds = ["loan", "insurance", "credit card", "other"]
aSkills = [["loan"], ["loan", "credit card"],
           ["insurance"], ["insurance", "other"]]
nrAgents = {0: 1, 1: 2, 2: 2, 3: 2}  # skill:nr agents of that skill
probNeeds = {"loan": 0.1, "insurance": 0.2, "credit card": 0.5, "other": 1.0}
tService = {"loan": 3., "insurance": 4.,
            "credit card": 2., "other": 3.}  # minutes
tImpatienceRange = (0.5, 1., 2.,)      # minutes
callrate = 7. / 10          # Callers per minute
endTime = 10 * 24 * 60    # minutes (10 days)
r.seed(12345)

# Model ----------------------------------------------------------------------


class CallCenterModel(Simulation):
    def run(self):
        self.initialize()
        callC = Callcenter(name=centerName, sim=self)
        for i in nrAgents.keys():  # loop over skills
            for j in range(nrAgents[i]):  # loop over nr agents of that skill
                a = Agent(name="Agent type {0}".format(
                    i), skills=aSkills[i], sim=self)
                callC.agents.append(a)
                self.activate(a, a.work(callCtr=callC))
        # buffer=callC.calls)
        cg = CallGenerator(name="Call generator", center=callC, sim=self)
        self.activate(cg, cg.generate())
        self.simulate(until=endTime)
        return callC


for tImpatience in tImpatienceRange:
    # Experiment --------------------------------------------------------------
    callCenter = CallCenterModel().run()
    # Analysis/output ---------------------------------------------------------
    print("\ntImpatience={0} minutes".format(tImpatience))
    print("==================")
    callCenter.waitMoni.setHistogram(low=0.0, high=float(tImpatience))
    try:
        print(callCenter.waitMoni.printHistogram(fmt="{0:6.1f}"))
    except:
        pass
    renegers = [1 for x in callCenter.renegeMoni.yseries() if x == renege]
    print("\nPercentage reneging callers: {0:4.1f}\n".format(
        100.0 * sum(renegers) / callCenter.renegeMoni.count()))
    for agent in callCenter.agents:
        print("Load on {0} (skills= {1}): {2:4.1f} percent".format(
            agent.name, agent.skills, agent.busyMon.timeAverage() * 100))

tImpatience=0.5 minutes
==================

Percentage reneging callers: 10.1

Load on Agent type 0 (skills= ['loan']): 13.9 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.1 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.1 percent
Load on Agent type 2 (skills= ['insurance']): 13.1 percent
Load on Agent type 2 (skills= ['insurance']): 13.1 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 44.3 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 44.3 percent

tImpatience=1.0 minutes
==================

Percentage reneging callers:  7.6

Load on Agent type 0 (skills= ['loan']): 13.5 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.4 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.4 percent
Load on Agent type 2 (skills= ['insurance']): 12.9 percent
Load on Agent type 2 (skills= ['insurance']): 12.9 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 46.0 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 45.8 percent

tImpatience=2.0 minutes
==================

Percentage reneging callers:  3.3

Load on Agent type 0 (skills= ['loan']): 13.8 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.8 percent
Load on Agent type 1 (skills= ['loan', 'credit card']): 24.8 percent
Load on Agent type 2 (skills= ['insurance']): 13.0 percent
Load on Agent type 2 (skills= ['insurance']): 13.0 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 49.5 percent
Load on Agent type 3 (skills= ['insurance', 'other']): 49.5 percent

cellphone.py, cellphone_OO.py

Simulate the operation of a BCC cellphone system. Calls arrive at random to a cellphone hub with a fixed number of channels. Service times are assumed exponential. The objective is to determine the statistics of busy periods in the operation of a BCC cellphone system.

The program simulates the operation for 10 observation periods and measures the mean and variance of the total time blocked, and the number of times blocking occurred in each hour. An observational gap occurs between the observation periods to make each one’s measurement independent. (TV)

""" cellphone.py

Simulate the operation of a BCC cellphone system.

Calls arrive at random to a cellphone hub with a fixed number of
channels. Service times are assumed exponential. The objective
is to determine the statistics of busy periods in the operation of a
BCC cellphone system.

The required measurements are
(1) the total busy time (all channels full) in each 1-hour period and
(2) the total number of busy times in a 1-hour period.

The simulation is continuous but the observing Process, a
Statistician, breaks the time into 1-hour observation periods
separated by 15-minute gaps to reduce autocorrelation. The total busy
time and number of busy times in each interval is printed.

   """
from SimPy.Simulation import *
import random as ran


# Model components ------------------------

class CallSource(Process):
    """ generates a sequence of calls """

    def execute(self, maxN, lam, cell):
        for i in range(maxN):
            j = Call("Call{0:03d}".format(i))
            activate(j, j.execute(cell))
            yield hold, self, ran.expovariate(lam)


class Call(Process):
    """ Calls arrive at random at the cellphone hub"""

    def execute(self, cell):
        self.trace("arrived")
        if cell.Nfree == 0:
            self.trace("blocked and left")
        else:
            self.trace("got a channel")
            cell.Nfree -= 1
            if cell.Nfree == 0:
                self.trace("start busy period======")
                cell.busyStartTime = now()
                cell.totalBusyVisits += 1
            yield hold, self, ran.expovariate(mu)
            self.trace("finished")
            if cell.Nfree == 0:
                self.trace("end   busy period++++++")
                cell.busyEndTime = now()
                busy = now() - cell.busyStartTime
                self.trace("         busy  = {0:9.4f}".format(busy))
                cell.totalBusyTime += busy
            cell.Nfree += 1

    def trace(self, message):
        if TRACING:
            print("{0:7.4f} {1:13s} {2} ".format(now(), message, self.name))


class Cell:
    """ Holds global measurements"""
    Nfree = 0
    totalBusyTime = 0.0
    totalBusyVisits = 0
    result = ()


class Statistician(Process):
    """ observes the system at intervals """

    def execute(self, Nperiods, obsPeriod, obsGap, cell):
        cell.busyEndTime = now()  # simulation start time
        if STRACING:
            print("Busy time Number")
        for i in range(Nperiods):
            yield hold, self, obsGap
            cell.totalBusyTime = 0.0
            cell.totalBusyVisits = 0
            if cell.Nfree == 0:
                cell.busyStartTime = now()
            yield hold, self, obsPeriod
            if cell.Nfree == 0:
                cell.totalBusyTime += now() - cell.busyStartTime
            if STRACING:
                print("{0:7.3f} {1:5d}".format(
                    cell.totalBusyTime, cell.totalBusyVisits))
            m.tally(cell.totalBusyTime)
            bn.tally(cell.totalBusyVisits)
        stopSimulation()
        cell.result = (m.mean(), m.var(), bn.mean(), bn.var())

# Experiment data -------------------------


NChannels = 4         # number of channels in the cell
maxN = 10000
ranSeed = 3333333
lam = 1.0              # per minute
mu = 0.6667            # per minute
Nperiods = 10
obsPeriod = 60.0       # minutes
obsGap = 15.0       # gap between observation periods

TRACING = False
STRACING = True


# Experiment ------------------------------

m = Monitor()
bn = Monitor()
ran.seed(ranSeed)

cell = Cell()           # the cellphone tower
cell.Nfree = NChannels

initialize()
s = Statistician('Statistician')
activate(s, s.execute(Nperiods, obsPeriod, obsGap, cell))
g = CallSource('CallSource')
activate(g, g.execute(maxN, lam, cell))
simulate(until=10000.0)

# Output -------------------------
print('cellphone')
# input data:
print("lambda    mu      s  Nperiods obsPeriod  obsGap")
FMT = "{0:7.4f} {1:6.4f} {2:4d}   {3:4d}      {4:6.2f}   {5:6.2f}"
print(FMT.format(lam, mu, NChannels, Nperiods, obsPeriod, obsGap))


sr = cell.result
print("Busy Time:   mean = {0:6.3f} var= {1:6.3f}".format(sr[0], sr[1]))
print("Busy Number: mean = {0:6.3f} var= {1:6.3f}".format(sr[2], sr[3]))
Busy time Number
  5.857    15
  2.890     6
  1.219     4
  1.512     7
  4.237     5
  1.140     1
  3.596     8
  3.964     8
  4.146     9
  2.091     5
cellphone
lambda    mu      s  Nperiods obsPeriod  obsGap
 1.0000 0.6667    4     10       60.00    15.00
Busy Time:   mean =  3.065 var=  2.193
Busy Number: mean =  6.800 var= 12.360

Object orientated

""" cellphone_OO.py

Simulate the operation of a BCC cellphone system.

Calls arrive at random to a cellphone hub with a fixed number of
channels. Service times are assumed exponential. The objective
is to determine the statistics of busy periods in the operation of a
BCC cellphone system.

The required measurements are
(1) the total busy time (all channels full) in each 1-hour period and
(2) the total number of busy times in a 1-hour period.

The simulation is continuous but the observing Process, a
Statistician, breaks the time into 1-hour observation periods
separated by 15-minute gaps to reduce autocorrelation. The total busy
time and number of busy times in each interval is printed.

   """
from SimPy.Simulation import *
import random as ran


# Model components ------------------------

class CallSource(Process):
    """ generates a sequence of calls """

    def execute(self, maxN, lam, cell):
        for i in range(maxN):
            j = Call("Call{0:03d}".format(i), sim=self.sim)
            self.sim.activate(j, j.execute(cell))
            yield hold, self, ran.expovariate(lam)


class Call(Process):
    """ Calls arrive at random at the cellphone hub"""

    def execute(self, cell):
        self.trace("arrived")
        if cell.Nfree == 0:
            self.trace("blocked and left")
        else:
            self.trace("got a channel")
            cell.Nfree -= 1
            if cell.Nfree == 0:
                self.trace("start busy period======")
                cell.busyStartTime = self.sim.now()
                cell.totalBusyVisits += 1
            yield hold, self, ran.expovariate(mu)
            self.trace("finished")
            if cell.Nfree == 0:
                self.trace("end   busy period++++++")
                cell.busyEndTime = self.sim.now()
                busy = self.sim.now() - cell.busyStartTime
                self.trace("         busy  = {0:9.4f}".format(busy))
                cell.totalBusyTime += busy
            cell.Nfree += 1

    def trace(self, message):
        if TRACING:
            print("{0:7.4f} {1:13s} {2}".format(
                self.sim.now(), message, self.name))


class Cell:
    """ Holds global measurements"""
    Nfree = 0
    totalBusyTime = 0.0
    totalBusyVisits = 0
    result = ()


class Statistician(Process):
    """ observes the system at intervals """

    def execute(self, Nperiods, obsPeriod, obsGap, cell):
        cell.busyEndTime = self.sim.now()  # simulation start time
        if STRACING:
            print("Busy time Number")
        for i in range(Nperiods):
            yield hold, self, obsGap
            cell.totalBusyTime = 0.0
            cell.totalBusyVisits = 0
            if cell.Nfree == 0:
                cell.busyStartTime = self.sim.now()
            yield hold, self, obsPeriod
            if cell.Nfree == 0:
                cell.totalBusyTime += self.sim.now() - cell.busyStartTime
            if STRACING:
                print("{0:7.3f} {1:5d}".format(
                    cell.totalBusyTime, cell.totalBusyVisits))
            self.sim.m.tally(cell.totalBusyTime)
            self.sim.bn.tally(cell.totalBusyVisits)
        self.sim.stopSimulation()
        cell.result = (self.sim.m.mean(), self.sim.m.var(),
                       self.sim.bn.mean(), self.sim.bn.var())

# Experiment data -------------------------


NChannels = 4         # number of channels in the cell
maxN = 10000
ranSeed = 3333333
lam = 1.0              # per minute
mu = 0.6667            # per minute
Nperiods = 10
obsPeriod = 60.0       # minutes
obsGap = 15.0       # gap between observation periods

TRACING = False
STRACING = True

# Model -----------------------------------


class CellphoneModel(Simulation):
    def run(self):
        self.initialize()
        self.m = Monitor(sim=self)
        self.bn = Monitor(sim=self)
        ran.seed(ranSeed)
        self.cell = Cell()           # the cellphone tower
        self.cell.Nfree = NChannels
        s = Statistician('Statistician', sim=self)
        self.activate(s, s.execute(Nperiods, obsPeriod, obsGap, self.cell))
        g = CallSource('CallSource', sim=self)
        self.activate(g, g.execute(maxN, lam, self.cell))
        self.simulate(until=10000.0)


# Experiment ------------------------------
modl = CellphoneModel()
modl.run()

# Output ----------------------------------
print('cellphone')
# input data:
print("lambda    mu      s  Nperiods obsPeriod  obsGap")
FMT = "{0:7.4f} {1:6.4f} {2:4d}   {3:4d}      {4:6.2f}   {5:6.2f}"
print(FMT.format(lam, mu, NChannels, Nperiods, obsPeriod, obsGap))

sr = modl.cell.result
print("Busy Time:   mean = {0:6.3f} var= {1:6.3f}".format(sr[0], sr[1]))
print("Busy Number: mean = {0:6.3f} var= {1:6.3f}".format(sr[2], sr[3]))
Busy time Number
  5.857    15
  2.890     6
  1.219     4
  1.512     7
  4.237     5
  1.140     1
  3.596     8
  3.964     8
  4.146     9
  2.091     5
cellphone
lambda    mu      s  Nperiods obsPeriod  obsGap
 1.0000 0.6667    4     10       60.00    15.00
Busy Time:   mean =  3.065 var=  2.193
Busy Number: mean =  6.800 var= 12.360

Computer CPU: centralserver.py, centralserver_OO.py

A primitive central-server model with a single CPU and a single disk. A fixed number of users send “tasks” to the system which are processed and sent back to the user who then thinks for a time before sending a task back. Service times are exponential. This system can be solved analytically. (TV)

The user of each terminal thinks for a time (exponential, mean 100.0 sec) and then submits a task to the CPU with a service time (exponential, mean 1.0 sec). The user then remains idle until the task completes service and returns to him or her. The arriving tasks form a single FCFS queue in front of the CPU.

Upon leaving the CPU a task is either finished (probability 0.20) and returns to its user to begin another think time, or requires data from a disk drive (probability 0.8). If a task requires access to the disk, it joins a FCFS queue before service (service time at the disk, exponential, mean 1.39 sec). When finished with the disk, a task returns to the CPU queue again for another compute time (exp, mean 1.$ sec).

The objective is to measure the throughput of the CPU (tasks per second)

""" centralserver.py

A time-shared computer consists of a single
central processing unit (CPU) and a number of
terminals. The operator of each terminal `thinks'
for a time (exponential, mean 100.0 sec) and then
submits a task to the computer with a service time
(exponential, mean 1.0 sec). The operator then
remains idle until the task completes service and
returns to him or her. The arriving tasks form a
single FCFS queue in front of the CPU.

Upon leaving the CPU a task is either finished
(probability 0.20) and returns to its operator
to begin another `think' time, or requires data
from a disk drive (probability 0.8). If a task
requires access to the disk, it joins a FCFS queue
before service (service time at the disk,
exponential, mean 1.39 sec). When finished with
the disk, a task returns to the CPU queue again
for another compute time (exp, mean 1.$ sec).

the objective is to measure the throughput of
the CPU (tasks per second)
"""
from SimPy.Simulation import *
# from SimPy.SimulationTrace import *
import random as ran

# Model components ------------------------


class Task(Process):
    """ A computer  task  requires at least
    one use of the CPU and possibly accesses to a
    disk drive."""
    completed = 0
    rate = 0.0

    def execute(self, maxCompletions):
        while Task.completed < maxCompletions:
            self.debug(" starts thinking")
            thinktime = ran.expovariate(1.0 / MeanThinkTime)
            yield hold, self, thinktime
            self.debug(" request cpu")
            yield request, self, cpu
            self.debug(" got cpu")
            CPUtime = ran.expovariate(1.0 / MeanCPUTime)
            yield hold, self, CPUtime
            yield release, self, cpu
            self.debug(" finish cpu")
            while ran.random() < pDisk:
                self.debug(" request disk")
                yield request, self, disk
                self.debug(" got disk")
                disktime = ran.expovariate(1.0 / MeanDiskTime)
                yield hold, self, disktime
                self.debug(" finish disk")
                yield release, self, disk
                self.debug(" request cpu")
                yield request, self, cpu
                self.debug(" got cpu")
                CPUtime = ran.expovariate(1.0 / MeanCPUTime)
                yield hold, self, CPUtime
                yield release, self, cpu
            Task.completed += 1
        self.debug(" completed {0:d} tasks".format(Task.completed))
        Task.rate = Task.completed / float(now())

    def debug(self, message):
        FMT = "{0:9.3f} {1} {2}"
        if DEBUG:
            print(FMT.format(now(), self.name, message))


# Model ------------------------------
def main():
    initialize()
    for i in range(Nterminals):
        t = Task(name="task{0}".format(i))
        activate(t, t.execute(MaxCompletions))
    simulate(until=MaxrunTime)
    return (now(), Task.rate)

# Experiment data -------------------------


cpu = Resource(name='cpu')
disk = Resource(name='disk')
Nterminals = 3  # Number of terminals = Tasks
pDisk = 0.8  # prob. of going to disk
MeanThinkTime = 10.0  # seconds
MeanCPUTime = 1.0  # seconds
MeanDiskTime = 1.39  # seconds

ran.seed(111113333)
MaxrunTime = 20000.0
MaxCompletions = 100
DEBUG = False


# Experiment

result = main()

# Analysis/output -------------------------

print('centralserver')
print('{0:7.4f}: CPU rate = {1:7.4f} tasks per second'.format(
    result[0], result[1]))
centralserver
913.7400: CPU rate =  0.1116 tasks per second

OO version

""" centralserver.py

A time-shared computer consists of a single
central processing unit (CPU) and a number of
terminals. The operator of each terminal `thinks'
for a time (exponential, mean 100.0 sec) and then
submits a task to the computer with a service time
(exponential, mean 1.0 sec). The operator then
remains idle until the task completes service and
returns to him or her. The arriving tasks form a
single FCFS queue in front of the CPU.

Upon leaving the CPU a task is either finished
(probability 0.20) and returns to its operator
to begin another `think' time, or requires data
from a disk drive (probability 0.8). If a task
requires access to the disk, it joins a FCFS queue
before service (service time at the disk,
exponential, mean 1.39 sec). When finished with
the disk, a task returns to the CPU queue again
for another compute time (exp, mean 1.$ sec).

the objective is to measure the throughput of
the CPU (tasks per second)
"""
from SimPy.Simulation import *
# from SimPy.SimulationTrace import *
import random as ran

# Model components ------------------------


class Task(Process):
    """ A computer  task  requires at least
    one use of the CPU and possibly accesses to a
    disk drive."""
    completed = 0
    rate = 0.0

    def execute(self, maxCompletions):
        while Task.completed < MaxCompletions:
            self.debug(" starts thinking")
            thinktime = ran.expovariate(1.0 / MeanThinkTime)
            yield hold, self, thinktime
            self.debug(" request cpu")
            yield request, self, self.sim.cpu
            self.debug(" got cpu")
            CPUtime = ran.expovariate(1.0 / MeanCPUTime)
            yield hold, self, CPUtime
            yield release, self, self.sim.cpu
            self.debug(" finish cpu")
            while ran.random() < pDisk:
                self.debug(" request disk")
                yield request, self, self.sim.disk
                self.debug(" got disk")
                disktime = ran.expovariate(1.0 / MeanDiskTime)
                yield hold, self, disktime
                self.debug(" finish disk")
                yield release, self, self.sim.disk
                self.debug(" request cpu")
                yield request, self, self.sim.cpu
                self.debug(" got cpu")
                CPUtime = ran.expovariate(1.0 / MeanCPUTime)
                yield hold, self, CPUtime
                yield release, self, self.sim.cpu
            Task.completed += 1
        self.debug(" completed {0:d} tasks".format(Task.completed))
        Task.rate = Task.completed / float(self.sim.now())

    def debug(self, message):
        FMT = "({0:9.3f} {1} {2}"
        if DEBUG:
            print(FMT.format(self.sim.now(), self.name, message))


# Model ------------------------------
class CentralServerModel(Simulation):
    def run(self):
        self.initialize()
        self.cpu = Resource(name='cpu', sim=self)
        self.disk = Resource(name='disk', sim=self)
        for i in range(Nterminals):
            t = Task(name="task{0}".format(i), sim=self)
            self.activate(t, t.execute(MaxCompletions))
        self.simulate(until=MaxrunTime)
        return (self.now(), Task.rate)


# Experiment data -------------------------
Nterminals = 3  # Number of terminals = Tasks
pDisk = 0.8  # prob. of going to disk
MeanThinkTime = 10.0  # seconds
MeanCPUTime = 1.0  # seconds
MeanDiskTime = 1.39  # seconds

ran.seed(111113333)
MaxrunTime = 20000.0
MaxCompletions = 100
DEBUG = False


# Experiment

result = CentralServerModel().run()

# Analysis/output -------------------------

print('centralserver')
print('{0:7.4f}: CPU rate = {1:7.4f} tasks per second'.format(
    result[0], result[1]))
centralserver
913.7400: CPU rate =  0.1116 tasks per second

Messages on a Jackson Network: jacksonnetwork.py, jacksonnetwork_OO.py

A Jackson network with 3 nodes, exponential service times and probability switching. The simulation measures the delay for jobs moving through the system. (TV)

Messages arrive randomly at rate 1.5 per second at a communication network with 3 nodes (computers). Each computer (node) can queue messages.

"""
  jacksonnetwork.py

  Messages arrive randomly at rate 1.5 per second
  at a communication network with 3 nodes
  (computers). Each computer (node) can queue
  messages. Service-times are exponential with
  mean m_i at node i. These values are given in
  the column headed n_i in the table below.  On
  completing service at node i a message transfers
  to node j with probability p_ij and leaves the
  system with probability p_i3.

  These transition probabilities are as follows:
    Node     m_i  p_i0   p_i1  p_i2    p_i3
     i                                (leave)
     0       1.0   0      0.5  0.5     0
     1       2.0   0      0    0.8     0.2
     2       1.0   0.2    0    0       0.8

  Your task is to estimate
  (1) the average time taken for jobs going
      through the system and
  (2) the average number of jobs in the system.

"""
from SimPy.Simulation import *
# from SimPy.SimulationTrace import *
import random as ran

# Model components ------------------------


def choose2dA(i, P):
    """  return a random choice from a set j = 0..n-1
       with probs held in list of lists P[j] (n by n)
       using row i
       call:  next = choose2d(i,P)
    """
    U = ran.random()
    sumP = 0.0
    for j in range(len(P[i])):  # j = 0..n-1
        sumP += P[i][j]
        if U < sumP:
            break
    return(j)


class Msg(Process):
    """a message."""
    noInSystem = 0

    def execute(self, i):
        """ executing a message """
        startTime = now()
        Msg.noInSystem += 1
        # print("DEBUG noInSystm = ",Msg.noInSystem)
        NoInSystem.observe(Msg.noInSystem)
        self.trace("Arrived node {0}".format(i))
        while i != 3:
            yield request, self, node[i]
            self.trace("Got node {0}".format(i))
            st = ran.expovariate(1.0 / mean[i])
            yield hold, self, st
            yield release, self, node[i]
            self.trace("Finished with {0}".format(i))
            i = choose2dA(i, P)
            self.trace("Transfer to {0}".format(i))
        TimeInSystem.tally(now() - startTime)
        self.trace("leaving       {0} {1} in system".format(i, Msg.noInSystem))
        Msg.noInSystem -= 1
        NoInSystem.accum(Msg.noInSystem)

    def trace(self, message):
        if MTRACING:
            print("{0:7.4f} {1:3s} {2:10s}".format(now(), self.name, message))


class MsgSource(Process):
    """ generates a sequence of msgs """

    def execute(self, rate, maxN):
        self.count = 0   # hold number of messages generated
        self.trace("starting MsgSource")
        while (self.count < maxN):
            self.count += 1
            p = Msg("Message {0}".format(self.count))
            activate(p, p.execute(startNode))
            yield hold, self, ran.expovariate(rate)
        self.trace("generator finished with {0} ========".format(self.count))

    def trace(self, message):
        if GTRACING:
            print("{0:7.4f} \t{1}".format(now(), message))

# Experiment data -------------------------


rate = 1.5  # arrivals per second
maxNumber = 1000  # of Messages
GTRACING = False  # tracing Messages Source?

startNode = 0  # Messages always enter at node 0
ran.seed(77777)
MTRACING = False  # tracing Message action?

TimeInSystem = Monitor("time")
NoInSystem = Monitor("Number")

node = [Resource(1), Resource(1), Resource(1)]

mean = [1.0, 2.0, 1.0]  # service times, seconds
P = [[0, 0.5, 0.5, 0],  # transition matrix P_ij
     [0, 0, 0.8, 0.2],
     [0.2, 0, 0, 0.8]]

# Model/Experiment ------------------------------

initialize()
g = MsgSource(name="MsgSource")
activate(g, g.execute(rate, maxNumber))
simulate(until=5000.0)

# Analysis/output -------------------------

print('jacksonnetwork')
print("Mean number in system = {0:10.4f}".format(NoInSystem.timeAverage()))
print("Mean delay in system  = {0:10.4f}".format(TimeInSystem.mean()))
print("Total time run        = {0:10.4f}".format(now()))
print("Total jobs arrived    = {0:10d}".format(g.count))
print("Total jobs completed  = {0:10d}".format(TimeInSystem.count()))
print("Average arrival rate  = {0:10.4f}".format(g.count / now()))
jacksonnetwork
Mean number in system =   250.8179
Mean delay in system  =   310.7710
Total time run        =  1239.0304
Total jobs arrived    =       1000
Total jobs completed  =       1000
Average arrival rate  =     0.8071

OO version

"""
  jacksonnetwork_OO.py

  Messages arrive randomly at rate 1.5 per second
  at a communication network with 3 nodes
  (computers). Each computer (node) can queue
  messages. Service-times are exponential with
  mean m_i at node i. These values are given in
  the column headed n_i in the table below.  On
  completing service at node i a message transfers
  to node j with probability p_ij and leaves the
  system with probability p_i3.

  These transition probabilities are as follows:
    Node     m_i  p_i0   p_i1  p_i2    p_i3
     i                                (leave)
     0       1.0   0      0.5  0.5     0
     1       2.0   0      0    0.8     0.2
     2       1.0   0.2    0    0       0.8

  Your task is to estimate
  (1) the average time taken for jobs going
      through the system and
  (2) the average number of jobs in the system.

"""
from SimPy.Simulation import *
import random as ran

# Model components ------------------------


def choose2dA(i, P):
    """  return a random choice from a set j = 0..n-1
       with probs held in list of lists P[j] (n by n)
       using row i
       call:  next = choose2d(i,P)
    """
    U = ran.random()
    sumP = 0.0
    for j in range(len(P[i])):  # j = 0..n-1
        sumP += P[i][j]
        if U < sumP:
            break
    return(j)


class Msg(Process):
    """a message"""
    noInSystem = 0

    def execute(self, i):
        """ executing a message """
        startTime = self.sim.now()
        Msg.noInSystem += 1
        # print("DEBUG noInSystm = ",Msg.noInSystem)
        self.sim.NoInSystem.observe(Msg.noInSystem)
        self.trace("Arrived node  {0}".format(i))
        while i != 3:
            yield request, self, self.sim.node[i]
            self.trace("Got node {0}".format(i))
            st = ran.expovariate(1.0 / mean[i])
            yield hold, self, st
            yield release, self, self.sim.node[i]
            self.trace("Finished with {0}".format(i))
            i = choose2dA(i, P)
            self.trace("Transfer to   {0}".format(i))
        self.sim.TimeInSystem.tally(self.sim.now() - startTime)
        self.trace("leaving       {0} {1} in system".format(i, Msg.noInSystem))
        Msg.noInSystem -= 1
        self.sim.NoInSystem.accum(Msg.noInSystem)

    def trace(self, message):
        if MTRACING:
            print("{0:7.4f} {1:3d} {2:10s}".format(
                self.sim.now(), self.name, message))


class MsgSource(Process):
    """ generates a sequence of msgs """

    def execute(self, rate, maxN):
        self.count = 0   # hold number of messages generated
        while (self.count < maxN):
            self.count += 1
            p = Msg("Message {0}".format(self.count), sim=self.sim)
            self.sim.activate(p, p.execute(i=startNode))
            yield hold, self, ran.expovariate(rate)
        self.trace("generator finished with {0} ========".format(self.count))

    def trace(self, message):
        if GTRACING:
            print("{0:7.4f} \t{1}".format(self.sim.now(), message))

# Experiment data -------------------------


rate = 1.5  # arrivals per second
maxNumber = 1000  # of Messages
GTRACING = False  # tracing Messages Source?

startNode = 0  # Messages always enter at node 0
ran.seed(77777)
MTRACING = False  # tracing Message action?


mean = [1.0, 2.0, 1.0]  # service times, seconds
P = [[0, 0.5, 0.5, 0],  # transition matrix P_ij
     [0, 0, 0.8, 0.2],
     [0.2, 0, 0, 0.8]]

# Model -----------------------------------


class JacksonnetworkModel(Simulation):
    def run(self):
        self.initialize()
        self.TimeInSystem = Monitor("time", sim=self)
        self.NoInSystem = Monitor("Number", sim=self)
        self.node = [Resource(1, sim=self), Resource(
            1, sim=self), Resource(1, sim=self)]
        self.g = MsgSource("MsgSource", sim=self)
        self.activate(self.g, self.g.execute(rate, maxNumber))
        self.simulate(until=5000.0)


# Experiment ------------------------------
modl = JacksonnetworkModel()
modl.run()

# Analysis/output -------------------------

print('jacksonnetwork')
print("Mean number in system = {0:10.4f}".format(
    modl.NoInSystem.timeAverage()))
print("Mean delay in system  = {0:10.4f}".format(modl.TimeInSystem.mean()))
print("Total time run        = {0:10.4f}".format(modl.now()))
print("Total jobs arrived    = {0:10d}".format(modl.g.count))
print("Total jobs completed  = {0:10d}".format(modl.TimeInSystem.count()))
print("Average arrival rate  = {0:10.4f}".format(modl.g.count / modl.now()))
jacksonnetwork
Mean number in system =   250.8179
Mean delay in system  =   310.7710
Total time run        =  1239.0304
Total jobs arrived    =       1000
Total jobs completed  =       1000
Average arrival rate  =     0.8071

Miscellaneous Models

Bank Customers who can renege: bank08renege.py, bank08renege_OO.py

(Note currently does not run under Python 3)

Use of reneging (compound yield request) based on bank08.py of the tutorial TheBank. Customers leave if they lose patience with waiting.

""" bank08

 A counter with a random service time
 and customers who renege. Based on the program bank08.py
 from TheBank tutorial. (KGM)
"""
from SimPy.Simulation import *
from random import expovariate, seed, uniform

# Model components ------------------------


class Source(Process):
    """ Source generates customers randomly"""

    def generate(self, number, interval, counter):
        for i in range(number):
            c = Customer(name="Customer{0:02d}".format(i))
            activate(c, c.visit(counter, timeInBank=12.0))
            t = expovariate(1.0 / interval)
            yield hold, self, t


class Customer(Process):
    """ Customer arrives, is served and leaves """

    def visit(self, counter, timeInBank=0):
        arrive = now()
        print("{0:7.4f} {1}: Here I am     ".format(now(), self.name))

        yield (request, self, counter), (hold, self, next(Customer.patience))

        if self.acquired(counter):
            wait = now() - arrive
            print("{0:7.4f} {1}: Waited {2:6.3f}".format(
                now(), self.name, wait))
            tib = expovariate(1.0 / timeInBank)
            yield hold, self, tib
            yield release, self, counter
            print("{0:7.4f} {1}: Finished".format(now(), self.name))
        else:
            wait = now() - arrive
            print("{0:7.4f} {1}: RENEGED after {2:6.3f}".format(
                now(), self.name, wait))

    def fpatience(minpatience=0, maxpatience=10000000000):
        while True:
            yield uniform(minpatience, maxpatience)
    fpatience = staticmethod(fpatience)

# Model ---------------------------------------------


def model():
    counter = Resource(name="Karen")
    Customer.patience = Customer.fpatience(minpatience=1, maxpatience=3)
    initialize()
    source = Source('Source')
    activate(source, source.generate(NumCustomers,
                                     interval=IntervalCustomers,
                                     counter=counter))
    simulate(until=maxTime)

# Experiment data -------------------------


maxTime = 400.0
theseed = 1234
NumCustomers = 5
IntervalCustomers = 10.0
# Experiment ------------------------------

seed(theseed)
print('bank08renege')
model()
bank08renege
 0.0000 Customer00: Here I am     
 0.0000 Customer00: Waited  0.000
 0.0902 Customer00: Finished
33.9482 Customer01: Here I am     
33.9482 Customer01: Waited  0.000
44.4221 Customer01: Finished
58.1367 Customer02: Here I am     
58.1367 Customer02: Waited  0.000
69.2708 Customer03: Here I am     
70.3325 Customer03: RENEGED after  1.062
71.9733 Customer04: Here I am     
73.6655 Customer04: RENEGED after  1.692
75.5906 Customer02: Finished

OO version

""" bank08_OO

 A counter with a random service time
 and customers who renege. Based on the program bank08.py
 from TheBank tutorial. (KGM)
"""
from SimPy.Simulation import *
from random import expovariate, seed, uniform

# Model components ------------------------


class Source(Process):
    """ Source generates customers randomly"""

    def generate(self, number, interval, counter):
        for i in range(number):
            c = Customer(name="Customer{0:02d}".format(i), sim=self.sim)
            self.sim.activate(c, c.visit(counter, timeInBank=12.0))
            t = expovariate(1.0 / interval)
            yield hold, self, t


class Customer(Process):
    """ Customer arrives, is served and leaves """

    def visit(self, counter, timeInBank=0):
        arrive = self.sim.now()
        print("{0:7.4f} {1}: Here I am     ".format(self.sim.now(), self.name))

        yield (request, self, counter), (hold, self, next(Customer.patience))

        if self.acquired(counter):
            wait = self.sim.now() - arrive
            print("{0:7.4f} {1}: Waited {2:6.3f}".format(
                self.sim.now(), self.name, wait))
            tib = expovariate(1.0 / timeInBank)
            yield hold, self, tib
            yield release, self, counter
            print("{0:7.4f} {1}: Finished".format(self.sim.now(), self.name))
        else:
            wait = self.sim.now() - arrive
            print("{0:7.4f} {1}: RENEGED after {2:6.3f}".format(
                self.sim.now(), self.name, wait))

    def fpatience(minpatience=0, maxpatience=10000000000):
        while True:
            yield uniform(minpatience, maxpatience)
    fpatience = staticmethod(fpatience)

# Model ---------------------------------------------


class BankModel(Simulation):
    def run(self):
        self.initialize()
        counter = Resource(name="Karen", sim=self)
        Customer.patience = Customer.fpatience(minpatience=1, maxpatience=3)
        source = Source(name='Source', sim=self)
        self.activate(source, source.generate(NumCustomers,
                                              interval=IntervalCustomers,
                                              counter=counter))
        self.simulate(until=maxTime)

# Experiment data -------------------------


maxTime = 400.0
theseed = 1234
NumCustomers = 5
IntervalCustomers = 10.0
# Experiment ------------------------------

seed(theseed)
print('bank08renege')
BankModel().run()
bank08renege
 0.0000 Customer00: Here I am     
 0.0000 Customer00: Waited  0.000
 0.0902 Customer00: Finished
33.9482 Customer01: Here I am     
33.9482 Customer01: Waited  0.000
44.4221 Customer01: Finished
58.1367 Customer02: Here I am     
58.1367 Customer02: Waited  0.000
69.2708 Customer03: Here I am     
70.3325 Customer03: RENEGED after  1.062
71.9733 Customer04: Here I am     
73.6655 Customer04: RENEGED after  1.692
75.5906 Customer02: Finished

Carwash: Carwash.py, Carwash_OO.py

Using a Store object for implementing master/slave cooperation between processes. Scenario is a carwash installation with multiple machines. Two model implementations are shown, one with the carwash as master in the cooperation, and the other with the car as master.

from SimPy.Simulation import *
import random
"""carwash.py
Scenario:
A carwash installation has nrMachines washing machines which wash a car in
washTime minutes.
Cars arrive with a negative exponential interarrival time with a mean of tInter
minutes.

Model the carwash operation as cooperation between two processes, the car being
washed and the machine doing the washing.

Build two implementations:
Model 1: the machine is master, the car slave;
Model 2: the car is master, the machine is slave.

"""
# Data:
nrMachines = 2
tInter = 2  # minutes
washtime = 3.5  # minutes
initialSeed = 123456
simTime = 100

#####################################################
# Model 1: Carwash is master, car is slave
#####################################################


class Carwash(Process):
    """Carwash machine; master"""

    def __init__(self, name):
        Process.__init__(self, name)
        self.carBeingWashed = None

    def lifecycle(self):
        while True:
            yield get, self, waitingCars, 1
            self.carBeingWashed = self.got[0]
            yield hold, self, washtime
            self.carBeingWashed.doneSignal.signal(self.name)


class Car(Process):
    """Car; slave"""

    def __init__(self, name):
        Process.__init__(self, name)
        self.doneSignal = SimEvent()

    def lifecycle(self):
        yield put, self, waitingCars, [self]
        yield waitevent, self, self.doneSignal
        whichWash = self.doneSignal.signalparam
        print("{0}: {1} done by {2}".format(now(), self.name, whichWash))


class CarGenerator(Process):
    """Car arrival generation"""

    def generate(self):
        i = 0
        while True:
            yield hold, self, r.expovariate(1.0 / tInter)
            c = Car("car{0}".format(i))
            activate(c, c.lifecycle())
            i += 1


print("Model 1: carwash is master")
print("--------------------------")
initialize()
r = random.Random()
r.seed(initialSeed)
waiting = []
for j in range(1, 5):
    c = Car("car-{0}".format(j))
    activate(c, c.lifecycle())
    waiting.append(c)
waitingCars = Store(capacity=40, initialBuffered=waiting)
cw = []
for i in range(2):
    c = Carwash("Carwash {0}".format(i))
    cw.append(c)
    activate(c, c.lifecycle())
cg = CarGenerator()
activate(cg, cg.generate())
simulate(until=simTime)
print("waiting cars: {0}".format([x.name for x in waitingCars.theBuffer]))
print("cars being washed: {0}".format([y.carBeingWashed.name for y in cw]))

#####################################################
# Model 2: Car is master, carwash is slave
#####################################################


class CarM(Process):
    """Car is master"""

    def __init__(self, name):
        Process.__init__(self, name)

    def lifecycle(self):
        yield get, self, washers, 1
        whichWash = self.got[0]
        carsBeingWashed.append(self)
        yield hold, self, washtime
        print("{0}: {1} done by {2}".format(now(), self.name, whichWash.name))
        whichWash.doneSignal.signal()
        carsBeingWashed.remove(self)


class CarwashS(Process):
    def __init__(self, name):
        Process.__init__(self, name)
        self.doneSignal = SimEvent()

    def lifecycle(self):
        while True:
            yield put, self, washers, [self]
            yield waitevent, self, self.doneSignal


class CarGenerator1(Process):
    def generate(self):
        i = 0
        while True:
            yield hold, self, r.expovariate(1.0 / tInter)
            c = CarM("car{0}".format(i))
            activate(c, c.lifecycle())
            i += 1


print("\nModel 2: car is master")
print("----------------------")
initialize()
r = random.Random()
r.seed(initialSeed)
washers = Store(capacity=nrMachines)
carsBeingWashed = []
for j in range(1, 5):
    c = CarM("car-{0}".format(j))
    activate(c, c.lifecycle())
for i in range(2):
    cw = CarwashS("Carwash {0}".format(i))
    activate(cw, cw.lifecycle())
cg = CarGenerator1()
activate(cg, cg.generate())
simulate(until=simTime)
print("waiting cars: {0}".format([x.name for x in washers.getQ]))
print("cars being washed: {0}".format([x.name for x in carsBeingWashed]))
Model 1: carwash is master
--------------------------
3.5: car-1 done by Carwash 0
3.5: car-2 done by Carwash 1
7.0: car-3 done by Carwash 0
7.0: car-4 done by Carwash 1
17.5: car0 done by Carwash 0
17.5: car1 done by Carwash 1
21.0: car2 done by Carwash 0
21.0: car3 done by Carwash 1
24.5: car4 done by Carwash 0
24.5: car5 done by Carwash 1
28.0: car6 done by Carwash 0
28.0: car7 done by Carwash 1
31.5: car8 done by Carwash 0
31.5: car9 done by Carwash 1
35.0: car10 done by Carwash 0
35.0: car11 done by Carwash 1
38.5: car12 done by Carwash 0
38.5: car13 done by Carwash 1
42.0: car14 done by Carwash 0
42.0: car15 done by Carwash 1
45.5: car16 done by Carwash 0
45.5: car17 done by Carwash 1
49.0: car18 done by Carwash 0
49.0: car19 done by Carwash 1
52.5: car20 done by Carwash 0
52.5: car21 done by Carwash 1
56.0: car22 done by Carwash 0
56.0: car23 done by Carwash 1
59.5: car24 done by Carwash 0
59.5: car25 done by Carwash 1
63.0: car26 done by Carwash 0
63.0: car27 done by Carwash 1
66.5: car28 done by Carwash 0
66.5: car29 done by Carwash 1
70.0: car30 done by Carwash 0
70.0: car31 done by Carwash 1
73.5: car32 done by Carwash 0
73.5: car33 done by Carwash 1
77.0: car34 done by Carwash 0
77.0: car35 done by Carwash 1
80.5: car36 done by Carwash 0
80.5: car37 done by Carwash 1
84.0: car38 done by Carwash 0
84.0: car39 done by Carwash 1
87.5: car40 done by Carwash 0
87.5: car41 done by Carwash 1
91.0: car42 done by Carwash 0
91.0: car43 done by Carwash 1
94.5: car44 done by Carwash 0
94.5: car45 done by Carwash 1
98.0: car46 done by Carwash 0
98.0: car47 done by Carwash 1
waiting cars: ['car50', 'car51', 'car52', 'car53', 'car54', 'car55', 'car56', 'car57', 'car58', 'car59']
cars being washed: ['car48', 'car49']

Model 2: car is master
----------------------
3.5: car-1 done by Carwash 0
3.5: car-2 done by Carwash 1
7.0: car-3 done by Carwash 0
7.0: car-4 done by Carwash 1
10.5: car0 done by Carwash 0
10.5: car1 done by Carwash 1
14.0: car2 done by Carwash 0
14.0: car3 done by Carwash 1
17.5: car4 done by Carwash 0
17.5: car5 done by Carwash 1
21.0: car6 done by Carwash 0
21.0: car7 done by Carwash 1
24.5: car8 done by Carwash 0
24.5: car9 done by Carwash 1
28.0: car10 done by Carwash 0
28.0: car11 done by Carwash 1
31.5: car12 done by Carwash 0
31.5: car13 done by Carwash 1
35.0: car14 done by Carwash 0
35.0: car15 done by Carwash 1
38.5: car16 done by Carwash 0
38.5: car17 done by Carwash 1
42.0: car18 done by Carwash 0
42.0: car19 done by Carwash 1
45.5: car20 done by Carwash 0
45.5: car21 done by Carwash 1
49.0: car22 done by Carwash 0
49.0: car23 done by Carwash 1
52.5: car24 done by Carwash 0
52.5: car25 done by Carwash 1
56.0: car26 done by Carwash 0
56.0: car27 done by Carwash 1
59.5: car28 done by Carwash 0
59.5: car29 done by Carwash 1
63.0: car30 done by Carwash 0
63.0: car31 done by Carwash 1
66.5: car32 done by Carwash 0
66.5: car33 done by Carwash 1
70.0: car34 done by Carwash 0
70.0: car35 done by Carwash 1
73.5: car36 done by Carwash 0
73.5: car37 done by Carwash 1
77.0: car38 done by Carwash 0
77.0: car39 done by Carwash 1
80.5: car40 done by Carwash 0
80.5: car41 done by Carwash 1
84.0: car42 done by Carwash 0
84.0: car43 done by Carwash 1
87.5: car44 done by Carwash 0
87.5: car45 done by Carwash 1
91.0: car46 done by Carwash 0
91.0: car47 done by Carwash 1
94.5: car48 done by Carwash 0
94.5: car49 done by Carwash 1
98.0: car50 done by Carwash 0
98.0: car51 done by Carwash 1
waiting cars: ['car54', 'car55', 'car56', 'car57', 'car58', 'car59']
cars being washed: ['car52', 'car53']

Here is the OO version:

from SimPy.Simulation import *
import random
"""Carwash_OO.py
Scenario:
A carwash installation has nrMachines washing machines which wash a car in
washTime minutes.
Cars arrive with a negative exponential interarrival time with a mean of tInter
minutes.

Model the carwash operation as cooperation between two processes, the car being
washed and the machine doing the washing.

Build two implementations:
Model 1: the machine is master, the car slave;
Model 2: the car is master, the machine is slave.

"""
# Experiment data -------------------------
nrMachines = 2
tInter = 2  # minutes
washtime = 3.5  # minutes
initialSeed = 123456
simTime = 100  # minutes

# Model 1 components ----------------------


class Carwash(Process):
    """Carwash machine; master"""

    def __init__(self, name, sim):
        Process.__init__(self, name=name, sim=sim)
        self.carBeingWashed = None

    def lifecycle(self):
        while True:
            yield get, self, self.sim.waitingCars, 1
            self.carBeingWashed = self.got[0]
            yield hold, self, washtime
            self.carBeingWashed.doneSignal.signal(self.name)


class Car(Process):
    """Car; slave"""

    def __init__(self, name, sim):
        Process.__init__(self, name=name, sim=sim)
        self.doneSignal = SimEvent(sim=sim)

    def lifecycle(self):
        yield put, self, self.sim.waitingCars, [self]
        yield waitevent, self, self.doneSignal
        whichWash = self.doneSignal.signalparam
        print("{0}: {1} done by {2}".format(
            self.sim.now(), self.name, whichWash))


class CarGenerator(Process):
    """Car arrival generation"""

    def generate(self):
        i = 0
        while True:
            yield hold, self, self.sim.r.expovariate(1.0 / tInter)
            c = Car("car{0}".format(i), sim=self.sim)
            self.sim.activate(c, c.lifecycle())
            i += 1

# Model 1: Carwash is master, car is slave


class CarWashModel1(Simulation):
    def run(self):
        print("Model 1: carwash is master")
        print("--------------------------")
        self.initialize()
        self.r = random.Random()
        self.r.seed(initialSeed)
        waiting = []
        for j in range(1, 5):
            c = Car("car{0}".format(-j), sim=self)
            self.activate(c, c.lifecycle())
            waiting.append(c)
        self.waitingCars = Store(
            capacity=40, initialBuffered=waiting, sim=self)
        cw = []
        for i in range(nrMachines):
            c = Carwash("Carwash {0}".format(i), sim=self)
            cw.append(c)
            self.activate(c, c.lifecycle())
        cg = CarGenerator(sim=self)
        self.activate(cg, cg.generate())
        self.simulate(until=simTime)

        print("waiting cars: {0}".format(
            [x.name for x in self.waitingCars.theBuffer]))
        print("cars being washed: {0}".format(
            [y.carBeingWashed.name for y in cw]))


# Experiment 1 ----------------------------
CarWashModel1().run()

############################################

# Model 2 components ----------------------


class CarM(Process):
    """Car is master"""

    def lifecycle(self):
        yield get, self, self.sim.washers, 1
        whichWash = self.got[0]
        self.sim.carsBeingWashed.append(self)
        yield hold, self, washtime
        print("{0}: {1} done by {2}".format(
            self.sim.now(), self.name, whichWash.name))
        whichWash.doneSignal.signal()
        self.sim.carsBeingWashed.remove(self)


class CarwashS(Process):
    def __init__(self, name, sim):
        Process.__init__(self, name=name, sim=sim)
        self.doneSignal = SimEvent(sim=sim)

    def lifecycle(self):
        while True:
            yield put, self, self.sim.washers, [self]
            yield waitevent, self, self.doneSignal


class CarGenerator1(Process):
    def generate(self):
        i = 0
        while True:
            yield hold, self, self.sim.r.expovariate(1.0 / tInter)
            c = CarM("car{0}".format(i), sim=self.sim)
            self.sim.activate(c, c.lifecycle())
            i += 1

# Model 2: Car is master, carwash is slave


class CarWashModel2(Simulation):
    def run(self):
        print("\nModel 2: car is master")
        print("----------------------")
        self.initialize()
        self.r = random.Random()
        self.r.seed(initialSeed)
        self.washers = Store(capacity=nrMachines, sim=self)
        self.carsBeingWashed = []
        for j in range(1, 5):
            c = CarM("car{0}".format(-j), sim=self)
            self.activate(c, c.lifecycle())
        for i in range(2):
            cw = CarwashS("Carwash {0}".format(i), sim=self)
            self.activate(cw, cw.lifecycle())
        cg = CarGenerator1(sim=self)
        self.activate(cg, cg.generate())
        self.simulate(until=simTime)

        print("waiting cars: {0}".format([x.name for x in self.washers.getQ]))
        print("cars being washed: {0}".format(
            [x.name for x in self.carsBeingWashed]))


# Experiment 1 ----------------------------
CarWashModel2().run()
Model 1: carwash is master
--------------------------
3.5: car-1 done by Carwash 0
3.5: car-2 done by Carwash 1
7.0: car-3 done by Carwash 0
7.0: car-4 done by Carwash 1
17.5: car0 done by Carwash 0
17.5: car1 done by Carwash 1
21.0: car2 done by Carwash 0
21.0: car3 done by Carwash 1
24.5: car4 done by Carwash 0
24.5: car5 done by Carwash 1
28.0: car6 done by Carwash 0
28.0: car7 done by Carwash 1
31.5: car8 done by Carwash 0
31.5: car9 done by Carwash 1
35.0: car10 done by Carwash 0
35.0: car11 done by Carwash 1
38.5: car12 done by Carwash 0
38.5: car13 done by Carwash 1
42.0: car14 done by Carwash 0
42.0: car15 done by Carwash 1
45.5: car16 done by Carwash 0
45.5: car17 done by Carwash 1
49.0: car18 done by Carwash 0
49.0: car19 done by Carwash 1
52.5: car20 done by Carwash 0
52.5: car21 done by Carwash 1
56.0: car22 done by Carwash 0
56.0: car23 done by Carwash 1
59.5: car24 done by Carwash 0
59.5: car25 done by Carwash 1
63.0: car26 done by Carwash 0
63.0: car27 done by Carwash 1
66.5: car28 done by Carwash 0
66.5: car29 done by Carwash 1
70.0: car30 done by Carwash 0
70.0: car31 done by Carwash 1
73.5: car32 done by Carwash 0
73.5: car33 done by Carwash 1
77.0: car34 done by Carwash 0
77.0: car35 done by Carwash 1
80.5: car36 done by Carwash 0
80.5: car37 done by Carwash 1
84.0: car38 done by Carwash 0
84.0: car39 done by Carwash 1
87.5: car40 done by Carwash 0
87.5: car41 done by Carwash 1
91.0: car42 done by Carwash 0
91.0: car43 done by Carwash 1
94.5: car44 done by Carwash 0
94.5: car45 done by Carwash 1
98.0: car46 done by Carwash 0
98.0: car47 done by Carwash 1
waiting cars: ['car50', 'car51', 'car52', 'car53', 'car54', 'car55', 'car56', 'car57', 'car58', 'car59']
cars being washed: ['car48', 'car49']

Model 2: car is master
----------------------
3.5: car-1 done by Carwash 0
3.5: car-2 done by Carwash 1
7.0: car-3 done by Carwash 0
7.0: car-4 done by Carwash 1
10.5: car0 done by Carwash 0
10.5: car1 done by Carwash 1
14.0: car2 done by Carwash 0
14.0: car3 done by Carwash 1
17.5: car4 done by Carwash 0
17.5: car5 done by Carwash 1
21.0: car6 done by Carwash 0
21.0: car7 done by Carwash 1
24.5: car8 done by Carwash 0
24.5: car9 done by Carwash 1
28.0: car10 done by Carwash 0
28.0: car11 done by Carwash 1
31.5: car12 done by Carwash 0
31.5: car13 done by Carwash 1
35.0: car14 done by Carwash 0
35.0: car15 done by Carwash 1
38.5: car16 done by Carwash 0
38.5: car17 done by Carwash 1
42.0: car18 done by Carwash 0
42.0: car19 done by Carwash 1
45.5: car20 done by Carwash 0
45.5: car21 done by Carwash 1
49.0: car22 done by Carwash 0
49.0: car23 done by Carwash 1
52.5: car24 done by Carwash 0
52.5: car25 done by Carwash 1
56.0: car26 done by Carwash 0
56.0: car27 done by Carwash 1
59.5: car28 done by Carwash 0
59.5: car29 done by Carwash 1
63.0: car30 done by Carwash 0
63.0: car31 done by Carwash 1
66.5: car32 done by Carwash 0
66.5: car33 done by Carwash 1
70.0: car34 done by Carwash 0
70.0: car35 done by Carwash 1
73.5: car36 done by Carwash 0
73.5: car37 done by Carwash 1
77.0: car38 done by Carwash 0
77.0: car39 done by Carwash 1
80.5: car40 done by Carwash 0
80.5: car41 done by Carwash 1
84.0: car42 done by Carwash 0
84.0: car43 done by Carwash 1
87.5: car44 done by Carwash 0
87.5: car45 done by Carwash 1
91.0: car46 done by Carwash 0
91.0: car47 done by Carwash 1
94.5: car48 done by Carwash 0
94.5: car49 done by Carwash 1
98.0: car50 done by Carwash 0
98.0: car51 done by Carwash 1
waiting cars: ['car54', 'car55', 'car56', 'car57', 'car58', 'car59']
cars being washed: ['car52', 'car53']

Game of Life: CellularAutomata.py

A two-dimensional cellular automaton. Does the game of Life. (KGM)

from SimPy.Simulation import *
"""CellularAutomata.py
Simulation of two-dimensional cellular automata. Plays game of Life.
"""


class Autom(Process):
    def __init__(self, coords):
        Process.__init__(self)
        self.x = coords[0]
        self.y = coords[1]
        self.state = False

    def nrActiveNeighbours(self, x, y):
        nr = 0
        coords = [(xco + x, yco + y) for xco in (-1, 0, 1)
                  for yco in (-1, 0, 1) if not (xco == 0 and yco == 0)]

        for a_coord in coords:
            try:
                if cells[a_coord].state:
                    nr += 1
            except KeyError:
                # wrap around
                nux = divmod(a_coord[0], size)[1]
                nuy = divmod(a_coord[1], size)[1]
                if cells[(nux, nuy)].state:
                    nr += 1
        return nr

    def decide(self, nrActive):
        return (self.state and (nrActive == 2 or nrActive == 3) or
                               (nrActive == 3))

    def celllife(self):
        while True:
            # calculate next state
            temp = self.decide(self.nrActiveNeighbours(self.x, self.y))
            yield hold, self, 0.5
            # set next state
            self.state = temp
            yield hold, self, 0.5


class Show(Process):
    def __init__(self):
        Process.__init__(self)

    def picture(self):
        while True:
            print("Generation {0}".format(now()))
            for i in range(size):
                cls = " "
                for j in range(size):
                    if cells[(i, j)].state:
                        cls += " *"
                    else:
                        cls += " ."
                print(cls)
            print("")
            yield hold, self, 1


size = 20
cells = {}
initialize()
for i in range(size):
    for j in range(size):
        a = cells[(i, j)] = Autom((i, j))
        activate(a, a.celllife())

# R-pentomino
cells[(9, 3)].state = True
cells[(10, 3)].state = True
cells[(9, 4)].state = True
cells[(8, 4)].state = True
cells[(9, 5)].state = True

cells[(5, 5)].state = True
cells[(5, 6)].state = True
cells[(4, 5)].state = True
cells[(4, 6)].state = True
cells[(4, 7)].state = True
cells[(10, 10)].state = True
cells[(10, 11)].state = True
cells[(10, 12)].state = True
cells[(10, 13)].state = True
cells[(11, 10)].state = True
cells[(11, 11)].state = True
cells[(11, 12)].state = True
cells[(11, 13)].state = True

print('CellularAutomata')
s = Show()
whenToStartShowing = 10
activate(s, s.picture(), delay=whenToStartShowing)
nrGenerations = 30
simulate(until=nrGenerations)
CellularAutomata
Generation 10
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . * . * * . . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . .
  . . . . . * * * . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 11
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . * . . * . . . . . . . . . . . . . .
  . . * . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . * * . . . . * * . . . . . . .
  . . . . . . * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 12
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * * . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . * * . . . . . . . . . . . . .
  . . . . . * * . . . . * * . . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 13
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  . * * . * . . . . . . . . . . . . . . .
  . . . . . * * . . . . . . . . . . . . .
  . . . . . * * . . . . . . . . . . . . .
  . . . . * . . * . . . * * . . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 14
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * * . . . . . . . . . . . . . .
  . * * * * . . . . . . . . . . . . . . .
  . . . . * . * . . . . . . . . . . . . .
  . . . . * . . * . . . . . . . . . . . .
  . . . . * . . * . . . * * . . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 15
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . * . . * . . . . . . . . . . . . . .
  . * . . . * . . . . . . . . . . . . . .
  . * * . . . . . . . . . . . . . . . . .
  . . * . * . . . . . . . . . . . . . . .
  . . . * * . * * . . . . . . . . . . . .
  . . . . * . . * . . . * * . . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 16
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * * . . * . . . . . . . . . . . . . .
  . * . . . . . . . . . . . . . . . . . .
  . * * * . . . . . . . . . . . . . . . .
  . * * . * * . . . . . . . . . . . . . .
  . . . . * . * * . . . . . . . . . . . .
  . . . * * . . * . . . * * . . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 17
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  * . . * . . . . . . . . . . . . . . . .
  * . . * * . . . . . . . . . . . . . . .
  . * . . * * * . . . . . . . . . . . . .
  . . * . . . * * . . . . . . . . . . . .
  . . . * * . . * . . . * * . . . . . . .
  . . . . * * * . . . * . . * . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 18
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * * . . . . . . . . . . . . . .
  * * * * * . . . . . . . . . . . . . . .
  * * * * . . . . . . . . . . . . . . . .
  * * * * . . . . . . . . . . . . . . . .
  . * * . * . * * . . . . . . . . . . . .
  . . * . . . . * . . . . . . . . . . . .
  . . . * * . . * . . . * * . . . . . . .
  . . . * * * * . . . * . . * . . . . . .
  . . . . . * . . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 19
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . * . . * . . . . . . . . . . . . . .
  * . . . . * . . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . *
  . . . . * . . . . . . . . . . . . . . .
  * . . . . . * * . . . . . . . . . . . .
  . * * . * * . * * . . . . . . . . . . .
  . . * . . . . * . . . * * . . . . . . .
  . . . * . . * . . . * . . * . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 20
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * * . . * . . . . . . . . . . . . . .
  . . . . * * * . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . * . * * . * * * . . . . . . . . . . .
  . * * * . * . . * . . . . . . . . . . .
  . * * . * * . * * . . * * . . . . . . .
  . . . . . * * * . . * . . * . . . . . .
  . . . . . * * . . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 21
  . . * * * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  . * * . . . * . . . . . . . . . . . . .
  . . . . * * * . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . * . . . . . . . . . . . .
  . * . * * * * * * . . . . . . . . . . .
  * . . . . . . . . * . . . . . . . . . .
  . * . . . . . . * * . * * . . . . . . .
  . . . . . . . . * * * . . * . . . . . .
  . . . . . * . * . . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 22
  . . * * * . . . . . . . . . . . . . . .
  . * . . * * . . . . . . . . . . . . . .
  . * * * * . * . . . . . . . . . . . . .
  . . . . * . * . . . . . . . . . . . . .
  . . . . * * * . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . * * . * * . . . . . . . . . . .
  . . . . * * * * * . . . . . . . . . . .
  * * * . * * * . . * * . . . . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . * . . . . . * . . . . . .
  . . . . . . . . * . * . . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .

Generation 23
  . . * . . * . . . . . . . . . . . . . .
  . * . . . . . . . . . . . . . . . . . .
  . * * . . . * . . . . . . . . . . . . .
  . . * . . . * * . . . . . . . . . . . .
  . . . . * . * . . . . . . . . . . . . .
  . . . . . . . * . . . . . . . . . . . .
  . . . . * . . . * . . . . . . . . . . .
  . * . . . . . . . . . . . . . . . . . .
  . * . * * . . . * * * * . . . . . . . .
  . * . . . * * . . . * * * . . . . . . .
  . . . . . . . . . . . * . * . . . . . .
  . . . . . . . . . . . * . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .

Generation 24
  . * * . * . . . . . . . . . . . . . . .
  . * . . . . . . . . . . . . . . . . . .
  . * * . . . * * . . . . . . . . . . . .
  . * * * . . * * . . . . . . . . . . . .
  . . . . . * * . . . . . . . . . . . . .
  . . . . . * . * . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . * . * . . . . . . . . .
  * * . . * * . . . * . . * . . . . . . .
  . . * . * * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . * . . . . . .
  . . . . . . . . . . * * . * . . . . . .
  . . . . . . . . . . . * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .

Generation 25
  . * . . * . . . . . . . . . . . . . . .
  * . . * . . . . . . . . . . . . . . . .
  * . . * . . * * . . . . . . . . . . . .
  . * . * . . . . . . . . . . . . . . . .
  . . * . * * . . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . .
  . . . * * . . . . . . . . . . . . . . .
  . * * * * * . . . * . . . . . . . . . .
  . * . . . . . . . * . . . . . . . . . .
  . * . * * * . . . . . . . . . . . . . .
  . . . . . . . . . . . . * . . . . . . .
  . . . . . . . . . . * * . * . . . . . .
  . . . . . . . . . . * * * . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .

Generation 26
  * * * * * . . . . . . . . . . . . . . .
  * * * * * . . . . . . . . . . . . . . .
  * * . * * . . . . . . . . . . . . . . .
  . * . * . * * . . . . . . . . . . . . .
  . . * * * * . . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . * . . . * . . . . . . . . . . . . . .
  * * . . . . . . . . . . . . . . . . . .
  . . * . * . . . . . . . . . . . . . . .
  . . . . * . . . . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . * . . . . . . .
  . . . . . . . . . . . * . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * * . . . . . . . . . . . . . .

Generation 27
  . . . . . . . . . . . . . . . . . . . .
  . . . . . * . . . . . . . . . . . . . *
  . . . . . . . . . . . . . . . . . . . .
  * * . . . . * . . . . . . . . . . . . .
  . . * * . . . . . . . . . . . . . . . .
  . . . * . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  * * . . . . . . . . . . . . . . . . . .
  * * * . . . . . . . . . . . . . . . . .
  . * . * . . . . . . . . . . . . . . . .
  . . . * . . . . . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . * . . . . . . .
  . . . . . . . . . . . * . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . * . . * . . . . . . . . . . . . . .
  * . . . . * . . . . . . . . . . . . . .

Generation 28
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  * . . . . . . . . . . . . . . . . . . .
  . * * . . . . . . . . . . . . . . . . .
  . * * * * . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  * . * . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  * * . * . . . . . . . . . . . . . . . .
  . . * . . . . . . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . * . . . . . . .
  . . . . . . . . . . . * . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * * . . * . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 29
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . * . . . . . . . . . . . . . . . . . .
  * . . . . . . . . . . . . . . . . . . .
  . . . . * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  . * * . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  * . * . . . . . . . . . . . . . . . . .
  . * * . . . . . . . . . . . . . . . . .
  . * * . . . . . . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . * . . . . . . .
  . . . . . . . . . . . * . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * . . . . . . . . . . . . . . .
  . * * . * . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

Generation 30
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . * * * . . . . . . . . . . . . . . . .
  . * * . . . . . . . . . . . . . . . . .
  . . * . . . . . . . . . . . . . . . . .
  . . * . . . . . . . . . . . . . . . . .
  * . . * . . . . . . . . . . . . . . . .
  . * * . . . . . . . . * * . . . . . . .
  . . . . . . . . . . * . . * . . . . . .
  . . . . . . . . . . * . * . . . . . . .
  . . . . . . . . . . . * . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .
  . . . * . . . . . . . . . . . . . . . .
  . . * * * . . . . . . . . . . . . . . .
  . * . . * * . . . . . . . . . . . . . .
  . * * * . . . . . . . . . . . . . . . .
  . . . . . . . . . . . . . . . . . . . .

SimPy’s event signalling synchronisation constructs: demoSimPyEvents.py

Demo of the event signalling constructs. Three small simulations are included: Pavlov’s drooling dogs, an activity simulation where a job is completed after a number of parallel activities, and the simulation of a US-style 4-way stop intersection.

from SimPy.Simulation import *
import random
"""
   Demo of SimPy's event signalling synchronization constructs
"""

print('demoSimPyEvents')

# Pavlov's dogs
"""Scenario:
Dogs start to drool when Pavlov rings the bell.
"""


class BellMan(Process):
    def ring(self):
        while True:
            bell.signal()
            print("{0} {1} rings bell".format(now(), self.name))
            yield hold, self, 5


class PavlovDog(Process):
    def behave(self):
        while True:
            yield waitevent, self, bell
            print("{0} {1} drools".format(now(), self.name))


random.seed(111333555)
initialize()
bell = SimEvent("bell")
for i in range(4):
    p = PavlovDog("Dog {0}".format(i + 1))
    activate(p, p.behave())
b = BellMan("Pavlov")
activate(b, b.ring())
print("\n Pavlov's dogs")
simulate(until=10)

# PERT simulation
"""
Scenario:
A job (TotalJob) requires 10 parallel activities with random duration to be
completed.
"""


class Activity(Process):
    def __init__(self, name):
        Process.__init__(self, name)
        self.event = SimEvent("completion of {0}".format(self.name))
        allEvents.append(self.event)

    def perform(self):
        yield hold, self, random.randint(1, 100)
        self.event.signal()
        print("{0} Event '{1}' fired".format(now(), self.event.name))


class TotalJob(Process):
    def perform(self, allEvents):
        for e in allEvents:
            yield waitevent, self, e
        print(now(), "All done")


random.seed(111333555)
initialize()
allEvents = []
for i in range(10):
    a = Activity("Activity {0}".format(i + 1))
    activate(a, a.perform())
t = TotalJob()
activate(t, t.perform(allEvents))
print("\n PERT network simulation")
simulate(until=100)

# US-style4-way stop intersection
"""
Scenario:
At a US-style 4-way stop intersection, a car may only enter the intersection
when it is free.
Cars enter in FIFO manner.
"""


class Car(Process):
    def drive(self):
        print("{0:4.1f} {1} waiting to enter intersection".format(now(),
                                                                  self.name))
        yield queueevent, self, intersectionFree
        # Intersection free, enter  . .
        # Begin Critical Section
        yield hold, self, 1  # drive across
        print("{0:4.1f} {1} crossed intersection".format(now(), self.name))
        # End Critical Section
        intersectionFree.signal()


random.seed(111333555)
initialize()
intersectionFree = SimEvent("Intersection free")
intersectionFree.signal()
arrtime = 0.0
for i in range(20):
    c = Car("Car {0}".format(i + 1))
    activate(c, c.drive(), at=arrtime)
    arrtime += 0.2
print("\n 4-way stop intersection")
print(simulate(until=100))
demoSimPyEvents

 Pavlov's dogs
0 Pavlov rings bell
0 Dog 4 drools
0 Dog 3 drools
0 Dog 2 drools
0 Dog 1 drools
5 Pavlov rings bell
5 Dog 1 drools
5 Dog 2 drools
5 Dog 3 drools
5 Dog 4 drools
10 Pavlov rings bell
10 Dog 4 drools
10 Dog 3 drools
10 Dog 2 drools
10 Dog 1 drools

 PERT network simulation
13 Event 'completion of Activity 6' fired
19 Event 'completion of Activity 10' fired
22 Event 'completion of Activity 7' fired
42 Event 'completion of Activity 1' fired
48 Event 'completion of Activity 4' fired
55 Event 'completion of Activity 8' fired
69 Event 'completion of Activity 5' fired
83 Event 'completion of Activity 2' fired
90 Event 'completion of Activity 9' fired
93 Event 'completion of Activity 3' fired
93 All done

 4-way stop intersection
 0.0 Car 1 waiting to enter intersection
 0.2 Car 2 waiting to enter intersection
 0.4 Car 3 waiting to enter intersection
 0.6 Car 4 waiting to enter intersection
 0.8 Car 5 waiting to enter intersection
 1.0 Car 6 waiting to enter intersection
 1.0 Car 1 crossed intersection
 1.2 Car 7 waiting to enter intersection
 1.4 Car 8 waiting to enter intersection
 1.6 Car 9 waiting to enter intersection
 1.8 Car 10 waiting to enter intersection
 2.0 Car 11 waiting to enter intersection
 2.0 Car 2 crossed intersection
 2.2 Car 12 waiting to enter intersection
 2.4 Car 13 waiting to enter intersection
 2.6 Car 14 waiting to enter intersection
 2.8 Car 15 waiting to enter intersection
 3.0 Car 3 crossed intersection
 3.0 Car 16 waiting to enter intersection
 3.2 Car 17 waiting to enter intersection
 3.4 Car 18 waiting to enter intersection
 3.6 Car 19 waiting to enter intersection
 3.8 Car 20 waiting to enter intersection
 4.0 Car 4 crossed intersection
 5.0 Car 5 crossed intersection
 6.0 Car 6 crossed intersection
 7.0 Car 7 crossed intersection
 8.0 Car 8 crossed intersection
 9.0 Car 9 crossed intersection
10.0 Car 10 crossed intersection
11.0 Car 11 crossed intersection
12.0 Car 12 crossed intersection
13.0 Car 13 crossed intersection
14.0 Car 14 crossed intersection
15.0 Car 15 crossed intersection
16.0 Car 16 crossed intersection
17.0 Car 17 crossed intersection
18.0 Car 18 crossed intersection
19.0 Car 19 crossed intersection
20.0 Car 20 crossed intersection
SimPy: No more events at time 20

Find the Shortest Path: shortestPath_SimPy.py, shortestPath_SimPy_OO.py

A fun example of using SimPy for non-queuing work. It simulates a searcher through a graph, seeking the shortest path. (KGM)

from SimPy.Simulation import *
""" shortestPath_SimPy.py

    Finds the shortest path in a network.
    Author: Klaus Muller
"""


class node:
    def __init__(self):
        self.reached = 0


class searcher(Process):
    def __init__(self, graph, path, length, from_node, to_node,
                 distance, goal_node):
        Process.__init__(self)
        self.path = path[:]
        self.length = length
        self.from_node = from_node
        self.to_node = to_node
        self.distance = distance
        self.graph = graph
        self.goal_node = goal_node

    def run(self, to_node):
        if DEMO:
            print("Path so far: {0} (length {1}). "
                  "Search from {2} to {3}".format(self.path, self.length,
                                                  self.from_node, to_node))
        yield hold, self, self.distance
        if not nodes[to_node].reached:
            self.path.append(to_node)
            self.length += self.distance
            nodes[to_node].reached = 1
            if to_node == self.goal_node:
                print("SHORTEST PATH", self.path, "Length:", self.length)
                stopSimulation()
            else:
                for i in self.graph[to_node]:
                    s = searcher(graph=self.graph, path=self.path,
                                 length=self.length, from_node=i[0],
                                 to_node=i[1], distance=i[2],
                                 goal_node=self.goal_node)
                    activate(s, s.run(i[1]))


print('shortestPath_SimPy')
initialize()
nodes = {}
DEMO = 1
for i in ("Atown", "Btown", "Ccity", "Dpueblo", "Evillage", "Fstadt"):
    nodes[i] = node()
""" Format graph definition:
a_graph={node_id:[(from,to,distance),
                  (from,to,distance)],
                  node_id:[ . . . ])
"""
net = {"Atown": (("Atown", "Btown", 3.5), ("Atown", "Ccity", 1),
                 ("Atown", "Atown", 9), ("Atown", "Evillage", 0.5)),
       "Btown": (("Btown", "Ccity", 5),),
       "Ccity": (("Ccity", "Ccity", 1), ("Ccity", "Fstadt", 9),
                 ("Ccity", "Dpueblo", 3), ("Ccity", "Atown", 3)),
       "Dpueblo": (("Dpueblo", "Ccity", 2), ("Dpueblo", "Fstadt", 10)),
       "Evillage": (("Evillage", "Btown", 1),),
       "Fstadt": (("Fstadt", "Ccity", 3),)}
if DEMO:
    print("Search for shortest path from {0} to {1} \nin graph {2}".format(
        "Atown", "Fstadt", sorted(net.items())))
startup = searcher(graph=net, path=[], length=0, from_node="Atown",
                   to_node="Atown", distance=0, goal_node="Fstadt")
activate(startup, startup.run("Atown"))
simulate(until=10000)
shortestPath_SimPy
Search for shortest path from Atown to Fstadt 
in graph [('Atown', (('Atown', 'Btown', 3.5), ('Atown', 'Ccity', 1), ('Atown', 'Atown', 9), ('Atown', 'Evillage', 0.5))), ('Btown', (('Btown', 'Ccity', 5),)), ('Ccity', (('Ccity', 'Ccity', 1), ('Ccity', 'Fstadt', 9), ('Ccity', 'Dpueblo', 3), ('Ccity', 'Atown', 3))), ('Dpueblo', (('Dpueblo', 'Ccity', 2), ('Dpueblo', 'Fstadt', 10))), ('Evillage', (('Evillage', 'Btown', 1),)), ('Fstadt', (('Fstadt', 'Ccity', 3),))]
Path so far: [] (length 0). Search from Atown to Atown
Path so far: ['Atown'] (length 0). Search from Atown to Btown
Path so far: ['Atown'] (length 0). Search from Atown to Ccity
Path so far: ['Atown'] (length 0). Search from Atown to Atown
Path so far: ['Atown'] (length 0). Search from Atown to Evillage
Path so far: ['Atown', 'Evillage'] (length 0.5). Search from Evillage to Btown
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Ccity
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Fstadt
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Dpueblo
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Atown
Path so far: ['Atown', 'Evillage', 'Btown'] (length 1.5). Search from Btown to Ccity
Path so far: ['Atown', 'Ccity', 'Dpueblo'] (length 4). Search from Dpueblo to Ccity
Path so far: ['Atown', 'Ccity', 'Dpueblo'] (length 4). Search from Dpueblo to Fstadt
SHORTEST PATH ['Atown', 'Ccity', 'Fstadt'] Length: 10

Here is the OO version:

from SimPy.Simulation import *
""" shortestPath_SimPy_OO.py

    Finds the shortest path in a network.
    Author: Klaus Muller
"""
# Model components ------------------------


class node:
    def __init__(self):
        self.reached = 0


class searcher(Process):
    def __init__(self, graph, path, length, from_node,
                 to_node, distance, goal_node, sim):
        Process.__init__(self, sim=sim)
        self.path = path[:]
        self.length = length
        self.from_node = from_node
        self.to_node = to_node
        self.distance = distance
        self.graph = graph
        self.goal_node = goal_node

    def run(self, to_node):
        if DEMO:
            print("Path so far: {0} (length {1}). "
                  "Search from {2} to {3}".format(self.path,
                                                  self.length,
                                                  self.from_node,
                                                  to_node))
        yield hold, self, self.distance
        if not self.sim.nodes[to_node].reached:
            self.path.append(to_node)
            self.length += self.distance
            self.sim.nodes[to_node].reached = 1
            if to_node == self.goal_node:
                print("SHORTEST PATH", self.path, "Length:", self.length)
                self.sim.stopSimulation()
            else:
                for i in self.graph[to_node]:
                    s = searcher(graph=self.graph, path=self.path,
                                 length=self.length, from_node=i[0],
                                 to_node=i[1], distance=i[2],
                                 goal_node=self.goal_node, sim=self.sim)
                    self.sim.activate(s, s.run(i[1]))

# Model -----------------------------------


class ShortestPathModel(Simulation):
    def search(self):
        print('shortestPath_SimPy')
        self.initialize()
        self.nodes = {}
        for i in ("Atown", "Btown", "Ccity", "Dpueblo", "Evillage", "Fstadt"):
            self.nodes[i] = node()
        """ Format graph definition:
        a_graph={node_id:[(from,to,distance),
                          (from,to,distance)],
                          node_id:[ . . . ])
        """
        net = {"Atown": (("Atown", "Btown", 3.5), ("Atown", "Ccity", 1),
                         ("Atown", "Atown", 9), ("Atown", "Evillage", 0.5)),
               "Btown": (("Btown", "Ccity", 5),),
               "Ccity": (("Ccity", "Ccity", 1), ("Ccity", "Fstadt", 9),
                         ("Ccity", "Dpueblo", 3), ("Ccity", "Atown", 3)),
               "Dpueblo": (("Dpueblo", "Ccity", 2), ("Dpueblo", "Fstadt", 10)),
               "Evillage": (("Evillage", "Btown", 1),),
               "Fstadt": (("Fstadt", "Ccity", 3),)}
        if DEMO:
            print("Search for shortest path from {0} to {1} \n"
                  "in graph {2}".format("Atown",
                                        "Fstadt",
                                        sorted(net.items())))
        startup = searcher(graph=net, path=[], length=0, from_node="Atown",
                           to_node="Atown", distance=0, goal_node="Fstadt",
                           sim=self)
        self.activate(startup, startup.run("Atown"))
        self.simulate(until=10000)


# Experiment ------------------------------
DEMO = 1
ShortestPathModel().search()
shortestPath_SimPy
Search for shortest path from Atown to Fstadt 
in graph [('Atown', (('Atown', 'Btown', 3.5), ('Atown', 'Ccity', 1), ('Atown', 'Atown', 9), ('Atown', 'Evillage', 0.5))), ('Btown', (('Btown', 'Ccity', 5),)), ('Ccity', (('Ccity', 'Ccity', 1), ('Ccity', 'Fstadt', 9), ('Ccity', 'Dpueblo', 3), ('Ccity', 'Atown', 3))), ('Dpueblo', (('Dpueblo', 'Ccity', 2), ('Dpueblo', 'Fstadt', 10))), ('Evillage', (('Evillage', 'Btown', 1),)), ('Fstadt', (('Fstadt', 'Ccity', 3),))]
Path so far: [] (length 0). Search from Atown to Atown
Path so far: ['Atown'] (length 0). Search from Atown to Btown
Path so far: ['Atown'] (length 0). Search from Atown to Ccity
Path so far: ['Atown'] (length 0). Search from Atown to Atown
Path so far: ['Atown'] (length 0). Search from Atown to Evillage
Path so far: ['Atown', 'Evillage'] (length 0.5). Search from Evillage to Btown
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Ccity
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Fstadt
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Dpueblo
Path so far: ['Atown', 'Ccity'] (length 1). Search from Ccity to Atown
Path so far: ['Atown', 'Evillage', 'Btown'] (length 1.5). Search from Btown to Ccity
Path so far: ['Atown', 'Ccity', 'Dpueblo'] (length 4). Search from Dpueblo to Ccity
Path so far: ['Atown', 'Ccity', 'Dpueblo'] (length 4). Search from Dpueblo to Fstadt
SHORTEST PATH ['Atown', 'Ccity', 'Fstadt'] Length: 10

Machine Shop Model: Machineshop.py, Machineshop_OO.py

A workshop has n identical machines. A stream of jobs (enough to keep the machines busy) arrives. Each machine breaks down periodically. Repairs are carried out by one repairman. The repairman has other, less important tasks to perform, too. Once he starts one of those, he completes it before starting with the machine repair. The workshop works continuously.

This is an example of the use of the interrupt() method. (KGM)

from SimPy.Simulation import *
import random
"""
Machineshop Model

An example showing interrupts and priority queuing.

Scenario:

A workshop has n identical machines. A stream of jobs (enough to keep
the machines busy) arrives. Each machine breaks down
periodically. Repairs are carried out by one repairman. The repairman
has other, less important tasks to perform, too. Once he starts one of
those, he completes it before starting with the machine repair. The
workshop works continously.  """

# Model components ------------------------


class Machine(Process):
    def __init__(self, name):
        Process.__init__(self, name)
        myBreaker = Breakdown(self)
        activate(myBreaker, myBreaker.breakmachine())
        self.partsMade = 0

    def working(self):
        while True:
            yield hold, self, timePerPart()
            if self.interrupted():
                # broken down
                parttimeleft = self.interruptLeft
                yield request, self, repairman, 1
                yield hold, self, repairtime
                # repaired
                yield release, self, repairman
                yield hold, self, parttimeleft
                # part completed
                self.partsMade += 1
            else:
                # part made
                self.partsMade += 1


class Breakdown(Process):
    def __init__(self, myMachine):
        Process.__init__(self)
        self.myMachine = myMachine

    def breakmachine(self):
        while True:
            yield hold, self, timeToFailure()
            self.interrupt(self.myMachine)


class OtherJobs(Process):
    def __init__(self):
        Process.__init__(self)

    def doingJobs(self):
        while True:
            yield request, self, repairman, 0
            # starts working on jobs
            yield hold, self, jobDuration
            yield release, self, repairman


def timePerPart():
    return random.normalvariate(processingTimeMean, processingTimeSigma)


def timeToFailure():
    return random.expovariate(mean)

# Experiment data -------------------------


repairtime = 30.0           # minutes
processingTimeMean = 10.0   # minutes
processingTimeSigma = 2.0
timeToFailureMean = 300.0  # minutes
mean = 1 / timeToFailureMean  # per minute
jobDuration = 30            # minutes
nrMachines = 10
random.seed(111333555)
weeks = 4  # weeks
simTime = weeks * 24 * 60 * 7  # minutes

# Model/Experiment ------------------------------

print('Machineshop')
initialize()
repairman = Resource(capacity=1, qType=PriorityQ)
m = {}
for i in range(nrMachines):
    m[i + 1] = Machine(name="Machine {0}".format(i + 1))
    activate(m[i + 1], m[i + 1].working())
oj = OtherJobs()
activate(oj, oj.doingJobs())
simulate(until=simTime)  # minutes

# Analysis/output -------------------------

print("Machineshop results after {0} weeks".format(weeks))
for i in range(nrMachines):
    print("Machine {0}: {1}".format(i + 1, m[i + 1].partsMade))
Machineshop
Machineshop results after 4 weeks
Machine 1: 3262
Machine 2: 3353
Machine 3: 3161
Machine 4: 3200
Machine 5: 3296
Machine 6: 3139
Machine 7: 3287
Machine 8: 3248
Machine 9: 3273
Machine 10: 3217

Here is the OO version:

from SimPy.Simulation import *
import random
""" Machineshop_OO.py
Machineshop Model

An example showing interrupts and priority queuing.

Scenario:

A workshop has n identical machines. A stream of jobs (enough to keep
the machines busy) arrives. Each machine breaks down
periodically. Repairs are carried out by one repairman. The repairman
has other, less important tasks to perform, too. Once he starts one of
those, he completes it before starting with the machine repair. The
workshop works continously.  """

# Model components ------------------------


class Machine(Process):
    def __init__(self, name, sim):
        Process.__init__(self, name=name, sim=sim)
        myBreaker = Breakdown(self, sim=sim)
        sim.activate(myBreaker, myBreaker.breakmachine())
        self.partsMade = 0

    def working(self):
        while True:
            yield hold, self, timePerPart()
            if self.interrupted():
                # broken down
                parttimeleft = self.interruptLeft
                yield request, self, self.sim.repairman, 1
                yield hold, self, repairtime
                # repaired
                yield release, self, self.sim.repairman
                yield hold, self, parttimeleft
                # part completed
                self.partsMade += 1
            else:
                # part made
                self.partsMade += 1


class Breakdown(Process):
    def __init__(self, myMachine, sim):
        Process.__init__(self, sim=sim)
        self.myMachine = myMachine

    def breakmachine(self):
        while True:
            yield hold, self, timeToFailure()
            self.interrupt(self.myMachine)


class OtherJobs(Process):

    def doingJobs(self):
        while True:
            yield request, self, self.sim.repairman, 0
            # starts working on jobs
            yield hold, self, jobDuration
            yield release, self, self.sim.repairman


def timePerPart():
    return random.normalvariate(processingTimeMean, processingTimeSigma)


def timeToFailure():
    return random.expovariate(mean)

# Experiment data -------------------------


repairtime = 30.0           # minutes
processingTimeMean = 10.0   # minutes
processingTimeSigma = 2.0
timeToFailureMean = 300.0  # minutes
mean = 1 / timeToFailureMean  # per minute
jobDuration = 30            # minutes
nrMachines = 10
random.seed(111333555)
weeks = 4  # weeks
simTime = weeks * 24 * 60 * 7  # minutes

# Model


class MachineshopModel(Simulation):
    def run(self):
        print('Machineshop')
        self.initialize()
        self.repairman = Resource(capacity=1, qType=PriorityQ, sim=self)
        self.m = {}
        for i in range(nrMachines):
            self.m[i + 1] = Machine(name="Machine {0}".format(i + 1), sim=self)
            self.activate(self.m[i + 1], self.m[i + 1].working())
        oj = OtherJobs(sim=self)
        self.activate(oj, oj.doingJobs())
        self.simulate(until=simTime)  # minutes


# Experiment ------------------------------
model = MachineshopModel()
model.run()

# Analysis/output -------------------------

print("Machineshop results after {0} weeks".format(weeks))
for i in range(nrMachines):
    print("Machine {0}: {1}".format(i + 1, model.m[i + 1].partsMade))
Machineshop
Machineshop results after 4 weeks
Machine 1: 3262
Machine 2: 3353
Machine 3: 3161
Machine 4: 3200
Machine 5: 3296
Machine 6: 3139
Machine 7: 3287
Machine 8: 3248
Machine 9: 3273
Machine 10: 3217

Supermarket: Market.py, Market_OO.py

A supermarket checkout with multiple counters and extended Monitor objects. Written and analysed by David Mertz in an article for developerWorks (). (MM)

""" Market.py
Model of a supermarket.
"""
from SimPy.Simulation import *
import random
from math import sqrt


# Model components ------------------------

class Customer(Process):
    def __init__(self):
        Process.__init__(self)
        # Randomly pick how many items this customer is buying
        self.items = 1 + int(random.expovariate(1.0 / AVGITEMS))

    def checkout(self):
        start = now()           # Customer decides to check out
        yield request, self, checkout_aisle
        at_checkout = now()     # Customer gets to front of line
        waittime.tally(at_checkout - start)
        yield hold, self, self.items * ITEMTIME
        leaving = now()         # Customer completes purchase
        checkouttime.tally(leaving - at_checkout)
        yield release, self, checkout_aisle


class Customer_Factory(Process):
    def run(self):
        while 1:
            c = Customer()
            activate(c, c.checkout())
            arrival = random.expovariate(float(AVGCUST) / CLOSING)
            yield hold, self, arrival


class Monitor2(Monitor):
    def __init__(self):
        Monitor.__init__(self)
        self.min, self.max = (sys.maxsize, 0)

    def tally(self, x):
        self.observe(x)
        self.min = min(self.min, x)
        self.max = max(self.max, x)

# Experiment data -------------------------


AISLES = 6         # Number of open aisles
ITEMTIME = 0.1     # Time to ring up one item
AVGITEMS = 20      # Average number of items purchased
CLOSING = 60 * 12    # Minutes from store open to store close
AVGCUST = 1500     # Average number of daily customers
RUNS = 8           # Number of times to run the simulation
SEED = 111333555   # seed value for random numbers


# Model/Experiment ------------------------------

random.seed(SEED)
print('Market')
for run in range(RUNS):
    waittime = Monitor2()
    checkouttime = Monitor2()
    checkout_aisle = Resource(AISLES)
    initialize()
    cf = Customer_Factory()
    activate(cf, cf.run(), 0.0)
    simulate(until=CLOSING)
    FMT = "Waiting time average: {0:.1f} (std dev {1:.1f}, maximum {2:.1f})"
    print(FMT.format(waittime.mean(), sqrt(waittime.var()), waittime.max))

# Analysis/output -------------------------

print('AISLES:', AISLES, '  ITEM TIME:', ITEMTIME)
Market
Waiting time average: 0.5 (std dev 1.0, maximum 6.2)
Waiting time average: 0.3 (std dev 0.7, maximum 3.8)
Waiting time average: 0.3 (std dev 0.5, maximum 3.1)
Waiting time average: 0.3 (std dev 0.7, maximum 4.1)
Waiting time average: 0.3 (std dev 0.6, maximum 3.5)
Waiting time average: 0.3 (std dev 0.6, maximum 4.3)
Waiting time average: 0.4 (std dev 0.9, maximum 5.6)
Waiting time average: 0.2 (std dev 0.5, maximum 3.7)
AISLES: 6   ITEM TIME: 0.1

Here is the OO version:

""" Market_OO.py
Model of a supermarket.
"""
from SimPy.Simulation import *
import random
from math import sqrt


# Model components ------------------------

class Customer(Process):
    def __init__(self, sim):
        Process.__init__(self, sim=sim)
        # Randomly pick how many items this customer is buying
        self.items = 1 + int(random.expovariate(1.0 / AVGITEMS))

    def checkout(self):
        start = self.sim.now()           # Customer decides to check out
        yield request, self, self.sim.checkout_aisle
        at_checkout = self.sim.now()     # Customer gets to front of line
        self.sim.waittime.tally(at_checkout - start)
        yield hold, self, self.items * ITEMTIME
        leaving = self.sim.now()         # Customer completes purchase
        self.sim.checkouttime.tally(leaving - at_checkout)
        yield release, self, self.sim.checkout_aisle


class Customer_Factory(Process):
    def run(self):
        while 1:
            c = Customer(sim=self.sim)
            self.sim.activate(c, c.checkout())
            arrival = random.expovariate(float(AVGCUST) / CLOSING)
            yield hold, self, arrival


class Monitor2(Monitor):
    def __init__(self, sim):
        Monitor.__init__(self, sim=sim)
        self.min, self.max = (sys.maxsize, 0)

    def tally(self, x):
        self.observe(x)
        self.min = min(self.min, x)
        self.max = max(self.max, x)

# Experiment data -------------------------


AISLES = 6         # Number of open aisles
ITEMTIME = 0.1     # Time to ring up one item
AVGITEMS = 20      # Average number of items purchased
CLOSING = 60 * 12    # Minutes from store open to store close
AVGCUST = 1500     # Average number of daily customers
RUNS = 8           # Number of times to run the simulation
SEED = 111333555   # seed value for random numbers


# Model
class MarketModel(Simulation):
    def runs(self):

        random.seed(SEED)
        print('Market')
        for run in range(RUNS):
            self.initialize()
            self.waittime = Monitor2(sim=self)
            self.checkouttime = Monitor2(sim=self)
            self.checkout_aisle = Resource(capacity=AISLES, sim=self)

            cf = Customer_Factory(sim=self)
            self.activate(cf, cf.run(), 0.0)
            self.simulate(until=CLOSING)
            # Analysis/output -------------
            FMT = ("Waiting time average: {0:.1f} "
                   "(std dev {1:.1f}, maximum {2:.1f})")
            print(FMT.format(self.waittime.mean(),
                             sqrt(self.waittime.var()),
                             self.waittime.max))


# Experiment ------------------------------
MarketModel().runs()
print('AISLES:', AISLES, '  ITEM TIME:', ITEMTIME)
Market
Waiting time average: 0.5 (std dev 1.0, maximum 6.2)
Waiting time average: 0.3 (std dev 0.7, maximum 3.8)
Waiting time average: 0.3 (std dev 0.5, maximum 3.1)
Waiting time average: 0.3 (std dev 0.7, maximum 4.1)
Waiting time average: 0.3 (std dev 0.6, maximum 3.5)
Waiting time average: 0.3 (std dev 0.6, maximum 4.3)
Waiting time average: 0.4 (std dev 0.9, maximum 5.6)
Waiting time average: 0.2 (std dev 0.5, maximum 3.7)
AISLES: 6   ITEM TIME: 0.1

Movie Theatre Ticket Counter: Movie_renege.py, Movie_renege_OO.py

Use of reneging (compound yield request) constructs for reneging at occurrence of an event. Scenario is a movie ticket counter with a limited number of tickets for three movies (next show only). When a movie is sold out, all people waiting to buy ticket for that movie renege (leave queue).

"""
Movie_renege

Demo program to show event-based reneging by
'yield (request,self,res),(waitevent,self,evt)'.

Scenario:
A movie theatre has one ticket counter selling tickets for
three movies (next show only). When a movie is sold out, all
people waiting to buy ticket for that movie renege (leave queue).
"""
from SimPy.Simulation import *
from random import *

# Model components ------------------------


class MovieGoer(Process):
    def getTickets(self, whichMovie, nrTickets):
        yield ((request, self, ticketCounter),
               (waitevent, self, soldOut[whichMovie]))
        if self.acquired(ticketCounter):
            if available[whichMovie] >= nrTickets:
                available[whichMovie] -= nrTickets
                if available[whichMovie] < 2:
                    soldOut[whichMovie].signal()
                    whenSoldOut[whichMovie] = now()
                    available[whichMovie] = 0
                yield hold, self, 1
            else:
                yield hold, self, 0.5
            yield release, self, ticketCounter
        else:
            nrRenegers[whichMovie] += 1


class CustomerArrivals(Process):
    def traffic(self):
        while now() < 120:
            yield hold, self, expovariate(1 / 0.5)
            movieChoice = choice(movies)
            nrTickets = randint(1, 6)
            if available[movieChoice]:
                m = MovieGoer()
                activate(m, m.getTickets(
                    whichMovie=movieChoice, nrTickets=nrTickets))


# Experiment data -------------------------
seed(111333555)
movies = ["Gone with the Windows", "Hard Core Dump", "Modern CPU Times"]

available = {}
soldOut = {}
nrRenegers = {}
whenSoldOut = {}
for film in movies:
    available[film] = 50
    soldOut[film] = SimEvent(film)
    nrRenegers[film] = 0
ticketCounter = Resource(capacity=1)

# Model/Experiment ------------------------------
print('Movie_renege')
initialize()
c = CustomerArrivals()
activate(c, c.traffic())
simulate(until=120)

for f in movies:
    if soldOut[f]:
        FMT = ("Movie '{0}' sold out {1:.0f} minutes after ticket counter "
               "opening.")
        print(FMT.format(f, int(whenSoldOut[f])))
        FMT = "\tNr people leaving queue when film '{0}' sold out: {1}"
        print(FMT.format(f, nrRenegers[f]))
Movie_renege
Movie 'Gone with the Windows' sold out 41 minutes after ticket counter opening.
	Nr people leaving queue when film 'Gone with the Windows' sold out: 9
Movie 'Hard Core Dump' sold out 37 minutes after ticket counter opening.
	Nr people leaving queue when film 'Hard Core Dump' sold out: 8
Movie 'Modern CPU Times' sold out 33 minutes after ticket counter opening.
	Nr people leaving queue when film 'Modern CPU Times' sold out: 9

Here is the OO version:

"""Movie_renege.py

Demo program to show event-based reneging by
'yield (request,self,res),(waitevent,self,evt)'.

Scenario:
A movie theatre has one ticket counter selling tickets for
three movies (next show only). When a movie is sold out, all
people waiting to buy ticket for that movie renege (leave queue).
"""
from SimPy.Simulation import *
from random import *

# Model components ------------------------


class MovieGoer(Process):
    def getTickets(self, whichMovie, nrTickets):
        yield ((request, self, self.sim.ticketCounter),
               (waitevent, self, self.sim.soldOut[whichMovie]))
        if self.acquired(self.sim.ticketCounter):
            if self.sim.available[whichMovie] >= nrTickets:
                self.sim.available[whichMovie] -= nrTickets
                if self.sim.available[whichMovie] < 2:
                    self.sim.soldOut[whichMovie].signal()
                    self.sim.whenSoldOut[whichMovie] = self.sim.now()
                    self.sim.available[whichMovie] = 0
                yield hold, self, 1
            else:
                yield hold, self, 0.5
            yield release, self, self.sim.ticketCounter
        else:
            self.sim.nrRenegers[whichMovie] += 1


class CustomerArrivals(Process):
    def traffic(self):
        while self.sim.now() < 120:
            yield hold, self, expovariate(1 / 0.5)
            movieChoice = choice(movies)
            nrTickets = randint(1, 6)
            if self.sim.available[movieChoice]:
                m = MovieGoer(sim=self.sim)
                self.sim.activate(m, m.getTickets(
                    whichMovie=movieChoice, nrTickets=nrTickets))


# Experiment data -------------------------
seed(111333555)
movies = ["Gone with the Windows", "Hard Core Dump", "Modern CPU Times"]

# Model -----------------------------------


class MovieRenegeModel(Simulation):
    def run(self):
        print('Movie_renege')
        self.initialize()
        self.available = {}
        self.soldOut = {}
        self.nrRenegers = {}
        self.whenSoldOut = {}
        for film in movies:
            self.available[film] = 50
            self.soldOut[film] = SimEvent(film, sim=self)
            self.nrRenegers[film] = 0
        self.ticketCounter = Resource(capacity=1, sim=self)
        c = CustomerArrivals(sim=self)
        self.activate(c, c.traffic())
        self.simulate(until=120)


# Experiment ------------------------------
model = MovieRenegeModel()
model.run()

# Analysis/output -------------------------
for f in movies:
    if model.soldOut[f]:
        FMT = ("Movie '{0}' sold out {1:.0f} minutes after ticket counter "
               "opening.")
        print(FMT.format(f, int(model.whenSoldOut[f])))
        FMT = "\tNr people leaving queue when film '{0}' sold out: {1}"
        print(FMT.format(f, model.nrRenegers[f]))
Movie_renege
Movie 'Gone with the Windows' sold out 41 minutes after ticket counter opening.
	Nr people leaving queue when film 'Gone with the Windows' sold out: 9
Movie 'Hard Core Dump' sold out 37 minutes after ticket counter opening.
	Nr people leaving queue when film 'Hard Core Dump' sold out: 8
Movie 'Modern CPU Times' sold out 33 minutes after ticket counter opening.
	Nr people leaving queue when film 'Modern CPU Times' sold out: 9

Workers Sharing Tools, waitUntil: needResources.py, needResources_OO.py

Demo of waitUntil capability. It simulates three workers each requiring a set of tools to do their jobs. Tools are shared, scarce resources for which they compete.

from SimPy.Simulation import *
import random

"""
needResources.py

Demo of waitUntil capability.

Scenario:
Three workers require sets of tools to do their jobs. Tools are shared, scarce
resources for which they compete.
"""


class Worker(Process):
    def work(self, heNeeds=[]):
        def workerNeeds():
            for item in heNeeds:
                if item.n == 0:
                    return False
            return True

        while now() < 8 * 60:
            yield waituntil, self, workerNeeds
            for item in heNeeds:
                yield request, self, item
            print("{0} {1} has {2} and starts job"
                  .format(now(),
                          self.name,
                          [x.name for x in heNeeds]))
            yield hold, self, random.uniform(10, 30)
            for item in heNeeds:
                yield release, self, item
            yield hold, self, 2  # rest


random.seed(111333555)
print('needResources')
initialize()
brush = Resource(capacity=1, name="brush")
ladder = Resource(capacity=2, name="ladder")
hammer = Resource(capacity=1, name="hammer")
saw = Resource(capacity=1, name="saw")
painter = Worker("painter")
activate(painter, painter.work([brush, ladder]))
roofer = Worker("roofer")
activate(roofer, roofer.work([hammer, ladder, ladder]))
treeguy = Worker("treeguy")
activate(treeguy, treeguy.work([saw, ladder]))
print(simulate(until=9 * 60))
needResources
0 painter has ['brush', 'ladder'] and starts job
16.4985835442738 roofer has ['hammer', 'ladder', 'ladder'] and starts job
39.41544751014284 treeguy has ['saw', 'ladder'] and starts job
39.41544751014284 painter has ['brush', 'ladder'] and starts job
56.801814464340836 roofer has ['hammer', 'ladder', 'ladder'] and starts job
75.30359743269759 painter has ['brush', 'ladder'] and starts job
75.30359743269759 treeguy has ['saw', 'ladder'] and starts job
103.081870230719 roofer has ['hammer', 'ladder', 'ladder'] and starts job
118.17856121225341 painter has ['brush', 'ladder'] and starts job
118.17856121225341 treeguy has ['saw', 'ladder'] and starts job
143.25197748702192 roofer has ['hammer', 'ladder', 'ladder'] and starts job
170.86533741129648 treeguy has ['saw', 'ladder'] and starts job
170.86533741129648 painter has ['brush', 'ladder'] and starts job
196.3748629530008 roofer has ['hammer', 'ladder', 'ladder'] and starts job
217.8426181665415 treeguy has ['saw', 'ladder'] and starts job
217.8426181665415 painter has ['brush', 'ladder'] and starts job
244.3699827271992 roofer has ['hammer', 'ladder', 'ladder'] and starts job
269.8696310798061 painter has ['brush', 'ladder'] and starts job
269.8696310798061 treeguy has ['saw', 'ladder'] and starts job
298.28127810116723 roofer has ['hammer', 'ladder', 'ladder'] and starts job
316.83616616186885 treeguy has ['saw', 'ladder'] and starts job
316.83616616186885 painter has ['brush', 'ladder'] and starts job
345.4248822185373 roofer has ['hammer', 'ladder', 'ladder'] and starts job
359.2558219106454 painter has ['brush', 'ladder'] and starts job
359.2558219106454 treeguy has ['saw', 'ladder'] and starts job
385.7096335445383 roofer has ['hammer', 'ladder', 'ladder'] and starts job
407.43697134395245 painter has ['brush', 'ladder'] and starts job
407.43697134395245 treeguy has ['saw', 'ladder'] and starts job
427.5649730427561 roofer has ['hammer', 'ladder', 'ladder'] and starts job
451.6157857370266 painter has ['brush', 'ladder'] and starts job
451.6157857370266 treeguy has ['saw', 'ladder'] and starts job
472.97306394134876 roofer has ['hammer', 'ladder', 'ladder'] and starts job
492.3976188422561 treeguy has ['saw', 'ladder'] and starts job
492.3976188422561 painter has ['brush', 'ladder'] and starts job
SimPy: No more events at time 515.0226394595458

Here is the OO version:

from SimPy.Simulation import *
import random

"""
needResources.py

Demo of waitUntil capability.

Scenario:
Three workers require sets of tools to do their jobs. Tools are shared, scarce
resources for which they compete.
"""

# Model components ------------------------


class Worker(Process):
    def work(self, heNeeds=[]):
        def workerNeeds():
            for item in heNeeds:
                if item.n == 0:
                    return False
            return True

        while self.sim.now() < 8 * 60:
            yield waituntil, self, workerNeeds
            for item in heNeeds:
                yield request, self, item
            print("{0} {1} has {2} and starts job"
                  .format(self.sim.now(),
                          self.name,
                          [x.name for x in heNeeds]))
            yield hold, self, random.uniform(10, 30)
            for item in heNeeds:
                yield release, self, item
            yield hold, self, 2  # rest


# Model -----------------------------------
class NeedResourcesModel(Simulation):
    def run(self):
        print('needResources')
        self.initialize()
        brush = Resource(capacity=1, name="brush", sim=self)
        ladder = Resource(capacity=2, name="ladder", sim=self)
        hammer = Resource(capacity=1, name="hammer", sim=self)
        saw = Resource(capacity=1, name="saw", sim=self)
        painter = Worker("painter", sim=self)
        self.activate(painter, painter.work([brush, ladder]))
        roofer = Worker("roofer", sim=self)
        self.activate(roofer, roofer.work([hammer, ladder, ladder]))
        treeguy = Worker("treeguy", sim=self)
        self.activate(treeguy, treeguy.work([saw, ladder]))
        print(self.simulate(until=9 * 60))


# Experiment data -------------------------
SEED = 111333555

# Experiment ------------------------------
random.seed(SEED)
NeedResourcesModel().run()
needResources
0 painter has ['brush', 'ladder'] and starts job
16.4985835442738 roofer has ['hammer', 'ladder', 'ladder'] and starts job
39.41544751014284 treeguy has ['saw', 'ladder'] and starts job
39.41544751014284 painter has ['brush', 'ladder'] and starts job
56.801814464340836 roofer has ['hammer', 'ladder', 'ladder'] and starts job
75.30359743269759 painter has ['brush', 'ladder'] and starts job
75.30359743269759 treeguy has ['saw', 'ladder'] and starts job
103.081870230719 roofer has ['hammer', 'ladder', 'ladder'] and starts job
118.17856121225341 painter has ['brush', 'ladder'] and starts job
118.17856121225341 treeguy has ['saw', 'ladder'] and starts job
143.25197748702192 roofer has ['hammer', 'ladder', 'ladder'] and starts job
170.86533741129648 treeguy has ['saw', 'ladder'] and starts job
170.86533741129648 painter has ['brush', 'ladder'] and starts job
196.3748629530008 roofer has ['hammer', 'ladder', 'ladder'] and starts job
217.8426181665415 treeguy has ['saw', 'ladder'] and starts job
217.8426181665415 painter has ['brush', 'ladder'] and starts job
244.3699827271992 roofer has ['hammer', 'ladder', 'ladder'] and starts job
269.8696310798061 painter has ['brush', 'ladder'] and starts job
269.8696310798061 treeguy has ['saw', 'ladder'] and starts job
298.28127810116723 roofer has ['hammer', 'ladder', 'ladder'] and starts job
316.83616616186885 treeguy has ['saw', 'ladder'] and starts job
316.83616616186885 painter has ['brush', 'ladder'] and starts job
345.4248822185373 roofer has ['hammer', 'ladder', 'ladder'] and starts job
359.2558219106454 painter has ['brush', 'ladder'] and starts job
359.2558219106454 treeguy has ['saw', 'ladder'] and starts job
385.7096335445383 roofer has ['hammer', 'ladder', 'ladder'] and starts job
407.43697134395245 painter has ['brush', 'ladder'] and starts job
407.43697134395245 treeguy has ['saw', 'ladder'] and starts job
427.5649730427561 roofer has ['hammer', 'ladder', 'ladder'] and starts job
451.6157857370266 painter has ['brush', 'ladder'] and starts job
451.6157857370266 treeguy has ['saw', 'ladder'] and starts job
472.97306394134876 roofer has ['hammer', 'ladder', 'ladder'] and starts job
492.3976188422561 treeguy has ['saw', 'ladder'] and starts job
492.3976188422561 painter has ['brush', 'ladder'] and starts job
SimPy: No more events at time 515.0226394595458

Widget Factory: SimPy_worker_extend.py, SimPy_worker_extend_OO.py

Factory making widgets with queues for machines. (MM)

from SimPy.Simulation import *
from random import uniform, seed


def theTime(time):

    hrs = int(time / 60)
    min = int(time - hrs * 60)
    return "{0}:{1}".format(str.zfill(str(hrs), 2), str.zfill(str(min), 2))


class worker(Process):
    def __init__(self, id):
        Process.__init__(self)
        self.id = id
        self.output = 0
        self.idle = 0
        self.total_idle = 0

    def working_day(self, foobar):
        print("{0} Worker {1} arrives in factory".format(
            theTime(now()), self.id))
        while now() < 17 * 60:  # work till 5 pm
            yield hold, self, uniform(3, 10)
            # print("{0} Widget completed".format(theTime(now())))
            foobar.queue.append(self)
            if foobar.idle:
                reactivate(foobar)
            else:
                self.idle = 1  # worker has to wait for foobar service
                start_idle = now()
                # print("{0} Worker {1} queuing for foobar machine"
                #       .format(theTime(now()),self.id))
            yield passivate, self  # waiting and foobar service
            self.output += 1
            if self.idle:
                self.total_idle += now() - start_idle
            self.idle = 0
        print("{0} {1} goes home, having built {2:d} widgets today.".format(
            theTime(now()), self.id, self.output))
        print("Worker {0} was idle waiting for foobar machine for "
              "{1:3.1f} hours".format(self.id, self.total_idle / 60))


class foobar_machine(Process):
    def __init__(self):
        Process.__init__(self)
        self.queue = []
        self.idle = 1

    def foobar_Process(self):
        yield passivate, self
        while 1:
            while len(self.queue) > 0:
                self.idle = 0
                yield hold, self, 3
                served = self.queue.pop(0)
                reactivate(served)
            self.idle = 1
            yield passivate, self


seed(111333555)
print('SimPy_worker_extend')
initialize()
foo = foobar_machine()
activate(foo, foo.foobar_Process(), delay=0)
john = worker("John")
activate(john, john.working_day(foo), at=510)  # start at 8:30 am
eve = worker("Eve")
activate(eve, eve.working_day(foo), at=510)
simulate(until=18 * 60)
# scheduler(till=18*60) #run simulation from midnight till 6 pm
SimPy_worker_extend
08:30 Worker John arrives in factory
08:30 Worker Eve arrives in factory
17:03 Eve goes home, having built 52 widgets today.
Worker Eve was idle waiting for foobar machine for 1.3 hours
17:09 John goes home, having built 52 widgets today.
Worker John was idle waiting for foobar machine for 0.8 hours

Here is the OO version:

from SimPy.Simulation import *
from random import uniform, seed

# Model components ------------------------


def theTime(time):

    hrs = int(time / 60)
    min = int(time - hrs * 60)
    return "{0}:{1}".format(str.zfill(str(hrs), 2), str.zfill(str(min), 2))


class worker(Process):
    def __init__(self, id, sim):
        Process.__init__(self, sim=sim)
        self.id = id
        self.output = 0
        self.idle = 0
        self.total_idle = 0

    def working_day(self, foobar):
        print("{0} Worker {1} arrives in factory".format(
            theTime(self.sim.now()), self.id))
        while self.sim.now() < 17 * 60:  # work till 5 pm
            yield hold, self, uniform(3, 10)
            # print("{0} Widget completed".format(theTime(now())))
            foobar.queue.append(self)
            if foobar.idle:
                self.sim.reactivate(foobar)
            else:
                self.idle = 1  # worker has to wait for foobar service
                start_idle = self.sim.now()
                # print("{0} Worker {1} queuing for foobar machine"
                #       .format(theTime(now()),self.id))
            yield passivate, self  # waiting and foobar service
            self.output += 1
            if self.idle:
                self.total_idle += self.sim.now() - start_idle
            self.idle = 0
        print("{0} {1} goes home, having built {2} widgets today.".format(
            theTime(self.sim.now()), self.id, self.output))
        print("Worker {0} was idle waiting for foobar machine for "
              "{1:3.1f} hours".format(self.id, self.total_idle / 60))


class foobar_machine(Process):
    def __init__(self, sim):
        Process.__init__(self, sim=sim)
        self.queue = []
        self.idle = 1

    def foobar_Process(self):
        yield passivate, self
        while 1:
            while len(self.queue) > 0:
                self.idle = 0
                yield hold, self, 3
                served = self.queue.pop(0)
                self.sim.reactivate(served)
            self.idle = 1
            yield passivate, self

# Model -----------------------------------


class SimPy_Worker_Extend_Model(Simulation):
    def run(self):
        print('SimPy_worker_extend')
        self.initialize()
        foo = foobar_machine(sim=self)
        self.activate(foo, foo.foobar_Process(), delay=0)
        john = worker("John", sim=self)
        self.activate(john, john.working_day(foo), at=510)  # start at 8:30 am
        eve = worker("Eve", sim=self)
        self.activate(eve, eve.working_day(foo), at=510)
        self.simulate(until=18 * 60)  # run simulation from midnight till 6 pm


# Expriment -------------------------------
seed(111333555)
SimPy_Worker_Extend_Model().run()
SimPy_worker_extend
08:30 Worker John arrives in factory
08:30 Worker Eve arrives in factory
17:03 Eve goes home, having built 52 widgets today.
Worker Eve was idle waiting for foobar machine for 1.3 hours
17:09 John goes home, having built 52 widgets today.
Worker John was idle waiting for foobar machine for 0.8 hours

Widget Packing Machine: WidgetPacking.py, WidgetPacking_OO.py

Using buffers for producer/consumer scenarios. Scenario is a group of widget producing machines and a widget packer, synchronised via a buffer. Two models are shown: the first uses a Level for buffering non-distinguishable items (widgets), and the second a Store for distinguishable items (widgets of different weight).

"""WidgetPacking.py
Scenario:
In a factory, nProd widget-making machines produce widgets with a weight
widgWeight (uniformly distributed [widgWeight-dWeight..widgWeight+dWeight])
on average every tMake minutes (uniform distribution
[tmake-deltaT..tMake+deltaT]).A widget-packing machine packs widgets in tPack
minutes into packages. Simulate for simTime minutes.

Model 1:
The widget-packer packs nWidgets into a package.

Model 2:
The widget-packer packs widgets into a package with package weight not
to exceed packMax.
"""
from SimPy.Simulation import *
import random

# Experiment data -------------------------
nProd = 2  # widget-making machines
widgWeight = 20  # kilogrammes
dWeight = 3  # kilogrammes
tMake = 2  # minutes
deltaT = 0.75  # minutes
tPack = 0.25  # minutes per idget
initialSeed = 1234567  # for random number stream
simTime = 100  # minutes

print('WidgetPacking')
################################################################
#
# Model 1
#
#################################################################
# Data
nWidgets = 6  # widgets per package


class Widget:
    def __init__(self, weight):
        self.weight = weight


class WidgetMakerN(Process):
    """Produces widgets"""

    def make(self, buffer):
        while True:
            yield hold, self, r.uniform(tMake - deltaT, tMake + deltaT)
            yield put, self, buffer, 1         # buffer 1 widget


class WidgetPackerN(Process):
    """Packs a number of widgets into a package"""

    def pack(self, buffer):
        while True:
            for i in range(nWidgets):
                yield get, self, buffer, 1  # get widget
                yield hold, self, tPack  # pack it
            print("{0}: package completed".format(now()))


print("Model 1: pack {0} widgets per package".format(nWidgets))
initialize()
r = random.Random()
r.seed(initialSeed)
wBuffer = Level(name="WidgetBuffer", capacity=500)
for i in range(nProd):
    wm = WidgetMakerN(name="WidgetMaker{0}".format(i))
    activate(wm, wm.make(wBuffer))
wp = WidgetPackerN(name="WidgetPacker")
activate(wp, wp.pack(wBuffer))
simulate(until=simTime)

################################################################
#
# Model 2
#
#################################################################
# Data
packMax = 120  # kilogrammes per package (max)


class WidgetMakerW(Process):
    """Produces widgets"""

    def make(self, buffer):
        while True:
            yield hold, self, r.uniform(tMake - deltaT, tMake + deltaT)
            widgetWeight = r.uniform(
                widgWeight - dWeight, widgWeight + dWeight)
            # buffer widget
            yield put, self, buffer, [Widget(weight=widgetWeight)]


class WidgetPackerW(Process):
    """Packs a number of widgets into a package"""

    def pack(self, buffer):
        weightLeft = 0
        while True:
            packWeight = weightLeft  # pack a widget which did not fit
            # into previous package
            weightLeft = 0
            while True:
                yield get, self, buffer, 1  # get widget
                weightReceived = self.got[0].weight
                if weightReceived + packWeight <= packMax:
                    yield hold, self, tPack  # pack it
                    packWeight += weightReceived
                else:
                    weightLeft = weightReceived  # for next package
                    break
                print("{0}: package completed. Weight= {1:6.2f} kg".format(
                    now(), packWeight))


print(
    "\nModel 2: pack widgets up to max package weight of {0}".format(packMax))
initialize()
r = random.Random()
r.seed(initialSeed)
wBuffer = Store(name="WidgetBuffer", capacity=500)
for i in range(nProd):
    wm = WidgetMakerW(name="WidgetMaker{0}".format(i))
    activate(wm, wm.make(wBuffer))
wp = WidgetPackerW(name="WidgetPacker")
activate(wp, wp.pack(wBuffer))
simulate(until=simTime)
WidgetPacking
Model 1: pack 6 widgets per package
6.925710569651607: package completed
12.932859800562765: package completed
19.296609507200795: package completed
25.827518016011588: package completed
32.28961167417064: package completed
38.27653780313223: package completed
43.516005487393166: package completed
49.84136801349117: package completed
56.02183282676389: package completed
61.22427700160065: package completed
66.78084592723073: package completed
73.45267491487972: package completed
78.52559623726368: package completed
84.36238918070774: package completed
91.00334807967428: package completed
96.25112005144973: package completed

Model 2: pack widgets up to max package weight of 120
1.8535993255800536: package completed. Weight=  20.49 kg
2.9447148714144333: package completed. Weight=  43.29 kg
3.443679099583584: package completed. Weight=  60.73 kg
5.111979202993875: package completed. Weight=  81.36 kg
5.495036360384358: package completed. Weight= 103.32 kg
7.53958534514355: package completed. Weight=  43.79 kg
9.454524267565933: package completed. Weight=  62.32 kg
9.704524267565933: package completed. Weight=  82.07 kg
11.223206345291937: package completed. Weight= 103.55 kg
13.534262617168128: package completed. Weight=  41.81 kg
13.930945836966112: package completed. Weight=  62.71 kg
15.109021173114078: package completed. Weight=  82.73 kg
15.9312940569179: package completed. Weight= 103.41 kg
18.483473187078456: package completed. Weight=  37.07 kg
19.489762737880447: package completed. Weight=  56.79 kg
20.019035270021426: package completed. Weight=  77.23 kg
21.516971139857024: package completed. Weight=  96.93 kg
21.922330284736397: package completed. Weight= 116.85 kg
24.14512998963336: package completed. Weight=  37.89 kg
24.39877824367329: package completed. Weight=  55.50 kg
26.67484997458752: package completed. Weight=  78.46 kg
26.92484997458752: package completed. Weight= 100.35 kg
28.1664758950799: package completed. Weight= 117.54 kg
29.69269527817336: package completed. Weight=  40.05 kg
31.039995345892564: package completed. Weight=  60.53 kg
31.416541860477725: package completed. Weight=  77.63 kg
32.41760810512986: package completed. Weight= 100.53 kg
32.95461868723966: package completed. Weight= 118.77 kg
35.03241015535913: package completed. Weight=  40.09 kg
37.13500099371781: package completed. Weight=  59.57 kg
37.54993412593156: package completed. Weight=  76.58 kg
39.30652910593313: package completed. Weight=  95.59 kg
39.55652910593313: package completed. Weight= 116.49 kg
42.0420842938092: package completed. Weight=  42.24 kg
42.2920842938092: package completed. Weight=  63.58 kg
43.65741443896232: package completed. Weight=  85.90 kg
43.90741443896232: package completed. Weight= 106.78 kg
46.0951882462178: package completed. Weight=  37.20 kg
47.438300452780354: package completed. Weight=  58.47 kg
47.688300452780354: package completed. Weight=  79.33 kg
49.40690976731008: package completed. Weight= 100.51 kg
50.09589376227701: package completed. Weight= 117.80 kg
52.015927666123616: package completed. Weight=  37.47 kg
53.308294328826435: package completed. Weight=  58.78 kg
54.03538112053091: package completed. Weight=  81.10 kg
55.13265226734849: package completed. Weight=  98.94 kg
56.54061780546726: package completed. Weight=  39.67 kg
58.42284478038792: package completed. Weight=  61.78 kg
58.67284478038792: package completed. Weight=  80.36 kg
60.363861577200076: package completed. Weight= 102.50 kg
62.30054431149251: package completed. Weight=  38.39 kg
62.72667501529395: package completed. Weight=  58.21 kg
64.04176585171321: package completed. Weight=  75.31 kg
64.78871826102055: package completed. Weight=  94.49 kg
65.5200550350699: package completed. Weight= 113.54 kg
67.70604486309713: package completed. Weight=  41.53 kg
69.2327615315688: package completed. Weight=  60.57 kg
69.64556242167215: package completed. Weight=  82.52 kg
70.56237737140228: package completed. Weight= 104.26 kg
72.1477929466095: package completed. Weight=  41.62 kg
72.98432452690808: package completed. Weight=  63.88 kg
73.5826902990221: package completed. Weight=  81.87 kg
75.05025424304776: package completed. Weight= 101.04 kg
77.26048662562239: package completed. Weight=  40.26 kg
77.71710036932708: package completed. Weight=  61.69 kg
79.75353580124: package completed. Weight=  80.72 kg
80.12811805319318: package completed. Weight= 100.29 kg
82.4142311699317: package completed. Weight=  39.35 kg
83.68305885096552: package completed. Weight=  57.13 kg
83.93305885096552: package completed. Weight=  74.55 kg
85.85481524806448: package completed. Weight=  93.29 kg
86.13093377784419: package completed. Weight= 115.05 kg
87.86043544925184: package completed. Weight=  41.13 kg
89.46190021907331: package completed. Weight=  63.30 kg
90.40626417525424: package completed. Weight=  82.59 kg
91.19235409838856: package completed. Weight= 105.00 kg
92.48486291641056: package completed. Weight=  37.14 kg
93.79148753229941: package completed. Weight=  56.83 kg
94.19510234363693: package completed. Weight=  76.57 kg
96.09493981469757: package completed. Weight=  94.85 kg
96.82617211079959: package completed. Weight= 114.58 kg
98.82720299189798: package completed. Weight=  39.12 kg
99.94853712506526: package completed. Weight=  58.33 kg

Here is the OO version:

"""WidgetPacking_OO.py
Scenario:
In a factory, nProd widget-making machines produce widgets with a weight
widgWeight (uniformly distributed [widgWeight-dWeight..widgWeight+dWeight])
on average every tMake minutes (uniform distribution
[tmake-deltaT..tMake+deltaT]).A widget-packing machine packs widgets in tPack
minutes into packages. Simulate for simTime minutes.

Model 1:
The widget-packer packs nWidgets into a package.

Model 2:
The widget-packer packs widgets into a package with package weight not
to exceed packMax.
"""
from SimPy.Simulation import *
import random

# Experiment data -------------------------
nProd = 2  # widget-making machines
widgWeight = 20  # kilogrammes
dWeight = 3  # kilogrammes
tMake = 2  # minutes
deltaT = 0.75  # minutes
tPack = 0.25  # minutes per idget
initialSeed = 1234567  # for random number stream
simTime = 100  # minutes

print('WidgetPacking')

# Model 1 components ----------------------

# Data
nWidgets = 6  # widgets per package


class Widget:
    def __init__(self, weight):
        self.weight = weight


class WidgetMakerN(Process):
    def make(self, buffer):
        while True:
            yield hold, self, self.sim.r.uniform(tMake - deltaT,
                                                 tMake + deltaT)
            yield put, self, buffer, 1         # buffer 1 widget


class WidgetPackerN(Process):
    """Packs a number of widgets into a package"""

    def pack(self, buffer):
        while True:
            for i in range(nWidgets):
                yield get, self, buffer, 1  # get widget
                yield hold, self, tPack  # pack it
            print("{0}: package completed".format(self.sim.now()))

# Model 1 ---------------------------------


class WidgetPackingModel1(Simulation):
    def run(self):
        print("Model 1: pack {0} widgets per package".format(nWidgets))
        self.initialize()
        self.r = random.Random()
        self.r.seed(initialSeed)
        wBuffer = Level(name="WidgetBuffer", capacity=500, sim=self)
        for i in range(nProd):
            wm = WidgetMakerN(name="WidgetMaker{0}".format(i), sim=self)
            self.activate(wm, wm.make(wBuffer))
        wp = WidgetPackerN(name="WidgetPacker", sim=self)
        self.activate(wp, wp.pack(wBuffer))
        self.simulate(until=simTime)


# Experiment ------------------------------
WidgetPackingModel1().run()

# Model 2 components ----------------------

# Data
packMax = 120  # kilogrammes per package (max)


class WidgetMakerW(Process):
    """Produces widgets"""

    def make(self, buffer):
        while True:
            yield hold, self, self.sim.r.uniform(tMake - deltaT,
                                                 tMake + deltaT)
            widgetWeight = self.sim.r.uniform(
                widgWeight - dWeight, widgWeight + dWeight)
            # buffer widget
            yield put, self, buffer, [Widget(weight=widgetWeight)]


class WidgetPackerW(Process):
    """Packs a number of widgets into a package"""

    def pack(self, buffer):
        weightLeft = 0
        while True:
            packWeight = weightLeft  # pack a widget which did not fit
            # into previous package
            weightLeft = 0
            while True:
                yield get, self, buffer, 1  # get widget
                weightReceived = self.got[0].weight
                if weightReceived + packWeight <= packMax:
                    yield hold, self, tPack  # pack it
                    packWeight += weightReceived
                else:
                    weightLeft = weightReceived  # for next package
                    break
                print("{0}: package completed. Weight= {1:6.2f} kg".format(
                    self.sim.now(), packWeight))

# Model 2 ---------------------------------


class WidgetPackingModel2(Simulation):
    def run(self):
        print(
            "\nModel 2: pack widgets up to max package weight of {0}"
            .format(packMax))
        self.initialize()
        self.r = random.Random()
        self.r.seed(initialSeed)
        wBuffer = Store(name="WidgetBuffer", capacity=500, sim=self)
        for i in range(nProd):
            wm = WidgetMakerW(name="WidgetMaker{0}".format(i), sim=self)
            self.activate(wm, wm.make(wBuffer))
        wp = WidgetPackerW(name="WidgetPacker", sim=self)
        self.activate(wp, wp.pack(wBuffer))
        self.simulate(until=simTime)


# Experiment ------------------------------
WidgetPackingModel2().run()
WidgetPacking
Model 1: pack 6 widgets per package
6.925710569651607: package completed
12.932859800562765: package completed
19.296609507200795: package completed
25.827518016011588: package completed
32.28961167417064: package completed
38.27653780313223: package completed
43.516005487393166: package completed
49.84136801349117: package completed
56.02183282676389: package completed
61.22427700160065: package completed
66.78084592723073: package completed
73.45267491487972: package completed
78.52559623726368: package completed
84.36238918070774: package completed
91.00334807967428: package completed
96.25112005144973: package completed

Model 2: pack widgets up to max package weight of 120
1.8535993255800536: package completed. Weight=  20.49 kg
2.9447148714144333: package completed. Weight=  43.29 kg
3.443679099583584: package completed. Weight=  60.73 kg
5.111979202993875: package completed. Weight=  81.36 kg
5.495036360384358: package completed. Weight= 103.32 kg
7.53958534514355: package completed. Weight=  43.79 kg
9.454524267565933: package completed. Weight=  62.32 kg
9.704524267565933: package completed. Weight=  82.07 kg
11.223206345291937: package completed. Weight= 103.55 kg
13.534262617168128: package completed. Weight=  41.81 kg
13.930945836966112: package completed. Weight=  62.71 kg
15.109021173114078: package completed. Weight=  82.73 kg
15.9312940569179: package completed. Weight= 103.41 kg
18.483473187078456: package completed. Weight=  37.07 kg
19.489762737880447: package completed. Weight=  56.79 kg
20.019035270021426: package completed. Weight=  77.23 kg
21.516971139857024: package completed. Weight=  96.93 kg
21.922330284736397: package completed. Weight= 116.85 kg
24.14512998963336: package completed. Weight=  37.89 kg
24.39877824367329: package completed. Weight=  55.50 kg
26.67484997458752: package completed. Weight=  78.46 kg
26.92484997458752: package completed. Weight= 100.35 kg
28.1664758950799: package completed. Weight= 117.54 kg
29.69269527817336: package completed. Weight=  40.05 kg
31.039995345892564: package completed. Weight=  60.53 kg
31.416541860477725: package completed. Weight=  77.63 kg
32.41760810512986: package completed. Weight= 100.53 kg
32.95461868723966: package completed. Weight= 118.77 kg
35.03241015535913: package completed. Weight=  40.09 kg
37.13500099371781: package completed. Weight=  59.57 kg
37.54993412593156: package completed. Weight=  76.58 kg
39.30652910593313: package completed. Weight=  95.59 kg
39.55652910593313: package completed. Weight= 116.49 kg
42.0420842938092: package completed. Weight=  42.24 kg
42.2920842938092: package completed. Weight=  63.58 kg
43.65741443896232: package completed. Weight=  85.90 kg
43.90741443896232: package completed. Weight= 106.78 kg
46.0951882462178: package completed. Weight=  37.20 kg
47.438300452780354: package completed. Weight=  58.47 kg
47.688300452780354: package completed. Weight=  79.33 kg
49.40690976731008: package completed. Weight= 100.51 kg
50.09589376227701: package completed. Weight= 117.80 kg
52.015927666123616: package completed. Weight=  37.47 kg
53.308294328826435: package completed. Weight=  58.78 kg
54.03538112053091: package completed. Weight=  81.10 kg
55.13265226734849: package completed. Weight=  98.94 kg
56.54061780546726: package completed. Weight=  39.67 kg
58.42284478038792: package completed. Weight=  61.78 kg
58.67284478038792: package completed. Weight=  80.36 kg
60.363861577200076: package completed. Weight= 102.50 kg
62.30054431149251: package completed. Weight=  38.39 kg
62.72667501529395: package completed. Weight=  58.21 kg
64.04176585171321: package completed. Weight=  75.31 kg
64.78871826102055: package completed. Weight=  94.49 kg
65.5200550350699: package completed. Weight= 113.54 kg
67.70604486309713: package completed. Weight=  41.53 kg
69.2327615315688: package completed. Weight=  60.57 kg
69.64556242167215: package completed. Weight=  82.52 kg
70.56237737140228: package completed. Weight= 104.26 kg
72.1477929466095: package completed. Weight=  41.62 kg
72.98432452690808: package completed. Weight=  63.88 kg
73.5826902990221: package completed. Weight=  81.87 kg
75.05025424304776: package completed. Weight= 101.04 kg
77.26048662562239: package completed. Weight=  40.26 kg
77.71710036932708: package completed. Weight=  61.69 kg
79.75353580124: package completed. Weight=  80.72 kg
80.12811805319318: package completed. Weight= 100.29 kg
82.4142311699317: package completed. Weight=  39.35 kg
83.68305885096552: package completed. Weight=  57.13 kg
83.93305885096552: package completed. Weight=  74.55 kg
85.85481524806448: package completed. Weight=  93.29 kg
86.13093377784419: package completed. Weight= 115.05 kg
87.86043544925184: package completed. Weight=  41.13 kg
89.46190021907331: package completed. Weight=  63.30 kg
90.40626417525424: package completed. Weight=  82.59 kg
91.19235409838856: package completed. Weight= 105.00 kg
92.48486291641056: package completed. Weight=  37.14 kg
93.79148753229941: package completed. Weight=  56.83 kg
94.19510234363693: package completed. Weight=  76.57 kg
96.09493981469757: package completed. Weight=  94.85 kg
96.82617211079959: package completed. Weight= 114.58 kg
98.82720299189798: package completed. Weight=  39.12 kg
99.94853712506526: package completed. Weight=  58.33 kg

GUI Input

The GUI examples do not run under Python 3.x, as only the core SimPy libraries were ported.

Fireworks using SimGUI: GUIdemo.py, GUIdemo_OO.py

A firework show. This is a very basic model, demonstrating the ease of interfacing to SimGUI.

__doc__ = """ GUIdemo.py
This is a very basic model, demonstrating the ease
of interfacing to SimGUI.
"""
from SimPy.Simulation import *
from random import *
from SimPy.SimGUI import *

# Model components ------------------------


class Launcher(Process):
    nrLaunched = 0

    def launch(self):
        while True:
            gui.writeConsole("Launch at {0:.1f}".format(now()))
            Launcher.nrLaunched += 1
            gui.launchmonitor.observe(Launcher.nrLaunched)
            yield hold, self, uniform(1, gui.params.maxFlightTime)
            gui.writeConsole("Boom!!! Aaaah!! at {0:.1f}".format(now()))


def model():
    gui.launchmonitor = Monitor(name="Rocket counter",
                                ylab="nr launched", tlab="time")
    initialize()
    Launcher.nrLaunched = 0
    for i in range(gui.params.nrLaunchers):
        lau = Launcher()
        activate(lau, lau.launch())
    simulate(until=gui.params.duration)
    gui.noRunYet = False
    gui.writeStatusLine("{0} rockets launched in {1:.1f} minutes"
                        .format(Launcher.nrLaunched, now()))


class MyGUI(SimGUI):
    def __init__(self, win, **par):
        SimGUI.__init__(self, win, **par)
        self.run.add_command(label="Start fireworks",
                             command=model, underline=0)
        self.params = Parameters(duration=duration,
                                 maxFlightTime=maxFlightTime,
                                 nrLaunchers=nrLaunchers)

# Experiment data ---------------------------------------


duration = 2000
maxFlightTime = 11.7
nrLaunchers = 3

# Model/Experiment/Display  ------------------------------

root = Tk()
gui = MyGUI(root, title="RocketGUI", doc=__doc__, consoleHeight=40)
gui.mainloop()

Here is the OO version:

__doc__ = """ GUIdemo_OO.py
This is a very basic model, demonstrating the ease
of interfacing to SimGUI.
"""
from SimPy.Simulation import *
from random import *
from SimPy.SimGUI import *

# Model components ---------------------------------------


class Launcher(Process):
    nrLaunched = 0

    def launch(self):
        while True:
            gui.writeConsole("Launch at {0:.1f}".format(self.sim.now()))
            Launcher.nrLaunched += 1
            gui.launchmonitor.observe(Launcher.nrLaunched)
            yield hold, self, uniform(1, gui.params.maxFlightTime)
            gui.writeConsole(
                "Boom!!! Aaaah!! at {0:.1f}".format(self.sim.now()))

# Model --------------------------------------------------


class GUIdemoModel(Simulation):
    def run(self):
        self.initialize()
        gui.launchmonitor = Monitor(name="Rocket counter",
                                    ylab="nr launched", tlab="time", sim=self)
        Launcher.nrLaunched = 0
        for i in range(gui.params.nrLaunchers):
            lau = Launcher(sim=self)
            self.activate(lau, lau.launch())
        self.simulate(until=gui.params.duration)
        gui.noRunYet = False
        gui.writeStatusLine("{0} rockets launched in {1:.1f} minutes"
                            .format(Launcher.nrLaunched, self.now()))

# Model GUI ----------------------------------------------


class MyGUI(SimGUI):
    def __init__(self, win, **par):
        SimGUI.__init__(self, win, **par)
        self.run.add_command(label="Start fireworks",
                             command=GUIdemoModel().run, underline=0)
        self.params = Parameters(duration=duration,
                                 maxFlightTime=maxFlightTime,
                                 nrLaunchers=nrLaunchers)

# Experiment data ----------------------------------------


duration = 2000
maxFlightTime = 11.7
nrLaunchers = 3

# Experiment/Display  ------------------------------------

root = Tk()
gui = MyGUI(root, title="RocketGUI", doc=__doc__, consoleHeight=40)
gui.mainloop()

Bank Customers using SimGUI: bank11GUI.py, bank11GUI_OO.py

Simulation with customers arriving at random to a bank with two counters. This is a modification of the bank11 simulation using SimGUI to run the simulation.

__doc__ = """ bank11.py: Simulate customers arriving
    at random, using a Source, requesting service
    from two counters each with their own queue
    random servicetime.
    Uses a Monitor object to record waiting times
"""
from SimPy.Simulation import *
# Lmona
from random import Random
from SimPy.SimGUI import *


class Source(Process):
    """ Source generates customers randomly"""

    def __init__(self, seed=333):
        Process.__init__(self)
        self.SEED = seed

    def generate(self, number, interval):
        rv = Random(self.SEED)
        for i in range(number):
            c = Customer(name="Customer{0:02d}".format(i))
            activate(c, c.visit(timeInBank=12.0))
            t = rv.expovariate(1.0 / interval)
            yield hold, self, t


def NoInSystem(R):
    """ The number of customers in the resource R
    in waitQ and active Q"""
    return (len(R.waitQ) + len(R.activeQ))


class Customer(Process):
    """ Customer arrives, is served and leaves """

    def __init__(self, name):
        Process.__init__(self)
        self.name = name

    def visit(self, timeInBank=0):
        arrive = now()
        Qlength = [NoInSystem(counter[i]) for i in range(Nc)]
        if gui.params.trace:
            gui.writeConsole("{0:7.4f} {1}: Here I am. Queues are: {2}".format(
                now(), self.name, Qlength))
        for i in range(Nc):
            if Qlength[i] == 0 or Qlength[i] == min(Qlength):
                join = i
                break
        yield request, self, counter[join]
        wait = now() - arrive
        waitMonitor.observe(wait, t=now())
        if gui.params.trace:
            gui.writeConsole("{0:7.4f} {1}: Waited {2:6.3f}".format(
                now(), self.name, wait))
        tib = counterRV.expovariate(1.0 / timeInBank)
        yield hold, self, tib
        yield release, self, counter[join]
        if gui.params.trace:
            gui.writeConsole(
                "{0:7.4f} {1}: Finished    ".format(now(), self.name))


def model(counterseed=3939393):
    global Nc, counter, counterRV, waitMonitor
    Nc = 2
    counter = [Resource(name="Clerk0"), Resource(name="Clerk1")]
    counterRV = Random(counterseed)
    gui.mon1 = waitMonitor = Monitor("Customer waiting times")
    waitMonitor.tlab = "arrival time"
    waitMonitor.ylab = "waiting time"
    initialize()
    source = Source(seed=gui.params.sourceseed)
    activate(source, source.generate(
        gui.params.numberCustomers, gui.params.interval), 0.0)
    result = simulate(until=gui.params.endtime)
    gui.writeStatusLine(text="Time at simulation end: {0:.1f}"
                        " -- Customers still waiting: {1}"
                        .format(now(),
                                len(counter[0].waitQ) +
                                len(counter[1].waitQ)))


def statistics():
    if gui.noRunYet:
        showwarning(title='Model warning',
                    message="Run simulation first -- no data available.")
        return
    gui.writeConsole(text="\nRun parameters: {0}".format(gui.params))
    gui.writeConsole(text="Average wait for {0:4d} customers was {1:6.2f}"
                     .format(waitMonitor.count(), waitMonitor.mean()))


def run():
    model(gui.params.counterseed)
    gui.noRunYet = False


def showAuthors():
    gui.showTextBox(text="Tony Vignaux\nKlaus Muller",
                    title="Author information")


class MyGUI(SimGUI):
    def __init__(self, win, **p):
        SimGUI.__init__(self, win, **p)
        self.help.add_command(label="Author(s)",
                              command=showAuthors, underline=0)
        self.view.add_command(label="Statistics",
                              command=statistics, underline=0)
        self.run.add_command(label="Run bank11 model",
                             command=run, underline=0)

        self.params = Parameters(
            endtime=2000,
            sourceseed=1133,
            counterseed=3939393,
            numberCustomers=50,
            interval=10.0,
            trace=0)


root = Tk()
gui = MyGUI(root, title="SimPy GUI example", doc=__doc__, consoleHeight=40)
gui.mainloop()

Here is the OO version:

__doc__ = """ bank11_OO.py: Simulate customers arriving
    at random, using a Source, requesting service
    from two counters each with their own queue
    random servicetime.
    Uses a Monitor object to record waiting times
"""
from SimPy.Simulation import *
from random import Random
from SimPy.SimGUI import *


class Source(Process):
    """ Source generates customers randomly"""

    def __init__(self, sim, seed=333):
        Process.__init__(self, sim=sim)
        self.SEED = seed

    def generate(self, number, interval):
        rv = Random(self.SEED)
        for i in range(number):
            c = Customer(name="Customer{0:02d}".format(i), sim=self.sim)
            self.sim.activate(c, c.visit(timeInBank=12.0))
            t = rv.expovariate(1.0 / interval)
            yield hold, self, t


def NoInSystem(R):
    """ The number of customers in the resource R
    in waitQ and active Q"""
    return (len(R.waitQ) + len(R.activeQ))


class Customer(Process):
    """ Customer arrives, is served and leaves """
    # def __init__(self,name,sim):
    # Process.__init__(self,name=name,sim=sim)

    def visit(self, timeInBank=0):
        arrive = self.sim.now()
        Qlength = [NoInSystem(self.sim.counter[i]) for i in range(self.sim.Nc)]
        if gui.params.trace:
            gui.writeConsole("{0:7.4f} {1}: Here I am. Queues are: {2}".format(
                self.sim.now(), self.name, Qlength))
        for i in range(self.sim.Nc):
            if Qlength[i] == 0 or Qlength[i] == min(Qlength):
                join = i
                break
        yield request, self, self.sim.counter[join]
        wait = self.sim.now() - arrive
        self.sim.waitMonitor.observe(wait, t=self.sim.now())
        if gui.params.trace:
            gui.writeConsole("{0:7.4f} {1}: Waited {2:6.3f}".format(
                self.sim.now(), self.name, wait))
        tib = self.sim.counterRV.expovariate(1.0 / timeInBank)
        yield hold, self, tib
        yield release, self, self.sim.counter[join]
        if gui.params.trace:
            gui.writeConsole("{0:7.4f} {1}: Finished    ".format(
                self.sim.now(), self.name))


class CounterModel(Simulation):
    def run(self, counterseed=3939393):
        self.initialize()
        self.Nc = 2
        self.counter = [Resource(name="Clerk0", sim=self),
                        Resource(name="Clerk1", sim=self)]
        self.counterRV = Random(counterseed)
        gui.mon1 = self.waitMonitor = Monitor(
            "Customer waiting times", sim=self)
        self.waitMonitor.tlab = "arrival time"
        self.waitMonitor.ylab = "waiting time"
        source = Source(seed=gui.params.sourceseed, sim=self)
        self.activate(source, source.generate(
            gui.params.numberCustomers, gui.params.interval), 0.0)
        result = self.simulate(until=gui.params.endtime)
        gui.writeStatusLine(text="Time at simulation end: {0:.1f}"
                            " -- Customers still waiting: {1}"
                            .format(self.now(),
                                    len(self.counter[0].waitQ) +
                                    len(self.counter[1].waitQ)))


def statistics():
    if gui.noRunYet:
        showwarning(title='Model warning',
                    message="Run simulation first -- no data available.")
        return
    gui.writeConsole(text="\nRun parameters: {0}".format(gui.params))
    gui.writeConsole(text="Average wait for {0:4d} customers was {1:6.2f}"
                     .format(gui.mon1.count(), gui.mon1.mean()))


def run():
    CounterModel().run(gui.params.counterseed)
    gui.noRunYet = False


def showAuthors():
    gui.showTextBox(text="Tony Vignaux\nKlaus Muller",
                    title="Author information")


class MyGUI(SimGUI):
    def __init__(self, win, **p):
        SimGUI.__init__(self, win, **p)
        self.help.add_command(label="Author(s)",
                              command=showAuthors, underline=0)
        self.view.add_command(label="Statistics",
                              command=statistics, underline=0)
        self.run.add_command(label="Run bank11 model",
                             command=run, underline=0)

        self.params = Parameters(
            endtime=2000,
            sourceseed=1133,
            counterseed=3939393,
            numberCustomers=50,
            interval=10.0,
            trace=0)


root = Tk()
gui = MyGUI(root, title="SimPy GUI example", doc=__doc__, consoleHeight=40)
gui.mainloop()

Bank Customers using SimulationStep: SimGUIStep.py

(broken for python 2.x global name ‘simulateStep’ is not defined)

Another modification of the bank11 simulation this time showing the ability to step between events.

from SimPy.SimGUI import *
from SimPy.SimulationStep import *
from random import Random

__version__ = '$Revision$ $Date$ kgm'

if __name__ == '__main__':

    class Source(Process):
        """ Source generates customers randomly"""

        def __init__(self, seed=333):
            Process.__init__(self)
            self.SEED = seed

        def generate(self, number, interval):
            rv = Random(self.SEED)
            for i in range(number):
                c = Customer(name="Customer{0:02d}".format(i,))
                activate(c, c.visit(timeInBank=12.0))
                t = rv.expovariate(1.0 / interval)
                yield hold, self, t

    def NoInSystem(R):
        """ The number of customers in the resource R
        in waitQ and active Q"""
        return (len(R.waitQ) + len(R.activeQ))

    class Customer(Process):
        """ Customer arrives, is served and leaves """

        def __init__(self, name):
            Process.__init__(self)
            self.name = name

        def visit(self, timeInBank=0):
            arrive = now()
            Qlength = [NoInSystem(counter[i]) for i in range(Nc)]
            # print("{0:7.4f} {1}: Here I am. {2}   ".format(now(),
            #                                                self.name,
            #                                                Qlength))
            for i in range(Nc):
                if Qlength[i] == 0 or Qlength[i] == min(Qlength):
                    join = i
                    break
            yield request, self, counter[join]
            wait = now() - arrive
            waitMonitor.observe(wait, t=now())  # Lmond
            # print("{0:7.4f} {1}: Waited {2:6.3f}".format(now(),
            #                                              self.name,wait))
            tib = counterRV.expovariate(1.0 / timeInBank)
            yield hold, self, tib
            yield release, self, counter[join]
            serviceMonitor.observe(now() - arrive, t=now())
            if trace:
                gui.writeConsole("Customer leaves at {0:.1f}".format(now()))
            # print("{0:7.4f} {1}: Finished    ".format(now(),self.name))

    def showtime():
        gui.topconsole.config(text="time = {0}".format(now()))
        gui.root.update()

    def runStep():
        if gui.noRunYet:
            showwarning("SimPy warning", "Run 'Start run (stepping)' first")
            return
        showtime()
        a = simulateStep(until=gui.params.endtime)
        if a[1] == "notResumable":
            gui.writeConsole(text="Run ended. Status: {0}".format(a[0]))
        showtime()

    def runNoStep():
        showtime()
        for i in range(gui.params.nrRuns):
            simulate(until=gui.param.sendtime)
        showtime()
        gui.writeConsole("{0} simulation run(s) completed\n".format(i + 1))

    def contStep():
        return

    def model():
        global Nc, counter, counterRV, waitMonitor, serviceMonitor, trace
        global lastLeave, noRunYet, initialized
        counterRV = Random(gui.params.counterseed)
        sourceseed = gui.params.sourceseed
        nrRuns = gui.params.nrRuns
        lastLeave = 0
        gui.noRunYet = True
        for runNr in range(nrRuns):
            gui.noRunYet = False
            trace = gui.params.trace
            if trace:
                gui.writeConsole(text='\n** Run {0}'.format(runNr + 1))
            Nc = 2
            counter = [Resource(name="Clerk0"), Resource(name="Clerk1")]
            gui.waitMoni = waitMonitor = Monitor(name='Waiting Times')
            waitMonitor.xlab = 'Time'
            waitMonitor.ylab = 'Customer waiting time'
            gui.serviceMoni = serviceMonitor = Monitor(name='Service Times')
            serviceMonitor.xlab = 'Time'
            serviceMonitor.ylab = 'Total service time = wait+service'
            initialize()
            source = Source(seed=sourceseed)
            activate(source, source.generate(
                gui.params.numberCustomers, gui.params.interval), 0.0)
            simulate(showtime, until=gui.params.endtime)
            showtime()
            lastLeave += now()
        gui.writeConsole("{0} simulation run(s) completed\n".format(nrRuns))
        gui.writeConsole("Parameters:\n{0}".format(gui.params))

    def modelstep():
        global Nc, counter, counterRV, waitMonitor, serviceMonitor, trace
        global lastLeave, noRunYet
        counterRV = Random(gui.params.counterseed)
        sourceseed = gui.params.sourceseed
        nrRuns = gui.params.nrRuns
        lastLeave = 0
        gui.noRunYet = True
        trace = gui.params.trace
        if trace:
            gui.writeConsole(text='\n** Run {0}'.format(runNr + 1))
        Nc = 2
        counter = [Resource(name="Clerk0"), Resource(name="Clerk1")]
        gui.waitMoni = waitMonitor = Monitor(name='Waiting Times')
        waitMonitor.xlab = 'Time'
        waitMonitor.ylab = 'Customer waiting time'
        gui.serviceMoni = serviceMonitor = Monitor(name='Service Times')
        serviceMonitor.xlab = 'Time'
        serviceMonitor.ylab = 'Total service time = wait+service'
        initialize()
        source = Source(seed=sourceseed)
        activate(source, source.generate(
            gui.params.numberCustomers, gui.params.interval), 0.0)
        simulateStep(until=gui.params.endtime)
        gui.noRunYet = False

    def statistics():
        if gui.noRunYet:
            showwarning(title='SimPy warning',
                        message="Run simulation first -- no data available.")
            return
        aver = lastLeave / gui.params.nrRuns
        gui.writeConsole(text="Average time for {0} customers to get through "
                              "bank: {1:.1f}\n({2} runs)\n"
                              .format(gui.params.numberCustomers, aver,
                                      gui.params.nrRuns))

    __doc__ = """
Modified bank11.py (from Bank Tutorial) with GUI.

Model: Simulate customers arriving
at random, using a Source, requesting service
from two counters each with their own queue
random servicetime.

Uses Monitor objects to record waiting times
and total service times."""

    def showAuthors():
        gui.showTextBox(text="Tony Vignaux\nKlaus Muller",
                        title="Author information")

    class MyGUI(SimGUI):
        def __init__(self, win, **p):
            SimGUI.__init__(self, win, **p)
            self.help.add_command(label="Author(s)",
                                  command=showAuthors, underline=0)
            self.view.add_command(label="Statistics",
                                  command=statistics, underline=0)
            self.run.add_command(label="Start run (event stepping)",
                                 command=modelstep, underline=0)
            self.run.add_command(label="Next event",
                                 command=runStep, underline=0)
            self.run.add_command(label="Complete run (no stepping)",
                                 command=model, underline=0)

    root = Tk()
    gui = MyGUI(root, title="SimPy GUI example", doc=__doc__)
    gui.params = Parameters(endtime=2000,
                            sourceseed=1133,
                            counterseed=3939393,
                            numberCustomers=50,
                            interval=10.0,
                            trace=0,
                            nrRuns=1)
    gui.mainloop()

Plot

Patisserie Francaise bakery: bakery.py, bakery_OO.py

The Patisserie Francaise bakery has three ovens baking their renowned baguettes for retail and restaurant customers. They start baking one hour before the shop opens and stop at closing time.

They bake batches of 40 breads at a time, taking 25..30 minutes (uniformly distributed) per batch. Retail customers arrive at a rate of 40 per hour (exponentially distributed). They buy 1, 2 or 3 baguettes with equal probability. Restaurant buyers arrive at a rate of 4 per hour (exponentially dist.). They buy 20,40 or 60 baguettes with equal probability.

The simulation answers the following questions:

a) What is the mean waiting time for retail and restaurant buyers?

  1. What is their maximum waiting time?
  2. What percentage of customer has to wait longer than 15 minutes?

SimPy.SimPlot is used to graph the number of baguettes over time. (KGM)

"""bakery.py
Scenario:
The Patisserie Francaise bakery has three ovens baking their renowned
baguettes for retail and restaurant customers. They start baking one
hour before the shop opens and stop at closing time.
They bake batches of 40 breads at a time,
taking 25..30 minutes (uniformly distributed) per batch. Retail customers
arrive at a rate of 40 per hour (exponentially distributed). They buy
1, 2 or 3 baguettes with equal probability. Restaurant buyers arrive
at a rate of 4 per hour (exponentially dist.). They buy 20,40 or 60
baguettes with equal probability.
Simulate this operation for 100 days of 8 hours shop opening time.
a) What is the mean waiting time for retail and restaurant buyers?
b) What is their maximum waiting time?
b) What percentage of customer has to wait longer than 15 minutes??
c) Plot the number of baguettes over time for an arbitrary day.
   (use PLOTTING=True to do this)

"""
from SimPy.Simulation import *
from SimPy.SimPlot import *
import random
# Model components


class Bakery:
    def __init__(self, nrOvens, toMonitor):
        self.stock = Level(name="baguette stock", monitored=toMonitor)
        for i in range(nrOvens):
            ov = Oven()
            activate(ov, ov.bake(capacity=batchsize, bakery=self))


class Oven(Process):
    def bake(self, capacity, bakery):
        while now() + tBakeMax < tEndBake:
            yield hold, self, r.uniform(tBakeMin, tBakeMax)
            yield put, self, bakery.stock, capacity


class Customer(Process):
    def buyBaguette(self, cusType, bakery):
        tIn = now()
        yield get, self, bakery.stock, r.choice(buy[cusType])
        waits[cusType].append(now() - tIn)


class CustomerGenerator(Process):
    def generate(self, cusType, bakery):
        while True:
            yield hold, self, r.expovariate(1.0 / tArrivals[cusType])
            if now() < (tShopOpen + tBeforeOpen):
                c = Customer(cusType)
                activate(c, c.buyBaguette(cusType, bakery=bakery))
# Model


def model():
    toMonitor = False
    initialize()
    if day == (nrDays - 1):
        toMoni = True
    else:
        toMoni = False
    b = Bakery(nrOvens=nrOvens, toMonitor=toMoni)
    for cType in ["retail", "restaurant"]:
        cg = CustomerGenerator()
        activate(cg, cg.generate(cusType=cType, bakery=b), delay=tBeforeOpen)
    simulate(until=tBeforeOpen + tShopOpen)
    return b


# Experiment data
nrOvens = 3
batchsize = 40  # nr baguettes
tBakeMin = 25 / 60.
tBakeMax = 30 / 60.  # hours
tArrivals = {"retail": 1.0 / 40, "restaurant": 1.0 / 4}  # hours
buy = {"retail": [1, 2, 3], "restaurant": [20, 40, 60]}  # nr baguettes
tShopOpen = 8
tBeforeOpen = 1
tEndBake = tBeforeOpen + tShopOpen  # hours
nrDays = 100
r = random.Random(12371)
PLOTTING = True
# Experiment
waits = {}
waits["retail"] = []
waits["restaurant"] = []
for day in range(nrDays):
    bakery = model()
# Analysis/output
print("bakery")
for cType in ["retail", "restaurant"]:
    print("Average wait for {0} customers: {1:4.2f} hours".format(
        cType, (1.0 * sum(waits[cType])) / len(waits[cType])))
    print("Longest wait for {0} customers: {1:4.1f} hours".format(
        cType, max(waits[cType])))
    nrLong = len([1 for x in waits[cType] if x > 0.25])
    nrCust = len(waits[cType])
    print("Percentage of {0} customers having to wait for more than"
          " 0.25 hours: {1}".format(cType, 100 * nrLong / nrCust))


if PLOTTING:
    plt = SimPlot()
    plt.plotStep(bakery.stock.bufferMon,
                 title="Number of baguettes in stock during arbitrary day",
                 color="blue")
    plt.mainloop()

Here is the OO version:

"""bakery_OO.py
Scenario:
The Patisserie Francaise bakery has three ovens baking their renowned
baguettes for retail and restaurant customers. They start baking one
hour before the shop opens and stop at closing time.
They bake batches of 40 breads at a time,
taking 25..30 minutes (uniformly distributed) per batch. Retail customers
arrive at a rate of 40 per hour (exponentially distributed). They buy
1, 2 or 3 baguettes with equal probability. Restaurant buyers arrive
at a rate of 4 per hour (exponentially dist.). They buy 20,40 or 60
baguettes with equal probability.
Simulate this operation for 100 days of 8 hours shop opening time.
a) What is the mean waiting time for retail and restaurant buyers?
b) What is their maximum waiting time?
b) What percentage of customer has to wait longer than 15 minutes??
c) Plot the number of baguettes over time for an arbitrary day.
   (use PLOTTING=True to do this)

"""
from SimPy.Simulation import *
from SimPy.SimPlot import *
import random
# Model components ------------------------


class Bakery:
    def __init__(self, nrOvens, toMonitor, sim):
        self.stock = Level(name="baguette stock", monitored=toMonitor, sim=sim)
        for i in range(nrOvens):
            ov = Oven(sim=sim)
            sim.activate(ov, ov.bake(capacity=batchsize, bakery=self))


class Oven(Process):
    def bake(self, capacity, bakery):
        while self.sim.now() + tBakeMax < tEndBake:
            yield hold, self, r.uniform(tBakeMin, tBakeMax)
            yield put, self, bakery.stock, capacity


class Customer(Process):
    def buyBaguette(self, cusType, bakery):
        tIn = self.sim.now()
        yield get, self, bakery.stock, r.choice(buy[cusType])
        waits[cusType].append(self.sim.now() - tIn)


class CustomerGenerator(Process):
    def generate(self, cusType, bakery):
        while True:
            yield hold, self, r.expovariate(1.0 / tArrivals[cusType])
            if self.sim.now() < (tShopOpen + tBeforeOpen):
                c = Customer(cusType, sim=self.sim)
                self.sim.activate(c, c.buyBaguette(cusType, bakery=bakery))
# Model -----------------------------------


class BakeryModel(Simulation):
    def run(self):
        # toMonitor=False
        self.initialize()
        toMoni = day == (nrDays - 1)
        b = Bakery(nrOvens=nrOvens, toMonitor=toMoni, sim=self)
        for cType in ["retail", "restaurant"]:
            cg = CustomerGenerator(sim=self)
            self.activate(cg, cg.generate(
                cusType=cType, bakery=b), delay=tBeforeOpen)
        self.simulate(until=tBeforeOpen + tShopOpen)
        return b


# Experiment data -------------------------
nrOvens = 3
batchsize = 40  # nr baguettes
tBakeMin = 25 / 60.
tBakeMax = 30 / 60.  # hours
tArrivals = {"retail": 1.0 / 40, "restaurant": 1.0 / 4}  # hours
buy = {"retail": [1, 2, 3], "restaurant": [20, 40, 60]}  # nr baguettes
tShopOpen = 8
tBeforeOpen = 1
tEndBake = tBeforeOpen + tShopOpen  # hours
nrDays = 100
r = random.Random(12371)
PLOTTING = True
# Experiment ------------------------------
waits = {}
waits["retail"] = []
waits["restaurant"] = []
bakMod = BakeryModel()
for day in range(nrDays):
    bakery = bakMod.run()
# Analysis/output -------------------------
print('bakery_OO')
for cType in ["retail", "restaurant"]:
    print("Average wait for {0} customers: {1:4.2f} hours"
          .format(cType, (1.0 * sum(waits[cType])) / len(waits[cType])))
    print("Longest wait for {0} customers: {1:4.1f} hours"
          .format(cType, max(waits[cType])))
    nrLong = len([1 for x in waits[cType] if x > 0.25])
    nrCust = len(waits[cType])
    print("Percentage of {0} customers having to wait for more than"
          " 0.25 hours: {1}".format(cType, 100 * nrLong / nrCust))


if PLOTTING:
    plt = SimPlot()
    plt.plotStep(bakery.stock.bufferMon,
                 title="Number of baguettes in stock during arbitrary day",
                 color="blue")
    plt.mainloop()

Bank Customers Demos SimPlot: bank11Plot.py

A modification of the bank11 simulation with graphical output. It plots service and waiting times.

""" bank11.py: Simulate customers arriving
    at random, using a Source, requesting service
    from two counters each with their own queue
    random servicetime.
    Uses a Monitor object to record waiting times
"""
from SimPy.Simulation import *
from SimPy.SimPlot import *
from random import Random


class Bank11(SimPlot):
    def __init__(self, **p):
        SimPlot.__init__(self, **p)

    def NoInSystem(self, R):
        """ The number of customers in the resource R
        in waitQ and active Q"""
        return (len(R.waitQ) + len(R.activeQ))

    def model(self):
        self.counterRV = Random(self.params["counterseed"])
        self.sourceseed = self.params["sourceseed"]
        nrRuns = self.params["nrRuns"]
        self.lastLeave = 0
        self.noRunYet = True
        for runNr in range(nrRuns):
            self.noRunYet = False
            self.Nc = 2
            self.counter = [Resource(name="Clerk0", monitored=False),
                            Resource(name="Clerk1", monitored=False)]
            self.waitMonitor = Monitor(name='Waiting Times')
            self.waitMonitor.xlab = 'Time'
            self.waitMonitor.ylab = 'Waiting time'
            self.serviceMonitor = Monitor(name='Service Times')
            self.serviceMonitor.xlab = 'Time'
            self.serviceMonitor.ylab = 'wait+service'
            initialize()
            source = Source(self, seed=self.sourceseed * 1000)
            activate(source, source.generate(self.params["numberCustomers"],
                                             self.params["interval"]), 0.0)
            simulate(until=self.params['endtime'])
            self.lastLeave += now()
        print("{0} run(s) completed".format(nrRuns))
        print("Parameters:\n{0}".format(self.params))


class Source(Process):
    """ Source generates customers randomly"""

    def __init__(self, modInst, seed=333):
        Process.__init__(self)
        self.modInst = modInst
        self.SEED = seed

    def generate(self, number, interval):
        rv = Random(self.SEED)
        for i in range(number):
            c = Customer(self.modInst, name="Customer{0:02d}".format(i))
            activate(c, c.visit(timeInBank=12.0))
            t = rv.expovariate(1.0 / interval)
            yield hold, self, t


class Customer(Process):
    """ Customer arrives, is served and leaves """

    def __init__(self, modInst, **p):
        Process.__init__(self, **p)
        self.modInst = modInst

    def visit(self, timeInBank=0):
        arrive = now()
        Qlength = [self.modInst.NoInSystem(self.modInst.counter[i])
                   for i in range(self.modInst.Nc)]
        for i in range(self.modInst.Nc):
            if Qlength[i] == 0 or Qlength[i] == min(Qlength):
                join = i
                break
        yield request, self, self.modInst.counter[join]
        wait = now() - arrive
        self.modInst.waitMonitor.observe(wait, t=now())
        # print("{0:7.4f} {1}: Waited {2:6.3f}".format(now(),self.name,wait))
        tib = self.modInst.counterRV.expovariate(1.0 / timeInBank)
        yield hold, self, tib
        yield release, self, self.modInst.counter[join]
        self.modInst.serviceMonitor.observe(now() - arrive, t=now())


root = Tk()
plt = Bank11()
plt.params = {"endtime": 2000,
              "sourceseed": 1133,
              "counterseed": 3939393,
              "numberCustomers": 50,
              "interval": 10.0,
              "trace": 0,
              "nrRuns": 1}
plt.model()
plt.plotLine(plt.waitMonitor, color='blue', width=2)
plt.plotLine(plt.serviceMonitor, color='red', width=2)
root.mainloop()

Debugger

Stepping thru Simpulation Events: SimpleDebugger.py

A utility module for stepping through the events of a simulation under user control, using SimulationTrace.

"""
A utility module for stepping through the events of a simulation
under user control.

REQUIRES SimPy 2.1
"""
from SimPy.SimulationTrace import *

from sys import version_info  # Needed to determine if Python 2 or 3
py_ver = version_info[0]


def stepper():
    evlist = Globals.sim._timestamps
    while True:
        if not evlist:
            print("No more events.")
            break
        tEvt = evlist[0][0]
        who = evlist[0][2]
        while evlist[0][3]:  # skip cancelled event notices
            step()
        print("\nTime now: {0}, next event at: {1} for process: {2} ".format(
            now(), tEvt, who.name))  # peekAll()[0],peekAll()[1].name)

        if(py_ver > 2):  # python3
            cmd = input(
                "'s' next event,'r' run to end,'e' to end run, "
                "<time> skip to event at <time>, 'l' show eventlist, "
                "'p<name>' skip to event for <name>: ")
        else:  # python2
            cmd = raw_input(
                "'s' next event,'r' run to end,'e' to end run, "
                "<time> skip to event at <time>, 'l' show eventlist, "
                "'p<name>' skip to event for <name>: ")
        try:
            nexttime = float(cmd)
            while peek() < nexttime:
                step()
        except:
            if cmd == 's':
                step()
            elif cmd == 'r':
                break
            elif cmd == 'e':
                stopSimulation()
                break
            elif cmd == 'l':
                print("Events scheduled: \n{0}".format(allEventNotices()))
            elif cmd[0] == 'p':
                while evlist and evlist[0][2].name != cmd[1:]:
                    step()
            else:
                print("{0} not a valid command".format(cmd))


if __name__ == "__main__":
    import random as r
    r.seed(1234567)

    class Test(Process):
        def run(self):
            while now() < until:
                yield hold, self, r.uniform(1, 10)

    class Waiter(Process):
        def run(self, evt):
            def gt30():
                return now() > 30

            yield waituntil, self, gt30
            print("now() is past 30")
            stopSimulation()

    until = 100
    initialize()
    evt = SimEvent()
    t = Test("Test1")
    activate(t, t.run())
    t2 = Test("Test2")
    activate(t2, t2.run())
    w = Waiter("Waiter")
    activate(w, w.run(evt=evt))
    stepper()

Here is the OO version:

"""
A utility module for stepping through the events of a simulation
under user control.

REQUIRES SimPy 2.1
"""
from SimPy.SimulationTrace import *

from sys import version_info  # Needed to determine if using Python 2 or 3
py_ver = version_info[0]


def stepper(whichsim):
    evlist = whichsim._timestamps
    while True:
        if not evlist:
            print("No more events.")
            break
        tEvt = evlist[0][0]
        who = evlist[0][2]
        while evlist[0][3]:  # skip cancelled event notices
            whichsim.step()
        print("\nTime now: {0}, next event at: {1} for process: {2} ".format(
            whichsim.now(), tEvt, who.name))

        if(py_ver > 2):  # Python 3
            cmd = input(
                "'s' next event,'r' run to end,'e' to end run, "
                "<time> skip to event at <time>, 'l' show eventlist, "
                "'p<name>' skip to event for <name>: ")
        else:  # Python 2
            cmd = raw_input(
                "'s' next event,'r' run to end,'e' to end run, "
                "<time> skip to event at <time>, 'l' show eventlist, "
                "'p<name>' skip to event for <name>: ")
        try:
            nexttime = float(cmd)
            while whichsim.peek() < nexttime:
                whichsim.step()
        except:
            if cmd == 's':
                whichsim.step()
            elif cmd == 'r':
                break
            elif cmd == 'e':
                stopSimulation()
                break
            elif cmd == 'l':
                print("Events scheduled: \n{0}".format(
                    whichsim.allEventNotices()))
            elif cmd[0] == 'p':
                while evlist and evlist[0][2].name != cmd[1:]:
                    whichsim.step()
            else:
                print("{0} not a valid command".format(cmd))


if __name__ == "__main__":
    import random as r
    r.seed(1234567)

    class Test(Process):
        def run(self):
            while self.sim.now() < until:
                yield hold, self, r.uniform(1, 10)

    class Waiter(Process):
        def run(self, evt):
            def gt30():
                return self.sim.now() > 30

            yield waituntil, self, gt30
            print("now() is past 30")
            self.sim.stopSimulation()

    until = 100
    s = SimulationTrace()
    s.initialize()
    evt = SimEvent(sim=s)
    t = Test("Test1", sim=s)
    s.activate(t, t.run())
    t2 = Test("Test2", sim=s)
    s.activate(t2, t2.run())
    w = Waiter("Waiter", sim=s)
    s.activate(w, w.run(evt=evt))
    stepper(whichsim=s)
Authors:
Created:

2002-December

Date:

2012-April