salabim: an alternative package for discrete event simulation in Python

1,497 views
Skip to first unread message

Ruud van der Ham

unread,
Aug 19, 2017, 12:47:59 PM8/19/17
to python-simpy
I would like to present here a new discrete simulation package for Python, called salabim.

This might be interesting for (potential) SimPy users.

The process control resembles more the SimPy 2 interface, with statements
like activate, hold and passivate. This is more in line with University of Delft
methodology as seen in tools like Prosim, must and Tomas.

The salabim package supports SimPy like resources and containers,
a full set of process control (including a [powerful standby concept).
The salabim package uses SimPy like environments, but supports also an easy
default environment, so the environment does not have to be passed explicitly
in most cases. 
Salabim offers queue handling, tracing, statistical distributions, monitors (for data 
collection and presentation) and an animation engine, which runs both under 
CPython (with tkinter) and iPad Pythonista.
The animation engine is able to produce videos without any additional components.

To give an idea of a salabim model, we show here our version of the Bank Renege
example model (from the SimPy 3 documentation):

"""
Bank renege example

Scenario:
  A counter with a random service time and customers who renege. Based on the
  program bank08.py from TheBank tutorial of SimPy 2. (KGM)

"""
import salabim as sim

NEW_CUSTOMERS = 5  # Total number of customers
INTERVAL_CUSTOMERS = 10.0  # Generate new customers roughly every x seconds
MIN_PATIENCE = 1  # Min. customer patience
MAX_PATIENCE = 3  # Max. customer patience
TIME_IN_BANK=12

class Source(sim.Component):
    """Source generates customers randomly"""
    def process(self):
        for i in range(NEW_CUSTOMERS):
            c = Customer()
            yield self.hold(sim.Exponential(INTERVAL_CUSTOMERS).sample())

class Customer(sim.Component):
    """Customer arrives, is served and leaves."""
    def process(self):
        arrive=de.now()
        de.print_trace('',self.name(),'arrrived')
    
        patience = sim.Uniform(MIN_PATIENCE, MAX_PATIENCE).sample()
        yield self.request(counter,fail_delay=patience)
        wait = de.now() - arrive
    
        if self.request_failed(): 
            # We reneged
            de.print_trace('',self.name(),'RENEGED after {:6.3f}'.format(wait))
        else:
            # We got to the counter
            de.print_trace('',self.name(),'waited for {:6.3f}'.format(wait))
            yield self.hold(sim.Exponential(TIME_IN_BANK).sample())
            de.print_trace('',self.name(),'finished')
            # auto release counter

# Setup and start the simulation
print('Bank renege')
de = sim.Environment(trace=True)

# Start processes and run

counter = sim.Resource(capacity=1)
Source()

de.run()
    
The output looks like:

Bank renege
     0.000 main                 current                             
                                source.............0 activate       scheduled for      0.000 @process
                                main run                            scheduled for        inf
     0.000 source.............0 current                             
                                customer...........0 activate       scheduled for      0.000 @process
                                source.............0 hold           scheduled for      3.577
     0.000 customer...........0 current                             
           customer...........0 arrrived                            
                                customer...........0                request for 1 from resource...........0 
                                customer...........0 request honour scheduled for      0.000
     0.000 customer...........0 current                             
           customer...........0 waited for  0.000                   
                                customer...........0 hold           scheduled for      7.461
     3.577 source.............0 current                             
                                customer...........1 activate       scheduled for      3.577 @process
                                source.............0 hold           scheduled for      6.572
     3.577 customer...........1 current                             
           customer...........1 arrrived                            
                                customer...........1                request for 1 from resource...........0 
                                customer...........1 request        scheduled for      5.134
     5.134 customer...........1 current                             
                                customer...........1                request failed
           customer...........1 RENEGED after  1.557                
     5.134 customer...........1 ended                               
     6.572 source.............0 current                             
                                customer...........2 activate       scheduled for      6.572 @process
                                source.............0 hold           scheduled for      8.728
     6.572 customer...........2 current                             
           customer...........2 arrrived                            
                                customer...........2                request for 1 from resource...........0 
                                customer...........2 request        scheduled for      8.465
     7.461 customer...........0 current                             
           customer...........0 finished                            
                                customer...........0                release 1 from resource...........0
                                customer...........2 request honour scheduled for      7.461
     7.461 customer...........0 ended                               
     7.461 customer...........2 current                             
           customer...........2 waited for  0.889                   
                                customer...........2 hold           scheduled for     15.157
     8.728 source.............0 current                             
                                customer...........3 activate       scheduled for      8.728 @process
                                source.............0 hold           scheduled for     17.645
     8.728 customer...........3 current                             
           customer...........3 arrrived                            
                                customer...........3                request for 1 from resource...........0 
                                customer...........3 request        scheduled for     11.394
    11.394 customer...........3 current                             
                                customer...........3                request failed
           customer...........3 RENEGED after  2.666                
    11.394 customer...........3 ended                               
    15.157 customer...........2 current                             
           customer...........2 finished                            
                                customer...........2                release 1 from resource...........0
    15.157 customer...........2 ended                               
    17.645 source.............0 current                             
                                customer...........4 activate       scheduled for     17.645 @process
                                source.............0 hold           scheduled for     26.062
    17.645 customer...........4 current                             
           customer...........4 arrrived                            
                                customer...........4                request for 1 from resource...........0 
                                customer...........4 request honour scheduled for     17.645
    17.645 customer...........4 current                             
           customer...........4 waited for  0.000                   
                                customer...........4 hold           scheduled for     19.997
    19.997 customer...........4 current                             
           customer...........4 finished                            
                                customer...........4                release 1 from resource...........0
    19.997 customer...........4 ended                               
    26.062 source.............0 current                             
    26.062 source.............0 ended                               
       inf main                 current  

Note that the general structure is very similar to SimPy (particularly to version 2). 

Unfortunately, the documentation is not yet finished, although all methods and
functions are properly documented, both in the on-line manual and docstrings.
We are working hard to improve the manual.

We welcome SimPy users to have a look at salabim at www.salabim.org. There you
will find a link to GitHub, where the package, several sample models and the 
release notes are available for download.
On www.salabim.org you will also find a link to the google user group and 
a e-mail address.

The package is distributed under the MIT license.

Ruud van der Ham

unread,
Aug 22, 2017, 4:50:45 AM8/22/17
to python-simpy
Here is the salabim version of the Machine Shop as presented in the SimPy manual.
The results are exactly the same as in the original version.

"""
Machine shop example

Covers:

- Interrupts
- Resources: PreemptiveResource

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. Broken machines
  preempt theses tasks. The repairman continues them when he is done
  with the machine repair. The workshop works continuously.

"""
import random

import salabim as sim

RANDOM_SEED = 42
PT_MEAN = 10.0         # Avg. processing time in minutes
PT_SIGMA = 2.0         # Sigma of processing time
MTTF = 300.0           # Mean time to failure in minutes
BREAK_MEAN = 1 / MTTF  # Param. for expovariate distribution
REPAIR_TIME = 30.0     # Time it takes to repair a machine in minutes
JOB_DURATION = 30.0    # Duration of other jobs in minutes
NUM_MACHINES = 10      # Number of machines in the machine shop
WEEKS = 4              # Simulation time in weeks
SIM_TIME = WEEKS * 7 * 24 * 60  # Simulation time in minutes


def time_per_part():
    """Return actual processing time for a concrete part."""
    return random.normalvariate(PT_MEAN, PT_SIGMA)


def time_to_failure():
    """Return time until next failure for a machine."""
    return random.expovariate(BREAK_MEAN)

class Machine(sim.Component):
    """A machine produces parts and my get broken every now and then.

    If it breaks, it requests a *repairman* and continues the production
    after the it is repaired.

    A machine has a *name* and a numberof *parts_made* thus far.

    """
    def __init__(self):
        super().__init__()
        self.parts_made = 0
        self.broken = False
        self.disturber=Disturber(self)
        
    def process(self):
        while True:
            self.remaining_time=time_per_part()
            while self.remaining_time>1e-8:
                yield self.hold(self.remaining_time,mode='work')
                self.remaining_time-=(self.env.now()-self.mode_time())
                if self.broken:
                    if repairman.claimers()[0]==other:
                        other.release()
                        other.activate()
                    yield self.request((repairman,1,0))
                    yield self.hold(REPAIR_TIME)
                    self.release()
                    self.broken=False
            self.parts_made += 1

class Disturber(sim.Component):
    def __init__(self,machine):
        super().__init__(name='disturber.')
        self.machine=machine
            
    def process(self):
        while True:
            yield self.hold(time_to_failure())
            if not self.machine.broken:
                self.machine.broken=True
                self.machine.activate() #postpone work
            
class Other(sim.Component):
    def process(self):
        while True:
            self.remaining_time=JOB_DURATION
            while self.remaining_time>1e-8:
                yield self.request((repairman,1,1))
                yield self.hold(self.remaining_time,mode='work')
                self.remaining_time-=(self.env.now()-self.mode_time())
            other.release()
                
# Setup and start the simulation
print('Machine shop')
de=sim.Environment() 
random.seed(RANDOM_SEED)  # This helps reproducing the results

repairman = sim.Resource('repairman',monitor=False)

machines = [Machine() for i in range(NUM_MACHINES)]
other=Other()

# Execute!
de.run(till=SIM_TIME*10)
# Analyis/results
print('Machine shop results after %s weeks' % WEEKS)
for machine in machines:
    print('%s made %d parts.' % (machine.name(), machine.parts_made))


Reply all
Reply to author
Forward
0 new messages