Source code for gemseo.disciplines.linear_combination

# 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.
"""Discipline computing a linear combination of its inputs."""

from __future__ import annotations

from typing import TYPE_CHECKING

from numpy import zeros
from scipy.sparse import eye

from gemseo.core.discipline import Discipline
from gemseo.utils.constants import READ_ONLY_EMPTY_DICT

if TYPE_CHECKING:
    from collections.abc import Iterable
    from collections.abc import Mapping

    from gemseo.typing import StrKeyMapping


[docs] class LinearCombination(Discipline): r"""Discipline computing a linear combination of its inputs. The user can specify the coefficients related to the variables as well as the offset. E.g., a discipline computing the output :math:`y` from :math:`d` inputs :math:`x_1,\ldots,x_d` with the function :math:`f(x_1,\ldots,x_d)=a_0+\sum_{i=1}^d a_i x_i`. When the offset :math:`a_0` is equal to 0 and the coefficients :math:`a_1,\ldots,a_d` are equal to 1, the discipline simply sums the inputs. Notes: By default, the :class:`.LinearCombination` simply sums the inputs. Examples: >>> discipline = LinearCombination(["alpha", "beta", "gamma"], "delta", input_coefficients={"alpha": 1.,"beta": 2.,"gamma": 3.}) >>> input_data = {"alpha": array([1.0]), "beta": array([1.0]), "gamma": array([1.0])} >>> discipline.execute(input_data) >>> delta = discipline.io.data["delta"] # delta = array([6.]) """ __offset: float r"""The offset :math:`a_0` in :math:`a_0+\sum_{i=1}^d a_i x_i`.""" __coefficients: dict[str, float] r"""The coefficients :math:`a_1,\ldots,a_d` in :math:`a_0+\sum_{i=1}^d a_i x_i`.""" __output_name: str """The name of the output.""" def __init__( self, input_names: Iterable[str], output_name: str, input_coefficients: Mapping[str, float] = READ_ONLY_EMPTY_DICT, offset: float = 0.0, input_size: int = 1, average: bool = False, ) -> None: """ Args: input_names: The names of the input variables. output_name: The name of the output variable. input_coefficients: The coefficients related to the input variables. If empty, use 1 for all the :math:`d` input variables when ``average`` is ``False``. or :math:`1/d` when ``average`` is ``True``. offset: The output value when all the input variables are equal to zero. input_size: The size of the inputs. average: Whether to average the inputs when ``input_coefficients`` is empty, """ # noqa: D205, D212, D415 super().__init__() input_grammar = self.io.input_grammar input_grammar.update_from_names(input_names) self.io.output_grammar.update_from_names((output_name,)) # TODO: API: remove this line in GEMSEO v7. default_size = 1 if input_size is None else input_size input_grammar.defaults.update({ input_name: zeros(default_size) for input_name in input_names }) if input_coefficients: self.__coefficients = dict.fromkeys(input_names, 1.0) self.__coefficients.update(input_coefficients) elif average: self.__coefficients = dict.fromkeys(input_names, 1.0 / len(input_grammar)) else: self.__coefficients = dict.fromkeys(input_names, 1.0) self.__offset = offset self.__output_name = output_name def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None: output_data = {self.__output_name: self.__offset} for input_name, input_value in input_data.items(): output_data[self.__output_name] += ( self.__coefficients[input_name] * input_value ) return output_data def _compute_jacobian( self, input_names: Iterable[str] = (), output_names: Iterable[str] = (), ) -> None: self._init_jacobian( input_names, output_names, init_type=self.InitJacobianType.SPARSE ) identity = eye(self.io.data[self.__output_name].size, format="csr") jac = self.jac[self.__output_name] for input_name in self.io.input_grammar: jac[input_name] = self.__coefficients[input_name] * identity