33profile. Input data can be:
44 - the coordinates arrays;
55 - the chord percentages, the associated nondimensional camber and thickness,
6- the real values of chord lengths, camber and thickness associated to the
6+ the real values of chord lengths, camber and thickness associated to the
77 single blade sections.
88"""
99
1414class CustomProfile (ProfileBase ):
1515 """
1616 Provide custom profile, given the airfoil coordinates or the airfoil parameters,
17- i.e. , chord percentages and length, nondimensional and maximum camber,
17+ i.e. , chord percentages and length, nondimensional and maximum camber,
1818 nondimensional and maximum thickness.
19-
19+
2020 If coordinates are directly given as input:
2121
2222 :param numpy.ndarray xup: 1D array that contains the X-components of the
@@ -27,17 +27,16 @@ class CustomProfile(ProfileBase):
2727 airfoil's upper surface
2828 :param numpy.ndarray ydown: 1D array that contains the Y-components of the
2929 airfoil's lower surface
30- :param float chord_len: the chord length of the airfoil section
31-
30+
3231 If section parameters are given as input:
33-
32+
3433 :param numpy.ndarray chord_perc: 1D array that contains the chord percentages
3534 of an airfoil section for which camber and thickness are measured
3635 :param numpy.ndarray camber_perc: 1D array that contains the camber percentage
3736 of an airfoil section at all considered chord percentages. The percentage is
3837 taken with respect to the section maximum camber
39- :param numpy.ndarray thickness_perc: 1D array that contains the thickness
40- percentage of an airfoil section at all considered chord percentages.
38+ :param numpy.ndarray thickness_perc: 1D array that contains the thickness
39+ percentage of an airfoil section at all considered chord percentages.
4140 The percentage is with respect to the section maximum thickness
4241 :param float chord_len: the length of the chord line of a certain airfoil section
4342 :param float camber_max: the maximum camber at a certain airfoil section
@@ -51,9 +50,7 @@ def __init__(self, **kwargs):
5150 self .yup_coordinates = kwargs ['yup' ]
5251 self .xdown_coordinates = kwargs ['xdown' ]
5352 self .ydown_coordinates = kwargs ['ydown' ]
54- self .chord_len = kwargs .get ('chord_len' , 1 )
55-
56- self ._generate_parameters ()
53+ self ._check_coordinates ()
5754
5855 elif set (kwargs .keys ()) == set ([
5956 'chord_perc' , 'camber_perc' , 'thickness_perc' , 'chord_len' ,
@@ -66,24 +63,22 @@ def __init__(self, **kwargs):
6663 self .chord_len = kwargs ['chord_len' ]
6764 self .camber_max = kwargs ['camber_max' ]
6865 self .thickness_max = kwargs ['thickness_max' ]
66+ self ._check_parameters ()
6967
70- self ._generate_coordinates ()
7168 else :
7269 raise RuntimeError (
7370 """Input arguments should be the section coordinates
7471 (xup, yup, xdown, ydown) and chord_len (optional)
7572 or the section parameters (camber_perc, thickness_perc,
7673 camber_max, thickness_max, chord_perc, chord_len).""" )
7774
78- self ._check_args ()
79- self ._check_coordinates ()
8075
81- def _check_args (self ):
76+ def _check_parameters (self ):
8277 """
8378 Private method that checks whether the airfoil parameters defined
8479 are provided correctly.
85- In particular, the chord, camber and thickness percentages are consistent and
86- have the same length.
80+ In particular, the chord, camber and thickness percentages are
81+ consistent and have the same length.
8782 """
8883
8984 if self .chord_percentage is None :
@@ -131,72 +126,124 @@ def _check_args(self):
131126 raise ValueError (
132127 'thickness_perc and chord_perc must have same shape.' )
133128
134- def _generate_coordinates (self ):
129+ def _compute_orth_camber_coordinates (self ):
130+ '''
131+ Compute the coordinates of points on upper and lower profile on the
132+ line orthogonal to the camber line.
133+
134+ :return: x and y coordinates of section points on line orthogonal to
135+ camber line
136+ '''
137+ # Compute the angular coefficient of the camber line
138+ n_pos = self .chord_percentage .shape [0 ]
139+ m = np .zeros (n_pos )
140+ for i in range (1 , n_pos , 1 ):
141+ m [i ] = (self .camber_percentage [i ]-
142+ self .camber_percentage [i - 1 ])/ (self .chord_percentage [i ]-
143+ self .chord_percentage [i - 1 ])* self .camber_max / self .chord_len
144+
145+ m_angle = np .arctan (m )
146+
147+ xup_tmp = (self .chord_percentage * self .chord_len -
148+ self .thickness_percentage * np .sin (m_angle )* self .thickness_max / 2 )
149+ xdown_tmp = (self .chord_percentage * self .chord_len +
150+ self .thickness_percentage * np .sin (m_angle )* self .thickness_max / 2 )
151+ yup_tmp = (self .camber_percentage * self .camber_max +
152+ self .thickness_max / 2 * self .thickness_percentage * np .cos (m_angle ))
153+ ydown_tmp = (self .camber_percentage * self .camber_max -
154+ self .thickness_max / 2 * self .thickness_percentage * np .cos (m_angle ))
155+
156+ if xup_tmp [1 ]< 0 :
157+ xup_tmp [1 ], xdown_tmp [1 ] = xup_tmp [2 ]- 1e-16 , xdown_tmp [2 ]- 1e-16
158+ yup_tmp [1 ], ydown_tmp [1 ] = yup_tmp [2 ]- 1e-16 , ydown_tmp [2 ]- 1e-16
159+
160+ return xup_tmp , xdown_tmp , yup_tmp , ydown_tmp
161+
162+
163+
164+ def generate_coordinates (self ):
135165 """
136- Private method that generates the coordinates of a general airfoil profile,
137- starting from the chord percantages and the related nondimensional
138- camber and thickness. input data should be integrated with the information
139- of chord length, camber and thickness of specific sections .
166+ Method that generates the coordinates of a general airfoil
167+ profile, starting from the chord percentages and the related
168+ nondimensional camber and thickness, the maximum values of thickness
169+ and camber .
140170 """
141171
142- # compute the angular coefficient of the camber line at each chord
143- # percentage and convert it from degrees to radiant
144- n_pos = len (self .chord_percentage )
145- m = np .zeros (n_pos )
146- m [0 ] = 0
147- for i in range (1 , len (self .chord_percentage ), 1 ):
148- m [i ] = (self .camber_percentage [i ] -
149- self .camber_percentage [i - 1 ]) / (
150- self .chord_percentage [i ] - self .chord_percentage [i - 1 ])
151-
152- m_angle = m * np .pi / 180
153- self .xup_coordinates = np .zeros (n_pos )
154- self .xdown_coordinates = np .zeros (n_pos )
155- self .yup_coordinates = np .zeros (n_pos )
156- self .ydown_coordinates = np .zeros (n_pos )
157-
158- #compute the coordinates starting from a single section data and parameters
159- for j in range (0 , n_pos , 1 ):
160- self .xup_coordinates [j ] = self .chord_percentage [j ]
161- self .xdown_coordinates [j ] = self .chord_percentage [j ]
162- self .yup_coordinates [j ] = (
163- self .camber_percentage [j ] * self .camber_max +
164- self .thickness_percentage [j ] * self .thickness_max *
165- np .cos (m_angle [j ])) * self .chord_len
166- self .ydown_coordinates [j ] = (
167- self .camber_percentage [j ] * self .camber_max -
168- self .thickness_percentage [j ] * self .thickness_max *
169- np .cos (m_angle [j ])) * self .chord_len
170-
171- def _generate_parameters (self ):
172+ self .xup_coordinates , self .xdown_coordinates , self .yup_coordinates , self .ydown_coordinates = self ._compute_orth_camber_coordinates ()
173+
174+ self .ydown_coordinates = self .ydown_curve (
175+ self .chord_len * (self .chord_percentage ).reshape (- 1 ,1 )).reshape (
176+ self .chord_percentage .shape )
177+ self .xup_coordinates = self .chord_percentage * self .chord_len
178+ self .xdown_coordinates = self .xup_coordinates .copy ()
179+ self .yup_coordinates = (2 * self .camber_max * self .camber_percentage -
180+ self .ydown_coordinates )
181+
182+ self .yup_coordinates [0 ] = 0
183+ self .yup_coordinates [- 1 ] = 0
184+ self .ydown_coordinates [0 ] = 0
185+ self .ydown_coordinates [- 1 ] = 0
186+
187+
188+ def adimensionalize (self ):
172189 '''
173- Private method to find parameters related to each section
174- (chord percentages, camber max, camber percentages,
175- thickness max and thickness percentage) starting from the
176- xup, yup, xdown, ydown coordinates of the section.
177- Useful for parametrization and deformation.
190+ Rescale coordinates of upper and lower profiles of section such that
191+ coordinates on x axis are between 0 and 1.
178192 '''
179- self .chord_percentage = self .xup_coordinates
193+ factor = abs (self .xup_coordinates [- 1 ]- self .xup_coordinates [0 ])
194+ self .yup_coordinates *= 1 / factor
195+ self .xdown_coordinates *= 1 / factor
196+ self .ydown_coordinates *= 1 / factor
197+ self .xup_coordinates *= 1 / factor
198+
199+ def generate_parameters (self ):
200+ '''
201+ Method that generates the parameters of a general airfoil profile
202+ (chord length, chord percentages, camber max, thickness max, camber and
203+ thickness percentages), starting from the upper and lower
204+ coordinates of the section profile.
205+ '''
206+ n_pos = self .xup_coordinates .shape [0 ]
207+
208+ self .chord_len = abs (np .max (self .xup_coordinates )-
209+ np .min (self .xup_coordinates ))
210+ self .chord_percentage = self .xup_coordinates / self .chord_len
211+ camber = (self .yup_coordinates + self .ydown_coordinates )/ 2
212+ self .camber_max = abs (np .max (camber )- np .min (camber ))
213+ self .camber_percentage = camber / self .camber_max
214+
215+ n_pos = self .chord_percentage .shape [0 ]
216+ m = np .zeros (n_pos )
217+ for i in range (1 , n_pos , 1 ):
218+ m [i ] = (self .camber_percentage [i ]-
219+ self .camber_percentage [i - 1 ])/ (self .chord_percentage [i ]-
220+ self .chord_percentage [i - 1 ])* self .camber_max / self .chord_len
221+ m_angle = np .arctan (m )
180222
181- camber = (self .yup_coordinates +
182- self .ydown_coordinates ) / (2 * self .chord_len )
183- self .camber_max = np .max (camber )
184- self .camber_percentage = camber / self .camber_max
223+ from scipy .optimize import newton
185224
186- delta_camber = np .zeros (len (camber ))
187- delta_x = np .zeros (len (camber ))
188- m_rad = np .zeros (len (camber ))
189- for i in range (1 , len (camber ), 1 ):
190- delta_camber [
191- i ] = self .camber_percentage [i ] - self .camber_percentage [i - 1 ]
192- delta_x [i ] = self .chord_percentage [i ] - self .chord_percentage [i - 1 ]
225+ ind_horizontal_camber = (np .sin (m_angle )== 0 )
226+ def eq_to_solve (x ):
227+ spline_curve = self .ydown_curve (x .reshape (- 1 ,1 )).reshape (x .shape [0 ],)
228+ line_orth_camber = (camber [~ ind_horizontal_camber ] +
229+ np .cos (m_angle [~ ind_horizontal_camber ])/
230+ np .sin (m_angle [~ ind_horizontal_camber ])* (self .chord_len *
231+ self .chord_percentage [~ ind_horizontal_camber ]- x ))
232+ return spline_curve - line_orth_camber
193233
194- m_rad [1 :] = delta_camber [1 :] * np .pi / (delta_x [1 :] * 180 )
234+ xdown_tmp = self .xdown_coordinates .copy ()
235+ xdown_tmp [~ ind_horizontal_camber ] = newton (eq_to_solve ,
236+ xdown_tmp [~ ind_horizontal_camber ])
237+ xup_tmp = 2 * self .chord_len * self .chord_percentage - xdown_tmp
238+ ydown_tmp = self .ydown_curve (xdown_tmp .reshape (- 1 ,1 )).reshape (xdown_tmp .shape [0 ],)
239+ yup_tmp = 2 * self .camber_max * self .camber_percentage - ydown_tmp
240+ if xup_tmp [1 ]< self .xup_coordinates [0 ]:
241+ xup_tmp [1 ], xdown_tmp [1 ] = xup_tmp [2 ], xdown_tmp [2 ]
242+ yup_tmp [1 ], ydown_tmp [1 ] = yup_tmp [2 ], ydown_tmp [2 ]
195243
196- thickness = (self .yup_coordinates - self .ydown_coordinates ) / (
197- 2 * self .chord_len * np .cos (m_rad ))
244+ thickness = np .sqrt ((xup_tmp - xdown_tmp )** 2 + (yup_tmp - ydown_tmp )** 2 )
198245 self .thickness_max = np .max (thickness )
199- self .thickness_percentage = thickness / self .thickness_max
246+ self .thickness_percentage = thickness / self .thickness_max
200247
201248 def _check_coordinates (self ):
202249 """
@@ -255,7 +302,7 @@ def _check_coordinates(self):
255302
256303 if not self .xdown_coordinates [0 ] == self .xup_coordinates [0 ]:
257304 raise ValueError ('(xdown[0]=xup[0]) not satisfied.' )
258- if not self .ydown_coordinates [0 ] == self .yup_coordinates [0 ]:
305+ if not np . allclose ( self .ydown_coordinates [0 ], self .yup_coordinates [0 ]) :
259306 raise ValueError ('(ydown[0]=yup[0]) not satisfied.' )
260307 if not self .xdown_coordinates [- 1 ] == self .xup_coordinates [- 1 ]:
261308 raise ValueError ('(xdown[-1]=xup[-1]) not satisfied.' )
0 commit comments