Source code for gemseo.problems.sobieski.core.aerodynamics

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# Contributors:
#    INITIAL AUTHORS - initial API and implementation
#               and/or initial documentation
#        :author: Sobieski, Agte, and Sandusky
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
#        :author: Damien Guenot
#        :author: Francois Gallard
# From NASA/TM-1998-208715
# Bi-Level Integrated System Synthesis (BLISS)
# Sobieski, Agte, and Sandusky
"""Aerodynamics discipline for the Sobieski's SSBJ use case."""
from __future__ import annotations

import logging
from math import pi

from numpy import array
from numpy import cos
from numpy import ndarray
from numpy import sin
from numpy import sqrt
from numpy import zeros

from gemseo.problems.sobieski.core.discipline import SobieskiDiscipline
from gemseo.problems.sobieski.core.utils import DEG_TO_RAD
from gemseo.problems.sobieski.core.utils import SobieskiBase

LOGGER = logging.getLogger(__name__)


[docs]class SobieskiAerodynamics(SobieskiDiscipline): """Aerodynamics discipline for the Sobieski's SSBJ use case.""" PRESSURE_GRADIENT_LIMIT = 1.04 def __init__(self, sobieski_base: SobieskiBase) -> None: super().__init__(sobieski_base) self.__flag1 = array([1, 1], dtype=self.dtype) self.__bound1 = array([0.25, 0.25], dtype=self.dtype) self.__flag2 = array([5], dtype=self.dtype) self.__bound2 = array([0.25], dtype=self.dtype) self.__flag3 = array([1], dtype=self.dtype) self.__bound3 = array([0.25], dtype=self.dtype) self.__ao_coeff_1 = zeros(1, dtype=self.dtype) self.__ai_coeff_1 = zeros(2, dtype=self.dtype) self.__aij_coeff_1 = zeros((2, 2), dtype=self.dtype) self.__ao_coeff_2 = zeros(1, dtype=self.dtype) self.__ai_coeff_2 = zeros(1, dtype=self.dtype) self.__aij_coeff_2 = zeros((1, 1), dtype=self.dtype) self.__ao_coeff_3 = zeros(1, dtype=self.dtype) self.__ai_coeff_3 = zeros(1, dtype=self.dtype) self.__aij_coeff_3 = zeros((1, 1), dtype=self.dtype) self.__esf_cf_initial = array( [self.esf_initial, self.cf_initial], dtype=self.dtype ) self.__twist_initial = array([self.twist_initial], dtype=self.dtype) self.__tc_initial = array([self.tc_initial], dtype=self.dtype) self.__rho = None self.__velocity = None self.__rhov2 = None self.__k_aero = None self.__lift_coeff = None def __compute_k_aero( self, mach: float, sweep: float, ) -> float: """Compute the induced drag coefficient (related to lift). Args: mach: The Mach number. sweep: The wing sweep. Returns: The induced drag coefficient. """ self.__k_aero = ( (mach**2 - 1) * self.math.cos(sweep * DEG_TO_RAD) / (4.0 * self.math.sqrt(sweep**2 - 1) - 2) ) return self.__k_aero @staticmethod def __compute_dk_aero_dsweep( mach: float, sweep: float, ) -> float: """Derive the induced drag coefficient with respect to the sweep. Args: mach: The Mach number. sweep: The wing sweep. Returns: The derivative of the induced drag coefficient with respect to the sweep. """ u_velo = (mach * mach - 1.0) * cos(sweep * DEG_TO_RAD) up_velo = -DEG_TO_RAD * (mach * mach - 1.0) * sin(sweep * DEG_TO_RAD) v_velo = 4.0 * sqrt(sweep * sweep - 1.0) - 2.0 vp_velo = 4.0 * sweep * (sweep * sweep - 1.0) ** -0.5 return (up_velo * v_velo - u_velo * vp_velo) / v_velo**2 @staticmethod def __compute_dk_aero_dmach( mach: float, sweep: float, ) -> float: """Derive the induced drag coefficient with respect to the Mach number. Args: mach: The Mach number. sweep: The wing sweep. Returns: The derivative of the induced drag coefficient with respect to the Mach number. """ return ( (2.0 * mach) * cos(sweep * pi / 180.0) / (4.0 * sqrt(sweep**2 - 1.0) - 2.0) ) def __compute_dadimcf_dcf( self, c_f: float, ) -> float: """Derive the adimensional friction coefficient with respect to the friction coefficient. Args: c_f: The skin friction coefficient. Returns: The derivative of the adimensional friction coefficient with respect to the friction coefficient. """ return self.base.derive_normalization(self.cf_initial, c_f) def __compute_dadimtwist_dtwist( self, twist: float, ) -> float: """Derive the adimensional twist with respect to the twist. Args: twist: The wing twist. Returns: The derivative of the adimensional twist with respect to the twist. """ return self.base.derive_normalization(self.twist_initial, twist) def __compute_dadimesf_desf( self, esf: float, ) -> float: """Derivate the adimensional ESF with respect to the ESF. Args: esf: The engine scale factor. Returns: The derivative of the adimensional ESF with respect to the ESF. """ return self.base.derive_normalization(self.esf_initial, esf) def __compute_dadimtaper_dtaper( self, tc_ratio: float, ) -> float: """Derive an adimensional taper-ratio of polynomial with respect to taper-ratio. Args: tc_ratio: The thickness-to-chord ratio. Returns: The derivative of the adimensioned taper-ratio of polynomial with respect to the taper-ratio. """ return self.base.derive_normalization(self.tc_initial, tc_ratio) def __compute_cd_min( self, tc_ratio: float, sweep: float, fo1: float, c_4: float | None = None, ) -> float: """Compute the 2D minimum drag coefficient. Args: tc_ratio: The thickness-to-chord ratio. sweep: The wing sweep. fo1: The coefficient for the engine size. c_4: The minimum drag coefficient. If ``None``, use :meth:`.SobieskiBase.constants`. Returns: The 2D minimum drag coefficient. """ c_4 = c_4 or self.constants[4] return ( c_4 * fo1 + 3.05 * tc_ratio ** (5.0 / 3.0) * (self.math.cos(sweep * DEG_TO_RAD)) ** 1.5 ) @staticmethod def __compute_dcdmin_dsweep( tc_ratio: float, sweep: float, ) -> float: """Derive the 2D minimum drag coefficient with respect to the sweep. Args: tc_ratio: The thickness-to-chord ratio. sweep: The wing sweep. Returns: The derivative of the the 2D minimum drag coefficient with respect to the sweep. """ ang_rad = sweep * DEG_TO_RAD return ( -3.05 * 1.5 * tc_ratio ** (5.0 / 3.0) * cos(ang_rad) ** 0.5 * DEG_TO_RAD * sin(ang_rad) ) def __compute_cd( self, lift_coeff: float, fo2: float, cdmin: float, ) -> float: """Compute of total drag coefficient. Args: lift_coeff: The lift coefficient. fo2: The coefficient for the twist influence on drag. cdmin: The 2D minimum drag coefficient. Returns: The drag coefficient. """ return fo2 * (cdmin + self.__k_aero * lift_coeff * lift_coeff) def __compute_dcd_dsweep( self, tc_ratio: float, mach: float, sweep: float, lift_coeff: float, fo2: float, ) -> float: """Derive the drag coefficient with respect to the sweep. Args: tc_ratio: The thickness-to-chord ratio. mach: The Mach number. sweep: The wing sweep. lift_coeff: The lift coefficient. fo2: The coefficient for the twist influence on the drag. Returns: The derivative of the drag coefficient with respect to the sweep. """ dcdmin_dsweep = self.__compute_dcdmin_dsweep(tc_ratio, sweep) dk_dsweep = self.__compute_dk_aero_dsweep(mach, sweep) return fo2 * (dcdmin_dsweep + lift_coeff * lift_coeff * dk_dsweep) def __compute_dcd_dsref( self, wing_area: float, fo2: float, ) -> float: """Derive the drag coefficient with respect to the reference surface. Args: wing_area: The wing surface area. fo2: The coefficient for the twist influence on the drag. Returns: The derivative of the drag coefficient with respect to the reference surface. """ dcl_dsref = self.__compute_dcl_dsref(wing_area) return 2 * self.__k_aero * self.__lift_coeff * dcl_dsref * fo2 def __compute_dcd_dmach( self, altitude: float, mach: float, sweep: float, wing_area: float, ac_mass: float, fo2: float, ) -> float: """Derive the drag coefficient with respect to Mach number. Args: altitude: The altitude. mach: The Mach number. sweep: The wing sweep. wing_area: The wing surface area. ac_mass: The total aicraft mass. fo2: The coefficient for the twist influence on the drag. Returns: The derivative of the drag coefficient with respect to the Mach number. """ dk_dmach = self.__compute_dk_aero_dmach(mach, sweep) dcl_dmach = self.__compute_dcl_dmach(altitude, wing_area, ac_mass) return ( (2.0 * self.__k_aero * dcl_dmach + self.__lift_coeff * dk_dmach) * self.__lift_coeff * fo2 ) def __compute_cl( self, wing_area: float, ac_mass: float, ) -> float: """Computation of the lift coefficient. Args: wing_area: The wing surface area. ac_mass: The total aircraft mass. Returns: The lift coefficient. """ self.__lift_coeff = self.__compute_adim_coeff(ac_mass, wing_area) return self.__lift_coeff def __compute_dcl_dh( self, altitude: float, mach: float, wing_area: float, ac_mass: float, ) -> float: """Derive the lift coefficient with respect to the altitude. Args: altitude: The altitude. mach: The Mach number. wing_area: The wing surface area. ac_mass: The total aicraft mass. Returns: The derivative of the lift coefficient with respect to the altitude. """ drhov2_dh = self.__compute_drhov2_dh(altitude, mach) return -2 * ac_mass / wing_area * drhov2_dh / (self.__rhov2**2) def __compute_dcl_dmach( self, altitude: float, wing_area: float, ac_mass: float, ) -> float: """Derive the lift coefficient with respect to the Mach number. Args: altitude: The altitude. wing_area: The wing surface area. ac_mass: The total aircraft mass. Returns: The derivative of the lift coefficient with respect to the Mach number. """ dv_dmach = self.__compute_dv_dmach(altitude) return ( -4.0 * ac_mass * dv_dmach / ( self.__rho * wing_area * self.__velocity * self.__velocity * self.__velocity ) ) def __compute_dcl_dsref( self, wing_area: float, ) -> float: """Derive the lift coefficient with respect to the reference surface. Args: wing_area: The wing surface area. Returns: The derivative of the lift coefficient with respect to the reference surface. """ return -self.__lift_coeff / wing_area def __compute_rhov2(self) -> float: """Compute :math:`\rho v^2` (2*dynamic pressure). Returns: :math:`\rho v^2`. """ self.__rhov2 = self.__rho * self.__velocity * self.__velocity return self.__rhov2 def __compute_drhov2_dh( self, altitude: float, mach: float, ) -> float: """Derive :math:`\rho v^2` with respect to the altitude. Args: altitude: The altitude. mach: The Mach number. Returns: The derivative of :math:`\rho v^2` with respect to the altitude. """ drho_dh, dv_dh = self.__compute_drho_dh_dv_dh(mach, altitude) return ( drho_dh * self.__velocity * self.__velocity + 2.0 * self.__rho * dv_dh * self.__velocity ) def __compute_drhov2_dmach( self, altitude: float, ) -> float: """Derive :math:`\rho v^2` with respect to the Mach number. Args: altitude: The altitude. Returns: The derivative of :math:`\rho v^2` with respect to the Mach number. """ dv_dmach = self.__compute_dv_dmach(altitude) return 2.0 * self.__rho * dv_dmach * self.__velocity def __compute_rho_v( self, mach: float, altitude: float, ) -> tuple[float, float]: """Compute the velocity and density from given Mach number and altitude. Args: mach: The Mach number. altitude: The altitude. Returns: The density, the velocity. """ if altitude.real < 36089.0: self.__velocity = mach * 1116.39 * self.math.sqrt(1 - 6.875e-6 * altitude) self.__rho = 2.377e-3 * (1 - 6.875e-6 * altitude) ** 4.2561 else: self.__velocity = mach * 968.1 self.__rho = ( 2.377e-3 * 0.2971 * self.math.exp((36089.0 - altitude) / 20806.7) ) return self.__rho, self.__velocity def __compute_dv_dmach( self, altitude: float, ) -> float: """Derive the velocity with respect to the Mach number from a given altitude. Args: altitude: The altitude. Returns: The derivative of the velocity with respect to the Mach number. """ if altitude.real < 36089.0: return 1116.39 * self.math.sqrt(1 - 6.875e-6 * altitude) else: return 968.1 def __compute_drho_dh_dv_dh( self, mach: float, altitude: float, ) -> tuple[float, float]: """Compute the derivative of the density and velocity wrt the altitude. Args: mach: The Mach number. altitude: The altitude. Returns: The derivative of the density with respect to the altitude, the derivative of the velocity with respect to the altitude. """ if altitude.real <= 36089.0: drho_dh = ( -2.377e-3 * 4.2561 * 6.875e-6 * (1.0 - 6.875e-6 * altitude) ** 3.2561 ) dv_dh = ( -6.875e-6 * 1116.39 * mach * 0.5 * (1.0 - 6.875e-6 * altitude) ** -0.5 ) else: drho_dh = ( -2.377e-3 * 0.2971 / 20806.7 * self.math.exp((36089.0 - altitude) / 20806.7) ) dv_dh = 0.0 return drho_dh, dv_dh def __compute_adim_coeff( self, force: float, sref: float, ) -> float: """Compute the adimensional force coefficient from the force (lift or drag). Args: force: The force. sref: The reference surface. Returns: The adimensional force coefficient, either the drag or lift coefficient. """ return force / (0.5 * self.__rhov2 * sref)
[docs] def execute( self, x_shared: ndarray, y_12: ndarray, y_32: ndarray, x_2: ndarray, true_cstr: bool = False, c_4: ndarray | None = None, ) -> tuple[ndarray, ndarray, ndarray, ndarray, ndarray]: """Compute the drag and the lift-to-drag ratio. Args: x_shared: The values of the shared design variables, where ``x_shared[0]`` is the thickness/chord ratio, ``x_shared[1]`` is the altitude, ``x_shared[2]`` is the Mach number, ``x_shared[3]`` is the aspect ratio, ``x_shared[4]`` is the wing sweep and ``x_shared[5]`` is the wing surface area. y_12: The coupling variable from the structure disciplines, where ``y_12[0]`` is the total aircraft weight and ``y_12[1]`` is the wing twist. y_32: The coupling variable (engine scale factor) from the propulsion discipline, x_2: The friction coefficient. true_cstr: If ``True``, return the value of the constraint outputs. Otherwise, return the distance to the corresponding constraint thresholds. c_4: The minimum drag coefficient. If ``None``, use :meth:`.SobieskiBase.constants`. Returns: The aerodynamics outputs: - ``y_2``: The outputs of the aerodynamics analysis: - ``y_2[0]``: the lift, - ``y_2[1]``: the drag, - ``y_2[2]``: the lift/drag ratio, - ``y_21``: The coupling variable (lift) for the structure discipline, - ``y_23``: The coupling variable (drag) for the propulsion discipline, - ``y_24``: The coupling variable (lift/drag ratio) for the mission discipline, - ``g_2``: The pressure gradient to be constrained. """ return self._execute( x_shared[0], x_shared[1], x_shared[2], x_shared[4], x_shared[5], y_12[0], y_12[1], y_32[0], x_2[0], true_cstr=true_cstr, c_4=c_4, )
def _execute( self, tc_ratio: float, altitude: float, mach: float, sweep: float, wing_area: float, ac_mass: float, twist: float, esf: float, c_f: float, true_cstr: bool = False, c_4: ndarray | None = None, ) -> tuple[ndarray, ndarray, ndarray, ndarray, ndarray]: """Compute the drag and the lift-to-drag ratio. Args: tc_ratio: The thickness-to-chord ratio. altitude: The altitude. mach: The Mach number. sweep: The wing sweep. wing_area: The wing surface area. ac_mass: The total aircraft weight. twist: The wing twist. esf: The engine scale factor. c_f: The friction coefficient. true_cstr: If ``True``, return the value of the constraint outputs. Otherwise, return the distance to the corresponding constraint thresholds. c_4: The minimum drag coefficient. If ``None``, use :meth:`.SobieskiBase.constants`. Returns: The aerodynamics outputs: - ``y_2``: The outputs of the aerodynamics analysis: - ``y_2[0]``: the lift, - ``y_2[1]``: the drag, - ``y_2[2]``: the lift/drag ratio, - ``y_21``: The coupling variable (lift) for the structure discipline, - ``y_23``: The coupling variable (drag) for the propulsion discipline, - ``y_24``: The coupling variable (lift/drag ratio) for the mission discipline, - ``g_2``: The pressure gradient to be constrained. """ c_4 = c_4 or self.constants[4] y_2 = zeros(3, dtype=self.dtype) y_23 = zeros(1, dtype=self.dtype) y_24 = zeros(1, dtype=self.dtype) y_21 = zeros(1, dtype=self.dtype) g_2 = zeros(1, dtype=self.dtype) self.__compute_rho_v(mach, altitude) rhov2 = self.__compute_rhov2() lift_coeff = self.__compute_cl(wing_area, ac_mass) # Modification of CDmin for ESF and Cf fo1 = self.base.compute_polynomial_approximation( self.__esf_cf_initial, array([esf, c_f], dtype=self.dtype), self.__flag1, self.__bound1, self.__ao_coeff_1, self.__ai_coeff_1, self.__aij_coeff_1, ) # Modification of drag_coeff for wing twist fo2 = self.base.compute_polynomial_approximation( self.__twist_initial, array([twist], dtype=self.dtype), self.__flag2, self.__bound2, self.__ao_coeff_2, self.__ai_coeff_2, self.__aij_coeff_2, ) cdmin = self.__compute_cd_min(tc_ratio, sweep, fo1, c_4=c_4) self.__compute_k_aero(mach, sweep) drag_coeff = self.__compute_cd(lift_coeff, fo2, cdmin) drag = 0.5 * rhov2 * drag_coeff * wing_area y_2[1] = drag y_2[2] = lift_coeff / drag_coeff y_2[0] = ac_mass y_23[0] = y_2[1] y_24[0] = y_2[2] y_21[0] = y_2[0] # Computation of total drag of A/C # adverse pressure gradient g_2[0] = self.base.compute_polynomial_approximation( self.__tc_initial, array([tc_ratio], dtype=self.dtype), self.__flag3, self.__bound3, self.__ao_coeff_3, self.__ai_coeff_3, self.__aij_coeff_3, ) # Custom Pgrad function: replace by a linear function # coeff_dir=(1.04-0.96)/(0.06-0.04)# = 4 # g_2[0] = coeff_dir*Z[0]+0.8 if not true_cstr: g_2[0] = g_2[0] - self.PRESSURE_GRADIENT_LIMIT return y_2, y_21, y_23, y_24, g_2 @staticmethod def __derive_liftoverdrag( cl_cd: ndarray, lift_jacobian: ndarray, drag_jacobian: ndarray, inv_drag: ndarray, ) -> ndarray: """Compute the Jacobian of the lift-over-drag ratio. Args: cl_cd: The lift-over-drag ratio. lift_jacobian: The Jacobian of the lift. drag_jacobian: The Jacobian of the drag. inv_drag: The inverse of the drag coefficient. Returns: The Jacobian of the lift-over-drag ratio. """ return inv_drag * (lift_jacobian - cl_cd * drag_jacobian) @staticmethod def __set_coupling_jacobian( jacobian: dict[str, dict[str, ndarray]], ) -> dict[str, dict[str, ndarray]]: """Set the Jacobian sub-structure related to output coupling variables.""" jacobian["y_21"]["x_2"] = jacobian["y_2"]["x_2"][0:1, :] jacobian["y_21"]["x_shared"] = jacobian["y_2"]["x_shared"][0:1, :] jacobian["y_21"]["y_12"] = jacobian["y_2"]["y_12"][0:1, :] jacobian["y_21"]["y_32"] = jacobian["y_2"]["y_32"][0:1, :] jacobian["y_21"]["c_4"] = jacobian["y_2"]["c_4"][0:1, :] jacobian["y_23"]["x_2"] = jacobian["y_2"]["x_2"][1:2, :] jacobian["y_23"]["x_shared"] = jacobian["y_2"]["x_shared"][1:2, :] jacobian["y_23"]["y_12"] = jacobian["y_2"]["y_12"][1:2, :] jacobian["y_23"]["y_32"] = jacobian["y_2"]["y_32"][1:2, :] jacobian["y_23"]["c_4"] = jacobian["y_2"]["c_4"][1:2, :] jacobian["y_24"]["x_2"] = jacobian["y_2"]["x_2"][2:3, :] jacobian["y_24"]["x_shared"] = jacobian["y_2"]["x_shared"][2:3, :] jacobian["y_24"]["y_12"] = jacobian["y_2"]["y_12"][2:3, :] jacobian["y_24"]["y_32"] = jacobian["y_2"]["y_32"][2:3, :] jacobian["y_24"]["c_4"] = jacobian["y_2"]["c_4"][2:3, :] return jacobian def __initialize_jacobian(self) -> dict[str, dict[str, ndarray]]: """Initialize the Jacobian structure. Returns: The Jacobian structure initialized with zeros. """ jacobian = {"y_2": {}, "g_2": {}, "y_21": {}, "y_23": {}, "y_24": {}} jacobian["y_2"]["x_shared"] = zeros((3, 6), dtype=self.dtype) jacobian["y_2"]["x_2"] = zeros((3, 1), dtype=self.dtype) jacobian["y_2"]["y_12"] = zeros((3, 2), dtype=self.dtype) jacobian["y_2"]["y_32"] = zeros((3, 1), dtype=self.dtype) jacobian["y_2"]["c_4"] = zeros((3, 1), dtype=self.dtype) jacobian["g_2"]["x_2"] = zeros((1, 1), dtype=self.dtype) jacobian["g_2"]["x_shared"] = zeros((1, 6), dtype=self.dtype) jacobian["g_2"]["y_12"] = zeros((1, 2), dtype=self.dtype) jacobian["g_2"]["y_32"] = zeros((1, 1), dtype=self.dtype) jacobian["g_2"]["c_4"] = zeros((1, 1), dtype=self.dtype) return jacobian
[docs] def linearize( self, x_shared: ndarray, y_12: ndarray, y_32: ndarray, x_2: ndarray, c_4: ndarray | None = None, ) -> dict[str, dict[str, ndarray]]: """Compute the Jacobian of the drag and lift-to-drag ratio. Args: x_shared: The values of the shared design variables, where ``x_shared[0]`` is the thickness/chord ratio, ``x_shared[1]`` is the altitude, ``x_shared[2]`` is the Mach number, ``x_shared[3]`` is the aspect ratio, ``x_shared[4]`` is the wing sweep and ``x_shared[5]`` is the wing surface area. y_12: The coupling variable from the structure disciplines, where ``y_12[0]`` is the total aircraft weight and ``y_12[1]`` is the wing twist. y_32: The coupling variable (engine scale factor) from the propulsion discipline, x_2: The friction coefficient. c_4: The minimum drag coefficient. If ``None``, use :meth:`.SobieskiBase.constants`. Returns: The Jacobian of the outputs. """ return self._linearize( x_shared[0], x_shared[1], x_shared[2], x_shared[4], x_shared[5], y_12[0], y_12[1], y_32[0], x_2[0], c_4=c_4, )
def _linearize( self, tc_ratio: float, altitude: float, mach: float, sweep: float, wing_area: float, ac_mass: float, twist: float, esf: float, c_f: float, c_4: float | None = None, ) -> dict[str, dict[str, ndarray]]: """Compute the Jacobian of the drag and lift-to-drag ratio. Args: tc_ratio: The thickness-to-chord ratio. altitude: The altitude. mach: The Mach number. sweep: The wing sweep. wing_area: The wing surface area. ac_mass: The total aircraft weight. twist: The wing twist. esf: The engine scale factor. c_f: The friction coefficient. c_4: The minimum drag coefficient. If ``None``, use :meth:`.SobieskiBase.constants`. Returns: The Jacobian of the outputs. """ jacobian = self.__initialize_jacobian() c_4 = c_4 or self.constants[4] self.__compute_rho_v(mach, altitude) rhov2 = self.__compute_rhov2() lift_coeff = ac_mass / (0.5 * rhov2 * wing_area) # Modification of drag_coeff_min for ESF and Cf ( fo1, ai_coeff1, aij_coeff1, s_shifted1, ) = self.base.derive_polynomial_approximation( self.__esf_cf_initial, array([esf, c_f], dtype=self.dtype), self.__flag1, self.__bound1, self.__ao_coeff_1, self.__ai_coeff_1, self.__aij_coeff_1, ) drag_coeff_incomp = c_4 cdmin = self.__compute_cd_min(tc_ratio, sweep, fo1, c_4=c_4) # Modification of drag_coeff for wing twist ( fo2, ai_coeff2, aij_coeff2, s_shifted2, ) = self.base.derive_polynomial_approximation( self.__twist_initial, array([twist], dtype=self.dtype), self.__flag2, self.__bound2, self.__ao_coeff_2, self.__ai_coeff_2, self.__aij_coeff_2, ) k_aero = self.__compute_k_aero(mach, sweep) drag_coeff = fo2 * (cdmin + k_aero * lift_coeff * lift_coeff) cl_cd = lift_coeff / drag_coeff fo1fo2 = fo1 * fo2 dy2dc4 = 0.5 * rhov2 * wing_area * fo1fo2 jacobian["y_2"]["c_4"][1, :] = dy2dc4 jacobian["y_2"]["c_4"][2, :] = -cl_cd / drag_coeff * fo1fo2 cl2 = lift_coeff * lift_coeff drag = 0.5 * rhov2 * drag_coeff * wing_area dyn_pressure = 0.5 * rhov2 dyn_force = dyn_pressure * wing_area inv_drag = 1.0 / drag # dDrag_dCf dadimcf_dcf = self.__compute_dadimcf_dcf(c_f) dy2dx2 = dyn_force * fo2 * drag_coeff_incomp * dadimcf_dcf dy2dx2 *= ( ai_coeff1[1] + aij_coeff1[1, 0] * s_shifted1[0] + aij_coeff1[1, 1] * s_shifted1[1] ) # dDrag_dCf jacobian["y_2"]["x_2"][1, 0:1] = dy2dx2 # d(Lift/Drag)_dCf dlod = self.__derive_liftoverdrag( cl_cd, jacobian["y_2"]["x_2"][0, 0], jacobian["y_2"]["x_2"][1, 0], inv_drag, ) jacobian["y_2"]["x_2"][2, 0:1] = dlod # dDrag/d(t/c) dy2dxs = dyn_force * fo2 * 3.05 * 5.0 / 3.0 * tc_ratio ** (2.0 / 3.0) dy2dxs *= (self.math.cos(sweep * DEG_TO_RAD)) ** 1.5 jacobian["y_2"]["x_shared"][1, 0] = dy2dxs # d(Drag)/dh drhov2_dh = self.__compute_drhov2_dh(altitude, mach) dcl_dh = self.__compute_dcl_dh(altitude, mach, wing_area, ac_mass) add_der = cl2 * drhov2_dh + rhov2 * 2.0 * lift_coeff * dcl_dh dy2dxs = (cdmin * drhov2_dh + k_aero * add_der) * 0.5 * wing_area * fo2 jacobian["y_2"]["x_shared"][1, 1:2] = dy2dxs # d(Drag)/dM dcd_dmach = self.__compute_dcd_dmach( altitude, mach, sweep, wing_area, ac_mass, fo2 ) drhov2_dmach = self.__compute_drhov2_dmach(altitude) jacobian["y_2"]["x_shared"][1, 2:3] = ( 0.5 * wing_area * (drag_coeff * drhov2_dmach + rhov2 * dcd_dmach) ) # d(Drag)/dsweep dcd_dsweep = self.__compute_dcd_dsweep(tc_ratio, mach, sweep, lift_coeff, fo2) jacobian["y_2"]["x_shared"][1, 4] = dyn_force * dcd_dsweep # d(Drag)/dsref dcd_dsef = self.__compute_dcd_dsref(wing_area, fo2) dy2dxs = drag / wing_area + dyn_force * dcd_dsef jacobian["y_2"]["x_shared"][1, 5:6] = dy2dxs for i in range(6): dy2dxs0i = jacobian["y_2"]["x_shared"][0, i] dy2dxs1i = jacobian["y_2"]["x_shared"][1, i] dlod = self.__derive_liftoverdrag(cl_cd, dy2dxs0i, dy2dxs1i, inv_drag) jacobian["y_2"]["x_shared"][2, i : i + 1] = dlod # d(Lift)/dWt jacobian["y_2"]["y_12"][0, 0] = 1.0 # d(Drag)/dWt jacobian["y_2"]["y_12"][1, 0] = 2.0 * ac_mass * k_aero * fo2 / dyn_force # d(Drag)/dtwist dadimtwist_dadim = self.__compute_dadimtwist_dtwist(twist) jacobian["y_2"]["y_12"][1, 1:2] = ( dyn_force * dadimtwist_dadim * (cdmin + k_aero * lift_coeff * lift_coeff) * (ai_coeff2[0] + aij_coeff2[0, 0] * s_shifted2[0]) ) # d(Lift/Drag)/dtwist for i in range(2): dy2dy120 = jacobian["y_2"]["y_12"][0, i] dy2dy121 = jacobian["y_2"]["y_12"][1, i] dy2dy122 = self.__derive_liftoverdrag(cl_cd, dy2dy120, dy2dy121, inv_drag) jacobian["y_2"]["y_12"][2, i : i + 1] = dy2dy122 # dDrag/dESF dadimesf_desf = self.__compute_dadimesf_desf(esf) jacobian["y_2"]["y_32"][1, 0:1] = ( dyn_force * fo2 * drag_coeff_incomp * dadimesf_desf * ( ai_coeff1[0] + aij_coeff1[0, 0] * s_shifted1[0] + aij_coeff1[0, 1] * s_shifted1[1] ) ) # d(Lift/Drag)/dtwist jacobian["y_2"]["y_32"][2, 0:1] = self.__derive_liftoverdrag( cl_cd, jacobian["y_2"]["y_32"][0, 0], jacobian["y_2"]["y_32"][1, 0], inv_drag, ) # d(dp/dx)/d(t/c) ( _, ai_coeff3, aij_coeff3, s_shifted3, ) = self.base.derive_polynomial_approximation( self.__tc_initial, array([tc_ratio], dtype=self.dtype), self.__flag3, self.__bound3, self.__ao_coeff_3, self.__ai_coeff_3, self.__aij_coeff_3, ) dadimtaper_dtaper = self.__compute_dadimtaper_dtaper(tc_ratio) jacobian["g_2"]["x_shared"][0, 0] = dadimtaper_dtaper * ( ai_coeff3[0] + aij_coeff3[0, 0] * s_shifted3[0] ) return self.__set_coupling_jacobian(jacobian)