# 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 - initial API and implementation and/or initial
# documentation
# :author: Francois Gallard, Charlie Vanaret
# OTHER AUTHORS - MACROSCOPIC CHANGES
"""A class to create :class:`.MDOFunction` objects from an :class:`.Discipline`."""
from __future__ import annotations
from typing import TYPE_CHECKING
from gemseo.core.mdo_functions.discipline_adapter import DisciplineAdapter
from gemseo.utils.constants import READ_ONLY_EMPTY_DICT
if TYPE_CHECKING:
from collections.abc import Mapping
from collections.abc import MutableMapping
from collections.abc import Sequence
from numpy import ndarray
from gemseo.core.discipline import Discipline
from gemseo.core.grammars.base_grammar import BaseGrammar
[docs]
class DisciplineAdapterGenerator:
"""A generator of discipline adapter.
Given a discipline,
an :class:`.DisciplineAdapter` computes specific outputs from specific inputs.
"""
discipline: Discipline
"""The discipline from which to generate discipline adapters."""
_names_to_sizes: MutableMapping[str, int]
"""The names of the inputs bound to their sizes, if known."""
def __init__(
self,
discipline: Discipline,
names_to_sizes: MutableMapping[str, int] = READ_ONLY_EMPTY_DICT,
) -> None:
"""
Args:
discipline: The discipline from which to generate discipline adapters.
names_to_sizes: The sizes of the input variables.
If empty,
determine them from the default inputs and local data of the discipline.
""" # noqa: D205, D212, D415
self.discipline = discipline
self._names_to_sizes = names_to_sizes or {}
[docs]
def get_function(
self,
input_names: Sequence[str],
output_names: Sequence[str],
default_input_data: Mapping[str, ndarray] = READ_ONLY_EMPTY_DICT,
is_differentiable: bool = True,
differentiated_input_names_substitute: Sequence[str] = (),
) -> DisciplineAdapter:
"""Build a function executing a discipline for some inputs and outputs.
Args:
input_names: The discipline input names defining the function input vector.
If empty,
use all the discipline inputs.
output_names: The discipline output names
defining the function output vector.
If empty,
use all the discipline outputs.
default_input_data: The default values of the input variables.
If empty,
use the default input values of the discipline.
is_differentiable: Whether the function is differentiable.
differentiated_input_names_substitute: The names of the inputs
with respect to which to differentiate the functions.
If empty,
use ``input_names``.
This argument is not used when ``is_differentiable`` is ``False``.
Returns:
The function.
Raises:
ValueError: When either
an input name is not a discipline input name,
a differentiated input name is not a discipline input name
or an output name is not a discipline output name.
"""
input_names = self._get_names(
"inputs",
input_names,
self.discipline.io.input_grammar,
)
output_names = self._get_names(
"outputs",
output_names,
self.discipline.io.output_grammar,
)
if differentiated_input_names_substitute:
self._get_names(
"inputs",
differentiated_input_names_substitute,
self.discipline.io.input_grammar,
)
else:
differentiated_input_names_substitute = input_names
if is_differentiable:
self.discipline.add_differentiated_inputs(
differentiated_input_names_substitute
)
self.discipline.add_differentiated_outputs(output_names)
return DisciplineAdapter(
input_names,
output_names,
default_input_data or {},
self.discipline,
self._names_to_sizes,
differentiated_input_names_substitute=differentiated_input_names_substitute,
)
def _get_names(
self,
group_name: str,
names: Sequence[str],
grammar: BaseGrammar,
) -> Sequence[str]:
"""Return the variable names.
Args:
group_name: The name of the group to which these variables shall belong.
names: The candidate variable names.
If empty,
return the names of all the variables in the group of interest.
grammar: The grammar defining the variables available in the group.
Returns:
The variable names.
Raises:
ValueError: When a variable name is not defined in the grammar.
"""
if names:
wrong_names = set(names) - grammar.names
if wrong_names:
msg = (
f"{sorted(wrong_names)} are not names of {group_name} "
f"in the discipline {self.discipline.name}; "
f"expected names among {sorted(grammar.names)}."
)
raise ValueError(msg)
return names
return list(grammar.names)