# -*- coding: utf-8 -*-
# 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
"""
SSBJ Structure computations
***************************
"""
from __future__ import division, unicode_literals
import logging
from math import pi
from numpy import append, array, ones, zeros
LOGGER = logging.getLogger(__name__)
DEG_TO_RAD = pi / 180.0
[docs]class SobieskiStructure(object):
"""Class defining structural analysis for Sobieski problem and related method to the
sructural problem such as disciplines computation, constraints, reference
optimum."""
DTYPE_COMPLEX = "complex128"
DTYPE_DOUBLE = "float64"
STRESS_LIMIT = 1.09
TWIST_UPPER_LIMIT = 1.04
TWIST_LOWER_LIMIT = 0.8
def __init__(self, sobieski_base):
"""Constructor.
:param sobieski_base: Sobieski problem
:type sobieski_base: SobieskiBase
"""
self.base = sobieski_base
self.constants = self.base.default_constants()
(
self.x_initial,
self.tc_initial,
self.half_span_initial,
self.aero_center_initial,
self.cf_initial,
self.mach_initial,
self.h_initial,
self.throttle_initial,
self.lift_initial,
self.twist_initial,
self.esf_initial,
) = self.base.get_initial_values()
self.dtype = self.base.dtype
self.math = self.base.math
def __set_coeff_twist(self, x_1, y_21, half_span, aero_center):
"""Prepare settings of polynomial function for wing twist computation.
:param x_1: local design variables
:type x_1: ndarray
:param y_21: coupling variables from aerodynamics
:type y_21: ndarray
:param half_span: half-span of wing
:type half_span: ndarray
:param aero_center: aerodynamic center
:type aero_center: ndarray
:returns: s_initial, s_new, flag, bound
:rtype: ndarray
"""
s_initial = array(
[
self.x_initial,
self.half_span_initial,
self.aero_center_initial,
self.lift_initial,
]
)
s_new = array([x_1[1], half_span, aero_center, y_21[0]], dtype=self.dtype)
flag = array([2, 4, 4, 3], dtype=self.dtype)
bound = array([0.25, 0.25, 0.25, 0.25], dtype=self.dtype)
return s_initial, s_new, flag, bound
def __set_coeff_secthick(self, x_1):
"""Prepare settings of polynomial function for sectional thickness.
:param x_1: local design variables
:type x_1: ndarray
:returns: s_initial, s_new, flag, bound
:rtype: ndarray
"""
s_initial = array([self.x_initial], dtype=self.dtype)
s_new = array([x_1[1]], dtype=self.dtype)
flag = array([1], dtype=self.dtype)
bound = array([0.008], dtype=self.dtype)
return s_initial, s_new, flag, bound
def __set_coeff_stress(self, x_1, x_shared, y_21, half_span, aero_center):
"""Prepare settings of polynomial function for stress.
:param x_1: local design variables
:type x_1: ndarray
:param x_shared: system design variables
:type x_shared: ndarray
:param y_21: coupling variables from aerodynamics
:type y_21: ndarray
:param half_span: half-span of wing
:type half_span: ndarray
:param aero_center: aerodynamic center
:type aero_center: ndarray
:returns: s_initial, s_new, flag, bound
:rtype: ndarray
"""
s_initial = array(
[
self.tc_initial,
self.lift_initial,
self.x_initial,
self.half_span_initial,
self.aero_center_initial,
],
dtype=self.dtype,
)
s_new = array(
[x_shared[0], y_21[0], x_1[1], half_span, aero_center], dtype=self.dtype
)
flag = array([4, 1, 4, 1, 1], dtype=self.dtype)
return s_initial, s_new, flag
def __compute_dadimcenter_dcenter(self, x_1):
"""Compute derivative of adim aero center wrt aero center.
:param x_1: local design variables
:type x_1: ndarray
:returns: derivative of adim aero center wrt aero center
:rtype: ndarray
"""
aero_center = self.base.compute_aero_center(x_1)
return self.base.derive_normalize_s(self.aero_center_initial, aero_center)
def __compute_dcenter_dlambda(self, x_1):
"""Compute derivative of aero center wrt wing taper ratio.
:param x_1: local design variables
:type x_1: ndarray
:returns: dR_dlambda
:rtype: ndarray
"""
dadimcenter_dcenter = self.__compute_dadimcenter_dcenter(x_1)
return dadimcenter_dcenter * 1.0 / (3.0 * (1.0 + x_1[0]) ** 2)
[docs] def compute_wing_weight(self, x_shared, x_1, y_21, linearize=False):
"""Compute wing weight.
:param x_shared: system design variables
:type x_shared: ndarray
:param y_21: coupling variables from aerodynamics
:type y_21: ndarray
:param x_1: local design variables
:type x_1: ndarray
:param linearize: Default value = False)
:returns: wing weight, wing_weight_coeff, value of poly. function
:rtype: ndarray
"""
s_initial, s_new, flag, bound = self.__set_coeff_secthick(x_1)
if linearize:
f_o, a_i, a_ij, s_shifted = self.base.derive_poly_approx(
s_initial, s_new, flag, bound
)
else:
f_o = self.base.poly_approx(s_initial, s_new, flag, bound)
wing_weight_coeff = (
0.0051
* ((y_21[0] * self.constants[2]) ** 0.557)
* (x_shared[5] ** 0.649)
* (x_shared[3] ** 0.5)
* (x_shared[0] ** -0.4)
* ((1 + x_1[0]) ** 0.1)
* ((self.math.cos(x_shared[4] * DEG_TO_RAD)) ** -1.0)
* ((0.1875 * x_shared[5]) ** 0.1)
)
wing_weight = wing_weight_coeff * f_o
if linearize:
return wing_weight, wing_weight_coeff, f_o, a_i, a_ij, s_shifted
return wing_weight
[docs] def compute_fuelwing_weight(self, x_shared):
"""Compute fuel wing weight.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: fuel wing weight
:rtype: ndarray
"""
thickness = self.base.compute_thickness(x_shared) # wing thickness
return (5.0 * x_shared[5] / 18.0) * (2.0 / 3.0 * thickness) * 42.5
[docs] def compute_dfuelwing_dtoverc(self, x_shared):
"""Compute fuel wing weight.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: fuel wing weight
:rtype: ndarray
"""
return 212.5 / 27.0 * x_shared[5] ** (3.0 / 2.0) / self.math.sqrt(x_shared[3])
[docs] @staticmethod
def compute_dfuelwing_dar(x_shared):
"""Compute fuel wing weight.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: fuel wing weight
:rtype: ndarray
"""
dfuelwing_dar = 212.5 / 27.0 * x_shared[5] ** (3.0 / 2.0)
dfuelwing_dar *= x_shared[0] * -0.5 * x_shared[3] ** (-3.0 / 2.0)
return dfuelwing_dar
[docs] def compute_dfuelwing_dsref(self, x_shared):
"""Compute fuel wing weight.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: fuel wing weight
:rtype: ndarray
"""
return (
637.5
/ 54.0
* x_shared[5] ** 0.5
* x_shared[0]
/ self.math.sqrt(x_shared[3])
)
def __compute_dhalfspan_dar(self, x_shared):
"""Compute partial derivative of half-span wrt aspect ratio.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: d(half_span)_d(AR)
:rtype: ndarray
"""
dadimspan_dspan = self.__compute_dadimspan_dspan(x_shared)
return (
dadimspan_dspan
* x_shared[5]
/ (8.0 * self.base.compute_half_span(x_shared))
)
def __compute_dadimspan_dsref(self, x_shared):
"""Compute partial derivative of half-span wrt wing surface.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: d(half_span)_d(sref)
:rtype: ndarray
"""
dadimspan_dspan = self.__compute_dadimspan_dspan(x_shared)
return (
dadimspan_dspan
* x_shared[3]
/ (8.0 * self.base.compute_half_span(x_shared))
)
def __compute_dadimspan_dspan(self, x_shared):
"""Compute partial derivative of adim half-span of polynomial function wrt half-
span.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: dadimhalf_span_dhalf_span
:rtype: ndarray
"""
half_span = self.base.compute_half_span(x_shared)
return self.base.derive_normalize_s(self.half_span_initial, half_span)
def __compute_dadimx_dx(self, x_1):
"""Compute partial derivative of adim sectional area of polynomial function wrt
sectional area.
:param x_1: local design variables
:type x_1: ndarray
:returns: dadimx_dx
:rtype: ndarray
"""
return self.base.derive_normalize_s(self.x_initial, x_1[1])
def __compute_dadimtaper_dtaper(self, x_shared):
"""Compute partial derivative of adim taper ratio area of polynomial function
wrt taper ratio.
:param x_shared: system design variables
:type x_shared: ndarray
:returns: dadimtaper_dtaper
:rtype: ndarray
"""
return self.base.derive_normalize_s(self.tc_initial, x_shared[0])
def __compute_dadimlift_dlift(self, y_21):
"""Compute partial derivative of adim lift area of polynomial function wrt
lift.
:param y_21: coupling design variables (weight)
:type y_21: ndarray
:returns: dadimtaper_dtaper
:rtype: ndarray
"""
return self.base.derive_normalize_s(self.lift_initial, y_21[0])
[docs] @staticmethod
def compute_weight_ratio(y_1):
"""Computation of weight ratio of Breguet formula.
:param y_1: shared variables coming from blackbox_structure
- y_1[0]: total aircraft weight
- y_1[1]: fuel weight
:type y_1: ndarray
:returns: Wt / (Wt -Wf)
:rtype: ndarray
"""
return y_1[0] / (y_1[0] - y_1[1])
[docs] def blackbox_structure(self, x_shared, y_21, y_31, x_1, true_cstr=False):
"""This function calculates the weight of the aircraft by structure and adds
them to obtain a total aircraft weight.
:param x_shared: shared design variable vector:
- x_shared[0]: thickness/chord ratio
- x_shared[1]: altitude
- x_shared[2]: Mach
- x_shared[3]: aspect ratio
- x_shared[4]: wing sweep
- x_shared[5]: wing surface area
:type x_shared: ndarray
:param y_21: lift
:type y_21: ndarray
:param y_31: engine weight
:type y_31: ndarray
:param x_1: weight design variables:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param true_cstr: Default value = False)
:returns: g_1,y_1, y_11, y_12, y_14:
- g_1: vector of constraints for weight analysis
- g_1[0] to g_1[4]: stress on wing
- g_1[5]: wing twist as constraint
- y_1: weight analysis outputs
- y_1[0]: total aircraft weight
- y_1[1]: fuel weight
- y_1[2]: wing twist
- y_12: shared variables used for aero computations
(blackbox_aerodynamics)
- y_12[0]: total aircraft weight
- y_12[1]: wing twist
- y_14: shared variables used for range computation
(BlackBoxMission)
- y_14[0]: total aircraft weight
- y_14[1]: fuel weight
:rtype: ndarray, ndarray, ndarray, ndarray
"""
y_12 = zeros(2, dtype=self.dtype)
y_14 = zeros(2, dtype=self.dtype)
y_1, y_11 = self.poly_structure(x_shared, x_1, y_21, y_31)
g_1 = self.poly_structure_constraints(x_shared, x_1, y_21)
g_1[5] = y_1[2]
# Coupling variables
y_12[1] = y_1[2]
y_12[0] = y_1[0]
y_14[0] = y_1[0]
y_14[1] = y_1[1]
if not true_cstr:
g_1 = append(
g_1[0:5] - self.STRESS_LIMIT,
(g_1[5] - self.TWIST_UPPER_LIMIT, self.TWIST_LOWER_LIMIT - g_1[5]),
)
return y_1, y_11, y_12, y_14, g_1
def __initialize_jacobian(self):
"""Initialization of jacobian matrix.
:returns: jacobian
:rtype: dict of dict of ndarray
"""
# Jacobian matrix as a dictionary
jacobian = {"y_1": {}, "g_1": {}, "y_12": {}, "y_14": {}, "y_11": {}}
n_y1 = 3
jacobian["y_1"]["x_shared"] = zeros((n_y1, 6), dtype=self.dtype)
jacobian["y_1"]["x_1"] = zeros((n_y1, 2), dtype=self.dtype)
jacobian["y_1"]["y_21"] = zeros((n_y1, 1), dtype=self.dtype)
jacobian["y_1"]["y_31"] = zeros((n_y1, 1), dtype=self.dtype)
n_y11 = 1
jacobian["y_11"]["x_shared"] = zeros((n_y11, 6), dtype=self.dtype)
jacobian["y_11"]["x_1"] = zeros((n_y11, 2), dtype=self.dtype)
jacobian["y_11"]["y_21"] = zeros((n_y11, 1), dtype=self.dtype)
jacobian["y_11"]["y_31"] = zeros((n_y11, 1), dtype=self.dtype)
n_y12 = 2
jacobian["y_12"]["x_shared"] = zeros((n_y12, 6), dtype=self.dtype)
jacobian["y_12"]["x_1"] = zeros((n_y12, 2), dtype=self.dtype)
jacobian["y_12"]["y_21"] = zeros((n_y12, 1), dtype=self.dtype)
jacobian["y_12"]["y_31"] = zeros((n_y12, 1), dtype=self.dtype)
for key, jac_val in jacobian["y_12"].items():
jacobian["y_14"][key] = jac_val
return jacobian
[docs] def derive_blackbox_structure(self, x_shared, y_21, y_31, x_1, true_cstr=False):
"""Compute jacobian matrix of structural analysis y_1 is the vector of
structural outputs and g_1 are the structural constraints:
- y_1[0]: total aircraft weight
- y_1[1]: fuel weight
- y_1[2]: wing twist
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param y_21: coupling variable from aerodynamics (lift)
:type y_21: ndarray
:param y_31: coupling variable from propulsion (Engine weight)
:type y_31: ndarray
:param x_1: structure design variable vector
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param true_cstr: Default value = False)
:returns: jacobian : Jacobian matrix
:rtype: dict(dict(ndarray))
"""
# Jacobian matrix as a dictionary
jacobian = self.__initialize_jacobian()
jacobian = self.derive_poly_structure(jacobian, x_shared, x_1, y_21, y_31)
# Stress constraints
jacobian = self.derive_constraints(jacobian, x_shared, x_1, y_21, true_cstr)
# Twist constraints
jacobian["g_1"]["x_1"][5, :] = jacobian["y_1"]["x_1"][2, :]
jacobian["g_1"]["x_shared"][5, :] = jacobian["y_1"]["x_shared"][2, :]
jacobian["g_1"]["y_21"][5, :] = jacobian["y_1"]["y_21"][2, :]
jacobian["g_1"]["y_31"][5, :] = jacobian["y_1"]["y_31"][2, :]
if not true_cstr:
jacobian["g_1"]["x_1"][6, :] = -jacobian["y_1"]["x_1"][2, :]
jacobian["g_1"]["x_shared"][6, :] = -jacobian["y_1"]["x_shared"][2, :]
jacobian["g_1"]["y_21"][6, :] = -jacobian["y_1"]["y_21"][2, :]
jacobian["g_1"]["y_31"][6, :] = -jacobian["y_1"]["y_31"][2, :]
# Coupling variables
jacobian = self.__set_coupling_jacobian(jacobian)
return jacobian
@staticmethod
def __set_coupling_jacobian(jacobian):
"""Set jacobian of coupling variables."""
# Coupling variables
for der_v, jac_loc in jacobian["y_1"].items():
jacobian["y_12"][der_v][1, :] = jac_loc[2, :]
jacobian["y_12"][der_v][0, :] = jac_loc[0, :]
jacobian["y_14"][der_v] = jac_loc[0:2, :]
return jacobian
[docs] def derive_poly_structure(self, jacobian, x_shared, x_1, y_21, y_31):
"""Compute derivatives of structural variables from a polynomial approximation.
:param jacobian: jacobian matrix
:type jacobian: dict(dict(ndarray))
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param x_1: structure design variable vector:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param y_21: coupling variable from aerodynamics
:type y_21: ndarray
:param y_31: coupling variable from propulsion
:type y_31: ndarray
:returns: updated jacobian matrix
:rtype: dict(dict(ndarray))
"""
half_span = self.base.compute_half_span(x_shared) # 1/2 span
aero_center = self.base.compute_aero_center(x_1)
# wing aero. center location
y_1, _ = self.poly_structure(x_shared, x_1, y_21, y_31)
# Derivation of Fuel Weight Wf=y_1[1]
# dWf/dx=dWfd(lamda= =0
# jacobian['y_1']['x_1'][1, :] = 0.0 # Fuel weigh does not depend on x_1
# dWf/d(t/c)
jacobian["y_1"]["x_shared"][1, 0] = self.compute_dfuelwing_dtoverc(x_shared)
# jacobian['y_1']['x_shared'][1, 1] = 0.0 # dWf/dh
# jacobian['y_1']['x_shared'][1, 2] = 0.0 # dWf/dM
# dWf/d(AR)
jacobian["y_1"]["x_shared"][1, 3] = self.compute_dfuelwing_dar(x_shared)
# jacobian['y_1']['x_shared'][1, 4] = 0.0 # dWf/d(sweep)
# dWf/dsref
jacobian["y_1"]["x_shared"][1, 5] = self.compute_dfuelwing_dsref(x_shared)
# dWf_dLift= 0.0
# dWf_dWe= 0.0
# Derivation of wing twist = y_1[2]
s_initial1, s_1, flag1, bound1 = self.__set_coeff_twist(
x_1, y_21, half_span, aero_center
)
_, a_i, a_ij, s_shifted = self.base.derive_poly_approx(
s_initial1, s_1, flag1, bound1
)
dcenter_dlambda = self.__compute_dcenter_dlambda(x_1)
jacobian["y_1"]["x_1"][2, 0] = dcenter_dlambda * (
a_i[2]
+ a_ij[2, 0] * s_shifted[0]
+ a_ij[2, 1] * s_shifted[1]
+ a_ij[2, 2] * s_shifted[2]
+ a_ij[2, 3] * s_shifted[3]
)
dadimx_dx = self.__compute_dadimx_dx(x_1)
jacobian["y_1"]["x_1"][2, 1] = dadimx_dx * (
a_i[0]
+ a_ij[0, 0] * s_shifted[0]
+ a_ij[0, 1] * s_shifted[1]
+ a_ij[0, 2] * s_shifted[2]
+ a_ij[0, 3] * s_shifted[3]
)
# J['y_1']['x_shared'][2, 0] = 0.0 # dtwist_d(t/c)
# J['y_1']['x_shared'][2, 1] = 0.0 # dtwist_dh
# J['y_1']['x_shared'][2, 2] = 0.0 # dtwist_dM
# dLbar_dL = 1.0 / self.L_initial # Half-span
dhalfspan_dar = self.__compute_dhalfspan_dar(x_shared)
jacobian["y_1"]["x_shared"][2, 3] = dhalfspan_dar * (
a_i[1]
+ a_ij[1, 0] * s_shifted[0]
+ a_ij[1, 1] * s_shifted[1]
+ a_ij[1, 2] * s_shifted[2]
+ a_ij[1, 3] * s_shifted[3]
)
# jacobian['y_1']['x_shared'][2, 4] = 0.0 # dtwistdsweep
dhalfspan_dsref = self.__compute_dadimspan_dsref(x_shared)
jacobian["y_1"]["x_shared"][2, 5] = dhalfspan_dsref * (
a_i[1]
+ a_ij[1, 0] * s_shifted[0]
+ a_ij[1, 1] * s_shifted[1]
+ a_ij[1, 2] * s_shifted[2]
+ a_ij[1, 3] * s_shifted[3]
)
dadimlift_dlift = self.__compute_dadimlift_dlift(y_21)
jacobian["y_1"]["y_21"][2, 0] = dadimlift_dlift * (
a_i[3]
+ a_ij[0, 3] * s_shifted[0]
+ a_ij[1, 3] * s_shifted[1]
+ a_ij[2, 3] * s_shifted[2]
)
# jacobian['y_1']['y_31'][2, 0] = 0.0 # dtwist_dWengine
# Derivation of total weight = y_1[0] (requires derivation of wing weight)
# Calculation of wingbox X-sectional thickness
wing_w, ww_coeff, _, a_i, a_ij, s_shifted = self.compute_wing_weight(
x_shared, x_1, y_21, linearize=True
)
# dtotal_weight_d(t/c)
jacobian["y_1"]["x_shared"][0, 0] = (
-0.4 * x_shared[0] ** (-1.0) * wing_w + jacobian["y_1"]["x_shared"][1, 0]
)
# dtotal_weight_dh = 0.0
# dtotal_weight_dM = 0.0
dy11_dz = jacobian["y_1"]["x_shared"]
# dtotal_weight_dAR
dy11_dz[0, 3] = 0.5 * x_shared[3] ** (-1.0) * wing_w
dy11_dz[0, 3] += jacobian["y_1"]["x_shared"][1, 3]
# d1total_weight_dsweep
dy11_dz[0, 4] = DEG_TO_RAD
dy11_dz[0, 4] *= self.math.sin(x_shared[4] * DEG_TO_RAD) * wing_w
dy11_dz[0, 4] /= self.math.cos(x_shared[4] * DEG_TO_RAD)
# dtotal_weight_dsref
dy11_dz[0, 5] = 0.749 * x_shared[5] ** (-1.0) * wing_w
dy11_dz[0, 5] += jacobian["y_1"]["x_shared"][1, 5]
# dtotal_weight_d(lambda)
jacobian["y_1"]["x_1"][0, 0] = (
0.1 * (1 + x_1[0]) ** (-1.0) * wing_w + jacobian["y_1"]["x_1"][1, 0]
)
# dtotal_weight_dx
jacobian["y_1"]["x_1"][0, 1] = ww_coeff * dadimx_dx
jacobian["y_1"]["x_1"][0, 1] *= a_i[0] + a_ij[0, 0] * s_shifted[0]
jacobian["y_1"]["x_1"][0, 1] += jacobian["y_1"]["x_1"][1, 1]
# dtotal_weight_dLift
jacobian["y_1"]["y_21"][0, 0] = 0.557 * y_21[0] ** -1 * wing_w
# dtotal_weight_dWe
jacobian["y_1"]["y_31"][0, 0] = 1.0
# y_11 = log(y_1[0])- log((y_1[0] - y_1[1]))
# dy_11 = dy_1[0]/y_1[0] +(dy_1[1]-dy_1[0])/(y_1[0] - y_1[1])
# y_11[3] = y_1[0] / (y_1[0] - y_1[1])
# y_11 = array([self.math.log( y_1[0] / (y_1[0] - y_1[1]))])
# return arr
for out_v, jac_y_1 in jacobian["y_1"].items():
val = jac_y_1[0, :] / y_1[0]
val -= (jac_y_1[0, :] - jac_y_1[1, :]) / (y_1[0] - y_1[1])
jacobian["y_11"][out_v][0, :] = val
return jacobian
[docs] def derive_constraints(self, jacobian, x_shared, x_1, y_21, true_cstr=False):
"""Compute derivative structural constraints from a polynomial approximation.
:param jacobian: Jacobian matrix
:type jacobian: dict(dict(ndarray))
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param x_1: structure design variable vector:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param y_21: coupling variable from aerodynamics
:type y_21: ndarray
:param true_cstr: Default value = False)
:returns: updated Jacobian matrix J
:rtype: dict(dict(ndarray))
"""
if true_cstr is False:
n_g = 7
else:
n_g = 6
jacobian["g_1"]["x_shared"] = zeros((n_g, 6), dtype=self.dtype)
jacobian["g_1"]["x_1"] = zeros((n_g, 2), dtype=self.dtype)
jacobian["g_1"]["y_21"] = zeros((n_g, 1), dtype=self.dtype)
jacobian["g_1"]["y_31"] = zeros((n_g, 1), dtype=self.dtype)
half_span = self.base.compute_half_span(x_shared) # 1/2 span
aero_center = self.base.compute_aero_center(x_1)
# Stress constraints
s_initial, s_new, flag = self.__set_coeff_stress(
x_1, x_shared, y_21, half_span, aero_center
)
ones_mat = ones(5, dtype=self.dtype)
for i in range(5):
bound = 0.1 * ones_mat + i * 0.05 * ones_mat
_, a_i, a_ij, s_shifted = self.base.derive_poly_approx(
s_initial, s_new, flag, bound
)
dg_dx_1, dg_dz, dg_dy_21, dg_dy_31 = self.__der_constraint(
x_1, x_shared, y_21, a_i, a_ij, s_shifted
)
jacobian["g_1"]["x_1"][i, :] = dg_dx_1[:]
jacobian["g_1"]["x_shared"][i, :] = dg_dz[:]
jacobian["g_1"]["y_21"][i, :] = dg_dy_21[:]
jacobian["g_1"]["y_31"][i, :] = dg_dy_31[:]
return jacobian
[docs] def poly_structure(self, x_shared, x_1, y_21, y_31):
"""Compute structural variables from a polynomial approximation.
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param x_1: structure design variable vector:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param y_21: coupling variable from aerodynamics
:type y_21: ndarray
:param y_31: coupling variable from propulsion: engine weight
:type y_31: ndarray
:returns: y_1, g_1: structural variables and structural constraints:
- y_1[0]: total aircraft weight
- y_1[1]: fuel weight
- y_1[2]: wing twist
:rtype: ndarray, ndarray
"""
y_1 = zeros(3, dtype=self.dtype)
# t = self.base.compute_thickness(x_shared) # wing thickness
# Calculation of wing twist
y_1[2] = self.compute_wing_twist(x_shared, x_1, y_21)
# Calculation of wingbox X-sectional thickness
wing_w = self.compute_wing_weight(x_shared, x_1, y_21)
fuel_wing_weight = self.compute_fuelwing_weight(x_shared)
y_1_i1 = self.constants[0] + fuel_wing_weight
y_1[1] = y_1_i1 # Fuel weight
y_1[0] = self.constants[1] + wing_w + y_1_i1 + y_31[0]
# This is the mass term in the Breguet range equation.
y_11 = array([self.math.log(self.compute_weight_ratio(y_1))], dtype=self.dtype)
return y_1, y_11
[docs] def compute_wing_twist(self, x_shared, x_1, y_21):
"""Compute the wing twist (a.k.a. theta, y_12[1], y_1[2]).
:param x_shared: shared design variables
:type x_shared: ndarray
:param x_1: structure design variable
:type x_1: ndarray
:param y_21: coupling variable from aerodynamics
:type y_21: ndarray
:returns: the wing twist
:rtype: float
"""
# Compute the half span and the aerodynamic center
half_span = self.base.compute_half_span(x_shared)
aero_center = self.base.compute_aero_center(x_1)
s_initial1, s_1, flag1, bound1 = self.__set_coeff_twist(
x_1, y_21, half_span, aero_center
)
return self.base.poly_approx(s_initial1, s_1, flag1, bound1)
def __der_constraint(self, x_1, x_shared, y_21, a_i, a_ij, s_shifted):
"""Compute derivative of a structural constraints from a polynomial
approximation on a section.
:param x_1: structure design variable vector:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param a_i: linear polynomial coeff
:type a_i: ndarray
:param a_ij: quadratic polynomial coeff
:type a_ij: ndarray
:param s_shifted: normalized design variables
(local at polynomial function)
:type s_shifted: ndarray
:returns: update Jacobian matrix
:rtype: dict(dict(ndarrays))
"""
dcenter_dlambda = self.__compute_dcenter_dlambda(x_1)
dadimx_dx = self.__compute_dadimx_dx(x_1)
dadimhalfspan_dar = self.__compute_dhalfspan_dar(x_shared)
dadimhalfspan_dsref = self.__compute_dadimspan_dsref(x_shared)
dadimtaper_dtaper = self.__compute_dadimtaper_dtaper(x_shared)
dadim_lift_dlift = self.__compute_dadimlift_dlift(y_21)
dg_dz = zeros((1, 6), dtype=self.dtype)
dg_dx_1 = zeros((1, 2), dtype=self.dtype)
dg_dy_21 = zeros((1, 1), dtype=self.dtype)
dg_dy_31 = zeros((1, 1), dtype=self.dtype)
dg_dx_1[0, 0] = dcenter_dlambda * (
a_i[4]
+ a_ij[4, 0] * s_shifted[0]
+ a_ij[4, 1] * s_shifted[1]
+ a_ij[4, 2] * s_shifted[2]
+ a_ij[4, 3] * s_shifted[3]
+ a_ij[4, 4] * s_shifted[4]
)
# dsigma/dx
dg_dx_1[0, 1] = dadimx_dx * (
a_i[2]
+ a_ij[2, 0] * s_shifted[0]
+ a_ij[2, 1] * s_shifted[1]
+ a_ij[2, 2] * s_shifted[2]
+ a_ij[2, 3] * s_shifted[3]
+ a_ij[2, 4] * s_shifted[4]
)
# dsigma1/d(t/c)
dg_dz[0, 0] = dadimtaper_dtaper * (
a_i[0]
+ a_ij[0, 0] * s_shifted[0]
+ a_ij[0, 1] * s_shifted[1]
+ a_ij[0, 2] * s_shifted[2]
+ a_ij[0, 3] * s_shifted[3]
+ a_ij[0, 4] * s_shifted[4]
)
# dsigma1/dh
dg_dz[0, 1] = 0.0
# dsigma1/dM
dg_dz[0, 2] = 0.0
# dsigma1/dAR
dg_dz[0, 3] = dadimhalfspan_dar * (
a_i[3]
+ a_ij[3, 0] * s_shifted[0]
+ a_ij[3, 1] * s_shifted[1]
+ a_ij[3, 2] * s_shifted[2]
+ a_ij[3, 3] * s_shifted[3]
+ a_ij[3, 4] * s_shifted[4]
)
# dsigma1/dsweep
dg_dz[0, 4] = 0.0
# dsigma1/dsref
dg_dz[0, 5] = dadimhalfspan_dsref * (
a_i[3]
+ a_ij[3, 0] * s_shifted[0]
+ a_ij[3, 1] * s_shifted[1]
+ a_ij[3, 2] * s_shifted[2]
+ a_ij[3, 3] * s_shifted[3]
+ a_ij[3, 4] * s_shifted[4]
)
# dsigma1/dLift
dg_dy_21[0, 0] = dadim_lift_dlift * (
a_i[1]
+ a_ij[1, 0] * s_shifted[0]
+ a_ij[1, 1] * s_shifted[1]
+ a_ij[1, 2] * s_shifted[2]
+ a_ij[1, 3] * s_shifted[3]
+ a_ij[1, 4] * s_shifted[4]
)
# dsigma1/dWengine
dg_dy_31[0, 0] = 0.0
return dg_dx_1, dg_dz, dg_dy_21, dg_dy_31
[docs] def poly_structure_constraints(self, x_shared, x_1, y_21):
"""Compute structural constraints from a polynomial approximation.
:param x_shared: shared design variable vector
:type x_shared: ndarray
:param x_1: structure design variable vector:
- x_1[0]: wing taper ratio
- x_1[1]: wingbox x-sectional area as poly. funct
:type x_1: ndarray
:param y_21: coupling variable from aerodynamics
:type y_21: ndarray
:returns: g_1: structural constraints
:rtype: ndarray
"""
half_span = self.base.compute_half_span(x_shared) # 1/2 span
# wing aero. center location
aero_center = self.base.compute_aero_center(x_1)
g_1 = zeros(6, dtype=self.dtype)
s_initial, s_new, flag = self.__set_coeff_stress(
x_1, x_shared, y_21, half_span, aero_center
)
loc_ones = ones(5, dtype=self.dtype)
for i in range(5):
bound = 0.1 * loc_ones + i * 0.05 * loc_ones
g_1[i] = self.base.poly_approx(s_initial, s_new, flag, bound)
return g_1