Exemple #1
0
def test_EngineSet():
    """Tests behaviour of FuelEngineSet"""
    engine = DummyEngine(1.2e5, 1.0e-5)

    # input = thrust rate
    thrust_rate = np.linspace(0.0, 1.0, 20)
    for engine_count in [1, 2, 3, 4]:
        engine_set = FuelEngineSet(engine, engine_count)
        flight_point = FlightPoint(mach=0.0,
                                   altitude=0.0,
                                   engine_setting=1,
                                   thrust_rate=thrust_rate)
        engine_set.compute_flight_points(flight_point)
        assert_allclose(flight_point.sfc, engine.max_sfc * thrust_rate)
        assert_allclose(flight_point.thrust,
                        engine.max_thrust * engine_count * thrust_rate)

    # input = thrust
    thrust = np.linspace(0.0, engine.max_thrust, 30)
    for engine_count in [1, 2, 3, 4]:
        engine_set = FuelEngineSet(engine, engine_count)
        flight_point = FlightPoint(mach=0.0,
                                   altitude=0.0,
                                   engine_setting=1,
                                   thrust=thrust)
        engine_set.compute_flight_points(flight_point)
        assert_allclose(flight_point.thrust_rate,
                        thrust / engine_count / engine.max_thrust)
        assert_allclose(flight_point.sfc,
                        engine.max_sfc * flight_point.thrust_rate)
def test_optimal_cruise(polar):
    propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2)

    segment = OptimalCruiseSegment(
        target=FlightPoint(ground_distance=5.0e5),
        propulsion=propulsion,
        reference_area=120.0,
        polar=polar,
    )
    flight_points = segment.compute_from(
        FlightPoint(mass=70000.0, time=1000.0, ground_distance=1e5,
                    mach=0.78), )
    print_dataframe(flight_points)

    first_point = flight_points.iloc[0]
    last_point = flight_points.iloc[-1]
    # Note: reference values are obtained by running the process with 0.05s as time step
    assert_allclose(first_point.altitude, 9156.0, atol=1.0)
    assert_allclose(first_point.true_airspeed, 236.4, atol=0.1)
    assert_allclose(first_point.CL, polar.optimal_cl)

    assert_allclose(last_point.ground_distance, 600000.0)
    assert_allclose(last_point.CL, polar.optimal_cl)
    assert_allclose(last_point.altitude, 9196.0, atol=1.0)
    assert_allclose(last_point.time, 3115.0, rtol=1e-2)
    assert_allclose(last_point.true_airspeed, 236.3, atol=0.1)
    assert_allclose(last_point.mass, 69577.0, rtol=1e-4)
Exemple #3
0
    def complete_flight_point(self, flight_point: FlightPoint):
        """
        Computes data for provided flight point.

        Assumes that it is already defined for time, altitude, mass,
        ground distance and speed (TAS, EAS, or Mach).

        :param flight_point: the flight point that will be completed in-place
        """
        flight_point.engine_setting = self.engine_setting

        self._complete_speed_values(flight_point)

        atm = AtmosphereSI(flight_point.altitude)
        reference_force = 0.5 * atm.density * flight_point.true_airspeed ** 2 * self.reference_area

        if self.polar:
            flight_point.CL = flight_point.mass * g / reference_force
            flight_point.CD = self.polar.cd(flight_point.CL)
        else:
            flight_point.CL = flight_point.CD = 0.0
        flight_point.drag = flight_point.CD * reference_force

        self._compute_propulsion(flight_point)
        flight_point.slope_angle, flight_point.acceleration = self._get_gamma_and_acceleration(
            flight_point.mass, flight_point.drag, flight_point.thrust
        )
Exemple #4
0
    def compute_from(self, start: FlightPoint) -> pd.DataFrame:
        self.complete_flight_point(
            start)  # needed to ensure all speed values are computed.

        if self.target.altitude:
            if isinstance(self.target.altitude, str):
                # Target altitude will be modified along the process, so we keep track
                # of the original order in target CL, that is not used otherwise.
                self.target.CL = self.target.altitude  # pylint: disable=invalid-name
                # let's put a numerical, negative value in self.target.altitude to
                # ensure there will be no problem in self._get_distance_to_target()
                self.target.altitude = -1000.0
                self.interrupt_if_getting_further_from_target = False
            else:
                # Target altitude is fixed, back to original settings (in case
                # this instance is used more than once)
                self.target.CL = None
                self.interrupt_if_getting_further_from_target = True

        atm = AtmosphereSI(start.altitude)
        if self.target.equivalent_airspeed == self.CONSTANT_VALUE:
            start.true_airspeed = atm.get_true_airspeed(
                start.equivalent_airspeed)
        elif self.target.mach == self.CONSTANT_VALUE:
            start.true_airspeed = start.mach * atm.speed_of_sound

        return super().compute_from(start)
Exemple #5
0
    def compute_next_flight_point(
        self, flight_points: List[FlightPoint], time_step: float
    ) -> FlightPoint:
        """
        Computes time, altitude, speed, mass and ground distance of next flight point.

        :param flight_points: previous flight points
        :param time_step: time step for computing next point
        :return: the computed next flight point
        """
        start = flight_points[0]
        previous = flight_points[-1]
        next_point = FlightPoint()

        next_point.mass = previous.mass - self.propulsion.get_consumed_mass(previous, time_step)
        next_point.time = previous.time + time_step
        next_point.ground_distance = (
            previous.ground_distance
            + previous.true_airspeed * time_step * np.cos(previous.slope_angle)
        )
        self._compute_next_altitude(next_point, previous)

        if self.target.true_airspeed == "constant":
            next_point.true_airspeed = previous.true_airspeed
        elif self.target.equivalent_airspeed == "constant":
            next_point.equivalent_airspeed = start.equivalent_airspeed
        elif self.target.mach == "constant":
            next_point.mach = start.mach
        else:
            next_point.true_airspeed = previous.true_airspeed + time_step * previous.acceleration

        # The naming is not done in complete_flight_point for not naming the start point
        next_point.name = self.name
        return next_point
Exemple #6
0
    def _climb_to_altitude_and_cruise(
        self,
        start: FlightPoint,
        cruise_altitude: float,
        climb_segment: AltitudeChangeSegment,
        cruise_segment: CruiseSegment,
    ):
        """
        Climbs up to cruise_altitude and cruise, while ensuring final ground_distance is
        equal to self.target.ground_distance.

        :param start:
        :param cruise_altitude:
        :param climb_segment:
        :param cruise_segment:
        :return:
        """
        climb_segment.target = FlightPoint(
            altitude=cruise_altitude,
            mach=cruise_segment.target.mach,
            true_airspeed=cruise_segment.target.true_airspeed,
            equivalent_airspeed=cruise_segment.target.equivalent_airspeed,
        )
        climb_points = climb_segment.compute_from(start)

        cruise_start = FlightPoint.create(climb_points.iloc[-1])
        cruise_segment.target.ground_distance = (self.target.ground_distance -
                                                 cruise_start.ground_distance)
        cruise_points = cruise_segment.compute_from(cruise_start)

        return pd.concat([climb_points, cruise_points]).reset_index(drop=True)
def test_breguet_cruise(polar):
    propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2)

    segment = BreguetCruiseSegment(
        target=FlightPoint(ground_distance=5.0e5),
        propulsion=propulsion,
        reference_area=120.0,
        polar=polar,
    )
    flight_points = segment.compute_from(
        FlightPoint(mass=70000.0, altitude=10000.0, mach=0.78))

    print_dataframe(flight_points)

    first_point = flight_points.iloc[0]
    last_point = flight_points.iloc[-1]
    # Note: reference values are obtained by running the process with CruiseSegment and 0.05s as
    # time step
    assert_allclose(first_point.altitude, 10000.0)
    assert_allclose(first_point.true_airspeed, 233.6, atol=0.1)

    assert_allclose(last_point.ground_distance, 500000.0)
    assert_allclose(last_point.altitude, 10000.0)
    assert_allclose(last_point.time, 2141.0, rtol=1e-2)
    assert_allclose(last_point.true_airspeed, 233.6, atol=0.1)
    assert_allclose(last_point.mass, 69568.0, rtol=1e-4)
    def compute(self, inputs: Vector, outputs: Vector,
                start_flight_point: FlightPoint) -> pd.DataFrame:
        """
        To be used during compute() of an OpenMDAO component.

        Builds the mission from input file, and computes it. `outputs` vector is
        filled with duration, burned fuel and covered ground distance for each
        part of the flight.

        :param inputs: the input vector of the OpenMDAO component
        :param outputs: the output vector of the OpenMDAO component
        :param start_flight_point: the starting flight point just after takeoff
        :return: a pandas DataFrame where columns names match
                 :meth:`fastoad.base.flight_point.FlightPoint.get_attribute_keys`
        """
        mission = self.build(inputs, self.mission_name)

        def _compute_vars(name_root, start: FlightPoint, end: FlightPoint):
            """Computes duration, burned fuel and covered distance."""
            if name_root + ":duration" in outputs:
                outputs[name_root + ":duration"] = end.time - start.time
            if name_root + ":fuel" in outputs:
                outputs[name_root + ":fuel"] = start.mass - end.mass
            if name_root + ":distance" in outputs:
                outputs[
                    name_root +
                    ":distance"] = end.ground_distance - start.ground_distance

        if not start_flight_point.name:
            start_flight_point.name = mission.flight_sequence[0].name

        current_flight_point = start_flight_point
        flight_points = mission.compute_from(start_flight_point)
        for part in mission.flight_sequence:
            var_name_root = "data:mission:%s" % part.name
            part_end = FlightPoint.create(
                flight_points.loc[flight_points.name.str.startswith(
                    part.name)].iloc[-1])
            _compute_vars(var_name_root, current_flight_point, part_end)

            if isinstance(part, FlightSequence):
                # In case of a route, outputs are computed for each phase in the route
                phase_start = current_flight_point
                for phase in part.flight_sequence:
                    phase_points = flight_points.loc[flight_points.name ==
                                                     phase.name]
                    if len(phase_points) > 0:
                        phase_end = FlightPoint.create(phase_points.iloc[-1])
                        var_name_root = "data:mission:%s" % phase.name
                        _compute_vars(var_name_root, phase_start, phase_end)
                        phase_start = phase_end

            current_flight_point = part_end

        # Outputs for the whole mission
        var_name_root = "data:mission:%s" % mission.name
        _compute_vars(var_name_root, start_flight_point, current_flight_point)

        return flight_points
Exemple #9
0
    def compute_flight_points(self, flight_point: FlightPoint):

        if flight_point.thrust_is_regulated or flight_point.thrust_rate is None:
            flight_point.thrust_rate = flight_point.thrust / self.max_thrust
        else:
            flight_point.thrust = self.max_thrust * flight_point.thrust_rate

        flight_point.sfc = self.max_sfc * (1.0 + flight_point.thrust_rate) / 2.0
def test_compute_flight_points():
    engine = RubberEngine(5, 30, 1500, 1, 0.95,
                          10000)  # f0=1 so that output is simply fc/f0

    # Test with scalars
    flight_point = FlightPoint(
        mach=0,
        altitude=0,
        engine_setting=EngineSetting.TAKEOFF,
        thrust_rate=0.8)  # with engine_setting as EngineSetting
    engine.compute_flight_points(flight_point)
    np.testing.assert_allclose(flight_point.thrust, 0.9553 * 0.8, rtol=1e-4)
    np.testing.assert_allclose(flight_point.sfc, 0.99210e-5, rtol=1e-4)

    flight_point = FlightPoint(mach=0.3,
                               altitude=0,
                               engine_setting=EngineSetting.CLIMB.value,
                               thrust=0.35677)  # with engine_setting as int
    engine.compute_flight_points(flight_point)
    np.testing.assert_allclose(flight_point.thrust_rate, 0.5, rtol=1e-4)
    np.testing.assert_allclose(flight_point.sfc, 1.3496e-5, rtol=1e-4)

    # Test full arrays
    # 2D arrays are used, where first line is for thrust rates, and second line
    # is for thrust values.
    # As thrust rates and thrust values match, thrust rate results are 2 equal
    # lines and so are thrust value results.
    machs = [0, 0.3, 0.3, 0.8, 0.8]
    altitudes = [0, 0, 0, 10000, 13000]
    thrust_rates = [0.8, 0.5, 0.5, 0.4, 0.7]
    thrusts = [0.9553 * 0.8, 0.38851, 0.35677, 0.09680, 0.11273]
    engine_settings = [
        EngineSetting.TAKEOFF,
        EngineSetting.TAKEOFF,
        EngineSetting.CLIMB,
        EngineSetting.IDLE,
        EngineSetting.CRUISE.value,
    ]  # mix EngineSetting with integers
    expected_sfc = [0.99210e-5, 1.3496e-5, 1.3496e-5, 1.8386e-5, 1.5957e-5]

    flight_points = pd.DataFrame()
    flight_points["mach"] = machs + machs
    flight_points["altitude"] = altitudes + altitudes
    flight_points["engine_setting"] = engine_settings + engine_settings
    flight_points["thrust_is_regulated"] = [False] * 5 + [True] * 5
    flight_points["thrust_rate"] = thrust_rates + [0.0] * 5
    flight_points["thrust"] = [0.0] * 5 + thrusts
    engine.compute_flight_points(flight_points)
    np.testing.assert_allclose(flight_points.sfc,
                               expected_sfc + expected_sfc,
                               rtol=1e-4)
    np.testing.assert_allclose(flight_points.thrust_rate,
                               thrust_rates + thrust_rates,
                               rtol=1e-4)
    np.testing.assert_allclose(flight_points.thrust,
                               thrusts + thrusts,
                               rtol=1e-4)
def test_dummy_reserve():
    dummy_reserve = DummyTransitionSegment(
        target=FlightPoint(altitude=0.0, mach=0.0),
        reserve_mass_ratio=0.1,
    )
    flight_points = dummy_reserve.compute_from(
        FlightPoint(altitude=0.0, mach=0.0, mass=55.0e3))
    assert_allclose(flight_points.mass, [55.0e3, 55.0e3, 50.0e3])
    assert_allclose(flight_points.altitude, [0.0, 0.0, 0.0])
    assert_allclose(flight_points.ground_distance, [0.0, 0.0, 0.0])
    assert_allclose(flight_points.mach, [0.0, 0.0, 0.0])
    assert_allclose(flight_points.true_airspeed, [0.0, 0.0, 0.0], rtol=1.0e-4)
def test_acceleration_not_enough_thrust(polar):
    propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2)

    segment = SpeedChangeSegment(
        target=FlightPoint(true_airspeed=250.0),
        propulsion=propulsion,
        reference_area=120.0,
        polar=polar,
        thrust_rate=0.1,
    )
    assert len(
        segment.compute_from(
            FlightPoint(altitude=5000.0, true_airspeed=150.0, mass=70000.0), ))
def test_dummy_descent_with_reserve():
    dummy_descent_reserve = DummyTransitionSegment(
        target=FlightPoint(altitude=0.0, mach=0.0, ground_distance=500.0e3),
        mass_ratio=0.9,
        reserve_mass_ratio=0.08,
    )
    flight_points = dummy_descent_reserve.compute_from(
        FlightPoint(altitude=9.0e3, mass=60.0e3, mach=0.8))
    assert_allclose(flight_points.mass, [60.0e3, 54.0e3, 50.0e3])
    assert_allclose(flight_points.altitude, [9.0e3, 0.0, 0.0])
    assert_allclose(flight_points.ground_distance, [0.0, 500.0e3, 500.0e3])
    assert_allclose(flight_points.mach, [0.8, 0.0, 0.0])
    assert_allclose(flight_points.true_airspeed, [243.04, 0.0, 0.0],
                    rtol=1.0e-4)
def test_climb_not_enough_thrust(polar):
    propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2)

    segment = AltitudeChangeSegment(
        target=FlightPoint(altitude=10000.0, true_airspeed="constant"),
        propulsion=propulsion,
        reference_area=120.0,
        polar=polar,
        thrust_rate=0.1,
    )
    assert (len(
        segment.compute_from(
            FlightPoint(altitude=5000.0, true_airspeed=150.0,
                        mass=70000.0), )) == 1)
Exemple #15
0
    def compute_flight_points(self, flight_points: Union[FlightPoint,
                                                         pd.DataFrame]):

        if isinstance(flight_points, FlightPoint):
            flight_points_per_engine = FlightPoint(flight_points)
        else:
            flight_points_per_engine = flight_points.copy()

        if flight_points.thrust is not None:
            flight_points_per_engine.thrust = flight_points.thrust / self.engine_count

        self.engine.compute_flight_points(flight_points_per_engine)
        flight_points.sfc = flight_points_per_engine.sfc
        flight_points.thrust = flight_points_per_engine.thrust * self.engine_count
        flight_points.thrust_rate = flight_points_per_engine.thrust_rate
Exemple #16
0
    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_wrapper.propulsion = propulsion_model
        self._mission_wrapper.reference_area = reference_area

        end_of_takeoff = FlightPoint(
            time=0.0,
            mass=inputs[self._mission_vars.TOW.value],
            true_airspeed=inputs[self._mission_vars.TAKEOFF_V2.value],
            altitude=inputs[self._mission_vars.TAKEOFF_ALTITUDE.value] +
            35 * foot,
            ground_distance=0.0,
        )

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

        # Final ================================================================
        end_of_mission = FlightPoint.create(self.flight_points.iloc[-1])
        zfw = end_of_mission.mass - self._mission_wrapper.get_reserve(
            self.flight_points, self.options["mission_name"])
        outputs[self._mission_vars.BLOCK_FUEL.value] = (
            inputs[self._mission_vars.TOW.value] +
            inputs[self._mission_vars.TAKEOFF_FUEL.value] +
            inputs[self._mission_vars.TAXI_OUT_FUEL.value] - zfw)
        if self.options["is_sizing"]:
            outputs["data:weight:aircraft:sizing_block_fuel"] = outputs[
                self._mission_vars.BLOCK_FUEL.value]

        def as_scalar(value):
            if isinstance(value, np.ndarray):
                return np.asscalar(value)
            return value

        self.flight_points = self.flight_points.applymap(as_scalar)
        if self.options["out_file"]:
            self.flight_points.to_csv(self.options["out_file"])
Exemple #17
0
    def __init__(
        self,
        *,
        target: FlightPoint,
        propulsion: IPropulsion,
        reference_area: float,
        polar: Polar,
        **kwargs
    ):
        """
        Only keyword arguments are accepted.

        :param target: the target flight point, defined for any relevant parameter
        :param propulsion: the propulsion model
        :param reference_area: the reference area for aerodynamic forces
        :param polar: the aerodynamic polar
        """
        self.propulsion = propulsion
        self.reference_area = reference_area
        self.polar = polar
        self.target = FlightPoint(target)

        # If true, computation will stop if a flight point is further from target
        # than previous one.
        self.interrupt_if_getting_further_from_target = True
        super().__init__(**kwargs)
def test_dummy_climb():
    dummy_climb = DummyTransitionSegment(target=FlightPoint(
        altitude=9.0e3, mach=0.8, ground_distance=400.0e3),
                                         mass_ratio=0.8)

    flight_points = dummy_climb.compute_from(
        FlightPoint(altitude=0.0,
                    mass=100.0e3,
                    mach=0.0,
                    ground_distance=100.0e3))

    assert_allclose(flight_points.mass, [100.0e3, 80.0e3])
    assert_allclose(flight_points.altitude, [0.0, 9.0e3])
    assert_allclose(flight_points.ground_distance, [100.0e3, 500.0e3])
    assert_allclose(flight_points.mach, [0.0, 0.8])
    assert_allclose(flight_points.true_airspeed, [0.0, 243.04], rtol=1.0e-4)
Exemple #19
0
    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
Exemple #20
0
    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
Exemple #21
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        n_eng = inputs["data:geometry:propulsion:count"]
        wing_area = inputs["data:geometry:wing:area"]
        mfw = inputs["data:weight:aircraft:MFW"]
        n_tank = 2.0

        propulsion_model = FuelEngineSet(
            self._engine_wrapper.get_model(inputs), n_eng)

        flight_point = FlightPoint(
            mach=0.0,
            altitude=0.0,
            engine_setting=EngineSetting.TAKEOFF,
            thrust_rate=1.0)  # with engine_setting as EngineSetting
        propulsion_model.compute_flight_points(flight_point)

        sl_thrust_newton = float(flight_point.thrust)
        sl_thrust_lbs = sl_thrust_newton / lbf
        sl_thrust_lbs_per_engine = sl_thrust_lbs / n_eng

        b3 = 11.5 * n_eng * sl_thrust_lbs_per_engine ** 0.2 + \
            0.07 * wing_area + \
            1.6 * n_tank * mfw ** 0.28

        outputs["data:weight:propulsion:unusable_fuel:mass"] = b3
    def 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
Exemple #23
0
def test_ranged_flight(low_speed_polar, high_speed_polar, cleanup):

    engine = RubberEngine(5.0, 30.0, 1500.0, 1.0e5, 0.95, 10000.0)
    propulsion = FuelEngineSet(engine, 2)

    total_distance = 2.0e6
    flight_calculator = RangedRoute(
        StandardFlight(
            propulsion=propulsion,
            reference_area=120.0,
            low_speed_climb_polar=low_speed_polar,
            high_speed_polar=high_speed_polar,
            cruise_mach=0.78,
            thrust_rates={FlightPhase.CLIMB: 0.93, FlightPhase.DESCENT: 0.2},
        ),
        flight_distance=total_distance,
    )

    start = FlightPoint(
        true_airspeed=150.0 * knot, altitude=100.0 * foot, mass=70000.0, ground_distance=100000.0,
    )
    flight_points = flight_calculator.compute_from(start)

    plot_flight(flight_points, "test_ranged_flight.png")

    assert_allclose(
        flight_points.iloc[-1].ground_distance,
        total_distance + start.ground_distance,
        atol=flight_calculator.distance_accuracy,
    )
def test_hold(polar):
    propulsion = FuelEngineSet(DummyEngine(0.5e5, 2.0e-5), 2)

    segment = HoldSegment(target=FlightPoint(time=3000.0),
                          propulsion=propulsion,
                          reference_area=120.0,
                          polar=polar)
    flight_points = segment.compute_from(
        FlightPoint(altitude=500.0, equivalent_airspeed=250.0, mass=60000.0), )

    last_point = flight_points.iloc[-1]
    assert_allclose(last_point.time, 3000.0)
    assert_allclose(last_point.altitude, 500.0)
    assert_allclose(last_point.equivalent_airspeed, 250.0, atol=0.1)
    assert_allclose(last_point.mass, 57776.0, rtol=1e-4)
    assert_allclose(last_point.ground_distance, 768323.0, rtol=1.0e-3)
Exemple #25
0
    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"])
Exemple #26
0
def test_standard_flight_fixed_altitude(low_speed_polar, high_speed_polar,
                                        cleanup):
    # Wild version of an additional flight phase.
    # Start altitude is high enough to skip initial climb and first segment of climb.
    # Cruise altitude is low so that first segment of descent will also be skipped.

    engine = RubberEngine(5.0, 30.0, 1500.0, 1.0e5, 0.95, 10000.0)
    propulsion = FuelEngineSet(engine, 2)

    flight_calculator = StandardFlight(
        propulsion=propulsion,
        reference_area=120.0,
        low_speed_climb_polar=low_speed_polar,
        high_speed_polar=high_speed_polar,
        cruise_mach=0.78,
        thrust_rates={
            FlightPhase.CLIMB: 0.93,
            FlightPhase.DESCENT: 0.2
        },
        cruise_distance=4.0e6,
        climb_target_altitude=20000.0 * foot,
        descent_target_altitude=1000.0 * foot,
        time_step=None,
    )

    flight_points = flight_calculator.compute_from(
        FlightPoint(equivalent_airspeed=260.0 * knot,
                    altitude=11000.0 * foot,
                    mass=60000.0), )
    plot_flight(flight_points, "test_standard_flight_fixed_altitude.png")

    assert not any(flight_points.name == FlightPhase.INITIAL_CLIMB.value)
    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])

    assert end_of_climb.mach < 0.78
    assert_allclose(end_of_climb.equivalent_airspeed, 300.0 * knot)
    assert_allclose(end_of_climb.altitude, 20000.0 * foot)
    assert_allclose(
        end_of_cruise.ground_distance - end_of_climb.ground_distance, 4.0e6)
    assert_allclose(end_of_descent.altitude, 1000.0 * foot)
    assert_allclose(end_of_descent.equivalent_airspeed, 250.0 * knot)
Exemple #27
0
    def compute_from(self, start: FlightPoint) -> pd.DataFrame:
        start = FlightPoint(start)
        self.complete_flight_point(start)  # needed to ensure all speed values are computed.

        if self.target.altitude and self.target.altitude < 0.0:
            # Target altitude will be modified along the process, so we keep track
            # of the original order in target CL, that is not used otherwise.
            self.target.CL = self.target.altitude
            self.interrupt_if_getting_further_from_target = False

        atm = AtmosphereSI(start.altitude)
        if self.target.equivalent_airspeed == "constant":
            start.true_airspeed = atm.get_true_airspeed(start.equivalent_airspeed)
        elif self.target.mach == "constant":
            start.true_airspeed = start.mach * atm.speed_of_sound

        return super().compute_from(start)
Exemple #28
0
def test_breguet_with_rubber_engine():
    ivc = om.IndepVarComp()
    ivc.add_output("data:mission:sizing:main_route:cruise:altitude",
                   35000,
                   units="ft")
    ivc.add_output("data:TLAR:cruise_mach", 0.78)
    ivc.add_output("data:TLAR:range", 500, units="NM")
    ivc.add_output("data:TLAR:NPAX", 150)
    ivc.add_output("data:aerodynamics:aircraft:cruise:L_D_max", 16.0)
    ivc.add_output("data:weight:aircraft:MTOW", 74000, units="kg")

    ivc.add_output("data:propulsion:rubber_engine:bypass_ratio", 5)
    ivc.add_output("data:propulsion:rubber_engine:maximum_mach", 0.95)
    ivc.add_output("data:propulsion:rubber_engine:design_altitude",
                   35000,
                   units="ft")
    ivc.add_output("data:propulsion:MTO_thrust", 100000, units="N")
    ivc.add_output("data:propulsion:rubber_engine:overall_pressure_ratio", 30)
    ivc.add_output("data:propulsion:rubber_engine:turbine_inlet_temperature",
                   1500,
                   units="K")

    # With rubber engine OM component
    group = om.Group()
    group.add_subsystem("breguet", OMBreguet(), promotes=["*"])
    group.add_subsystem("engine", OMRubberEngineComponent(), promotes=["*"])
    group.nonlinear_solver = om.NonlinearBlockGS()
    problem = run_system(group, ivc)

    assert_allclose(problem["data:mission:sizing:ZFW"], 65076.0, atol=1)
    assert_allclose(problem["data:mission:sizing:fuel"], 8924.0, atol=1)
    assert_allclose(problem["data:mission:sizing:fuel:unitary"],
                    0.0642,
                    rtol=1e-3)

    # With direct call to rubber engine
    problem2 = run_system(
        OMBreguet(propulsion_id="fastoad.wrapper.propulsion.rubber_engine"),
        ivc)

    assert_allclose(problem2["data:mission:sizing:ZFW"], 65076.0, atol=1)
    assert_allclose(problem2["data:mission:sizing:fuel"], 8924.0, atol=1)
    assert_allclose(problem2["data:mission:sizing:fuel:unitary"],
                    0.0642,
                    rtol=1e-3)

    engine = RubberEngine(5, 30, 1500, 100000, 0.95, 35000 * foot, -50)
    directly_computed_flight_point = FlightPoint(
        mach=0.78,
        altitude=35000 * foot,
        engine_setting=EngineSetting.CRUISE,
        thrust=problem["data:propulsion:required_thrust"],
    )
    engine.compute_flight_points(directly_computed_flight_point)
    assert_allclose(
        directly_computed_flight_point.sfc,
        problem["data:propulsion:SFC"],
    )
Exemple #29
0
def test_standard_flight_optimal_altitude(low_speed_polar, high_speed_polar,
                                          cleanup):

    engine = RubberEngine(5.0, 30.0, 1500.0, 1.0e5, 0.95, 10000.0)
    propulsion = FuelEngineSet(engine, 2)

    flight_calculator = StandardFlight(
        propulsion=propulsion,
        reference_area=120.0,
        low_speed_climb_polar=low_speed_polar,
        high_speed_polar=high_speed_polar,
        cruise_mach=0.78,
        thrust_rates={
            FlightPhase.CLIMB: 0.93,
            FlightPhase.DESCENT: 0.2
        },
        cruise_distance=4.0e6,
        time_step=None,
    )

    flight_points = flight_calculator.compute_from(
        FlightPoint(true_airspeed=150.0 * knot,
                    altitude=100.0 * foot,
                    mass=70000.0), )
    print_dataframe(flight_points)
    plot_flight(flight_points, "test_standard_flight_max_finesse.png")

    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])

    assert_allclose(end_of_initial_climb.altitude, 1500.0 * foot)
    assert_allclose(end_of_initial_climb.equivalent_airspeed, 250.0 * knot)
    assert_allclose(end_of_climb.mach, 0.78)
    assert end_of_climb.equivalent_airspeed <= 300.0 * knot
    assert_allclose(
        end_of_cruise.ground_distance - end_of_climb.ground_distance, 4.0e6)
    assert_allclose(end_of_cruise.mach, 0.78)
    assert_allclose(end_of_descent.altitude, 1500.0 * foot)
    assert_allclose(end_of_descent.equivalent_airspeed, 250.0 * knot)
def test_taxi():
    propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2)

    segment = TaxiSegment(target=FlightPoint(time=500.0),
                          propulsion=propulsion,
                          thrust_rate=0.1)
    flight_points = segment.compute_from(
        FlightPoint(altitude=10.0,
                    true_airspeed=10.0,
                    mass=50000.0,
                    time=10000.0), )

    last_point = flight_points.iloc[-1]
    assert_allclose(last_point.altitude, 10.0, atol=1.0)
    assert_allclose(last_point.time, 10500.0, rtol=1e-2)
    assert_allclose(last_point.true_airspeed, 10.0, atol=0.1)
    assert_allclose(last_point.mass, 49973.0, rtol=1e-4)
    assert_allclose(last_point.ground_distance, 5000.0)