Source code for pyoptsparse.pyPSQP.pyPSQP

"""
pyPSQP - the pyPSQP wrapper
"""
# Standard Python modules
import datetime
import os
import time

# External modules
import numpy as np

# Local modules
from ..pyOpt_error import Error
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_utils import try_import_compiled_module_from_path

# import the compiled module
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
psqp = try_import_compiled_module_from_path("psqp", THIS_DIR)


[docs] class PSQP(Optimizer): """ PSQP Optimizer Class - Inherited from Optimizer Abstract Class """ def __init__(self, raiseError=True, options={}): name = "PSQP" category = "Local Optimizer" defOpts = self._getDefaultOptions() informs = self._getInforms() if isinstance(psqp, str) and raiseError: raise ImportError(psqp) super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options) # PSQP needs Jacobians in dense format self.jacType = "dense2d" @staticmethod def _getDefaultOptions(): defOpts = { "XMAX": [float, 1e16], "TOLX": [float, 1e-16], "TOLC": [float, 1e-6], "TOLG": [float, 1e-6], "RPF": [float, 1e-4], "MIT": [int, 1000], "MFV": [int, 2000], "MET": [int, 2], "MEC": [int, 2], "IPRINT": [int, 2], "IOUT": [int, 6], "IFILE": [str, "PSQP.out"], } return defOpts @staticmethod def _getInforms(): informs = { 1: "Change in design variable was less than or equal to tolerance", 2: "Change in objective function was less than or equal to tolerance", 3: "Objective function less than or equal to tolerance", 4: "Maximum constraint value is less than or equal to tolerance", 11: "Maximum number of iterations exceeded", 12: "Maximum number of function evaluations exceeded", 13: "Maximum number of gradient evaluations exceeded", -6: "Termination criterion not satisfied, but obtained point is acceptable", -7: "Positive directional derivative in line search", -8: "Interpolation error in line search", -10: "Optimization failed", } return informs
[docs] def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True ): """ This is the main routine used to solve the optimization problem. Parameters ---------- optProb : Optimization or Solution class instance This is the complete description of the optimization problem to be solved by the optimizer sens : str or python Function. Specifiy method to compute sensitivities. To explictly use pyOptSparse gradient class to do the derivatives with finite differenes use 'FD'. 'sens' may also be 'CS' which will cause pyOptSpare to compute the derivatives using the complex step method. Finally, 'sens' may be a python function handle which is expected to compute the sensitivities directly. For expensive function evaluations and/or problems with large numbers of design variables this is the preferred method. sensStep : float Set the step size to use for design variables. Defaults to 1e-6 when sens is 'FD' and 1e-40j when sens is 'CS'. sensMode : str Use 'pgc' for parallel gradient computations. Only available with mpi4py and each objective evaluation is otherwise serial storeHistory : str File name of the history file into which the history of this optimization will be stored hotStart : str File name of the history file to "replay" for the optimziation. The optimization problem used to generate the history file specified in 'hotStart' must be **IDENTICAL** to the currently supplied 'optProb'. By identical we mean, **EVERY SINGLE PARAMETER MUST BE IDENTICAL**. As soon as he requested evaluation point from PSQP does not match the history, function and gradient evaluations revert back to normal evaluations. storeSens : bool Flag sepcifying if sensitivities are to be stored in hist. This is necessay for hot-starting only. """ self.startTime = time.time() self.callCounter = 0 self.storeSens = storeSens if len(optProb.constraints) == 0: self.unconstrained = True optProb.dummyConstraint = False # Set optProb and finalize self.optProb = optProb self.optProb.finalize() # Set history/hotstart self._setHistory(storeHistory, hotStart) self._setInitialCacheValues() self._setSens(sens, sensStep, sensMode) blx, bux, xs = self._assembleContinuousVariables() xi = 3 * np.ones(len(xs), "int") xs = np.maximum(xs, blx) xs = np.minimum(xs, bux) nvar = len(xs) ff = self._assembleObjective() # pSQP CAN handle two sided constraints, but need to know # which ones are which. That is a little tricky to determine, # so we will split them into one-sided <= zero constraints and # equality=0 constraints such that it is simple to determine # the type of constraints. Put the equality constraints frist. oneSided = True # Set the number of nonlinear constraints snopt *thinks* we have: if self.unconstrained: ncon = 0 cf = np.zeros(1) cl = np.zeros(1) cu = np.zeros(1) ic = np.zeros(1) # as done for SLSQP, we need this dummy constraint to make the code work optProb.dummyConstraint = True else: indices, blc, buc, fact = self.optProb.getOrdering(["ne", "le", "ni", "li"], oneSided=oneSided) ncon = len(indices) cl = np.zeros(ncon) # -self.getOption('XMAX')*np.ones(ncon) cu = np.zeros(ncon) cf = np.zeros(ncon + 1) ic = 2 * np.ones(ncon, "intc") self.optProb.jacIndices = indices self.optProb.fact = fact self.optProb.offset = buc # Also figure out the number of equality and set the type # of constraint to 5 tmp0, __, __, __ = self.optProb.getOrdering(["ne", "le"], oneSided=oneSided) ic[0 : len(tmp0)] = 5 if self.optProb.comm.rank == 0: # ====================================================================== # PSQP - Objective Values Function # ====================================================================== def pobj(n, x, f): if self._checkEval(x): self._internalEval(x) f = self.storedData["fobj"] psqp.pyflush(self.getOption("IOUT")) return f # ====================================================================== # PSQP - Constraint Values Function # ====================================================================== def pcon(n, k, x, g): if self._checkEval(x): self._internalEval(x) g = self.storedData["fcon"][k - 1] psqp.pyflush(self.getOption("IOUT")) return g # ====================================================================== # PSQP - Objective Gradients Function # ====================================================================== def pdobj(n, x, df): if self._checkEval(x): self._internalEval(x) df = self.storedData["gobj"] psqp.pyflush(self.getOption("IOUT")) return df # ====================================================================== # PSQP - Constraint Gradients Function # ====================================================================== def pdcon(n, k, x, dg): if self._checkEval(x): self._internalEval(x) dg = self.storedData["gcon"][k - 1] psqp.pyflush(self.getOption("IOUT")) return dg # Setup argument list values iterm = np.array(0, int) gmax = 0.0 cmax = 0.0 opt = self.getOption if not opt("IPRINT") <= 2: raise Error("Incorrect output level setting (IPRINT) option. Must be <= 2") if opt("IPRINT") != 0: if os.path.isfile(opt("IFILE")): os.remove(opt("IFILE")) # Run PSQP t0 = time.time() # fmt: off psqp.psqp_wrap(nvar, ncon, xs, xi, blx, bux, cf, ic, cl, cu, opt('MIT'), opt('MFV'), opt('MET'), opt('MEC'), opt('XMAX'), opt('TOLX'), opt('TOLC'), opt('TOLG'), opt('RPF'), ff, gmax, cmax, opt('IPRINT'), opt('IOUT'), opt('IFILE'), iterm, pobj, pdobj, pcon, pdcon) # fmt: on optTime = time.time() - t0 if opt("IPRINT") > 0: psqp.closeunit(opt("IPRINT")) # Broadcast a -1 to indcate SLSQP has finished self.optProb.comm.bcast(-1, root=0) # Store Results inform = iterm.item() if inform < 0 and inform not in self.informs: inform = -10 sol_inform = {} sol_inform["value"] = inform sol_inform["text"] = self.informs[inform] if self.storeHistory: self.metadata["endTime"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.metadata["optTime"] = optTime self.hist.writeData("metadata", self.metadata) self.hist.close() # Create the optimization solution sol = self._createSolution(optTime, sol_inform, ff, xs) else: # We are not on the root process so go into waiting loop: self._waitLoop() sol = None # Communication solution and return sol = self._communicateSolution(sol) return sol