Source code for gemseo.disciplines.taylor
# 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.
"""A discipline to create Taylor polynomials from another discipline."""
from __future__ import annotations
from copy import deepcopy
from typing import TYPE_CHECKING
from gemseo.core.discipline import MDODiscipline
from gemseo.utils.constants import READ_ONLY_EMPTY_DICT
if TYPE_CHECKING:
from collections.abc import Mapping
from numpy.typing import NDArray
[docs]
class TaylorDiscipline(MDODiscipline):
r"""The first-order polynomial of a discipline.
The first-order polynomial
of a function :math:`f`
at an expansion point :math:`a`
is :math:`f(a)+\sum_{i=1}^d\frac{\partial f(a)}{\partial x_i}(x_i-a_i)`.
"""
__offset: Mapping[str, NDArray[float]]
"""The offset of the polynomial."""
def __init__(
self,
discipline: MDODiscipline,
input_data: Mapping[str, NDArray[float]] = READ_ONLY_EMPTY_DICT,
name: str = "",
) -> None:
"""
Args:
discipline: The discipline to be approximated by a Taylor polynomial.
input_data: The point of expansion.
If empty, use the default inputs of ``discipline``.
Raises:
ValueError: If neither ``input_data`` nor ``discipline.default_inputs``
is specified.
""" # noqa: D205 D212
input_names = set(discipline.get_input_data_names())
if (input_data and (input_data.keys() < input_names)) or (
not input_data and discipline.default_inputs.keys() < input_names
):
msg = (
"All the discipline input values must be "
"specified either in input_data or in discipline.default_inputs."
)
raise ValueError(msg)
discipline.linearize(compute_all_jacobians=True, input_data=input_data)
super().__init__(name=name)
self.input_grammar.update_from_names(input_names)
self.output_grammar.update_from_names(discipline.get_output_data_names())
self.default_inputs = input_data or discipline.default_inputs
self.__offset = {}
for output_name in self.get_output_data_names():
self.__offset[output_name] = discipline.local_data[output_name] - sum(
discipline.jac[output_name][input_name] @ input_value
for input_name, input_value in self.default_inputs.items()
)
self.jac = deepcopy(discipline.jac)
self._is_linearized = True
def _run(self) -> None:
input_data = self.get_input_data()
for output_name in self.get_output_data_names():
self.local_data[output_name] = self.__offset[output_name] + sum(
self.jac[output_name][input_name] @ input_value
for input_name, input_value in input_data.items()
)