Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WeatherRoutingTool/ship/direct_power_boat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from astropy import units as u

import WeatherRoutingTool.utils.formatting as form
Expand Down Expand Up @@ -55,6 +54,7 @@ def __init__(self, ship_config: ShipConfig):

# mandatory parameters for direct power method
# determine power at the service propulsion point i.e. 'subtract' 15% sea and 10% engine margin
self.weather_path = str(ship_config.WEATHER_DATA)
self.power_at_sp = ship_config.BOAT_SMCR_POWER * u.kiloWatt
self.power_at_sp = self.power_at_sp.to(u.Watt) * 0.75
self.speed_at_sp = ship_config.BOAT_SMCR_SPEED * u.meter / u.second
Expand Down
284 changes: 244 additions & 40 deletions WeatherRoutingTool/ship/ship.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,108 @@
import WeatherRoutingTool.utils.formatting as form
from WeatherRoutingTool.ship.shipparams import ShipParams
from WeatherRoutingTool.ship.ship_config import ShipConfig
from collections import deque
from typing import Tuple, Optional, Sequence, Any

logger = logging.getLogger('WRT.ship')

logger = logging.getLogger("WRT.ship")


# Boat: Main class for boats. Classes 'Tanker' and 'SailingBoat' derive from it
# Tanker: implements interface to mariPower package which is used for power estimation.


class Cache:
"""
keys are arbitrary hashable objects
stores arbitrary values
evicts oldest entries when max_entries exceeded
"""

def __init__(self, max_entries: int = 10_000):
if max_entries <= 0:
raise ValueError("max_entries must be > 0")
self.max_entries = int(max_entries)
self._cache = dict()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check whether the deletion of self._order and the replacement of self._cache by an OrderedDict increases the run-time performance. You can then use the command 'popitem(last=False)' to delete the first entry of _order.

self._order = deque() # oldest keys on left

def __len__(self) -> int:
return len(self._cache)

def contains(self, key: Any) -> bool:
return key in self._cache

def get(self, key: Any, default: Any = None) -> Any:
return self._cache.get(key, default)

def set(self, key: Any, value: Any) -> None:
if key in self._cache:
# update value but do not change order
self._cache[key] = value
return
self._cache[key] = value
self._order.append(key)
# evict oldest if necessary
while len(self._order) > self.max_entries:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop the while, as every time the function is called only one entry needs to be deleted.

old = self._order.popleft()
# guard: old might already been removed
self._cache.pop(old, None)

def pop_oldest(self) -> Optional[Tuple[Any, Any]]:
if not self._order:
return None
oldest = self._order.popleft()
val = self._cache.pop(oldest, None)
return (oldest, val)


class Boat:
weather_path: str # path to netCDF containing weather data

def __init__(self, ship_config: ShipConfig):
self.counter = 0
self.under_keel_clearance = ship_config.BOAT_UNDER_KEEL_CLEARANCE * u.meter
self.draught_aft = ship_config.BOAT_DRAUGHT_AFT * u.meter
self.draught_fore = ship_config.BOAT_DRAUGHT_FORE * u.meter
self._weather_data = None
self._weather_access_counter = None
self._weather_access_dims = ("time", "latitude", "longitude")
self._lat_min = None
self._lon_min = None
self._lat_width = 1.0 / 12.0
self._lon_width = 1.0 / 12.0
self._weather_cache = Cache(max_entries=10000)

def _get_weather_data(self):
if self._weather_data is not None:
return self._weather_data

# Keep the dataset lazy so only requested slices are read into memory.
self._weather_data = xr.open_dataset(self.weather_path)
return self._weather_data

def close(self):
if self._weather_data is not None:
self._weather_data.close()
self._weather_data = None

def get_required_water_depth(self):
needs_water_depth = max(self.draught_aft, self.draught_fore) + self.under_keel_clearance
needs_water_depth = (
max(self.draught_aft, self.draught_fore) + self.under_keel_clearance
)
return needs_water_depth.value

def get_ship_parameters(self, courses, lats, lons, time, speed, unique_coords=False):
def get_ship_parameters(
self, courses, lats, lons, time, speed, unique_coords=False
):
pass

def print_init(self):
pass

def evaluate_weather(self, ship_params, lats, lons, time):
weather_data = xr.open_dataset(self.weather_path)
weather_data = self._get_weather_data()
# weather_data= xr.open_dataset(self.weather_path)
n_coords = len(lats)

wave_height = []
Expand All @@ -49,53 +124,180 @@ def evaluate_weather(self, ship_params, lats, lons, time):
salinity = []
water_temperature = []

def cached_lookup(var_key, da, lat, lon, t, height=None, depth=None):
key = (
var_key,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please convert the latitude, longitude and time values into indices and use these as keys to refer to the data in the lookup table.

float(lat),
float(lon),
np.datetime64(t) if np.issubdtype(type(t), np.datetime64) else t,
height,
depth,
)
val = self._weather_cache.get(key)
if val is not None:
return val
v = self.approx_weather(da, lat, lon, t, height, depth)
self._weather_cache.set(key, v)
return v

for i_coord in range(0, n_coords):
wave_direction.append(
self.approx_weather(weather_data['VMDR'], lats[i_coord], lons[i_coord], time[i_coord]))
wave_period.append(self.approx_weather(weather_data['VTPK'], lats[i_coord], lons[i_coord], time[i_coord]))
wave_height.append(self.approx_weather(weather_data['VHM0'], lats[i_coord], lons[i_coord], time[i_coord]))
cached_lookup(
"VMDR",
weather_data["VMDR"],
lats[i_coord],
lons[i_coord],
time[i_coord],
)
)
wave_period.append(
cached_lookup(
"VTPK",
weather_data["VTPK"],
lats[i_coord],
lons[i_coord],
time[i_coord],
)
)
wave_height.append(
cached_lookup(
"VHM0",
weather_data["VHM0"],
lats[i_coord],
lons[i_coord],
time[i_coord],
)
)
v_currents.append(
self.approx_weather(weather_data['vtotal'], lats[i_coord], lons[i_coord], time[i_coord], None, 0.5))
cached_lookup(
"vtotal",
weather_data["vtotal"],
lats[i_coord],
lons[i_coord],
time[i_coord],
None,
0.5,
)
)
u_currents.append(
self.approx_weather(weather_data['utotal'], lats[i_coord], lons[i_coord], time[i_coord], None, 0.5))
cached_lookup(
"utotal",
weather_data["utotal"],
lats[i_coord],
lons[i_coord],
time[i_coord],
None,
0.5,
)
)
pressure.append(
self.approx_weather(weather_data['Pressure_reduced_to_MSL_msl'], lats[i_coord], lons[i_coord],
time[i_coord]))
cached_lookup(
"Pressure_reduced_to_MSL_msl",
weather_data["Pressure_reduced_to_MSL_msl"],
lats[i_coord],
lons[i_coord],
time[i_coord],
)
)
water_temperature.append(
self.approx_weather(weather_data['thetao'], lats[i_coord], lons[i_coord], time[i_coord], None, 0.5))
cached_lookup(
"thetao",
weather_data["thetao"],
lats[i_coord],
lons[i_coord],
time[i_coord],
None,
0.5,
)
)
salinity.append(
self.approx_weather(weather_data['so'], lats[i_coord], lons[i_coord], time[i_coord], None, 0.5))
cached_lookup(
"so",
weather_data["so"],
lats[i_coord],
lons[i_coord],
time[i_coord],
None,
0.5,
)
)
air_temperature.append(
self.approx_weather(weather_data['Temperature_surface'], lats[i_coord], lons[i_coord], time[i_coord]))
cached_lookup(
"Temperature_surface",
weather_data["Temperature_surface"],
lats[i_coord],
lons[i_coord],
time[i_coord],
)
)
u_wind_speed.append(
self.approx_weather(weather_data['u-component_of_wind_height_above_ground'], lats[i_coord],
lons[i_coord], time[i_coord], 10))
cached_lookup(
"u-component_of_wind_height_above_ground",
weather_data["u-component_of_wind_height_above_ground"],
lats[i_coord],
lons[i_coord],
time[i_coord],
10,
)
)
v_wind_speed.append(
self.approx_weather(weather_data['v-component_of_wind_height_above_ground'], lats[i_coord],
lons[i_coord], time[i_coord], 10))

ship_params.wave_direction = np.array(wave_direction, dtype='float32') * u.radian
ship_params.wave_period = np.array(wave_period, dtype='float32') * u.second
ship_params.wave_height = np.array(wave_height, dtype='float32') * u.meter
ship_params.u_wind_speed = np.array(u_wind_speed, dtype='float32') * u.meter / u.second
ship_params.v_wind_speed = np.array(v_wind_speed, dtype='float32') * u.meter / u.second
ship_params.v_currents = np.array(v_currents, dtype='float32') * u.meter / u.second
ship_params.u_currents = np.array(u_currents, dtype='float32') * u.meter / u.second
ship_params.pressure = np.array(pressure, dtype='float32') * u.kg / (u.meter * u.second ** 2)
ship_params.air_temperature = np.array(air_temperature, dtype='float32') * u.Kelvin
ship_params.air_temperature = ship_params.air_temperature.to(u.deg_C, equivalencies=u.temperature())
ship_params.salinity = np.array(salinity, dtype='float32') * 0.001 * u.dimensionless_unscaled
ship_params.water_temperature = np.array(water_temperature, dtype='float32') * u.deg_C
cached_lookup(
"v-component_of_wind_height_above_ground",
weather_data["v-component_of_wind_height_above_ground"],
lats[i_coord],
lons[i_coord],
time[i_coord],
10,
)
)

ship_params.wave_direction = (
np.array(wave_direction, dtype="float32") * u.radian
)
ship_params.wave_period = np.array(wave_period, dtype="float32") * u.second
ship_params.wave_height = np.array(wave_height, dtype="float32") * u.meter
ship_params.u_wind_speed = (
np.array(u_wind_speed, dtype="float32") * u.meter / u.second
)
ship_params.v_wind_speed = (
np.array(v_wind_speed, dtype="float32") * u.meter / u.second
)
ship_params.v_currents = (
np.array(v_currents, dtype="float32") * u.meter / u.second
)
ship_params.u_currents = (
np.array(u_currents, dtype="float32") * u.meter / u.second
)
ship_params.pressure = (
np.array(pressure, dtype="float32") * u.kg / (u.meter * u.second**2)
)
ship_params.air_temperature = (
np.array(air_temperature, dtype="float32") * u.Kelvin
)
ship_params.air_temperature = ship_params.air_temperature.to(
u.deg_C, equivalencies=u.temperature()
)
ship_params.salinity = (
np.array(salinity, dtype="float32") * 0.001 * u.dimensionless_unscaled
)
ship_params.water_temperature = (
np.array(water_temperature, dtype="float32") * u.deg_C
)

return ship_params

def approx_weather(self, var, lats, lons, time, height=None, depth=None):
ship_var = var.sel(latitude=lats, longitude=lons, time=time, method='nearest', drop=False)
ship_var = var.sel(
latitude=lats, longitude=lons, time=time, method="nearest", drop=False
)
if height:
ship_var = ship_var.sel(height_above_ground=height, method='nearest', drop=False)
ship_var = ship_var.sel(
height_above_ground=height, method="nearest", drop=False
)
if depth:
ship_var = ship_var.sel(depth=depth, method='nearest', drop=False)
ship_var = ship_var.sel(depth=depth, method="nearest", drop=False)
ship_var = ship_var.fillna(0).to_numpy()
self.counter = +1

return ship_var

Expand All @@ -120,10 +322,12 @@ def __init__(self, ship_config: ShipConfig):
self.fuel_rate = ship_config.BOAT_FUEL_RATE * u.kg / u.second

def print_init(self):
logger.info(form.get_log_step('boat fuel rate' + str(self.fuel_rate), 1))
logger.info(form.get_log_step("boat fuel rate" + str(self.fuel_rate), 1))
form.print_line()

def get_ship_parameters(self, courses, lats, lons, time, speed, unique_coords=False):
def get_ship_parameters(
self, courses, lats, lons, time, speed, unique_coords=False
):
debug = False
n_requests = len(courses)

Expand All @@ -147,16 +351,16 @@ def get_ship_parameters(self, courses, lats, lons, time, speed, unique_coords=Fa
v_currents=dummy_array * u.meter / u.second,
u_wind_speed=dummy_array * u.meter / u.second,
v_wind_speed=dummy_array * u.meter / u.second,
pressure=dummy_array * u.kg / u.meter / u.second ** 2,
pressure=dummy_array * u.kg / u.meter / u.second**2,
air_temperature=dummy_array * u.deg_C,
salinity=dummy_array * u.dimensionless_unscaled,
water_temperature=dummy_array * u.deg_C,
status=dummy_array,
message=np.full(n_requests, "")
message=np.full(n_requests, ""),
)

if (debug):
if debug:
ship_params.print()
form.print_step('fuel result' + str(ship_params.get_fuel_rate()))
form.print_step("fuel result" + str(ship_params.get_fuel_rate()))

return ship_params
Loading