py_windblade_opa 0.6.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_windblade_opa/__init__.py +19 -0
- py_windblade_opa/airfoils/__init__.py +1 -0
- py_windblade_opa/airfoils/af_naca4415.py +75 -0
- py_windblade_opa/airfoils/af_naca4415_test.py +78 -0
- py_windblade_opa/airfoils/airfoil_abstract.py +48 -0
- py_windblade_opa/airfoils/obsolete_cl.py +24 -0
- py_windblade_opa/bem_hansen/__init__.py +5 -0
- py_windblade_opa/bem_hansen/aero_coefficient_interpolator.py +219 -0
- py_windblade_opa/bem_hansen/blade_aux_functions.py +257 -0
- py_windblade_opa/bem_hansen/blade_performance_calculator.py +253 -0
- py_windblade_opa/bem_hansen/data_models.py +25 -0
- py_windblade_opa/bem_hansen/support_functions.py +16 -0
- py_windblade_opa/blade_geometry.py +122 -0
- py_windblade_opa/blade_geometry_v2.py +268 -0
- py_windblade_opa/blade_plotter.py +146 -0
- py_windblade_opa/deol_orch/__init__.py +0 -0
- py_windblade_opa/deol_orch/_common.py +72 -0
- py_windblade_opa/deol_orch/_components_array.py +63 -0
- py_windblade_opa/deol_orch/_components_loop.py +127 -0
- py_windblade_opa/deol_orch/blade_calc_orchestrator.py +249 -0
- py_windblade_opa/deol_orch/data_models.py +35 -0
- py_windblade_opa/deol_orch/refactoring.md +158 -0
- py_windblade_opa/performance/__init__.py +12 -0
- py_windblade_opa/performance/curve_data.py +30 -0
- py_windblade_opa/performance/formulas.py +82 -0
- py_windblade_opa/performance/post_processor.py +127 -0
- py_windblade_opa/performance/report.py +94 -0
- py_windblade_opa/support.py +79 -0
- py_windblade_opa/utils.py +66 -0
- py_windblade_opa/wel_blade_designer.py +177 -0
- py_windblade_opa-0.6.6.dist-info/METADATA +135 -0
- py_windblade_opa-0.6.6.dist-info/RECORD +34 -0
- py_windblade_opa-0.6.6.dist-info/WHEEL +4 -0
- py_windblade_opa-0.6.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from importlib.metadata import version as _version, PackageNotFoundError as _PackageNotFoundError
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = _version("py_windblade_opa")
|
|
5
|
+
except _PackageNotFoundError:
|
|
6
|
+
__version__ = "0.0.0+unknown"
|
|
7
|
+
|
|
8
|
+
__np_package_name__ = 'py_windblade_opa'
|
|
9
|
+
|
|
10
|
+
from .support import WECSSpecs, MiscConstants, WECSConfig
|
|
11
|
+
from .airfoils.airfoil_abstract import AirfoilAC
|
|
12
|
+
from .blade_geometry import BladeGeometry
|
|
13
|
+
from .blade_geometry_v2 import BladeGeometryContainer
|
|
14
|
+
from .wel_blade_designer import WELBladeDesigner
|
|
15
|
+
from .blade_plotter import BladePlot
|
|
16
|
+
from .deol_orch.blade_calc_orchestrator import BladeCalcOrchestrator
|
|
17
|
+
from .utils import x_irregular_to_xnew, x_irregular_to_linspace
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .af_naca4415 import NACA4415
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .airfoil_abstract import AirfoilAC
|
|
3
|
+
|
|
4
|
+
class NACA4415(AirfoilAC):
|
|
5
|
+
""" Class representing the NACA 4415 airfoil. """
|
|
6
|
+
|
|
7
|
+
def __init__(self, thickness):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.designation = "NACA4415"
|
|
10
|
+
self._t = thickness
|
|
11
|
+
self._airfoil_elastic_centre = 0.296
|
|
12
|
+
self.CLdataRe250000 = np.array([[-10, -0.43], [-9, -0.41], [-4, -0.16], [-2, 0.04],
|
|
13
|
+
[0, 0.23], [2, 0.43], [5, 0.66], [7, 0.85], [9, 1.03],
|
|
14
|
+
[11, 1.19], [13, 1.32], [17, 1.42], [19, 1.44], [21, 1.39],
|
|
15
|
+
[25, 1.23], [30, 0.92], [35, 1.08], [40, 1.22], [45, 1.21],
|
|
16
|
+
[50, 1.17], [60, 1.02], [69, 0.75], [78, 0.43], [87, 0.11],
|
|
17
|
+
[97, -0.26], [106, -0.63]]) # Fill in the rest of the data
|
|
18
|
+
self.CDdataRe250000 = np.array([[-10.67, 0.16], [-8.75, 0.11], [-6.67, 0.03], [-4.42, 0.02],
|
|
19
|
+
[-2.33, 0.02], [0.0, 0.02], [2.5, 0.02], [4.58, 0.03], [6.83, 0.03],
|
|
20
|
+
[9.0, 0.04], [11.25, 0.06], [13.33, 0.08], [15.42, 0.09], [17.5, 0.13],
|
|
21
|
+
[19.33, 0.15], [21.25, 0.19], [26.67, 0.3], [31.08, 0.53], [36.25, 0.77],
|
|
22
|
+
[41.67, 1.04], [46.67, 1.22], [51.67, 1.4], [61.67, 1.7], [71.25, 1.87],
|
|
23
|
+
[81.0, 2.05], [90.67, 2.07], [100.17, 2.05], [109.58, 2.02]]) # Fill in the rest of the data
|
|
24
|
+
|
|
25
|
+
def cl(self, angle_deg):
|
|
26
|
+
""" Returns the lift coefficient of the airfoil. """
|
|
27
|
+
return self._interpolate(angle_deg, self.CLdataRe250000)
|
|
28
|
+
|
|
29
|
+
def cd(self, angle_deg):
|
|
30
|
+
""" Calculates the drag coefficient. """
|
|
31
|
+
return self._interpolate(angle_deg, self.CDdataRe250000)
|
|
32
|
+
|
|
33
|
+
def _interpolate(self, angle_deg, xy_array):
|
|
34
|
+
""" Function that interpolates between a given array. """
|
|
35
|
+
no_rows = len(xy_array)
|
|
36
|
+
if angle_deg <= xy_array[0, 0]:
|
|
37
|
+
return xy_array[0, 1]
|
|
38
|
+
elif angle_deg >= xy_array[-1, 0]:
|
|
39
|
+
return xy_array[-1, 1]
|
|
40
|
+
else:
|
|
41
|
+
for i in range(no_rows):
|
|
42
|
+
if xy_array[i, 0] >= angle_deg:
|
|
43
|
+
break
|
|
44
|
+
return xy_array[i, 1] - (xy_array[i, 0] - angle_deg) * (xy_array[i, 1] - xy_array[i - 1, 1]) / (xy_array[i, 0] - xy_array[i - 1, 0])
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def cl_approx(iattack_deg):
|
|
48
|
+
"""
|
|
49
|
+
Calculates the CL (lift coefficient) for a single Iattack (angle of attack) value.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
iattack_deg (float): Single Iattack (angle of attack) value in degrees.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
float: CL (lift coefficient) value for the given Iattack.
|
|
56
|
+
"""
|
|
57
|
+
if iattack_deg < -65:
|
|
58
|
+
CL = iattack_deg * (-0.0373) - 3.5224
|
|
59
|
+
elif iattack_deg > 30:
|
|
60
|
+
CL = (7e-6 * iattack_deg**3 - 0.0018 * iattack_deg**2 + 0.119 * iattack_deg - 1.196)
|
|
61
|
+
elif iattack_deg > -10:
|
|
62
|
+
CL = (-7e-5 * iattack_deg**3 - 0.0004 * iattack_deg**2 + 0.0962 * iattack_deg + 0.242)
|
|
63
|
+
else:
|
|
64
|
+
CL = -0.95
|
|
65
|
+
return CL
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__== "__main__":
|
|
70
|
+
# Example usage
|
|
71
|
+
naca4415 = NACA4415(thickness=0.15)
|
|
72
|
+
lift_coefficient = naca4415.cl(5) # Example angle
|
|
73
|
+
drag_coefficient = naca4415.cd(5) # Example angle
|
|
74
|
+
|
|
75
|
+
# %%
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#%%
|
|
2
|
+
import numpy as np
|
|
3
|
+
try:
|
|
4
|
+
from .airfoil_abstract import AirfoilAC
|
|
5
|
+
except:
|
|
6
|
+
from airfoil_abstract import AirfoilAC
|
|
7
|
+
|
|
8
|
+
class NACAtest(AirfoilAC):
|
|
9
|
+
""" Class representing the test cases
|
|
10
|
+
|
|
11
|
+
It uses a version of the NACAtest airfoil .
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, thickness):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.designation = "NACAtest"
|
|
17
|
+
self._t = thickness
|
|
18
|
+
self._airfoil_elastic_centre = 0.296
|
|
19
|
+
self.CLdataRe250000 = np.array([[-10, -0.43], [-9, -0.41], [-4, -0.16], [-2, 0.04],
|
|
20
|
+
[0, 0.23], [2, 0.43], [5, 0.66], [7, 0.85], [9, 1.03],
|
|
21
|
+
[11, 1.19], [13, 1.32], [17, 1.42], [19, 1.44], [21, 1.39],
|
|
22
|
+
[25, 1.23], [30, 0.92], [35, 1.08], [40, 1.22], [45, 1.21],
|
|
23
|
+
[50, 1.17], [60, 1.02], [69, 0.75], [78, 0.43], [87, 0.11],
|
|
24
|
+
[97, -0.26], [106, -0.63]]) # Fill in the rest of the data
|
|
25
|
+
self.CDdataRe250000 = np.array([[-10.67, 0.16], [-8.75, 0.11], [-6.67, 0.03], [-4.42, 0.02],
|
|
26
|
+
[-2.33, 0.02], [0.0, 0.02], [2.5, 0.02], [4.58, 0.03], [6.83, 0.03],
|
|
27
|
+
[9.0, 0.04], [11.25, 0.06], [13.33, 0.08], [15.42, 0.09], [17.5, 0.13],
|
|
28
|
+
[19.33, 0.15], [21.25, 0.19], [26.67, 0.3], [31.08, 0.53], [36.25, 0.77],
|
|
29
|
+
[41.67, 1.04], [46.67, 1.22], [51.67, 1.4], [61.67, 1.7], [71.25, 1.87],
|
|
30
|
+
[81.0, 2.05], [90.67, 2.07], [100.17, 2.05], [109.58, 2.02]]) # Fill in the rest of the data
|
|
31
|
+
|
|
32
|
+
def cl(self, angle_deg):
|
|
33
|
+
""" Returns the lift coefficient of the airfoil.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
- angle_deg (float): Angle of attack in degrees.
|
|
37
|
+
"""
|
|
38
|
+
return NACAtest.cl_approx(angle_deg)
|
|
39
|
+
|
|
40
|
+
def cd(self, angle_deg):
|
|
41
|
+
""" Calculates the drag coefficient. """
|
|
42
|
+
# return self._interpolate(angle_deg, self.CDdataRe250000)
|
|
43
|
+
raise NotImplementedError("Method not implemented yet")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def cl_approx(iattack_deg):
|
|
49
|
+
"""
|
|
50
|
+
Calculates the CL (lift coefficient) for a single Iattack (angle of attack) value.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
iattack_deg (float): Single Iattack (angle of attack) value in degrees.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
float: CL (lift coefficient) value for the given Iattack.
|
|
57
|
+
"""
|
|
58
|
+
if iattack_deg < -65:
|
|
59
|
+
CL = iattack_deg * (-0.0373) - 3.5224
|
|
60
|
+
elif iattack_deg > 30:
|
|
61
|
+
CL = (7e-6 * iattack_deg**3 - 0.0018 * iattack_deg**2 + 0.119 * iattack_deg - 1.196)
|
|
62
|
+
elif iattack_deg > -10:
|
|
63
|
+
CL = (-7e-5 * iattack_deg**3 - 0.0004 * iattack_deg**2 + 0.0962 * iattack_deg + 0.242)
|
|
64
|
+
else:
|
|
65
|
+
CL = -0.95
|
|
66
|
+
return CL
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__== "__main__":
|
|
71
|
+
# Example usage
|
|
72
|
+
|
|
73
|
+
naca4415 = NACAtest(thickness=0.15)
|
|
74
|
+
lift_coefficient = naca4415.cl(5) # Example angle
|
|
75
|
+
print(lift_coefficient)
|
|
76
|
+
# drag_coefficient = naca4415.cd(5) # Example angle
|
|
77
|
+
|
|
78
|
+
# %%
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#%%
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
class AirfoilAC(ABC):
|
|
6
|
+
""" Abstract class that defines airfoil types. Only provides by default Cl, Cd. """
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.af_designation = ""
|
|
10
|
+
self._airfoil_elastic_centre = 0.0
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def cl(self, pip):
|
|
14
|
+
""" Calculate the lift coefficient. """
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def cd(self, pip):
|
|
19
|
+
""" Calculate the drag coefficient. """
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def designation(self)-> str:
|
|
24
|
+
"""the designation of the airfoil
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
_type_: _description_
|
|
28
|
+
"""
|
|
29
|
+
return self.af_designation
|
|
30
|
+
|
|
31
|
+
@designation.setter
|
|
32
|
+
def designation(self, value):
|
|
33
|
+
self.af_designation = value
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def airfoil_centre(self)->float:
|
|
37
|
+
"""the position of the elastic center as percentage of the chord
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
float: _description_
|
|
41
|
+
"""
|
|
42
|
+
return self._airfoil_elastic_centre
|
|
43
|
+
|
|
44
|
+
@airfoil_centre.setter
|
|
45
|
+
def airfoil_centre(self, value):
|
|
46
|
+
self._airfoil_elastic_centre = value
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# this file contains some obsolete functions that were used in the past for the calculation of the lift coefficient (CL) for the wind turbine blade airfoil.
|
|
2
|
+
# they should be removed at some point.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def cl_approx(iattack_deg:float) -> float:
|
|
6
|
+
""" Calculates the CL (lift coefficient) for a single Iattack (angle of attack) value.
|
|
7
|
+
|
|
8
|
+
This is probably an approximation for NACA4415 airfoil.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
iattack_deg (float): Single Iattack (angle of attack) value in degrees.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
float: CL (lift coefficient) value for the given Iattack.
|
|
15
|
+
"""
|
|
16
|
+
if iattack_deg < -65:
|
|
17
|
+
CL = iattack_deg * (-0.0373) - 3.5224
|
|
18
|
+
elif iattack_deg > 30:
|
|
19
|
+
CL = (7e-6 * iattack_deg**3 - 0.0018 * iattack_deg**2 + 0.119 * iattack_deg - 1.196)
|
|
20
|
+
elif iattack_deg > -10:
|
|
21
|
+
CL = (-7e-5 * iattack_deg**3 - 0.0004 * iattack_deg**2 + 0.0962 * iattack_deg + 0.242)
|
|
22
|
+
else:
|
|
23
|
+
CL = -0.95
|
|
24
|
+
return CL
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .blade_aux_functions import BladeAuxFunctions
|
|
2
|
+
from .aero_coefficient_interpolator import AeroCoefficientInterpolator
|
|
3
|
+
from .support_functions import discretize_blade
|
|
4
|
+
from .blade_performance_calculator import BladePerformanceCalculator
|
|
5
|
+
from .data_models import BladeOperatingPoint
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from scipy.interpolate import griddata
|
|
4
|
+
from scipy.interpolate import interp1d
|
|
5
|
+
|
|
6
|
+
class AeroCoefficientInterpolator:
|
|
7
|
+
def __init__(self, df:pd.DataFrame):
|
|
8
|
+
"""
|
|
9
|
+
Initialize the interpolator with a DataFrame containing aoa_deg, tcratio, C_L, C_D, and C_M.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
df (pd.DataFrame): DataFrame with columns 'aoa_deg', 'tcratio', 'C_L', 'C_D', 'C_M'.
|
|
13
|
+
"""
|
|
14
|
+
self._df = df
|
|
15
|
+
# Store the points (aoa_deg and tcratio) and values (C_L, C_D, C_M) for interpolation
|
|
16
|
+
self.points = df[['aoa_deg', 'tcratio']].values # Independent variables for interpolation
|
|
17
|
+
|
|
18
|
+
# Store the corresponding values for interpolation
|
|
19
|
+
self.values_cl = df['C_L'].values # Coefficient of lift
|
|
20
|
+
self.values_cd = df['C_D'].values # Coefficient of drag
|
|
21
|
+
self.values_cm = df['C_M'].values # Coefficient of moment
|
|
22
|
+
|
|
23
|
+
def interpolate_cl(self, aoa, tcratio, method:str="interp1d"):
|
|
24
|
+
assert method in ["interp1d", "griddata"], f"Invalid interpolation method: {method}"
|
|
25
|
+
|
|
26
|
+
if method == "interp1d":
|
|
27
|
+
return self.interpolate_cl_interp1d(aoa, tcratio)
|
|
28
|
+
elif method == "griddata":
|
|
29
|
+
return self.interpolate_cl_gd(aoa, tcratio)
|
|
30
|
+
|
|
31
|
+
def interpolate_cd(self, aoa, tcratio, method:str="interp1d"):
|
|
32
|
+
assert method in ["interp1d", "griddata"], f"Invalid interpolation method: {method}"
|
|
33
|
+
|
|
34
|
+
if method == "interp1d":
|
|
35
|
+
return self.interpolate_cd_interp1d(aoa, tcratio)
|
|
36
|
+
elif method == "griddata":
|
|
37
|
+
return self.interpolate_cd_gd(aoa, tcratio)
|
|
38
|
+
|
|
39
|
+
def interpolate_cm(self, aoa, tcratio, method:str="interp1d"):
|
|
40
|
+
assert method in ["interp1d", "griddata"], f"Invalid interpolation method: {method}"
|
|
41
|
+
|
|
42
|
+
if method == "interp1d":
|
|
43
|
+
return self.interpolate_cm_interp1d(aoa, tcratio)
|
|
44
|
+
elif method == "griddata":
|
|
45
|
+
return self.interpolate_cm_gd(aoa, tcratio)
|
|
46
|
+
|
|
47
|
+
#region Backwards compatible interpolation methods
|
|
48
|
+
def interpolate_cl_interp1d(self, aoa, tcratio):
|
|
49
|
+
"""
|
|
50
|
+
Alternative interpolation method for coefficient of lift (C_L).
|
|
51
|
+
Interpolates for each available thickness and then for the given thickness.
|
|
52
|
+
|
|
53
|
+
Parameters:
|
|
54
|
+
aoa (float): The angle of attack for which to interpolate.
|
|
55
|
+
tcratio (float): The thickness to chord ratio for which to interpolate.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
float: Interpolated coefficient of lift (C_L).
|
|
59
|
+
"""
|
|
60
|
+
# Code for alternative interpolation method
|
|
61
|
+
self.cl_wide = self._df.pivot(index='aoa_deg', columns='tcratio', values='C_L')
|
|
62
|
+
self.aoa_values = self.cl_wide.index.values # aoa_deg values (from rows)
|
|
63
|
+
self.tcratio_values = self.cl_wide.columns.values # tcratio values (from columns)
|
|
64
|
+
|
|
65
|
+
# Step 1: Interpolate C_L values for each thickness at the given aoa (interpolating across aoa_deg)
|
|
66
|
+
clthick = np.zeros_like(self.tcratio_values)
|
|
67
|
+
for i, tcr in enumerate(self.tcratio_values):
|
|
68
|
+
# Extract C_L values for this particular tcratio
|
|
69
|
+
cl_column = self.cl_wide[tcr].values
|
|
70
|
+
|
|
71
|
+
# Create an interpolator for aoa_deg vs C_L for the current tcratio
|
|
72
|
+
aoa_interpolator = interp1d(self.aoa_values, cl_column, kind='linear', fill_value="extrapolate")
|
|
73
|
+
|
|
74
|
+
# Interpolate the C_L value for the given aoa
|
|
75
|
+
clthick[i] = aoa_interpolator(aoa)
|
|
76
|
+
|
|
77
|
+
# Step 2: Interpolate the resulting clthick values for the actual tcratio (interpolating across tcratio)
|
|
78
|
+
cl_interpolator = interp1d(self.tcratio_values, clthick, kind='linear', fill_value="extrapolate")
|
|
79
|
+
return cl_interpolator(tcratio)
|
|
80
|
+
|
|
81
|
+
def interpolate_cd_interp1d(self, aoa, tcratio):
|
|
82
|
+
"""Backwards compatible interpolation method for coefficient of drag (C_D).
|
|
83
|
+
|
|
84
|
+
It uses interp1d for interpolation. This method is currently used for validation purposes (units tests and matlab).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
aoa (_type_): _description_
|
|
88
|
+
tcratio (_type_): _description_
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
_type_: _description_
|
|
92
|
+
"""
|
|
93
|
+
self.cd_wide = self._df.pivot(index='aoa_deg', columns='tcratio', values='C_D')
|
|
94
|
+
self.aoa_values = self.cd_wide.index.values # aoa_deg values (from rows)
|
|
95
|
+
self.tcratio_values = self.cd_wide.columns.values # tcratio values (from columns)
|
|
96
|
+
|
|
97
|
+
# Step1: Interpolate C_D values for each thickness at the given aoa (interpolating across aoa_deg)
|
|
98
|
+
cdthick = np.zeros_like(self.tcratio_values)
|
|
99
|
+
for i, tcr in enumerate(self.tcratio_values):
|
|
100
|
+
# Extract C_D values for this particular tcratio
|
|
101
|
+
cd_column = self.cd_wide[tcr].values
|
|
102
|
+
|
|
103
|
+
# Create an interpolator for aoa_deg vs C_D for the current tcratio
|
|
104
|
+
aoa_interpolator = interp1d(self.aoa_values, cd_column, kind='linear', fill_value="extrapolate")
|
|
105
|
+
|
|
106
|
+
# Interpolate the C_D value for the given aoa
|
|
107
|
+
cdthick[i] = aoa_interpolator(aoa)
|
|
108
|
+
|
|
109
|
+
# Step 2: Interpolate the resulting cdthick values for the actual tcratio (interpolating across tcratio)
|
|
110
|
+
cd_interpolator = interp1d(self.tcratio_values, cdthick, kind='linear', fill_value="extrapolate")
|
|
111
|
+
return cd_interpolator(tcratio)
|
|
112
|
+
|
|
113
|
+
def interpolate_cm_interp1d(self, aoa, tcratio):
|
|
114
|
+
"""
|
|
115
|
+
Backwards compatible interpolation method for coefficient of moment (C_M).
|
|
116
|
+
|
|
117
|
+
It uses interp1d for interpolation. This method is currently used for validation purposes (units tests and matlab).
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
aoa (_type_): _description_
|
|
121
|
+
tcratio (_type_): _description_
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
float: Coefficient of moment (C_M) for the given aoa and tcratio.
|
|
125
|
+
"""
|
|
126
|
+
self.cm_wide = self._df.pivot(index='aoa_deg', columns='tcratio', values='C_M')
|
|
127
|
+
self.aoa_values = self.cm_wide.index.values
|
|
128
|
+
self.tcratio_values = self.cm_wide.columns.values
|
|
129
|
+
|
|
130
|
+
# Step1: Interpolate C_M values for each thickness at the given aoa (interpolating across aoa_deg)
|
|
131
|
+
cmthick = np.zeros_like(self.tcratio_values)
|
|
132
|
+
for i, tcr in enumerate(self.tcratio_values):
|
|
133
|
+
# Extract C_M values for this particular tcratio
|
|
134
|
+
cm_column = self.cm_wide[tcr].values
|
|
135
|
+
|
|
136
|
+
# Create an interpolator for aoa_deg vs C_M for the current tcratio
|
|
137
|
+
aoa_interpolator = interp1d(self.aoa_values, cm_column, kind='linear', fill_value="extrapolate")
|
|
138
|
+
|
|
139
|
+
# Interpolate the C_M value for the given aoa
|
|
140
|
+
cmthick[i] = aoa_interpolator(aoa)
|
|
141
|
+
|
|
142
|
+
# Step 2: Interpolate the resulting cmthick values for the actual tcratio (interpolating across tcratio)
|
|
143
|
+
cm_interpolator = interp1d(self.tcratio_values, cmthick, kind='linear', fill_value="extrapolate")
|
|
144
|
+
return cm_interpolator(tcratio)
|
|
145
|
+
|
|
146
|
+
#endregion
|
|
147
|
+
|
|
148
|
+
#region Griddata interpolation methods
|
|
149
|
+
def __interpolate(self, aoa, tcratio, values):
|
|
150
|
+
"""
|
|
151
|
+
Interpolate the values for given aoa and tcratio.
|
|
152
|
+
|
|
153
|
+
Parameters:
|
|
154
|
+
aoa (float): The angle of attack for which to interpolate.
|
|
155
|
+
tcratio (float): The thickness to chord ratio for which to interpolate.
|
|
156
|
+
values (np.array): The values to interpolate.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
float: Interpolated value.
|
|
160
|
+
"""
|
|
161
|
+
point_to_interpolate = np.array([[aoa, tcratio]])
|
|
162
|
+
interpolated_value = griddata(self.points, values, point_to_interpolate, method='linear')
|
|
163
|
+
return interpolated_value[0]
|
|
164
|
+
|
|
165
|
+
def interpolate_cl_gd(self, aoa, tcratio):
|
|
166
|
+
"""
|
|
167
|
+
Interpolate the coefficient of lift (C_L) for given aoa and tcratio.
|
|
168
|
+
|
|
169
|
+
This is an advanced method, however, it is not compatible with the current unit tests.
|
|
170
|
+
TODO: Develop suitable unit tests
|
|
171
|
+
|
|
172
|
+
Parameters:
|
|
173
|
+
aoa (float): The angle of attack for which to interpolate.
|
|
174
|
+
tcratio (float): The thickness to chord ratio for which to interpolate.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
float: Interpolated coefficient of lift (C_L).
|
|
178
|
+
"""
|
|
179
|
+
return self.__interpolate(aoa, tcratio, self.values_cl)
|
|
180
|
+
# point_to_interpolate = np.array([[aoa, tcratio]])
|
|
181
|
+
# cl_interpolated = griddata(self.points, self.values_cl, point_to_interpolate, method='linear')
|
|
182
|
+
# return cl_interpolated[0] # Return the interpolated value
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def interpolate_cd_gd(self, aoa, tcratio):
|
|
187
|
+
"""
|
|
188
|
+
Interpolate the coefficient of drag (C_D) for given aoa and tcratio.
|
|
189
|
+
|
|
190
|
+
Parameters:
|
|
191
|
+
aoa (float): The angle of attack for which to interpolate.
|
|
192
|
+
tcratio (float): The thickness to chord ratio for which to interpolate.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
float: Interpolated coefficient of drag (C_D).
|
|
196
|
+
"""
|
|
197
|
+
# point_to_interpolate = np.array([[aoa, tcratio]])
|
|
198
|
+
# cd_interpolated = griddata(self.points, self.values_cd, point_to_interpolate, method='linear')
|
|
199
|
+
# return cd_interpolated[0] # Return the interpolated value
|
|
200
|
+
return self.__interpolate(aoa, tcratio, self.values_cd)
|
|
201
|
+
|
|
202
|
+
def interpolate_cm_gd(self, aoa, tcratio):
|
|
203
|
+
"""
|
|
204
|
+
Interpolate the coefficient of moment (C_M) for given aoa and tcratio.
|
|
205
|
+
|
|
206
|
+
Parameters:
|
|
207
|
+
aoa (float): The angle of attack for which to interpolate.
|
|
208
|
+
tcratio (float): The thickness to chord ratio for which to interpolate.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
float: Interpolated coefficient of moment (C_M).
|
|
212
|
+
"""
|
|
213
|
+
# point_to_interpolate = np.array([[aoa, tcratio]])
|
|
214
|
+
# cm_interpolated = griddata(self.points, self.values_cm, point_to_interpolate, method='linear')
|
|
215
|
+
# return cm_interpolated[0] # Return the interpolated value
|
|
216
|
+
return self.__interpolate(aoa, tcratio, self.values_cm)
|
|
217
|
+
|
|
218
|
+
#endregion
|
|
219
|
+
|