Energy calculation for a single trip#

In this notebook, we set up a basic simulation where one vessel moves over a 1D network path. We add the ConsumesEnergy and VesselProperties mixins to the vessel object. Based on the vessel and network properties, now also an estimate of the energy that is required to pass the edge at the set speed can be made.

0. Import libraries#

# package(s) used for creating and geo-locating the graph
import networkx as nx
import pyproj
from pyproj import Geod
from shapely.geometry import Point

# package(s) related to the simulation (creating the vessel, running the simulation)
import datetime, time
import simpy
import opentnsim
from opentnsim.core.logutils import logbook2eventtable
from opentnsim.energy.logutils import (
    add_energy_attributes_to_eventtable,
    add_fuel_attributes_to_event_table,
)

# package(s) needed for inspecting the output
import pandas as pd

print("This notebook is executed with OpenTNSim version {}".format(opentnsim.__version__))
This notebook is executed with OpenTNSim version 1.3.7

1. Define object classes#

# make your preferred Vessel class out of available mix-ins.
Vessel = type(
    "Vessel", 
    (
        opentnsim.energy.mixins.ConsumesEnergy,
        opentnsim.core.Identifiable,                       # allows to give the object a name and a random ID,
        opentnsim.core.Movable,                            # allows the object to move, with a fixed speed, while logging this activity
        opentnsim.core.vessel_properties.VesselProperties,
        opentnsim.core.ExtraMetadata,
    ), 
    {}
)

2. Create graph#

Next we create a network (a graph) along which the vessel can move. This case we create a single edge of 100 km exactly.

# initialize geodetic calculator with WGS84 ellipsoid
geod = Geod(ellps="WGS84")
# starting point (longitude, latitude)
lon0, lat0 = 0, 0

# compute the other point 100 km East from Point 0
lon1, lat1, _ = geod.fwd(lon0, lat0, 90, 100000) # East from Point 0

# define nodes with their geographic coordinates
coords = {
    "0": (lon0, lat0),
    "1": (lon1, lat1),
}
# create list of edges
edges = [("0", "1"), ("1", "0")] # bi-directional edge
# create a directed graph
FG = nx.DiGraph()

# add nodes
for name, coord in coords.items():
    FG.add_node(name, geometry=Point(coord[0], coord[1]))

# add edges
for edge in edges:
    FG.add_edge(edge[0], edge[1], weight=1)
opentnsim.graph.plot_graph(FG)

3. Run simulation#

def mission(env, vessel):
    """Method that defines the mission of the vessel. 
    
    In this case: 
        keep moving along the path until its end point is reached
    """
    
    while True:
        yield from vessel.move()
        
        if vessel.geometry == nx.get_node_attributes(env.graph, "geometry")[vessel.route[-1]]:
            break
# start simpy environment
simulation_start = datetime.datetime(2024, 1, 1, 0, 0, 0)
env = simpy.Environment(initial_time=simulation_start.timestamp())
env.epoch = simulation_start

# add graph to environment
FG.edges['0', '1']['GeneralDepth']=10
env.graph = FG

# create vessel from a dict 
route = nx.dijkstra_path(env.graph, "0", "1")
data_vessel = {
    "env": env,
    "name": 'Vessel',           # you can give the vessel an arbitratry name
    "type": 'Va/M9 - Verl. Groot Rijnschip', # This indicates the vessel class. This info is mainly informative.
    "L": 135,                   # m
    "B": 11.45,                 # m
    "T": 2.75,                  # m
    "v": 5,                     # m/s If None: this value is calculated based on P_tot_given
    "safety_margin": 0.2,       # for tanker vessel with sandy bed the safety margin is recommended as 0.2 m 
    "h_squat": False,           # if the ship should squat while moving, set to True, otherwise set to False
    "P_installed": 1750.0,      # kW
    "P_tot_given": None,        # kW If None: this value is calculated value based on speed
    "bulbous_bow": False,       # if a vessel has no bulbous_bow, set to False; otherwise set to True.
    "karpov_correction": False, # if False, don't apply the karpov correction, if True, apply the karpov correction
    "P_hotel_perc": 0.05,       # 0: all power goes to propulsion
    "P_hotel": None,            # None: calculate P_hotel from percentage
    "x": 2,                     # number of propellers
    "L_w": 3.0 ,
    "C_B": 0.85,                # block coefficient 
    "C_year": 1990,             # engine build year
    "arrival_time": datetime.datetime(2024, 1, 1, 0, 0, 0),
    "geometry": env.graph.nodes[route[0]]['geometry'],
    "route": route,             # the route to sail
}  # 

# create an instance of the Vessel class using the input dict data_vessel
vessel = Vessel(**data_vessel)

# start the simulation
env.process(mission(env, vessel))
env.run()

4. Inspect output#

We can now inspect the simulation output by inspecting the vessel.logbook. Note that the Log mix-in was included when we added Movable. The vessel.logbook keeps track of the moving activities of the vessel. For each discrete event OpenTNSim logs an event message, the start/stop time and the location. The vessel.logbook is of type dict. For convenient inspection it can be loaded into a Pandas dataframe.

# load the logbook data into a dataframe
df = pd.DataFrame.from_dict(vessel.logbook)

display(df)
Message Timestamp Value Geometry
0 Sailing from node 0 to node 1 start 2024-01-01 00:00:00 0.0 POINT (0 0)
1 Sailing from node 0 to node 1 stop 2024-01-01 05:33:20 100000.0 POINT (0.8983152841195217 0)

The inspection of the logbook data shows that Vessel moved from its origin (Node 0) to its destination (Node 1). The print statements show that the length of the route from Node 0 to Node 1 is exactly 100 km. At a given speed of 1 m/s the trip duration should be exactly 100000 seconds, as is indeed shown to be the case.

df_eventtable = logbook2eventtable([vessel])
df_eventtable
object id object name activity name start location stop location start time stop time distance (m) duration (s)
0 f7f9a194-efad-4479-95b4-7d74f2123f43 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 2024-01-01 05:33:20 100000.0 20000.0
# add energy attributes to the minimum event table
df_eventtable_energy = add_energy_attributes_to_eventtable(df_eventtable, [vessel])        

pd.set_option('display.max_columns', None)
df_eventtable_energy
object id object name activity name start location stop location start time stop time distance (m) duration (s) waterdepth (m) waterway width (m) current (m/s) engine age (year) P_tot (kW) P_given (kW) P_installed (kW) total_energy (kWh)
0 f7f9a194-efad-4479-95b4-7d74f2123f43 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 2024-01-01 05:33:20 100000.0 20000.0 10.0 None 0.0 1990.0 1162.327962 1162.327962 1750.0 6457.377568
# add fuel attributes to the minimum event table with energy attributes
df_eventtable_energy_fuel = add_fuel_attributes_to_event_table(df_eventtable_energy, [vessel])

pd.set_option('display.max_columns', None)
df_eventtable_energy_fuel
object id object name activity name start location stop location start time stop time distance (m) duration (s) waterdepth (m) waterway width (m) current (m/s) engine age (year) P_tot (kW) P_given (kW) P_installed (kW) total_energy (kWh) diesel_consumption (g) diesel_consumption_m (g/m) diesel_consumption_s (g/s) CO2_emission_total (g) PM10_emission_total (g) NOX_emission_total (g) CO2_emission_per_m (g/m) PM10_emission_per_m (g/m) NOX_emission_per_m (g/m) CO2_emission_per_s (g/s) PM10_emission_per_s (g/s) NOX_emission_per_s (g/s)
0 f7f9a194-efad-4479-95b4-7d74f2123f43 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 2024-01-01 05:33:20 100000.0 20000.0 10.0 None 0.0 1990.0 1162.327962 1162.327962 1750.0 6457.377568 1.434829e+06 14.348293 71.741465 4.552322e+06 2557.121517 64382.259146 45.52322 0.025571 0.643823 227.616102 0.127856 3.219113
# TODO: deze en de volgende cell kunnen eigenlijk weg. Het meeste is nu in de nieuwe vorm geimplementeerd
# execute the calculation of energy consumption and emissions by post-processing the vessel log info
energycalculation = opentnsim.energy.mixins.EnergyCalculation(env.graph, vessel)
energycalculation.calculate_energy_consumption()
# process the data frame to get the right output values
df = pd.DataFrame.from_dict(energycalculation.energy_use)
df['fuel_kg_per_km'] = (df['total_diesel_consumption_ICE_mass'] / 1000) / (df['distance']/1000)
df['CO2_g_per_km']   = (df['total_emission_CO2']) / (df['distance']/1000)
df['PM10_g_per_km']  = (df['total_emission_PM10']) / (df['distance']/1000)
df['NOx_g_per_km']   = (df['total_emission_NOX']) / (df['distance']/1000)
df
time_start time_stop edge_start edge_stop P_tot P_given P_installed total_energy total_diesel_consumption_C_year_ICE_mass total_diesel_consumption_ICE_mass total_diesel_consumption_ICE_vol total_LH2_consumption_PEMFC_mass total_LH2_consumption_SOFC_mass total_LH2_consumption_PEMFC_vol total_LH2_consumption_SOFC_vol total_eLNG_consumption_PEMFC_mass total_eLNG_consumption_SOFC_mass total_eLNG_consumption_PEMFC_vol total_eLNG_consumption_SOFC_vol total_eLNG_consumption_ICE_mass total_eLNG_consumption_ICE_vol total_eMethanol_consumption_PEMFC_mass total_eMethanol_consumption_SOFC_mass total_eMethanol_consumption_PEMFC_vol total_eMethanol_consumption_SOFC_vol total_eMethanol_consumption_ICE_mass total_eMethanol_consumption_ICE_vol total_eNH3_consumption_PEMFC_mass total_eNH3_consumption_SOFC_mass total_eNH3_consumption_PEMFC_vol total_eNH3_consumption_SOFC_vol total_eNH3_consumption_ICE_mass total_eNH3_consumption_ICE_vol total_Li_NMC_Battery_mass total_Li_NMC_Battery_vol total_Battery2000kWh_consumption_num total_emission_CO2 total_emission_PM10 total_emission_NOX stationary water depth distance delta_t fuel_kg_per_km CO2_g_per_km PM10_g_per_km NOx_g_per_km
0 2024-01-01 2024-01-01 05:33:20 POINT (0 0) POINT (0.8983152841195217 0) 1162.327962 1162.327962 1750.0 6457.377568 1.434829e+06 1.470697e+06 1.725968 491192.639127 460446.332219 6.399341 5.998773 1.229828e+06 1.152847e+06 2.900641 2.719075 1.290453e+06 3.04363 2.990259e+06 2.803083e+06 3.774917 3.538625 3.137665e+06 3.961004 3.200923e+06 3.000560e+06 5.210804 4.884633 3.358714e+06 5.467674 7.337929e+07 58.069942 4.035861 4.552322e+06 2557.121517 64382.259146 6457.377568 10 100000.0 20000.0 14.706966 45523.220379 25.571215 643.822591