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

if TYPE_CHECKING:
    from collections.abc import Iterable

    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: dict[str, float] | None = None, offset: float = 0.0, input_size: int | None = None, ) -> None: """ Args: input_names: The names of input variables. output_name: The name of the output variable. input_coefficients: The coefficients related to the input variables. If ``None``, use 1 for all the input variables. offset: The output value when all the input variables are equal to zero. input_size: The size of the inputs. If ``None``, the default inputs are initialized with size 1 arrays. """ # noqa: D205, D212, D415 super().__init__() self.input_grammar.update_from_names(input_names) self.output_grammar.update_from_names([output_name]) default_size = 1 if input_size is None else input_size self.default_input_data.update({ input_name: zeros(default_size) for input_name in input_names }) self.__coefficients = dict.fromkeys(input_names, 1.0) if input_coefficients: self.__coefficients.update(input_coefficients) self.__offset = offset self.__output_name = output_name def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None: output_data = {} 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.names: jac[input_name] = self.__coefficients[input_name] * identity