Source code for gemseo.post.obj_constr_hist

# -*- coding: utf-8 -*-
# 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 constraints plot."""
from __future__ import division, unicode_literals

import logging
from typing import Optional, Sequence, Tuple

import matplotlib.gridspec as gridspec
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.ticker import MaxNLocator
from numpy import ndarray

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

LOGGER = logging.getLogger(__name__)


[docs]class ObjConstrHist(OptPostProcessor): """The constraint function history in line charts. By default, all the constraints are considered. A sublist of constraints can be passed as options. """ DEFAULT_FIG_SIZE = (11.0, 6.0) def __init__( self, opt_problem, # type: OptimizationProblem ): # type: (...) -> None """ Args: opt_problem: The optimization problem to be post-processed. """ super(ObjConstrHist, self).__init__(opt_problem) self.opt_problem = opt_problem self.cmap = PARULA # "viridis" # "jet" self.ineq_cstr_cmap = RG_SEISMIC # "seismic" "PRGn_r" self.eq_cstr_cmap = "seismic" # "seismic" "PRGn_r" def _plot( self, constr_names=None, # type: Optional[Sequence[str]] ): # type: (...) -> None """Create the design variables plot. Args: constr_names: The names of the constraints to plot. If None, use all the constraints. """ obj_name = self.opt_problem.get_objective_name() obj_history, x_history, n_iter = self.__get_history(obj_name) fmin = np.min(obj_history) fmax = np.max(obj_history) if not self.opt_problem.minimize_objective and fmax < 0.0: obj_history = -obj_history fmin = np.min(obj_history) fmax = np.max(obj_history) grid = gridspec.GridSpec(1, 2, width_ratios=[15, 1], wspace=0.04, hspace=0.6) fig = plt.figure(figsize=self.DEFAULT_FIG_SIZE) ax1 = fig.add_subplot(grid[0, 0]) # objective function plt.xlabel("Iterations", fontsize=12) plt.ylabel("Objective value", fontsize=12) plt.plot(np.arange(len(obj_history)), obj_history) plt.ylim([fmin, fmax]) plt.xlim([0, n_iter]) ax1.xaxis.set_major_locator(MaxNLocator(integer=True)) plt.grid(True) plt.title("Evolution of the objective value and maximal constraint ") # Set window size mng = plt.get_current_fig_manager() mng.resize(700, 1000) # number of iterations nb_iter = x_history.shape[0] ineq_vals, eq_vals, ineq_cstr_id, eq_cstr_id = self.__get_constraints( constr_names=constr_names ) # concatenate all constraints values (ineq + eq) # NB : we take absolute values of equality constraints for color map eq_vals_abs = np.abs(eq_vals) all_cstr_list_abs = [ineq_vals, eq_vals_abs] all_cstr_vals_abs = np.concatenate( [cstr for cstr in all_cstr_list_abs if cstr.size > 0], axis=1 ) maxi_by_iter = np.amax(all_cstr_vals_abs, axis=1) indmaxi_by_iter = np.argmax(all_cstr_vals_abs, axis=1) cstr_ids = np.concatenate((ineq_cstr_id, eq_cstr_id)) values = np.atleast_2d(maxi_by_iter) cmap = RG_SEISMIC vmax = fmax vmin = -vmax extent = -0.5, nb_iter - 0.5, fmin, fmax norm = SymLogNorm(linthresh=1.0, vmin=vmin * 0.75, vmax=vmax * 0.75) im1 = ax1.imshow( values, cmap=cmap, interpolation="nearest", aspect="auto", extent=extent, norm=norm, alpha=0.6, ) # add labels with constraint violation information all_cstr_list = [ineq_vals, eq_vals] all_cstr_vals = np.concatenate( [cstr for cstr in all_cstr_list if cstr.size > 0], axis=1 ) for iteration, ind in zip(range(nb_iter), indmaxi_by_iter): y_vals = 0.5 * (fmax + fmin) x_vals = iteration max_label = cstr_ids[ind] # no abs on equality constraint max_value = all_cstr_vals[iteration, ind] text = "constraint " + max_label + " = " + str(max_value) ax1.text(x_vals - 0.0, y_vals + 1.1 * fmin, text, rotation="vertical") # color map cax = fig.add_subplot(grid[0, 1]) thick_min = int(np.log10(1.0)) thick_max = int(np.log10(np.abs(vmax))) thick_num = thick_max - thick_min + 1 levels_pos = np.logspace(thick_min, thick_max, num=thick_num, base=10.0) levels_pos = np.append(levels_pos, vmax) levels_neg = np.sort(-levels_pos) levels_neg = np.append(levels_neg, 0) levels = np.concatenate((levels_neg, levels_pos)) col_bar = fig.colorbar(im1, cax=cax, ticks=levels) col_bar.ax.tick_params(labelsize=9) cax.set_xlabel("symlog") self._add_figure(fig) def __get_history( self, function_name, # type: str ): # type: (...) -> Tuple[ndarray,ndarray,int] """Access the optimization history of a function. Also the design variables at which it was computed. Args: function_name: The name of the function. Returns: The function values, the design variables values and the number of iterations. """ f_hist, x_hist = self.database.get_func_history(function_name, x_hist=True) f_hist = np.array(f_hist).real x_hist = np.array(x_hist).real n_iter = x_hist.shape[0] return f_hist, x_hist, n_iter def __get_constraints( self, constr_names=None, # type: Optional[Sequence[str]] ): # type: (...) -> Tuple[ndarray,ndarray,ndarray,ndarray] """Return the constraints with formatted shape. Args: constr_names: The names of the constraints. If None, use all the constraints. """ # retrieve the constraints values ineq_cstr_names = [] eq_cstr_names = [] for cstr in self.opt_problem.get_ineq_constraints(): if constr_names is None: ineq_cstr_names.append(cstr.name) else: if cstr.name in constr_names: ineq_cstr_names.append(cstr.name) for cstr in self.opt_problem.get_eq_constraints(): if constr_names is None: eq_cstr_names.append(cstr.name) else: if cstr.name in constr_names: eq_cstr_names.append(cstr.name) get_hist_array = self.database.get_history_array if ineq_cstr_names: ineq_vals, ineq_id, _ = get_hist_array(ineq_cstr_names, add_dv=False) else: ineq_vals, ineq_id = np.array([]), np.array([]) if eq_cstr_names: eq_vals, eq_cstr_id, _ = get_hist_array(eq_cstr_names, add_dv=False) else: eq_vals, eq_cstr_id = np.array([]), np.array([]) # harmonization of tables format because constraints can be vectorial # or scalars. *vals.shape[0] = iteration, *vals.shape[1] = cstr values ineq_vals = np.atleast_3d(ineq_vals) ineq_shape = ineq_vals.shape ineq_vals = np.reshape( ineq_vals, (ineq_shape[0], ineq_shape[1] * ineq_shape[2]) ) eq_vals = np.atleast_3d(eq_vals) eq_shape = eq_vals.shape eq_vals = np.reshape(eq_vals, (eq_shape[0], eq_shape[1] * eq_shape[2])) return ineq_vals, eq_vals, ineq_id, eq_cstr_id