Source code for gemseo.problems.mdo.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

from math import pi
from typing import TYPE_CHECKING

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

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

if TYPE_CHECKING:
    from numpy import ndarray

    from gemseo.problems.mdo.sobieski.core.utils import SobieskiBase


[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: # noqa: D107 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 wrt 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 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: r"""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: r"""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: r"""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) 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] -= 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)