class ModifyBladePlanformBase(Component): """ Base for classes that modify a blade planform object """ pfIn = VarTree(BladePlanformVT(), iotype='in') pfOut = VarTree(BladePlanformVT(), iotype='out')
def redistribute_blade_planform(pfIn, x): pfOut = BladePlanformVT() pfOut.s = x.copy() for name in pfIn.list_vars(): var = getattr(pfIn, name) if not isinstance(var, np.ndarray): continue tck = Akima1DInterpolator(pfIn.s, var) newvar = tck(x) setattr(pfOut, name, newvar) return pfOut
class BladePlanformWriter(Component): filebase = Str('blade') pf = VarTree(BladePlanformVT(), iotype='in') def execute(self): name = self.filebase + self.itername + '.pfd' try: if '-fd' in self.itername or '-fd' in self.parent.itername: name = self.filebase + '.pfd' except: pass data = np.array([ self.pf.x, self.pf.y, self.pf.z, self.pf.rot_x, self.pf.rot_y, self.pf.rot_z, self.pf.chord, self.pf.rthick, self.pf.p_le ]).T fid = open(name, 'w') header = [ 'main_axis_x', 'main_axis_y', 'main_axis_z', 'rot_x', 'rot_y', 'rot_z', 'chord', 'rthick', 'p_le' ] exp_prec = 10 # exponential precesion col_width = exp_prec + 8 # column width required for exp precision header_full = '# ' + ''.join([(hh + ' [%i]').center(col_width + 2) % i for i, hh in enumerate(header)]) + '\n' fid.write(header_full) np.savetxt(fid, data, fmt='%' + ' %i.%ie' % (col_width, exp_prec)) fid.close()
class RotorAeroCode(Component): """ Wrapper for a code capable of predicting the distributed loads on a rotor """ pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade geometric definition') inflow = VarTree(TurbineEnvironmentVT(), iotype='in', desc='Rotor inflow conditions') oper = VarTree(RotorOperationalData(), iotype='out', desc='Operational data') rotor_loads = VarTree(RotorLoadsVT(), iotype='out', desc='Rotor torque, power, and thrust') blade_loads = VarTree(DistributedLoadsExtVT(), iotype='out', desc='Spanwise load distributions') def execute(self): print 'perform analysis here'
class RotorAeroBase(Component): """ Base class for models that can predict the power and thrust of wind turbine rotor for a single inflow case """ pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade geometric definition') inflow = VarTree(TurbineEnvironmentVT(), iotype='in', desc='Rotor inflow conditions') oper = VarTree(RotorOperationalData(), iotype='out', desc='Operational data') rotor_loads = VarTree(RotorLoadsVT(), iotype='out', desc='Rotor torque, power, and thrust')
class RedistributedBladePlanform(Component): """ Redistribute an existing planform onto a new distribution x """ x = Array(iotype='in', desc='New spanwise discretization') pfIn = VarTree(BladePlanformVT(), iotype='in') pfOut = VarTree(BladePlanformVT(), iotype='out') def execute(self): self.pfOut.s = self.x.copy() self.pfIn._compute_s() for name in self.pfIn.list_vars(): var = getattr(self.pfIn, name) if not isinstance(var, np.ndarray): continue tck = pchip(self.pfIn.s, var) newvar = tck(self.x) setattr(self.pfOut, name, newvar)
class RotorAero(Component): """ Class that extends the TurbineAeroBase with distributed blade loads for a single inflow case """ pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade geometric definition') inflow = VarTree(TurbineEnvironmentVT(), iotype='in', desc='Rotor inflow conditions') oper = VarTree(RotorOperationalData(), iotype='out', desc='Operational data') rotor_loads = VarTree(RotorLoadsVT(), iotype='out', desc='Rotor torque, power, and thrust') blade_loads = VarTree(DistributedLoadsExtVT(), iotype='out', desc='Spanwise load distributions')
class CS2Dsolver(Component): cs2d = List(CrossSectionStructureVT, iotype='in', desc='Blade cross sectional structure geometry') pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade planform discretized according to' 'the structural resolution') beam_structure = VarTree(BeamStructureVT(), iotype='out', desc='Structural beam properties') def execute(self): print '' for i, cs in enumerate(self.cs2d): print 'processing cross section %i' % i
class ComputeHAWC2BeamProps(Component): """ Component that postprocesses the individual outputs from the BECAS case runs returning a BeamStructureVT, mass and mass moment. """ pf = VarTree(BladePlanformVT(), iotype='in') hub_radius = Float(iotype='in') cs2d_cases = List(iotype='in') g = Float(9.81, iotype='in') beam_structure = VarTree(BeamStructureVT(), iotype='out') mass = Float(iotype='out') mass_moment = Float(iotype='out') def execute(self): self.beam_structure = self.beam_structure.copy() ni = len(self.cs2d_cases) for name in self.beam_structure.list_vars(): var = getattr(self.beam_structure, name) if name == 'J': nname = 'K' else: nname = name if isinstance(var, np.ndarray): var = np.zeros(ni) for i, h2d in enumerate(self.cs2d_cases): try: var[i] = getattr(h2d, nname) except: pass setattr(self.beam_structure, name, var) self.beam_structure.s = self.pf.s * self.pf.smax * self.pf.blade_length self.mass = np.trapz(self.beam_structure.dm, self.beam_structure.s) self.mass_moment = np.trapz( self.g * self.beam_structure.dm * (self.beam_structure.s + self.hub_radius), self.beam_structure.s) print 'Mass: ', self.mass print 'Mass moment: ', self.mass_moment self.beam_structure.x_e += ( 0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length self.beam_structure.x_cg += ( 0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length self.beam_structure.x_sh += ( 0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length
def add_main_body(self, name, body=None): if body is None: body = MainBody() if 'blade' in name: body.remove('geom') body.add('geom', VarTree(BladePlanformVT())) if 'tower' in name: body.remove('geom') body.add('geom', VarTree(TubularTowerGeometryVT())) self.add(name, VarTree(body)) self.bodies.append(name) return getattr(self, name)
class BeamStructureCSCode(Component): """ Base class for computing beam structural properties using a cross-sectional code such as PreComp, BECAS or VABS. The analysis assumes that the list of CrossSectionStructureVT's and the BladePlanformVT are interpolated onto the structural grid, and that the code itself is responsible for the meshing of the cross sections. """ cs2d = List(CrossSectionStructureVT, iotype='in', desc='Blade cross sectional structure geometry') pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade planform discretized according to' 'the structural resolution') beam_structure = VarTree(BeamStructureVT(), iotype='out', desc='Structural beam properties')
class BECASBeamStructure(Assembly): """ Assembly that implements the FUSED-Wind I/O interface defined in BeamStructureCSCode. The assembly sets up a series of BECAS computations that iterate over a list of CrossSectionStructureVT's and computes beam structural properties suitable for an aeroelastic solver. """ becas_inputs = Str('becas_inputs', desc='Relative path to BECAS input files', iotype='in') section_name = Str( 'BECAS_SECTION', desc='Section name used by shellexpander and BECASWrapper', iotype='in') cs2d = List(iotype='in', desc='Blade cross sectional structure geometry') pf = VarTree( BladePlanformVT(), iotype='in', desc='Blade planform with same spanwise discretization as cs2d') beam_structure = VarTree(BeamStructureVT(), iotype='out', desc='Structural beam properties') def _pre_execute(self): super(BECASBeamStructure, self)._pre_execute() self.tt = time.time() def _post_execute(self): super(BECASBeamStructure, self)._post_execute() t = time.time() - self.tt self._logger.info('BECASBeamStructure time: %f' % t)
def read_blade_planform(filename): data = np.loadtxt(filename) s = calculate_length(data[:, [0, 1, 2]]) pf = BladePlanformVT() pf.blade_length = data[-1, 2] pf.s = s / s[-1] pf.x = data[:, 0] / data[-1, 2] pf.y = data[:, 1] / data[-1, 2] pf.z = data[:, 2] / data[-1, 2] pf.rot_x = data[:, 3] pf.rot_y = data[:, 4] pf.rot_z = data[:, 5] pf.chord = data[:, 6] / data[-1, 2] pf.rthick = data[:, 7] pf.rthick /= pf.rthick.max() pf.athick = pf.rthick * pf.chord pf.p_le = data[:, 8] return pf
class SplinedBladePlanform(Assembly): x_dist = Array(iotype='in', desc='spanwise resolution of blade') nC = Int(8, iotype='in', desc='Number of spline control points along span') Cx = Array(iotype='in', desc='spanwise distribution of spline control points') blade_length = Float(iotype='in') span_ni = Int(50, iotype='in') pfIn = VarTree(BladePlanformVT(), iotype='in') pfOut = VarTree(BladePlanformVT(), iotype='out') def __init__(self): super(SplinedBladePlanform, self).__init__() self.blade_length_ref = 0. def _pre_execute(self): super(SplinedBladePlanform, self)._pre_execute() # set reference length first time this comp is executed if self.blade_length_ref == 0.: self.blade_length_ref = self.blade_length self.configure_splines() def compute_x(self): # simple distfunc for now self.x_dist = distfunc([[0., -1, 1], [1., 0.2 * 1. / self.span_ni, self.span_ni]]) def configure_splines(self): if hasattr(self, 'chord_C'): return if self.Cx.shape[0] == 0: self.Cx = np.linspace(0, 1, self.nC) else: self.nC = self.Cx.shape[0] self.compute_x() self.pfOut = self.pfIn.copy() for vname in self.pfIn.list_vars(): if vname in ['athick', 'blade_length']: continue cIn = self.get('pfIn.' + vname) cOut = self.get('pfOut.' + vname) sname = vname.replace('.', '_') if vname == 's': self.connect('x_dist', 'pfOut.s') else: spl = self.add(sname, FFDSplineComponentBase(self.nC)) self.driver.workflow.add(sname) # spl.log_level = logging.DEBUG self.connect('x_dist', sname + '.x') self.connect('Cx', sname + '.Cx') spl.xinit = self.get('pfIn.s') spl.Pinit = cIn self.connect(sname + '.P', 'pfOut.' + vname) self.create_passthrough(sname + '.C', alias=sname + '_C') self.create_passthrough(sname + '.dPds', alias=sname + '_dPds') def _post_execute(self): super(SplinedBladePlanform, self)._post_execute() self.pfOut.chord *= self.blade_length / self.blade_length_ref self.pfOut.athick = self.pfOut.chord * self.pfOut.rthick
class SplinedBladePlanform(Assembly): x_dist = Array(iotype='in', desc='spanwise resolution of blade') nC = Int(8, iotype='in', desc='Number of spline control points along span') Cx = Array(iotype='in', desc='spanwise distribution of spline control points') blade_length = Float(iotype='in') blade_length_ref = Float(iotype='in') span_ni = Int(50, iotype='in') pfIn = VarTree(BladePlanformVT(), iotype='in') pfOut = VarTree(BladePlanformVT(), iotype='out') def __init__(self): super(SplinedBladePlanform, self).__init__() self.blade_length_ref = 0. def _pre_execute(self): super(SplinedBladePlanform, self)._pre_execute() # set reference length first time this comp is executed if self.blade_length_ref == 0.: self.blade_length_ref = self.blade_length def configure_splines(self): if hasattr(self, 'chord_C'): return if self.Cx.shape[0] == 0: self.Cx = np.linspace(0, 1, self.nC) else: self.nC = self.Cx.shape[0] self.connect('blade_length', 'pfOut.blade_length') self.add('compute_x', ComputeDist()) self.driver.workflow.add('compute_x') self.connect('span_ni', 'compute_x.span_ni') for vname in self.pfIn.list_vars(): if vname in ['athick', 'blade_length']: continue cIn = self.get('pfIn.' + vname) cOut = self.get('pfOut.' + vname) sname = vname.replace('.', '_') if vname == 's': self.connect('compute_x.x', 'pfOut.s') else: spl = self.add(sname, FFDSplineComponentBase(self.nC)) self.driver.workflow.add(sname) # spl.log_level = logging.DEBUG self.connect('compute_x.x', sname + '.x') self.connect('Cx', sname + '.Cx') spl.xinit = self.get('pfIn.s') spl.Pinit = cIn if vname == 'chord': self.add('scaleC', ScaleChord()) self.driver.workflow.add('scaleC') self.connect('chord.P', 'scaleC.cIn') self.connect('blade_length/blade_length_ref', 'scaleC.scaler') self.connect('scaleC.cOut', 'pfOut.chord') # self.connect(sname + '.P'+ # '*blade_length/blade_length_ref', 'pfOut.' + vname) else: self.connect(sname + '.P', 'pfOut.' + vname) self.create_passthrough(sname + '.C', alias=sname + '_C') self.create_passthrough(sname + '.dPds', alias=sname + '_dPds') self.add('athick', ComputeAthick()) self.driver.workflow.add('athick') self.connect('chord.P', 'athick.chord') self.connect('rthick.P', 'athick.rthick') self.connect('athick.athick', 'pfOut.athick')
def read_blade_planform(filename): data = np.loadtxt(filename) s = calculate_length(data[:, [0, 1, 2]]) pf = BladePlanformVT() pf.blade_length = data[-1, 2] pf.s = s / s[-1] pf.smax = s[-1] pf.x = data[:, 0] / data[-1, 2] pf.y = data[:, 1] / data[-1, 2] pf.z = data[:, 2] / data[-1, 2] pf.rot_x = data[:, 3] pf.rot_y = data[:, 4] pf.rot_z = data[:, 5] pf.chord = data[:, 6] / data[-1, 2] pf.rthick = data[:, 7] pf.rthick /= pf.rthick.max() pf.athick = pf.rthick * pf.chord pf.p_le = data[:, 8] return pf
def configure(self): pf = BladePlanformVT() pf.x = np.zeros(7) pf.y = np.zeros(7) pf.z = np.array([0, 0.08, 0.25, 0.5, 0.7, 0.98, 1.]) pf.rot_x = np.zeros(7) pf.rot_y = np.zeros(7) pf.rot_z = np.array([-14.5 , -14.46339951, -8.39059169, -3.80326048, -0.3093841 , 3.24525784, 3.428 ]) pf.chord = np.array([ 0.06229303, 0.06274697, 0.07170661, 0.05870869, 0.0417073 , 0.01726638, 0.00694718]) pf.rthick = np.array([ 1. , 0.90303866, 0.40834206, 0.26394705, 0.24103804, 0.241 , 0.241 ]) pf.athick = pf.chord * pf.rthick pf.p_le = np.array([ 0.5 , 0.49662459, 0.38370686, 0.35000317, 0.35 , 0.35 , 0.35 ]) self.add('pf', pf) self.add('redist', RedistributedBladePlanform()) self.driver.workflow.add('redist') self.redist.pfIn = self.pf self.redist.x = np.linspace(0, 1, 20)
def configure(self): pf = BladePlanformVT() pf.x = np.zeros(7) pf.y = np.zeros(7) pf.z = np.array([0, 0.08, 0.25, 0.5, 0.7, 0.98, 1.]) pf.rot_x = np.zeros(7) pf.rot_y = np.zeros(7) pf.rot_z = np.array([ -14.5, -14.46339951, -8.39059169, -3.80326048, -0.3093841, 3.24525784, 3.428 ]) pf.chord = np.array([ 0.06229303, 0.06274697, 0.07170661, 0.05870869, 0.0417073, 0.01726638, 0.00694718 ]) pf.rthick = np.array( [1., 0.90303866, 0.40834206, 0.26394705, 0.24103804, 0.241, 0.241]) pf.athick = pf.chord * pf.rthick pf.p_le = np.array( [0.5, 0.49662459, 0.38370686, 0.35000317, 0.35, 0.35, 0.35]) self.add('pf', pf) self.add('redist', RedistributedBladePlanform()) self.driver.workflow.add('redist') self.redist.pfIn = self.pf self.redist.x = np.linspace(0, 1, 20)
class LoftedBladeSurface(Component): pf = VarTree(BladePlanformVT(), iotype='in') base_airfoils = List(iotype='in') blend_var = Array(iotype='in') chord_ni = Int(300, iotype='in') span_ni = Int(300, iotype='in') interp_type = Enum('rthick', ('rthick', 's'), iotype='in') surface_spline = Str('akima', iotype='in', desc='Spline') rot_order = Array(np.array([2, 1, 0]), iotype='in', desc='rotation order of airfoil sections' 'default z,y,x (twist,sweep,dihedral)') surfout = VarTree(BladeSurfaceVT(), iotype='out') surfnorot = VarTree(BladeSurfaceVT(), iotype='out') def execute(self): self.interpolator = BlendAirfoilShapes() self.interpolator.ni = self.chord_ni self.interpolator.spline = self.surface_spline self.interpolator.blend_var = self.blend_var self.interpolator.airfoil_list = self.base_airfoils self.interpolator.initialize() self.span_ni = self.pf.s.shape[0] x = np.zeros((self.chord_ni, self.span_ni, 3)) for i in range(self.span_ni): s = self.pf.s[i] pos_x = self.pf.x[i] pos_y = self.pf.y[i] pos_z = self.pf.z[i] chord = self.pf.chord[i] p_le = self.pf.p_le[i] # generate the blended airfoil shape if self.interp_type == 'rthick': rthick = self.pf.rthick[i] points = self.interpolator(rthick) else: points = self.interpolator(s) points *= chord points[:, 0] += pos_x - chord * p_le # x-coordinate needs to be inverted for clockwise rotating blades x[:, i, :] = (np.array( [-points[:, 0], points[:, 1], x.shape[0] * [pos_z]]).T) # save non-rotated blade (only really applicable for straight blades) x_norm = x.copy() x[:, :, 1] += self.pf.y x = self.rotate(x) self.surfnorot.surface = x_norm self.surfout.surface = x def rotate(self, x): """ rotate blade sections accounting for twist and main axis orientation the blade will be built with a "sheared" layout, ie no rotation around y in the case of sweep. if the main axis includes a winglet, the blade sections will be rotated accordingly. ensure that an adequate point distribution is used in this case to avoid sections colliding in the winglet junction! """ main_axis = Curve(points=np.array([self.pf.x, self.pf.y, self.pf.z]).T) rot_normals = np.zeros((3, 3)) x_rot = np.zeros(x.shape) for i in range(x.shape[1]): points = x[:, i, :] rot_center = main_axis.points[i] # rotation angles read from file angles = np.array([ self.pf.rot_x[i], self.pf.rot_y[i], self.pf.rot_z[i] ]) * np.pi / 180. # compute rotation angles of main_axis t = main_axis.dp[i] rot = np.zeros(3) rot[0] = -np.arctan(t[1] / (t[2] + 1.e-20)) v = np.array([t[2], t[1]]) vt = np.dot(v, v)**0.5 rot[1] = (np.arcsin(t[0] / vt)) angles[0] += rot[0] angles[1] += rot[1] # compute x-y-z normal vectors of rotation n_y = np.cross(t, [1, 0, 0]) n_y = n_y / norm(n_y) rot_normals[0, :] = np.array([1, 0, 0]) rot_normals[1, :] = n_y rot_normals[2, :] = t # compute final rotation matrix rotation_matrix = np.matrix([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) for n, ii in enumerate(self.rot_order): mat = np.matrix(RotMat(rot_normals[ii], angles[ii])) rotation_matrix = mat * rotation_matrix # apply rotation x_rot[:, i, :] = dotXC(rotation_matrix, points, rot_center) return x_rot
class HAWC2GeometryBuilder(Component): bladegeom = VarTree(BladePlanformVT(), iotype='in') c12axis_init = Array(iotype='in') blade_ae = VarTree(HAWC2BladeGeometry(), iotype='out') interp_from_htc = Bool( True, iotype='in', desc= 'Interpolate blade onto the distribution defined in the htc master file' ) blade_ni_span = Int(30, iotype='in', desc='spanwise distribution of blade planform') blade_length = Float(86.366, iotype='in') hub_radius = Float(2.8, iotype='in') def execute(self): c12axis = self.calculate_c12axis() if self.interp_from_htc: cni = self.c12axis_init.shape[0] if self.c12axis_init[-1, 2] > 1.: self.c12axis_init /= self.blade_length # interpolate blade_ae distribution onto c12 distribution self.blade_ae.c12axis = np.zeros((cni, 4)) for i in range(4): tck = pchip(c12axis[:, 2], c12axis[:, i]) self.blade_ae.c12axis[:, i] = tck(self.c12axis_init[:, 2]) else: ds_root = 1. / self.blade_ni_span ds_tip = 1. / self.blade_ni_span / 3. dist = np.array([[0., ds_root, 1], [1., ds_tip, self.blade_ni_span]]) x = distfunc(dist) self.blade_ae.c12axis = np.zeros((x.shape[0], 4)) for i in range(4): tck = pchip(c12axis[:, 2], c12axis[:, i]) self.blade_ae.c12axis[:, i] = tck(x) # scale main axis according to radius self.blade_ae.c12axis[:, :3] *= self.blade_length self.blade_ae.radius = self.blade_length + self.hub_radius self.blade_ae.s = self.bladegeom.smax * self.bladegeom.s * self.blade_length self.blade_ae.rthick = self.bladegeom.rthick * 100. self.blade_ae.chord = self.bladegeom.chord * self.blade_length self.blade_ae.aeset = np.ones(len(self.blade_ae.s)) def calculate_c12axis(self): """ compute the 1/2 chord axis based on the blade axis and chordwise rotation point nb: this only works for straight blades! """ # The HAWC2 blade axis is defined using the 1/2 chord points b = self.bladegeom c12axis = np.zeros((b.x.shape[0], 4)) for i in range(b.x.shape[0]): xc12 = (0.5 - b.p_le[i]) * b.chord[i] * np.cos( b.rot_z[i] * np.pi / 180.) yc12 = -(0.5 - b.p_le[i]) * b.chord[i] * np.sin( b.rot_z[i] * np.pi / 180.) c12axis[i, 0] = -(b.x[i] + xc12) c12axis[i, 1] = b.y[i] + yc12 c12axis[i, 2] = b.z[i] c12axis[:, 3] = b.rot_z return c12axis