Component

Components are the key elements of salabim simulations.

Components can be either data or active. An active component has one or more process descriptions and is activated at some point of time. You can make a data component active with activate. And an active component can become data either with a cancel or by reaching the end of its process method.

It is easy to create a data component by:

data_component = sim.Component()

Data components may be placed in a queue. This component will not be actiavted as there is no associated process method.

In order to make an active component it is necessary to first define a class:

class Ship(sim.Component):

And then there has to be a (usually generator) method, normally called process:

class Ship(sim.Component):
    def process(self):
        ....
        yield ...
        ....

Normally, the process will contain at least one yield (or yield from) statement. But that’s not a requirement.

Creation and activation can be combined by making a new instance of the class:

ship1 = Ship()
ship2 = Ship()
ship3 = Ship()

This causes three Ships to be created and to start them at Sim.process(). The ships will automatically get the name ship.0, etc., unless a name is given explicitly.

If no process method is found for Ship, the ship will be a data component. In that case, it may become active by means of an activate statement:

class Crane(sim.Component):
    def unload(self):
        ....
        yield ...
        ....

crane1 = Crane()
crane1.activate(process='unload')

crane2 = Crane(process='unload')

Effectively, creation and start of crane1 and crane2 is the same.

Although not very common, it is possible to activate a component at a certain time or with a specified delay:

ship1.activate(at=100)
ship2.activate(delay=50)

At time of creation it is sometimes useful to be able to set attributes, prepare for actions, etc. This is possible in salabim by defining an __init__ and/or a setup method:

If the __init__ method is used, it is required to call the Component.__init__ method from within the overridden method:

class Ship(sim.Component):
    def __init__(self, length, *args, **kwargs):
        sim.Component.__init__(self, *args, **kwargs)
        self.length = length

ship = Ship(length=250)

This sets ship.length to 250.

In most cases, the setup method is preferred, however. This method is called after ALL initialization code of Component is executed.

class Ship(sim.Component):
    def setup(self, length):
        self.length = length

ship = Ship(length=250)

Now, ship.length will be 250.

Note that setup gets all arguments and keyword arguments, that are not ‘consumed’ by __init__ and/or the process call.

Only in very specific cases, __init__ will be necessary.

Note that the setup code can be used for data components as well.

Process interaction

A component may be in one of the following states:

  • data
  • current
  • scheduled
  • passive
  • requesting
  • waiting
  • standby
  • interrupted

The scheme below shows how components can go from state to state.

from/to data current scheduled passive requesting waiting standby interrupted
data   activate[1] activate          
current process end   yield hold yield passivate yield request yield wait yield standby  
. yield cancel   yield activate          
scheduled cancel next event hold passivate request wait standby interrupt
.     activate          
passive cancel activate[1] activate   request wait standby interrupt
.     hold[2]          
requesting cancel claim honor activate[3] passivate request wait standby interrupt
.   time out     activate[4]      
waiting cancel wait honor activate[5] passivate wait wait standby interrupt
.   timeout       activate[6]    
standby cancel next event activate passivate request wait   interrupt
interrupted cancel   resume[7] resume[7] resume[7] resume[7] resume[7] interrupt[8]
.     activate passivate request wait standby  

[1] via scheduled
[2] not recommended
[3] with keep_request=False (default)
[4] with keep_request=True. This allows to set a new time out
[5] with keep_wait=False (default)
[6] with keep_wait=True. This allows to set a new time out
[7] state at time of interrupt
[8] increases the interrupt_level

Creation of a component

Although it is possible to create a component directly with x=sim.Component(), this makes it very hard to make that component into an active component, because there’s no process method. So, nearly always we define a class based on sim.Component

def Car(sim.Component):
    def process(self):
        ...

If we then say car=Car(), a component is created and it activated from process. This process is nearly always, but not necessarily a generator method (i.e. it has at least one yield (or yield from) statement.

The result is that car is put on the future event list (for time now) and when it’s its turn, the component becomes current.

It is also possible to set a time at which the component (car) becomes active, like car=Car(at=10).

And instead of starting at process, the component may be initialized to start at another (generator) method, like car=Car(process='wash').

And, finally, if there is a process method, you can disable the automatic activation (i.e. make it a data component) , by specifying process=''.

If there is no process method, and process= is not given, the component will be a data component.

activate

Activate is the way to turn a data component into a live component. If you do not specify a process, the generator function process is assumed. So you can say

car0 = Car(process='')  # data component
car0.activate()  # activate @ process if exists, otherwise error
car1 = Car(process='')  # data component
car1.activate(process='wash')  # activate @ wash
  • If the component to be activated is current, always use yield self.activate. The effect is that the component becomes scheduled, thus this is essentially equivalent to the preferred hold method.
  • If the component to be activated is passive, the component will be activated at the specified time.
  • If the component to be activated is scheduled, the component will get a new scheduled time.
  • If the component to be activated is requesting, the request will be terminated, the attribute failed set and the component will become scheduled. If keep_request=True is specified, only the fail_at will be updated and the component will stay requesting.
  • If the component to be activated is waiting, the wait will be terminated, the attribute failed set and the component will become scheduled. If keep_wait=True is specified, only the fail_at will be updated and the component will stay waiting.
  • If the component to be activated is standby, the component will get a new scheduled time and become scheduled.
  • If the component is interrupted, the component will be activated at the specified time.

hold

Hold is the way to make a, usually current, component scheduled.

  • If the component to be held is current, the component becomes scheduled for the specified time. Always use yield self.hold() is this case.
  • If the component to be held is passive, the component becomes scheduled for the specified time.
  • If the component to be held is scheduled, the component will be rescheduled for the specified time, thus essentially the same as activate.
  • If the component to be held is standby, the component becomes scheduled for the specified time.
  • If the component to be activated is requesting, the request will be terminated, the attribute failed set and the component will become scheduled. It is recommended to use the more versatile activate method.
  • If the component to be activated is waiting, the wait will be terminated, the attribute failed set and the component will become scheduled. It is recommended to use the more versatile activate method.
  • If the component is interrupted, the component will be activated at the specified time.

passivate

Passivate is the way to make a, usually current, component passive. This isessentially the same as scheduling for time=inf.

  • If the component to be passivated is current, the component becomes passive. Always use yield seld.passivate() is this case.
  • If the component to be passivated is passive, the component remains passive.
  • If the component to be passivated is scheduled, the component becomes passive.
  • If the component to be held is standby, the component becomes passive.
  • If the component to be activated is requesting, the request will be terminated, the attribute failed set and the component becomes passive. It is recommended to use the more versatile activate method.
  • If the component to be activated is waiting, the wait will be terminated, the attribute failed set and the component becomes passive. It is recommended to use the more versatile activate method.
  • If the component is interrupted, the component becomes passive.

cancel

Cancel has the effect that the component becomes a data component.

  • If the component to be cancelled is current, always use yield self.cancel().
  • If the component to be cancelled is passive, scheduled, interrupted or standby, the component becomes a data component.
  • If the component to be cancelled is requesting, the request will be terminated, the attribute failed set and the component becomes a data component.
  • If the component to be cancelled is waiting, the wait will be terminated, the attribute failed set and the component becomes a data component.

standby

Standby has the effect that the component will be triggered on the next simulation event.

  • If the component is current, use always yield self.standby()
  • Although theoretically possible, it is not recommended to use standby for non current components.

request

Request has the effect that the component will check whether the requested quantity from a resource is available. It is possible to check for multiple availability of a certain quantity from several resources. By default, there is no limit on the time to wait for the resource(s) to become available. But, it is possible to set a time with fail_at at which the condition has to be met. If that failed, the component becomes current at the given point of time. The code should then check whether the request had failed. That can be checked with the Component.failed() method.

If the component is canceled, activated, passivated, interrupted or held the failed flag will be set as well.

  • If the component is current, always use yield self.request()
  • Although theoretically possible it is not recommended to use request for non current components.

wait

Wait has the effect that the component will check whether the value of a state meets a given condition. available. It is possible to check for multiple states. By default, there is no limit on the time to wait for the condition(s) to be met. But, it is possible to set a time with fail_at at which the condition has to be met. If that failed, the component becomes current at the given point of time. The code should then check whether the wait had failed. That can be checked with the Component.failed() method.

If the component is canceled, activated, passivated, interrupted or held the failed flag will be set as well.

  • If the component is current, use always yield self.wait()
  • Although theoretically possible it is not recommended to use wait for non current components.

interrupt

With interrupt components that are not current or data can be temporarily be interrupted. Once a resume is called for the component, the component will continue (for scheduled with the remaining time, for waiting or requesting possibly with the remaining fail_at duration).

Usage of process interaction methods within a function or method

There is a way to put process interaction statement in another function or method. This requires a slightly different way than just calling the method.

As an example, let’s assume that we want a method that holds a component for a number of minutes and that the time unit is actually seconds. So we need a method to wait 60 times the given parameter

We start with a not so nice solution:

class X(sim.Component):
    def process(self):
        yield self.hold(60 * 2)
        yield self.hold(60 * 5)

Now we just addd a method hold_minutes:

def hold_minutes(self, minutes):
    yield self.hold(60 * minutes)

Direct calling hold_minutes is not possible. Instead we have to say:

class X(sim.Component):
   def hold_minutes(self, minutes):
        yield self.hold(60 * minutes)

   def process(self):
        yield from self.hold_minutes(2)
        yield from self.hold_minutes(5)

All process interaction statements including passivate, request and wait can be used that way!

So remember if the method contains a yield statement (technically speaking that’s a generator method), it should be called with yield from.