From 594ed32748dab3d2d170036a87988e34aa09424f Mon Sep 17 00:00:00 2001 From: Evan Nawfal Date: Fri, 8 Mar 2024 19:41:07 -0800 Subject: [PATCH] Resolved comments and parameterized tests --- controller/wingsail/controllers.py | 52 ++++++------ tests/unit/wingsail/test_controllers.py | 102 +++++++++++++++--------- 2 files changed, 89 insertions(+), 65 deletions(-) diff --git a/controller/wingsail/controllers.py b/controller/wingsail/controllers.py index d64e402..41faaf7 100644 --- a/controller/wingsail/controllers.py +++ b/controller/wingsail/controllers.py @@ -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: @@ -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( @@ -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 diff --git a/tests/unit/wingsail/test_controllers.py b/tests/unit/wingsail/test_controllers.py index 9f6b6a1..d2e2b65 100644 --- a/tests/unit/wingsail/test_controllers.py +++ b/tests/unit/wingsail/test_controllers.py @@ -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: @@ -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)