Ejemplo n.º 1
0
    def compute(self, desvars):
        with open(run_dir + f"CCBlade_inputs_{self.n_span}.pkl", "rb") as f:
            saved_dict = dill.load(f)

        chord_opt_gain = desvars["blade.opt_var.chord_opt_gain"]

        chord_original = saved_dict["chord_original"]
        s = saved_dict["s"]
        s_opt_chord = np.linspace(0.0, 1.0, len(chord_opt_gain))

        spline = PchipInterpolator
        chord_spline = spline(s_opt_chord, chord_opt_gain)
        chord = chord_original * chord_spline(s)

        get_cp_cm = CCBladeOrig(
            saved_dict["r"],
            chord,
            saved_dict["twist"],
            saved_dict["af"],
            saved_dict["Rhub"],
            saved_dict["Rtip"],
            saved_dict["nBlades"],
            saved_dict["rho"],
            saved_dict["mu"],
            saved_dict["precone"],
            saved_dict["tilt"],
            saved_dict["yaw"],
            saved_dict["shearExp"],
            saved_dict["hub_height"],
            saved_dict["nSector"],
            saved_dict["precurve"],
            saved_dict["precurveTip"],
            saved_dict["presweep"],
            saved_dict["presweepTip"],
            saved_dict["tiploss"],
            saved_dict["hubloss"],
            saved_dict["wakerotation"],
            saved_dict["usecd"],
        )
        get_cp_cm.inverse_analysis = False
        get_cp_cm.induction = True

        # Compute omega given TSR
        Omega = (saved_dict["Uhub"] * saved_dict["tsr"] / saved_dict["Rtip"] *
                 30.0 / np.pi)

        myout, derivs = get_cp_cm.evaluate([saved_dict["Uhub"]], [Omega],
                                           [saved_dict["pitch"]],
                                           coefficients=True)

        outputs = {}
        outputs["CP"] = myout["CP"]

        return outputs
Ejemplo n.º 2
0
class Performance_coef(unittest.TestCase):
    def setup(self):
        """
        Parameters
        ----------
        r : array_like (m)
            locations defining the blade along z-axis of :ref:`blade coordinate system <azimuth_blade_coord>`
            (values should be increasing).
        chord : array_like (m)
            corresponding chord length at each section
        theta : array_like (deg)
            corresponding :ref:`twist angle <blade_airfoil_coord>` at each section---
            positive twist decreases angle of attack.
        Rhub : float (m)
            location of hub
        Rtip : float (m)
            location of tip
        B : int, optional
            number of blades
        rho : float, optional (kg/m^3)
            freestream fluid density
        mu : float, optional (kg/m/s)
            dynamic viscosity of fluid
        precone : float, optional (deg)
            :ref:`hub precone angle <azimuth_blade_coord>`
        tilt : float, optional (deg)
            nacelle :ref:`tilt angle <yaw_hub_coord>`
        yaw : float, optional (deg)
            nacelle :ref:`yaw angle<wind_yaw_coord>`
        shearExp : float, optional
            shear exponent for a power-law wind profile across hub
        hubHt : float, optional
            hub height used for power-law wind profile.
            U = Uref*(z/hubHt)**shearExp
        nSector : int, optional
            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.
        """
        self.Rhub = 1.5
        self.Rtip = 63
        self.r = np.array([
            self.Rtip * 0.045503175, self.Rtip * 0.088888889,
            self.Rtip * 0.132274603, self.Rtip * 0.186507937,
            self.Rtip * 0.251587302, self.Rtip * 0.316666667,
            self.Rtip * 0.381746032, self.Rtip * 0.446825397,
            self.Rtip * 0.511904762, self.Rtip * 0.576984127,
            self.Rtip * 0.642063492, self.Rtip * 0.707142857,
            self.Rtip * 0.772222222, self.Rtip * 0.837301587,
            self.Rtip * 0.891534921, self.Rtip * 0.934920635,
            self.Rtip * 0.978306349
        ])

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

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

        # load all airfoils
        airfoil_types = [0] * 8
        airfoil_types[0] = afinit(basepath + 'Cylinder1.dat')
        airfoil_types[1] = afinit(basepath + 'Cylinder2.dat')
        airfoil_types[2] = afinit(basepath + 'DU40_A17.dat')
        airfoil_types[3] = afinit(basepath + 'DU35_A17.dat')
        airfoil_types[4] = afinit(basepath + 'DU30_A17.dat')
        airfoil_types[5] = afinit(basepath + 'DU25_A17.dat')
        airfoil_types[6] = afinit(basepath + 'DU21_A17.dat')
        airfoil_types[7] = afinit(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(self.r)
        for i in range(len(self.r)):
            af[i] = airfoil_types[af_idx[i]]

        B = 3  # number of blades
        # atmosphere
        rho = 1.225
        mu = 1.81206e-5

        tilt = 5.0
        precone = 2.5
        yaw = 0.0
        shearExp = 0.2
        hubHt = (self.Rtip * 2) * 0.92
        nSector = 8

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

    def compute(self):
        ''' 
        Uinf : array_like (m/s)
            hub height wind speed
        Omega : array_like (RPM)
            rotor rotation speed
        pitch : array_like (deg)
            blade pitch setting
        '''
        # set conditions
        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
        ])

        P, T, Q, M, CP, CT, CQ, CM = self.aeroanalysis.evaluate(
            Uinf, Omega, pitch, coefficients=True)

        print('CP:' + str(CP))

        path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                            "Output\Cp_res.txt")

        with open(path, 'w') as f:
            f.write('Cp:\n')
            for i in CP:
                f.write("%s\n" % str(i))
            f.write('\n')
            f.write('Radius: \n')
            f.write(str(self.Rtip))
            f.close()
        '''
Ejemplo n.º 3
0
class Turbine():
    """
    Class Turbine defines a turbine in terms of what is needed to 
        design the controller and to run the 'tiny' simulation.

    Primary functions (at a high level):
        - Reads an OpenFAST input deck 
        - Runs cc-blade rotor performance analysis
        - Writes a text file containing Cp, Ct, and Cq tables

    Methods:
    -------
    __str__
    load
    save
    load_from_fast
    load_from_ccblade
    load_from_txt
    write_rotor_performance

    Parameters:
    -----------
    turbine_params : dict
                    Dictionary containing known turbine paramaters that are not directly available from OpenFAST input files
    """

    def __init__(self, turbine_params):
        """
        Load turbine parameters from input dictionary
        """
        print('-----------------------------------------------------------------------------')
        print('Loading wind turbine data for NREL\'s ROSCO tuning and simulation processeses')
        print('-----------------------------------------------------------------------------')

        # ------ Turbine Parameters------
        self.rotor_inertia = turbine_params['rotor_inertia']         
        self.rated_rotor_speed = turbine_params['rated_rotor_speed']     
        self.v_min = turbine_params['v_min']                 
        self.v_rated = turbine_params['v_rated']               
        self.v_max = turbine_params['v_max']
        self.max_pitch_rate = turbine_params['max_pitch_rate'] 
        self.min_pitch_rate = -1 * self.max_pitch_rate
        self.max_torque_rate = turbine_params['max_torque_rate']             
        self.rated_power = turbine_params['rated_power']           
        self.bld_edgewise_freq = turbine_params['bld_edgewise_freq']     

        if turbine_params['twr_freq']:
            self.twr_freq = turbine_params['twr_freq']
        else:
            self.twr_freq = 0.0

        if turbine_params['ptfm_freq']:
            self.ptfm_freq = turbine_params['ptfm_freq']
        else:
            self.ptfm_freq = 0.0

        if turbine_params['TSR_operational']:
            self.TSR_operational = turbine_params['TSR_operational']
        else:
            self.TSR_operational = None

        if turbine_params['bld_flapwise_freq']:
            self.bld_flapwise_freq = turbine_params['bld_flapwise_freq']
        else:
            self.bld_flapwise_freq = 0.0


    # Allow print out of class
    def __str__(self): 
        '''
        Print some data about the turbine.
        '''
        print('-------------- Turbine Info --------------')
        print('Turbine Name: {}'.format(self.TurbineName))
        print('Rated Power: {} [W]'.format(self.rated_power))
        print('Total Inertia: {:.1f} [kg m^2]'.format(self.J))
        print('Rotor Radius: {:.1f} [m]'.format(self.rotor_radius))
        print('Rated Rotor Speed: {:.1f} [rad/s]'.format(self.rated_rotor_speed))
        print('Max Cp: {:.2f}'.format(self.Cp.max))
        print('------------------------------------------')
        return ' '

    # Save function
    def save(self,filename):
        '''
        Save turbine to pickle

        Parameters:
        ----------
        filename : str
                   Name of file to save pickle 
        # '''
        pickle.dump(self, open( filename, "wb" ) )

    # Load function
    @staticmethod
    def load(filename):
        '''
        Load turbine from pickle - outdated, but might be okay!!

        Parameters:
        ----------
        filename : str
                   Name of pickle file
        '''
        turbine = pickle.load(open(filename,'rb'))
        return turbine
    
    # Load data from fast input deck
    def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_branch=True,rot_source=None, txt_filename=None):
        """
        Load the parameter files directly from a FAST input deck

        Parameters:
        -----------
            Fast_InputFile: str
                            Primary fast model input file (*.fst)
            FAST_directory: str
                            Directory for primary fast model input file
            FAST_ver: string, optional
                      fast version, usually OpenFAST
            dev_branch: bool, optional
                        dev_branch input to InputReader_OpenFAST, probably True
            rot_source: str, optional
                        desired source for rotor to get Cp, Ct, Cq tables. Default is to run cc-blade. 
                            options: cc-blade - run cc-blade
                                     txt - from *.txt file
            txt_filename: str, optional
                          filename for *.txt, only used if rot_source='txt'
        """
        from wisdem.aeroelasticse.FAST_reader import InputReader_OpenFAST

        print('Loading FAST model: %s ' % FAST_InputFile)
        self.TurbineName = FAST_InputFile.strip('.fst')
        fast = self.fast = InputReader_OpenFAST(FAST_ver=FAST_ver,dev_branch=dev_branch)
        fast.FAST_InputFile = FAST_InputFile
        fast.FAST_directory = FAST_directory
        fast.execute()

        if txt_filename:
            self.rotor_performance_filename = txt_filename
        else:
            self.rotor_performance_filename = 'Cp_Ct_Cq.txt'


        # Grab general turbine parameters
        self.TipRad = fast.fst_vt['ElastoDyn']['TipRad']
        self.Rhub =  fast.fst_vt['ElastoDyn']['HubRad']
        self.hubHt = fast.fst_vt['ElastoDyn']['TowerHt'] 
        self.NumBl = fast.fst_vt['ElastoDyn']['NumBl']
        self.TowerHt = fast.fst_vt['ElastoDyn']['TowerHt']
        self.shearExp = 0.2  #HARD CODED FOR NOW
        self.rho = fast.fst_vt['AeroDyn15']['AirDens']
        self.mu = fast.fst_vt['AeroDyn15']['KinVisc']
        self.Ng = fast.fst_vt['ElastoDyn']['GBRatio']
        self.GenEff = fast.fst_vt['ServoDyn']['GenEff']
        self.GBoxEff = fast.fst_vt['ElastoDyn']['GBoxEff']
        self.DTTorSpr = fast.fst_vt['ElastoDyn']['DTTorSpr']
        self.generator_inertia = fast.fst_vt['ElastoDyn']['GenIner']
        self.tilt = fast.fst_vt['ElastoDyn']['ShftTilt'] 
        try:
            self.precone = fast.fst_vt['ElastoDyn']['PreCone1'] # May need to change to PreCone(1) depending on OpenFAST files
        except:
            self.precone = fast.fst_vt['ElastoDyn']['PreCone(1)']
        self.yaw = 0.0
        self.J = self.rotor_inertia + self.generator_inertia * self.Ng**2
        self.rated_torque = self.rated_power/(self.GenEff/100*self.rated_rotor_speed*self.Ng)
        self.max_torque = self.rated_torque * 1.1
        self.rotor_radius = self.TipRad
        # self.omega_dt = np.sqrt(self.DTTorSpr/self.J)

        # Load Cp, Ct, Cq tables
        if rot_source == 'cc-blade': # Use cc-blade
            self.load_from_ccblade()
        elif rot_source == 'txt':    # Use specified text file
            file_processing = ROSCO_utilities.FileProcessing()
            self.pitch_initial_rad, self.TSR_initial, self.Cp_table, self.Ct_table, self.Cq_table = file_processing.load_from_txt(
                txt_filename)
        else:   # Use text file from DISCON.in
            if os.path.exists(os.path.join(FAST_directory, fast.fst_vt['ServoDyn']['DLL_InFile'])):
                if  os.path.exists(fast.fst_vt['DISCON_in']['PerfFileName']):
                    self.pitch_initial_rad = fast.fst_vt['DISCON_in']['Cp_pitch_initial_rad']
                    self.TSR_initial = fast.fst_vt['DISCON_in']['Cp_TSR_initial']
                    self.Cp_table = fast.fst_vt['DISCON_in']['Cp_table']
                    self.Ct_table = fast.fst_vt['DISCON_in']['Ct_table']
                    self.Cq_table = fast.fst_vt['DISCON_in']['Cq_table']
            else:   # Load from cc-blade
                print('No rotor performance data source available, running CC-Blade.')
                self.load_from_ccblade()

        # Parse rotor performance data
        self.Cp = RotorPerformance(self.Cp_table,self.pitch_initial_rad,self.TSR_initial)
        self.Ct = RotorPerformance(self.Ct_table,self.pitch_initial_rad,self.TSR_initial)
        self.Cq = RotorPerformance(self.Cq_table,self.pitch_initial_rad,self.TSR_initial)

        # Define operational TSR
        if not self.TSR_operational:
            self.TSR_operational = self.Cp.TSR_opt

        # Pull out some floating-related data
        wave_tp = fast.fst_vt['HydroDyn']['WaveTp'] 
        try:
            self.wave_peak_period = 1/wave_tp       # Will work if HydroDyn exists and a peak period is defined...
        except:
            self.wave_peak_period = 0.0             # Set as 0.0 when HydroDyn doesn't exist (fixed bottom)

    # Load rotor performance data from CCBlade 
    def load_from_ccblade(self):
        '''
        Loads rotor performance information by running cc-blade aerodynamic analysis. Designed to work with Aerodyn15 blade input files. 

        Parameters:
        -----------
            fast: dict
                  Dictionary containing fast model details - defined using from InputReader_OpenFAST (distributed as a part of AeroelasticSE)

        '''
        print('Loading rotor performance data from CC-Blade.')
        
        # Create CC-Blade Rotor
        r0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlSpn']) 
        chord0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlChord'])
        theta0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlTwist'])
        # -- Adjust for Aerodyn15
        r = r0 + self.Rhub
        chord_intfun = interpolate.interp1d(r0,chord0, bounds_error=None, fill_value='extrapolate', kind='zero')
        chord = chord_intfun(r)
        theta_intfun = interpolate.interp1d(r0,theta0, bounds_error=None, fill_value='extrapolate', kind='zero')
        theta = theta_intfun(r)
        af_idx = np.array(self.fast.fst_vt['AeroDynBlade']['BlAFID']).astype(int) - 1 #Reset to 0 index
        AFNames = self.fast.fst_vt['AeroDyn15']['AFNames']   

        # Use airfoil data from FAST file read, assumes AeroDyn 15, assumes 1 Re num per airfoil
        af_dict = {}
        try:
            for i, _ in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re    = [self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Re']]
                Alpha = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Alpha']
                Cl    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cl']
                Cd    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cd']
                Cm    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cm']
                af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)
        except: # Read airfoil tables without tab cabalities (will remove once wisdem master branch cleans up)
            for i, _ in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re    = [self.fast.fst_vt['AeroDyn15']['af_data'][i]['Re']]
                Alpha = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Alpha']
                Cl    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cl']
                Cd    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cd']
                Cm    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cm']
                af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)

        # define airfoils for CCBlade
        af = [0]*len(r)
        for i in range(len(r)):
            af[i] = af_dict[af_idx[i]]

        # Now save the CC-Blade rotor
        nSector = 8  # azimuthal discretizations
        self.cc_rotor = CCBlade(r, chord, theta, af, self.Rhub, self.rotor_radius, self.NumBl, rho=self.rho, mu=self.mu,
                        precone=-self.precone, tilt=-self.tilt, yaw=self.yaw, shearExp=self.shearExp, hubHt=self.hubHt, nSector=nSector)
        print('CCBlade initiated successfully.')
        
        # Generate the look-up tables, mesh the grid and flatten the arrays for cc_rotor aerodynamic analysis
        TSR_initial = np.arange(3, 15,0.5)
        pitch_initial = np.arange(-1,25,0.5)
        pitch_initial_rad = pitch_initial * deg2rad
        ws_array = np.ones_like(TSR_initial) * self.v_rated # evaluate at rated wind speed
        omega_array = (TSR_initial * ws_array / self.rotor_radius) * RadSec2rpm
        ws_mesh, pitch_mesh = np.meshgrid(ws_array, pitch_initial)
        ws_flat = ws_mesh.flatten()
        pitch_flat = pitch_mesh.flatten()
        omega_mesh, _ = np.meshgrid(omega_array, pitch_initial)
        omega_flat = omega_mesh.flatten()
        # tsr_flat = (omega_flat * rpm2RadSec * self.rotor_radius)  / ws_flat


        # Get values from cc-blade
        print('Running CCBlade aerodynamic analysis, this may take a minute...')
        try:
            _, _, _, _, CP, CT, CQ, _ = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True)
        except ValueError: # On IEAontology4all
            outputs, derivs = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True)
            CP = outputs['CP']
            CT = outputs['CT']
            CQ = outputs['CQ']
        print('CCBlade aerodynamic analysis run successfully.')

        # Reshape Cp, Ct and Cq
        Cp = np.transpose(np.reshape(CP, (len(pitch_initial), len(TSR_initial))))
        Ct = np.transpose(np.reshape(CT, (len(pitch_initial), len(TSR_initial))))
        Cq = np.transpose(np.reshape(CQ, (len(pitch_initial), len(TSR_initial))))

        # Store necessary metrics for analysis
        self.pitch_initial_rad = pitch_initial_rad
        self.TSR_initial = TSR_initial
        self.Cp_table = Cp
        self.Ct_table = Ct 
        self.Cq_table = Cq
        
        # Save some blade parameters
        self.span = r
        self.chord = chord
        self.twist = theta
    
    def load_blade_info(self):
        '''
        Loads wind turbine blade data from an OpenFAST model. 
        Should be used if blade information is needed (i.e.) for flap controller tuning, but a rotor performance file is defined and and cc-blade is not run already. 
        
        Parameters:
        -----------
            self - note: needs to contain fast input file info provided by load_from_fast.
        '''
        from wisdem.aeroelasticse.FAST_reader import InputReader_OpenFAST

        # Load Fast input deck
        # self.TurbineName = FAST_InputFile.strip('.fst')
        # fast = InputReader_OpenFAST(FAST_ver=FAST_ver,dev_branch=dev_branch)
        # self.fast.FAST_InputFile = FAST_InputFile
        # self.fast.FAST_directory = FAST_directory
        # self.fast.execute()

        # Make sure cc_rotor exists for DAC analysis
        try:
            if self.cc_rotor:
                self.af_data = self.fast.fst_vt['AeroDyn15']['af_data']
                self.bld_flapwise_damp = self.fast.fst_vt['ElastoDynBlade']['BldFlDmp1']/100 * 0.7
        except AttributeError:
            # Create CC-Blade Rotor
            r0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlSpn']) 
            chord0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlChord'])
            theta0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlTwist'])
            # -- Adjust for Aerodyn15
            r = r0 + self.Rhub
            chord_intfun = interpolate.interp1d(r0,chord0, bounds_error=None, fill_value='extrapolate', kind='zero')
            chord = chord_intfun(r)
            theta_intfun = interpolate.interp1d(r0,theta0, bounds_error=None, fill_value='extrapolate', kind='zero')
            theta = theta_intfun(r)
            af_idx = np.array(self.fast.fst_vt['AeroDynBlade']['BlAFID']).astype(int) - 1 #Reset to 0 index
            AFNames = self.fast.fst_vt['AeroDyn15']['AFNames']   

            # Use airfoil data from FAST file read, assumes AeroDyn 15, assumes 1 Re num per airfoil
            af_dict = {}
            for i, section in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re = [section[0]['Re']]
                Alpha = section[0]['Alpha']
                if section[0]['NumTabs'] > 1:  # sections with flaps
                    ref_tab = int(np.floor(section[0]['NumTabs']/2))
                    Cl = section[ref_tab]['Cl']
                    Cd = section[ref_tab]['Cd']
                    Cm = section[ref_tab]['Cm']
                    af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)
                else:                           # sections without flaps
                    Cl = section[0]['Cl']
                    Cd = section[0]['Cd']
                    Cm = section[0]['Cm']
                    af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)





            # define airfoils for CCBlade
            af = [0]*len(r)
            for i in range(len(r)):
                af[i] = af_dict[af_idx[i]]

            # Now save the CC-Blade rotor
            nSector = 8  # azimuthal discretizations
            self.cc_rotor = CCBlade(r, chord, theta, af, self.Rhub, self.rotor_radius, self.NumBl, rho=self.rho, mu=self.mu,
                            precone=-self.precone, tilt=-self.tilt, yaw=self.yaw, shearExp=self.shearExp, hubHt=self.hubHt, nSector=nSector)

            # Save some blade  data 
            self.af_data = self.fast.fst_vt['AeroDyn15']['af_data']
            self.span = r 
            self.chord = chord
            self.twist = theta
            self.bld_flapwise_damp = self.fast.fst_vt['ElastoDynBlade']['BldFlDmp1']/100 * 0.7
Ejemplo n.º 4
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']
        blade_init_options = modeling_options['blade']
        servose_init_options = modeling_options['servose']
        airfoils = modeling_options['airfoils']
        self.n_span = n_span = blade_init_options['n_span']
        self.n_aoa = n_aoa = airfoils['n_aoa']  # Number of angle of attacks
        self.n_Re = n_Re = airfoils[
            'n_Re']  # Number of Reynolds, so far hard set at 1
        self.n_tab = n_tab = airfoils[
            'n_tab']  # Number of tabulated data. For distributed aerodynamic control this could be > 1
        self.n_pitch = n_pitch = servose_init_options['n_pitch_perf_surfaces']
        self.n_tsr = n_tsr = servose_init_options['n_tsr_perf_surfaces']
        self.n_U = n_U = servose_init_options['n_U_perf_surfaces']
        self.min_TSR = servose_init_options['min_tsr_perf_surfaces']
        self.max_TSR = servose_init_options['max_tsr_perf_surfaces']
        self.min_pitch = servose_init_options['min_pitch_perf_surfaces']
        self.max_pitch = servose_init_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']]
Ejemplo n.º 5
0
class Turbine():
    """
    Class Turbine defines a turbine in terms of what is needed to 
        design the controller and to run the 'tiny' simulation.

    Primary functions (at a high level):
        - Reads an OpenFAST input deck 
        - Runs cc-blade rotor performance analysis
        - Writes a text file containing Cp, Ct, and Cq tables

    Methods:
    -------
    __str__
    load
    save
    load_from_fast
    load_from_ccblade
    load_from_txt
    generate_rotperf_fast
    write_rotor_performance

    Parameters:
    -----------
    turbine_params : dict
                    Dictionary containing known turbine paramaters that are not directly available from OpenFAST input files
    """

    def __init__(self, turbine_params):
        """
        Load turbine parameters from input dictionary
        """
        print('-----------------------------------------------------------------------------')
        print('Loading wind turbine data for NREL\'s ROSCO tuning and simulation processeses')
        print('-----------------------------------------------------------------------------')

        # ------ Turbine Parameters------
        self.rotor_inertia = turbine_params['rotor_inertia']         
        self.rated_rotor_speed = turbine_params['rated_rotor_speed']     
        self.v_min = turbine_params['v_min']                 
        self.v_rated = turbine_params['v_rated']               
        self.v_max = turbine_params['v_max']
        self.max_pitch_rate = turbine_params['max_pitch_rate'] 
        self.min_pitch_rate = -1 * self.max_pitch_rate
        self.max_torque_rate = turbine_params['max_torque_rate']             
        self.rated_power = turbine_params['rated_power']           
        self.bld_edgewise_freq = turbine_params['bld_edgewise_freq']     

        if turbine_params['twr_freq']:
            self.twr_freq = turbine_params['twr_freq']
        else:
            self.twr_freq = 0.0

        if turbine_params['ptfm_freq']:
            self.ptfm_freq = turbine_params['ptfm_freq']
        else:
            self.ptfm_freq = 0.0

        if turbine_params['TSR_operational']:
            self.TSR_operational = turbine_params['TSR_operational']
        else:
            self.TSR_operational = None

        if turbine_params['bld_flapwise_freq']:
            self.bld_flapwise_freq = turbine_params['bld_flapwise_freq']
        else:
            self.bld_flapwise_freq = 0.0


    # Allow print out of class
    def __str__(self): 
        '''
        Print some data about the turbine.
        '''
        print('-------------- Turbine Info --------------')
        print('Turbine Name: {}'.format(self.TurbineName))
        print('Rated Power: {} [W]'.format(self.rated_power))
        print('Total Inertia: {:.1f} [kg m^2]'.format(self.J))
        print('Rotor Radius: {:.1f} [m]'.format(self.rotor_radius))
        print('Rated Rotor Speed: {:.1f} [rad/s]'.format(self.rated_rotor_speed))
        print('Max Cp: {:.2f}'.format(self.Cp.max))
        print('------------------------------------------')
        return ' '

    # Save function
    def save(self,filename):
        '''
        Save turbine to pickle

        Parameters:
        ----------
        filename : str
                   Name of file to save pickle 
        # '''
        pickle.dump(self, open( filename, "wb" ) )

    # Load function
    @staticmethod
    def load(filename):
        '''
        Load turbine from pickle - outdated, but might be okay!!

        Parameters:
        ----------
        filename : str
                   Name of pickle file
        '''
        turbine = pickle.load(open(filename,'rb'))
        return turbine
    
    # Load data from fast input deck
    def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_branch=True,rot_source=None, txt_filename=None):
        """
        Load the parameter files directly from a FAST input deck

        Parameters:
        -----------
            Fast_InputFile: str
                            Primary fast model input file (*.fst)
            FAST_directory: str
                            Directory for primary fast model input file
            FAST_ver: string, optional
                      fast version, usually OpenFAST
            dev_branch: bool, optional
                        dev_branch input to InputReader_OpenFAST, probably True
            rot_source: str, optional
                        desired source for rotor to get Cp, Ct, Cq tables. Default is to run cc-blade. 
                            options: cc-blade - run cc-blade
                                     txt - from *.txt file
            txt_filename: str, optional
                          filename for *.txt, only used if rot_source='txt'
        """
        from wisdem.aeroelasticse.FAST_reader import InputReader_OpenFAST

        print('Loading FAST model: %s ' % FAST_InputFile)
        self.TurbineName = FAST_InputFile.strip('.fst')
        fast = self.fast = InputReader_OpenFAST(FAST_ver=FAST_ver,dev_branch=dev_branch)
        fast.FAST_InputFile = FAST_InputFile
        fast.FAST_directory = FAST_directory
        fast.execute()

        if txt_filename:
            self.rotor_performance_filename = txt_filename
        else:
            self.rotor_performance_filename = 'Cp_Ct_Cq.txt'


        # Grab general turbine parameters
        self.TipRad             = fast.fst_vt['ElastoDyn']['TipRad']
        self.Rhub               = fast.fst_vt['ElastoDyn']['HubRad']
        self.hubHt              = fast.fst_vt['ElastoDyn']['TowerHt'] + fast.fst_vt['ElastoDyn']['Twr2Shft']
        self.NumBl              = fast.fst_vt['ElastoDyn']['NumBl']
        self.TowerHt            = fast.fst_vt['ElastoDyn']['TowerHt']
        self.shearExp           = 0.2  #HARD CODED FOR NOW
        self.rho                = fast.fst_vt['AeroDyn15']['AirDens']
        self.mu                 = fast.fst_vt['AeroDyn15']['KinVisc']
        self.Ng                 = fast.fst_vt['ElastoDyn']['GBRatio']
        self.GenEff             = fast.fst_vt['ServoDyn']['GenEff']
        self.GBoxEff            = fast.fst_vt['ElastoDyn']['GBoxEff']
        self.DTTorSpr           = fast.fst_vt['ElastoDyn']['DTTorSpr']
        self.generator_inertia  = fast.fst_vt['ElastoDyn']['GenIner']
        self.tilt               = fast.fst_vt['ElastoDyn']['ShftTilt'] 
        try:
            self.precone = fast.fst_vt['ElastoDyn']['PreCone1'] # May need to change to PreCone(1) depending on OpenFAST files
        except:
            self.precone = fast.fst_vt['ElastoDyn']['PreCone(1)']
        self.yaw = 0.0
        self.J = self.rotor_inertia + self.generator_inertia * self.Ng**2
        self.rated_torque = self.rated_power/(self.GenEff/100*self.rated_rotor_speed*self.Ng*self.GBoxEff/100)
        self.max_torque = self.rated_torque * 1.1
        self.rotor_radius = self.TipRad
        # self.omega_dt = np.sqrt(self.DTTorSpr/self.J)

        # Load Cp, Ct, Cq tables
        if rot_source == 'cc-blade': # Use cc-blade
            self.load_from_ccblade()
        elif rot_source == 'txt':    # Use specified text file
            file_processing = ROSCO_utilities.FileProcessing()
            self.pitch_initial_rad, self.TSR_initial, self.Cp_table, self.Ct_table, self.Cq_table = file_processing.load_from_txt(
                txt_filename)
        else:   # Use text file from DISCON.in
            if os.path.exists(os.path.join(FAST_directory, fast.fst_vt['ServoDyn']['DLL_InFile'])):
                try:
                    self.pitch_initial_rad = fast.fst_vt['DISCON_in']['Cp_pitch_initial_rad']
                    self.TSR_initial = fast.fst_vt['DISCON_in']['Cp_TSR_initial']
                    self.Cp_table = fast.fst_vt['DISCON_in']['Cp_table']
                    self.Ct_table = fast.fst_vt['DISCON_in']['Ct_table']
                    self.Cq_table = fast.fst_vt['DISCON_in']['Cq_table']
                except:   # Load from cc-blade
                    print('No rotor performance data source available, running CC-Blade.')
                    self.load_from_ccblade()

        # Parse rotor performance data
        self.Cp = RotorPerformance(self.Cp_table,self.pitch_initial_rad,self.TSR_initial)
        self.Ct = RotorPerformance(self.Ct_table,self.pitch_initial_rad,self.TSR_initial)
        self.Cq = RotorPerformance(self.Cq_table,self.pitch_initial_rad,self.TSR_initial)

        # Define operational TSR
        if not self.TSR_operational:
            self.TSR_operational = self.Cp.TSR_opt

        # Pull out some floating-related data
        wave_tp = fast.fst_vt['HydroDyn']['WaveTp'] 
        try:
            self.wave_peak_period = 1/wave_tp       # Will work if HydroDyn exists and a peak period is defined...
        except:
            self.wave_peak_period = 0.0             # Set as 0.0 when HydroDyn doesn't exist (fixed bottom)

    # Load rotor performance data from CCBlade 
    def load_from_ccblade(self):
        '''
        Loads rotor performance information by running cc-blade aerodynamic analysis. Designed to work with Aerodyn15 blade input files. 

        Parameters:
        -----------
            fast: dict
                  Dictionary containing fast model details - defined using from InputReader_OpenFAST (distributed as a part of AeroelasticSE)

        '''
        print('Loading rotor performance data from CC-Blade.')
        
        # Create CC-Blade Rotor
        r0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlSpn']) 
        chord0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlChord'])
        theta0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlTwist'])
        # -- Adjust for Aerodyn15
        r = r0 + self.Rhub
        chord_intfun = interpolate.interp1d(r0,chord0, bounds_error=None, fill_value='extrapolate', kind='zero')
        chord = chord_intfun(r)
        theta_intfun = interpolate.interp1d(r0,theta0, bounds_error=None, fill_value='extrapolate', kind='zero')
        theta = theta_intfun(r)
        af_idx = np.array(self.fast.fst_vt['AeroDynBlade']['BlAFID']).astype(int) - 1 #Reset to 0 index
        AFNames = self.fast.fst_vt['AeroDyn15']['AFNames']   

        # Use airfoil data from FAST file read, assumes AeroDyn 15, assumes 1 Re num per airfoil
        af_dict = {}
        try:
            for i, _ in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re    = [self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Re']]
                Alpha = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Alpha']
                Cl    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cl']
                Cd    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cd']
                Cm    = self.fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cm']
                af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)
        except: # Read airfoil tables without tab cabalities (will remove once wisdem master branch cleans up)
            for i, _ in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re    = [self.fast.fst_vt['AeroDyn15']['af_data'][i]['Re']]
                Alpha = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Alpha']
                Cl    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cl']
                Cd    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cd']
                Cm    = self.fast.fst_vt['AeroDyn15']['af_data'][i]['Cm']
                af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)

        # define airfoils for CCBlade
        af = [0]*len(r)
        for i in range(len(r)):
            af[i] = af_dict[af_idx[i]]

        # Now save the CC-Blade rotor
        nSector = 8  # azimuthal discretizations
        self.cc_rotor = CCBlade(r, chord, theta, af, self.Rhub, self.rotor_radius, self.NumBl, rho=self.rho, mu=self.mu,
                        precone=-self.precone, tilt=-self.tilt, yaw=self.yaw, shearExp=self.shearExp, hubHt=self.hubHt, nSector=nSector)
        print('CCBlade initiated successfully.')
        
        # Generate the look-up tables, mesh the grid and flatten the arrays for cc_rotor aerodynamic analysis
        TSR_initial = np.arange(3, 15,0.5)
        pitch_initial = np.arange(-1,25,0.5)
        pitch_initial_rad = pitch_initial * deg2rad
        ws_array = np.ones_like(TSR_initial) * self.v_rated # evaluate at rated wind speed
        omega_array = (TSR_initial * ws_array / self.rotor_radius) * RadSec2rpm
        ws_mesh, pitch_mesh = np.meshgrid(ws_array, pitch_initial)
        ws_flat = ws_mesh.flatten()
        pitch_flat = pitch_mesh.flatten()
        omega_mesh, _ = np.meshgrid(omega_array, pitch_initial)
        omega_flat = omega_mesh.flatten()
        # tsr_flat = (omega_flat * rpm2RadSec * self.rotor_radius)  / ws_flat


        # Get values from cc-blade
        print('Running CCBlade aerodynamic analysis, this may take a minute...')
        try:
            _, _, _, _, CP, CT, CQ, _ = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True)
        except ValueError: # On IEAontology4all
            outputs, derivs = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True)
            CP = outputs['CP']
            CT = outputs['CT']
            CQ = outputs['CQ']
        print('CCBlade aerodynamic analysis run successfully.')

        # Reshape Cp, Ct and Cq
        Cp = np.transpose(np.reshape(CP, (len(pitch_initial), len(TSR_initial))))
        Ct = np.transpose(np.reshape(CT, (len(pitch_initial), len(TSR_initial))))
        Cq = np.transpose(np.reshape(CQ, (len(pitch_initial), len(TSR_initial))))

        # Store necessary metrics for analysis
        self.pitch_initial_rad = pitch_initial_rad
        self.TSR_initial = TSR_initial
        self.Cp_table = Cp
        self.Ct_table = Ct 
        self.Cq_table = Cq
        
        # Save some blade parameters
        self.span = r
        self.chord = chord
        self.twist = theta
    
    def generate_rotperf_fast(self, openfast_path, FAST_runDirectory=None, run_BeamDyn=False,
                              debug_level=1, run_type='multi'):
        '''
        Use openfast to generate Cp surface data. Will be slow, especially if using BeamDyn,
        but may be necessary if cc-blade is not sufficient.

        Parameters:
        -----------
        openfast_path: str
            path to openfast
        FAST_runDirectory: str
            directory to run openfast simulations in
        run_BeamDyn: bool
            Flag to run beamdyn - does not exist yet
        debug_level: float
            0 - no outputs, 1 - simple outputs, 2 - all outputs
        run_type: str
            'serial' - run in serial, 'multi' - run using python multiprocessing tools, 
            'mpi' - run using mpi tools
        '''

        # Load additional WISDEM tools
        from wisdem.aeroelasticse import runFAST_pywrapper, CaseGen_General
        from wisdem.aeroelasticse.Util import FileTools
        # Load pCrunch tools
        from pCrunch import pdTools, Processing


        # setup values for surface
        v0 = self.v_rated + 2
        TSR_initial = np.arange(3, 15,1)
        pitch_initial = np.arange(-1,25,1)
        rotspeed_initial = TSR_initial*v0/self.rotor_radius * RadSec2rpm # rpms

        # Specify Case Inputs
        case_inputs = {}

        # ------- Setup OpenFAST inputs --------
        case_inputs[('Fst','TMax')] = {'vals': [330], 'group': 0}
        case_inputs[('Fst','Compinflow')] = {'vals': [1], 'group': 0}
        case_inputs[('Fst','CompAero')] = {'vals': [2], 'group': 0}
        case_inputs[('Fst','CompServo')] = {'vals': [1], 'group': 0}
        case_inputs[('Fst','CompHydro')] = {'vals': [0], 'group': 0}
        if run_BeamDyn:
            case_inputs[('Fst','CompElast')] = {'vals': [2], 'group': 0}
        else:
            case_inputs[('Fst','CompElast')] = {'vals': [1], 'group': 0}
        case_inputs[('Fst', 'OutFileFmt')] = {'vals': [2], 'group': 0}

        # AeroDyn15
        case_inputs[('AeroDyn15', 'WakeMod')] = {'vals': [1], 'group': 0}
        case_inputs[('AeroDyn15', 'AfAeroMod')] = {'vals': [1], 'group': 0}
        case_inputs[('AeroDyn15', 'TwrPotent')] = {'vals': [0], 'group': 0}
        
        # ElastoDyn
        case_inputs[('ElastoDyn', 'FlapDOF1')] = {'vals': ['True'], 'group': 0}
        case_inputs[('ElastoDyn', 'FlapDOF2')] = {'vals': ['True'], 'group': 0}
        case_inputs[('ElastoDyn', 'EdgeDOF')] = {'vals': ['True'], 'group': 0}
        case_inputs[('ElastoDyn', 'TeetDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'DrTrDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'GenDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'YawDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'TwFADOF1')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'TwFADOF2')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'TwSSDOF1')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'TwSSDOF2')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmSgDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmSwDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmHvDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmRDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmPDOF')] = {'vals': ['False'], 'group': 0}
        case_inputs[('ElastoDyn', 'PtfmYDOF')] = {'vals': ['False'], 'group': 0}

        # BeamDyn
        # NEEDED

        # InflowWind
        case_inputs[('InflowWind', 'WindType')] = {'vals': [1], 'group': 0}
        case_inputs[('InflowWind', 'HWindSpeed')] = {'vals': [v0], 'group': 0}
        case_inputs[('InflowWind', 'PLexp')] = {'vals': [0], 'group': 0}

        # ServoDyn
        case_inputs[('ServoDyn', 'PCMode')] = {'vals': [0], 'group': 0}
        case_inputs[('ServoDyn', 'VSContrl')] = {'vals': [0], 'group': 0} 
        case_inputs[('ServoDyn', 'HSSBrMode')] = {'vals': [0], 'group': 0}
        case_inputs[('ServoDyn', 'YCMode')] = {'vals': [0], 'group': 0}

        # ------- Setup sweep values inputs --------
        case_inputs[('ElastoDyn', 'BlPitch1')] = {'vals': list(pitch_initial), 'group': 1}
        case_inputs[('ElastoDyn', 'BlPitch2')] = {'vals': list(pitch_initial), 'group': 1}
        case_inputs[('ElastoDyn', 'BlPitch3')] = {'vals': list(pitch_initial), 'group': 1}
        case_inputs[('ElastoDyn', 'RotSpeed')] = {'vals': list(rotspeed_initial), 'group': 2}


        # FAST details
        fastBatch = runFAST_pywrapper.runFAST_pywrapper_batch(FAST_ver='OpenFAST', dev_branch=True)
        fastBatch.FAST_exe = openfast_path  # Path to executable
        fastBatch.FAST_InputFile = self.fast.FAST_InputFile
        fastBatch.FAST_directory = self.fast.FAST_directory
        if not FAST_runDirectory:
            FAST_runDirectory = os.path.join(os.getcwd(), 'RotPerf_OpenFAST')
        fastBatch.FAST_runDirectory = FAST_runDirectory
        fastBatch.debug_level = debug_level

        # Generate cases
        case_name_base = self.TurbineName + '_rotperf'
        case_list, case_name_list = CaseGen_General.CaseGen_General(
            case_inputs, dir_matrix=fastBatch.FAST_runDirectory, namebase=case_name_base)
        fastBatch.case_list = case_list
        fastBatch.case_name_list = case_name_list

        # Make sure proper outputs exist
        var_out = [
            # ElastoDyn (this is probably overkill on the outputs)
            "BldPitch1", "BldPitch2", "BldPitch3", "Azimuth", "RotSpeed", "GenSpeed", "NacYaw",
            "OoPDefl1", "IPDefl1", "TwstDefl1", "OoPDefl2", "IPDefl2", "TwstDefl2", "OoPDefl3",
            "IPDefl3", "TwstDefl3", "RootFxc1",
            "RootFyc1", "RootFzc1", "RootMxc1", "RootMyc1", "RootMzc1", "RootFxc2", "RootFyc2",
            "RootFzc2", "RootMxc2", "RootMyc2", "RootMzc2", "RootFxc3", "RootFyc3", "RootFzc3",
            "RootMxc3", "RootMyc3", "RootMzc3", "Spn1MLxb1", "Spn1MLyb1", "Spn1MLzb1", "Spn1MLxb2",
            "Spn1MLyb2", "Spn1MLzb2", "Spn1MLxb3", "Spn1MLyb3", "Spn1MLzb3", "RotThrust", "LSSGagFya",
            "LSSGagFza", "RotTorq", "LSSGagMya", "LSSGagMza", 
            # ServoDyn
            "GenPwr", "GenTq",
            # AeroDyn15
            "RtArea", "RtVAvgxh", "B1N3Clrnc", "B2N3Clrnc", "B3N3Clrnc",
            "RtAeroCp", 'RtAeroCq', 'RtAeroCt', 'RtTSR', # NECESSARY
            # InflowWind
            "Wind1VelX", 
        ]
        channels = {}
        for var in var_out:
            channels[var] = True
        fastBatch.channels = channels

        # Run OpenFAST
        if run_type.lower() == 'multi':
            fastBatch.run_multi()
        elif run_type.lower()=='mpi':
            fastBatch.run_mpi()
        elif run_type.lower()=='serial':
            fastBatch.run_serial()

        # ========== Post Processing ==========
        # Save statistics
        fp = Processing.FAST_Processing()

        # Find all outfiles
        fname_case_matrix = os.path.join(FAST_runDirectory, 'case_matrix.yaml')
        case_matrix = FileTools.load_yaml(fname_case_matrix, package=1)
        cm = pd.DataFrame(case_matrix)
        # Parse case matrix and find outfiles names
        outfiles = []
        case_names = cm['Case_Name']
        outfiles = []
        for name in case_names:
            outfiles.append(os.path.join(FAST_runDirectory, name + '.outb'))



        # Set some processing parameters
        fp.OpenFAST_outfile_list = outfiles
        fp.namebase = case_name_base
        fp.t0 = 270 
        fp.parallel_analysis = True
        fp.results_dir = os.path.join(FAST_runDirectory,'stats')
        fp.verbose = True
        # Save for debug!
        fp.save_LoadRanking = False
        fp.save_SummaryStats = False

        print('Processing openfast data on {} cores.'.format(fp.parallel_cores))

        # Load and save statistics and load rankings
        stats, load_rankings = fp.batch_processing()

        # Get means of last 30 seconds of 300 second simulation
        CP = stats[0]['RtAeroCp']['mean']
        CT = stats[0]['RtAeroCt']['mean']
        CQ = stats[0]['RtAeroCq']['mean']

        # Reshape Cp, Ct and Cq
        Cp = np.transpose(np.reshape(CP, (len(pitch_initial), len(TSR_initial))))
        Ct = np.transpose(np.reshape(CT, (len(pitch_initial), len(TSR_initial))))
        Cq = np.transpose(np.reshape(CQ, (len(pitch_initial), len(TSR_initial))))

        # Store necessary metrics for analysis
        self.pitch_initial_rad = pitch_initial * deg2rad
        self.TSR_initial = TSR_initial
        self.Cp_table = Cp
        self.Ct_table = Ct
        self.Cq_table = Cq

    
    def load_blade_info(self):
        '''
        Loads wind turbine blade data from an OpenFAST model. 
        Should be used if blade information is needed (i.e.) for flap controller tuning, but a rotor performance file is defined and and cc-blade is not run already. 
        
        Parameters:
        -----------
            self - note: needs to contain fast input file info provided by load_from_fast.
        '''
        from wisdem.aeroelasticse.FAST_reader import InputReader_OpenFAST

        # Load Fast input deck
        # self.TurbineName = FAST_InputFile.strip('.fst')
        # fast = InputReader_OpenFAST(FAST_ver=FAST_ver,dev_branch=dev_branch)
        # self.fast.FAST_InputFile = FAST_InputFile
        # self.fast.FAST_directory = FAST_directory
        # self.fast.execute()

        # Make sure cc_rotor exists for DAC analysis
        try:
            if self.cc_rotor:
                self.af_data = self.fast.fst_vt['AeroDyn15']['af_data']
                self.bld_flapwise_damp = self.fast.fst_vt['ElastoDynBlade']['BldFlDmp1']/100 * 0.7
        except AttributeError:
            # Create CC-Blade Rotor
            r0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlSpn']) 
            chord0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlChord'])
            theta0 = np.array(self.fast.fst_vt['AeroDynBlade']['BlTwist'])
            # -- Adjust for Aerodyn15
            r = r0 + self.Rhub
            chord_intfun = interpolate.interp1d(r0,chord0, bounds_error=None, fill_value='extrapolate', kind='zero')
            chord = chord_intfun(r)
            theta_intfun = interpolate.interp1d(r0,theta0, bounds_error=None, fill_value='extrapolate', kind='zero')
            theta = theta_intfun(r)
            af_idx = np.array(self.fast.fst_vt['AeroDynBlade']['BlAFID']).astype(int) - 1 #Reset to 0 index
            AFNames = self.fast.fst_vt['AeroDyn15']['AFNames']   

            # Use airfoil data from FAST file read, assumes AeroDyn 15, assumes 1 Re num per airfoil
            af_dict = {}
            for i, section in enumerate(self.fast.fst_vt['AeroDyn15']['af_data']):
                Re = [section[0]['Re']]
                Alpha = section[0]['Alpha']
                if section[0]['NumTabs'] > 1:  # sections with flaps
                    ref_tab = int(np.floor(section[0]['NumTabs']/2))
                    Cl = section[ref_tab]['Cl']
                    Cd = section[ref_tab]['Cd']
                    Cm = section[ref_tab]['Cm']
                    af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)
                else:                           # sections without flaps
                    Cl = section[0]['Cl']
                    Cd = section[0]['Cd']
                    Cm = section[0]['Cm']
                    af_dict[i] = CCAirfoil(Alpha, Re, Cl, Cd, Cm)





            # define airfoils for CCBlade
            af = [0]*len(r)
            for i in range(len(r)):
                af[i] = af_dict[af_idx[i]]

            # Now save the CC-Blade rotor
            nSector = 8  # azimuthal discretizations
            self.cc_rotor = CCBlade(r, chord, theta, af, self.Rhub, self.rotor_radius, self.NumBl, rho=self.rho, mu=self.mu,
                            precone=-self.precone, tilt=-self.tilt, yaw=self.yaw, shearExp=self.shearExp, hubHt=self.hubHt, nSector=nSector)

            # Save some blade  data 
            self.af_data = self.fast.fst_vt['AeroDyn15']['af_data']
            self.span = r 
            self.chord = chord
            self.twist = theta
            self.bld_flapwise_damp = self.fast.fst_vt['ElastoDynBlade']['BldFlDmp1']/100 * 0.7
Ejemplo n.º 6
0
class Performance_coef(unittest.TestCase):
    def setup(self):
        """
        Parameters
        ----------
        r : array_like (m)
            locations defining the blade along z-axis of :ref:`blade coordinate system <azimuth_blade_coord>`
            (values should be increasing).
        chord : array_like (m)
            corresponding chord length at each section
        theta : array_like (deg)
            corresponding :ref:`twist angle <blade_airfoil_coord>` at each section---
            positive twist decreases angle of attack.
        Rhub : float (m)
            location of hub
        Rtip : float (m)
            location of tip
        B : int, optional
            number of blades
        rho : float, optional (kg/m^3)
            freestream fluid density
        mu : float, optional (kg/m/s)
            dynamic viscosity of fluid
        precone : float, optional (deg)
            :ref:`hub precone angle <azimuth_blade_coord>`
        tilt : float, optional (deg)
            nacelle :ref:`tilt angle <yaw_hub_coord>`
        yaw : float, optional (deg)
            nacelle :ref:`yaw angle<wind_yaw_coord>`
        shearExp : float, optional
            shear exponent for a power-law wind profile across hub
        hubHt : float, optional
            hub height used for power-law wind profile.
            U = Uref*(z/hubHt)**shearExp
        nSector : int, optional
            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.
        """
        self.Rhub = 1.5
        self.Rtip = 55.60721094924956
        B = 3  # number of blades
        # atmosphere
        rho = 1.225
        mu = 1.81206e-5

        tilt = 5.0
        precone = 2.5
        yaw = 0.0
        shearExp = 0.2
        hubHt = (self.Rtip * 2) * 0.92
        nSector = 8

        tsr = 7.55
        # max chord length
        c_max = 5
        # lift coefficients for the given airfoils
        Cl = [
            1.3871, 1.3871, 1.3871, 1.3871, 1.464, 1.464, 1.464, 1.464, 1.4509,
            1.4509, 1.4509, 1.4509, 1.4509, 1.4509
        ]

        self.r = np.array([
            self.Rtip * 0.045503175, self.Rtip * 0.088888889,
            self.Rtip * 0.132274603, self.Rtip * 0.186507937,
            self.Rtip * 0.251587302, self.Rtip * 0.316666667,
            self.Rtip * 0.381746032, self.Rtip * 0.446825397,
            self.Rtip * 0.511904762, self.Rtip * 0.576984127,
            self.Rtip * 0.642063492, self.Rtip * 0.707142857,
            self.Rtip * 0.772222222, self.Rtip * 0.837301587,
            self.Rtip * 0.891534921, self.Rtip * 0.934920635,
            self.Rtip * 0.978306349
        ])

        afinit = CCAirfoil.initFromAerodynFile  # just for shorthand
        basepath = 'C:/Users/Thomas/OneDrive/Tieto/Airfoils/'

        # load all airfoils
        airfoil_types = [0] * 5
        airfoil_types[0] = afinit(basepath + 'Cylinder1.dat')
        airfoil_types[1] = afinit(basepath + 'Cylinder2.dat')
        airfoil_types[2] = afinit(basepath + 'S818_output3_extrap.txt')
        airfoil_types[3] = afinit(basepath + 'S830_output_extrap.txt')
        airfoil_types[4] = afinit(basepath + 'S831_output_extrap.txt')

        # place at appropriate radial sections
        af_idx = [0, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4]

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

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

        chord = np.array(
            blade_geometry(self.Rtip, self.r, B, Cl, tsr, c_max, plot=False))

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

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

    def compute(self):
        ''' 
        Uinf : array_like (m/s)
            hub height wind speed
        Omega : array_like (RPM)
            rotor rotation speed
        pitch : array_like (deg)
            blade pitch setting
        '''
        # set conditions
        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
        ])

        P, T, Q, M, CP, CT, CQ, CM = self.aeroanalysis.evaluate(
            Uinf, Omega, pitch, coefficients=True)

        print('CP:' + str(CP))

        path = "C:/Users/Thomas/OneDrive/Tieto/Scripts/Output/Cp_res.txt"

        with open(path, 'w') as f:
            f.write('Cp:\n')
            for i in CP:
                f.write("%s\n" % str(i))
            f.write('\n')
            f.write('Radius: \n')
            f.write(str(self.Rtip))
            f.close()
Ejemplo n.º 7
0
    cm[i, :, 0, 0] = np.interp(aoa, airfoils[i]['polars'][0]['c_m']['grid'],
                               airfoils[i]['polars'][0]['c_m']['values'])

af = [None] * len(s)
for i in range(len(s)):
    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'),
Ejemplo n.º 8
0
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']
Ejemplo n.º 9
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)
Ejemplo n.º 10
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__)),
                             '5MW_AFFiles')

        # 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

        P, T, Q, M = self.rotor.evaluate(Uinf, Omega, pitch)

        # 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)
Ejemplo n.º 11
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']
        Uhub = inputs['Uhub']
        Omega = inputs['Omega']
        pitch = inputs['pitch']

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

        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)

        # power, thrust, torque
        myoutputs, self.derivs = ccblade.evaluate(Uhub,
                                                  Omega,
                                                  pitch,
                                                  coefficients=False)
        outputs['T'] = myoutputs['T']
        outputs['Q'] = myoutputs['Q']
        outputs['P'] = myoutputs['P']
Ejemplo n.º 12
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]
Ejemplo n.º 13
0
class CCBladePower(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('Uhub',
                       val=np.zeros(npower),
                       units='m/s',
                       desc='hub height wind speed')
        self.add_input('Omega',
                       val=np.zeros(npower),
                       units='rpm',
                       desc='rotor rotation speed')
        self.add_input('pitch',
                       val=np.zeros(npower),
                       units='deg',
                       desc='blade pitch setting')

        # outputs
        self.add_output('T',
                        val=np.zeros(npower),
                        units='N',
                        desc='rotor aerodynamic thrust')
        self.add_output('Q',
                        val=np.zeros(npower),
                        units='N*m',
                        desc='rotor aerodynamic torque')
        self.add_output('P',
                        val=np.zeros(npower),
                        units='W',
                        desc='rotor aerodynamic power')

        # (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_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('airfoils', val=[0]*naero, desc='CCAirfoil instances')
        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(['P', 'T', 'Q'],['precone', 'tilt', 'hub_height', 'Rhub', 'Rtip', 'yaw',
        #                                       'Uhub', 'Omega', 'pitch', 'r', 'chord', 'theta',
        #                                       'precurve', 'precurveTip'])

    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.Uhub = inputs['Uhub']
        self.Omega = inputs['Omega']
        self.pitch = inputs['pitch']

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

        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)

        # power, thrust, torque
        self.P, self.T, self.Q, self.M, self.dP, self.dT, self.dQ \
            = self.ccblade.evaluate(self.Uhub, self.Omega, self.pitch, coefficients=False)
        outputs['T'] = self.T
        outputs['Q'] = self.Q
        outputs['P'] = self.P
        '''