1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """The blade definition. The blade is first described in an
24 aerodynamics context for the BEM calculations.
25 The final L{Blade} class will also have MBDyn nodes and
26 elements as attributes. The aerodynamics calculations
27 are in fact done by C{Sections} described in
28 L{windSimSuite.sections} while the coupling with MBDyn is assured
29 by the C{BemNode} class from L{windSimSuite.bem_node}.
30 """
31 import numpy as N
32 from windSimSuite.common import ObjectWithNodesAndElems, Angle
33 from windSimSuite.references import REFERENCE_FRAME, ReferenceFrames
34 from windSimSuite.sections import Section
35 from windSimSuite.bem_node import BemNode
36
37
39 """The Blade Element Momentum part concerning the blade
40 """
41
43 self.angle = {"wing" : Angle(0., "deg"),
44 "cone" : Angle(0., "deg"),
45 "pitch" : Angle(0., "deg")}
46 self.offset_wing_angle = Angle(0., "deg")
47 self.references = ReferenceFrames()
48 self._add_reference_frames()
49
50 self.radius = None
51 self.correction_radius = None
52 self.skew_section = None
53
54
55 self.sections = []
56 self.nb_sections = 0.
57
58 self.tangential_force = 0.
59 self.normal_force = 0.
60 self.torque = 0.
61 self.power = 0.
62
63 self.force = {}
64 self.normal_vector_wake = N.array([ [1.],
65 [0.],
66 [0.] ])
67
76
78 """Set the blade radius"""
79 self.radius = value
80
82 """Set the wind angle for the blade and recalculate
83 the corresponding reference frame.
84
85 @type rotor_wing: a L{Angle} instance
86 @param rotor_wing: the rotor wing angle
87
88 @type unit_key: a string
89 @param unit_key: the 'rad' or 'deg' value"""
90 self.angle["wing"][unit_key] = rotor_wing[unit_key] + \
91 self.offset_wing_angle[unit_key]
92 self.references["wing"].calculate()
93
95 """Set the pitch angle for the blade and recalculate
96 the corresponding reference frame.
97
98 @type value: a float
99 @param value: the pitch value
100
101 @type unit_key: a string
102 @param unit_key: the 'rad' or 'deg' value"""
103 self.angle["pitch"][unit_key] = value
104 self.references["pitch"].calculate()
105
107 """Set the blade offset angle relative to the rotor.
108
109 @type value: a float
110 @param value: the offset value
111
112 @type unit_key: a string
113 @param unit_key: the 'rad' or 'deg' value"""
114 self.offset_wing_angle[unit_key] = value
115
117 """The correction radius when a problem occurs with
118 the Prandlt's correction"""
119 self.correction_radius = value
120
122 """Set the cone angle for the blade and recalculate
123 the corresponding reference frame.
124
125 @type value: a float
126 @param value: the cone value
127
128 @type unit_key: a string
129 @param unit_key: the 'rad' or 'deg' value"""
130 self.angle["cone"][unit_key] = value
131 self.references["cone"].calculate()
132
134 """Set the skew section, used for applying the yaw model
135 on the rotor."""
136 self.skew_section = self.sections[skew_section_id]
137
139 """Get the normal and tangential forces as well
140 as the torque and power applied on
141 the blade. The calculations are performed from the sections"""
142 for fkey in ["normal", "tangential"]:
143 self.force[fkey] = 0.
144 for section_id in range(self.nb_sections - 1):
145 sec2 = self.sections[section_id +1]
146 sec1 = self.sections[section_id]
147 delta_r = sec2.radial_position - sec1.radial_position
148 acoeff = (sec2.press[fkey] - sec1.press[fkey]) / delta_r
149 bcoeff = (sec1.press[fkey] * sec2.radial_position - \
150 sec2.press[fkey] * sec1.radial_position) / delta_r
151 square_delta_r = sec2.radial_position**2 - \
152 sec1.radial_position**2
153 self.force[fkey] += 0.5 * acoeff * square_delta_r \
154 + bcoeff * delta_r
155 self.normal_force = self.force["normal"]
156 self.tangential_force = self.force["tangential"]
157
158 self.torque = 0.
159 for section_id in range(self.nb_sections - 1):
160 sec2 = self.sections[section_id +1]
161 sec1 = self.sections[section_id]
162 delta_r = sec2.radial_position - sec1.radial_position
163 acoeff = (sec2.press[fkey] - sec1.press[fkey]) / delta_r
164 bcoeff = (sec1.press[fkey] * sec2.radial_position - \
165 sec2.press[fkey] * sec1.radial_position) / delta_r
166 square_delta_r = sec2.radial_position**2 - \
167 sec1.radial_position**2
168 threep_delta_r = sec2.radial_position**3 - \
169 sec1.radial_position**3
170 self.torque += 1./3. * acoeff * threep_delta_r + \
171 0.5 * bcoeff * square_delta_r
172 self.power = self.torque * rotational_speed["rad/s"]
173
175 """Return the coefficient of the yaw model. Used
176 to get the restoring moment when the turbine is yawed"""
177 scalar = N.dot(self.normal_vector_wake.transpose(),
178 self.skew_section.velocity_prime)
179 skew_angle = N.arccos(float(scalar) / \
180 self.skew_section.velocity_prime_norm)
181 return rotor.skew_radius_ratio * N.tan(skew_angle/2.) * \
182 N.cos(self.angle["wing"]["rad"] - rotor.deepest_pt_angle) + 1.
183
184
185 -class Blade(AerodynamicsBlade, ObjectWithNodesAndElems):
186 """The blade interface manipulated by the user.
187 """
188
190 AerodynamicsBlade.__init__(self)
191 self.idx = idx
192 ObjectWithNodesAndElems.__init__(self, "Blade %i" % idx)
193
194 self.sections = []
195 self.section_paras = []
196 self.section_radius = []
197 self.nb_sections = 0
198
199 self.bem_nodes = []
200 self.bem_node_paras = []
201 self.nb_bem_nodes = 0
202
203 self.save_pressures = False
204
205 self.own_para_names += ["idx", "section_paras",
206 "radius",
207 "correction_radius",
208 "angle",
209 "save_pressures",
210 "bem_node_paras"]
211
212 self.coupling_torque = 0.
213 self.will_save("tangential_force",
214 "normal_force",
215 "torque",
216 "power",
217 "coupling_torque")
218
220 """Add an aerodynamic section to the blade"""
221 self.sections.append(section)
222 self.section_radius.append(section.radial_position)
223 self.nb_sections += 1
224
226 """Add a bem node to the blade"""
227 self.bem_nodes.append(bem_node)
228 self.nb_bem_nodes += 1
229
231 """Will save the pressures on the blades"""
232 self.save_pressures = boolean
233
235 """Set the force range of actions, according
236 to the bem nodes present"""
237
238 if self.nb_bem_nodes == 0:
239 return
240
241 elif self.nb_bem_nodes == 1:
242 self.bem_nodes[0].create_force()
243 self.bem_nodes[0].create_force_areas_from(self.sections)
244
245 elif self.nb_bem_nodes == 2:
246 one = self.bem_nodes[0]
247 two = self.bem_nodes[1]
248 rsep = (two.radial_position - one.radial_position) / 2. \
249 + one.radial_position
250
251 one.set_lower_radius(0.)
252 one.set_upper_radius(rsep)
253
254 two.set_lower_radius(rsep)
255 two.set_upper_radius(self.radius)
256
257 else:
258 for idx in range(1, self.nb_bem_nodes - 1):
259 lower = self.bem_nodes[idx - 1]
260 current = self.bem_nodes[idx]
261 upper = self.bem_nodes[idx + 1]
262
263 current.set_boundaries(lower.radial_position,
264 upper.radial_position)
265
266
267 first = self.bem_nodes[0]
268 first.set_boundaries(0.,
269 self.bem_nodes[1].lower_radius)
270
271 last = self.bem_nodes[-1]
272 last.set_boundaries(self.bem_nodes[-2].upper_radius,
273 self.radius)
274
275 section_radius = N.array(self.section_radius)
276 for bem_node in self.bem_nodes:
277 bem_node.find_sections(section_radius,
278 self.sections)
279
281 """Init the coupling between MBDyn and the unsteady BEM
282 code, as well as the result objects."""
283 if self.nb_sections > 0:
284 self._set_force_ranges()
285
287 """Initialize the results objects for the
288 parameters that will be saved."""
289 self.common_init_results()
290 for section in self.sections:
291 if self.save_pressures:
292 section.will_save("tangential_press",
293 "normal_press")
294 section.init_results()
295
297 """Save the blade status"""
298 self.coupling_torque = 0.
299
300
301
302
303 self.save_results()
304 for section in self.sections:
305 section.save()
306 for bem_node in self.bem_nodes:
307 bem_node.save()
308
321
339