1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """The wind turbine and simulation definitions. This module
23 derived from the main module of the mbdyn package to provide
24 a simulation where a L{WindTurbine} can be added.
25 """
26 import numpy as N
27
28 from windSimSuite.common import BasicObject
29 from windSimSuite.rotor import Rotor
30 from windSimSuite.nacelle import Nacelle
31 from windSimSuite.tower import Tower
32
33 from mbdyn.main import Simulation as SimulationBase
34 from mbdyn.main import PyMBDynFile as PyMBDynFileBase
35
36
37 CLASS = {"rotor" : Rotor,
38 "nacelle" : Nacelle,
39 "tower" : Tower}
40
41
43 """The wind turbine manipulating all the others components
44 """
45
46 - def __init__(self, name="Wind turbine"):
47 BasicObject.__init__(self, name)
48 self.children_names = []
49
50 self.tower = None
51 self.has_tower = False
52 self.nacelle = None
53 self.has_nacelle = False
54 self.rotor = None
55 self.has_rotor = False
56 self.has_update = False
57
58 self.mbdyn_coupling = True
59 self.own_para_names = ["children_names", "has_tower",
60 "has_nacelle", "has_rotor"]
61
63 """Set the C{Tower} instance from L{windSimSuite.tower}"""
64 self.has_tower = True
65 self.tower = tower
66 self.children_names += ["tower"]
67
69 """Set the C{Rotor} instance from L{windSimSuite.rotor}"""
70 self.has_rotor = True
71 self.rotor = rotor
72 self.children_names += ["rotor"]
73
75 """Set the C{Nacelle} instance from L{windSimSuite.nacelle}"""
76 self.has_nacelle = True
77 self.nacelle = nacelle
78 self.children_names += ["nacelle"]
79
81 """If False, only the unsteady computation will be run"""
82 self.mbdyn_coupling = boolean
83
89
91 """Update the attributes stored on the wind turbine on
92 all the objects that belong to it.
93 For example the wing angle information, set on the rotor,
94 is now transmitted to the blades. The reference frames
95 used for the unsteady calculation are also updated in
96 case MBDyn is not used."""
97 if not self.has_update:
98 if not self.mbdyn_coupling:
99 self._update_reference_frames()
100 if self.has_rotor:
101 self.rotor.update_data()
102 self.has_update = True
103
105 """Init the coupling between MBDyn and the unsteady BEM code.
106 MBDyn objects are created by being attributes of wind turbine
107 components. According to their type, they are stored in C{refs},
108 C{nodes} or C{elts}.
109 Until now, this method is in development and is supposed to be
110 defined by a specific model. The reference frames, nodes and
111 elements defined will then be added on the simulation.
112 """
113 pass
114
116 """Add the wind turbine objects add all its components,
117 reference frames, nodes and elements on the simulation.
118 Each method will scan the C{refs}, C{nodes} and C{elts} list
119 attributes."""
120 for component_name in ["tower", "nacelle", "rotor"]:
121 if getattr(self, "has_" + component_name):
122 getattr(self, component_name).add_on_simulation(simu)
123
125 """Suppose to initialize all the wind turbine object
126 results. Until now, results are only collected on
127 the rotor."""
128 if self.has_rotor:
129 self.rotor.init_results()
130
132 """Supposed to udpate some of the wind turbine
133 parameters according to the MBDyn new state.
134 For the same reason as L{create}, this method is in
135 development and this is the model that is supposed
136 to supply that method."""
137 pass
138
140 """Store the wind turbine parameters in a dictionary"""
141 self.collect_own_parameters()
142 for children_name in self.children_names:
143 children = getattr(self, children_name)
144 children.collect_parameters()
145 self.para[children_name] = children.para
146
148 """Set the wind turbine parameters from the C{para} dictionary"""
149 self.set_own_parameters(para)
150 for children_name in self.children_names:
151 children = CLASS[children_name]()
152 children.set_parameters(para[children_name])
153 setattr(self, children_name, children)
154
155
157 """The unsteady Blade Element Momentum method
158 applied on the wind turbine from the simulation.
159 """
160
162
163 self.turbine = None
164 self.wind_speed = None
165 self.air_density = None
166 self.shear = 0.2
167
168 self.current_time = 0.
169 self.time_step = 0.
170 self.init_time = 1.
171 self.induction_fmax = 0.5
172 self.adjust_factor = 0.6
173
174 self.has_init_unsteady = False
175 self._try_enable_dynamic_wake = self._enable_dynamic_wake
176
178 """Set the time to run before starting
179 to save the results and applying the dynamic wake"""
180 self.init_time = init_time
181
183 """Set the wind spee value in M{m/s}"""
184 self.wind_speed = value
185
187 """Set the air density in M{kg/m^3}."""
188 self.air_density = value
189
191 """Set the shear value for the wind profile.
192 The default is 0.2, will depend of the site roughness"""
193 self.shear = value
194
196 """Return the velocity at a particular height from
197 the wind profile"""
198 if height <= self.turbine.tower.aero_height:
199 return self.wind_speed * \
200 (height/self.turbine.tower.aero_height)**self.shear
201 else:
202 return self.wind_speed
203
205 """Return the velocity for a point in the absolute
206 referential. The wind tower model is applied.
207
208 @type pt_array: a Numpy array
209 @param pt_array: a position vector of shape 3x1
210
211 In the referential used, C{pt_array[2]}
212 will be the height along the M{z} axis.
213
214 @rtype: a Numpy array
215 @return: the wind vector of size 3x1
216 """
217 xval = pt_array[0][0]
218 yval = pt_array[1][0]
219 height = pt_array[2][0]
220
221 wind_speed = self.get_velocity_at_height(height)
222
223 if height < self.turbine.tower.aero_height:
224 tower_radius = self.turbine.tower.get_radius_at(height)
225 pt_radius = N.sqrt(xval**2 + yval**2)
226 cos_theta = xval / pt_radius
227 sin_theta = yval / pt_radius
228 radius_velocity = wind_speed * \
229 (1. - (tower_radius/pt_radius)**2)*cos_theta
230 theta_velocity = - wind_speed * \
231 (1. + (tower_radius/pt_radius)**2)*sin_theta
232 x_velocity = radius_velocity * cos_theta - \
233 theta_velocity * sin_theta
234 y_velocity = radius_velocity * sin_theta + \
235 theta_velocity * cos_theta
236
237
238 return N.array([ [x_velocity],
239 [y_velocity],
240 [0.] ])
241 else:
242 return N.array([ [wind_speed],
243 [0.],
244 [0.] ])
245
253
255 """The method executed for an absent dynamic wake"""
256 pass
257
259 """If C{True}, enable the dynamic wake on each aerodynamic
260 section. A private method, the user needs to use
261 L{enable_dynamic_wake}."""
262 for blade in self.turbine.rotor.blades:
263 for section in blade.sections:
264 if enable:
265 section.calculate_induced_velocity = \
266 section.apply_dynamic_wake
267 else:
268 section.calculate_induced_velocity = \
269 section.disable_dynamic_wake
270
272 """Initialize the aerodynamics calculation of the unsteady
273 BEM code"""
274 self.turbine.rotor.init_unsteady()
275 init_time_list = N.arange(0.,
276 self.init_time + self.time_step,
277 self.time_step)
278 self._try_enable_dynamic_wake(False)
279 start_angle = self.turbine.rotor.wing_angle["rad"]
280
281 for time in init_time_list:
282 self.run_unsteady_at_time(time)
283
284 self.turbine.rotor.set_wing_angle(start_angle, "rad")
285 self._try_enable_dynamic_wake(True)
286 self.current_time = -self.time_step
287 self.has_init_unsteady = True
288
290 """Will be defined by the derived class"""
291 pass
292
294 """Will be defined by the derived class"""
295 pass
296
298 """Run the unsteady Blade Element Momentum code
299 for the C{time_lenght}. C{nb_pts} is an integer that
300 gives the number of calculation points for one revolution.
301 This method is used only when there is no coupling with MBDyn,
302 this is else the MBDyn solver that gives the current time."""
303 revolution_period = 2*N.pi / \
304 self.turbine.rotor.rotational_speed["rad/s"]
305 self.time_step = revolution_period / nb_pts
306 if not self.has_init_unsteady:
307 self.init_unsteady()
308 final_time = time_length + self.current_time
309 time_list = N.arange(self.current_time + self.time_step,
310 final_time + self.time_step,
311 self.time_step)
312
313
314
315 getattr(self, "init_results")()
316 self.turbine.init_results()
317
318 for time in time_list:
319 self.run_unsteady_at_time(time)
320 self.save_unsteady_state_at(time)
321 self.current_time = time
322
324 """Run the unsteady BEM code calculations on the whole
325 turbine at a given time. At the end, nothing is saved"""
326 rotor = self.turbine.rotor
327 rotor.set_wing_angle_at_time(time)
328 vec_to_shaft = self.turbine.tower.get_abs_vector() + \
329 self.turbine.nacelle.get_abs_vector()
330 for blade in rotor.blades:
331 for section in blade.sections:
332 matrix = blade.references.get_matrix_from("pitch")
333 abs_rvector = N.asarray(vec_to_shaft + \
334 matrix * section.rvector)
335 abs_velocity = self.get_velocity_at(abs_rvector)
336 section.wind_speed = matrix.T * abs_velocity
337 section.calculate_relative_velocity(
338 rotor.rotational_speed,
339 blade.angle["cone"],
340 blade.references["pitch"].matrix
341 )
342 section.calculate_pressures(self.air_density,
343 self.time_step,
344 blade.angle["pitch"])
345 prandtlf = rotor.get_prandtl_factor(blade, section)
346 section.calculate_qs_induced_velocity(prandtlf,
347 self.air_density,
348 rotor.nb_blades)
349 section.calculate_induced_velocity(self.induction_fmax,
350 self.adjust_factor,
351 self.wind_speed,
352 self.time_step,
353 blade.radius)
354 blade.compute_forces_and_power(rotor.rotational_speed)
355 rotor.update_induced_velocity()
356 rotor.get_forces_and_power()
357
359 """Save the unsteady BEM code on the rotor."""
360 self.turbine.rotor.save()
361
362
363 -class Simulation(AerodynamicsSimulation, SimulationBase):
364 """A specific simulation for the wind turbine.
365 The gathering between the aerodynamics calculation and
366 MBDyn.
367 """
368
369 - def __init__(self, name="mbdyn simu", title="Mbdyn used from Python",
370 keep=False, overwrite=False, mbdyn_coupling=True):
371 AerodynamicsSimulation.__init__(self)
372 SimulationBase.__init__(self, name, title, keep, overwrite)
373 self.mbdyn_coupling = mbdyn_coupling
374
375 self.has_turbine = False
376 self.turbine_para = None
377
378 self.children_names = ["references"]
379 self.own_para_names += ["has_turbine",
380 "children_names",
381 "turbine_para",
382 "wind_speed",
383 "air_density"]
384
386 """Set the L{WindTurbine} instance"""
387 turbine.set_mbdyn_coupling(self.mbdyn_coupling)
388 turbine.update_data()
389
390 self.turbine = turbine
391 self.turbine_para = turbine.para
392 self.children_names += ["turbine"]
393 self.has_turbine = True
394
396 """Init the unsteady BEM code by running it
397 for the 'init_time'"""
398 if self.mbdyn_coupling:
399
400
401 self.time_step = 0.027
402
403
404 self.init_aero_unsteady()
405
407 """Initialize the simulation according to WraptMBDyn.
408 The Python object get references to the object from
409 the bindings module, interfacing the C++
410 ones. This method is called by the C{run_full} method
411 of the C{SimulationBase}. It also overwrites the C{init}
412 of the C{SimulationBase}."""
413 if not self.has_init:
414 self._init_coupling()
415 self.turbine.add_on_simulation(self)
416 self._init_wrapt_mbdyn()
417 self._init_wind_simulation()
418 self.has_init = True
419 else:
420 print "'init()' already called on the Simulation"
421
423 """Init the unsteady code and stabilize the aerodynamics loads.
424 Then create the wind turbine with MBDyn objects, that take into
425 account the ones from the unsteady BEM code.
426 Then run the MBDyn simulation, first L{init} will be called.
427 Then L{save} and L{update} are called at each MBDyn time step."""
428 self.init_unsteady()
429 self.turbine.create()
430
432 """The Python objects are getting a reference of the C++ ones.
433 The results are also initialized."""
434 self.turbine.init_results()
435
436 self.init_simulation()
437
439 """Run the full simulation"""
440 self.run_full()
441
443 """Update the wind turbine simulation.
444 Called by the C{run_full()} method at each MBDyn iteration.
445 This method is doing the coupling between MBDyn and
446 the unsteady BEM code. First the state of the wind turbine
447 is updated according to the MBDyn objects that are attached
448 to it. Then the unsteady BEM code is run from this new
449 configuration. Finally the MBDyn elements are updated."""
450 self.turbine.update()
451 self.run_unsteady_at_time(self.wm.solver.current_time)
452 self.update_elts()
453
455 """Save the unsteady state at a particular time. This function
456 is used only when C{mbdyn_coupling} is set to C{False}. Else
457 the time is saved from the MBDyn simulation."""
458 self.time = time
459 self.save_results()
460 self.save_unsteady()
461
463 """The save method called by C{WraptMBDyn}. This method is called
464 before L{update}, because the values are saved just after a successful
465 time step. L{save_mbdyn} will also save the simulation
466 status (by calling the C{save_results} method."""
467 self.save_mbdyn()
468 self.save_unsteady()
469
471 """Collect the simulation parameters into a dictionary"""
472 self.collect_own_parameters()
473 self.refs.collect_parameters()
474 self.para["refs"] = self.refs.para
475 if self.has_turbine:
476 self.turbine.collect_parameters()
477
479 """Set the simulation parameters from the C{para} dictionary"""
480 self.set_own_parameters(para)
481 self.refs.set_parameters(para["refs"])
482 if self.has_turbine:
483 self.turbine = WindTurbine()
484 self.turbine.set_parameters(self.turbine_para)
485 self.turbine.add_on_simulation(self)
486
487
489 """The container to save a wind simulation.
490 The only difference with its base class is now to
491 save a L{Simulation} class suited to wind engineering.
492 """
493
494 - def __init__(self, name, mode="r", path=".", autoload=True):
495 PyMBDynFileBase.__init__(self, name, mode, path, autoload=False)
496 self.CLASS = Simulation
497 if (mode=="r") and autoload:
498 self.load()
499