Ejemplo n.º 1
0
    def compute_dimensions(self) -> (float, float, float, float, float, float):
        """
        Computes propulsion dimensions (engine/nacelle/propeller) from maximum power.
        Model from :...

        """

        # Compute engine dimensions
        self.engine.length = self.ref["length"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)
        self.engine.height = self.ref["height"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)
        self.engine.width = self.ref["width"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)

        if self.prop_layout == 3.0:
            nacelle_length = 1.15 * self.engine.length
            # Based on the length between nose and firewall for TB20 and SR22
        else:
            nacelle_length = 1.50 * self.engine.length

        # Compute nacelle dimensions
        self.nacelle = Nacelle(
            height=self.engine.height * 1.1,
            width=self.engine.width * 1.1,
            length=nacelle_length,
        )
        self.nacelle.wet_area = 2 * (self.nacelle.height +
                                     self.nacelle.width) * self.nacelle.length

        # Compute propeller dimensions (2-blades)
        w_propeller = 2500  # regulated propeller speed in RPM
        v_sound = Atmosphere(self.design_altitude,
                             altitude_in_feet=False).speed_of_sound
        d_max = (((v_sound * 0.85)**2 - self.design_speed**2) /
                 ((w_propeller * math.pi / 30) / 2)**2)**0.5
        d_opt = 1.04**2 * ((self.max_power / 735.5) * 1e8 /
                           (w_propeller**2 * self.design_speed * 3.6))**(1 / 4)
        d = min(d_max, d_opt)
        t_0 = 7.4 * ((self.max_power / 735.5) * d)**(2 / 3)
        area = 13307 * t_0 / (d**2 * w_propeller**2)
        chord = area / d
        self.propeller = Propeller(
            area=area,
            depth=chord * 1.1,
            diameter=d,
            thrust_SL=t_0,
        )
        propeller_depth = max(chord * 1.1, 0.2 * d)
        # For clarity purposes, it has been assimilated as the spinner length

        return self.nacelle["height"], self.nacelle["width"], self.nacelle[
            "length"], self.nacelle["wet_area"], self.propeller[
                "diameter"], self.propeller["depth"]
Ejemplo n.º 2
0
    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
        
        x0_wing = inputs["data:geometry:wing:MAC:leading_edge:x:local"]
        l0_wing = inputs["data:geometry:wing:MAC:length"]
        l1_wing = inputs["data:geometry:wing:root:virtual_chord"]
        width_max = inputs["data:geometry:fuselage:maximum_width"]
        fa_length = inputs["data:geometry:wing:MAC:at25percent:x"]
        fus_length = inputs["data:geometry:fuselage:length"]
        wing_area = inputs["data:geometry:wing:area"]
        aspect_ratio = inputs["data:geometry:wing:aspect_ratio"]
        lp_ht = inputs["data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"]
        cl_alpha_wing = inputs["data:aerodynamics:wing:cruise:CL_alpha"]
        cl_alpha_ht = inputs["data:aerodynamics:horizontal_tail:cruise:CL_alpha"]
        cl_delta_ht = inputs["data:aerodynamics:elevator:low_speed:CL_delta"]
        ch_alpha_3d = inputs["data:aerodynamics:horizontal_tail:cruise:hinge_moment:CH_alpha"]
        ch_delta_3d = inputs["data:aerodynamics:horizontal_tail:cruise:hinge_moment:CH_delta"]
        tail_efficiency = inputs["data:aerodynamics:horizontal_tail:efficiency"]
        v_cruise = inputs["data:TLAR:v_cruise"]
        alt_cruise = inputs["data:mission:sizing:main_route:cruise:altitude"]

        # TODO: make variable name in computation sequence more english
        x0_25 = fa_length - 0.25 * l0_wing - x0_wing + 0.25 * l1_wing
        ratio_x025 = x0_25 / fus_length
        # fitting result of Raymer book, figure 16.14
        k_h = 0.01222 - 7.40541e-4 * ratio_x025 * 100 + 2.1956e-5 * (ratio_x025 * 100) ** 2
        # equation from Raymer book, eqn 16.22
        # FIXME: introduce cm_alpha_wing to the equation (non-symmetrical profile)
        cm_alpha_fus = k_h * width_max ** 2 * fus_length / (l0_wing * wing_area) * 180.0 / np.pi
        x_ca_plane = (tail_efficiency * cl_alpha_ht * lp_ht - cm_alpha_fus * l0_wing) / \
                     (cl_alpha_wing + tail_efficiency * cl_alpha_ht)
        x_aero_center = x_ca_plane / l0_wing + 0.25

        outputs["data:aerodynamics:cruise:neutral_point:stick_fixed:x"] = x_aero_center

        sos = Atmosphere(alt_cruise).speed_of_sound
        mach = v_cruise / sos
        beta = math.sqrt(1. - mach ** 2.0)
        cl_delta_ht_cruise = cl_delta_ht / beta

        # The cl_alpha_ht in the formula for the free_elevator_factor is defined with respect to the tail angle of
        # attack, the one we compute is wth respect to the plane so it includes downwash, as a consequence we must
        # correct it influence for this specific calculation. We will use the formula for elliptical wing as it is well
        # known
        downwash_effect = (1. - 2. * cl_alpha_wing / (math.pi * aspect_ratio))
        cl_alpha_ht_ht = cl_alpha_ht / downwash_effect
        free_elevator_factor = 1. - (cl_delta_ht_cruise/cl_alpha_ht_ht)*(ch_alpha_3d/ch_delta_3d)

        outputs["data:aerodynamics:cruise:neutral_point:free_elevator_factor"] = free_elevator_factor

        x_ca_plane_free = (tail_efficiency * free_elevator_factor * cl_alpha_ht * lp_ht - cm_alpha_fus * l0_wing) / \
                          (cl_alpha_wing + tail_efficiency * free_elevator_factor * cl_alpha_ht)
        x_aero_center_free = x_ca_plane_free / l0_wing + 0.25

        outputs["data:aerodynamics:cruise:neutral_point:stick_free:x"] = x_aero_center_free
Ejemplo n.º 3
0
    def compute_flight_points(self, flight_points: Union[FlightPoint, pd.DataFrame]):

        altitude = float(Atmosphere(np.array(flight_points.altitude)).get_altitude(altitude_in_feet=True))
        mach = np.array(flight_points.mach)
        thrust = np.array(flight_points.thrust)
        sigma = Atmosphere(altitude).density / Atmosphere(0.0).density
        max_power = self.max_power * (sigma - (1 - sigma) / 7.55)
        max_thrust = min(
            self.max_thrust * sigma ** (1. / 3.),
            max_power * 0.8 / np.maximum(mach * Atmosphere(altitude).speed_of_sound, 1e-20)
        )
        if flight_points.thrust_rate is None:
            flight_points.thrust = min(max_thrust, float(thrust))
            flight_points.thrust_rate = float(thrust) / max_thrust
        else:
            flight_points.thrust = max_thrust * np.array(flight_points.thrust_rate)
        sfc_pmax = 8.5080e-08  # fixed whatever the thrust ratio, sfc for ONE 130kW engine !
        sfc = sfc_pmax * flight_points.thrust_rate * mach * Atmosphere(altitude).speed_of_sound

        flight_points['sfc'] = sfc
Ejemplo n.º 4
0
    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):

        fus_length = inputs["data:geometry:fuselage:length"]
        lav = inputs["data:geometry:fuselage:front_length"]
        lar = inputs["data:geometry:fuselage:rear_length"]
        maximum_width = inputs["data:geometry:fuselage:maximum_width"]
        maximum_height = inputs["data:geometry:fuselage:maximum_height"]
        wet_area_fus = inputs["data:geometry:fuselage:wet_area"]
        sizing_factor_ultimate = inputs["data:mission:sizing:cs23:sizing_factor_ultimate"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        lp_ht = inputs["data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"]
        cruise_alt = inputs["data:mission:sizing:main_route:cruise:altitude"]
        v_cruise = inputs["data:TLAR:v_cruise"] * 0.5144

        atm_cruise = Atmosphere(cruise_alt)
        rho_cruise = atm_cruise.density
        pressure_cruise = atm_cruise.pressure

        atm_sl = Atmosphere(0.0)
        pressure_sl = atm_sl.pressure

        dynamic_pressure = 1. / 2. * rho_cruise * v_cruise ** 2. * 0.020885434273039

        if cruise_alt > 10000.:
            fus_dia = (maximum_height + maximum_width) / 2.
            v_press = (fus_length - lar - lav) * math.pi * (fus_dia / 2.) ** 2.0
            delta_p = (pressure_sl - pressure_cruise) * 0.000145038
        else:
            v_press = 0.0
            delta_p = 0.0

        a2 = 0.052 * (
                wet_area_fus ** 1.086 *
                (sizing_factor_ultimate * mtow) ** 0.177 *
                lp_ht ** (-0.051) *
                ((fus_length - lar - lav) / maximum_height) ** (-0.072) *
                dynamic_pressure ** 0.241 +
                11.9 * (v_press * delta_p) ** 0.271
        )

        outputs["data:weight:airframe:fuselage:mass_raymer"] = a2
Ejemplo n.º 5
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        wing_area = inputs["data:geometry:wing:area"]
        ht_area = inputs["data:geometry:horizontal_tail:area"]
        mlw = inputs["data:weight:aircraft:MLW"]
        cl0_htp = inputs["data:aerodynamics:horizontal_tail:low_speed:CL0"]
        cl_alpha_htp_isolated = inputs[
            "data:aerodynamics:horizontal_tail:low_speed:CL_alpha_isolated"]
        cl_alpha_htp = inputs[
            "data:aerodynamics:horizontal_tail:low_speed:CL_alpha"]
        cl0_clean_wing = inputs["data:aerodynamics:wing:low_speed:CL0_clean"]
        cl_alpha_wing = inputs["data:aerodynamics:wing:low_speed:CL_alpha"]
        cl_flaps_landing = inputs["data:aerodynamics:flaps:landing:CL"]
        cl_max_landing = inputs["data:aerodynamics:aircraft:landing:CL_max"]
        cl_delta_elev = inputs["data:aerodynamics:elevator:low_speed:CL_delta"]

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

        # Calculate elevator max. additional lift
        if self.options["landing"]:
            elev_angle = inputs["data:mission:sizing:landing:elevator_angle"]
        else:
            elev_angle = inputs["data:mission:sizing:takeoff:elevator_angle"]
        cl_elev = cl_delta_elev * elev_angle
        # Define alpha angle depending on phase
        if self.options["landing"]:
            # Calculation of take-off minimum speed
            weight = mlw * g
            vs0 = math.sqrt(weight / (0.5 * rho * wing_area * cl_max_landing))
            # Rotation speed correction
            v_r = vs0 * 1.3
            # Evaluate aircraft overall angle (aoa)
            cl0_landing = cl0_clean_wing + cl_flaps_landing
            cl_landing = weight / (0.5 * rho * v_r**2 * wing_area)
            alpha = (cl_landing - cl0_landing) / cl_alpha_wing * 180 / math.pi
        else:
            # Define aircraft overall angle (aoa)
            alpha = 0.0
        # Interpolate cl/cm and define with ht reference surface
        cl_htp = ((cl0_htp +
                   (alpha * math.pi / 180) * cl_alpha_htp + cl_elev) *
                  wing_area / ht_area)
        # Define Cl_alpha with htp reference surface
        cl_alpha_htp_isolated = cl_alpha_htp_isolated * wing_area / ht_area

        outputs["cl_htp"] = cl_htp
        outputs["cl_alpha_htp_isolated"] = cl_alpha_htp_isolated
Ejemplo n.º 6
0
    def sfc_ratio(
        self,
        altitude: Union[float, Sequence[float]],
        thrust_rate: Union[float, Sequence[float]],
        mach: Union[float, Sequence[float]] = 0.8,
    ) -> Tuple[np.ndarray, np.ndarray]:
        """
        Computation of ratio :math:`\\frac{SFC(P)}{SFC(Pmax)}`, given altitude
        and thrust_rate :math:`\\frac{F}{Fmax}`.
        Warning: this model is very limited
        :param altitude:
        :param thrust_rate:
        :param mach: Mach number(s)
        :return: SFC ratio and Power (in W)
        """

        altitude = np.asarray(altitude)
        thrust_rate = np.asarray(thrust_rate)
        mach = np.asarray(mach)
        mach = mach + (mach == 0) * 1e-12
        max_thrust = self.max_thrust(
            Atmosphere(altitude, altitude_in_feet=False), mach)
        sigma = Atmosphere(
            altitude, altitude_in_feet=False).density / Atmosphere(0.0).density
        max_power = np.minimum(
            self.max_power * (sigma - (1 - sigma) / 7.55),
            max_thrust * mach * Atmosphere(altitude).speed_of_sound /
            PROPELLER_EFFICIENCY)
        prop_power = (max_thrust * thrust_rate * mach *
                      Atmosphere(altitude).speed_of_sound)
        mech_power = prop_power / PROPELLER_EFFICIENCY
        # FIXME: low speed efficiency should drop down leading to high mechanical power!

        power_rate = mech_power / max_power

        sfc_ratio = (-0.9976 * power_rate**2 + 1.9964 * power_rate)

        return sfc_ratio, (power_rate * max_power)
Ejemplo n.º 7
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
def test_sfc_at_max_thrust():
    """
    Checks model against values from :...

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

    # Check with arrays
    # BasicICEngine(max_power(W), design_altitude(m), design_speed(m/s), fuel_type, strokes_nb)
    _50kw_engine = BasicICEngine(50000.0, 2400.0, 81.0, 1.0, 4.0)
    atm = Atmosphere([0, 10668, 13000], altitude_in_feet=False)
    sfc = _50kw_engine.sfc_at_max_power(atm)
    # Note: value for alt==10668 is different from PhD report
    #       alt=13000 is here just for testing in stratosphere
    np.testing.assert_allclose(
        sfc, [7.09319444e-08, 6.52497276e-08, 6.44112478e-08], rtol=1e-4)

    # Check with scalars
    # BasicICEngine(max_power(W), design_altitude(m), design_speed(m/s), fuel_type, strokes_nb)
    _250kw_engine = BasicICEngine(250000.0, 2400.0, 81.0, 1.0, 4.0)
    atm = Atmosphere(0, altitude_in_feet=False)
    sfc = _250kw_engine.sfc_at_max_power(atm)
    np.testing.assert_allclose(sfc, 8.540416666666667e-08, rtol=1e-4)
Ejemplo n.º 9
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)
        mach = speed / atm.speed_of_sound
        reynolds = atm.get_unitary_reynolds(mach) * l0_wing

        outputs["data:aerodynamics:aircraft:landing:mach"] = mach
        outputs["data:aerodynamics:aircraft:landing:reynolds"] = reynolds
Ejemplo n.º 10
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        sizing_factor_ultimate = inputs[
            "data:mission:sizing:cs23:sizing_factor_ultimate"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        v_cruise_ktas = inputs["data:TLAR:v_cruise"]
        cruise_alt = inputs["data:mission:sizing:main_route:cruise:altitude"]

        area_ht = inputs["data:geometry:horizontal_tail:area"]
        t_c_ht = inputs["data:geometry:horizontal_tail:thickness_ratio"]
        sweep_25_ht = inputs["data:geometry:horizontal_tail:sweep_25"]
        ar_ht = inputs["data:geometry:horizontal_tail:aspect_ratio"]
        taper_ht = inputs["data:geometry:horizontal_tail:taper_ratio"]

        rho_cruise = Atmosphere(cruise_alt).density
        dynamic_pressure = 1. / 2. * rho_cruise * (v_cruise_ktas *
                                                   0.5144)**2. * 0.0208854
        # In lb/ft2

        a31 = 0.016 * (
            (sizing_factor_ultimate * mtow)**0.414 * dynamic_pressure**0.168 *
            area_ht**0.896 *
            (100 * t_c_ht / math.cos(sweep_25_ht * math.pi / 180.))**-0.12 *
            (ar_ht / (math.cos(sweep_25_ht * math.pi / 180.))**2.0)**0.043 *
            taper_ht**-0.02)
        # Mass formula in lb

        outputs["data:weight:airframe:horizontal_tail:mass"] = a31

        has_t_tail = inputs["data:geometry:has_T_tail"]
        area_vt = inputs["data:geometry:vertical_tail:area"]
        t_c_vt = inputs["data:geometry:vertical_tail:thickness_ratio"]
        sweep_25_vt = inputs["data:geometry:vertical_tail:sweep_25"]
        ar_vt = inputs["data:geometry:vertical_tail:aspect_ratio"]
        taper_vt = inputs["data:geometry:vertical_tail:taper_ratio"]

        a32 = 0.073 * (1. + 0.2 * has_t_tail) * (
            (sizing_factor_ultimate * mtow)**0.376 * dynamic_pressure**0.122 *
            area_vt**0.873 *
            (100 * t_c_vt / math.cos(sweep_25_vt * math.pi / 180.))**-0.49 *
            (ar_vt / (math.cos(sweep_25_vt * math.pi / 180.))**2.0)**0.357 *
            taper_vt**0.039)
        # Mass formula in lb

        outputs["data:weight:airframe:vertical_tail:mass"] = a32
Ejemplo n.º 11
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        mtow = inputs["data:weight:aircraft:MTOW"]
        npax = inputs["data:TLAR:NPAX"]
        m_iae = inputs["data:weight:systems:navigation:mass"]
        limit_speed = inputs["data:TLAR:v_limit"]

        atm = Atmosphere(0.0)
        limit_speed = limit_speed / atm.speed_of_sound  # converted to mach
        c22 = 0.261 * mtow**.52 * npax**0.68 * m_iae**0.17 * limit_speed**0.08  # mass formula in lb

        outputs["data:weight:systems:life_support:air_conditioning:mass"] = c22
Ejemplo n.º 12
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        # Get inputs
        b_f = inputs['data:geometry:fuselage:maximum_width']
        span = inputs['data:geometry:wing:span']
        aspect_ratio = inputs['data:geometry:wing:aspect_ratio']
        wing_area = inputs['data:geometry:wing:area']
        if self.options["low_speed_aero"]:
            mach = inputs["data:aerodynamics:low_speed:mach"]
            v_inf = max(Atmosphere(0.0).speed_of_sound * mach,
                        0.01)  # avoid V=0 m/s crashes
            cl_clean = inputs["data:aerodynamics:wing:low_speed:CL"]
            cdp_clean = inputs["data:aerodynamics:wing:low_speed:CDp"]
        else:
            mach = inputs["data:aerodynamics:cruise:mach"]
            v_inf = inputs["data:TLAR:v_cruise"]
            cl_clean = inputs["data:aerodynamics:wing:cruise:CL"]
            cdp_clean = inputs["data:aerodynamics:wing:cruise:CDp"]

        super()._run(inputs)
        cl, _, oswald, _ = super().compute_wing(inputs,
                                                _INPUT_AOAList,
                                                v_inf,
                                                flaps_angle=0.0,
                                                use_airfoil=True)
        k_fus = 1 - 2 * (b_f / span)**2
        oswald = oswald[0] * k_fus  # Fuselage correction
        if mach > 0.4:
            oswald = oswald * (-0.001521 * (
                (mach - 0.05) / 0.3 - 1)**10.82 + 1)  # Mach correction
        cdp_foil = self._interpolate_cdp(cl_clean, cdp_clean, cl[0])
        cdi = (1.05 * cl[0])**2 / (
            math.pi * aspect_ratio *
            oswald) + cdp_foil  # Aircraft cor.: Wing = 105% total lift.
        coef_e = cl[0]**2 / (math.pi * aspect_ratio * cdi)
        coef_k = 1. / (math.pi * span**2 / wing_area * coef_e)

        if self.options["low_speed_aero"]:
            outputs[
                'data:aerodynamics:aircraft:low_speed:induced_drag_coefficient'] = coef_k
        else:
            outputs[
                'data:aerodynamics:aircraft:cruise:induced_drag_coefficient'] = coef_k
Ejemplo n.º 13
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"]
        )
        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)
Ejemplo n.º 14
0
    def compute_dimensions(self) -> (float, float, float, float):
        """
        Computes propulsion dimensions (engine/nacelle/propeller) from maximum power.
        Model from :...

        """

        # Compute engine dimensions
        self.engine.length = self.ref["length"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)
        self.engine.height = self.ref["height"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)
        self.engine.width = self.ref["width"] * (
            self.max_power / self.ref["max_power"])**(1 / 3)

        # Compute nacelle dimensions
        self.nacelle = Nacelle(
            height=self.engine.height * 1.1,
            width=self.engine.width * 1.1,
            length=1.5 * self.engine.length,
        )
        self.nacelle.wet_area = 2 * (self.nacelle.height +
                                     self.nacelle.width) * self.nacelle.length

        # Compute propeller dimensions (2-blades)
        w_propeller = 2500  # regulated propeller speed in RPM
        v_sound = Atmosphere(self.design_altitude,
                             altitude_in_feet=False).speed_of_sound
        d_max = (((v_sound * 0.85)**2 - self.design_speed**2) /
                 ((w_propeller * math.pi / 30) / 2)**2)**0.5
        d_opt = 1.04**2 * ((self.max_power / 735.5) * 1e8 /
                           (w_propeller**2 * self.design_speed * 3.6))**(1 / 4)
        d = min(d_max, d_opt)
        t_0 = 7.4 * ((self.max_power / 735.5) * d)**(2 / 3)
        area = 13307 * t_0 / (d**2 * w_propeller**2)
        chord = area / d
        self.propeller = Propeller(
            area=area,
            depth=chord * 1.1,
            diameter=d,
            thrust_SL=t_0,
        )

        return self.nacelle["height"], self.nacelle["width"], self.nacelle[
            "length"], self.nacelle["wet_area"]
Ejemplo n.º 15
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"]
        )
        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
Ejemplo n.º 16
0
    def get_cd0(alt, mach, cl, low_speed_aero):
        reynolds = Atmosphere(alt).get_unitary_reynolds(mach)

        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]
    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
Ejemplo n.º 18
0
    def compute(self, inputs, outputs):
        v_tas = inputs["data:TLAR:v_cruise"]
        cruise_altitude = inputs["data:mission:sizing:main_route:cruise:altitude"]
        design_mass = inputs["data:weight:aircraft:MTOW"]

        design_vc = Atmosphere(cruise_altitude, altitude_in_feet=False).get_equivalent_airspeed(v_tas)
        velocity_array, load_factor_array, _ = self.flight_domain(inputs, outputs, design_mass,
                                                                  cruise_altitude, design_vc,
                                                                  design_n_ps=0.0, design_n_ng=0.0)

        if DOMAIN_PTS_NB < len(velocity_array):
            velocity_array = velocity_array[0:DOMAIN_PTS_NB - 1]
            load_factor_array = load_factor_array[0:DOMAIN_PTS_NB - 1]
            warnings.warn("Defined maximum stored domain points in fast compute_vn.py exceeded!")
        else:
            additional_zeros = list(np.zeros(DOMAIN_PTS_NB - len(velocity_array)))
            velocity_array.extend(additional_zeros)
            load_factor_array.extend(additional_zeros)

        outputs["data:flight_domain:velocity"] = np.array(velocity_array)
        outputs["data:flight_domain:load_factor"] = np.array(load_factor_array)
Ejemplo n.º 19
0
    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 compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):

        mtow = inputs["data:weight:aircraft:MTOW"]
        n_pax = inputs["data:geometry:cabin:seats:passenger:NPAX_max"]
        m_iae = inputs["data:weight:systems:navigation:mass"]
        limit_speed = inputs["data:TLAR:v_limit"]
        cruise_alt = inputs["data:mission:sizing:main_route:cruise:altitude"]

        n_occ = n_pax + 2.
        # Because there are two pilots that needs to be taken into account

        atm = Atmosphere(cruise_alt)
        limit_mach = limit_speed / atm.speed_of_sound  # converted to mach

        c21 = 0.0

        c22 = 0.265 * mtow**.52 * n_occ**0.68 * m_iae**0.17 * limit_mach**0.08  # mass formula in lb

        c23 = 0.0
        c24 = 0.0
        c25 = 0.0

        c26 = 7. * n_occ**0.702

        c27 = 0.0

        outputs["data:weight:systems:life_support:insulation:mass"] = c21
        outputs["data:weight:systems:life_support:air_conditioning:mass"] = c22
        outputs["data:weight:systems:life_support:de_icing:mass"] = c23
        outputs[
            "data:weight:systems:life_support:internal_lighting:mass"] = c24
        outputs[
            "data:weight:systems:life_support:seat_installation:mass"] = c25
        outputs["data:weight:systems:life_support:fixed_oxygen:mass"] = c26
        outputs["data:weight:systems:life_support:security_kits:mass"] = c27
Ejemplo n.º 21
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
Ejemplo n.º 22
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]]:
        """
        Same as :meth:`compute_flight_points`.

        :param mach: Mach number
        :param altitude: (unit=m) altitude w.r.t. to sea level
        :param engine_setting: define engine settings
        :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)
        """
        """
        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

        # Now SFC can be computed
        sfc_pmax = self.sfc_at_max_power(atmosphere)
        sfc_ratio, mech_power = self.sfc_ratio(altitude, out_thrust_rate, mach)
        sfc = (sfc_pmax * sfc_ratio * mech_power) / np.maximum(
            out_thrust, 1e-6)  # avoid 0 division

        return sfc, out_thrust_rate, out_thrust
Ejemplo n.º 23
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"]
        )
        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
Ejemplo n.º 24
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"]
        )
        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
Ejemplo n.º 25
0
    def flight_domain(self,
                      inputs,
                      outputs,
                      mass,
                      altitude,
                      design_vc,
                      design_n_ps=0.0,
                      design_n_ng=0.0):

        # Get necessary inputs
        wing_area = inputs["data:geometry:wing:area"]
        mtow = inputs["data:weight:aircraft:MTOW"]
        category = inputs[
            "data:TLAR:category"]  # Aerobatic = 1.0, Utility = 2.0, Normal = 3.0, Commuter = 4.0
        level = inputs["data:TLAR:level"]
        Vh = inputs["data:TLAR:v_max_sl"]
        root_chord = inputs["data:geometry:wing:root:chord"]
        tip_chord = inputs["data:geometry:wing:tip:chord"]
        cl_max_flaps = inputs["data:aerodynamics:aircraft:landing:CL_max"]
        cl_max = inputs["data:aerodynamics:wing:low_speed:CL_max_clean"]
        cl_min = inputs["data:aerodynamics:wing:low_speed:CL_min_clean"]
        mean_chord = (root_chord + tip_chord) / 2.0
        atm_0 = Atmosphere(0.0)
        atm = Atmosphere(altitude, altitude_in_feet=False)

        # Initialise the lists in which we will store the data
        velocity_array = []
        load_factor_array = []

        # For some of the correlation presented in the regulation, we need to convert the data
        # of the airplane to imperial units
        weight_lbf = (mass * g) / self.lbf_to_N
        mtow_lbf = (mtow * g) / self.lbf_to_N
        wing_area_sft = wing_area / (self.ft_to_m**2.0)
        mtow_loading_psf = mtow_lbf / wing_area_sft  # [lbf/ft**2]

        # We can now start computing the values of the different air-speeds given in the regulation
        # as well as the load factors. We will here make the choice to stick with the limits given
        # in the certifications even though they sometimes allow to choose design speeds and loads
        # over the values written in the documents.

        # Lets start by computing the 1g/-1g stall speeds using the usual formulations
        Vs_1g_ps = math.sqrt(
            (2. * mass * g) / (atm_0.density * wing_area * cl_max))  # [m/s]
        Vs_1g_ng = math.sqrt(
            (2. * mass * g) /
            (atm_0.density * wing_area * abs(cl_min)))  # [m/s]
        velocity_array.append(float(Vs_1g_ps))
        load_factor_array.append(1.0)
        velocity_array.append(float(Vs_1g_ng))
        load_factor_array.append(-1.0)

        # As we will consider all the calculated speed to be Vs_1g_ps < V < 1.4*Vh, we will compute cl_alpha for N
        # points equally spaced on log scale (to take into account the high non-linearity effect).
        # If the option is not selected, we will only consider low_speed and cruise cl_alpha points and consider a
        # square regression between both.

        if self.options["compute_cl_alpha"]:
            mach_interp = inputs[
                "data:aerodynamics:aircraft:mach_interpolation:mach_vector"]
            v_interp = []
            for mach in mach_interp:
                v_interp.append(float(mach * atm.speed_of_sound))
            cl_alpha_interp = inputs[
                "data:aerodynamics:aircraft:mach_interpolation:CL_alpha_vector"]
            cl_alpha_fct = interpolate.interp1d(v_interp,
                                                cl_alpha_interp,
                                                fill_value="extrapolate",
                                                kind="quadratic")
        else:
            v_interp = np.array([
                float(inputs["data:TLAR:v_approach"]),
                float(inputs["data:TLAR:v_cruise"])
            ])
            cl_alpha_1 = float(
                inputs["data:aerodynamics:wing:low_speed:CL_alpha"] +
                inputs["data:aerodynamics:horizontal_tail:low_speed:CL_alpha"])
            cl_alpha_2 = float(
                inputs["data:aerodynamics:wing:cruise:CL_alpha"] +
                inputs["data:aerodynamics:horizontal_tail:cruise:CL_alpha"])
            cl_alpha_interp = np.array([cl_alpha_1, cl_alpha_2])
            cl_alpha_fct = interpolate.interp1d(v_interp,
                                                cl_alpha_interp,
                                                fill_value="extrapolate",
                                                kind="linear")

        # We will now establish the minimum limit maneuvering load factors outside of gust load
        # factors. Th designer can take higher load factor if he so wish. As will later be done for the
        # the cruising speed, we will simply ensure that the designer choice agrees with certifications
        # The limit load factor can be found in section CS 23.337 (a) and (b)
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        if category == 1.0:
            n_lim_1 = 6.0  # For aerobatic GA aircraft
        else:
            n_lim_1 = 3.80  # For non aerobatic GA aircraft
        n_lim_2 = 2.1 + 24000. / (mtow_lbf + 10000.)  # CS 23.337 (a)
        n_lim_ps_min = min(n_lim_1, n_lim_2)  # CS 23.337 (a)
        n_lim_ps = max(n_lim_ps_min, design_n_ps)

        if category == 1.0:
            n_lim_ng_max = -0.5 * n_lim_ps  # CS 23.337 (b)
        else:
            n_lim_ng_max = -0.4 * n_lim_ps  # CS 23.337 (b)
        n_lim_ng = min(n_lim_ng_max, design_n_ng)

        load_factor_array.append(float(n_lim_ps))
        load_factor_array.append(float(n_lim_ng))

        # Starting from there, we need to compute the gust lines as it can have an impact on the choice
        # of the maneuvering speed. We will also compute the maximum intensity gust line for later
        # use but keep in mind that this is specific for commuter or level 4 aircraft
        # The values used to compute the gust lines can be found in CS 23.341
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf

        # We first compute the gust velocities as presented in CS 23.333 (c), for now, we don't take into account
        # the case of the commuter nor do we implement the reduction of gust intensity with the location
        # of the gust center

        if altitude < 20000.0:
            U_de_Vc = 50.  # [ft/s]
            U_de_Vd = 25.  # [ft/s]
            U_de_Vmg = 66.  # [ft/s]
        elif 20000.0 < altitude < 50000.0:
            U_de_Vc = 66.7 - 0.000833 * altitude  # [ft/s]
            U_de_Vd = 33.4 - 0.000417 * altitude  # [ft/s]
            U_de_Vmg = 84.7 - 0.000933 * altitude  # [ft/s]
        else:
            U_de_Vc = 25.  # [ft/s]
            U_de_Vd = 12.5  # [ft/s]
            U_de_Vmg = 38.  # [ft/s]

        # Let us define aeroplane mass ratio formula and alleviation factor formula
        mu_g = lambda x: (2.0 * mass * g / wing_area) / (
            atm.density * mean_chord * x * g)  # [x = cl_alpha]
        K_g = lambda x: (0.88 * x) / (5.3 + x)  # [x = mu_g]
        # Now, define the gust function
        load_factor_gust_p = lambda u_de_v, x: float(
            1 + K_g(mu_g(cl_alpha_fct(x))) * atm_0.density * u_de_v * self.
            ft_to_m * x * cl_alpha_fct(x) / (2.0 * weight_lbf / wing_area_sft *
                                             self.lbf_to_N / self.ft_to_m**2))
        load_factor_gust_n = lambda u_de_v, x: float(
            1 - K_g(mu_g(cl_alpha_fct(x))) * atm_0.density * u_de_v * self.
            ft_to_m * x * cl_alpha_fct(x) / (2.0 * weight_lbf / wing_area_sft *
                                             self.lbf_to_N / self.ft_to_m**2))
        load_factor_stall_p = lambda x: (x / Vs_1g_ps)**2.0
        load_factor_stall_n = lambda x: -(x / Vs_1g_ng)**2.0

        # We can now go back to the computation of the maneuvering speeds, we will first compute it
        # "traditionally" and should we find out that the line limited by the Cl max is under the gust
        # line, we will adjust it (see Step 10. of section 16.4.1 of (1)). As for the traditional
        # computation they can be found in CS 23.335 (c)
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        Vma_ps = Vs_1g_ps * math.sqrt(n_lim_ps)  # [m/s]
        Vma_ng = Vs_1g_ng * math.sqrt(abs(n_lim_ng))  # [m/s]
        velocity_array.append(float(Vma_ps))
        velocity_array.append(float(Vma_ng))

        # We now need to check if we are in the aforementioned case (usually happens for low design wing
        # loading aircraft and/or mission wing loading)

        n_ma_ps = load_factor_gust_p(U_de_Vc, Vma_ps)

        if n_ma_ps > n_lim_ps:
            # In case the gust line load factor is above the maneuvering load factor, we need to solve the difference
            # between both curve to be 0.0 to find intersect
            delta = lambda x: load_factor_gust_p(U_de_Vc, x
                                                 ) - load_factor_stall_p(x)
            Vma_ps = max(optimize.fsolve(delta, np.array(1000.0)))
            n_ma_ps = load_factor_gust_p(U_de_Vc, Vma_ps)  # [-]
            velocity_array.append(float(Vma_ps))
            load_factor_array.append(float(n_ma_ps))
        else:
            velocity_array.append(0.0)
            load_factor_array.append(0.0)

        # We now need to do the same thing for the negative maneuvering speed

        n_ma_ng = load_factor_gust_n(U_de_Vc, Vma_ng)  # [-]

        if n_ma_ng < n_lim_ng:
            # In case the gust line load factor is above the maneuvering load factor, we need to solve the difference
            # between both curve to be 0.0 to find intersect
            delta = lambda x: load_factor_gust_n(U_de_Vc, x
                                                 ) - load_factor_stall_n(x)
            Vma_ng = max(optimize.fsolve(delta, np.array(1000.0))[0])
            n_ma_ng = load_factor_gust_n(U_de_Vc, Vma_ng)  # [-]
            velocity_array.append(float(Vma_ng))
            load_factor_array.append(float(n_ma_ng))
        else:
            velocity_array.append(0.0)
            load_factor_array.append(0.0)

        # For the cruise velocity, things will be different since it is an entry choice. As such we will
        # simply check that it complies with the values given in the certification papers and re-adjust
        # it if necessary. For airplane certified for aerobatics, the coefficient in front of the wing
        # loading in psf is slightly different than for normal aircraft but for either case it becomes
        # 28.6 at wing loading superior to 100 psf
        # Values and methodology used can be found in CS 23.335 (a)
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        if category == 1.0:
            if mtow_loading_psf < 20.0:
                k_c = 36.0
            elif mtow_loading_psf < 100.:
                # Linear variation from 33.0 to 28.6
                k_c = 36.0 + (mtow_loading_psf -
                              20.0) * (28.6 - 36.0) / (100.0 - 20.0)
            else:
                k_c = 28.6
        else:
            if mtow_loading_psf < 20.0:
                k_c = 33.0
            elif mtow_loading_psf < 100.:
                # Linear variation from 33.0 to 28.6
                k_c = 33.0 + (mtow_loading_psf -
                              20.0) * (28.6 - 33.0) / (100.0 - 20.0)
            else:
                k_c = 28.6

        Vc_min_1 = k_c * math.sqrt(
            weight_lbf / wing_area_sft) * self.kts_to_ms  # [m/s]

        # This second constraint rather refers to the paragraph on maneuvering speeds, which needs to be chosen
        # so that they are smaller than cruising speeds
        Vc_min_2 = Vma_ps  # [m/s]
        Vc_min = max(Vc_min_1, Vc_min_2)  # [m/s]

        # The certifications specifies that Vc need not be more than 0.9 Vh so we will simply take the
        # minimum value between the Vc_min and this value

        Vc_min_fin = min(Vc_min, 0.9 * Vh)  # [m/s]

        # The constraint regarding the maximum velocity for cruise does not appear in the certifications but
        # from a physics point of view we can easily infer that the cruise speed will never be greater than
        # the maximum level velocity at sea level hence

        Vc = max(min(design_vc, Vh), Vc_min_fin)  # [m/s]
        velocity_array.append(float(Vc))
        load_factor_array.append(float(n_lim_ng))

        # Lets now look at the load factors associated with the Vc, since it is here that the greatest
        # load factors can appear

        n_Vc_ps = max(load_factor_gust_p(U_de_Vc, Vc), n_lim_ps)  # [-]
        n_Vc_ng = min(load_factor_gust_n(U_de_Vc, Vc), n_lim_ng)  # [-]

        velocity_array.append(float(Vc))
        load_factor_array.append(float(n_Vc_ps))
        velocity_array.append(float(Vc))
        load_factor_array.append(float(n_Vc_ng))

        # We now compute the diving speed, methods are described in CS 23.335 (b). We will take the minimum
        # diving speed allowable as our design diving speed. We need to keep in mind that this speed could
        # be greater if the designer was willing to show that the structure holds for the wanted Vd. For
        # airplane that needs to be certified for aerobatics use, the factor between Vd_min and Vc_min is
        # slightly different, but they both become 1.35 for wing loading higher than 100 psf
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        Vd_min_1 = 1.25 * Vc  # [m/s]

        if category == 1.0:
            if mtow_loading_psf < 20.0:
                k_d = 1.55
            elif mtow_loading_psf < 100.:
                # Linear variation from 1.55 to 1.35
                k_d = 1.55 + (mtow_loading_psf -
                              20.0) * (1.35 - 1.55) / (100.0 - 20.0)
            else:
                k_d = 1.35
        elif category == 2.0:
            if mtow_loading_psf < 20.0:
                k_d = 1.50
            elif mtow_loading_psf < 100.:
                # Linear variation from 1.5 to 1.35
                k_d = 1.50 + (mtow_loading_psf -
                              20.0) * (1.35 - 1.50) / (100.0 - 20.0)
            else:
                k_d = 1.35
        else:
            if mtow_loading_psf < 20.0:
                k_d = 1.4
            elif mtow_loading_psf < 100.:
                # Linear variation from 1.4 to 1.35
                k_d = 1.4 + (mtow_loading_psf - 20.0) * (1.35 - 1.4) / (100.0 -
                                                                        20.0)
            else:
                k_d = 1.35

        Vd_min_2 = k_d * Vc_min_fin  # [m/s]
        Vd = max(Vd_min_1, Vd_min_2)  # [m/s]

        velocity_array.append(float(Vd))
        load_factor_array.append(float(n_lim_ps))
        velocity_array.append(float(Vd))
        load_factor_array.append(0.0)

        # Similarly to what was done for the design cruising speed we will explore the load factors
        # associated with the diving speed since gusts are likely to broaden the flight domain around
        # these points

        n_Vd_ps = load_factor_gust_p(U_de_Vd, Vd)  # [-]

        # For the negative load factor at the diving speed, it seems that for non_aerobatic airplanes, it is
        # always sized according to the gust lines, regardless of the negative design load factor. For aerobatic
        # airplanes however, it seems as if it is sized for a greater value (more negative) but it does not look
        # to be equal to the negative diving factor as can be seen in figure 16-13 of (1). No information was
        # found for the location of this precises point, so the choice was made to take it as the negative
        # design load factor or the load factor given by the gust, whichever is the greatest (most negative).
        # This way, for non aerobatic airplane, we ensure to be conservative.

        n_Vd_ng = load_factor_gust_n(U_de_Vd, Vd)  # [-]

        velocity_array.append(float(Vd))
        load_factor_array.append(float(n_Vd_ps))
        velocity_array.append(float(Vd))
        load_factor_array.append(float(n_Vd_ng))

        # We have now calculated all the velocities need to plot the flight domain. For the sake of
        # thoroughness we will also compute the maximal structural cruising speed and cruise never-exceed
        # speed. The computation for these two can be found in CS 23.1505
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        # Let us start, as presented in the certifications papers with the never-exceed speed
        # Since we made the choice to take the Vd as the minimum value allowed by certifications, the V_ne
        # will have a fixed value and not a range as one would have expect. Indeed if Vd = Vd_min and since
        # V_ne has to be greater or equal to 0.9 x Vd_min and smaller or equal to 0.9 x Vd, V_ne will be equal
        # to 0.9 Vd. For future implementations, it should be noted that this section will need to be rewritten
        # should the Vd become a design parameter like what was made on Vc. Additionally the effect of
        # buffeting which serves as an additional upper limit is not included but should be taken into
        # account in detailed analysis phases

        V_ne = 0.9 * Vd  # [m/s]

        velocity_array.append(float(V_ne))
        load_factor_array.append(0.0)

        V_no_min = Vc_min  # [m/s]
        V_no_max = min(Vc, 0.89 * V_ne)  # [m/s]

        # Again we need to make a choice for this speed : what value would be retained. We will take the
        # highest speed acceptable for certification, i.e

        V_no = max(V_no_min, V_no_max)  # [m/s]

        velocity_array.append(float(V_no))
        load_factor_array.append(0.0)

        # One additional velocity needs to be computed if we are talking about commuter aircraft. It is
        # the maximum gust intensity velocity. Due to the way we are returning the values, even if we are not
        # investigating a commuter aircraft we need to return a value for Vmg so we will put it to 0.0. If we
        # are investigating a commuter aircraft, we will compute it according ot the guidelines from CS 23.335 (d)
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm
        # We decided to put this computation here as we may need the gust load factor in cruise conditions for
        # one of the possible candidates for the Vmg. While writing this program, the writer realized they were
        # no paragraph that impeach the Vc from being at a value such that one one of the conditions for the
        # minimum speed was above the Vc creating a problem with point (2). This case may however never appear
        # in practice as it would suppose that the Vc chosen is above the stall line which is more than certainly
        # avoided by the correlation between Vc_min and W/S in CS 23.335 (a)

        if (level == 4.0) or (category == 4.0):

            # We first need to compute the intersection of the stall line with the gust line given by the
            # gust of maximum intensity. Similar calculation were already done in case the maneuvering speed
            # is dictated by the Vc gust line so the computation will be very similar
            delta = lambda x: load_factor_gust_p(U_de_Vmg, x
                                                 ) - load_factor_stall_p(x)
            Vmg_min_1 = max(optimize.fsolve(delta, np.array(1000.0))[0])

            # The second candidate for the Vmg is given by the stall speed and the load factor at the cruise
            # speed
            Vmg_min_2 = Vs_1g_ps * math.sqrt(load_factor_gust_p(U_de_Vc,
                                                                Vc))  # [m/s]
            Vmg = min(Vmg_min_1, Vmg_min_2)  # [m/s]

            # As for the computation of the associated load factor, no source were found for any formula or
            # hint as to its computation. It can however be guessed that depending on the minimum value found
            # above, it will either be on the stall line or at the maximum design load factor

            if Vmg == Vmg_min_1:  # On the gust line
                n_Vmg = load_factor_gust_n(U_de_Vmg, Vmg_min_1)  # [-]
            else:
                n_Vmg = n_Vc_ps  # [-]

        else:
            Vmg = 0.0  # [m/s]
            n_Vmg = 0.0

        velocity_array.append(float(Vmg))
        load_factor_array.append(float(n_Vmg))

        # Let us now look at the flight domain in the flap extended configuration. For the computation of these
        # speeds and load factors, we will use the formula provided in CS 23.1511
        # https://www.easa.europa.eu/sites/default/files/dfu/CS-23%20Amendment%204.pdf
        # https://www.astm.org/Standards/F3116.htm

        # For the computation of the Vfe CS 23.1511, refers to CS 23.345 but there only seems to be a
        # requirement for the lowest the Vfe can be, hence we will take this speed as the Vfe. As for the
        # load factors that are prescribed we will use the guidelines provided in CS 23.345 (b)

        # Let us start by computing the Vfe
        Vsfe_1g_ps = math.sqrt(
            (2. * mass * g) /
            (atm_0.density * wing_area * cl_max_flaps))  # [m/s]
        Vfe_min_1 = 1.4 * Vs_1g_ps  # [m/s]
        Vfe_min_2 = 1.8 * Vsfe_1g_ps  # [m/s]
        Vfe_min = max(Vfe_min_1, Vfe_min_2)  # [m/s]
        Vfe = Vfe_min  # [m/s]

        velocity_array.append(float(Vsfe_1g_ps))
        load_factor_array.append(1.0)

        # We can then move on to the computation of the load limitation of the flapped flight domain, which
        # must be equal to either a constant load factor of 2 or a load factor dictated by a gust of 25 fps.
        # Also since the use of flaps is limited to take-off, approach and landing, we will use the SL density
        # and a constant gust velocity

        U_de_fe = 25.  # [ft/s]
        n_lim_ps_fe = 2.0
        n_Vfe = max(n_lim_ps_fe, load_factor_gust_n(U_de_fe, Vfe))

        velocity_array.append(float(Vsfe_1g_ps * math.sqrt(n_Vfe)))
        load_factor_array.append(float(n_Vfe))
        velocity_array.append(float(Vfe))
        load_factor_array.append(float(n_Vfe))

        # We also store the conditions in which the values were computed so that we can easily access
        # them when drawing the flight domains

        conditions = [mass, altitude]

        return velocity_array, load_factor_array, conditions
Ejemplo n.º 26
0
    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)
Ejemplo n.º 27
0
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        sweep_25_wing = float(inputs["data:geometry:wing:sweep_25"])
        aspect_ratio_wing = float(inputs["data:geometry:wing:aspect_ratio"])
        taper_ratio_wing = float(inputs["data:geometry:wing:taper_ratio"])
        area_wing = float(inputs["data:geometry:wing:area"])
        span_wing = float(inputs["data:geometry:wing:span"])

        sweep_25_htp = float(inputs["data:geometry:horizontal_tail:sweep_25"])
        aspect_ratio_htp = float(
            inputs["data:geometry:horizontal_tail:aspect_ratio"])
        efficiency_htp = float(
            inputs["data:aerodynamics:horizontal_tail:efficiency"])
        area_htp = float(inputs["data:geometry:horizontal_tail:area"])
        lp_ht = float(inputs[
            "data:geometry:horizontal_tail:MAC:at25percent:x:from_wingMAC25"])
        delta_z_htp = float(
            inputs["data:geometry:horizontal_tail:z:from_wingMAC25"])

        fuselage_width = float(inputs["data:geometry:fuselage:maximum_width"])
        fuselage_height = float(
            inputs["data:geometry:fuselage:maximum_height"])
        fuselage_diameter = np.sqrt(fuselage_width * fuselage_height)

        area_ratio = area_htp / area_wing

        sos_cruise = Atmosphere(
            inputs["data:mission:sizing:main_route:cruise:altitude"],
            altitude_in_feet=False).speed_of_sound
        mach_cruise = float(inputs["data:TLAR:v_cruise"]) / float(sos_cruise)

        wing_cl = inputs["xfoil:wing:CL"]
        wing_alpha = inputs["xfoil:wing:alpha"]
        index_1 = np.where(wing_alpha == 1.0)
        index_2 = np.where(wing_alpha == 11.0)
        wing_airfoil_cl_alpha = (wing_cl[index_2] -
                                 wing_cl[index_1]) / (10. * math.pi / 180.)

        htp_cl = inputs["xfoil:horizontal_tail:CL"]
        htp_alpha = inputs["xfoil:horizontal_tail:alpha"]
        index_3 = np.where(htp_alpha == 1.0)
        index_4 = np.where(htp_alpha == 11.0)
        htp_airfoil_cl_alpha = (htp_cl[index_4] -
                                htp_cl[index_3]) / (10. * math.pi / 180.)

        mach_array = np.linspace(0., 1.55 * mach_cruise, MACH_NB_PTS + 1)

        beta = np.sqrt(1. - mach_array**2.)
        k_wing = wing_airfoil_cl_alpha / beta / (2. * math.pi)
        tan_sweep_wing = math.tan(sweep_25_wing * math.pi / 180.)
        cos_sweep_wing = math.cos(sweep_25_wing * math.pi / 180.)

        wing_cl_alpha = (2. * math.pi * aspect_ratio_wing) / (
            2. + np.sqrt(aspect_ratio_wing**2. * beta**2. / k_wing**2. *
                         (1. + tan_sweep_wing**2. / beta**2.) + 4.))

        k_htp = htp_airfoil_cl_alpha / beta / (2. * math.pi)
        tan_sweep_htp = math.tan(sweep_25_htp * math.pi / 180.)

        # Computing the fuselage interference factor
        k_wf = 1 + 0.025 * (fuselage_diameter / span_wing) - 0.25 * (
            fuselage_diameter / span_wing)**2.0

        htp_cl_alpha = (2. * math.pi * aspect_ratio_htp) / (
            2. + np.sqrt(aspect_ratio_htp**2. * beta**2. / k_htp**2. *
                         (1. + tan_sweep_htp**2. / beta**2.) + 4.))

        k_a = 1. / aspect_ratio_wing - 1. / (1. + aspect_ratio_wing**1.7)
        k_lambda = (10. - 3. * taper_ratio_wing) / 7.
        k_h = (1. - delta_z_htp / span_wing) / (2. * lp_ht / span_wing)**(1. /
                                                                          3.)

        downwash_gradient = 4.44 * (
            k_a * k_lambda * k_h *
            np.sqrt(cos_sweep_wing))**1.19 * wing_cl_alpha / wing_cl_alpha[0]

        aircraft_cl_alpha = k_wf * wing_cl_alpha + htp_cl_alpha * efficiency_htp * area_ratio * (
            1. - downwash_gradient)

        outputs[
            "data:aerodynamics:aircraft:mach_interpolation:CL_alpha_vector"] = aircraft_cl_alpha
        outputs[
            "data:aerodynamics:aircraft:mach_interpolation:mach_vector"] = mach_array
    def compute(self,
                inputs,
                outputs,
                discrete_inputs=None,
                discrete_outputs=None):
        elevator_chord_ratio = inputs[
            "data:geometry:horizontal_tail:elevator_chord_ratio"]
        tail_thickness_ratio = inputs[
            "data:geometry:horizontal_tail:thickness_ratio"]
        cl_alpha_ht = inputs[
            "data:aerodynamics:horizontal_tail:cruise:CL_alpha"]
        wing_area = inputs["data:geometry:wing:area"]
        ht_area = inputs["data:geometry:horizontal_tail:area"]
        v_cruise = inputs["data:TLAR:v_cruise"]
        cruise_alt = inputs["data:mission:sizing:main_route:cruise:altitude"]

        # Section 10.4.1.1
        # Step 1.
        y = lambda x: tail_thickness_ratio / 0.2 * (
            0.2969 * x**0.5 - 0.126 * x - 0.3516 * x**2.0 + 0.2843 * x**3.0 -
            0.105 * x**4.0)

        y_90 = y(0.90)
        y_95 = y(0.95)
        y_99 = y(0.99)

        tan_0_5_phi_te = (y(1. - 1.e-6) - y(1.)) / 1e-6
        tan_0_5_phi_te_prime = (y_90 / 2.0 - y_99 / 2.0) / 9.0
        tan_0_5_phi_te_prime_prime = (y_95 / 2.0 - y_99 / 2.0) / 9.0

        if (tan_0_5_phi_te == tan_0_5_phi_te_prime) and (
                tan_0_5_phi_te_prime
                == tan_0_5_phi_te_prime_prime) and (tan_0_5_phi_te_prime_prime
                                                    == tail_thickness_ratio):
            condition = True
        else:
            condition = False

        # Step 2.
        cl_alpha_ht *= wing_area / ht_area
        cl_alpha_ht_th = 6.3 + tail_thickness_ratio / 0.2 * (7.3 - 6.3)

        k_cl_alpha_inter = inter.interp1d([0., 0.2], [0.9, 0.69])
        k_cl_alpha = k_cl_alpha_inter(tail_thickness_ratio)[0]

        k_cl_alpha_array = [
            0.7, 0.72, 0.74, 0.76, 0.78, 0.8, 0.82, 0.84, 0.86, 0.88, 0.9,
            0.92, 0.94, 0.96, 0.98, 1.0
        ]
        k_ch_alpha_min_array = [
            -0.1, 0.0, 0.08, 0.19, 0.28, 0.36, 0.43, 0.5, 0.58, 0.65, 0.70,
            0.75, 0.82, 0.87, 0.93, 1.0
        ]
        k_ch_alpha_max_array = [
            0.12, 0.22, 0.30, 0.38, 0.46, 0.53, 0.6, 0.65, 0.70, 0.75, 0.80,
            0.84, 0.88, 0.92, 0.96, 1.0
        ]
        k_ch_alpha_min_inter = inter.interp1d(k_cl_alpha_array,
                                              k_ch_alpha_min_array)
        k_ch_alpha_max_inter = inter.interp1d(k_cl_alpha_array,
                                              k_ch_alpha_max_array)

        k_ch_alpha_min = k_ch_alpha_min_inter(k_cl_alpha)
        k_ch_alpha_max = k_ch_alpha_max_inter(k_cl_alpha)

        if elevator_chord_ratio < 0.1:
            k_ch_alpha = k_ch_alpha_min
        elif elevator_chord_ratio > 0.4:
            k_ch_alpha = k_ch_alpha_max
        else:
            k_ch_alpha_inter = inter.interp1d([0.1, 0.4],
                                              [k_ch_alpha_min, k_ch_alpha_max])
            k_ch_alpha = k_ch_alpha_inter(elevator_chord_ratio)

        thickness_ratio_array = [0., 0.4, 0.6, 0.8, 0.10, 0.12, 0.16]
        ch_alpha_min_array = [
            -0.225, -0.25, -0.27, -0.295, -0.305, -0.315, -0.3
        ]
        ch_alpha_max_array = [-0.65, -0.67, -0.685, -0.7, -0.71, -0.72, -0.75]

        ch_alpha_min_inter = inter.interp1d(thickness_ratio_array,
                                            ch_alpha_min_array)
        ch_alpha_max_inter = inter.interp1d(thickness_ratio_array,
                                            ch_alpha_max_array)

        ch_alpha_min = ch_alpha_min_inter(tail_thickness_ratio)[0]
        ch_alpha_max = ch_alpha_max_inter(tail_thickness_ratio)[0]

        if elevator_chord_ratio < 0.1:
            ch_alpha = ch_alpha_min
        elif elevator_chord_ratio > 0.4:
            ch_alpha = ch_alpha_max
        else:
            ch_alpha_inter = inter.interp1d([0.1, 0.4],
                                            [ch_alpha_min, ch_alpha_max])
            ch_alpha = ch_alpha_inter(elevator_chord_ratio)

        ch_prime_alpha = k_ch_alpha * ch_alpha

        # Step 3.

        if condition:
            ch_prime_prime_alpha = ch_prime_alpha

        else:
            ch_prime_prime_alpha = ch_prime_alpha + \
                                   2. * cl_alpha_ht_th * (1. - k_cl_alpha) * \
                                   (tan_0_5_phi_te_prime_prime - tail_thickness_ratio)

        # Step 4.
        # The overhang (cb/cf will we taken equal to 3 as we assume a 1/4, 3/4 chord repartition for the hinge line)
        # We will also assume that the thickness ratio of the elevator is the same as the tail and that it has a
        # round nose

        balance_ratio = math.sqrt((1. / 3.)**2.0 -
                                  (tail_thickness_ratio * 5. / 4)**2.0)

        if balance_ratio < 0.15:
            balance_ratio = 0.15
        elif balance_ratio > 0.5:
            balance_ratio = 0.5

        k_ch_alpha_balance_inter = inter.interp1d([0.15, 0.50], [0.93, 0.2])
        k_ch_alpha_balance = k_ch_alpha_balance_inter(balance_ratio)

        ch_alpha_balance = k_ch_alpha_balance * ch_prime_prime_alpha

        # Step 5.

        sos_cruise = Atmosphere(cruise_alt,
                                altitude_in_feet=False).speed_of_sound
        mach = v_cruise / sos_cruise
        beta = math.sqrt(1. - mach**2.0)

        ch_alpha_fin = ch_alpha_balance / beta

        # Section 10.4.1.2
        # Step 1., same as step 1. in previous section

        # Step 2.

        k_cl_alpha_array = [0.6, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.0]
        k_ch_delta_min = [0.64, 0.70, 0.75, 0.80, 0.84, 0.88, 0.92, 0.96, 1.0]
        k_ch_delta_avg = [
            0.56, 0.65, 0.725, 0.775, 0.83, 0.87, 0.91, 0.955, 1.0
        ]
        k_ch_delta_max = [
            0.415, 0.55, 0.66, 0.745, 0.80, 0.85, 0.90, 0.95, 1.0
        ]

        k_ch_delta_min_inter = inter.interp1d(k_cl_alpha_array, k_ch_delta_min)
        k_ch_delta_avg_inter = inter.interp1d(k_cl_alpha_array, k_ch_delta_avg)
        k_ch_delta_max_inter = inter.interp1d(k_cl_alpha_array, k_ch_delta_max)

        k_ch_delta_min = k_ch_delta_min_inter(k_cl_alpha)
        k_ch_delta_avg = k_ch_delta_avg_inter(k_cl_alpha)
        k_ch_delta_max = k_ch_delta_max_inter(k_cl_alpha)

        if elevator_chord_ratio < 0.1:
            k_ch_delta = k_ch_delta_min
        elif elevator_chord_ratio > 0.4:
            k_ch_delta = k_ch_delta_max
        else:
            k_ch_delta_inter = inter.interp1d(
                [0.1, 0.25, 0.4],
                [k_ch_delta_min, k_ch_delta_avg, k_ch_delta_max],
                kind='quadratic')
            k_ch_delta = k_ch_delta_inter(elevator_chord_ratio)

        thickness_ratio_array = [0., 0.4, 0.6, 0.8, 0.10, 0.12, 0.16]
        ch_delta_min_array = [-0.88, -0.83, -0.8, -0.77, -0.735, -0.7, -0.64]
        ch_delta_max_array = [
            -1.02, -0.995, -0.985, -0.97, -0.955, -0.94, -0.925
        ]

        ch_delta_min_inter = inter.interp1d(thickness_ratio_array,
                                            ch_delta_min_array)
        ch_delta_max_inter = inter.interp1d(thickness_ratio_array,
                                            ch_delta_max_array)

        ch_delta_min = ch_delta_min_inter(tail_thickness_ratio)[0]
        ch_delta_max = ch_delta_max_inter(tail_thickness_ratio)[0]

        if elevator_chord_ratio < 0.1:
            ch_delta = ch_delta_min
        elif elevator_chord_ratio > 0.4:
            ch_delta = ch_delta_max
        else:
            ch_delta_inter = inter.interp1d([0.1, 0.4],
                                            [ch_delta_min, ch_delta_max])
            ch_delta = ch_delta_inter(elevator_chord_ratio)

        ch_prime_delta = k_ch_delta * ch_delta

        # Step 3.

        if condition:
            ch_prime_prime_delta = ch_prime_delta

        else:
            thickness_ratio_array = [
                0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.12, 0.15
            ]
            cl_delta_th_min_array = [
                1.75, 1.75, 1.75, 1.75, 1.75, 1.75, 1.75, 1.75
            ]
            cl_delta_th_avg_array = [
                4.125, 4.1875, 4.25, 4.3125, 4.375, 4.4375, 4.5, 4.5625
            ]
            cl_delta_th_max_array = [
                5.15, 5.25, 5.35, 5.45, 5.55, 5.7, 5.8, 5.95
            ]

            cl_delta_th_min_inter = inter.interp1d(thickness_ratio_array,
                                                   cl_delta_th_min_array)
            cl_delta_th_avg_inter = inter.interp1d(thickness_ratio_array,
                                                   cl_delta_th_avg_array)
            cl_delta_th_max_inter = inter.interp1d(thickness_ratio_array,
                                                   cl_delta_th_max_array)

            cl_delta_th_min = cl_delta_th_min_inter(tail_thickness_ratio)[0]
            cl_delta_th_avg = cl_delta_th_avg_inter(tail_thickness_ratio)[0]
            cl_delta_th_max = cl_delta_th_max_inter(tail_thickness_ratio)[0]

            if elevator_chord_ratio < 0.05:
                cl_delta_th = cl_delta_th_min
            elif elevator_chord_ratio > 0.5:
                cl_delta_th = cl_delta_th_max
            else:
                cl_delta_th_inter = inter.interp1d(
                    [0.05, 0.3, 0.5],
                    [cl_delta_th_min, cl_delta_th_avg, cl_delta_th_max],
                    kind='quadratic')
                cl_delta_th = cl_delta_th_inter(elevator_chord_ratio)

            k_cl_alpha_array = [
                0.7, 0.72, 0.74, 0.76, 0.78, 0.8, 0.82, 0.84, 0.86, 0.88, 0.9,
                0.92, 0.94, 0.96, 0.98, 1.0
            ]
            k_cl_delta_min_array = [
                0.36, 0.4, 0.44, 0.48, 0.53, 0.575, 0.615, 0.655, 0.7, 0.735,
                0.78, 0.83, 0.86, 0.91, 0.95, 1.0
            ]
            k_cl_delta_max_array = [
                0.55, 0.59, 0.62, 0.655, 0.695, 0.733, 0.766, 0.8, 0.82, 0.845,
                0.87, 0.9, 0.925, 0.95, 0.975, 1.0
            ]

            k_cl_delta_min_array_inter = inter.interp1d(
                k_cl_alpha_array, k_cl_delta_min_array)
            k_cl_delta_max_array_inter = inter.interp1d(
                k_cl_alpha_array, k_cl_delta_max_array)

            k_cl_delta_min = k_cl_delta_min_array_inter(k_cl_alpha)
            k_cl_delta_max = k_cl_delta_max_array_inter(k_cl_alpha)

            if elevator_chord_ratio < 0.05:
                k_cl_delta = k_cl_delta_min
            elif elevator_chord_ratio > 0.5:
                k_cl_delta = k_cl_delta_max
            else:
                k_cl_delta_inter = inter.interp1d(
                    [0.05, 0.5], [k_cl_delta_min, k_cl_delta_max])
                k_cl_delta = k_cl_delta_inter(elevator_chord_ratio)

            ch_prime_prime_delta = ch_prime_delta + (
                2. * cl_delta_th * (1. - k_cl_delta) *
                (tan_0_5_phi_te_prime_prime - tail_thickness_ratio))

        # Step 4. Same assumption as the step 4. of the previous section. Only this time we only take the curve for
        # the NACA 0009
        k_ch_delta_balance = inter.interp1d([0.15, 0.50],
                                            [0.93, 0.2])(balance_ratio)

        ch_delta_balance = k_ch_delta_balance * ch_prime_prime_delta

        # Step 5.
        ch_delta_fin = ch_delta_balance / beta

        outputs[
            "data:aerodynamics:horizontal_tail:cruise:hinge_moment:CH_alpha_2D"] = ch_alpha_fin
        outputs[
            "data:aerodynamics:horizontal_tail:cruise:hinge_moment:CH_delta_2D"] = ch_delta_fin
Ejemplo n.º 29
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
Ejemplo n.º 30
0
    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)