コード例 #1
0
def design_F8745D4_prop():  
    prop                                  = SUAVE.Components.Energy.Converters.Propeller()
    prop.tag                              = 'F8745_D4_Propeller'  
    prop.tip_radius                       = 2.03/2
    prop.hub_radius                       = prop.tip_radius*0.20
    prop.number_of_blades                 = 2  
    r_R_data                              = np.array([ 0.2,0.300,0.450,0.601,0.747,0.901,0.950,0.975,0.998   ])    
    t_c_data                              = np.array([ 0.3585,0.1976,0.1148,0.0834,0.0648,0.0591,0.0562,0.0542,0.0533    ])    
    b_R_data                              = np.array([0.116,0.143,0.163,0.169,0.166,0.148,0.135,0.113,0.075  ])    
    beta_data                             = np.array([  0.362,0.286,0.216,0.170,0.135,0.112,0.105,0.101,0.098])* 100 
        
    dim                                   = 20          
    new_radius_distribution               = np.linspace(0.2,0.98 ,dim)
    func_twist_distribution               = interp1d(r_R_data, (beta_data)* Units.degrees   , kind='cubic')
    func_chord_distribution               = interp1d(r_R_data, b_R_data * prop.tip_radius , kind='cubic')
    func_radius_distribution              = interp1d(r_R_data, r_R_data * prop.tip_radius , kind='cubic')
    func_max_thickness_distribution       = interp1d(r_R_data, t_c_data * b_R_data , kind='cubic')  
    
    prop.twist_distribution               = func_twist_distribution(new_radius_distribution)     
    prop.chord_distribution               = func_chord_distribution(new_radius_distribution)         
    prop.radius_distribution              = func_radius_distribution(new_radius_distribution)        
    prop.max_thickness_distribution       = func_max_thickness_distribution(new_radius_distribution) 
    prop.thickness_to_chord               = prop.max_thickness_distribution/prop.chord_distribution  
          
    ospath                                = os.path.abspath(__file__)
    separator                             = os.path.sep
    rel_path                              = ospath.split('noise_fidelity_one' + separator + 'propeller_noise.py')[0] + 'Vehicles/Airfoils' + separator
    prop.airfoil_geometry                 = [ rel_path +'Clark_y.txt']
    prop.airfoil_polars                   = [[rel_path +'Polars/Clark_y_polar_Re_50000.txt' ,rel_path +'Polars/Clark_y_polar_Re_100000.txt',rel_path +'Polars/Clark_y_polar_Re_200000.txt',
                                              rel_path +'Polars/Clark_y_polar_Re_500000.txt',rel_path +'Polars/Clark_y_polar_Re_1000000.txt']]
    airfoil_polar_stations                = np.zeros(dim)
    prop.airfoil_polar_stations           = list(airfoil_polar_stations.astype(int))     
    airfoil_polars                        = compute_airfoil_polars(prop.airfoil_geometry, prop.airfoil_polars)  
    airfoil_cl_surs                       = airfoil_polars.lift_coefficient_surrogates 
    airfoil_cd_surs                       = airfoil_polars.drag_coefficient_surrogates     
    prop.airfoil_flag                     = True 
    prop.airfoil_cl_surrogates            = airfoil_cl_surs
    prop.airfoil_cd_surrogates            = airfoil_cd_surs    
    prop.mid_chord_alignment              = np.zeros_like(prop.chord_distribution)
    prop.number_of_airfoil_section_points = 102
    prop.airfoil_data                     = import_airfoil_geometry(prop.airfoil_geometry, npoints = prop.number_of_airfoil_section_points) 

    return prop
コード例 #2
0
def propeller_design(prop,number_of_stations=20):
    """ Optimizes propeller chord and twist given input parameters.
          
          Inputs:
          Either design power or thrust
          prop_attributes.
            hub radius                       [m]
            tip radius                       [m]
            rotation rate                    [rad/s]
            freestream velocity              [m/s]
            number of blades               
            number of stations
            design lift coefficient
            airfoil data
            
          Outputs:
          Twist distribution                 [array of radians]
          Chord distribution                 [array of meters]
              
          Assumptions/ Source:
          Based on Design of Optimum Propellers by Adkins and Liebeck
          
    """    
    # Unpack
    N      = number_of_stations       # this number determines the discretization of the propeller into stations 
    B      = prop.number_blades
    R      = prop.tip_radius
    Rh     = prop.hub_radius
    omega  = prop.angular_velocity    # Rotation Rate in rad/s
    V      = prop.freestream_velocity # Freestream Velocity
    Cl     = prop.design_Cl           # Design Lift Coefficient
    alt    = prop.design_altitude
    Thrust = prop.design_thrust
    Power  = prop.design_power
    a_geo  = prop.airfoil_geometry
    a_pol  = prop.airfoil_polars        
    a_loc  = prop.airfoil_polar_stations    
    
    if (Thrust == None) and (Power== None):
        raise AssertionError('Specify either design thrust or design power!')
    
    elif (Thrust!= None) and (Power!= None):
        raise AssertionError('Specify either design thrust or design power!')
    
    if prop.rotation == None:
        prop.rotation = list(np.ones(int(B))) 
        
    # Calculate atmospheric properties
    atmosphere = SUAVE.Analyses.Atmospheric.US_Standard_1976()
    atmo_data = atmosphere.compute_values(alt)
    
    p              = atmo_data.pressure[0]
    T              = atmo_data.temperature[0]
    rho            = atmo_data.density[0]
    speed_of_sound = atmo_data.speed_of_sound[0]
    mu             = atmo_data.dynamic_viscosity[0]
    nu             = mu/rho
    
    # Nondimensional thrust
    if (Thrust!= None) and (Power == None):
        Tc = 2.*Thrust/(rho*(V*V)*np.pi*(R*R))     
        Pc = 0.0 
    
    elif (Thrust== None) and (Power != None):
        Tc = 0.0   
        Pc = 2.*Power/(rho*(V*V*V)*np.pi*(R*R))  
    
    tol   = 1e-10 # Convergence tolerance

    # Step 1, assume a zeta
    zeta = 0.1 # Assume to be small initially
    
    # Step 2, determine F and phi at each blade station
    
    chi0    = Rh/R # Where the propeller blade actually starts
    chi     = np.linspace(chi0,1,N+1) # Vector of nondimensional radii
    chi     = chi[0:N]
    lamda   = V/(omega*R)             # Speed ratio
    r       = chi*R                   # Radial coordinate
    x       = omega*r/V               # Nondimensional distance
    diff    = 1.0                     # Difference between zetas
    n       = omega/(2*np.pi)         # Cycles per second
    D       = 2.*R
    J       = V/(D*n)
    
    c = 0.2 * np.ones_like(chi)
    
    # if user defines airfoil, check dimension of stations
    if  a_pol != None and a_loc != None:
        if len(a_loc) != N:
            raise AssertionError('\nDimension of airfoil sections must be equal to number of stations on propeller')
    
    else:
        # Import Airfoil from regression
        print('\nNo airfoils specified for propeller or rotor airfoil specified. \nDefaulting to NACA 4412 airfoils that will provide conservative estimates.') 
        import os
        ospath    = os.path.abspath(__file__)
        separator = os.path.sep
        path      = ospath.replace('\\','/').split('trunk/SUAVE/Methods/Propulsion/propeller_design.py')[0] \
            + 'regression' + separator + 'scripts' + separator + 'Vehicles' + separator 
        a_geo  = [ path +  'NACA_4412.txt'] 
        a_pol  = [[path +  'NACA_4412_polar_Re_50000.txt' ,
                   path +  'NACA_4412_polar_Re_100000.txt' ,
                   path +  'NACA_4412_polar_Re_200000.txt' ,
                   path +  'NACA_4412_polar_Re_500000.txt' ,
                   path +  'NACA_4412_polar_Re_1000000.txt' ]]   # airfoil polars for at different reynolds numbers  
       
        # 0 represents the first airfoil, 1 represents the second airfoil etc. 
        a_loc = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]  
    
        prop.airfoil_geometry        = a_geo
        prop.airfoil_polars          = a_pol     
        prop.airfoil_polar_stations  = a_loc     
     
    while diff>tol:      
        # assign chord distribution
        prop.chord_distribution = c 
                         
        # compute airfoil polars for airfoils 
        airfoil_polars  = compute_airfoil_polars(prop, a_geo, a_pol)  
        airfoil_cl_surs = airfoil_polars.lift_coefficient_surrogates 
        airfoil_cd_surs = airfoil_polars.drag_coefficient_surrogates 
                         
        #Things that need a loop
        Tcnew   = Tc     
        tanphit = lamda*(1.+zeta/2.)   # Tangent of the flow angle at the tip
        phit    = np.arctan(tanphit)   # Flow angle at the tip
        tanphi  = tanphit/chi          # Flow angle at every station
        f       = (B/2.)*(1.-chi)/np.sin(phit) 
        F       = (2./np.pi)*np.arccos(np.exp(-f)) #Prandtl momentum loss factor
        phi     = np.arctan(tanphi)  #Flow angle at every station
        
        #Step 3, determine the product Wc, and RE
        G       = F*x*np.cos(phi)*np.sin(phi) #Circulation function
        Wc      = 4.*np.pi*lamda*G*V*R*zeta/(Cl*B)
        Ma      = Wc/speed_of_sound
        RE      = Wc/nu

        #Step 4, determine epsilon and alpha from airfoil data  
        alpha = np.zeros_like(RE)
        Cdval = np.zeros_like(RE)  
        for i in range(N):
            AoA_old_guess = 0.01
            cl_diff       = 1  
            broke         = False   
            ii            = 0
            
            # Newton Raphson Iteration 
            while cl_diff > 1E-3:
                
                Cl_guess       = airfoil_cl_surs[a_geo[a_loc[i]]](RE[i],AoA_old_guess,grid=False) - Cl 
                
                # central difference derivative 
                dx             = 1E-5
                dCL            = (airfoil_cl_surs[a_geo[a_loc[i]]](RE[i],AoA_old_guess + dx,grid=False) - airfoil_cl_surs[a_geo[a_loc[i]]](RE[i],AoA_old_guess- dx,grid=False))/ (2*dx)
                 
                # update AoA guess 
                AoA_new_guess  = AoA_old_guess - Cl_guess/dCL
                AoA_old_guess  = AoA_new_guess 
                
                # compute difference for tolerance check
                cl_diff        = abs(Cl_guess)      
                 
                ii+=1 	
                if ii>10000:	
                    # maximum iterations is 10000
                    print('Propeller/Rotor section is not converging to solution')
                    broke = True	
                    break                    
                
            alpha[i] = AoA_old_guess     
            Cdval[i] = airfoil_cd_surs[a_geo[a_loc[i]]](RE[i],alpha[i],grid=False)  

        #More Cd scaling from Mach from AA241ab notes for turbulent skin friction
        Tw_Tinf = 1. + 1.78*(Ma**2)
        Tp_Tinf = 1. + 0.035*(Ma**2) + 0.45*(Tw_Tinf-1.)
        Tp      = Tp_Tinf*T
        Rp_Rinf = (Tp_Tinf**2.5)*(Tp+110.4)/(T+110.4) 
        Cd      = ((1/Tp_Tinf)*(1/Rp_Rinf)**0.2)*Cdval
        
        #Step 5, change Cl and repeat steps 3 and 4 until epsilon is minimized 
        epsilon = Cd/Cl  
        
        #Step 6, determine a and a', and W 
        a       = (zeta/2.)*(np.cos(phi)**2.)*(1.-epsilon*np.tan(phi))
        aprime  = (zeta/(2.*x))*np.cos(phi)*np.sin(phi)*(1.+epsilon/np.tan(phi))
        W       = V*(1.+a)/np.sin(phi)
        
        #Step 7, compute the chord length and blade twist angle  
        c       = Wc/W
        beta    = alpha + phi # Blade twist angle
    
        #Step 8, determine 4 derivatives in I and J 
        Iprime1 = 4.*chi*G*(1.-epsilon*np.tan(phi))
        Iprime2 = lamda*(Iprime1/(2.*chi))*(1.+epsilon/np.tan(phi)
                                            )*np.sin(phi)*np.cos(phi)
        Jprime1 = 4.*chi*G*(1.+epsilon/np.tan(phi))
        Jprime2 = (Jprime1/2.)*(1.-epsilon*np.tan(phi))*(np.cos(phi)**2.)
        
        dR      = (r[1]-r[0])*np.ones_like(Jprime1)
        dchi    = (chi[1]-chi[0])*np.ones_like(Jprime1)
        
        #Integrate derivatives from chi=chi0 to chi=1 
        I1      = np.dot(Iprime1,dchi)
        I2      = np.dot(Iprime2,dchi)
        J1      = np.dot(Jprime1,dchi)
        J2      = np.dot(Jprime2,dchi)        

        #Step 9, determine zeta and and Pc or zeta and Tc 
        if (Pc==0.)&(Tc!=0.): 
            #First Case, Thrust is given
            #Check to see if Tc is feasible, otherwise try a reasonable number
            if Tcnew>=I2*(I1/(2.*I2))**2.:
                Tcnew = I2*(I1/(2.*I2))**2.
            zetan    = (I1/(2.*I2)) - ((I1/(2.*I2))**2.-Tcnew/I2)**0.5

        elif (Pc!=0.)&(Tc==0.): 
            #Second Case, Thrust is given
            zetan    = -(J1/(J2*2.)) + ((J1/(J2*2.))**2.+Pc/J2)**0.5 
    
        #Step 10, repeat starting at step 2 with the new zeta
        diff = abs(zeta-zetan)
        
        zeta = zetan
    
    #Step 11, determine propeller efficiency etc...
    
    if (Pc==0.)&(Tc!=0.): 
        if Tcnew>=I2*(I1/(2.*I2))**2.:
            Tcnew = I2*(I1/(2.*I2))**2.
            print('Tc infeasible, reset to:')
            print(Tcnew)        
        #First Case, Thrust is given
        zeta    = (I1/(2.*I2)) - ((I1/(2.*I2))**2.-Tcnew/I2)**0.5
        Pc      = J1*zeta + J2*(zeta**2.)
        Tc      = I1*zeta - I2*(zeta**2.)
        
    elif (Pc!=0.)&(Tc==0.): 
        #Second Case, Thrust is given
        zeta    = -(J1/(2.*J2)) + ((J1/(2.*J2))**2.+Pc/J2)**0.5
        Tc      = I1*zeta - I2*(zeta**2.)
        Pc      = J1*zeta + J2*(zeta**2.) 
        
    # Calculate mid-chord alignment angle, MCA
    # This is the distance from the mid chord to the line axis out of the center of the blade
    # In this case the 1/4 chords are all aligned 
    MCA    = c/4. - c[0]/4.
    
    Thrust = Tc*rho*(V**2)*np.pi*(R**2)/2
    Power  = Pc*rho*(V**3)*np.pi*(R**2)/2 
    Ct     = Thrust/(rho*(n*n)*(D*D*D*D))
    Cp     = Power/(rho*(n*n*n)*(D*D*D*D*D))  
    
    # compute max thickness distribution  
    t_max  = np.zeros(N)    
    t_c    = np.zeros(N)    
    airfoil_geometry_data = import_airfoil_geometry(a_geo)
    for i in range(N):
        t_c[i]   = airfoil_geometry_data.thickness_to_chord[a_loc[i]]    
        t_max[i] = airfoil_geometry_data.max_thickness[a_loc[i]]*c[i]
        
    # Nondimensional thrust
    if prop.design_power == None: 
        prop.design_power = Power[0]        
    elif prop.design_thrust == None: 
        prop.design_thrust = Thrust[0]      
    
    # approximate thickness to chord ratio  
    t_c_at_70_percent = t_c[int(N*0.7)]
    
    # blade solidity
    r          = chi*R                    # Radial coordinate   
    blade_area = sp.integrate.cumtrapz(B*c, r-r[0])
    sigma      = blade_area[-1]/(np.pi*R**2)   
    
    prop.design_torque              = Power[0]/omega
    prop.max_thickness_distribution = t_max
    prop.twist_distribution         = beta
    prop.chord_distribution         = c
    prop.radius_distribution        = r 
    prop.number_blades              = int(B)
    prop.design_power_coefficient   = Cp 
    prop.design_thrust_coefficient  = Ct 
    prop.mid_chord_aligment         = MCA
    prop.thickness_to_chord         = t_c_at_70_percent
    prop.blade_solidity             = sigma  
    prop.airfoil_cl_surrogates      = airfoil_cl_surs
    prop.airfoil_cd_surrogates      = airfoil_cd_surs 

    return prop
コード例 #3
0
    def spin(self,conditions):
        """Analyzes a propeller given geometry and operating conditions.

        Assumptions:
        per source

        Source:
        Drela, M. "Qprop Formulation", MIT AeroAstro, June 2006
        http://web.mit.edu/drela/Public/web/qprop/qprop_theory.pdf

        Inputs:
        self.inputs.omega            [radian/s]
        conditions.freestream.
          density                    [kg/m^3]
          dynamic_viscosity          [kg/(m-s)]
          speed_of_sound             [m/s]
          temperature                [K]
        conditions.frames.
          body.transform_to_inertial (rotation matrix)
          inertial.velocity_vector   [m/s]
        conditions.propulsion.
          throttle                   [-]

        Outputs:
        conditions.propulsion.acoustic_outputs.
          number_sections            [-]
          r0                         [m]
          airfoil_chord              [m]
          blades_number              [-]
          propeller_diameter         [m]
          drag_coefficient           [-]
          lift_coefficient           [-]
          omega                      [radian/s]
          velocity                   [m/s]
          thrust                     [N]
          power                      [W]
          mid_chord_aligment         [m] (distance from the mid chord to the line axis out of the center of the blade)
        conditions.propulsion.etap   [-]
        thrust                       [N]
        torque                       [Nm]
        power                        [W]
        Cp                           [-] (coefficient of power)

        Properties Used:
        self. 
          number_blades              [-]
          tip_radius                 [m]
          hub_radius                 [m]
          twist_distribution         [radians]
          chord_distribution         [m]
          mid_chord_aligment         [m] (distance from the mid chord to the line axis out of the center of the blade)
          thrust_angle               [radians]
        """         
           
        #Unpack    
        B      = self.number_blades
        R      = self.tip_radius
        Rh     = self.hub_radius
        beta   = self.twist_distribution
        c      = self.chord_distribution
        chi    = self.radius_distribution
        omega  = self.inputs.omega 
        a_geo  = self.airfoil_geometry
        a_pol  = self.airfoil_polars        
        a_loc  = self.airfoil_polar_stations        
        rho    = conditions.freestream.density[:,0,None]
        mu     = conditions.freestream.dynamic_viscosity[:,0,None]
        Vv     = conditions.frames.inertial.velocity_vector
        Vh     = self.induced_hover_velocity 
        a      = conditions.freestream.speed_of_sound[:,0,None]
        T      = conditions.freestream.temperature[:,0,None]
        theta  = self.thrust_angle
        tc     = self.thickness_to_chord  
        sigma  = self.blade_solidity        
        
        BB     = B*B
        BBB    = BB*B
            
        # Velocity in the Body frame
        T_body2inertial = conditions.frames.body.transform_to_inertial
        T_inertial2body = orientation_transpose(T_body2inertial)
        V_body          = orientation_product(T_inertial2body,Vv)
        body2thrust     = np.array([[np.cos(theta), 0., np.sin(theta)],[0., 1., 0.], [-np.sin(theta), 0., np.cos(theta)]])
        T_body2thrust   = orientation_transpose(np.ones_like(T_body2inertial[:])*body2thrust)  
        V_thrust        = orientation_product(T_body2thrust,V_body)
        
        # Now just use the aligned velocity
        V  = V_thrust[:,0,None] 
        ua = np.zeros_like(V)  
        ut = np.zeros_like(V)  
        
        nu    = mu/rho
        tol   = 1e-5 # Convergence tolerance 
        
        #Things that don't change with iteration
        N       = len(c) # Number of stations     
        
        if  a_pol != None and a_loc != None:
            airfoil_polars = Data() 
            # check dimension of section
            if len(a_loc) != N:
                raise AssertionError('Dimension of airfoil sections must be equal to number of stations on propeller')
            # compute airfoil polars for airfoils 
            airfoil_polars = compute_airfoil_polars(self, a_geo, a_pol)
            airfoil_cl     = airfoil_polars.lift_coefficients
            airfoil_cd     = airfoil_polars.drag_coefficients
            AoA_sweep      = airfoil_polars.angle_of_attacks
        
        if self.radius_distribution is None:
            chi0    = Rh/R   # Where the propeller blade actually starts
            chi     = np.linspace(chi0,1,N+1)  # Vector of nondimensional radii
            chi     = chi[0:N] 
        
        lamda   = V/(omega*R)              # Speed ratio
        r       = chi*R                    # Radial coordinate
        pi      = np.pi
        pi2     = pi*pi
        x       = r*np.multiply(omega,1/V) # Nondimensional distance
        n       = omega/(2.*pi)            # Cycles per second
        J       = V/(2.*R*n)      
        
        omegar = np.outer(omega,r)
        Ua = np.outer((V + ua),np.ones_like(r))
        Ut = omegar - ut
        U  = np.sqrt(Ua*Ua + Ut*Ut)
        
        #Things that will change with iteration
        size = (len(a),N)
        Cl = np.zeros((1,N))  
        
        #Setup a Newton iteration
        psi    = np.ones(size)
        psiold = np.zeros(size)
        diff   = 1.
        
        ii = 0
        broke = False   
        while (diff>tol):
            sin_psi = np.sin(psi)
            cos_psi = np.cos(psi)
            Wa      = 0.5*Ua + 0.5*U*sin_psi
            Wt      = 0.5*Ut + 0.5*U*cos_psi   
            va     = Wa - Ua
            vt      = Ut - Wt
            alpha   = beta - np.arctan2(Wa,Wt)
            W       = (Wa*Wa + Wt*Wt)**0.5
            Ma      = (W)/a #a is the speed of sound 
            
            lamdaw = r*Wa/(R*Wt)
            
            # Limiter to keep from Nan-ing
            lamdaw[lamdaw<0.] = 0.
            
            f            = (B/2.)*(1.-r/R)/lamdaw
            piece        = np.exp(-f)
            arccos_piece = np.arccos(piece)
            F            = 2.*arccos_piece/pi
            Gamma        = vt*(4.*pi*r/B)*F*(1.+(4.*lamdaw*R/(pi*B*r))*(4.*lamdaw*R/(pi*B*r)))**0.5
            
            # Estimate Cl max
            Re         = (W*c)/nu 
            Cl_max_ref = -0.0009*tc**3 + 0.0217*tc**2 - 0.0442*tc + 0.7005
            Re_ref     = 9.*10**6      
            Cl1maxp    = Cl_max_ref * ( Re / Re_ref ) **0.1
            
            # Compute blade CL distribution from the airfoil data 
            if  a_pol != None and a_loc != None: 
                for k in range(N):
                    Cl[0,k] = np.interp(alpha[0,k],AoA_sweep,airfoil_cl[a_loc[k]])
            else:
                # If not airfoil polar provided, use 2*pi as lift curve slope
                Cl = 2.*pi*alpha
            
            # By 90 deg, it's totally stalled.
            Cl[Cl>Cl1maxp]  = Cl1maxp[Cl>Cl1maxp] # This line of code is what changed the regression testing
            Cl[alpha>=pi/2] = 0.
                
            # Scale for Mach, this is Karmen_Tsien
            Cl[Ma[:,:]<1.] = Cl[Ma[:,:]<1.]/((1-Ma[Ma[:,:]<1.]*Ma[Ma[:,:]<1.])**0.5+((Ma[Ma[:,:]<1.]*Ma[Ma[:,:]<1.])/(1+(1-Ma[Ma[:,:]<1.]*Ma[Ma[:,:]<1.])**0.5))*Cl[Ma<1.]/2)
        
            # If the blade segments are supersonic, don't scale
            Cl[Ma[:,:]>=1.] = Cl[Ma[:,:]>=1.] 
        
            Rsquiggly = Gamma - 0.5*W*c*Cl
            
            #An analytical derivative for dR_dpsi, this is derived by taking a derivative of the above equations
            #This was solved symbolically in Matlab and exported        
            f_wt_2 = 4*Wt*Wt
            f_wa_2 = 4*Wa*Wa
            Ucospsi  = U*cos_psi
            Usinpsi  = U*sin_psi
            Utcospsi = Ut*cos_psi
            Uasinpsi = Ua*sin_psi
            
            UapUsinpsi = (Ua + Usinpsi)
            utpUcospsi = (Ut + Ucospsi)
            
            utpUcospsi2 = utpUcospsi*utpUcospsi
            UapUsinpsi2 = UapUsinpsi*UapUsinpsi
            
            dR_dpsi = ((4.*U*r*arccos_piece*sin_psi*((16.*UapUsinpsi2)/(BB*pi2*f_wt_2) + 1.)**(0.5))/B - 
                       (pi*U*(Ua*cos_psi - Ut*sin_psi)*(beta - np.arctan((Wa+Wa)/(Wt+Wt))))/(2.*(f_wt_2 + f_wa_2)**(0.5))
                       + (pi*U*(f_wt_2 +f_wa_2)**(0.5)*(U + Utcospsi  +  Uasinpsi))/(2.*(f_wa_2/(f_wt_2) + 1.)*utpUcospsi2)
                       - (4.*U*piece*((16.*UapUsinpsi2)/(BB*pi2*f_wt_2) + 1.)**(0.5)*(R - r)*(Ut/2. - 
                      (Ucospsi)/2.)*(U + Utcospsi + Uasinpsi ))/(f_wa_2*(1. - np.exp(-(B*(Wt+Wt)*(R - 
                       r))/(r*(Wa+Wa))))**(0.5)) + (128.*U*r*arccos_piece*(Wa+Wa)*(Ut/2. - (Ucospsi)/2.)*(U + 
                       Utcospsi  + Uasinpsi ))/(BBB*pi2*utpUcospsi*utpUcospsi2*((16.*f_wa_2)/(BB*pi2*f_wt_2) + 1.)**(0.5))) 
            
            dR_dpsi[np.isnan(dR_dpsi)] = 0.1
                      
            dpsi   = -Rsquiggly/dR_dpsi
            psi    = psi + dpsi
            diff   = np.max(abs(psiold-psi))
            psiold = psi
            
            # If its really not going to converge
            if np.any(psi>pi/2) and np.any(dpsi>0.0):
                break
                
            ii+=1
                
            if ii>2000:
                broke = True
                break
        
        #There is also RE scaling
        #This is an atrocious fit of DAE51 data at RE=50k for Cd
        Cdval = (0.108*(Cl*Cl*Cl*Cl)-0.2612*(Cl*Cl*Cl)+0.181*(Cl*Cl)-0.0139*Cl+0.0278)*((50000./Re)**0.2)
        Cdval[alpha>=pi/2] = 2.
        
        #More Cd scaling from Mach from AA241ab notes for turbulent skin friction
        Tw_Tinf = 1. + 1.78*(Ma*Ma)
        Tp_Tinf = 1. + 0.035*(Ma*Ma) + 0.45*(Tw_Tinf-1.)
        Tp      = (Tp_Tinf)*T
        Rp_Rinf = (Tp_Tinf**2.5)*(Tp+110.4)/(T+110.4)
        
        Cd = ((1/Tp_Tinf)*(1/Rp_Rinf)**0.2)*Cdval 
        
        epsilon  = Cd/Cl
        epsilon[epsilon==np.inf] = 10. 
        deltar   = (r[1]-r[0])
        thrust   = rho*B*(np.sum(Gamma*(Wt-epsilon*Wa)*deltar,axis=1)[:,None])
        torque   = rho*B*np.sum(Gamma*(Wa+epsilon*Wt)*r*deltar,axis=1)[:,None]
        D        = 2*R 
        Ct       = thrust/(rho*(n*n)*(D*D*D*D))
        Ct[Ct<0] = 0.        #prevent things from breaking
        kappa    = self.induced_power_factor 
        Cd0      = self.profile_drag_coefficient   
        Cp    = np.zeros_like(Ct)
        power = np.zeros_like(Ct)        
        for i in range(len(Vv)):
            if -1. <Vv[i][0] <1.: # vertical/axial flight
                Cp[i]       = (kappa*(Ct[i]**1.5)/(2**.5))+sigma*Cd0/8.
                power[i]    = Cp[i]*(rho[i]*(n[i]*n[i]*n[i])*(D*D*D*D*D))
                torque[i]   = power[i]/omega[i]  
            else:  
                power[i]    = torque[i]*omega[i]   
                Cp[i]       = power[i]/(rho[i]*(n[i]*n[i]*n[i])*(D*D*D*D*D)) 
  
        # torque coefficient 
        Cq = torque/(rho*(n*n)*(D*D*D*D)*R) 
        
        thrust[conditions.propulsion.throttle[:,0] <=0.0] = 0.0
        power[conditions.propulsion.throttle[:,0]  <=0.0] = 0.0 
        torque[conditions.propulsion.throttle[:,0]  <=0.0] = 0.0 
        thrust[omega<0.0] = - thrust[omega<0.0] 
        
        etap     = V*thrust/power     
        
        conditions.propulsion.etap = etap 
        
        # store data
        results_conditions                   = Data     
        outputs                              = results_conditions(
                num_blades                       = B,
                rotor_radius                     = R,
                rotor_diameter                   = D,
                number_sections                  = N,
                radius_distribution              = np.linspace(Rh ,R, N),
                chord_distribution               = c,     
                twist_distribution               = beta,            
                normalized_radial_distribution   = r,
                thrust_angle                     = theta,
                speed_of_sound                   = conditions.freestream.speed_of_sound,
                density                          = conditions.freestream.density,
                velocity                         = Vv, 
                tangential_velocity_distribution = vt, 
                axial_velocity_distribution      = va, 
                drag_coefficient                 = Cd,
                lift_coefficient                 = Cl,       
                omega                            = omega, 
                dT_dR                            = rho*(Gamma*(Wt-epsilon*Wa)),   
                dT_dr                            = rho*(Gamma*(Wt-epsilon*Wa))*R,  
                thrust_distribution              = rho*(Gamma*(Wt-epsilon*Wa))*deltar, 
                thrust_per_blade                 = thrust/B,  
                thrust_coefficient               = Ct,  
                dQ_dR                            = rho*(Gamma*(Wa+epsilon*Wt)*r), 
                dQ_dr                            = rho*(Gamma*(Wa+epsilon*Wt)*r)*R,
                torque_distribution              = rho*(Gamma*(Wa+epsilon*Wt)*r)*deltar,
                torque_per_blade                 = torque/B,   
                torque_coefficient               = Cq,   
                power                            = power,
                power_coefficient                = Cp, 
                mid_chord_aligment               = self.mid_chord_aligment     
            ) 
        
        return thrust, torque, power, Cp, outputs  , etap  
コード例 #4
0
ファイル: Airfoil_Plots.py プロジェクト: kushalam/SUAVE
def plot_airfoil_polar_files(airfoil_path, airfoil_polar_paths, line_color = 'k-', use_surrogate = False, 
                display_plot = False, save_figure = False, save_filename = "Airfoil_Polars", file_type = ".png"):
    """This plots all airfoil polars in the list "airfoil_polar_paths" 

    Assumptions:
    None

    Source:
    None

    Inputs:
    airfoil_polar_paths   [list of strings]

    Outputs: 
    Plots

    Properties Used:
    N/A	
    """
    shape = np.shape(airfoil_polar_paths)
    n_airfoils = shape[0]
    n_Re       = shape[1]
    
    if use_surrogate:
        # Compute airfoil surrogates
        a_data = compute_airfoil_polars(airfoil_path, airfoil_polar_paths, use_pre_stall_data=False)
        CL_sur = a_data.lift_coefficient_surrogates
        CD_sur = a_data.drag_coefficient_surrogates
        
        alpha   = np.asarray(a_data.aoa_from_polar)
        n_alpha = len(alpha.T)
        alpha   = np.reshape(alpha,(n_airfoils,1,n_alpha))
        alpha   = np.repeat(alpha, n_Re, axis=1)
        
        Re      = a_data.re_from_polar
        Re      = np.reshape(Re,(n_airfoils,n_Re,1))
        Re      = np.repeat(Re, n_alpha, axis=2)

        CL = np.zeros_like(Re)
        CD = np.zeros_like(Re)

    else:
        # Use the raw data polars
        airfoil_polar_data = import_airfoil_polars(airfoil_polar_paths)
        CL = airfoil_polar_data.lift_coefficients
        CD = airfoil_polar_data.drag_coefficients
        
        n_alpha = np.shape(CL)[2]
        Re = airfoil_polar_data.reynolds_number
        Re = np.reshape(Re, (n_airfoils,n_Re,1))
        Re = np.repeat(Re, n_alpha, axis=2)


    for i in range(n_airfoils):
        airfoil_name = os.path.basename(airfoil_path[i][0])
        if use_surrogate:
            CL[i,:,:] = CL_sur[airfoil_path[i]](Re[i,:,:],alpha[i,:,:],grid=False)
            CD[i,:,:] = CD_sur[airfoil_path[i]](Re[i,:,:],alpha[i,:,:],grid=False)
            
        # plot all Reynolds number polars for ith airfoil
        fig  = plt.figure(save_filename +'_'+ str(i))
        fig.set_size_inches(10, 4)
        axes = fig.add_subplot(1,1,1)
        axes.set_title(airfoil_name)            
        for j in range(n_Re):
            Re_val = str(round(Re[i,j,0]))
            axes.plot(CD[i,j,:], CL[i,j,:], label='Re='+Re_val)
            
        axes.set_xlabel('$C_D$')  
        axes.set_ylabel('$C_L$')  
        axes.legend(bbox_to_anchor=(1,1), loc='upper left', ncol=1)
        
        if save_figure:
            plt.savefig(save_filename +'_' + str(i) + file_type)   
        if display_plot:
            plt.show()
    return
コード例 #5
0
def propeller_design(prop, number_of_stations=20):
    """ Optimizes propeller chord and twist given input parameters.
          
          Inputs:
          Either design power or thrust
          prop_attributes.
            hub radius                       [m]
            tip radius                       [m]
            rotation rate                    [rad/s]
            freestream velocity              [m/s]
            number of blades               
            number of stations
            design lift coefficient
            airfoil data
            
          Outputs:
          Twist distribution                 [array of radians]
          Chord distribution                 [array of meters]
              
          Assumptions/ Source:
          Based on Design of Optimum Propellers by Adkins and Liebeck
          
    """
    print('\nDesigning', prop.tag)

    # Unpack
    N = number_of_stations  # this number determines the discretization of the propeller into stations
    B = prop.number_of_blades
    R = prop.tip_radius
    Rh = prop.hub_radius
    omega = prop.angular_velocity  # Rotation Rate in rad/s
    V = prop.freestream_velocity  # Freestream Velocity
    Cl = prop.design_Cl  # Design Lift Coefficient
    alt = prop.design_altitude
    Thrust = prop.design_thrust
    Power = prop.design_power
    a_geo = prop.airfoil_geometry
    a_pol = prop.airfoil_polars
    a_loc = prop.airfoil_polar_stations

    if (Thrust == None) and (Power == None):
        raise AssertionError('Specify either design thrust or design power!')

    elif (Thrust != None) and (Power != None):
        raise AssertionError('Specify either design thrust or design power!')

    if V == 0.0:
        V = 1E-6

    # Calculate atmospheric properties
    atmosphere = SUAVE.Analyses.Atmospheric.US_Standard_1976()
    atmo_data = atmosphere.compute_values(alt)

    p = atmo_data.pressure[0]
    T = atmo_data.temperature[0]
    rho = atmo_data.density[0]
    speed_of_sound = atmo_data.speed_of_sound[0]
    mu = atmo_data.dynamic_viscosity[0]
    nu = mu / rho

    # Nondimensional thrust
    if (Thrust != None) and (Power == None):
        Tc = 2. * Thrust / (rho * (V * V) * np.pi * (R * R))
        Pc = 0.0

    elif (Thrust == None) and (Power != None):
        Tc = 0.0
        Pc = 2. * Power / (rho * (V * V * V) * np.pi * (R * R))

    tol = 1e-10  # Convergence tolerance

    # Step 1, assume a zeta
    zeta = 0.1  # Assume to be small initially

    # Step 2, determine F and phi at each blade station

    chi0 = Rh / R  # Where the propeller blade actually starts
    chi = np.linspace(chi0, 1, N + 1)  # Vector of nondimensional radii
    chi = chi[0:N]
    lamda = V / (omega * R)  # Speed ratio
    r = chi * R  # Radial coordinate
    x = omega * r / V  # Nondimensional distance
    diff = 1.0  # Difference between zetas
    n = omega / (2 * np.pi)  # Cycles per second
    D = 2. * R
    J = V / (D * n)

    c = 0.2 * np.ones_like(chi)

    # if user defines airfoil, check dimension of stations
    if a_pol != None and a_loc != None:
        if len(a_loc) != N:
            raise AssertionError(
                '\nDimension of airfoil sections must be equal to number of stations on propeller'
            )
        airfoil_flag = True
    else:
        print('\nDefaulting to scaled DAE51')
        airfoil_flag = False
        airfoil_cl_surs = None
        airfoil_cd_surs = None

    # Step 4, determine epsilon and alpha from airfoil data
    if airfoil_flag:
        # compute airfoil polars for airfoils
        airfoil_polars = compute_airfoil_polars(a_geo, a_pol)
        airfoil_cl_surs = airfoil_polars.lift_coefficient_surrogates
        airfoil_cd_surs = airfoil_polars.drag_coefficient_surrogates

    while diff > tol:
        # assign chord distribution
        prop.chord_distribution = c

        #Things that need a loop
        Tcnew = Tc
        tanphit = lamda * (1. + zeta / 2.
                           )  # Tangent of the flow angle at the tip
        phit = np.arctan(tanphit)  # Flow angle at the tip
        tanphi = tanphit / chi  # Flow angle at every station
        f = (B / 2.) * (1. - chi) / np.sin(phit)
        F = (2. / np.pi) * np.arccos(np.exp(-f))  #Prandtl momentum loss factor
        phi = np.arctan(tanphi)  # Flow angle at every station

        #Step 3, determine the product Wc, and RE
        G = F * x * np.cos(phi) * np.sin(phi)  #Circulation function
        Wc = 4. * np.pi * lamda * G * V * R * zeta / (Cl * B)
        Ma = Wc / speed_of_sound
        RE = Wc / nu

        if airfoil_flag:
            # assign initial values
            alpha0 = np.ones(N) * 0.05

            # solve for optimal alpha to meet design Cl target
            sol = root(objective,
                       x0=alpha0,
                       args=(airfoil_cl_surs, RE, a_geo, a_loc, Cl, N))
            alpha = sol.x

            # query surrogate for sectional Cls at stations
            Cdval = np.zeros_like(RE)
            for j in range(len(airfoil_cd_surs)):
                Cdval_af = airfoil_cd_surs[a_geo[j]](RE, alpha, grid=False)
                locs = np.where(np.array(a_loc) == j)
                Cdval[locs] = Cdval_af[locs]

        else:
            Cdval = (0.108 * (Cl**4) - 0.2612 * (Cl**3) + 0.181 *
                     (Cl**2) - 0.0139 * Cl + 0.0278) * ((50000. / RE)**0.2)
            alpha = Cl / (2. * np.pi)

        #More Cd scaling from Mach from AA241ab notes for turbulent skin friction
        Tw_Tinf = 1. + 1.78 * (Ma**2)
        Tp_Tinf = 1. + 0.035 * (Ma**2) + 0.45 * (Tw_Tinf - 1.)
        Tp = Tp_Tinf * T
        Rp_Rinf = (Tp_Tinf**2.5) * (Tp + 110.4) / (T + 110.4)
        Cd = ((1 / Tp_Tinf) * (1 / Rp_Rinf)**0.2) * Cdval

        #Step 5, change Cl and repeat steps 3 and 4 until epsilon is minimized
        epsilon = Cd / Cl

        #Step 6, determine a and a', and W
        a = (zeta / 2.) * (np.cos(phi)**2.) * (1. - epsilon * np.tan(phi))
        aprime = (zeta / (2. * x)) * np.cos(phi) * np.sin(phi) * (
            1. + epsilon / np.tan(phi))
        W = V * (1. + a) / np.sin(phi)

        #Step 7, compute the chord length and blade twist angle
        c = Wc / W
        beta = alpha + phi  # Blade twist angle

        #Step 8, determine 4 derivatives in I and J
        Iprime1 = 4. * chi * G * (1. - epsilon * np.tan(phi))
        Iprime2 = lamda * (Iprime1 / (2. * chi)) * (
            1. + epsilon / np.tan(phi)) * np.sin(phi) * np.cos(phi)
        Jprime1 = 4. * chi * G * (1. + epsilon / np.tan(phi))
        Jprime2 = (Jprime1 / 2.) * (1. - epsilon * np.tan(phi)) * (np.cos(phi)
                                                                   **2.)

        dR = (r[1] - r[0]) * np.ones_like(Jprime1)
        dchi = (chi[1] - chi[0]) * np.ones_like(Jprime1)

        #Integrate derivatives from chi=chi0 to chi=1
        I1 = np.dot(Iprime1, dchi)
        I2 = np.dot(Iprime2, dchi)
        J1 = np.dot(Jprime1, dchi)
        J2 = np.dot(Jprime2, dchi)

        #Step 9, determine zeta and and Pc or zeta and Tc
        if (Pc == 0.) & (Tc != 0.):
            #First Case, Thrust is given
            #Check to see if Tc is feasible, otherwise try a reasonable number
            if Tcnew >= I2 * (I1 / (2. * I2))**2.:
                Tcnew = I2 * (I1 / (2. * I2))**2.
            zetan = (I1 / (2. * I2)) - ((I1 / (2. * I2))**2. - Tcnew / I2)**0.5

        elif (Pc != 0.) & (Tc == 0.):
            #Second Case, Thrust is given
            zetan = -(J1 / (J2 * 2.)) + ((J1 / (J2 * 2.))**2. + Pc / J2)**0.5

        #Step 10, repeat starting at step 2 with the new zeta
        diff = abs(zeta - zetan)

        zeta = zetan

    #Step 11, determine propeller efficiency etc...
    if (Pc == 0.) & (Tc != 0.):
        if Tcnew >= I2 * (I1 / (2. * I2))**2.:
            Tcnew = I2 * (I1 / (2. * I2))**2.
            print('Tc infeasible, reset to:')
            print(Tcnew)
        #First Case, Thrust is given
        zeta = (I1 / (2. * I2)) - ((I1 / (2. * I2))**2. - Tcnew / I2)**0.5
        Pc = J1 * zeta + J2 * (zeta**2.)
        Tc = I1 * zeta - I2 * (zeta**2.)

    elif (Pc != 0.) & (Tc == 0.):
        #Second Case, Thrust is given
        zeta = -(J1 / (2. * J2)) + ((J1 / (2. * J2))**2. + Pc / J2)**0.5
        Tc = I1 * zeta - I2 * (zeta**2.)
        Pc = J1 * zeta + J2 * (zeta**2.)

    # Calculate mid-chord alignment angle, MCA
    # This is the distance from the mid chord to the line axis out of the center of the blade
    # In this case the 1/4 chords are all aligned
    MCA = c / 4. - c[0] / 4.

    Thrust = Tc * rho * (V**2) * np.pi * (R**2) / 2
    Power = Pc * rho * (V**3) * np.pi * (R**2) / 2
    Ct = Thrust / (rho * (n * n) * (D * D * D * D))
    Cp = Power / (rho * (n * n * n) * (D * D * D * D * D))

    # compute max thickness distribution
    t_max = np.zeros(N)
    t_c = np.zeros(N)
    if airfoil_flag:
        airfoil_geometry_data = import_airfoil_geometry(a_geo)
        t_max = np.take(airfoil_geometry_data.max_thickness, a_loc, axis=0) * c
        t_c = np.take(airfoil_geometry_data.thickness_to_chord, a_loc, axis=0)
    else:
        c_blade = np.repeat(np.atleast_2d(np.linspace(0, 1, N)), N,
                            axis=0) * np.repeat(np.atleast_2d(c).T, N, axis=1)
        t = (5 * c_blade) * (
            0.2969 * np.sqrt(c_blade) - 0.1260 * c_blade - 0.3516 *
            (c_blade**2) + 0.2843 * (c_blade**3) - 0.1015 *
            (c_blade**4))  # local thickness distribution
        t_max = np.max(t, axis=1)
        t_c = np.max(t, axis=1) / c

    # Nondimensional thrust
    if prop.design_power == None:
        prop.design_power = Power[0]
    elif prop.design_thrust == None:
        prop.design_thrust = Thrust[0]

    # blade solidity
    r = chi * R  # Radial coordinate
    blade_area = sp.integrate.cumtrapz(B * c, r - r[0])
    sigma = blade_area[-1] / (np.pi * R**2)

    prop.design_torque = Power[0] / omega
    prop.max_thickness_distribution = t_max
    prop.twist_distribution = beta
    prop.chord_distribution = c
    prop.radius_distribution = r
    prop.number_of_blades = int(B)
    prop.design_power_coefficient = Cp
    prop.design_thrust_coefficient = Ct
    prop.mid_chord_alignment = MCA
    prop.thickness_to_chord = t_c
    prop.blade_solidity = sigma
    prop.airfoil_cl_surrogates = airfoil_cl_surs
    prop.airfoil_cd_surrogates = airfoil_cd_surs
    prop.airfoil_flag = airfoil_flag

    return prop
コード例 #6
0
def propeller_geometry():

    # --------------------------------------------------------------------------------------------------
    # Propeller Geometry:
    # --------------------------------------------------------------------------------------------------

    prop = SUAVE.Components.Energy.Converters.Propeller()

    prop.tag = "APC 10x7 Propeller"
    prop.tip_radius = 5 * Units.inches
    prop.number_of_blades = 2
    prop.hub_radius = prop.tip_radius * 0.1
    prop.inputs = Data()
    prop.inputs.omega = np.array([[4500 * Units.rpm]])

    r_R = np.array([
        0.15,
        0.20,
        0.25,
        0.30,
        0.35,
        0.40,
        0.45,
        0.50,
        0.55,
        0.60,
        0.65,
        0.70,
        0.75,
        0.80,
        0.85,
        0.90,
        0.95,
    ])
    c_R = np.array([
        0.138,
        0.154,
        0.175,
        0.190,
        0.198,
        0.202,
        0.200,
        0.195,
        0.186,
        0.174,
        0.161,
        0.145,
        0.129,
        0.112,
        0.096,
        0.081,
        0.061,
    ])
    beta = np.array([
        37.86,
        45.82,
        44.19,
        38.35,
        33.64,
        29.90,
        27.02,
        24.67,
        22.62,
        20.88,
        19.36,
        17.98,
        16.74,
        15.79,
        14.64,
        13.86,
        12.72,
    ])

    prop.pitch_command = 0.0 * Units.deg
    prop.twist_distribution = beta * Units.deg
    prop.chord_distribution = c_R * prop.tip_radius
    prop.radius_distribution = r_R * prop.tip_radius
    prop.max_thickness_distribution = 0.12

    prop.number_azimuthal_stations = 24
    prop.number_radial_stations = len(r_R)

    # Distance from mid chord to the line axis out of the center of the blade - In this case the 1/4 chords are all aligned
    MCA = prop.chord_distribution / 4. - prop.chord_distribution[0] / 4.
    prop.mid_chord_alignment = MCA

    airfoils_path = os.path.join(os.path.dirname(__file__), "../Airfoils/")
    polars_path = os.path.join(os.path.dirname(__file__),
                               "../Airfoils/Polars/")
    prop.airfoil_geometry = [airfoils_path + "Clark_y.txt"]
    prop.airfoil_polars = [
        [
            polars_path + "Clark_y_polar_Re_50000.txt",
            polars_path + "Clark_y_polar_Re_100000.txt",
            polars_path + "Clark_y_polar_Re_200000.txt",
            polars_path + "Clark_y_polar_Re_500000.txt",
            polars_path + "Clark_y_polar_Re_1000000.txt",
        ],
    ]

    prop.airfoil_polar_stations = np.zeros(len(r_R))
    prop.airfoil_polar_stations = list(prop.airfoil_polar_stations.astype(int))

    airfoil_polars = compute_airfoil_polars(prop.airfoil_geometry,
                                            prop.airfoil_polars)
    airfoil_cl_surs = airfoil_polars.lift_coefficient_surrogates
    airfoil_cd_surs = airfoil_polars.drag_coefficient_surrogates

    prop.airfoil_cl_surrogates = airfoil_cl_surs
    prop.airfoil_cd_surrogates = airfoil_cd_surs

    results = Data()
    results.lift_coefficient_surrogates = airfoil_polars.lift_coefficient_surrogates
    results.drag_coefficient_surrogates = airfoil_polars.drag_coefficient_surrogates
    results.cl_airfoiltools = airfoil_polars.lift_coefficients_from_polar
    results.cd_airfoiltools = airfoil_polars.drag_coefficients_from_polar
    results.re_airfoiltools = airfoil_polars.re_from_polar
    results.aoa_airfoiltools = airfoil_polars.aoa_from_polar

    return prop