Source code for modopt.external_libraries.cobyqa.cobyqa

import numpy as np
from scipy.optimize import Bounds, LinearConstraint, NonlinearConstraint #, minimize
import time
from modopt.utils.options_dictionary import OptionsDictionary
from modopt import Optimizer
from typing import Callable

[docs]class COBYQA(Optimizer): ''' Class that interfaces modOpt with the COBYQA optimization algorithm. Constrained Optimization BY Quadratic Approximations or COBYQA is a gradient-free optimization algorithm. Unlike COBYLA, COBYQA also supports equality constraints. Parameters ---------- problem : Problem or ProblemLite Object containing the problem to be solved. recording : bool, default=False If ``True``, record all outputs from the optimization. This needs to be enabled for hot-starting the same problem later, if the optimization is interrupted. out_dir : str, optional The directory to store all the output files generated from the optimization. hot_start_from : str, optional The record file from which to hot-start the optimization. hot_start_atol : float, default=0. The absolute tolerance check for the inputs when reusing outputs from the hot-start record. hot_start_rtol : float, default=0. The relative tolerance check for the inputs when reusing outputs from the hot-start record. visualize : list, default=[] The list of scalar variables to visualize during the optimization. keep_viz_open : bool, default=False If ``True``, keep the visualization window open after the optimization is complete. turn_off_outputs : bool, default=False If ``True``, prevent modOpt from generating any output files. solver_options : dict, default={} Dictionary containing the options to be passed to the solver. Available options are: 'maxfev', 'maxiter', 'target', 'feasibility_tol', 'radius_init', 'radius_final', 'nb_points', 'scale', 'filter_size', 'store_history', 'history_size', 'debug', 'disp', 'callback'. See the COBYQA page in modOpt's documentation for more information. readable_outputs : list, default=[] List of outputs to be written to readable text output files. Available outputs are: 'x', 'obj'. ''' def initialize(self): ''' Initialize the optimizer. Declare options, solver_options and outputs. ''' # Declare options self.solver_name = 'cobyqa' self.options.declare('solver_options', types=dict, default={}) NPT = int(2*self.problem.nx+1) self.default_solver_options = { 'maxfev': (int, 500), # Maximum number of function evaluations (default: 500 * len(x0)) 'maxiter': (int, 1000), # Maximum number of iterations (default: 1000 * len(x0)) 'target': (float, -np.inf), # Target objective function value 'feasibility_tol': (float, 1e-8), # Tolerance on constraint violations 'radius_init': (float, 1.0), # Initial trust region radius 'radius_final': (float, 1e-6), # Final trust region radius 'nb_points': (int, NPT), # Number of interpolation points used to build the quadratic model of f and c, 0<NPT<=(n+1)*(n+2)//2 'scale': (bool, False), # Whether to scale the variables according to the bounds 'filter_size': (int, int(1e6)), # Maximum number of points in the filter. The filter is used to store the best point returned by the algorithm 'store_history': (bool, False), # Whether to store the history of the function evaluations 'history_size': (int, int(1e6)), # Maximum number of function evaluations to store in the history 'debug': (bool, False), # To perform additional checks during the optimization procedure. 'disp': (bool, False), 'callback': ((type(None), Callable), None), } # Used for verifying the keys and value-types of user-provided solver_options self.solver_options = OptionsDictionary() for key, value in self.default_solver_options.items(): self.solver_options.declare(key, types=value[0], default=value[1]) # Declare outputs self.available_outputs = { 'x' : (float, (self.problem.nx,)), 'obj': float } self.options.declare('readable_outputs', values=([], ['x'], ['obj'], ['x', 'obj']), default=[]) # Define the initial guess, objective, constraints self.x0 = self.problem.x0 * 1.0 self.obj = self.problem._compute_objective self.active_callbacks = ['obj'] if self.problem.constrained: self.con = self.problem._compute_constraints self.active_callbacks += ['con'] def setup(self): ''' Setup the optimizer. Setup outputs, bounds, and constraints. Check the validity of user-provided 'solver_options'. ''' # Check if user-provided solver_options have valid keys and value-types self.solver_options.update(self.options['solver_options']) self.options_to_pass = self.solver_options.get_pure_dict() self.user_callback = self.options_to_pass.pop('callback') # Adapt bounds as scipy Bounds() object self.setup_bounds() # Adapt constraints as a list of dictionaries with constraints = 0 or >= 0 if self.problem.constrained: self.setup_constraints() else: self.constraints = () def setup_bounds(self): ''' Adapt bounds as a Scipy Bounds() object. Only for Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell, trust-constr, COBYLA, and COBYQA methods. ''' xl = self.problem.x_lower xu = self.problem.x_upper if np.all(xl == -np.inf) and np.all(xu == np.inf): self.bounds = None else: self.bounds = Bounds(xl, xu, keep_feasible=False) # OR the following can also be used # self.bounds = np.array([xl, xu]).T def setup_constraints(self): ''' Adapt constraints as a a single/list of scipy LinearConstraint() or NonlinearConstraint() objects. ''' cl = self.problem.c_lower cu = self.problem.c_upper self.constraints = NonlinearConstraint(self.con, cl, cu)
[docs] def solve(self): try: # import latest cobyqa if available from cobyqa import minimize except ImportError: # else access cobyqa installed with Scipy>=1.14.0 (requires python>=3.10) try: from scipy._lib.cobyqa import minimize except ImportError: raise ImportError("'cobyqa' could not be imported. Install cobyqa using 'pip install cobyqa' for using COBYQA optimizer.") def callback(intermediate_result): x = intermediate_result['x'] f = intermediate_result['fun'] self.update_outputs(x=x, obj=f) if self.user_callback: self.user_callback(x, f) # self.update_outputs(x=self.x0, obj=self.obj(self.x0)) # maybe required for scipy.optimize.minimize # Call the cobyqa algorithm (not from Scipy) start_time = time.time() self.results = minimize( self.obj, self.x0, args=(), # method='COBYQA', # for scipy.optimize.minimize # jac=None, # for scipy.optimize.minimize # hess=None, # for scipy.optimize.minimize # hessp=None, # for scipy.optimize.minimize bounds=self.bounds, constraints=self.constraints, # tol=None, # for scipy.optimize.minimize callback=callback, options=self.options_to_pass ) self.total_time = time.time() - start_time self.run_post_processing() return self.results
[docs] def print_results(self, optimal_variables=False, obj_history=False, max_con_viol_history=False, all=False): ''' Print the optimization results to the console. Parameters ---------- optimal_variables : bool, default=False If ``True``, print the optimal variables. obj_history : bool, default=False If ``True``, print the objective history. max_con_viol_history : bool, default=False If ``True``, print the maximum constraint violation history. all : bool, default=False If ``True``, print all available information. ''' output = "\n\tSolution from COBYQA:" output += "\n\t"+"-" * 100 output += f"\n\t{'Problem':25}: {self.problem_name}" output += f"\n\t{'Solver':25}: {self.solver_name}" output += f"\n\t{'Success':25}: {self.results['success']}" output += f"\n\t{'Message':25}: {self.results['message']}" output += f"\n\t{'Status':25}: {self.results['status']}" output += f"\n\t{'Total time':25}: {self.total_time}" output += f"\n\t{'Objective':25}: {self.results['fun']}" output += f"\n\t{'Max. constraint violation':25}: {self.results['maxcv']}" output += f"\n\t{'Total function evals':25}: {self.results['nfev']}" output += f"\n\t{'Total iterations':25}: {self.results['nit']}" output += self.get_callback_counts_string(25) if optimal_variables or all: output += f"\n\t{'Optimal variables':25}: {self.results['x']}" if (obj_history or all) and self.solver_options['store_history']: output += f"\n\t{'Objective history':25}: {self.results['fun_history']}" if (max_con_viol_history or all) and self.solver_options['store_history'] and self.problem.constrained: output += f"\n\t{'Max. con. viol. history':25}: {self.results['maxcv_history']}" output += '\n\t' + '-'*100 print(output)