Examples

This small example guide will cover the basic start-up and the three main elements of the OpenClSim package:

  • Start-Up (Minimal set-up)
  • Locations (Sites or stockpiles)
  • Resources (Processors and transporters)
  • Activities (Rule-based operations)

Once the elements above are explained some small simulations examples are presented.

Start-Up

The first part of every OpenClSim simulation is to import the required libraries and to initiate the simulation environment.

Required Libraries

Depending on the simulation it might be required to import additional libraries. The minimal set-up of an OpenCLSim project has the following import statements:

# Import openclsim for the logistical components
import openclsim.model as model
import openclsim.core as core

# Import simpy for the simulation environment
import simpy

Simulation Environment

OpenClSim continues on the SimPy discrete event simulation package. Some components are modified, such as the resources and container objects, but the simulation environment is pure SimPy. Starting the simulation environment can be done with the following line of code. For more information in SimPy environment please refer to the SimPy documentation.

# Start the SimPy environment
env = simpy.Environment()

Locations

Basic processes do not require a location but more comprehensive simulations do. Locations are added to an OpenCLSim environment so that it becomes possible to track all events in both time and space. If a site is initiated with a container it can store materials as well. Adding a processor allows the location to load or unload as well.

Basic Location

The code below illustrates how a basic location can be created using OpenClSim. Such a location can be used to add information on events in space, such as tracking movement events or creating paths to follow.

# Import the library required to add coordinates
import shapely.geometry

# Create a location class
Location = type(
    "Location",
    (
        core.Identifiable,  # Give it a name and unique UUID
        core.Log,           # To keep track of all events
        core.HasResource,   # Add information on the number of resources
        core.Locatable,     # Add coordinates to extract distance information
    ),
    {},
)

location_data = {
    "env": env,                               # The SimPy environment
    "name": "Location 01",                    # Name of the location
    "geometry": shapely.geometry.Point(0, 0), # The lat, lon coordinates
}

location_01 = Location(**location_data)

Storage Location

The code below illustrates how a location can be created that is capable of storing an amount. Such a location can be used by the OpenClSim.model activities as origin or destination.

# Import the library required to add coordinates
import shapely.geometry

# Create a location class
StorageLocation = type(
    "StorageLocation",
    (
        core.Identifiable,  # Give it a name and unique UUID
        core.Log,           # To keep track of all events
        core.HasResource,   # Add information on the number of resources
        core.Locatable,     # Add coordinates to extract distance information
        core.HasContainer,  # Add information on storage capacity
    ),
    {},
)

location_data = {
    "env": env,                               # The SimPy environment
    "name": "Location 02",                    # Name of the location
    "geometry": shapely.geometry.Point(0, 0), # The lat, lon coordinates
    "capacity": 10_000,                       # The maximum number of units
    "level": 10_000,                          # The number of units in the location
}

location_02 = StorageLocation(**location_data)

Processing Storage Location

The code below illustrates how a location can be created that is capable of storing an amount. Additional to the storage location, a processing- and storage location can be used as both the origin and loader or destination and unloader in a OpenClSim.model activity.

# Import the library required to add coordinates
import shapely.geometry

# Create a location class
ProcessingStorageLocation = type(
    "ProcessingStorageLocation",
    (
        core.Identifiable,  # Give it a name and unique UUID
        core.Log,           # To keep track of all events
        core.HasResource,   # Add information on the number of resources
        core.Locatable,     # Add coordinates to extract distance information
        core.HasContainer,  # Add information on storage capacity
        core.Processor,     # Add information on processing
    ),
    {},
)

# Create a processing function
processing_rate = lambda x: x

location_data = {
    "env": env,                               # The SimPy environment
    "name": "Location 03",                    # Name of the location
    "geometry": shapely.geometry.Point(0, 1), # The lat, lon coordinates
    "capacity": 10_000,                       # The maximum number of units
    "level": 0,                               # The number of units in the location
    "loading_func": processing_rate,          # Loading rate of 1 unit per 1 unit time
    "unloading_func": processing_rate,        # Unloading rate of 1 unit per 1 unit time
}

location_03 = ProcessingStorageLocation(**location_data)

Optionally a OpenCLSim.core.Log mixin can be added to all locations to keep track of all the events that are taking place.

Resources

OpenCLSim resources can be used to process and transport units. The OpenCLSim.model activity class requires a loader, an unloader and a mover, this are examples of resources. A resource will always interact with another resource in an OpenClSim.model activity, but it is possible to initiate a simpy process to keep track of a single resource.

Processing Resource

An example of a processing resource is a harbour crane, it processes units from a storage location to a transporting resource or vice versa. In the OpenClSim.model activity such a processing resource could be selected as the loader or unloader. The example code is presented below.

# Create a resource
ProcessingResource = type(
    "ProcessingResource",
    (
        core.Identifiable,  # Give it a name and unique UUID
        core.Log,           # To keep track of all events
        core.HasResource,   # Add information on the number of resources
        core.Locatable,     # Add coordinates to extract distance information
        core.Processor,     # Add information on processing
    ),
    {},
)

# The next step is to define all the required parameters for the defined metaclass
# Create a processing function
processing_rate = lambda x: x

resource_data = {
    "env": env,                         # The SimPy environment
    "name": "Resource 01",              # Name of the location
    "geometry": location_01.geometry,   # The lat, lon coordinates
    "loading_func": processing_rate,    # Loading rate of 1 unit per 1 unit time
    "unloading_func": processing_rate,  # Unloading rate of 1 unit per 1 unit time
}

# Create an object based on the metaclass and vessel data
resource_01 = ProcessingResource(**resource_data)

Transporting Resource

A harbour crane will service transporting resources. To continue with the harbour crane example, basically any vessel is a transporting resource because it is capable of moving units from location A to location B. In the OpenClSim.model activity such a processing resource could be selected as the mover.

# Create a resource
TransportingResource = type(
    "TransportingResource",
    (
        core.Identifiable,              # Give it a name and unique UUID
        core.Log,                       # To keep track of all events
        core.HasResource,               # Add information on the number of resources
        core.ContainerDependentMovable, # It can transport an amount
    ),
    {},
)

# The next step is to define all the required parameters for the defined metaclass
# For more realistic simulation you might want to have speed dependent on the filling degree
v_full = 8  # meters per second
v_empty = 5  # meters per second

def variable_speed(v_empty, v_full):
    return lambda x: x * (v_full - v_empty) + v_empty

# Other variables
resource_data = {
    "env": env,                                   # The SimPy environment
    "name": "Resource 02",                        # Name of the location
    "geometry": location_01.geometry,             # The lat, lon coordinates
    "capacity": 5_000,                            # Capacity of the vessel
    "compute_v": variable_speed(v_empty, v_full), # Variable speed
}

# Create an object based on the metaclass and vessel data
resource_02 = TransportingResource(**resource_data)

Transporting Processing Resource

Finally, some resources are capable of both processing and moving units. Examples are dredging vessels or container vessels with deck cranes. These specific vessels have the unique property that they can act as the loader, unloader and mover in the OpenClSim.model activity.

# Create a resource
TransportingProcessingResource = type(
    "TransportingProcessingResource",
    (
        core.Identifiable,              # Give it a name and unique UUID
        core.Log,                       # To keep track of all events
        core.HasResource,               # Add information on the number of resources
        core.ContainerDependentMovable, # It can transport an amount
        core.Processor,                 # Add information on processing
    ),
    {},
)

# The next step is to define all the required parameters for the defined metaclass
# For more realistic simulation you might want to have speed dependent on the filling degree
v_full = 8  # meters per second
v_empty = 5  # meters per second

def variable_speed(v_empty, v_full):
    return lambda x: x * (v_full - v_empty) + v_empty

# Create a processing function
processing_rate = lambda x: x

# Other variables
resource_data = {
    "env": env,                                   # The SimPy environment
    "name": "Resource 03",                        # Name of the location
    "geometry": location_01.geometry,             # The lat, lon coordinates
    "capacity": 5_000,                            # Capacity of the vessel
    "compute_v": variable_speed(v_empty, v_full), # Variable speed
    "loading_func": processing_rate,              # Loading rate of 1 unit per 1 unit time
    "unloading_func": processing_rate,            # Unloading rate of 1 unit per 1 unit time
}

# Create an object based on the metaclass and vessel data
resource_03 = TransportingProcessingResource(**resource_data)

Simulations

The code below will start the simulation if SimPy processes are added to the environment. These SimPy processes can be added using a combination of SimPy and OpenCLSim, or by using OpenCLSim activities.

env.run()

SimPy processes

A SimPy process can be initiated using the code below. The code below will instruct Resource 02, which was a TransportingResource, to sail from Location 01 (at Lat, Long (0, 0)) to Location 02 (at Lat, Long (0, 1)). The simulation will stop as soon as Resource 02 is at Location 02.

# Create the process function
def move_resource(mover, destination):

    # the is_at function is part of core.Movable
    while not mover.is_at(destination):

      # the move function is part of core.Movable
      yield from mover.move(destination)

# Add to the SimPy environment
env.process(move_resource(resource_02, location_03))

# Run the simulation
env.run()

Unconditional Activities

Activities are at the core of what OpenCLSim adds to SimPy, an activity is a collection of SimPy Processes. These activities schedule cyclic events, which could be production or logistical processes and, but the current OpenCLSim.model.activity assumes the following cycle:

  • Loading
  • Transporting
  • Unloading
  • Transporting

This cycle is repeated until a certain condition is met. Between the individual components of the cycle waiting events can occur due to arising queues, equipment failure or weather events. The minimal input for an activity is listed below.

  • Origin
  • Destination
  • Loader
  • Mover
  • Unloader

If no additional input is provided, the cyclic process will be repeated until either the origin is empty or the destination is full. The example activity below will stop after two cycles because the origin will be empty and the destination will be full.

# Define the activity
activity_01 = model.Activity(
    env=env,                  # The simpy environment defined in the first cel
    name="Activity 01",       # Name of the activity
    origin=location_02,       # Location 02 was filled with 10_000 units
    destination=location_03,  # Location 03 was empty
    loader=resource_03,       # Resource 03 could load
    mover=resource_03,        # Resource 03 could move
    unloader=resource_03,     # Resource 03 could unload
)

# Run the simulation
env.run()

Conditional Activities

Additionally, start and stop events can be added to the activity. The process will only start as soon as a start event (or a list of start events) is completed and it will stop as soon as the stop event (or a list of stop events) are completed. These can be any SimPy event, such as a time-out, but OpenClSim provides some additional events as well, such as empty- or full events. The activity in the example below will start as soon as the previous activity is finished, but not sooner than 2 days after the simulation is started.

# Activity starts after both
#  - Activity 01 is finished
#  - A minimum of 2 days after the simulation starts
start_event = [activity_01.main_process, env.timeout(2 * 24 * 3600)]

# Define the activity
activity_02 = model.Activity(
    env=env,                  # The simpy environment defined in the first cel
    name="Activity 02",       # Name of the activity
    origin=location_03,       # Location 03 will be filled
    destination=location_02,  # Location 02 will be empty
    loader=resource_03,       # Resource 03 could load
    mover=resource_03,        # Resource 03 could move
    unloader=resource_03,     # Resource 03 could unload
    start_event=start_event,  # Start Event
)

# Run the simulation
env.run()