From 83f1d0552de863b35ec139a6b52bb5da07e609cf Mon Sep 17 00:00:00 2001 From: Neil Wu <602725+nwu63@users.noreply.github.com> Date: Mon, 15 Mar 2021 11:15:02 -0400 Subject: [PATCH] Use `optionstable` to display options and informs tables (#216) * moved default options and informs to staticmethod * updated SNOPT docs * updated ALPSO * updated SLSQP * updated PSQP * updated NLPQLP * updated NSGA2 * updated IPOPT * updated CONMIN * removed optimizertable * bugfix after changing option from '' to EXT * updated informs * removed options description in comments * pin a newer version of sphinx_mdolab_theme * version bump --- doc/conf.py | 2 +- doc/ext/optimizertable.py | 148 ----------------------------- doc/optimizers/ALPSO.rst | 9 +- doc/optimizers/ALPSO_options.yaml | 70 ++++++++++++++ doc/optimizers/CONMIN.rst | 8 +- doc/optimizers/CONMIN_options.yaml | 16 ++++ doc/optimizers/IPOPT.rst | 7 +- doc/optimizers/IPOPT_options.yaml | 8 ++ doc/optimizers/NLPQLP.rst | 7 +- doc/optimizers/NLPQLP_options.yaml | 31 ++++++ doc/optimizers/NSGA2.rst | 8 +- doc/optimizers/NSGA2_options.yaml | 22 +++++ doc/optimizers/PSQP.rst | 7 +- doc/optimizers/PSQP_options.yaml | 24 +++++ doc/optimizers/SLSQP.rst | 7 +- doc/optimizers/SLSQP_options.yaml | 10 ++ doc/optimizers/SNOPT.rst | 31 ++---- doc/optimizers/SNOPT_options.yaml | 64 +++++++++++++ doc/requirements.txt | 2 +- pyoptsparse/__init__.py | 2 +- pyoptsparse/pyALPSO/pyALPSO.py | 107 ++++++++++----------- pyoptsparse/pyCONMIN/pyCONMIN.py | 34 ++++--- pyoptsparse/pyIPOPT/pyIPOPT.py | 52 +++++----- pyoptsparse/pyNLPQLP/pyNLPQLP.py | 63 ++++++------ pyoptsparse/pyNSGA2/pyNSGA2.py | 30 ++++-- pyoptsparse/pyPSQP/pyPSQP.py | 56 ++++++----- pyoptsparse/pySLSQP/pySLSQP.py | 43 +++++---- pyoptsparse/pySNOPT/pySNOPT.py | 109 +++++++++++---------- 28 files changed, 550 insertions(+), 427 deletions(-) delete mode 100644 doc/ext/optimizertable.py create mode 100644 doc/optimizers/ALPSO_options.yaml create mode 100644 doc/optimizers/CONMIN_options.yaml create mode 100644 doc/optimizers/IPOPT_options.yaml create mode 100644 doc/optimizers/NLPQLP_options.yaml create mode 100644 doc/optimizers/NSGA2_options.yaml create mode 100644 doc/optimizers/PSQP_options.yaml create mode 100644 doc/optimizers/SLSQP_options.yaml create mode 100644 doc/optimizers/SNOPT_options.yaml diff --git a/doc/conf.py b/doc/conf.py index 55f07b32..8e206fa7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -21,7 +21,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions.extend(["numpydoc", "ext.optimizertable", "sphinxcontrib.bibtex"]) +extensions.extend(["numpydoc", "sphinxcontrib.bibtex"]) # mock import for autodoc autodoc_mock_imports = ["baseclasses", "scipy", "numpy", "sqlitedict"] diff --git a/doc/ext/optimizertable.py b/doc/ext/optimizertable.py deleted file mode 100644 index d4d0ae1f..00000000 --- a/doc/ext/optimizertable.py +++ /dev/null @@ -1,148 +0,0 @@ -from importlib import import_module -from docutils.parsers.rst import directives -from docutils.parsers.rst.directives.tables import Table -from docutils.statemachine import ViewList -from docutils import nodes - - -class OptimizerTable(Table): - """ - This Table directive formats the defOpts and informs dictionaries in a nice table - - This is heavily adapted from - https://github.com/BU-NU-CLOUD-SP16/Trusted-Platform-Module-nova/blob/master/api-ref/ext/rest_parameters.py - """ - - required_arguments = 1 - has_content = True - # type is a required argument and must be one of ["options", "informs"] - # the rest, if not specified, will be based on the type - option_spec = { - "type": directives.uri, - "header": str, - "widths": directives.positive_int_list, - "nodefvalues": directives.flag, - } - - def get_options_informs(self, cls): - # extracts the dictionaries from the optimizer instance - optimizer_instance = cls(raiseError=False) - self.defOpts = optimizer_instance.defOpts - self.informs = optimizer_instance.informs - - def set_header(self): - # sets the self.header and self.max_cols - # based on options - if "header" in self.options: - self.header = self.options["header"].split(",") - else: - if self.options["type"] == "options": - self.header = ["Option name", "Type", "Default value"] - if self.options["nodefvalues"]: - self.header = self.header[:-1] - elif self.options["type"] == "informs": - self.header = ["Code", "Description"] - - self.max_cols = len(self.header) - - def set_width(self): - # sets the self.col_widths - if "widths" in self.options: - self.col_widths = self.options["widths"] - else: - if self.options["type"] == "options": - self.col_widths = [20, 10, 30] - if self.options["nodefvalues"]: - self.col_widths = self.col_widths[:-1] - elif self.options["type"] == "informs": - self.col_widths = [5, 55] - - def run(self): - module_path, member_name = self.arguments[0].rsplit(".", 1) - class_name = getattr(import_module(module_path), member_name) - # set options - self.options["nodefvalues"] = "nodefvalues" in self.options - # set the self.defOpts and self.informs attribute - self.get_options_informs(class_name) - # set header option - self.set_header() - # set width - self.set_width() - table_node = self.build_table() - return [table_node] - - def collect_rows(self): - # Add a column for a field. In order to have the RST inside - # these fields get rendered, we need to use the - # ViewList. Note, ViewList expects a list of lines, so chunk - # up our content as a list to make it happy. - def add_col(value): - entry = nodes.entry() - result = ViewList(value.split("\n")) - self.state.nested_parse(result, 0, entry) - return entry - - rows = [] - groups = [] - # options - if self.options["type"] == "options": - for key, values in self.defOpts.items(): - trow = nodes.row() - # first add the name column, with text = key - trow += add_col("``" + key + "``") - # loop over dictionary values - for idx, value in enumerate(values): - # this is the type of the default value, so we need to extract the __name__ attribute - if idx == 0: - trow += add_col(value.__name__) - # this is the default value - else: - # if we want to show the default values - if not self.options["nodefvalues"]: - if isinstance(value, (float, int)): - limit = 5 - if abs(value) > 10 ** limit or (abs(value) < 10 ** (-limit) and abs(value) > 0): - value = "{:.3E}".format(value) - else: - value = "{}".format(value) - trow += add_col(str(value)) - rows.append(trow) - # informs - elif self.options["type"] == "informs": - for key, value in self.informs.items(): - trow = nodes.row() - # first add the name column, with text = key - trow += add_col("``" + str(key) + "``") - # add inform description - trow += add_col(value) - rows.append(trow) - return rows, groups - - def build_table(self): - table = nodes.table() - tgroup = nodes.tgroup(cols=len(self.header)) - table += tgroup - - tgroup.extend( - nodes.colspec(colwidth=col_width, colname="c" + str(idx)) for idx, col_width in enumerate(self.col_widths) - ) - - thead = nodes.thead() - tgroup += thead - - row_node = nodes.row() - thead += row_node - row_node.extend(nodes.entry(h, nodes.paragraph(text=h)) for h in self.header) - - tbody = nodes.tbody() - tgroup += tbody - - rows, groups = self.collect_rows() - tbody.extend(rows) - table.extend(groups) - - return table - - -def setup(app): - app.add_directive("optimizertable", OptimizerTable) diff --git a/doc/optimizers/ALPSO.rst b/doc/optimizers/ALPSO.rst index fffc80f5..e09b952c 100644 --- a/doc/optimizers/ALPSO.rst +++ b/doc/optimizers/ALPSO.rst @@ -6,13 +6,8 @@ Augmented Lagrangian Particle Swarm Optimizer (ALPSO) is a PSO method that uses Options ------- -.. optimizertable:: pyoptsparse.pyALPSO.pyALPSO.ALPSO - :type: options - -Informs -------- -.. optimizertable:: pyoptsparse.pyALPSO.pyALPSO.ALPSO - :type: informs +.. optionstable:: pyoptsparse.pyALPSO.pyALPSO.ALPSO + :filename: optimizers/ALPSO_options.yaml API --- diff --git a/doc/optimizers/ALPSO_options.yaml b/doc/optimizers/ALPSO_options.yaml new file mode 100644 index 00000000..abf75ead --- /dev/null +++ b/doc/optimizers/ALPSO_options.yaml @@ -0,0 +1,70 @@ +SwarmSize: + desc: Number of Particles (Depends on Problem dimensions) +maxOuterIter: + desc: Maximum Number of Outer Loop Iterations (Major Iterations) +maxInnerIter: + desc: Maximum Number of Inner Loop Iterations (Minor Iterations) +minInnerIter: + desc: Minimum Number of Inner Loop Iterations (Dynamic Inner Iterations) +dynInnerIter: + desc: Dynamic Number of Inner Iterations Flag +stopCriteria: + desc: Stopping Criteria Flag (0 - maxIters, 1 - convergence) +stopIters: + desc: Consecutive Number of Iterations for which the Stopping Criteria must be Satisfied +etol: + desc: Absolute Tolerance for Equality constraints +itol: + desc: Absolute Tolerance for Inequality constraints +rtol: + desc: Relative Tolerance for Lagrange Multipliers +atol: + desc: Absolute Tolerance for Lagrange Function +dtol: + desc: Relative Tolerance in Distance of All Particles to Terminate (GCPSO) +printOuterIters: + desc: Number of Iterations Before Print Outer Loop Information +printInnerIters: + desc: Number of Iterations Before Print Inner Loop Information +rinit: + desc: Initial Penalty Factor +xinit: + desc: Initial Position Flag (0 - no position, 1 - position given) +vinit: + desc: Initial Velocity of Particles in Normalized [-1, 1] Design Space +vmax: + desc: Maximum Velocity of Particles in Normalized [-1, 1] Design Space +c1: + desc: Cognitive Parameter +c2: + desc: Social Parameter +w1: + desc: Initial Inertia Weight +w2: + desc: Final Inertia Weight +ns: + desc: Number of Consecutive Successes in Finding New Best Position of Best Particle Before Search Radius will be Increased (GCPSO) +nf: + desc: Number of Consecutive Failures in Finding New Best Position of Best Particle Before Search Radius will be Increased (GCPSO) +dt: + desc: Time step +vcrazy: + desc: Craziness Velocity (Added to Particle Velocity After Updating the Penalty Factors and Langangian Multipliers) +fileout: + desc: Flag to Turn On Output to filename +filename: + desc: We could probably remove fileout flag if filename or fileinstance is given +seed: + desc: Random Number Seed (0 - Auto-Seed based on time clock) +HoodSize: + desc: Number of Neighbours of Each Particle +HoodModel: + desc: Neighbourhood Model (dl/slring - Double/Single Link Ring, wheel - Wheel, Spatial - based on spatial distance, sfrac - Spatial Fraction) +HoodSelf: + desc: Selfless Neighbourhood Model (0 - Include Particle i in NH i, 1 - Don't Include Particle i) +Scaling: + desc: Design Variables Scaling Flag (0 - no scaling, 1 - scaling between [-1, 1]) +parallelType: + desc: Type of parallelization + null: No parallel function evaluations + EXT: Use parallel function evaluations diff --git a/doc/optimizers/CONMIN.rst b/doc/optimizers/CONMIN.rst index 5f2e6aaf..24d43fa2 100644 --- a/doc/optimizers/CONMIN.rst +++ b/doc/optimizers/CONMIN.rst @@ -6,13 +6,9 @@ CONstrained function MINimization (CONMIN) is a gradient-based optimizer that us Options ------- -.. optimizertable:: pyoptsparse.pyCONMIN.pyCONMIN.CONMIN - :type: options +.. optionstable:: pyoptsparse.pyCONMIN.pyCONMIN.CONMIN + :filename: optimizers/CONMIN_options.yaml -Informs -------- -.. optimizertable:: pyoptsparse.pyCONMIN.pyCONMIN.CONMIN - :type: informs API --- diff --git a/doc/optimizers/CONMIN_options.yaml b/doc/optimizers/CONMIN_options.yaml new file mode 100644 index 00000000..c1d6e5d1 --- /dev/null +++ b/doc/optimizers/CONMIN_options.yaml @@ -0,0 +1,16 @@ +ITMAX: + desc: Maximum Number of Iterations +DELFUN: + desc: Objective Relative Tolerance +DABFUN: + desc: Objective Absolute Tolerance +ITRM: + desc: None +NFEASCT: + desc: None +IPRINT: + desc: Print Control (0 - None, 1 - Final, 2,3,4 - Debug) +IOUT: + desc: Output Unit Number +IFILE: + desc: Output File Name diff --git a/doc/optimizers/IPOPT.rst b/doc/optimizers/IPOPT.rst index 59a60306..8a6fe74a 100644 --- a/doc/optimizers/IPOPT.rst +++ b/doc/optimizers/IPOPT.rst @@ -82,12 +82,13 @@ Please refer to the `IPOPT website + This specifies the problem type for SNOPT. + Minimize: minimization problem. + Maximize: maximization problem. + Feasible point: compute a feasible point only. + +Derivative level: + desc: > + The SNOPT derivative level. Only "3" is tested, where all derivatives are provided to SNOPT. + +Proximal iterations limit: + desc: > + The iterations limit for solving the proximal point problem. + We set this by default to a very large value in order to fully solve the proximal point problem to optimality + +Total character workspace: + desc: > + The total character workspace length. + If ``None``, a default value of ``500`` is used, as recommended by SNOPT. + If SNOPT determines that the default value is too small, the Python wrapper will overwrite the defaults with estimates for the required workspace lengths from SNOPT and initialize the optimizer for a second time. + SNOPT might still exit with ``82``, ``83``, or ``84``, but this should automate the storage allocation for most cases. + User-specified values are not overwritten. + +Total integer workspace: + desc: > + The total integer workspace length. + If ``None``, a default value of ``500 + 100 * (ncon + nvar)`` is used, as recommended by SNOPT. + If SNOPT determines that the default value is too small, the Python wrapper will overwrite the defaults with estimates for the required workspace lengths from SNOPT and initialize the optimizer for a second time. + SNOPT might still exit with ``82``, ``83``, or ``84``, but this should automate the storage allocation for most cases. + User-specified values are not overwritten. + + +Total real workspace: + desc: > + The total real workspace length. + If ``None``, a default value of ``500 + 200 * (ncon + nvar)`` is used, as recommended by SNOPT. + If SNOPT determines that the default value is too small, the Python wrapper will overwrite the defaults with estimates for the required workspace lengths from SNOPT and initialize the optimizer for a second time. + SNOPT might still exit with ``82``, ``83``, or ``84``, but this should automate the storage allocation for most cases. + User-specified values are not overwritten. + + +Save major iteration variables: + desc: > + This option is unique to the Python wrapper, and takes a list of values which can be saved at each iteration to the History file. + This specifies the list of major iteration variables to be stored in the history file. + ``Hessian``, ``slack``, ``lambda`` and ``condZHZ`` are also supported. \ No newline at end of file diff --git a/doc/requirements.txt b/doc/requirements.txt index df521922..6e378c4c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ numpydoc -sphinx_mdolab_theme +sphinx_mdolab_theme>=1.2 sphinxcontrib-bibtex diff --git a/pyoptsparse/__init__.py b/pyoptsparse/__init__.py index 26b6a7b0..57f3f09e 100644 --- a/pyoptsparse/__init__.py +++ b/pyoptsparse/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.4.1" +__version__ = "2.5.0" from .pyOpt_history import History from .pyOpt_variable import Variable diff --git a/pyoptsparse/pyALPSO/pyALPSO.py b/pyoptsparse/pyALPSO/pyALPSO.py index 3e2bcfd8..348c6a2f 100644 --- a/pyoptsparse/pyALPSO/pyALPSO.py +++ b/pyoptsparse/pyALPSO/pyALPSO.py @@ -39,63 +39,55 @@ def __init__(self, raiseError=True, options={}): self.alpso = alpso category = "Global Optimizer" - self.defOpts = { - "SwarmSize": [int, 40], # Number of Particles (Depends on Problem dimensions) - "maxOuterIter": [int, 200], # Maximum Number of Outer Loop Iterations (Major Iterations) - "maxInnerIter": [int, 6], # Maximum Number of Inner Loop Iterations (Minor Iterations) - "minInnerIter": [int, 6], # Minimum Number of Inner Loop Iterations (Dynamic Inner Iterations) - "dynInnerIter": [int, 0], # Dynamic Number of Inner Iterations Flag - "stopCriteria": [int, 1], # Stopping Criteria Flag (0 - maxIters, 1 - convergence) - "stopIters": [int, 5], # Consecutive Number of Iterations for which the Stopping Criteria must be Satisfied - "etol": [float, 1e-3], # Absolute Tolerance for Equality constraints - "itol": [float, 1e-3], # Absolute Tolerance for Inequality constraints - # 'ltol':[float, 1e-2], # Absolute Tolerance for Lagrange Multipliers - "rtol": [float, 1e-2], # Relative Tolerance for Lagrange Multipliers - "atol": [float, 1e-2], # Absolute Tolerance for Lagrange Function - "dtol": [float, 1e-1], # Relative Tolerance in Distance of All Particles to Terminate (GCPSO) - "printOuterIters": [int, 0], # Number of Iterations Before Print Outer Loop Information - "printInnerIters": [int, 0], # Number of Iterations Before Print Inner Loop Information - "rinit": [float, 1.0], # Initial Penalty Factor - "xinit": [int, 0], # Initial Position Flag (0 - no position, 1 - position given) - "vinit": [float, 1.0], # Initial Velocity of Particles in Normalized [-1, 1] Design Space - "vmax": [float, 2.0], # Maximum Velocity of Particles in Normalized [-1, 1] Design Space - "c1": [float, 2.0], # Cognitive Parameter - "c2": [float, 1.0], # Social Parameter - "w1": [float, 0.99], # Initial Inertia Weight - "w2": [float, 0.55], # Final Inertia Weight - "ns": [ - int, - 15, - ], # Number of Consecutive Successes in Finding New Best Position of Best Particle Before Search Radius will be Increased (GCPSO) - "nf": [ - int, - 5, - ], # Number of Consecutive Failures in Finding New Best Position of Best Particle Before Search Radius will be Increased (GCPSO) - "dt": [float, 1.0], # Time step - "vcrazy": [ - float, - 1e-4, - ], # Craziness Velocity (Added to Particle Velocity After Updating the Penalty Factors and Langangian Multipliers) - "fileout": [int, 1], # Flag to Turn On Output to filename - "filename": [ - str, - "ALPSO.out", - ], # We could probably remove fileout flag if filename or fileinstance is given - "seed": [int, 0], # Random Number Seed (0 - Auto-Seed based on time clock) - "HoodSize": [int, 40], # Number of Neighbours of Each Particle - "HoodModel": [ - str, - "gbest", - ], # Neighbourhood Model (dl/slring - Double/Single Link Ring, wheel - Wheel, Spatial - based on spatial distance, sfrac - Spatial Fraction) - "HoodSelf": [ - int, - 1, - ], # Selfless Neighbourhood Model (0 - Include Particle i in NH i, 1 - Don't Include Particle i) - "Scaling": [int, 1], # Design Variables Scaling Flag (0 - no scaling, 1 - scaling between [-1, 1]) - "parallelType": [str, ["", "EXT"]], # Type of parallelization ('' or 'EXT') + defOpts = self._getDefaultOptions() + informs = self._getInforms() + super().__init__("ALPSO", category, defaultOptions=defOpts, informs=informs, options=options) + + @staticmethod + def _getInforms(): + informs = {} + return informs + + @staticmethod + def _getDefaultOptions(): + defOpts = { + "SwarmSize": [int, 40], + "maxOuterIter": [int, 200], + "maxInnerIter": [int, 6], + "minInnerIter": [int, 6], + "dynInnerIter": [int, 0], + "stopCriteria": [int, 1], + "stopIters": [int, 5], + "etol": [float, 1e-3], + "itol": [float, 1e-3], + # 'ltol':[float, 1e-2], + "rtol": [float, 1e-2], + "atol": [float, 1e-2], + "dtol": [float, 1e-1], + "printOuterIters": [int, 0], + "printInnerIters": [int, 0], + "rinit": [float, 1.0], + "xinit": [int, 0], + "vinit": [float, 1.0], + "vmax": [float, 2.0], + "c1": [float, 2.0], + "c2": [float, 1.0], + "w1": [float, 0.99], + "w2": [float, 0.55], + "ns": [int, 15], + "nf": [int, 5], + "dt": [float, 1.0], + "vcrazy": [float, 1e-4], + "fileout": [int, 1], + "filename": [str, "ALPSO.out"], + "seed": [int, 0], + "HoodSize": [int, 40], + "HoodModel": [str, "gbest"], + "HoodSelf": [int, 1], + "Scaling": [int, 1], + "parallelType": [str, [None, "EXT"]], } - self.informs = {} - super().__init__("ALPSO", category, defaultOptions=self.defOpts, informs=self.informs, options=options) + return defOpts def __call__(self, optProb, storeHistory=None, **kwargs): """ @@ -211,8 +203,7 @@ def objconfunc(x): def _on_setOption(self, name, value): if name == "parallelType": - value = value.upper() - if value == "EXT": + if isinstance(value, str) and value.upper() == "EXT": try: from . import alpso_ext diff --git a/pyoptsparse/pyCONMIN/pyCONMIN.py b/pyoptsparse/pyCONMIN/pyCONMIN.py index 72670362..04696fdd 100644 --- a/pyoptsparse/pyCONMIN/pyCONMIN.py +++ b/pyoptsparse/pyCONMIN/pyCONMIN.py @@ -39,27 +39,37 @@ class CONMIN(Optimizer): def __init__(self, raiseError=True, options={}): name = "CONMIN" category = "Local Optimizer" - self.defOpts = { - "ITMAX": [int, int(1e4)], # Maximum Number of Iterations - "DELFUN": [float, 1e-6], # Objective Relative Tolerance - "DABFUN": [float, 1e-6], # Objective Absolute Tolerance - "ITRM": [int, 5], - "NFEASCT": [int, 20], - "IPRINT": [int, 4], # Print Control (0 - None, 1 - Final, 2,3,4 - Debug) - "IOUT": [int, 6], # Output Unit Number - "IFILE": [str, "CONMIN.out"], # Output File Name - } - self.informs = {} + defOpts = self._getDefaultOptions() + informs = self._getInforms() if conmin is None: if raiseError: raise Error("There was an error importing the compiled conmin module") self.set_options = [] - super().__init__(name, category, defaultOptions=self.defOpts, informs=self.informs, options=options) + super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options) # CONMIN needs Jacobians in dense format self.jacType = "dense2d" + @staticmethod + def _getInforms(): + informs = {} + return informs + + @staticmethod + def _getDefaultOptions(): + defOpts = { + "ITMAX": [int, int(1e4)], + "DELFUN": [float, 1e-6], + "DABFUN": [float, 1e-6], + "ITRM": [int, 5], + "NFEASCT": [int, 20], + "IPRINT": [int, 4], + "IOUT": [int, 6], + "IFILE": [str, "CONMIN.out"], + } + return defOpts + def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True ): diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index ec5536f4..2f663096 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -45,15 +45,28 @@ def __init__(self, raiseError=True, options={}): name = "IPOPT" category = "Local Optimizer" + defOpts = self._getDefaultOptions() + informs = self._getInforms() - self.defOpts = { - "print_level": [int, 0], - "output_file": [str, "IPOPT.out"], - "option_file_name": [str, "IPOPT_options.opt"], - "linear_solver": [str, "mumps"], - } + if pyipoptcore is None: + if raiseError: + raise Error("There was an error importing the compiled IPOPT module") + + super().__init__( + name, + category, + defaultOptions=defOpts, + informs=informs, + options=options, + checkDefaultOptions=False, + ) + + # IPOPT needs Jacobians in coo format + self.jacType = "coo" - self.informs = { + @staticmethod + def _getInforms(): + informs = { 0: "Solve Succeeded", 1: "Solved To Acceptable Level", 2: "Infeasible Problem Detected", @@ -74,22 +87,17 @@ def __init__(self, raiseError=True, options={}): -102: "Insufficient Memory", -199: "Internal Error", } + return informs - if pyipoptcore is None: - if raiseError: - raise Error("There was an error importing the compiled IPOPT module") - - super().__init__( - name, - category, - defaultOptions=self.defOpts, - informs=self.informs, - options=options, - checkDefaultOptions=False, - ) - - # IPOPT needs Jacobians in coo format - self.jacType = "coo" + @staticmethod + def _getDefaultOptions(): + defOpts = { + "print_level": [int, 0], + "output_file": [str, "IPOPT.out"], + "option_file_name": [str, "IPOPT_options.opt"], + "linear_solver": [str, "mumps"], + } + return defOpts def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True diff --git a/pyoptsparse/pyNLPQLP/pyNLPQLP.py b/pyoptsparse/pyNLPQLP/pyNLPQLP.py index 9cfeee6f..bae4b4e3 100644 --- a/pyoptsparse/pyNLPQLP/pyNLPQLP.py +++ b/pyoptsparse/pyNLPQLP/pyNLPQLP.py @@ -39,23 +39,19 @@ class NLPQLP(Optimizer): def __init__(self, raiseError=True, options={}): name = "NLPQLP" category = "Local Optimizer" - self.defOpts = { - # NLPQL Options - "accuracy": [float, 1e-6], # Convergence accuracy - "accuracyQP": [float, 1e-14], # Convergence accuracy for QP - "stepMin": [float, 1e-6], # Minimum step length - "maxFun": [int, 20], # Maximum Number of Function Calls During Line Search - "maxIt": [int, 500], # Maximum Number of Iterations - "maxNM": [int, 1], # Maximum stack size for non-monotone line search - "rho": [float, 1.0], # Factor scaling identify for IFAIL=2 - "iPrint": [int, 2], # Output Level (0 - None, 1 - Final, 2 - Major, 3 - Major/Minor, 4 - Full) - "mode": [int, 0], # NLPQL Mode (0 - Normal Execution, 1 to 18 - See Manual) - "iOut": [int, 6], # Output Unit Number - "lMerit": [bool, True], # Merit Function Type (True - L2 Augmented Penalty, False - L1 Penalty) - "lQl": [bool, False], # QP Subproblem Solver (True - Quasi-Newton, False - Cholesky) - "iFile": [str, "NLPQLP.out"], # Output File Name - } - self.informs = { + defOpts = self._getDefaultOptions() + informs = self._getInforms() + if nlpqlp is None: + if raiseError: + raise Error("There was an error importing the compiled nlpqlp module") + + super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options) + # NLPQLP needs Jacobians in dense format + self.jacType = "dense2d" + + @staticmethod + def _getInforms(): + informs = { -2: ( "Compute gradient values w.r.t. the variables stored in" + " first column of X, and store them in DF and DG." @@ -66,9 +62,9 @@ def __init__(self, raiseError=True, options={}): + "the variables found in the first L columns of X, and store them in F and G." ), 0: "The optimality conditions are satisfied.", - 1: " The algorithm has been stopped after MAXIT iterations.", - 2: " The algorithm computed an uphill search direction.", - 3: " Underflow occurred when determining a new approximation matrix for the Hessian of the Lagrangian.", + 1: "The algorithm has been stopped after MAXIT iterations.", + 2: "The algorithm computed an uphill search direction.", + 3: "Underflow occurred when determining a new approximation matrix for the Hessian of the Lagrangian.", 4: "The line search could not be terminated successfully.", 5: "Length of a working array is too short. More detailed error information is obtained with IPRINT>0", 6: "There are false dimensions, for example M>MMAX, N>=NMAX, or MNN2<>M+N+N+2.", @@ -83,13 +79,26 @@ def __init__(self, raiseError=True, options={}): + " where IFQL denotes the index of an inconsistent constraint." ), } - if nlpqlp is None: - if raiseError: - raise Error("There was an error importing the compiled nlpqlp module") - - super().__init__(name, category, defaultOptions=self.defOpts, informs=self.informs, options=options) - # NLPQLP needs Jacobians in dense format - self.jacType = "dense2d" + return informs + + @staticmethod + def _getDefaultOptions(): + defOpts = { + "accuracy": [float, 1e-6], + "accuracyQP": [float, 1e-14], + "stepMin": [float, 1e-6], + "maxFun": [int, 20], + "maxIt": [int, 500], + "maxNM": [int, 1], + "rho": [float, 1.0], + "iPrint": [int, [2, 0, 1, 3, 4]], + "mode": [int, 0], + "iOut": [int, 6], + "lMerit": [bool, True], + "lQl": [bool, False], + "iFile": [str, "NLPQLP.out"], + } + return defOpts def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True diff --git a/pyoptsparse/pyNSGA2/pyNSGA2.py b/pyoptsparse/pyNSGA2/pyNSGA2.py index 64cda2da..c54abdff 100644 --- a/pyoptsparse/pyNSGA2/pyNSGA2.py +++ b/pyoptsparse/pyNSGA2/pyNSGA2.py @@ -38,7 +38,22 @@ def __init__(self, raiseError=True, options={}): name = "NSGA-II" category = "Global Optimizer" - self.defOpts = { + defOpts = self._getDefaultOptions() + informs = self._getInforms() + super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options) + + if nsga2 is None: + if raiseError: + raise Error("There was an error importing the compiled nsga2 module") + + @staticmethod + def _getInforms(): + informs = {} + return informs + + @staticmethod + def _getDefaultOptions(): + defOpts = { "PopSize": [int, 100], "maxGen": [int, 1000], "pCross_real": [float, 0.6], @@ -47,16 +62,11 @@ def __init__(self, raiseError=True, options={}): "eta_m": [float, 20.0], "pCross_bin": [float, 0.0], "pMut_bin": [float, 0.0], - "PrintOut": [int, 1], # Flag to Turn On Output to filename (0 - , 1 - , 2 - ) - "seed": [int, 0], # Random Number Seed (0 - Auto-Seed based on time clock) - "xinit": [int, 0], # Use Initial Solution Flag (0 - random population, 1 - use given solution) + "PrintOut": [int, 1], + "seed": [int, 0], + "xinit": [int, 0], } - self.informs = {} - super().__init__(name, category, defaultOptions=self.defOpts, informs=self.informs, options=options) - - if nsga2 is None: - if raiseError: - raise Error("There was an error importing the compiled nsga2 module") + return defOpts def __call__(self, optProb, storeHistory=None, hotStart=None, **kwargs): """ diff --git a/pyoptsparse/pyPSQP/pyPSQP.py b/pyoptsparse/pyPSQP/pyPSQP.py index 3542954f..fdf5804f 100644 --- a/pyoptsparse/pyPSQP/pyPSQP.py +++ b/pyoptsparse/pyPSQP/pyPSQP.py @@ -38,21 +38,39 @@ class PSQP(Optimizer): def __init__(self, raiseError=True, options={}): name = "PSQP" category = "Local Optimizer" - self.defOpts = { - "XMAX": [float, 1e16], # Maximum Stepsize - "TOLX": [float, 1e-16], # Variable Change Tolerance - "TOLC": [float, 1e-6], # Constraint Violation Tolerance - "TOLG": [float, 1e-6], # Lagrangian Gradient Tolerance - "RPF": [float, 1e-4], # Penalty Coefficient - "MIT": [int, 1000], # Maximum Number of Iterations - "MFV": [int, 2000], # Maximum Number of Function Evaluations - "MET": [int, 2], # Variable Metric Update (1 - BFGS, 2 - Hoshino) - "MEC": [int, 2], # Negative Curvature Correction (1 - None, 2 - Powell's Correction) - "IPRINT": [int, 2], # Output Level (0 - None, 1 - Final, 2 - Iter) - "IOUT": [int, 6], # Output Unit Number - "IFILE": [str, "PSQP.out"], # Output File Name + defOpts = self._getDefaultOptions() + informs = self._getInforms() + + if psqp is None: + if raiseError: + raise Error("There was an error importing the compiled psqp module") + + 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"], } - self.informs = { + 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", @@ -65,15 +83,7 @@ def __init__(self, raiseError=True, options={}): -8: "Interpolation error in line search", -10: "Optimization failed", } - - if psqp is None: - if raiseError: - raise Error("There was an error importing the compiled psqp module") - - super().__init__(name, category, defaultOptions=self.defOpts, informs=self.informs, options=options) - - # PSQP needs Jacobians in dense format - self.jacType = "dense2d" + return informs def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True diff --git a/pyoptsparse/pySLSQP/pySLSQP.py b/pyoptsparse/pySLSQP/pySLSQP.py index a0f0d809..3c19ebda 100644 --- a/pyoptsparse/pySLSQP/pySLSQP.py +++ b/pyoptsparse/pySLSQP/pySLSQP.py @@ -39,15 +39,32 @@ class SLSQP(Optimizer): def __init__(self, raiseError=True, options={}): name = "SLSQP" category = "Local Optimizer" - self.defOpts = { - # SLSQP Options - "ACC": [float, 1e-6], # Convergence Accurancy - "MAXIT": [int, 500], # Maximum Iterations - "IPRINT": [int, 1], # Output Level (<0 - None, 0 - Screen, 1 - File) - "IOUT": [int, 60], # Output Unit Number - "IFILE": [str, "SLSQP.out"], # Output File Name + defOpts = self._getDefaultOptions() + informs = self._getInforms() + if slsqp is None: + if raiseError: + raise Error("There was an error importing the compiled slsqp module") + + self.set_options = [] + super().__init__(name, category, defaultOptions=defOpts, informs=informs, options=options) + + # SLSQP needs Jacobians in dense format + self.jacType = "dense2d" + + @staticmethod + def _getDefaultOptions(): + defOpts = { + "ACC": [float, 1e-6], + "MAXIT": [int, 500], + "IPRINT": [int, 1], + "IOUT": [int, 60], + "IFILE": [str, "SLSQP.out"], } - self.informs = { + return defOpts + + @staticmethod + def _getInforms(): + informs = { -1: "Gradient evaluation required (g & a)", 0: "Optimization terminated successfully.", 1: "Function evaluation required (f & c)", @@ -60,15 +77,7 @@ def __init__(self, raiseError=True, options={}): 8: "Positive directional derivative for linesearch", 9: "Iteration limit exceeded", } - if slsqp is None: - if raiseError: - raise Error("There was an error importing the compiled slsqp module") - - self.set_options = [] - super().__init__(name, category, defaultOptions=self.defOpts, informs=self.informs, options=options) - - # SLSQP needs Jacobians in dense format - self.jacType = "dense2d" + return informs def __call__( self, optProb, sens=None, sensStep=None, sensMode=None, storeHistory=None, hotStart=None, storeSens=True diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index 336aafee..12415d67 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -46,23 +46,7 @@ def __init__(self, raiseError=True, options={}): name = "SNOPT" category = "Local Optimizer" - self.defOpts = { - "iPrint": [int, 18], # Print File Output Unit (override internally in snopt?) - "iSumm": [int, 19], # Summary File Output Unit (override internally in snopt?) - "Print file": [str, "SNOPT_print.out"], # Print File Name (specified by subroutine snInit) - "Summary file": [str, "SNOPT_summary.out"], # Summary File Name (specified by subroutine snInit) - "Problem Type": [str, ["Minimize", "Maximize", "Feasible point"]], - "Start": [str, ["Cold", "Warm"]], # used in call to snkerc, the option "Cold Start" etc are NOT allowed - "Derivative level": [int, 3], - "Proximal iterations limit": [int, 10000], # very large # to solve proximal point problem to optimality - "Total character workspace": [int, None], # lencw: 500 - "Total integer workspace": [int, None], # leniw: 500 + 100 * (ncon + nvar) - "Total real workspace": [int, None], # lenrw: 500 + 200 * (ncon + nvar) - "Save major iteration variables": [ - list, - ["step", "merit", "feasibility", "optimality", "penalty"], - ], # 'Hessian', 'slack', 'lambda' and 'condZHZ' are also supported - } + defOpts = self._getDefaultOptions() # these are SNOPT-related options that do not get set via snset self.specialOptions = CaseInsensitiveSet( { @@ -74,7 +58,62 @@ def __init__(self, raiseError=True, options={}): # this is purely within pySNOPT, nothing to do with SNOPT itself self.pythonOptions = CaseInsensitiveSet({"Save major iteration variables"}) - self.informs = { + informs = self._getInforms() + + if snopt is None: + if raiseError: + raise Error("There was an error importing the compiled snopt module") + else: + version = None + else: + # extract SNOPT version + version_str = snopt.sntitle().decode("utf-8") + # The version_str is going to look like + # S N O P T 7.7.5 (Oct 2020) + # we search between "S N O P T" and "(" + res = re.search("S N O P T(.*)\(", version_str) + if res is not None: + version = res.group(1).strip() + else: + version = None + + super().__init__( + name, + category, + defaultOptions=defOpts, + informs=informs, + options=options, + checkDefaultOptions=False, + version=version, + ) + + # SNOPT need Jacobians in csc format + self.jacType = "csc" + + # SNOPT specific Jacobian map + self._snopt_jac_map_csr_to_csc = None + + @staticmethod + def _getDefaultOptions(): + defOpts = { + "iPrint": [int, 18], + "iSumm": [int, 19], + "Print file": [str, "SNOPT_print.out"], + "Summary file": [str, "SNOPT_summary.out"], + "Problem Type": [str, ["Minimize", "Maximize", "Feasible point"]], + "Start": [str, ["Cold", "Warm"]], + "Derivative level": [int, 3], + "Proximal iterations limit": [int, 10000], + "Total character workspace": [int, None], + "Total integer workspace": [int, None], + "Total real workspace": [int, None], + "Save major iteration variables": [list, ["step", "merit", "feasibility", "optimality", "penalty"]], + } + return defOpts + + @staticmethod + def _getInforms(): + informs = { 0: "finished successfully", 1: "optimality conditions satisfied", 2: "feasible point found", @@ -146,39 +185,7 @@ def __init__(self, raiseError=True, options={}): 141: "wrong no of basic variables", 142: "error in basis package", } - - if snopt is None: - if raiseError: - raise Error("There was an error importing the compiled snopt module") - else: - version = None - else: - # extract SNOPT version - version_str = snopt.sntitle().decode("utf-8") - # The version_str is going to look like - # S N O P T 7.7.5 (Oct 2020) - # we search between "S N O P T" and "(" - res = re.search("S N O P T(.*)\(", version_str) - if res is not None: - version = res.group(1).strip() - else: - version = None - - super().__init__( - name, - category, - defaultOptions=self.defOpts, - informs=self.informs, - options=options, - checkDefaultOptions=False, - version=version, - ) - - # SNOPT need Jacobians in csc format - self.jacType = "csc" - - # SNOPT specific Jacobian map - self._snopt_jac_map_csr_to_csc = None + return informs def __call__( self,