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_component0 = sim.Component()
data_component1 = env.Component()

Data components may be placed in a queue. This component will not be activated 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 method, normally called process:

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

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):
        ...

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 parent __init__ method from within the overridden method:

class Ship(sim.Component):
    def __init__(self, length, *args, **kwargs):
        super().__init__(*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 table below shows how components can go from state to state.

_images/component0.png

Creation of a component

Although it is possible to create a component directly with x=sim.Component(), this makes it 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.

The result is that car is put on the future event list (for time now) and when it is 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 (usually 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, use 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.

  • 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.

  • 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 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.

  • 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.

Instead of checking for all of number of resources, it is also possible to check for any of a number of resources, by setting the oneof parameter to True.

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.

  • 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. 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.

  • Although theoretically possible it is not recommended to use wait for non current components.

interrupt

With interrupt scheduled and interrupted components can be temporarily be interrupted. Once a resume is called for the component, the component will continue for scheduled with the remaining time.

Using priority and urgent to control order of execution

All process interaction methods supports a priority and urgent parameter:

With priority it is possible to sort a component before or after other components, scheduled for the same time. Note that the urgent parameters only applies to components scheduled with the same time and same priority.

The priority is 0 by default.

This is particularly useful for race conditions. It is possible to change the priority of a component by cancelling it prior to activating it with another priority.

The priority can be accessed with the Component.scheduled_priority() method.

Status of a component

The status of a component can be any of:

  • sim.data = “data”

  • sim.current = “current”

  • sim.standby = “standby”

  • sim.passive = “passive”

  • sim.interrupted = “interrupted”

  • sim.scheduled = “scheduled”

  • sim.requesting = “requesting”

  • sim.waiting = “waiting”

The status is automatically tracked in the status level monitor. Thus it possible to check how long a component has been in passive state with

passive_duration = component.status.value_duration("passive")

And it is possible to print a histogram with all the statuses a component has been in with

component.status.print_histogram(values=True)

Handling negative durations

When sampling from a distribution (particularly a normal distribution), a negative duration may be returned

self.hold(sim.Normal(5, 3))

Such negative values can’t be used to hold a period, as salabim can’t go back in time and thus raise an exception.

There are a couple of ways to prevent a negative value to be returned

self.hold(sim.Normal(5, 3).bounded_sample(lowerbound=0))

self.hold(sim.Bounded(sim.Normal(5, 3), lower_bound=0))

In both cases, if negative value is sampled, salabim will resample until a value >=0 is found.

Alternatively

self.hold(sim.Map(sim.Normal(5, 3), lambda x: 0 if x<0 else x)

This will map negative values to 0.

Finally, we can do

self.hold(Normal(5, 3), cap_now=True)

The latter version just uses 0 in case a negative value is given. Be careful with this as it might hide model mistakes.

The cap_now parameter is available for each method that sets a scheduled time.

The cap_now functionality can also be enabled globally

sim.default_cap_now(True)

Or for a certain block

with sim.cap_now():
    self.hold(Normal(5, 3))
    self.hold(Normal(12, 7))