def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input("data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:horizontal_tail:area", val=np.nan, units="m**2") self.add_input("data:geometry:propulsion:nacelle:height", val=np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:airframe:landing_gear:main:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:CG:z", val=np.nan, units="m") self.add_input("data:weight:propulsion:engine:CG:z", val=np.nan, units="m") self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", val=np.nan) self.add_input("data:aerodynamics:aircraft:takeoff:CL_max", val=np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CM", val=np.nan) self.add_input("data:aerodynamics:horizontal_tail:low_speed:CL_alpha_isolated", val=np.nan, units="rad**-1") self.add_input("data:aerodynamics:horizontal_tail:efficiency", val=np.nan) self.add_input("takeoff:cl_htp", val=np.nan) self.add_input("takeoff:cm_wing", val=np.nan) self.add_input("low_speed:cl_alpha_htp", val=np.nan) self.add_output("data:handling_qualities:to_rotation_limit:x", units="m") self.add_output("data:handling_qualities:to_rotation_limit:MAC_position") self.declare_partials("*", "*", method="fd")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:aircraft:cruise:CD0", np.nan) self.add_input("data:aerodynamics:aircraft:cruise:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:taxi_out:fuel", np.nan, units="kg") self.add_input("data:mission:sizing:holding:fuel", 0.0, units="kg") self.add_input("data:mission:sizing:takeoff:fuel", np.nan, units="kg") self.add_input("data:mission:sizing:initial_climb:fuel", np.nan, units="kg") self.add_input("data:mission:sizing:main_route:climb:thrust_rate", np.nan) self.add_output("data:mission:sizing:main_route:climb:fuel", units="kg") self.add_output("data:mission:sizing:main_route:climb:distance", units="m") self.add_output("data:mission:sizing:main_route:climb:duration", units="s") self.add_output("data:mission:sizing:main_route:climb:v_cas", units="m/s") self.declare_partials( "*", [ "data:aerodynamics:aircraft:cruise:CD0", "data:aerodynamics:aircraft:cruise:induced_drag_coefficient", "data:geometry:wing:area", "data:weight:aircraft:MTOW", "data:mission:sizing:taxi_out:fuel", "data:mission:sizing:holding:fuel", "data:mission:sizing:takeoff:fuel", "data:mission:sizing:initial_climb:fuel", ], method="fd", )
class ComputeEngineWeight(ExplicitComponent): """ Engine weight estimation calling wrapper """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_output("data:weight:propulsion:engine:mass", units="lb") self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) b1 = propulsion_model.compute_weight() outputs["data:weight:propulsion:engine:mass"] = b1
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:mission:sizing:main_route:cruise:altitude", np.nan, units="m") self.add_input("data:TLAR:cruise_mach", np.nan) self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:aerodynamics:aircraft:cruise:L_D_max", np.nan) self.add_input("data:geometry:propulsion:engine:count", 2) self.add_input("settings:mission:sizing:breguet:climb:mass_ratio", 0.97) self.add_input("settings:mission:sizing:breguet:descent:mass_ratio", 0.98) self.add_input("settings:mission:sizing:breguet:reserve:mass_ratio", 0.06) self.add_input("data:TLAR:range", np.nan, units="m") self.add_input("settings:mission:sizing:breguet:climb_descent_distance", 500.0e3, units="m") self.add_output("data:mission:sizing:main_route:climb:distance", units="m", ref=1e3) self.add_output("data:mission:sizing:main_route:cruise:distance", units="m", ref=1e3) self.add_output("data:mission:sizing:main_route:descent:distance", units="m", ref=1e3) self.add_output("data:mission:sizing:ZFW", units="kg", ref=1e4) self.add_output("data:mission:sizing:fuel", units="kg", ref=1e4) self.add_output("data:mission:sizing:main_route:fuel", units="kg", ref=1e4) self.add_output("data:mission:sizing:main_route:climb:fuel", units="kg", ref=1e4) self.add_output("data:mission:sizing:main_route:cruise:fuel", units="kg", ref=1e4) self.add_output("data:mission:sizing:main_route:descent:fuel", units="kg", ref=1e4) self.add_output("data:mission:sizing:fuel_reserve", units="kg", ref=1e4) self.add_output("data:propulsion:SFC", units="kg/s/N", ref=1e-4) self.add_output("data:propulsion:thrust_rate", lower=0.0, upper=1.0) self.add_output("data:propulsion:thrust", units="N", ref=1e5) self.declare_partials("*", "*", method="fd")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) if self.options["taxi_out"]: self.add_input("data:mission:sizing:taxi_out:thrust_rate", np.nan) self.add_input("data:mission:sizing:taxi_out:duration", np.nan, units="s") self.add_input("data:mission:sizing:taxi_out:speed", np.nan, units="m/s") self.add_output("data:mission:sizing:taxi_out:fuel", units='kg') else: self.add_input("data:mission:sizing:taxi_in:thrust_rate", np.nan) self.add_input("data:mission:sizing:taxi_in:duration", np.nan, units="s") self.add_input("data:mission:sizing:taxi_in:speed", np.nan, units="m/s") self.add_output("data:mission:sizing:taxi_in:fuel", units='kg') self.declare_partials("*", "*", method="fd")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:geometry:wing:span", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:weight:aircraft:CG:aft:MAC_position", val=np.nan) self.add_input("data:aerodynamics:fuselage:cruise:CnBeta", val=np.nan) self.add_input("data:aerodynamics:vertical_tail:cruise:CL_alpha", val=np.nan, units="rad**-1") self.add_input("data:TLAR:v_approach", val=np.nan, units="m/s") self.add_input( "data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:propulsion:nacelle:wet_area", val=np.nan, units="m**2") self.add_input("data:geometry:propulsion:nacelle:y", val=np.nan, units="m") self.add_output("data:geometry:vertical_tail:area", val=2.5, units="m**2") self.declare_partials( "*", "*", method="fd", )
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_alpha", np.nan, units="rad**-1") self.add_input("data:aerodynamics:aircraft:low_speed:CD0", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CD", np.nan) self.add_input("data:aerodynamics:wing:low_speed:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:geometry:wing:span", np.nan, units="m") self.add_input("data:geometry:landing_gear:height", np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:thrust_rate", np.nan) self.add_input("data:mission:sizing:takeoff:friction_coefficient_no_brake", np.nan) self.add_input("vr:speed", np.nan, units='m/s') self.add_input("v2:angle", np.nan, units='rad') self.add_output("data:mission:sizing:takeoff:VR", units='m/s') self.add_output("data:mission:sizing:takeoff:VLOF", units='m/s') self.add_output("data:mission:sizing:takeoff:V2", units='m/s') self.add_output("data:mission:sizing:takeoff:TOFL", units='m') self.add_output("data:mission:sizing:takeoff:duration", units='s') self.add_output("data:mission:sizing:takeoff:fuel", units='kg') self.add_output("data:mission:sizing:initial_climb:fuel", units='kg') self.declare_partials("*", "*", method="fd")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_output("data:weight:propulsion:engine_oil:mass", units="lb")
class _compute_taxi(om.ExplicitComponent): """ Compute the fuel consumption for taxi based on speed and duration. """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) self.options.declare("taxi_out", default=True, types=bool) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) if self.options["taxi_out"]: self.add_input("data:mission:sizing:taxi_out:thrust_rate", np.nan) self.add_input("data:mission:sizing:taxi_out:duration", np.nan, units="s") self.add_input("data:mission:sizing:taxi_out:speed", np.nan, units="m/s") self.add_output("data:mission:sizing:taxi_out:fuel", units='kg') else: self.add_input("data:mission:sizing:taxi_in:thrust_rate", np.nan) self.add_input("data:mission:sizing:taxi_in:duration", np.nan, units="s") self.add_input("data:mission:sizing:taxi_in:speed", np.nan, units="m/s") self.add_output("data:mission:sizing:taxi_in:fuel", units='kg') self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) if self.options["taxi_out"]: thrust_rate = inputs["data:mission:sizing:taxi_out:thrust_rate"] duration = inputs["data:mission:sizing:taxi_out:duration"] mach = inputs["data:mission:sizing:taxi_out:speed"]/Atmosphere(0.0).speed_of_sound else: thrust_rate = inputs["data:mission:sizing:taxi_in:thrust_rate"] duration = inputs["data:mission:sizing:taxi_in:duration"] mach = inputs["data:mission:sizing:taxi_in:speed"] / Atmosphere(0.0).speed_of_sound # FIXME: no specific settings for taxi (to be changed in fastoad\constants.py) flight_point = FlightPoint( mach=mach, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=thrust_rate ) propulsion_model.compute_flight_points(flight_point) fuel_mass = propulsion_model.get_consumed_mass(flight_point, duration) if self.options["taxi_out"]: outputs["data:mission:sizing:taxi_out:fuel"] = fuel_mass else: outputs["data:mission:sizing:taxi_in:fuel"] = fuel_mass
class ComputeUnusableFuelWeight(ExplicitComponent): """ Weight estimation for motor oil Based on : Wells, Douglas P., Bryce L. Horvath, and Linwood A. McCullers. "The Flight Optimization System Weights Estimation Method." (2017). Equation 121 """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="ft**2") self.add_input("data:weight:aircraft:MFW", val=np.nan, units="lb") self.add_output("data:weight:propulsion:unusable_fuel:mass", units="lb") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): n_eng = inputs["data:geometry:propulsion:count"] wing_area = inputs["data:geometry:wing:area"] mfw = inputs["data:weight:aircraft:MFW"] n_tank = 2.0 propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), n_eng) flight_point = FlightPoint( mach=0.0, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=1.0) # with engine_setting as EngineSetting propulsion_model.compute_flight_points(flight_point) sl_thrust_newton = float(flight_point.thrust) sl_thrust_lbs = sl_thrust_newton / lbf sl_thrust_lbs_per_engine = sl_thrust_lbs / n_eng b3 = 11.5 * n_eng * sl_thrust_lbs_per_engine ** 0.2 + \ 0.07 * wing_area + \ 1.6 * n_tank * mfw ** 0.28 outputs["data:weight:propulsion:unusable_fuel:mass"] = b3
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:cabin:luggage:mass_max", val=np.nan, units="kg") self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:aerodynamics:aircraft:cruise:CD0", val=np.nan) self.add_input( "data:aerodynamics:wing:cruise:induced_drag_coefficient", val=np.nan) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:cabin:seats:passenger:NPAX_max", val=np.nan) self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input("data:geometry:fuselage:front_length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:pilot:length", val=np.nan, units="m") self.add_input("data:geometry:fuselage:PAX_length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:count_by_row", val=np.nan) self.add_input("data:geometry:cabin:seats:passenger:length", val=np.nan, units="m") self.add_input("data:weight:payload:rear_fret:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:aircraft_empty:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:unusable_fuel:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:tank:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft:MFW", val=np.nan, units="kg") self.add_output( "data:weight:aircraft:CG:flight_condition:max:MAC_position") self.add_output( "data:weight:aircraft:CG:flight_condition:min:MAC_position")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:TLAR:NPAX", val=np.nan) self.add_input("data:geometry:cabin:seats:pilot:length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:pilot:width", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:width", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:count_by_row", val=np.nan) self.add_input("data:geometry:cabin:aisle_width", val=np.nan, units="m") self.add_input("data:geometry:propulsion:layout", val=np.nan) self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input( "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input( "data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:horizontal_tail:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:vertical_tail:MAC:length", val=np.nan, units="m") self.add_output("data:geometry:cabin:NPAX") self.add_output("data:geometry:fuselage:length", units="m") self.add_output("data:geometry:fuselage:maximum_width", units="m") self.add_output("data:geometry:fuselage:maximum_height", units="m") self.add_output("data:geometry:fuselage:front_length", units="m") self.add_output("data:geometry:fuselage:rear_length", units="m") self.add_output("data:geometry:fuselage:PAX_length", units="m") self.add_output("data:geometry:cabin:length", units="m") self.add_output("data:geometry:fuselage:wet_area", units="m**2") self.add_output("data:geometry:fuselage:luggage_length", units="m") self.declare_partials( "*", "*", method="fd") # FIXME: declare proper partials without int values
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="ft**2") self.add_input("data:weight:aircraft:MFW", val=np.nan, units="lb") self.add_output("data:weight:propulsion:unusable_fuel:mass", units="lb")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("settings:weight:aircraft:CG:range", val=0.3) self.add_input("data:mission:sizing:takeoff:thrust_rate", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input( "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:propulsion:nacelle:height", val=np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:aircraft:MLW", val=np.nan, units="kg") self.add_input("data:weight:aircraft:CG:aft:x", val=np.nan, units="m") self.add_input("data:weight:airframe:landing_gear:main:CG:x", val=np.nan, units="m") self.add_input("data:aerodynamics:aircraft:low_speed:CL0_clean", val=np.nan) self.add_input("data:aerodynamics:aircraft:landing:CL_max", val=np.nan) self.add_input("data:aerodynamics:aircraft:takeoff:CL_max", val=np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", val=np.nan) self.add_input("data:aerodynamics:flaps:landing:CL", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", val=np.nan) self.add_input("data:aerodynamics:flaps:landing:CM", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CM", val=np.nan) self.add_input("data:aerodynamics:horizontal_tail:low_speed:CL_alpha", val=np.nan, units="rad**-1") self.add_input("landing:cl_ht", val=np.nan) self.add_input("takeoff:cl_ht", val=np.nan) self.add_input("landing:cm_wing", val=np.nan) self.add_input("takeoff:cm_wing", val=np.nan) self.add_input("low_speed:cl_alpha_ht", val=np.nan) self.add_output("data:geometry:horizontal_tail:area", val=4.0, units="m**2") self.declare_partials( "*", "*", method="fd") # FIXME: write partial avoiding discrete parameters
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:aerodynamics:aircraft:cruise:CD0", val=np.nan) self.add_input("data:aerodynamics:wing:cruise:induced_drag_coefficient", val=np.nan) self.add_output("data:TLAR:v_max_sl", units="m/s") self.declare_partials("*", "*", method="fd")
class ComputeOilWeight(ExplicitComponent): """ Weight estimation for motor oil Based on : Wells, Douglas P., Bryce L. Horvath, and Linwood A. McCullers. "The Flight Optimization System Weights Estimation Method." (2017). Equation 123 Not used since already included in the engine installed weight but left there in case """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_output("data:weight:propulsion:engine_oil:mass", units="lb") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): n_eng = inputs["data:geometry:propulsion:count"] propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), n_eng) flight_point = FlightPoint( mach=0.0, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=1.0) # with engine_setting as EngineSetting propulsion_model.compute_flight_points(flight_point) # This should give the UNINSTALLED weight sl_thrust_newton = float(flight_point.thrust) sl_thrust_lbs = sl_thrust_newton / lbf b1_2 = 0.082 * n_eng * sl_thrust_lbs**0.65 outputs["data:weight:propulsion:engine_oil:mass"] = b1_2
class ComputeEngineWeight(ExplicitComponent): """ Engine weight estimation calling wrapper Based on : Raymer Daniel. Aircraft Design: A Conceptual Approach. AIAA Education Series 1996 for installed engine weight, table 15.2 """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_output("data:weight:propulsion:engine:mass", units="lb") self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"]) # This should give the UNINSTALLED weight uninstalled_engine_weight = propulsion_model.compute_weight() b1 = 1.4 * uninstalled_engine_weight outputs["data:weight:propulsion:engine:mass"] = b1
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("data:geometry:propulsion:layout", val=np.nan) self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") if self.options["low_speed_aero"]: self.add_input("data:aerodynamics:low_speed:mach", val=np.nan) self.add_input("data:aerodynamics:low_speed:unit_reynolds", val=np.nan) self.add_output("data:aerodynamics:nacelles:low_speed:CD0") else: self.add_input("data:aerodynamics:cruise:mach", val=np.nan) self.add_input("data:aerodynamics:cruise:unit_reynolds", val=np.nan) self.add_output("data:aerodynamics:nacelles:cruise:CD0") self.declare_partials("*", "*", method="fd")
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:layout", val=np.nan) self.add_input("data:geometry:wing:span", val=np.nan, units="m") self.add_input("data:geometry:propulsion:y_ratio", val=np.nan) self.add_input("data:geometry:fuselage:maximum_width", val=np.nan, units="m") self.add_output("data:geometry:propulsion:nacelle:length", units="m") self.add_output("data:geometry:propulsion:nacelle:height", units="m") self.add_output("data:geometry:propulsion:nacelle:width", units="m") self.add_output("data:geometry:propulsion:nacelle:wet_area", units="m**2") self.add_output("data:geometry:landing_gear:height", units="m") self.add_output("data:geometry:propulsion:nacelle:y", units="m") self.declare_partials("*", "*", method="fd")
def setup(self): super().setup() self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:aircraft:MLW", val=np.nan, units="kg") self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input( "data:aerodynamics:wing:low_speed:induced_drag_coefficient", val=np.nan) self.add_input( "data:aerodynamics:horizontal_tail:low_speed:induced_drag_coefficient", val=np.nan) self.add_input("data:aerodynamics:aircraft:landing:CL_max", val=np.nan) self.add_input("data:aerodynamics:aircraft:low_speed:CD0", val=np.nan) self.add_output("data:handling_qualities:balked_landing_limit:x", val=4.0, units="m") self.add_output( "data:handling_qualities:balked_landing_limit:MAC_position", val=np.nan)
def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self._mission_input = self.options["mission_file_path"] if not self._mission_input: with path(resources, "sizing_mission.yml") as mission_input_file: self._mission_input = MissionDefinition(mission_input_file) self._mission = MissionWrapper(self._mission_input) self._mission.setup(self) self.add_input("data:geometry:propulsion:engine:count", 2) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:taxi_out:fuel", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:altitude", np.nan, units="m") self.add_input("data:mission:sizing:takeoff:fuel", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:V2", np.nan, units="m/s") self.add_output("data:mission:sizing:ZFW", units="kg") self.declare_partials(["*"], ["*"])
@staticmethod def get_model(inputs) -> IPropulsion: engine_params = { "max_power": inputs["data:propulsion:IC_engine:max_power"], "design_altitude": inputs["data:mission:sizing:main_route:cruise:altitude"], "design_speed": inputs["data:TLAR:v_cruise"], "fuel_type": inputs["data:propulsion:IC_engine:fuel_type"], "strokes_nb": inputs["data:propulsion:IC_engine:strokes_nb"], "prop_layout": inputs["data:geometry:propulsion:layout"] } return DummyEngine(**engine_params) BundleLoader().context.install_bundle(__name__).start() def test_update_vt_area(): """ Tests computation of the vertical tail area """ # Research independent input value in .xml file ivc = get_indep_var_comp(list_inputs(UpdateVTArea(propulsion_id=ENGINE_WRAPPER)), __file__, XML_FILE) ivc.add_output("data:weight:aircraft:CG:aft:MAC_position", 0.364924) ivc.add_output("data:aerodynamics:fuselage:cruise:CnBeta", -0.0599) # Run problem and check obtained value(s) is/(are) correct register_wrappers() problem = run_system(UpdateVTArea(propulsion_id=ENGINE_WRAPPER), ivc) vt_area = problem.get_val("data:geometry:vertical_tail:area", units="m**2") assert vt_area == pytest.approx(2.44, abs=1e-2) # old-version obtained value 2.4m²
class ComputeNacelleGeometry(om.ExplicitComponent): # TODO: Document equations. Cite sources """ Nacelle and pylon geometry estimation """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:wing:span", val=np.nan, units="m") self.add_input("data:geometry:propulsion:y_ratio", val=np.nan) self.add_input("data:geometry:fuselage:maximum_width", val=np.nan, units="m") self.add_output("data:geometry:propulsion:nacelle:length", units="m") self.add_output("data:geometry:propulsion:nacelle:height", units="m") self.add_output("data:geometry:propulsion:nacelle:width", units="m") self.add_output("data:geometry:propulsion:nacelle:wet_area", units="m**2") self.add_output("data:geometry:propulsion:propeller:depth", units="m") self.add_output("data:geometry:propulsion:propeller:diameter", units="m") self.add_output("data:geometry:landing_gear:height", units="m") self.add_output("data:geometry:propulsion:nacelle:y", units="m") self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), 1.0) prop_layout = inputs["data:geometry:propulsion:layout"] span = inputs["data:geometry:wing:span"] y_ratio = inputs["data:geometry:propulsion:y_ratio"] b_f = inputs["data:geometry:fuselage:maximum_width"] nac_height, nac_width, nac_length, nac_wet_area, prop_dia, prop_depth = propulsion_model.compute_dimensions( ) if prop_layout == 1.0: y_nacelle = y_ratio * span / 2 elif prop_layout == 2.0: y_nacelle = b_f / 2 + 0.8 * nac_width elif prop_layout == 3.0: y_nacelle = 0.0 else: y_nacelle = y_ratio * span / 2 warnings.warn( 'Propulsion layout {} not implemented in model, replaced by layout 1!' .format(prop_layout)) lg_height = 0.41 * prop_dia outputs["data:geometry:propulsion:nacelle:length"] = nac_length outputs["data:geometry:propulsion:nacelle:height"] = nac_height outputs["data:geometry:propulsion:nacelle:width"] = nac_width outputs["data:geometry:propulsion:nacelle:wet_area"] = nac_wet_area outputs["data:geometry:propulsion:propeller:depth"] = prop_depth outputs["data:geometry:propulsion:propeller:diameter"] = prop_dia outputs["data:geometry:landing_gear:height"] = lg_height outputs["data:geometry:propulsion:nacelle:y"] = y_nacelle
class ComputeVh(om.ExplicitComponent): def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:aerodynamics:aircraft:cruise:CD0", val=np.nan) self.add_input( "data:aerodynamics:wing:cruise:induced_drag_coefficient", val=np.nan) self.add_output("data:TLAR:v_max_sl", units="kn") self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): # The maximum Sea Level flight velocity is computed using a method which finds for which speed # the thrust required for flight (drag) is equal to the thrust available design_mass = inputs["data:weight:aircraft:MTOW"] Vh = self.max_speed(inputs, 0.0, design_mass) outputs["data:TLAR:v_max_sl"] = Vh def max_speed(self, inputs, altitude, mass): # noinspection PyTypeChecker roots = optimize.fsolve(self.delta_axial_load, 300.0, args=(inputs, altitude, mass))[0] return np.max(roots[roots > 0.0]) def delta_axial_load(self, air_speed, inputs, altitude, mass): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"]) wing_area = inputs["data:geometry:wing:area"] cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"] coef_k = inputs[ "data:aerodynamics:wing:cruise:induced_drag_coefficient"] # Get the available thrust from propulsion system atm = Atmosphere(altitude, altitude_in_feet=False) flight_point = FlightPoint(mach=air_speed / atm.speed_of_sound, altitude=altitude, engine_setting=EngineSetting.TAKEOFF, thrust_rate=1.0) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) # Get the necessary thrust to overcome cl = (mass * g) / (0.5 * atm.density * wing_area * air_speed**2.0) cd = cd0 + coef_k * cl**2.0 drag = 0.5 * atm.density * wing_area * cd * air_speed**2.0 return thrust - drag
class ComputeFuselageGeometryCabinSizing(ExplicitComponent): # TODO: Document equations. Cite sources """ Geometry of fuselage part A - Cabin (Commercial) estimation """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:TLAR:NPAX", val=np.nan) self.add_input("data:geometry:cabin:seats:pilot:length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:pilot:width", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:width", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:count_by_row", val=np.nan) self.add_input("data:geometry:cabin:aisle_width", val=np.nan, units="m") self.add_input("data:geometry:propulsion:layout", val=np.nan) self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input( "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input( "data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:horizontal_tail:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:vertical_tail:MAC:length", val=np.nan, units="m") self.add_output("data:geometry:cabin:NPAX") self.add_output("data:geometry:fuselage:length", units="m") self.add_output("data:geometry:fuselage:maximum_width", units="m") self.add_output("data:geometry:fuselage:maximum_height", units="m") self.add_output("data:geometry:fuselage:front_length", units="m") self.add_output("data:geometry:fuselage:rear_length", units="m") self.add_output("data:geometry:fuselage:PAX_length", units="m") self.add_output("data:geometry:cabin:length", units="m") self.add_output("data:geometry:fuselage:wet_area", units="m**2") self.add_output("data:geometry:fuselage:luggage_length", units="m") self.declare_partials( "*", "*", method="fd") # FIXME: declare proper partials without int values def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), 1.0) npax = inputs["data:TLAR:NPAX"] l_pilot_seats = inputs["data:geometry:cabin:seats:pilot:length"] w_pilot_seats = inputs["data:geometry:cabin:seats:pilot:width"] l_pass_seats = inputs["data:geometry:cabin:seats:passenger:length"] w_pass_seats = inputs["data:geometry:cabin:seats:passenger:width"] seats_p_row = inputs[ "data:geometry:cabin:seats:passenger:count_by_row"] w_aisle = inputs["data:geometry:cabin:aisle_width"] prop_layout = inputs["data:geometry:propulsion:layout"] fa_length = inputs["data:geometry:wing:MAC:at25percent:x"] ht_lp = inputs[ "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"] vt_lp = inputs[ "data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25"] ht_length = inputs["data:geometry:horizontal_tail:MAC:length"] vt_length = inputs["data:geometry:vertical_tail:MAC:length"] # Length of instrument panel l_instr = 0.7 # Length of pax cabin # noinspection PyBroadException try: npax_1 = math.ceil(npax / seats_p_row) * seats_p_row except: npax_1 = npax n_rows = npax_1 / seats_p_row lpax = l_pilot_seats + n_rows * l_pass_seats # Cabin width considered is for side by side seats wcabin = max(2 * w_pilot_seats, seats_p_row * w_pass_seats + w_aisle) r_i = wcabin / 2 radius = 1.06 * r_i # Cylindrical fuselage b_f = 2 * radius # 0.14m is the distance between both lobe centers of the fuselage h_f = b_f + 0.14 # Luggage length l_lug = npax_1 * 0.20 / (math.pi * radius**2) # Cabin total length cabin_length = l_instr + lpax + l_lug # Calculate nose length if prop_layout == 3.0: # engine located in nose _, _, propulsion_length, _ = propulsion_model.compute_dimensions() lav = propulsion_length else: lav = 1.7 * h_f # Calculate fuselage length fus_length = fa_length + max(ht_lp + 0.75 * ht_length, vt_lp + 0.75 * vt_length) lar = fus_length - (lav + cabin_length) # Calculate wet area fus_dia = math.sqrt(b_f * h_f) # equivalent diameter of the fuselage cyl_length = fus_length - lav - lar wet_area_nose = 2.45 * fus_dia * lav wet_area_cyl = 3.1416 * fus_dia * cyl_length wet_area_tail = 2.3 * fus_dia * lar wet_area_fus = (wet_area_nose + wet_area_cyl + wet_area_tail) outputs["data:geometry:cabin:NPAX"] = npax_1 outputs["data:geometry:fuselage:length"] = fus_length outputs["data:geometry:fuselage:maximum_width"] = b_f outputs["data:geometry:fuselage:maximum_height"] = h_f outputs["data:geometry:fuselage:front_length"] = lav outputs["data:geometry:fuselage:rear_length"] = lar outputs["data:geometry:fuselage:PAX_length"] = lpax outputs["data:geometry:cabin:length"] = cabin_length outputs["data:geometry:fuselage:wet_area"] = wet_area_fus outputs["data:geometry:fuselage:luggage_length"] = l_lug
class _simulate_takeoff(om.ExplicitComponent): """ Simulate take-off from 0m/s speed to safety height using input VR. Fuel burn is supposed negligible : mass = MTOW. """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_alpha", np.nan, units="rad**-1") self.add_input("data:aerodynamics:aircraft:low_speed:CD0", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CD", np.nan) self.add_input("data:aerodynamics:wing:low_speed:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:geometry:wing:span", np.nan, units="m") self.add_input("data:geometry:landing_gear:height", np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:thrust_rate", np.nan) self.add_input("data:mission:sizing:takeoff:friction_coefficient_no_brake", np.nan) self.add_input("vr:speed", np.nan, units='m/s') self.add_input("v2:angle", np.nan, units='rad') self.add_output("data:mission:sizing:takeoff:VR", units='m/s') self.add_output("data:mission:sizing:takeoff:VLOF", units='m/s') self.add_output("data:mission:sizing:takeoff:V2", units='m/s') self.add_output("data:mission:sizing:takeoff:TOFL", units='m') self.add_output("data:mission:sizing:takeoff:duration", units='s') self.add_output("data:mission:sizing:takeoff:fuel", units='kg') self.add_output("data:mission:sizing:initial_climb:fuel", units='kg') self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) cl_max_clean = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"] cl0 = inputs["data:aerodynamics:wing:low_speed:CL0_clean"] + inputs["data:aerodynamics:flaps:takeoff:CL"] cl_alpha = inputs["data:aerodynamics:wing:low_speed:CL_alpha"] cd0 = inputs["data:aerodynamics:aircraft:low_speed:CD0"] + inputs["data:aerodynamics:flaps:takeoff:CD"] coef_k = inputs["data:aerodynamics:wing:low_speed:induced_drag_coefficient"] wing_area = inputs["data:geometry:wing:area"] wing_span = inputs["data:geometry:wing:span"] lg_height = inputs["data:geometry:landing_gear:height"] mtow = inputs["data:weight:aircraft:MTOW"] thrust_rate = inputs["data:mission:sizing:takeoff:thrust_rate"] friction_coeff = inputs["data:mission:sizing:takeoff:friction_coefficient_no_brake"] alpha_v2 = float(inputs["v2:angle"]) # Define ground factor effect on Drag k_ground = lambda altitude: ( 33. * ((lg_height + altitude) / wing_span) ** 1.5 / (1. + 33. * ((lg_height + altitude) / wing_span) ** 1.5) ) # Determine rotation speed from regulation CS23.51 vs1 = math.sqrt((mtow * g) / (0.5 * Atmosphere(0).density * wing_area * cl_max_clean)) if inputs["data:geometry:propulsion:count"] == 1.0: k = 1.0 else: k = 1.1 vr = max(k * vs1, float(inputs["vr:speed"])) # Start calculation of flight from null speed to 35ft high alpha_t = 0.0 gamma_t = 0.0 v_t = 0.0 altitude_t = 0.0 distance_t = 0.0 mass_fuel1_t = 0.0 mass_fuel2_t = 0.0 time_t = 0.0 vloff = 0.0 climb = False while altitude_t < SAFETY_HEIGHT: # Estimation of thrust atm = Atmosphere(altitude_t, altitude_in_feet=False) flight_point = FlightPoint( mach=max(v_t, vr) / atm.speed_of_sound, altitude=altitude_t, engine_setting=EngineSetting.TAKEOFF, thrust_rate=thrust_rate ) # FIXME: (speed increased to vr to have feasible consumptions) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) # Calculate lift and drag cl = cl0 + cl_alpha * alpha_t lift = 0.5 * atm.density * wing_area * cl * v_t ** 2 cd = cd0 + k_ground(altitude_t) * coef_k * cl ** 2 drag = 0.5 * atm.density * wing_area * cd * v_t ** 2 # Check if lift-off condition reached if ((lift + thrust * math.sin(alpha_t) - mtow * g * math.cos(gamma_t)) >= 0.0) and not climb: climb = True vloff = v_t # Calculate acceleration on x/z air axis if climb: acc_z = (lift + thrust * math.sin(alpha_t) - mtow * g * math.cos(gamma_t)) / mtow acc_x = (thrust * math.cos(alpha_t) - mtow * g * math.sin(gamma_t) - drag) / mtow else: friction = (mtow * g - lift - thrust * math.sin(alpha_t)) * friction_coeff acc_z = 0.0 acc_x = (thrust * math.cos(alpha_t) - drag - friction) / mtow # Calculate gamma change and new speed delta_gamma = math.atan((acc_z * TIME_STEP) / (v_t + acc_x * TIME_STEP)) v_t_new = math.sqrt((acc_z * TIME_STEP) ** 2 + (v_t + acc_x * TIME_STEP) ** 2) # Trapezoidal integration on distance/altitude delta_altitude = (v_t_new * math.sin(gamma_t + delta_gamma) + v_t * math.sin(gamma_t)) / 2 * TIME_STEP delta_distance = (v_t_new * math.cos(gamma_t + delta_gamma) + v_t * math.cos(gamma_t)) / 2 * TIME_STEP # Update temporal values if v_t >= vr: alpha_t = min(alpha_v2, alpha_t + ALPHA_RATE * TIME_STEP) gamma_t = gamma_t + delta_gamma altitude_t = altitude_t + delta_altitude if not climb: mass_fuel1_t += propulsion_model.get_consumed_mass(flight_point, TIME_STEP) distance_t = distance_t + delta_distance time_t = time_t + TIME_STEP else: mass_fuel2_t += propulsion_model.get_consumed_mass(flight_point, TIME_STEP) time_t = time_t + TIME_STEP v_t = v_t_new outputs["data:mission:sizing:takeoff:VR"] = vr outputs["data:mission:sizing:takeoff:VLOF"] = vloff outputs["data:mission:sizing:takeoff:V2"] = v_t outputs["data:mission:sizing:takeoff:TOFL"] = distance_t outputs["data:mission:sizing:takeoff:duration"] = time_t outputs["data:mission:sizing:takeoff:fuel"] = mass_fuel1_t outputs["data:mission:sizing:initial_climb:fuel"] = mass_fuel2_t
class _vr_from_v2(om.ExplicitComponent): """ Search VR for given lift-off conditions by doing reverted simulation. The error introduced comes from acceleration acc(t)~acc(t+dt) => v(t-dt)~V(t)-acc(t)*dt. Time step has been reduced by 1/5 to limit integration error. """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_alpha", np.nan, units="rad**-1") self.add_input("data:aerodynamics:aircraft:low_speed:CD0", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CD", np.nan) self.add_input("data:aerodynamics:wing:low_speed:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:geometry:wing:span", np.nan, units="m") self.add_input("data:geometry:landing_gear:height", np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:thrust_rate", np.nan) self.add_input("data:mission:sizing:takeoff:friction_coefficient_no_brake", np.nan) self.add_input("vloff:speed", np.nan, units='m/s') self.add_input("vloff:angle", np.nan, units='rad') self.add_output("vr:speed", units='m/s') self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) cl0 = inputs["data:aerodynamics:wing:low_speed:CL0_clean"] + inputs["data:aerodynamics:flaps:takeoff:CL"] cl_alpha = inputs["data:aerodynamics:wing:low_speed:CL_alpha"] cd0 = inputs["data:aerodynamics:aircraft:low_speed:CD0"] + inputs["data:aerodynamics:flaps:takeoff:CD"] coef_k = inputs["data:aerodynamics:wing:low_speed:induced_drag_coefficient"] wing_area = inputs["data:geometry:wing:area"] wing_span = inputs["data:geometry:wing:span"] lg_height = inputs["data:geometry:landing_gear:height"] mtow = inputs["data:weight:aircraft:MTOW"] thrust_rate = inputs["data:mission:sizing:takeoff:thrust_rate"] friction_coeff = inputs["data:mission:sizing:takeoff:friction_coefficient_no_brake"] v_t = float(inputs["vloff:speed"]) alpha_t = float(inputs["vloff:angle"]) # Define ground factor effect on Drag k_ground = 33. * (lg_height / wing_span) ** 1.5 / (1. + 33. * (lg_height / wing_span) ** 1.5) # Start reverted calculation of flight from lift-off to 0° alpha angle atm = Atmosphere(0.0) while (alpha_t != 0.0) and (v_t != 0.0): # Estimation of thrust flight_point = FlightPoint( mach=v_t / atm.speed_of_sound, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=thrust_rate ) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) # Calculate lift and drag cl = cl0 + cl_alpha * alpha_t lift = 0.5 * atm.density * wing_area * cl * v_t ** 2 cd = cd0 + k_ground * coef_k * cl ** 2 drag = 0.5 * atm.density * wing_area * cd * v_t ** 2 # Calculate rolling resistance load friction = (mtow * g - lift - thrust * math.sin(alpha_t)) * friction_coeff # Calculate acceleration acc_x = (thrust * math.cos(alpha_t) - drag - friction) / mtow # Speed and angle update (feedback) dt = min(TIME_STEP / 5, alpha_t / ALPHA_RATE, v_t / acc_x) v_t = v_t - acc_x * dt alpha_t = alpha_t - ALPHA_RATE * dt outputs["vr:speed"] = v_t
class _vloff_from_v2(om.ExplicitComponent): """ Search alpha-angle<=alpha(v2) at which Vloff is operated such that aircraft reaches v>=v2 speed @ safety height with imposed rotation speed. Fuel burn is neglected : mass = MTOW. """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_alpha", np.nan, units="rad**-1") self.add_input("data:aerodynamics:aircraft:low_speed:CD0", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CD", np.nan) self.add_input("data:aerodynamics:wing:low_speed:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:geometry:wing:span", np.nan, units="m") self.add_input("data:geometry:landing_gear:height", np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_input("data:mission:sizing:takeoff:thrust_rate", np.nan) self.add_input("v2:speed", np.nan, units='m/s') self.add_input("v2:angle", np.nan, units='rad') self.add_output("vloff:speed", units='m/s') self.add_output("vloff:angle", units='rad') self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) cl0 = inputs["data:aerodynamics:wing:low_speed:CL0_clean"] + inputs["data:aerodynamics:flaps:takeoff:CL"] cl_alpha = inputs["data:aerodynamics:wing:low_speed:CL_alpha"] cd0 = inputs["data:aerodynamics:aircraft:low_speed:CD0"] + inputs["data:aerodynamics:flaps:takeoff:CD"] coef_k = inputs["data:aerodynamics:wing:low_speed:induced_drag_coefficient"] wing_area = inputs["data:geometry:wing:area"] wing_span = inputs["data:geometry:wing:span"] lg_height = inputs["data:geometry:landing_gear:height"] mtow = inputs["data:weight:aircraft:MTOW"] thrust_rate = inputs["data:mission:sizing:takeoff:thrust_rate"] v2_target = float(inputs["v2:speed"]) alpha_v2 = float(inputs["v2:angle"]) # Define ground factor effect on Drag k_ground = lambda altitude: ( 33. * ((lg_height + altitude) / wing_span) ** 1.5 / (1. + 33. * ((lg_height + altitude) / wing_span) ** 1.5) ) # Calculate v2 speed @ safety height for different alpha lift-off alpha = np.linspace(0.0, min(ALPHA_LIMIT, alpha_v2), num=10) vloff = np.zeros(np.size(alpha)) v2 = np.zeros(np.size(alpha)) atm_0 = Atmosphere(0.0) for i in range(len(alpha)): # Calculate lift coefficient cl = cl0 + cl_alpha * alpha[i] # Loop on estimated lift-off speed error induced by thrust estimation rel_error = 0.1 vloff[i] = math.sqrt((mtow * g) / (0.5 * atm_0.density * wing_area * cl)) while rel_error > 0.05: # Update thrust with vloff flight_point = FlightPoint( mach=vloff[i] / atm_0.speed_of_sound, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=thrust_rate ) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) # Calculate vloff necessary to overcome weight if thrust * math.sin(alpha[i]) > mtow * g: break else: v = math.sqrt((mtow * g - thrust * math.sin(alpha[i])) / (0.5 * atm_0.density * wing_area * cl)) rel_error = abs(v - vloff[i]) / v vloff[i] = v # Perform climb with imposed rotational speed till reaching safety height alpha_t = alpha[i] gamma_t = 0.0 v_t = float(vloff[i]) altitude_t = 0.0 distance_t = 0.0 while altitude_t < SAFETY_HEIGHT: # Estimation of thrust atm = Atmosphere(altitude_t, altitude_in_feet=False) flight_point = FlightPoint( mach=v_t / atm.speed_of_sound, altitude=altitude_t, engine_setting=EngineSetting.TAKEOFF, thrust_rate=thrust_rate ) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) # Calculate lift and drag cl = cl0 + cl_alpha * alpha_t lift = 0.5 * atm.density * wing_area * cl * v_t ** 2 cd = cd0 + k_ground(altitude_t) * coef_k * cl ** 2 drag = 0.5 * atm.density * wing_area * cd * v_t ** 2 # Calculate acceleration on x/z air axis weight = mtow * g acc_x = (thrust * math.cos(alpha_t) - weight * math.sin(gamma_t) - drag) / mtow acc_z = (lift + thrust * math.sin(alpha_t) - weight * math.cos(gamma_t)) / mtow # Calculate gamma change and new speed delta_gamma = math.atan((acc_z * TIME_STEP) / (v_t + acc_x * TIME_STEP)) v_t_new = math.sqrt((acc_z * TIME_STEP) ** 2 + (v_t + acc_x * TIME_STEP) ** 2) # Trapezoidal integration on distance/altitude delta_altitude = (v_t_new * math.sin(gamma_t + delta_gamma) + v_t * math.sin(gamma_t)) / 2 * TIME_STEP delta_distance = (v_t_new * math.cos(gamma_t + delta_gamma) + v_t * math.cos(gamma_t)) / 2 * TIME_STEP # Update temporal values alpha_t = min(alpha_v2, alpha_t + ALPHA_RATE * TIME_STEP) gamma_t = gamma_t + delta_gamma altitude_t = altitude_t + delta_altitude distance_t = distance_t + delta_distance v_t = v_t_new # Save obtained v2 v2[i] = v_t # If v2 target speed not reachable maximum lift-off speed chosen (alpha=0°) if sum(v2 > v2_target) == 0: alpha = 0.0 vloff = vloff[0] # FIXME: not reachable v2 warnings.warn("V2 @ 50ft requirement not reachable with max lift-off speed!") else: # If max alpha angle lead to v2 > v2 target take it if v2[-1] > v2_target: alpha = alpha[-1] vloff = vloff[-1] else: alpha = np.interp(v2_target, v2, alpha) vloff = np.interp(v2_target, v2, vloff) outputs["vloff:speed"] = vloff outputs["vloff:angle"] = alpha
class _v2(om.ExplicitComponent): """ Calculate V2 safety speed @ defined altitude considering a 30% safety margin on max lift capability (alpha imposed). Find corresponding climb rate margin for imposed thrust rate. Fuel burn is neglected : mass = MTOW. """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_alpha", np.nan, units="rad**-1") self.add_input("data:aerodynamics:aircraft:low_speed:CD0", np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CD", np.nan) self.add_input("data:aerodynamics:wing:low_speed:induced_drag_coefficient", np.nan) self.add_input("data:geometry:wing:area", np.nan, units="m**2") self.add_input("data:geometry:wing:span", np.nan, units="m") self.add_input("data:geometry:landing_gear:height", np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", np.nan, units="kg") self.add_output("v2:speed", units='m/s') self.add_output("v2:angle", units='rad') self.add_output("v2:climb_rate") self.declare_partials("*", "*", method="fd") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"] ) cl_max_clean = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"] cl0 = inputs["data:aerodynamics:wing:low_speed:CL0_clean"] + inputs["data:aerodynamics:flaps:takeoff:CL"] cl_alpha = inputs["data:aerodynamics:wing:low_speed:CL_alpha"] cd0 = inputs["data:aerodynamics:aircraft:low_speed:CD0"] + inputs["data:aerodynamics:flaps:takeoff:CD"] coef_k = inputs["data:aerodynamics:wing:low_speed:induced_drag_coefficient"] wing_area = inputs["data:geometry:wing:area"] wing_span = inputs["data:geometry:wing:span"] lg_height = inputs["data:geometry:landing_gear:height"] mtow = inputs["data:weight:aircraft:MTOW"] # Define atmospheric condition for safety height atm = Atmosphere(SAFETY_HEIGHT, altitude_in_feet=False) # Define Cl considering 30% margin and estimate alpha cl = cl_max_clean / 1.2 ** 2 # V2>=1.2*VS1 alpha_interp = np.linspace(0.0, 30.0, 31) * math.pi / 180.0 cl_interp = cl0 + alpha_interp * cl_alpha alpha = np.interp(cl, cl_interp, alpha_interp) # Calculate drag coefficient k_ground = ( 33. * ((lg_height + SAFETY_HEIGHT) / wing_span) ** 1.5 / (1. + 33. * ((lg_height + SAFETY_HEIGHT) / wing_span) ** 1.5) ) cd = cd0 + k_ground * coef_k * cl ** 2 # Find v2 safety speed for 0% climb rate v2 = math.sqrt((mtow * g) / (0.5 * atm.density * wing_area * cl)) # Estimate climb rate considering alpha~0° and max thrust rate for CS23.65 (loop on error) flight_point = FlightPoint( mach=v2 / atm.speed_of_sound, altitude=SAFETY_HEIGHT, engine_setting=EngineSetting.TAKEOFF, thrust_rate=1.0 ) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) gamma = math.asin(thrust / (mtow * g) - cd / cl) rel_error = 0.1 while rel_error > 0.05: new_gamma = math.asin(thrust / (mtow * g) - cd / cl * math.cos(gamma)) rel_error = abs((new_gamma - gamma) / new_gamma) gamma = new_gamma outputs["v2:speed"] = v2 outputs["v2:angle"] = alpha outputs["v2:climb_rate"] = math.sin(gamma)
class _UpdateArea(om.ExplicitComponent): """ Computes area of horizontal tail plane (internal function) """ def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None def initialize(self): self.options.declare("propulsion_id", default="", types=str) def setup(self): self._engine_wrapper = BundleLoader().instantiate_component( self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:propulsion:count", val=np.nan) self.add_input("settings:weight:aircraft:CG:range", val=0.3) self.add_input("data:mission:sizing:takeoff:thrust_rate", val=np.nan) self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input( "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:propulsion:nacelle:height", val=np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:aircraft:MLW", val=np.nan, units="kg") self.add_input("data:weight:aircraft:CG:aft:x", val=np.nan, units="m") self.add_input("data:weight:airframe:landing_gear:main:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:CG:z", val=np.nan, units="m") self.add_input("data:weight:propulsion:engine:CG:z", val=np.nan, units="m") self.add_input("data:aerodynamics:wing:low_speed:CL0_clean", val=np.nan) self.add_input("data:aerodynamics:wing:low_speed:CM0_clean", val=np.nan) self.add_input("data:aerodynamics:aircraft:landing:CL_max", val=np.nan) self.add_input("data:aerodynamics:aircraft:takeoff:CL_max", val=np.nan) self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean", val=np.nan) self.add_input("data:aerodynamics:flaps:landing:CL", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CL", val=np.nan) self.add_input("data:aerodynamics:flaps:landing:CM", val=np.nan) self.add_input("data:aerodynamics:flaps:takeoff:CM", val=np.nan) self.add_input("data:aerodynamics:horizontal_tail:low_speed:CL_alpha", val=np.nan, units="rad**-1") self.add_input("data:aerodynamics:horizontal_tail:efficiency", val=np.nan) self.add_input("landing:cl_htp", val=np.nan) self.add_input("takeoff:cl_htp", val=np.nan) self.add_input("low_speed:cl_alpha_htp_isolated", val=np.nan) self.add_output("data:geometry:horizontal_tail:area", val=4.0, units="m**2") self.declare_partials( "*", "*", method="fd") # FIXME: write partial avoiding discrete parameters def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): # Sizing constraints for the horizontal tail (methods from Torenbeek). # Limiting cases: Rotating power at takeoff/landing, with the most # forward CG position. Returns maximum area. propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"]) n_engines = inputs["data:geometry:propulsion:count"] cg_range = inputs["settings:weight:aircraft:CG:range"] takeoff_t_rate = inputs["data:mission:sizing:takeoff:thrust_rate"] wing_area = inputs["data:geometry:wing:area"] x_wing_aero_center = inputs["data:geometry:wing:MAC:at25percent:x"] lp_ht = inputs[ "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"] wing_mac = inputs["data:geometry:wing:MAC:length"] mtow = inputs["data:weight:aircraft:MTOW"] mlw = inputs["data:weight:aircraft:MLW"] x_cg_aft = inputs["data:weight:aircraft:CG:aft:x"] z_cg_aircraft = inputs["data:weight:aircraft_empty:CG:z"] z_cg_engine = inputs["data:weight:propulsion:engine:CG:z"] x_lg = inputs["data:weight:airframe:landing_gear:main:CG:x"] cl0_clean = inputs["data:aerodynamics:wing:low_speed:CL0_clean"] cl_max_clean = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"] cl_max_landing = inputs["data:aerodynamics:aircraft:landing:CL_max"] cl_max_takeoff = inputs["data:aerodynamics:aircraft:takeoff:CL_max"] cl_flaps_landing = inputs["data:aerodynamics:flaps:landing:CL"] cl_flaps_takeoff = inputs["data:aerodynamics:flaps:takeoff:CL"] tail_efficiency_factor = inputs[ "data:aerodynamics:horizontal_tail:efficiency"] cl_htp_landing = inputs["landing:cl_htp"] cl_htp_takeoff = inputs["takeoff:cl_htp"] cm_landing = inputs[ "data:aerodynamics:wing:low_speed:CM0_clean"] + inputs[ "data:aerodynamics:flaps:landing:CM"] cm_takeoff = inputs[ "data:aerodynamics:wing:low_speed:CM0_clean"] + inputs[ "data:aerodynamics:flaps:takeoff:CM"] cl_alpha_htp_isolated = inputs["low_speed:cl_alpha_htp_isolated"] z_eng = z_cg_aircraft - z_cg_engine # Conditions for calculation atm = Atmosphere(0.0) rho = atm.density # CASE1: TAKE-OFF ############################################################################################## # method extracted from Torenbeek 1982 p325 # Calculation of take-off minimum speed weight = mtow * g vs0 = math.sqrt(weight / (0.5 * rho * wing_area * cl_max_takeoff)) vs1 = math.sqrt(weight / (0.5 * rho * wing_area * cl_max_clean)) # Rotation speed requirement from FAR 23.51 (depends on number of engines) if n_engines == 1: v_r = vs1 * 1.0 else: v_r = vs1 * 1.1 # Definition of max forward gravity center position x_cg = x_cg_aft - cg_range * wing_mac # Definition of horizontal tail global position x_ht = x_wing_aero_center + lp_ht # Calculation of wheel factor flight_point = FlightPoint(mach=v_r / atm.speed_of_sound, altitude=0.0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=takeoff_t_rate) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) fact_wheel = ( (x_lg - x_cg - z_eng * thrust / weight) / wing_mac * (vs0 / v_r)**2 ) # FIXME: not clear if vs0 or vs1 should be used in formula # Compute aerodynamic coefficients for takeoff @ 0° aircraft angle cl0_takeoff = cl0_clean + cl_flaps_takeoff # Calculation of correction coefficient n_h and n_q n_h = ( x_ht - x_lg ) / lp_ht * tail_efficiency_factor # tail_efficiency_factor: dynamic pressure reduction at # tail (typical value) n_q = 1 + cl_alpha_htp_isolated / cl_htp_takeoff * _ANG_VEL * ( x_ht - x_lg) / v_r # Calculation of volume coefficient based on Torenbeek formula coef_vol = (cl_max_takeoff / (n_h * n_q * cl_htp_takeoff) * (cm_takeoff / cl_max_takeoff - fact_wheel) + cl0_takeoff / cl_htp_takeoff * (x_lg - x_wing_aero_center) / wing_mac) # Calculation of equivalent area area_1 = coef_vol * wing_area * wing_mac / lp_ht # CASE2: LANDING ############################################################################################### # method extracted from Torenbeek 1982 p325 # Calculation of take-off minimum speed weight = mlw * g vs0 = math.sqrt(weight / (0.5 * rho * wing_area * cl_max_landing)) # Rotation speed requirement from FAR 23.73 v_r = vs0 * 1.3 # Calculation of wheel factor flight_point = FlightPoint( mach=v_r / atm.speed_of_sound, altitude=0.0, engine_setting=EngineSetting.IDLE, thrust_rate=0.1 ) # FIXME: fixed thrust rate (should depend on wished descent rate) propulsion_model.compute_flight_points(flight_point) thrust = float(flight_point.thrust) fact_wheel = ( (x_lg - x_cg - z_eng * thrust / weight) / wing_mac * (vs0 / v_r)**2 ) # FIXME: not clear if vs0 or vs1 should be used in formula # Evaluate aircraft overall angle (aoa) cl0_landing = cl0_clean + cl_flaps_landing # Calculation of correction coefficient n_h and n_q n_h = ( x_ht - x_lg ) / lp_ht * tail_efficiency_factor # tail_efficiency_factor: dynamic pressure reduction at # tail (typical value) n_q = 1 + cl_alpha_htp_isolated / cl_htp_landing * _ANG_VEL * ( x_ht - x_lg) / v_r # Calculation of volume coefficient based on Torenbeek formula coef_vol = (cl_max_landing / (n_h * n_q * cl_htp_landing) * (cm_landing / cl_max_landing - fact_wheel) + cl0_landing / cl_htp_landing * (x_lg - x_wing_aero_center) / wing_mac) # Calculation of equivalent area area_2 = coef_vol * wing_area * wing_mac / lp_ht if max(area_1, area_2) < 0.0: print( "Warning: HTP area estimated negative (in ComputeHTArea) forced to 1m²!" ) outputs["data:geometry:horizontal_tail:area"] = 1.0 else: outputs["data:geometry:horizontal_tail:area"] = max(area_1, area_2)