|
9 | 9 | # This software is distributed under the 3-clause BSD License. |
10 | 10 | # ___________________________________________________________________________ |
11 | 11 |
|
12 | | -import os |
| 12 | +import os, sys |
13 | 13 | import subprocess |
| 14 | +from contextlib import contextmanager |
14 | 15 |
|
15 | 16 | import pyomo.environ as pyo |
16 | 17 | from pyomo.common.envvar import is_windows |
|
29 | 30 | ipopt_available = ipopt.Ipopt().available() |
30 | 31 |
|
31 | 32 |
|
| 33 | +@contextmanager |
| 34 | +def windows_tee_buffer(size=1 << 20): |
| 35 | + """Temporarily increase TeeStream buffer size on Windows""" |
| 36 | + if not sys.platform.startswith("win"): |
| 37 | + # Only windows has an issue |
| 38 | + yield |
| 39 | + return |
| 40 | + import pyomo.common.tee as tee |
| 41 | + |
| 42 | + old = tee._pipe_buffersize |
| 43 | + tee._pipe_buffersize = size |
| 44 | + try: |
| 45 | + yield |
| 46 | + finally: |
| 47 | + tee._pipe_buffersize = old |
| 48 | + |
| 49 | + |
32 | 50 | @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") |
33 | 51 | class TestIpoptSolverConfig(unittest.TestCase): |
34 | 52 | def test_default_instantiation(self): |
@@ -321,6 +339,99 @@ def test_empty_output_parsing(self): |
321 | 339 | logs.output[0], |
322 | 340 | ) |
323 | 341 |
|
| 342 | + def test_parse_output_diagnostic_tags(self): |
| 343 | + output = """****************************************************************************** |
| 344 | +This program contains Ipopt, a library for large-scale nonlinear optimization. |
| 345 | + Ipopt is released as open source code under the Eclipse Public License (EPL). |
| 346 | + For more information visit http://projects.coin-or.org/Ipopt |
| 347 | +
|
| 348 | +This version of Ipopt was compiled from source code available at |
| 349 | + https://github.com/IDAES/Ipopt as part of the Institute for the Design of |
| 350 | + Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE |
| 351 | + Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse. |
| 352 | +
|
| 353 | +This version of Ipopt was compiled using HSL, a collection of Fortran codes |
| 354 | + for large-scale scientific computation. All technical papers, sales and |
| 355 | + publicity material resulting from use of the HSL codes within IPOPT must |
| 356 | + contain the following acknowledgement: |
| 357 | + HSL, a collection of Fortran codes for large-scale scientific |
| 358 | + computation. See http://www.hsl.rl.ac.uk/. |
| 359 | +****************************************************************************** |
| 360 | +
|
| 361 | +This is Ipopt version 3.13.2, running with linear solver ma57. |
| 362 | +
|
| 363 | +Number of nonzeros in equality constraint Jacobian...: 77541 |
| 364 | +Number of nonzeros in inequality constraint Jacobian.: 0 |
| 365 | +Number of nonzeros in Lagrangian Hessian.............: 51855 |
| 366 | +
|
| 367 | +Total number of variables............................: 15468 |
| 368 | + variables with only lower bounds: 3491 |
| 369 | + variables with lower and upper bounds: 5026 |
| 370 | + variables with only upper bounds: 186 |
| 371 | +Total number of equality constraints.................: 15417 |
| 372 | +Total number of inequality constraints...............: 0 |
| 373 | + inequality constraints with only lower bounds: 0 |
| 374 | + inequality constraints with lower and upper bounds: 0 |
| 375 | + inequality constraints with only upper bounds: 0 |
| 376 | +
|
| 377 | +iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls |
| 378 | + 0 4.3126674e+00 1.34e+00 1.00e+00 -5.0 0.00e+00 - 0.00e+00 0.00e+00 0 |
| 379 | +Reallocating memory for MA57: lfact (2247250) |
| 380 | + 1r 4.3126674e+00 1.34e+00 9.99e+02 0.1 0.00e+00 -4.0 0.00e+00 3.29e-10R 2 |
| 381 | + 2r 3.0519246e+08 1.13e+00 9.90e+02 0.1 2.30e+02 - 2.60e-02 9.32e-03f 1 |
| 382 | + 3r 2.2712595e+09 1.69e+00 9.73e+02 0.1 2.23e+02 - 2.54e-02 1.71e-02f 1 Nhj |
| 383 | + 4 2.2712065e+09 1.69e+00 1.37e+09 -5.0 3.08e+03 - 1.32e-05 1.17e-05f 1 q |
| 384 | + 5 1.9062986e+09 1.55e+00 1.25e+09 -5.0 5.13e+03 - 1.19e-01 8.38e-02f 1 |
| 385 | + 6 1.7041594e+09 1.46e+00 1.18e+09 -5.0 5.66e+03 - 7.06e-02 5.45e-02f 1 |
| 386 | + 7 1.4763158e+09 1.36e+00 1.10e+09 -5.0 3.94e+03 - 2.30e-01 6.92e-02f 1 |
| 387 | + 8 8.5873108e+08 1.04e+00 8.41e+08 -5.0 2.38e+05 - 3.49e-06 2.37e-01f 1 |
| 388 | + 9 4.4215572e+08 7.45e-01 6.03e+08 -5.0 1.63e+06 - 7.97e-02 2.82e-01f 1 |
| 389 | +iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls |
| 390 | + 10 5.0251884e+01 1.65e-01 1.57e+04 -5.0 1.24e+06 - 3.92e-05 1.00e+00f 1 |
| 391 | + 11 4.9121733e+01 4.97e-02 4.68e+03 -5.0 8.11e+04 - 4.31e-02 7.01e-01h 1 |
| 392 | + 12 4.1483985e+01 2.24e-02 5.97e+03 -5.0 1.15e+06 - 5.93e-02 1.00e+00f 1 |
| 393 | + 13 3.5762585e+01 1.75e-02 5.00e+03 -5.0 1.03e+06 - 1.25e-01 1.00e+00f 1 |
| 394 | + 14 3.2291014e+01 1.08e-02 3.51e+03 -5.0 8.25e+05 - 6.68e-01 1.00e+00f 1 |
| 395 | + 15 3.2274630e+01 3.31e-05 1.17e+00 -5.0 4.26e+04 - 9.92e-01 1.00e+00h 1 |
| 396 | + 16 3.2274631e+01 7.45e-09 2.71e-03 -5.0 6.11e+02 - 8.97e-01 1.00e+00h 1 |
| 397 | + 17 3.2274635e+01 7.45e-09 2.35e-03 -5.0 2.71e+04 - 1.32e-01 1.00e+00f 1 |
| 398 | + 18 3.2274635e+01 7.45e-09 1.15e-04 -5.0 5.53e+03 - 9.51e-01 1.00e+00h 1 |
| 399 | + 19 3.2274635e+01 7.45e-09 2.84e-05 -5.0 4.41e+04 - 7.54e-01 1.00e+00f 1 |
| 400 | +iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls |
| 401 | + 20 3.2274635e+01 7.45e-09 8.54e-07 -5.0 1.83e+04 - 1.00e+00 1.00e+00h 1 |
| 402 | +
|
| 403 | +Number of Iterations....: 20 |
| 404 | +
|
| 405 | + (scaled) (unscaled) |
| 406 | +Objective...............: 3.2274635418964841e+01 3.2274635418964841e+01 |
| 407 | +Dual infeasibility......: 8.5365078678328669e-07 8.5365078678328669e-07 |
| 408 | +Constraint violation....: 8.0780625068607930e-13 7.4505805969238281e-09 |
| 409 | +Complementarity.........: 1.2275904566414160e-05 1.2275904566414160e-05 |
| 410 | +Overall NLP error.......: 1.2275904566414160e-05 1.2275904566414160e-05 |
| 411 | +
|
| 412 | +
|
| 413 | +Number of objective function evaluations = 23 |
| 414 | +Number of objective gradient evaluations = 20 |
| 415 | +Number of equality constraint evaluations = 23 |
| 416 | +Number of inequality constraint evaluations = 0 |
| 417 | +Number of equality constraint Jacobian evaluations = 22 |
| 418 | +Number of inequality constraint Jacobian evaluations = 0 |
| 419 | +Number of Lagrangian Hessian evaluations = 20 |
| 420 | +Total CPU secs in IPOPT (w/o function evaluations) = 10.450 |
| 421 | +Total CPU secs in NLP function evaluations = 1.651 |
| 422 | +
|
| 423 | +EXIT: Optimal Solution Found. |
| 424 | + """ |
| 425 | + parsed_output = ipopt.Ipopt()._parse_ipopt_output(output) |
| 426 | + self.assertEqual(parsed_output["iters"], 20) |
| 427 | + self.assertEqual(len(parsed_output["iteration_log"]), 21) |
| 428 | + self.assertEqual(parsed_output["incumbent_objective"], 3.2274635418964841e01) |
| 429 | + self.assertEqual(parsed_output["iteration_log"][3]["diagnostic_tags"], 'Nhj') |
| 430 | + self.assertIn("final_scaled_results", parsed_output.keys()) |
| 431 | + self.assertIn( |
| 432 | + 'IPOPT (w/o function evaluations)', parsed_output['cpu_seconds'].keys() |
| 433 | + ) |
| 434 | + |
324 | 435 | def test_verify_ipopt_options(self): |
325 | 436 | opt = ipopt.Ipopt(solver_options={'max_iter': 4}) |
326 | 437 | opt._verify_ipopt_options(opt.config) |
@@ -478,6 +589,37 @@ def test_ipopt_solve(self): |
478 | 589 | self.assertAlmostEqual(model.x.value, 1) |
479 | 590 | self.assertAlmostEqual(model.y.value, 1) |
480 | 591 |
|
| 592 | + def test_ipopt_quiet_print_level(self): |
| 593 | + model = self.create_model() |
| 594 | + result = ipopt.Ipopt().solve(model, solver_options={'print_level': 0}) |
| 595 | + # IPOPT doesn't tell us anything about the iters if the print level |
| 596 | + # is set to 0 |
| 597 | + self.assertIsNone(result.iteration_count) |
| 598 | + self.assertFalse(hasattr(result.extra_info, 'iteration_log')) |
| 599 | + model = self.create_model() |
| 600 | + result = ipopt.Ipopt().solve(model, solver_options={'print_level': 3}) |
| 601 | + # At a slightly higher level, we get some of the info, like |
| 602 | + # iteration count, but NOT iteration_log |
| 603 | + self.assertEqual(result.iteration_count, 11) |
| 604 | + self.assertFalse(hasattr(result.extra_info, 'iteration_log')) |
| 605 | + |
| 606 | + def test_ipopt_loud_print_level(self): |
| 607 | + with windows_tee_buffer(1 << 20): |
| 608 | + model = self.create_model() |
| 609 | + result = ipopt.Ipopt().solve(model, solver_options={'print_level': 8}) |
| 610 | + # Nothing unexpected should be in the results object at this point, |
| 611 | + # except that the solver_log is significantly longer |
| 612 | + self.assertEqual(result.iteration_count, 11) |
| 613 | + self.assertEqual(result.incumbent_objective, 7.013645951336496e-25) |
| 614 | + self.assertIn('Optimal Solution Found', result.extra_info.solver_message) |
| 615 | + self.assertTrue(hasattr(result.extra_info, 'iteration_log')) |
| 616 | + model = self.create_model() |
| 617 | + result = ipopt.Ipopt().solve(model, solver_options={'print_level': 12}) |
| 618 | + self.assertEqual(result.iteration_count, 11) |
| 619 | + self.assertEqual(result.incumbent_objective, 7.013645951336496e-25) |
| 620 | + self.assertIn('Optimal Solution Found', result.extra_info.solver_message) |
| 621 | + self.assertTrue(hasattr(result.extra_info, 'iteration_log')) |
| 622 | + |
481 | 623 | def test_ipopt_results(self): |
482 | 624 | model = self.create_model() |
483 | 625 | results = ipopt.Ipopt().solve(model) |
@@ -612,14 +754,13 @@ def test_map_OF_options(self): |
612 | 754 | solver_options={'OF_bogus_option': 5}, |
613 | 755 | load_solutions=False, |
614 | 756 | ) |
615 | | - print(LOG.getvalue()) |
616 | 757 | self.assertIn('OPTION_INVALID', LOG.getvalue()) |
617 | 758 | # Note: OF_ is stripped |
618 | 759 | self.assertIn( |
619 | 760 | 'Read Option: "bogus_option". It is not a valid option', LOG.getvalue() |
620 | 761 | ) |
621 | 762 |
|
622 | | - with self.assertRaisesRegex(ValueError, "unallowed ipopt option 'wantsol'"): |
| 763 | + with self.assertRaisesRegex(ValueError, "unallowed Ipopt option 'wantsol'"): |
623 | 764 | results = ipopt.LegacyIpoptSolver().solve( |
624 | 765 | model, |
625 | 766 | tee=True, |
|
0 commit comments