Exemple #1
0
class Cp_Ct_Cq_Tables(ExplicitComponent):
    def initialize(self):
        self.options.declare('modeling_options')
        # self.options.declare('n_span')
        # self.options.declare('n_pitch', default=20)
        # self.options.declare('n_tsr', default=20)
        # self.options.declare('n_U', default=1)
        # self.options.declare('n_aoa')
        # self.options.declare('n_re')

    def setup(self):
        modeling_options = self.options['modeling_options']
        rotorse_options = modeling_options['WISDEM']['RotorSE']
        self.n_span = n_span = rotorse_options['n_span']
        self.n_aoa = n_aoa = rotorse_options[
            'n_aoa']  # Number of angle of attacks
        self.n_Re = n_Re = rotorse_options[
            'n_Re']  # Number of Reynolds, so far hard set at 1
        self.n_tab = n_tab = rotorse_options[
            'n_tab']  # Number of tabulated data. For distributed aerodynamic control this could be > 1
        self.n_pitch = n_pitch = rotorse_options['n_pitch_perf_surfaces']
        self.n_tsr = n_tsr = rotorse_options['n_tsr_perf_surfaces']
        self.n_U = n_U = rotorse_options['n_U_perf_surfaces']
        self.min_TSR = rotorse_options['min_tsr_perf_surfaces']
        self.max_TSR = rotorse_options['max_tsr_perf_surfaces']
        self.min_pitch = rotorse_options['min_pitch_perf_surfaces']
        self.max_pitch = rotorse_options['max_pitch_perf_surfaces']

        # 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(
            '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_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('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_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.add_input('pitch_vector_in',
                       val=np.zeros(n_pitch),
                       units='deg',
                       desc='pitch vector specified by the user')
        self.add_input('tsr_vector_in',
                       val=np.zeros(n_tsr),
                       desc='tsr vector specified by the user')
        self.add_input('U_vector_in',
                       val=np.zeros(n_U),
                       units='m/s',
                       desc='wind vector specified by the user')

        # outputs
        self.add_output('Cp',
                        val=np.zeros((n_tsr, n_pitch, n_U)),
                        desc='table of aero power coefficient')
        self.add_output('Ct',
                        val=np.zeros((n_tsr, n_pitch, n_U)),
                        desc='table of aero thrust coefficient')
        self.add_output('Cq',
                        val=np.zeros((n_tsr, n_pitch, n_U)),
                        desc='table of aero torque coefficient')
        self.add_output('pitch_vector',
                        val=np.zeros(n_pitch),
                        units='deg',
                        desc='pitch vector used')
        self.add_output('tsr_vector',
                        val=np.zeros(n_tsr),
                        desc='tsr vector used')
        self.add_output('U_vector',
                        val=np.zeros(n_U),
                        units='m/s',
                        desc='wind vector used')

    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])

        n_pitch = self.n_pitch
        n_tsr = self.n_tsr
        n_U = self.n_U
        min_TSR = self.min_TSR
        max_TSR = self.max_TSR
        min_pitch = self.min_pitch
        max_pitch = self.max_pitch
        U_vector = inputs['U_vector_in']
        V_in = inputs['v_min']
        V_out = inputs['v_max']

        tsr_vector = inputs['tsr_vector_in']
        pitch_vector = inputs['pitch_vector_in']

        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'])

        if max(U_vector) == 0.:
            U_vector = np.linspace(V_in[0], V_out[0], n_U)
        if max(tsr_vector) == 0.:
            tsr_vector = np.linspace(min_TSR, max_TSR, n_tsr)
        if max(pitch_vector) == 0.:
            pitch_vector = np.linspace(min_pitch, max_pitch, n_pitch)

        outputs['pitch_vector'] = pitch_vector
        outputs['tsr_vector'] = tsr_vector
        outputs['U_vector'] = U_vector

        R = inputs['Rtip']
        k = 0
        for i in range(n_U):
            for j in range(n_tsr):
                k += 1
                # if k/2. == int(k/2.) :
                print('Cp-Ct-Cq surfaces completed at ' +
                      str(int(k / (n_U * n_tsr) * 100.)) + ' %')
                U = U_vector[i] * np.ones(n_pitch)
                Omega = tsr_vector[j] * U_vector[
                    i] / R * 30. / np.pi * np.ones(n_pitch)
                myout, _ = self.ccblade.evaluate(U,
                                                 Omega,
                                                 pitch_vector,
                                                 coefficients=True)
                outputs['Cp'][j, :, i], outputs['Ct'][j, :, i], outputs['Cq'][
                    j, :, i] = [myout[key] for key in ['CP', 'CT', 'CQ']]
Exemple #2
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["WISDEM"]["RotorSE"]["n_span"]
        self.n_aoa = n_aoa = modeling_options["WISDEM"]["RotorSE"][
            "n_aoa"]  # Number of angle of attacks
        self.n_Re = n_Re = modeling_options["WISDEM"]["RotorSE"][
            "n_Re"]  # Number of Reynolds, so far hard set at 1
        self.n_tab = n_tab = modeling_options["WISDEM"]["RotorSE"][
            "n_tab"]  # Number of tabulated data. For distributed aerodynamic control this could be > 1
        self.regulation_reg_III = modeling_options["WISDEM"]["RotorSE"][
            "regulation_reg_III"]
        self.n_pc = modeling_options["WISDEM"]["RotorSE"]["n_pc"]
        self.n_pc_spline = modeling_options["WISDEM"]["RotorSE"]["n_pc_spline"]

        # 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=1.0)
        self.add_input(
            "generator_efficiency",
            val=np.ones(self.n_pc),
            desc=
            "Generator efficiency at various rpm values to support table lookup",
        )
        self.add_input(
            "lss_rpm",
            val=np.zeros(self.n_pc),
            units="rpm",
            desc=
            "Low speed shaft RPM values at which the generator efficiency values are given",
        )

        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("P_aero",
                        val=np.zeros(self.n_pc),
                        units="W",
                        desc="rotor mechanical 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("rated_mech",
                        val=0.0,
                        units="W",
                        desc="Mechanical shaft power 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.add_output("rated_efficiency",
                        val=1.0,
                        desc="Efficiency at rated conditions")

        # 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.0, np.pi / 2.0,
                                    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"]

        # 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.0
        ])

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

        # Create table lookup of total drivetrain efficiency, where rpm is first column and second column is gearbox*generator
        lss_rpm = inputs["lss_rpm"]
        gen_eff = inputs["generator_efficiency"]
        if not np.any(lss_rpm):
            lss_rpm = np.linspace(np.maximum(0.1, Omega_rpm[0]), Omega_rpm[-1],
                                  self.n_pc)
            _, gen_eff = compute_P_and_eff(P_rated * lss_rpm / lss_rpm[-1],
                                           P_rated, np.zeros(self.n_pc),
                                           driveType, np.zeros((self.n_pc, 2)))

        # driveEta  = np.c_[lss_rpm, float(inputs['gearbox_efficiency'])*gen_eff]
        driveEta = float(inputs["gearbox_efficiency"]) * gen_eff

        # 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, Omega_rpm, driveType, driveEta)
        eff = np.interp(Omega_rpm, lss_rpm, driveEta)
        P = P_aero * eff
        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.0, pitch0 + 10.0]
            pitch[i] = minimize_scalar(
                lambda x: maximizePower(x, Uhub[i], Omega_rpm[i]),
                bounds=bnds,
                method="bounded",
                options={
                    "disp": False,
                    "xatol": TOL,
                    "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[i] = compute_P_and_eff(P_aero[i], P_rated, Omega_rpm[i], driveType, driveEta)
            eff[i] = np.interp(Omega_rpm[i], lss_rpm, driveEta)
            P[i] = P_aero[i] * eff[i]
            Cp[i] = Cp_aero[i] * eff[i]

            # 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_i = x[0]
                Uhub_i = x[1]
                Omega_i = min([Uhub_i * tsr / R_tip, Omega_max])
                Omega_i_rpm = Omega_i * 30.0 / np.pi
                myout, _ = self.ccblade.evaluate([Uhub_i], [Omega_i_rpm],
                                                 [pitch_i],
                                                 coefficients=False)
                P_aero_i = float(myout["P"])
                # P_i,_  = compute_P_and_eff(P_aero_i.flatten(), P_rated, Omega_i_rpm, driveType, driveEta)
                eff_i = np.interp(Omega_i_rpm, lss_rpm, driveEta)
                P_i = float(P_aero_i * eff_i)
                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=TOL)

                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-1 * TOL,
                        rtol=1e-2 * TOL,
                        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": TOL,
                            "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.0 / 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[i] = compute_P_and_eff(P_aero[i], P_rated, Omega_rpm[i], driveType, driveEta)
            eff[i] = np.interp(Omega_rpm[i], lss_rpm, driveEta)
            P[i] = P_aero[i] * eff[i]
            Cp[i] = Cp_aero[i] * eff[i]
            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]
        outputs["rated_mech"] = P_aero[i]
        outputs["rated_efficiency"] = eff[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_i, Uhub_i, Omega_rpm_i):
                myout, _ = self.ccblade.evaluate([Uhub_i], [Omega_rpm_i],
                                                 [pitch_i],
                                                 coefficients=False)
                P_aero_i = myout["P"]
                # P_i, _   = compute_P_and_eff(P_aero_i, P_rated, Omega_rpm_i, driveType, driveEta)
                eff_i = np.interp(Omega_rpm_i, lss_rpm, driveEta)
                P_i = P_aero_i * eff_i
                return P_i - 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.0,
                            xtol=1e-1 * TOL,
                            rtol=1e-2 * TOL,
                            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.0, pitch0 + 15.0],
                            method="bounded",
                            options={
                                "disp": False,
                                "xatol": TOL,
                                "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[i] = compute_P_and_eff(P_aero[i], P_rated, Omega_rpm[i], driveType, driveEta)
                    eff[i] = np.interp(Omega_rpm[i], lss_rpm, driveEta)
                    P[i] = P_aero[i] * eff[i]
                    Cp[i] = Cp_aero[i] * eff[i]
                    # 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["P_aero"] = P_aero
        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.0 * 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]
Exemple #3
0
class Rotor:
    def __init__(self, turbine, w, old=False):
        '''
        >>>> add mean offset parameters add move this to runCCBlade<<<<
        '''

        # Should inherit these from raft_model or _env?
        self.w = np.array(w)

        self.Zhub = turbine['Zhub']  # [m]
        self.shaft_tilt = turbine['shaft_tilt']  # [deg]
        self.overhang = turbine['overhang']
        self.R_rot = turbine['blade']['Rtip']  # rotor radius [m]
        #yaw  = 0

        self.Uhub = np.array(turbine['wt_ops']['v'])
        self.Omega_rpm = np.array(turbine['wt_ops']['omega_op'])
        self.pitch_deg = np.array(turbine['wt_ops']['pitch_op'])
        self.I_drivetrain = float(turbine['I_drivetrain'])

        self.aeroServoMod = getFromDict(
            turbine, 'aeroServoMod', default=1
        )  # flag for aeroservodynamics (0=none, 1=aero only, 2=aero and control)

        # Add parked pitch, rotor speed, assuming fully shut down by 40% above cut-out
        self.Uhub = np.r_[self.Uhub, self.Uhub.max() * 1.4, 100]
        self.Omega_rpm = np.r_[self.Omega_rpm, 0, 0]
        self.pitch_deg = np.r_[self.pitch_deg, 90, 90]

        # Set default control gains
        self.kp_0 = np.zeros_like(self.Uhub)
        self.ki_0 = np.zeros_like(self.Uhub)
        self.k_float = 0  # np.zeros_like(self.Uhub), right now this is a single value, but this may change    <<< what is this?

        # Set CCBlade flags
        tiploss = True  # Tip loss model True/False
        hubloss = True  # Hub loss model, True/False
        wakerotation = True  # Wake rotation, True/False
        usecd = True  # Use drag coefficient within BEMT, True/False

        # Set discretization parameters
        nSector = 4  # [-] - number of equally spaced azimuthal positions where CCBlade should be interrogated. The results are averaged across the n positions. 4 is a good first guess
        n_span = 30  # [-] - number of blade stations along span
        grid = np.linspace(
            0., 1.,
            n_span)  # equally spaced grid along blade span, root=0 tip=1

        # ----- AIRFOIL STUFF ------
        n_aoa = 200  # [-] - number of angles of attack to discretize airfoil polars - MUST BE MULTIPLE OF 4
        n_af = len(turbine["airfoils"])
        af_used = [b for [a, b] in turbine['blade']["airfoils"]]
        af_position = [a for [a, b] in turbine['blade']["airfoils"]]
        #af_used     = turbine['blade']["airfoils"]["labels"]
        #af_position = turbine['blade']["airfoils"]["grid"]
        n_af_span = len(af_used)

        # One fourth of the angles of attack from -pi to -pi/6, half between -pi/6 to pi/6, and one fourth from pi/6 to pi
        aoa = np.unique(
            np.hstack([
                np.linspace(-180, -30, int(n_aoa / 4.0 + 1)),
                np.linspace(
                    -30,
                    30,
                    int(n_aoa / 2.0),
                ),
                np.linspace(30, 180, int(n_aoa / 4.0 + 1))
            ]))

        af_name = n_af * [""]
        r_thick = np.zeros(n_af)
        for i in range(n_af):
            af_name[i] = turbine["airfoils"][i]["name"]
            r_thick[i] = turbine["airfoils"][i]["relative_thickness"]

        cl = np.zeros((n_af, n_aoa, 1))
        cd = np.zeros((n_af, n_aoa, 1))
        cm = np.zeros((n_af, n_aoa, 1))

        # Interp cl-cd-cm along predefined grid of angle of attack
        for i in range(n_af):

            #cl[i, :, 0] = np.interp(aoa, turbine["airfoils"][i]["alpha"], turbine["airfoils"][i]["c_l"])
            #cd[i, :, 0] = np.interp(aoa, turbine["airfoils"][i]["alpha"], turbine["airfoils"][i]["c_d"])
            #cm[i, :, 0] = np.interp(aoa, turbine["airfoils"][i]["alpha"], turbine["airfoils"][i]["c_m"])

            polar_table = np.array(turbine["airfoils"][i]['data'])

            # Note: polar_table[:,0] must be in degrees
            cl[i, :, 0] = np.interp(aoa, polar_table[:, 0], polar_table[:, 1])
            cd[i, :, 0] = np.interp(aoa, polar_table[:, 0], polar_table[:, 2])
            cm[i, :, 0] = np.interp(aoa, polar_table[:, 0], polar_table[:, 3])

            #plt.figure()
            #plt.plot(polar_table[:,0], polar_table[:,1])
            #plt.plot(polar_table[:,0], polar_table[:,2])
            #plt.title(af_name[i])

            if abs(cl[i, 0, 0] - cl[i, -1, 0]) > 1.0e-5:
                print(
                    "WARNING: Ai " + af_name[i] +
                    " has the lift coefficient different between + and - pi rad. This is fixed automatically, but please check the input data."
                )
                cl[i, 0, 0] = cl[i, -1, 0]
            if abs(cd[i, 0, 0] - cd[i, -1, 0]) > 1.0e-5:
                print(
                    "WARNING: Airfoil " + af_name[i] +
                    " has the drag coefficient different between + and - pi rad. This is fixed automatically, but please check the input data."
                )
                cd[i, 0, 0] = cd[i, -1, 0]
            if abs(cm[i, 0, 0] - cm[i, -1, 0]) > 1.0e-5:
                print(
                    "WARNING: Airfoil " + af_name[i] +
                    " has the moment coefficient different between + and - pi rad. This is fixed automatically, but please check the input data."
                )
                cm[i, 0, 0] = cm[i, -1, 0]

        # Interpolate along blade span using a pchip on relative thickness
        r_thick_used = np.zeros(n_af_span)
        cl_used = np.zeros((n_af_span, n_aoa, 1))
        cd_used = np.zeros((n_af_span, n_aoa, 1))
        cm_used = np.zeros((n_af_span, n_aoa, 1))

        for i in range(n_af_span):
            for j in range(n_af):
                if af_used[i] == af_name[j]:
                    r_thick_used[i] = r_thick[j]
                    cl_used[i, :, :] = cl[j, :, :]
                    cd_used[i, :, :] = cd[j, :, :]
                    cm_used[i, :, :] = cm[j, :, :]
                    break

        # Pchip does have an associated derivative method built-in:
        # https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PchipInterpolator.derivative.html#scipy.interpolate.PchipInterpolator.derivative
        spline = PchipInterpolator
        rthick_spline = spline(af_position, r_thick_used)
        # GB: HAVE TO TALK TO PIETRO ABOUT THIS
        #r_thick_interp = rthick_spline(grid[1:-1])
        r_thick_interp = rthick_spline(grid)

        # Spanwise interpolation of the airfoil polars with a pchip
        r_thick_unique, indices = np.unique(r_thick_used, return_index=True)
        cl_spline = spline(r_thick_unique, cl_used[indices, :, :])
        self.cl_interp = np.flip(cl_spline(np.flip(r_thick_interp)), axis=0)
        cd_spline = spline(r_thick_unique, cd_used[indices, :, :])
        self.cd_interp = np.flip(cd_spline(np.flip(r_thick_interp)), axis=0)
        cm_spline = spline(r_thick_unique, cm_used[indices, :, :])
        self.cm_interp = np.flip(cm_spline(np.flip(r_thick_interp)), axis=0)

        self.aoa = aoa

        # split out blade geometry info from table
        geometry_table = np.array(turbine['blade']['geometry'])
        blade_r = geometry_table[:, 0]
        blade_chord = geometry_table[:, 1]
        blade_theta = geometry_table[:, 2]
        blade_precurve = geometry_table[:, 3]
        blade_presweep = geometry_table[:, 4]

        af = []
        for i in range(self.cl_interp.shape[0]):
            af.append(
                CCAirfoil(self.aoa, [], self.cl_interp[i, :, :],
                          self.cd_interp[i, :, :], self.cm_interp[i, :, :]))

        self.ccblade = CCBlade(
            blade_r,  # (m) locations defining the blade along z-axis of blade coordinate system
            blade_chord,  # (m) corresponding chord length at each section
            blade_theta,  # (deg) corresponding :ref:`twist angle <blade_airfoil_coord>` at each section---positive twist decreases angle of attack.
            af,  # CCAirfoil object
            turbine['Rhub'],  # (m) radius of hub
            turbine['blade']['Rtip'],  # (m) radius of tip
            turbine['nBlades'],  # number of blades
            turbine['rho_air'],  # (kg/m^3) freestream fluid density
            turbine['mu_air'],  # (kg/m/s) dynamic viscosity of fluid
            turbine['precone'],  # (deg) hub precone angle
            self.shaft_tilt,  # (deg) hub tilt angle
            0.0,  # (deg) nacelle yaw angle
            turbine[
                'shearExp'],  # shear exponent for a power-law wind profile across hub
            turbine[
                'Zhub'],  # (m) hub height used for power-law wind profile.  U = Uref*(z/hubHt)**shearExp
            nSector,  # number of azimuthal sectors to descretize aerodynamic calculation.  automatically set to 1 if tilt, yaw, and shearExp are all 0.0.  Otherwise set to a minimum of 4.
            blade_precurve,  # (m) location of blade pitch axis in x-direction of :ref:`blade coordinate system <azimuth_blade_coord>`
            turbine['blade']
            ['precurveTip'],  # (m) location of blade pitch axis in x-direction at the tip (analogous to Rtip)
            blade_presweep,  # (m) location of blade pitch axis in y-direction of :ref:`blade coordinate system <azimuth_blade_coord>`
            turbine['blade']
            ['presweepTip'],  # (m) location of blade pitch axis in y-direction at the tip (analogous to Rtip)
            tiploss=tiploss,  # if True, include Prandtl tip loss model
            hubloss=hubloss,  # if True, include Prandtl hub loss model
            wakerotation=
            wakerotation,  # if True, include effect of wake rotation (i.e., tangential induction factor is nonzero)
            usecd=
            usecd,  # If True, use drag coefficient in computing induction factors (always used in evaluating distributed loads from the induction factors).
            derivatives=
            True,  # if True, derivatives along with function values will be returned for the various methods
        )

        # pull control gains out of dictionary
        self.setControlGains(turbine)

    def runCCBlade(self, Uhub, ptfm_pitch=0, yaw_misalign=0):
        '''This performs a single CCBlade evaluation at specified conditions.
        
        ptfm_pitch
            mean platform pitch angle to be included in rotor tilt angle [rad]
        yaw_misalign
            turbine yaw misalignment angle [deg]
        '''

        # find turbine operating point at the provided wind speed
        Omega_rpm = np.interp(Uhub, self.Uhub,
                              self.Omega_rpm)  # rotor speed [rpm]
        pitch_deg = np.interp(Uhub, self.Uhub,
                              self.pitch_deg)  # blade pitch angle [deg]

        # adjust rotor angles based on provided info (I think this intervention in CCBlade should work...)
        self.ccblade.tilt = np.deg2rad(self.shaft_tilt) + ptfm_pitch
        self.ccblade.yaw = np.deg2rad(yaw_misalign)

        # evaluate aero loads and derivatives with CCBlade
        loads, derivs = self.ccblade.evaluate(Uhub,
                                              Omega_rpm,
                                              pitch_deg,
                                              coefficients=True)

        # organize and save the relevant outputs...
        self.U_case = Uhub
        self.Omega_case = Omega_rpm
        self.aero_torque = loads["Q"][0]
        self.aero_power = loads["P"][0]
        self.pitch_case = pitch_deg

        outputs = {}

        outputs["P"] = loads["P"]
        outputs["Mb"] = loads["Mb"]
        outputs["CP"] = loads["CP"]
        outputs["CMb"] = loads["CMb"]
        outputs["Fhub"] = np.array(
            [loads["T"][0], loads["Y"][0], loads["Z"][0]])
        outputs["Mhub"] = np.array(
            [loads["Q"][0], loads["My"][0], loads["Mz"][0]])
        outputs["CFhub"] = np.array(
            [loads["CT"][0], loads["CY"][0], loads["CZ"][0]])
        outputs["CMhub"] = np.array(
            [loads["CQ"][0], loads["CMy"][0], loads["CMz"][0]])

        print(
            f"Wind speed: {Uhub} m/s, Aerodynamic power coefficient: {loads['CP'][0]:4.3f}"
        )

        J = {}  # Jacobian/derivatives

        dP = derivs["dP"]
        J["P", "r"] = dP["dr"]
        # J["P", "chord"] = dP["dchord"]
        # J["P", "theta"] = dP["dtheta"]
        # J["P", "Rhub"] = np.squeeze(dP["dRhub"])
        # J["P", "Rtip"] = np.squeeze(dP["dRtip"])
        # J["P", "hub_height"] = np.squeeze(dP["dhubHt"])
        # J["P", "precone"] = np.squeeze(dP["dprecone"])
        # J["P", "tilt"] = np.squeeze(dP["dtilt"])
        # J["P", "yaw"] = np.squeeze(dP["dyaw"])
        # J["P", "shearExp"] = np.squeeze(dP["dshear"])
        # J["P", "V_load"] = np.squeeze(dP["dUinf"])
        # J["P", "Omega_load"] = np.squeeze(dP["dOmega"])
        # J["P", "pitch_load"] = np.squeeze(dP["dpitch"])
        # J["P", "precurve"] = dP["dprecurve"]
        # J["P", "precurveTip"] = dP["dprecurveTip"]
        # J["P", "presweep"] = dP["dpresweep"]
        # J["P", "presweepTip"] = dP["dpresweepTip"]

        dQ = derivs["dQ"]
        J["Q", "Uhub"] = np.atleast_1d(np.diag(dQ["dUinf"]))
        J["Q", "pitch_deg"] = np.atleast_1d(np.diag(dQ["dpitch"]))
        J["Q", "Omega_rpm"] = np.atleast_1d(np.diag(dQ["dOmega"]))

        dT = derivs["dT"]
        J["T", "Uhub"] = np.atleast_1d(np.diag(dT["dUinf"]))
        J["T", "pitch_deg"] = np.atleast_1d(np.diag(dT["dpitch"]))
        J["T", "Omega_rpm"] = np.atleast_1d(np.diag(dT["dOmega"]))

        # dT = derivs["dT"]
        # .J["Fhub", "r"][0,:] = dT["dr"]     # 0 is for thrust force, 1 would be y, 2 z
        # .J["Fhub", "chord"][0,:] = dT["dchord"]
        # .J["Fhub", "theta"][0,:] = dT["dtheta"]
        # .J["Fhub", "Rhub"][0,:] = np.squeeze(dT["dRhub"])
        # .J["Fhub", "Rtip"][0,:] = np.squeeze(dT["dRtip"])
        # .J["Fhub", "hub_height"][0,:] = np.squeeze(dT["dhubHt"])
        # .J["Fhub", "precone"][0,:] = np.squeeze(dT["dprecone"])
        # .J["Fhub", "tilt"][0,:] = np.squeeze(dT["dtilt"])
        # .J["Fhub", "yaw"][0,:] = np.squeeze(dT["dyaw"])
        # .J["Fhub", "shearExp"][0,:] = np.squeeze(dT["dshear"])
        # .J["Fhub", "V_load"][0,:] = np.squeeze(dT["dUinf"])
        # .J["Fhub", "Omega_load"][0,:] = np.squeeze(dT["dOmega"])
        # .J["Fhub", "pitch_load"][0,:] = np.squeeze(dT["dpitch"])
        # .J["Fhub", "precurve"][0,:] = dT["dprecurve"]
        # .J["Fhub", "precurveTip"][0,:] = dT["dprecurveTip"]
        # .J["Fhub", "presweep"][0,:] = dT["dpresweep"]
        # .J["Fhub", "presweepTip"][0,:] = dT["dpresweepTip"]

        self.J = J

        return loads, derivs

    def setControlGains(self, turbine):
        '''
        Use flipped sign version of ROSCO
        '''

        # Convert gain-scheduling wrt pitch to wind speed, Add zero gains for parked "control"
        pc_angles = np.array(turbine['pitch_control']['GS_Angles']) * rad2deg
        self.kp_0 = np.interp(self.pitch_deg,
                              pc_angles,
                              turbine['pitch_control']['GS_Kp'],
                              left=0,
                              right=0)
        self.ki_0 = np.interp(self.pitch_deg,
                              pc_angles,
                              turbine['pitch_control']['GS_Ki'],
                              left=0,
                              right=0)
        self.k_float = -turbine['pitch_control']['Fl_Kp']

        # Torque control
        self.kp_tau = -turbine['torque_control']['VS_KP']
        self.ki_tau = -turbine['torque_control']['VS_KI']
        self.Ng = turbine['gear_ratio']

    def calcAeroServoContributions(self, case, ptfm_pitch=0, display=0):
Exemple #4
0
if plot_flag:
    plt.plot(rstar, Tp / 1e3, label="lead-lag")
    plt.plot(rstar, Np / 1e3, label="flapwise")
    plt.xlabel("blade fraction")
    plt.ylabel("distributed aerodynamic loads (kN)")
    plt.legend(loc="upper left")
    plt.grid()
    plt.show()
# 5 ----------

# plt.savefig('/Users/sning/Dropbox/NREL/SysEng/TWISTER/ccblade-beta-setup/docs/images/distributedAeroLoads.pdf')
# plt.savefig('/Users/sning/Dropbox/NREL/SysEng/TWISTER/ccblade-beta-setup/docs/images/distributedAeroLoads.png')

# 6 ----------

outputs, derivs = rotor.evaluate([Uinf], [Omega], [pitch])

P = outputs["P"]
T = outputs["T"]
Q = outputs["Q"]

outputs, derivs = rotor.evaluate([Uinf], [Omega], [pitch], coefficients=True)

CP = outputs["CP"]
CT = outputs["CT"]
CQ = outputs["CQ"]

print("CP =", CP)
print("CT =", CT)
print("CQ =", CQ)
Exemple #5
0
dNp_dRhub = dNp["dRhub"]
dNp_dRtip = dNp["dRtip"]
dNp_dprecone = dNp["dprecone"]
dNp_dtilt = dNp["dtilt"]
dNp_dhubHt = dNp["dhubHt"]
dNp_dyaw = dNp["dyaw"]
dNp_dazimuth = dNp["dazimuth"]
dNp_dUinf = dNp["dUinf"]
dNp_dOmega = dNp["dOmega"]
dNp_dpitch = dNp["dpitch"]

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

# 6 ----------

loads, derivs = rotor.evaluate([Uinf], [Omega], [pitch])
P = loads["P"]
T = loads["T"]
Q = loads["Q"]

dP = derivs["dP"]
dT = derivs["dT"]
dQ = derivs["dQ"]

npts = len(P)

# npts x 1
dP_dprecone = dP["dprecone"]
dP_dtilt = dP["dtilt"]
dP_dhubHt = dP["dhubHt"]
dP_dRhub = dP["dRhub"]
Exemple #6
0
class TestNREL5MW(unittest.TestCase):
    def setUp(self):

        # geometry
        Rhub = 1.5
        Rtip = 63.0

        r = np.array(
            [
                2.8667,
                5.6000,
                8.3333,
                11.7500,
                15.8500,
                19.9500,
                24.0500,
                28.1500,
                32.2500,
                36.3500,
                40.4500,
                44.5500,
                48.6500,
                52.7500,
                56.1667,
                58.9000,
                61.6333,
            ]
        )
        chord = np.array(
            [
                3.542,
                3.854,
                4.167,
                4.557,
                4.652,
                4.458,
                4.249,
                4.007,
                3.748,
                3.502,
                3.256,
                3.010,
                2.764,
                2.518,
                2.313,
                2.086,
                1.419,
            ]
        )
        theta = np.array(
            [
                13.308,
                13.308,
                13.308,
                13.308,
                11.480,
                10.162,
                9.011,
                7.795,
                6.544,
                5.361,
                4.188,
                3.125,
                2.319,
                1.526,
                0.863,
                0.370,
                0.106,
            ]
        )
        B = 3  # number of blades

        # atmosphere
        rho = 1.225
        mu = 1.81206e-5

        afinit = CCAirfoil.initFromAerodynFile  # just for shorthand
        basepath = path.join(path.dirname(path.realpath(__file__)), "../../../examples/_airfoil_files")

        # load all airfoils
        airfoil_types = [0] * 8
        airfoil_types[0] = afinit(path.join(basepath, "Cylinder1.dat"))
        airfoil_types[1] = afinit(path.join(basepath, "Cylinder2.dat"))
        airfoil_types[2] = afinit(path.join(basepath, "DU40_A17.dat"))
        airfoil_types[3] = afinit(path.join(basepath, "DU35_A17.dat"))
        airfoil_types[4] = afinit(path.join(basepath, "DU30_A17.dat"))
        airfoil_types[5] = afinit(path.join(basepath, "DU25_A17.dat"))
        airfoil_types[6] = afinit(path.join(basepath, "DU21_A17.dat"))
        airfoil_types[7] = afinit(path.join(basepath, "NACA64_A17.dat"))

        # place at appropriate radial stations
        af_idx = [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7]

        af = [0] * len(r)
        for i in range(len(r)):
            af[i] = airfoil_types[af_idx[i]]

        tilt = -5.0
        precone = 2.5
        yaw = 0.0

        # create CCBlade object
        self.rotor = CCBlade(r, chord, theta, af, Rhub, Rtip, B, rho, mu, precone, tilt, yaw, shearExp=0.2, hubHt=90.0)

    def test_thrust_torque(self):

        Uinf = np.array([3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25])
        Omega = np.array(
            [
                6.972,
                7.183,
                7.506,
                7.942,
                8.469,
                9.156,
                10.296,
                11.431,
                11.890,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
                12.100,
            ]
        )
        pitch = np.array(
            [
                0.000,
                0.000,
                0.000,
                0.000,
                0.000,
                0.000,
                0.000,
                0.000,
                0.000,
                3.823,
                6.602,
                8.668,
                10.450,
                12.055,
                13.536,
                14.920,
                16.226,
                17.473,
                18.699,
                19.941,
                21.177,
                22.347,
                23.469,
            ]
        )

        Pref = np.array(
            [
                42.9,
                188.2,
                427.9,
                781.3,
                1257.6,
                1876.2,
                2668.0,
                3653.0,
                4833.2,
                5296.6,
                5296.6,
                5296.6,
                5296.6,
                5296.6,
                5296.6,
                5296.6,
                5296.6,
                5296.7,
                5296.6,
                5296.7,
                5296.6,
                5296.6,
                5296.7,
            ]
        )
        Tref = np.array(
            [
                171.7,
                215.9,
                268.9,
                330.3,
                398.6,
                478.0,
                579.2,
                691.5,
                790.6,
                690.0,
                608.4,
                557.9,
                520.5,
                491.2,
                467.7,
                448.4,
                432.3,
                418.8,
                406.7,
                395.3,
                385.1,
                376.7,
                369.3,
            ]
        )
        Qref = np.array(
            [
                58.8,
                250.2,
                544.3,
                939.5,
                1418.1,
                1956.9,
                2474.5,
                3051.1,
                3881.3,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
                4180.1,
            ]
        )

        m_rotor = 110.0  # kg
        g = 9.81
        tilt = 5 * math.pi / 180.0
        Tref -= m_rotor * g * math.sin(tilt)  # remove weight of rotor that is included in reported results

        outputs, derivs = self.rotor.evaluate(Uinf, Omega, pitch)
        P, T, Q = [outputs[key] for key in ("P", "T", "Q")]

        # import matplotlib.pyplot as plt
        # plt.plot(Uinf, P/1e6)
        # plt.plot(Uinf, Pref/1e3)
        # plt.figure()
        # plt.plot(Uinf, T/1e6)
        # plt.plot(Uinf, Tref/1e3)
        # plt.show()

        idx = Uinf < 15
        np.testing.assert_allclose(Q[idx] / 1e6, Qref[idx] / 1e3, atol=0.15)
        np.testing.assert_allclose(P[idx] / 1e6, Pref[idx] / 1e3, atol=0.2)  # within 0.2 of 1MW
        np.testing.assert_allclose(T[idx] / 1e6, Tref[idx] / 1e3, atol=0.15)