Source code for fastga.models.load_analysis.wing.structural_loads

"""
Computes the structural loads on the wing of the aircraft in the most stringent case according
to aero-structural loads.
"""

#  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 numpy as np
import openmdao.api as om

import fastoad.api as oad

from .aerostructural_loads import AerostructuralLoad, SPAN_MESH_POINT_LOADS
from .constants import SUBMODEL_STRUCTURAL_LOADS


[docs]@oad.RegisterSubmodel(SUBMODEL_STRUCTURAL_LOADS, "fastga.submodel.loads.wings.structural.legacy") class StructuralLoads(om.ExplicitComponent):
[docs] def setup(self): self.add_input("data:loads:max_shear:load_factor", val=np.nan) self.add_input("data:loads:max_rbm:load_factor", val=np.nan) self.add_input( "data:aerodynamics:wing:low_speed:Y_vector", val=np.nan, shape_by_conn=True, units="m", ) self.add_input( "data:aerodynamics:wing:low_speed:chord_vector", val=np.nan, shape_by_conn=True, copy_shape="data:aerodynamics:wing:low_speed:Y_vector", units="m", ) # This add_input is needed because in the other module of the wing load computation, # the shape of this vector is copied based on the Y_vector and not having it here would # cause the code to crash. self.add_input( "data:aerodynamics:wing:low_speed:CL_vector", val=np.nan, shape_by_conn=True, copy_shape="data:aerodynamics:wing:low_speed:Y_vector", ) self.add_input("data:weight:propulsion:engine:mass", val=np.nan, units="kg") self.add_input("data:weight:airframe:landing_gear:main:mass", val=np.nan, units="kg") self.add_input("data:weight:airframe:wing:mass", val=np.nan, units="kg") self.add_input("data:weight:aircraft_empty:CG:z", val=np.nan, units="m") self.add_input("data:geometry:landing_gear:height", val=np.nan, units="m") self.add_input("data:geometry:landing_gear:y", val=np.nan, units="m") self.add_input("data:geometry:landing_gear:type", val=np.nan) self.add_input("data:geometry:wing:root:chord", val=np.nan, units="m") self.add_input("data:geometry:wing:tip:chord", val=np.nan, units="m") self.add_input("data:geometry:wing:root:y", val=np.nan, units="m") self.add_input("data:geometry:wing:tip:y", val=np.nan, units="m") self.add_input("data:geometry:wing:root:thickness_ratio", val=np.nan) self.add_input("data:geometry:wing:tip:thickness_ratio", val=np.nan) self.add_input("data:geometry:wing:span", val=np.nan, units="m") self.add_input( "data:geometry:propulsion:engine:y_ratio", shape_by_conn=True, ) self.add_input("data:geometry:propulsion:engine:layout", val=np.nan) self.add_input("data:geometry:propulsion:engine:count", val=np.nan) self.add_input("data:geometry:propulsion:nacelle:width", val=np.nan, units="m") self.add_input("data:geometry:propulsion:tank:y_ratio_tank_end", val=np.nan) self.add_input("data:geometry:propulsion:tank:y_ratio_tank_beginning", val=np.nan) self.add_input("data:geometry:propulsion:tank:LE_chord_percentage", val=np.nan) self.add_input("data:geometry:propulsion:tank:TE_chord_percentage", val=np.nan) self.add_input("data:geometry:flap:chord_ratio", val=np.nan) self.add_input("data:geometry:wing:aileron:chord_ratio", val=np.nan) self.add_input( "data:weight:airframe:wing:punctual_mass:y_ratio", shape_by_conn=True, val=0.0, ) self.add_input( "data:weight:airframe:wing:punctual_mass:mass", shape_by_conn=True, copy_shape="data:weight:airframe:wing:punctual_mass:y_ratio", units="kg", val=0.0, ) self.add_input("data:mission:sizing:fuel", val=np.nan, units="kg") self.add_input("settings:geometry:fuel_tanks:depth", val=np.nan) self.add_output( "data:loads:structure:ultimate:force_distribution:wing", units="N/m", shape=SPAN_MESH_POINT_LOADS, ) self.add_output( "data:loads:structure:ultimate:force_distribution:fuel", units="N/m", shape=SPAN_MESH_POINT_LOADS, ) self.add_output( "data:loads:structure:ultimate:force_distribution:point_mass", units="N/m", shape=SPAN_MESH_POINT_LOADS, ) self.add_output( "data:loads:structure:ultimate:shear:wing", units="N", shape=SPAN_MESH_POINT_LOADS ) self.add_output( "data:loads:structure:ultimate:shear:fuel", units="N", shape=SPAN_MESH_POINT_LOADS ) self.add_output( "data:loads:structure:ultimate:shear:point_mass", units="N", shape=SPAN_MESH_POINT_LOADS ) self.add_output( "data:loads:structure:ultimate:root_bending:wing", units="N*m", shape=SPAN_MESH_POINT_LOADS, ) self.add_output( "data:loads:structure:ultimate:root_bending:fuel", units="N*m", shape=SPAN_MESH_POINT_LOADS, ) self.add_output( "data:loads:structure:ultimate:root_bending:point_mass", units="N*m", shape=SPAN_MESH_POINT_LOADS, )
[docs] def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): # STEP 1/XX - DEFINE OR CALCULATE INPUT DATA FOR LOAD COMPUTATION y_vector = inputs["data:aerodynamics:wing:low_speed:Y_vector"] chord_vector = inputs["data:aerodynamics:wing:low_speed:chord_vector"] semi_span = float(inputs["data:geometry:wing:span"]) / 2.0 root_chord = inputs["data:geometry:wing:root:chord"] tip_chord = inputs["data:geometry:wing:tip:chord"] load_factor_shear = float(inputs["data:loads:max_shear:load_factor"]) load_factor_rbm = float(inputs["data:loads:max_rbm:load_factor"]) wing_mass = inputs["data:weight:airframe:wing:mass"] fuel_mass = inputs["data:mission:sizing:fuel"] # STEP 2/XX - DELETE THE ADDITIONAL ZEROS WE HAD TO PUT TO FIT OPENMDAO AND ADD A POINT # AT THE ROOT (Y=0) AND AT THE VERY TIP (Y=SPAN/2) TO GET THE WHOLE SPAN OF THE WING IN # THE INTERPOLATION WE WILL DO LATER # Reformat the y_vector array as was done in the aerostructural component y_vector = AerostructuralLoad.delete_additional_zeros(y_vector) chord_vector = AerostructuralLoad.delete_additional_zeros(chord_vector) y_vector, _ = AerostructuralLoad.insert_in_sorted_array(y_vector, 0.0) chord_vector = np.insert(chord_vector, 0, root_chord) y_vector_orig, _ = AerostructuralLoad.insert_in_sorted_array(y_vector, semi_span) chord_vector_orig = np.append(chord_vector, tip_chord) # STEP 3/XX - WE COMPUTE THE BASELINE WEiGHT DISTRIBUTION AND SCALE IT UP ACCORDING TO # THE MOST CONSTRAINING CASE IDENTIFIED IN THE AEROSTRUCTURAL ANALYSIS y_vector, point_mass_array_orig = AerostructuralLoad.compute_relief_force( inputs, y_vector_orig, chord_vector_orig, 0.0, 0.0 ) _, wing_mass_array_orig = AerostructuralLoad.compute_relief_force( inputs, y_vector_orig, chord_vector_orig, wing_mass, 0.0, False ) _, fuel_mass_array_orig = AerostructuralLoad.compute_relief_force( inputs, y_vector_orig, chord_vector_orig, 0.0, fuel_mass, False ) point_mass_array = max(load_factor_shear, load_factor_rbm) * point_mass_array_orig wing_mass_array = max(load_factor_shear, load_factor_rbm) * wing_mass_array_orig fuel_mass_array = max(load_factor_shear, load_factor_rbm) * fuel_mass_array_orig additional_zeros = np.zeros(SPAN_MESH_POINT_LOADS - len(y_vector)) point_mass_array_outputs = np.concatenate([point_mass_array, additional_zeros]) wing_mass_array_outputs = np.concatenate([wing_mass_array, additional_zeros]) fuel_mass_array_outputs = np.concatenate([fuel_mass_array, additional_zeros]) outputs["data:loads:structure:ultimate:force_distribution:wing"] = wing_mass_array_outputs outputs["data:loads:structure:ultimate:force_distribution:fuel"] = fuel_mass_array_outputs outputs["data:loads:structure:ultimate:force_distribution:point_mass"] = ( point_mass_array_outputs ) point_shear_array = AerostructuralLoad.compute_shear_diagram( y_vector, load_factor_shear * point_mass_array_orig ) wing_shear_array = AerostructuralLoad.compute_shear_diagram( y_vector, load_factor_shear * wing_mass_array_orig ) fuel_shear_array = AerostructuralLoad.compute_shear_diagram( y_vector, load_factor_shear * fuel_mass_array_orig ) # STEP 4/XX - WE ADD ZEROS AT THE END OF THE RESULT LIFT DISTRIBUTION TO FIT THE FORMAT # IMPOSED BY OPENMDAO point_shear_array = np.concatenate([point_shear_array, additional_zeros]) wing_shear_array = np.concatenate([wing_shear_array, additional_zeros]) fuel_shear_array = np.concatenate([fuel_shear_array, additional_zeros]) outputs["data:loads:structure:ultimate:shear:wing"] = wing_shear_array outputs["data:loads:structure:ultimate:shear:fuel"] = fuel_shear_array outputs["data:loads:structure:ultimate:shear:point_mass"] = point_shear_array point_root_bending_array = AerostructuralLoad.compute_bending_moment_diagram( y_vector, load_factor_rbm * point_mass_array_orig ) wing_root_bending_array = AerostructuralLoad.compute_bending_moment_diagram( y_vector, load_factor_rbm * wing_mass_array_orig ) fuel_root_bending_array = AerostructuralLoad.compute_bending_moment_diagram( y_vector, load_factor_rbm * fuel_mass_array_orig ) # STEP 4/XX - WE ADD ZEROS AT THE END OF THE RESULT LIFT DISTRIBUTION TO FIT THE FORMAT # IMPOSED BY OPENMDAO point_root_bending_array = np.concatenate([point_root_bending_array, additional_zeros]) wing_root_bending_array = np.concatenate([wing_root_bending_array, additional_zeros]) fuel_root_bending_array = np.concatenate([fuel_root_bending_array, additional_zeros]) outputs["data:loads:structure:ultimate:root_bending:wing"] = wing_root_bending_array outputs["data:loads:structure:ultimate:root_bending:fuel"] = fuel_root_bending_array outputs["data:loads:structure:ultimate:root_bending:point_mass"] = point_root_bending_array