Skip to content
Open
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
289 changes: 58 additions & 231 deletions tests/test_direct_power_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,168 +16,115 @@
try:
import mariPower
from tests.test_maripower_tanker import TestMariPowerTanker

have_maripower = True
except ModuleNotFoundError:
pass # maripower installation is optional
pass


class TestDPM:
'''
DIRECT POWER METHOD: check whether class variables (speed, eta_prop, power_at_sp, overload_factor) are set as
expected and correct power and corresponding unit are returned
'''
"""
DIRECT POWER METHOD: check whether class variables (speed, eta_prop, power_at_sp, overload_factor) are set as
expected and correct power and corresponding unit are returned.
"""

def _assert_geometry(self, pol):
"""Helper to verify standard ship geometry."""
hbr = 30 * u.meter
breadth = 32 * u.meter
length = 180 * u.meter

assert pol.hbr == hbr
assert pol.breadth == breadth
assert pol.length == length
assert pol.ls1 == 0.2 * length
assert pol.ls2 == 0.3 * length
assert pol.bs1 == 0.9 * breadth
assert pol.hs1 == 0.2 * hbr
assert pol.hs2 == 0.1 * hbr
assert pol.cmc == -0.035 * length
assert pol.hc == 10 * u.meter
assert pol.Axv == 940.8 * u.meter**2
assert pol.Ayv == 4248 * u.meter**2
assert pol.Aod == 378 * u.meter**2

@pytest.mark.parametrize("DeltaR,speed,design_power", [(5000, 6, 6502000 * 0.75)])
def test_get_power_for_direct_power_method(self, DeltaR, speed, design_power):
pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')
P = DeltaR * speed / 0.63 + design_power

Ptest = pol.get_power(5000 * u.N)
assert P == Ptest.value
assert 'W' == Ptest.unit
expected_p = DeltaR * speed / 0.63 + design_power

'''
DIRECT POWER METHOD: check whether relative angle between wind direction and course of the ship is correctly
calculated from u_wind and v_wind
'''
p_test = pol.get_power(5000 * u.N)
assert p_test.value == pytest.approx(expected_p)
assert p_test.unit == 'W'

def test_get_wind_dir(self):
wind_dir = np.array([30, 120, 210, 300]) * u.degree
absv = 20
courses = np.array([10, 10, 20, 20]) * u.degree
rel_wind_dir = np.array([20, 110, 170, 70]) * u.degree
rel_wind_dir_expected = np.array([20, 110, 170, 70]) * u.degree

pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')

v_wind = -absv * np.cos(np.radians(wind_dir)) * u.meter / u.second
u_wind = -absv * np.sin(np.radians(wind_dir)) * u.meter / u.second

true_wind_dir = pol.get_wind_dir(u_wind, v_wind)
true_wind_dir = pol.get_relative_wind_dir(courses, true_wind_dir)
rel_wind_dir_actual = pol.get_relative_wind_dir(courses, true_wind_dir)

assert np.all((rel_wind_dir - true_wind_dir) < 0.0001 * u.degree)

'''
DIRECT POWER METHOD: check whether apparent wind speed and direction are correctly calculated for single
values of wind speed and wind dir
'''
diff = (rel_wind_dir_expected - rel_wind_dir_actual).value
assert np.all(np.abs(diff) < 0.0001)

def test_get_apparent_wind(self):
wind_dir = np.array([0, 45, 90, 135, 180]) * u.degree
wind_speed = np.array([10, 10, 10, 10, 10]) * u.meter / u.second
wind_speed_test = np.array([16, 14.86112, 11.66190, 7.15173, 4]) * u.meter / u.second
wind_dir_test = np.array([0, 28.41, 59.04, 98.606, 180]) * u.degree

speed_expected = np.array([16, 14.86112, 11.66190, 7.15173, 4]) * u.meter / u.second
angle_expected = np.array([0, 28.41, 59.04, 98.606, 180]) * u.degree

pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')
wind_result = pol.get_apparent_wind(wind_speed, wind_dir)

for i in range(0, 4):
assert abs(wind_result['app_wind_speed'][i] - wind_speed_test[i]) < 0.01 * u.meter / u.second
assert abs(wind_result['app_wind_angle'][i] - wind_dir_test[i]) < 0.01 * u.degree

'''
DIRECT POWER METHOD: check whether apparent wind speed and direction look fine on polar plot
'''
# Fixed range from 0, 4 to 0, 5 to cover all test data
for i in range(len(wind_dir)):
assert wind_result['app_wind_speed'][i].value == pytest.approx(speed_expected[i].value, abs=0.01)
assert wind_result['app_wind_angle'][i].value == pytest.approx(angle_expected[i].value, abs=0.01)

@pytest.mark.manual
def test_get_apparent_wind_polar_plot(self):
"""Image of the resulting polar plot showing vector relationship between true and apparent wind."""
wind_dir = np.linspace(0, 180, 19) * u.degree
wind_speed = np.full(19, 10) * u.meter / u.second

pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')
wind_result = pol.get_apparent_wind(wind_speed, wind_dir)

fig, axes = plt.subplots(subplot_kw={'projection': 'polar'})

axes.plot(np.radians(wind_dir), wind_speed, label="true wind")
axes.plot(np.radians(wind_result['app_wind_angle']), wind_result['app_wind_speed'], label="apparent wind")
axes.plot(np.radians(wind_result['app_wind_angle']), wind_speed, label="apparent wind dir, fixed speed")
axes.legend(loc="upper right")
axes.set_title("Wind direction", va='bottom')
plt.show()

'''
DIRECT POWER METHOD: check whether ship geometry is approximated correctly if only mandatory parameters
are supplied
'''

def test_calculate_geometry_simple_method(self):
pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')
pol.load_data()

hbr = 30 * u.meter
breadth = 32 * u.meter
length = 180 * u.meter
ls1 = 0.2 * length
ls2 = 0.3 * length
hs1 = 0.2 * hbr
hs2 = 0.1 * hbr
bs1 = 0.9 * breadth
cmc = -0.035 * length
hc = 10 * u.meter
Axv = 940.8 * u.meter * u.meter
Ayv = 4248 * u.meter * u.meter
Aod = 378 * u.meter * u.meter

assert pol.hbr == hbr
assert pol.breadth == breadth
assert pol.length == length
assert pol.ls1 == ls1
assert pol.ls2 == ls2
assert pol.bs1 == bs1
assert pol.hs1 == hs1
assert pol.hs2 == hs2
assert pol.cmc == cmc
assert pol.hc == hc
assert pol.Axv == Axv
assert pol.Ayv == Ayv
assert pol.Aod == Aod

'''
DIRECT POWER METHOD: check whether ship geometry parameters are set correctly if manual values are supplied
'''
self._assert_geometry(pol)

def test_calculate_geometry_manual_method(self):
pol = basic_test_func.create_dummy_Direct_Power_Ship('manualship')
pol.load_data()

hbr = 30 * u.meter
breadth = 32 * u.meter
length = 180 * u.meter
ls1 = 0.2 * length
ls2 = 0.3 * length
hs1 = 0.2 * hbr
hs2 = 0.1 * hbr
bs1 = 0.9 * breadth
cmc = 8.1 * u.meter
hc = 7.06 * u.meter
Axv = 716 * u.meter * u.meter
Ayv = 1910 * u.meter * u.meter
Aod = 529 * u.meter * u.meter

assert pol.hbr == hbr
assert pol.breadth == breadth
assert pol.length == length
assert pol.ls1 == ls1
assert pol.ls2 == ls2
assert pol.bs1 == bs1
assert pol.hs1 == hs1
assert pol.hs2 == hs2
assert pol.cmc == cmc
assert pol.hc == hc
assert pol.Axv == Axv
assert pol.Ayv == Ayv
assert pol.Aod == Aod

'''
DIRECT POWER METHOD: check for reasonable behaviour of wind coefficient C_AA
'''
# Values specific to 'manualship' config
assert pol.cmc == 8.1 * u.meter
assert pol.hc == 7.06 * u.meter
assert pol.Axv == 716 * u.meter**2
assert pol.Ayv == 1910 * u.meter**2
assert pol.Aod == 529 * u.meter**2

@pytest.mark.manual
def test_wind_coeff(self):
u_wind_speed = np.full(19, 0) * u.meter / u.second
u_wind_speed = np.zeros(19) * u.meter / u.second
v_wind_speed = np.full(19, -10) * u.meter / u.second

courses = np.linspace(0, 180, 19) * u.degree

pol = basic_test_func.create_dummy_Direct_Power_Ship('manualship')
Expand All @@ -190,83 +137,6 @@ def test_wind_coeff(self):
ax.set_ylabel(r'$C_{AA}$')
plt.show()

'''
DIRECT POWER METHOD: check for reasonable behaviour of wind resistance on polar plot
'''

@pytest.mark.manual
def test_wind_resistance(self):
u_wind_speed = np.full(19, 0) * u.meter / u.second
v_wind_speed = np.full(19, -10) * u.meter / u.second

courses = np.linspace(0, 180, 19)
courses_rad = np.radians(courses)
courses = courses * u.degree

pol = basic_test_func.create_dummy_Direct_Power_Ship('manualship')
r_wind = pol.get_wind_resistance(u_wind_speed, v_wind_speed, courses)

fig, axes = plt.subplots(1, 2, subplot_kw={'projection': 'polar'})

axes[0].plot(courses_rad, r_wind['r_wind'])
axes[0].legend()
for ax in axes.flatten():
ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
ax.set_theta_zero_location("S")
ax.grid(True)
axes[1].plot(courses_rad, r_wind['r_wind'])
axes[0].set_title("Power", va='bottom')
axes[1].set_title("Wind resistence", va='top')

plt.show()

'''
DIRECT POWER METHOD: compare wind resistance and power from the Direct Power Method to results from maripower.

- relative difference of wind direction and boat course is changing in steps of 10 degrees
- effect from wave resistance is turned of for maripower; all other resistances are considerd by maripower
'''
@pytest.mark.skipif(not have_maripower, reason="maripower is not installed")
@pytest.mark.manual
def test_compare_wind_resistance_to_maripower(self):
lats = np.full(10, 54.9) # 37
lons = np.full(10, 13.2)
courses = np.linspace(0, 360, 10) * u.degree
courses_rad = utils.degree_to_pmpi(courses)

time = np.full(10, datetime.strptime("2023-07-20T10:00Z", '%Y-%m-%dT%H:%MZ'))
bs = np.full(10, 7.7) * u.meter / u.second

pol_maripower = basic_test_func.create_dummy_Tanker_object()
pol_maripower.set_ship_property('WaveForcesFactor', 0)

pol_maripower.use_depth_data = False
ship_params_maripower = pol_maripower.get_ship_parameters(courses, lats, lons, time, bs, True)
rwind_maripower = ship_params_maripower.get_rwind()
P_maripower = ship_params_maripower.get_power()

pol_simple = basic_test_func.create_dummy_Direct_Power_Ship('manualship')
ship_params_simple = pol_simple.get_ship_parameters(courses, lats, lons, time, bs)
r_wind_simple = ship_params_simple.get_rwind()
P_simple = ship_params_simple.get_power()

fig, axes = plt.subplots(1, 2, subplot_kw={'projection': 'polar'})

axes[0].plot(courses_rad, rwind_maripower, label="maripower")
axes[0].plot(courses_rad, r_wind_simple, label="Fujiwara")
axes[0].legend(loc="upper right")
for ax in axes.flatten():
ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
ax.set_theta_zero_location("S")
ax.grid(True)
axes[0].set_title("Wind resistance", va='bottom')

axes[1].plot(courses_rad, P_maripower, label="maripower")
axes[1].plot(courses_rad, P_simple, label="DPM")
axes[1].set_title("Power", va='top')
axes[0].legend(loc="upper right")
plt.show()

def test_dpm_via_dict_config(self):
config = {
'BOAT_BREADTH': 32,
Expand All @@ -281,54 +151,16 @@ def test_dpm_via_dict_config(self):

pol = DirectPowerBoat(init_mode="from_dict", config_dict=config)
pol.load_data()

hbr = 30 * u.meter
breadth = 32 * u.meter
length = 180 * u.meter
ls1 = 0.2 * length
ls2 = 0.3 * length
hs1 = 0.2 * hbr
hs2 = 0.1 * hbr
bs1 = 0.9 * breadth
cmc = -0.035 * length
hc = 10 * u.meter
Axv = 940.8 * u.meter * u.meter
Ayv = 4248 * u.meter * u.meter
Aod = 378 * u.meter * u.meter

assert pol.hbr == hbr
assert pol.breadth == breadth
assert pol.length == length
assert pol.ls1 == ls1
assert pol.ls2 == ls2
assert pol.bs1 == bs1
assert pol.hs1 == hs1
assert pol.hs2 == hs2
assert pol.cmc == cmc
assert pol.hc == hc
assert pol.Axv == Axv
assert pol.Ayv == Ayv
assert pol.Aod == Aod
self._assert_geometry(pol)

def test_validate_parameters(self):
"""
Test that the validate_parameters method directly identifies and raises an error for dummy values (-99).
"""
# Create a boat object with dummy values
pol = basic_test_func.create_dummy_Direct_Power_Ship('simpleship')
pol.Axv = -99 * u.meter * u.meter
pol.hs1 = -99 * u.meter

with pytest.raises(ValueError) as excinfo:
pol.Axv = -99 * u.meter**2

with pytest.raises(ValueError, match="dummy values \(-99\)"):
pol.validate_parameters()

assert "dummy values (-99)" in str(excinfo.value)
assert "Axv" in str(excinfo.value)

def test_valid_parameters(self):
"""
Test that valid parameters pass validation.
"""
config = {
"BOAT_TYPE": "direct_power_method",
"BOAT_BREADTH": 32,
Expand All @@ -347,12 +179,7 @@ def test_valid_parameters(self):
"BOAT_BS1": 28.8,
"BOAT_HC": 10.0
}

try:
pol = DirectPowerBoat(init_mode="from_dict", config_dict=config)
pol.load_data()
# If we get here, the test passes
assert True
except ValueError:
# If we get here, there was an unexpected ValueError
assert False, "Valid parameters should not raise ValueError"
pol = DirectPowerBoat(init_mode="from_dict", config_dict=config)
pol.load_data()
# Ensure it doesn't raise
pol.validate_parameters()