• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WISDEM / WEIS / 9244539224

26 May 2024 04:03PM UTC coverage: 80.482% (+9.6%) from 70.847%
9244539224

push

github

web-flow
Merge pull request #279 from WISDEM/wisdem_315

5 of 7 new or added lines in 2 files covered. (71.43%)

689 existing lines in 18 files now uncovered.

21615 of 26857 relevant lines covered (80.48%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.79
/weis/control/dac.py
1
import numpy as np
1✔
2
import os, sys, subprocess
1✔
3
import copy
1✔
4
from openmdao.api import ExplicitComponent
1✔
5
from wisdem.ccblade.ccblade import CCAirfoil, CCBlade
1✔
6
from wisdem.ccblade.Polar import Polar, _find_alpha0, _find_slope, _alpha_window_in_bounds
1✔
7
import csv  # for exporting airfoil polar tables
1✔
8
import matplotlib.pyplot as plt
1✔
9
import time
1✔
10

11
import multiprocessing as mp
1✔
12
from functools import partial
1✔
13
from wisdem.commonse.mpi_tools import MPI
1✔
14

15
def runXfoil(xfoil_path, x, y, Re, AoA_min=-9, AoA_max=25, AoA_inc=0.5, Ma=0.0, multi_run=False, MPI_run=False):
1✔
16
    #This function is used to create and run xfoil simulations for a given set of airfoil coordinates
17

18
    # Set initial parameters needed in xfoil
19
    numNodes   = 260 # number of panels to use (260...but increases if needed)
×
20
    #dist_param = 0.15 # TE/LE panel density ratio (0.15)
21
    dist_param = 0.12 #This is current value that i am trying to help with convergence (!bem)
×
22
    #IterLimit = 100 # Maximum number of iterations to try and get to convergence
23
    IterLimit = 10 #This decreased IterLimit will speed up analysis (!bem)
×
24
    #panelBunch = 1.5 # Panel bunching parameter to bunch near larger changes in profile gradients (1.5)
25
    panelBunch = 1.6 #This is the value I am currently using to try and improve convergence (!bem)
×
26
    #rBunch = 0.15 # Region to LE bunching parameter (used to put additional panels near flap hinge) (0.15)
27
    rBunch = 0.08 #This is the current value that I am using (!bem)
×
28
    XT1 = 0.55 # Defining left boundary of bunching region on top surface (should be before flap)
×
29
    # XT1 = 1.0
30
    #XT2 = 0.85 # Defining right boundary of bunching region on top surface (should be after flap)
31
    XT2 = 0.9 #This is the value I am currently using (!bem)
×
32
    # XT2 = 1.0
33
    XB1 = 0.55 # Defining left boundary of bunching region on bottom surface (should be before flap)
×
34
    # XB1 = 1.0
35
    #XB2 = 0.85 # Defining right boundary of bunching region on bottom surface (should be after flap)
36
    XB2 = 0.9 #This is the current value that I am using (!bem)
×
37
    # XB2 = 1.0
38
    runFlag = True # Flag used in error handling
×
39
    dfdn = -0.5 # Change in angle of attack during initialization runs down to AoA_min
×
40
    runNum = 0 # Initialized run number
×
41
    dfnFlag = False # This flag is used to determine if xfoil needs to be re-run if the simulation fails due to convergence issues at low angles of attack
×
42

43
    # Set filenames 
44
    # if multi_run or MPI_run:
45
    pid = mp.current_process().pid
×
46
    print('Running xfoil on PID = {}'.format(pid))
×
47

48
    xfoil_rundir = 'xfoil_run_p{}'.format(pid)
×
49
    if not os.path.exists(xfoil_rundir):
×
50
        os.makedirs(xfoil_rundir)
×
51
    LoadFlnmAF    = os.path.join(xfoil_rundir,'airfoil_p{}.txt'.format(pid))
×
52
    saveFlnmPolar = os.path.join(xfoil_rundir,'Polar_p{}.txt'.format(pid))
×
53
    xfoilFlnm     = os.path.join(xfoil_rundir,'xfoil_input_p{}.txt'.format(pid))
×
54
    NUL_fname     = os.path.join(xfoil_rundir,'NUL_p{}'.format(pid))
×
55

56
    # if MPI_run:
57
    #     rank = MPI.COMM_WORLD.Get_rank()
58
    #     LoadFlnmAF = 'airfoil_r{}.txt'.format(rank) # This is a temporary file that will be deleted after it is no longer needed
59
    #     saveFlnmPolar = 'Polar_r{}.txt'.format(rank) # file name of outpur xfoil polar (can be useful to look at during debugging...can also delete at end if you don't want it stored)
60
    #     xfoilFlnm  = 'xfoil_input_r{}.txt'.format(rank) # Xfoil run script that will be deleted after it is no longer needed
61
    # else:
62
    #     LoadFlnmAF = 'airfoil.txt' # This is a temporary file that will be deleted after it is no longer needed
63
    #     saveFlnmPolar = 'Polar.txt' # file name of outpur xfoil polar (can be useful to look at during debugging...can also delete at end if you don't want it stored)
64
    #     xfoilFlnm  = 'xfoil_input.txt' # Xfoil run script that will be deleted after it is no longer needed
65
    #     NUL_fname = 'NUL'
66
    t0 = time.time()
×
67
    while runFlag:
×
68
        # Cleaning up old files to prevent replacement issues
69
        if os.path.exists(saveFlnmPolar):
×
70
            os.remove(saveFlnmPolar)
×
71
        if os.path.exists(xfoilFlnm):
×
72
            os.remove(xfoilFlnm)
×
73
        if os.path.exists(LoadFlnmAF):
×
74
            os.remove(LoadFlnmAF)
×
75
        if os.path.exists(NUL_fname):
×
76
            os.remove(NUL_fname)
×
77
            
78
        # Writing temporary airfoil coordinate file for use in xfoil
79
        dat=np.array([x,y])
×
80
        np.savetxt(LoadFlnmAF, dat.T, fmt=['%f','%f'])
×
81

82
        # %% Writes the Xfoil run script to read in coordinates, create flap, re-pannel, and create polar
83
        # Create the airfoil with flap
84
        fid = open(xfoilFlnm,"w")
×
85
        fid.write("PLOP \n G \n\n") # turn off graphics
×
86
        fid.write("LOAD \n")
×
87
        fid.write( LoadFlnmAF + "\n" + "\n") # name of .txt file with airfoil coordinates
×
88
        # fid.write( self.AFName + "\n") # set name of airfoil (internal to xfoil)
89
        fid.write("GDES \n") # enter into geometry editing tools in xfoil
×
90
        fid.write("UNIT \n") # normalize profile to unit chord
×
91
        fid.write("EXEC \n \n") # move buffer airfoil to current airfoil
×
92

93
        # Re-panel with specified number of panes and LE/TE panel density ratio
94
        fid.write("PPAR\n")
×
95
        fid.write("N \n" )
×
96
        fid.write(str(numNodes) + "\n")
×
97
        fid.write("P \n") # set panel bunching parameter
×
98
        fid.write(str(panelBunch) + " \n")
×
99
        fid.write("T \n") # set TE/LE panel density ratio
×
100
        fid.write( str(dist_param) + "\n")
×
101
        fid.write("R \n") # set region panel bunching ratio
×
102
        fid.write(str(rBunch) + " \n")
×
103
        fid.write("XT \n") # set region panel bunching bounds on top surface
×
104
        fid.write(str(XT1) +" \n" + str(XT2) + " \n")
×
105
        fid.write("XB \n") # set region panel bunching bounds on bottom surface
×
106
        fid.write(str(XB1) +" \n" + str(XB2) + " \n")
×
107
        fid.write("\n\n")
×
108

109
        # Set Simulation parameters (Re and max number of iterations)
110
        fid.write("OPER\n")
×
111
        fid.write("VISC \n")
×
112
        fid.write( str(Re) + "\n") # this sets Re to value specified in yaml file as an input
×
113
        #fid.write( "5000000 \n") # bem: I was having trouble geting convergence for some of the thinner airfoils at the tip for the large Re specified in the yaml, so I am hard coding in Re (5e6 is the highest I was able to get to using these paneling parameters)
114
        fid.write("MACH\n")
×
115
        fid.write(str(Ma)+" \n")
×
116
        fid.write("ITER \n")
×
117
        fid.write( str(IterLimit) + "\n")
×
118

119
        # Run simulations for range of AoA
120

121
        if dfnFlag: # bem: This if statement is for the case when there are issues getting convergence at AoA_min.  It runs a preliminary set of AoA's down to AoA_min (does not save them)
×
122
            for ii in range(int((0.0-AoA_min)/AoA_inc+1)):
×
123
                fid.write("ALFA "+ str(0.0-ii*float(AoA_inc)) +"\n")
×
124

125
        fid.write("PACC\n\n\n") #Toggle saving polar on
×
126
        # fid.write("ASEQ 0 " + str(AoA_min) + " " + str(dfdn) + "\n") # The preliminary runs are just to get an initialize airfoil solution at min AoA so that the actual runs will not become unstable
127

128
        for ii in range(int((AoA_max-AoA_min)/AoA_inc+1)): # bem: run each AoA seperately (makes polar generation more convergence error tolerant)
×
129
            fid.write("ALFA "+ str(AoA_min+ii*float(AoA_inc)) +"\n")
×
130

131
        #fid.write("ASEQ " + str(AoA_min) + " " + "16" + " " + str(AoA_inc) + "\n") #run simulations for desired range of AoA using a coarse step size in AoA up to 16 deg
132
        #fid.write("ASEQ " + "16.5" + " " + str(AoA_max) + " " + "0.1" + "\n") #run simulations for desired range of AoA using a fine AoA increment up to final AoA to help with convergence issues at high Re
133
        fid.write("PWRT\n") #Toggle saving polar off
×
134
        fid.write(saveFlnmPolar + " \n \n")
×
135
        fid.write("QUIT \n")
×
136
        fid.close()
×
137

138
        # Run the XFoil calling command
139
        try:
×
140
            subprocess.run([xfoil_path], stdin=open(xfoilFlnm,'r'), stdout=open(NUL_fname, 'w'), timeout=300)
×
141
            flap_polar = np.loadtxt(saveFlnmPolar,skiprows=12)
×
142
        except subprocess.TimeoutExpired:
×
143
            print('XFOIL timeout on p{}'.format(pid)) 
×
144
            try: 
×
145
                flap_polar = np.loadtxt(saveFlnmPolar,skiprows=12) # Sometimes xfoil will hang up but still generate a good set of polars
×
146
            except:
×
147
                flap_polar = []  # in case no convergence was achieved
×
148
        except:
×
149
            flap_polar = []  # in case no convergence was achieved
×
150

151
        # Check for linear region
152
        try:
×
153
            window = _alpha_window_in_bounds(flap_polar[:,0],[-30, 30])
×
154
            alpha0 = _find_alpha0(np.array(flap_polar[:,0]), np.array(flap_polar[:,1]), window)
×
155
            window2 = [alpha0, alpha0+4]
×
156
            window2 = _alpha_window_in_bounds(flap_polar[:,0], [alpha0, alpha0 + 4])
×
157
            # Max and Safety checks
158
            s1, _ = _find_slope(flap_polar[:,0], flap_polar[:,1], xi=alpha0, window=window2, method="max")
×
159
            if len(flap_polar[:,1]) > 10:
×
160
                s2, _ = _find_slope(flap_polar[:,0], flap_polar[:,1], xi=alpha0, window=window2, method="finitediff_1c")
×
161
            lin_region_len = len(np.where(flap_polar[:,0] < alpha0)[0])
×
162
            lin_region_len_idx = np.where(flap_polar[:,0] < alpha0)[0][-1]
×
163
            if lin_region_len_idx < 1:
×
164
                lin_region_len = 0 
×
165
                raise IndexError('Invalid index for linear region.')
×
166
        except (IndexError, TypeError):
×
167
            lin_region_len = 0
×
168
            
169
        if lin_region_len < 1:
×
170
            print('Error: No linear region detected for XFOIL run on p{}'.format(pid))
×
171

172
        # Error handling (re-run simulations with more panels if there is not enough data in polars)
173
        if np.size(flap_polar) < 3 or lin_region_len < 1: # This case is if there are convergence issues or bad angles of attack
×
174
            plen = 0
×
175
            a0 = 0
×
176
            a1 = 0
×
177
            dfdn = -0.25 # decrease AoA step size during initialization to try and get convergence in the next run
×
178
            dfnFlag = True # Set flag to run initialization AoA down to AoA_min
×
179
            print('XFOIL convergence issues on p{}'.format(pid))
×
180
        else:
181
            plen = len(flap_polar[:,0]) # Number of AoA's in polar
×
182
            a0 = flap_polar[-1,0] # Maximum AoA in Polar
×
183
            a1 = flap_polar[0,0] # Minimum AoA in Polar
×
184
            dfnFlag = False # Set flag so that you don't need to run initialization sequence
×
185

186
        if a0 > 19. and plen >= 40 and a1 < -12.5: # The a0 > 19 is to check to make sure polar entered into stall regiem plen >= 40 makes sure there are enough AoA's in polar for interpolation and a1 < -15 makes sure polar contains negative stall.
×
187
            runFlag = False # No need ro re-run polar
×
188
            if numNodes > 310:
×
189
                print('Xfoil completed after {} attempts on run on p{}.'.format(runNum+1, pid))
×
190
        else:
191
            numNodes += 50 # Re-run with additional panels
×
192
            # AoA_inc *= 0.5
193
            runNum += 1 # Update run number
×
194
            # AoA_min = -9
195
            # AoA_max = 25
196
            # if numNodes > 480:
197
            if runNum > 10:
×
198
                # Warning('NO convergence in XFoil achieved!')
199
                print('No convergence in XFOIL achieved on p{}!'.format(pid))
×
200
                if not os.path.exists('xfoil_errorfiles'):
×
201
                    os.makedirs('xfoil_errorfiles')
×
202
                try:
×
203
                    os.rename(xfoilFlnm, os.path.join('xfoil_errorfiles', xfoilFlnm))
×
204
                except:
×
205
                    pass
×
206
                try:
×
207
                    os.rename(saveFlnmPolar, os.path.join('xfoil_errorfiles', saveFlnmPolar))
×
208
                except:
×
209
                    pass
×
210
                try:
×
211
                    os.rename(LoadFlnmAF, os.path.join('xfoil_errorfiles', LoadFlnmAF))
×
212
                except:
×
213
                    pass
×
214
                try:
×
215
                    os.rename(NUL_fname, os.path.join('xfoil_errorfiles', NUL_fname))
×
216
                except:
×
217
                    pass
×
218
                
219
                break
×
220
            print('Refining paneling to ' + str(numNodes) + ' nodes')
×
221

222
    # Load back in polar data to be saved in instance variables
223
    #flap_polar = np.loadtxt(LoadFlnmAF,skiprows=12) # (note, we are assuming raw Xfoil polars when skipping the first 12 lines)
224
    # self.af_flap_polar = flap_polar
225
    # self.flap_polar_flnm = saveFlnmPolar # Not really needed unless you keep the files and want to load them later
226

227
    # Delete Xfoil run script file
228
    if os.path.exists(xfoilFlnm):
×
229
        os.remove(xfoilFlnm)
×
230
    if os.path.exists(saveFlnmPolar): # bem: For now leave the files, but eventually we can get rid of them (remove # in front of commands) so that we don't have to store them
×
231
        os.remove(saveFlnmPolar)
×
232
    if os.path.exists(LoadFlnmAF):
×
233
        os.remove(LoadFlnmAF)
×
234
    if os.path.exists(NUL_fname):
×
235
        os.remove(NUL_fname)
×
236
    if os.path.exists(xfoil_rundir):
×
237
        os.rmdir(xfoil_rundir)
×
238

239
    print('Xfoil calls on p{} completed in {} seconds'.format(pid, time.time()-t0))
×
240

241
    return flap_polar
×
242

243
class RunXFOIL(ExplicitComponent):
1✔
244
    # Openmdao component to run XFOIL and re-compute polars
245
    def initialize(self):
1✔
246
        self.options.declare('modeling_options')
1✔
247
        self.options.declare('opt_options')
1✔
248
        
249
    def setup(self):
1✔
250
        rotorse_options = self.options['modeling_options']['WISDEM']['RotorSE']
1✔
251
        self.n_span        = n_span     = rotorse_options['n_span']
1✔
252
        self.n_te_flaps    = n_te_flaps = rotorse_options['n_te_flaps']
1✔
253
        self.n_tab         = rotorse_options['n_tab']
1✔
254
        self.n_aoa         = n_aoa      = rotorse_options['n_aoa'] # Number of angle of attacks
1✔
255
        self.n_Re          = n_Re      = rotorse_options['n_Re'] # Number of Reynolds, so far hard set at 1
1✔
256
        self.n_tab         = n_tab     = rotorse_options['n_tab']# Number of tabulated data. For distributed aerodynamic control this could be > 1
1✔
257
        self.n_xy          = n_xy      = rotorse_options['n_xy'] # Number of coordinate points to describe the airfoil geometry
1✔
258

259
        # Use openfast cores for parallelization of xfoil 
260
        xfoilpref = self.options['modeling_options']['Level3']['xfoil']
1✔
261
        self.xfoil_path = xfoilpref['path']
1✔
262

263
        try:
1✔
264
            if xfoilpref['run_parallel']:
1✔
265
                self.cores = mp.cpu_count()
×
266
            else:
267
                self.cores = 1
1✔
268
        except KeyError:
×
269
            self.cores = 1
×
270
        
271
        if MPI and self.options['modeling_options']['Level3']['flag'] and not self.options['opt_options']['driver']['optimization']['flag']:
1✔
272
            self.mpi_comm_map_down = self.options['modeling_options']['General']['openfast_configuration']['mpi_comm_map_down']
×
273

274
        # Inputs blade outer shape
275
        self.add_input('s',          val=np.zeros(n_span),                      desc='1D array of the non-dimensional spanwise grid defined along blade axis (0-blade root, 1-blade tip)')
1✔
276
        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)')
1✔
277
        self.add_input('coord_xy_interp',  val=np.zeros((n_span, n_xy, 2)),     desc='3D array of the non-dimensional x and y airfoil coordinates of the airfoils interpolated along span for n_span stations.')
1✔
278
        self.add_input('chord',         val=np.zeros(n_span), units='m',   desc='chord length at each section')
1✔
279

280
        # Inputs flaps
281
        self.add_input('span_end',   val=np.zeros(n_te_flaps),                  desc='1D array of the positions along blade span where the trailing edge flap(s) end. Only values between 0 and 1 are meaningful.')
1✔
282
        self.add_input('span_ext',   val=np.zeros(n_te_flaps),                  desc='1D array of the extensions along blade span of the trailing edge flap(s). Only values between 0 and 1 are meaningful.')
1✔
283
        self.add_input('chord_start',val=np.zeros(n_te_flaps),                  desc='1D array of the positions along chord where the trailing edge flap(s) start. Only values between 0 and 1 are meaningful.')
1✔
284
        self.add_input('delta_max_pos', val=np.zeros(n_te_flaps), units='rad',  desc='1D array of the max angle of the trailing edge flaps.')
1✔
285
        self.add_input('delta_max_neg', val=np.zeros(n_te_flaps), units='rad',  desc='1D array of the min angle of the trailing edge flaps.')
1✔
286

287
        # Inputs control
288
        self.add_input('max_TS',         val=0.0, units='m/s',     desc='Maximum allowed blade tip speed.')
1✔
289
        self.add_input('rated_TSR',      val=0.0,                  desc='Constant tip speed ratio in region II.')
1✔
290

291
        # Inputs environment
292
        self.add_input('rho_air',      val=1.225,        units='kg/m**3',    desc='Density of air')
1✔
293
        self.add_input('mu_air',       val=1.81e-5,      units='kg/(m*s)',   desc='Dynamic viscosity of air')
1✔
294
        self.add_input('speed_sound_air',  val=340.,     units='m/s',        desc='Speed of sound in air.')
1✔
295

296
        # Inputs polars
297
        self.add_input('aoa',       val=np.zeros(n_aoa),        units='rad',    desc='1D array of the angles of attack used to define the polars of the airfoils. All airfoils defined in openmdao share this grid.')
1✔
298
        self.add_input('cl_interp',        val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the lift coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
299
        self.add_input('cd_interp',        val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the drag coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
300
        self.add_input('cm_interp',        val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the moment coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
301

302
        # Outputs flap geometry
303
        self.add_output('span_start', val=np.zeros(n_te_flaps),                  desc='1D array of the positions along blade span where the trailing edge flap(s) start. Only values between 0 and 1 are meaningful.')
1✔
304
        
305
        # Output polars
306
        self.add_output('cl_interp_flaps',  val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the lift coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
307
        self.add_output('cd_interp_flaps',  val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the drag coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
308
        self.add_output('cm_interp_flaps',  val=np.zeros((n_span, n_aoa, n_Re, n_tab)),   desc='4D array with the moment coefficients of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the angles of attack, dimension 2 is along the Reynolds number, dimension 3 is along the number of tabs, which may describe multiple sets at the same station, for example in presence of a flap.')
1✔
309
        self.add_output('flap_angles',      val=np.zeros((n_span, n_Re, n_tab)), units = 'deg',   desc='3D array with the flap angles of the airfoils. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the Reynolds number, dimension 2 is along the number of tabs, which may describe multiple sets at the same station.')
1✔
310
        self.add_output('Re_loc',           val=np.zeros((n_span, n_Re, n_tab)),   desc='3D array with the Re. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the Reynolds number, dimension 2 is along the number of tabs, which may describe multiple sets at the same station.')
1✔
311
        self.add_output('Ma_loc',           val=np.zeros((n_span, n_Re, n_tab)),   desc='3D array with the Mach number. Dimension 0 is along the blade span for n_span stations, dimension 1 is along the Reynolds number, dimension 2 is along the number of tabs, which may describe multiple sets at the same station.')
1✔
312

313
        # initialize saved data polar data. 
314
        # - This is filled if we're not changing the flaps, so we don't need to re-run xfoil every time
315
        self.saved_polar_data = {}
1✔
316

317
    def compute(self, inputs, outputs):
1✔
318

319
        # If trailing edge flaps are present, compute the perturbed profiles with XFOIL
320
        self.flap_profiles = [{} for i in range(self.n_span)]
1✔
321
        if self.n_te_flaps > 0:
1✔
322
            try:
×
323
                from scipy.ndimage import gaussian_filter
×
324
            except:
×
325
                print('Cannot import the library gaussian_filter from scipy. Please check the conda environment and potential conflicts between numpy and scipy')
×
326
            
327
            # Make sure flaps are viable
328
            if inputs['span_end'] > 1.0:
×
329
                print('WARNING: TE Flap end is off the blade! Moving it to the end of the blade.')
×
330
            if self.options['opt_options']['design_variables']['control']['flaps']['te_flap_end']['flag']: 
×
331
                np.clip(inputs['span_end'], 
×
332
                        self.options['opt_options']['design_variables']['control']['flaps']['te_flap_end']['min'], 
333
                        self.options['opt_options']['design_variables']['control']['flaps']['te_flap_end']['max']
334
                        )
335

336
            outputs['span_start'] = inputs['span_end'] - inputs['span_ext']
×
337

338
            xfoil_kw = {}
×
339
            if MPI:
×
340
                xfoil_kw['MPI_run'] = True
×
341
            elif self.cores > 1:
×
342
                xfoil_kw['multi_run'] = True
×
343
            for i in range(self.n_span):
×
344
                # Loop through the flaps specified in yaml file
345
                for k in range(self.n_te_flaps):
×
346
                    # Only create flap geometries where the yaml file specifies there is a flap (Currently going to nearest blade station location)
347
                    if inputs['s'][i] >= outputs['span_start'][k] and inputs['s'][i] <= inputs['span_end'][k]: 
×
348
                        self.flap_profiles[i]['flap_angles']= []
×
349
                        # Initialize the profile coordinates to zeros
350
                        self.flap_profiles[i]['coords']     = np.zeros([self.n_xy,2,self.n_tab]) 
×
351
                            # Ben:I am not going to force it to include delta=0.  If this is needed, a more complicated way of getting flap deflections to calculate is needed.
352
                        flap_angles = np.linspace(inputs['delta_max_neg'][k],inputs['delta_max_pos'][k],self.n_tab) * 180. / np.pi
×
353
                        # Loop through the flap angles
354
                        for ind, fa in enumerate(flap_angles):
×
355
                            # NOTE: negative flap angles are deflected to the suction side, i.e. positively along the positive z- (radial) axis
356
                            af_flap = CCAirfoil(np.array([1,2,3]), np.array([100]), np.zeros(3), np.zeros(3), np.zeros(3), inputs['coord_xy_interp'][i,:,0], inputs['coord_xy_interp'][i,:,1], "Profile"+str(i)) # bem:I am creating an airfoil name based on index...this structure/naming convention is being assumed in CCAirfoil.runXfoil() via the naming convention used in CCAirfoil.af_flap_coords(). Note that all of the inputs besides profile coordinates and name are just dummy varaiables at this point.
×
357
                            af_flap.af_flap_coords(self.xfoil_path, fa,  inputs['chord_start'][k],0.5,200, **xfoil_kw) #bem: the last number is the number of points in the profile.  It is currently being hard coded at 200 but should be changed to make sure it is the same number of points as the other profiles
×
358
                            # self.flap_profiles[i]['coords'][:,0,ind] = af_flap.af_flap_xcoords # x-coords from xfoil file with flaps
359
                            # self.flap_profiles[i]['coords'][:,1,ind] = af_flap.af_flap_ycoords # y-coords from xfoil file with flaps
360
                            # self.flap_profiles[i]['coords'][:,0,ind] = af_flap.af_flap_xcoords  # x-coords from xfoil file with flaps and NO gaussian filter for smoothing
361
                            # self.flap_profiles[i]['coords'][:,1,ind] = af_flap.af_flap_ycoords  # y-coords from xfoil file with flaps and NO gaussian filter for smoothing
362
                            try:
×
363
                                self.flap_profiles[i]['coords'][:,0,ind] = gaussian_filter(af_flap.af_flap_xcoords, sigma=1) # x-coords from xfoil file with flaps and gaussian filter for smoothing
×
364
                                self.flap_profiles[i]['coords'][:,1,ind] = gaussian_filter(af_flap.af_flap_ycoords, sigma=1) # y-coords from xfoil file with flaps and gaussian filter for smoothing
×
365
                            except:
×
366
                                self.flap_profiles[i]['coords'][:,0,ind] = af_flap.af_flap_xcoords
×
367
                                self.flap_profiles[i]['coords'][:,1,ind] = af_flap.af_flap_ycoords
×
368
                            self.flap_profiles[i]['flap_angles'].append([])
×
369
                            self.flap_profiles[i]['flap_angles'][ind] = fa # Putting in flap angles to blade for each profile (can be used for debugging later)
×
370

371

372
                        if False:
×
373
                            import pickle
374
                            f = open('flap_profiles.pkl', 'wb')
375
                            pickle.dump(self.flap_profiles, f)
376
                            f.close()
377
                            
378
                        # # ** The code below will plot the first three flap deflection profiles (in the case where there are only 3 this will correspond to max negative, zero, and max positive deflection cases)
379
                        # font = {'family': 'Times New Roman',
380
                        #         'weight': 'normal',
381
                        #         'size': 18}
382
                        # plt.rc('font', **font)
383
                        # plt.figure
384
                        # fig, ax = plt.subplots(1, 1, figsize=(8, 5))
385
                        # # plt.plot(self.flap_profiles[i]['coords'][:,0,0], self.flap_profiles[i]['coords'][:,1,0], 'r',self.flap_profiles[i]['coords'][:,0,1], self.flap_profiles[i]['coords'][:,1,1], 'k',self.flap_profiles[i]['coords'][:,0,2], self.flap_profiles[i]['coords'][:,1,2], 'b')
386
                        # plt.plot(self.flap_profiles[i]['coords'][:, 0, 0],
387
                        #         self.flap_profiles[i]['coords'][:, 1, 0], '.r',
388
                        #         self.flap_profiles[i]['coords'][:, 0, 2],
389
                        #         self.flap_profiles[i]['coords'][:, 1, 2], '.b',
390
                        #         self.flap_profiles[i]['coords'][:, 0, 1],
391
                        #         self.flap_profiles[i]['coords'][:, 1, 1], '.k')
392
                        
393
                        # # plt.xlabel('x')
394
                        # # plt.ylabel('y')
395
                        # plt.axis('equal')
396
                        # plt.axis('off')
397
                        # plt.tight_layout()
398
                        # plt.show()
399
                        # # # plt.savefig('temp/airfoil_polars/NACA63-self.618_flap_profiles.png', dpi=300)
400
                        # # # plt.savefig('temp/airfoil_polars/FFA-W3-self.211_flap_profiles.png', dpi=300)
401
                        # # # plt.savefig('temp/airfoil_polars/FFA-W3-self.241_flap_profiles.png', dpi=300)
402
                        # # # plt.savefig('temp/airfoil_polars/FFA-W3-self.301_flap_profiles.png', dpi=300)
403

404

405
        # ----------------------------------------------------- #
406
        # Determine airfoil polar tables blade sections #
407

408
        #  ToDo: shape of blade['profile'] differs from self.flap_profiles <<< change to same shape
409
        # only execute when flag_airfoil_polars = True
410
        flag_airfoil_polars = False  # <<< ToDo get through Yaml in the future ?!?
1✔
411

412
        if flag_airfoil_polars == True:
1✔
413
            # OUTDATED!!! - NJA
414

415
            af_orig_grid = blade['outer_shape_bem']['airfoil_position']['grid']
×
416
            af_orig_labels = blade['outer_shape_bem']['airfoil_position']['labels']
×
417
            af_orig_chord_grid = blade['outer_shape_bem']['chord']['grid']  # note: different grid than airfoil labels
×
418
            af_orig_chord_value = blade['outer_shape_bem']['chord']['values']
×
419

420
            for i_af_orig in range(len(af_orig_grid)):
×
421
                if af_orig_labels[i_af_orig] != 'circular':
×
422
                    print('Determine airfoil polars:')
×
423

424
                    # check index of chord grid for given airfoil radial station
425
                    for i_chord_grid in range(len(af_orig_chord_grid)):
×
426
                        if af_orig_chord_grid[i_chord_grid] == af_orig_grid[i_af_orig]:
×
427
                            c = af_orig_chord_value[i_chord_grid]  # get chord length at current radial station of original airfoil
×
428
                            c_index = i_chord_grid
×
429

430

431
                    flag_coord = 3  # Define which blade airfoil outer shapes coordinates to use (watch out for consistency throughout the model/analysis !!!)
×
432
                    #  Get orig coordinates (too many for XFoil)
433
                    if flag_coord == 1:
×
434
                        x_af = self.wt_ref['airfoils'][1]['coordinates']['x']
×
435
                        y_af = self.wt_ref['airfoils'][1]['coordinates']['y']
×
436

437

438
                    #  Get interpolated coords
439
                    if flag_coord == 2:
×
440
                        x_af = blade['profile'][:,0,c_index]
×
441
                        y_af = blade['profile'][:,1,c_index]
×
442

443

444
                    # create coords using ccblade and calling XFoil in order to be consistent with the flap method
445
                    if flag_coord == 3:
×
446
                        flap_angle = 0  # no te-flaps !
×
447
                        af_temp = CCAirfoil(np.array([1,2,3]), np.array([100]), np.zeros(3), np.zeros(3), np.zeros(3), blade['profile'][:,0,c_index],blade['profile'][:,1,c_index], "Profile"+str(c_index)) # bem:I am creating an airfoil name based on index...this structure/naming convention is being assumed in CCAirfoil.runXfoil() via the naming convention used in CCAirfoil.af_flap_coords(). Note that all of the inputs besides profile coordinates and name are just dummy varaiables at this point.
×
448
                        af_temp.af_flap_coords(self.xfoil_path, flap_angle,  0.8, 0.5, 200) #bem: the last number is the number of points in the profile.  It is currently being hard coded at 200 but should be changed to make sure it is the same number of points as the other profiles
×
449
                        # x_af = af_temp.af_flap_xcoords
450
                        # y_af = af_temp.af_flap_ycoords
451

452
                        x_af = gaussian_filter(af_temp.af_flap_xcoords, sigma=1)  # gaussian filter for smoothing (in order to be consistent with flap capabilities)
×
453
                        y_af = gaussian_filter(af_temp.af_flap_ycoords, sigma=1)  # gaussian filter for smoothing (in order to be consistent with flap capabilities)
×
454

455

456
                    rR = af_orig_grid[i_af_orig]  # non-dimensional blade radial station at cross section
×
457
                    R = blade['pf']['r'][-1]  # blade (global) radial length
×
458
                    tsr = blade['config']['tsr']  # tip-speed ratio
×
459
                    maxTS = blade['assembly']['control']['maxTS']  # max blade-tip speed (m/s) from yaml file
×
460
                    KinVisc = blade['environment']['air_data']['KinVisc']  # Kinematic viscosity (m^2/s) from yaml file
×
461
                    SpdSound = blade['environment']['air_data']['SpdSound']  # speed of sound (m/s) from yaml file
×
462
                    Re_af_orig_loc = c * maxTS * rR / KinVisc
×
463
                    Ma_af_orig_loc = maxTS * rR / SpdSound
×
464

465
                    print('Run xfoil for airfoil ' + af_orig_labels[i_af_orig] + ' at span section r/R = ' + str(rR) + ' with Re equal to ' + str(Re_af_orig_loc) + ' and Ma equal to ' + str(Ma_af_orig_loc))
×
466
                    # if af_orig_labels[i_af_orig] == 'NACA63-618':  # reduce AoAmin for (thinner) airfoil at the blade tip due to convergence reasons in XFoil
467
                    #     data = self.runXfoil(x_af, y_af_orig, Re_af_orig_loc, -13.5, 25., 0.5, Ma_af_orig_loc)
468
                    # else:
469
                    data = self.runXfoil(x_af, y_af, Re_af_orig_loc, -20., 25., 0.5, Ma_af_orig_loc)
×
470

471
                    oldpolar = Polar(Re_af_orig_loc, data[:, 0], data[:, 1], data[:, 2], data[:, 4])  # p[:,0] is alpha, p[:,1] is Cl, p[:,2] is Cd, p[:,4] is Cm
×
472

473
                    polar3d = oldpolar.correction3D(rR, c/R, tsr)  # Apply 3D corrections (made sure to change the r/R, c/R, and tsr values appropriately when calling AFcorrections())
×
474
                    cdmax = 1.5
×
475
                    polar = polar3d.extrapolate(cdmax)  # Extrapolate polars for alpha between -180 deg and 180 deg
×
476

477
                    cl_interp = np.interp(np.degrees(alpha), polar.alpha, polar.cl)
×
478
                    cd_interp = np.interp(np.degrees(alpha), polar.alpha, polar.cd)
×
479
                    cm_interp = np.interp(np.degrees(alpha), polar.alpha, polar.cm)
×
480

481
                    # --- PROFILE ---#
482
                    # write profile (that was input to XFoil; although previously provided in the yaml file)
483
                    with open('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_profile.csv', 'w') as profile_csvfile:
×
484
                        profile_csvfile_writer = csv.writer(profile_csvfile, delimiter=',')
×
485
                        profile_csvfile_writer.writerow(['x', 'y'])
×
486
                        for i in range(len(x_af)):
×
487
                            profile_csvfile_writer.writerow([x_af[i], y_af[i]])
×
488

489
                    # plot profile
490
                    plt.figure(i_af_orig)
×
491
                    plt.plot(x_af, y_af, 'k')
×
492
                    plt.axis('equal')
×
493
                    # plt.show()
494
                    plt.savefig('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_profile.png')
×
495
                    plt.close(i_af_orig)
×
496

497
                    # --- CL --- #
498
                    # write cl
499
                    with open('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cl.csv', 'w') as cl_csvfile:
×
500
                        cl_csvfile_writer = csv.writer(cl_csvfile, delimiter=',')
×
501
                        cl_csvfile_writer.writerow(['alpha, deg', 'alpha, rad', 'cl'])
×
502
                        for i in range(len(cl_interp)):
×
503
                            cl_csvfile_writer.writerow([np.degrees(alpha[i]), alpha[i], cl_interp[i]])
×
504

505
                    # plot cl
506
                    plt.figure(i_af_orig)
×
507
                    fig, ax = plt.subplots(1,1, figsize= (8,5))
×
508
                    plt.plot(np.degrees(alpha), cl_interp, 'b')
×
509
                    plt.xlim(xmin=-25, xmax=25)
×
510
                    plt.grid(True)
×
511
                    autoscale_y(ax)
×
512
                    plt.xlabel('Angles of attack, deg')
×
513
                    plt.ylabel('Lift coefficient')
×
514
                    # plt.show()
515
                    plt.savefig('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cl.png')
×
516
                    plt.close(i_af_orig)
×
517

518
                    # write cd
519
                    with open('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cd.csv', 'w') as cd_csvfile:
×
520
                        cd_csvfile_writer = csv.writer(cd_csvfile, delimiter=',')
×
521
                        cd_csvfile_writer.writerow(['alpha, deg', 'alpha, rad', 'cd'])
×
522
                        for i in range(len(cd_interp)):
×
523
                            cd_csvfile_writer.writerow([np.degrees(alpha[i]), alpha[i], cd_interp[i]])
×
524

525
                    # plot cd
526
                    plt.figure(i_af_orig)
×
527
                    fig, ax = plt.subplots(1,1, figsize= (8,5))
×
528
                    plt.plot(np.degrees(alpha), cd_interp, 'r')
×
529
                    plt.xlim(xmin=-25, xmax=25)
×
530
                    plt.grid(True)
×
531
                    autoscale_y(ax)
×
532
                    plt.xlabel('Angles of attack, deg')
×
533
                    plt.ylabel('Drag coefficient')
×
534
                    # plt.show()
535
                    plt.savefig('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cd.png')
×
536
                    plt.close(i_af_orig)
×
537

538
                    # write cm
539
                    with open('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cm.csv', 'w') as cm_csvfile:
×
540
                        cm_csvfile_writer = csv.writer(cm_csvfile, delimiter=',')
×
541
                        cm_csvfile_writer.writerow(['alpha, deg', 'alpha, rad', 'cm'])
×
542
                        for i in range(len(cm_interp)):
×
543
                            cm_csvfile_writer.writerow([np.degrees(alpha[i]), alpha[i], cm_interp[i]])
×
544

545
                    # plot cm
546
                    plt.figure(i_af_orig)
×
547
                    fig, ax = plt.subplots(1,1, figsize= (8,5))
×
548
                    plt.plot(np.degrees(alpha), cm_interp, 'g')
×
549
                    plt.xlim(xmin=-25, xmax=25)
×
550
                    plt.grid(True)
×
551
                    autoscale_y(ax)
×
552
                    plt.xlabel('Angles of attack, deg')
×
553
                    plt.ylabel('Torque coefficient')
×
554
                    # plt.show()
555
                    plt.savefig('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_cm.png')
×
556
                    plt.close(i_af_orig)
×
557

558
                    # write additional information (Re, Ma, r/R)
559
                    with open('temp/airfoil_polars/' + af_orig_labels[i_af_orig] + '_add_info.csv', 'w') as csvfile:
×
560
                        csvfile_writer = csv.writer(csvfile, delimiter=',')
×
561
                        csvfile_writer.writerow(['Re', 'Ma', 'r/R'])
×
562
                        csvfile_writer.writerow([Re_af_orig_loc, Ma_af_orig_loc, rR])
×
563

564
                    plt.close('all')
×
565
        # ------------------------------------------------------------ #
566
        # Determine airfoil polar tables for blade sections with flaps #
567

568
        self.R        = inputs['r'][-1]  # Rotor radius in meters
1✔
569
        self.tsr      = inputs['rated_TSR']  # tip-speed ratio
1✔
570
        self.maxTS    = inputs['max_TS']  # max blade-tip speed (m/s) from yaml file
1✔
571
        self.KinVisc  = inputs['mu_air'] / inputs['rho_air']  # Kinematic viscosity (m^2/s) from yaml file
1✔
572
        self.SpdSound = inputs['speed_sound_air'] # speed of sound (m/s) from yaml file
1✔
573
        
574
        # Initialize
575
        cl_interp_flaps = inputs['cl_interp']
1✔
576
        cd_interp_flaps = inputs['cd_interp']
1✔
577
        cm_interp_flaps = inputs['cm_interp']
1✔
578
        fa_control = np.zeros((self.n_span, self.n_Re, self.n_tab))
1✔
579
        Re_loc = np.zeros((self.n_span, self.n_Re, self.n_tab))
1✔
580
        Ma_loc = np.zeros((self.n_span, self.n_Re, self.n_tab))
1✔
581

582
        # Get polars for flap angles
583
        if self.n_te_flaps > 0:
1✔
584
            if 'cl_interp_flaps' not in self.saved_polar_data.keys():
×
585
                
586
                run_xfoil_params = {}
×
587
                # Self
588
                run_xfoil_params['xfoil_path'] = self.xfoil_path
×
589
                run_xfoil_params['cores'] = self.cores
×
590
                run_xfoil_params['n_span'] = self.n_span
×
591
                run_xfoil_params['n_Re'] = self.n_Re
×
592
                run_xfoil_params['n_tab'] = self.n_tab
×
593
                run_xfoil_params['flap_profiles'] = copy.copy(self.flap_profiles)
×
594
                run_xfoil_params['R'] = self.R
×
595
                run_xfoil_params['tsr'] = self.tsr
×
596
                run_xfoil_params['maxTS'] = self.maxTS
×
597
                run_xfoil_params['KinVisc'] = self.KinVisc
×
598
                run_xfoil_params['SpdSound'] = self.SpdSound
×
599
                # inputs
600
                run_xfoil_params['cl_interp'] = inputs['cl_interp']
×
601
                run_xfoil_params['cd_interp'] = inputs['cd_interp']
×
602
                run_xfoil_params['cm_interp'] = inputs['cm_interp']
×
603
                run_xfoil_params['chord'] = inputs['chord']
×
604
                run_xfoil_params['s'] = inputs['s']
×
605
                run_xfoil_params['r'] = inputs['r']
×
606
                run_xfoil_params['aoa'] = inputs['aoa']
×
607

608

609
                # Run XFoil as multiple processors with MPI
610
                if MPI and not self.options['opt_options']['driver']['design_of_experiments']['flag']:
×
611
                    run_xfoil_params['run_MPI'] = True
×
612
                    # mpi comm management
613
                    comm = MPI.COMM_WORLD
×
614
                    rank = comm.Get_rank()
×
615
                    sub_ranks = self.mpi_comm_map_down[rank]
×
616
                    size = len(sub_ranks)
×
617
                    
618
                    print('Parallelizing Xfoil on {} subranks.'.format(len(sub_ranks)))
×
619
                    N_cases = self.n_span # total number of airfoil sections
×
620
                    N_loops = int(np.ceil(float(N_cases)/float(size)))  # number of times function calls need to "loop"
×
621

622
                    # iterate loops, populate polar tables
623
                    for i in range(N_loops):
×
624
                        idx_s = i*size
×
625
                        idx_e = min((i+1)*size, N_cases)
×
626

627
                        for idx, afi in enumerate(np.arange(idx_s,idx_e)):
×
628
                            data = [partial(get_flap_polars, run_xfoil_params), afi]
×
629
                            rank_j = sub_ranks[idx]
×
630
                            comm.send(data, dest=rank_j, tag=0)
×
631

632
                        # for rank_j in sub_ranks:
633
                        for idx, afi in enumerate(np.arange(idx_s, idx_e)):
×
634
                            rank_j = sub_ranks[idx]
×
635
                            polars_separate_af = comm.recv(source=rank_j, tag=1)
×
636
                            cl_interp_flaps[afi,:,:,:] = polars_separate_af[0]
×
637
                            cd_interp_flaps[afi,:,:,:] = polars_separate_af[1]
×
638
                            cm_interp_flaps[afi,:,:,:] = polars_separate_af[2]
×
639
                            fa_control[afi,:,:] = polars_separate_af[3]
×
640
                            Re_loc[afi,:,:] = polars_separate_af[4]
×
641
                            Ma_loc[afi,:,:] = polars_separate_af[5]
×
642
                    
643
                    # for afi in range(self.n_span):
644
                    #     # re-structure outputs
645
                        
646
                # Multiple processors, but not MPI
647
                elif self.cores > 1 and not self.options['opt_options']['driver']['design_of_experiments']['flag']:
×
648
                    run_xfoil_params['run_multi'] = True
×
649

650
                    # separate airfoil sections w/ and w/o flaps
651
                    af_with_flaps = []
×
652
                    af_without_flaps = []
×
653
                    for afi in range(len(run_xfoil_params['flap_profiles'])):
×
654
                        if 'coords' in run_xfoil_params['flap_profiles'][afi]:
×
655
                            af_with_flaps.append(afi)
×
656
                        else:
657
                            af_without_flaps.append(afi)
×
658

659
                    print('Parallelizing Xfoil on {} cores'.format(self.cores))
×
660
                    pool = mp.Pool(self.cores)
×
UNCOV
661
                    polars_separate_flaps = pool.map(
×
662
                        partial(get_flap_polars, run_xfoil_params), af_with_flaps)
663
                    # parallelize flap-specific calls for better efficiency
UNCOV
664
                    polars_separate_noflaps = pool.map(
×
665
                        partial(get_flap_polars, run_xfoil_params), af_without_flaps)
666
                    pool.close()
×
667
                    pool.join()
×
668

669
                    for i, afi in enumerate(af_with_flaps):
×
670
                        cl_interp_flaps[afi,:,:,:] = polars_separate_flaps[i][0]
×
671
                        cd_interp_flaps[afi,:,:,:] = polars_separate_flaps[i][1]
×
672
                        cm_interp_flaps[afi,:,:,:] = polars_separate_flaps[i][2]
×
673
                        fa_control[afi,:,:] = polars_separate_flaps[i][3]
×
674
                        Re_loc[afi,:,:] = polars_separate_flaps[i][4]
×
675
                        Ma_loc[afi,:,:] = polars_separate_flaps[i][5]
×
676

677
                    for i, afi in enumerate(af_without_flaps):
×
678
                        cl_interp_flaps[afi,:,:,:] = polars_separate_noflaps[i][0]
×
679
                        cd_interp_flaps[afi,:,:,:] = polars_separate_noflaps[i][1]
×
680
                        cm_interp_flaps[afi,:,:,:] = polars_separate_noflaps[i][2]
×
681
                        fa_control[afi,:,:] = polars_separate_noflaps[i][3]
×
682
                        Re_loc[afi,:,:] = polars_separate_noflaps[i][4]
×
683
                        Ma_loc[afi,:,:] = polars_separate_noflaps[i][5]
×
684
                                            
685
                else:
686
                    for afi in range(self.n_span): # iterate number of radial stations for various airfoil tables
×
687
                        cl_interp_flaps_af, cd_interp_flaps_af, cm_interp_flaps_af, fa_control_af, Re_loc_af, Ma_loc_af = get_flap_polars(run_xfoil_params, afi)
×
688

689
                        cl_interp_flaps[afi,:,:,:] = cl_interp_flaps_af
×
690
                        cd_interp_flaps[afi,:,:,:] = cd_interp_flaps_af
×
691
                        cm_interp_flaps[afi,:,:,:] = cm_interp_flaps_af
×
692
                        fa_control[afi,:,:] = fa_control_af
×
693
                        Re_loc[afi,:,:] = Re_loc_af
×
694
                        Ma_loc[afi,:,:] = Ma_loc_af
×
695

696
                if not any([self.options['opt_options']['design_variables']['control']['flaps']['te_flap_ext']['flag'],
×
697
                            self.options['opt_options']['design_variables']['control']['flaps']['te_flap_end']['flag']]):
698
                    self.saved_polar_data['cl_interp_flaps'] = copy.copy(cl_interp_flaps)
×
699
                    self.saved_polar_data['cd_interp_flaps'] = copy.copy(cd_interp_flaps)
×
700
                    self.saved_polar_data['cm_interp_flaps'] = copy.copy(cm_interp_flaps)
×
701
                    self.saved_polar_data['fa_control'] = copy.copy(fa_control)
×
702
                    self.saved_polar_data['Re_loc'] = copy.copy(Re_loc)
×
703
                    self.saved_polar_data['Ma_loc'] = copy.copy(Ma_loc)
×
704
                    
705
            else:
706
                # load xfoil data from previous runs
707
                print('Skipping XFOIL and loading blade polar data from previous iteration.')
×
708
                cl_interp_flaps = self.saved_polar_data['cl_interp_flaps']
×
709
                cd_interp_flaps = self.saved_polar_data['cd_interp_flaps']
×
710
                cm_interp_flaps = self.saved_polar_data['cm_interp_flaps']
×
711
                fa_control = self.saved_polar_data['fa_control']  
×
712
                Re_loc = self.saved_polar_data['Re_loc']       
×
713
                Ma_loc = self.saved_polar_data['Ma_loc']       
×
714

715

716

717
                    # else:  # no flap at specific radial location (but in general 'aerodynamic_control' is defined in blade from yaml)
718
                    #     # for j in range(n_Re): # ToDo incorporade variable Re capability
719
                    #     for ind in range(self.n_tab):  # fill all self.n_tab slots even though no flaps exist at current radial position
720
                    #         c = inputs['chord'][afi]  # blade chord length at cross section
721
                    #         rR = inputs['r'][afi] / inputs['r'][-1]  # non-dimensional blade radial station at cross section
722
                    #         Re_loc[afi, :, ind] = c * maxTS * rR / KinVisc
723
                    #         Ma_loc[afi, :, ind] = maxTS * rR / SpdSound
724
                    #         for j in range(self.n_Re):
725
                    #             cl_interp_flaps[afi, :, j, ind] = inputs['cl_interp'][afi, :, j, 0]
726
                    #             cd_interp_flaps[afi, :, j, ind] = inputs['cl_interp'][afi, :, j, 0]
727
                    #             cm_interp_flaps[afi, :, j, ind] = inputs['cl_interp'][afi, :, j, 0]
728

729
        else:
730
            for afi in range(self.n_span):
1✔
731
                # for j in range(n_Re):  # ToDo incorporade variable Re capability
732
                for ind in range(self.n_tab):  # fill all self.n_tab slots even though no flaps exist at current radial position
1✔
733
                    c = inputs['chord'][afi]  # blade chord length at cross section
1✔
734
                    rR = inputs['r'][afi] / inputs['r'][-1]  # non-dimensional blade radial station at cross section
1✔
735
                    Re_loc[afi, :, ind] = c * self.maxTS * rR / self.KinVisc
1✔
736
                    Ma_loc[afi, :, ind] = self.maxTS * rR / self.SpdSound
1✔
737
                    
738
        outputs['cl_interp_flaps']  = cl_interp_flaps
1✔
739
        outputs['cd_interp_flaps']  = cd_interp_flaps
1✔
740
        outputs['cm_interp_flaps']  = cm_interp_flaps
1✔
741
        outputs['flap_angles']      = fa_control # use vector of flap angle controls
1✔
742
        outputs['Re_loc'] = Re_loc
1✔
743
        outputs['Ma_loc'] = Ma_loc
1✔
744

745
def get_flap_polars(run_xfoil_params, afi):
1✔
746
    '''
747
    Sort of a wrapper script for runXfoil - makes parallelization possible
748

749
    Parameters:
750
    -----------
751
    run_xfoil_params: dict
752
        contains all necessary information to succesfully run xFoil
753
    afi: int
754
        airfoil section index
755

756
    Returns:
757
    --------
758
    cl_interp_flaps_af: 3D array
759
        lift coefficient tables
760
    cd_interp_flaps_af: 3D array
761
        drag coefficient  tables
762
    cm_interp_flaps_af: 3D array
763
        moment coefficient tables
764
    fa_control_af: 2D array
765
        flap angle tables
766
    Re_loc_af: 2D array
767
        Reynolds number table
768
    Ma_loc_af: 2D array
769
        Mach number table
770
    '''
771
    cl_interp_flaps_af  = copy.deepcopy(run_xfoil_params['cl_interp'][afi])
×
772
    cd_interp_flaps_af  = copy.deepcopy(run_xfoil_params['cd_interp'][afi])
×
773
    cm_interp_flaps_af  = copy.deepcopy(run_xfoil_params['cm_interp'][afi])
×
774
    fa_control_af       = copy.deepcopy(np.zeros((run_xfoil_params['n_Re'], run_xfoil_params['n_tab'])))
×
775
    Re_loc_af           = copy.deepcopy(np.zeros((run_xfoil_params['n_Re'], run_xfoil_params['n_tab'])))
×
776
    Ma_loc_af           = copy.deepcopy(np.zeros((run_xfoil_params['n_Re'], run_xfoil_params['n_tab'])))
×
777
    n_tab               = copy.deepcopy(run_xfoil_params['n_tab'])
×
778
    flap_profiles       = copy.deepcopy(run_xfoil_params['flap_profiles'])
×
779
    chord               = copy.deepcopy(run_xfoil_params['chord'])
×
780
    span                = copy.deepcopy(run_xfoil_params['s'])
×
781
    rad_loc             = copy.deepcopy(run_xfoil_params['r'])
×
782
    R                   = copy.deepcopy(run_xfoil_params['R'])
×
783
    KinVisc             = copy.deepcopy(run_xfoil_params['KinVisc'])
×
784
    maxTS               = copy.deepcopy(run_xfoil_params['maxTS'])
×
785
    SpdSound            = copy.deepcopy(run_xfoil_params['SpdSound'])
×
786
    xfoil_path          = copy.deepcopy(run_xfoil_params['xfoil_path'])
×
787
    aoa                 = copy.deepcopy(run_xfoil_params['aoa'])
×
788

789
    if 'coords' in flap_profiles[afi]: # check if 'coords' is an element of 'flap_profiles', i.e. if we have various flap angles
×
790
        # for j in range(n_Re): # ToDo incorporade variable Re capability
791
        for ind in range(n_tab):
×
792
            #fa = flap_profiles[afi]['flap_angles'][ind] # value of respective flap angle
793
            fa_control_af[:,ind] = flap_profiles[afi]['flap_angles'][ind] # flap angle vector of distributed aerodynamics control
×
794
            # eta = (blade['pf']['r'][afi]/blade['pf']['r'][-1])
795
            # eta = blade['outer_shape_bem']['chord']['grid'][afi]
796
            c   = chord[afi]  # blade chord length at cross section
×
797
            s   = span[afi]
×
798
            cr  = chord[afi] / rad_loc[afi]
×
799
            rR  = rad_loc[afi] / rad_loc[-1]  # non-dimensional blade radial station at cross section in the rotor coordinate system
×
800
            Re_loc_af[:,ind] = c* maxTS * rR / KinVisc
×
801
            Ma_loc_af[:,ind] = maxTS * rR / SpdSound
×
802
            print('Run xfoil for nondimensional blade span section s = ' + str(s) + ' with ' + str(fa_control_af[0,ind]) + ' deg flap deflection angle; Re equal to ' + str(Re_loc_af[0,ind]) + '; Ma equal to ' + str(Ma_loc_af[0,ind]))
×
803

804
            xfoil_kw = {'AoA_min': -20,
×
805
                        'AoA_max': 25,
806
                        'AoA_inc': 0.25,
807
                        'Ma':  Ma_loc_af[0, ind],
808
                        }
809

810
            data = runXfoil(xfoil_path, flap_profiles[afi]['coords'][:, 0, ind],flap_profiles[afi]['coords'][:, 1, ind],Re_loc_af[0, ind], **xfoil_kw)
×
811

812
            oldpolar= Polar(Re_loc_af[0,ind], data[:,0],data[:,1],data[:,2],data[:,4]) # data[:,0] is alpha, data[:,1] is Cl, data[:,2] is Cd, data[:,4] is Cm
×
813
            try:
×
814
                polar3d = oldpolar.correction3D(rR,cr,run_xfoil_params['tsr']) # Apply 3D corrections (made sure to change the r/R, c/r, and tsr values appropriately when calling AFcorrections())
×
815
            except IndexError:
×
816
                for key in run_xfoil_params:
×
817
                    print('{} = {}'.format(key, run_xfoil_params[key]))
×
818
                print('XFOIL DATA: {}'.format(data))
×
819
                raise
×
820

821
            cdmax   = np.max(data[:,2]) # Keep the same max Cd as before
×
822
            polar   = polar3d.extrapolate(cdmax) # Extrapolate polars for alpha between -180 deg and 180 deg
×
823

824
            for j in range(run_xfoil_params['n_Re']):
×
825
                cl_interp_flaps_af[:,j,ind] = np.interp(np.degrees(aoa), polar.alpha, polar.cl)
×
826
                cd_interp_flaps_af[:,j,ind] = np.interp(np.degrees(aoa), polar.alpha, polar.cd)
×
827
                cm_interp_flaps_af[:,j,ind] = np.interp(np.degrees(aoa), polar.alpha, polar.cm)
×
828

829
        # # ** The code below will plot the three cl polars
830
            # import matplotlib.pyplot as plt
831
            # font = {'family': 'Times New Roman',
832
            #         'weight': 'normal',
833
            #         'size': 18}
834
            # plt.rc('font', **font)
835
            # plt.figure
836
            # fig, ax = plt.subplots(1, 1, figsize=(8, 5))
837
            # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,0],'r', label='$\\delta_{flap}$ = -10 deg')  # -10
838
            # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,1],'k', label='$\\delta_{flap}$ = 0 deg')  # 0
839
            # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,2],'b', label='$\\delta_{flap}$ = +10 deg')  # +10
840
            # # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,0],'r')  # -10
841
            # # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,1],'k')  # 0
842
            # # plt.plot(np.degrees(run_xfoil_params['aoa']), cl_interp_flaps_af[afi,:,0,2],'b')  # +10
843
            # plt.xlim(xmin=-15, xmax=15)
844
            # plt.ylim(ymin=-1.7, ymax=2.2)
845
            # plt.grid(True)
846
            # # autoscale_y(ax)
847
            # plt.xlabel('Angles of attack, deg')
848
            # plt.ylabel('Lift coefficient')
849
            # plt.legend(loc='lower right')
850
            # plt.tight_layout()
851
            # plt.show()
852
            # # # # plt.savefig('airfoil_polars_check/r_R_1_0_cl_flaps.png', dpi=300)
853
            # # # # plt.savefig('airfoil_polars_check/NACA63-618_cl_flaps.png', dpi=300)
854
            # # # # plt.savefig('airfoil_polars_check/FFA-W3-211_cl_flaps.png', dpi=300)
855
            # # # # plt.savefig('airfoil_polars_check/FFA-W3-241_cl_flaps.png', dpi=300)
856
            # # # # plt.savefig('airfoil_polars_check/FFA-W3-301_cl_flaps.png', dpi=300)
857

858

859

860
    else:  # no flap at specific radial location (but in general 'aerodynamic_control' is defined in blade from yaml)
861
        for ind in range(n_tab):  # fill all run_xfoil_params['n_tab'] slots even though no flaps exist at current radial position
×
862
            c = chord[afi]  # blade chord length at cross section
×
863
            rR = rad_loc[afi] / rad_loc[-1]  # non-dimensional blade radial station at cross section
×
864
            Re_loc_af[:, ind] = c * maxTS * rR / KinVisc
×
865
            Ma_loc_af[:, ind] = maxTS * rR / SpdSound            
×
866

867
            for j in range(run_xfoil_params['n_Re']):
×
868
                cl_interp_flaps_af[:, j, ind] = copy.deepcopy(cl_interp_flaps_af[:, j, 0])
×
869
                cd_interp_flaps_af[:, j, ind] = copy.deepcopy(cd_interp_flaps_af[:, j, 0])
×
870
                cm_interp_flaps_af[:, j, ind] = copy.deepcopy(cm_interp_flaps_af[:, j, 0])
×
871
    
872
    return cl_interp_flaps_af, cd_interp_flaps_af, cm_interp_flaps_af, fa_control_af, Re_loc_af, Ma_loc_af
×
873

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc