Skip to content

Commit dffc4cb

Browse files
authored
Bugfix and testing for unconstrained optimization (#162)
1 parent a8f837d commit dffc4cb

File tree

4 files changed

+258
-7
lines changed

4 files changed

+258
-7
lines changed

pyoptsparse/pyCONMIN/pyCONMIN.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def __call__(
121121
# least one constraint. So we will add one
122122
# automatically here:
123123
self.unconstrained = True
124-
optProb.dummyConstraint = False
124+
optProb.dummyConstraint = True
125125

126126
# Save the optimization problem and finalize constraint
127127
# Jacobian, in general can only do on root proc
@@ -189,7 +189,12 @@ def cnmngrad(n1, n2, x, f, g, ct, df, a, ic, nac):
189189
nn3 = max(nn2, ndv)
190190
nn4 = max(nn2, ndv)
191191
nn5 = 2 * nn4
192-
gg = np.zeros(ncn, np.float)
192+
193+
if ncn > 0:
194+
gg = np.zeros(ncn, np.float)
195+
else:
196+
gg = np.array([0], np.float)
197+
193198
if self.getOption("IPRINT") >= 0 and self.getOption("IPRINT") <= 4:
194199
iprint = self.getOption("IPRINT")
195200
else:

pyoptsparse/pyNLPQLP/pyNLPQLP.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def __call__(
148148

149149
if len(optProb.constraints) == 0:
150150
self.unconstrained = True
151-
optProb.dummyConstraint = False
151+
optProb.dummyConstraint = True
152152

153153
# Save the optimization problem and finalize constraint
154154
# Jacobian, in general can only do on root proc

pyoptsparse/pyPSQP/pyPSQP.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,13 @@ def __call__(
158158
# Set the number of nonlinear constraints snopt *thinks* we have:
159159
if self.unconstrained:
160160
ncon = 0
161-
cf = [0.0]
162-
cl = []
163-
cu = []
164-
ic = []
161+
cf = np.zeros(1)
162+
cl = np.zeros(1)
163+
cu = np.zeros(1)
164+
ic = np.zeros(1)
165+
# as done for SLSQP, we need this dummy constraint to make the code work
166+
optProb.dummyConstraint = True
167+
165168
else:
166169
indices, blc, buc, fact = self.optProb.getOrdering(["ne", "le", "ni", "li"], oneSided=oneSided)
167170
ncon = len(indices)

test/test_rosenbrock.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
"""Test solution of Rosenbrock problem"""
2+
3+
import unittest
4+
import numpy as np
5+
from numpy.testing import assert_allclose
6+
from pyoptsparse import Optimization, OPT, History
7+
from pyoptsparse.pyOpt_error import Error
8+
9+
10+
class TestRosenbrock(unittest.TestCase):
11+
12+
## Solve unconstrained Rosenbrock problem.
13+
# This problem is scalable w.r.t. design variables number.
14+
# We select a problem with 4 design variables, but the
15+
# location and value of the minimum do not change with DV
16+
# dimensionality
17+
#
18+
#
19+
# min 100 * (x[i + 1] - x[i] ** 2) ** 2 + (1 - x[i]) ** 2
20+
#
21+
# The minimum is located at x=(1,....,1) where x
22+
# is an arbitrarily sized vector depending on the number N
23+
# of design variables.
24+
# At the optimum, the function is f(x) = 0.
25+
# We select a random initial point for our test.
26+
##
27+
28+
def objfunc(self, xdict):
29+
self.nf += 1
30+
x = xdict["xvars"]
31+
32+
funcs = {}
33+
funcs["obj"] = 0
34+
35+
for i in range(len(x) - 1):
36+
funcs["obj"] += 100 * (x[i + 1] - x[i] ** 2) ** 2 + (1 - x[i]) ** 2
37+
38+
fail = False
39+
return funcs, fail
40+
41+
def sens(self, xdict, funcs):
42+
self.ng += 1
43+
x = xdict["xvars"]
44+
funcsSens = {}
45+
grads = np.zeros(len(x))
46+
47+
for i in range(len(x) - 1):
48+
grads[i] += 2 * (200 * x[i] ** 3 - 200 * x[i] * x[i + 1] + x[i] - 1)
49+
grads[i + 1] += 200 * (x[i + 1] - x[i] ** 2)
50+
51+
funcsSens["obj"] = {"xvars": grads}
52+
53+
fail = False
54+
return funcsSens, fail
55+
56+
def optimize(self, optName, tol, optOptions={}, storeHistory=False, hotStart=None):
57+
self.nf = 0 # number of function evaluations
58+
self.ng = 0 # number of gradient evaluations
59+
# Optimization Object
60+
61+
optProb = Optimization("Rosenbrock Problem", self.objfunc)
62+
63+
n = 4 # Number of design variables
64+
np.random.seed(10)
65+
value = np.random.normal(size=n)
66+
67+
lower = np.ones(n) * -50
68+
upper = np.ones(n) * 50
69+
optProb.addVarGroup("xvars", n, lower=lower, upper=upper, value=value)
70+
71+
# Objective
72+
optProb.addObj("obj")
73+
74+
# Check optimization problem:
75+
print(optProb)
76+
77+
# Optimizer
78+
try:
79+
opt = OPT(optName, options=optOptions)
80+
except Error:
81+
raise unittest.SkipTest("Optimizer not available:", optName)
82+
83+
# Solution
84+
if storeHistory is not None:
85+
if storeHistory is True:
86+
self.histFileName = "%s_Rsbrk_Hist.hst" % (optName.lower())
87+
elif isinstance(storeHistory, str):
88+
self.histFileName = storeHistory
89+
else:
90+
self.histFileName = None
91+
92+
sol = opt(optProb, sens=self.sens, storeHistory=self.histFileName, hotStart=hotStart)
93+
94+
# Test printing solution to screen
95+
print(sol)
96+
97+
# Check Solution
98+
self.fStar1 = 0.0
99+
100+
self.xStar1 = np.ones(n)
101+
102+
dv = sol.getDVs()
103+
sol_xvars = [sol.variables["xvars"][i].value for i in range(n)]
104+
105+
assert_allclose(sol_xvars, dv["xvars"], atol=tol, rtol=tol)
106+
107+
assert_allclose(sol.objectives["obj"].value, self.fStar1, atol=tol, rtol=tol)
108+
assert_allclose(dv["xvars"], self.xStar1, atol=tol, rtol=tol)
109+
110+
def check_hist_file(self, optimizer, tol):
111+
"""
112+
We check the history file here along with the API
113+
"""
114+
hist = History(self.histFileName, flag="r")
115+
# Metadata checks
116+
metadata = hist.getMetadata()
117+
self.assertEqual(metadata["optimizer"], optimizer)
118+
metadata_def_keys = ["optName", "optOptions", "nprocs", "startTime", "endTime", "optTime", "version"]
119+
for key in metadata_def_keys:
120+
self.assertIn(key, metadata)
121+
hist.getOptProb()
122+
123+
# Info checks
124+
self.assertEqual(hist.getDVNames(), ["xvars"])
125+
self.assertEqual(hist.getObjNames(), ["obj"])
126+
dvInfo = hist.getDVInfo()
127+
self.assertEqual(len(dvInfo), 1)
128+
self.assertEqual(dvInfo["xvars"], hist.getDVInfo(key="xvars"))
129+
conInfo = hist.getConInfo()
130+
self.assertEqual(len(conInfo), 0)
131+
objInfo = hist.getObjInfo()
132+
self.assertEqual(len(objInfo), 1)
133+
self.assertEqual(objInfo["obj"], hist.getObjInfo(key="obj"))
134+
for key in ["lower", "upper", "scale"]:
135+
self.assertIn(key, dvInfo["xvars"])
136+
self.assertIn("scale", objInfo["obj"])
137+
138+
# callCounter checks
139+
callCounters = hist.getCallCounters()
140+
last = hist.read("last") # 'last' key should be present
141+
self.assertIn(last, callCounters)
142+
143+
# iterKey checks
144+
iterKeys = hist.getIterKeys()
145+
for key in ["xuser", "fail", "isMajor"]:
146+
self.assertIn(key, iterKeys)
147+
148+
# this check is only used for optimizers that guarantee '0' and 'last' contain funcs
149+
if optimizer in ["SNOPT", "SLSQP", "PSQP"]:
150+
val = hist.getValues(callCounters=["0", "last"], stack=True)
151+
self.assertEqual(val["isMajor"].size, 2)
152+
self.assertTrue(val["isMajor"][0]) # the first callCounter must be a major iteration
153+
self.assertTrue(val["isMajor"][-1]) # the last callCounter must be a major iteration
154+
# check optimum stored in history file against xstar
155+
assert_allclose(val["xuser"][-1], self.xStar1, atol=tol, rtol=tol)
156+
157+
def optimize_with_hotstart(self, optName, tol, optOptions={}):
158+
"""
159+
This code will perform 4 optimizations, one real opt and three restarts.
160+
In this process, it will check various combinations of storeHistory and hotStart filenames.
161+
It will also call `check_hist_file` after the first optimization.
162+
"""
163+
164+
self.optimize(optName, tol, storeHistory=True, optOptions=optOptions)
165+
self.assertGreater(self.nf, 0)
166+
self.assertGreater(self.ng, 0)
167+
self.check_hist_file(optName, tol)
168+
169+
# re-optimize with hotstart
170+
self.optimize(optName, tol, storeHistory=False, hotStart=self.histFileName, optOptions=optOptions)
171+
# we should have zero actual function/gradient evaluations
172+
self.assertEqual(self.nf, 0)
173+
self.assertEqual(self.ng, 0)
174+
# another test with hotstart, this time with storeHistory = hotStart
175+
self.optimize(optName, tol, storeHistory=True, hotStart=self.histFileName, optOptions=optOptions)
176+
# we should have zero actual function/gradient evaluations
177+
self.assertEqual(self.nf, 0)
178+
self.assertEqual(self.ng, 0)
179+
# final test with hotstart, this time with a different storeHistory
180+
self.optimize(
181+
optName,
182+
tol,
183+
storeHistory="{}_new_hotstart.hst".format(optName),
184+
hotStart=self.histFileName,
185+
optOptions=optOptions,
186+
)
187+
# we should have zero actual function/gradient evaluations
188+
self.assertEqual(self.nf, 0)
189+
self.assertEqual(self.ng, 0)
190+
191+
def test_snopt(self):
192+
test_name = "Rsbrk_SNOPT"
193+
store_vars = ["step", "merit", "feasibility", "optimality", "penalty", "Hessian", "condZHZ", "slack", "lambda"]
194+
optOptions = {
195+
"Save major iteration variables": store_vars,
196+
"Print file": "{}.out".format(test_name),
197+
"Summary file": "{}_summary.out".format(test_name),
198+
}
199+
self.optimize_with_hotstart("SNOPT", 1e-8, optOptions=optOptions)
200+
201+
hist = History(self.histFileName, flag="r")
202+
data = hist.getValues(callCounters=["last"])
203+
keys = hist.getIterKeys()
204+
self.assertIn("isMajor", keys)
205+
self.assertEqual(36, data["nMajor"])
206+
for var in store_vars:
207+
self.assertIn(var, data.keys())
208+
self.assertEqual(data["Hessian"].shape, (1, 4, 4))
209+
self.assertEqual(data["feasibility"].shape, (1, 1))
210+
self.assertEqual(data["slack"].shape, (1, 1))
211+
self.assertEqual(data["lambda"].shape, (1, 1))
212+
213+
def test_slsqp(self):
214+
optOptions = {"ACC": 1e-10, "IFILE": "Rsbrk_SLSQP.out"}
215+
self.optimize_with_hotstart("SLSQP", 1e-6, optOptions=optOptions)
216+
217+
def test_nlpqlp(self):
218+
optOptions = {"accuracy": 1e-10, "iFile": "Rsbrk_NLPQLP.out"}
219+
self.optimize_with_hotstart("NLPQLP", 1e-6, optOptions=optOptions)
220+
221+
def test_ipopt(self):
222+
optOptions = {"output_file": "Rsbrk_IPOPT.out"}
223+
self.optimize_with_hotstart("IPOPT", 1e-6, optOptions=optOptions)
224+
225+
def test_paropt(self):
226+
optOptions = {"output_file": "Rsbrk_ParOpt.out"}
227+
self.optimize_with_hotstart("ParOpt", 1e-8, optOptions=optOptions)
228+
229+
def test_conmin(self):
230+
optOptions = {
231+
"DELFUN": 1e-10,
232+
"DABFUN": 1e-10,
233+
"IFILE": "Rsbrk_CONMIN.out",
234+
}
235+
self.optimize_with_hotstart("CONMIN", 1e-9, optOptions=optOptions)
236+
237+
def test_psqp(self):
238+
optOptions = {"IFILE": "Rsbrk_PSQP.out"}
239+
self.optimize_with_hotstart("PSQP", 1e-8, optOptions=optOptions)
240+
241+
242+
if __name__ == "__main__":
243+
unittest.main()

0 commit comments

Comments
 (0)