Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added restartDict to snstop #404

Merged
merged 21 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/optimizers/SNOPT_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,13 @@ snSTOP function handle:
desc: >
This option is unique to the Python wrapper.
A function handle can be supplied which is called at the end of each major iteration.
The following is an example of a callback function that saves the restart dictionary.
This is useful if you want to restart an optimization that did not exit cleanly.

.. code-block:: python

def snstopCallback(iterDict, restartDict):
# Save the restart dictionary
writePickle("restart.pickle", restartDict)

return 0
12 changes: 11 additions & 1 deletion pyoptsparse/pySNOPT/pySNOPT.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,12 +663,22 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor,
if "funcs" in self.cache.keys():
iterDict["funcs"].update(self.cache["funcs"])

# Create the restart dictionary to be passed to snstop_handle
restartDict = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO for the future: this should probably be a dataclass object instead of a dict... That way we can avoid some code duplication with above.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ewu63 I can do it if you open an issue with the specification of what you would like to have ;-)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #405

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments. It would be nice for the function to be more flexible. I'm not sure how returning values would work though because the user does not call _snstop. It seems like the dictionaries have to be passed in.

"cw": cw,
"iw": iw,
"rw": rw,
"xs": x, # x is the same as xs; we call it x here to be consistent with the SNOPT subroutine snSTOP
"hs": hs,
"pi": pi,
}

# perform callback if requested
snstop_handle = self.getOption("snSTOP function handle")
if snstop_handle is not None:
if not self.storeHistory:
raise Error("snSTOP function handle must be used with storeHistory=True")
iabort = snstop_handle(iterDict)
iabort = snstop_handle(iterDict, restartDict)
# write iterDict again if anything was inserted
if self.storeHistory and callCounter is not None:
self.hist.write(callCounter, iterDict)
Expand Down
49 changes: 48 additions & 1 deletion tests/test_hs015.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Test solution of problem HS15 from the Hock & Schittkowski collection"""

# Standard Python modules
import os
import unittest

# External modules
from baseclasses.utils import readPickle, writePickle
import numpy as np
from parameterized import parameterized

Expand Down Expand Up @@ -176,7 +178,7 @@ def test_snopt_hotstart(self):
self.assertEqual(self.ng, 1)

@staticmethod
def my_snstop(iterDict):
def my_snstop(iterDict, restartDict):
"""manually terminate SNOPT after 1 major iteration"""
if iterDict["nMajor"] == 1:
return 1
Expand All @@ -193,6 +195,51 @@ def test_snopt_snstop(self):
# we should get 70/74
self.assert_inform_equal(sol, optInform=74)

@staticmethod
def my_snstop_restart(iterDict, restartDict):
# Save the restart dictionary
writePickle("restart.pickle", restartDict)
ewu63 marked this conversation as resolved.
Show resolved Hide resolved

# Exit after 5 major iterations
if iterDict["nMajor"] == 5:
return 1

return 0

def test_snopt_snstop_restart(self):
# Run the optimization for 5 major iterations
self.optName = "SNOPT"
self.setup_optProb()
optOptions = {
"snSTOP function handle": self.my_snstop_restart,
}
sol = self.optimize(optOptions=optOptions, storeHistory=True)
ewu63 marked this conversation as resolved.
Show resolved Hide resolved

# Read the restart dictionary pickle file saved by snstop
pickleFile = "restart.pickle"
restartDict = readPickle(pickleFile)

# Now optimize again but using the restart dictionary
self.setup_optProb()
opt = OPT(
marcomangano marked this conversation as resolved.
Show resolved Hide resolved
self.optName,
options={
"Start": "Hot",
"Verify level": -1,
"snSTOP function handle": self.my_snstop_restart,
},
)
histFile = "restart.hst"
sol = opt(self.optProb, sens=self.sens, storeHistory=histFile, restartDict=restartDict)

# Check that the optimization converged in fewer than 5 more major iterations
self.assert_solution_allclose(sol, 1e-12)
self.assert_inform_equal(sol, optInform=1)

# Delete the pickle and history files
os.remove(pickleFile)
os.remove(histFile)

def test_snopt_failed_initial(self):
def failed_fun(x_dict):
funcs = {"obj": 0.0, "con": [np.nan, np.nan]}
Expand Down
Loading