Exemplo n.º 1
0
def main():
    ospath        = os.path.abspath(__file__)
    separator     = os.path.sep
    airfoils_path = ospath.split('airfoil_import' + separator + 'airfoil_interpolation_test.py')[0] + 'Vehicles/Airfoils' + separator
    a_labels      = ["Clark_y", "E63"]
    nairfoils     = 4   # number of total airfoils
    
    a1 = airfoils_path + a_labels[0]+ ".txt"
    a2 = airfoils_path + a_labels[1]+ ".txt"
    new_files = generate_interpolated_airfoils(a1, a2, nairfoils, save_filename="Transition")
    plt.show()
    
    # import the new airfoil geometries and compare to the regression:
    airfoil_data_1   = import_airfoil_geometry([new_files['a_1'].name])
    airfoil_data_2   = import_airfoil_geometry([new_files['a_2'].name])    
    airfoil_data_1_r = import_airfoil_geometry(["Transition1_regression.txt"])
    airfoil_data_2_r = import_airfoil_geometry(["Transition2_regression.txt"])
    
    # ensure coordinates are the same:
    
    assert( max(abs(airfoil_data_1.x_coordinates[0] - airfoil_data_1_r.x_coordinates[0])) < 1e-5)
    assert( max(abs(airfoil_data_2.x_coordinates[0] - airfoil_data_2_r.x_coordinates[0])) < 1e-5)
    assert( max(abs(airfoil_data_1.y_coordinates[0] - airfoil_data_1_r.y_coordinates[0])) < 1e-5)
    assert( max(abs(airfoil_data_2.y_coordinates[0] - airfoil_data_2_r.y_coordinates[0])) < 1e-5)

    return
Exemplo n.º 2
0
def main():   
    
    airfoil_polar_names    = ['airfoil_polar_1.txt','airfoil_polar_2.txt']    
    airfoil_polar_data =  import_airfoil_polars(airfoil_polar_names) 

    airfoil_geometry_names = ['airfoil_geometry_1.txt','airfoil_geometry_2.txt', 'airfoil_geometry_2-selig.txt']    
    airfoil_geometry_data = import_airfoil_geometry(airfoil_geometry_names)

    # Actual t/c values 

    airfoil_tc_actual = [0.11806510344827587, 0.11175207317073171, 0.11175207317073171]


    # Check t/c calculation against previously calculated values

    for i in range(0, len(airfoil_geometry_names)):
        assert(np.abs(airfoil_tc_actual[i]-airfoil_geometry_data.thickness_to_chord[i]) < 1E-8 )
    

    # Check that camber line comes back the same for the Lednicer and Selig formats

    for j in range(0, len(airfoil_geometry_data.camber_coordinates[1])):
         assert( np.abs(airfoil_geometry_data.camber_coordinates[1][j] - airfoil_geometry_data.camber_coordinates[2][j]) < 1E-8 )

    plot_airfoil(airfoil_geometry_names)
    return  
Exemplo n.º 3
0
def make_airfoil_text(vsp_bem,prop):   
    """This function writes the airfoil geometry into the vsp file
    Assumptions:
        None

    Source:
        None
    Inputs:
        vsp_bem - OpenVSP .bem file 
        prop    - SUAVE propeller data structure 

    Outputs:
        NA                
    Properties Used:
        N/A
    """ 

    N             = len(prop.radius_distribution)  
    airfoil_data  = import_airfoil_geometry(prop.airfoil_geometry)
    a_sec         = prop.airfoil_polar_stations
    for i in range(N):
        airfoil_station_header = '\nSection ' + str(i) + ' X, Y\n'  
        vsp_bem.write(airfoil_station_header)   

        airfoil_x     = airfoil_data.x_coordinates[int(a_sec[i])] 
        airfoil_y     = airfoil_data.y_coordinates[int(a_sec[i])] 

        for j in range(len(airfoil_x)): 
            section_text = format(airfoil_x[j], '.7f')+ ", " + format(airfoil_y[j], '.7f') + "\n"  
            vsp_bem.write(section_text)      
    return 
Exemplo n.º 4
0
def plot_airfoil(airfoil_names,
                 line_color='k-',
                 overlay=False,
                 save_figure=False,
                 save_filename="Airfoil_Geometry",
                 file_type=".png"):
    """This plots all airfoil defined in the list "airfoil_names" 

    Assumptions:
    None

    Source:
    None

    Inputs:
    airfoil_geometry_files   <list of strings>

    Outputs: 
    Plots

    Properties Used:
    N/A	
    """
    # get airfoil coordinate geometry
    airfoil_data = import_airfoil_geometry(airfoil_names)

    if overlay:
        name = save_filename
        fig = plt.figure(name)
        fig.set_size_inches(10, 4)
        axes = fig.add_subplot(1, 1, 1)
        for i in range(len(airfoil_names)):
            # separate x and y coordinates
            airfoil_x = airfoil_data.x_coordinates[i]
            airfoil_y = airfoil_data.y_coordinates[i]
            axes.plot(airfoil_x, airfoil_y, label=airfoil_names[i])

        axes.set_title("Airfoil Geometry")
        axes.legend()
        if save_figure:
            plt.savefig(name + file_type)

    else:
        for i in range(len(airfoil_names)):
            # separate x and y coordinates
            airfoil_x = airfoil_data.x_coordinates[i]
            airfoil_y = airfoil_data.y_coordinates[i]

            name = save_filename + '_' + str(i)
            fig = plt.figure(name)
            axes = fig.add_subplot(1, 1, 1)
            axes.set_title(airfoil_names[i])
            axes.plot(airfoil_x, airfoil_y, line_color)
            axes.axis('equal')
            if save_figure:
                plt.savefig(name + file_type)

    return
def main():   
    
    airfoil_polar_names    = ['airfoil_polar_1.txt','airfoil_polar_2.txt']    
    airfoil_polar_data =  import_airfoil_polars(airfoil_polar_names) 

    airfoil_geometry_names = ['airfoil_geometry_1.txt','airfoil_geometry_2.txt']    
    airfoil_geometry_data = import_airfoil_geometry (airfoil_geometry_names)
    
    return  
Exemplo n.º 6
0
def main():
    ospath = os.path.abspath(__file__)
    separator = os.path.sep

    rel_path = ospath.split(
        'airfoil_import' + separator + 'airfoil_import_test.py'
    )[0] + 'Vehicles' + separator + 'Airfoils' + separator
    airfoil_geometry_with_selig = [
        rel_path + 'NACA_4412.txt', 'airfoil_geometry_2.txt',
        'airfoil_geometry_2-selig.txt'
    ]
    airfoil_geometry = [rel_path + 'NACA_4412.txt']
    airfoil_polar_names = [[
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_50000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_100000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_200000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_500000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_1000000.txt'
    ]]

    # plot airfoil polar data with and without surrogate
    plot_airfoil_polar_files(airfoil_geometry,
                             airfoil_polar_names,
                             display_plot=True)
    plot_airfoil_polar_files(airfoil_geometry,
                             airfoil_polar_names,
                             use_surrogate=True,
                             display_plot=True)

    airfoil_polar_data = import_airfoil_polars(airfoil_polar_names)

    airfoil_geometry_data = import_airfoil_geometry(
        airfoil_geometry_with_selig)

    # Actual t/c values
    airfoil_tc_actual = [
        0.12031526401402462, 0.11177063928743779, 0.11177063928743779
    ]

    # Check t/c calculation against previously calculated values

    for i in range(0, len(airfoil_geometry_with_selig)):
        assert (np.abs(airfoil_tc_actual[i] -
                       airfoil_geometry_data.thickness_to_chord[i]) < 1E-8)

    # Check that camber line comes back the same for the Lednicer and Selig formats

    for j in range(0, len(airfoil_geometry_data.camber_coordinates[1])):
        assert (np.abs(airfoil_geometry_data.camber_coordinates[1][j] -
                       airfoil_geometry_data.camber_coordinates[2][j]) < 1E-8)

    plot_airfoil(airfoil_geometry_with_selig)

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

    return prop
Exemplo n.º 8
0
def main():
    ospath = os.path.abspath(__file__)
    separator = os.path.sep

    rel_path = ospath.split(
        'airfoil_import' + separator + 'airfoil_import_test.py'
    )[0] + 'Vehicles' + separator + 'Airfoils' + separator
    airfoil_polar_names = [[
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_50000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_100000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_200000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_500000.txt',
        rel_path + 'Polars' + separator + 'NACA_4412_polar_Re_1000000.txt'
    ]]

    airfoil_polar_data = import_airfoil_polars(airfoil_polar_names)

    airfoil_geometry_names = [
        rel_path + 'NACA_4412.txt', 'airfoil_geometry_2.txt',
        'airfoil_geometry_2-selig.txt'
    ]
    airfoil_geometry_data = import_airfoil_geometry(airfoil_geometry_names)

    # Actual t/c values
    airfoil_tc_actual = [
        0.12012222222222223, 0.11171495959595959, 0.11171495959595959
    ]

    # Check t/c calculation against previously calculated values

    for i in range(0, len(airfoil_geometry_names)):
        assert (np.abs(airfoil_tc_actual[i] -
                       airfoil_geometry_data.thickness_to_chord[i]) < 1E-8)

    # Check that camber line comes back the same for the Lednicer and Selig formats

    for j in range(0, len(airfoil_geometry_data.camber_coordinates[1])):
        assert (np.abs(airfoil_geometry_data.camber_coordinates[1][j] -
                       airfoil_geometry_data.camber_coordinates[2][j]) < 1E-8)

    plot_airfoil(airfoil_geometry_names)
    return
Exemplo n.º 9
0
def propeller_design(prop, number_of_stations=20):
    """ Optimizes propeller chord and twist given input parameters.
          
          Inputs:
          Either design power or thrust
          prop_attributes.
            hub radius                       [m]
            tip radius                       [m]
            rotation rate                    [rad/s]
            freestream velocity              [m/s]
            number of blades               
            number of stations
            design lift coefficient
            airfoil data
            
          Outputs:
          Twist distribution                 [array of radians]
          Chord distribution                 [array of meters]
              
          Assumptions/ Source:
          Based on Design of Optimum Propellers by Adkins and Liebeck
          
    """
    # Unpack
    N = number_of_stations  # this number determines the discretization of the propeller into stations
    B = prop.number_blades
    R = prop.tip_radius
    Rh = prop.hub_radius
    omega = prop.angular_velocity  # Rotation Rate in rad/s
    V = prop.freestream_velocity  # Freestream Velocity
    Cl = prop.design_Cl  # Design Lift Coefficient
    alt = prop.design_altitude
    Thrust = prop.design_thrust
    Power = prop.design_power
    a_geo = prop.airfoil_geometry

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

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

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

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

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

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

    tol = 1e-10  # Convergence tolerance

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

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

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

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

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

        #Step 4, determine epsilon and alpha from airfoil data

        #This is an atrocious fit of DAE51 data at RE=50k for Cd
        #There is also RE scaling
        Cdval = (0.108 * (Cl**4) - 0.2612 * (Cl**3) + 0.181 *
                 (Cl**2) - 0.0139 * Cl + 0.0278) * ((50000. / RE)**0.2)

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

        Cd = ((1 / Tp_Tinf) * (1 / Rp_Rinf)**0.2) * Cdval

        alpha = Cl / (2. * np.pi)
        epsilon = Cd / Cl

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

        #Step 6, determine a and a', and W

        a = (zeta / 2.) * (np.cos(phi)**2.) * (1. - epsilon * np.tan(phi))
        aprime = (zeta / (2. * x)) * np.cos(phi) * np.sin(phi) * (
            1. + epsilon / np.tan(phi))
        W = V * (1. + a) / np.sin(phi)

        #Step 7, compute the chord length and blade twist angle

        c = Wc / W
        beta = alpha + phi  # Blade twist angle

        #Step 8, determine 4 derivatives in I and J

        Iprime1 = 4. * chi * G * (1. - epsilon * np.tan(phi))
        Iprime2 = lamda * (Iprime1 / (2. * chi)) * (
            1. + epsilon / np.tan(phi)) * np.sin(phi) * np.cos(phi)
        Jprime1 = 4. * chi * G * (1. + epsilon / np.tan(phi))
        Jprime2 = (Jprime1 / 2.) * (1. - epsilon * np.tan(phi)) * (np.cos(phi)
                                                                   **2.)

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

        #Integrate derivatives from chi=chi0 to chi=1

        I1 = np.dot(Iprime1, dchi)
        I2 = np.dot(Iprime2, dchi)
        J1 = np.dot(Jprime1, dchi)
        J2 = np.dot(Jprime2, dchi)

        #Step 9, determine zeta and and Pc or zeta and Tc

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

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

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

        zeta = zetan

    #Step 11, determine propeller efficiency etc...

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

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

    else:
        print('Power and thrust are both specified!')

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

    MCA = c / 4. - c[0] / 4.

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

    # compute max thickness distribution using NACA 4 series eqn
    t_max = np.zeros(20)
    for idx in range(20):
        c_blade = np.linspace(0, c[idx], 20)  # local chord
        t = (5 * c_blade) * (
            0.2969 * np.sqrt(c_blade) - 0.1260 * c_blade - 0.3516 *
            (c_blade**2) + 0.2843 * (c_blade**3) - 0.1015 *
            (c_blade**4))  # local thickness distribution
        t_max[idx] = np.max(t)

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

    # approximate thickness to chord ratio
    t_c = t_max / c
    t_c_at_70_percent = t_c[int(N * 0.7)]

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

    prop.design_torque = Power[0] / omega
    prop.max_thickness_distribution = t_max
    prop.twist_distribution = beta
    prop.chord_distribution = c
    prop.power_coefficient = Cp
    prop.mid_chord_aligment = MCA
    prop.thickness_to_chord = t_c_at_70_percent
    prop.blade_solidity = sigma

    # compute airfoil sections if given
    if a_geo != None:
        airfoil_geometry = Data()
        prop.airfoil_data = import_airfoil_geometry(a_geo)
    return prop
Exemplo n.º 10
0
def plot_propeller_geometry(prop,
                            line_color='bo-',
                            save_figure=False,
                            save_filename="Propeller_Geometry",
                            file_type=".png"):
    """This plots the geoemtry of a propeller or rotor

	Assumptions:
	None

	Source:
	None

	Inputs:
	SUAVE.Components.Energy.Converters.Propeller()

	Outputs: 
	Plots

	Properties Used:
	N/A	
	"""
    # 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
    t = prop.max_thickness_distribution

    # prepare plot parameters
    dim = len(b)
    theta = np.linspace(0, 2 * np.pi, num_B + 1)

    fig = plt.figure(save_filename)
    fig.set_size_inches(10, 8)
    axes = plt.axes(projection='3d')
    axes.set_zlim3d(-1, 1)
    axes.set_ylim3d(-1, 1)
    axes.set_xlim3d(-1, 1)

    chord = np.outer(np.linspace(0, 1, 10), b)
    if r == None:
        r = np.linspace(Rh, Rt, len(b))
    for i in range(num_B):
        # plot propeller planfrom
        surf_x = np.cos(theta[i]) * (chord * np.cos(beta)) - np.sin(
            theta[i]) * (r)
        surf_y = np.sin(theta[i]) * (chord * np.cos(beta)) + np.cos(
            theta[i]) * (r)
        surf_z = chord * np.sin(beta)
        axes.plot_surface(surf_x, surf_y, surf_z, color='gray')

        if a_sec != None and a_secl != None:
            # check dimension of section
            dim_sec = len(a_secl)
            if dim_sec != dim:
                raise AssertionError(
                    "Number of sections not equal to number of stations")

            # get airfoil coordinate geometry
            airfoil_data = import_airfoil_geometry(a_sec)

            #plot airfoils
            for j in range(dim):
                airfoil_max_t = airfoil_data.thickness_to_chord[a_secl[j]]
                airfoil_xp = b[j] - airfoil_data.x_coordinates[
                    a_secl[j]] * b[j]
                airfoil_yp = r[j] * np.ones_like(airfoil_xp)
                airfoil_zp = airfoil_data.y_coordinates[a_secl[j]] * b[j] * (
                    t[j] / (airfoil_max_t * b[j]))

                transformation_1 = [[np.cos(beta[j]), 0, -np.sin(beta[j])],
                                    [0, 1, 0],
                                    [np.sin(beta[j]), 0,
                                     np.cos(beta[j])]]
                transformation_2 = [[np.cos(theta[i]), -np.sin(theta[i]), 0],
                                    [np.sin(theta[i]),
                                     np.cos(theta[i]), 0], [0, 0, 1]]
                transformation = np.matmul(transformation_2, transformation_1)

                airfoil_x = np.zeros(len(airfoil_yp))
                airfoil_y = np.zeros(len(airfoil_yp))
                airfoil_z = np.zeros(len(airfoil_yp))

                for k in range(len(airfoil_yp)):
                    vec_1 = [[airfoil_xp[k]], [airfoil_yp[k]], [airfoil_zp[k]]]
                    vec_2 = np.matmul(transformation, vec_1)
                    airfoil_x[k] = vec_2[0]
                    airfoil_y[k] = vec_2[1]
                    airfoil_z[k] = vec_2[2]

                axes.plot3D(airfoil_x, airfoil_y, airfoil_z, color='gray')

    if save_figure:
        plt.savefig(save_filename + file_type)

    return
Exemplo n.º 11
0
def plot_propeller_geometry(axes,prop,network,network_name,prop_face_color='red',prop_edge_color='darkred',prop_alpha=1):
    """ This plots a 3D surface of the  propeller

    Assumptions:
    None

    Source:
    None

    Inputs:
    network            - network data structure

    Properties Used:
    N/A
    """

    # unpack
    num_B  = prop.number_of_blades
    a_sec  = prop.airfoil_geometry
    a_secl = prop.airfoil_polar_stations
    beta   = prop.twist_distribution
    a_o    = prop.azimuthal_offset_angle
    b      = prop.chord_distribution
    r      = prop.radius_distribution
    MCA    = prop.mid_chord_alignment
    t      = prop.max_thickness_distribution
    origin = prop.origin
 
    n_points  = 20
    af_pts    = n_points-1
    dim       = len(b)
    theta     = np.linspace(0,2*np.pi,num_B+1)[:-1]

    # create empty data structure for storing geometry
    G = Data()


    rot    = prop.rotation 
    flip_1 =  (np.pi/2)
    flip_2 =  (np.pi/2)

    MCA_2d = np.repeat(np.atleast_2d(MCA).T,n_points,axis=1)
    b_2d   = np.repeat(np.atleast_2d(b).T  ,n_points,axis=1)
    t_2d   = np.repeat(np.atleast_2d(t).T  ,n_points,axis=1)
    r_2d   = np.repeat(np.atleast_2d(r).T  ,n_points,axis=1)
    shift  = np.repeat(np.atleast_2d(np.ones_like(b)*b[0]).T  ,n_points,axis=1)

    for i in range(num_B):
        # get airfoil coordinate geometry
        if a_sec != None:
            airfoil_data = import_airfoil_geometry(a_sec,npoints=n_points)
            xpts         = np.take(airfoil_data.x_coordinates,a_secl,axis=0)
            zpts         = np.take(airfoil_data.y_coordinates,a_secl,axis=0)
            max_t        = np.take(airfoil_data.thickness_to_chord,a_secl,axis=0)

        else:
            camber       = 0.02
            camber_loc   = 0.4
            thickness    = 0.10
            airfoil_data = compute_naca_4series(camber, camber_loc, thickness,(n_points - 2))
            xpts         = np.repeat(np.atleast_2d(airfoil_data.x_coordinates) ,dim,axis=0)
            zpts         = np.repeat(np.atleast_2d(airfoil_data.y_coordinates) ,dim,axis=0)
            max_t        = np.repeat(airfoil_data.thickness_to_chord,dim,axis=0)

        # store points of airfoil in similar format as Vortex Points (i.e. in vertices)
        max_t2d = np.repeat(np.atleast_2d(max_t).T ,n_points,axis=1)

        xp      = (- MCA_2d + xpts*b_2d - shift/2 )     # x-coord of airfoil
        yp      = r_2d*np.ones_like(xp)       # radial location
        zp      = zpts*(t_2d/max_t2d)         # former airfoil y coord

        matrix = np.zeros((len(zp),n_points,3)) # radial location, airfoil pts (same y)
        matrix[:,:,0] = xp*rot
        matrix[:,:,1] = yp
        matrix[:,:,2] = zp

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

        # rotation about x axis to create azimuth locations
        trans_2 = np.array([[1 , 0 , 0],
                       [0 , np.cos(theta[i] + a_o + flip_2 ), -np.sin(theta[i] +a_o +  flip_2)],
                       [0,np.sin(theta[i] + a_o + flip_2), np.cos(theta[i] + a_o + flip_2)]])
        trans_2 =  np.repeat(trans_2[ np.newaxis,:,: ],dim,axis=0)

        # rotation about y to orient propeller/rotor to thrust angle
        trans_3 =  prop.prop_vel_to_body()
        trans_3 =  np.repeat(trans_3[ np.newaxis,:,: ],dim,axis=0) 
        
        # rotation 180 degrees 
        trans_4 = np.zeros((dim,3,3))
        trans_4[:,0,0] = np.cos(np.pi)
        trans_4[:,0,2] = -np.sin(np.pi)
        trans_4[:,1,1] = 1
        trans_4[:,2,0] = np.sin(np.pi)
        trans_4[:,2,2] = np.cos(np.pi)
        
        trans     = np.matmul(trans_4,np.matmul(trans_3,np.matmul(trans_2,trans_1)))
        rot_mat   = np.repeat(trans[:, np.newaxis,:,:],n_points,axis=1)

        # ---------------------------------------------------------------------------------------------
        # ROTATE POINTS
        mat  =  np.matmul(rot_mat,matrix[...,None]).squeeze()

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

        G.XB1  = mat[1:,:-1,0] + origin[0][0]
        G.YB1  = mat[1:,:-1,1] + origin[0][1]
        G.ZB1  = mat[1:,:-1,2] + origin[0][2]
        G.XB2  = mat[1:,1:,0]  + origin[0][0]
        G.YB2  = mat[1:,1:,1]  + origin[0][1]
        G.ZB2  = mat[1:,1:,2]  + origin[0][2]

        # ------------------------------------------------------------------------
        # Plot Propeller Blade
        # ------------------------------------------------------------------------
        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)
    return
Exemplo n.º 12
0
def generate_nacelle_points(nac,tessellation = 24):
    """ This generates the coordinate points on the surface of the nacelle

    Assumptions:
    None

    Source:
    None

    Inputs: 
    Properties Used:
    N/A 
    """
     
    
    num_nac_segs = len(nac.Segments.keys())   
    theta        = np.linspace(0,2*np.pi,tessellation)
    n_points     = 20
    
    if num_nac_segs == 0:
        num_nac_segs = int(n_points/2)
        nac_pts      = np.zeros((num_nac_segs,tessellation,3))
        naf          = nac.Airfoil
        
        if naf.naca_4_series_airfoil != None: 
            # use mean camber surface of airfoil
            camber       = float(naf.naca_4_series_airfoil[0])/100
            camber_loc   = float(naf.naca_4_series_airfoil[1])/10
            thickness    = float(naf.naca_4_series_airfoil[2:])/100 
            airfoil_data = compute_naca_4series(camber, camber_loc, thickness,(n_points - 2))
            xpts         = np.repeat(np.atleast_2d(airfoil_data.x_lower_surface).T,tessellation,axis = 1)*nac.length 
            zpts         = np.repeat(np.atleast_2d(airfoil_data.camber_coordinates[0]).T,tessellation,axis = 1)*nac.length  
        
        elif naf.coordinate_file != None: 
            a_sec        = naf.coordinate_file
            a_secl       = [0]
            airfoil_data = import_airfoil_geometry(a_sec,npoints=num_nac_segs)
            xpts         = np.repeat(np.atleast_2d(np.take(airfoil_data.x_coordinates,a_secl,axis=0)).T,tessellation,axis = 1)*nac.length  
            zpts         = np.repeat(np.atleast_2d(np.take(airfoil_data.y_coordinates,a_secl,axis=0)).T,tessellation,axis = 1)*nac.length  
        
        else:
            # if no airfoil defined, use super ellipse as default
            a =  nac.length/2 
            b =  (nac.diameter - nac.inlet_diameter)/2 
            b = np.maximum(b,1E-3) # ensure 
            xpts =  np.repeat(np.atleast_2d(np.linspace(-a,a,num_nac_segs)).T,tessellation,axis = 1) 
            zpts = (np.sqrt((b**2)*(1 - (xpts**2)/(a**2) )))*nac.length 
            xpts = (xpts+a)*nac.length  

        if nac.flow_through: 
            zpts = zpts + nac.inlet_diameter/2  
                
        # create geometry 
        theta_2d = np.repeat(np.atleast_2d(theta),num_nac_segs,axis =0) 
        nac_pts[:,:,0] =  xpts
        nac_pts[:,:,1] =  zpts*np.cos(theta_2d)
        nac_pts[:,:,2] =  zpts*np.sin(theta_2d)  
                
    else:
        nac_pts = np.zeros((num_nac_segs,tessellation,3)) 
        for i_seg in range(num_nac_segs):
            a        = nac.Segments[i_seg].width/2
            b        = nac.Segments[i_seg].height/2
            r        = np.sqrt((b*np.sin(theta))**2  + (a*np.cos(theta))**2)
            nac_ypts = r*np.cos(theta)
            nac_zpts = r*np.sin(theta)
            nac_pts[i_seg,:,0] = nac.Segments[i_seg].percent_x_location*nac.length
            nac_pts[i_seg,:,1] = nac_ypts + nac.Segments[i_seg].percent_y_location*nac.length 
            nac_pts[i_seg,:,2] = nac_zpts + nac.Segments[i_seg].percent_z_location*nac.length  
            
    # rotation about y to orient propeller/rotor to thrust angle
    rot_trans =  nac.nac_vel_to_body()
    rot_trans =  np.repeat( np.repeat(rot_trans[ np.newaxis,:,: ],tessellation,axis=0)[ np.newaxis,:,:,: ],num_nac_segs,axis=0)    
    
    NAC_PTS  =  np.matmul(rot_trans,nac_pts[...,None]).squeeze()  
     
    # translate to body 
    NAC_PTS[:,:,0] = NAC_PTS[:,:,0] + nac.origin[0][0]
    NAC_PTS[:,:,1] = NAC_PTS[:,:,1] + nac.origin[0][1]
    NAC_PTS[:,:,2] = NAC_PTS[:,:,2] + nac.origin[0][2]
    return NAC_PTS
Exemplo n.º 13
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
def generate_interpolated_airfoils(a1,
                                   a2,
                                   nairfoils,
                                   npoints=200,
                                   save_filename="Transition"):
    """ Takes in two airfoils, interpolates between their coordinates to generate new
    airfoil geometries and saves new airfoil files.
    
    Assumptions: Linear geometric transition between airfoils
    
    Source: None
    
    Inputs:
    a1                 first airfoil                                [ airfoil ]
    a2                 second airfoil                               [ airfoil ]
    nairfoils          number of airfoils                           [ unitless ]
    
    """

    # import airfoil geometry for the two airfoils
    airfoil_geo_files = [a1, a2]
    a1_name = os.path.basename(a1)
    a2_name = os.path.basename(a2)
    a_geo = import_airfoil_geometry(airfoil_geo_files, npoints)

    # identify x and y coordinates of the two airfoils
    x_upper = a_geo.x_upper_surface
    y_upper = a_geo.y_upper_surface
    x_lower = a_geo.x_lower_surface
    y_lower = a_geo.y_lower_surface

    # identify points on airfoils to interpolate between
    yairfoils_upper = np.array(y_upper).T
    yairfoils_lower = np.array(y_lower).T
    xairfoils_upper = np.array(x_upper).T
    xairfoils_lower = np.array(x_lower).T

    # for each point around the airfoil, interpolate between the two given airfoil coordinates
    z = np.linspace(0, 1, nairfoils)

    y_u_lb = yairfoils_upper[:, 0]
    y_u_ub = yairfoils_upper[:, 1]
    y_l_lb = yairfoils_lower[:, 0]
    y_l_ub = yairfoils_lower[:, 1]

    x_u_lb = xairfoils_upper[:, 0]
    x_u_ub = xairfoils_upper[:, 1]
    x_l_lb = xairfoils_lower[:, 0]
    x_l_ub = xairfoils_lower[:, 1]

    # broadcasting interpolation
    y_n_upper = (z[None, ...] * (y_u_ub[..., None] - y_u_lb[..., None]) +
                 (y_u_lb[..., None])).T
    y_n_lower = (z[None, ...] * (y_l_ub[..., None] - y_l_lb[..., None]) +
                 (y_l_lb[..., None])).T
    x_n_upper = (z[None, ...] * (x_u_ub[..., None] - x_u_lb[..., None]) +
                 (x_u_lb[..., None])).T
    x_n_lower = (z[None, ...] * (x_l_ub[..., None] - x_l_lb[..., None]) +
                 (x_l_lb[..., None])).T

    # save new airfoil geometry files:

    new_files = {'a_{}'.format(i + 1): [] for i in range(nairfoils - 2)}
    airfoil_files = []

    for k in range(nairfoils - 2):
        # create new files and write title block for each new airfoil
        title_block = "Airfoil Transition " + str(
            k +
            1) + " between " + a1_name + " and " + a2_name + "\n 61. 61.\n\n"
        file = 'a_' + str(k + 1)
        new_files[file] = open(save_filename + str(k + 1) + ".txt", "w+")
        new_files[file].write(title_block)

        y_n_u = np.reshape(y_n_upper[k + 1], (npoints // 2, 1))
        y_n_l = np.reshape(y_n_lower[k + 1], (npoints // 2, 1))
        x_n_u = np.reshape(x_n_upper[k + 1], (npoints // 2, 1))
        x_n_l = np.reshape(x_n_lower[k + 1], (npoints // 2, 1))

        airfoil_files.append(new_files[file].name)
        upper_data = np.append(x_n_u, y_n_u, axis=1)
        lower_data = np.append(x_n_l, y_n_l, axis=1)

        # write lines to files
        for lines in upper_data:  #upper_data[file]:
            line = str(lines[0]) + " " + str(lines[1]) + "\n"
            new_files[file].write(line)

        new_files[file].write("\n")

        for lines in lower_data:  #[file]:
            line = str(lines[0]) + " " + str(lines[1]) + "\n"
            new_files[file].write(line)

        new_files[file].close()

    # plot new and original airfoils:
    airfoil_files.insert(0, a1)
    airfoil_files.append(a2)
    plot_airfoil(airfoil_files, overlay=True)
    plot_airfoil(airfoil_files, overlay=False)

    return new_files
Exemplo n.º 15
0
def propeller_design(prop, number_of_stations=20):
    """ Optimizes propeller chord and twist given input parameters.
          
          Inputs:
          Either design power or thrust
          prop_attributes.
            hub radius                       [m]
            tip radius                       [m]
            rotation rate                    [rad/s]
            freestream velocity              [m/s]
            number of blades               
            number of stations
            design lift coefficient
            airfoil data
            
          Outputs:
          Twist distribution                 [array of radians]
          Chord distribution                 [array of meters]
              
          Assumptions/ Source:
          Based on Design of Optimum Propellers by Adkins and Liebeck
          
    """
    print('\nDesigning', prop.tag)

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

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

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

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

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

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

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

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

    tol = 1e-10  # Convergence tolerance

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

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

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

    c = 0.2 * np.ones_like(chi)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        zeta = zetan

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

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

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

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

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

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

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

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

    return prop
def generate_propeller_wake_distribution(props, identical, m, VD,
                                         init_timestep_offset, time,
                                         number_of_wake_timesteps, conditions):
    """ This generates the propeller wake control points used to compute the 
    influence of the wake

    Assumptions: 
    None

    Source:   
    None

    Inputs:  
    identical                - if all props are identical        [Bool]
    m                        - control point                     [Unitless] 
    VD                       - vortex distribution               
    prop                     - propeller/rotor data structure         
    init_timestep_offset     - intial time step                  [Unitless] 
    time                     - time                              [s]
    number_of_wake_timesteps - number of wake timesteps          [Unitless]
    conditions.
       noise.sources.propellers   -  propeller noise sources data structure
       
    Properties Used:
    N/A
    """
    num_prop = len(props)

    if identical:
        # All props are identical in geometry, so only the first one is unpacked
        prop_keys = list(props.keys())
        prop_key = prop_keys[0]
        prop = props[prop_key]
        prop_outputs = conditions.noise.sources.propellers[prop_key]

        Bmax = int(prop.number_of_blades)
        nmax = int(prop_outputs.number_radial_stations - 1)

    else:
        # Props are unique, must find required matrix sizes to fit all vortex distributions
        prop_keys = list(props.keys())
        B_list = np.ones(len(prop_keys))
        Nr_list = np.ones(len(prop_keys))

        for i in range(len(prop_keys)):
            p_key = list(props.keys())[i]
            p = props[p_key]
            p_out = conditions.noise.sources.propellers[p_key]

            B_list[i] = p.number_of_blades
            Nr_list[i] = p_out.number_radial_stations

        # Identify max indices for pre-allocating vortex wake distribution matrices
        Bmax = int(max(B_list))
        nmax = int(max(Nr_list) - 1)

    # Add additional time step to include lifting line panel on rotor
    nts = number_of_wake_timesteps

    # Initialize empty arrays with required sizes
    VD, WD, Wmid = initialize_distributions(nmax, Bmax, nts, num_prop, m, VD)

    # for each propeller, unpack and compute
    for i, propi in enumerate(props):
        propi_key = list(props.keys())[i]
        if identical:
            propi_outputs = prop_outputs
        else:
            propi_outputs = conditions.noise.sources.propellers[propi_key]

        # Unpack
        R = propi.tip_radius
        r = propi.radius_distribution
        c = propi.chord_distribution
        MCA = propi.mid_chord_alignment
        B = propi.number_of_blades

        Na = propi_outputs.number_azimuthal_stations
        Nr = propi_outputs.number_radial_stations
        omega = propi_outputs.omega
        va = propi_outputs.disc_axial_induced_velocity
        V_inf = propi_outputs.velocity
        gamma = propi_outputs.disc_circulation

        blade_angles = np.linspace(0, 2 * np.pi, B + 1)[:-1]
        dt = time / number_of_wake_timesteps
        ts = np.linspace(0, time, number_of_wake_timesteps)

        t0 = dt * init_timestep_offset
        start_angle = omega[0] * t0
        propi.start_angle = start_angle[0]

        # compute lambda and mu
        mean_induced_velocity = np.mean(np.mean(va, axis=1), axis=1)

        alpha = propi.orientation_euler_angles[1]
        rots = np.array([[np.cos(alpha), 0, np.sin(alpha)], [0, 1, 0],
                         [-np.sin(alpha), 0, np.cos(alpha)]])

        lambda_tot = np.atleast_2d(
            (np.dot(V_inf, rots[0]) + mean_induced_velocity)).T / (
                omega * R)  # inflow advance ratio (page 99 Leishman)
        mu_prop = np.atleast_2d(np.dot(V_inf, rots[2])).T / (
            omega * R)  # rotor advance ratio  (page 99 Leishman)
        V_prop = np.atleast_2d(
            np.sqrt((V_inf[:, 0] + mean_induced_velocity)**2 +
                    (V_inf[:, 2])**2)).T

        # wake skew angle
        wake_skew_angle = -np.arctan(mu_prop / lambda_tot)

        # reshape gamma to find the average between stations
        gamma_new = np.zeros(
            (m, (Nr - 1), Na)
        )  # [control points, Nr-1, Na ] one less radial station because ring
        gamma_new = (gamma[:, :-1, :] + gamma[:, 1:, :]) * 0.5

        num = int(Na / B)
        time_idx = np.arange(nts)
        t_idx = np.atleast_2d(time_idx).T
        B_idx = np.arange(B)
        B_loc = (B_idx * num + t_idx) % Na
        Gamma = gamma_new[:, :, B_loc]
        Gamma = Gamma.transpose(0, 3, 1, 2)

        # --------------------------------------------------------------------------------------------------------------
        #    ( control point , blade number , radial location on blade , time step )
        # --------------------------------------------------------------------------------------------------------------
        sx_inf0 = np.multiply(V_prop * np.cos(wake_skew_angle),
                              np.atleast_2d(ts))
        sx_inf = np.repeat(np.repeat(sx_inf0[:, None, :], B, axis=1)[:, :,
                                                                     None, :],
                           Nr,
                           axis=2)

        sy_inf0 = np.multiply(np.atleast_2d(V_inf[:, 1]).T,
                              np.atleast_2d(ts))  # = zero since no crosswind
        sy_inf = np.repeat(np.repeat(sy_inf0[:, None, :], B, axis=1)[:, :,
                                                                     None, :],
                           Nr,
                           axis=2)

        sz_inf0 = np.multiply(V_prop * np.sin(wake_skew_angle),
                              np.atleast_2d(ts))
        sz_inf = np.repeat(np.repeat(sz_inf0[:, None, :], B, axis=1)[:, :,
                                                                     None, :],
                           Nr,
                           axis=2)

        angle_offset = np.repeat(np.repeat(np.multiply(
            omega, np.atleast_2d(ts))[:, None, :],
                                           B,
                                           axis=1)[:, :, None, :],
                                 Nr,
                                 axis=2)
        blade_angle_loc = np.repeat(np.repeat(np.tile(
            np.atleast_2d(blade_angles), (m, 1))[:, :, None],
                                              Nr,
                                              axis=2)[:, :, :, None],
                                    number_of_wake_timesteps,
                                    axis=3)
        start_angle_offset = np.repeat(np.repeat(
            np.atleast_2d(start_angle)[:, None, :], B, axis=1)[:, :, None, :],
                                       Nr,
                                       axis=2)

        total_angle_offset = angle_offset - start_angle_offset

        if (propi.rotation != None) and (propi.rotation == 1):
            total_angle_offset = -total_angle_offset

        azi_y = np.sin(blade_angle_loc + total_angle_offset)
        azi_z = np.cos(blade_angle_loc + total_angle_offset)

        # extract airfoil trailing edge coordinates for initial location of vortex wake
        a_sec = propi.airfoil_geometry
        a_secl = propi.airfoil_polar_stations
        airfoil_data = import_airfoil_geometry(a_sec, npoints=100)

        # trailing edge points in airfoil coordinates
        xupper = np.take(airfoil_data.x_upper_surface, a_secl, axis=0)
        yupper = np.take(airfoil_data.y_upper_surface, a_secl, axis=0)

        # Align the quarter chords of the airfoils (zero sweep)
        airfoil_le_offset = (c[0] / 2 - c / 2)
        xte_airfoils = xupper[:, -1] * c + airfoil_le_offset
        yte_airfoils = yupper[:, -1] * c

        xle_airfoils = xupper[:, 0] * c + airfoil_le_offset
        yle_airfoils = yupper[:, 0] * c

        x_c_4_airfoils = (xle_airfoils - xte_airfoils) / 4 - airfoil_le_offset
        y_c_4_airfoils = (yle_airfoils - yte_airfoils) / 4
        x_cp_airfoils = 1 * (xle_airfoils -
                             xte_airfoils) / 2 - airfoil_le_offset
        y_cp_airfoils = 1 * (yle_airfoils - yte_airfoils) / 2

        # apply blade twist rotation along rotor radius
        beta = propi.twist_distribution
        xte_twisted = np.cos(beta) * xte_airfoils - np.sin(beta) * yte_airfoils
        yte_twisted = np.sin(beta) * xte_airfoils + np.cos(beta) * yte_airfoils

        x_c_4_twisted = np.cos(beta) * x_c_4_airfoils - np.sin(
            beta) * y_c_4_airfoils
        y_c_4_twisted = np.sin(beta) * x_c_4_airfoils + np.cos(
            beta) * y_c_4_airfoils

        x_cp_twisted = np.cos(beta) * x_cp_airfoils - np.sin(
            beta) * y_cp_airfoils
        y_cp_twisted = np.sin(beta) * x_cp_airfoils + np.cos(
            beta) * y_cp_airfoils

        # transform coordinates from airfoil frame to rotor frame
        xte = np.tile(np.atleast_2d(yte_twisted), (B, 1))
        xte_rotor = np.tile(xte[None, :, :, None],
                            (m, 1, 1, number_of_wake_timesteps))
        yte_rotor = -np.tile(xte_twisted[None, None, :, None],
                             (m, B, 1, 1)) * np.cos(blade_angle_loc +
                                                    total_angle_offset)
        zte_rotor = np.tile(
            xte_twisted[None, None, :, None],
            (m, B, 1, 1)) * np.sin(blade_angle_loc + total_angle_offset)

        r_4d = np.tile(r[None, None, :, None],
                       (m, B, 1, number_of_wake_timesteps))

        x0 = 0
        y0 = r_4d * azi_y
        z0 = r_4d * azi_z

        x_pts0 = x0 + xte_rotor
        y_pts0 = y0 + yte_rotor
        z_pts0 = z0 + zte_rotor

        x_c_4_rotor = x0 - np.tile(y_c_4_twisted[None, None, :, None],
                                   (m, B, 1, number_of_wake_timesteps))
        y_c_4_rotor = y0 + np.tile(
            x_c_4_twisted[None, None, :, None],
            (m, B, 1, number_of_wake_timesteps)) * np.cos(blade_angle_loc +
                                                          total_angle_offset)
        z_c_4_rotor = z0 - np.tile(
            x_c_4_twisted[None, None, :, None],
            (m, B, 1, number_of_wake_timesteps)) * np.sin(blade_angle_loc +
                                                          total_angle_offset)
        x_cp_rotor = x0 - np.tile(y_cp_twisted[None, None, :, None],
                                  (m, B, 1, number_of_wake_timesteps))
        y_cp_rotor = y0 + np.tile(
            x_cp_twisted[None, None, :, None],
            (m, B, 1, number_of_wake_timesteps)) * np.cos(blade_angle_loc +
                                                          total_angle_offset)
        z_cp_rotor = z0 - np.tile(
            x_cp_twisted[None, None, :, None],
            (m, B, 1, number_of_wake_timesteps)) * np.sin(blade_angle_loc +
                                                          total_angle_offset)

        # compute wake contraction, apply to y-z plane
        X_pts0 = x_pts0 + sx_inf
        wake_contraction = compute_wake_contraction_matrix(
            i, propi, Nr, m, number_of_wake_timesteps, X_pts0, propi_outputs)
        Y_pts0 = y_pts0 * wake_contraction + sy_inf
        Z_pts0 = z_pts0 * wake_contraction + sz_inf

        # Rotate wake by thrust angle
        rot_to_body = propi.prop_vel_to_body(
        )  # rotate points into the body frame: [Z,Y,X]' = R*[Z,Y,X]

        # append propeller wake to each of its repeated origins
        X_pts = propi.origin[0][0] + X_pts0 * rot_to_body[
            2, 2] + Z_pts0 * rot_to_body[2, 0]
        Y_pts = propi.origin[0][1] + Y_pts0 * rot_to_body[1, 1]
        Z_pts = propi.origin[0][2] + Z_pts0 * rot_to_body[
            0, 0] + X_pts0 * rot_to_body[0, 2]

        #------------------------------------------------------
        # Account for lifting line panels
        #------------------------------------------------------
        rots = np.array([[np.cos(alpha), 0, np.sin(alpha)], [0, 1, 0],
                         [-np.sin(alpha), 0, np.cos(alpha)]])

        # rotate rotor points to incidence angle
        x_c_4 = x_c_4_rotor * rots[0, 0] + y_c_4_rotor * rots[
            0, 1] + z_c_4_rotor * rots[0, 2]
        y_c_4 = x_c_4_rotor * rots[1, 0] + y_c_4_rotor * rots[
            1, 1] + z_c_4_rotor * rots[1, 2]
        z_c_4 = x_c_4_rotor * rots[2, 0] + y_c_4_rotor * rots[
            2, 1] + z_c_4_rotor * rots[2, 2]

        # prepend points at quarter chord to account for rotor lifting line
        X_pts = np.append(x_c_4[:, :, :, 0][:, :, :, None], X_pts, axis=3)
        Y_pts = np.append(y_c_4[:, :, :, 0][:, :, :, None], Y_pts, axis=3)
        Z_pts = np.append(z_c_4[:, :, :, 0][:, :, :, None], Z_pts, axis=3)

        #------------------------------------------------------
        # Store points
        #------------------------------------------------------
        # ( control point,  prop  , blade number , location on blade, time step )
        if (propi.rotation != None) and (propi.rotation == -1):
            Wmid.WD_XA1[:, i, 0:B, :, :] = X_pts[:, :, :-1, :-1]
            Wmid.WD_YA1[:, i, 0:B, :, :] = Y_pts[:, :, :-1, :-1]
            Wmid.WD_ZA1[:, i, 0:B, :, :] = Z_pts[:, :, :-1, :-1]
            Wmid.WD_XA2[:, i, 0:B, :, :] = X_pts[:, :, :-1, 1:]
            Wmid.WD_YA2[:, i, 0:B, :, :] = Y_pts[:, :, :-1, 1:]
            Wmid.WD_ZA2[:, i, 0:B, :, :] = Z_pts[:, :, :-1, 1:]
            Wmid.WD_XB1[:, i, 0:B, :, :] = X_pts[:, :, 1:, :-1]
            Wmid.WD_YB1[:, i, 0:B, :, :] = Y_pts[:, :, 1:, :-1]
            Wmid.WD_ZB1[:, i, 0:B, :, :] = Z_pts[:, :, 1:, :-1]
            Wmid.WD_XB2[:, i, 0:B, :, :] = X_pts[:, :, 1:, 1:]
            Wmid.WD_YB2[:, i, 0:B, :, :] = Y_pts[:, :, 1:, 1:]
            Wmid.WD_ZB2[:, i, 0:B, :, :] = Z_pts[:, :, 1:, 1:]
        else:
            Wmid.WD_XA1[:, i, 0:B, :, :] = X_pts[:, :, 1:, :-1]
            Wmid.WD_YA1[:, i, 0:B, :, :] = Y_pts[:, :, 1:, :-1]
            Wmid.WD_ZA1[:, i, 0:B, :, :] = Z_pts[:, :, 1:, :-1]
            Wmid.WD_XA2[:, i, 0:B, :, :] = X_pts[:, :, 1:, 1:]
            Wmid.WD_YA2[:, i, 0:B, :, :] = Y_pts[:, :, 1:, 1:]
            Wmid.WD_ZA2[:, i, 0:B, :, :] = Z_pts[:, :, 1:, 1:]
            Wmid.WD_XB1[:, i, 0:B, :, :] = X_pts[:, :, :-1, :-1]
            Wmid.WD_YB1[:, i, 0:B, :, :] = Y_pts[:, :, :-1, :-1]
            Wmid.WD_ZB1[:, i, 0:B, :, :] = Z_pts[:, :, :-1, :-1]
            Wmid.WD_XB2[:, i, 0:B, :, :] = X_pts[:, :, :-1, 1:]
            Wmid.WD_YB2[:, i, 0:B, :, :] = Y_pts[:, :, :-1, 1:]
            Wmid.WD_ZB2[:, i, 0:B, :, :] = Z_pts[:, :, :-1, 1:]

        Wmid.WD_GAMMA[:, i, 0:B, :, :] = Gamma

        # store points for plotting
        VD.Wake.XA1[:, i, 0:B, :, :] = X_pts[:, :, :-1, :-1]
        VD.Wake.YA1[:, i, 0:B, :, :] = Y_pts[:, :, :-1, :-1]
        VD.Wake.ZA1[:, i, 0:B, :, :] = Z_pts[:, :, :-1, :-1]
        VD.Wake.XA2[:, i, 0:B, :, :] = X_pts[:, :, :-1, 1:]
        VD.Wake.YA2[:, i, 0:B, :, :] = Y_pts[:, :, :-1, 1:]
        VD.Wake.ZA2[:, i, 0:B, :, :] = Z_pts[:, :, :-1, 1:]
        VD.Wake.XB1[:, i, 0:B, :, :] = X_pts[:, :, 1:, :-1]
        VD.Wake.YB1[:, i, 0:B, :, :] = Y_pts[:, :, 1:, :-1]
        VD.Wake.ZB1[:, i, 0:B, :, :] = Z_pts[:, :, 1:, :-1]
        VD.Wake.XB2[:, i, 0:B, :, :] = X_pts[:, :, 1:, 1:]
        VD.Wake.YB2[:, i, 0:B, :, :] = Y_pts[:, :, 1:, 1:]
        VD.Wake.ZB2[:, i, 0:B, :, :] = Z_pts[:, :, 1:, 1:]

        # Append wake geometry and vortex strengths to each individual propeller
        propi.Wake_VD.XA1 = VD.Wake.XA1[:, i, 0:B, :, :]
        propi.Wake_VD.YA1 = VD.Wake.YA1[:, i, 0:B, :, :]
        propi.Wake_VD.ZA1 = VD.Wake.ZA1[:, i, 0:B, :, :]
        propi.Wake_VD.XA2 = VD.Wake.XA2[:, i, 0:B, :, :]
        propi.Wake_VD.YA2 = VD.Wake.YA2[:, i, 0:B, :, :]
        propi.Wake_VD.ZA2 = VD.Wake.ZA2[:, i, 0:B, :, :]
        propi.Wake_VD.XB1 = VD.Wake.XB1[:, i, 0:B, :, :]
        propi.Wake_VD.YB1 = VD.Wake.YB1[:, i, 0:B, :, :]
        propi.Wake_VD.ZB1 = VD.Wake.ZB1[:, i, 0:B, :, :]
        propi.Wake_VD.XB2 = VD.Wake.XB2[:, i, 0:B, :, :]
        propi.Wake_VD.YB2 = VD.Wake.YB2[:, i, 0:B, :, :]
        propi.Wake_VD.ZB2 = VD.Wake.ZB2[:, i, 0:B, :, :]
        propi.Wake_VD.GAMMA = Wmid.WD_GAMMA[:, i, 0:B, :, :]

        # append trailing edge locations
        propi.Wake_VD.Xblades_te = X_pts[0, :, :, 0]
        propi.Wake_VD.Yblades_te = Y_pts[0, :, :, 0]
        propi.Wake_VD.Zblades_te = Z_pts[0, :, :, 0]

        # append quarter chord lifting line point locations
        propi.Wake_VD.Xblades_c_4 = x_c_4_rotor
        propi.Wake_VD.Yblades_c_4 = y_c_4_rotor
        propi.Wake_VD.Zblades_c_4 = z_c_4_rotor

        # append three-quarter chord evaluation point locations
        propi.Wake_VD.Xblades_cp = x_cp_rotor  #
        propi.Wake_VD.Yblades_cp = y_cp_rotor  #
        propi.Wake_VD.Zblades_cp = z_cp_rotor  #

        propi.Wake_VD.Xblades_cp2 = X_pts[0, :, :, 0] + (X_pts[0, :, :, 0] -
                                                         X_pts[0, :, :, 1]) / 2
        propi.Wake_VD.Yblades_cp2 = Y_pts[0, :, :, 0] + (Y_pts[0, :, :, 0] -
                                                         Y_pts[0, :, :, 1]) / 2
        propi.Wake_VD.Zblades_cp2 = Z_pts[0, :, :, 0] + (Z_pts[0, :, :, 0] -
                                                         Z_pts[0, :, :, 1]) / 2

    # Compress Data into 1D Arrays
    mat6_size = (m, num_prop * nts * Bmax * nmax)

    WD.XA1 = np.reshape(Wmid.WD_XA1, mat6_size)
    WD.YA1 = np.reshape(Wmid.WD_YA1, mat6_size)
    WD.ZA1 = np.reshape(Wmid.WD_ZA1, mat6_size)
    WD.XA2 = np.reshape(Wmid.WD_XA2, mat6_size)
    WD.YA2 = np.reshape(Wmid.WD_YA2, mat6_size)
    WD.ZA2 = np.reshape(Wmid.WD_ZA2, mat6_size)
    WD.XB1 = np.reshape(Wmid.WD_XB1, mat6_size)
    WD.YB1 = np.reshape(Wmid.WD_YB1, mat6_size)
    WD.ZB1 = np.reshape(Wmid.WD_ZB1, mat6_size)
    WD.XB2 = np.reshape(Wmid.WD_XB2, mat6_size)
    WD.YB2 = np.reshape(Wmid.WD_YB2, mat6_size)
    WD.ZB2 = np.reshape(Wmid.WD_ZB2, mat6_size)
    WD.GAMMA = np.reshape(Wmid.WD_GAMMA, mat6_size)

    return WD, dt, ts, B, Nr
Exemplo n.º 17
0
def write_avl_airfoil_file(suave_airfoil_filename):
    """ This function writes the standard airfoil file format from Airfoil tools 
    to avl file format

    Assumptions:
        None
        
    Source: 
   
    Inputs:
        filename
   
    Outputs:
        airfoil.dat
   
    Properties Used:
        N/A
    """

    # unpack avl_inputs
    avl_airfoil_filename = suave_airfoil_filename.split(".")[-2].split(
        "/")[-1] + '.dat'

    # purge file
    purge_files([avl_airfoil_filename])

    # read airfoil file header
    origin = os.getcwd()
    os_path = os.path.split(origin)[0]
    f_path = os_path + '/' + suave_airfoil_filename
    f = open(f_path)
    data_block = f.readlines()
    f.close()
    airfoil_name = data_block[0].strip()

    # import airfoil coordinates
    airfoil_geometry_data = import_airfoil_geometry([f_path])
    dim = len(airfoil_geometry_data.x_coordinates[0])

    # write file
    with open(avl_airfoil_filename, 'w') as afile:
        afile.write(airfoil_name + "\n")
        for i in range(dim - 1):
            if i == int(dim / 2):
                pass
            elif airfoil_geometry_data.y_coordinates[0][i] < 0.0:
                case_text = '\t' + format(
                    airfoil_geometry_data.x_coordinates[0][i],
                    '.7f') + "   " + format(
                        airfoil_geometry_data.y_coordinates[0][i],
                        '.7f') + "\n"
                afile.write(case_text)
            else:
                case_text = '\t' + format(
                    airfoil_geometry_data.x_coordinates[0][i],
                    '.7f') + "    " + format(
                        airfoil_geometry_data.y_coordinates[0][i],
                        '.7f') + "\n"
                afile.write(case_text)
    afile.close()
    return avl_airfoil_filename
Exemplo n.º 18
0
def main():
    # Define Panelization
    npanel = 200

    # -----------------------------------------------
    # Batch analysis of single airfoil - NACA 2410
    # -----------------------------------------------
    Re_batch = np.atleast_2d(np.array([1E5, 2E5])).T
    AoA_batch = np.atleast_2d(np.linspace(-5, 10, 4) * Units.degrees).T
    airfoil_geometry = compute_naca_4series(0.02, 0.4, 0.1, npoints=npanel)
    airfoil_properties_1 = airfoil_analysis(airfoil_geometry,
                                            AoA_batch,
                                            Re_batch,
                                            npanel,
                                            batch_analysis=True)

    # Plots
    plot_airfoil_analysis_polars(airfoil_properties_1, show_legend=True)
    plot_airfoil_analysis_surface_forces(airfoil_properties_1,
                                         show_legend=True)
    plot_airfoil_analysis_boundary_layer_properties(airfoil_properties_1,
                                                    show_legend=True)

    # XFOIL Validation - Source :
    xfoil_data_Cl = 0.7922
    xfoil_data_Cd = 0.01588
    xfoil_data_Cm = -0.0504

    diff_CL = np.abs(airfoil_properties_1.Cl[2, 0] - xfoil_data_Cl)
    expected_Cl_error = -0.01801472136594917
    print('\nCL difference')
    print(diff_CL)
    assert np.abs(
        ((airfoil_properties_1.Cl[2, 0] - expected_Cl_error) - xfoil_data_Cl) /
        xfoil_data_Cl) < 1e-6

    diff_CD = np.abs(airfoil_properties_1.Cd[2, 0] - xfoil_data_Cd)
    expected_Cd_error = -0.00012125071270768784
    print('\nCD difference')
    print(diff_CD)
    assert np.abs(
        ((airfoil_properties_1.Cd[2, 0] - expected_Cd_error) - xfoil_data_Cd) /
        xfoil_data_Cd) < 1e-6

    diff_CM = np.abs(airfoil_properties_1.Cm[2, 0] - xfoil_data_Cm)
    expected_Cm_error = -0.002782281182096287
    print('\nCM difference')
    print(diff_CM)
    assert np.abs(
        ((airfoil_properties_1.Cm[2, 0] - expected_Cm_error) - xfoil_data_Cm) /
        xfoil_data_Cm) < 1e-6

    # -----------------------------------------------
    # Single Condition Analysis of multiple airfoils
    # -----------------------------------------------
    ospath = os.path.abspath(__file__)
    separator = os.path.sep
    rel_path = ospath.split(
        'airfoil_analysis' + separator + 'airfoil_panel_method_test.py'
    )[0] + 'Vehicles' + separator + 'Airfoils' + separator
    Re_vals = np.atleast_2d(np.array([1E5, 1E5, 1E5, 1E5, 1E5, 1E5])).T
    AoA_vals = np.atleast_2d(np.array([2, 2, 2, 2, 2, 2]) * Units.degrees).T
    airfoil_stations = [0, 1, 0, 1, 0, 1]
    airfoils = [rel_path + 'NACA_4412.txt', rel_path + 'Clark_y.txt']
    airfoil_geometry = import_airfoil_geometry(airfoils, npoints=(npanel + 2))
    airfoil_properties_2 = airfoil_analysis(airfoil_geometry,
                                            AoA_vals,
                                            Re_vals,
                                            npanel,
                                            batch_analysis=False,
                                            airfoil_stations=airfoil_stations)

    True_Cls = np.array([[0.68788905], [0.60906619], [0.68788905],
                         [0.60906619], [0.68788905], [0.60906619]])
    True_Cds = np.array([[0.01015395], [0.00846174], [0.01015395],
                         [0.00846174], [0.01015395], [0.00846174]])
    True_Cms = np.array([[-0.10478782], [-0.08727453], [-0.10478782],
                         [-0.08727453], [-0.10478782], [-0.08727453]])

    print('\n\nSingle Point Validation')
    print('\nCL difference')
    print(np.sum(np.abs((airfoil_properties_2.Cl - True_Cls) / True_Cls)))
    assert np.sum(np.abs(
        (airfoil_properties_2.Cl - True_Cls) / True_Cls)) < 1e-5

    print('\nCD difference')
    print(np.sum(np.abs((airfoil_properties_2.Cd - True_Cds) / True_Cds)))
    assert np.sum(np.abs(
        (airfoil_properties_2.Cd - True_Cds) / True_Cds)) < 1e-5

    print('\nCM difference')
    print(np.sum(np.abs((airfoil_properties_2.Cm - True_Cms) / True_Cms)))
    assert np.sum(np.abs(
        (airfoil_properties_2.Cm - True_Cms) / True_Cms)) < 1e-5
    return
Exemplo n.º 19
0
def generate_lofted_propeller_points(prop):
    """
    Generates nodes on the lofted propeller.

    Inputs:
       prop          Data structure of SUAVE propeller                  [Unitless]

    Outputs:
       N/A

    Properties Used:
       N/A

    Assumptions:
       Quad cell structures for mesh

    Source:
       None

    """
    num_B  = prop.number_of_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_alignment
    t      = prop.max_thickness_distribution
    ta     = prop.orientation_euler_angles[1]
    origin = prop.origin

    try:
        a_o = -prop.start_angle[0]
    except:
        # default is no azimuthal offset (blade 1 starts vertical)
        a_o = 0.0

    n_r       = len(b)                               # number radial points
    n_a_loft  = prop.number_points_around_airfoil    # number points around airfoil
    n_a_cw    = n_a_loft//2                          # number of airfoil chordwise points
    theta     = np.linspace(0,2*np.pi,num_B+1)[:-1]  # azimuthal stations

    # create empty data structure for storing propeller geometries
    G           = Data()
    Gprops      = Data()
    Gprops.n_af = n_a_loft

    rot         = prop.rotation
    flip_1      = (np.pi/2)
    flip_2      = (np.pi/2)

    MCA_2d = np.repeat(np.atleast_2d(MCA).T,n_a_loft,axis=1)
    b_2d   = np.repeat(np.atleast_2d(b).T  ,n_a_loft,axis=1)
    t_2d   = np.repeat(np.atleast_2d(t).T  ,n_a_loft,axis=1)
    r_2d   = np.repeat(np.atleast_2d(r).T  ,n_a_loft,axis=1)

    for i in range(num_B):
        Gprops[i] = Data()
        # get airfoil coordinate geometry
        if a_sec != None:
            airfoil_data   = import_airfoil_geometry(a_sec,npoints=n_a_loft)

            xpts         = np.take(airfoil_data.x_coordinates,a_secl,axis=0)
            zpts         = np.take(airfoil_data.y_coordinates,a_secl,axis=0)
            max_t        = np.take(airfoil_data.thickness_to_chord,a_secl,axis=0)

        else:
            camber       = 0.02
            camber_loc   = 0.4
            thickness    = 0.10
            airfoil_data = compute_naca_4series(camber, camber_loc, thickness,(n_a_loft - 2))
            xpts         = np.repeat(np.atleast_2d(airfoil_data.x_coordinates) ,n_r,axis=0)
            zpts         = np.repeat(np.atleast_2d(airfoil_data.y_coordinates) ,n_r,axis=0)
            max_t        = np.repeat(airfoil_data.thickness_to_chord,n_r,axis=0)

        # store points of airfoil in similar format as Vortex Points (i.e. in vertices)
        max_t2d = np.repeat(np.atleast_2d(max_t).T ,n_a_loft,axis=1)

        airfoil_le_offset = (b[0]/2 - np.repeat(b[:,None], n_a_loft, axis=1)/2 ) # no sweep
        xp      = rot*(- MCA_2d + xpts*b_2d + airfoil_le_offset)  # x coord of airfoil
        yp      = r_2d*np.ones_like(xp)                           # radial location
        zp      = zpts*(t_2d/max_t2d)                             # former airfoil y coord

        matrix = np.zeros((n_r,n_a_loft,3)) # radial location, airfoil pts (same y)
        matrix[:,:,0] = xp
        matrix[:,:,1] = yp
        matrix[:,:,2] = zp


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

        # rotation about x axis to create azimuth locations
        trans_2 = np.array([[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)]   ])
        trans_2 =  np.repeat(trans_2[ np.newaxis,:,: ],n_r,axis=0)

        # rotation about y to orient propeller/rotor to thrust angle
        trans_3 = prop.body_to_prop_vel() #prop.prop_vel_to_body()
        trans_3 =  np.repeat(trans_3[ np.newaxis,:,: ],n_r,axis=0)

        trans   = np.matmul(trans_3,np.matmul(trans_2,trans_1))
        rot_mat = np.repeat(trans[:, np.newaxis,:,:],n_a_loft,axis=1)

        # ---------------------------------------------------------------------------------------------
        # ROTATE POINTS
        mat  =  np.matmul(rot_mat,matrix[...,None]).squeeze()

        # ---------------------------------------------------------------------------------------------
        # store node points
        G.X  = mat[:,:,0] + origin[0][0]
        G.Y  = mat[:,:,1] + origin[0][1]
        G.Z  = mat[:,:,2] + origin[0][2]

        # store cell points
        G.XA1  = mat[:-1,:-1,0] + origin[0][0]
        G.YA1  = mat[:-1,:-1,1] + origin[0][1]
        G.ZA1  = mat[:-1,:-1,2] + origin[0][2]
        G.XA2  = mat[:-1,1:,0]  + origin[0][0]
        G.YA2  = mat[:-1,1:,1]  + origin[0][1]
        G.ZA2  = mat[:-1,1:,2]  + origin[0][2]

        G.XB1  = mat[1:,:-1,0] + origin[0][0]
        G.YB1  = mat[1:,:-1,1] + origin[0][1]
        G.ZB1  = mat[1:,:-1,2] + origin[0][2]
        G.XB2  = mat[1:,1:,0]  + origin[0][0]
        G.YB2  = mat[1:,1:,1]  + origin[0][1]
        G.ZB2  = mat[1:,1:,2]  + origin[0][2]

        # Store G for this blade:
        Gprops[i] = copy.deepcopy(G)

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

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

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

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

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

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

    return prop
Exemplo n.º 21
0
def generate_propeller_geometry(prop, angle_offset=0):
    """This plots the geoemtry of a propeller or rotor

    Assumptions:
    None

    Source:
    None

    Inputs:
    SUAVE.Components.Energy.Converters.Propeller()

    Outputs: 
    Plots

    Properties Used:
    N/A	
    """

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

    airfoil_pts = 20
    dim = len(b)
    num_props = len(prop.origin)
    theta = np.linspace(0, 2 * np.pi, num_B + 1)[:-1]
    if any(r) == None:
        r = np.linspace(Rh, Rt, len(b))

    # create empty arrays for storing geometry
    G = Data()
    G.XA1 = np.zeros((num_props, num_B, dim - 1, (2 * airfoil_pts) - 1))
    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):

        a_o = prop.rotation[n_p] * angle_offset
        flip_1 = (np.pi / 2) * prop.rotation[n_p]
        flip_2 = (np.pi / 2) * prop.rotation[n_p]
        if prop.rotation[n_p] == -1:
            flip_3 = -np.pi
        else:
            flip_3 = 0

        for i in range(num_B):
            # check if airfoils are defined
            if a_sec != None and a_secl != None:
                # check dimension of section
                dim_sec = len(a_secl)
                if dim_sec != dim:
                    raise AssertionError(
                        "Number of sections not equal to number of stations")

                # get airfoil coordinate geometry
                airfoil_data = import_airfoil_geometry(a_sec,
                                                       npoints=airfoil_pts)

            # if no airfoil defined, use NACA 0012
            else:
                airfoil_data = compute_naca_4series(0,
                                                    0,
                                                    12,
                                                    npoints=(airfoil_pts * 2) -
                                                    2)
                a_secl = np.zeros(dim).astype(int)

            # 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
                # iba - imboard airfoil section
                iba_max_t = airfoil_data.thickness_to_chord[a_secl[j]]
                iba_xp = b[j] - MCA[j] - airfoil_data.x_coordinates[
                    a_secl[j]] * b[j]  # x coord of airfoil
                iba_yp = r[j] * np.ones_like(iba_xp)  # radial location
                iba_zp = airfoil_data.y_coordinates[a_secl[j]] * b[j] * (
                    t[j] / iba_max_t)  # former airfoil y coord

                iba_trans_1 = [
                    [np.cos(beta[j] + flip_3), 0, -np.sin(beta[j] + flip_3)],
                    [0, 1, 0],
                    [np.sin(beta[j] + flip_3), 0,
                     np.cos(beta[j] + flip_3)]
                ]

                iba_trans_2 = [[
                    np.cos(theta[i] + a_o), -np.sin(theta[i] + a_o), 0
                ], [np.sin(theta[i] + a_o),
                    np.cos(theta[i] + a_o), 0], [0, 0, 1]]

                iba_trans_3 = [[1, 0, 0], [0,
                                           np.cos(flip_1),
                                           np.sin(flip_1)],
                               [0, np.sin(flip_1),
                                np.cos(flip_1)]]

                iba_trans_4 = [[np.cos(flip_2), -np.sin(flip_2), 0],
                               [np.sin(flip_2),
                                np.cos(flip_2), 0], [0, 0, 1]]

                iba_trans = np.matmul(
                    iba_trans_4,
                    np.matmul(iba_trans_3, np.matmul(iba_trans_2,
                                                     iba_trans_1)))

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

                oba_trans_1 = [[
                    np.cos(beta[j + 1] + flip_3), 0,
                    -np.sin(beta[j + 1] + flip_3)
                ], [0, 1, 0],
                               [
                                   np.sin(beta[j + 1] + flip_3), 0,
                                   np.cos(beta[j + 1] + flip_3)
                               ]]

                oba_trans_2 = [[
                    np.cos(theta[i] + a_o), -np.sin(theta[i] + a_o), 0
                ], [np.sin(theta[i] + a_o),
                    np.cos(theta[i] + a_o), 0], [0, 0, 1]]

                oba_trans_3 = [[1, 0, 0], [0,
                                           np.cos(flip_1),
                                           np.sin(flip_1)],
                               [0, np.sin(flip_1),
                                np.cos(flip_1)]]

                oba_trans_4 = [[np.cos(flip_2), -np.sin(flip_2), 0],
                               [np.sin(flip_2),
                                np.cos(flip_2), 0], [0, 0, 1]]

                oba_trans = np.matmul(
                    oba_trans_4,
                    np.matmul(oba_trans_3, np.matmul(oba_trans_2,
                                                     oba_trans_1)))

                iba_x = np.zeros(len(iba_xp))
                iba_y = np.zeros(len(iba_yp))
                iba_z = np.zeros(len(iba_zp))
                oba_x = np.zeros(len(oba_xp))
                oba_y = np.zeros(len(oba_yp))
                oba_z = np.zeros(len(oba_zp))

                for k in range(len(iba_yp)):
                    iba_vec_1 = [[iba_xp[k]], [iba_yp[k]], [iba_zp[k]]]
                    iba_vec_2 = np.matmul(iba_trans, iba_vec_1)

                    iba_x[k] = iba_vec_2[0]
                    iba_y[k] = iba_vec_2[1]
                    iba_z[k] = iba_vec_2[2]

                    oba_vec_1 = [[oba_xp[k]], [oba_yp[k]], [oba_zp[k]]]
                    oba_vec_2 = np.matmul(oba_trans, oba_vec_1)
                    oba_x[k] = oba_vec_2[0]
                    oba_y[k] = oba_vec_2[1]
                    oba_z[k] = oba_vec_2[2]

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

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

    return G