Beispiel #1
0
 def __init__(self):
     self._db = pth.db.airfoil
     self.name             = None
     self.pts              = None
     self.ptsUp            = None
     self.ptsLo            = None
     self.thickness        = None
     self.camber           = None
     self.camberLocation   = None
     self.leRadius         = None
     self.zTrailingEdge    = 0.0
     self._curveUp         = None
     self._curveLo         = None
     self.polar            = AirfoilPolar()
     self._distribution    = 'cos'
Beispiel #2
0
class Airfoil:
    def __init__(self):
        self._db = pth.db.airfoil
        self.name             = None
        self.pts              = None
        self.ptsUp            = None
        self.ptsLo            = None
        self.thickness        = None
        self.camber           = None
        self.camberLocation   = None
        self.leRadius         = None
        self.zTrailingEdge    = 0.0
        self._curveUp         = None
        self._curveLo         = None
        self.polar            = AirfoilPolar()
        self._distribution    = 'cos'

    def read_db(self,name,xlsPath=None):
        """
        Reads airfoil from xls database. If additional geometry information 
        and aerodynamic tables exist then existing tables are used 
        otherwise information will be created using internal tools of module.
        """
        if xlsPath==None:
            xlsPath = pth.db.airfoil
        db = db_tools.ReadDatabase(xlsPath,name)
        self.name = name
        xCoord    = db.read_row(0,1)
        yCoord    = db.read_row(1,1)
        self.pts = np.transpose([xCoord,yCoord])
        self._separate_coordinates()
        # geometry
        i = db.find_header('GEOMETRY')
        if i==-1:
            self._analyze_geometry()
        else:
            self.thickness = db.read_row(i+1,1)
            self.camber    = db.read_row(i+2,1)
            self._analyze_geometry()
        # analysis
        i = db.find_header('ANALYSIS')
        if i==-1:
            self.build_aero_table()
        else:
            self.polar.source = db.read_row(i+1,1)
            i = db.find_header('LIFT COEFFICIENT')
            nAlpha = db.find_header('DRAG COEFFICIENT') - i-2
            self.polar.Mach  = np.array(db.read_row(i+1,1))
            self.polar.alpha = np.array(db.read_column(0,i+2,nAlpha))
            self.polar.cl    = db.read_row_range(i+2,1,nAlpha)
            i = db.find_header('DRAG COEFFICIENT')
            self.polar.cd    = db.read_row_range(i+2,1,nAlpha)
            i = db.find_header('MOMENT COEFFICIENT')
            self.polar.cm    = db.read_row_range(i+2,1,nAlpha)
            self.polar.cl    = np.transpose(self.polar.cl)
            self.polar.cd    = np.transpose(self.polar.cd)
            self.polar.cm    = np.transpose(self.polar.cm)
            self.polar._create_splines()
        self.polar._calc_clmax()

    def write_db(self,xlsPath=None,includePolars=True):
        """
        Writes airfoil to xls format database.
        """
        if xlsPath==None:
            xlsPath=pth.db.airfoil
        db = db_tools.WriteDatabase(xlsPath,self.name)
        db.write_row('X',self.pts[:,0])
        db.write_row('Y',self.pts[:,1])
        db.write_row('GEOMETRY')
        db.write_row('thickness',self.thickness)
        db.write_row('camber'   ,self.camber)
        if includePolars:
            db.write_row('ANALYSIS')
            db.write_row('method', self.polar.source)
            db.write_row('LIFT COEFFICIENT')
            db.write_row('Mach list',self.polar.Mach)
            i = db._irowPrev
            db.write_column(self.polar.alpha)
            db._irowPrev = i
            db.write_row_range(np.transpose(self.polar.cl),-1,1)
            db.write_row('DRAG COEFFICIENT')
            db.write_row('Mach list',self.polar.Mach)
            i = db._irowPrev
            db.write_column(self.polar.alpha)
            db._irowPrev = i
            db.write_row_range(np.transpose(self.polar.cd),-1,1)
            db.write_row('MOMENT COEFFICIENT')
            db.write_row('Mach list',self.polar.Mach)
            i = db._irowPrev
            db.write_column(self.polar.alpha)
            db._irowPrev = i
            db.write_row_range(np.transpose(self.polar.cm),-1,1)
        db.save()

    def read_txt(self,airfoilPath):
        """
        Reads airfoil coordinates from text file. Two formats of coordinates 
        are supported.
        """
        fid = open(airfoilPath,'rt')
        self.name = str(fid.readline()[:-1])
        lines = fid.readlines()
        fid.close()
        seg = lines[0].split()
        if float(seg[0])>3 and float(seg[1])>3:
            iup = int(round(float(seg[0])))
            ilo = int(round(float(seg[1])))
            self._read_txt_type2(lines[1:],iup,ilo)
        else:
            self._read_txt_type1(lines)
        self._analyze_geometry()
        
    def write_txt(self,filePath,tab=True,writeName=True):
        """
        writes airfoil coordinates to text file.
        
        Parameters
        ----------
        
        filePath : path
            airfoil text file path to write coordinates
        tab : bool
            white space parameter. If True then x,y coordinates are separated 
            by tabbing otherwise 
            separated by 4 spacings as required for X-foil input.
        writeName : bool
            if True then airfoil name will be written to the text file header 
            otherwise only coordinates will be stored
        """
        afFile = open(filePath,'wt')
        if writeName:
            afFile.write('%s\n'%self.name)
        if tab:
            whiteSpace = '\t'
        else:
            whiteSpace = '    '
        for point in self.pts:
            afFile.write('%.6f%s%.6f\n'%(point[0],whiteSpace,point[1]))
        afFile.close()

    def _line_coord_to_float(self,lines):
        output = list()
        for line in lines:
            if line.strip()!='':
                seg = line.split()
                outline = np.zeros(2)
                outline[0] = float(seg[0])
                outline[1] = float(seg[1])
                output.append(outline)
        return np.array(output)
    
    def _separate_coordinates(self):
        self.ptsUp, self.ptsLo = geom.separate_coordinates(self.pts)

    def _join_coordinates(self):
        self.pts = geom.join_coordinates(self.ptsUp, self.ptsLo)

    def _read_txt_type1(self,lines):
        """
        reads airfoil coordinates from text file stored in xfoil and javafoil 
        like format.
        
        Text format as follows:  
            1. airfoil name
            2. coordinates starting from upper curve trailing edge (xx yy)
        
        Parameters
        ----------
        
        filePath : path
            airfoil coordinates file path
        
        Returns
        -------
        
        coord : 2d array
            airfoil coordinates
        name : string
            airfoil name
        """
        self.pts = self._line_coord_to_float(lines)
        self._separate_coordinates()

    def _read_txt_type2(self,lines,nPtsUp,nPtsLo):
        """
        reads airfoil coordinates from text file stored in format similar to 
        uiuc db file format.
        
        Text format as follows:
            1. airfoil name
            2. number of upper and lower curve points (n n)
            3. upper curve points starting from leading edge (xx yy)
            4. lower curve points starting from leading edge (xx yy)
        
        Parameters
        ----------
        
        filePath : path
            airfoil coordinates file path

        Returns
        -------
        
        coord : 2d array
            airfoil coordinates
        name : string
            airfoil name
        """
        coordRaw = self._line_coord_to_float(lines)
        self.ptsUp = coordRaw[:nPtsUp]
        self.ptsLo = coordRaw[nPtsUp:nPtsUp+nPtsLo]
        self._join_coordinates()

    def display(self,linetype='ko-'):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.grid(True)
        ax.axis('equal')
        ax.set_title(self.name)
        ax.plot(self.pts[:,0],self.pts[:,1],linetype)
        plt.show()
    
    def _create_splines(self):
        """
        create cubic splines for x and y coordinate with respect to curve 
        length parameter
        """
        # parametric splines x = x(t), y = y(t)
        lenUp = geom.curve_pt_dist_normalized(self.ptsUp)
        lenLo = geom.curve_pt_dist_normalized(self.ptsLo)
        self._curveUp = CurveXyt(self.ptsUp[:,0],self.ptsUp[:,1],lenUp)
        self._curveLo = CurveXyt(self.ptsLo[:,0],self.ptsLo[:,1],lenLo)
        ptsUp,ptsLo = self._sort_pts()
        self._curveUp2 = interp1d(ptsUp[:,0],ptsUp[:,1],'cubic')
        self._curveLo2 = interp1d(ptsLo[:,0],ptsLo[:,1],'cubic')
        self._xmin = max([ptsUp[0,0],ptsLo[0,0]])
        self._xmax = min([ptsUp[-1,0],ptsLo[-1,0]])

    def _analyze_geometry(self):
        """
        calculates airfoil geometry parameters
        """
        self._create_splines()
        tc     = lambda x:  self._curveLo2(x) - self._curveUp2(x)
        camber = lambda x:  -(self._curveLo2(x) + self._curveUp2(x))
        self.thicknessLocation = fminbound(tc,self._xmin,self._xmax)
        self.camberLocation = fminbound(camber,self._xmin,self._xmax)
        self.thickness = -tc(self.thicknessLocation)
        self.camber = -camber(self.camberLocation)/2.0
        n = len(self.pts)-1
        L = np.zeros(n)
        for i in range(n):
            L[i] = ((self.pts[i,0]-self.pts[i+1,0])**2.0 + (self.pts[i,1]-self.pts[i+1,1])**2.0)**0.5
        self.length = L.sum()
        self.zTrailingEdge = self.ptsUp[-1,1] - self.ptsLo[-1,1]
#        self._calc_leadingedge_radius()
#        
#    def _calc_leadingedge_radius(self):
#        dt = 1e-5
#        pt1 = self._curveLo(dt)
#        pt2 = self._curveLo(0.0)
#        pt3 = self._curveUp(dt)
#        dx1,dx2 = pt3[0]-pt2[0], pt2[0]-pt1[0]
#        print dx1, dx2
    
    def _sort_pts(self):
        xmin = 0.05
        xmax = 0.95
        def sort_pts(pts,xmin,xmax):
            for i,pt in enumerate(pts):
                if pt[0]>=xmin:
                    idxStart = i
                    break
            for i,pt in enumerate(pts[idxStart:]):
                if pt[0]>=xmax:
                    idxEnd = i+idxStart
                    break
            return pts[idxStart:idxEnd]
        ptsUp = sort_pts(self.ptsUp,xmin,xmax)
        ptsLo = sort_pts(self.ptsLo,xmin,xmax)
        return ptsUp,ptsLo
    
    def _get_point_distribution(self,nPts=30,distribution=None):
        if distribution==None:
            distribution = self._distribution
        if distribution=='sin':
            return geom.get_sine_distribution(nPts)
        elif distribution=='cos':
            return geom.get_cosine_distribution(nPts)
    def redim(self,nPts,overwrite=True,distribution='sin'):
        """
        Redimension and redistribution of airfoil points using cosine function. 
        More points are located at leading and trailing edge.
        
        Parameters
        ----------
        
        numPts : integer
            number of points for target airfoil. If number of points is same 
            as source airfoil, points will be redistributed to make smooth 
            surface
        overwrite : bool
            If True then self.pts, self.upPts and self.loPts will be 
            overwritten, otherwise will return array of redimensioned points 
            in format of self.pts
        """
        if nPts%2==0:
            nPts1 = nPts/2
            nPts2 = nPts/2+1
        else:
            nPts1 = (nPts+1)/2
            nPts2 = nPts1
        #nPts *= 0.5
        t1 = self._get_point_distribution(nPts1,distribution)
        t2 = self._get_point_distribution(nPts2,distribution)
        self._create_splines()
        xUp, yUp = self._curveUp(t1)
        xLo, yLo = self._curveLo(t2)
        upPts = np.transpose(np.vstack([xUp,yUp]))
        loPts = np.transpose(np.vstack([xLo,yLo]))
        coord = geom.join_coordinates(upPts,loPts)
        if overwrite:
            self.pts   = coord
            self.ptsUp = upPts
            self.ptsLo = loPts
        else:
            return coord

    def scale_thickness(self,thicknessNew,analysis=False):
        """
        Scales airfoil in Y-direction to achieve required thickness. Useful 
        to be used with propeller analysis when same airfoil has different 
        thickness at different *x* stations.
        
        Parameters
        ----------
        
        newThickness: float
            thickness/chord of result airfoil
        analysis : bool
            If True then aerodynamic table will be generated using 
            self.build_aero_table, otherwise only geometry will be changed.
        """
        scale = thicknessNew / self.thickness
        self.name = self.name + '_tc%.2f'%(thicknessNew*100.0)
        self.pts[:,1] = self.pts[:,1]*scale
        self._separate_coordinates()
        self.redim(50,overwrite=True)
        if analysis:
            self.build_aero_table()
    
    def build_aero_table(self,MachSeq=[0.1,0.9,0.1],alphaSeq=[-20,20,1.0],
                         mode='javafoil'):
        """
        builds full table of aerodynamic coefficients using *fast* solvers
        Coefficients to be caclulated are as follows:
            
            - lift coefficient vs. alpha, Mach
            - drag coefficient vs. alpha, Mach
            - moment coefficient vs. alpha, Mach
        
        Parameters
        ----------
        
        MachSeq : array float
            array of Mach number to be analyzed in format [start, end, step]
        alphaSeq : array float
            array of angle of attack sequence to be analyzed in format 
            [start, end, step]
        mode : string
            if mode='javafoil' then coefficients will be generated using 
            javafoil, if 'xfoil' then using xfoil
        """
        Mach = np.arange(MachSeq[0], MachSeq[1], MachSeq[2])
        Re = list()
        for i,M in enumerate(Mach):
            Re.append(fc.FlightConditions(M,0.0).Re)
            if mode=='javafoil':
                tmpPolar = self.get_jfoil_polar(M, Re[i], alphaSeq)
            elif mode=='xfoil':
                tmpPolar = self.get_xfoil_polar(M, Re[i], alphaSeq, 200)
            if i==0:
                self.polar.cl = tmpPolar.cl
                self.polar.cd = tmpPolar.cd
                self.polar.cm = tmpPolar.cm
            else:
                self.polar.cl = np.vstack([self.polar.cl, tmpPolar.cl])
                self.polar.cd = np.vstack([self.polar.cd, tmpPolar.cd])
                self.polar.cm = np.vstack([self.polar.cm, tmpPolar.cm])
        self.polar.Re     = Re
        self.polar.source = 'javafoil'
        self.polar.Mach   = Mach
        self.polar.alpha  = tmpPolar.alpha
        self.polar._create_splines()

    def get_xfoil_polar(self,Mach,Re,alphaSeq=[-15,15,1],nIter=10,graphic=False,smooth=False,
                        flapChordRatio=0.3,flapDefl=None):
        """
        Calculates aerodynamic coefficients at given flight conditions using Xfoil.
        
        Parameters
        ----------
        
        Mach : float
            Mach number
        Re : float
            Reynolds number
        alphaSeq : array float
            array of angle of attack sequence to be analyzed in format 
            [start, end, step]
        nIter : integer
            number of xfoil iterations. Default value is that is enough for 
            conventional airfoil configurations. For complex airfoil shapes 
            this number should be increased (for example while optimization 
            using genetic algorithm)
        
        Returns
        -------
        
        polar : AirfoilPolar
            airfoil polar with all aerodynamic data at specified flight 
            conditions
        """
        return xf.get_xfoil_analysis(self,Mach,Re,alphaSeq,nIter,graphic,smooth,flapChordRatio,flapDefl)

    def get_jfoil_polar(self,Mach,Re,alphaSeq=[-15,15,1], 
                        stall='eppler',transition='drelaAfter1991',surface='NACAstandard',
                        flapDefl=None,flapChordRatio=30.0):
        """
        Calculates aerodynamic coefficients at given flight conditions using Xfoil.
        
        Parameters
        ----------
        
        Mach : float
            Mach number
        Re : float
            Reynolds number
        alphaSeq : array float
            array of angle of attack sequence to be analyzed in format 
            [start, end, step]
        stall : string
            stall model. Available options: calcfoil, eppler
        transition : string
            transition model. Availabel options: epplerStandard, epplerExtended, 
            michel1, michel2, H12Re, granville, drelaBefore1991, drelaAfter1991, 
            arnal
        surface : string
            surface type. Available options: smooth, paintedFabrid, 
            NACAstandard, bugsDirt
        
        Returns
        -------
        
        polar : AirfoilPolar
            airfoil polar with all aerodynamic data at specified flight 
            conditions
        
        Note
        ----
        
        Javafoil requires path to java installed in system in 
        :file:`javapath.txt`. For linux OS file should contain *java* 
        keyword only
        """
        return jf.get_javafoil_analysis(self,Mach,Re,alphaSeq,stall,transition,
                                        surface,flapChordRatio,flapDefl)
    
    def create_cst(self,Au,Al,nPts=25,zTE=None):
        self.upCurve = geom.CstCurve(Au)
        self.loCurve = geom.CstCurve(Al)
        x = self._get_point_distribution(nPts)
        self.ptsUp = self.upCurve.get_coordinates(x)
        self.ptsLo = self.loCurve.get_coordinates(x)
        self._join_coordinates()
        if not zTE==None:
            self.set_trailing_edge(zTE)
        self._analyze_geometry()
        self.name = 'CST airfoil'

    def create_naca4(self,thickness=12.0,camber=0.0,camberLoc=0.0,nPts=25):
        t = thickness / 100.0
        m = camber / 100.0
        p = camberLoc / 100.0
        x = self._get_point_distribution(nPts)
        y = t/0.2*(0.2969*x**0.5 - 0.1281*x - 0.3516*x*x + 0.2843*x**3.0 - 0.1015*x**4.0)
        self.thickness = t
        self.camber = m
        self.camberLocation = p
        self.name = 'NACA%.0f%.0f%.0f'%(camber,camberLoc/10.0,thickness)
        if not m*p==0.0:
            yc = np.zeros(len(x))
            tanTheta = np.zeros(len(x))
            for i,xpt in enumerate(x):
                if xpt<=p:
                    yc[i] = m*xpt/(p*p)*(2.0*p - xpt)
                    tanTheta[i] = 2.0*m/(p*p)*(p-xpt)
                else:
                    yc[i] = m*(1.0-xpt)/((1.0-p)**2.0)*(1.0+xpt-2.0*p)
                    tanTheta[i] = -2.0*m/((1.0-p)**2.0)*(xpt-p)
            theta = np.arctan(tanTheta)
            xu = x - y*np.sin(theta)
            xl = x + y*np.sin(theta)
            yu = yc + y*np.cos(theta)
            yl = yc - y*np.cos(theta)
            self.ptsUp = np.transpose(np.vstack([xu,yu]))
            self.ptsLo = np.transpose(np.vstack([xl,yl]))            
        else:
            self.ptsUp = np.transpose(np.vstack([x,y]))
            self.ptsLo = np.transpose(np.vstack([x,-y]))
        self._join_coordinates()

    def set_trailing_edge(self,zTEnew=0.0):
        zTEcurrent = self.ptsUp[-1,1] - self.ptsLo[-1,1]
        dzTE = zTEnew - zTEcurrent
        ptsUpNew = self.ptsUp[:,1] + self.ptsUp[:,0]*dzTE/2.
        ptsLoNew = self.ptsLo[:,1] - self.ptsLo[:,0]*dzTE/2.
        self.ptsUp[:,1] = ptsUpNew
        self.ptsLo[:,1] = ptsLoNew
        self._join_coordinates()
        self._analyze_geometry()
    
    def get_clmax(self,velocity,altitude,flapToChord,flapDefl):
        """
        analysis using javafoil since it's more robust than xfoil
        with calcfoil stall model. Note that only plain flap are possible.
        
        Parameters
        ----------
        
        velocity : m/sec or Mach
            airspeed
        altitude : m
            
        flapToChord : [0;1]
            flap to chord ratio
        
        flapDefl : deg
            flap deflection angle.
        """
        fc1 = fc.FlightConditions(velocity,altitude,1.0)
        Mach = fc1.Mach
        Re   = fc1.Re
        pol = self.get_jfoil_polar(Mach,Re,[0,20,1],'eppler',
                                   flapChordRatio=flapToChord, flapDefl=flapDefl)
        return float(pol.clmax), pol.alphaClmax
    
    def fit_cst(self, nBPO=3):
        """
        generates CST coefficients of given BPO for current airfoil.
        
        Parameters
        ----------
        
        nBPO : int
            Bernstein polynomial order
        """
        nvar = nBPO*2+1
        # ----
        def get_error(curve,pts):
            sqErr = np.zeros(len(pts[:,0]))
            for i,x in enumerate(pts[:,0]):
                ynew = curve(x)
                sqErr[i] = (ynew-pts[i,1])**2.0
            return sqErr
                
        def obj(x):
            n = len(x)
            if n%2==0:
                Au = x[:n/2]
                Al = x[n/2:]
            else:
                Au = np.array(x[:int(n/2)+1])
                Al = np.hstack([-x[0],x[int(n/2)+1:]])
            upCurve = geom.CstCurve(Au,TEgap=self.zTrailingEdge)
            loCurve = geom.CstCurve(Al,TEgap=self.zTrailingEdge)
            sqErrUp = get_error(upCurve,self.ptsUp)
            sqErrLo = get_error(loCurve,self.ptsLo)
            err = np.sum(sqErrUp) + np.sum(sqErrLo)
            return err
        # ----
        xl = -0.5*np.ones(nvar)
        xu = 0.5*np.ones(nvar)
        bnds = np.vstack([xl,xu])
        bnds = np.transpose(bnds)
        x0 = np.zeros(nvar)
        rslt = minimize(obj,x0,bounds=bnds,method='SLSQP')
        xopt = rslt.x
        return xopt, obj(xopt)/(len(self.pts)+1)