def __defaults__(self): self.tag = ' U.S. Standard Atmosphere (1976)' # break point data: self.fluid_properties = Air() self.planet = Earth() self.breaks = Data() self.breaks.altitude = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.breaks.temperature = np.array( [301.15 , 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, 186.95]) # K self.breaks.pressure = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.breaks.density = np.array( [1.47808e0, 1.2250e0, 3.63918e-1, 8.80349e-2, 1.32250e-2, 1.42753e-3, 8.61606e-4, 6.42099e-5, 6.95792e-6]) # kg/m^3
def __defaults__(self): self.tag = 'Fuel Cell' self.propellant = Gaseous_H2() self.oxidizer = Air() self.MaxPower = 0. #maximum power fuel cell is capable of outputting [W] """ self.tag='Fuel_Cell' self.type='H2' #only uses a Hydrogen fuel cell for now self.Power=0 self.SpecificPower = 2.08 # kW/kg self.eff=0.6 #stack efficiency self.MassDensity =1203.208556 # kg/m^3 self.Volume = 0.034 # m^3 self.Fuel = Propellant() """ print self.propellant.tag if self.propellant.tag == 'H2 Gas': self.efficiency = .65 #normal fuel cell operating efficiency at sea level self.SpecificPower = 2.08 #specific power of fuel cell [kW/kg]; default is Nissan 2011 level self.MassDensity = 1203.208556 #take default as specs from Nissan 2011 fuel cell self.Volume = .034 else: self.SpecificPower = 0.0 self.eff = 0.0 self.MassDensity = 0.0 self.Volume = 0.0
def __defaults__(self): """This sets the default values for the component to function. Assumptions: None Source: Some default values come from a Nissan 2011 fuel cell Inputs: None Outputs: None Properties Used: None """ self.propellant = Gaseous_H2() self.oxidizer = Air() self.efficiency = .65 # normal fuel cell operating efficiency at sea level self.specific_power = 2.08 * Units.kW / Units.kg # specific power of fuel cell [kW/kg]; default is Nissan 2011 level self.mass_density = 1203.208556 * Units.kg / Units.m**3. # take default as specs from Nissan 2011 fuel cell self.volume = 0.0 self.max_power = 0.0 self.discharge_model = zero_fidelity
def __defaults__(self): """This sets the default values at breaks in the atmosphere. Assumptions: Constant temperature Source: U.S. Standard Atmosphere (1976 version) Inputs: None Outputs: None Properties Used: None """ self.fluid_properties = Air() self.planet = Earth() self.breaks = Data() self.breaks.altitude = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.breaks.temperature = np.array( [301.15 , 301.15, 301.15, 301.15, 301.15, 301.15, 301.15, 301.15, 301.15]) # K self.breaks.pressure = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.breaks.density = np.array( [1.545586 , 1.2256523,.273764, .0662256, 0.0105000 , 1.3415E-03, 8.0971E-04, 4.78579E-05, 4.51674E-06]) #kg/m^3
def __defaults__(self): self.fluid_properties = Air() self.planet = Earth() self.breaks = Data() self.breaks.altitude = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.breaks.temperature = np.array( [301.15 , 301.15, 301.15, 301.15, 301.15, 301.15, 301.15, 301.15, 301.15]) # K self.breaks.pressure = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.breaks.density = np.array( [1.545586 , 1.2256523,.273764, .0662256, 0.0105000 , 1.3415E-03, 8.0971E-04, 4.78579E-05, 4.51674E-06]) #kg/m^3
def __defaults__(self): self.propellant = Gaseous_H2() self.oxidizer = Air() self.efficiency = .65 #normal fuel cell operating efficiency at sea level self.specific_power = 2.08 * Units.kW / Units.kg #specific power of fuel cell [kW/kg]; default is Nissan 2011 level self.mass_density = 1203.208556 * Units.kg / Units.m**3. #take default as specs from Nissan 2011 fuel cell self.volume = 0.0 self.max_power = 0.0 self.discharge_model = zero_fidelity
def __defaults__(self): self.tag = ' U.S. Standard Atmosphere (1976)' # break point data: self.gas = Air() self.planet = Earth() self.z_breaks = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.T_breaks = np.array( [301.15 , 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, 186.95]) # K self.p_breaks = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.rho_breaks = np.array( [1.47808e0, 1.2250e0, 3.63918e-1, 8.80349e-2, 1.32250e-2, 1.42753e-3, 8.61606e-4, 6.42099e-5, 6.95792e-6]) # kg/m^3
def __defaults__(self): self.tag = 'Fuel Cell' self.propellant = Gaseous_H2() self.oxidizer = Air() self.type='H2' #only uses a Hydrogen fuel cell for now self.Ncell=0.0 #number of fuel cells in the stack self.A=875. # area of the fuel cell interface (cm^2) self.Prat=2. # fuel cell compressor ratio self.MassDensity = 0.0, # kg/m^3 self.SpecificPower = 0.0, # kW/kg self.Volume = 0.0 # m^3 self.rhoc=1988. # fuel cell density in kg/m^3 self.zeta=.6 # porosity coefficient self.twall=.0022224 # thickness of cell wall in meters if self.propellant.tag=='H2 Gas'or 'H2': self.r=2.45E-4 # area specific resistance [k-Ohm-cm^2] self.Eoc=.931 # effective activation energy (V) self.A1=.03 # slope of the Tafel line (models activation losses) (V) self.m=1.05E-4 # constant in mass-transfer overvoltage equation (V) self.n=8E-3 # constant in mass-transfer overvoltage equation
def __defaults__(self): self.tag = 'Generic_Lithium_Ion_Battery_Cell' self.cell = Data() self.module = Data() self.pack_config = Data() self.module_config = Data() self.age = 0 # [days] self.cell.mass = None self.cell.charging_SOC_cutoff = 1. self.cell.charging_current = 3.0 # [Amps] self.cell.charging_voltage = 3 # [Volts] self.convective_heat_transfer_coefficient = 35. # [W/m^2K] self.heat_transfer_efficiency = 1.0 self.pack_config.series = 1 self.pack_config.parallel = 1 self.pack_config.total = 1 self.module_config.total = 1 self.module_config.normal_count = 1 # number of cells normal to flow self.module_config.parallel_count = 1 # number of cells parallel to flow self.module_config.normal_spacing = 0.02 self.module_config.parallel_spacing = 0.02 self.cooling_fluid = Air() self.cooling_fluid.cooling_flowspeed = 0.01 # defaults that are overwritten if specific cell chemistry is used self.specific_energy = 200. * Units.Wh / Units.kg self.specific_power = 1. * Units.kW / Units.kg self.ragone.const_1 = 88.818 * Units.kW / Units.kg self.ragone.const_2 = -.01533 / (Units.Wh / Units.kg) self.ragone.lower_bound = 60. * Units.Wh / Units.kg self.ragone.upper_bound = 225. * Units.Wh / Units.kg return
# Modified: # ---------------------------------------------------------------------- # Imports # ---------------------------------------------------------------------- # local imports from compressible_turbulent_flat_plate import compressible_turbulent_flat_plate # suave imports from compressible_turbulent_flat_plate import compressible_turbulent_flat_plate from SUAVE.Attributes.Gases import Air # you should let the user pass this as input from SUAVE.Attributes.Results.Result import Result air = Air() compute_speed_of_sound = air.compute_speed_of_sound # python imports import os, sys, shutil from copy import deepcopy from warnings import warn # package imports import numpy as np import scipy as sp # ---------------------------------------------------------------------- # The Function # ----------------------------------------------------------------------
class US_Standard_1976(Atmosphere): """ Implements the U.S. Standard Atmosphere (1976 version) """ def __defaults__(self): self.tag = ' U.S. Standard Atmosphere (1976)' # break point data: self.gas = Air() self.planet = Earth() self.z_breaks = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.T_breaks = np.array( [301.15 , 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, 186.95]) # K self.p_breaks = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.rho_breaks = np.array( [1.47808e0, 1.2250e0, 3.63918e-1, 8.80349e-2, 1.32250e-2, 1.42753e-3, 8.61606e-4, 6.42099e-5, 6.95792e-6]) # kg/m^3 def compute_values(self,altitude,type="all"): """ Computes values from the International Standard Atmosphere Inputs: altitude : geometric altitude (elevation) (m) can be a float, list or 1D array of floats Outputs: list of conditions - pressure : static pressure (Pa) temperature : static temperature (K) density : density (kg/m^3) speed_of_sound : speed of sound (m/s) viscosity : viscosity (kg/m-s) Example: atmosphere = SUAVE.Attributes.Atmospheres.Earth.USStandard1976() atmosphere.ComputeValues(1000).pressure """ # unpack zs = altitude gas = self.gas grav = self.planet.sea_level_gravity Rad = self.planet.mean_radius # return options all_vars = ["all", "everything"] pressure = ["p", "pressure"] temp = ["t", "temp", "temperature"] density = ["rho", "density"] speed_of_sound = ["speed_of_sound", "a"] viscosity = ["viscosity", "mew"] # some up-front logic for outputs based on thermo need_p = True; need_T = True; need_rho = True; need_a = True; need_mew = True; if type.lower() in pressure: need_T = False; need_rho = False; need_a = False; need_mew = False elif type.lower() in temp: need_p = False; need_rho = False; need_a = False; need_mew = False elif type.lower() in density: need_a = False; need_mew = False elif type.lower() in speed_of_sound: need_p = False; need_rho = False; need_mew = False elif type.lower() in viscosity: need_p = False; need_rho = False; need_a = False # convert input if necessary if isinstance(zs, int): zs = np.array([float(zs)]) elif isinstance(zs, float): zs = np.array([zs]) # convert geometric to geopotential altitude zs = zs/(1 + zs/Rad) # initialize return data p = np.array([]) T = np.array([]) rho = np.array([]) a = np.array([]) mew = np.array([]) # evaluate at each altitude for z in zs: # check altitude range too_low = False; too_high = False if (z < self.z_breaks[0]): too_low = True print "Warning: altitude requested below minimum for this atmospheric model; returning values for h = -2.0 km" elif (z > self.z_breaks[-1]): too_high = True print "Warning: altitude requested above maximum for this atmospheric model; returning values for h = 86.0 km" else: # loop through breaks for i in range(len(self.z_breaks)): if z >= self.z_breaks[i] and z < self.z_breaks[i+1]: z0 = self.z_breaks[i]; T0 = self.T_breaks[i]; p0 = self.p_breaks[i] alpha = -(self.T_breaks[i+1] - self.T_breaks[i])/ \ (self.z_breaks[i+1] - self.z_breaks[i]) # lapse rate K/km dz = z - z0 break # pressure if need_p: if too_low: pz = self.p_breaks[0] elif too_high: pz = self.p_breaks[-1] else: if alpha == 0.0: pz = p0*np.exp(-1*dz*grav/(gas.R*T0)) else: pz = p0*((1 - alpha*dz/T0)**(1*grav/(alpha*gas.R))) p = np.append(p,pz) # temperature if need_T: if too_low: Tz = self.T_breaks[0] elif too_high: Tz = self.T_breaks[-1] else: Tz = T0 - dz*alpha # note: alpha = lapse rate (negative) T = np.append(T,Tz) if need_rho: if too_low: rho = np.append(rho,self.rho_breaks[0]) elif too_high: rho = np.append(rho,self.rho_breaks[-1]) else: rho = np.append(rho,self.gas.compute_density(Tz,pz)) if need_a: a = np.append(a,self.gas.compute_speed_of_sound(Tz)) if need_mew: mew = np.append(mew,self.gas.compute_absolute_viscosity(Tz)) # for each altitude # return requested data if type.lower() in all_vars: return (p, T, rho, a, mew) if type.lower() in pressure: return p elif type.lower() in temp: return T elif type.lower() in density: return rho elif type.lower() in speed_of_sound: return a elif type.lower() in viscosity: return mew else: raise Exception , "Unknown atmosphere data type, " + type
def __defaults__(self): self.tag = 'Propulsor' self.breathing = True self.gas = Air()
class US_Standard_1976(Atmosphere): """ Implements the U.S. Standard Atmosphere (1976 version) """ def __defaults__(self): self.tag = ' U.S. Standard Atmosphere (1976)' # break point data: self.fluid_properties = Air() self.planet = Earth() self.breaks = Data() self.breaks.altitude = np.array( [-2.00 , 0.00, 11.00, 20.00, 32.00, 47.00, 51.00, 71.00, 84.852]) * Units.km # m, geopotential altitude self.breaks.temperature = np.array( [301.15 , 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, 186.95]) # K self.breaks.pressure = np.array( [127774.0 , 101325.0, 22632.1, 5474.89, 868.019, 110.906, 66.9389, 3.95642, 0.3734]) # Pa self.breaks.density = np.array( [1.47808e0, 1.2250e0, 3.63918e-1, 8.80349e-2, 1.32250e-2, 1.42753e-3, 8.61606e-4, 6.42099e-5, 6.95792e-6]) # kg/m^3 def compute_values(self,altitude,type="all"): """ Computes values from the International Standard Atmosphere Inputs: altitude : geometric altitude (elevation) (m) can be a float, list or 1D array of floats Outputs: list of conditions - pressure : static pressure (Pa) temperature : static temperature (K) density : density (kg/m^3) speed_of_sound : speed of sound (m/s) viscosity : viscosity (kg/m-s) Example: atmosphere = SUAVE.Attributes.Atmospheres.Earth.USStandard1976() atmosphere.ComputeValues(1000).pressure """ # unpack zs = altitude gas = self.fluid_properties grav = self.planet.sea_level_gravity Rad = self.planet.mean_radius # return options all_vars = ["all", "everything"] pressure = ["p", "pressure"] temp = ["t", "temp", "temperature"] density = ["rho", "density"] speed_of_sound = ["speed_of_sound", "a"] viscosity = ["viscosity", "mew"] # some up-front logic for outputs based on thermo need_p = True; need_T = True; need_rho = True; need_a = True; need_mew = True; if type.lower() in pressure: need_T = False; need_rho = False; need_a = False; need_mew = False elif type.lower() in temp: need_p = False; need_rho = False; need_a = False; need_mew = False elif type.lower() in density: need_a = False; need_mew = False elif type.lower() in speed_of_sound: need_p = False; need_rho = False; need_mew = False elif type.lower() in viscosity: need_p = False; need_rho = False; need_a = False # convert input if necessary if isinstance(zs, int): zs = np.array([float(zs)]) elif isinstance(zs, float): zs = np.array([zs]) # convert geometric to geopotential altitude zs = zs/(1 + zs/Rad) # initialize return data p = np.array([]) T = np.array([]) rho = np.array([]) a = np.array([]) mew = np.array([]) # evaluate at each altitude for z in zs: # check altitude range too_low = False; too_high = False if (z < self.breaks.altitude[0]): too_low = True print "Warning: altitude requested below minimum for this atmospheric model; returning values for h = -2.0 km" elif (z > self.breaks.altitude[-1]): too_high = True print "Warning: altitude requested above maximum for this atmospheric model; returning values for h = 86.0 km" else: # loop through breaks for i in range(len(self.breaks.altitude)): if z >= self.breaks.altitude[i] and z < self.breaks.altitude[i+1]: z0 = self.breaks.altitude[i]; T0 = self.breaks.temperature[i]; p0 = self.breaks.pressure[i] alpha = -(self.breaks.temperature[i+1] - self.breaks.temperature[i])/ \ (self.breaks.altitude[i+1] - self.breaks.altitude[i]) # lapse rate K/km dz = z - z0 break # pressure if need_p: if too_low: pz = self.breaks.pressure[0] elif too_high: pz = self.breaks.pressure[-1] else: if alpha == 0.0: pz = p0*np.exp(-1*dz*grav/(gas.gas_specific_constant*T0)) else: pz = p0*((1 - alpha*dz/T0)**(1*grav/(alpha*gas.gas_specific_constant))) p = np.append(p,pz) # temperature if need_T: if too_low: Tz = self.breaks.temperature[0] elif too_high: Tz = self.breaks.temperature[-1] else: Tz = T0 - dz*alpha # note: alpha = lapse rate (negative) T = np.append(T,Tz) if need_rho: if too_low: rho = np.append(rho,self.breaks.density[0]) elif too_high: rho = np.append(rho,self.breaks.density[-1]) else: rho = np.append(rho,self.fluid_properties.compute_density(Tz,pz)) if need_a: a = np.append(a,self.fluid_properties.compute_speed_of_sound(Tz)) if need_mew: mew = np.append(mew,self.fluid_properties.compute_absolute_viscosity(Tz)) # for each altitude # return requested data if type.lower() in all_vars: return (p, T, rho, a, mew) if type.lower() in pressure: return p elif type.lower() in temp: return T elif type.lower() in density: return rho elif type.lower() in speed_of_sound: return a elif type.lower() in viscosity: return mew else: raise Exception , "Unknown atmosphere data type, " + type
def setup_vehicle(): # ------------------------------------------------------------------ # Initialize the Vehicle # ------------------------------------------------------------------ # Create a vehicle and set level properties vehicle = SUAVE.Vehicle() vehicle.tag = 'eVTOL' # ------------------------------------------------------------------ # Vehicle-level Properties # ------------------------------------------------------------------ # mass properties vehicle.mass_properties.takeoff = 2500. * Units.lb vehicle.mass_properties.operating_empty = 2150. * Units.lb vehicle.mass_properties.max_takeoff = 2500. * Units.lb vehicle.mass_properties.max_payload = 100. * Units.lb vehicle.mass_properties.center_of_gravity = [[2.0, 0., 0.]] # I made this up # basic parameters vehicle.envelope.ultimate_load = 5.7 vehicle.envelope.limit_load = 3. # ------------------------------------------------------------------ # WINGS # ------------------------------------------------------------------ # WING PROPERTIES wing = SUAVE.Components.Wings.Main_Wing() wing.tag = 'main_wing' wing.origin = [[1.5, 0., -0.5]] wing.spans.projected = 35.0 * Units.feet wing.chords.root = 3.25 * Units.feet # Segment segment = SUAVE.Components.Wings.Segment() segment.tag = 'Root' segment.percent_span_location = 0. segment.twist = 0. segment.root_chord_percent = 1.5 segment.dihedral_outboard = 1.0 * Units.degrees segment.sweeps.quarter_chord = 8.5 * Units.degrees segment.thickness_to_chord = 0.18 wing.Segments.append(segment) # Segment segment = SUAVE.Components.Wings.Segment() segment.tag = 'Section_2' segment.percent_span_location = 0.227 segment.twist = 0. segment.root_chord_percent = 1. segment.dihedral_outboard = 1.0 * Units.degrees segment.sweeps.quarter_chord = 0.0 * Units.degrees segment.thickness_to_chord = 0.12 wing.Segments.append(segment) # Segment segment = SUAVE.Components.Wings.Segment() segment.tag = 'Tip' segment.percent_span_location = 1.0 segment.twist = 0. segment.root_chord_percent = 1.0 segment.dihedral_outboard = 0.0 * Units.degrees segment.sweeps.quarter_chord = 0.0 * Units.degrees segment.thickness_to_chord = 0.12 wing.Segments.append(segment) # Fill out more segment properties automatically wing = segment_properties(wing) wing = wing_segmented_planform(wing) ## ALSO SET THE VEHICLE REFERENCE AREA vehicle.reference_area = wing.areas.reference # add to vehicle vehicle.append_component(wing) # Add a horizontal tail # WING PROPERTIES wing = SUAVE.Components.Wings.Horizontal_Tail() wing.tag = 'horizontal_tail' wing.areas.reference = 2.0 wing.taper = 0.5 wing.sweeps.quarter_chord = 20. * Units.degrees wing.aspect_ratio = 5.0 wing.thickness_to_chord = 0.12 wing.dihedral = 5. * Units.degrees wing.origin = [[5.5, 0.0, 0.65]] # Fill out more segment properties automatically wing = wing_planform(wing) # add to vehicle vehicle.append_component(wing) # Add a vertical tail wing = SUAVE.Components.Wings.Vertical_Tail() wing.tag = 'vertical_tail' wing.areas.reference = 1.0 wing.taper = 0.5 wing.sweeps.quarter_chord = 30 * Units.degrees wing.aspect_ratio = 2.5 wing.thickness_to_chord = 0.12 wing.origin = [[5.5, 0.0, 0.65]] # Fill out more segment properties automatically wing = wing_planform(wing) # add to vehicle vehicle.append_component(wing) # Add a fuseelage # --------------------------------------------------------------- # FUSELAGE # --------------------------------------------------------------- # FUSELAGE PROPERTIES fuselage = SUAVE.Components.Fuselages.Fuselage() fuselage.tag = 'fuselage' fuselage.seats_abreast = 2. fuselage.fineness.nose = 0.88 fuselage.fineness.tail = 1.13 fuselage.lengths.nose = 3.2 * Units.feet fuselage.lengths.tail = 6.4 * Units.feet fuselage.lengths.cabin = 6.4 * Units.feet fuselage.lengths.total = 6.0 fuselage.width = 5.85 * Units.feet fuselage.heights.maximum = 4.65 * Units.feet fuselage.heights.at_quarter_length = 3.75 * Units.feet fuselage.heights.at_wing_root_quarter_chord = 4.65 * Units.feet fuselage.heights.at_three_quarters_length = 4.26 * Units.feet fuselage.areas.wetted = 236. * Units.feet**2 fuselage.areas.front_projected = 0.14 * Units.feet**2 fuselage.effective_diameter = 5.85 * Units.feet fuselage.differential_pressure = 0. # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_0' segment.percent_x_location = 0. segment.percent_z_location = -0.05 segment.height = 0.1 segment.width = 0.1 fuselage.Segments.append(segment) # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_1' segment.percent_x_location = 0.06 segment.percent_z_location = -0.05 segment.height = 0.52 segment.width = 0.75 fuselage.Segments.append(segment) # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_2' segment.percent_x_location = 0.25 segment.percent_z_location = -.01 segment.height = 1.2 segment.width = 1.43 fuselage.Segments.append(segment) # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_3' segment.percent_x_location = 0.475 segment.percent_z_location = 0 segment.height = 1.4 segment.width = 1.4 fuselage.Segments.append(segment) # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_4' segment.percent_x_location = 0.75 segment.percent_z_location = 0.06 segment.height = 0.6 segment.width = 0.4 fuselage.Segments.append(segment) # Segment segment = SUAVE.Components.Lofted_Body_Segment.Segment() segment.tag = 'segment_5' segment.percent_x_location = 1. segment.percent_z_location = 0.1 segment.height = 0.05 segment.width = 0.05 fuselage.Segments.append(segment) # add to vehicle vehicle.append_component(fuselage) #------------------------------------------------------------------- # Booms #------------------------------------------------------------------- # Add booms for the motors boom = SUAVE.Components.Fuselages.Fuselage() boom.tag = 'boom_R' boom.origin = [[0.525, 3.0, -0.35]] boom.lengths.nose = 0.2 boom.lengths.tail = 0.2 boom.lengths.total = 4 boom.width = 0.15 boom.heights.maximum = 0.15 boom.heights.at_quarter_length = 0.15 boom.heights.at_three_quarters_length = 0.15 boom.heights.at_wing_root_quarter_chord = 0.15 boom.effective_diameter = 0.15 boom.areas.wetted = 2 * np.pi * (0.075) * 3.5 boom.areas.front_projected = np.pi * 0.15 boom.fineness.nose = 0.15 / 0.2 boom.fineness.tail = 0.15 / 0.2 vehicle.append_component(boom) # Now attach the mirrored boom other_boom = deepcopy(boom) other_boom.origin[0][1] = -boom.origin[0][1] other_boom.tag = 'boom_L' vehicle.append_component(other_boom) #------------------------------------------------------------------ # Network #------------------------------------------------------------------ net = SUAVE.Components.Energy.Networks.Lift_Cruise() net.number_of_lift_rotor_engines = 4 net.number_of_propeller_engines = 1 net.identical_propellers = True net.identical_lift_rotors = True net.voltage = 400. #------------------------------------------------------------------ # Electronic Speed Controller #------------------------------------------------------------------ lift_rotor_esc = SUAVE.Components.Energy.Distributors.Electronic_Speed_Controller( ) lift_rotor_esc.efficiency = 0.95 net.lift_rotor_esc = lift_rotor_esc propeller_esc = SUAVE.Components.Energy.Distributors.Electronic_Speed_Controller( ) propeller_esc.efficiency = 0.95 net.propeller_esc = propeller_esc #------------------------------------------------------------------ # Payload #------------------------------------------------------------------ payload = SUAVE.Components.Energy.Peripherals.Avionics() payload.power_draw = 0. net.payload = payload #------------------------------------------------------------------ # Avionics #------------------------------------------------------------------ avionics = SUAVE.Components.Energy.Peripherals.Avionics() avionics.power_draw = 300. * Units.watts net.avionics = avionics #------------------------------------------------------------------ # Design Battery #------------------------------------------------------------------ bat = SUAVE.Components.Energy.Storages.Batteries.Constant_Mass.Lithium_Ion_LiNiMnCoO2_18650( ) bat.mass_properties.mass = 1000. * Units.lb bat.max_voltage = net.voltage initialize_from_mass(bat) net.battery = bat #------------------------------------------------------------------ # Design Rotors and Propellers #------------------------------------------------------------------ # The tractor propeller propeller = SUAVE.Components.Energy.Converters.Propeller() propeller.origin = [[0, 0, -0.325]] propeller.number_of_blades = 3 propeller.tip_radius = 0.9 propeller.hub_radius = 0.1 propeller.angular_velocity = 2200 * Units.rpm propeller.freestream_velocity = 100. * Units.knots propeller.design_Cl = 0.7 propeller.design_altitude = 5000. * Units.feet propeller.design_thrust = 500. * Units.lbf propeller.airfoil_geometry = ['./Airfoils/NACA_4412.txt'] propeller.airfoil_polars = [[ './Airfoils/Polars/NACA_4412_polar_Re_50000.txt', './Airfoils/Polars/NACA_4412_polar_Re_100000.txt', './Airfoils/Polars/NACA_4412_polar_Re_200000.txt', './Airfoils/Polars/NACA_4412_polar_Re_500000.txt', './Airfoils/Polars/NACA_4412_polar_Re_1000000.txt' ]] propeller.airfoil_polar_stations = np.zeros((20), dtype=np.int8).tolist() propeller = propeller_design(propeller) net.propellers.append(propeller) # The lift rotors lift_rotor = SUAVE.Components.Energy.Converters.Lift_Rotor() lift_rotor.tip_radius = 1.5 lift_rotor.hub_radius = 0.15 lift_rotor.number_of_blades = 4 lift_rotor.design_tip_mach = 0.65 lift_rotor.freestream_velocity = 500. * Units['ft/min'] lift_rotor.angular_velocity = lift_rotor.design_tip_mach * Air( ).compute_speed_of_sound() / lift_rotor.tip_radius lift_rotor.design_Cl = 0.7 lift_rotor.design_altitude = 3000. * Units.feet lift_rotor.design_thrust = 2500 * Units.lbf / 4 lift_rotor.variable_pitch = False lift_rotor.airfoil_geometry = ['./Airfoils/NACA_4412.txt'] lift_rotor.airfoil_polars = [[ './Airfoils/Polars/NACA_4412_polar_Re_50000.txt', './Airfoils/Polars/NACA_4412_polar_Re_100000.txt', './Airfoils/Polars/NACA_4412_polar_Re_200000.txt', './Airfoils/Polars/NACA_4412_polar_Re_500000.txt', './Airfoils/Polars/NACA_4412_polar_Re_1000000.txt' ]] lift_rotor.airfoil_polar_stations = np.zeros((20), dtype=np.int8).tolist() lift_rotor = propeller_design(lift_rotor) # Appending rotors with different origins rotations = [1, -1, -1, 1] origins = [[0.6, 3., -0.125], [4.5, 3., -0.125], [0.6, -3., -0.125], [4.5, -3., -0.125]] for ii in range(4): lift_rotor = deepcopy(lift_rotor) lift_rotor.tag = 'lift_rotor' lift_rotor.rotation = rotations[ii] lift_rotor.origin = [origins[ii]] net.lift_rotors.append(lift_rotor) #------------------------------------------------------------------ # Design Motors #------------------------------------------------------------------ # Propeller (Thrust) motor propeller_motor = SUAVE.Components.Energy.Converters.Motor() propeller_motor.efficiency = 0.95 propeller_motor.nominal_voltage = bat.max_voltage propeller_motor.mass_properties.mass = 2.0 * Units.kg propeller_motor.origin = propeller.origin propeller_motor.propeller_radius = propeller.tip_radius propeller_motor.no_load_current = 2.0 propeller_motor = size_optimal_motor(propeller_motor, propeller) net.propeller_motors.append(propeller_motor) # Rotor (Lift) Motor lift_rotor_motor = SUAVE.Components.Energy.Converters.Motor() lift_rotor_motor.efficiency = 0.85 lift_rotor_motor.nominal_voltage = bat.max_voltage * 3 / 4 lift_rotor_motor.mass_properties.mass = 3. * Units.kg lift_rotor_motor.origin = lift_rotor.origin lift_rotor_motor.propeller_radius = lift_rotor.tip_radius lift_rotor_motor.gearbox_efficiency = 1.0 lift_rotor_motor.no_load_current = 4.0 lift_rotor_motor = size_optimal_motor(lift_rotor_motor, lift_rotor) for _ in range(4): lift_rotor_motor = deepcopy(lift_rotor_motor) lift_rotor_motor.tag = 'motor' net.lift_rotor_motors.append(lift_rotor_motor) vehicle.append_component(net) # Now account for things that have been overlooked for now: vehicle.excrescence_area = 0.1 return vehicle
def compute_values(self,altitude,temperature=288.15): """Computes atmospheric values. Assumptions: Constant temperature atmosphere Source: U.S. Standard Atmosphere, 1976, U.S. Government Printing Office, Washington, D.C., 1976 Inputs: altitude [m] temperature [K] Outputs: atmo_data. pressure [Pa] temperature [K] speed_of_sound [m/s] dynamic_viscosity [kg/(m*s)] Properties Used: self. fluid_properties.gas_specific_constant [J/(kg*K)] planet.sea_level_gravity [m/s^2] planet.mean_radius [m] breaks. altitude [m] pressure [Pa] """ # unpack zs = altitude gas = self.fluid_properties planet = self.planet grav = self.planet.sea_level_gravity Rad = self.planet.mean_radius R = gas.gas_specific_constant # check properties if not gas == Air(): warn('Constant_Temperature Atmosphere not using Air fluid properties') if not planet == Earth(): warn('Constant_Temperature Atmosphere not using Earth planet properties') # convert input if necessary zs = atleast_2d_col(zs) # get model altitude bounds zmin = self.breaks.altitude[0] zmax = self.breaks.altitude[-1] # convert geometric to geopotential altitude zs = zs/(1 + zs/Rad) # check ranges if np.amin(zs) < zmin: print "Warning: altitude requested below minimum for this atmospheric model; returning values for h = -2.0 km" zs[zs < zmin] = zmin if np.amax(zs) > zmax: print "Warning: altitude requested above maximum for this atmospheric model; returning values for h = 86.0 km" zs[zs > zmax] = zmax # initialize return data zeros = np.zeros_like(zs) p = zeros * 0.0 T = zeros * 0.0 rho = zeros * 0.0 a = zeros * 0.0 mu = zeros * 0.0 z0 = zeros * 0.0 T0 = zeros * 0.0 p0 = zeros * 0.0 alpha = zeros * 0.0 # populate the altitude breaks # this uses >= and <= to capture both edges and because values should be the same at the edges for i in range( len(self.breaks.altitude)-1 ): i_inside = (zs >= self.breaks.altitude[i]) & (zs <= self.breaks.altitude[i+1]) z0[ i_inside ] = self.breaks.altitude[i] T0[ i_inside ] = temperature p0[ i_inside ] = self.breaks.pressure[i] self.breaks.temperature[i+1]=temperature alpha[ i_inside ] = -(temperature - temperature)/ \ (self.breaks.altitude[i+1] - self.breaks.altitude[i]) # interpolate the breaks dz = zs-z0 i_isoth = (alpha == 0.) p = p0* np.exp(-1.*dz*grav/(R*T0)) T = temperature rho = gas.compute_density(T,p) a = gas.compute_speed_of_sound(T) mu = gas.compute_absolute_viscosity(T) atmo_data = Conditions() atmo_data.expand_rows(zs.shape[0]) atmo_data.pressure = p atmo_data.temperature = T atmo_data.density = rho atmo_data.speed_of_sound = a atmo_data.dynamic_viscosity = mu return atmo_data
def compute_values(self,altitude,temperature_deviation = 0): """ Computes values from the International Standard Atmosphere Inputs: altitude : geometric altitude (elevation) (m) can be a float, list or 1D array of floats temperature_deviation : delta_isa Outputs: list of conditions - pressure : static pressure (Pa) temperature : static temperature (K) density : density (kg/m^3) speed_of_sound : speed of sound (m/s) dynamic_viscosity : dynamic_viscosity (kg/m-s) Example: atmosphere = SUAVE.Attributes.Atmospheres.Earth.USStandard1976() atmosphere.ComputeValues(1000).pressure """ # unpack zs = altitude gas = self.fluid_properties planet = self.planet grav = self.planet.sea_level_gravity Rad = self.planet.mean_radius gamma = gas.gas_specific_constant delta_isa = temperature_deviation # check properties if not gas == Air(): warn('US Standard Atmosphere not using Air fluid properties') if not planet == Earth(): warn('US Standard Atmosphere not using Earth planet properties') # convert input if necessary zs = atleast_2d_col(zs) # get model altitude bounds zmin = self.breaks.altitude[0] zmax = self.breaks.altitude[-1] # convert geometric to geopotential altitude zs = zs/(1 + zs/Rad) # check ranges if np.amin(zs) < zmin: print "Warning: altitude requested below minimum for this atmospheric model; returning values for h = -2.0 km" zs[zs < zmin] = zmin if np.amax(zs) > zmax: print "Warning: altitude requested above maximum for this atmospheric model; returning values for h = 86.0 km" zs[zs > zmax] = zmax # initialize return data zeros = np.zeros_like(zs) p = zeros * 0.0 T = zeros * 0.0 rho = zeros * 0.0 a = zeros * 0.0 mew = zeros * 0.0 z0 = zeros * 0.0 T0 = zeros * 0.0 p0 = zeros * 0.0 alpha = zeros * 0.0 # populate the altitude breaks # this uses >= and <= to capture both edges and because values should be the same at the edges for i in range( len(self.breaks.altitude)-1 ): i_inside = (zs >= self.breaks.altitude[i]) & (zs <= self.breaks.altitude[i+1]) z0[ i_inside ] = self.breaks.altitude[i] T0[ i_inside ] = self.breaks.temperature[i] p0[ i_inside ] = self.breaks.pressure[i] alpha[ i_inside ] = -(self.breaks.temperature[i+1] - self.breaks.temperature[i])/ \ (self.breaks.altitude[i+1] - self.breaks.altitude[i]) # interpolate the breaks dz = zs-z0 i_isoth = (alpha == 0.) i_adiab = (alpha != 0.) p[i_isoth] = p0[i_isoth] * np.exp(-1.*dz[i_isoth]*grav/(gamma*T0[i_isoth])) p[i_adiab] = p0[i_adiab] * ( (1.-alpha[i_adiab]*dz[i_adiab]/T0[i_adiab]) **(1.*grav/(alpha[i_adiab]*gamma)) ) T = T0 - dz*alpha + delta_isa rho = gas.compute_density(T,p) a = gas.compute_speed_of_sound(T) mew = gas.compute_absolute_viscosity(T) atmo_data = Conditions() atmo_data.expand_rows(zs.shape[0]) atmo_data.pressure = p atmo_data.temperature = T atmo_data.density = rho atmo_data.speed_of_sound = a atmo_data.dynamic_viscosity = mew return atmo_data
def compute_values(self,altitude,temperature_deviation=0.0,var_gamma=False): """Computes atmospheric values. Assumptions: US 1976 Standard Atmosphere Source: U.S. Standard Atmosphere, 1976, U.S. Government Printing Office, Washington, D.C., 1976 Inputs: altitude [m] temperature_deviation [K] Output: atmo_data. pressure [Pa] temperature [K] speed_of_sound [m/s] dynamic_viscosity [kg/(m*s)] kinematic_viscosity [m^2/s] thermal_conductivity [W/(m*K)] prandtl_number [-] Properties Used: self. fluid_properties.gas_specific_constant [J/(kg*K)] planet.sea_level_gravity [m/s^2] planet.mean_radius [m] breaks. altitude [m] temperature [K] pressure [Pa] """ # unpack zs = altitude gas = self.fluid_properties planet = self.planet grav = self.planet.sea_level_gravity Rad = self.planet.mean_radius R = gas.gas_specific_constant delta_isa = temperature_deviation # check properties if not gas == Air(): warn('US Standard Atmosphere not using Air fluid properties') if not planet == Earth(): warn('US Standard Atmosphere not using Earth planet properties') # convert input if necessary zs = atleast_2d_col(zs) # get model altitude bounds zmin = self.breaks.altitude[0] zmax = self.breaks.altitude[-1] # convert geometric to geopotential altitude zs = zs/(1 + zs/Rad) # check ranges if np.amin(zs) < zmin: print("Warning: altitude requested below minimum for this atmospheric model; returning values for h = -2.0 km") zs[zs < zmin] = zmin if np.amax(zs) > zmax: print("Warning: altitude requested above maximum for this atmospheric model; returning values for h = 86.0 km") zs[zs > zmax] = zmax # initialize return data zeros = np.zeros_like(zs) p = zeros * 0.0 T = zeros * 0.0 rho = zeros * 0.0 a = zeros * 0.0 mu = zeros * 0.0 z0 = zeros * 0.0 T0 = zeros * 0.0 p0 = zeros * 0.0 alpha = zeros * 0.0 # populate the altitude breaks # this uses >= and <= to capture both edges and because values should be the same at the edges for i in range( len(self.breaks.altitude)-1 ): i_inside = (zs >= self.breaks.altitude[i]) & (zs <= self.breaks.altitude[i+1]) z0[ i_inside ] = self.breaks.altitude[i] T0[ i_inside ] = self.breaks.temperature[i] p0[ i_inside ] = self.breaks.pressure[i] alpha[ i_inside ] = -(self.breaks.temperature[i+1] - self.breaks.temperature[i])/ \ (self.breaks.altitude[i+1] - self.breaks.altitude[i]) # interpolate the breaks dz = zs-z0 i_isoth = (alpha == 0.) i_adiab = (alpha != 0.) p[i_isoth] = p0[i_isoth] * np.exp(-1.*dz[i_isoth]*grav/(R*T0[i_isoth])) p[i_adiab] = p0[i_adiab] * ( (1.-alpha[i_adiab]*dz[i_adiab]/T0[i_adiab]) **(1.*grav/(alpha[i_adiab]*R)) ) T = T0 - dz*alpha + delta_isa rho = gas.compute_density(T,p) a = gas.compute_speed_of_sound(T,p,var_gamma) mu = gas.compute_absolute_viscosity(T) K = gas.compute_thermal_conductivity(T) Pr = gas.compute_prandtl_number(T) atmo_data = Conditions() atmo_data.expand_rows(zs.shape[0]) atmo_data.pressure = p atmo_data.temperature = T atmo_data.density = rho atmo_data.speed_of_sound = a atmo_data.dynamic_viscosity = mu atmo_data.kinematic_viscosity = mu/rho atmo_data.thermal_conductivity = K atmo_data.prandtl_number = Pr return atmo_data
def vehicle_setup(): """This is the full physical definition of the vehicle, and is designed to be independent of the analyses that are selected.""" # ------------------------------------------------------------------ # Initialize the Vehicle # ------------------------------------------------------------------ vehicle = SUAVE.Vehicle() vehicle.tag = 'Boeing_737-800' # ------------------------------------------------------------------ # Vehicle-level Properties # ------------------------------------------------------------------ # Vehicle level mass properties # The maximum takeoff gross weight is used by a number of methods, most notably the weight # method. However, it does not directly inform mission analysis. vehicle.mass_properties.max_takeoff = 79015.8 * Units.kilogram # The takeoff weight is used to determine the weight of the vehicle at the start of the mission vehicle.mass_properties.takeoff = 79015.8 * Units.kilogram # Operating empty may be used by various weight methods or other methods. Importantly, it does # not constrain the mission analysis directly, meaning that the vehicle weight in a mission # can drop below this value if more fuel is needed than is available. vehicle.mass_properties.operating_empty = 62746.4 * Units.kilogram # The maximum zero fuel weight is also used by methods such as weights vehicle.mass_properties.max_zero_fuel = 62732.0 * Units.kilogram # Cargo weight typically feeds directly into weights output and does not affect the mission vehicle.mass_properties.cargo = 10000. * Units.kilogram # Envelope properties # These values are typical FAR values for a transport of this type vehicle.envelope.ultimate_load = 3.75 vehicle.envelope.limit_load = 2.5 # Vehicle level parameters # The vehicle reference area typically matches the main wing reference area vehicle.reference_area = 124.862 * Units['meters**2'] # Number of passengers, control settings, and accessories settings are used by the weights # methods vehicle.passengers = 170 vehicle.systems.control = "fully powered" vehicle.systems.accessories = "medium range" # ------------------------------------------------------------------ # Landing Gear # ------------------------------------------------------------------ # The settings here can be used for noise analysis, but are not used in this tutorial landing_gear = SUAVE.Components.Landing_Gear.Landing_Gear() landing_gear.tag = "main_landing_gear" landing_gear.main_tire_diameter = 1.12000 * Units.m landing_gear.nose_tire_diameter = 0.6858 * Units.m landing_gear.main_strut_length = 1.8 * Units.m landing_gear.nose_strut_length = 1.3 * Units.m landing_gear.main_units = 2 # Number of main landing gear landing_gear.nose_units = 1 # Number of nose landing gear landing_gear.main_wheels = 2 # Number of wheels on the main landing gear landing_gear.nose_wheels = 2 # Number of wheels on the nose landing gear vehicle.landing_gear = landing_gear # ------------------------------------------------------------------ # Main Wing # ------------------------------------------------------------------ # This main wing is approximated as a simple trapezoid. A segmented wing can also be created if # desired. Segmented wings appear in later tutorials, and a version of the 737 with segmented # wings can be found in the SUAVE testing scripts. # SUAVE allows conflicting geometric values to be set in terms of items such as aspect ratio # when compared with span and reference area. Sizing scripts may be used to enforce # consistency if desired. wing = SUAVE.Components.Wings.Main_Wing() wing.tag = 'main_wing' wing.aspect_ratio = 10.18 # Quarter chord sweep is used as the driving sweep in most of the low fidelity analysis methods. # If a different known value (such as leading edge sweep) is given, it should be converted to # quarter chord sweep and added here. In some cases leading edge sweep will be used directly as # well, and can be entered here too. wing.sweeps.quarter_chord = 25 * Units.deg wing.thickness_to_chord = 0.1 wing.taper = 0.1 wing.spans.projected = 34.32 * Units.meter wing.chords.root = 7.760 * Units.meter wing.chords.tip = 0.782 * Units.meter wing.chords.mean_aerodynamic = 4.235 * Units.meter wing.areas.reference = 124.862 * Units['meters**2'] wing.twists.root = 4.0 * Units.degrees wing.twists.tip = 0.0 * Units.degrees wing.origin = [[13.61, 0, -1.27]] * Units.meter wing.vertical = False wing.symmetric = True # The high lift flag controls aspects of maximum lift coefficient calculations wing.high_lift = True # The dynamic pressure ratio is used in stability calculations wing.dynamic_pressure_ratio = 1.0 # ------------------------------------------------------------------ # Main Wing Control Surfaces # ------------------------------------------------------------------ # Information in this section is used for high lift calculations and when conversion to AVL # is desired. # Deflections will typically be specified separately in individual vehicle configurations. flap = SUAVE.Components.Wings.Control_Surfaces.Flap() flap.tag = 'flap' flap.span_fraction_start = 0.20 flap.span_fraction_end = 0.70 flap.deflection = 0.0 * Units.degrees # Flap configuration types are used in computing maximum CL and noise flap.configuration_type = 'double_slotted' flap.chord_fraction = 0.30 wing.append_control_surface(flap) slat = SUAVE.Components.Wings.Control_Surfaces.Slat() slat.tag = 'slat' slat.span_fraction_start = 0.324 slat.span_fraction_end = 0.963 slat.deflection = 0.0 * Units.degrees slat.chord_fraction = 0.1 wing.append_control_surface(slat) aileron = SUAVE.Components.Wings.Control_Surfaces.Aileron() aileron.tag = 'aileron' aileron.span_fraction_start = 0.7 aileron.span_fraction_end = 0.963 aileron.deflection = 0.0 * Units.degrees aileron.chord_fraction = 0.16 wing.append_control_surface(aileron) # Add to vehicle vehicle.append_component(wing) # ------------------------------------------------------------------ # Horizontal Stabilizer # ------------------------------------------------------------------ wing = SUAVE.Components.Wings.Horizontal_Tail() wing.tag = 'horizontal_stabilizer' wing.aspect_ratio = 6.16 wing.sweeps.quarter_chord = 40.0 * Units.deg wing.thickness_to_chord = 0.08 wing.taper = 0.2 wing.spans.projected = 14.2 * Units.meter wing.chords.root = 4.7 * Units.meter wing.chords.tip = 0.955 * Units.meter wing.chords.mean_aerodynamic = 3.0 * Units.meter wing.areas.reference = 32.488 * Units['meters**2'] wing.twists.root = 3.0 * Units.degrees wing.twists.tip = 3.0 * Units.degrees wing.origin = [[32.83 * Units.meter, 0, 1.14 * Units.meter]] wing.vertical = False wing.symmetric = True wing.dynamic_pressure_ratio = 0.9 # Add to vehicle vehicle.append_component(wing) # ------------------------------------------------------------------ # Vertical Stabilizer # ------------------------------------------------------------------ wing = SUAVE.Components.Wings.Vertical_Tail() wing.tag = 'vertical_stabilizer' wing.aspect_ratio = 1.91 wing.sweeps.quarter_chord = 25. * Units.deg wing.thickness_to_chord = 0.08 wing.taper = 0.25 wing.spans.projected = 7.777 * Units.meter wing.chords.root = 8.19 * Units.meter wing.chords.tip = 0.95 * Units.meter wing.chords.mean_aerodynamic = 4.0 * Units.meter wing.areas.reference = 27.316 * Units['meters**2'] wing.twists.root = 0.0 * Units.degrees wing.twists.tip = 0.0 * Units.degrees wing.origin = [[28.79 * Units.meter, 0, 1.54 * Units.meter]] # meters wing.vertical = True wing.symmetric = False # The t tail flag is used in weights calculations wing.t_tail = False wing.dynamic_pressure_ratio = 1.0 # Add to vehicle vehicle.append_component(wing) # ------------------------------------------------------------------ # Fuselage # ------------------------------------------------------------------ fuselage = SUAVE.Components.Fuselages.Fuselage() fuselage.tag = 'fuselage' # Number of coach seats is used in some weights methods fuselage.number_coach_seats = vehicle.passengers # The seats abreast can be used along with seat pitch and the number of coach seats to # determine the length of the cabin if desired. fuselage.seats_abreast = 6 fuselage.seat_pitch = 1 * Units.meter # Fineness ratios are used to determine VLM fuselage shape and sections to use in OpenVSP # output fuselage.fineness.nose = 1.6 fuselage.fineness.tail = 2. # Nose and tail lengths are used in the VLM setup fuselage.lengths.nose = 6.4 * Units.meter fuselage.lengths.tail = 8.0 * Units.meter fuselage.lengths.total = 38.02 * Units.meter # Fore and aft space are added to the cabin length if the fuselage is sized based on # number of seats fuselage.lengths.fore_space = 6. * Units.meter fuselage.lengths.aft_space = 5. * Units.meter fuselage.width = 3.74 * Units.meter fuselage.heights.maximum = 3.74 * Units.meter fuselage.effective_diameter = 3.74 * Units.meter fuselage.areas.side_projected = 142.1948 * Units['meters**2'] fuselage.areas.wetted = 446.718 * Units['meters**2'] fuselage.areas.front_projected = 12.57 * Units['meters**2'] # Maximum differential pressure between the cabin and the atmosphere fuselage.differential_pressure = 5.0e4 * Units.pascal # Heights at different longitudinal locations are used in stability calculations and # in output to OpenVSP fuselage.heights.at_quarter_length = 3.74 * Units.meter fuselage.heights.at_three_quarters_length = 3.65 * Units.meter fuselage.heights.at_wing_root_quarter_chord = 3.74 * Units.meter # add to vehicle vehicle.append_component(fuselage) # ------------------------------------------------------------------ # Nacelles # ------------------------------------------------------------------ nacelle = SUAVE.Components.Nacelles.Nacelle() nacelle.tag = 'nacelle_1' nacelle.length = 2.71 nacelle.inlet_diameter = 1.90 nacelle.diameter = 2.05 nacelle.areas.wetted = 1.1 * np.pi * nacelle.diameter * nacelle.length nacelle.origin = [[13.72, -4.86, -1.9]] nacelle.flow_through = True nacelle_airfoil = SUAVE.Components.Airfoils.Airfoil() nacelle_airfoil.naca_4_series_airfoil = '2410' nacelle.append_airfoil(nacelle_airfoil) nacelle_2 = deepcopy(nacelle) nacelle_2.tag = 'nacelle_2' nacelle_2.origin = [[13.72, 4.86, -1.9]] vehicle.append_component(nacelle) vehicle.append_component(nacelle_2) # ------------------------------------------------------------------ # Turboelectric HTS Ducted Fan Network # ------------------------------------------------------------------ # Instantiate the Turboelectric HTS Ducted Fan Network # This also instantiates the component parts of the efan network, then below each part has its properties modified so they are no longer the default properties as created here at instantiation. efan = Turboelectric_HTS_Ducted_Fan() efan.tag = 'turbo_fan' # Outline of Turboelectric drivetrain components. These are populated below. # 1. Propulsor Ducted_fan # 1.1 Ram # 1.2 Inlet Nozzle # 1.3 Fan Nozzle # 1.4 Fan # 1.5 Thrust # 2. Motor # 3. Powersupply # 4. ESC # 5. Rotor # 6. Lead # 7. CCS # 8. Cryocooler # 9. Heat Exchanger # The components are then sized # ------------------------------------------------------------------ #Component 1 - Ducted Fan efan.ducted_fan = SUAVE.Components.Energy.Networks.Ducted_Fan() efan.ducted_fan.tag = 'ducted_fan' efan.ducted_fan.number_of_engines = 12. efan.number_of_engines = efan.ducted_fan.number_of_engines efan.ducted_fan.engine_length = 1.1 * Units.meter # Positioning variables for the propulsor locations - xStart = 15.0 xSpace = 1.0 yStart = 3.0 ySpace = 1.8 efan.ducted_fan.origin = [ [xStart + xSpace * 5, -(yStart + ySpace * 5), -2.0], [xStart + xSpace * 4, -(yStart + ySpace * 4), -2.0], [xStart + xSpace * 3, -(yStart + ySpace * 3), -2.0], [xStart + xSpace * 2, -(yStart + ySpace * 2), -2.0], [xStart + xSpace * 1, -(yStart + ySpace * 1), -2.0], [xStart + xSpace * 0, -(yStart + ySpace * 0), -2.0], [xStart + xSpace * 5, (yStart + ySpace * 5), -2.0], [xStart + xSpace * 4, (yStart + ySpace * 4), -2.0], [xStart + xSpace * 3, (yStart + ySpace * 3), -2.0], [xStart + xSpace * 2, (yStart + ySpace * 2), -2.0], [xStart + xSpace * 1, (yStart + ySpace * 1), -2.0], [xStart + xSpace * 0, (yStart + ySpace * 0), -2.0] ] # meters # copy the ducted fan details to the turboelectric ducted fan network to enable drag calculations efan.engine_length = efan.ducted_fan.engine_length efan.origin = efan.ducted_fan.origin # working fluid efan.ducted_fan.working_fluid = SUAVE.Attributes.Gases.Air() # ------------------------------------------------------------------ # Component 1.1 - Ram # to convert freestream static to stagnation quantities # instantiate ram = SUAVE.Components.Energy.Converters.Ram() ram.tag = 'ram' # add to the network efan.ducted_fan.append(ram) # ------------------------------------------------------------------ # Component 1.2 - Inlet Nozzle # instantiate inlet_nozzle = SUAVE.Components.Energy.Converters.Compression_Nozzle() inlet_nozzle.tag = 'inlet_nozzle' # setup inlet_nozzle.polytropic_efficiency = 0.98 inlet_nozzle.pressure_ratio = 0.98 # add to network efan.ducted_fan.append(inlet_nozzle) # ------------------------------------------------------------------ # Component 1.3 - Fan Nozzle # instantiate fan_nozzle = SUAVE.Components.Energy.Converters.Expansion_Nozzle() fan_nozzle.tag = 'fan_nozzle' # setup fan_nozzle.polytropic_efficiency = 0.95 fan_nozzle.pressure_ratio = 0.99 # add to network efan.ducted_fan.append(fan_nozzle) # ------------------------------------------------------------------ # Component 1.4 - Fan # instantiate fan = SUAVE.Components.Energy.Converters.Fan() fan.tag = 'fan' # setup fan.polytropic_efficiency = 0.93 fan.pressure_ratio = 1.7 # add to network efan.ducted_fan.append(fan) # ------------------------------------------------------------------ # Component 1.5 : thrust # To compute the thrust thrust = SUAVE.Components.Energy.Processes.Thrust() thrust.tag = 'compute_thrust' # total design thrust (includes all the propulsors) thrust.total_design = 2. * 24000. * Units.N #Newtons # design sizing conditions altitude = 35000.0 * Units.ft mach_number = 0.78 isa_deviation = 0. # add to network efan.ducted_fan.thrust = thrust # ------------------------------------------------------------------ # Component 2 : HTS motor efan.motor = SUAVE.Components.Energy.Converters.Motor_Lo_Fid() efan.motor.tag = 'motor' # number_of_motors is not used as the motor count is assumed to match the engine count # Set the origin of each motor to match its ducted fan efan.motor.origin = efan.ducted_fan.origin efan.motor.gear_ratio = 1.0 efan.motor.gearbox_efficiency = 1.0 efan.motor.motor_efficiency = 0.96 # ------------------------------------------------------------------ # Component 3 - Powersupply efan.powersupply = SUAVE.Components.Energy.Converters.Turboelectric() efan.powersupply.tag = 'powersupply' efan.number_of_powersupplies = 2. efan.powersupply.propellant = SUAVE.Attributes.Propellants.Jet_A() efan.powersupply.oxidizer = Air() efan.powersupply.number_of_engines = 2.0 # number of turboelectric machines, not propulsors efan.powersupply.efficiency = .37 # Approximate average gross efficiency across the product range. efan.powersupply.volume = 2.36 * Units.m**3. # 3m long from RB211 datasheet. 1m estimated radius. efan.powersupply.rated_power = 37400.0 * Units.kW efan.powersupply.mass_properties.mass = 2500.0 * Units.kg # 2.5 tonnes from Rolls Royce RB211 datasheet 2013. efan.powersupply.specific_power = efan.powersupply.rated_power / efan.powersupply.mass_properties.mass efan.powersupply.mass_density = efan.powersupply.mass_properties.mass / efan.powersupply.volume # ------------------------------------------------------------------ # Component 4 - Electronic Speed Controller (ESC) efan.esc = SUAVE.Components.Energy.Distributors.HTS_DC_Supply( ) # Could make this where the ESC is defined as a Siemens SD104 efan.esc.tag = 'esc' efan.esc.efficiency = 0.95 # Siemens SD104 SiC Power Electronicss reported to be this efficient # ------------------------------------------------------------------ # Component 5 - HTS rotor (part of the propulsor motor) efan.rotor = SUAVE.Components.Energy.Converters.Motor_HTS_Rotor() efan.rotor.tag = 'rotor' efan.rotor.temperature = 50.0 # [K] efan.rotor.skin_temp = 300.0 # [K] Temp of rotor outer surface is not ambient efan.rotor.current = 1000.0 # [A] Most of the cryoload will scale with this number if not using HTS Dynamo efan.rotor.resistance = 0.0001 # [ohm] 20 x 100 nOhm joints should be possible (2uOhm total) so 1mOhm is an overestimation. efan.rotor.number_of_engines = efan.ducted_fan.number_of_engines efan.rotor.length = 0.573 * Units.meter # From paper: DOI:10.2514/6.2019-4517 Would be good to estimate this from power instead. efan.rotor.diameter = 0.310 * Units.meter # From paper: DOI:10.2514/6.2019-4517 Would be good to estimate this from power instead. rotor_end_area = np.pi * (efan.rotor.diameter / 2.0)**2.0 rotor_end_circumference = np.pi * efan.rotor.diameter efan.rotor.surface_area = 2.0 * rotor_end_area + efan.rotor.length * rotor_end_circumference efan.rotor.R_value = 125.0 # [K.m2/W] 2.0 W/m2 based on experience at Robinson Research # ------------------------------------------------------------------ # Component 6 - Copper Supply Leads of propulsion motor rotors efan.lead = SUAVE.Components.Energy.Distributors.Cryogenic_Lead() efan.lead.tag = 'lead' copper = Copper() efan.lead.cold_temp = efan.rotor.temperature # [K] efan.lead.hot_temp = efan.rotor.skin_temp # [K] efan.lead.current = efan.rotor.current # [A] efan.lead.length = 0.3 # [m] efan.lead.material = copper efan.leads = efan.ducted_fan.number_of_engines * 2.0 # Each motor has two leads to make a complete circuit # ------------------------------------------------------------------ # Component 7 - Rotor Constant Current Supply (CCS) efan.ccs = SUAVE.Components.Energy.Distributors.HTS_DC_Supply() efan.ccs.tag = 'ccs' efan.ccs.efficiency = 0.95 # Siemens SD104 SiC Power Electronics reported to be this efficient # ------------------------------------------------------------------ # Component 8 - Cryocooler, to cool the HTS Rotor efan.cryocooler = SUAVE.Components.Energy.Cooling.Cryocooler() efan.cryocooler.tag = 'cryocooler' efan.cryocooler.cooler_type = 'GM' efan.cryocooler.min_cryo_temp = efan.rotor.temperature # [K] efan.cryocooler.ambient_temp = 300.0 # [K] # ------------------------------------------------------------------ # Component 9 - Cryogenic Heat Exchanger, to cool the HTS Rotor efan.heat_exchanger = SUAVE.Components.Energy.Cooling.Cryogenic_Heat_Exchanger( ) efan.heat_exchanger.tag = 'heat_exchanger' efan.heat_exchanger.cryogen = SUAVE.Attributes.Cryogens.Liquid_H2() efan.heat_exchanger.cryogen_inlet_temperature = 20.0 # [K] efan.heat_exchanger.cryogen_outlet_temperature = efan.rotor.temperature # [K] efan.heat_exchanger.cryogen_pressure = 100000.0 # [Pa] efan.heat_exchanger.cryogen_is_fuel = 0.0 # Sizing Conditions. The cryocooler may have greater power requirement at low altitude as the cooling requirement may be static during the flight but the ambient temperature may change. cryo_temp = 50.0 # [K] amb_temp = 300.0 # [K] # ------------------------------------------------------------------ # Powertrain Sizing ducted_fan_sizing(efan.ducted_fan, mach_number, altitude) serial_HTS_turboelectric_sizing(efan, mach_number, altitude, cryo_cold_temp=cryo_temp, cryo_amb_temp=amb_temp) # add turboelectric network to the vehicle vehicle.append_component(efan) # ------------------------------------------------------------------ # Vehicle Definition Complete # ------------------------------------------------------------------ return vehicle