From 158161af659a3365ac44234c61fc8fe795811563 Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Thu, 2 Jan 2025 14:57:43 +0100 Subject: [PATCH 1/6] removed 'compute_all_jacobians_tag' from hybrid mode; rebase --- src/gemseo/core/discipline/discipline.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/gemseo/core/discipline/discipline.py b/src/gemseo/core/discipline/discipline.py index 4631af665d..b224e1117c 100644 --- a/src/gemseo/core/discipline/discipline.py +++ b/src/gemseo/core/discipline/discipline.py @@ -241,14 +241,21 @@ class Discipline(BaseDiscipline, metaclass=ClassInjector): ) if not compute_all_jacobians: - for output_name in tuple(self.jac.keys()): - if output_name not in output_names: - del self.jac[output_name] - else: - jac = self.jac[output_name] - for input_name in list(jac.keys()): - if input_name not in input_names: - del jac[input_name] + if self._linearization_mode in self._hybrid_approximation_modes: + self.execution_status.handle( + self.execution_status.Status.LINEARIZING, + self.execution_statistics.record_linearization, + self.__compute_jacobian, + ) + else: + for output_name in tuple(self.jac.keys()): + if output_name not in output_names: + del self.jac[output_name] + else: + jac = self.jac[output_name] + for input_name in list(jac.keys()): + if input_name not in input_names: + del jac[input_name] # The check of the jacobian shape is required only when some of its # components are requested. -- GitLab From d875fd0425f760988dcc3085298e1a733191610c Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Wed, 5 Feb 2025 10:35:54 +0100 Subject: [PATCH 2/6] doc: added an example on how to use the hybrid approximation mode for the jacobian computation. --- .../basics/plot_compute_hybrid_jacobian.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py diff --git a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py new file mode 100644 index 0000000000..25171c18af --- /dev/null +++ b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py @@ -0,0 +1,116 @@ +# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com +# +# This work is licensed under a BSD 0-Clause License. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING +# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# Contributors: +# INITIAL AUTHORS - initial API and implementation and/or initial +# documentation +# :author: Fabian Castañeda +# OTHER AUTHORS - MACROSCOPIC CHANGES +""" +Compute the Jacobian of a discipline with analytical and approximated elements +============================================================================== + +In this example, +we will compute the Jacobians of some outputs of an :class:`.Discipline` +with respect to some inputs, based on some analytical derivatives and approximative +methods. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from numpy import array + +from gemseo.core.discipline import Discipline + +if TYPE_CHECKING: + from collections.abc import Iterable + + from gemseo import StrKeyMapping + +# %% +# For many different reasons, one might be in a situation where not all the derivatives +# of a giving discipline are at hand and approximating all of them might not be +# convinient for a reason or another. For situations like these, being able to compute +# the Jacobian of a discipline using both analytical expressions for certain +# inputs-outputs and approximative methods for the rest can be handy. +# First, +# we create a discipline, e.g. a :class:`.Discipline`: + + +class HybridDiscipline(Discipline): + def __init__(self) -> None: + super().__init__() + self.input_grammar.update_from_names(["x_1"]) + self.input_grammar.update_from_names(["x_2"]) + self.input_grammar.update_from_names(["x_3"]) + self.output_grammar.update_from_names(["y_1"]) + self.output_grammar.update_from_names(["y_2"]) + self.output_grammar.update_from_names(["y_3"]) + self.default_input_data = { + "x_1": array([1.0]), + "x_2": array([2.0]), + "x_3": array([1.0]), + } + + def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None: + self.io.data["y_1"] = input_data["x_1"] * input_data["x_2"] + self.io.data["y_2"] = input_data["x_1"] * input_data["x_2"] * input_data["x_3"] + self.io.data["y_3"] = input_data["x_1"] + + def _compute_jacobian( + self, + input_names: Iterable[str] = (), + output_names: Iterable[str] = (), + ) -> None: + self._init_jacobian() + x1 = array([self.get_input_data(with_namespaces=False)["x_1"]]) + x2 = array([self.get_input_data(with_namespaces=False)["x_2"]]) + x3 = array([self.get_input_data(with_namespaces=False)["x_3"]]) + self.jac = {"y_1": {"x_1": x2}, "y_2": {"x_2": x1 * x3}} + + +# %% +# As you can see, we define the jacobian of the discipline inside the discipline's method +# :meth:`._compute_jacobian`. However, we are only defining the derivatives that +# we have or care about. + +# %% +# In this case we define ``"y_1"`` wrt ``"x_1"`` and ``"y_2"`` wrt to ``"x_2"``. +# This means that we are missing ``"y_1"`` wrt to ``"x_2"``, ``"y_2"`` wrt to ``"x_1"`` +# and ``"x_3"`` and finally ``"y_3"`` wrt ``"x_1"``. +# we can call the discipline's method :meth:`.linearize` to fill in the missing +# derivatives. Nonetheless, we need to parametrized it to just compute the missing +# derivatives. For this we assign to the attribute :attr:`.linearization_mode` one of +# the hybrid available modes which are accessible from the attribute +# :attr:`.ApproximationMode`. + + +discipline = HybridDiscipline() +discipline.linearization_mode = discipline.ApproximationMode.HYBRID_FINITE_DIFFERENCES + +# %% +# There are three modes available, ``HYBRID_FINITE_DIFFERENCES``, +# ``HYBRID_CENTERED_DIFFERENCES`` and ``HYBRID_COMPLEX_STEP``. Being the difference +# between each other the approximation type used to approximate the missing derivatives. +# We can also define the inputs to be used to compute the Jacobian, in this case we are +# using the default inputs. Finally, we need to set the ``"compute_all_jacobians"`` flag +# to True. Even if we are not computing them all, this option needs to be active in +# order to access the data for the hybrid linearization. + +inputs = discipline.default_input_data +jacobian_data = discipline.linearize(inputs, compute_all_jacobians=True) +jacobian_data -- GitLab From afa7102569eda3052fec474c60116aaaa82859ec Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Thu, 6 Feb 2025 10:24:21 +0000 Subject: [PATCH 3/6] form: Suggestions --- .../basics/plot_compute_hybrid_jacobian.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py index 25171c18af..e2000cc666 100644 --- a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py +++ b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py @@ -43,7 +43,7 @@ if TYPE_CHECKING: # %% # For many different reasons, one might be in a situation where not all the derivatives -# of a giving discipline are at hand and approximating all of them might not be +# of a given discipline are at hand and approximating all of them might not be # convinient for a reason or another. For situations like these, being able to compute # the Jacobian of a discipline using both analytical expressions for certain # inputs-outputs and approximative methods for the rest can be handy. @@ -54,13 +54,9 @@ if TYPE_CHECKING: class HybridDiscipline(Discipline): def __init__(self) -> None: super().__init__() - self.input_grammar.update_from_names(["x_1"]) - self.input_grammar.update_from_names(["x_2"]) - self.input_grammar.update_from_names(["x_3"]) - self.output_grammar.update_from_names(["y_1"]) - self.output_grammar.update_from_names(["y_2"]) - self.output_grammar.update_from_names(["y_3"]) - self.default_input_data = { + self.io.input_grammar.update_from_names(["x_1", "x_2", "x_3"]) + self.io.output_grammar.update_from_names(["y_1", "y_2", "y_3"]) + self.io.input_grammar.defaults = { "x_1": array([1.0]), "x_2": array([2.0]), "x_3": array([1.0]), -- GitLab From dd3956d469ca775cd7322c043387028f0cbc04d5 Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Thu, 6 Feb 2025 11:56:05 +0100 Subject: [PATCH 4/6] form: typo and rebase. --- .../disciplines/basics/plot_compute_hybrid_jacobian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py index e2000cc666..d3760a8199 100644 --- a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py +++ b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py @@ -44,7 +44,7 @@ if TYPE_CHECKING: # %% # For many different reasons, one might be in a situation where not all the derivatives # of a given discipline are at hand and approximating all of them might not be -# convinient for a reason or another. For situations like these, being able to compute +# convenient for a reason or another. For situations like these, being able to compute # the Jacobian of a discipline using both analytical expressions for certain # inputs-outputs and approximative methods for the rest can be handy. # First, -- GitLab From 138da61fb052829805ef0ea6ed1c429a21b4ffe2 Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Thu, 6 Feb 2025 16:56:43 +0100 Subject: [PATCH 5/6] Fix: unexpected change due to rebase. --- src/gemseo/core/discipline/discipline.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/gemseo/core/discipline/discipline.py b/src/gemseo/core/discipline/discipline.py index b224e1117c..4631af665d 100644 --- a/src/gemseo/core/discipline/discipline.py +++ b/src/gemseo/core/discipline/discipline.py @@ -241,21 +241,14 @@ class Discipline(BaseDiscipline, metaclass=ClassInjector): ) if not compute_all_jacobians: - if self._linearization_mode in self._hybrid_approximation_modes: - self.execution_status.handle( - self.execution_status.Status.LINEARIZING, - self.execution_statistics.record_linearization, - self.__compute_jacobian, - ) - else: - for output_name in tuple(self.jac.keys()): - if output_name not in output_names: - del self.jac[output_name] - else: - jac = self.jac[output_name] - for input_name in list(jac.keys()): - if input_name not in input_names: - del jac[input_name] + for output_name in tuple(self.jac.keys()): + if output_name not in output_names: + del self.jac[output_name] + else: + jac = self.jac[output_name] + for input_name in list(jac.keys()): + if input_name not in input_names: + del jac[input_name] # The check of the jacobian shape is required only when some of its # components are requested. -- GitLab From 1e2ae6eef3ca969d4c27a5b2b383314a4b6af4a1 Mon Sep 17 00:00:00 2001 From: Fabian Castaneda Date: Thu, 6 Feb 2025 17:10:36 +0100 Subject: [PATCH 6/6] refact: refactor _run method of the hybrid jacobian example. --- .../disciplines/basics/plot_compute_hybrid_jacobian.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py index d3760a8199..49597b1dae 100644 --- a/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py +++ b/doc_src/_examples/disciplines/basics/plot_compute_hybrid_jacobian.py @@ -63,9 +63,10 @@ class HybridDiscipline(Discipline): } def _run(self, input_data: StrKeyMapping) -> StrKeyMapping | None: - self.io.data["y_1"] = input_data["x_1"] * input_data["x_2"] - self.io.data["y_2"] = input_data["x_1"] * input_data["x_2"] * input_data["x_3"] - self.io.data["y_3"] = input_data["x_1"] + y_1 = input_data["x_1"] * input_data["x_2"] + y_2 = input_data["x_1"] * input_data["x_2"] * input_data["x_3"] + y_3 = input_data["x_1"] + return {"y_1": y_1, "y_2": y_2, "y_3": y_3} def _compute_jacobian( self, -- GitLab