Пример #1
0
    def sfc_at_max_thrust(self, atmosphere: Atmosphere,
                          mach: Union[float, Sequence[float]]) -> np.ndarray:
        """
        Computation of Specific Fuel Consumption at maximum thrust.

        Uses model described in :cite:`roux:2005`, p.41.

        :param atmosphere: Atmosphere instance at intended altitude
        :param mach: Mach number(s)
        :return: SFC (in kg/s/N)
        """

        altitude = atmosphere.get_altitude(False)
        mach = np.asarray(mach)

        # Following coefficients are constant for alt<=0 and alt >=11000m.
        # We use numpy to implement that so we are safe if altitude is a sequence.
        bound_altitude = np.minimum(11000, np.maximum(0, altitude))

        # pylint: disable=invalid-name  # coefficients are named after model
        a1 = -7.44e-13 * bound_altitude + 6.54e-7
        a2 = -3.32e-10 * bound_altitude + 8.54e-6
        b1 = -3.47e-11 * bound_altitude - 6.58e-7
        b2 = 4.23e-10 * bound_altitude + 1.32e-5
        c = -1.05e-7

        theta = atmosphere.temperature / ATM_SEA_LEVEL.temperature
        sfc = (mach * (a1 * self.bypass_ratio + a2) +
               (b1 * self.bypass_ratio + b2) * np.sqrt(theta) +
               ((7.4e-13 *
                 (self.overall_pressure_ratio - 30) * altitude) + c) *
               (self.overall_pressure_ratio - 30))

        return sfc
Пример #2
0
    def max_thrust(
        self,
        atmosphere: Atmosphere,
        mach: Union[float, Sequence[float]],
    ) -> np.ndarray:
        """
        Computation of maximum thrust.
        Uses model described in ...
        :param atmosphere: Atmosphere instance at intended altitude (should be <=20km)
        :param mach: Mach number(s) (should be between 0.05 and 1.0)
        :return: maximum thrust (in N)
        """

        # Calculate maximum mechanical power @ given altitude
        altitude = atmosphere.get_altitude(altitude_in_feet=True)
        mach = np.asarray(mach)
        sigma = Atmosphere(altitude).density / Atmosphere(0.0).density
        max_power = self.max_power * (sigma - (1 - sigma) / 7.55)
        _, _, _, _, _, _ = self.compute_dimensions()
        thrust_1 = (self.propeller["thrust_SL"] * g) * sigma**(
            1 / 3)  # considered fixed point @altitude
        thrust_2 = max_power * PROPELLER_EFFICIENCY / np.maximum(
            mach * Atmosphere(altitude).speed_of_sound, 1e-20)

        return np.minimum(thrust_1, thrust_2)
Пример #3
0
    def sfc_at_max_power(self,
                         atmosphere: Atmosphere) -> Union[float, Sequence]:
        """
        Computation of Specific Fuel Consumption at maximum power.
        :param atmosphere: Atmosphere instance at intended altitude
        :return: SFC_P (in kg/s/W)
        """

        altitude = atmosphere.get_altitude(altitude_in_feet=True)
        sigma = Atmosphere(altitude).density / Atmosphere(0.0).density
        max_power = (self.max_power / 1e3) * (sigma - (1 - sigma) / 7.55
                                              )  # max power in kW

        if self.fuel_type == 1.:
            if self.strokes_nb == 2.:  # Gasoline 2-strokes
                sfc_p = 1125.9 * max_power**(-0.2441)
            else:  # Gasoline 4-strokes
                sfc_p = -0.0011 * max_power**2 + 0.5905 * max_power + 228.58
        elif self.fuel_type == 2.:
            if self.strokes_nb == 2.:  # Diesel 2-strokes
                sfc_p = -0.765 * max_power + 334.94
            else:  # Diesel 4-strokes
                sfc_p = -0.964 * max_power + 231.91
        else:
            warnings.warn(
                'Propulsion layout {} not implemented in model, replaced by layout 1!'
                .format(self.fuel_type))
            if self.strokes_nb == 2.:  # Gasoline 2-strokes
                sfc_p = 1125.9 * max_power**(-0.2441)
            else:  # Gasoline 4-strokes
                sfc_p = -0.0011 * max_power**2 + 0.5905 * max_power + 228.58

        sfc_p = sfc_p / 1e6 / 3600.0  # change units to be in kg/s/W

        return sfc_p
Пример #4
0
    def sfc_at_max_power(self,
                         atmosphere: Atmosphere) -> Union[float, Sequence]:
        """
        Computation of Specific Fuel Consumption at maximum power.
        :param atmosphere: Atmosphere instance at intended altitude
        :return: SFC_P (in kg/s/W)
        """

        altitude = atmosphere.get_altitude(altitude_in_feet=True)
        sigma = Atmosphere(altitude).density / Atmosphere(0.0).density
        max_power = (self.max_power / 1e3) * (sigma - (1 - sigma) / 7.55
                                              )  # max power in kW

        if self.fuel_type == 1.:
            if self.strokes_nb == 2.:  # Gasoline 2-strokes
                sfc_p = 1125.9 * max_power**(-0.2441)
            else:  # Gasoline 4-strokes
                sfc_p = -0.0011 * max_power**2 + 0.5905 * max_power + 228.58
        elif self.fuel_type == 2.:
            if self.strokes_nb == 2.:  # Diesel 2-strokes
                sfc_p = -0.765 * max_power + 334.94
            else:  # Diesel 4-strokes
                sfc_p = -0.964 * max_power + 231.91
        else:
            raise FastBasicICEngineInconsistentInputParametersError(
                "Bad engine configuration: fuel type {0:f} model does not exist."
                .format(float(self.fuel_type)))

        sfc_p = sfc_p / 1e6 / 3600.0  # change units to be in kg/s/W

        return sfc_p
Пример #5
0
    def max_thrust(
        self,
        atmosphere: Atmosphere,
        mach: Union[float, Sequence[float]],
        delta_t4: Union[float, Sequence[float]],
    ) -> np.ndarray:
        """
        Computation of maximum thrust.

        Uses model described in :cite:`roux:2005`, p.57-58

        :param atmosphere: Atmosphere instance at intended altitude (should be <=20km)
        :param mach: Mach number(s) (should be between 0.05 and 1.0)
        :param delta_t4: (unit=K) difference between operational and design values of
                         turbine inlet temperature in K
        :return: maximum thrust (in N)
        """

        altitude = atmosphere.get_altitude(altitude_in_feet=False)
        mach = np.asarray(mach)
        delta_t4 = np.asarray(delta_t4)

        def _mach_effect():
            """Computation of Mach effect."""
            vect = [
                (self.overall_pressure_ratio - 30)**2,
                (self.overall_pressure_ratio - 30),
                1.0,
                self.t_4,
                delta_t4,
            ]

            def _calc_coef(a_coeffs, b_coeffs):
                # We don't use np.dot because delta_t4 can be a sequence
                return (a_coeffs[0] * vect[0] + a_coeffs[1] * vect[1] +
                        a_coeffs[2] + a_coeffs[3] * vect[3] +
                        a_coeffs[4] * vect[4]) * self.bypass_ratio + (
                            b_coeffs[0] * vect[0] + b_coeffs[1] * vect[1] +
                            b_coeffs[2] + b_coeffs[3] * vect[3] +
                            b_coeffs[4] * vect[4])

            f_ms = _calc_coef(ALPHA[0], BETA[0])
            g_ms = _calc_coef(ALPHA[1], BETA[1])
            f_fm = _calc_coef(ALPHA[2], BETA[2])
            g_fm = _calc_coef(ALPHA[3], BETA[3])

            ms_11000 = (A_MS * self.t_4 + B_MS * self.bypass_ratio + C_MS *
                        (self.overall_pressure_ratio - 30) + D_MS * delta_t4 +
                        E_MS)

            fm_11000 = (A_FM * self.t_4 + B_FM * self.bypass_ratio + C_FM *
                        (self.overall_pressure_ratio - 30) + D_FM * delta_t4 +
                        E_FM)

            # Following coefficients are constant for alt >=11000m.
            # We use numpy to implement that so we are safe if altitude is a sequence.
            bound_altitude = np.minimum(11000, altitude)

            m_s = ms_11000 + f_ms * (bound_altitude - 11000)**2 + g_ms * (
                bound_altitude - 11000)
            f_m = fm_11000 + f_fm * (bound_altitude - 11000)**2 + g_fm * (
                bound_altitude - 11000)

            alpha_mach_effect = (1 - f_m) / (m_s * m_s)

            return alpha_mach_effect * (mach - m_s)**2 + f_m

        def _altitude_effect():
            """Computation of altitude effect."""
            # pylint: disable=invalid-name  # coefficients are named after model
            k = 1 + 1.2e-3 * delta_t4
            nf = 0.98 + 8e-4 * delta_t4

            def _troposhere_effect(density, altitude, k, nf):
                return (k * ((density / ATM_SEA_LEVEL.density)**nf) *
                        (1 / (1 - (0.04 * np.sin(
                            (np.pi * altitude) / 11000)))))

            def _stratosphere_effect(density, k, nf):
                return (k * (
                    (ATM_TROPOPAUSE.density / ATM_SEA_LEVEL.density)**nf) *
                        density / ATM_TROPOPAUSE.density)

            if np.size(altitude) == 1:
                if altitude <= 11000:
                    h = _troposhere_effect(atmosphere.density, altitude, k, nf)
                else:
                    h = _stratosphere_effect(atmosphere.density, k, nf)
            else:
                h = np.empty(np.shape(altitude))
                idx = altitude <= 11000
                if np.size(delta_t4) == 1:
                    h[idx] = _troposhere_effect(atmosphere.density[idx],
                                                altitude[idx], k, nf)
                    idx = np.logical_not(idx)
                    h[idx] = _stratosphere_effect(atmosphere.density[idx], k,
                                                  nf)
                else:
                    h[idx] = _troposhere_effect(atmosphere.density[idx],
                                                altitude[idx], k[idx], nf[idx])
                    idx = np.logical_not(idx)
                    h[idx] = _stratosphere_effect(atmosphere.density[idx],
                                                  k[idx], nf[idx])

            return h

        def _residuals():
            """Computation of residuals."""
            return (-4.51e-3 * self.bypass_ratio + 2.19e-5 * self.t_4 -
                    3.09e-4 * (self.overall_pressure_ratio - 30) + 0.945)

        return self.f_0 * _mach_effect() * _altitude_effect() * _residuals()
Пример #6
0
    def _compute_flight_points(
        self,
        mach: Union[float, Sequence],
        altitude: Union[float, Sequence],
        engine_setting: Union[float, Sequence],
        thrust_is_regulated: Optional[Union[bool, Sequence]] = None,
        thrust_rate: Optional[Union[float, Sequence]] = None,
        thrust: Optional[Union[float, Sequence]] = None,
    ) -> Tuple[Union[float, Sequence], Union[float, Sequence], Union[
            float, Sequence]]:
        """
        Computes the Specific Fuel Consumption based on aircraft trajectory conditions.
        
        :param flight_points.mach: Mach number
        :param flight_points.altitude: (unit=m) altitude w.r.t. to sea level
        :param flight_points.engine_setting: define
        :param flight_points.thrust_is_regulated: tells if thrust_rate or thrust should be used (works element-wise)
        :param flight_points.thrust_rate: thrust rate (unit=none)
        :param flight_points.thrust: required thrust (unit=N)
        :return: SFC (in kg/s/N), thrust rate, thrust (in N)
        """

        # Treat inputs (with check on thrust rate <=1.0)
        mach = np.asarray(mach)
        altitude = np.asarray(altitude)
        if thrust_is_regulated is not None:
            thrust_is_regulated = np.asarray(np.round(thrust_is_regulated, 0),
                                             dtype=bool)
        thrust_is_regulated, thrust_rate, thrust = self._check_thrust_inputs(
            thrust_is_regulated, thrust_rate, thrust)
        thrust_is_regulated = np.asarray(np.round(thrust_is_regulated, 0),
                                         dtype=bool)
        thrust_rate = np.asarray(thrust_rate)
        thrust = np.asarray(thrust)

        # Get maximum thrust @ given altitude
        atmosphere = Atmosphere(altitude, altitude_in_feet=False)
        max_thrust = self.max_thrust(atmosphere, mach)

        # We compute thrust values from thrust rates when needed
        idx = np.logical_not(thrust_is_regulated)
        if np.size(max_thrust) == 1:
            maximum_thrust = max_thrust
            out_thrust_rate = thrust_rate
            out_thrust = thrust
        else:
            out_thrust_rate = (np.full(np.shape(max_thrust),
                                       thrust_rate.item())
                               if np.size(thrust_rate) == 1 else thrust_rate)
            out_thrust = (np.full(np.shape(max_thrust), thrust.item())
                          if np.size(thrust) == 1 else thrust)
            maximum_thrust = max_thrust[idx]
        if np.any(idx):
            out_thrust[idx] = out_thrust_rate[idx] * maximum_thrust
        if np.any(thrust_is_regulated):
            out_thrust[thrust_is_regulated] = np.minimum(
                out_thrust[thrust_is_regulated],
                max_thrust[thrust_is_regulated])

        # thrust_rate is obtained from entire thrust vector (could be optimized if needed,
        # as some thrust rates that are computed may have been provided as input)
        out_thrust_rate = out_thrust / max_thrust

        # Resetting the model is required to re-run from scratch the FMU
        self.model.reset()

        # Evaluate max power
        altitude = atmosphere.get_altitude(altitude_in_feet=True)
        sigma = Atmosphere(altitude).density / Atmosphere(0.0).density
        max_power = (self.max_power / 1e3) * (sigma - (1 - sigma) / 7.55
                                              )  # max power in kW

        # Set all parameters before computation
        self.model.set("maximum_power", max_power)
        self.model.set("fuel_type", self.fuel_type)
        self.model.set("strokes_nb", self.strokes_nb)
        self.model.set("propeller_efficiency", PROPELLER_EFFICIENCY)
        self.model.set("thrust.k", out_thrust)
        self.model.set("mach.k", mach)
        self.model.initialize()

        result = self.model.simulate(
            start_time=0.0,
            final_time=0.0,
            options={"ncp": 2},
        )

        sfc = result['sfc.y'][-1] / np.maximum(out_thrust,
                                               1e-6)  # avoid 0 division

        return sfc, out_thrust_rate, out_thrust