Skip to content
Merged
2 changes: 1 addition & 1 deletion pyoptsparse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.14.0"
__version__ = "2.14.1"

from .pyOpt_history import History
from .pyOpt_variable import Variable
Expand Down
4 changes: 2 additions & 2 deletions pyoptsparse/pyALPSO/pyALPSO.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ def objconfunc(x):
# Broadcast a -1 to indcate NSGA2 has finished
self.optProb.comm.bcast(-1, root=0)

# Store Results
sol_inform = {"value": "", "text": ""}
# Optimizer has no exit conditions, so nothing to set
sol_inform = None

# Create the optimization solution
sol = self._createSolution(optTime, sol_inform, opt_f, opt_x)
Expand Down
4 changes: 2 additions & 2 deletions pyoptsparse/pyCONMIN/pyCONMIN.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ def cnmngrad(n1, n2, x, f, g, ct, df, a, ic, nac):
# Broadcast a -1 to indcate SLSQP has finished
self.optProb.comm.bcast(-1, root=0)

# Store Results
sol_inform = {"value": "", "text": ""}
# Optimizer has no exit conditions, so nothing to set
sol_inform = None

# Create the optimization solution
sol = self._createSolution(optTime, sol_inform, ff, xs)
Expand Down
7 changes: 3 additions & 4 deletions pyoptsparse/pyIPOPT/pyIPOPT.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

# Local modules
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_solution import SolutionInform
from ..pyOpt_utils import ICOL, INFINITY, IROW, convertToCOO, extractRows, scaleRows


Expand Down Expand Up @@ -274,10 +275,8 @@ def intermediate(_, *args, **kwargs):
self.hist.writeData("metadata", self.metadata)
self.hist.close()

# Store Results
sol_inform = {}
sol_inform["value"] = info["status"]
sol_inform["text"] = self.informs[info["status"]]
# Store optimizer exit condition and message
sol_inform = SolutionInform.from_informs(self.informs, info["status"])

# Create the optimization solution
sol = self._createSolution(optTime, sol_inform, info["obj_val"], x, multipliers=info["mult_g"])
Expand Down
7 changes: 3 additions & 4 deletions pyoptsparse/pyNLPQLP/pyNLPQLP.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Local modules
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_solution import SolutionInform
from ..pyOpt_utils import try_import_compiled_module_from_path

# import the compiled module
Expand Down Expand Up @@ -254,11 +255,9 @@ def nlgrad(m, me, mmax, n, f, g, df, dg, x, active, wa):
self.hist.writeData("metadata", self.metadata)
self.hist.close()

# Store Results
# Store optimizer exit condition and message
inform = ifail.item()
sol_inform = {}
sol_inform["value"] = inform
sol_inform["text"] = self.informs[inform]
sol_inform = SolutionInform.from_informs(self.informs, inform)

# Create the optimization solution
sol = self._createSolution(optTime, sol_inform, f, xs)
Expand Down
4 changes: 2 additions & 2 deletions pyoptsparse/pyNSGA2/pyNSGA2.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ def objconfunc(nreal, nobj, ncon, x, f, g):
# Broadcast a -1 to indcate NSGA2 has finished
self.optProb.comm.bcast(-1, root=0)

# Store Results
sol_inform = {"value": "", "text": ""}
# Optimizer has no exit conditions, so nothing to set
sol_inform = None

xstar = [0.0] * n
for i in range(n):
Expand Down
2 changes: 1 addition & 1 deletion pyoptsparse/pyOpt_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def _processDB(self):

if self.metadata["version"] != __version__:
pyOptSparseWarning(
"The version of pyoptsparse used to generate the history file does not match the one being run right now. There may be compatibility issues."
f"The version of pyoptsparse used to generate the history file (v{self.metadata['version']}) does not match the one being run right now (v{__version__}). There may be compatibility issues."
)

def getIterKeys(self):
Expand Down
4 changes: 2 additions & 2 deletions pyoptsparse/pyOpt_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ def _assembleObjective(self):

return np.real(np.squeeze(ff))

def _createSolution(self, optTime, sol_inform, obj, xopt, multipliers=None) -> Solution:
def _createSolution(self, optTime, solInform, obj, xopt, multipliers=None) -> Solution:
"""
Generic routine to create the solution after an optimizer
finishes.
Expand Down Expand Up @@ -824,7 +824,7 @@ def _createSolution(self, optTime, sol_inform, obj, xopt, multipliers=None) -> S
"interfaceTime": self.interfaceTime - self.userSensTime - self.userObjTime,
"optCodeTime": optTime - self.interfaceTime,
}
sol = Solution(self.optProb, xStar, fStar, multipliers, sol_inform, info)
sol = Solution(self.optProb, xStar, fStar, multipliers, solInform, info)

return sol

Expand Down
37 changes: 28 additions & 9 deletions pyoptsparse/pyOpt_solution.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Standard Python modules
import copy
from dataclasses import dataclass
from typing import Optional

# External modules
import numpy as np
Expand All @@ -8,8 +10,22 @@
from .pyOpt_optimization import Optimization


@dataclass(frozen=True)
class SolutionInform:
"""Data class that contains the optimizer solution value and message"""

value: int
"""The integer return code"""
message: str
"""The message string accompanying the return code"""

@classmethod
def from_informs(cls, informs: dict[int, str], value: int):
return cls(value=value, message=informs[value])


class Solution(Optimization):
def __init__(self, optProb, xStar, fStar, lambdaStar, optInform, info):
def __init__(self, optProb, xStar, fStar, lambdaStar, optInform: Optional[SolutionInform], info):
"""
This class is used to describe the solution of an optimization
problem. This class inherits from Optimization which enables a
Expand All @@ -29,8 +45,9 @@ def __init__(self, optProb, xStar, fStar, lambdaStar, optInform, info):
lambdaStar : dict
The final Lagrange multipliers

optInform : int
The inform code returned by the optimizer
optInform : SolutionInform or None
Object containing the inform code and message returned by the optimizer.
Optimizers that do not have inform exit codes do not set this variable.

info : dict
A dictionary containing timing and call counter info to be stored
Expand Down Expand Up @@ -95,12 +112,14 @@ def __str__(self) -> str:
for i in range(5, len(lines)):
text1 += lines[i] + "\n"

inform_val = self.optInform["value"]
inform_text = self.optInform["text"]
text1 += "\n"
text1 += " Exit Status\n"
text1 += " Inform Description\n"
text1 += f" {inform_val:>6} {inform_text:<0}\n"
# Only print exit status, inform, and description if the optimizer provides informs
if self.optInform:
inform_val = self.optInform.value
inform_text = self.optInform.message
text1 += "\n"
text1 += " Exit Status\n"
text1 += " Inform Description\n"
text1 += f" {inform_val:>6} {inform_text:<0}\n"

text1 += ("-" * 80) + "\n"

Expand Down
5 changes: 2 additions & 3 deletions pyoptsparse/pyPSQP/pyPSQP.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# Local modules
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_solution import SolutionInform
from ..pyOpt_utils import try_import_compiled_module_from_path

# import the compiled module
Expand Down Expand Up @@ -259,9 +260,7 @@ def pdcon(n, k, x, dg):
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]
sol_inform = SolutionInform.from_informs(self.informs, inform)
if self.storeHistory:
self.metadata["endTime"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.metadata["optTime"] = optTime
Expand Down
9 changes: 4 additions & 5 deletions pyoptsparse/pySLSQP/pySLSQP.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Local modules
from ..pyOpt_error import pyOptSparseWarning
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_solution import SolutionInform
from ..pyOpt_utils import try_import_compiled_module_from_path

# import the compiled module
Expand Down Expand Up @@ -168,7 +169,7 @@ def __call__(
# =================================================================
def slfunc(m, me, la, n, f, g, x):
if (x < blx).any() or (x > bux).any():
pyOptSparseWarning("Values in x were outside bounds during" " a minimize step, clipping to bounds")
pyOptSparseWarning("Values in x were outside bounds during a minimize step, clipping to bounds")
fobj, fcon, fail = self._masterFunc(np.clip(x, blx, bux), ["fobj", "fcon"])
f = fobj
g[0:m] = -fcon
Expand Down Expand Up @@ -258,11 +259,9 @@ def slgrad(m, me, la, n, f, g, df, dg, x):
# Broadcast a -1 to indcate SLSQP has finished
self.optProb.comm.bcast(-1, root=0)

# Store Results
# Store optimizer exit condition and message
inform = mode.item()
sol_inform = {}
sol_inform["value"] = inform
sol_inform["text"] = self.informs[inform]
sol_inform = SolutionInform.from_informs(self.informs, inform)

# Create the optimization solution
sol = self._createSolution(optTime, sol_inform, ff, xs)
Expand Down
7 changes: 3 additions & 4 deletions pyoptsparse/pySNOPT/pySNOPT.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# Local modules
from ..pyOpt_optimization import Optimization
from ..pyOpt_optimizer import Optimizer
from ..pyOpt_solution import SolutionInform
from ..pyOpt_utils import (
ICOL,
IDATA,
Expand Down Expand Up @@ -499,10 +500,8 @@ def __call__(
if iSumm != 0 and iSumm != 6:
snopt.closeunit(self.getOption("iSumm"))

# Store Results
sol_inform = {}
sol_inform["value"] = inform
sol_inform["text"] = self.informs[inform]
# Store optimizer exit condition and message
sol_inform = SolutionInform.from_informs(self.informs, inform)

# Create the optimization solution
if parse_version(self.version) > parse_version("7.7.0") and parse_version(self.version) < parse_version(
Expand Down
6 changes: 3 additions & 3 deletions pyoptsparse/testing/pyOpt_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ def assert_inform_equal(self, sol, optInform=None):
----------
sol : Solution object
The solution object after optimization
optInform : int, optional
optInform : SolutionInform or None, optional
The expected inform. If None, the default inform is used, which corresponds to
a successful optimization termination.
"""
if optInform is not None:
self.assertEqual(sol.optInform["value"], optInform)
self.assertEqual(sol.optInform.value, optInform)
else:
# some optimizers do not have informs
if self.optName in SUCCESS_INFORM:
self.assertEqual(sol.optInform["value"], SUCCESS_INFORM[self.optName])
self.assertEqual(sol.optInform.value, SUCCESS_INFORM[self.optName])

def get_default_hst_name(self):
# self.id() is provided by unittest.TestCase automatically
Expand Down
2 changes: 1 addition & 1 deletion tests/test_slsqp.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ def sens(xdict, funcs):
optProb.addObj("obj")
opt = OPT("SLSQP")
sol = opt(optProb, sens=sens)
self.assertEqual(sol.optInform["value"], 0)
self.assertEqual(sol.optInform.value, 0)
self.assertGreaterEqual(sol.xStar["xvars"][0], 0)
self.assertAlmostEqual(sol.xStar["xvars"][0], 0, places=9)
4 changes: 2 additions & 2 deletions tests/test_user_termination.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def test_obj(self, optName):
self.assertEqual(termcomp.obj_count, 3)

# Exit code for user requested termination.
self.assertEqual(sol.optInform["value"], self.optExitCode[optName])
self.assertEqual(sol.optInform.value, self.optExitCode[optName])

@parameterized.expand(["IPOPT", "SNOPT"])
def test_sens(self, optName):
Expand All @@ -131,7 +131,7 @@ def test_sens(self, optName):
self.assertEqual(termcomp.sens_count, 4)

# Exit code for user requested termination.
self.assertEqual(sol.optInform["value"], self.optExitCode[optName])
self.assertEqual(sol.optInform.value, self.optExitCode[optName])


if __name__ == "__main__":
Expand Down
Loading