Skip to content

Commit c9c8c9d

Browse files
kanekoshewu63
andauthored
Bug fix in IPOPT wrapper (#470)
* fix x scaling in IPOPT wrapper * update test * patch version * print warning and keep going when call counter cannot be found * moved warning --------- Co-authored-by: Ella Wu <602725+ewu63@users.noreply.github.com>
1 parent 647fc06 commit c9c8c9d

3 files changed

Lines changed: 20 additions & 10 deletions

File tree

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.4"
1+
__version__ = "2.14.5"
22

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

pyoptsparse/pyIPOPT/pyIPOPT.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import numpy as np
1212

1313
# Local modules
14+
from ..pyOpt_error import pyOptSparseWarning
1415
from ..pyOpt_optimizer import Optimizer
1516
from ..pyOpt_solution import SolutionInform
1617
from ..pyOpt_utils import ICOL, INFINITY, IROW, convertToCOO, extractRows, import_module, scaleRows
@@ -281,12 +282,17 @@ def intermediate(self_cyipopt, alg_mod, iter_count, obj_value, inf_pr, inf_du, m
281282

282283
# Find pyoptsparse call counters for objective and constraints calls at current x.
283284
# IPOPT calls objective and constraints separately, so we find two call counters and append iter_dict to both counters.
284-
call_counter_1 = self.hist._searchCallCounter(self.cache["x"])
285-
call_counter_2 = self.hist._searchCallCounter(self.cache["x"], last=call_counter_1 - 1)
285+
call_counter_1 = self.hist._searchCallCounter(self.optProb._mapXtoUser(self.cache["x"]))
286+
if call_counter_1 is None:
287+
call_counter_2 = None
288+
else:
289+
call_counter_2 = self.hist._searchCallCounter(self.optProb._mapXtoUser(self.cache["x"]), last=call_counter_1 - 1)
286290

287291
for call_counter in [call_counter_2, call_counter_1]:
288292
if call_counter is not None:
289293
self.hist.write(call_counter, iterDict)
294+
else:
295+
pyOptSparseWarning("Failed to find a corresponding call counter at current x. Skipping writing to history file.")
290296

291297
if self.userRequestedTermination is True:
292298
return False

tests/test_hs071.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,24 +117,28 @@ def test_snopt_setDVFromHist(self):
117117
# since we restarted from the optimum
118118
self.assertEqual(second["xvars"].shape, (1, 4))
119119

120-
def test_slsqp_scaling_offset_optProb(self):
120+
@parameterized.expand(["SLSQP", "IPOPT"])
121+
def test_scaling_offset_optProb(self, optName):
121122
"""
122123
Test that scaling and offset works as expected
123124
Also test optProb stored in the history file is correct
124125
"""
125-
self.optName = "SLSQP"
126-
histFileName = "hs071_SLSQP_scaling_offset.hst"
126+
self.optName = optName
127+
histFileName = f"hs071_{optName}_scaling_offset.hst"
127128
objScale = 4.2
128129
xScale = [2, 3, 4, 5]
129130
conScale = [0.6, 1.7]
130131
offset = [1, -2, 40, 2.5]
131132
self.setup_optProb(objScale=objScale, xScale=xScale, conScale=conScale, offset=offset)
132133
sol = self.optimize(storeHistory=histFileName)
133-
self.assert_solution_allclose(sol, self.tol["SLSQP"])
134+
lambda_sign = -1.0 if optName == "IPOPT" else 1.0
135+
self.assert_solution_allclose(sol, self.tol[optName], lambda_sign=lambda_sign)
134136
# now we retrieve the history file, and check the scale=True option is indeed
135137
# scaling things correctly
138+
# IPOPT calls gradient and jacobian at first, so set callCounter = 2 for the first non-derivative call
139+
callCounter = "2" if optName == "IPOPT" else "0"
136140
hist = History(histFileName, flag="r")
137-
orig_values = hist.getValues(callCounters="0", scale=False)
141+
orig_values = hist.getValues(callCounters=callCounter, scale=False)
138142
optProb = hist.getOptProb()
139143

140144
# check that the scales are stored properly
@@ -147,13 +151,13 @@ def test_slsqp_scaling_offset_optProb(self):
147151
assert_allclose(objScale, optProb.objectives[obj].scale, atol=1e-12, rtol=1e-12)
148152

149153
# verify the scale option in getValues
150-
scaled_values = hist.getValues(callCounters="0", scale=True, stack=False)
154+
scaled_values = hist.getValues(callCounters=callCounter, scale=True, stack=False)
151155
x = orig_values["xvars"][0]
152156
x_scaled = scaled_values["xvars"][0]
153157
assert_allclose(x_scaled, (x - offset) * xScale, atol=1e-12, rtol=1e-12)
154158

155159
# now do the same but with stack=True
156-
stacked_values = hist.getValues(callCounters="0", scale=True, stack=True)
160+
stacked_values = hist.getValues(callCounters=callCounter, scale=True, stack=True)
157161
x_scaled = stacked_values["xuser"][0]
158162
assert_allclose(x_scaled, (x - offset) * xScale, atol=1e-12, rtol=1e-12)
159163

0 commit comments

Comments
 (0)