Source code for gemseo.post.constraints_history

# 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 - API and implementation and/or documentation
#        :author: Pierre-Jean Barjhoux
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""A matrix of constraint history plots."""

from __future__ import annotations

from math import ceil
from typing import TYPE_CHECKING
from typing import ClassVar

from matplotlib import pyplot
from matplotlib.colors import SymLogNorm
from matplotlib.ticker import MaxNLocator
from numpy import abs as np_abs
from numpy import arange
from numpy import atleast_2d
from numpy import atleast_3d
from numpy import diff
from numpy import flip
from numpy import interp
from numpy import max as np_max
from numpy import sign

from gemseo.post.base_post import BasePost
from gemseo.post.constraints_history_settings import ConstraintsHistory_Settings
from gemseo.post.core.colormaps import RG_SEISMIC

if TYPE_CHECKING:
    from matplotlib.colors import ListedColormap


[docs] class ConstraintsHistory(BasePost[ConstraintsHistory_Settings]): r"""A matrix of constraint history plots. A blue line represents the values of a constraint w.r.t. the iterations. A background color indicates whether the constraint is satisfied (green), active (white) or violated (red). A horizontal black line indicates the value for which an inequality constraint is active or an equality constraint is satisfied, namely :math:`0`. A horizontal black dashed line indicates the value below which an inequality constraint is satisfied *with a tolerance level*, namely :math:`\varepsilon`. For an equality constraint, the horizontal dashed black lines indicate the values between which the constraint is satisfied *with a tolerance level*, namely :math:`-\varepsilon` and :math:`\varepsilon`. A vertical black line indicates the last iteration (or pseudo-iteration) where the constraint is (or should be) active. """ Settings: ClassVar[type[ConstraintsHistory_Settings]] = ConstraintsHistory_Settings def _plot(self, settings: ConstraintsHistory_Settings) -> None: """ Raises: ValueError: When an item of ``constraint_names`` is not a constraint name. """ # noqa: D205, D212, D415 constraint_names = settings.constraint_names line_style = settings.line_style add_points = settings.add_points all_constraint_names = ( self._optimization_metadata.output_names_to_constraint_names.keys() ) for constraint_name in constraint_names: if constraint_name not in all_constraint_names: msg = ( "Cannot build constraints history plot, " f"{constraint_name} is not a constraint name." ) raise ValueError(msg) constraint_names = [ item for variable in constraint_names for item in ( self._optimization_metadata.output_names_to_constraint_names[variable] ) ] constraint_histories = ( self._dataset.get_view(variable_names=constraint_names).dropna().to_numpy() ) constraint_names = self._dataset.get_columns(constraint_names) # harmonization of tables format because constraints can be vectorial # or scalars. *vals.shape[0] = iteration, *vals.shape[1] = cstr values constraint_histories = atleast_3d(constraint_histories) constraint_histories = constraint_histories.reshape(( constraint_histories.shape[0], constraint_histories.shape[1] * constraint_histories.shape[2], )) # prepare the main window fig, axs = pyplot.subplots( nrows=ceil(len(constraint_names) / 2), ncols=2, sharex=True, figsize=settings.fig_size, ) fig.suptitle("Evolution of the constraints w.r.t. iterations", fontsize=14) iterations = arange(len(constraint_histories)) n_iterations = len(iterations) eq_constraint_names = self._dataset.equality_constraint_names # for each subplot for constraint_history, constraint_name, ax in zip( constraint_histories.T, constraint_names, axs.ravel(), strict=False ): cmap: str | ListedColormap f_name = constraint_name.split("[")[0] is_eq_constraint = f_name in eq_constraint_names if is_eq_constraint: cmap = "seismic" constraint_type = "equality" tolerance = self._optimization_metadata.tolerances.equality else: cmap = RG_SEISMIC constraint_type = "inequality" tolerance = self._optimization_metadata.tolerances.inequality # prepare the graph ax.grid(True) ax.set_title(f"{constraint_name} ({constraint_type})") ax.set_xticks(range(n_iterations)) ax.set_xticklabels(range(1, n_iterations + 1)) ax.get_xaxis().set_major_locator(MaxNLocator(integer=True)) ax.axhline(tolerance, color="k", linestyle="--") ax.axhline(0.0, color="k") if is_eq_constraint: ax.axhline(-tolerance, color="k", linestyle="--") # Add line and points ax.plot(iterations, constraint_history, linestyle=line_style) if add_points: ax.scatter(iterations, constraint_history) # Plot color bars maximum = np_max(np_abs(constraint_history)) margin = 2 * maximum * 0.05 ax.imshow( atleast_2d(constraint_history), cmap=cmap, interpolation="nearest", aspect="auto", norm=SymLogNorm(vmin=-maximum, vmax=maximum, linthresh=1.0), extent=[-0.5, n_iterations - 0.5, -maximum - margin, maximum + margin], alpha=0.6, ) # Plot a vertical line at the last iteration (or pseudo-iteration) # where the constraint is (or should be) active. indices_before_sign_change = diff(sign(constraint_history)).nonzero()[0] if indices_before_sign_change.size != 0: index_before_last_sign_change = indices_before_sign_change[-1] indices = [ index_before_last_sign_change, index_before_last_sign_change + 1, ] constraint_values = constraint_history[indices] iteration_values = iterations[indices] if constraint_values[1] < constraint_values[0]: constraint_values = flip(constraint_values) iteration_values = flip(iteration_values) ax.axvline(interp(0.0, constraint_values, iteration_values), color="k") fig.tight_layout() self._add_figure(fig)