示例#1
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
示例#2
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
示例#3
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
示例#4
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
示例#6
0
class ComputeFuselageGeometryCabinSizingFL(ExplicitComponent):
    # TODO: Document equations. Cite sources
    """
    Geometry of fuselage - Cabin is sized based on layout (seats, aisle...) and additional rear length (Fixed Length).
    """
    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:cabin:seats:passenger:NPAX_max",
                       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:cabin:luggage:mass_max",
                       val=np.nan,
                       units="kg")
        self.add_input("data:geometry:fuselage:rear_length", units="m")

        self.add_output("data:geometry:cabin:NPAX")
        self.add_output("data:geometry:fuselage:length", val=10.0, 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: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_max = inputs["data:geometry:cabin:seats:passenger:NPAX_max"]
        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"]
        luggage_mass_max = inputs["data:geometry:cabin:luggage:mass_max"]
        prop_layout = inputs["data:geometry:propulsion:layout"]
        lar = inputs["data:geometry:fuselage:rear_length"]

        # Length of instrument panel
        l_instr = 0.7
        # Length of pax cabin
        # noinspection PyBroadException
        npax = math.ceil(
            float(npax_max) / float(seats_p_row)) * float(seats_p_row)
        n_rows = npax / float(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 (80% of internal radius section can be filled with luggage)
        luggage_density = 161.0  # In kg/m3
        l_lug = (luggage_mass_max / luggage_density) / (0.8 * math.pi * r_i**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 = lav + cabin_length + lar

        # 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
        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: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
示例#7
0
class ComputeFlightCGCase(ExplicitComponent):
    """ Center of gravity estimation for all load cases in flight"""
    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: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 compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        luggage_mass_max = float(
            inputs["data:geometry:cabin:luggage:mass_max"])
        n_pax_max = inputs["data:geometry:cabin:seats:passenger:NPAX_max"]
        l0_wing = inputs["data:geometry:wing:MAC:length"]
        fa_length = inputs["data:geometry:wing:MAC:at25percent:x"]
        lav = inputs["data:geometry:fuselage:front_length"]
        l_pax = inputs["data:geometry:fuselage:PAX_length"]
        l_pilot_seat = inputs["data:geometry:cabin:seats:pilot:length"]
        count_by_row = inputs[
            "data:geometry:cabin:seats:passenger:count_by_row"]
        l_pass_seat = inputs["data:geometry:cabin:seats:passenger:length"]
        cg_rear_fret = inputs["data:weight:payload:rear_fret:CG:x"]
        x_cg_plane_aft = inputs["data:weight:aircraft_empty:CG:x"]
        m_empty = inputs["data:weight:aircraft_empty:mass"]
        m_unusable_fuel = inputs["data:weight:propulsion:unusable_fuel:mass"]
        cg_tank = inputs["data:weight:propulsion:tank:CG:x"]
        mfw = inputs["data:weight:aircraft:MFW"]

        l_instr = 0.7
        cg_pilot = lav + l_instr + l_pilot_seat / 2.0

        n_pax_array = np.linspace(0., n_pax_max, int(n_pax_max) + 1)

        m_pilot_single = 77.
        m_pilot_array = np.array(
            [2. * m_pilot_single])  # Without the pilots and with the 2 pilots

        m_fuel_min = m_unusable_fuel + self.min_in_flight_fuel(inputs)

        m_fuel_array = np.array([m_fuel_min, mfw])

        m_lug_array = np.array([0.0, luggage_mass_max])

        cg_list = []

        for m_pilot in m_pilot_array:

            for m_fuel in m_fuel_array:

                for m_lug in m_lug_array:

                    for n_pax in n_pax_array:

                        n_row = np.ceil(n_pax / count_by_row)

                        x_cg_pax_fwd = 0.0
                        for idx in range(int(n_row)):
                            row_cg = (idx + 0.5) * l_pass_seat
                            nb_pers = min(count_by_row,
                                          n_pax_max - idx * count_by_row)
                            x_cg_pax_fwd += row_cg * nb_pers / n_pax_max

                        x_cg_pax_aft = 0.0
                        for idx in range(int(n_row)):
                            row_cg = l_pax - l_pilot_seat - (idx +
                                                             0.5) * l_pass_seat
                            nb_pers = min(count_by_row,
                                          n_pax_max - idx * count_by_row)
                            x_cg_pax_aft += row_cg * nb_pers / n_pax_max

                        cg_pax_array = np.array([
                            lav + l_instr + l_pilot_seat + x_cg_pax_fwd,
                            lav + l_instr + l_pilot_seat + x_cg_pax_aft
                        ])

                        for cg_pax in cg_pax_array:

                            m_pax_array = np.array([n_pax * 80., n_pax * 90.])

                            for m_pax in m_pax_array:

                                mass = m_pax + m_pilot + m_fuel + m_lug + m_empty
                                cg = (m_empty * x_cg_plane_aft + m_pax * cg_pax
                                      + m_pilot * cg_pilot + m_fuel * cg_tank +
                                      m_lug * cg_rear_fret) / mass
                                cg_list.append(cg)

        cg_aft = max(cg_list)
        cg_fwd = min(cg_list)

        cg_fwd_ratio_pl = (cg_fwd - fa_length + 0.25 * l0_wing) / l0_wing
        cg_aft_ratio_pl = (cg_aft - fa_length + 0.25 * l0_wing) / l0_wing

        outputs[
            "data:weight:aircraft:CG:flight_condition:max:MAC_position"] = cg_aft_ratio_pl
        outputs[
            "data:weight:aircraft:CG:flight_condition:min:MAC_position"] = cg_fwd_ratio_pl

    def min_in_flight_fuel(self, inputs):

        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs),
            inputs["data:geometry:propulsion:count"])

        # noinspection PyTypeChecker
        mtow = inputs["data:weight:aircraft:MTOW"]

        vh = self.max_speed(inputs, 0.0, mtow)

        atm = Atmosphere(0.0, altitude_in_feet=False)
        flight_point = FlightPoint(mach=vh / atm.speed_of_sound,
                                   altitude=0.0,
                                   engine_setting=EngineSetting.TAKEOFF,
                                   thrust_rate=1.0)

        propulsion_model.compute_flight_points(flight_point)
        m_fuel = propulsion_model.get_consumed_mass(flight_point, 30. * 60.)
        # Fuel necessary for a half-hour at max continuous power

        return m_fuel

    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
示例#8
0
class _compute_cruise(om.ExplicitComponent):
    """
    Compute the fuel consumption on cruise segment with constant VTAS and altitude.
    The hypothesis of small alpha/gamma angles is done.
    """

    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:TLAR:range", np.nan, units="m")
        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:fuel", np.nan, units="kg")
        self.add_input("data:mission:sizing:main_route:climb:distance", np.nan, units="m")
        self.add_input("data:mission:sizing:main_route:descent:distance", np.nan, units="m")

        self.add_output("data:mission:sizing:main_route:cruise:fuel", units="kg")
        self.add_output("data:mission:sizing:main_route:cruise:distance", units="m")
        self.add_output("data:mission:sizing:main_route:cruise:duration", units="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",
                "data:mission:sizing:main_route:climb:fuel",
                "data:mission:sizing:main_route:climb:distance",
                "data:mission:sizing:main_route:descent:distance",
            ],
            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"]
        )
        v_tas = inputs["data:TLAR:v_cruise"]
        cruise_distance = max(
            0.0,
            (
                inputs["data:TLAR:range"]
                - inputs["data:mission:sizing:main_route:climb:distance"]
                - inputs["data:mission:sizing:main_route:descent:distance"]
            )
        )
        cruise_altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k = inputs["data:aerodynamics:aircraft:cruise:induced_drag_coefficient"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_ho = inputs["data:mission:sizing:holding:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        m_cl = inputs["data:mission:sizing:main_route:climb:fuel"]

        # Define specific time step ~1000 points for calculation
        time_step = (cruise_distance / v_tas) / 1000.0

        # Define initial conditions
        distance_t = 0.0
        time_t = 0.0
        mass_fuel_t = 0.0
        mass_t = mtow - (m_to + m_ho + m_tk + m_ic + m_cl)
        atm_0 = Atmosphere(0.0)
        atm = Atmosphere(cruise_altitude, altitude_in_feet=False)
        v_cas = v_tas / math.sqrt(atm_0.density / atm.density)

        while distance_t < cruise_distance:

            # Calculate Cl - Cd and corresponding drag
            cl = mass_t * g / (0.5 * atm.density * wing_area * v_tas ** 2)
            cd = cd0 + coef_k * cl ** 2
            drag = 0.5 * atm.density * wing_area * cd * v_tas ** 2

            # Evaluate sfc
            mach = math.sqrt(
                5 * ((atm_0.pressure / atm.pressure * (
                        (1 + 0.2 * (v_cas / atm_0.speed_of_sound) ** 2) ** 3.5 - 1
                ) + 1) ** (1 / 3.5) - 1)
            )
            flight_point = FlightPoint(
                mach=mach, altitude=cruise_altitude, engine_setting=EngineSetting.CRUISE,
                thrust_is_regulated=True, thrust=drag,
            )
            propulsion_model.compute_flight_points(flight_point)
            # If thrust exceed max thrust exit cruise calculation
            if float(flight_point.thrust_rate) > 1.0:
                warnings.warn("The cruise strategy exceeds propulsion power!")
                mass_fuel_t = 0.0
                time_t = 0.0
                break

            # Calculate distance increase
            distance_t += v_tas * min(time_step, (cruise_distance - distance_t) / v_tas)

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(
                flight_point,
                min(time_step, (cruise_distance - distance_t) / v_tas)
            )
            mass_t = mass_t - propulsion_model.get_consumed_mass(
                flight_point,
                min(time_step, (cruise_distance - distance_t) / v_tas)
            )
            time_t += min(time_step, (cruise_distance - distance_t) / v_tas)

        outputs["data:mission:sizing:main_route:cruise:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:cruise:distance"] = distance_t
        outputs["data:mission:sizing:main_route:cruise:duration"] = time_t
示例#9
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
示例#10
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)
示例#11
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
示例#12
0
class ComputeVTArea(om.ExplicitComponent):
    """
    Computes needed vt area to:
      - have enough rotational moment/controllability during cruise
      - compensate 1-failed engine linear trajectory at limited altitude (5000ft)
    """

    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="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(
            "*",
            [
                "data:geometry:wing:area",
                "data:geometry:wing:span",
                "data:geometry:wing:MAC:length",
                "data:weight:aircraft:CG:aft:MAC_position",
                "data:aerodynamics:fuselage:cruise:CnBeta",
                "data:aerodynamics:vertical_tail:cruise:CL_alpha",
                "data:TLAR:v_cruise",
                "data:TLAR:v_approach",
                "data:mission:sizing:main_route:cruise:altitude",
                "data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25",
                "data:geometry:propulsion:nacelle:wet_area",
                "data:geometry:propulsion:nacelle:y",
                "data:geometry:vertical_tail:area",
            ],
            method="fd",
        )

    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
        # Sizing constraints for the vertical tail.
        # Limiting cases: rotating torque objective (cn_beta_goal) during cruise, and  
        # compensation of engine failure induced torque at approach speed/altitude. 
        # Returns maximum area.

        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"]
        )
        engine_number = inputs["data:geometry:propulsion:count"]
        wing_area = inputs["data:geometry:wing:area"]
        span = inputs["data:geometry:wing:span"]
        l0_wing = inputs["data:geometry:wing:MAC:length"]
        cg_mac_position = inputs["data:weight:aircraft:CG:aft:MAC_position"]
        cn_beta_fuselage = inputs["data:aerodynamics:fuselage:cruise:CnBeta"]
        cl_alpha_vt = inputs["data:aerodynamics:vertical_tail:cruise:CL_alpha"]
        cruise_speed = inputs["data:TLAR:v_cruise"]
        approach_speed = inputs["data:TLAR:v_approach"]
        cruise_altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
        wing_htp_distance = inputs["data:geometry:vertical_tail:MAC:at25percent:x:from_wingMAC25"]
        nac_wet_area = inputs["data:geometry:propulsion:nacelle:wet_area"]
        y_nacelle = inputs["data:geometry:propulsion:nacelle:y"]

        # CASE1: OBJECTIVE TORQUE @ CRUISE #############################################################################

        atm = Atmosphere(cruise_altitude)
        speed_of_sound = atm.speed_of_sound
        cruise_mach = cruise_speed / speed_of_sound
        # Matches suggested goal by Raymer, Fig 16.20
        cn_beta_goal = 0.0569 - 0.01694 * cruise_mach + 0.15904 * cruise_mach ** 2

        required_cnbeta_vtp = cn_beta_goal - cn_beta_fuselage
        distance_to_cg = wing_htp_distance + 0.25 * l0_wing - cg_mac_position * l0_wing
        area_1 = required_cnbeta_vtp / (distance_to_cg / wing_area / span * cl_alpha_vt)

        # CASE2: ENGINE FAILURE COMPENSATION DURING CLIMB ##############################################################

        failure_altitude = 5000.0  # CS23 for Twin engine - at 5000ft
        atm = Atmosphere(failure_altitude)
        speed_of_sound = atm.speed_of_sound
        pressure = atm.pressure
        if engine_number == 2.0:
            stall_speed = approach_speed / 1.3
            mc_speed = 1.2 * stall_speed  # Flights mechanics from GA - Serge Bonnet CS23
            mc_mach = mc_speed / speed_of_sound
            # Calculation of engine power for given conditions
            flight_point = FlightPoint(
                mach=mc_mach, altitude=failure_altitude, engine_setting=EngineSetting.CLIMB,
                thrust_rate=1.0
            )  # forced to maximum thrust
            propulsion_model.compute_flight_points(flight_point)
            thrust = float(flight_point.thrust)
            # Calculation of engine thrust and nacelle drag (failed one)
            nac_drag = 0.07 * nac_wet_area  # FIXME: the form factor should not be fixed outside propulsion module!
            # Torque compensation
            area_2 = (
                    2 * (y_nacelle / wing_htp_distance) * (thrust + nac_drag)
                    / (pressure * mc_mach ** 2 * 0.9 * 0.42 * 10)
            )
        else:
            area_2 = 0.0

        outputs["data:geometry:vertical_tail:area"] = max(area_1, area_2)
示例#13
0
class ComputeTORotationLimit(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("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 compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):

        cl_max_takeoff = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"]
        cl0_clean = inputs["data:aerodynamics:wing:low_speed:CL0_clean"]
        cl_flaps_takeoff = inputs["data:aerodynamics:flaps:takeoff:CL"]
        cm_takeoff = inputs["takeoff:cm_wing"]
        cl_alpha_htp_isolated = inputs["data:aerodynamics:horizontal_tail:low_speed:CL_alpha_isolated"]
        cl_htp = inputs["takeoff:cl_htp"]
        tail_efficiency_factor = inputs["data:aerodynamics:horizontal_tail:efficiency"]

        n_engines = inputs["data:geometry:propulsion:count"]
        x_wing_aero_center = inputs["data:geometry:wing:MAC:at25percent:x"]
        wing_area = inputs["data:geometry:wing:area"]
        wing_mac = inputs["data:geometry:wing:MAC:length"]
        ht_area = inputs["data:geometry:horizontal_tail:area"]
        lp_ht = inputs["data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"]

        mtow = inputs["data:weight:aircraft:MTOW"]

        x_lg = inputs["data:weight:airframe:landing_gear:main:CG:x"]
        z_cg_aircraft = inputs["data:weight:aircraft_empty:CG:z"]
        z_cg_engine = inputs["data:weight:propulsion:engine:CG:z"]

        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:count"]
        )

        # Conditions for calculation
        atm = Atmosphere(0.0)
        rho = atm.density
        sos = atm.speed_of_sound

        # Calculation of take-off minimum speed
        weight = mtow * g
        vs1 = math.sqrt(weight / (0.5 * rho * wing_area * cl_max_takeoff))

        if n_engines == 1.0:
            vr = 1.10 * vs1
        else:
            vr = 1.0 * vs1

        mach_r = vr / sos

        flight_point = FlightPoint(
            mach=mach_r, altitude=0.0, engine_setting=EngineSetting.TAKEOFF,
            thrust_rate=1.0
        )
        propulsion_model.compute_flight_points(flight_point)
        thrust = float(flight_point.thrust)

        x_ht = x_wing_aero_center + lp_ht

        # Compute aerodynamic coefficients for takeoff @ 0° aircraft angle
        cl0_takeoff = cl0_clean + cl_flaps_takeoff

        eta_q = 1. + cl_alpha_htp_isolated / cl_htp * _ANG_VEL * (x_ht - x_lg) / vr
        eta_h = (x_ht - x_lg) / lp_ht * tail_efficiency_factor

        k_cl = cl_max_takeoff / (eta_q * eta_h * cl_htp)

        tail_volume_coefficient = ht_area * lp_ht / (wing_area * wing_mac)

        zt = z_cg_aircraft - z_cg_engine
        engine_contribution = zt * thrust / weight

        x_cg = (
                       1. / k_cl * (
                       tail_volume_coefficient - cl0_takeoff / cl_htp * (x_lg / wing_mac - 0.25)
               ) - cm_takeoff / cl_max_takeoff
               ) * (vr / vs1) ** 2.0 + x_lg - engine_contribution

        outputs["data:handling_qualities:to_rotation_limit:x"] = x_cg

        x_cg_ratio = (x_cg - x_wing_aero_center + 0.25 * wing_mac) / wing_mac

        outputs["data:handling_qualities:to_rotation_limit:MAC_position"] = x_cg_ratio
示例#14
0
class _compute_descent(AircraftEquilibrium):
    """
    Compute the fuel consumption on descent segment with constant VCAS and descent
    rate.
    The hypothesis of small alpha angle is done.
    Warning: Descent rate is reduced if cd/cl < abs(desc_rate)!
    """
    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):
        super().setup()
        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:mission:sizing:main_route:descent:descent_rate",
                       np.nan)
        self.add_input("data:aerodynamics:aircraft:cruise:optimal_CL", np.nan)
        self.add_input("data:aerodynamics:aircraft:cruise:CD0", np.nan)
        self.add_input(
            "data:aerodynamics:wing:cruise:induced_drag_coefficient", np.nan)
        self.add_input(
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient",
            np.nan)
        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:fuel",
                       np.nan,
                       units="kg")
        self.add_input("data:mission:sizing:main_route:cruise:fuel",
                       np.nan,
                       units="kg")

        self.add_output("data:mission:sizing:main_route:descent:fuel",
                        units="kg")
        self.add_output("data:mission:sizing:main_route:descent:distance",
                        0.0,
                        units="m")
        self.add_output("data:mission:sizing:main_route:descent:duration",
                        units="s")

        self.declare_partials(
            "*",
            [
                "data:aerodynamics:aircraft:cruise:optimal_CL",
                "data:aerodynamics:aircraft:cruise:CD0",
                "data:aerodynamics:wing:cruise:induced_drag_coefficient",
                "data:aerodynamics:horizontal_tail: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",
                "data:mission:sizing:main_route:climb:fuel",
                "data:mission:sizing:main_route:cruise:fuel",
            ],
            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"])
        cruise_altitude = inputs[
            "data:mission:sizing:main_route:cruise:altitude"]
        descent_rate = -abs(
            inputs["data:mission:sizing:main_route:descent:descent_rate"])
        cl = inputs["data:aerodynamics:aircraft:cruise:optimal_CL"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k_wing = inputs[
            "data:aerodynamics:wing:cruise:induced_drag_coefficient"]
        coef_k_htp = inputs[
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_ho = inputs["data:mission:sizing:holding:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        m_cl = inputs["data:mission:sizing:main_route:climb:fuel"]
        m_cr = inputs["data:mission:sizing:main_route:cruise:fuel"]

        # Define initial conditions
        t_start = time.time()
        gamma = math.asin(descent_rate)
        altitude_t = copy.deepcopy(cruise_altitude)
        distance_t = 0.0
        time_t = 0.0
        mass_fuel_t = 0.0
        mass_t = mtow - (m_to + m_ho + m_tk + m_ic + m_cl + m_cr)
        atm_0 = Atmosphere(0.0)
        warning = False
        # Calculate defined VCAS at the beginning of descent (cos(gamma)~1)
        v_cas = math.sqrt((mass_t * g) * math.cos(descent_rate) /
                          (0.5 * atm_0.density * wing_area * cl))

        # Define specific time step ~POINTS_NB_CLIMB points for calculation (with ground conditions)
        time_step = (
            -cruise_altitude /
            (v_cas * math.sin(descent_rate))) / float(POINTS_NB_DESCENT)

        while altitude_t > 0.0:

            # Define air properties and calculate VTAS
            atm = Atmosphere(altitude_t, altitude_in_feet=False)
            mach = math.sqrt(
                5 * ((atm_0.pressure / atm.pressure *
                      ((1 + 0.2 *
                        (v_cas / atm_0.speed_of_sound)**2)**3.5 - 1) + 1)**
                     (1 / 3.5) - 1))
            v_tas = mach * atm.speed_of_sound
            # Calculate equilibrium and induced drag
            cl_wing, cl_htp_only, cl_elevator, _ = self.found_cl_repartition(
                inputs, 1.0, mass_t, (0.5 * atm.density * v_tas**2), False)
            cd = cd0 + coef_k_wing * cl_wing**2 + coef_k_htp * (cl_htp_only +
                                                                cl_elevator)**2
            cl = ((mass_t * g) * math.cos(descent_rate) /
                  (0.5 * atm.density * wing_area * v_tas**2))
            cl_cd = cl / cd
            drag = 0.5 * atm.density * wing_area * cd * v_tas**2

            # Calculate necessary Thrust to maintain VCAS and descent rate
            # if T<0N, VCAS is maintained reducing gamma/descent rate and engine in IDLE condition
            thrust = drag + (mass_t * g) * math.sin(gamma)
            if thrust <= 0.0:
                flight_point = FlightPoint(
                    mach=mach,
                    altitude=altitude_t,
                    engine_setting=EngineSetting.IDLE,
                    thrust_rate=0.2)  # FIXME: define IDLE maybe?
                descent_rate = -1 / cl_cd
                gamma = math.asin(descent_rate)
                warning = True
            else:
                # FIXME: DESCENT setting on engine does not exist, replaced by CRUISE for test
                flight_point = FlightPoint(
                    mach=mach,
                    altitude=altitude_t,
                    engine_setting=EngineSetting.CRUISE,
                    thrust_is_regulated=True,
                    thrust=thrust,
                )
            propulsion_model.compute_flight_points(flight_point)

            # Calculate distance increase
            v_x = v_tas * math.cos(descent_rate)
            v_z = v_tas * math.sin(descent_rate)
            time_step = min(time_step, -altitude_t / v_z)
            distance_t += v_x * time_step
            altitude_t += v_z * time_step

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(
                flight_point, time_step)
            mass_t = mass_t - propulsion_model.get_consumed_mass(
                flight_point, time_step)
            time_t += time_step

            # Check calculation duration
            if (time.time() - t_start) > MAX_CALCULATION_TIME:
                raise Exception(
                    "Time calculation duration for descent phase [{}s] exceeded!"
                    .format(MAX_CALCULATION_TIME))

        if warning:
            warnings.warn("Descent rate has been reduced!")

        outputs["data:mission:sizing:main_route:descent:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:descent:distance"] = distance_t
        outputs["data:mission:sizing:main_route:descent:duration"] = time_t
示例#15
0
class _compute_cruise(AircraftEquilibrium):
    """
    Compute the fuel consumption on cruise segment with constant VTAS and altitude.
    The hypothesis of small alpha/gamma angles is done.
    """
    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):
        super().setup()
        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:TLAR:range", np.nan, units="m")
        self.add_input("data:aerodynamics:aircraft:cruise:CD0", np.nan)
        self.add_input(
            "data:aerodynamics:wing:cruise:induced_drag_coefficient", np.nan)
        self.add_input(
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient",
            np.nan)
        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:fuel",
                       np.nan,
                       units="kg")
        self.add_input("data:mission:sizing:main_route:climb:distance",
                       np.nan,
                       units="m")
        self.add_input("data:mission:sizing:main_route:descent:distance",
                       np.nan,
                       units="m")

        self.add_output("data:mission:sizing:main_route:cruise:fuel",
                        units="kg")
        self.add_output("data:mission:sizing:main_route:cruise:distance",
                        units="m")
        self.add_output("data:mission:sizing:main_route:cruise:duration",
                        units="s")

        self.declare_partials(
            "*",
            [
                "data:aerodynamics:aircraft:cruise:CD0",
                "data:aerodynamics:wing:cruise:induced_drag_coefficient",
                "data:aerodynamics:horizontal_tail: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",
                "data:mission:sizing:main_route:climb:fuel",
                "data:mission:sizing:main_route:climb:distance",
                "data:mission:sizing:main_route:descent:distance",
            ],
            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"])
        v_tas = inputs["data:TLAR:v_cruise"]
        cruise_distance = max(
            0.0, (inputs["data:TLAR:range"] -
                  inputs["data:mission:sizing:main_route:climb:distance"] -
                  inputs["data:mission:sizing:main_route:descent:distance"]))
        cruise_altitude = inputs[
            "data:mission:sizing:main_route:cruise:altitude"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k_wing = inputs[
            "data:aerodynamics:wing:cruise:induced_drag_coefficient"]
        coef_k_htp = inputs[
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        m_cl = inputs["data:mission:sizing:main_route:climb:fuel"]

        # Define specific time step ~POINTS_NB_CRUISE points for calculation
        time_step = (cruise_distance / v_tas) / float(POINTS_NB_CRUISE)

        # Define initial conditions
        t_start = time.time()
        distance_t = 0.0
        time_t = 0.0
        mass_fuel_t = 0.0
        mass_t = mtow - (m_to + m_tk + m_ic + m_cl)
        atm = Atmosphere(cruise_altitude, altitude_in_feet=False)

        while distance_t < cruise_distance:

            # Calculate equilibrium and induced drag
            cl_wing, cl_htp_only, cl_elevator, _ = self.found_cl_repartition(
                inputs, 1.0, mass_t, (0.5 * atm.density * v_tas**2), False)
            cd = cd0 + coef_k_wing * cl_wing**2 + coef_k_htp * (cl_htp_only +
                                                                cl_elevator)**2
            drag = 0.5 * atm.density * wing_area * cd * v_tas**2

            # Evaluate sfc
            mach = v_tas / atm.speed_of_sound
            flight_point = FlightPoint(
                mach=mach,
                altitude=cruise_altitude,
                engine_setting=EngineSetting.CRUISE,
                thrust_is_regulated=True,
                thrust=drag,
            )
            propulsion_model.compute_flight_points(flight_point)
            # If thrust exceed max thrust exit cruise calculation
            if float(flight_point.thrust_rate) > 1.0:
                warnings.warn("The cruise strategy exceeds propulsion power!")
                mass_fuel_t = 0.0
                time_t = 0.0
                break

            # Calculate distance increase
            distance_t += v_tas * min(time_step,
                                      (cruise_distance - distance_t) / v_tas)

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(
                flight_point,
                min(time_step, (cruise_distance - distance_t) / v_tas))
            mass_t = mass_t - propulsion_model.get_consumed_mass(
                flight_point,
                min(time_step, (cruise_distance - distance_t) / v_tas))
            time_t += min(time_step, (cruise_distance - distance_t) / v_tas)

            # Check calculation duration
            if (time.time() - t_start) > MAX_CALCULATION_TIME:
                raise Exception(
                    "Time calculation duration for cruise phase [{}s] exceeded!"
                    .format(MAX_CALCULATION_TIME))

        outputs["data:mission:sizing:main_route:cruise:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:cruise:distance"] = distance_t
        outputs["data:mission:sizing:main_route:cruise:duration"] = time_t
示例#16
0
class _compute_climb(AircraftEquilibrium):
    """
    Compute the fuel consumption on climb segment with constant VCAS and fixed thrust ratio.
    The hypothesis of small alpha/gamma angles is done.
    """
    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):
        super().setup()
        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:wing:cruise:induced_drag_coefficient", np.nan)
        self.add_input(
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient",
            np.nan)
        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:wing:cruise:induced_drag_coefficient",
                "data:aerodynamics:horizontal_tail: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",
        )

    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"])
        cruise_altitude = inputs[
            "data:mission:sizing:main_route:cruise:altitude"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k_wing = inputs[
            "data:aerodynamics:wing:cruise:induced_drag_coefficient"]
        coef_k_htp = inputs[
            "data:aerodynamics:horizontal_tail:cruise:induced_drag_coefficient"]
        cl_max_clean = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        thrust_rate = inputs[
            "data:mission:sizing:main_route:climb:thrust_rate"]

        # Define initial conditions
        t_start = time.time()
        altitude_t = SAFETY_HEIGHT  # conversion to m
        distance_t = 0.0
        time_t = 0.0
        mass_t = mtow - (m_to + m_tk + m_ic)
        mass_fuel_t = 0.0
        atm_0 = Atmosphere(0.0)

        # FIXME: VCAS strategy is specific to ICE-propeller configuration, should be an input
        cl = math.sqrt(3 * cd0 / coef_k_wing)
        atm = Atmosphere(altitude_t, altitude_in_feet=False)
        v_cas = math.sqrt((mass_t * g) / (0.5 * atm.density * wing_area * cl))
        vs1 = math.sqrt(
            (mass_t * g) / (0.5 * atm.density * wing_area * cl_max_clean))

        mach = math.sqrt(5 * ((atm_0.pressure / atm.pressure * (
            (1 + 0.2 *
             (v_cas / atm_0.speed_of_sound)**2)**3.5 - 1) + 1)**(1 / 3.5) - 1))
        v_tas = max(mach * atm.speed_of_sound, 1.3 * vs1)
        mach = v_tas / atm.speed_of_sound
        # Define specific time step ~POINTS_NB_CLIMB points for calculation (with ground conditions)
        cl_wing, cl_htp_only, cl_elevator, _ = self.found_cl_repartition(
            inputs, 1.0, mass_t, (0.5 * atm.density * v_tas**2), False)
        cd = cd0 + coef_k_wing * cl_wing**2 + coef_k_htp * (cl_htp_only +
                                                            cl_elevator)**2
        flight_point = FlightPoint(
            mach=mach,
            altitude=SAFETY_HEIGHT,
            engine_setting=EngineSetting.CLIMB,
            thrust_rate=thrust_rate)  # with engine_setting as EngineSetting
        propulsion_model.compute_flight_points(flight_point)
        thrust = float(flight_point.thrust)
        climb_rate = thrust / (mass_t * g) - cd / (cl_wing + cl_htp_only +
                                                   cl_elevator)
        time_step = ((cruise_altitude - SAFETY_HEIGHT) /
                     (v_tas * math.sin(climb_rate))) / float(POINTS_NB_CLIMB)

        while altitude_t < cruise_altitude:

            # Define air properties
            atm = Atmosphere(altitude_t, altitude_in_feet=False)
            vs1 = math.sqrt(
                (mass_t * g) / (0.5 * atm.density * wing_area * cl_max_clean))
            # Evaluate thrust and sfc
            mach = math.sqrt(
                5 * ((atm_0.pressure / atm.pressure *
                      ((1 + 0.2 *
                        (v_cas / atm_0.speed_of_sound)**2)**3.5 - 1) + 1)**
                     (1 / 3.5) - 1))
            v_tas = max(mach * atm.speed_of_sound, 1.3 * vs1)
            mach = v_tas / atm.speed_of_sound
            flight_point = FlightPoint(
                mach=mach,
                altitude=altitude_t,
                engine_setting=EngineSetting.CLIMB,
                thrust_rate=thrust_rate
            )  # with engine_setting as EngineSetting
            propulsion_model.compute_flight_points(flight_point)
            thrust = float(flight_point.thrust)

            # Calculate equilibrium and induced drag
            cl_wing, cl_htp_only, cl_elevator, _ = self.found_cl_repartition(
                inputs, 1.0, mass_t, (0.5 * atm.density * v_tas**2), False)
            cd = cd0 + coef_k_wing * cl_wing**2 + coef_k_htp * (cl_htp_only +
                                                                cl_elevator)**2

            # Calculate climb rate and height increase
            climb_rate = thrust / (mass_t * g) - cd / (cl_wing + cl_htp_only +
                                                       cl_elevator)
            v_z = v_tas * math.sin(climb_rate)
            v_x = v_tas * math.cos(climb_rate)
            time_step = min(time_step, (cruise_altitude - altitude_t) / v_z)
            altitude_t += v_z * time_step
            distance_t += v_x * time_step

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(
                flight_point, time_step)
            mass_t = mass_t - propulsion_model.get_consumed_mass(
                flight_point, time_step)
            time_t += time_step

            # Check calculation duration
            if (time.time() - t_start) > MAX_CALCULATION_TIME:
                raise Exception(
                    "Time calculation duration for climb phase [{}s] exceeded!"
                    .format(MAX_CALCULATION_TIME))

        outputs["data:mission:sizing:main_route:climb:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:climb:distance"] = distance_t
        outputs["data:mission:sizing:main_route:climb:duration"] = time_t
        outputs["data:mission:sizing:main_route:climb:v_cas"] = v_cas
示例#17
0
class SizingFlight(om.ExplicitComponent):
    """
    Simulates a complete flight mission with diversion.
    """

    def __init__(self, **kwargs):
        """
        Computes thrust, SFC and thrust rate by direct call to engine model.

        Options:
          - propulsion_id: (mandatory) the identifier of the propulsion wrapper.
          - out_file: if provided, a csv file will be written at provided path with all computed
                      flight points. If path is relative, it will be resolved from working
                      directory
        """
        super().__init__(**kwargs)
        self.flight_points = None
        self._engine_wrapper = None

    def initialize(self):
        self.options.declare("propulsion_id", default="", types=str)
        self.options.declare("out_file", default="", types=str)

    def setup(self):
        self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"])
        self._engine_wrapper.setup(self)

        # Inputs -----------------------------------------------------------------------------------
        self.add_input("data:TLAR:cruise_mach", np.nan)
        self.add_input("data:TLAR:range", np.nan, units="m")

        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:aerodynamics:aircraft:cruise:CL", np.nan, shape=POLAR_POINT_COUNT)
        self.add_input("data:aerodynamics:aircraft:cruise:CD", np.nan, shape=POLAR_POINT_COUNT)

        self.add_input("data:aerodynamics:aircraft:takeoff:CL", np.nan, shape=POLAR_POINT_COUNT)
        self.add_input("data:aerodynamics:aircraft:takeoff:CD", np.nan, shape=POLAR_POINT_COUNT)

        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:V2", np.nan, units="m/s")

        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:climb:thrust_rate", np.nan)
        self.add_input("data:mission:sizing:descent:thrust_rate", np.nan)

        self.add_input("data:mission:sizing:diversion:distance", np.nan, units="m")
        self.add_input("data:mission:sizing:holding:duration", np.nan, units="s")

        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_input("data:mission:sizing:taxi_in:thrust_rate", np.nan)

        # Outputs ----------------------------------------------------------------------------------
        self.add_output("data:mission:sizing:initial_climb:fuel", units="kg")
        self.add_output("data:mission:sizing:main_route:climb:fuel", units="kg")
        self.add_output("data:mission:sizing:main_route:cruise:fuel", units="kg")
        self.add_output("data:mission:sizing:main_route:descent:fuel", units="kg")

        self.add_output("data:mission:sizing:initial_climb:distance", units="m")
        self.add_output("data:mission:sizing:main_route:climb:distance", units="m")
        self.add_output("data:mission:sizing:main_route:cruise:distance", units="m")
        self.add_output("data:mission:sizing:main_route:descent:distance", units="m")

        self.add_output("data:mission:sizing:initial_climb:duration", units="s")
        self.add_output("data:mission:sizing:main_route:climb:duration", units="s")
        self.add_output("data:mission:sizing:main_route:cruise:duration", units="s")
        self.add_output("data:mission:sizing:main_route:descent:duration", units="s")

        self.add_output("data:mission:sizing:diversion:climb:fuel", units="kg")
        self.add_output("data:mission:sizing:diversion:cruise:fuel", units="kg")
        self.add_output("data:mission:sizing:diversion:descent:fuel", units="kg")

        self.add_output("data:mission:sizing:diversion:climb:distance", units="m")
        self.add_output("data:mission:sizing:diversion:cruise:distance", units="m")
        self.add_output("data:mission:sizing:diversion:descent:distance", units="m")

        self.add_output("data:mission:sizing:diversion:climb:duration", units="s")
        self.add_output("data:mission:sizing:diversion:cruise:duration", units="s")
        self.add_output("data:mission:sizing:diversion:descent:duration", units="s")

        self.add_output("data:mission:sizing:holding:fuel", units="kg")
        self.add_output("data:mission:sizing:taxi_in:fuel", units="kg")

        self.add_output("data:mission:sizing:ZFW", units="kg")
        self.add_output("data:mission:sizing:fuel", units="kg")

        self.declare_partials(["*"], ["*"])

    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
        try:
            self.compute_mission(inputs, outputs)
        except IndexError:
            self.compute_breguet(inputs, outputs)

    def compute_breguet(self, inputs, outputs):
        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:engine:count"]
        )
        high_speed_polar = Polar(
            inputs["data:aerodynamics:aircraft:cruise:CL"],
            inputs["data:aerodynamics:aircraft:cruise:CD"],
        )

        breguet = Breguet(
            propulsion_model,
            max(
                10.0, high_speed_polar.optimal_cl / high_speed_polar.cd(high_speed_polar.optimal_cl)
            ),
            inputs["data:TLAR:cruise_mach"],
            10000.0,
        )
        breguet.compute(
            inputs["data:weight:aircraft:MTOW"], inputs["data:TLAR:range"],
        )

        outputs["data:mission:sizing:ZFW"] = breguet.zfw
        outputs["data:mission:sizing:fuel"] = breguet.mission_fuel

    def compute_mission(self, inputs, outputs):
        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:engine:count"]
        )

        reference_area = inputs["data:geometry:wing:area"]
        cruise_mach = inputs["data:TLAR:cruise_mach"]
        flight_distance = inputs["data:TLAR:range"]
        thrust_rates = {
            FlightPhase.CLIMB: inputs["data:mission:sizing:climb:thrust_rate"],
            FlightPhase.DESCENT: inputs["data:mission:sizing:descent:thrust_rate"],
        }
        high_speed_polar = Polar(
            inputs["data:aerodynamics:aircraft:cruise:CL"],
            inputs["data:aerodynamics:aircraft:cruise:CD"],
        )
        low_speed_climb_polar = Polar(
            inputs["data:aerodynamics:aircraft:takeoff:CL"],
            inputs["data:aerodynamics:aircraft:takeoff:CD"],
        )

        base_flight_calculator = RangedFlight(
            StandardFlight(
                propulsion=propulsion_model,
                reference_area=reference_area,
                low_speed_climb_polar=low_speed_climb_polar,
                high_speed_polar=high_speed_polar,
                cruise_mach=cruise_mach,
                thrust_rates=thrust_rates,
            ),
            flight_distance,
        )

        end_of_takeoff = FlightPoint(
            mass=inputs["data:weight:aircraft:MTOW"] - inputs["data:mission:sizing:takeoff:fuel"],
            true_airspeed=inputs["data:mission:sizing:takeoff:V2"],
            altitude=inputs["data:mission:sizing:takeoff:altitude"] + 35 * foot,
            ground_distance=0.0,
        )

        flight_points = base_flight_calculator.compute_from(end_of_takeoff)

        # Update start flight point with computed (non initialized) parameters
        end_of_takeoff = FlightPoint(flight_points.iloc[0])

        # Get flight points for each end of phase
        end_of_initial_climb = FlightPoint(
            flight_points.loc[flight_points.name == FlightPhase.INITIAL_CLIMB.value].iloc[-1]
        )
        end_of_climb = FlightPoint(
            flight_points.loc[flight_points.name == FlightPhase.CLIMB.value].iloc[-1]
        )
        end_of_cruise = FlightPoint(
            flight_points.loc[flight_points.name == FlightPhase.CRUISE.value].iloc[-1]
        )
        end_of_descent = FlightPoint(
            flight_points.loc[flight_points.name == FlightPhase.DESCENT.value].iloc[-1]
        )

        # Set OpenMDAO outputs
        outputs["data:mission:sizing:initial_climb:fuel"] = (
            end_of_takeoff.mass - end_of_initial_climb.mass
        )
        outputs["data:mission:sizing:main_route:climb:fuel"] = (
            end_of_initial_climb.mass - end_of_climb.mass
        )
        outputs["data:mission:sizing:main_route:cruise:fuel"] = (
            end_of_climb.mass - end_of_cruise.mass
        )
        outputs["data:mission:sizing:main_route:descent:fuel"] = (
            end_of_cruise.mass - end_of_descent.mass
        )
        outputs["data:mission:sizing:initial_climb:distance"] = (
            end_of_initial_climb.ground_distance - end_of_takeoff.ground_distance
        )
        outputs["data:mission:sizing:main_route:climb:distance"] = (
            end_of_climb.ground_distance - end_of_initial_climb.ground_distance
        )
        outputs["data:mission:sizing:main_route:cruise:distance"] = (
            end_of_cruise.ground_distance - end_of_climb.ground_distance
        )
        outputs["data:mission:sizing:main_route:descent:distance"] = (
            end_of_descent.ground_distance - end_of_cruise.ground_distance
        )
        outputs["data:mission:sizing:initial_climb:duration"] = (
            end_of_initial_climb.time - end_of_takeoff.time
        )
        outputs["data:mission:sizing:main_route:climb:duration"] = (
            end_of_climb.time - end_of_initial_climb.time
        )
        outputs["data:mission:sizing:main_route:cruise:duration"] = (
            end_of_cruise.time - end_of_climb.time
        )
        outputs["data:mission:sizing:main_route:descent:duration"] = (
            end_of_descent.time - end_of_cruise.time
        )

        # Diversion flight =====================================================
        diversion_distance = inputs["data:mission:sizing:diversion:distance"]
        if diversion_distance <= 200 * nautical_mile:
            diversion_cruise_altitude = 22000 * foot

        else:
            diversion_cruise_altitude = 31000 * foot

        diversion_flight_calculator = RangedFlight(
            StandardFlight(
                propulsion=propulsion_model,
                reference_area=reference_area,
                low_speed_climb_polar=low_speed_climb_polar,
                high_speed_polar=high_speed_polar,
                cruise_mach=cruise_mach,
                thrust_rates=thrust_rates,
                climb_target_altitude=diversion_cruise_altitude,
            ),
            diversion_distance,
        )
        diversion_flight_points = diversion_flight_calculator.compute_from(end_of_descent)

        # Get flight points for each end of phase
        end_of_diversion_climb = FlightPoint(
            diversion_flight_points.loc[
                diversion_flight_points.name == FlightPhase.CLIMB.value
            ].iloc[-1]
        )
        end_of_diversion_cruise = FlightPoint(
            diversion_flight_points.loc[
                diversion_flight_points.name == FlightPhase.CRUISE.value
            ].iloc[-1]
        )
        end_of_diversion_descent = FlightPoint(
            diversion_flight_points.loc[
                diversion_flight_points.name == FlightPhase.DESCENT.value
            ].iloc[-1]
        )

        # rename phases because all flight points will be concatenated later.
        diversion_flight_points.name = "diversion_" + diversion_flight_points.name

        # Set OpenMDAO outputs
        outputs["data:mission:sizing:diversion:climb:fuel"] = (
            end_of_descent.mass - end_of_diversion_climb.mass
        )
        outputs["data:mission:sizing:diversion:cruise:fuel"] = (
            end_of_diversion_climb.mass - end_of_diversion_cruise.mass
        )
        outputs["data:mission:sizing:diversion:descent:fuel"] = (
            end_of_diversion_cruise.mass - end_of_diversion_descent.mass
        )
        outputs["data:mission:sizing:diversion:climb:distance"] = (
            end_of_diversion_climb.ground_distance - end_of_descent.ground_distance
        )
        outputs["data:mission:sizing:diversion:cruise:distance"] = (
            end_of_diversion_cruise.ground_distance - end_of_diversion_climb.ground_distance
        )
        outputs["data:mission:sizing:diversion:descent:distance"] = (
            end_of_diversion_descent.ground_distance - end_of_diversion_cruise.ground_distance
        )
        outputs["data:mission:sizing:diversion:climb:duration"] = (
            end_of_diversion_climb.time - end_of_descent.time
        )
        outputs["data:mission:sizing:diversion:cruise:duration"] = (
            end_of_diversion_cruise.time - end_of_diversion_climb.time
        )
        outputs["data:mission:sizing:diversion:descent:duration"] = (
            end_of_diversion_descent.time - end_of_diversion_cruise.time
        )

        # Holding ==============================================================

        holding_duration = inputs["data:mission:sizing:holding:duration"]

        holding_calculator = HoldSegment(
            target=FlightPoint(time=holding_duration),
            propulsion=propulsion_model,
            reference_area=reference_area,
            polar=high_speed_polar,
            name="holding",
        )

        holding_flight_points = holding_calculator.compute_from(end_of_diversion_descent)

        end_of_holding = FlightPoint(holding_flight_points.iloc[-1])
        outputs["data:mission:sizing:holding:fuel"] = (
            end_of_diversion_descent.mass - end_of_holding.mass
        )

        # Taxi-in ==============================================================
        taxi_in_duration = inputs["data:mission:sizing:taxi_in:duration"]
        taxi_in_thrust_rate = inputs["data:mission:sizing:taxi_in:thrust_rate"]

        taxi_in_calculator = TaxiSegment(
            target=FlightPoint(time=taxi_in_duration),
            propulsion=propulsion_model,
            thrust_rate=taxi_in_thrust_rate,
            name=FlightPhase.TAXI_IN.value,
        )
        start_of_taxi_in = FlightPoint(end_of_holding)
        start_of_taxi_in.true_airspeed = inputs["data:mission:sizing:taxi_in:speed"]
        taxi_in_flight_points = taxi_in_calculator.compute_from(end_of_holding)

        end_of_taxi_in = FlightPoint(taxi_in_flight_points.iloc[-1])
        outputs["data:mission:sizing:taxi_in:fuel"] = end_of_holding.mass - end_of_taxi_in.mass

        # Final ================================================================
        fuel_route = inputs["data:weight:aircraft:MTOW"] - end_of_descent.mass
        outputs["data:mission:sizing:ZFW"] = end_of_taxi_in.mass - 0.03 * fuel_route
        outputs["data:mission:sizing:fuel"] = (
            inputs["data:weight:aircraft:MTOW"] - outputs["data:mission:sizing:ZFW"]
        )

        self.flight_points = (
            pd.concat(
                [
                    flight_points,
                    diversion_flight_points,
                    holding_flight_points,
                    taxi_in_flight_points,
                ]
            )
            .reset_index(drop=True)
            .applymap(lambda x: np.asscalar(np.asarray(x)))
        )

        if self.options["out_file"]:
            self.flight_points.to_csv(self.options["out_file"])
示例#18
0
class _compute_descent(om.ExplicitComponent):
    """
    Compute the fuel consumption on descent segment with constant VCAS and descent
    rate.
    The hypothesis of small alpha angle is done.
    Warning: Descent rate is reduced if cd/cl < abs(desc_rate)!
    """

    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:mission:sizing:main_route:descent:descent_rate", np.nan)
        self.add_input("data:aerodynamics:aircraft:cruise:optimal_CL", 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:fuel", np.nan, units="kg")
        self.add_input("data:mission:sizing:main_route:cruise:fuel", np.nan, units="kg")

        self.add_output("data:mission:sizing:main_route:descent:fuel", units="kg")
        self.add_output("data:mission:sizing:main_route:descent:distance", 0.0, units="m")
        self.add_output("data:mission:sizing:main_route:descent:duration", units="s")

        self.declare_partials(
            "*",
            [
                "data:aerodynamics:aircraft:cruise:optimal_CL",
                "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",
                "data:mission:sizing:main_route:climb:fuel",
                "data:mission:sizing:main_route:cruise:fuel",
            ],
            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"]
        )
        cruise_altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
        descent_rate = -abs(inputs["data:mission:sizing:main_route:descent:descent_rate"])
        cl = inputs["data:aerodynamics:aircraft:cruise:optimal_CL"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k = inputs["data:aerodynamics:aircraft:cruise:induced_drag_coefficient"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_ho = inputs["data:mission:sizing:holding:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        m_cl = inputs["data:mission:sizing:main_route:climb:fuel"]
        m_cr = inputs["data:mission:sizing:main_route:cruise:fuel"]

        # Define initial conditions
        gamma = math.asin(descent_rate)
        altitude_t = copy.deepcopy(cruise_altitude)
        distance_t = 0.0
        time_t = 0.0
        mass_fuel_t = 0.0
        mass_t = mtow - (m_to + m_ho + m_tk + m_ic + m_cl + m_cr)
        atm_0 = Atmosphere(0.0)
        warning = False
        # Calculate defined VCAS at the beginning of descent (cos(gamma)~1)
        v_cas = math.sqrt((mass_t * g) * math.cos(descent_rate) / (0.5 * atm_0.density * wing_area * cl))

        while altitude_t > SAFETY_HEIGHT:

            # Define air properties and calculate VTAS
            atm = Atmosphere(altitude_t, altitude_in_feet=False)
            v_tas = v_cas * math.sqrt(atm_0.density / atm.density)
            # Calculate lift and drag coefficients changes to maintain speed (cos(gamma)~1)
            cl = ((mass_t * g) * math.cos(descent_rate) / (0.5 * atm.density * wing_area * v_tas**2))
            cd = cd0 + coef_k * cl**2
            cl_cd = cl/cd
            drag = 0.5 * atm.density * wing_area * cd * v_tas**2
            # Evaluate mach
            mach = math.sqrt(
                5 * ((atm_0.pressure / atm.pressure * (
                        (1 + 0.2 * (v_cas / atm_0.speed_of_sound) ** 2) ** 3.5 - 1
                ) + 1) ** (1 / 3.5) - 1)
            )
            # Calculate necessary Thrust to maintain VCAS and descent rate
            # if T<0N, VCAS is maintained reducing gamma/descent rate and engine in IDLE condition
            thrust = drag + (mass_t * g) * math.sin(gamma)
            if thrust <= 0.0:
                flight_point = FlightPoint(
                    mach=mach, altitude=altitude_t, engine_setting=EngineSetting.IDLE,
                    thrust_rate=0.2)  # FIXME: define IDLE maybe?
                descent_rate = -1/cl_cd
                gamma = math.asin(descent_rate)
                warning = True
            else:
                # FIXME: DESCENT setting on engine does not exist, replaced by CRUISE for test
                flight_point = FlightPoint(
                    mach=mach, altitude=altitude_t, engine_setting=EngineSetting.CRUISE,
                    thrust_is_regulated=True, thrust=thrust,
                )
            propulsion_model.compute_flight_points(flight_point)

            # Calculate distance increase
            v_x = v_tas * math.cos(descent_rate)
            v_z = v_tas * math.sin(descent_rate)
            distance_t += v_x * TIME_STEP
            altitude_t += v_z * TIME_STEP

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(flight_point, TIME_STEP)
            mass_t = mass_t - propulsion_model.get_consumed_mass(flight_point, TIME_STEP)
            time_t += TIME_STEP

        if warning:
            warnings.warn("Descent rate has been reduced!")

        outputs["data:mission:sizing:main_route:descent:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:descent:distance"] = distance_t
        outputs["data:mission:sizing:main_route:descent:duration"] = time_t
示例#19
0
class SizingMission(om.ExplicitComponent):
    def __init__(self, **kwargs):
        """
        Computes thrust, SFC and thrust rate by direct call to engine model.

        Options:
          - propulsion_id: (mandatory) the identifier of the propulsion wrapper.
          - out_file: if provided, a csv file will be written at provided path with all computed
                      flight points. If path is relative, it will be resolved from working
                      directory
        """
        super().__init__(**kwargs)
        self.flight_points = None
        self._engine_wrapper = None
        self._mission_input = None
        self._mission: MissionWrapper = None

    def initialize(self):
        self.options.declare("propulsion_id", default="", types=str)
        self.options.declare("out_file", default="", types=str)
        self.options.declare("breguet_iterations", default=2, types=int)
        self.options.declare("mission_file_path",
                             types=str,
                             allow_none=True,
                             default=None)

    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(["*"], ["*"])

    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        if self.iter_count_without_approx < self.options["breguet_iterations"]:
            _LOGGER.info("Using Breguet for computing sizing mission.")
            self.compute_breguet(inputs, outputs)
        else:
            _LOGGER.info(
                "Using time-step integration for computing sizing mission.")
            self.compute_mission(inputs, outputs)

    def compute_breguet(self, inputs, outputs):
        """
        Computes mission using simple Breguet formula at altitude==10000m

        Useful for initiating the computation.

        :param inputs: OpenMDAO input vector
        :param outputs: OpenMDAO output vector
        """
        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs),
            inputs["data:geometry:propulsion:engine:count"])
        high_speed_polar = Polar(
            inputs["data:aerodynamics:aircraft:cruise:CL"],
            inputs["data:aerodynamics:aircraft:cruise:CD"],
        )

        breguet = Breguet(
            propulsion_model,
            max(
                10.0, high_speed_polar.optimal_cl /
                high_speed_polar.cd(high_speed_polar.optimal_cl)),
            inputs["data:TLAR:cruise_mach"],
            10000.0,
        )
        breguet.compute(
            inputs["data:weight:aircraft:MTOW"],
            inputs["data:TLAR:range"],
        )

        outputs["data:mission:sizing:ZFW"] = breguet.zfw
        outputs["data:mission:sizing:fuel"] = breguet.mission_fuel

    def compute_mission(self, inputs, outputs):
        """
        Computes mission using time-step integration.

        :param inputs: OpenMDAO input vector
        :param outputs: OpenMDAO output vector
        """
        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs),
            inputs["data:geometry:propulsion:engine:count"])

        reference_area = inputs["data:geometry:wing:area"]

        self._mission.propulsion = propulsion_model
        self._mission.reference_area = reference_area

        end_of_takeoff = FlightPoint(
            time=0.0,
            # FIXME: legacy FAST was considering that aircraft was at MTOW before taxi out,
            #  though it supposed to be at MTOW at takeoff. We keep this logic for sake of
            #  non-regression, but it should be corrected later.
            mass=inputs["data:weight:aircraft:MTOW"] -
            inputs["data:mission:sizing:takeoff:fuel"] -
            inputs["data:mission:sizing:taxi_out:fuel"],
            true_airspeed=inputs["data:mission:sizing:takeoff:V2"],
            altitude=inputs["data:mission:sizing:takeoff:altitude"] +
            35 * foot,
            ground_distance=0.0,
        )

        self.flight_points = self._mission.compute(inputs, outputs,
                                                   end_of_takeoff)

        # Final ================================================================
        end_of_descent = FlightPoint.create(self.flight_points.loc[
            self.flight_points.name == "sizing:main_route:descent"].iloc[-1])
        end_of_taxi_in = FlightPoint.create(self.flight_points.iloc[-1])

        fuel_route = inputs["data:weight:aircraft:MTOW"] - end_of_descent.mass
        outputs[
            "data:mission:sizing:ZFW"] = end_of_taxi_in.mass - 0.03 * fuel_route
        outputs["data:mission:sizing:fuel"] = (
            inputs["data:weight:aircraft:MTOW"] -
            outputs["data:mission:sizing:ZFW"])

        if self.options["out_file"]:
            self.flight_points.to_csv(self.options["out_file"])
示例#20
0
class ComputeFuselageGeometryCabinSizingFD(ExplicitComponent):
    # TODO: Document equations. Cite sources
    """
    Geometry of fuselage - Cabin is sized based on layout (seats, aisle...) and HTP/VTP position (Fixed tail Distance).
    """
    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:cabin:seats:passenger:NPAX_max",
                       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:cabin:luggage:mass_max",
                       val=np.nan,
                       units="kg")
        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_input("data:geometry:horizontal_tail:sweep_25",
                       val=np.nan,
                       units="deg")
        self.add_input("data:geometry:horizontal_tail:span",
                       val=np.nan,
                       units="m")
        self.add_input("data:geometry:vertical_tail:sweep_25",
                       val=np.nan,
                       units="deg")
        self.add_input("data:geometry:vertical_tail:span",
                       val=np.nan,
                       units="m")

        self.add_output("data:geometry:cabin:NPAX")
        self.add_output("data:geometry:plane:length", units="m")
        self.add_output("data:geometry:fuselage:length", val=10.0, 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_max = inputs["data:geometry:cabin:seats:passenger:NPAX_max"]
        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"]
        luggage_mass_max = inputs["data:geometry:cabin:luggage:mass_max"]
        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"]
        sweep_25_vt = inputs["data:geometry:vertical_tail:sweep_25"]
        b_v = inputs["data:geometry:vertical_tail:span"]
        sweep_25_ht = inputs["data:geometry:horizontal_tail:sweep_25"]
        b_h = inputs["data:geometry:horizontal_tail:span"]

        # Length of instrument panel
        l_instr = 0.7
        # Length of pax cabin
        npax = math.ceil(
            float(npax_max) / float(seats_p_row)) * float(seats_p_row)
        n_rows = npax / float(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 (80% of internal radius section can be filled with luggage)
        luggage_density = 161.0  # In kg/m3
        l_lug = (luggage_mass_max / luggage_density) / (0.8 * math.pi * r_i**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, _, _, spinner_length = propulsion_model.compute_dimensions(
            )
            lav = propulsion_length + spinner_length
        else:
            lav = 1.40 * h_f
            # Used to be 1.7, supposedly as an A320 according to FAST legacy. Results on the BE76 tend to say it is
            # around 1.40, though it varies a lot depending on the airplane and its use
        # Calculate fuselage length
        fus_length = fa_length + max(ht_lp + 0.75 * ht_length,
                                     vt_lp + 0.75 * vt_length)
        plane_length = fa_length + max(
            ht_lp + 0.75 * ht_length +
            b_h / 2.0 * math.tan(sweep_25_ht * math.pi / 180), vt_lp +
            0.75 * vt_length + b_v * math.tan(sweep_25_vt * math.pi / 180))
        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
        outputs["data:geometry:fuselage:length"] = fus_length
        outputs["data:geometry:plane:length"] = plane_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
示例#21
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
class ComputeBalkedLandingLimit(aircraft_equilibrium_limit):
    """
    Computes fwd limit position of cg in case of a balked landing
    """
    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):
        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 compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        mlw = inputs["data:weight:aircraft:MLW"]

        cl_max_landing = inputs["data:aerodynamics:aircraft:landing:CL_max"]
        wing_area = inputs["data:geometry:wing:area"]
        fa_length = inputs["data:geometry:wing:MAC:at25percent:x"]
        l0_wing = inputs["data:geometry:wing:MAC:length"]

        rho = Atmosphere(0.0).density

        v_s0 = math.sqrt(
            (mlw * 9.81) / (0.5 * rho * wing_area * cl_max_landing))
        v_ref = 1.3 * v_s0

        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs),
            inputs["data:geometry:propulsion:count"])

        x_cg = float(fa_length)
        increment = l0_wing / 100.
        equilibrium_found = True
        climb_gradient_achieved = True

        while equilibrium_found and climb_gradient_achieved:
            climb_gradient, equilibrium_found = self.delta_climb_rate(
                x_cg, v_ref, mlw, propulsion_model, inputs)
            if climb_gradient < 0.033:
                climb_gradient_achieved = False
            x_cg -= increment

        outputs["data:handling_qualities:balked_landing_limit:x"] = x_cg

        x_cg_ratio = (x_cg - fa_length + 0.25 * l0_wing) / l0_wing

        outputs[
            "data:handling_qualities:balked_landing_limit:MAC_position"] = x_cg_ratio

    def delta_climb_rate(self, x_cg, v_ref, mass, propulsion_model, inputs):

        coeff_k_wing = inputs[
            "data:aerodynamics:wing:low_speed:induced_drag_coefficient"]
        coeff_k_htp = inputs[
            "data:aerodynamics:horizontal_tail:low_speed:induced_drag_coefficient"]
        cl_alpha_wing = inputs["data:aerodynamics:wing:low_speed:CL_alpha"]
        cl_alpha_htp = inputs[
            "data:aerodynamics:horizontal_tail:low_speed:CL_alpha"]
        cl_delta_htp = inputs["data:aerodynamics:elevator:low_speed:CL_delta"]
        cd_flaps = inputs["data:aerodynamics:flaps:landing:CD"]
        cl_flaps = inputs["data:aerodynamics:flaps:landing:CL"]
        cd_0 = inputs["data:aerodynamics:aircraft:low_speed:CD0"]

        rho = Atmosphere(0.0).density
        sos = Atmosphere(0.0).speed_of_sound

        dynamic_pressure = 1. / 2. * rho * v_ref**2.0

        alpha_ac, delta_e, equilibrium_found = self.found_cl_repartition(
            inputs, 1.0, mass, dynamic_pressure, x_cg)
        cl_AOA_wing = cl_alpha_wing * alpha_ac
        cl_AOA_htp = cl_alpha_htp * alpha_ac
        cl_elevator = cl_delta_htp * delta_e

        cd_min = cd_0 + cd_flaps

        cl = cl_AOA_wing + cl_AOA_htp + cl_elevator + cl_flaps
        cd = cd_min + \
            coeff_k_wing * (cl_AOA_wing + cl_flaps) ** 2.0 + \
            coeff_k_htp * (cl_AOA_htp + cl_elevator) ** 2.0

        flight_point = FlightPoint(
            mach=v_ref / sos,
            altitude=0.0,
            engine_setting=EngineSetting.TAKEOFF,
            thrust_rate=1.0)  # with engine_setting as EngineSetting
        propulsion_model.compute_flight_points(flight_point)
        thrust = float(flight_point.thrust)
        propeller_advance_ratio = v_ref / (2700. / 60. * 1.97)
        propeller_efficiency_reduction = math.sin(propeller_advance_ratio *
                                                  math.pi / 2.)

        climb_angle = math.asin(propeller_efficiency_reduction * thrust /
                                (mass * 9.81) - cd / cl)
        climb_gradient = math.tan(climb_angle)

        return climb_gradient, equilibrium_found
示例#23
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)
示例#24
0
class Cd0Nacelle(ExplicitComponent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._engine_wrapper = None

    def initialize(self):
        self.options.declare("low_speed_aero", default=False, types=bool)
        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: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,
                           units="m**-1")
            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,
                           units="m**-1")
            self.add_output("data:aerodynamics:nacelles:cruise:CD0")

        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)
        engine_number = inputs["data:geometry:propulsion:count"]
        prop_layout = inputs["data:geometry:propulsion:layout"]
        l0_wing = inputs["data:geometry:wing:MAC:length"]
        wing_area = inputs["data:geometry:wing:area"]
        if self.options["low_speed_aero"]:
            mach = inputs["data:aerodynamics:low_speed:mach"]
            unit_reynolds = inputs["data:aerodynamics:low_speed:unit_reynolds"]
        else:
            mach = inputs["data:aerodynamics:cruise:mach"]
            unit_reynolds = inputs["data:aerodynamics:cruise:unit_reynolds"]

        drag_force = propulsion_model.compute_drag(mach, unit_reynolds,
                                                   l0_wing)

        if (prop_layout == 1.0) or (prop_layout == 2.0):
            cd0 = drag_force / wing_area * engine_number
        elif prop_layout == 3.0:
            cd0 = 0.0
        else:
            cd0 = 0.0
            warnings.warn(
                'Propulsion layout {} not implemented in model, replaced by layout 1!'
                .format(prop_layout))

        if self.options["low_speed_aero"]:
            outputs["data:aerodynamics:nacelles:low_speed:CD0"] = cd0
        else:
            outputs["data:aerodynamics:nacelles:cruise:CD0"] = cd0
示例#25
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
示例#26
0
class _ComputeSlipstreamOpenvsp(OPENVSPSimpleGeometry):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._engine_wrapper = None

    def initialize(self):
        self.options.declare("low_speed_aero", default=False, types=bool)
        super().initialize()

    def setup(self):
        super().setup()
        self._engine_wrapper = BundleLoader().instantiate_component(
            self.options["propulsion_id"])
        self._engine_wrapper.setup(self)
        if self.options["low_speed_aero"]:
            self.add_input("data:aerodynamics:low_speed:mach", val=np.nan)

            self.add_input("data:aerodynamics:wing:low_speed:CL0_clean",
                           val=np.nan)
            self.add_input("data:aerodynamics:wing:low_speed:CL_alpha",
                           val=np.nan,
                           units="deg**-1")
        else:
            self.add_input("data:aerodynamics:cruise:mach", val=np.nan)

            self.add_input("data:aerodynamics:wing:cruise:CL0_clean",
                           val=np.nan)
            self.add_input("data:aerodynamics:wing:cruise:CL_alpha",
                           val=np.nan,
                           units="deg**-1")

        self.add_input("data:aerodynamics:wing:low_speed:CL_max_clean")

        self.add_input("data:geometry:propulsion:propeller:diameter",
                       val=np.nan,
                       units="m")
        self.add_input("data:geometry:propulsion:y_ratio", val=np.nan)
        self.add_input("data:geometry:propulsion:count", val=np.nan)
        self.add_input("data:geometry:propulsion:nacelle:length",
                       val=np.nan,
                       units="m")

        self.add_input("data:propulsion:IC_engine:engine_rpm",
                       val=np.nan,
                       units="min**-1")

        if self.options["low_speed_aero"]:
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:Y_vector",
                shape=SPAN_MESH_POINT,
                units="m")
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CL_vector",
                shape=SPAN_MESH_POINT)
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CT_ref")
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CL")
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:velocity",
                units="m/s")

            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:Y_vector",
                shape=SPAN_MESH_POINT,
                units="m")
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:CL_vector",
                shape=SPAN_MESH_POINT)
            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:CL")

            self.add_output(
                "data:aerodynamics:slipstream:wing:low_speed:only_prop:CL_vector",
                shape=SPAN_MESH_POINT)
        else:
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_on:Y_vector",
                shape=SPAN_MESH_POINT,
                units="m")
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CL_vector",
                shape=SPAN_MESH_POINT)
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CT_ref")
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CL")
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_on:velocity",
                units="m/s")

            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_off:Y_vector",
                shape=SPAN_MESH_POINT,
                units="m")
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_off:CL_vector",
                shape=SPAN_MESH_POINT)
            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:prop_off:CL")

            self.add_output(
                "data:aerodynamics:slipstream:wing:cruise:only_prop:CL_vector",
                shape=SPAN_MESH_POINT)

    def check_config(self, logger):
        # let void to avoid logger error on "The command cannot be empty"
        pass

    def compute(self, inputs, outputs):

        if self.options["low_speed_aero"]:
            altitude = 0.0
            mach = inputs["data:aerodynamics:low_speed:mach"]
            cl0 = inputs["data:aerodynamics:wing:low_speed:CL0_clean"]
            cl_alpha = inputs["data:aerodynamics:wing:low_speed:CL_alpha"]
        else:
            altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
            mach = inputs["data:aerodynamics:cruise:mach"]
            cl0 = inputs["data:aerodynamics:wing:cruise:CL0_clean"]
            cl_alpha = inputs["data:aerodynamics:wing:cruise:CL_alpha"]

        cl_max_clean = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"]

        atm = Atmosphere(altitude, altitude_in_feet=False)
        velocity = mach * atm.speed_of_sound

        # We need to compute the AOA for which the most constraining Delta_Cl due to slipstream will appear, this
        # is taken as the angle for which the clean wing is at its max angle of attack

        alpha_max = (cl_max_clean - cl0) / cl_alpha

        wing_rotor = self.compute_wing_rotor(inputs, outputs, altitude, mach,
                                             alpha_max, 1.0)
        wing = self.compute_wing(inputs, outputs, altitude, mach, alpha_max)

        cl_vector_prop_on = wing_rotor["cl_vector"]
        y_vector_prop_on = wing_rotor["y_vector"]

        cl_vector_prop_off = wing["cl_vector"]
        y_vector_prop_off = wing["y_vector"]

        additional_zeros = list(
            np.zeros(SPAN_MESH_POINT - len(cl_vector_prop_on)))
        cl_vector_prop_on.extend(additional_zeros)
        y_vector_prop_on.extend(additional_zeros)
        cl_vector_prop_off.extend(additional_zeros)
        y_vector_prop_off.extend(additional_zeros)

        cl_diff = []
        for i in range(len(cl_vector_prop_on)):
            cl_diff.append(
                round(cl_vector_prop_on[i] - cl_vector_prop_off[i], 4))

        if self.options["low_speed_aero"]:
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:Y_vector"] = y_vector_prop_on
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CL_vector"] = cl_vector_prop_on
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CT_ref"] = wing_rotor[
                    "ct"]
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:CL"] = wing_rotor[
                    "cl"]
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_on:velocity"] = velocity

            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:Y_vector"] = y_vector_prop_off
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:CL_vector"] = cl_vector_prop_off
            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:prop_off:CL"] = wing[
                    "cl"]

            outputs[
                "data:aerodynamics:slipstream:wing:low_speed:only_prop:CL_vector"] = cl_diff
        else:
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_on:Y_vector"] = y_vector_prop_on
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CL_vector"] = cl_vector_prop_on
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CT_ref"] = wing_rotor[
                    "ct"]
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_on:CL"] = wing_rotor[
                    "cl"]
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_on:velocity"] = velocity

            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_off:Y_vector"] = y_vector_prop_off
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_off:CL_vector"] = cl_vector_prop_off
            outputs[
                "data:aerodynamics:slipstream:wing:cruise:prop_off:CL"] = wing[
                    "cl"]

            outputs[
                "data:aerodynamics:slipstream:wing:cruise:only_prop:CL_vector"] = cl_diff
示例#27
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
示例#28
0
class BreguetWithPropulsion(om.ExplicitComponent):
    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: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 compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:engine:count"]
        )

        breguet = Breguet(
            propulsion_model,
            inputs["data:aerodynamics:aircraft:cruise:L_D_max"],
            inputs["data:TLAR:cruise_mach"],
            inputs["data:mission:sizing:main_route:cruise:altitude"],
            inputs["settings:mission:sizing:breguet:climb:mass_ratio"],
            inputs["settings:mission:sizing:breguet:descent:mass_ratio"],
            inputs["settings:mission:sizing:breguet:reserve:mass_ratio"],
            inputs["settings:mission:sizing:breguet:climb_descent_distance"],
        )
        breguet.compute(
            inputs["data:weight:aircraft:MTOW"], inputs["data:TLAR:range"],
        )

        outputs["data:propulsion:SFC"] = breguet.sfc
        outputs["data:propulsion:thrust_rate"] = breguet.thrust_rate
        outputs["data:propulsion:thrust"] = breguet.thrust
        outputs["data:mission:sizing:ZFW"] = breguet.zfw
        outputs["data:mission:sizing:fuel"] = breguet.mission_fuel
        outputs["data:mission:sizing:main_route:fuel"] = breguet.flight_fuel
        outputs["data:mission:sizing:main_route:climb:fuel"] = breguet.climb_fuel
        outputs["data:mission:sizing:main_route:cruise:fuel"] = breguet.cruise_fuel
        outputs["data:mission:sizing:main_route:descent:fuel"] = breguet.descent_fuel
        outputs["data:mission:sizing:main_route:climb:distance"] = breguet.climb_distance
        outputs["data:mission:sizing:main_route:cruise:distance"] = breguet.cruise_distance
        outputs["data:mission:sizing:main_route:descent:distance"] = breguet.descent_distance
        outputs["data:mission:sizing:fuel_reserve"] = breguet.reserve_fuel
示例#29
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
示例#30
0
class _compute_climb(om.ExplicitComponent):
    """
    Compute the fuel consumption on climb segment with constant VCAS and fixed thrust ratio.
    The hypothesis of small alpha/gamma angles is done.
    """

    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: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",
        )
        
    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"]
        )
        cruise_altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
        cd0 = inputs["data:aerodynamics:aircraft:cruise:CD0"]
        coef_k = inputs["data:aerodynamics:aircraft:cruise:induced_drag_coefficient"]
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        m_to = inputs["data:mission:sizing:taxi_out:fuel"]
        m_ho = inputs["data:mission:sizing:holding:fuel"]
        m_tk = inputs["data:mission:sizing:takeoff:fuel"]
        m_ic = inputs["data:mission:sizing:initial_climb:fuel"]
        thrust_rate = inputs["data:mission:sizing:main_route:climb:thrust_rate"]

        # Define initial conditions
        altitude_t = SAFETY_HEIGHT  # conversion to m
        distance_t = 0.0
        time_t = 0.0
        mass_t = mtow - (m_to + m_ho + m_tk + m_ic)
        mass_fuel_t = 0.0
        atm_0 = Atmosphere(0.0)

        # FIXME: VCAS strategy is specific to ICE-propeller configuration, should be an input
        cl = math.sqrt(3*cd0/coef_k)
        atm = Atmosphere(altitude_t, altitude_in_feet=False)
        v_cas = math.sqrt((mass_t * g) / (0.5 * atm.density * wing_area * cl))

        while altitude_t < cruise_altitude:

            # Define air properties
            atm = Atmosphere(altitude_t, altitude_in_feet=False)
            v_tas = v_cas * math.sqrt(atm_0.density / atm.density)

            # Evaluate thrust and sfc
            mach = math.sqrt(
                5 * ((atm_0.pressure / atm.pressure * (
                        (1 + 0.2 * (v_cas / atm_0.speed_of_sound) ** 2) ** 3.5 - 1
                ) + 1) ** (1 / 3.5) - 1)
            )
            flight_point = FlightPoint(
                mach=mach, altitude=altitude_t, engine_setting=EngineSetting.CLIMB,
                thrust_rate=thrust_rate
            )  # with engine_setting as EngineSetting
            propulsion_model.compute_flight_points(flight_point)
            thrust = float(flight_point.thrust)

            # Calculates cl and drag considering constant climb rate
            cl = mass_t * g / (0.5 * atm.density * wing_area * v_tas**2)
            cd = cd0 + coef_k * cl**2

            # Calculate climb rate and height increase
            climb_rate = thrust / (mass_t * g) - cd / cl
            vz = v_tas * math.sin(climb_rate)
            vx = v_tas * math.cos(climb_rate)
            altitude_t += vz * TIME_STEP
            distance_t += vx * TIME_STEP

            # Estimate mass evolution and update time
            mass_fuel_t += propulsion_model.get_consumed_mass(flight_point, TIME_STEP)
            mass_t = mass_t - propulsion_model.get_consumed_mass(flight_point, TIME_STEP)
            time_t += TIME_STEP

        outputs["data:mission:sizing:main_route:climb:fuel"] = mass_fuel_t
        outputs["data:mission:sizing:main_route:climb:distance"] = distance_t
        outputs["data:mission:sizing:main_route:climb:duration"] = time_t
        outputs["data:mission:sizing:main_route:climb:v_cas"] = v_cas