示例#1
0
    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")
示例#2
0
    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",
        )
示例#3
0
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
示例#4
0
    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")
示例#5
0
    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",
        )
示例#7
0
    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")
示例#8
0
    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")
示例#9
0
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
示例#10
0
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
示例#11
0
    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")
示例#12
0
    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
示例#13
0
    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
示例#15
0
    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")
示例#16
0
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
示例#18
0
    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")
示例#19
0
    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)
示例#21
0
    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²
示例#23
0
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
示例#24
0
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
示例#25
0
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
示例#26
0
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
示例#27
0
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
示例#28
0
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
示例#29
0
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)
示例#30
0
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)