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 Sequence

from matplotlib import pyplot
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 numpy import where

from gemseo.algos.opt_problem import OptimizationProblem
from gemseo.post.core.colormaps import PARULA
from gemseo.post.core.colormaps import RG_SEISMIC
from gemseo.post.opt_post_processor import OptPostProcessor
from gemseo.utils.compatibility.matplotlib import SymLogNorm


[docs]class ConstraintsHistory(OptPostProcessor): """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 vertical black line indicates the last iteration (or pseudo-iteration) where the constraint is (or should be) active. """ DEFAULT_FIG_SIZE = (11.0, 11.0) def __init__(self, opt_problem: OptimizationProblem) -> None: # noqa:D107 super().__init__(opt_problem) self.cmap = PARULA self.ineq_cstr_cmap = RG_SEISMIC self.eq_cstr_cmap = "seismic" def _plot( self, constraint_names: Sequence[str], line_style: str = "--", add_points: bool = True, ) -> None: """ Args: constraint_names: The names of the constraints. line_style: The style of the line, e.g. ``"-"`` or ``"--"``. If ``""``, do not plot the line. add_points: Whether to add one point per iteration on the line. Raises: ValueError: When an item of ``constraint_names`` is not a constraint name. """ # noqa: D205, D212, D415 all_constraint_names = self.opt_problem.constraint_names.keys() for constraint_name in constraint_names: if constraint_name not in all_constraint_names: raise ValueError( "Cannot build constraints history plot, " f"{constraint_name} is not a constraint name." ) constraint_names = self.opt_problem.get_function_names(constraint_names) constraint_histories, constraint_names, _ = self.database.get_history_array( constraint_names, add_dv=False ) # 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, axes = pyplot.subplots( nrows=ceil(len(constraint_names) / 2), ncols=2, sharex=True, figsize=self.DEFAULT_FIG_SIZE, ) fig.suptitle("Evolution of the constraints w.r.t. iterations", fontsize=14) iterations = arange(len(constraint_histories)) eq_constraint_names = [f.name for f in self.opt_problem.get_eq_constraints()] # for each subplot for constraint_history, constraint_name, axe in zip( constraint_histories.T, constraint_names, axes.ravel() ): f_name = self.opt_problem.database.retrieve_variable_name(constraint_name) if f_name in eq_constraint_names: cmap = self.eq_cstr_cmap constraint_type = "equality" else: cmap = self.ineq_cstr_cmap constraint_type = "inequality" # prepare the graph axe.grid(True) axe.set_title(f"{constraint_name} ({constraint_type})") axe.xaxis.set_major_locator(MaxNLocator(integer=True)) axe.axhline(0.0, color="k", linewidth=2) # Add line and points axe.plot(iterations, constraint_history, linestyle=line_style) if add_points: axe.scatter(iterations, constraint_history) # Plot color bars maximum = np_max(np_abs(constraint_history)) axe.imshow( atleast_2d(constraint_history), cmap=cmap, interpolation="nearest", aspect="auto", norm=SymLogNorm(linthresh=1.0, vmin=-maximum, vmax=maximum), 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 = where(diff(sign(constraint_history)))[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) axe.axvline( interp(0.0, constraint_values, iteration_values), color="k", linewidth=2, ) self._add_figure(fig)