Skip to content

Commit dd1e3bc

Browse files
eirikurjewu63
andauthored
Change solution inform into a dataclass (#437)
* revert sol_inform back to emtpy dict, update Solution class * rename variable to camelCase * solution inform as dataclass * whoops leftover from local branch * fix for python < 3.12 * fix bad merge * isort * update testing utils * update tests * update type hints to support python 3.9 * reason -> message * added alternate constructor * rebase confusion * version bump * solInform -> sol_inform --------- Co-authored-by: Ella Wu <[email protected]>
1 parent 2b35f53 commit dd1e3bc

File tree

15 files changed

+59
-45
lines changed

15 files changed

+59
-45
lines changed

pyoptsparse/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.14.0"
1+
__version__ = "2.14.1"
22

33
from .pyOpt_history import History
44
from .pyOpt_variable import Variable

pyoptsparse/pyALPSO/pyALPSO.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ def objconfunc(x):
190190
# Broadcast a -1 to indcate NSGA2 has finished
191191
self.optProb.comm.bcast(-1, root=0)
192192

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

196196
# Create the optimization solution
197197
sol = self._createSolution(optTime, sol_inform, opt_f, opt_x)

pyoptsparse/pyCONMIN/pyCONMIN.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ def cnmngrad(n1, n2, x, f, g, ct, df, a, ic, nac):
239239
# Broadcast a -1 to indcate SLSQP has finished
240240
self.optProb.comm.bcast(-1, root=0)
241241

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

245245
# Create the optimization solution
246246
sol = self._createSolution(optTime, sol_inform, ff, xs)

pyoptsparse/pyIPOPT/pyIPOPT.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
# Local modules
2020
from ..pyOpt_optimizer import Optimizer
21+
from ..pyOpt_solution import SolutionInform
2122
from ..pyOpt_utils import ICOL, INFINITY, IROW, convertToCOO, extractRows, scaleRows
2223

2324

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

277-
# Store Results
278-
sol_inform = {}
279-
sol_inform["value"] = info["status"]
280-
sol_inform["text"] = self.informs[info["status"]]
278+
# Store optimizer exit condition and message
279+
sol_inform = SolutionInform.from_informs(self.informs, info["status"])
281280

282281
# Create the optimization solution
283282
sol = self._createSolution(optTime, sol_inform, info["obj_val"], x, multipliers=info["mult_g"])

pyoptsparse/pyNLPQLP/pyNLPQLP.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# Local modules
1515
from ..pyOpt_optimizer import Optimizer
16+
from ..pyOpt_solution import SolutionInform
1617
from ..pyOpt_utils import try_import_compiled_module_from_path
1718

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

257-
# Store Results
258+
# Store optimizer exit condition and message
258259
inform = ifail.item()
259-
sol_inform = {}
260-
sol_inform["value"] = inform
261-
sol_inform["text"] = self.informs[inform]
260+
sol_inform = SolutionInform.from_informs(self.informs, inform)
262261

263262
# Create the optimization solution
264263
sol = self._createSolution(optTime, sol_inform, f, xs)

pyoptsparse/pyNSGA2/pyNSGA2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ def objconfunc(nreal, nobj, ncon, x, f, g):
189189
# Broadcast a -1 to indcate NSGA2 has finished
190190
self.optProb.comm.bcast(-1, root=0)
191191

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

195195
xstar = [0.0] * n
196196
for i in range(n):

pyoptsparse/pyOpt_history.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ def _processDB(self):
221221

222222
if self.metadata["version"] != __version__:
223223
pyOptSparseWarning(
224-
"The version of pyoptsparse used to generate the history file does not match the one being run right now. There may be compatibility issues."
224+
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."
225225
)
226226

227227
def getIterKeys(self):

pyoptsparse/pyOpt_optimizer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ def _assembleObjective(self):
794794

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

797-
def _createSolution(self, optTime, sol_inform, obj, xopt, multipliers=None) -> Solution:
797+
def _createSolution(self, optTime, solInform, obj, xopt, multipliers=None) -> Solution:
798798
"""
799799
Generic routine to create the solution after an optimizer
800800
finishes.
@@ -824,7 +824,7 @@ def _createSolution(self, optTime, sol_inform, obj, xopt, multipliers=None) -> S
824824
"interfaceTime": self.interfaceTime - self.userSensTime - self.userObjTime,
825825
"optCodeTime": optTime - self.interfaceTime,
826826
}
827-
sol = Solution(self.optProb, xStar, fStar, multipliers, sol_inform, info)
827+
sol = Solution(self.optProb, xStar, fStar, multipliers, solInform, info)
828828

829829
return sol
830830

pyoptsparse/pyOpt_solution.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Standard Python modules
22
import copy
3+
from dataclasses import dataclass
4+
from typing import Optional
35

46
# External modules
57
import numpy as np
@@ -8,8 +10,22 @@
810
from .pyOpt_optimization import Optimization
911

1012

13+
@dataclass(frozen=True)
14+
class SolutionInform:
15+
"""Data class that contains the optimizer solution value and message"""
16+
17+
value: int
18+
"""The integer return code"""
19+
message: str
20+
"""The message string accompanying the return code"""
21+
22+
@classmethod
23+
def from_informs(cls, informs: dict[int, str], value: int):
24+
return cls(value=value, message=informs[value])
25+
26+
1127
class Solution(Optimization):
12-
def __init__(self, optProb, xStar, fStar, lambdaStar, optInform, info):
28+
def __init__(self, optProb, xStar, fStar, lambdaStar, optInform: Optional[SolutionInform], info):
1329
"""
1430
This class is used to describe the solution of an optimization
1531
problem. This class inherits from Optimization which enables a
@@ -29,8 +45,9 @@ def __init__(self, optProb, xStar, fStar, lambdaStar, optInform, info):
2945
lambdaStar : dict
3046
The final Lagrange multipliers
3147
32-
optInform : int
33-
The inform code returned by the optimizer
48+
optInform : SolutionInform or None
49+
Object containing the inform code and message returned by the optimizer.
50+
Optimizers that do not have inform exit codes do not set this variable.
3451
3552
info : dict
3653
A dictionary containing timing and call counter info to be stored
@@ -95,12 +112,14 @@ def __str__(self) -> str:
95112
for i in range(5, len(lines)):
96113
text1 += lines[i] + "\n"
97114

98-
inform_val = self.optInform["value"]
99-
inform_text = self.optInform["text"]
100-
text1 += "\n"
101-
text1 += " Exit Status\n"
102-
text1 += " Inform Description\n"
103-
text1 += f" {inform_val:>6} {inform_text:<0}\n"
115+
# Only print exit status, inform, and description if the optimizer provides informs
116+
if self.optInform:
117+
inform_val = self.optInform.value
118+
inform_text = self.optInform.message
119+
text1 += "\n"
120+
text1 += " Exit Status\n"
121+
text1 += " Inform Description\n"
122+
text1 += f" {inform_val:>6} {inform_text:<0}\n"
104123

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

pyoptsparse/pyPSQP/pyPSQP.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
# Local modules
1414
from ..pyOpt_optimizer import Optimizer
15+
from ..pyOpt_solution import SolutionInform
1516
from ..pyOpt_utils import try_import_compiled_module_from_path
1617

1718
# import the compiled module
@@ -259,9 +260,7 @@ def pdcon(n, k, x, dg):
259260
inform = iterm.item()
260261
if inform < 0 and inform not in self.informs:
261262
inform = -10
262-
sol_inform = {}
263-
sol_inform["value"] = inform
264-
sol_inform["text"] = self.informs[inform]
263+
sol_inform = SolutionInform.from_informs(self.informs, inform)
265264
if self.storeHistory:
266265
self.metadata["endTime"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
267266
self.metadata["optTime"] = optTime

0 commit comments

Comments
 (0)