@@ -17,7 +17,7 @@ class CustomProfile(ProfileBase):
1717 i.e. , chord percentages and length, nondimensional and maximum camber,
1818 nondimensional and maximum thickness.
1919
20- If coordinates are direclty given as input:
20+ If coordinates are directly given as input:
2121
2222 :param numpy.ndarray xup: 1D array that contains the X-components of the
2323 airfoil's upper surface
@@ -27,6 +27,7 @@ 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
3031
3132 If section parameters are given as input:
3233
@@ -38,74 +39,76 @@ class CustomProfile(ProfileBase):
3839 :param numpy.ndarray thickness_perc: 1D array that contains the thickness
3940 percentage of an airfoil section at all considered chord percentages.
4041 The percentage is with respect to the section maximum thickness
41- :param numpy.ndarray chord_len: 1-element array expressing the length of the chord
42- line of a certain airfoil section
43- :param numpy.ndarray camber_max: 1-element array expressing the maximum camber at a
44- certain airfoil section
45- :param numpy.ndarray thickness_max: 1-element array expressing the maximum thickness
46- at a certain airfoil section
42+ :param float chord_len: the length of the chord line of a certain airfoil section
43+ :param float camber_max: the maximum camber at a certain airfoil section
44+ :param float thickness_max: the maximum thickness at a certain airfoil section
4745 """
48-
4946 def __init__ (self , ** kwargs ):
5047 super (CustomProfile , self ).__init__ ()
51-
52- if len (kwargs ) == 4 :
53- xup , yup , xdown , ydown = kwargs .values ()
54- self .xup_coordinates = xup
55- self .yup_coordinates = yup
56- self .xdown_coordinates = xdown
57- self .ydown_coordinates = ydown
58-
59- if len (kwargs ) == 6 :
60- chord_perc , camber_perc , thickness_perc , chord_len , camber_max , thickness_max = kwargs .values ()
61- self .chord_percentage = chord_perc
62- self .camber_percentage = camber_perc
63- self .thickness_percentage = thickness_perc
64- self .chord_len = chord_len
65- self .camber_max = camber_max
66- self .thickness_max = thickness_max
67-
68- self ._check_args ()
48+
49+ if all ([key in ['xup' , 'yup' , 'xdown' , 'ydown' ] for key in kwargs ]):
50+ self .xup_coordinates = kwargs ['xup' ]
51+ self .yup_coordinates = kwargs ['yup' ]
52+ self .xdown_coordinates = kwargs ['xdown' ]
53+ self .ydown_coordinates = kwargs ['ydown' ]
54+ self .chord_len = kwargs .get ('chord_len' , 1 )
55+
56+ self ._generate_parameters ()
57+
58+ elif set (kwargs .keys ()) == set ([
59+ 'chord_perc' , 'camber_perc' , 'thickness_perc' , 'chord_len' ,
60+ 'camber_max' , 'thickness_max'
61+ ]):
62+
63+ self .chord_percentage = kwargs ['chord_perc' ]
64+ self .camber_percentage = kwargs ['camber_perc' ]
65+ self .thickness_percentage = kwargs ['thickness_perc' ]
66+ self .chord_len = kwargs ['chord_len' ]
67+ self .camber_max = kwargs ['camber_max' ]
68+ self .thickness_max = kwargs ['thickness_max' ]
69+
6970 self ._generate_coordinates ()
70-
71+ else :
72+ raise RuntimeError (
73+ """Input arguments should be the section coordinates
74+ (xup, yup, xdown, ydown) and chord_len (optional)
75+ or the section parameters (camber_perc, thickness_perc,
76+ camber_max, thickness_max, chord_perc, chord_len).""" )
77+
78+ self ._check_args ()
7179 self ._check_coordinates ()
72-
73-
80+
7481 def _check_args (self ):
7582 """
7683 Private method that checks whether the airfoil parameters defined
7784 are provided correctly.
7885 In particular, the chord, camber and thickness percentages are consistent and
7986 have the same length.
8087 """
81-
82-
88+
8389 if self .chord_percentage is None :
84- raise ValueError (
85- 'object "chord_perc" refers to an empty array.' )
90+ raise ValueError ('object "chord_perc" refers to an empty array.' )
8691 if self .camber_percentage is None :
87- raise ValueError (
88- 'object "camber_perc" refers to an empty array.' )
92+ raise ValueError ('object "camber_perc" refers to an empty array.' )
8993 if self .thickness_percentage is None :
9094 raise ValueError (
9195 'object "thickness_perc" refers to an empty array.' )
9296 if self .chord_len is None :
93- raise ValueError (
94- 'object "chorf_len" refers to an empty array.' )
97+ raise ValueError ('object "chorf_len" refers to an empty array.' )
9598 if self .camber_max is None :
96- raise ValueError (
97- 'object "camber_max" refers to an empty array.' )
99+ raise ValueError ('object "camber_max" refers to an empty array.' )
98100 if self .thickness_max is None :
99- raise ValueError (
100- 'object "thickness_max" refers to an empty array.' )
101-
101+ raise ValueError ('object "thickness_max" refers to an empty array.' )
102102
103103 if not isinstance (self .chord_percentage , np .ndarray ):
104- self .chord_percentage = np .asarray (self .chord_percentage , dtype = float )
104+ self .chord_percentage = np .asarray (self .chord_percentage ,
105+ dtype = float )
105106 if not isinstance (self .camber_percentage , np .ndarray ):
106- self .camber_percentage = np .asarray (self .camber_percentage , dtype = float )
107+ self .camber_percentage = np .asarray (self .camber_percentage ,
108+ dtype = float )
107109 if not isinstance (self .thickness_percentage , np .ndarray ):
108- self .thickness_percentage = np .asarray (self .thickness_percentage , dtype = float )
110+ self .thickness_percentage = np .asarray (self .thickness_percentage ,
111+ dtype = float )
109112 if not isinstance (self .chord_len , np .ndarray ):
110113 self .chord_len = np .asarray (self .chord_len , dtype = float )
111114 if not isinstance (self .camber_max , np .ndarray ):
@@ -120,47 +123,81 @@ def _check_args(self):
120123 raise ValueError ('thickness_max must be positive.' )
121124
122125 # Therefore the arrays camber_percentage and thickness_percentage
123- # should have the same length of chord_percentage, equal to n_pos,
126+ # should have the same length of chord_percentage, equal to n_pos,
124127 # which is the number of cuts along the chord line
125128 if self .camber_percentage .shape != self .chord_percentage .shape :
126- raise ValueError (
127- 'camber_perc and chord_perc must have same shape.' )
129+ raise ValueError ('camber_perc and chord_perc must have same shape.' )
128130 if self .thickness_percentage .shape != self .chord_percentage .shape :
129131 raise ValueError (
130132 'thickness_perc and chord_perc must have same shape.' )
131-
132-
133+
133134 def _generate_coordinates (self ):
134135 """
135136 Private method that generates the coordinates of a general airfoil profile,
136137 starting from the chord percantages and the related nondimensional
137138 camber and thickness. input data should be integrated with the information
138139 of chord length, camber and thickness of specific sections.
139140 """
140-
141+
141142 # compute the angular coefficient of the camber line at each chord
142143 # percentage and convert it from degrees to radiant
143144 n_pos = len (self .chord_percentage )
144145 m = np .zeros (n_pos )
145146 m [0 ] = 0
146- for i in range (1 ,len (self .chord_percentage ),1 ):
147- m [i ] = (self .camber_percentage [i ]- self .camber_percentage [i - 1 ])/ (self .chord_percentage [i ]- self .chord_percentage [i - 1 ])
148-
149- m_angle = m * np .pi / 180
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
150153 self .xup_coordinates = np .zeros (n_pos )
151154 self .xdown_coordinates = np .zeros (n_pos )
152155 self .yup_coordinates = np .zeros (n_pos )
153156 self .ydown_coordinates = np .zeros (n_pos )
154-
157+
155158 #compute the coordinates starting from a single section data and parameters
156- for j in range (0 ,n_pos ,1 ):
157- self .xup_coordinates [j ] = (self .chord_percentage [j ]- self .thickness_percentage [j ]* np .sin (m_angle [j ])* self .thickness_max )
158- self .xdown_coordinates [j ] = (self .chord_percentage [j ]+ self .thickness_percentage [j ]* np .sin (m_angle [j ])* self .thickness_max )
159- self .yup_coordinates [j ] = (self .camber_percentage [j ]* self .camber_max + self .thickness_percentage [j ]* self .thickness_max * np .cos (m_angle [j ]))* self .chord_len
160- self .ydown_coordinates [j ] = (self .camber_percentage [j ]* self .camber_max - self .thickness_percentage [j ]* self .thickness_max * np .cos (m_angle [j ]))* self .chord_len
161-
162-
163-
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+ '''
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.
178+ '''
179+ self .chord_percentage = self .xup_coordinates
180+
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
185+
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 ]
193+
194+ m_rad [1 :] = delta_camber [1 :] * np .pi / (delta_x [1 :] * 180 )
195+
196+ thickness = (self .yup_coordinates - self .ydown_coordinates ) / (
197+ 2 * self .chord_len * np .cos (m_rad ))
198+ self .thickness_max = np .max (thickness )
199+ self .thickness_percentage = thickness / self .thickness_max
200+
164201 def _check_coordinates (self ):
165202 """
166203 Private method that checks whether the airfoil coordinates defined (or
@@ -183,53 +220,42 @@ def _check_coordinates(self):
183220 or xdown[-1] != xup[-1]
184221 """
185222 if self .xup_coordinates is None :
186- raise ValueError (
187- 'object "xup" refers to an empty array.' )
223+ raise ValueError ('object "xup" refers to an empty array.' )
188224 if self .xdown_coordinates is None :
189- raise ValueError (
190- 'object "xdown" refers to an empty array.' )
225+ raise ValueError ('object "xdown" refers to an empty array.' )
191226 if self .yup_coordinates is None :
192- raise ValueError (
193- 'object "yup" refers to an empty array.' )
227+ raise ValueError ('object "yup" refers to an empty array.' )
194228 if self .ydown_coordinates is None :
195- raise ValueError (
196- 'object "ydown" refers to an empty array.' )
229+ raise ValueError ('object "ydown" refers to an empty array.' )
197230
198231 if not isinstance (self .xup_coordinates , np .ndarray ):
199232 self .xup_coordinates = np .asarray (self .xup_coordinates , dtype = float )
200233 if not isinstance (self .xdown_coordinates , np .ndarray ):
201- self .xdown_coordinates = np .asarray (
202- self . xdown_coordinates , dtype = float )
234+ self .xdown_coordinates = np .asarray (self . xdown_coordinates ,
235+ dtype = float )
203236 if not isinstance (self .yup_coordinates , np .ndarray ):
204237 self .yup_coordinates = np .asarray (self .yup_coordinates , dtype = float )
205238 if not isinstance (self .ydown_coordinates , np .ndarray ):
206- self .ydown_coordinates = np .asarray (
207- self . ydown_coordinates , dtype = float )
239+ self .ydown_coordinates = np .asarray (self . ydown_coordinates ,
240+ dtype = float )
208241
209242 # Therefore the arrays xup_coordinates and yup_coordinates must have
210243 # the same length = N, same holds for the arrays xdown_coordinates
211244 # and ydown_coordinates.
212245 if self .xup_coordinates .shape != self .yup_coordinates .shape :
213- raise ValueError (
214- 'xup and yup must have same shape.' )
246+ raise ValueError ('xup and yup must have same shape.' )
215247 if self .xdown_coordinates .shape != self .ydown_coordinates .shape :
216- raise ValueError (
217- 'xdown and ydown must have same shape.' )
248+ raise ValueError ('xdown and ydown must have same shape.' )
218249
219250 # The condition yup_coordinates >= ydown_coordinates must be satisfied
220251 # element-wise to the whole elements in the mentioned arrays.
221252 if not all (
222253 np .greater_equal (self .yup_coordinates , self .ydown_coordinates )):
223- raise ValueError (
224- 'yup is not >= ydown elementwise.' )
254+ raise ValueError ('yup is not >= ydown elementwise.' )
225255
226256 if not self .xdown_coordinates [0 ] == self .xup_coordinates [0 ]:
227- raise ValueError (
228- '(xdown[0]=xup[0]) not satisfied.' )
257+ raise ValueError ('(xdown[0]=xup[0]) not satisfied.' )
229258 if not self .ydown_coordinates [0 ] == self .yup_coordinates [0 ]:
230- raise ValueError (
231- '(ydown[0]=yup[0]) not satisfied.' )
259+ raise ValueError ('(ydown[0]=yup[0]) not satisfied.' )
232260 if not self .xdown_coordinates [- 1 ] == self .xup_coordinates [- 1 ]:
233- raise ValueError (
234- '(xdown[-1]=xup[-1]) not satisfied.' )
235-
261+ raise ValueError ('(xdown[-1]=xup[-1]) not satisfied.' )
0 commit comments