From 1e95510d1cf0425f095cdaf5ff909a3401096be5 Mon Sep 17 00:00:00 2001 From: Nikhar Abbas Date: Tue, 4 Feb 2020 15:06:16 -0700 Subject: [PATCH] Move load_from_txt to utilities --- ROSCO_toolbox/turbine.py | 118 +++++++++++++++++++++---------------- ROSCO_toolbox/utilities.py | 63 ++++++++++++++++++-- 2 files changed, 126 insertions(+), 55 deletions(-) diff --git a/ROSCO_toolbox/turbine.py b/ROSCO_toolbox/turbine.py index 462100f00..8b3b959b7 100644 --- a/ROSCO_toolbox/turbine.py +++ b/ROSCO_toolbox/turbine.py @@ -13,12 +13,13 @@ import numpy as np import datetime from wisdem.ccblade import CCAirfoil, CCBlade -from wisdem.aeroelasticse.FAST_reader import InputReader_OpenFAST from scipy import interpolate from numpy import gradient import pickle import matplotlib.pyplot as plt +from ROSCO_toolbox import utilities as ROSCO_utilities + # Some useful constants now = datetime.datetime.now() pi = np.pi @@ -158,6 +159,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ 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') @@ -199,7 +201,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ # Load Cp, Ct, Cq tables if rot_source == 'txt': - self.load_from_txt(txt_filename) + self.pitch_initial_rad, self.TSR_initial, self.Cp_table, self.Ct_table, self.Cq_table = ROSCO_utilities.FileProcessing.load_from_txt(txt_filename) elif rot_source == 'cc-blade': self.load_from_ccblade(fast) else: # default load from cc-blade @@ -207,7 +209,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ print('No rotor performance data source available, running CC-Blade.') self.load_from_ccblade(fast) elif os.path.exists(txt_filename): - self.load_from_txt(txt_filename) + self.pitch_initial_rad, self.TSR_initial, self.Cp_table, self.Ct_table, self.Cq_table = ROSCO_utilities.FileProcessing.load_from_txt(txt_filename) else: print('No rotor performance data source available, running CC-Blade.') self.load_from_ccblade(fast) @@ -305,57 +307,73 @@ def load_from_ccblade(self,fast): self.Cp_table = Cp self.Ct_table = Ct self.Cq_table = Cq - - def load_from_txt(self,txt_filename): + + def load_blade_info(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_branch=True): ''' - Load rotor performance data from a *.txt file. - + Loads wind turbine blade data from an OpenFAST model + Parameters: ----------- - txt_filename: str - Filename of the text containing the Cp, Ct, and Cq data. This should be in the format printed by the write_rotorperformance function + 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 ''' - print('Loading rotor performace data from text file:', txt_filename) - - with open(txt_filename) as pfile: - for line in pfile: - # Read Blade Pitch Angles (degrees) - if 'Pitch angle' in line: - pitch_initial = np.array([float(x) for x in pfile.readline().strip().split()]) - pitch_initial_rad = pitch_initial * deg2rad # degrees to rad -- should this be conditional? - - # Read Tip Speed Ratios (rad) - if 'TSR' in line: - TSR_initial = np.array([float(x) for x in pfile.readline().strip().split()]) - - # Read Power Coefficients - if 'Power' in line: - pfile.readline() - Cp = np.empty((len(TSR_initial),len(pitch_initial))) - for tsr_i in range(len(TSR_initial)): - Cp[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) - - # Read Thrust Coefficients - if 'Thrust' in line: - pfile.readline() - Ct = np.empty((len(TSR_initial),len(pitch_initial))) - for tsr_i in range(len(TSR_initial)): - Ct[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) - - # Read Torque Coefficients - if 'Torque' in line: - pfile.readline() - Cq = np.empty((len(TSR_initial),len(pitch_initial))) - for tsr_i in range(len(TSR_initial)): - Cq[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) - - # Store necessary metrics for analysis and tuning - self.pitch_initial_rad = pitch_initial_rad - self.TSR_initial = TSR_initial - self.Cp_table = Cp - self.Ct_table = Ct - self.Cq_table = Cq - + 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) + fast.FAST_InputFile = FAST_InputFile + fast.FAST_directory = FAST_directory + fast.execute() + + # Make sure cc_rotor exists for DAC analysis + try: + exists(self.cc_rotor) + except NameError: + # Create CC-Blade Rotor + r0 = np.array(fast.fst_vt['AeroDynBlade']['BlSpn']) + chord0 = np.array(fast.fst_vt['AeroDynBlade']['BlChord']) + theta0 = np.array(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(fast.fst_vt['AeroDynBlade']['BlAFID']).astype(int) - 1 #Reset to 0 index + AFNames = 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, _ in enumerate(fast.fst_vt['AeroDyn15']['af_data']): + Re = [fast.fst_vt['AeroDyn15']['af_data'][i][0]['Re']] + Alpha = fast.fst_vt['AeroDyn15']['af_data'][i][0]['Alpha'] + Cl = fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cl'] + Cd = fast.fst_vt['AeroDyn15']['af_data'][i][0]['Cd'] + Cm = fast.fst_vt['AeroDyn15']['af_data'][i][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 = fast.fst_vt['AeroDyn15']['af_data'] + self.span = r + self.chord = chord + self.twist = theta + self.bld_flapwise_damp = fast.fst_vt['ElastoDynBlade']['BldFlDmp1'] class RotorPerformance(): ''' diff --git a/ROSCO_toolbox/utilities.py b/ROSCO_toolbox/utilities.py index c046cf8ce..0133983de 100644 --- a/ROSCO_toolbox/utilities.py +++ b/ROSCO_toolbox/utilities.py @@ -401,6 +401,7 @@ class FileProcessing(): def __init__(self): pass + def write_param_file(self, turbine, controller, param_file='DISCON.IN', txt_filename='Cp_Ct_Cq.txt'): """ Print the controller parameters to the DISCON.IN input file for the generic controller @@ -437,7 +438,7 @@ def write_param_file(self, turbine, controller, param_file='DISCON.IN', txt_file file.write('{0:<12d} ! PS_Mode - Pitch saturation mode {{0: no pitch saturation, 1: implement pitch saturation}}\n'.format(controller.PS_Mode > 0)) file.write('{0:<12d} ! SD_Mode - Shutdown mode {{0: no shutdown procedure, 1: pitch to max pitch at shutdown}}\n'.format(controller.SD_Mode)) file.write('{0:<12d} ! Fl_Mode - Floating specific feedback mode {{0: no nacelle velocity feedback, 1: nacelle velocity feedback}}\n'.format(controller.Fl_Mode)) - file.write('{0:<12d} ! Flp_Mode - Flap control mode {{0: no flap control, 1: steady state flap angle, 2: Proportional flap control}}\n'.format(controller.Fl_Mode)) + file.write('{0:<12d} ! Flp_Mode - Flap control mode {{0: no flap control, 1: steady state flap angle, 2: Proportional flap control}}\n'.format(controller.Flp_Mode)) file.write('\n') file.write('!------- FILTERS ----------------------------------------------------------\n') file.write('{:<13.5f} ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s]\n'.format(turbine.bld_edgewise_freq * 1/4)) @@ -446,7 +447,7 @@ def write_param_file(self, turbine, controller, param_file='DISCON.IN', txt_file file.write('{:<10.5f}{:<9.5f} ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-]\n'.format(0.0,0.25)) file.write('{:<014.5f} ! F_SSCornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, [rad/s].\n'.format(controller.ss_cornerfreq)) file.write('{:<10.5f}{:<9.5f} ! F_FlCornerFreq - Corner frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -].\n'.format(turbine.ptfm_freq, 1.0)) - file.write('{:<10.5f}{:<9.5f} ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -].\n'.format(turbine.bld_flapwise_freq, 0.7)) + file.write('{:<10.5f}{:<9.5f} ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -].\n'.format(turbine.bld_flapwise_freq*1/2, 0.7)) file.write('\n') file.write('!------- BLADE PITCH CONTROL ----------------------------------------------\n') @@ -539,8 +540,8 @@ def write_param_file(self, turbine, controller, param_file='DISCON.IN', txt_file file.write('\n') file.write('!------- FLAP ACTUATION -----------------------------------------------------\n') file.write('{:<014.5f} ! Flp_Angle - Initial or steady state flap angle [rad]\n'.format(controller.flp_angle)) - file.write('{:<014.5f} ! Flp_Kp - Blade root bending moment proportional gain for flap control [s]\n'.format(controller.Kp_flap)) - file.write('{:<014.5f} ! Flp_Ki - Flap displacement integral gain for flap control [s]'.format(controller.Ki_flap)) + file.write('{:<014.8e} ! Flp_Kp - Blade root bending moment proportional gain for flap control [s]\n'.format(controller.Kp_flap[-1])) + file.write('{:<014.8e} ! Flp_Ki - Flap displacement integral gain for flap control [s]'.format(controller.Ki_flap[-1])) file.close() def write_rotor_performance(self,turbine,txt_filename='Cp_Ct_Cq.txt'): @@ -592,4 +593,56 @@ def write_rotor_performance(self,turbine,txt_filename='Cp_Ct_Cq.txt'): file.write('{0:.6f} '.format(turbine.Cq_table[i,j])) file.write('\n') file.write('\n') - file.close() \ No newline at end of file + file.close() + + def load_from_txt(txt_filename): + ''' + Load rotor performance data from a *.txt file. + + Parameters: + ----------- + txt_filename: str + Filename of the text containing the Cp, Ct, and Cq data. This should be in the format printed by the write_rotorperformance function + ''' + print('Loading rotor performace data from text file:', txt_filename) + + with open(txt_filename) as pfile: + for line in pfile: + # Read Blade Pitch Angles (degrees) + if 'Pitch angle' in line: + pitch_initial = np.array([float(x) for x in pfile.readline().strip().split()]) + pitch_initial_rad = pitch_initial * deg2rad # degrees to rad -- should this be conditional? + + # Read Tip Speed Ratios (rad) + if 'TSR' in line: + TSR_initial = np.array([float(x) for x in pfile.readline().strip().split()]) + + # Read Power Coefficients + if 'Power' in line: + pfile.readline() + Cp = np.empty((len(TSR_initial),len(pitch_initial))) + for tsr_i in range(len(TSR_initial)): + Cp[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) + + # Read Thrust Coefficients + if 'Thrust' in line: + pfile.readline() + Ct = np.empty((len(TSR_initial),len(pitch_initial))) + for tsr_i in range(len(TSR_initial)): + Ct[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) + + # Read Torque Coefficients + if 'Torque' in line: + pfile.readline() + Cq = np.empty((len(TSR_initial),len(pitch_initial))) + for tsr_i in range(len(TSR_initial)): + Cq[tsr_i] = np.array([float(x) for x in pfile.readline().strip().split()]) + + # return pitch_initial_rad TSR_initial Cp Ct Cq + # Store necessary metrics for analysis and tuning + # self.pitch_initial_rad = pitch_initial_rad + # self.TSR_initial = TSR_initial + # self.Cp_table = Cp + # self.Ct_table = Ct + # self.Cq_table = Cq + return pitch_initial_rad, TSR_initial, Cp, Ct, Cq \ No newline at end of file