SingleRun + WeatherCriterion#

This notebook provides an example of a simulation that takes a number of sub_processes, grouped in a SequentialActivity, that is executed while a stop condition is not yet met. In this case the elements of the sub_processes have weather limitations. A separate ‘significant wave height’ criterion is defined for moving and for amount shifting.

For this example we work with the following sub processes:

  • sailing empty

  • loading

  • sailing full

  • unloading

0. Import libraries#

import datetime, time
import simpy

import shapely.geometry
import pandas as pd
import numpy as np

import openclsim.core as core
import openclsim.utils as utils
import openclsim.model as model
import openclsim.plot as plot
import openclsim.plugins as plugin

from plotly.offline import init_notebook_mode, iplot
import plotly.graph_objs as go

src_path = utils.find_src_path()
init_notebook_mode(connected=True)

1. Initialise simpy environment#

# setup environment (simulation time needs to match the available weather data)
simulation_start  = datetime.datetime(2010, 1, 1, 0, 0)
my_env = simpy.Environment(initial_time=simulation_start.timestamp())

2. Define object classes#

# create a Site object based on desired mixin classes
Site = type(
    "Site",
    (
        core.Identifiable,
        core.Log,
        core.Locatable,
        core.HasContainer,
        core.HasResource,
    ),
    {},
)

# create a TransportProcessingResource object based on desired mixin classes
TransportProcessingResource = type(
    "TransportProcessingResource",
    (
        core.ContainerDependentMovable,
        core.Processor,
        core.HasResource,  # NB: LoadingFunction and UnloadingFunction are not mixed in
        core.Identifiable,
        core.Log,
    ),
    {},
)

# create a TestMoveActivity object based on desired mixin classes
TestMoveActivity =  type(
    "TestMoveActivity",
    (
        plugin.HasWeatherPluginActivity,
        model.MoveActivity,  # the order is critical!
    ),
    {},
)

# create a TestShiftActivity object based on desired mixin classes
TestShiftActivity =  type(
    "TestShiftActivity",
    (
        plugin.HasWeatherPluginActivity,
        model.ShiftAmountActivity,  # the order is critical!
    ),
    {},
)

3. Create objects#

3.1. Create site object(s)#

# prepare input data for from_site
location_from_site = shapely.geometry.Point(4.18055556, 52.18664444)
data_from_site = {"env": my_env,
                  "name": "from_site",
                  "geometry": location_from_site,
                  "capacity": 120,
                  "level": 120
                 }
# instantiate from_site 
from_site = Site(**data_from_site)

# prepare input data for to_site
location_to_site = shapely.geometry.Point(4.25222222, 52.11428333)
data_to_site = {"env": my_env,
                "name": "to_site",
                "geometry": location_to_site,
                "capacity": 120,
                "level": 0
               }
# instantiate to_site 
to_site = Site(**data_to_site)

3.2. Create vessel object(s)#

# prepare input data for vessel_01
data_vessel01 = {"env": my_env,
                 "name": "vessel01",
                 "geometry": location_from_site, 
                 "capacity": 4,
                 "compute_v": lambda x: 10
               }
# instantiate vessel_01 
vessel01 = TransportProcessingResource(**data_vessel01)

3.3 Create activity/activities#

# generate a dataframe with weather data
metocean_df = pd.read_csv(src_path / ".." / "tests" / "data" / "unit_test_weather.csv")
metocean_df = metocean_df.set_index(pd.to_datetime(metocean_df["Time"], dayfirst=True))
metocean_df = metocean_df.sort_index()

metocean_df["Hs [m]"] = 4 + \
                        1.5*np.sin(metocean_df['  Hour']/24 * 8 *np.pi) + \
                        1.5*np.sin(metocean_df['  Hour']/24 * 6 *np.pi)

metocean_df = metocean_df.set_index(pd.to_datetime(metocean_df["Time"], dayfirst=True))
metocean_df = metocean_df.sort_index()
metocean_df["ts"] = metocean_df.index.values.astype(float) / 1_000_000_000

metocean_df.head()
Year Month Day Hour Hour.1 Time U10 [m/s] Hs [m] ts
Time
2010-01-01 00:00:00 2010 1 1 0 1 01-01-2010 00:00 10 4.000000 1.262304e+09
2010-01-01 01:00:00 2010 1 1 1 2 01-01-2010 01:00 10 6.359698 1.262308e+09
2010-01-01 02:00:00 2010 1 1 2 3 01-01-2010 02:00 10 6.799038 1.262311e+09
2010-01-01 03:00:00 2010 1 1 3 4 01-01-2010 03:00 10 5.060660 1.262315e+09
2010-01-01 04:00:00 2010 1 1 4 5 01-01-2010 04:00 10 2.700962 1.262318e+09
# generate a weather criterion for the sailing process
sailing_crit = plugin.WeatherCriterion(
    name="sailing_crit",
    condition="Hs [m]",
    maximum=6,
    window_length=3600,
)

# generate a weather criterion for the loading process
loading_crit = plugin.WeatherCriterion(
    name="loading_crit",
    condition="Hs [m]",
    maximum=4.5,
    window_length=3600,
)
# initialise registry
registry = {}
# create a list of the sub processes
sub_processes = [
    TestMoveActivity(
        env=my_env,
        name="sailing empty",
        registry=registry,
        mover=vessel01,
        destination=from_site,
        metocean_criteria=sailing_crit,
        metocean_df=metocean_df,
    ),
    TestShiftActivity(
        env=my_env,
        name="loading",
        registry=registry,
        processor=vessel01,
        origin=from_site,
        destination=vessel01,
        amount=4,
        duration=3600,
        metocean_criteria=loading_crit,
        metocean_df=metocean_df,
    ),
    TestMoveActivity(
        env=my_env,
        name="sailing full",
        registry=registry,
        mover=vessel01,
        destination=to_site,
        metocean_criteria=sailing_crit,
        metocean_df=metocean_df,
    ),
    TestShiftActivity(
        env=my_env,
        name="unloading",
        registry=registry,
        processor=vessel01,
        origin=vessel01,
        destination=to_site,
        amount=4,
        duration=3600,
        metocean_criteria=loading_crit,
        metocean_df=metocean_df,
    ),
]

# create a 'sequential activity' that is made up of the 'sub_processes'
sequential_activity = model.SequentialActivity(
    env=my_env,
    name="sequential_activity_subcycle",
    registry=registry,
    sub_processes=sub_processes,
)

# create a while activity that executes the 'sequential activity' while the stop condition is not triggered 
while_activity = model.WhileActivity(
    env=my_env,
    name="while_sequential_activity_subcycle",
    registry=registry,
    sub_processes=[sequential_activity],
    condition_event=[{"type": "container", "concept": to_site, "state": "full"}],
)

4. Register processes and run simpy#

# initate the simpy processes defined in the 'while activity' and run simpy
model.register_processes([while_activity])
my_env.run()

5. Inspect results#

5.1 Inspect logs#

plot.get_log_dataframe(while_activity, [while_activity]).head()
Activity Timestamp ActivityState type ref
0 while_sequential_activity_subcycle 2010-01-01 00:00:00.000000 START NaN NaN
1 while_sequential_activity_subcycle 2010-01-01 00:00:00.000000 START subprocess b981c39f-065d-4a9a-9a54-d293ce6d4964
2 while_sequential_activity_subcycle 2010-01-01 06:15:42.824591 STOP subprocess b981c39f-065d-4a9a-9a54-d293ce6d4964
3 while_sequential_activity_subcycle 2010-01-01 06:15:42.824591 START subprocess b981c39f-065d-4a9a-9a54-d293ce6d4964
4 while_sequential_activity_subcycle 2010-01-01 11:00:00.000000 STOP subprocess b981c39f-065d-4a9a-9a54-d293ce6d4964
plot.get_log_dataframe(sub_processes[0], [while_activity]).head()
Activity Timestamp ActivityState type ref
0 sailing empty 2010-01-01 00:00:00.000000 WAIT_START plugin waiting on weather
1 sailing empty 2010-01-01 03:00:00.000000 WAIT_STOP plugin waiting on weather
2 sailing empty 2010-01-01 03:00:00.000000 START NaN NaN
3 sailing empty 2010-01-01 03:00:00.000000 STOP NaN NaN
4 sailing empty 2010-01-01 06:15:42.824591 START NaN NaN

5.2 Visualise gantt charts#

res = plot.get_gantt_chart([vessel01], y_scale="numbers", static=True, id_map=[while_activity])
data = res['data']
layout = res['layout']

data.append({
    "name": "sailing_crit",
    "x":[vessel01.log["Timestamp"][0].isoformat(), vessel01.log["Timestamp"][-1].isoformat()],
    "y":[sailing_crit.maximum,sailing_crit.maximum],
})

data.append({
    "name": "Weather",
    "x":metocean_df.index,
    "y":metocean_df[sailing_crit.condition],
})

data.append({
    "name": "loading_crit",
    "x":[vessel01.log["Timestamp"][0].isoformat(), vessel01.log["Timestamp"][-1].isoformat()],
    "y":[loading_crit.maximum,loading_crit.maximum],
})

go.Figure(data=data, layout=layout)

Some observations:

  • The moving criterion states that sailing can only commence when Hs is below 6 m, and there is a window of at least 3600 seconds. While the first data point from the metocean file has a Hs below 6 m, viz. 4 m, the next data point has a Hs higher than 6 m. Since the time interval is exactly 3600 seconds the vessel decides to wait. The first data point with a Hs lower than 6 m is found at 03:00 hours. The vessel then starts ‘sailing empty’. However, since the vessels origin is ‘to_site’, ‘sailing empty’ takes 0 seconds.

  • The vessel is ready to start loading at 03:00 hours. However, the loading criterion states that loading can only commence when Hs is below 4.5 m. The first time this happens in the metocean dataseries is 04:00 o’clock. So the loading commences at 04:00 hours.

plot.get_gantt_chart(sub_processes)

5.3 Visualise container volume developments#

fig = plot.get_step_chart([vessel01, from_site, to_site])
../_images/c8e0b8cd94f8f641c9556dec8d3efde15e573d63f5d0fa0020f7701f54f87376.png