From e7264e83155b585ee9ca57234d2c6c2341b3cf87 Mon Sep 17 00:00:00 2001 From: sseraj Date: Mon, 7 Aug 2023 16:37:36 -0400 Subject: [PATCH 01/14] save restartDict after every major iteration --- pyoptsparse/pySNOPT/pySNOPT.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index 0408521d..f738b35e 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -521,24 +521,16 @@ def __call__( # Create the optimization solution sol = self._createSolution(optTime, sol_inform, obj, xs[:nvar], multipliers=pi) - restartDict = { - "cw": cw, - "iw": iw, - "rw": rw, - "xs": xs, - "hs": hs, - "pi": pi, - } else: # We are not on the root process so go into waiting loop: self._waitLoop() - restartDict = None + self.restartDict = None # Communication solution and return commSol = self._communicateSolution(sol) if self.getOption("Return work arrays"): - restartDict = self.optProb.comm.bcast(restartDict, root=0) - return commSol, restartDict + self.restartDict = self.optProb.comm.bcast(self.restartDict, root=0) + return commSol, self.restartDict else: return commSol @@ -680,6 +672,16 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, if "funcs" in self.cache.keys(): iterDict["funcs"].update(self.cache["funcs"]) + # Save the restart dictionary + self.restartDict = { + "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: From a953cd494f236b9357d1dc354b4eb9a2101546d5 Mon Sep 17 00:00:00 2001 From: sseraj Date: Mon, 7 Aug 2023 16:52:59 -0400 Subject: [PATCH 02/14] added restartDict as snstop callback argument --- pyoptsparse/pySNOPT/pySNOPT.py | 2 +- tests/test_hs015.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index f738b35e..3d8c22e2 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -687,7 +687,7 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, 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, self.restartDict) # write iterDict again if anything was inserted if self.storeHistory and callCounter is not None: self.hist.write(callCounter, iterDict) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index a7870044..f6c00a1e 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -176,7 +176,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 From 480fbfbeeef7835c5183999b42f128eba47f5553 Mon Sep 17 00:00:00 2001 From: sseraj Date: Wed, 9 Aug 2023 15:53:54 -0400 Subject: [PATCH 03/14] keep the final restartDict --- pyoptsparse/pySNOPT/pySNOPT.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index 3d8c22e2..271d8873 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -521,16 +521,24 @@ def __call__( # Create the optimization solution sol = self._createSolution(optTime, sol_inform, obj, xs[:nvar], multipliers=pi) + restartDict = { + "cw": cw, + "iw": iw, + "rw": rw, + "xs": xs, + "hs": hs, + "pi": pi, + } else: # We are not on the root process so go into waiting loop: self._waitLoop() - self.restartDict = None + restartDict = None # Communication solution and return commSol = self._communicateSolution(sol) if self.getOption("Return work arrays"): - self.restartDict = self.optProb.comm.bcast(self.restartDict, root=0) - return commSol, self.restartDict + restartDict = self.optProb.comm.bcast(restartDict, root=0) + return commSol, restartDict else: return commSol @@ -672,8 +680,8 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, if "funcs" in self.cache.keys(): iterDict["funcs"].update(self.cache["funcs"]) - # Save the restart dictionary - self.restartDict = { + # Create the restart dictionary to be passed to snstop_handle + restartDict = { "cw": cw, "iw": iw, "rw": rw, @@ -687,7 +695,7 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, 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, self.restartDict) + 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) From 44b50e28845683dc5310ae7d2855d793dfcafd56 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 31 May 2024 15:26:26 -0400 Subject: [PATCH 04/14] added example to docs --- doc/optimizers/SNOPT_options.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/optimizers/SNOPT_options.yaml b/doc/optimizers/SNOPT_options.yaml index 0c740234..8cf43d1f 100644 --- a/doc/optimizers/SNOPT_options.yaml +++ b/doc/optimizers/SNOPT_options.yaml @@ -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 From 7e8b781f6e801d9f1772ec16a4883637225dc0d9 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 31 May 2024 17:13:11 -0400 Subject: [PATCH 05/14] added test --- tests/test_hs015.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index f89de4a3..dba84373 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -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 @@ -193,6 +195,47 @@ 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) + + # Exit after 4 major iterations + if iterDict["nMajor"] == 4: + return 1 + + return 0 + + def test_snopt_snstop_restart(self): + # Run the optimization for 4 major iterations + self.optName = "SNOPT" + self.setup_optProb() + optOptions = { + "snSTOP function handle": self.my_snstop_restart, + } + sol = self.optimize(optOptions=optOptions, storeHistory=True) + + # 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() + self.nf = 0 + self.ng = 0 + opt = OPT(self.optName, options={"Start": "Hot", "Verify level": -1}) + sol = opt(self.optProb, sens=self.sens, restartDict=restartDict) + + # Check solution + self.assert_solution_allclose(sol, 1e-12) + + # The optimization should converge in 4 more iterations + self.assertEqual(self.nf, 4) + self.assertEqual(self.ng, 4) + + # Delete the pickle file + os.remove(pickleFile) + def test_snopt_failed_initial(self): def failed_fun(x_dict): funcs = {"obj": 0.0, "con": [np.nan, np.nan]} From 9f1537d28e74dcbb9186010b60074584a6476782 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 31 May 2024 17:52:29 -0400 Subject: [PATCH 06/14] test major iterations instead of function evaluations --- tests/test_hs015.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index dba84373..bdce9684 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -200,14 +200,14 @@ def my_snstop_restart(iterDict, restartDict): # Save the restart dictionary writePickle("restart.pickle", restartDict) - # Exit after 4 major iterations - if iterDict["nMajor"] == 4: + # Exit after 5 major iterations + if iterDict["nMajor"] == 5: return 1 return 0 def test_snopt_snstop_restart(self): - # Run the optimization for 4 major iterations + # Run the optimization for 5 major iterations self.optName = "SNOPT" self.setup_optProb() optOptions = { @@ -221,20 +221,24 @@ def test_snopt_snstop_restart(self): # Now optimize again but using the restart dictionary self.setup_optProb() - self.nf = 0 - self.ng = 0 - opt = OPT(self.optName, options={"Start": "Hot", "Verify level": -1}) - sol = opt(self.optProb, sens=self.sens, restartDict=restartDict) - - # Check solution + opt = OPT( + 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) - # The optimization should converge in 4 more iterations - self.assertEqual(self.nf, 4) - self.assertEqual(self.ng, 4) - - # Delete the pickle file + # Delete the pickle and history files os.remove(pickleFile) + os.remove(histFile) def test_snopt_failed_initial(self): def failed_fun(x_dict): From 2e164ef0084c42429d937eb1b151ff144054b431 Mon Sep 17 00:00:00 2001 From: sseraj Date: Wed, 5 Jun 2024 13:53:34 -0400 Subject: [PATCH 07/14] bumped minor version --- pyoptsparse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoptsparse/__init__.py b/pyoptsparse/__init__.py index 903a0881..c122965d 100644 --- a/pyoptsparse/__init__.py +++ b/pyoptsparse/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.11.1" +__version__ = "2.12.0" from .pyOpt_history import History from .pyOpt_variable import Variable From bb78dabf491dd636ebc5a9dd4efa9960983ed131 Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 13 Jun 2024 21:56:16 -0400 Subject: [PATCH 08/14] added snstop arguments option --- pyoptsparse/pySNOPT/pySNOPT.py | 12 +++++++++++- tests/test_hs015.py | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index 5f1091c8..f5abddca 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -61,6 +61,7 @@ def __init__(self, raiseError=True, options: Dict = {}): "Save major iteration variables", "Return work arrays", "snSTOP function handle", + "snSTOP arguments", } ) @@ -119,6 +120,7 @@ def _getDefaultOptions() -> Dict[str, Any]: "Save major iteration variables": [list, []], "Return work arrays": [bool, False], "snSTOP function handle": [(type(None), type(lambda: None)), None], + "snSTOP arguments": [list, []], } return defOpts @@ -680,9 +682,17 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, # perform callback if requested snstop_handle = self.getOption("snSTOP function handle") if snstop_handle is not None: + + # Get the arguments to pass in to snstop_handle + # iterDict is always included + snstopArgs = [iterDict] + for snstopArg in self.getOption("snSTOP arguments"): + if snstopArg == "restartDict": + snstopArgs.append(restartDict) + if not self.storeHistory: raise Error("snSTOP function handle must be used with storeHistory=True") - iabort = snstop_handle(iterDict, restartDict) + iabort = snstop_handle(*snstopArgs) # write iterDict again if anything was inserted if self.storeHistory and callCounter is not None: self.hist.write(callCounter, iterDict) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index bdce9684..bb71cec9 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -178,7 +178,7 @@ def test_snopt_hotstart(self): self.assertEqual(self.ng, 1) @staticmethod - def my_snstop(iterDict, restartDict): + def my_snstop(iterDict): """manually terminate SNOPT after 1 major iteration""" if iterDict["nMajor"] == 1: return 1 @@ -212,6 +212,7 @@ def test_snopt_snstop_restart(self): self.setup_optProb() optOptions = { "snSTOP function handle": self.my_snstop_restart, + "snSTOP arguments": ["restartDict"], } sol = self.optimize(optOptions=optOptions, storeHistory=True) @@ -227,6 +228,7 @@ def test_snopt_snstop_restart(self): "Start": "Hot", "Verify level": -1, "snSTOP function handle": self.my_snstop_restart, + "snSTOP arguments": ["restartDict"], }, ) histFile = "restart.hst" From 180f2e790f493838003271e18fd01e6cb272db38 Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 13 Jun 2024 23:55:07 -0400 Subject: [PATCH 09/14] added work arrays save option --- pyoptsparse/pySNOPT/pySNOPT.py | 9 ++++++++- tests/test_hs015.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index f5abddca..80ad2662 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -12,7 +12,7 @@ from typing import Any, Dict, Optional, Tuple # External modules -from baseclasses.utils import CaseInsensitiveSet +from baseclasses.utils import CaseInsensitiveSet, writePickle import numpy as np from numpy import ndarray from pkg_resources import parse_version @@ -60,6 +60,7 @@ def __init__(self, raiseError=True, options: Dict = {}): { "Save major iteration variables", "Return work arrays", + "Work arrays save file", "snSTOP function handle", "snSTOP arguments", } @@ -119,6 +120,7 @@ def _getDefaultOptions() -> Dict[str, Any]: "Total real workspace": [int, None], "Save major iteration variables": [list, []], "Return work arrays": [bool, False], + "Work arrays save file": [(type(None), str), None], "snSTOP function handle": [(type(None), type(lambda: None)), None], "snSTOP arguments": [list, []], } @@ -679,6 +681,11 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, "pi": pi, } + workArraysSave = self.getOption("Work arrays save file") + if workArraysSave is not None: + # Save the restart dictionary + writePickle(workArraysSave, restartDict) + # perform callback if requested snstop_handle = self.getOption("snSTOP function handle") if snstop_handle is not None: diff --git a/tests/test_hs015.py b/tests/test_hs015.py index bb71cec9..b06badb4 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -242,6 +242,40 @@ def test_snopt_snstop_restart(self): os.remove(pickleFile) os.remove(histFile) + def test_snopt_work_arrays_save(self): + # Run the optimization for 5 major iterations + self.optName = "SNOPT" + self.setup_optProb() + pickleFile = "work_arrays_save.pickle" + optOptions = { + "snSTOP function handle": self.my_snstop, + "Work arrays save file": pickleFile, + } + sol = self.optimize(optOptions=optOptions, storeHistory=True) + + # Read the restart dictionary pickle file saved by snstop + restartDict = readPickle(pickleFile) + + # Now optimize again but using the restart dictionary + self.setup_optProb() + opt = OPT( + self.optName, + options={ + "Start": "Hot", + "Verify level": -1, + }, + ) + histFile = "work_arrays_save.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]} From 2bbb555adafa49b63f9f4345508af31692ea3dda Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 14 Jun 2024 00:10:22 -0400 Subject: [PATCH 10/14] added error for unknown snstop arg --- pyoptsparse/pySNOPT/pySNOPT.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyoptsparse/pySNOPT/pySNOPT.py b/pyoptsparse/pySNOPT/pySNOPT.py index 80ad2662..1a39fb2b 100644 --- a/pyoptsparse/pySNOPT/pySNOPT.py +++ b/pyoptsparse/pySNOPT/pySNOPT.py @@ -696,6 +696,10 @@ def _snstop(self, ktcond, mjrprtlvl, minimize, n, nncon, nnobj, ns, itn, nmajor, for snstopArg in self.getOption("snSTOP arguments"): if snstopArg == "restartDict": snstopArgs.append(restartDict) + else: + raise Error(f"Received unknown snSTOP argument {snstopArg}. " + + "Please see 'snSTOP arguments' option in the pyOptSparse documentation " + + "under 'SNOPT'.") if not self.storeHistory: raise Error("snSTOP function handle must be used with storeHistory=True") From 942f784073e836856ee47e9298866ab72d8aa0b9 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 14 Jun 2024 00:38:21 -0400 Subject: [PATCH 11/14] updated docs --- doc/optimizers/SNOPT_options.yaml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/doc/optimizers/SNOPT_options.yaml b/doc/optimizers/SNOPT_options.yaml index 8cf43d1f..65240285 100644 --- a/doc/optimizers/SNOPT_options.yaml +++ b/doc/optimizers/SNOPT_options.yaml @@ -100,17 +100,37 @@ Return work arrays: These arrays can be used to hot start a subsequent optimization. The SNOPT option 'Sticky parameters' will also be automatically set to 'Yes' to facilitate the hot start. +Work arrays save file: + desc: > + This option is unique to the Python wrapper. + The SNOPT work arrays will be pickled and saved to this file after each major iteration. + This file is useful if you want to restart an optimization that did not exit cleanly. + If None, the work arrays are not saved. + 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. + The following is an example of a callback function that saves the restart dictionary + to a different file after each major iteration. .. code-block:: python def snstopCallback(iterDict, restartDict): + # Get the major iteration number + nMajor = iterDict["nMajor"] + # Save the restart dictionary - writePickle("restart.pickle", restartDict) + writePickle(f"restart_{nMajor}.pickle", restartDict) return 0 + +snSTOP arguments: + desc: | + This option is unique to the Python wrapper. + It specifies a list of arguments that will be passed to the snSTOP function handle. + ``iterDict`` is always passed as an argument. + Additional arguments are passed in the same order as this list. + The possible values are + + - ``restartDict`` From 5b845ac5ee99e6dd470c42fefbeb11e8161c90c7 Mon Sep 17 00:00:00 2001 From: sseraj Date: Sun, 30 Jun 2024 22:06:24 -0600 Subject: [PATCH 12/14] addressed Ella's comments --- tests/test_hs015.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index b06badb4..6e69e220 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -195,29 +195,32 @@ 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) + def test_snopt_snstop_restart(self): + pickleFile = "restart.pickle" - # Exit after 5 major iterations - if iterDict["nMajor"] == 5: - return 1 + def my_snstop_restart(iterDict, restartDict): + # Save the restart dictionary + writePickle(pickleFile, restartDict) - return 0 + # 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, + "snSTOP function handle": my_snstop_restart, "snSTOP arguments": ["restartDict"], } sol = self.optimize(optOptions=optOptions, storeHistory=True) + # Check that the optimization exited with 74 + self.assert_inform_equal(sol, optInform=74) + # Read the restart dictionary pickle file saved by snstop - pickleFile = "restart.pickle" restartDict = readPickle(pickleFile) # Now optimize again but using the restart dictionary @@ -227,7 +230,7 @@ def test_snopt_snstop_restart(self): options={ "Start": "Hot", "Verify level": -1, - "snSTOP function handle": self.my_snstop_restart, + "snSTOP function handle": my_snstop_restart, "snSTOP arguments": ["restartDict"], }, ) From 6d09cd443f97886212efd553de7c0d42228c24e1 Mon Sep 17 00:00:00 2001 From: Marco Mangano <36549388+marcomangano@users.noreply.github.com> Date: Wed, 3 Jul 2024 06:29:21 -0400 Subject: [PATCH 13/14] minor version bump --- pyoptsparse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoptsparse/__init__.py b/pyoptsparse/__init__.py index 5ab4a539..c122965d 100644 --- a/pyoptsparse/__init__.py +++ b/pyoptsparse/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.11.3" +__version__ = "2.12.0" from .pyOpt_history import History from .pyOpt_variable import Variable From f725379618dd0ef556966c6f33d61f37b8677f53 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 5 Jul 2024 21:52:34 -0600 Subject: [PATCH 14/14] updated comment --- tests/test_hs015.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index 6e69e220..27ee8fd3 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -271,7 +271,7 @@ def test_snopt_work_arrays_save(self): histFile = "work_arrays_save.hst" sol = opt(self.optProb, sens=self.sens, storeHistory=histFile, restartDict=restartDict) - # Check that the optimization converged in fewer than 5 more major iterations + # Check that the optimization converged self.assert_solution_allclose(sol, 1e-12) self.assert_inform_equal(sol, optInform=1)