Skip to content

Commit 58f9f59

Browse files
committed
Added test for new-style ipopt; fixed bug that wouldn't have been caught without it
1 parent aa89364 commit 58f9f59

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

pyomo/contrib/solver/solvers/ipopt.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,8 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
622622
r'Objective\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
623623
r'Dual infeasibility\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
624624
r'Constraint violation\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
625+
# Next field is optional because it shows up in new-style ipopt output, but not old style
626+
r'(?:Variable bound violation: *([-+eE0-9.]+) *([-+eE0-9.]+) *\s*)?'
625627
r'Complementarity\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
626628
r'Overall NLP error\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)',
627629
output,
@@ -636,10 +638,14 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
636638
"overall_nlp_error",
637639
]
638640
scaled = {
639-
k: float(v) for k, v in zip(fields, scaled_unscaled_match[0][0:10:2])
641+
k: float(v)
642+
for k, v in zip(fields, scaled_unscaled_match[0][0:10:2])
643+
if v
640644
}
641645
unscaled = {
642-
k: float(v) for k, v in zip(fields, scaled_unscaled_match[0][1:10:2])
646+
k: float(v)
647+
for k, v in zip(fields, scaled_unscaled_match[0][1:10:2])
648+
if v
643649
}
644650

645651
parsed_data.update(unscaled)

pyomo/contrib/solver/tests/solvers/test_ipopt.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def test_version_cache(self):
153153
self.assertIsNone(opt._version_cache[1])
154154

155155
def test_parse_output(self):
156+
# Old ipopt style (<=3.13)
156157
output = """Ipopt 3.13.2:
157158
158159
******************************************************************************
@@ -233,6 +234,74 @@ def test_parse_output(self):
233234
self.assertEqual(parsed_output["incumbent_objective"], 7.0136459513364959e-25)
234235
self.assertIn("final_scaled_results", parsed_output.keys())
235236

237+
# New ipopt style (3.14+)
238+
output = """******************************************************************************
239+
This program contains Ipopt, a library for large-scale nonlinear optimization.
240+
Ipopt is released as open source code under the Eclipse Public License (EPL).
241+
For more information visit https://github.com/coin-or/Ipopt
242+
******************************************************************************
243+
244+
This is Ipopt version 3.14.17, running with linear solver ma27.
245+
246+
Number of nonzeros in equality constraint Jacobian...: 0
247+
Number of nonzeros in inequality constraint Jacobian.: 0
248+
Number of nonzeros in Lagrangian Hessian.............: 3
249+
250+
Total number of variables............................: 2
251+
variables with only lower bounds: 0
252+
variables with lower and upper bounds: 0
253+
variables with only upper bounds: 0
254+
Total number of equality constraints.................: 0
255+
Total number of inequality constraints...............: 0
256+
inequality constraints with only lower bounds: 0
257+
inequality constraints with lower and upper bounds: 0
258+
inequality constraints with only upper bounds: 0
259+
260+
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
261+
0 5.6500000e+01 0.00e+00 1.00e+02 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0
262+
1 2.4669972e-01 0.00e+00 2.22e-01 -1.0 7.40e-01 - 1.00e+00 1.00e+00f 1
263+
2 1.6256267e-01 0.00e+00 2.04e+00 -1.7 1.48e+00 - 1.00e+00 2.50e-01f 3
264+
3 8.6119444e-02 0.00e+00 1.08e+00 -1.7 2.36e-01 - 1.00e+00 1.00e+00f 1
265+
4 4.3223836e-02 0.00e+00 1.23e+00 -1.7 2.61e-01 - 1.00e+00 1.00e+00f 1
266+
5 1.5610508e-02 0.00e+00 3.54e-01 -1.7 1.18e-01 - 1.00e+00 1.00e+00f 1
267+
6 5.3544798e-03 0.00e+00 5.51e-01 -1.7 1.67e-01 - 1.00e+00 1.00e+00f 1
268+
7 6.1281576e-04 0.00e+00 5.19e-02 -1.7 3.87e-02 - 1.00e+00 1.00e+00f 1
269+
8 2.8893076e-05 0.00e+00 4.52e-02 -2.5 4.53e-02 - 1.00e+00 1.00e+00f 1
270+
9 3.4591761e-08 0.00e+00 3.80e-04 -2.5 3.18e-03 - 1.00e+00 1.00e+00f 1
271+
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
272+
10 1.2680803e-13 0.00e+00 3.02e-06 -5.7 3.62e-04 - 1.00e+00 1.00e+00f 1
273+
11 7.0136460e-25 0.00e+00 1.72e-12 -8.6 2.13e-07 - 1.00e+00 1.00e+00f 1
274+
275+
Number of Iterations....: 11
276+
277+
(scaled) (unscaled)
278+
Objective...............: 1.5551321399859192e-25 7.0136459513364959e-25
279+
Dual infeasibility......: 1.7239720368203862e-12 7.7751138860599418e-12
280+
Constraint violation....: 0.0000000000000000e+00 0.0000000000000000e+00
281+
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
282+
Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00
283+
Overall NLP error.......: 1.7239720368203862e-12 7.7751138860599418e-12
284+
285+
286+
Number of objective function evaluations = 18
287+
Number of objective gradient evaluations = 12
288+
Number of equality constraint evaluations = 0
289+
Number of inequality constraint evaluations = 0
290+
Number of equality constraint Jacobian evaluations = 0
291+
Number of inequality constraint Jacobian evaluations = 0
292+
Number of Lagrangian Hessian evaluations = 11
293+
Total seconds in IPOPT = 0.002
294+
295+
EXIT: Optimal Solution Found.
296+
297+
Ipopt 3.14.17: Optimal Solution Found
298+
"""
299+
parsed_output = ipopt.Ipopt()._parse_ipopt_output(output)
300+
self.assertEqual(parsed_output["iters"], 11)
301+
self.assertEqual(len(parsed_output["iteration_log"]), 12)
302+
self.assertEqual(parsed_output["incumbent_objective"], 7.0136459513364959e-25)
303+
self.assertIn("final_scaled_results", parsed_output.keys())
304+
236305
def test_empty_output_parsing(self):
237306
with self.assertLogs(
238307
"pyomo.contrib.solver.solvers.ipopt", level="WARNING"

0 commit comments

Comments
 (0)