Exemplo n.º 1
0
# set conditions
Uinf = 10.0
tsr = 7.55
pitch = 0.0
Omega = Uinf * tsr / Rtip * 30.0 / pi  # convert to RPM
azimuth = 0.0

# # evaluate distributed loads
# Np, Tp = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth)

# 4 ----------

# 5 ----------

loads, derivs \
    = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth)
Np = loads['Np']
Tp = loads['Tp']
dNp = derivs['dNp']
dTp = derivs['dTp']

n = len(r)

# n x n (diagonal)
dNp_dr = dNp['dr']
dNp_dchord = dNp['dchord']
dNp_dtheta = dNp['dtheta']
dNp_dpresweep = dNp['dpresweep']

# n x n (tridiagonal)
dNp_dprecurve = dNp['dprecurve']
Exemplo n.º 2
0
    af[i] = CCAirfoil(np.rad2deg(aoa), [1e+6], cl[i, :, 0, 0], cd[i, :, 0, 0],
                      cm[i, :, 0, 0])

########## Run CCBlade ##########
get_cp_cm = CCBlade(r, chord, twist, af, hub_r, Rtip, B, rho, mu, cone, tilt,
                    0., shearExp, hub_height, nSector, presweep, precurve[-1],
                    presweep, presweep[-1], tiploss, hubloss, wakerotation,
                    usecd)
# get_cp_cm.induction        = True
Omega = Uhub * tsr / Rtip * 30.0 / np.pi  # Rotor speed
myout, derivs = get_cp_cm.evaluate([Uhub], [Omega], [pitch], coefficients=True)
P, T, Q, M, CP, CT, CQ, CM = [
    myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM']
]
get_cp_cm.induction_inflow = True
loads, deriv = get_cp_cm.distributedAeroLoads([Uhub], Omega, pitch, 0.)

########## Post Process Output ##########
# Compute forces in the airfoil coordinate system, pag 21 of https://www.nrel.gov/docs/fy13osti/58819.pdf
P_b = DirectionVector(loads['Np'], -loads['Tp'], 0)
P_af = P_b.bladeToAirfoil(twist)
# Compute lift and drag forces
F = P_b.bladeToAirfoil(twist + loads['alpha'] + pitch)
# Print output .dat file with the quantities distributed along span
np.savetxt(
    os.path.join(output_folder, 'distributed_quantities.dat'),
    np.array([
        s, r, loads['alpha'], loads['a'], loads['ap'], loads['Cl'],
        loads['Cd'], F.x, F.y, loads['Np'], -loads['Tp'], P_af.x, P_af.y
    ]).T,
    header=
Exemplo n.º 3
0
class ComputePowerCurve(ExplicitComponent):
    """
    Iteratively call CCBlade to compute the power curve.
    """
    def initialize(self):
        self.options.declare('modeling_options')

    def setup(self):
        modeling_options = self.options['modeling_options']
        self.n_span = n_span = modeling_options['blade']['n_span']
        # self.n_af          = n_af      = af_init_options['n_af'] # Number of airfoils
        self.n_aoa = n_aoa = modeling_options['airfoils'][
            'n_aoa']  # Number of angle of attacks
        self.n_Re = n_Re = modeling_options['airfoils'][
            'n_Re']  # Number of Reynolds, so far hard set at 1
        self.n_tab = n_tab = modeling_options['airfoils'][
            'n_tab']  # Number of tabulated data. For distributed aerodynamic control this could be > 1
        # self.n_xy          = n_xy      = af_init_options['n_xy'] # Number of coordinate points to describe the airfoil geometry
        self.regulation_reg_III = modeling_options['servose'][
            'regulation_reg_III']
        # n_span       = self.n_span = self.options['n_span']
        self.n_pc = modeling_options['servose']['n_pc']
        self.n_pc_spline = modeling_options['servose']['n_pc_spline']
        # n_aoa  = self.options['n_aoa']
        # n_re   = self.options['n_re']

        # parameters
        self.add_input('v_min', val=0.0, units='m/s', desc='cut-in wind speed')
        self.add_input('v_max',
                       val=0.0,
                       units='m/s',
                       desc='cut-out wind speed')
        self.add_input('rated_power',
                       val=0.0,
                       units='W',
                       desc='electrical rated power')
        self.add_input('omega_min',
                       val=0.0,
                       units='rpm',
                       desc='minimum allowed rotor rotation speed')
        self.add_input('omega_max',
                       val=0.0,
                       units='rpm',
                       desc='maximum allowed rotor rotation speed')
        self.add_input('control_maxTS',
                       val=0.0,
                       units='m/s',
                       desc='maximum allowed blade tip speed')
        self.add_input(
            'tsr_operational',
            val=0.0,
            desc='tip-speed ratio in Region 2 (should be optimized externally)'
        )
        self.add_input(
            'control_pitch',
            val=0.0,
            units='deg',
            desc=
            'pitch angle in region 2 (and region 3 for fixed pitch machines)')
        self.add_discrete_input('drivetrainType', val='GEARED')
        self.add_input('gearbox_efficiency',
                       val=0.0,
                       desc='Gearbox efficiency')
        self.add_input('generator_efficiency',
                       val=0.0,
                       desc='Generator efficiency')

        self.add_input(
            'r',
            val=np.zeros(n_span),
            units='m',
            desc=
            'radial locations where blade is defined (should be increasing and not go all the way to hub or tip)'
        )
        self.add_input('chord',
                       val=np.zeros(n_span),
                       units='m',
                       desc='chord length at each section')
        self.add_input(
            'theta',
            val=np.zeros(n_span),
            units='deg',
            desc=
            'twist angle at each section (positive decreases angle of attack)')
        self.add_input('Rhub', val=0.0, units='m', desc='hub radius')
        self.add_input('Rtip', val=0.0, units='m', desc='tip radius')
        self.add_input('hub_height', val=0.0, units='m', desc='hub height')
        self.add_input(
            'precone',
            val=0.0,
            units='deg',
            desc='precone angle',
        )
        self.add_input(
            'tilt',
            val=0.0,
            units='deg',
            desc='shaft tilt',
        )
        self.add_input(
            'yaw',
            val=0.0,
            units='deg',
            desc='yaw error',
        )
        self.add_input('precurve',
                       val=np.zeros(n_span),
                       units='m',
                       desc='precurve at each section')
        self.add_input('precurveTip',
                       val=0.0,
                       units='m',
                       desc='precurve at tip')
        self.add_input('presweep',
                       val=np.zeros(n_span),
                       units='m',
                       desc='presweep at each section')
        self.add_input('presweepTip',
                       val=0.0,
                       units='m',
                       desc='presweep at tip')

        # self.add_discrete_input('airfoils',  val=[0]*n_span,                      desc='CCAirfoil instances')
        self.add_input('airfoils_cl',
                       val=np.zeros((n_span, n_aoa, n_Re, n_tab)),
                       desc='lift coefficients, spanwise')
        self.add_input('airfoils_cd',
                       val=np.zeros((n_span, n_aoa, n_Re, n_tab)),
                       desc='drag coefficients, spanwise')
        self.add_input('airfoils_cm',
                       val=np.zeros((n_span, n_aoa, n_Re, n_tab)),
                       desc='moment coefficients, spanwise')
        self.add_input('airfoils_aoa',
                       val=np.zeros((n_aoa)),
                       units='deg',
                       desc='angle of attack grid for polars')
        self.add_input('airfoils_Re',
                       val=np.zeros((n_Re)),
                       desc='Reynolds numbers of polars')
        self.add_discrete_input('nBlades', val=0, desc='number of blades')
        self.add_input('rho',
                       val=1.225,
                       units='kg/m**3',
                       desc='density of air')
        self.add_input('mu',
                       val=1.81e-5,
                       units='kg/(m*s)',
                       desc='dynamic viscosity of air')
        self.add_input('shearExp', val=0.0, desc='shear exponent')
        self.add_discrete_input(
            'nSector',
            val=4,
            desc=
            'number of sectors to divide rotor face into in computing thrust and power'
        )
        self.add_discrete_input('tiploss',
                                val=True,
                                desc='include Prandtl tip loss model')
        self.add_discrete_input('hubloss',
                                val=True,
                                desc='include Prandtl hub loss model')
        self.add_discrete_input(
            'wakerotation',
            val=True,
            desc=
            'include effect of wake rotation (i.e., tangential induction factor is nonzero)'
        )
        self.add_discrete_input(
            'usecd',
            val=True,
            desc='use drag coefficient in computing induction factors')

        # outputs
        self.add_output('V',
                        val=np.zeros(self.n_pc),
                        units='m/s',
                        desc='wind vector')
        self.add_output('Omega',
                        val=np.zeros(self.n_pc),
                        units='rpm',
                        desc='rotor rotational speed')
        self.add_output('pitch',
                        val=np.zeros(self.n_pc),
                        units='deg',
                        desc='rotor pitch schedule')
        self.add_output('P',
                        val=np.zeros(self.n_pc),
                        units='W',
                        desc='rotor electrical power')
        self.add_output('T',
                        val=np.zeros(self.n_pc),
                        units='N',
                        desc='rotor aerodynamic thrust')
        self.add_output('Q',
                        val=np.zeros(self.n_pc),
                        units='N*m',
                        desc='rotor aerodynamic torque')
        self.add_output('M',
                        val=np.zeros(self.n_pc),
                        units='N*m',
                        desc='blade root moment')
        self.add_output('Cp',
                        val=np.zeros(self.n_pc),
                        desc='rotor electrical power coefficient')
        self.add_output('Cp_aero',
                        val=np.zeros(self.n_pc),
                        desc='rotor aerodynamic power coefficient')
        self.add_output('Ct_aero',
                        val=np.zeros(self.n_pc),
                        desc='rotor aerodynamic thrust coefficient')
        self.add_output('Cq_aero',
                        val=np.zeros(self.n_pc),
                        desc='rotor aerodynamic torque coefficient')
        self.add_output('Cm_aero',
                        val=np.zeros(self.n_pc),
                        desc='rotor aerodynamic moment coefficient')

        self.add_output('V_R25',
                        val=0.0,
                        units='m/s',
                        desc='region 2.5 transition wind speed')
        self.add_output('rated_V',
                        val=0.0,
                        units='m/s',
                        desc='rated wind speed')
        self.add_output('rated_Omega',
                        val=0.0,
                        units='rpm',
                        desc='rotor rotation speed at rated')
        self.add_output('rated_pitch',
                        val=0.0,
                        units='deg',
                        desc='pitch setting at rated')
        self.add_output('rated_T',
                        val=0.0,
                        units='N',
                        desc='rotor aerodynamic thrust at rated')
        self.add_output('rated_Q',
                        val=0.0,
                        units='N*m',
                        desc='rotor aerodynamic torque at rated')
        self.add_output(
            'ax_induct_regII',
            val=np.zeros(n_span),
            desc='rotor axial induction at cut-in wind speed along blade span')
        self.add_output(
            'tang_induct_regII',
            val=np.zeros(n_span),
            desc=
            'rotor tangential induction at cut-in wind speed along blade span')
        self.add_output(
            'aoa_regII',
            val=np.zeros(n_span),
            units='deg',
            desc=
            'angle of attack distribution along blade span at cut-in wind speed'
        )
        self.add_output('Cp_regII',
                        val=0.0,
                        desc='power coefficient at cut-in wind speed')
        self.add_output(
            'cl_regII',
            val=np.zeros(n_span),
            desc=
            'lift coefficient distribution along blade span at cut-in wind speed'
        )
        self.add_output(
            'cd_regII',
            val=np.zeros(n_span),
            desc=
            'drag coefficient distribution along blade span at cut-in wind speed'
        )

        # self.declare_partials('*', '*', method='fd', form='central', step=1e-6)

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):

        # Create Airfoil class instances
        af = [None] * self.n_span
        for i in range(self.n_span):
            if self.n_tab > 1:
                ref_tab = int(np.floor(self.n_tab / 2))
                af[i] = CCAirfoil(inputs['airfoils_aoa'],
                                  inputs['airfoils_Re'],
                                  inputs['airfoils_cl'][i, :, :, ref_tab],
                                  inputs['airfoils_cd'][i, :, :, ref_tab],
                                  inputs['airfoils_cm'][i, :, :, ref_tab])
            else:
                af[i] = CCAirfoil(inputs['airfoils_aoa'],
                                  inputs['airfoils_Re'],
                                  inputs['airfoils_cl'][i, :, :, 0],
                                  inputs['airfoils_cd'][i, :, :, 0],
                                  inputs['airfoils_cm'][i, :, :, 0])

        self.ccblade = CCBlade(
            inputs['r'], inputs['chord'], inputs['theta'], af, inputs['Rhub'],
            inputs['Rtip'], discrete_inputs['nBlades'], inputs['rho'],
            inputs['mu'], inputs['precone'], inputs['tilt'], inputs['yaw'],
            inputs['shearExp'], inputs['hub_height'],
            discrete_inputs['nSector'], inputs['precurve'],
            inputs['precurveTip'], inputs['presweep'], inputs['presweepTip'],
            discrete_inputs['tiploss'], discrete_inputs['hubloss'],
            discrete_inputs['wakerotation'], discrete_inputs['usecd'])

        # JPJ: what is this grid for? Seems to be a special distribution of velocities
        # for the hub
        grid0 = np.cumsum(
            np.abs(
                np.diff(
                    np.cos(np.linspace(-np.pi / 4., np.pi / 2.,
                                       self.n_pc + 1)))))
        grid1 = (grid0 - grid0[0]) / (grid0[-1] - grid0[0])
        Uhub = grid1 * (inputs['v_max'] - inputs['v_min']) + inputs['v_min']

        P_aero = np.zeros(Uhub.shape)
        Cp_aero = np.zeros(Uhub.shape)
        Ct_aero = np.zeros(Uhub.shape)
        Cq_aero = np.zeros(Uhub.shape)
        Cm_aero = np.zeros(Uhub.shape)
        P = np.zeros(Uhub.shape)
        Cp = np.zeros(Uhub.shape)
        T = np.zeros(Uhub.shape)
        Q = np.zeros(Uhub.shape)
        M = np.zeros(Uhub.shape)
        pitch = np.zeros(Uhub.shape) + inputs['control_pitch']

        # Unpack variables
        P_rated = inputs['rated_power']
        R_tip = inputs['Rtip']
        tsr = inputs['tsr_operational']
        driveType = discrete_inputs['drivetrainType']
        driveEta = inputs['gearbox_efficiency'] * inputs['generator_efficiency']

        # Set rotor speed based on TSR
        Omega_tsr = Uhub * tsr / R_tip

        # Determine maximum rotor speed (rad/s)- either by TS or by control input
        Omega_max = min([
            inputs['control_maxTS'] / R_tip, inputs['omega_max'] * np.pi / 30.
        ])

        # Apply maximum and minimum rotor speed limits
        Omega = np.maximum(np.minimum(Omega_tsr, Omega_max),
                           inputs['omega_min'] * np.pi / 30.)
        Omega_rpm = Omega * 30. / np.pi

        # Set baseline power production
        myout, derivs = self.ccblade.evaluate(Uhub,
                                              Omega_rpm,
                                              pitch,
                                              coefficients=True)
        P_aero, T, Q, M, Cp_aero, Ct_aero, Cq_aero, Cm_aero = [
            myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM']
        ]
        P, eff = compute_P_and_eff(P_aero, P_rated, driveType, driveEta)
        Cp = Cp_aero * eff

        # Find Region 3 index
        region_bool = np.nonzero(P >= P_rated)[0]
        if len(region_bool) == 0:
            i_3 = self.n_pc
            region3 = False
        else:
            i_3 = region_bool[0] + 1
            region3 = True

        # Guess at Region 2.5, but we will do a more rigorous search below
        if Omega_max < Omega_tsr[-1]:
            U_2p5 = np.interp(Omega[-1], Omega_tsr, Uhub)
            outputs['V_R25'] = U_2p5
        else:
            U_2p5 = Uhub[-1]
        i_2p5 = np.nonzero(U_2p5 <= Uhub)[0][0]

        # Find rated index and guess at rated speed
        if P_aero[-1] > P_rated:
            U_rated = np.interp(P_rated, P_aero * eff, Uhub)
        else:
            U_rated = Uhub[-1]
        i_rated = np.nonzero(U_rated <= Uhub)[0][0]

        # Function to be used inside of power maximization until Region 3
        def maximizePower(pitch, Uhub, Omega_rpm):
            myout, _ = self.ccblade.evaluate([Uhub], [Omega_rpm], [pitch],
                                             coefficients=False)
            return -myout['P']

        # Maximize power until Region 3
        region2p5 = False
        for i in range(i_3):
            # No need to optimize if already doing well
            if Omega[i] == Omega_tsr[i]: continue

            # Find pitch value that gives highest power rating
            pitch0 = pitch[i] if i == 0 else pitch[i - 1]
            bnds = [pitch0 - 10., pitch0 + 10.]
            pitch[i] = minimize_scalar(
                lambda x: maximizePower(x, Uhub[i], Omega_rpm[i]),
                bounds=bnds,
                method='bounded',
                options={
                    'disp': False,
                    'xatol': 1e-2,
                    'maxiter': 40
                })['x']

            # Find associated power
            myout, _ = self.ccblade.evaluate([Uhub[i]], [Omega_rpm[i]],
                                             [pitch[i]],
                                             coefficients=True)
            P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[i], Cq_aero[
                i], Cm_aero[i] = [
                    myout[key]
                    for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM']
                ]
            P[i], eff = compute_P_and_eff(P_aero[i], P_rated, driveType,
                                          driveEta)
            Cp[i] = Cp_aero[i] * eff

            # Note if we find Region 2.5
            if ((not region2p5) and (Omega[i] == Omega_max)
                    and (P[i] < P_rated)):
                region2p5 = True
                i_2p5 = i

            # Stop if we find Region 3 early
            if P[i] > P_rated:
                i_3 = i + 1
                i_rated = i
                break

        # Solve for rated velocity
        # JPJ: why rename i_rated to i here? It removes clarity in the following 50 lines that we're looking at the rated properties
        i = i_rated
        if i < self.n_pc - 1:

            def const_Urated(x):
                pitch = x[0]
                Uhub_i = x[1]
                Omega_i = min([Uhub_i * tsr / R_tip, Omega_max])
                myout, _ = self.ccblade.evaluate([Uhub_i],
                                                 [Omega_i * 30. / np.pi],
                                                 [pitch],
                                                 coefficients=False)
                P_aero_i = myout['P']
                P_i, eff = compute_P_and_eff(P_aero_i.flatten(), P_rated,
                                             driveType, driveEta)
                return (P_i - P_rated)

            if region2p5:
                # Have to search over both pitch and speed
                x0 = [0.0, Uhub[i]]
                bnds = [
                    np.sort([pitch[i - 1], pitch[i + 1]]),
                    [Uhub[i - 1], Uhub[i + 1]]
                ]
                const = {}
                const['type'] = 'eq'
                const['fun'] = const_Urated
                params_rated = minimize(lambda x: x[1],
                                        x0,
                                        method='slsqp',
                                        bounds=bnds,
                                        constraints=const,
                                        tol=1e-3)

                if params_rated.success and not np.isnan(params_rated.x[1]):
                    U_rated = params_rated.x[1]
                    pitch[i] = params_rated.x[0]
                else:
                    U_rated = U_rated  # Use guessed value earlier
                    pitch[i] = 0.0
            else:
                # Just search over speed
                pitch[i] = 0.0
                try:
                    U_rated = brentq(lambda x: const_Urated([0.0, x]),
                                     Uhub[i - 1],
                                     Uhub[i + 1],
                                     xtol=1e-4,
                                     rtol=1e-5,
                                     maxiter=40,
                                     disp=False)
                except ValueError:
                    U_rated = minimize_scalar(
                        lambda x: np.abs(const_Urated([0.0, x])),
                        bounds=[Uhub[i - 1], Uhub[i + 1]],
                        method='bounded',
                        options={
                            'disp': False,
                            'xatol': 1e-3,
                            'maxiter': 40
                        })['x']

            Omega_rated = min([U_rated * tsr / R_tip, Omega_max])
            Omega[i:] = np.minimum(
                Omega[i:],
                Omega_rated)  # Stay at this speed if hit rated too early
            Omega_rpm = Omega * 30. / np.pi
            myout, _ = self.ccblade.evaluate([U_rated], [Omega_rpm[i]],
                                             [pitch[i]],
                                             coefficients=True)
            P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[i], Cq_aero[
                i], Cm_aero[i] = [
                    myout[key]
                    for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM']
                ]
            P[i], eff = compute_P_and_eff(P_aero[i], P_rated, driveType,
                                          driveEta)
            Cp[i] = Cp_aero[i] * eff
            P[i] = P_rated

        # Store rated speed in array
        Uhub[i_rated] = U_rated

        # Store outputs
        outputs['rated_V'] = np.float64(U_rated)
        outputs['rated_Omega'] = Omega_rpm[i]
        outputs['rated_pitch'] = pitch[i]
        outputs['rated_T'] = T[i]
        outputs['rated_Q'] = Q[i]

        # JPJ: this part can be converted into a BalanceComp with a solver.
        # This will be less expensive and allow us to get derivatives through the process.
        if region3:
            # Function to be used to stay at rated power in Region 3
            def rated_power_dist(pitch, Uhub, Omega_rpm):
                myout, _ = self.ccblade.evaluate([Uhub], [Omega_rpm], [pitch],
                                                 coefficients=False)
                P_aero = myout['P']
                P, eff = compute_P_and_eff(P_aero, P_rated, driveType,
                                           driveEta)
                return (P - P_rated)

            # Solve for Region 3 pitch
            options = {'disp': False}
            if self.regulation_reg_III:
                for i in range(i_3, self.n_pc):
                    pitch0 = pitch[i - 1]
                    try:
                        pitch[i] = brentq(lambda x: rated_power_dist(
                            x, Uhub[i], Omega_rpm[i]),
                                          pitch0,
                                          pitch0 + 10.,
                                          xtol=1e-4,
                                          rtol=1e-5,
                                          maxiter=40,
                                          disp=False)
                    except ValueError:
                        pitch[i] = minimize_scalar(
                            lambda x: np.abs(
                                rated_power_dist(x, Uhub[i], Omega_rpm[i])),
                            bounds=[pitch0 - 5., pitch0 + 15.],
                            method='bounded',
                            options={
                                'disp': False,
                                'xatol': 1e-3,
                                'maxiter': 40
                            })['x']

                    myout, _ = self.ccblade.evaluate([Uhub[i]], [Omega_rpm[i]],
                                                     [pitch[i]],
                                                     coefficients=True)
                    P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[
                        i], Cq_aero[i], Cm_aero[i] = [
                            myout[key] for key in
                            ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM']
                        ]
                    P[i], eff = compute_P_and_eff(P_aero[i], P_rated,
                                                  driveType, driveEta)
                    Cp[i] = Cp_aero[i] * eff
                    #P[i]       = P_rated

            else:
                P[i_3:] = P_rated
                T[i_3:] = 0
                Q[i_3:] = P[i_3:] / Omega[i_3:]
                M[i_3:] = 0
                pitch[i_3:] = 0
                Cp[i_3:] = P[i_3:] / (0.5 * inputs['rho'] * np.pi * R_tip**2 *
                                      Uhub[i_3:]**3)
                Ct_aero[i_3:] = 0
                Cq_aero[i_3:] = 0
                Cm_aero[i_3:] = 0

        outputs['T'] = T
        outputs['Q'] = Q
        outputs['Omega'] = Omega_rpm

        outputs['P'] = P
        outputs['Cp'] = Cp
        outputs['Cp_aero'] = Cp_aero
        outputs['Ct_aero'] = Ct_aero
        outputs['Cq_aero'] = Cq_aero
        outputs['Cm_aero'] = Cm_aero
        outputs['V'] = Uhub
        outputs['M'] = M
        outputs['pitch'] = pitch

        self.ccblade.induction_inflow = True
        tsr_vec = Omega_rpm / 30. * np.pi * R_tip / Uhub
        id_regII = np.argmin(abs(tsr_vec - inputs['tsr_operational']))
        loads, derivs = self.ccblade.distributedAeroLoads(
            Uhub[id_regII], Omega_rpm[id_regII], pitch[id_regII], 0.0)

        # outputs
        outputs['ax_induct_regII'] = loads['a']
        outputs['tang_induct_regII'] = loads['ap']
        outputs['aoa_regII'] = loads['alpha']
        outputs['cl_regII'] = loads['Cl']
        outputs['cd_regII'] = loads['Cd']
        outputs['Cp_regII'] = Cp_aero[id_regII]
Exemplo n.º 4
0
    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
        r = inputs['r']
        chord = inputs['chord']
        theta = inputs['theta']
        Rhub = inputs['Rhub']
        Rtip = inputs['Rtip']
        hub_height = inputs['hub_height']
        precone = inputs['precone']
        tilt = inputs['tilt']
        yaw = inputs['yaw']
        precurve = inputs['precurve']
        precurveTip = inputs['precurveTip']
        # airfoils = discrete_inputs['airfoils']
        B = discrete_inputs['nBlades']
        rho = inputs['rho']
        mu = inputs['mu']
        shearExp = inputs['shearExp']
        nSector = discrete_inputs['nSector']
        tiploss = discrete_inputs['tiploss']
        hubloss = discrete_inputs['hubloss']
        wakerotation = discrete_inputs['wakerotation']
        usecd = discrete_inputs['usecd']
        V_load = inputs['V_load']
        Omega_load = inputs['Omega_load']
        pitch_load = inputs['pitch_load']
        azimuth_load = inputs['azimuth_load']

        if len(precurve) == 0:
            precurve = np.zeros_like(r)

        # airfoil files
        af = [None] * self.n_span
        for i in range(self.n_span):
            af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'],
                              inputs['airfoils_cl'][i, :, :, 0],
                              inputs['airfoils_cd'][i, :, :, 0],
                              inputs['airfoils_cm'][i, :, :, 0])

        ccblade = CCBlade(r,
                          chord,
                          theta,
                          af,
                          Rhub,
                          Rtip,
                          B,
                          rho,
                          mu,
                          precone,
                          tilt,
                          yaw,
                          shearExp,
                          hub_height,
                          nSector,
                          precurve,
                          precurveTip,
                          tiploss=tiploss,
                          hubloss=hubloss,
                          wakerotation=wakerotation,
                          usecd=usecd,
                          derivatives=True)

        # distributed loads
        loads, self.derivs = ccblade.distributedAeroLoads(
            V_load, Omega_load, pitch_load, azimuth_load)
        Np = loads['Np']
        Tp = loads['Tp']

        # concatenate loads at root/tip
        outputs['loads_r'] = r

        # conform to blade-aligned coordinate system
        outputs['loads_Px'] = Np
        outputs['loads_Py'] = -Tp
        outputs['loads_Pz'] = 0 * Np
Exemplo n.º 5
0
class CCBladeLoads(ExplicitComponent):
    def initialize(self):
        self.options.declare('naero')
        self.options.declare('npower')

        self.options.declare('n_aoa_grid')
        self.options.declare('n_Re_grid')

    def setup(self):
        self.naero = naero = self.options['naero']
        npower = self.options['npower']
        n_aoa_grid = self.options['n_aoa_grid']
        n_Re_grid = self.options['n_Re_grid']
        """blade element momentum code"""

        # inputs
        self.add_input('V_load',
                       val=0.0,
                       units='m/s',
                       desc='hub height wind speed')
        self.add_input('Omega_load',
                       val=0.0,
                       units='rpm',
                       desc='rotor rotation speed')
        self.add_input('pitch_load',
                       val=0.0,
                       units='deg',
                       desc='blade pitch setting')
        self.add_input('azimuth_load',
                       val=0.0,
                       units='deg',
                       desc='blade azimuthal location')

        # outputs
        self.add_output('loads_r',
                        val=np.zeros(naero),
                        units='m',
                        desc='radial positions along blade going toward tip')
        self.add_output('loads_Px',
                        val=np.zeros(naero),
                        units='N/m',
                        desc='distributed loads in blade-aligned x-direction')
        self.add_output('loads_Py',
                        val=np.zeros(naero),
                        units='N/m',
                        desc='distributed loads in blade-aligned y-direction')
        self.add_output('loads_Pz',
                        val=np.zeros(naero),
                        units='N/m',
                        desc='distributed loads in blade-aligned z-direction')

        # corresponding setting for loads
        self.add_output('loads_V',
                        val=0.0,
                        units='m/s',
                        desc='hub height wind speed')
        self.add_output('loads_Omega',
                        val=0.0,
                        units='rpm',
                        desc='rotor rotation speed')
        self.add_output('loads_pitch',
                        val=0.0,
                        units='deg',
                        desc='pitch angle')
        self.add_output('loads_azimuth',
                        val=0.0,
                        units='deg',
                        desc='azimuthal angle')

        # (potential) variables
        self.add_input(
            'r',
            val=np.zeros(naero),
            units='m',
            desc=
            'radial locations where blade is defined (should be increasing and not go all the way to hub or tip)'
        )
        self.add_input('chord',
                       val=np.zeros(naero),
                       units='m',
                       desc='chord length at each section')
        self.add_input(
            'theta',
            val=np.zeros(naero),
            units='deg',
            desc=
            'twist angle at each section (positive decreases angle of attack)')
        self.add_input('Rhub', val=0.0, units='m', desc='hub radius')
        self.add_input('Rtip', val=0.0, units='m', desc='tip radius')
        self.add_input('hub_height', val=0.0, units='m', desc='hub height')
        self.add_input('precone', val=0.0, desc='precone angle', units='deg')
        self.add_input('tilt', val=0.0, desc='shaft tilt', units='deg')
        self.add_input('yaw', val=0.0, desc='yaw error', units='deg')

        # TODO: I've not hooked up the gradients for these ones yet.
        self.add_input('precurve',
                       val=np.zeros(naero),
                       units='m',
                       desc='precurve at each section')
        self.add_input('precurveTip',
                       val=0.0,
                       units='m',
                       desc='precurve at tip')

        # parameters
        # self.add_discrete_input('airfoils', val=[0]*naero, desc='CCAirfoil instances')
        self.add_input('airfoils_cl',
                       val=np.zeros((n_aoa_grid, naero, n_Re_grid)),
                       desc='lift coefficients, spanwise')
        self.add_input('airfoils_cd',
                       val=np.zeros((n_aoa_grid, naero, n_Re_grid)),
                       desc='drag coefficients, spanwise')
        self.add_input('airfoils_cm',
                       val=np.zeros((n_aoa_grid, naero, n_Re_grid)),
                       desc='moment coefficients, spanwise')
        self.add_input('airfoils_aoa',
                       val=np.zeros((n_aoa_grid)),
                       units='deg',
                       desc='angle of attack grid for polars')
        self.add_input('airfoils_Re',
                       val=np.zeros((n_Re_grid)),
                       desc='Reynolds numbers of polars')

        self.add_discrete_input('nBlades', val=0, desc='number of blades')
        self.add_input('rho', val=0.0, units='kg/m**3', desc='density of air')
        self.add_input('mu',
                       val=0.0,
                       units='kg/(m*s)',
                       desc='dynamic viscosity of air')
        self.add_input('shearExp', val=0.0, desc='shear exponent')
        self.add_discrete_input(
            'nSector',
            val=4,
            desc=
            'number of sectors to divide rotor face into in computing thrust and power'
        )
        self.add_discrete_input('tiploss',
                                val=True,
                                desc='include Prandtl tip loss model')
        self.add_discrete_input('hubloss',
                                val=True,
                                desc='include Prandtl hub loss model')
        self.add_discrete_input(
            'wakerotation',
            val=True,
            desc=
            'include effect of wake rotation (i.e., tangential induction factor is nonzero)'
        )
        self.add_discrete_input(
            'usecd',
            val=True,
            desc='use drag coefficient in computing induction factors')

        #self.declare_partials('loads_r', ['r', 'Rhub', 'Rtip'])
        #self.declare_partials(['loads_Px', 'loads_Py'],
        #                      ['r', 'chord', 'theta', 'Rhub', 'Rtip', 'hub_height', 'precone', 'tilt',
        #                       'yaw', 'V_load', 'Omega_load', 'pitch_load', 'azimuth_load', 'precurve'])
        #self.declare_partials('loads_V', 'V_load')
        #self.declare_partials('loads_Omega', 'Omega_load')
        #self.declare_partials('loads_pitch', 'pitch_load')
        #self.declare_partials('loads_azimuth', 'azimuth_load')

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
        self.r = inputs['r']
        self.chord = inputs['chord']
        self.theta = inputs['theta']
        self.Rhub = inputs['Rhub']
        self.Rtip = inputs['Rtip']
        self.hub_height = inputs['hub_height']
        self.precone = inputs['precone']
        self.tilt = inputs['tilt']
        self.yaw = inputs['yaw']
        self.precurve = inputs['precurve']
        self.precurveTip = inputs['precurveTip']
        # self.airfoils = discrete_inputs['airfoils']
        self.B = discrete_inputs['nBlades']
        self.rho = inputs['rho']
        self.mu = inputs['mu']
        self.shearExp = inputs['shearExp']
        self.nSector = discrete_inputs['nSector']
        self.tiploss = discrete_inputs['tiploss']
        self.hubloss = discrete_inputs['hubloss']
        self.wakerotation = discrete_inputs['wakerotation']
        self.usecd = discrete_inputs['usecd']
        self.V_load = inputs['V_load']
        self.Omega_load = inputs['Omega_load']
        self.pitch_load = inputs['pitch_load']
        self.azimuth_load = inputs['azimuth_load']

        if len(self.precurve) == 0:
            self.precurve = np.zeros_like(self.r)

        # airfoil files
        # n = len(self.airfoils)
        af = [None] * self.naero
        for i in range(self.naero):
            af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'],
                              inputs['airfoils_cl'][:, i, :],
                              inputs['airfoils_cd'][:, i, :],
                              inputs['airfoils_cm'][:, i, :])
        # af = self.airfoils

        self.ccblade = CCBlade(self.r,
                               self.chord,
                               self.theta,
                               af,
                               self.Rhub,
                               self.Rtip,
                               self.B,
                               self.rho,
                               self.mu,
                               self.precone,
                               self.tilt,
                               self.yaw,
                               self.shearExp,
                               self.hub_height,
                               self.nSector,
                               self.precurve,
                               self.precurveTip,
                               tiploss=self.tiploss,
                               hubloss=self.hubloss,
                               wakerotation=self.wakerotation,
                               usecd=self.usecd,
                               derivatives=True)

        # distributed loads
        Np, Tp, self.dNp, self.dTp \
            = self.ccblade.distributedAeroLoads(self.V_load, self.Omega_load, self.pitch_load, self.azimuth_load)

        # concatenate loads at root/tip
        outputs['loads_r'] = self.r

        # conform to blade-aligned coordinate system
        outputs['loads_Px'] = Np
        outputs['loads_Py'] = -Tp
        outputs['loads_Pz'] = 0 * Np

        # return other outputs needed
        outputs['loads_V'] = self.V_load
        outputs['loads_Omega'] = self.Omega_load
        outputs['loads_pitch'] = self.pitch_load
        outputs['loads_azimuth'] = self.azimuth_load
        '''