1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """The aerodynamics sections definition for the unsteady BEM code.
23 Each section will also need an L{Airfoil} definition that will
24 be made of L{Curve} instances.
25 """
26 import math as M
27 import numpy as N
28
29 from windSimSuite.common import BasicObject, Angle, get_norm
30
31
33 """Made of a list of angles and a list of coefficients.
34 The coefficient type will be either lift or drag. But
35 for each type two different situations can exist:
36 fully attached or fully separated.
37 """
38
40 self.name = name
41 self.angles = []
42 self.coeffs = []
43
44 self.array_angle = None
45
47 """Return the angle instance"""
48 if isinstance(angle, Angle):
49 return angle
50 elif type(angle) == tuple and len(angle) == 2:
51 return Angle(angle[0], angle[1])
52 else:
53 return Angle(angle[0])
54
56 """Add a point to the curve. That is to say
57 an angle and its corresponding coefficient value"""
58 angle = self._get_angle_inst(angle)
59 self.angles.append(angle["rad"])
60 self.coeffs.append(coeff_value)
61
63 """Initialize the process for getting
64 back a coefficient from an angle."""
65 self.array_angle = N.asarray(self.angles)
66
68 """Return a coefficient from a given angle of attack.
69
70 @type attack_angle: a float
71 @param attack_angle: the angle in radians.
72 """
73 attack_indices = N.where(self.array_angle < attack_angle["rad"])
74 aid = int(attack_indices[0][-1])
75 slope = (self.coeffs[aid + 1] - self.coeffs[aid]) / \
76 (self.angles[aid + 1] - self.angles[aid])
77 angle_diff = attack_angle["rad"] - self.angles[aid]
78 return slope * angle_diff + self.coeffs[aid]
79
80
82 """An airfoil definition. It can be defined in two different manners.
83 In the first case, it can handle lift and drag coefficients according
84 to the angle of attack. In the second case, it can handle
85 lift fully attached, lift fully separated and drag coefficients
86 according to the angle of attack. The difference between the two
87 definitions will be set in the L{set_lift} method.
88 """
90 self.name = name
91 self.curves = {}
92 self.coeffs = {}
93
95 """Set the airfoil name"""
96 self.name = name
97
98 - def set_lift(self, lift_curve, separated_lift_curve=None):
99 """Set the lift L{Curve} instance(s)."""
100 if separated_lift_curve == None:
101 self.curves["lift"] = lift_curve
102 else:
103 self.curves["alift"] = lift_curve
104 self.curves["slift"] = separated_lift_curve
105
107 """Set the drag L{Curve} instance."""
108 self.curves["drag"] = drag_curve
109
111 """Initialize the curves"""
112 for curve in self.curves.values():
113 curve.init()
114
116 """Return a coefficient from the curve key and an angle of attack"""
117 return self.curves[key].get_coefficient_from(attack_angle)
118
119
121 """The section for the aerodynamics calculations
122 of the unsteady BEM code.
123 """
124
126 self.airfoil = Airfoil()
127 self.flow_angle = Angle(1., "rad")
128 self.attack_angle = Angle(0., "deg")
129
130 self.radial_position = 0.
131 self.twist = None
132 self.chord = None
133
134
135
136
137 self.induced_velocity = {}
138 self.old_induced_velocity = {}
139 for key in ["cu", "qs"]:
140 self.induced_velocity[key] = N.zeros((3, 1))
141 for key in ["cu", "int", "qs"]:
142 self.old_induced_velocity[key] = N.zeros((3, 1))
143
144 self.rvector = N.zeros((3, 1))
145 self.wind_speed = N.zeros((3, 1))
146 self.velocity_prime = N.zeros((3, 1))
147 self.velocity_prime_norm = 0.
148 self.relative_velocity = N.zeros((3, 1))
149
150 self.prandtl_factor = 1.
151 self.lift = 1.
152
153
154 self.stall_coeff = {"lift" : 0.}
155 self.old_attached_flow = 1.
156 self.first_run = True
157
158 self.press = {"tangential" : 0., "normal" : 0.}
159 self.tangential_press = 0.
160 self.normal_press = 0.
161
162 self.rotor_plane_normal = N.array([ [-1.],
163 [0.],
164 [0.] ])
165 self._get_coefficients = self._get_direct_coefficients
166
168 """Set the radial position of the section along
169 the blade radius"""
170 self.radial_position = radial_position
171
173 """Set the chord of the section"""
174 self.chord = chord
175
177 """Set the twist angle for the section according
178 to the blade plane.
179
180 @type value: a float
181 @param value: the twist value
182
183 @type unit: a string
184 @param unit: the 'rad' or 'deg' value"""
185
186 self.twist = Angle(value, unit)
187
189 """Set the L{Airfoil} instance. In case the airfoil
190 has a fully attached and fully separated lift curves,
191 a dynamic stall model will be applied."""
192 if airfoil.curves.has_key("lift"):
193 self._get_coefficients = self._get_direct_coefficients
194 else:
195 self._get_coefficients = self._get_dynamic_stall_coefficients
196 self.airfoil = airfoil
197
199 """Return the lift and drag coefficient"""
200 return self.airfoil.get_coefficient("lift", self.attack_angle), \
201 self.airfoil.get_coefficient("drag", self.attack_angle)
202
205 """Apply the dynamic stall model"""
206
207 return attached_coeff
208 time_constant = 4. * self.chord / \
209 get_norm(self.relative_velocity)
210 if attached_coeff == separated_coeff or self.first_run:
211 attached_flow_st = 1.
212 self.first_run = False
213 else:
214 attached_flow_st = (self.stall_coeff[key] - separated_coeff) / \
215 (attached_coeff - separated_coeff)
216 attached_flow = attached_flow_st + \
217 (self.old_attached_flow - attached_flow_st) * \
218 M.exp(-time_step / time_constant)
219 coeff = attached_flow * attached_coeff + \
220 (1. - attached_flow) * separated_coeff
221
222 self.old_attached_flow = attached_flow
223 self.stall_coeff[key] = coeff
224 return coeff
225
227 """Return the lift and drag coefficients by applying
228 a dynamic stall model"""
229 acoeff = self.airfoil.get_coefficient("alift", self.attack_angle)
230 scoeff = self.airfoil.get_coefficient("slift", self.attack_angle)
231 lift_coeff = self._apply_dynamic_stall("lift", acoeff,
232 scoeff, time_step)
233 drag_coeff = self.airfoil.get_coefficient("drag", self.attack_angle)
234 return lift_coeff, drag_coeff
235
240
242 """Calculate the section vector"""
243 self.rvector = N.array([ [0.],
244 [0.],
245 [self.radial_position] ])
246
249 """Calculate the relative velocity from the velocity triangle"""
250 rotational_vel = N.array([ [0.],
251 [self.radial_position * \
252 rotational_speed["rad/s"] * \
253 N.cos(cone_angle["rad"])],
254 [0.] ])
255 pitch_rotational_vel = N.asarray(pitch_matrix * rotational_vel)
256 self.relative_velocity = self.wind_speed + pitch_rotational_vel + \
257 self.induced_velocity["cu"]
258
260 """Calculate the aerodynamics pressures (in M{N/m}) at the
261 section"""
262 rvelocity_norm = get_norm(self.relative_velocity)
263 self.flow_angle["rad"] = float(N.arctan(self.relative_velocity[0] / \
264 self.relative_velocity[1]))
265 self.attack_angle["rad"] = self.flow_angle["rad"] - self.twist["rad"]
266
267 lift_coeff, drag_coeff = self._get_coefficients(time_step)
268 press = 0.5 * air_density * rvelocity_norm**2 * self.chord
269 self.lift = press * lift_coeff
270 drag = press * drag_coeff
271 flow_sin = M.sin(self.flow_angle["rad"] + pitch["rad"])
272 flow_cos = M.cos(self.flow_angle["rad"] + pitch["rad"])
273 self.press["tangential"] = self.lift * flow_sin - drag * flow_cos
274 self.press["normal"] = self.lift * flow_cos + drag * flow_sin
275 self.tangential_press = self.press["tangential"]
276 self.normal_press = self.press["normal"]
277
280 """Calculate the quasi static induced velocity, without taking
281 into account the dynamic wake"""
282 self.old_induced_velocity["qs"] = self.induced_velocity["qs"].copy()
283 num = - nb_blades * self.lift
284 dem = 4. * air_density * N.pi * \
285 self.radial_position * prandlt_factor
286 scalar = N.dot(self.rotor_plane_normal.transpose(),
287 self.induced_velocity["cu"])
288 self.velocity_prime = self.wind_speed + \
289 scalar * self.rotor_plane_normal
290 self.velocity_prime_norm = get_norm(self.velocity_prime)
291 self.induced_velocity["qs"][0] = num * N.cos(self.flow_angle["rad"]) \
292 / (dem * self.velocity_prime_norm)
293 self.induced_velocity["qs"][1] = -num * N.sin(self.flow_angle["rad"]) \
294 / (dem * self.velocity_prime_norm)
295
296 - def disable_dynamic_wake(self, induction_factor_max, adjust_factor,
297 wind_speed, time_step, blade_radius):
298 """Disable the dynamic wake by giving the reference of
299 the quasi static induced velocity to the current induced
300 velocity"""
301 for key in ["cu", "int", "qs"]:
302 self.old_induced_velocity[key] = self.induced_velocity["qs"].copy()
303 self.induced_velocity["cu"] = self.induced_velocity["qs"].copy()
304
305 - def apply_dynamic_wake(self, induction_factor_max, adjust_factor,
306 wind_speed, time_step, blade_radius):
307 """Calculate the current induced velocity by applying the two filters
308 of the dynamic wake"""
309 self.old_induced_velocity["cu"] = self.induced_velocity["cu"].copy()
310 norm_wake_prime_velocity = get_norm(self.wind_speed + \
311 self.induced_velocity["cu"])
312 norm_wake_prime_velocity = self.velocity_prime_norm
313 induction_factor = 1. - norm_wake_prime_velocity / wind_speed
314 if induction_factor > induction_factor_max:
315 induction_factor = induction_factor_max
316 time_cst1 = 1.1 * blade_radius / \
317 ((1. - 1.3 * induction_factor) * wind_speed)
318 time_cst2 = (0.39 - 0.26 * (self.radial_position/blade_radius)**2) \
319 * time_cst1
320
321
322 cst = self.induced_velocity["qs"] + \
323 adjust_factor * time_cst1 / time_step * \
324 (self.induced_velocity["qs"] - self.old_induced_velocity["qs"])
325 int_induced_velocity = cst + M.exp(-time_step / time_cst1) * \
326 (self.old_induced_velocity["int"] - cst)
327
328
329 self.induced_velocity["cu"] = int_induced_velocity + \
330 M.exp(-time_step / time_cst2) * \
331 (self.old_induced_velocity["cu"] - \
332 int_induced_velocity)
333
334
335 self.old_induced_velocity["int"] = int_induced_velocity.copy()
336
337
338 -class Section(AerodynamicsSection, BasicObject):
339 """The section definition. This class gathers
340 the aerodynamics calculations and the saving tasks
341 for retrieving the section status in another process,
342 after calculations.
343 """
344
353
355 """Collect the section parameters into a dictionary"""
356 self.collect_own_parameters()
357
359 """Set the sections parameters from the C{para}
360 dictionary"""
361 self.set_own_parameters(para)
362