Source code for RCAIDE.Library.Methods.Aerodynamics.Athena_Vortex_Lattice.create_avl_datastructures

# RCAIDE/Library/Methods/Aerodynamics/Athena_Vortex_Lattice/create_avl_datastructures.py
#  
# Created: Oct 2024, M. Clarke

# ----------------------------------------------------------------------------------------------------------------------
#  IMPORT
# ----------------------------------------------------------------------------------------------------------------------

# RCAIDE imports
import RCAIDE
from RCAIDE.Framework.Core import  Data ,  Units 
from RCAIDE.Library.Components.Wings.Control_Surfaces                                      import Aileron , Elevator , Slat , Flap , Rudder 
from RCAIDE.Library.Methods.Aerodynamics.Athena_Vortex_Lattice.write_avl_airfoil_file      import write_avl_airfoil_file  
from .AVL_Objects.Wing                                                                     import Wing, Section, Control_Surface
from .AVL_Objects.Body                                                                     import Body

# package imports
import  numpy as  np


# ----------------------------------------------------------------------------------------------------------------------
#  create_avl_datastructures
# ----------------------------------------------------------------------------------------------------------------------  
[docs] def translate_avl_wing(rcaide_wing): """ Translates wing geometry from the vehicle setup to AVL format Assumptions: None Source: None Inputs: rcaide_wing.tag [-] rcaide_wing.symmetric [boolean] rcaide_wing.verical [boolean] rcaide_wing - passed into the populate_wing_sections function [data stucture] Outputs: w - aircraft wing in AVL format [data stucture] Properties Used: N/A """ w = Wing() w.tag = rcaide_wing.tag w.symmetric = rcaide_wing.symmetric w.vertical = rcaide_wing.vertical w = populate_wing_sections(w,rcaide_wing) return w
[docs] def translate_avl_body(rcaide_body): """ Translates body geometry from the vehicle setup to AVL format Assumptions: None Source: None Inputs: body.tag [-] rcaide_wing.lengths.total [meters] rcaide_body.lengths.nose [meters] rcaide_body.lengths.tail [meters] rcaide_wing.verical [meters] rcaide_body.width [meters] rcaide_body.heights.maximum [meters] rcaide_wing - passed into the populate_body_sections function [data stucture] Outputs: b - aircraft body in AVL format [data stucture] Properties Used: N/A """ b = Body() b.tag = rcaide_body.tag b.symmetric = True b.lengths.total = rcaide_body.lengths.total b.lengths.nose = rcaide_body.lengths.nose b.lengths.tail = rcaide_body.lengths.tail b.widths.maximum = rcaide_body.width b.heights.maximum = rcaide_body.heights.maximum b = populate_body_sections(b,rcaide_body) return b
[docs] def populate_wing_sections(avl_wing,rcaide_wing): """ Creates sections of wing geometry and populates the AVL wing data structure Assumptions: None Source: None Inputs: avl_wing.symmetric [boolean] rcaide_wing.spans.projected [meters] rcaide_wing.origin [meters] rcaide_wing.dihedral [radians] rcaide_wing.segments.sweeps.leading_edge [radians] rcaide_wing.segments.root_chord_percent [-] rcaide_wing.segments.percent_span_location [-] rcaide_wing.segments.sweeps.quarter_chord [radians] rcaide_wing.segment.twist [radians] Outputs: avl_wing - aircraft wing in AVL format [data stucture] Properties Used: N/A """ # obtain the geometry for each segment in a loop symm = avl_wing.symmetric semispan = rcaide_wing.spans.projected*0.5 * (2 - symm) avl_wing.semispan = semispan root_chord = rcaide_wing.chords.root segments = rcaide_wing.segments segment_names = list(segments.keys()) n_segments = len(segment_names) origin = rcaide_wing.origin if n_segments>0: for i_segs in range(n_segments): current_seg = segment_names[i_segs] if (i_segs == n_segments-1): sweep = 0 else: # This converts all sweeps defined by the quarter chord to leading edge sweep since AVL needs the start of each wing section # from the leading edge coordinate and not the quarter chord coordinate next_seg = segment_names[i_segs+1] if segments[current_seg].sweeps.leading_edge is not None: # If leading edge sweep is defined sweep = segments[current_seg].sweeps.leading_edge else: # If quarter chord sweep is defined, convert it to leading edge sweep sweep_quarter_chord = segments[current_seg].sweeps.quarter_chord chord_fraction = 0.25 segment_root_chord = root_chord*segments[current_seg].root_chord_percent segment_tip_chord = root_chord*segments[next_seg].root_chord_percent segment_span = semispan*(segments[next_seg].percent_span_location - segments[current_seg].percent_span_location ) sweep = np.arctan(((segment_root_chord*chord_fraction) + (np.tan(sweep_quarter_chord )*segment_span - chord_fraction*segment_tip_chord)) /segment_span) dihedral = segments[current_seg].dihedral_outboard # append section section = Section() section.tag = segments[current_seg].tag section.chord = root_chord*segments[current_seg].root_chord_percent section.twist = segments[current_seg].twist/Units.degrees section.origin = origin # first origin in wing root, overwritten by section origin if isinstance(segments[current_seg].airfoil, RCAIDE.Library.Components.Airfoils.Airfoil): if type(segments[current_seg].airfoil) == RCAIDE.Library.Components.Airfoils.NACA_4_Series_Airfoil: section.naca_airfoil = segments[current_seg].airfoil.NACA_4_Series_code else: section.airfoil_coord_file = write_avl_airfoil_file(segments[current_seg].airfoil.coordinate_file) # append section to wing avl_wing.append_section(section) if (i_segs == n_segments-1): return avl_wing else: # condition for the presence of control surfaces in segment if getattr(rcaide_wing,'control_surfaces',False): root_chord_percent = segments[current_seg].root_chord_percent tip_chord_percent = segments[next_seg].root_chord_percent tip_percent_span = segments[next_seg].percent_span_location root_percent_span = segments[current_seg].percent_span_location root_twist = segments[current_seg].twist tip_twist = segments[next_seg].twist tip_airfoil = segments[next_seg].airfoil seg_tag = segments[next_seg].tag # append control surfaces append_avl_wing_control_surfaces(rcaide_wing,avl_wing,semispan,root_chord_percent,tip_chord_percent,tip_percent_span, root_percent_span,root_twist,tip_twist,tip_airfoil,seg_tag,dihedral,origin,sweep) # update origin for next segment segment_percent_span = segments[next_seg].percent_span_location - segments[current_seg].percent_span_location if avl_wing.vertical: inverted_wing = -np.sign(abs(dihedral) - np.pi/2) if inverted_wing == 0: inverted_wing = 1 dz = inverted_wing*semispan*segment_percent_span dy = dz*np.tan(dihedral) l = dz/np.cos(dihedral) dx = l*np.tan(sweep) else: inverted_wing = np.sign(dihedral) if inverted_wing == 0: inverted_wing = 1 dy = inverted_wing*semispan*segment_percent_span dz = dy*np.tan(dihedral) l = dy/np.cos(dihedral) dx = l*np.tan(sweep) origin= [[origin[0][0] + dx , origin[0][1] + dy, origin[0][2] + dz]] else: dihedral = rcaide_wing.dihedral if rcaide_wing.sweeps.leading_edge is not None: sweep = rcaide_wing.sweeps.leading_edge else: sweep_quarter_chord = rcaide_wing.sweeps.quarter_chord chord_fraction = 0.25 segment_root_chord = rcaide_wing.chords.root segment_tip_chord = rcaide_wing.chords.tip segment_span = semispan sweep = np.arctan(((segment_root_chord*chord_fraction) + (np.tan(sweep_quarter_chord )*segment_span - chord_fraction*segment_tip_chord)) /segment_span) avl_wing.semispan = semispan # define root section root_section = Section() root_section.tag = 'root_section' root_section.origin = origin root_section.chord = rcaide_wing.chords.root root_section.twist = rcaide_wing.twists.root/Units.degrees # append control surfaces tip_airfoil = rcaide_wing.airfoil seg_tag = 'section' append_avl_wing_control_surfaces(rcaide_wing,avl_wing,semispan,1.0,rcaide_wing.taper,1.0, 0.0,rcaide_wing.twists.root,rcaide_wing.twists.tip,tip_airfoil,seg_tag,dihedral,origin,sweep) # define tip section tip_section = Section() tip_section.tag = 'tip_section' tip_section.chord = rcaide_wing.chords.tip tip_section.twist = rcaide_wing.twists.tip/Units.degrees # assign location of wing tip if avl_wing.vertical: tip_section.origin = [[origin[0][0]+semispan*np.tan(sweep), origin[0][1]+semispan*np.tan(dihedral), origin[0][2]+semispan]] else: tip_section.origin = [[origin[0][0]+semispan*np.tan(sweep), origin[0][1]+semispan,origin[0][2]+semispan*np.tan(dihedral)]] # assign wing airfoil if (rcaide_wing.airfoil != None) and (isinstance(rcaide_wing.airfoil) == RCAIDE.Library.Components.Airfoils.Airfoil) : root_section.airfoil_coord_file = rcaide_wing.airfoil.coordinate_file tip_section.airfoil_coord_file = rcaide_wing.airfoil.coordinate_file avl_wing.append_section(root_section) avl_wing.append_section(tip_section) return avl_wing
[docs] def append_avl_wing_control_surfaces(rcaide_wing,avl_wing,semispan,root_chord_percent,tip_chord_percent,tip_percent_span, root_percent_span,root_twist,tip_twist,tip_airfoil,seg_tag,dihedral,origin,sweep): """ Converts control surfaces on a rcaide wing to sections in avl wing Assumptions: None Source: None Inputs: rcaide_wing [-] avl_wing [-] semispan [meters] root_chord_percent [unitless] tip_chord_percent [unitless] tip_percent_span [unitless] root_percent_span [unitless] root_twist [radians] tip_twist [radians] tip_airfoil [unitless] seg_tag [unitless] dihedral [radians] origin [meters] sweep [radians] Outputs: None Properties Used: N/A """ root_chord = rcaide_wing.chords.root section_spans = [] for cs in rcaide_wing.control_surfaces: # Create a vectorof all the section breaks from control surfaces on wings. # Section breaks include beginning and end of control surfaces as well as the end of segment control_surface_start = semispan*cs.span_fraction_start control_surface_end = semispan*cs.span_fraction_end if (control_surface_start < semispan*tip_percent_span) and (control_surface_start >= semispan*root_percent_span) : section_spans.append(control_surface_start) if (control_surface_end < semispan*tip_percent_span) and (control_surface_end >= semispan*root_percent_span): section_spans.append(control_surface_end) ordered_section_spans = sorted(list(set(section_spans))) # sort the section_spans in order to create sections in spanwise order num_sections = len(ordered_section_spans) # count the number of sections breaks that the segment will contain \ for section_count in range(num_sections): # create and append sections onto avl wing structure if ordered_section_spans[section_count] == semispan*root_percent_span: # if control surface begins at beginning of segment, redundant section is removed section_tags = list(avl_wing.sections.keys()) del avl_wing.sections[section_tags[-1]] # create section for each break in the wing section = Section() section.tag = seg_tag + '_section_'+ str(ordered_section_spans[section_count]) + 'm' root_section_chord = root_chord*root_chord_percent tip_section_chord = root_chord*tip_chord_percent semispan_section_fraction = (ordered_section_spans[section_count] - semispan*root_percent_span)/(semispan*(tip_percent_span - root_percent_span )) section.chord = np.interp(semispan_section_fraction,[0.,1.],[root_section_chord,tip_section_chord]) root_section_twist = root_twist/Units.degrees tip_section_twist = root_chord*tip_twist/Units.degrees section.twist = np.interp(semispan_section_fraction,[0.,1.],[root_section_twist,tip_section_twist]) # if wing is a vertical wing, the y and z coordinates are swapped if avl_wing.vertical: inverted_wing = -np.sign(abs(dihedral) - np.pi/2) if inverted_wing == 0: inverted_wing = 1 dz = ordered_section_spans[section_count] - inverted_wing*semispan*root_percent_span dy = dz*np.tan(dihedral) l = dz/np.cos(dihedral) dx = l*np.tan(sweep) else: inverted_wing = np.sign(dihedral) if inverted_wing == 0: inverted_wing = 1 dy = ordered_section_spans[section_count] - inverted_wing*semispan*root_percent_span dz = dy*np.tan(dihedral) l = dy/np.cos(dihedral) dx = l*np.tan(sweep) section.origin = [[origin[0][0] + dx , origin[0][1] + dy, origin[0][2] + dz]] # this loop appends all the control surfaces within a particular wing section for index , ctrl_surf in enumerate(rcaide_wing.control_surfaces): if (semispan*ctrl_surf.span_fraction_start == ordered_section_spans[section_count]) or (ordered_section_spans[section_count] == semispan*ctrl_surf.span_fraction_end): c = Control_Surface() c.tag = ctrl_surf.tag # name of control surface c.sign_duplicate = '+1' # this float indicates control surface deflection symmetry c.x_hinge = 1 - ctrl_surf.chord_fraction # this float is the % location of the control surface hinge on the wing c.deflection = ctrl_surf.deflection / Units.degrees c.order = index # if control surface is an aileron, the deflection is asymmetric. This is standard convention from AVL if (type(ctrl_surf) == Aileron): c.sign_duplicate = '-1' c.function = 'aileron' c.gain = -1.0 # if control surface is a slat, the hinge is taken from the leading edge elif (type(ctrl_surf) == Slat): c.x_hinge = -ctrl_surf.chord_fraction c.function = 'slat' c.gain = -1.0 elif (type(ctrl_surf) == Flap): c.function = 'flap' c.gain = 1.0 elif (type(ctrl_surf) == Elevator): c.function = 'elevator' c.gain = 1.0 elif (type(ctrl_surf) == Rudder): c.function = 'rudder' c.gain = 1.0 else: raise AttributeError("Define control surface function as 'slat', 'flap', 'elevator' , 'aileron' or 'rudder'") section.append_control_surface(c) elif (semispan*ctrl_surf.span_fraction_start < ordered_section_spans[section_count]) and (ordered_section_spans[section_count] < semispan*ctrl_surf.span_fraction_end): c = Control_Surface() c.tag = ctrl_surf.tag # name of control surface c.sign_duplicate = '+1' # this float indicates control surface deflection symmetry c.x_hinge = 1 - ctrl_surf.chord_fraction # this float is the % location of the control surface hinge on the wing c.deflection = ctrl_surf.deflection / Units.degrees c.order = index # if control surface is an aileron, the deflection is asymmetric. This is standard convention from AVL if (type(ctrl_surf) == Aileron): c.sign_duplicate = '-1' c.function = 'aileron' c.gain = -1.0 # if control surface is a slat, the hinge is taken from the leading edge elif (type(ctrl_surf) == Slat): c.x_hinge = -ctrl_surf.chord_fraction c.function = 'slat' c.gain = -1.0 elif (type(ctrl_surf) == Flap): c.function = 'flap' c.gain = 1.0 elif (type(ctrl_surf) == Elevator): c.function = 'elevator' c.gain = 1.0 elif (type(ctrl_surf) == Rudder): c.function = 'rudder' c.gain = 1.0 else: raise AttributeError("Define control surface function as 'slat', 'flap', 'elevator' , 'aileron' or 'rudder'") section.append_control_surface(c) if isinstance(tip_airfoil,RCAIDE.Library.Components.Airfoils.Airfoil): if type(tip_airfoil) == RCAIDE.Library.Components.Airfoils.NACA_4_Series_Airfoil: section.naca_airfoil = tip_airfoil.NACA_4_Series_code else: section.airfoil_coord_file = write_avl_airfoil_file(tip_airfoil.coordinate_file) avl_wing.append_section(section) return
[docs] def populate_body_sections(avl_body,rcaide_body): """ Creates sections of body geometry and populates the AVL body data structure Assumptions: None Source: None Inputs: avl_wing.symmetric [boolean] avl_body.widths.maximum [meters] avl_body.heights.maximum [meters] rcaide_body.fineness.nose [meters] rcaide_body.fineness.tail [meters] avl_body.lengths.total [meters] avl_body.lengths.nose [meters] avl_body.lengths.tail [meters] Outputs: avl_body - aircraft body in AVL format [data stucture] Properties Used: N/A """ symm = avl_body.symmetric semispan_h = avl_body.widths.maximum * 0.5 * (2 - symm) semispan_v = avl_body.heights.maximum * 0.5 origin = rcaide_body.origin[0] # Compute the curvature of the nose/tail given fineness ratio. Curvature is derived from general quadratic equation # This method relates the fineness ratio to the quadratic curve formula via a spline fit interpolation vec1 = [2 , 1.5, 1.2 , 1] vec2 = [1 ,1.57 , 3.2, 8] x = np.linspace(0,1,4) fuselage_nose_curvature = np.interp(np.interp(rcaide_body.fineness.nose,vec2,x), x , vec1) fuselage_tail_curvature = np.interp(np.interp(rcaide_body.fineness.tail,vec2,x), x , vec1) # Horizontal Sections of Fuselage if semispan_h != 0.0: width_array = np.linspace(-semispan_h, semispan_h, num=11,endpoint=True) for section_width in width_array: fuselage_h_section = Section() fuselage_h_section_cabin_length = avl_body.lengths.total - (avl_body.lengths.nose + avl_body.lengths.tail) fuselage_h_section_nose_length = ((1 - ((abs(section_width/semispan_h))**fuselage_nose_curvature ))**(1/fuselage_nose_curvature))*avl_body.lengths.nose fuselage_h_section_tail_length = ((1 - ((abs(section_width/semispan_h))**fuselage_tail_curvature ))**(1/fuselage_tail_curvature))*avl_body.lengths.tail fuselage_h_section_nose_origin = avl_body.lengths.nose - fuselage_h_section_nose_length fuselage_h_section.tag = 'fuselage_horizontal_section_at_' + str(section_width) + '_m' fuselage_h_section.origin = [ origin[0] + fuselage_h_section_nose_origin , origin[1] + section_width, origin[2]] fuselage_h_section.chord = fuselage_h_section_cabin_length + fuselage_h_section_nose_length + fuselage_h_section_tail_length avl_body.append_section(fuselage_h_section,'horizontal') # Vertical Sections of Fuselage if semispan_v != 0: height_array = np.linspace(-semispan_v, semispan_v, num=11,endpoint=True) for section_height in height_array : fuselage_v_section = Section() fuselage_v_section_cabin_length = avl_body.lengths.total - (avl_body.lengths.nose + avl_body.lengths.tail) fuselage_v_section_nose_length = ((1 - ((abs(section_height/semispan_v))**fuselage_nose_curvature ))**(1/fuselage_nose_curvature))*avl_body.lengths.nose fuselage_v_section_tail_length = ((1 - ((abs(section_height/semispan_v))**fuselage_tail_curvature ))**(1/fuselage_tail_curvature))*avl_body.lengths.tail fuselage_v_section_nose_origin = avl_body.lengths.nose - fuselage_v_section_nose_length fuselage_v_section.tag = 'fuselage_vertical_top_section_at_' + str(section_height) + '_m' fuselage_v_section.origin = [ origin[0] + fuselage_v_section_nose_origin, origin[1], origin[2] + section_height ] fuselage_v_section.chord = fuselage_v_section_cabin_length + fuselage_v_section_nose_length + fuselage_v_section_tail_length avl_body.append_section(fuselage_v_section,'vertical') return avl_body