Beispiel #1
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        sea_level_density = Atmosphere(0).density
        wing_area = inputs["data:geometry:wing:area"]
        span = inputs["data:geometry:wing:span"]
        mzfw = inputs["data:weight:aircraft:MZFW"]
        mfw = inputs["data:weight:aircraft:MFW"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        cl_alpha = inputs["data:aerodynamics:aircraft:cruise:CL_alpha"]
        u_gust1 = inputs["data:load_case:lc1:U_gust"]
        alt_1 = inputs["data:load_case:lc1:altitude"]
        vc_eas1 = inputs["data:load_case:lc1:Vc_EAS"]
        u_gust2 = inputs["data:load_case:lc2:U_gust"]
        alt_2 = inputs["data:load_case:lc2:altitude"]
        vc_eas2 = inputs["data:load_case:lc2:Vc_EAS"]

        # calculation of mean geometric chord
        chord_geom = wing_area / span

        # load case #1
        m1 = 1.05 * mzfw
        n_gust_1 = self.__n_gust(
            m1,
            wing_area,
            Atmosphere(alt_1).density,
            sea_level_density,
            chord_geom,
            vc_eas1,
            cl_alpha,
            u_gust1,
        )
        n1 = 1.5 * max(2.5, n_gust_1)
        n1m1 = n1 * m1

        # load case #2
        n_gust_2 = self.__n_gust(
            mtow,
            wing_area,
            Atmosphere(alt_2).density,
            sea_level_density,
            chord_geom,
            vc_eas2,
            cl_alpha,
            u_gust2,
        )
        n2 = 1.5 * max(2.5, n_gust_2)
        mcv = min(0.8 * mfw, mtow - mzfw)
        n2m2 = n2 * (mtow - 0.55 * mcv)

        outputs["data:mission:sizing:cs25:sizing_load_1"] = n1m1
        outputs["data:mission:sizing:cs25:sizing_load_2"] = n2m2
Beispiel #2
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        l0_wing = inputs["data:geometry:wing:MAC:length"]
        speed = inputs["data:TLAR:approach_speed"]

        atm = Atmosphere(0.0, 15.0)
        atm.true_airspeed = speed
        reynolds = atm.unitary_reynolds * l0_wing

        outputs["data:aerodynamics:aircraft:landing:mach"] = atm.mach
        outputs["data:aerodynamics:wing:landing:reynolds"] = reynolds
Beispiel #3
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
Beispiel #4
0
def test_sfc_at_max_thrust():
    """
    Checks model against values from :cite:`roux:2005` p.40
    (only for ground/Mach=0 values, as cruise values of the report look flawed)

    .. bibliography:: ../refs.bib
    """

    # Check with arrays
    cfm56_3c1 = RubberEngine(6, 25.7, 0, 0, 0, 0)
    atm = Atmosphere([0, 10668, 13000], altitude_in_feet=False)
    sfc = cfm56_3c1.sfc_at_max_thrust(atm, [0, 0.8, 0.8])
    # Note: value for alt==10668 is different from PhD report
    #       alt=13000 is here just for testing in stratosphere
    np.testing.assert_allclose(sfc, [0.97035e-5, 1.7756e-5, 1.7711e-5],
                               rtol=1e-4)

    # Check with scalars
    trent900 = RubberEngine(7.14, 41, 0, 0, 0, 0)
    atm = Atmosphere(0, altitude_in_feet=False)
    sfc = trent900.sfc_at_max_thrust(atm, 0)
    np.testing.assert_allclose(sfc, 0.73469e-5, rtol=1e-4)

    atm = Atmosphere(9144, altitude_in_feet=False)
    sfc = trent900.sfc_at_max_thrust(atm, 0.8)
    np.testing.assert_allclose(sfc, 1.6766e-5,
                               rtol=1e-4)  # value is different from PhD report

    # Check with arrays
    pw2037 = RubberEngine(6, 31.8, 0, 0, 0, 0)
    atm = Atmosphere(0, altitude_in_feet=False)
    sfc = pw2037.sfc_at_max_thrust(atm, 0)
    np.testing.assert_allclose(sfc, 0.9063e-5, rtol=1e-4)

    atm = Atmosphere(10668, altitude_in_feet=False)
    sfc = pw2037.sfc_at_max_thrust(atm, 0.85)
    np.testing.assert_allclose(sfc, 1.7439e-5,
                               rtol=1e-4)  # value is different from PhD report
Beispiel #5
0
    def get_cd0(alt, mach, cl, low_speed_aero):
        atm = Atmosphere(alt)
        atm.mach = mach
        reynolds = atm.unitary_reynolds

        ivc = get_indep_var_comp(input_list)
        if low_speed_aero:
            ivc.add_output("data:aerodynamics:aircraft:takeoff:mach", mach)
            ivc.add_output("data:aerodynamics:wing:low_speed:reynolds", reynolds)
            ivc.add_output(
                "data:aerodynamics:aircraft:low_speed:CL", 150 * [cl]
            )  # needed because size of input array is fixed
        else:
            ivc.add_output("data:TLAR:cruise_mach", mach)
            ivc.add_output("data:aerodynamics:wing:cruise:reynolds", reynolds)
            ivc.add_output(
                "data:aerodynamics:aircraft:cruise:CL", 150 * [cl]
            )  # needed because size of input array is fixed
        problem = run_system(CD0(low_speed_aero=low_speed_aero), ivc)
        if low_speed_aero:
            return problem["data:aerodynamics:aircraft:low_speed:CD0"][0]
        else:
            return problem["data:aerodynamics:aircraft:cruise:CD0"][0]
Beispiel #6
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        # Area of HTP is computed so its "lift" can counter the moment of weight
        # on front landing gear w.r.t. main landing gear when the CG is in its
        # most front position.

        tail_type = np.round(inputs["data:geometry:has_T_tail"])
        fuselage_length = inputs["data:geometry:fuselage:length"]
        x_wing_aero_center = inputs["data:geometry:wing:MAC:at25percent:x"]
        x_main_lg = inputs["data:weight:airframe:landing_gear:main:CG:x"]
        x_front_lg = inputs["data:weight:airframe:landing_gear:front:CG:x"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        wing_area = inputs["data:geometry:wing:area"]
        wing_mac = inputs["data:geometry:wing:MAC:length"]
        cg_range = inputs["settings:weight:aircraft:CG:range"]
        front_lg_weight_ratio = inputs[
            "settings:weight:airframe:landing_gear:front:weight_ratio"]
        htp_aero_center_ratio = inputs[
            "settings:geometry:horizontal_tail:position_ratio_on_fuselage"]

        delta_lg = x_main_lg - x_front_lg
        atm = Atmosphere(0.0)
        rho = atm.density
        vspeed = atm.speed_of_sound * 0.2  # assume the corresponding Mach of VR is 0.2

        # Proportion of weight on front landing gear is equal to distance between
        # main landing gear and center of gravity, divided by distance between landing gears.

        # If CG is in the most front position, the distance between main landing gear
        # and center of gravity is:
        distance_cg_to_mlg = front_lg_weight_ratio * delta_lg + wing_mac * cg_range

        # So with this front CG, moment of (weight on front landing gear) w.r.t.
        # main landing gear is:
        m_front_lg = mtow * g * distance_cg_to_mlg

        # Moment coefficient
        pdyn = 0.5 * rho * vspeed**2
        cm_front_lg = m_front_lg / (pdyn * wing_area * wing_mac)

        # # CM of MTOW on main landing gear w.r.t 25% wing MAC
        # lever_arm = front_lg_weight_ratio * delta_lg  # lever arm wrt CoG
        # lever_arm += wing_mac * cg_range  # and now wrt 25% wing MAC
        # cm_wheel = mtow * g * lever_arm / (pdyn * wing_area * wing_mac)

        ht_volume_coeff = cm_front_lg

        if tail_type == 1:
            aero_centers_distance = fuselage_length - x_wing_aero_center
            wet_area_coeff = 1.6
        elif tail_type == 0:
            aero_centers_distance = htp_aero_center_ratio * fuselage_length - x_wing_aero_center
            wet_area_coeff = 2.0
        else:
            raise ValueError(
                "Value of data:geometry:has_T_tail can only be 0 or 1")

        htp_area = ht_volume_coeff / aero_centers_distance * wing_area * wing_mac
        wet_area_htp = wet_area_coeff * htp_area

        outputs[
            "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"] = aero_centers_distance
        outputs["data:geometry:horizontal_tail:wetted_area"] = wet_area_htp
        outputs["data:geometry:horizontal_tail:area"] = htp_area
Beispiel #7
0
def test_max_thrust():
    """
    Checks model against simplified (but analytically equivalent) formulas
    as in p. 59 of :cite:`roux:2005`, but with correct coefficients (yes, those in report
    are not consistent with the complete formula nor the figure 2.19 just below)

    .. bibliography:: ../refs.bib
    """
    engine = RubberEngine(5, 30, 1500, 1, 0,
                          0)  # f0=1 so that output is simply fmax/f0
    machs = np.arange(0, 1.01, 0.1)

    # Check with cruise altitude
    atm = Atmosphere(11000, altitude_in_feet=False)
    max_thrust_ratio = engine.max_thrust(atm, machs, -100)
    ref_max_thrust_ratio = (0.94916 * atm.density / 1.225 *
                            (1 - 0.68060 * machs + 0.51149 * machs**2))
    np.testing.assert_allclose(max_thrust_ratio,
                               ref_max_thrust_ratio,
                               rtol=1e-4)

    # Check with Takeoff altitude
    atm = Atmosphere(0, altitude_in_feet=False)
    max_thrust_ratio = engine.max_thrust(atm, machs, 0)
    ref_max_thrust_ratio = (0.9553 * atm.density / 1.225 *
                            (1 - 0.72971 * machs + 0.35886 * machs**2))
    np.testing.assert_allclose(max_thrust_ratio,
                               ref_max_thrust_ratio,
                               rtol=1e-4)

    # Check Cruise above 11000 with compression rate != 30 and bypass ratio != 5
    engine = RubberEngine(4, 35, 1500, 1, 0,
                          0)  # f0=1 so that output is simply fmax/f0
    atm = Atmosphere(13000, altitude_in_feet=False)
    max_thrust_ratio = engine.max_thrust(atm, machs, -50)
    ref_max_thrust_ratio = (0.96880 * atm.density / 1.225 *
                            (1 - 0.63557 * machs + 0.52108 * machs**2))
    np.testing.assert_allclose(max_thrust_ratio,
                               ref_max_thrust_ratio,
                               rtol=1e-4)

    # Check with compression rate != 30 and bypass ratio != 5 and an array for altitudes (as
    # many values as mach numbers)
    engine = RubberEngine(6, 22, 1500, 1, 0,
                          0)  # f0=1 so that output is simply fmax/f0
    atm = Atmosphere(np.arange(3000, 13100, 1000), altitude_in_feet=False)
    max_thrust_ratio = engine.max_thrust(atm, machs, -50)
    ref_max_thrust_ratio = [
        0.69811,
        0.59162,
        0.50117,
        0.42573,
        0.36417,
        0.31512,
        0.27704,
        0.24820,
        0.22678,
        0.19965,
        0.17795,
    ]
    np.testing.assert_allclose(max_thrust_ratio,
                               ref_max_thrust_ratio,
                               rtol=1e-4)
Beispiel #8
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()
Beispiel #9
0
    def compute_flight_points_from_dt4(
        self,
        mach: Union[float, Sequence],
        altitude: Union[float, Sequence],
        delta_t4: 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]]:
        # pylint: disable=too-many-arguments  # they define the trajectory
        """
        Same as :meth:`compute_flight_points` except that delta_t4 is used directly
        instead of specifying flight engine_setting.

        :param mach: Mach number
        :param altitude: (unit=m) altitude w.r.t. to sea level
        :param delta_t4: (unit=K) difference between operational and design values of
                         turbine inlet temperature in K
        :param thrust_is_regulated: tells if thrust_rate or thrust should be used (works element-wise)
        :param thrust_rate: thrust rate (unit=none)
        :param thrust: required thrust (unit=N)
        :return: SFC (in kg/s/N), thrust rate, thrust (in N)
        """
        mach = np.asarray(mach)
        altitude = np.asarray(altitude)
        delta_t4 = np.asarray(delta_t4)

        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)

        atmosphere = Atmosphere(altitude, altitude_in_feet=False)

        max_thrust = self.max_thrust(atmosphere, mach, delta_t4)

        # 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

        # 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

        # Now SFC can be computed
        sfc_0 = self.sfc_at_max_thrust(atmosphere, mach)
        sfc = sfc_0 * self.sfc_ratio(altitude, out_thrust_rate)

        return sfc, out_thrust_rate, out_thrust