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 absolute_import, division, unicode_literals

import matplotlib.gridspec as gridspec
import numpy as np
from future import standard_library
from matplotlib import pyplot as plt
from matplotlib.colors import SymLogNorm
from matplotlib.ticker import MaxNLocator

from gemseo.post.core.colormaps import PARULA, RG_SEISMIC
from gemseo.post.opt_post_processor import OptPostProcessor

standard_library.install_aliases()
from gemseo import LOGGER


[docs]class ObjConstrHist(OptPostProcessor): """ The **ObjConstrHist** post processing plots the constraint functions history in lines charts. By default, all constraints are considered. A sublist of constraints can be passed as options. It is possible either to save the plot, to show the plot or both. """ def __init__(self, opt_problem): """ Constructor :param opt_problem: the optimization problem to run """ 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, save=False, show=False, file_path="obj_constr_hist", constr_names=None, extension="pdf", ): """ Creates the design variables plot :param show: if True, displays the plot windows :type show: bool :param save: if True, exports plot to pdf :type save: bool :param file_path: the base paths of the files to export :type file_path: str :param constr_names: names of the constraints to plot :type constr_names: list(str) :param extension: file extension :type extension: str """ 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=(11, 6)) 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 im1 = ax1.imshow( values, cmap=cmap, interpolation="nearest", aspect="auto", extent=extent, norm=SymLogNorm(linthresh=1.0, vmin=vmin * 0.75, vmax=vmax * 0.75), 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") # save and show self._save_and_show( fig, save=save, show=show, file_path=file_path, extension=extension ) def __get_history(self, fname): """ Access the optimization history of a function and the design variables at which it was computed :param fname: name of the function :returns: list of function values list of design variables values list of iterations """ f_hist, x_hist = self.database.get_func_history(fname, 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): """ Returns constraints with formated shape :param constr_name: list of constraint names """ # 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