Source code for fastga.models.weight.cg.cg_components.loadcase

"""
New estimation method of center of gravity for all load cases.
"""

#  This file is part of FAST-OAD_CS23 : A framework for rapid Overall Aircraft Design
#  Copyright (C) 2022  ONERA & ISAE-SUPAERO
#  FAST is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import fastoad.api as oad
import numpy as np
import scipy.optimize as optimize
from fastoad.constants import EngineSetting

# noinspection PyProtectedMember
from fastoad.module_management._bundle_loader import BundleLoader
from openmdao.core.explicitcomponent import ExplicitComponent
from scipy.constants import g
from stdatm import Atmosphere

from .constants import SUBMODEL_LOADCASE_GROUND_X, SUBMODEL_LOADCASE_FLIGHT_X


[docs]@oad.RegisterSubmodel( SUBMODEL_LOADCASE_GROUND_X, "fastga.submodel.weight.cg.loadcase.ground.legacy" ) class ComputeGroundCGCase(ExplicitComponent): # TODO: Document equations. Cite sources """Center of gravity estimation for all load cases on ground."""
[docs] def setup(self): self.add_input("data:geometry:cabin:luggage:mass_max", val=np.nan, units="kg") self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input("data:geometry:fuselage:front_length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:pilot:length", val=np.nan, units="m") self.add_input("data:weight:furniture:passenger_seats:CG:x", val=np.nan, units="m") self.add_input("data:weight:payload:rear_fret:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:unusable_fuel:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:tank:CG:x", val=np.nan, units="m") self.add_output("data:weight:aircraft:CG:ground_condition:max:MAC_position") self.add_output("data:weight:aircraft:CG:ground_condition:min:MAC_position")
[docs] def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): luggage_mass_max = float(inputs["data:geometry:cabin:luggage:mass_max"]) l0_wing = inputs["data:geometry:wing:MAC:length"] fa_length = inputs["data:geometry:wing:MAC:at25percent:x"] cg_pax = inputs["data:weight:furniture:passenger_seats:CG:x"] lav = inputs["data:geometry:fuselage:front_length"] l_pilot_seat = inputs["data:geometry:cabin:seats:pilot:length"] cg_rear_fret = inputs["data:weight:payload:rear_fret:CG:x"] x_cg_plane_aft = inputs["data:weight:aircraft_empty:CG:x"] m_empty = inputs["data:weight:aircraft_empty:mass"] m_unusable_fuel = inputs["data:weight:propulsion:unusable_fuel:mass"] cg_tank = inputs["data:weight:propulsion:tank:CG:x"] l_instr = 0.7 cg_pilot = lav + l_instr + l_pilot_seat / 2.0 m_pilot = 77.0 cg_list = [] m_pax_array = np.zeros(1) m_pilot_array = np.array([0.0, 2.0 * m_pilot]) # Without the pilots and with the 2 pilots m_fuel_array = np.array([m_unusable_fuel]) m_lug_array = np.array([0.0, luggage_mass_max]) for m_lug in m_lug_array: for m_fuel in m_fuel_array: for m_pilot in m_pilot_array: for m_pax in m_pax_array: mass = m_pax + m_pilot + m_fuel + m_lug + m_empty cg = ( m_empty * x_cg_plane_aft + m_pax * cg_pax + m_pilot * cg_pilot + m_fuel * cg_tank + m_lug * cg_rear_fret ) / mass cg_list.append(cg) cg_fwd = min(cg_list) cg_aft = max(cg_list) cg_fwd_ratio_pl = (cg_fwd - fa_length + 0.25 * l0_wing) / l0_wing cg_aft_ratio_pl = (cg_aft - fa_length + 0.25 * l0_wing) / l0_wing outputs["data:weight:aircraft:CG:ground_condition:max:MAC_position"] = cg_aft_ratio_pl outputs["data:weight:aircraft:CG:ground_condition:min:MAC_position"] = cg_fwd_ratio_pl
[docs]@oad.RegisterSubmodel( SUBMODEL_LOADCASE_FLIGHT_X, "fastga.submodel.weight.cg.loadcase.flight.legacy" ) class ComputeFlightCGCase(ExplicitComponent): """Center of gravity estimation for all load cases in flight""" def __init__(self, **kwargs): super().__init__(**kwargs) self._engine_wrapper = None
[docs] def initialize(self): self.options.declare("propulsion_id", default="", types=str)
[docs] def setup(self): self._engine_wrapper = BundleLoader().instantiate_component(self.options["propulsion_id"]) self._engine_wrapper.setup(self) self.add_input("data:geometry:cabin:luggage:mass_max", val=np.nan, units="kg") self.add_input("data:geometry:wing:area", val=np.nan, units="m**2") self.add_input("data:aerodynamics:aircraft:cruise:CD0", val=np.nan) self.add_input("data:aerodynamics:wing:cruise:induced_drag_coefficient", val=np.nan) self.add_input("data:geometry:cabin:seats:passenger:NPAX_max", val=np.nan) self.add_input("data:geometry:wing:MAC:length", val=np.nan, units="m") self.add_input("data:geometry:wing:MAC:at25percent:x", val=np.nan, units="m") self.add_input("data:geometry:fuselage:front_length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:pilot:length", val=np.nan, units="m") self.add_input("data:geometry:fuselage:PAX_length", val=np.nan, units="m") self.add_input("data:geometry:cabin:seats:passenger:count_by_row", val=np.nan) self.add_input("data:geometry:cabin:seats:passenger:length", val=np.nan, units="m") self.add_input("data:weight:payload:rear_fret:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft_empty:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft:MTOW", val=np.nan, units="kg") self.add_input("data:weight:aircraft_empty:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:unusable_fuel:mass", val=np.nan, units="kg") self.add_input("data:weight:propulsion:tank:CG:x", val=np.nan, units="m") self.add_input("data:weight:aircraft:MFW", val=np.nan, units="kg") self.add_output("data:weight:aircraft:CG:flight_condition:max:MAC_position") self.add_output("data:weight:aircraft:CG:flight_condition:min:MAC_position")
[docs] def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): luggage_mass_max = float(inputs["data:geometry:cabin:luggage:mass_max"]) n_pax_max = inputs["data:geometry:cabin:seats:passenger:NPAX_max"] l0_wing = inputs["data:geometry:wing:MAC:length"] fa_length = inputs["data:geometry:wing:MAC:at25percent:x"] lav = inputs["data:geometry:fuselage:front_length"] l_pax = inputs["data:geometry:fuselage:PAX_length"] l_pilot_seat = inputs["data:geometry:cabin:seats:pilot:length"] count_by_row = inputs["data:geometry:cabin:seats:passenger:count_by_row"] l_pass_seat = inputs["data:geometry:cabin:seats:passenger:length"] cg_rear_fret = inputs["data:weight:payload:rear_fret:CG:x"] x_cg_plane_aft = inputs["data:weight:aircraft_empty:CG:x"] m_empty = inputs["data:weight:aircraft_empty:mass"] m_unusable_fuel = inputs["data:weight:propulsion:unusable_fuel:mass"] cg_tank = inputs["data:weight:propulsion:tank:CG:x"] mfw = inputs["data:weight:aircraft:MFW"] l_instr = 0.7 cg_pilot = lav + l_instr + l_pilot_seat / 2.0 n_pax_array = np.linspace(0.0, n_pax_max, int(n_pax_max) + 1) m_pilot_single = 77.0 m_pilot_array = np.array([2.0 * m_pilot_single]) # Without the pilots and with the 2 pilots m_fuel_min = m_unusable_fuel + self.min_in_flight_fuel(inputs) m_fuel_array = np.array([m_fuel_min, mfw]) m_lug_array = np.array([0.0, luggage_mass_max]) cg_list = [] for m_pilot in m_pilot_array: for m_fuel in m_fuel_array: for m_lug in m_lug_array: for n_pax in n_pax_array: n_row = np.ceil(n_pax / count_by_row) x_cg_pax_fwd = 0.0 for idx in range(int(n_row)): row_cg = (idx + 0.5) * l_pass_seat nb_pers = min(count_by_row, n_pax_max - idx * count_by_row) x_cg_pax_fwd += row_cg * nb_pers / n_pax_max x_cg_pax_aft = 0.0 for idx in range(int(n_row)): row_cg = l_pax - l_pilot_seat - (idx + 0.5) * l_pass_seat nb_pers = min(count_by_row, n_pax_max - idx * count_by_row) x_cg_pax_aft += row_cg * nb_pers / n_pax_max cg_pax_array = np.array( [ lav + l_instr + l_pilot_seat + x_cg_pax_fwd, lav + l_instr + l_pilot_seat + x_cg_pax_aft, ] ) for cg_pax in cg_pax_array: m_pax_array = np.array([n_pax * 80.0, n_pax * 90.0]) for m_pax in m_pax_array: mass = m_pax + m_pilot + m_fuel + m_lug + m_empty cg = ( m_empty * x_cg_plane_aft + m_pax * cg_pax + m_pilot * cg_pilot + m_fuel * cg_tank + m_lug * cg_rear_fret ) / mass cg_list.append(cg) cg_aft = max(cg_list) cg_fwd = min(cg_list) cg_fwd_ratio_pl = (cg_fwd - fa_length + 0.25 * l0_wing) / l0_wing cg_aft_ratio_pl = (cg_aft - fa_length + 0.25 * l0_wing) / l0_wing outputs["data:weight:aircraft:CG:flight_condition:max:MAC_position"] = cg_aft_ratio_pl outputs["data:weight:aircraft:CG:flight_condition:min:MAC_position"] = cg_fwd_ratio_pl
[docs] def min_in_flight_fuel(self, inputs): propulsion_model = self._engine_wrapper.get_model(inputs) # 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 = oad.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.0 * 60.0) # Fuel necessary for a half-hour at max continuous power return m_fuel
[docs] def max_speed(self, inputs, altitude, mass): # noinspection PyTypeChecker roots = optimize.fsolve(self.delta_axial_load, 300.0, args=(inputs, altitude, mass))[0] return np.max(roots[roots > 0.0])
[docs] def delta_axial_load(self, air_speed, inputs, altitude, mass): propulsion_model = self._engine_wrapper.get_model(inputs) 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 = oad.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