示例#1
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)
示例#2
0
    def _get_distance_to_target(self,
                                flight_points: List[FlightPoint]) -> float:
        current = flight_points[-1]

        # Max flight level is first priority
        max_authorized_altitude = self.maximum_flight_level * 100.0 * foot
        if current.altitude >= max_authorized_altitude:
            return max_authorized_altitude - current.altitude

        if self.target.CL:
            # Optimal altitude is based on a target Mach number, though target speed
            # may be specified as TAS or EAS. If so, Mach number has to be computed
            # for target altitude and speed.

            # First, as target speed is expected to be set to self.CONSTANT_VALUE for one
            # parameter. Let's get the real value from start point.
            target_speed = copy(self.target)
            for speed_param in [
                    "true_airspeed", "equivalent_airspeed", "mach"
            ]:
                if isinstance(getattr(target_speed, speed_param), str):
                    setattr(target_speed, speed_param,
                            getattr(flight_points[0], speed_param))

            # Now, let's compute target Mach number
            atm = AtmosphereSI(max(self.target.altitude, current.altitude))
            if target_speed.equivalent_airspeed:
                target_speed.true_airspeed = atm.get_true_airspeed(
                    target_speed.equivalent_airspeed)
            if target_speed.true_airspeed:
                target_speed.mach = target_speed.true_airspeed / atm.speed_of_sound

            # Now we compute optimal altitude
            optimal_altitude = self._get_optimal_altitude(
                current.mass, target_speed.mach, current.altitude)
            if self.target.CL == self.OPTIMAL_ALTITUDE:
                self.target.altitude = optimal_altitude
            else:  # self.target.CL == self.OPTIMAL_FLIGHT_LEVEL:
                self.target.altitude = get_closest_flight_level(
                    optimal_altitude, up_direction=False)

        if self.target.altitude is not None:
            return self.target.altitude - current.altitude
        if self.target.true_airspeed and self.target.true_airspeed != self.CONSTANT_VALUE:
            return self.target.true_airspeed - current.true_airspeed
        if (self.target.equivalent_airspeed
                and self.target.equivalent_airspeed != self.CONSTANT_VALUE):
            return self.target.equivalent_airspeed - current.equivalent_airspeed
        if self.target.mach is not None and self.target.mach != self.CONSTANT_VALUE:
            return self.target.mach - current.mach

        raise FastFlightSegmentIncompleteFlightPoint(
            "No valid target definition for altitude change.")
示例#3
0
 def distance_to_optimum(altitude):
     atm = AtmosphereSI(altitude)
     true_airspeed = mach * atm.speed_of_sound
     optimal_air_density = (
         2.0 * mass * g / (self.reference_area * true_airspeed ** 2 * self.polar.optimal_cl)
     )
     return (atm.density - optimal_air_density) * 100.0
示例#4
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)
        # Mach number is capped by self.maximum_mach
        if flight_point.mach > self.maximum_mach:
            flight_point.mach = self.maximum_mach
            flight_point.true_airspeed = flight_point.equivalent_airspeed = None
            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
        )
示例#5
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        atmosphere = AtmosphereSI(
            inputs["data:mission:sizing:main_route:cruise:altitude"])
        cruise_speed = atmosphere.speed_of_sound * inputs[
            "data:TLAR:cruise_mach"]

        cruise_distance = inputs[
            "data:mission:sizing:main_route:cruise:distance"]
        ld_ratio = inputs["data:aerodynamics:aircraft:cruise:L_D_max"]
        sfc = inputs["data:propulsion:SFC"]

        range_factor = cruise_speed * ld_ratio / g / sfc
        # During first iterations, SFC will be incorrect and range_factor may be too low,
        # resulting in null or too small cruise_mass_ratio.
        # Forcing cruise_mass_ratio to a minimum of 0.3 avoids problems and should not
        # harm (no airplane loses 70% of its weight from fuel consumption)
        cruise_mass_ratio = np.maximum(
            0.3, 1.0 / np.exp(cruise_distance / range_factor))

        outputs[
            "data:mission:sizing:main_route:cruise:mass_ratio"] = cruise_mass_ratio
示例#6
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)
示例#7
0
    def _get_distance_to_target(self, flight_points: List[FlightPoint]) -> bool:
        current = flight_points[-1]
        if self.target.CL:
            # Optimal altitude is based on a target Mach number, though target speed
            # may be specified as TAS or EAS. If so, Mach number has to be computed
            # for target altitude and speed.

            # First, as target speed is expected to be set to "constant" for one
            # parameter. Let's get the real value from start point.
            target_speed = FlightPoint(self.target)
            for speed_param in ["true_airspeed", "equivalent_airspeed", "mach"]:
                if isinstance(target_speed.get(speed_param), str):
                    target_speed[speed_param] = flight_points[0][speed_param]

            # Now, let's compute target Mach number
            atm = AtmosphereSI(max(self.target.altitude, current.altitude))
            if target_speed.equivalent_airspeed:
                target_speed.true_airspeed = atm.get_true_airspeed(target_speed.equivalent_airspeed)
            if target_speed.true_airspeed:
                target_speed.mach = target_speed.true_airspeed / atm.speed_of_sound

            # Mach number has to be capped by self.maximum_mach
            target_mach = min(target_speed.mach, self.maximum_mach)

            # Now we compute optimal altitude
            optimal_altitude = self._get_optimal_altitude(
                current.mass, target_mach, current.altitude
            )
            if self.target.CL == self.OPTIMAL_ALTITUDE:
                self.target.altitude = optimal_altitude
            else:  # self.target.CL == self.OPTIMAL_FLIGHT_LEVEL:
                flight_level = 1000 * foot
                self.target.altitude = flight_level * np.floor(optimal_altitude / flight_level)

        if self.target.altitude:
            return self.target.altitude - current.altitude
        elif self.target.true_airspeed:
            return self.target.true_airspeed - current.true_airspeed
        elif self.target.equivalent_airspeed:
            return self.target.equivalent_airspeed - current.equivalent_airspeed
        elif self.target.mach:
            return self.target.mach - current.mach
示例#8
0
    def _complete_speed_values(flight_point: FlightPoint):
        """
        Computes consistent values between TAS, EAS and Mach, assuming one of them is defined.
        """
        atm = AtmosphereSI(flight_point.altitude)

        if flight_point.true_airspeed is None:
            if flight_point.mach is not None:
                flight_point.true_airspeed = flight_point.mach * atm.speed_of_sound
            elif flight_point.equivalent_airspeed is not None:
                flight_point.true_airspeed = atm.get_true_airspeed(
                    flight_point.equivalent_airspeed)
            else:
                raise FastFlightSegmentIncompleteFlightPoint(
                    "Flight point should be defined for true_airspeed, "
                    "equivalent_airspeed, or mach.")
        if flight_point.mach is None:
            flight_point.mach = flight_point.true_airspeed / atm.speed_of_sound

        if flight_point.equivalent_airspeed is None:
            flight_point.equivalent_airspeed = atm.get_equivalent_airspeed(
                flight_point.true_airspeed)
示例#9
0
    def compute(self, inputs, outputs):
        if self.low_speed_aero:
            mach = inputs["data:aerodynamics:aircraft:takeoff:mach"]
            altitude = 0.0
        else:
            mach = inputs["data:TLAR:cruise_mach"]
            altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]

        reynolds = AtmosphereSI(altitude).get_unitary_reynolds(mach)

        if self.low_speed_aero:
            outputs["data:aerodynamics:wing:low_speed:reynolds"] = reynolds
        else:
            outputs["data:aerodynamics:wing:cruise:reynolds"] = reynolds
示例#10
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        F = inputs["data:propeller:force:hover:z"]
        D = inputs["data:propeller:diameter"]
        Ct = inputs["data:propeller:Ct"]
        altitude = inputs["settings:altitude"]

        atm = AtmosphereSI(altitude)
        rho = atm.density

        n = np.sqrt(F / (Ct * rho * D**4.0))
        omega = n * 2.0 * pi

        outputs["data:propeller:frequency:hover"] = n
        outputs["data:propeller:speed:hover"] = omega
示例#11
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        F_pro = inputs["data:propeller:force:takeoff:z"]
        Ct = inputs["data:propeller:Ct"]
        altitude = inputs["settings:altitude"]
        k_ND = inputs["data:propeller:ND:k"]
        ND_max = inputs["data:propeller:ND:max"]

        atm = AtmosphereSI(altitude)
        rho = atm.density

        ND = ND_max / k_ND
        D_pro = (F_pro / (Ct * rho * ND**2.0))**0.5

        outputs["data:propeller:ND"] = ND
        outputs["data:propeller:diameter"] = D_pro
示例#12
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        D = inputs["data:propeller:diameter"]
        n = inputs["data:propeller:frequency:hover"]
        omega = inputs["data:propeller:speed:hover"]
        Cp = inputs["data:propeller:Cp"]
        altitude = inputs["settings:altitude"]

        atm = AtmosphereSI(altitude)
        rho = atm.density

        P = Cp * rho * n**3.0 * D**5.0
        T = P / omega

        outputs["data:propeller:power:hover"] = P
        outputs["data:propeller:torque:hover"] = T
示例#13
0
    def compute_cruise_mass_ratio(self, initial_cruise_mass, cruise_distance):
        """

        :param initial_cruise_mass:
        :param cruise_distance:
        :return: (mass at end of cruise) / (mass at start of cruise)
        """
        self.thrust = initial_cruise_mass / self.lift_drag_ratio * g
        flight_point = FlightPoint(
            mach=self.cruise_mach,
            altitude=self.cruise_altitude,
            engine_setting=EngineSetting.CRUISE,
            thrust=self.thrust,
        )
        self.propulsion.compute_flight_points(flight_point)
        self.sfc = flight_point.sfc
        self.thrust_rate = flight_point.thrust_rate

        atmosphere = AtmosphereSI(self.cruise_altitude)
        cruise_speed = atmosphere.speed_of_sound * self.cruise_mach
        range_factor = cruise_speed * self.lift_drag_ratio / g / self.sfc
        return 1.0 / np.exp(cruise_distance / range_factor)
示例#14
0
#  Copyright (C) 2020  ONERA & ISAE-SUPAERO
#  FAST is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

from fastoad.utils.physics import AtmosphereSI

# Atmosphere at limits of troposhere
ATM_SEA_LEVEL = AtmosphereSI(0)
ATM_TROPOPAUSE = AtmosphereSI(11000)

# Constants for computation of maximum thrust ---------------------------------
# (see E. Roux model definition in roux:2005)
A_MS = -2.74e-4
A_FM = 2.67e-4
B_MS = 1.91e-2
B_FM = -2.35e-2
C_MS = 1.21e-3
C_FM = -1.32e-3
D_MS = -8.48e-4
D_FM = 3.14e-4
E_MS = 8.96e-1
E_FM = 5.22e-1