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 typing import TYPE_CHECKING
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 numpy.typing import NDArray
from gemseo.typing import StrKeyMapping
[docs]
class TaylorDiscipline(Discipline):
r"""The first-order Taylor polynomial of a discipline.
The first-order Taylor 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)`.
The default output values of this discipline correspond
to the first term :math:`f(a)` of this polynomial
and can be accessed using ``taylor_discipline.io.output_grammar.defaults``.
"""
__offset: Mapping[str, NDArray[float]]
"""The offset of the polynomial."""
def __init__(
self,
discipline: Discipline,
input_data: Mapping[str, NDArray[float]] = READ_ONLY_EMPTY_DICT,
input_names: Iterable[str] = (),
output_names: Iterable[str] = (),
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``.
input_names: The names of the input variables of interest.
If empty, use all the input variables of ``discipline``.
output_names: The names of the output variables of interest.
If empty, use all the output variables of ``discipline``.
Raises:
ValueError: If neither ``input_data`` nor
``discipline.io.input_grammar.defaults`` is specified.
""" # noqa: D205 D212
all_input_names = set(discipline.io.input_grammar)
input_names = sorted(input_names or all_input_names)
output_names = output_names or discipline.io.output_grammar
input_data = input_data or discipline.io.input_grammar.defaults
if input_data.keys() < all_input_names:
msg = (
"All the discipline input values must be specified either in "
"input_data or in discipline.io.input_grammar.defaults."
)
raise ValueError(msg)
discipline.linearize(compute_all_jacobians=True, input_data=input_data)
super().__init__(name=name)
self.io.input_grammar.update_from_names(input_names)
self.io.output_grammar.update_from_names(output_names)
descriptions = discipline.io.input_grammar.descriptions
self.io.input_grammar.descriptions = {
k: descriptions[k] for k in input_names if k in descriptions
}
descriptions = discipline.io.output_grammar.descriptions
self.io.output_grammar.descriptions = {
k: descriptions[k] for k in output_names if k in descriptions
}
self.io.input_grammar.defaults = {k: input_data[k] for k in input_names}
self.__offset = {}
data = discipline.io.data
input_defaults = self.io.input_grammar.defaults
output_defaults = self.io.output_grammar.defaults
for output_name in output_names:
default_output_value = data[output_name]
jac = discipline.jac[output_name]
self.__offset[output_name] = default_output_value - sum(
jac[input_name] @ input_defaults[input_name]
for input_name in input_names
)
output_defaults[output_name] = default_output_value
discipline_jac = discipline.jac
self.jac = {
output_name: {
input_name: __jac.copy()
for input_name, __jac in _jac.items()
if input_name in input_names
}
for output_name, _jac in discipline_jac.items()
if output_name in output_names
}
self._has_jacobian = True
def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None:
output_data = {}
for output_name in self.io.output_grammar:
output_data[output_name] = self.__offset[output_name] + sum(
self.jac[output_name][input_name] @ input_data[input_name]
for input_name in sorted(input_data)
)
return output_data