1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """A blade element momentum node. The main class
23 is described in L{BemNode}.
24 """
25 import numpy as N
26
27 from mbdyn.nodes import StructuralNode
28 from mbdyn.elements import Body
29 from mbdyn.elts.force import BindingsForce
30
31 from windSimSuite.common import BasicObject
32
34 """Integrate the force between two sections according
35 to the force key, fkey, 'normal' or 'tangential'"""
36 delta_r = sec2.radial_position - sec1.radial_position
37 acoeff = (sec2.press[fkey] - sec1.press[fkey]) / delta_r
38 bcoeff = (sec1.press[fkey] * sec2.radial_position - \
39 sec2.press[fkey] * sec1.radial_position) / delta_r
40 square_delta_r = sec2.radial_position**2 - \
41 sec1.radial_position**2
42 return 0.5 * acoeff * square_delta_r + bcoeff * delta_r
43
44
46 """Return the torque contribution between
47 the two sections"""
48 delta_r = sec2.radial_position - sec1.radial_position
49 acoeff = (sec2.press[fkey] - sec1.press[fkey]) / delta_r
50 bcoeff = (sec1.press[fkey] * sec2.radial_position - \
51 sec2.press[fkey] * sec1.radial_position) / delta_r
52 square_delta_r = sec2.radial_position**2 - \
53 sec1.radial_position**2
54 threep_delta_r = sec2.radial_position**3 - \
55 sec1.radial_position**3
56 return 1./3. * acoeff * threep_delta_r + \
57 0.5 * bcoeff * square_delta_r
58
59
61 """The resulting force from 2 aerodynamics sections.
62 """
63
64 - def __init__(self, lower_section, upper_section):
65 self.lower_section = lower_section
66 self.upper_section = upper_section
67
69 """Return the force value according to the C{fkey},
70 normal or tangential."""
71 return get_force(fkey, self.lower_section, self.upper_section)
72
73
75 """A section only used for calculating the load in one area
76 according to its radial position and two others
77 aerodynamic sections."""
78
80 self.radial_position = radial_position
81 self.press = {"normal" : 0.,
82 "tangential" : 0.}
83
84 - def fill(self, fkey, sec1, sec2):
85 """Fill the pressure from the two sections according
86 to 'fkey'"""
87 slope = (sec2.press[fkey] - sec1.press[fkey]) / \
88 (sec2.radial_position - sec1.radial_position)
89 deltar = self.radial_position - sec1.radial_position
90 self.press[fkey] = slope * deltar + sec1.press[fkey]
91
92
94 """The top class for calculating the force area
95 at the boundaries of the L{BemNode} range of action
96 """
97
98 - def __init__(self, radius, lower_section, upper_section):
99 self.section = _CalculationSection(radius)
100 self.lower_section = lower_section
101 self.upper_section = upper_section
102
103
105 """The lower boundary of the L{BemNode} range of action,
106 closer to the root.
107 """
108
110 """Return the force contribution of the area"""
111 self.section.fill(fkey, self.lower_section, self.upper_section)
112 return get_force(fkey, self.section, self.upper_section)
113
114
116 """The upper boundary of the L{BemNode} range of action,
117 closer to the tip.
118 """
119
121 """Return the force contribution of the area"""
122 self.section.fill(fkey, self.lower_section, self.upper_section)
123 return get_force(fkey, self.lower_section, self.section)
124
125
127 """A custom bindings force that finds its value
128 from the aerodynamic sections, organised
129 into force areas.
130 """
131
133 BindingsForce.__init__(self, name)
134 self.areas = []
135 self.forces = {"tangential" : 0.,
136 "normal" : 0.}
137 self.torque = {}
138 self.tangential_force = self.forces["tangential"]
139 self.normal_force = self.forces["normal"]
140
141 self.xcontrib = 0.
142 self.ycontrib = 0.
143 self._load = N.zeros((3, 1))
144
145 self.will_save("tangential_force",
146 "normal_force")
147
148
150 """Init the results"""
151 self.common_init_results()
152
154 """Add a force area to the list"""
155 self.areas.append(area)
156
158 """Add the lower force area to the list"""
159 self.areas.insert(0, area)
160
162 """Add the upper force area to the list"""
163 self.areas.append(area)
164
166 """Initialize the loading for the MBDyn input file"""
167 self._get_loads()
168 norm = N.sqrt(self.tangential_force ** 2 + \
169 self.normal_force ** 2)
170 self.xcontrib = self.normal_force / norm
171 self.ycontrib = - self.tangential_force / norm
172
173 self.set_direction(self.xcontrib,
174 self.ycontrib,
175 0.)
176 self.set_amplitude(0.)
177
178
179
181 """Fill the C{_load} attribute.
182 The normal force is along M{x}, the tangential force
183 along M{y}."""
184 for key in ["normal", "tangential"]:
185
186 self.torque[key] = 0.
187 for area in self.areas:
188
189 self.torque[key] += area.get_force(key)
190 self.forces[key] = self.torque[key] / self.node.radial_position
191
192
193 self.tangential_force = self.forces["tangential"]
194 self.normal_force = self.forces["normal"]
195 self._load[0][0] = self.normal_force
196 self._load[1][0] = - self.tangential_force
197
199 """Before a new BEM step, the aerodynamics forces are calculated
200 and are applied on the MBDyn force"""
201 self._get_loads()
202 rot_matrix = N.asmatrix(self.node.mbdyn_inst.get_rotation_matrix())
203 self.mbdyn_inst.set_value(N.asarray(rot_matrix * self._load))
204
205
207 """A node used for applying the Blade Element Momentum method.
208 This node gathers a C{StructuralNode} and a C{Body} from
209 the MBdyn package. It could have also
210 a C{BemForce} if its radial position contains aerodynamic sections
211 on which pressures are calculated. The C{BemNode} are defined by the user
212 along the blade and are not twisted, it means that all the
213 C{StructuralNode} have initially the same rotation matrix.
214 However the C{BemNode} follows the blade pitching.
215 Moreover the rotation matrix of each node could change
216 if the blade starts to deform.
217 """
218
220 BasicObject.__init__(self, name)
221 self.mass = 0.
222 self.radial_position = 0.
223
224 self.lower_radius = None
225 self.upper_radius = None
226 self.force = None
227
228 self.structural = StructuralNode(self.name)
229 self.structural.set_type("dynamic")
230
231 self.body = Body(self.name + "body")
232 self.body.attach_to_node(self.structural)
233
234 self.own_para_names += ["mass",
235 "radial_position"]
236
238 """Applied the mass on the body"""
239 self.mass = mass
240 self.body.set_mass(mass)
241
243 """Set the radial position of the BEM node"""
244 self.radial_position = radius
245 self.structural.radial_position = radius
246
248 """Create the L{BemForce}.
249 Supposed to do more but there is still a problem
250 between a general interface and a specific model
251 with MBDyn objects"""
252 self.force = BemForce(self.name + " force")
253
262
264 """Find the boundaries from the two other bem node limits"""
265 deltar = (self.radial_position - lower_node_radius) / 2.
266 self.lower_radius = lower_node_radius + deltar
267
268 deltar = (upper_node_radius - self.radial_position) / 2.
269 self.upper_radius = self.radial_position + deltar
270
272 """Find the relevant sections for the bem node"""
273 cond_lower = self.lower_radius <= section_radius
274 cond_upper = section_radius <= self.upper_radius
275
276 section_idx = list(N.where(cond_lower * cond_upper)[0])
277
278 if len(section_idx) > 0:
279 self.create_force()
280 for idx in range(len(section_idx) - 1):
281 area = _ForceArea(sections[idx],
282 sections[idx + 1])
283 self.force.add_area(area)
284 self._handle_boundary_areas(section_idx[0],
285 section_idx[-1],
286 sections)
287
290 """Create the boundary force areas for a C{BemNode}.
291 A boundary area exists only if there is not already
292 an aerodynamics section on the C{BemNode} border and if
293 an extra aerodynamics section exists above the border."""
294 first_section = sections[first_section_idx]
295 if first_section.radial_position != self.lower_radius:
296 if (first_section_idx - 1) != 0:
297 area = _LowerForceArea(self.lower_radius,
298 sections[first_section_idx - 1],
299 first_section)
300 self.force.add_lower_area(area)
301
302 last_section = sections[last_section_idx]
303 if last_section.radial_position != self.upper_radius:
304 if (last_section_idx + 1) != (len(sections) - 1):
305 area = _UpperForceArea(self.upper_radius,
306 last_section,
307 sections[last_section_idx + 1])
308 self.force.add_upper_area(area)
309
311 """Save the bem node data"""
312
313
314
315 if self.force:
316 self.force.save()
317
319 """Update the forces before running MBDyn"""
320 if self.force:
321 self.force.update(None)
322