Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Commit

Permalink
Resolved comments and parameterized tests
Browse files Browse the repository at this point in the history
  • Loading branch information
evannawfal committed Mar 9, 2024
1 parent f51c577 commit 594ed32
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 65 deletions.
52 changes: 24 additions & 28 deletions controller/wingsail/controllers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import math

from controller.common.constants import CHORD_WIDTH_MAIN_SAIL, KINEMATIC_VISCOSITY
from controller.common.lut import LUT


class WingsailController:
"""
The controller class for computing trim tab angles for controlling the mainsail.
Args:
- chord_width_main_sail (float): The chord width of the main sail.
- kinematic_viscosity (float): The kinematic viscosity of the fluid.
- lut (LUT): A lookup table containing Reynolds numbers and corresponding desired angles of
attack.
"""

def __init__(self, chord_width_main_sail: float, kinematic_viscosity: float, lut: LUT) -> None:
self.chord_width_main_sail = chord_width_main_sail
self.kinematic_viscosity = kinematic_viscosity
self.chord_width_main_sail = CHORD_WIDTH_MAIN_SAIL
self.kinematic_viscosity = KINEMATIC_VISCOSITY
self.lut: LUT = lut

def _compute_reynolds_number(self, apparent_wind_speed: float) -> float:
Expand All @@ -24,37 +35,24 @@ def _compute_reynolds_number(self, apparent_wind_speed: float) -> float:
) / self.kinematic_viscosity
return reynolds_number

def _compute_angle_of_attack(self, reynolds_number: float, look_up_table: LUT) -> float:
"""
Computes the desired angle of attack based on Reynolds number and a lookup table.
Args:
- reynolds_number (float): The Reynolds number.
- look_up_table: A 2D numpy array containing Reynolds numbers in the first column
and corresponding desired angles of attack in the second column.
Returns:
- desired_alpha (float): The computed desired angle of attack based on the provided
Reynolds number and lookup table.
"""
desired_alpha: float = self.lut(reynolds_number) # Using __call__ method
return desired_alpha

def _compute_trim_tab_angle(
self, desired_alpha: float, apparent_wind_direction: float
self, reynolds_number: float, apparent_wind_direction: float
) -> float:
"""
Range: -180 < direction <= 180 for symmetry
Computes the trim tab angle based on Reynolds number and apparent wind direction.
Args:
- desired_alpha (float): The desired angle of attack.
- apparent_wind_direction (float): The apparent wind direction in degrees.
- reynolds_number (float): The Reynolds number.
- apparent_wind_direction (float): The absolute bearing (true heading) of the wind.
Degrees, 0° means the apparent wind is blowing from the bow to the stern of the boat,
increase CW
Range: -180 < direction <= 180 for symmetry
Returns:
- trim_tab_angle (float): The computed trim tab angle based on the provided desired angle
of attack, apparent wind direction, and boat direction.
- trim_tab_angle (float): The computed trim tab angle based on the provided
Reynolds number and apparent wind direction.
"""
desired_alpha: float = self.lut(reynolds_number) # Using __call__ method
return math.copysign(desired_alpha, apparent_wind_direction)

def get_trim_tab_angle(
Expand All @@ -73,9 +71,7 @@ def get_trim_tab_angle(
- trim_tab_angle (float): The computed trim tab angle.
"""
reynolds_number: float = self._compute_reynolds_number(apparent_wind_speed)
desired_alpha: float = self._compute_angle_of_attack(reynolds_number, self.lut)
trim_tab_angle: float = self._compute_trim_tab_angle(
desired_alpha, apparent_wind_direction
reynolds_number, apparent_wind_direction
)

return trim_tab_angle
102 changes: 65 additions & 37 deletions tests/unit/wingsail/test_controllers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import pytest
import math
import numpy as np
import pytest

from controller.common.constants import CHORD_WIDTH_MAIN_SAIL, KINEMATIC_VISCOSITY
from controller.common.lut import LUT
from controller.wingsail.controllers import WingsailController

# Define test data
test_lut_data = np.array([[50000, 5.75], [100000, 6.75], [200000, 7], [500000, 9.25], [1000000, 10]
])
test_lut_data = np.array(
[[50000, 5.75], [100000, 6.75], [200000, 7], [500000, 9.25], [1000000, 10]]
)
test_lut = LUT(test_lut_data)
test_chord_width = 0.14
test_kinematic_viscosity = 0.000014207
test_chord_width = CHORD_WIDTH_MAIN_SAIL
test_kinematic_viscosity = KINEMATIC_VISCOSITY


class TestWingsailController:
Expand All @@ -24,56 +26,82 @@ def wingsail_controller(self):
"""
return WingsailController(test_chord_width, test_kinematic_viscosity, test_lut)

def test_compute_reynolds_number(self, wingsail_controller):
@pytest.mark.parametrize(
"apparent_wind_speed, expected_reynolds_number",
[
(0, 0),
(3, 3 * CHORD_WIDTH_MAIN_SAIL / KINEMATIC_VISCOSITY),
(5, 5 * CHORD_WIDTH_MAIN_SAIL / KINEMATIC_VISCOSITY),
(10, 10 * CHORD_WIDTH_MAIN_SAIL / KINEMATIC_VISCOSITY),
(20, 20 * CHORD_WIDTH_MAIN_SAIL / KINEMATIC_VISCOSITY),
],
)
def test_compute_reynolds_number(
self, wingsail_controller, apparent_wind_speed, expected_reynolds_number
):
"""
Tests the computation of Reynolds number.
Args:
wingsail_controller: Instance of WingsailController.
"""
apparent_wind_speed = 1.0
expected_reynolds_number = 9854.297177
computed_reynolds_number = wingsail_controller._compute_reynolds_number(apparent_wind_speed
)
assert math.isclose(computed_reynolds_number, expected_reynolds_number)

def test_compute_angle_of_attack(self, wingsail_controller):
"""
Tests the computation of angle of attack.
Args:
wingsail_controller: Instance of WingsailController.
"""
reynolds_number = 75000
expected_desired_alpha = 6.25
computed_desired_alpha = wingsail_controller._compute_angle_of_attack(reynolds_number
, test_lut)
assert math.isclose(computed_desired_alpha, expected_desired_alpha)
computed_reynolds_number = wingsail_controller._compute_reynolds_number(
apparent_wind_speed
)
assert np.isclose(computed_reynolds_number, expected_reynolds_number)

def test_compute_trim_tab_angle(self, wingsail_controller):
@pytest.mark.parametrize(
"reynolds_number, apparent_wind_direction, expected_trim_tab_angle",
[
(1250, 45.0, 5.75),
(15388, -90.0, -5.75),
(210945, 170.0, 7.0820875),
(824000, -120.0, -9.736),
(2000000, 0, 10.0),
],
)
def test_compute_trim_tab_angle(
self,
wingsail_controller,
reynolds_number,
apparent_wind_direction,
expected_trim_tab_angle,
):
"""
Tests the computation of trim tab angle.
Args:
wingsail_controller: Instance of WingsailController.
"""
desired_alpha = 10.0
apparent_wind_direction = 45.0
expected_trim_tab_angle = 10.0
computed_trim_tab_angle = wingsail_controller._compute_trim_tab_angle(
desired_alpha, apparent_wind_direction)
assert math.isclose(computed_trim_tab_angle, expected_trim_tab_angle)
reynolds_number, apparent_wind_direction
)
assert np.isclose(computed_trim_tab_angle, expected_trim_tab_angle)

def test_get_trim_tab_angle(self, wingsail_controller):
@pytest.mark.parametrize(
"apparent_wind_speed, apparent_wind_direction, expected_trim_tab_angle",
[
(10.0, 0, 6.720859435489),
(4.0, 90.0, 5.75),
(10.0, 180.0, 6.720859435489),
(15.0, -50.0, -6.869536144),
(20.0, -120.0, -6.99271485887),
],
)
def test_get_trim_tab_angle(
self,
wingsail_controller,
apparent_wind_speed,
apparent_wind_direction,
expected_trim_tab_angle,
):
"""
Tests the computation of final trim tab angle.
Args:
wingsail_controller: Instance of WingsailController.
"""
apparent_wind_speed = 1.0
apparent_wind_direction = 45.0
expected_trim_tab_angle = 5.75
computed_trim_tab_angle = wingsail_controller.get_trim_tab_angle(
apparent_wind_speed, apparent_wind_direction)
assert math.isclose(computed_trim_tab_angle, expected_trim_tab_angle)
apparent_wind_speed, apparent_wind_direction
)
assert np.isclose(computed_trim_tab_angle, expected_trim_tab_angle)

0 comments on commit 594ed32

Please sign in to comment.