Energy calculation for a single trip (with current)

Energy calculation for a single trip (with current)#

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. This noteboook investigates the effect of adding a current on the overall emission performance.

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.core.plotutils import generate_vessel_gantt_chart
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

# package(s) needed for plotting
import plotly.express as px

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

GeneralDepth = 10 # m
Current = 1 # m/s

# add graph to environment
FG.edges['0', '1']['Info'] = {'GeneralDepth': GeneralDepth, 'Current': -Current} # from '0' to '1' you sail against the current
FG.edges['1', '0']['Info'] = {'GeneralDepth': GeneralDepth, 'Current':  Current} # from '1' to '0' you sail with the current
env.graph = FG

# create vessel from a dict 
route = nx.dijkstra_path(env.graph, "0", "1")
route.append('0')

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": 3,                     # 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 13:53:20 100000.0 POINT (0.8983152841195217 0)
2 Sailing from node 1 to node 0 start 2024-01-01 13:53:20 100000.0 POINT (0.8983152841195217 0)
3 Sailing from node 1 to node 0 stop 2024-01-01 20:50:00 200000.0 POINT (0 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 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 00:00:00 2024-01-01 13:53:20 100000.0 50000.0
1 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 1 to node 0 POINT (0.8983152841195217 0) POINT (0 0) 2024-01-01 13:53:20 2024-01-01 20:50:00 100000.0 25000.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 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 00:00:00 2024-01-01 13:53:20 100000.0 50000.0 10.0 None 0.0 1990.0 302.873956 302.873956 1750.0 4206.582719
1 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 1 to node 0 POINT (0.8983152841195217 0) POINT (0 0) 2024-01-01 13:53:20 2024-01-01 20:50:00 100000.0 25000.0 10.0 None 0.0 1990.0 302.873956 302.873956 1750.0 2103.291359
# 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 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 0 to node 1 POINT (0 0) POINT (0.8983152841195217 0) 2024-01-01 00:00:00 2024-01-01 13:53:20 100000.0 50000.0 10.0 None 0.0 1990.0 302.873956 302.873956 1750.0 4206.582719 1.079218e+06 10.792184 21.584367 3.424066e+06 2120.144338 48336.909999 34.240655 0.021201 0.483369 68.48131 0.042403 0.966738
1 d0b24b60-0aec-45d2-ad11-ab2ba9a2cc96 Vessel Sailing from node 1 to node 0 POINT (0.8983152841195217 0) POINT (0 0) 2024-01-01 13:53:20 2024-01-01 20:50:00 100000.0 25000.0 10.0 None 0.0 1990.0 302.873956 302.873956 1750.0 2103.291359 5.396092e+05 5.396092 21.584367 1.712033e+06 1060.072169 24168.455000 17.120328 0.010601 0.241685 68.48131 0.042403 0.966738
generate_vessel_gantt_chart(df_eventtable)
for index, row in df_eventtable_energy_fuel.iterrows(): 
    print('{} (current {:.0f} m/s): average P_tot {:.1f} kW, CO2 emission per m {:.1f} g/m, CO2 emissions per s {:.1f} g/s'.format(
        row['activity name'], 
        row['current (m/s)'], 
        row['P_tot (kW)'], 
        row['CO2_emission_per_m (g/m)'], 
        row['CO2_emission_per_s (g/s)']))
Sailing from node 0 to node 1 (current 0 m/s): average P_tot 302.9 kW, CO2 emission per m 34.2 g/m, CO2 emissions per s 68.5 g/s
Sailing from node 1 to node 0 (current 0 m/s): average P_tot 302.9 kW, CO2 emission per m 17.1 g/m, CO2 emissions per s 68.5 g/s

The results in the event table, and the graph above, show the effect of current on the overall performance. The same distance obviously takes longer to complete when the current is against, then when it comes from behind. At a fixed speed the vessel emits the same emissions per second no matter if the current is from behind or against. But because it takes longer to pass an edge, the emissions per m are higher with an adverse current than a current from behind.

# 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 00:00:00 2024-01-01 13:53:20 POINT (0 0) POINT (0.8983152841195217 0) 154.061760 154.061760 1750.0 2139.746660 5.741064e+05 5.884577e+05 0.690598 238463.591736 204193.374960 3.106744 2.660266 597055.459009 511251.081666 1.408200 1.205824 516338.467592 1.217823 1.451707e+06 1.243078e+06 1.832642 1.569268 1.255448e+06 1.584884 1.553980e+06 1.330654e+06 2.529735 2.166180 1.343895e+06 2.187735 2.431530e+07 19.242326 1.337342 1.821483e+06 1561.012191 31493.362401 2139.746660 10 100000.0 50000.0 5.884577 18214.830814 15.610122 314.933624
1 2024-01-01 13:53:20 2024-01-01 20:50:00 POINT (0.8983152841195217 0) POINT (0 0) 603.811623 603.811623 1750.0 4193.136274 1.007346e+06 1.032527e+06 1.211745 288216.907743 279822.240527 3.754939 3.645571 721625.791567 700607.564628 1.702008 1.652435 905984.517146 2.136832 1.754593e+06 1.703488e+06 2.215006 2.150492 2.202851e+06 2.780889 1.878204e+06 1.823499e+06 3.057542 2.968487 2.358042e+06 3.838673 4.764928e+07 37.708060 2.620710 3.196034e+06 1766.113562 43663.249075 4193.136274 10 100000.0 25000.0 10.325273 31960.343333 17.661136 436.632491