Ejemplo n.º 1
0
def update_forces(segment, state):

    # unpack
    conditions = state.conditions

    # unpack forces
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector
    wind_drag_force_vector = conditions.frames.wind.drag_force_vector
    body_thrust_force_vector = conditions.frames.body.thrust_force_vector
    inertial_gravity_force_vector = conditions.frames.inertial.gravity_force_vector

    # unpack transformation matrices
    T_body2inertial = conditions.frames.body.transform_to_inertial
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial, wind_lift_force_vector)
    D = orientation_product(T_wind2inertial, wind_drag_force_vector)
    T = orientation_product(T_body2inertial, body_thrust_force_vector)
    W = inertial_gravity_force_vector

    # sum of the forces
    F = L + D + T + W
    # like a boss

    # pack
    conditions.frames.inertial.total_force_vector[:, :] = F[:, :]

    return
Ejemplo n.º 2
0
def update_forces(segment,state):

    # unpack
    conditions = state.conditions

    # unpack forces
    wind_lift_force_vector        = conditions.frames.wind.lift_force_vector
    wind_drag_force_vector        = conditions.frames.wind.drag_force_vector
    body_thrust_force_vector      = conditions.frames.body.thrust_force_vector
    inertial_gravity_force_vector = conditions.frames.inertial.gravity_force_vector

    # unpack transformation matrices
    T_body2inertial = conditions.frames.body.transform_to_inertial
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial,wind_lift_force_vector)
    D = orientation_product(T_wind2inertial,wind_drag_force_vector)
    T = orientation_product(T_body2inertial,body_thrust_force_vector)
    W = inertial_gravity_force_vector

    # sum of the forces
    F = L + D + T + W
    # like a boss

    # pack
    conditions.frames.inertial.total_force_vector[:,:] = F[:,:]

    return
Ejemplo n.º 3
0
def update_forces(segment,state):
    
    """ Summation of forces: lift, drag, thrust, weight. Future versions will include new definitions of dreams, FAA, money, and reality.
    
        Assumptions:
        You only have these 4 forces applied to the vehicle
        
        Inputs:
        state.conditions:
            frames.wind.lift_force_vector          [newtons]
            frames.wind.drag_force_vector          [newtons]
            frames.inertial.gravity_force_vector   [newtons]
            frames.body.thrust_force_vector        [newtons]
            frames.body.transform_to_inertial      [newtons]
            frames.wind.transform_to_inertial      [newtons]

            
        Outputs:
            state.conditions:           
                frames.inertial.total_force_vector [newtons]

    

        Properties Used:
        N/A
    """    

    # unpack
    conditions = state.conditions

    # unpack forces
    wind_lift_force_vector        = conditions.frames.wind.lift_force_vector
    wind_drag_force_vector        = conditions.frames.wind.drag_force_vector
    body_thrust_force_vector      = conditions.frames.body.thrust_force_vector
    inertial_gravity_force_vector = conditions.frames.inertial.gravity_force_vector

    # unpack transformation matrices
    T_body2inertial = conditions.frames.body.transform_to_inertial
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial,wind_lift_force_vector)
    D = orientation_product(T_wind2inertial,wind_drag_force_vector)
    T = orientation_product(T_body2inertial,body_thrust_force_vector)
    W = inertial_gravity_force_vector

    # sum of the forces
    F = L + D + T + W
    # like a boss

    # pack
    conditions.frames.inertial.total_force_vector[:,:] = F[:,:]

    return
Ejemplo n.º 4
0
def update_forces(segment):
    """ Summation of forces: lift, drag, thrust, weight. Future versions will include new definitions of dreams, FAA, money, and reality.
    
        Assumptions:
        You only have these 4 forces applied to the vehicle
        
        Inputs:
            segment.state.conditions:
                frames.wind.lift_force_vector          [newtons]
                frames.wind.drag_force_vector          [newtons]
                frames.inertial.gravity_force_vector   [newtons]
                frames.body.thrust_force_vector        [newtons]
                frames.body.transform_to_inertial      [newtons]
                frames.wind.transform_to_inertial      [newtons]

            
        Outputs:
            segment.state.conditions:           
                frames.inertial.total_force_vector [newtons]

    

        Properties Used:
        N/A
    """

    # unpack
    conditions = segment.state.conditions

    # unpack forces
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector
    wind_drag_force_vector = conditions.frames.wind.drag_force_vector
    body_thrust_force_vector = conditions.frames.body.thrust_force_vector
    inertial_gravity_force_vector = conditions.frames.inertial.gravity_force_vector

    # unpack transformation matrices
    T_body2inertial = conditions.frames.body.transform_to_inertial
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial, wind_lift_force_vector)
    D = orientation_product(T_wind2inertial, wind_drag_force_vector)
    T = orientation_product(T_body2inertial, body_thrust_force_vector)
    W = inertial_gravity_force_vector

    # sum of the forces
    F = L + D + T + W
    # like a boss

    # pack
    conditions.frames.inertial.total_force_vector[:, :] = F[:, :]

    return
Ejemplo n.º 5
0
def compute_ground_forces(segment):
    """ Compute the rolling friction on the aircraft 
    
    Assumptions:
        Does a force balance to calculate the load on the wheels using only lift. Uses only a single friction coefficient.
    
    Source:
        N/A
    
    Inputs:
    conditions:
        frames.inertial.gravity_force_vector       [meters/second^2]
        ground.friction_coefficient                [unitless]
        frames.wind.lift_force_vector              [newtons]
    
    Outputs:
        conditions.frames.inertial.ground_force_vector [newtons]
    
    Properties Used:
        N/A
    """

    # unpack
    conditions = segment.state.conditions
    W = conditions.frames.inertial.gravity_force_vector[:, 2, None]
    friction_coeff = conditions.ground.friction_coefficient
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector

    #transformation matrix to get lift in inertial frame
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial, wind_lift_force_vector)[:, 2,
                                                                     None]

    #compute friction force
    N = -(W + L)
    Ff = N * friction_coeff

    #pack results. Friction acts along x-direction
    conditions.frames.inertial.ground_force_vector[:, 2] = N[:, 0]
    conditions.frames.inertial.ground_force_vector[:, 0] = Ff[:, 0]
Ejemplo n.º 6
0
def compute_ground_forces(segment):
    """ Compute the rolling friction on the aircraft 
    
    Assumptions:
    Does a force balance to calculate the load on the wheels using only lift. Uses only a single friction coefficient.

    Source:
    N/A

    Inputs:
    conditions:
        frames.inertial.gravity_force_vector       [meters/second^2]
        ground.friction_coefficient                [unitless]
        frames.wind.lift_force_vector              [newtons]

    Outputs:
    conditions.frames.inertial.ground_force_vector [newtons]

    Properties Used:
    N/A
    """   

    # unpack
    conditions             = segment.state.conditions
    W                      = conditions.frames.inertial.gravity_force_vector[:,2,None]
    friction_coeff         = conditions.ground.friction_coefficient
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector

    #transformation matrix to get lift in inertial frame
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial,wind_lift_force_vector)[:,2,None]

    #compute friction force
    N  = -(W + L)
    Ff = N * friction_coeff

    #pack results. Friction acts along x-direction
    conditions.frames.inertial.ground_force_vector[:,2] = N[:,0]
    conditions.frames.inertial.ground_force_vector[:,0] = Ff[:,0]
Ejemplo n.º 7
0
def compute_ground_forces(segment,state):
    """ Compute the rolling friction on the aircraft """

    # unpack
    conditions             = state.conditions
    W                      = conditions.frames.inertial.gravity_force_vector[:,2,None]
    friction_coeff         = conditions.ground.friction_coefficient
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector

    #transformation matrix to get lift in inertial frame
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial,wind_lift_force_vector)[:,2,None]

    #compute friction force
    N  = -(W + L)
    Ff = N * friction_coeff

    #pack results. Friction acts along x-direction
    conditions.frames.inertial.ground_force_vector[:,2] = N[:,0]
    conditions.frames.inertial.ground_force_vector[:,0] = Ff[:,0]
Ejemplo n.º 8
0
def compute_ground_forces(segment, state):
    """ Compute the rolling friction on the aircraft """

    # unpack
    conditions = state.conditions
    W = conditions.frames.inertial.gravity_force_vector[:, 2, None]
    friction_coeff = conditions.ground.friction_coefficient
    wind_lift_force_vector = conditions.frames.wind.lift_force_vector

    #transformation matrix to get lift in inertial frame
    T_wind2inertial = conditions.frames.wind.transform_to_inertial

    # to inertial frame
    L = orientation_product(T_wind2inertial, wind_lift_force_vector)[:, 2,
                                                                     None]

    #compute friction force
    N = -(W + L)
    Ff = N * friction_coeff

    #pack results. Friction acts along x-direction
    conditions.frames.inertial.ground_force_vector[:, 2] = N[:, 0]
    conditions.frames.inertial.ground_force_vector[:, 0] = Ff[:, 0]
Ejemplo n.º 9
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
        
        Leishman, Gordon J. Principles of helicopter aerodynamics
        Cambridge university press, 2006.      

        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.outputs.        
           number_radial_stations            [-]
           number_azimuthal_stations         [-] 
           disc_radial_distribution          [m]
           thrust_angle                      [rad]
           speed_of_sound                    [m/s]
           density                           [kg/m-3]
           velocity                          [m/s]
           disc_tangential_induced_velocity  [m/s]
           disc_axial_induced_velocity       [m/s]
           disc_tangential_velocity          [m/s]
           disc_axial_velocity               [m/s]
           drag_coefficient                  [-]
           lift_coefficient                  [-]
           omega                             [rad/s]
           disc_circulation                  [-] 
           blade_dQ_dR                       [N/m]
           blade_dT_dr                       [N]
           blade_thrust_distribution         [N]
           disc_thrust_distribution          [N]
           thrust_per_blade                  [N]
           thrust_coefficient                [-] 
           azimuthal_distribution            [rad]
           disc_azimuthal_distribution       [rad]
           blade_dQ_dR                       [N]
           blade_dQ_dr                       [Nm]
           blade_torque_distribution         [Nm] 
           disc_torque_distribution          [Nm] 
           torque_per_blade                  [Nm] 
           torque_coefficient                [-] 
           power                             [W]    
           power_coefficient                 [-] 
                                             
        Properties Used:                     
        self.                                
          number_of_blades                   [-]
          tip_radius                         [m]
          hub_radius                         [m]
          twist_distribution                 [radians]
          chord_distribution                 [m]
          mid_chord_alignment                [m] 
          thrust_angle                       [radians]
        """         
           
        #Unpack    
        B       = self.number_of_blades 
        R       = self.tip_radius
        Rh      = self.hub_radius
        beta_0  = self.twist_distribution
        c       = self.chord_distribution
        chi     = self.radius_distribution 
        omega   = self.inputs.omega
        a_geo   = self.airfoil_geometry      
        a_loc   = self.airfoil_polar_stations  
        cl_sur  = self.airfoil_cl_surrogates
        cd_sur  = self.airfoil_cd_surrogates 
        tc      = self.thickness_to_chord
        ua      = self.induced_hover_velocity
        VTOL    = self.VTOL_flag
        rho     = conditions.freestream.density[:,0,None]
        mu      = conditions.freestream.dynamic_viscosity[:,0,None]
        Vv      = conditions.frames.inertial.velocity_vector 
        a       = conditions.freestream.speed_of_sound[:,0,None]
        T       = conditions.freestream.temperature[:,0,None]
        pitch_c = self.pitch_command
        theta   = self.thrust_angle 
        Na      = self.number_azimuthal_stations 
        BB      = B*B    
        BBB     = BB*B
        
        nonuniform_freestream = self.nonuniform_freestream
        rotation              = self.rotation
        
        # calculate total blade pitch
        total_blade_pitch = beta_0 + pitch_c  
         
        # 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) 
        
        if VTOL:
            V        = V_thrust[:,0,None] + ua
        else:
            V        = V_thrust[:,0,None]
        ut  = np.zeros_like(V)

        #Things that don't change with iteration
        Nr       = len(c) # Number of stations radially    
        ctrl_pts = len(Vv)
        
        # set up non dimensional radial distribution  
        chi      = self.radius_distribution/R
            
        V        = V_thrust[:,0,None] 
        omega    = np.abs(omega)        
        r        = chi*R            # Radial coordinate 
        omegar   = np.outer(omega,r)
        pi       = np.pi            
        pi2      = pi*pi        
        n        = omega/(2.*pi)    # Cycles per second  
        nu       = mu/rho  
        
        deltar   = (r[1]-r[0])  
        deltachi = (chi[1]-chi[0])          
        rho_0    = rho
        
        # azimuth distribution 
        psi            = np.linspace(0,2*pi,Na+1)[:-1]
        psi_2d         = np.tile(np.atleast_2d(psi).T,(1,Nr))
        psi_2d         = np.repeat(psi_2d[np.newaxis, :, :], ctrl_pts, axis=0)    
        
        # 2D radial distribution non dimensionalized
        chi_2d         = np.tile(chi ,(Na,1))            
        chi_2d         = np.repeat(chi_2d[ np.newaxis,:, :], ctrl_pts, axis=0) 
        r_dim_2d       = np.tile(r ,(Na,1))  
        r_dim_2d       = np.repeat(r_dim_2d[ np.newaxis,:, :], ctrl_pts, axis=0)  
     
        # Setup a Newton iteration
        diff   = 1. 
        ii     = 0
        tol    = 1e-6  # Convergence tolerance
        
        use_2d_analysis = False
        
        if theta !=0:
            # thrust angle creates disturbances in radial and tangential velocities
            use_2d_analysis       = True
            
            # component of freestream in the propeller plane
            Vz  = V_thrust[:,2,None,None]
            Vz  = np.repeat(Vz, Na,axis=1)
            Vz  = np.repeat(Vz, Nr,axis=2)
            
            if rotation == None:
                print("Propeller rotation not specified. Set to 1 (clockwise when viewed from behind).")
                rotation = 1
            
            # compute resulting radial and tangential velocities in propeller frame
            ut =  ( Vz*np.cos(psi_2d)  ) * rotation
            ur =  (-Vz*np.sin(psi_2d)  )
            ua =  np.zeros_like(ut)
            
        if nonuniform_freestream:
            use_2d_analysis   = True

            # account for upstream influences
            ua = self.axial_velocities_2d
            ut = self.tangential_velocities_2d
            ur = self.radial_velocities_2d
            
        if use_2d_analysis:
            # make everything 2D with shape (ctrl_pts,Na,Nr)
            size   = (ctrl_pts,Na,Nr)
            PSI    = np.ones(size)
            PSIold = np.zeros(size)
            
            # 2-D freestream velocity and omega*r
            V_2d  = V_thrust[:,0,None,None]
            V_2d  = np.repeat(V_2d, Na,axis=1)
            V_2d  = np.repeat(V_2d, Nr,axis=2)
            
            omegar = (np.repeat(np.outer(omega,r)[:,None,:], Na, axis=1))
            
            # total velocities
            Ua     = V_2d + ua         
            
            # 2-D blade pitch and radial distributions
            beta = np.tile(total_blade_pitch,(Na ,1))
            beta = np.repeat(beta[np.newaxis,:, :], ctrl_pts, axis=0)
            r    = np.tile(r,(Na ,1))
            r    = np.repeat(r[np.newaxis,:, :], ctrl_pts, axis=0) 
            
            # 2-D atmospheric properties
            a   = np.tile(np.atleast_2d(a),(1,Nr))
            a   = np.repeat(a[:, np.newaxis,  :], Na, axis=1)  
            nu  = np.tile(np.atleast_2d(nu),(1,Nr))
            nu  = np.repeat(nu[:, np.newaxis,  :], Na, axis=1)    
            rho = np.tile(np.atleast_2d(rho),(1,Nr))
            rho = np.repeat(rho[:, np.newaxis,  :], Na, axis=1)  
            T   = np.tile(np.atleast_2d(T),(1,Nr))
            T   = np.repeat(T[:, np.newaxis,  :], Na, axis=1)              
            
        else:
            # uniform freestream
            ua       = np.zeros_like(V)              
            ut       = np.zeros_like(V)             
            ur       = np.zeros_like(V)
            
            # total velocities
            Ua     = np.outer((V + ua),np.ones_like(r))
            beta   = total_blade_pitch
            
            # Things that will change with iteration
            size   = (ctrl_pts,Nr)
            PSI    = np.ones(size)
            PSIold = np.zeros(size)  
        
        # total velocities
        Ut   = omegar - ut
        U    = np.sqrt(Ua*Ua + Ut*Ut + ur*ur)
        
        # Drela's Theory
        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
            f[f<0.]      = 0.
            piece        = np.exp(-f)
            arccos_piece = np.arccos(piece)
            F            = 2.*arccos_piece/pi # Prandtl's tip factor
            Gamma        = vt*(4.*pi*r/B)*F*(1.+(4.*lamdaw*R/(pi*B*r))*(4.*lamdaw*R/(pi*B*r)))**0.5 
            Re           = (W*c)/nu  
            
            # Compute aerodynamic forces based on specified input airfoil or using a surrogate
            Cl, Cdval = compute_aerodynamic_forces(a_loc, a_geo, cl_sur, cd_sur, ctrl_pts, Nr, Na, Re, Ma, alpha, tc, use_2d_analysis)

            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
        
            # omega = 0, do not run BEMT convergence loop 
            if all(omega[:,0]) == 0. :
                break
            
            # If its really not going to converge
            if np.any(PSI>pi/2) and np.any(dpsi>0.0):
                print("Propeller BEMT did not converge to a solution (Stall)")
                break
        
            ii+=1 
            if ii>10000:
                print("Propeller BEMT did not converge to a solution (Iteration Limit)")
                break
    
        # 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. 

        blade_T_distribution     = rho*(Gamma*(Wt-epsilon*Wa))*deltar 
        blade_Q_distribution     = rho*(Gamma*(Wa+epsilon*Wt)*r)*deltar 
        
        if use_2d_analysis:
            blade_T_distribution_2d = blade_T_distribution
            blade_Q_distribution_2d = blade_Q_distribution
            blade_Gamma_2d = Gamma
            
            # set 1d blade loadings to be the average:
            blade_T_distribution    = np.mean((blade_T_distribution_2d), axis = 1)
            blade_Q_distribution    = np.mean((blade_Q_distribution_2d), axis = 1)  
            
            Va_2d = Wa
            Vt_2d = Wt
            Va_avg = np.average(Wa, axis=1)      # averaged around the azimuth
            Vt_avg = np.average(Wt, axis=1)      # averaged around the azimuth
            
            Va_ind_2d = va
            Vt_ind_2d = vt
            Vt_ind_avg    = np.average(vt, axis=1)
            Va_ind_avg    = np.average(va, axis=1)          
            
        else:
            Va_2d   = np.repeat(Wa.T[ : , np.newaxis , :], Na, axis=1).T
            Vt_2d   = np.repeat(Wt.T[ : , np.newaxis , :], Na, axis=1).T
    
            blade_T_distribution_2d  = np.repeat(blade_T_distribution.T[ np.newaxis,:  , :], Na, axis=0).T 
            blade_Q_distribution_2d  = np.repeat(blade_Q_distribution.T[ np.newaxis,:  , :], Na, axis=0).T 
            blade_Gamma_2d           = np.repeat(Gamma.T[ : , np.newaxis , :], Na, axis=1).T

            Vt_avg                  = Wt
            Va_avg                  = Wa 
            Vt_ind_avg              = vt
            Va_ind_avg              = va            
            Va_ind_2d               = np.repeat(va.T[ : , np.newaxis , :], Na, axis=1).T
            Vt_ind_2d               = np.repeat(vt.T[ : , np.newaxis , :], Na, axis=1).T     
            
        # thrust and torque derivatives on the blade. 
        blade_dT_dr = rho*(Gamma*(Wt-epsilon*Wa))
        blade_dQ_dr = rho*(Gamma*(Wa+epsilon*Wt)*r)     
        
        thrust                  = np.atleast_2d((B * np.sum(blade_T_distribution, axis = 1))).T 
        torque                  = np.atleast_2d((B * np.sum(blade_Q_distribution, axis = 1))).T         
        power                   = omega*torque   
        
        # calculate coefficients 
        D        = 2*R 
        Cq       = torque/(rho_0*(n*n)*(D*D*D*D*D)) 
        Ct       = thrust/(rho_0*(n*n)*(D*D*D*D))
        Cp       = power/(rho_0*(n*n*n)*(D*D*D*D*D))
        etap     = V*thrust/power 

        # prevent things from breaking 
        Cq[Cq<0]                                           = 0.  
        Ct[Ct<0]                                           = 0.  
        Cp[Cp<0]                                           = 0.  
        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]  
        thrust[omega==0.0]                                 = 0.0
        power[omega==0.0]                                  = 0.0
        torque[omega==0.0]                                 = 0.0
        Ct[omega==0.0]                                     = 0.0
        Cp[omega==0.0]                                     = 0.0 
        etap[omega==0.0]                                   = 0.0 
        
        # assign efficiency to network
        conditions.propulsion.etap = etap   
        
        # store data
        self.azimuthal_distribution                   = psi  
        results_conditions                            = Data     
        outputs                                       = results_conditions( 
                    number_radial_stations            = Nr,
                    number_azimuthal_stations         = Na,   
                    disc_radial_distribution          = r_dim_2d,  
                    thrust_angle                      = theta,
                    speed_of_sound                    = conditions.freestream.speed_of_sound,
                    density                           = conditions.freestream.density,
                    velocity                          = Vv, 
                    blade_tangential_induced_velocity = Vt_ind_avg, 
                    blade_axial_induced_velocity      = Va_ind_avg,  
                    blade_tangential_velocity         = Vt_avg, 
                    blade_axial_velocity              = Va_avg,  
                    disc_tangential_induced_velocity  = Vt_ind_2d, 
                    disc_axial_induced_velocity       = Va_ind_2d,  
                    disc_tangential_velocity          = Vt_2d, 
                    disc_axial_velocity               = Va_2d, 
                    drag_coefficient                  = Cd,
                    lift_coefficient                  = Cl,       
                    omega                             = omega,  
                    disc_circulation                  = blade_Gamma_2d, 
                    blade_dT_dr                       = blade_dT_dr,
                    blade_thrust_distribution         = blade_T_distribution, 
                    disc_thrust_distribution          = blade_T_distribution_2d, 
                    thrust_per_blade                  = thrust/B, 
                    thrust_coefficient                = Ct, 
                    disc_azimuthal_distribution       = psi_2d, 
                    blade_dQ_dr                       = blade_dQ_dr,
                    blade_torque_distribution         = blade_Q_distribution, 
                    disc_torque_distribution          = blade_Q_distribution_2d, 
                    torque_per_blade                  = torque/B,   
                    torque_coefficient                = Cq,   
                    power                             = power,
                    power_coefficient                 = Cp,    
                    converged_inflow_ratio            = lamdaw,
                    disc_local_angle_of_attack        = alpha
            ) 
    
        return thrust, torque, power, Cp, outputs , etap
Ejemplo n.º 10
0
    def spin(self, conditions):
        """ Analyzes a propeller given geometry and operating conditions

                 Inputs:
                     hub radius
                     tip radius
                     rotation rate
                     freestream velocity
                     number of blades
                     number of stations
                     chord distribution
                     twist distribution
                     airfoil data

                 Outputs:
                     Power coefficient
                     Thrust coefficient

                 Assumptions:
                     Based on Qprop Theory document

           """

        #Unpack
        B = self.prop_attributes.number_blades
        R = self.prop_attributes.tip_radius
        Rh = self.prop_attributes.hub_radius
        beta = self.prop_attributes.twist_distribution
        c = self.prop_attributes.chord_distribution
        omega1 = self.inputs.omega
        rho = conditions.freestream.density[:, 0, None]
        mu = conditions.freestream.dynamic_viscosity[:, 0, None]
        Vv = conditions.frames.inertial.velocity_vector
        a = conditions.freestream.speed_of_sound[:, 0, None]
        T = conditions.freestream.temperature[:, 0, None]
        theta = self.thrust_angle
        tc = .12  # Thickness to chord

        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)

        # Velocity transformed to the propulsor frame
        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]

        nu = mu / rho
        tol = 1e-6  # Convergence tolerance

        omega = omega1 * 1.0
        omega = np.abs(omega)

        ######
        # Enter airfoil data in a better way, there is currently Re and Ma scaling from DAE51 data
        ######

        #Things that don't change with iteration
        N = len(c)  # Number of stations
        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)
        sigma = np.multiply(B * c, 1. / (2. * pi * r))

        #I make the assumption that externally-induced velocity at the disk is zero
        #This can be easily changed if needed in the future:
        ua = 0.0
        ut = 0.0

        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)

        #Setup a Newton iteration
        psi = np.ones(size)
        psiold = np.zeros(size)
        diff = 1.

        ii = 0
        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

            #if np.any(Ma> 1.0):
            #warn('Propeller blade tips are supersonic.', Warning)

            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

            # Ok, from the airfoil data, given Re, Ma, alpha we need to find Cl
            Cl = 2. * pi * alpha

            # By 90 deg, it's totally stalled.
            #Cl[Cl>Cl1maxp]  = Cl1maxp[Cl>Cl1maxp]
            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 * 85.0 / 180.)) and np.any(dpsi > 0.0):
                print 'broke'
                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]
        power = torque * omega

        D = 2 * R
        Cp = power / (rho * (n * n * n) * (D * D * D * D * D))

        thrust[conditions.propulsion.throttle[:, 0] <= 0.0] = 0.0
        power[conditions.propulsion.throttle[:, 0] <= 0.0] = 0.0

        thrust[omega1 < 0.0] = -thrust[omega1 < 0.0]

        etap = V * thrust / power

        conditions.propulsion.etap = etap

        # calculating Activity Factor (per blade)
        #AF = 100000./16. * np.trapz((c/D) * (r/R)**3, x=r/R)
        AF = (10. / D)**5. * np.trapz(c * r**3, x=r)

        # store data
        results_conditions = Results
        conditions.propulsion.acoustic_outputs = results_conditions(
            number_sections=N,
            r0=r,
            airfoil_chord=c,
            blades_number=B,
            propeller_diameter=D,
            drag_coefficient=Cd,
            lift_coefficient=Cl,
            rpm=omega,
            velocity=V,
            thrust=thrust,
            hp=power,
            blade_activity_factor=AF,
        )

        return thrust, torque, power, Cp
Ejemplo n.º 11
0
def update_orientations(segment):
    """ Updates the orientation of the vehicle throughout the mission for each relevant axis
    
        Assumptions:
        This assumes the vehicle has 3 frames: inertial, body, and wind. There also contains bits for stability axis which are not used. Creates tensors and solves for alpha and beta.
        
        Inputs:
        segment.state.conditions:
            frames.inertial.velocity_vector          [meters/second]
            frames.body.inertial_rotations           [Radians]
        segment.analyses.planet.features.mean_radius [meters]
        state.numerics.time.integrate                [float]
            
        Outputs:
            segment.state.conditions:           
                aerodynamics.angle_of_attack      [Radians]
                aerodynamics.side_slip_angle      [Radians]
                aerodynamics.roll_angle           [Radians]
                frames.body.transform_to_inertial [Radians]
                frames.wind.body_rotations        [Radians]
                frames.wind.transform_to_inertial [Radians]
    

        Properties Used:
        N/A
    """

    # unpack
    conditions = segment.state.conditions
    V_inertial = conditions.frames.inertial.velocity_vector
    body_inertial_rotations = conditions.frames.body.inertial_rotations

    # ------------------------------------------------------------------
    #  Body Frame
    # ------------------------------------------------------------------

    # body frame rotations
    phi = body_inertial_rotations[:, 0, None]
    theta = body_inertial_rotations[:, 1, None]
    psi = body_inertial_rotations[:, 2, None]

    # body frame tranformation matrices
    T_inertial2body = angles_to_dcms(body_inertial_rotations, (2, 1, 0))
    T_body2inertial = orientation_transpose(T_inertial2body)

    # transform inertial velocity to body frame
    V_body = orientation_product(T_inertial2body, V_inertial)

    # project inertial velocity into body x-z plane
    V_stability = V_body * 1.
    V_stability[:, 1] = 0
    V_stability_magnitude = np.sqrt(np.sum(V_stability**2, axis=1))[:, None]

    # calculate angle of attack
    alpha = np.arctan2(V_stability[:, 2], V_stability[:, 0])[:, None]

    # calculate side slip
    beta = np.arctan2(V_body[:, 1], V_stability_magnitude[:, 0])[:, None]

    # pack aerodynamics angles
    conditions.aerodynamics.angle_of_attack[:, 0] = alpha[:, 0]
    conditions.aerodynamics.side_slip_angle[:, 0] = -beta[:, 0]
    conditions.aerodynamics.roll_angle[:, 0] = phi[:, 0]

    # pack transformation tensor
    conditions.frames.body.transform_to_inertial = T_body2inertial

    # ------------------------------------------------------------------
    #  Wind Frame
    # ------------------------------------------------------------------

    # back calculate wind frame rotations
    wind_body_rotations = body_inertial_rotations * 0.
    wind_body_rotations[:, 0] = 0  # no roll in wind frame
    wind_body_rotations[:, 1] = alpha[:, 0]  # theta is angle of attack
    wind_body_rotations[:, 2] = beta[:, 0]  # psi is side slip angle

    # wind frame tranformation matricies
    T_wind2body = angles_to_dcms(wind_body_rotations, (2, 1, 0))
    T_body2wind = orientation_transpose(T_wind2body)
    T_wind2inertial = orientation_product(T_wind2body, T_body2inertial)

    # pack wind rotations
    conditions.frames.wind.body_rotations = wind_body_rotations

    # pack transformation tensor
    conditions.frames.wind.transform_to_inertial = T_wind2inertial

    return
Ejemplo n.º 12
0
    def spin(self, conditions):
        """Analyzes a rotor 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
        
        Leishman, Gordon J. Principles of helicopter aerodynamics
        Cambridge university press, 2006.      

        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.outputs.        
           number_radial_stations            [-]
           number_azimuthal_stations         [-] 
           disc_radial_distribution          [m]
           thrust_angle                      [rad]
           speed_of_sound                    [m/s]
           density                           [kg/m-3]
           velocity                          [m/s]
           disc_tangential_induced_velocity  [m/s]
           disc_axial_induced_velocity       [m/s]
           disc_tangential_velocity          [m/s]
           disc_axial_velocity               [m/s]
           drag_coefficient                  [-]
           lift_coefficient                  [-]
           omega                             [rad/s]
           disc_circulation                  [-] 
           blade_dQ_dR                       [N/m]
           blade_dT_dr                       [N]
           blade_thrust_distribution         [N]
           disc_thrust_distribution          [N]
           thrust_per_blade                  [N]
           thrust_coefficient                [-] 
           azimuthal_distribution            [rad]
           disc_azimuthal_distribution       [rad]
           blade_dQ_dR                       [N]
           blade_dQ_dr                       [Nm]
           blade_torque_distribution         [Nm] 
           disc_torque_distribution          [Nm] 
           torque_per_blade                  [Nm] 
           torque_coefficient                [-] 
           power                             [W]    
           power_coefficient                 [-] 
                                             
        Properties Used:                     
        self.                                
          number_of_blades                   [-]
          tip_radius                         [m]
          hub_radius                         [m]
          twist_distribution                 [radians]
          chord_distribution                 [m]
          mid_chord_alignment                [m] 
          thrust_angle                       [radians]
        """

        #Unpack
        B = self.number_of_blades
        R = self.tip_radius
        Rh = self.hub_radius
        beta_0 = self.twist_distribution
        c = self.chord_distribution
        chi = self.radius_distribution
        omega = self.inputs.omega
        a_geo = self.airfoil_geometry
        a_loc = self.airfoil_polar_stations
        cl_sur = self.airfoil_cl_surrogates
        cd_sur = self.airfoil_cd_surrogates
        tc = self.thickness_to_chord
        ua = self.induced_hover_velocity
        VTOL = self.VTOL_flag
        rho = conditions.freestream.density[:, 0, None]
        mu = conditions.freestream.dynamic_viscosity[:, 0, None]
        Vv = conditions.frames.inertial.velocity_vector
        a = conditions.freestream.speed_of_sound[:, 0, None]
        T = conditions.freestream.temperature[:, 0, None]
        pitch_c = self.pitch_command
        theta = self.thrust_angle
        Na = self.number_azimuthal_stations
        BB = B * B
        BBB = BB * B

        # calculate total blade pitch
        total_blade_pitch = beta_0 + pitch_c

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

        if VTOL:
            V = V_thrust[:, 0, None] + ua
        else:
            V = V_thrust[:, 0, None]
        ut = np.zeros_like(V)

        #Things that don't change with iteration
        Nr = len(c)  # Number of stations radially
        ctrl_pts = len(Vv)

        # set up non dimensional radial distribution
        chi = self.radius_distribution / R

        omega = np.abs(omega)
        r = chi * R  # Radial coordinate
        pi = np.pi
        pi2 = pi * pi
        n = omega / (2. * pi)  # Cycles per second
        nu = mu / rho

        # azimuth distribution
        psi = np.linspace(0, 2 * pi, Na + 1)[:-1]
        psi_2d = np.tile(np.atleast_2d(psi).T, (1, Nr))
        psi_2d = np.repeat(psi_2d[np.newaxis, :, :], ctrl_pts, axis=0)
        azimuth_2d = np.repeat(np.atleast_2d(psi).T[np.newaxis, :, :],
                               Na,
                               axis=0).T

        # 2 dimensiona radial distribution non dimensionalized
        chi_2d = np.tile(chi, (Na, 1))
        chi_2d = np.repeat(chi_2d[np.newaxis, :, :], ctrl_pts, axis=0)

        r_dim_2d = np.tile(r, (Na, 1))
        r_dim_2d = np.repeat(r_dim_2d[np.newaxis, :, :], ctrl_pts, axis=0)

        # Things that will change with iteration
        size = (len(a), Nr)
        omegar = np.outer(omega, r)
        Ua = np.outer((V + ua), np.ones_like(r))
        Ut = omegar - ut
        U = np.sqrt(Ua * Ua + Ut * Ut)
        beta = total_blade_pitch

        # Setup a Newton iteration
        PSI = np.ones(size)
        PSIold = np.zeros(size)
        diff = 1.
        ii = 0
        broke = False
        tol = 1e-6  # Convergence tolerance

        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
            f[f < 0.] = 0.
            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
            Re = (W * c) / nu

            # If rotor airfoils are defined, using airfoil surrogate
            if a_loc != None:
                # Compute blade Cl and Cd distribution from the airfoil data
                Cl = np.zeros((ctrl_pts, Nr))
                Cdval = np.zeros((ctrl_pts, Nr))
                dim_sur = len(cl_sur)
                for jj in range(dim_sur):
                    Cl_af = cl_sur[a_geo[jj]](Re, alpha, grid=False)
                    Cdval_af = cd_sur[a_geo[jj]](Re, alpha, grid=False)
                    locs = np.where(np.array(a_loc) == jj)
                    Cl[:, locs] = Cl_af[:, locs]
                    Cdval[:, locs] = Cdval_af[:, locs]

            else:
                # Estimate Cl max
                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

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

                #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.

            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

            # omega = 0, do not run BEMT convergence loop
            if all(omega[:, 0]) == 0.:
                break

            # If its really not going to converge
            if np.any(PSI > pi / 2) and np.any(dpsi > 0.0):
                print("Rotor BEMT did not converge to a solution")
                break

            ii += 1
            if ii > 10000:
                broke = True
                print(
                    "Rotor BEMT did not converge to a solution (Iteration Limit)"
                )
                break

        # 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])
        deltachi = (chi[1] - chi[0])
        blade_T_distribution = rho * (Gamma * (Wt - epsilon * Wa)) * deltar
        blade_Q_distribution = rho * (Gamma * (Wa + epsilon * Wt) * r) * deltar
        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]
        power = omega * torque
        Va_2d = np.repeat(Wa.T[:, np.newaxis, :], Na, axis=1).T
        Vt_2d = np.repeat(Wt.T[:, np.newaxis, :], Na, axis=1).T
        Va_ind_2d = np.repeat(va.T[:, np.newaxis, :], Na, axis=1).T
        Vt_ind_2d = np.repeat(vt.T[:, np.newaxis, :], Na, axis=1).T
        blade_T_distribution_2d = np.repeat(
            blade_T_distribution.T[np.newaxis, :, :], Na, axis=0).T
        blade_Q_distribution_2d = np.repeat(
            blade_Q_distribution.T[np.newaxis, :, :], Na, axis=0).T

        blade_Gamma_2d = np.repeat(Gamma.T[:, np.newaxis, :], Na, axis=1).T

        # thrust and torque derivatives on the blade.
        blade_dT_dr = rho * (Gamma * (Wt - epsilon * Wa))
        blade_dQ_dr = rho * (Gamma * (Wa + epsilon * Wt) * r)

        Vt_ind_avg = vt
        Va_ind_avg = va
        Vt_avg = Wt
        Va_avg = Wa

        # calculate coefficients
        D = 2 * R
        Cq = torque / (rho * (n * n) * (D * D * D * D * D))
        Ct = thrust / (rho * (n * n) * (D * D * D * D))
        Cp = power / (rho * (n * n * n) * (D * D * D * D * D))
        etap = V * thrust / power  # efficiency

        # prevent things from breaking
        Cq[Cq < 0] = 0.
        Ct[Ct < 0] = 0.
        Cp[Cp < 0] = 0.
        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]
        thrust[omega == 0.0] = 0.0
        power[omega == 0.0] = 0.0
        torque[omega == 0.0] = 0.0
        Ct[omega == 0.0] = 0.0
        Cp[omega == 0.0] = 0.0
        etap[omega == 0.0] = 0.0

        # assign efficiency to network
        conditions.propulsion.etap = etap

        # store data
        self.azimuthal_distribution = psi
        results_conditions = Data
        outputs = results_conditions(
            number_radial_stations=Nr,
            number_azimuthal_stations=Na,
            disc_radial_distribution=r_dim_2d,
            thrust_angle=theta,
            speed_of_sound=conditions.freestream.speed_of_sound,
            density=conditions.freestream.density,
            velocity=Vv,
            blade_tangential_induced_velocity=Vt_ind_avg,
            blade_axial_induced_velocity=Va_ind_avg,
            blade_tangential_velocity=Vt_avg,
            blade_axial_velocity=Va_avg,
            disc_tangential_induced_velocity=Vt_ind_2d,
            disc_axial_induced_velocity=Va_ind_2d,
            disc_tangential_velocity=Vt_2d,
            disc_axial_velocity=Va_2d,
            drag_coefficient=Cd,
            lift_coefficient=Cl,
            omega=omega,
            disc_circulation=blade_Gamma_2d,
            blade_dT_dr=blade_dT_dr,
            blade_thrust_distribution=blade_T_distribution,
            disc_thrust_distribution=blade_T_distribution_2d,
            thrust_per_blade=thrust / B,
            thrust_coefficient=Ct,
            disc_azimuthal_distribution=azimuth_2d,
            blade_dQ_dr=blade_dQ_dr,
            blade_torque_distribution=blade_Q_distribution,
            disc_torque_distribution=blade_Q_distribution_2d,
            torque_per_blade=torque / B,
            torque_coefficient=Cq,
            power=power,
            power_coefficient=Cp,
        )

        return thrust, torque, power, Cp, outputs, etap
Ejemplo n.º 13
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  
Ejemplo n.º 14
0
def update_orientations(segment, state):

    # unpack
    conditions = state.conditions
    V_inertial = conditions.frames.inertial.velocity_vector
    body_inertial_rotations = conditions.frames.body.inertial_rotations

    # ------------------------------------------------------------------
    #  Body Frame
    # ------------------------------------------------------------------

    # body frame rotations
    phi = body_inertial_rotations[:, 0, None]
    theta = body_inertial_rotations[:, 1, None]
    psi = body_inertial_rotations[:, 2, None]

    # body frame tranformation matrices
    T_inertial2body = angles_to_dcms(body_inertial_rotations, (2, 1, 0))
    T_body2inertial = orientation_transpose(T_inertial2body)

    # transform inertial velocity to body frame
    V_body = orientation_product(T_inertial2body, V_inertial)

    # project inertial velocity into body x-z plane
    V_stability = V_body
    V_stability[:, 1] = 0
    V_stability_magnitude = np.sqrt(np.sum(V_stability**2, axis=1))[:, None]
    #V_stability_direction = V_stability / V_stability_magnitude

    # calculate angle of attack
    alpha = np.arctan2(V_stability[:, 2], V_stability[:, 0])[:, None]

    # calculate side slip
    beta = np.arctan2(V_body[:, 1], V_stability_magnitude[:, 0])[:, None]

    # pack aerodynamics angles
    conditions.aerodynamics.angle_of_attack[:, 0] = alpha[:, 0]
    conditions.aerodynamics.side_slip_angle[:, 0] = beta[:, 0]
    conditions.aerodynamics.roll_angle[:, 0] = phi[:, 0]

    # pack transformation tensor
    conditions.frames.body.transform_to_inertial = T_body2inertial

    # ------------------------------------------------------------------
    #  Wind Frame
    # ------------------------------------------------------------------

    # back calculate wind frame rotations
    wind_body_rotations = body_inertial_rotations * 0.
    wind_body_rotations[:, 0] = 0  # no roll in wind frame
    wind_body_rotations[:, 1] = alpha[:, 0]  # theta is angle of attack
    wind_body_rotations[:, 2] = beta[:, 0]  # psi is side slip angle

    # wind frame tranformation matricies
    T_wind2body = angles_to_dcms(wind_body_rotations, (2, 1, 0))
    T_body2wind = orientation_transpose(T_wind2body)
    T_wind2inertial = orientation_product(T_wind2body, T_body2inertial)

    # pack wind rotations
    conditions.frames.wind.body_rotations = wind_body_rotations

    # pack transformation tensor
    conditions.frames.wind.transform_to_inertial = T_wind2inertial

    return
Ejemplo n.º 15
0
    def spin(self, conditions):
        """Analyzes a general rotor 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

        Leishman, Gordon J. Principles of helicopter aerodynamics
        Cambridge university press, 2006.

        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.outputs.
           number_radial_stations            [-]
           number_azimuthal_stations         [-]
           disc_radial_distribution          [m]
           speed_of_sound                    [m/s]
           density                           [kg/m-3]
           velocity                          [m/s]
           disc_tangential_induced_velocity  [m/s]
           disc_axial_induced_velocity       [m/s]
           disc_tangential_velocity          [m/s]
           disc_axial_velocity               [m/s]
           drag_coefficient                  [-]
           lift_coefficient                  [-]
           omega                             [rad/s]
           disc_circulation                  [-]
           blade_dQ_dR                       [N/m]
           blade_dT_dr                       [N]
           blade_thrust_distribution         [N]
           disc_thrust_distribution          [N]
           thrust_per_blade                  [N]
           thrust_coefficient                [-]
           azimuthal_distribution            [rad]
           disc_azimuthal_distribution       [rad]
           blade_dQ_dR                       [N]
           blade_dQ_dr                       [Nm]
           blade_torque_distribution         [Nm]
           disc_torque_distribution          [Nm]
           torque_per_blade                  [Nm]
           torque_coefficient                [-]
           power                             [W]
           power_coefficient                 [-]

        Properties Used:
        self.
          number_of_blades                   [-]
          tip_radius                         [m]
          twist_distribution                 [radians]
          chord_distribution                 [m]
          orientation_euler_angles           [rad, rad, rad]
        """

        # Unpack rotor blade parameters
        B = self.number_of_blades
        R = self.tip_radius
        beta_0 = self.twist_distribution
        c = self.chord_distribution
        sweep = self.sweep_distribution  # quarter chord distance from quarter chord of root airfoil
        r_1d = self.radius_distribution
        tc = self.thickness_to_chord

        # Unpack rotor airfoil data
        a_geo = self.airfoil_geometry
        a_loc = self.airfoil_polar_stations
        cl_sur = self.airfoil_cl_surrogates
        cd_sur = self.airfoil_cd_surrogates

        # Unpack rotor inputs and conditions
        omega = self.inputs.omega
        Na = self.number_azimuthal_stations
        nonuniform_freestream = self.nonuniform_freestream
        use_2d_analysis = self.use_2d_analysis
        wake_method = self.wake_method
        rotation = self.rotation
        pitch_c = self.inputs.pitch_command

        # Check for variable pitch
        if np.any(pitch_c != 0) and not self.variable_pitch:
            print(
                "Warning: pitch commanded for a fixed-pitch rotor. Changing to variable pitch rotor for weights analysis."
            )
            self.variable_pitch = True

        # Unpack freestream conditions
        rho = conditions.freestream.density[:, 0, None]
        mu = conditions.freestream.dynamic_viscosity[:, 0, None]
        a = conditions.freestream.speed_of_sound[:, 0, None]
        T = conditions.freestream.temperature[:, 0, None]
        Vv = conditions.frames.inertial.velocity_vector
        nu = mu / rho
        rho_0 = rho

        # Helpful shorthands
        pi = np.pi

        # Calculate total blade pitch
        total_blade_pitch = beta_0 + pitch_c

        # Velocity in the rotor frame
        T_body2inertial = conditions.frames.body.transform_to_inertial
        T_inertial2body = orientation_transpose(T_body2inertial)
        V_body = orientation_product(T_inertial2body, Vv)
        body2thrust = self.body_to_prop_vel()
        T_body2thrust = orientation_transpose(
            np.ones_like(T_body2inertial[:]) * body2thrust)
        V_thrust = orientation_product(T_body2thrust, V_body)

        # Check and correct for hover
        V = V_thrust[:, 0, None]
        V[V == 0.0] = 1E-6

        # Number of radial stations and segment control points
        Nr = len(c)
        ctrl_pts = len(Vv)

        # Non-dimensional radial distribution and differential radius
        chi = r_1d / R
        diff_r = np.diff(r_1d)
        deltar = np.zeros(len(r_1d))
        deltar[1:-1] = diff_r[0:-1] / 2 + diff_r[1:] / 2
        deltar[0] = diff_r[0] / 2
        deltar[-1] = diff_r[-1] / 2

        # Calculating rotational parameters
        omegar = np.outer(omega, r_1d)
        n = omega / (2. * pi)  # Rotations per second

        # 2 dimensional radial distribution non dimensionalized
        chi_2d = np.tile(chi[:, None], (1, Na))
        chi_2d = np.repeat(chi_2d[None, :, :], ctrl_pts, axis=0)
        r_dim_2d = np.tile(r_1d[:, None], (1, Na))
        r_dim_2d = np.repeat(r_dim_2d[None, :, :], ctrl_pts, axis=0)
        c_2d = np.tile(c[:, None], (1, Na))
        c_2d = np.repeat(c_2d[None, :, :], ctrl_pts, axis=0)

        # Azimuthal distribution of stations
        psi = np.linspace(0, 2 * pi, Na + 1)[:-1]
        psi_2d = np.tile(np.atleast_2d(psi), (Nr, 1))
        psi_2d = np.repeat(psi_2d[None, :, :], ctrl_pts, axis=0)

        # apply blade sweep to azimuthal position
        if np.any(np.array([sweep]) != 0):
            use_2d_analysis = True
            sweep_2d = np.repeat(sweep[:, None], (1, Na))
            sweep_offset_angles = np.tan(sweep_2d / r_dim_2d)
            psi_2d += sweep_offset_angles

        # Starting with uniform freestream
        ua = 0
        ut = 0
        ur = 0

        # Include velocities introduced by rotor incidence angles
        if (np.any(abs(V_thrust[:, 1]) > 1e-3)
                or np.any(abs(V_thrust[:, 2]) > 1e-3)) and use_2d_analysis:

            # y-component of freestream in the propeller cartesian plane
            Vy = V_thrust[:, 1, None, None]
            Vy = np.repeat(Vy, Nr, axis=1)
            Vy = np.repeat(Vy, Na, axis=2)

            # z-component of freestream in the propeller cartesian plane
            Vz = V_thrust[:, 2, None, None]
            Vz = np.repeat(Vz, Nr, axis=1)
            Vz = np.repeat(Vz, Na, axis=2)

            # check for invalid rotation angle
            if (rotation == 1) or (rotation == -1):
                pass
            else:
                print("Invalid rotation direction. Setting to 1.")
                rotation = 1

            # compute resulting radial and tangential velocities in polar frame
            utz = Vz * np.cos(psi_2d * rotation)
            urz = Vz * np.sin(psi_2d * rotation)
            uty = Vy * np.sin(psi_2d * rotation)
            ury = Vy * np.cos(psi_2d * rotation)

            ut += (utz + uty)
            ur += (urz + ury)
            ua += np.zeros_like(ut)

        # Include external velocities introduced by user
        if nonuniform_freestream:
            use_2d_analysis = True

            # include additional influences specified at rotor sections, shape=(ctrl_pts,Nr,Na)
            ua += self.axial_velocities_2d
            ut += self.tangential_velocities_2d
            ur += self.radial_velocities_2d

        if use_2d_analysis:
            # make everything 2D with shape (ctrl_pts,Nr,Na)
            size = (ctrl_pts, Nr, Na)
            PSI = np.ones(size)
            PSIold = np.zeros(size)

            # 2-D freestream velocity and omega*r
            V_2d = V_thrust[:, 0, None, None]
            V_2d = np.repeat(V_2d, Na, axis=2)
            V_2d = np.repeat(V_2d, Nr, axis=1)
            omegar = (np.repeat(np.outer(omega, r_1d)[:, :, None], Na, axis=2))

            # total velocities
            Ua = V_2d + ua

            # 2-D blade pitch and radial distributions
            if np.size(pitch_c) > 1:
                # control variable is the blade pitch, repeat around azimuth
                beta = np.repeat(total_blade_pitch[:, :, None], Na, axis=2)
            else:
                beta = np.tile(total_blade_pitch[None, :, None],
                               (ctrl_pts, 1, Na))

            r = np.tile(r_1d[None, :, None], (ctrl_pts, 1, Na))
            c = np.tile(c[None, :, None], (ctrl_pts, 1, Na))
            deltar = np.tile(deltar[None, :, None], (ctrl_pts, 1, Na))

            # 2-D atmospheric properties
            a = np.tile(np.atleast_2d(a), (1, Nr))
            a = np.repeat(a[:, :, None], Na, axis=2)
            nu = np.tile(np.atleast_2d(nu), (1, Nr))
            nu = np.repeat(nu[:, :, None], Na, axis=2)
            rho = np.tile(np.atleast_2d(rho), (1, Nr))
            rho = np.repeat(rho[:, :, None], Na, axis=2)
            T = np.tile(np.atleast_2d(T), (1, Nr))
            T = np.repeat(T[:, :, None], Na, axis=2)

        else:
            # total velocities
            r = r_1d
            Ua = np.outer((V + ua), np.ones_like(r))
            beta = total_blade_pitch

            # Things that will change with iteration
            size = (ctrl_pts, Nr)
            PSI = np.ones(size)
            PSIold = np.zeros(size)

        # Total velocities
        Ut = omegar - ut
        U = np.sqrt(Ua * Ua + Ut * Ut + ur * ur)

        if wake_method == 'momentum':
            # Setup a Newton iteration
            diff = 1.
            tol = 1e-6  # Convergence tolerance
            ii = 0

            # BEMT Iteration
            while (diff > tol):
                # compute velocities
                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

                # compute blade airfoil forces and properties
                Cl, Cdval, alpha, Ma, W = compute_airfoil_aerodynamics(
                    beta, c, r, R, B, Wa, Wt, a, nu, a_loc, a_geo, cl_sur,
                    cd_sur, ctrl_pts, Nr, Na, tc, use_2d_analysis)

                # compute inflow velocity and tip loss factor
                lamdaw, F, piece = compute_inflow_and_tip_loss(r, R, Wa, Wt, B)

                # compute Newton residual on circulation
                Gamma = vt * (4. * pi * r / B) * F * (1. + (4. * lamdaw * R /
                                                            (pi * B * r)) *
                                                      (4. * lamdaw * R /
                                                       (pi * B * r)))**0.5
                Rsquiggly = Gamma - 0.5 * W * c * Cl

                # use analytical derivative to get dR_dpsi
                dR_dpsi = compute_dR_dpsi(B, beta, r, R, Wt, Wa, U, Ut, Ua,
                                          cos_psi, sin_psi, piece)

                # update inflow angle
                dpsi = -Rsquiggly / dR_dpsi
                PSI = PSI + dpsi
                diff = np.max(abs(PSIold - PSI))
                PSIold = PSI

                # If omega = 0, do not run BEMT convergence loop
                if all(omega[:, 0]) == 0.:
                    break

                # If its really not going to converge
                if np.any(PSI > pi / 2) and np.any(dpsi > 0.0):
                    print("Rotor BEMT did not converge to a solution (Stall)")
                    break

                ii += 1
                if ii > 10000:
                    print(
                        "Rotor BEMT did not converge to a solution (Iteration Limit)"
                    )
                    break

        elif wake_method == "helical_fixed_wake":

            # converge on va for a semi-prescribed wake method
            ii, ii_max = 0, 50
            va_diff, tol = 1, 1e-3
            while va_diff > tol:

                # compute axial wake-induced velocity (a byproduct of the circulation distribution which is an input to the wake geometry)
                va, vt = compute_HFW_inflow_velocities(self)

                # compute new blade velocities
                Wa = va + Ua
                Wt = Ut - vt

                # Compute aerodynamic forces based on specified input airfoil or surrogate
                Cl, Cdval, alpha, Ma, W = compute_airfoil_aerodynamics(
                    beta, c, r, R, B, Wa, Wt, a, nu, a_loc, a_geo, cl_sur,
                    cd_sur, ctrl_pts, Nr, Na, tc, use_2d_analysis)

                lamdaw, F, _ = compute_inflow_and_tip_loss(r, R, Wa, Wt, B)

                va_diff = np.max(
                    abs(va - self.outputs.disc_axial_induced_velocity))
                # compute HFW circulation at the blade
                Gamma = 0.5 * W * c * Cl

                # update the axial disc velocity based on new va from HFW
                self.outputs.disc_axial_induced_velocity = self.outputs.disc_axial_induced_velocity + 0.5 * (
                    va - self.outputs.disc_axial_induced_velocity)

                ii += 1
                if ii > ii_max and va_diff > tol:
                    print(
                        "Semi-prescribed helical wake did not converge on axial inflow used for wake shape."
                    )

        # tip loss correction for velocities, since tip loss correction is only applied to loads in prior BEMT iteration
        va = F * va
        vt = F * vt
        lamdaw = r * (va + Ua) / (R * (Ut - vt))

        # 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.

        # thrust and torque and their derivatives on the blade.
        blade_T_distribution = rho * (Gamma * (Wt - epsilon * Wa)) * deltar
        blade_Q_distribution = rho * (Gamma * (Wa + epsilon * Wt) * r) * deltar
        blade_dT_dr = rho * (Gamma * (Wt - epsilon * Wa))
        blade_dQ_dr = rho * (Gamma * (Wa + epsilon * Wt) * r)

        if use_2d_analysis:
            blade_T_distribution_2d = blade_T_distribution
            blade_Q_distribution_2d = blade_Q_distribution
            blade_dT_dr_2d = blade_dT_dr
            blade_dQ_dr_2d = blade_dQ_dr
            blade_Gamma_2d = Gamma
            alpha_2d = alpha

            Va_2d = Wa
            Vt_2d = Wt
            Va_avg = np.average(Wa, axis=2)  # averaged around the azimuth
            Vt_avg = np.average(Wt, axis=2)  # averaged around the azimuth

            Va_ind_2d = va
            Vt_ind_2d = vt
            Vt_ind_avg = np.average(vt, axis=2)
            Va_ind_avg = np.average(va, axis=2)

            # set 1d blade loadings to be the average:
            blade_T_distribution = np.mean((blade_T_distribution_2d), axis=2)
            blade_Q_distribution = np.mean((blade_Q_distribution_2d), axis=2)
            blade_dT_dr = np.mean((blade_dT_dr_2d), axis=2)
            blade_dQ_dr = np.mean((blade_dQ_dr_2d), axis=2)

            # compute the hub force / rotor drag distribution along the blade
            dL_2d = 0.5 * rho * c_2d * Cd * omegar**2 * deltar
            dD_2d = 0.5 * rho * c_2d * Cl * omegar**2 * deltar

            rotor_drag_distribution = np.mean(dL_2d * np.sin(psi_2d) +
                                              dD_2d * np.cos(psi_2d),
                                              axis=2)

        else:
            Va_2d = np.repeat(Wa[:, :, None], Na, axis=2)
            Vt_2d = np.repeat(Wt[:, :, None], Na, axis=2)

            blade_T_distribution_2d = np.repeat(blade_T_distribution[:, :,
                                                                     None],
                                                Na,
                                                axis=2)
            blade_Q_distribution_2d = np.repeat(blade_Q_distribution[:, :,
                                                                     None],
                                                Na,
                                                axis=2)
            blade_dT_dr_2d = np.repeat(blade_dT_dr[:, :, None], Na, axis=2)
            blade_dQ_dr_2d = np.repeat(blade_dQ_dr[:, :, None], Na, axis=2)
            blade_Gamma_2d = np.repeat(Gamma[:, :, None], Na, axis=2)
            alpha_2d = np.repeat(alpha[:, :, None], Na, axis=2)

            Vt_avg = Wt
            Va_avg = Wa
            Vt_ind_avg = vt
            Va_ind_avg = va
            Va_ind_2d = np.repeat(va[:, :, None], Na, axis=2)
            Vt_ind_2d = np.repeat(vt[:, :, None], Na, axis=2)

            # compute the hub force / rotor drag distribution along the blade
            dL = 0.5 * rho * c * Cd * omegar**2 * deltar
            dL_2d = np.repeat(dL[:, :, None], Na, axis=2)
            dD = 0.5 * rho * c * Cl * omegar**2 * deltar
            dD_2d = np.repeat(dD[:, :, None], Na, axis=2)

            rotor_drag_distribution = np.mean(dL_2d * np.sin(psi_2d) +
                                              dD_2d * np.cos(psi_2d),
                                              axis=2)

        # forces
        thrust = np.atleast_2d((B * np.sum(blade_T_distribution, axis=1))).T
        torque = np.atleast_2d((B * np.sum(blade_Q_distribution, axis=1))).T
        rotor_drag = np.atleast_2d(
            (B * np.sum(rotor_drag_distribution, axis=1))).T
        power = omega * torque

        # calculate coefficients
        D = 2 * R
        Cq = torque / (rho_0 * (n * n) * (D * D * D * D * D))
        Ct = thrust / (rho_0 * (n * n) * (D * D * D * D))
        Cp = power / (rho_0 * (n * n * n) * (D * D * D * D * D))
        Crd = rotor_drag / (rho_0 * (n * n) * (D * D * D * D))
        etap = V * thrust / power

        # prevent things from breaking
        Cq[Cq < 0] = 0.
        Ct[Ct < 0] = 0.
        Cp[Cp < 0] = 0.
        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
        rotor_drag[conditions.propulsion.throttle[:, 0] <= 0.0] = 0.0
        thrust[omega < 0.0] = -thrust[omega < 0.0]
        thrust[omega == 0.0] = 0.0
        power[omega == 0.0] = 0.0
        torque[omega == 0.0] = 0.0
        rotor_drag[omega == 0.0] = 0.0
        Ct[omega == 0.0] = 0.0
        Cp[omega == 0.0] = 0.0
        etap[omega == 0.0] = 0.0

        # Make the thrust a 3D vector
        thrust_prop_frame = np.zeros((ctrl_pts, 3))
        thrust_prop_frame[:, 0] = thrust[:, 0]
        thrust_vector = orientation_product(
            orientation_transpose(T_body2thrust), thrust_prop_frame)

        # Assign efficiency to network
        conditions.propulsion.etap = etap

        # Store data
        self.azimuthal_distribution = psi
        results_conditions = Data
        outputs = results_conditions(
            number_radial_stations=Nr,
            number_azimuthal_stations=Na,
            disc_radial_distribution=r_dim_2d,
            speed_of_sound=conditions.freestream.speed_of_sound,
            density=conditions.freestream.density,
            velocity=Vv,
            blade_tangential_induced_velocity=Vt_ind_avg,
            blade_axial_induced_velocity=Va_ind_avg,
            blade_tangential_velocity=Vt_avg,
            blade_axial_velocity=Va_avg,
            disc_tangential_induced_velocity=Vt_ind_2d,
            disc_axial_induced_velocity=Va_ind_2d,
            disc_tangential_velocity=Vt_2d,
            disc_axial_velocity=Va_2d,
            drag_coefficient=Cd,
            lift_coefficient=Cl,
            omega=omega,
            disc_circulation=blade_Gamma_2d,
            blade_dT_dr=blade_dT_dr,
            disc_dT_dr=blade_dT_dr_2d,
            blade_thrust_distribution=blade_T_distribution,
            disc_thrust_distribution=blade_T_distribution_2d,
            disc_effective_angle_of_attack=alpha_2d,
            thrust_per_blade=thrust / B,
            thrust_coefficient=Ct,
            disc_azimuthal_distribution=psi_2d,
            blade_dQ_dr=blade_dQ_dr,
            disc_dQ_dr=blade_dQ_dr_2d,
            blade_torque_distribution=blade_Q_distribution,
            disc_torque_distribution=blade_Q_distribution_2d,
            torque_per_blade=torque / B,
            torque_coefficient=Cq,
            power=power,
            power_coefficient=Cp,
            converged_inflow_ratio=lamdaw,
            propeller_efficiency=etap,
            blade_H_distribution=rotor_drag_distribution,
            rotor_drag=rotor_drag,
            rotor_drag_coefficient=Crd,
        )

        return thrust_vector, torque, power, Cp, outputs, etap
Ejemplo n.º 16
0
def update_orientations(segment,state):
    
    """ Updates the orientation of the vehicle throughout the mission for each relevant axis
    
        Assumptions:
        This assumes the vehicle has 3 frames: inertial, body, and wind. There also contains bits for stability axis which are not used. Creates tensors and solves for alpha and beta.
        
        Inputs:
        state.conditions:
            frames.inertial.velocity_vector          [meters/second]
            frames.body.inertial_rotations           [Radians]
        segment.analyses.planet.features.mean_radius [meters]
        state.numerics.time.integrate                [float]
            
        Outputs:
            state.conditions:           
                aerodynamics.angle_of_attack      [Radians]
                aerodynamics.side_slip_angle      [Radians]
                aerodynamics.roll_angle           [Radians]
                frames.body.transform_to_inertial [Radians]
                frames.wind.body_rotations        [Radians]
                frames.wind.transform_to_inertial [Radians]
    

        Properties Used:
        N/A
    """

    # unpack
    conditions = state.conditions
    V_inertial = conditions.frames.inertial.velocity_vector
    body_inertial_rotations = conditions.frames.body.inertial_rotations

    # ------------------------------------------------------------------
    #  Body Frame
    # ------------------------------------------------------------------

    # body frame rotations
    phi   = body_inertial_rotations[:,0,None]
    theta = body_inertial_rotations[:,1,None]
    psi   = body_inertial_rotations[:,2,None]

    # body frame tranformation matrices
    T_inertial2body = angles_to_dcms(body_inertial_rotations,(2,1,0))
    T_body2inertial = orientation_transpose(T_inertial2body)

    # transform inertial velocity to body frame
    V_body = orientation_product(T_inertial2body,V_inertial)

    # project inertial velocity into body x-z plane
    V_stability = V_body
    V_stability[:,1] = 0
    V_stability_magnitude = np.sqrt( np.sum(V_stability**2,axis=1) )[:,None]
    #V_stability_direction = V_stability / V_stability_magnitude

    # calculate angle of attack
    alpha = np.arctan2(V_stability[:,2],V_stability[:,0])[:,None]

    # calculate side slip
    beta = np.arctan2(V_body[:,1],V_stability_magnitude[:,0])[:,None]

    # pack aerodynamics angles
    conditions.aerodynamics.angle_of_attack[:,0] = alpha[:,0]
    conditions.aerodynamics.side_slip_angle[:,0] = beta[:,0]
    conditions.aerodynamics.roll_angle[:,0]      = phi[:,0]

    # pack transformation tensor
    conditions.frames.body.transform_to_inertial = T_body2inertial


    # ------------------------------------------------------------------
    #  Wind Frame
    # ------------------------------------------------------------------

    # back calculate wind frame rotations
    wind_body_rotations = body_inertial_rotations * 0.
    wind_body_rotations[:,0] = 0          # no roll in wind frame
    wind_body_rotations[:,1] = alpha[:,0] # theta is angle of attack
    wind_body_rotations[:,2] = beta[:,0]  # psi is side slip angle

    # wind frame tranformation matricies
    T_wind2body = angles_to_dcms(wind_body_rotations,(2,1,0))
    T_body2wind = orientation_transpose(T_wind2body)
    T_wind2inertial = orientation_product(T_wind2body,T_body2inertial)

    # pack wind rotations
    conditions.frames.wind.body_rotations = wind_body_rotations

    # pack transformation tensor
    conditions.frames.wind.transform_to_inertial = T_wind2inertial
    
    return
Ejemplo n.º 17
0
def plot_vehicle(vehicle,
                 save_figure=False,
                 plot_control_points=True,
                 save_filename="Vehicle_Geometry"):
    """This plots vortex lattice panels created when Fidelity Zero  Aerodynamics 
    Routine is initialized

    Assumptions:
    None

    Source:
    None

    Inputs:
    vehicle 

    Outputs: 
    Plots

    Properties Used:
    N/A	
    """
    # unpack vortex distribution
    VD = vehicle.vortex_distribution

    face_color = 'grey'
    edge_color = 'grey'
    alpha_val = 0.5

    # initalize figure
    fig = plt.figure(save_filename)
    fig.set_size_inches(8, 8)
    axes = Axes3D(fig)
    axes.view_init(elev=30, azim=210)

    n_cp = VD.n_cp
    for i in range(n_cp):
        X = [VD.XA1[i], VD.XB1[i], VD.XB2[i], VD.XA2[i]]
        Y = [VD.YA1[i], VD.YB1[i], VD.YB2[i], VD.YA2[i]]
        Z = [VD.ZA1[i], VD.ZB1[i], VD.ZB2[i], VD.ZA2[i]]
        verts = [list(zip(X, Y, Z))]
        collection = Poly3DCollection(verts)
        collection.set_facecolor(face_color)
        collection.set_edgecolor(edge_color)
        collection.set_alpha(alpha_val)
        axes.add_collection3d(collection)
        max_range = np.array([
            VD.X.max() - VD.X.min(),
            VD.Y.max() - VD.Y.min(),
            VD.Z.max() - VD.Z.min()
        ]).max() / 2.0
        mid_x = (VD.X.max() + VD.X.min()) * 0.5
        mid_y = (VD.Y.max() + VD.Y.min()) * 0.5
        mid_z = (VD.Z.max() + VD.Z.min()) * 0.5
        axes.set_xlim(mid_x - max_range, mid_x + max_range)
        axes.set_ylim(mid_y - max_range, mid_y + max_range)
        axes.set_zlim(mid_z - max_range, mid_z + max_range)

    if plot_control_points:
        axes.scatter(VD.XC, VD.YC, VD.ZC, c='r', marker='o')

    if 'Wake' in VD:
        face_color = 'white'
        edge_color = 'blue'
        alpha = 0.5

        num_prop = len(VD.Wake.XA1[:, 0, 0, 0])
        nts = len(VD.Wake.XA1[0, :, 0, 0])
        num_B = len(VD.Wake.XA1[0, 0, :, 0])
        dim_R = len(VD.Wake.XA1[0, 0, 0, :])
        for p_idx in range(num_prop):
            for t_idx in range(nts):
                for B_idx in range(num_B):
                    for loc in range(dim_R):
                        X = [
                            VD.Wake.XA1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.XB1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.XB2[p_idx, t_idx, B_idx,
                                        loc], VD.Wake.XA2[p_idx, t_idx, B_idx,
                                                          loc]
                        ]
                        Y = [
                            VD.Wake.YA1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.YB1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.YB2[p_idx, t_idx, B_idx,
                                        loc], VD.Wake.YA2[p_idx, t_idx, B_idx,
                                                          loc]
                        ]
                        Z = [
                            VD.Wake.ZA1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.ZB1[p_idx, t_idx, B_idx, loc],
                            VD.Wake.ZB2[p_idx, t_idx, B_idx,
                                        loc], VD.Wake.ZA2[p_idx, t_idx, B_idx,
                                                          loc]
                        ]
                        verts = [list(zip(X, Y, Z))]
                        collection = Poly3DCollection(verts)
                        collection.set_facecolor(face_color)
                        collection.set_edgecolor(edge_color)
                        collection.set_alpha(alpha)
                        axes.add_collection3d(collection)

    if np.all(VD.FUS_SURF_PTS) != None:
        face_color = 'grey'
        edge_color = 'black'
        alpha = 1

        num_fus_segs = len(VD.FUS_SURF_PTS[:, 0, 0])
        tesselation = len(VD.FUS_SURF_PTS[0, :, 0])
        for i_seg in range(num_fus_segs - 1):
            for i_tes in range(tesselation - 1):
                X = [
                    VD.FUS_SURF_PTS[i_seg, i_tes,
                                    0], VD.FUS_SURF_PTS[i_seg, i_tes + 1, 0],
                    VD.FUS_SURF_PTS[i_seg + 1, i_tes + 1,
                                    0], VD.FUS_SURF_PTS[i_seg + 1, i_tes, 0]
                ]
                Y = [
                    VD.FUS_SURF_PTS[i_seg, i_tes,
                                    1], VD.FUS_SURF_PTS[i_seg, i_tes + 1, 1],
                    VD.FUS_SURF_PTS[i_seg + 1, i_tes + 1,
                                    1], VD.FUS_SURF_PTS[i_seg + 1, i_tes, 1]
                ]
                Z = [
                    VD.FUS_SURF_PTS[i_seg, i_tes,
                                    2], VD.FUS_SURF_PTS[i_seg, i_tes + 1, 2],
                    VD.FUS_SURF_PTS[i_seg + 1, i_tes + 1,
                                    2], VD.FUS_SURF_PTS[i_seg + 1, i_tes, 2]
                ]
                verts = [list(zip(X, Y, Z))]
                collection = Poly3DCollection(verts)
                collection.set_facecolor(face_color)
                collection.set_edgecolor(edge_color)
                collection.set_alpha(alpha)
                axes.add_collection3d(collection)

    for propulsor in vehicle.propulsors:
        if ('propeller' in propulsor.keys()) or ('rotor' in propulsor.keys()):
            try:
                prop = propulsor.propeller
            except:
                prop = propulsor.rotor

            # ------------------------------------------------------------------------
            # Generate Propeller Geoemtry
            # ------------------------------------------------------------------------

            # unpack
            Rt = prop.tip_radius
            Rh = prop.hub_radius
            num_B = prop.number_blades
            a_sec = prop.airfoil_geometry
            a_secl = prop.airfoil_polar_stations
            beta = prop.twist_distribution
            b = prop.chord_distribution
            r = prop.radius_distribution
            MCA = prop.mid_chord_aligment
            t = prop.max_thickness_distribution
            ta = -propulsor.thrust_angle

            n_points = 10
            af_pts = (2 * n_points) - 1
            dim = len(b)
            num_props = len(prop.origin)
            theta = np.linspace(0, 2 * np.pi, num_B + 1)[:-1]

            # create empty arrays for storing geometry
            G = Data()
            G.XA1 = np.zeros((dim - 1, af_pts))
            G.YA1 = np.zeros_like(G.XA1)
            G.ZA1 = np.zeros_like(G.XA1)
            G.XA2 = np.zeros_like(G.XA1)
            G.YA2 = np.zeros_like(G.XA1)
            G.ZA2 = np.zeros_like(G.XA1)
            G.XB1 = np.zeros_like(G.XA1)
            G.YB1 = np.zeros_like(G.XA1)
            G.ZB1 = np.zeros_like(G.XA1)
            G.XB2 = np.zeros_like(G.XA1)
            G.YB2 = np.zeros_like(G.XA1)
            G.ZB2 = np.zeros_like(G.XA1)

            for n_p in range(num_props):
                rot = prop.rotation[n_p]
                a_o = 0
                flip_1 = (np.pi / 2)
                flip_2 = (np.pi / 2)

                for i in range(num_B):
                    # get airfoil coordinate geometry
                    airfoil_data = import_airfoil_geometry(a_sec,
                                                           npoints=n_points)

                    # store points of airfoil in similar format as Vortex Points (i.e. in vertices)
                    for j in range(dim -
                                   1):  # loop through each radial station
                        # --------------------------------------------
                        # INNER SECTION
                        # --------------------------------------------
                        # INNER SECTION POINTS
                        ixpts = airfoil_data.x_coordinates[a_secl[j]]
                        izpts = airfoil_data.y_coordinates[a_secl[j]]

                        iba_max_t = airfoil_data.thickness_to_chord[a_secl[j]]
                        iba_xp = rot * (-MCA[j] + ixpts * b[j]
                                        )  # x coord of airfoil
                        iba_yp = r[j] * np.ones_like(iba_xp)  # radial location
                        iba_zp = izpts * (t[j] / iba_max_t
                                          )  # former airfoil y coord

                        iba_matrix = np.zeros((len(iba_zp), 3))
                        iba_matrix[:, 0] = iba_xp
                        iba_matrix[:, 1] = iba_yp
                        iba_matrix[:, 2] = iba_zp

                        # ROTATION MATRICES FOR INNER SECTION
                        # rotation about y axis to create twist and position blade upright
                        iba_trans_1 = [[
                            np.cos(rot * flip_1 - rot * beta[j]), 0,
                            -np.sin(rot * flip_1 - rot * beta[j])
                        ], [0, 1, 0],
                                       [
                                           np.sin(rot * flip_1 -
                                                  rot * beta[j]), 0,
                                           np.cos(rot * flip_1 - rot * beta[j])
                                       ]]

                        # rotation about x axis to create azimuth locations
                        iba_trans_2 = [
                            [1, 0, 0],
                            [
                                0,
                                np.cos(theta[i] + rot * a_o + flip_2),
                                np.sin(theta[i] + rot * a_o + flip_2)
                            ],
                            [
                                0,
                                np.sin(theta[i] + rot * a_o + flip_2),
                                np.cos(theta[i] + rot * a_o + flip_2)
                            ]
                        ]

                        # roation about y to orient propeller/rotor to thrust angle
                        iba_trans_3 = [[np.cos(ta), 0, -np.sin(ta)], [0, 1, 0],
                                       [np.sin(ta), 0,
                                        np.cos(ta)]]

                        iba_trans = np.matmul(
                            iba_trans_3, np.matmul(iba_trans_2, iba_trans_1))
                        irot_mat = np.repeat(iba_trans[np.newaxis, :, :],
                                             len(iba_yp),
                                             axis=0)

                        # --------------------------------------------
                        # OUTER SECTION
                        # --------------------------------------------
                        # OUTER SECTION POINTS
                        oxpts = airfoil_data.x_coordinates[a_secl[j + 1]]
                        ozpts = airfoil_data.y_coordinates[a_secl[j + 1]]

                        oba_max_t = airfoil_data.thickness_to_chord[a_secl[j +
                                                                           1]]
                        oba_xp = -MCA[j + 1] + oxpts * b[
                            j + 1]  # x coord of airfoil
                        oba_yp = r[j + 1] * np.ones_like(
                            oba_xp)  # radial location
                        oba_zp = ozpts * (t[j + 1] / oba_max_t
                                          )  # former airfoil y coord

                        oba_matrix = np.zeros((len(oba_zp), 3))
                        oba_matrix[:, 0] = oba_xp
                        oba_matrix[:, 1] = oba_yp
                        oba_matrix[:, 2] = oba_zp

                        # ROTATION MATRICES FOR OUTER SECTION
                        # rotation about y axis to create twist and position blade upright
                        oba_trans_1 = [
                            [
                                np.cos(rot * flip_1 - rot * beta[j + 1]), 0,
                                -np.sin(rot * flip_1 - rot * beta[j + 1])
                            ], [0, 1, 0],
                            [
                                np.sin(rot * flip_1 - rot * beta[j + 1]), 0,
                                np.cos(rot * flip_1 - rot * beta[j + 1])
                            ]
                        ]

                        # rotation about x axis to create azimuth locations
                        oba_trans_2 = [
                            [1, 0, 0],
                            [
                                0,
                                np.cos(theta[i] + rot * a_o + flip_2),
                                np.sin(theta[i] + rot * a_o + flip_2)
                            ],
                            [
                                0,
                                np.sin(theta[i] + rot * a_o + flip_2),
                                np.cos(theta[i] + rot * a_o + flip_2)
                            ]
                        ]

                        # roation about y to orient propeller/rotor to thrust angle
                        oba_trans_3 = [[np.cos(ta), 0, -np.sin(ta)], [0, 1, 0],
                                       [np.sin(ta), 0,
                                        np.cos(ta)]]

                        oba_trans = np.matmul(
                            oba_trans_3, np.matmul(oba_trans_2, oba_trans_1))
                        orot_mat = np.repeat(oba_trans[np.newaxis, :, :],
                                             len(oba_yp),
                                             axis=0)

                        # ---------------------------------------------------------------------------------------------
                        # ROTATE POINTS
                        iba_mat = orientation_product(irot_mat, iba_matrix)
                        oba_mat = orientation_product(orot_mat, oba_matrix)

                        # ---------------------------------------------------------------------------------------------
                        # store points
                        G.XA1[j, :] = iba_mat[:-1, 0] + prop.origin[n_p][0]
                        G.YA1[j, :] = iba_mat[:-1, 1] + prop.origin[n_p][1]
                        G.ZA1[j, :] = iba_mat[:-1, 2] + prop.origin[n_p][2]
                        G.XA2[j, :] = iba_mat[1:, 0] + prop.origin[n_p][0]
                        G.YA2[j, :] = iba_mat[1:, 1] + prop.origin[n_p][1]
                        G.ZA2[j, :] = iba_mat[1:, 2] + prop.origin[n_p][2]

                        G.XB1[j, :] = oba_mat[:-1, 0] + prop.origin[n_p][0]
                        G.YB1[j, :] = oba_mat[:-1, 1] + prop.origin[n_p][1]
                        G.ZB1[j, :] = oba_mat[:-1, 2] + prop.origin[n_p][2]
                        G.XB2[j, :] = oba_mat[1:, 0] + prop.origin[n_p][0]
                        G.YB2[j, :] = oba_mat[1:, 1] + prop.origin[n_p][1]
                        G.ZB2[j, :] = oba_mat[1:, 2] + prop.origin[n_p][2]

                    # ------------------------------------------------------------------------
                    # Plot Propeller Blade
                    # ------------------------------------------------------------------------
                    prop_face_color = 'red'
                    prop_edge_color = 'red'
                    prop_alpha = 1
                    for sec in range(dim - 1):
                        for loc in range(af_pts):
                            X = [
                                G.XA1[sec, loc], G.XB1[sec, loc],
                                G.XB2[sec, loc], G.XA2[sec, loc]
                            ]
                            Y = [
                                G.YA1[sec, loc], G.YB1[sec, loc],
                                G.YB2[sec, loc], G.YA2[sec, loc]
                            ]
                            Z = [
                                G.ZA1[sec, loc], G.ZB1[sec, loc],
                                G.ZB2[sec, loc], G.ZA2[sec, loc]
                            ]
                            prop_verts = [list(zip(X, Y, Z))]
                            prop_collection = Poly3DCollection(prop_verts)
                            prop_collection.set_facecolor(prop_face_color)
                            prop_collection.set_edgecolor(prop_edge_color)
                            prop_collection.set_alpha(prop_alpha)
                            axes.add_collection3d(prop_collection)

    plt.axis('off')
    plt.grid(None)

    return
Ejemplo n.º 18
0
def update_orientations(segment,state):

    # unpack
    conditions = state.conditions
    V_inertial = conditions.frames.inertial.velocity_vector
    body_inertial_rotations = conditions.frames.body.inertial_rotations

    # ------------------------------------------------------------------
    #  Body Frame
    # ------------------------------------------------------------------

    # body frame rotations
    phi   = body_inertial_rotations[:,0,None]
    theta = body_inertial_rotations[:,1,None]
    psi   = body_inertial_rotations[:,2,None]

    # body frame tranformation matrices
    T_inertial2body = angles_to_dcms(body_inertial_rotations,(2,1,0))
    T_body2inertial = orientation_transpose(T_inertial2body)

    # transform inertial velocity to body frame
    V_body = orientation_product(T_inertial2body,V_inertial)

    # project inertial velocity into body x-z plane
    V_stability = V_body
    V_stability[:,1] = 0
    V_stability_magnitude = np.sqrt( np.sum(V_stability**2,axis=1) )[:,None]
    #V_stability_direction = V_stability / V_stability_magnitude

    # calculate angle of attack
    alpha = np.arctan2(V_stability[:,2],V_stability[:,0])[:,None]

    # calculate side slip
    beta = np.arctan2(V_body[:,1],V_stability_magnitude[:,0])[:,None]

    # pack aerodynamics angles
    conditions.aerodynamics.angle_of_attack[:,0] = alpha[:,0]
    conditions.aerodynamics.side_slip_angle[:,0] = beta[:,0]
    conditions.aerodynamics.roll_angle[:,0]      = phi[:,0]

    # pack transformation tensor
    conditions.frames.body.transform_to_inertial = T_body2inertial


    # ------------------------------------------------------------------
    #  Wind Frame
    # ------------------------------------------------------------------

    # back calculate wind frame rotations
    wind_body_rotations = body_inertial_rotations * 0.
    wind_body_rotations[:,0] = 0          # no roll in wind frame
    wind_body_rotations[:,1] = alpha[:,0] # theta is angle of attack
    wind_body_rotations[:,2] = beta[:,0]  # psi is side slip angle

    # wind frame tranformation matricies
    T_wind2body = angles_to_dcms(wind_body_rotations,(2,1,0))
    T_body2wind = orientation_transpose(T_wind2body)
    T_wind2inertial = orientation_product(T_wind2body,T_body2inertial)

    # pack wind rotations
    conditions.frames.wind.body_rotations = wind_body_rotations

    # pack transformation tensor
    conditions.frames.wind.transform_to_inertial = T_wind2inertial
    
    return
def compute_propeller_nonuniform_freestream(prop, upstream_wake, conditions):
    """ Computes the inflow velocities in the frame of the rotating propeller
    
    Inputs:
       prop.                               SUAVE propeller 
            tip_radius                     - propeller radius                         [m]
            rotation                       - propeller rotation direction             [-]
            thrust_angle                   - thrust angle of prop                     [rad]
            number_radial_stations         - number of propeller radial stations      [-]
            number_azimuthal_stations      - number of propeller azimuthal stations   [-]
       upstream_wake.
          u_velocities                     - Streamwise velocities from upstream wake
          v_velocities                     - Spanwise velocities from upstream wake
          w_velocities                     - Downwash velocities from upstream wake
          VD                               - Vortex distribution from upstream wake
       conditions.
          frames
       
    Outputs:
       Va                     Axial velocities at propeller             [m/s]
       Vt                     Tangential velocities at propeller        [m/s]
       Vr                     Radial velocities at propeller            [m/s]
    """
    # unpack propeller parameters
    Vv = conditions.frames.inertial.velocity_vector
    R = prop.tip_radius
    rotation = prop.rotation
    theta = prop.thrust_angle
    c = prop.chord_distribution
    Na = prop.number_azimuthal_stations
    Nr = len(c)

    ua_wing = upstream_wake.u_velocities
    uv_wing = upstream_wake.v_velocities
    uw_wing = upstream_wake.w_velocities
    VD = upstream_wake.VD

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

    # azimuth distribution
    psi = np.linspace(0, 2 * np.pi, Na + 1)[:-1]
    psi_2d = np.tile(np.atleast_2d(psi).T, (1, Nr))

    # 2 dimensiona radial distribution non dimensionalized
    chi = prop.radius_distribution / R

    # Reframe the wing induced velocities:
    y_center = prop.origin[0][1]

    # New points to interpolate data: (corresponding to r,phi locations on propeller disc)
    points = np.array([[VD.YC[i], VD.ZC[i]] for i in range(len(VD.YC))])
    ycoords = np.reshape(R * chi * np.cos(psi_2d), (Nr * Na, ))
    zcoords = prop.origin[0][2] + np.reshape(R * chi * np.sin(psi_2d),
                                             (Nr * Na, ))
    xi = np.array([[y_center + ycoords[i], zcoords[i]]
                   for i in range(len(ycoords))])

    ua_w = sp.interpolate.griddata(points, ua_wing, xi, method='linear')
    uv_w = sp.interpolate.griddata(points, uv_wing, xi, method='linear')
    uw_w = sp.interpolate.griddata(points, uw_wing, xi, method='linear')

    ua_wing = np.reshape(ua_w, (Na, Nr))
    uw_wing = np.reshape(uw_w, (Na, Nr))
    uv_wing = np.reshape(uv_w, (Na, Nr))

    if rotation == [1]:
        Vt_2d = V_thrust[:, 0] * (
            -np.array(uw_wing) * np.cos(psi_2d) +
            np.array(uv_wing) * np.sin(psi_2d)
        )  # velocity tangential to the disk plane, positive toward the trailing edge eqn 6.34 pg 165
        Vr_2d = V_thrust[:, 0] * (-np.array(uw_wing) * np.sin(psi_2d) -
                                  np.array(uv_wing) * np.cos(psi_2d)
                                  )  # radial velocity , positive outward
        Va_2d = V_thrust[:, 0] * np.array(
            ua_wing
        )  # velocity perpendicular to the disk plane, positive downward  eqn 6.36 pg 166
    else:
        Vt_2d = V_thrust[:, 0] * (
            np.array(uw_wing) * np.cos(psi_2d) -
            np.array(uv_wing) * np.sin(psi_2d)
        )  # velocity tangential to the disk plane, positive toward the trailing edge
        Vr_2d = V_thrust[:, 0] * (-np.array(uw_wing) * np.sin(psi_2d) -
                                  np.array(uv_wing) * np.cos(psi_2d)
                                  )  # radial velocity , positive outward
        Va_2d = V_thrust[:, 0] * np.array(
            ua_wing
        )  # velocity perpendicular to the disk plane, positive downward

    # Append velocities to propeller
    prop.tangential_velocities_2d = Vt_2d
    prop.radial_velocities_2d = Vr_2d
    prop.axial_velocities_2d = Va_2d

    return prop
Ejemplo n.º 20
0
    def spin(self,conditions):
        """Analyzes a propeller given geometry and operating conditions.

        Assumptions:
        per source

        Source:
        Qprop theory document

        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.prop_attributes.
          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)
        self.thrust_angle            [radians]
        """         
           
        #Unpack    
        B      = self.prop_attributes.number_blades
        R      = self.prop_attributes.tip_radius
        Rh     = self.prop_attributes.hub_radius
        beta   = self.prop_attributes.twist_distribution
        c      = self.prop_attributes.chord_distribution
        omega1 = self.inputs.omega
        rho    = conditions.freestream.density[:,0,None]
        mu     = conditions.freestream.dynamic_viscosity[:,0,None]
        Vv     = conditions.frames.inertial.velocity_vector
        a      = conditions.freestream.speed_of_sound[:,0,None]
        T      = conditions.freestream.temperature[:,0,None]
        theta  = self.thrust_angle
        
        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)
        
        # Velocity transformed to the propulsor frame
        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]
        
        nu    = mu/rho
        tol   = 1e-5 # Convergence tolerance
        
        omega = omega1*1.0
        omega = np.abs(omega)
           
        ######
        # Enter airfoil data in a better way, there is currently Re and Ma scaling from DAE51 data
        ######

        #Things that don't change with iteration
        N       = len(c) # Number of stations
        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)    
        sigma   = np.multiply(B*c,1./(2.*pi*r))   
    
        #I make the assumption that externally-induced velocity at the disk is zero
        #This can be easily changed if needed in the future:
        ua = 0.0
        ut = 0.0
        
        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)
    
        #Setup a Newton iteration
        psi    = np.ones(size)
        psiold = np.zeros(size)
        diff   = 1.
        
        ii = 0
        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
            
            #if np.any(Ma> 1.0):
                #warn('Propeller blade tips are supersonic.', Warning)
            
            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
            
            # Ok, from the airfoil data, given Re, Ma, alpha we need to find Cl
            Cl = 2.*pi*alpha
            
            # By 90 deg, it's totally stalled.
            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*85.0/180.)) and np.any(dpsi>0.0):
                break

        #This is an atrocious fit of DAE51 data at RE=50k for Cd
        #There is also RE scaling
        Re      = (W*c)/nu
        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]
        power    = torque*omega       
       
        D        = 2*R
        Cp       = power/(rho*(n*n*n)*(D*D*D*D*D))

        thrust[conditions.propulsion.throttle[:,0] <=0.0] = 0.0
        power[conditions.propulsion.throttle[:,0]  <=0.0] = 0.0
        
        thrust[omega1<0.0] = - thrust[omega1<0.0]

        etap     = V*thrust/power     
        
        conditions.propulsion.etap = etap
        
        # store data
        results_conditions = Data      
        conditions.propulsion.acoustic_outputs = results_conditions(
            number_sections    = N,
            r0                 = r,
            airfoil_chord      = c,
            blades_number      = B,
            propeller_diameter = D,
            drag_coefficient   = Cd,
            lift_coefficient   = Cl,
            omega              = omega,
            velocity           = V,
            thrust             = thrust,
            power              = power,
            mid_chord_aligment = self.prop_attributes.mid_chord_aligment
        )
        
        
        return thrust, torque, power, Cp