Source code for gemseo.problems.mdo.sellar.sellar_system

# 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 - API and implementation and/or documentation
#        :author: Charlie Vanaret
#                 Francois Gallard
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""The system discipline of the customizable Sellar MDO problem."""

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import ClassVar

from numpy import array
from numpy import exp
from numpy import ones
from numpy import repeat
from scipy.sparse import block_diag
from scipy.sparse import diags
from scipy.sparse import eye

from gemseo.problems.mdo.sellar import WITH_2D_ARRAY
from gemseo.problems.mdo.sellar.base_sellar import BaseSellar
from gemseo.problems.mdo.sellar.variables import ALPHA
from gemseo.problems.mdo.sellar.variables import BETA
from gemseo.problems.mdo.sellar.variables import C_1
from gemseo.problems.mdo.sellar.variables import C_2
from gemseo.problems.mdo.sellar.variables import OBJ
from gemseo.problems.mdo.sellar.variables import X_1
from gemseo.problems.mdo.sellar.variables import X_2
from gemseo.problems.mdo.sellar.variables import X_SHARED
from gemseo.problems.mdo.sellar.variables import Y_1
from gemseo.problems.mdo.sellar.variables import Y_2

if TYPE_CHECKING:
    from collections.abc import Iterable

    from gemseo.typing import RealArray
    from gemseo.typing import StrKeyMapping


[docs] class SellarSystem(BaseSellar): """The discipline to compute the objective and constraints of the Sellar problem.""" _INPUT_NAMES: ClassVar[tuple[str, ...]] = ( X_SHARED, X_1, X_2, Y_1, Y_2, ALPHA, BETA, ) _OUTPUT_NAMES: ClassVar[tuple[str, ...]] = (OBJ, C_1, C_2) __eye_n: RealArray """The identity matrix of dimension n.""" __inv_n: float """The inverse of the size of the local and coupling variables.""" __inv_n_double: float """The double of the inverse of the size of the local and coupling variables.""" __ones_n: RealArray """The one vector.""" def __init__(self, n: int = 1) -> None: """ Args: n: The size of the local design variables and coupling variables. """ # noqa: D107 D205 D205 D212 D415 super().__init__(n) self.io.output_grammar.update_from_names(self._OUTPUT_NAMES) self.__inv_n = 1.0 / n self.__inv_n_double = self.__inv_n * 2.0 self.__eye_n = eye(n) self.__ones_n = ones((n, 1)) def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None: x_shared = input_data[X_SHARED] x_1 = input_data[X_1] x_2 = input_data[X_2] y_1 = input_data[Y_1] y_2 = input_data[Y_2] alpha = input_data[ALPHA] beta = input_data[BETA] if WITH_2D_ARRAY: # pragma: no cover x_shared = x_shared[0] else: defaults = self.io.input_grammar.defaults x_shared = x_shared.reshape((-1, defaults[X_SHARED].size)) x_1 = x_1.reshape((-1, defaults[X_1].size)) x_2 = x_2.reshape((-1, defaults[X_2].size)) y_1 = y_1.reshape((-1, defaults[Y_1].size)) y_2 = y_2.reshape((-1, defaults[Y_2].size)) alpha = alpha.reshape((-1, defaults[ALPHA].size)) beta = beta.reshape((-1, defaults[BETA].size)) obj = ( ((x_1**2).sum(-1) + (x_2**2).sum(-1) + (y_1**2).sum(-1)) * self.__inv_n + x_shared[..., 1] + exp(-y_2.mean(-1)) ) return { "obj": obj.ravel(), "c_1": (alpha - y_1**2).ravel(), "c_2": (y_2 - beta).ravel(), } def _compute_jacobian( self, input_names: Iterable[str] = (), output_names: Iterable[str] = (), ) -> None: input_data = self.io.data x_1 = input_data[X_1] x_2 = input_data[X_2] y_1 = input_data[Y_1] y_2 = input_data[Y_2] alpha = input_data[ALPHA] beta = input_data[BETA] n_samples = 1 if not WITH_2D_ARRAY: # pragma: no cover defaults = self.io.input_grammar.defaults x_1 = x_1.reshape((-1, defaults[X_1].size)) x_2 = x_2.reshape((-1, defaults[X_2].size)) y_1 = y_1.reshape((-1, defaults[Y_1].size)) y_2 = y_2.reshape((-1, defaults[Y_2].size)) alpha = alpha.reshape((-1, defaults[ALPHA].size)) beta = beta.reshape((-1, defaults[BETA].size)) n_samples = self._get_n_samples(x_1, x_2, y_1, y_2, alpha, beta) self._init_jacobian(input_names, output_names) if n_samples > 1 and not WITH_2D_ARRAY: jac = self.jac[C_1] ones_m_n_1 = block_diag([ones((self._n, 1))] * n_samples, format="csr") jac[Y_1] = block_diag([diags(-2.0 * y_1_i) for y_1_i in y_1], format="csr") jac[ALPHA] = ones_m_n_1 jac = self.jac[C_2] jac[Y_2] = block_diag([eye(self._n)] * n_samples, format="csr") jac[BETA] = -ones_m_n_1 jac = self.jac[OBJ] jac[X_1] = block_diag( [array([x * self.__inv_n_double]) for x in x_1], format="csr" ) jac[X_2] = block_diag( [array([x * self.__inv_n_double]) for x in x_2], format="csr" ) jac[X_SHARED] = block_diag([array([[0.0, 1.0]])] * n_samples, format="csr") jac[Y_1] = block_diag( [array([y * self.__inv_n_double]) for y in y_1], format="csr" ) jac[Y_2] = block_diag( [ array([ repeat(-exp(-y.sum() * self.__inv_n) * self.__inv_n, self._n) ]) for y in y_2 ], format="csr", ) else: jac = self.jac[C_1] jac[Y_1] = diags(-2.0 * y_1.ravel(), format="csr") jac[ALPHA] = self.__ones_n jac = self.jac[C_2] jac[Y_2] = self.__eye_n jac[BETA] = -self.__ones_n jac = self.jac[OBJ] jac[X_1] = array([x_1.ravel() * self.__inv_n_double]) jac[X_2] = array([x_2.ravel() * self.__inv_n_double]) jac[X_SHARED] = array([[0.0, 1.0]]) jac[Y_1] = array([y_1.ravel() * self.__inv_n_double]) exp_sum_y2 = -exp(-y_2.sum() * self.__inv_n) * self.__inv_n jac[Y_2] = array([repeat(exp_sum_y2, self._n)])