Pyomo / pyomo

An object-oriented algebraic modeling language in Python for structured optimization problems.
https://www.pyomo.org
Other
1.99k stars 511 forks source link

Support user-provided CyIpopt callbacks with 13 arguments #3289

Closed Robbybp closed 1 month ago

Robbybp commented 3 months ago

Summary/Motivation:

Currently, users are unable to access the get_current_iterate and get_current_violations methods during an intermediate callback as they don't have access to the cyipopt.Problem object. We currently expect a user-provided callback to be a function with 12 arguments: the 11 arguments that Ipopt gives us, plus the NLP object that cyipopt is using. This PR adds support for a new callback API, where the user's callback accepts 13 arguments. The new argument is the cyipopt.Problem instance (our CyIpoptNLP), from which the user can access the get_current* methods.

This is the first step towards implementing something like https://github.com/Pyomo/pyomo/pull/2860. With this PR, users can implement their own callbacks to track primal-dual iterates and infeasibilities. Eventually, I'd like to commit a basic debugging callback that automatically tracks useful information.

Changes proposed in this PR:

Legal Acknowledgement

By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.
blnicho commented 1 month ago

Looks like the Jenkins test failure is real. Here is the stack-trace:

15:25:17 =================================== FAILURES ===================================
15:25:17 _________________ TestCyIpoptSolver.test_solve_13arg_callback __________________
15:25:17 
15:25:17 self = <pyomo.contrib.pynumero.algorithms.solvers.tests.test_cyipopt_solver.TestCyIpoptSolver testMethod=test_solve_13arg_callback>
15:25:17 
15:25:17     @unittest.skipIf(
15:25:17         not cyipopt_available or not cyipopt_ge_1_3, "cyipopt version < 1.3.0"
15:25:17     )
15:25:17     def test_solve_13arg_callback(self):
15:25:17         m = create_model1()
15:25:17     
15:25:17         iterate_data = []
15:25:17     
15:25:17         def intermediate(
15:25:17             nlp,
15:25:17             problem,
15:25:17             alg_mod,
15:25:17             iter_count,
15:25:17             obj_value,
15:25:17             inf_pr,
15:25:17             inf_du,
15:25:17             mu,
15:25:17             d_norm,
15:25:17             regularization_size,
15:25:17             alpha_du,
15:25:17             alpha_pr,
15:25:17             ls_trials,
15:25:17         ):
15:25:17             iterate = problem.get_current_iterate()
15:25:17             x = iterate["x"]
15:25:17             y = iterate["mult_g"]
15:25:17             iterate_data.append((x, y))
15:25:17     
15:25:17         x_sol = np.array([3.85958688, 4.67936007, 3.10358931])
15:25:17         y_sol = np.array([-1.0, 53.90357665])
15:25:17     
15:25:17         solver = pyo.SolverFactory("cyipopt", intermediate_callback=intermediate)
15:25:17         res = solver.solve(m, tee=True)
15:25:17 >       pyo.assert_optimal_termination(res)
15:25:17 
15:25:17 pyomo/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py:406: 
15:25:17 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
15:25:17 
15:25:17 results = {'Problem': [{'Name': 'unknown', 'Lower bound': -inf, 'Upper bound': -504.0, 'Number of objectives': 1, 'Number of con... termination of the optimization.', 'Wallclock time': 0.003202378051355481, 'Termination condition': 'userInterrupt'}]}
15:25:17 
15:25:17     def assert_optimal_termination(results):
15:25:17         """
15:25:17         This function checks if the termination condition for the solver
15:25:17         is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok'
15:25:17         and it raises a RuntimeError exception if this is not true.
15:25:17     
15:25:17         Parameters
15:25:17         ----------
15:25:17         results : Pyomo results object returned from solver.solve
15:25:17         """
15:25:17         if not check_optimal_termination(results):
15:25:17             msg = (
15:25:17                 'Solver failed to return an optimal solution. '
15:25:17                 'Solver status: {}, Termination condition: {}'.format(
15:25:17                     results.solver.status, results.solver.termination_condition
15:25:17                 )
15:25:17             )
15:25:17 >           raise RuntimeError(msg)
15:25:17 E           RuntimeError: Solver failed to return an optimal solution. Solver status: aborted, Termination condition: userInterrupt
15:25:17 
15:25:17 pyomo/pyomo/opt/results/solver.py:178: RuntimeError
15:25:17 ----------------------------- Captured stdout call -----------------------------
15:25:17 This is Ipopt version 3.13.2, running with linear solver ma27.
15:25:17 
15:25:17 Number of nonzeros in equality constraint Jacobian...:        2
15:25:17 Number of nonzeros in inequality constraint Jacobian.:        2
15:25:17 Number of nonzeros in Lagrangian Hessian.............:        4
15:25:17 
15:25:17 Total number of variables............................:        3
15:25:17                      variables with only lower bounds:        2
15:25:17                 variables with lower and upper bounds:        0
15:25:17                      variables with only upper bounds:        0
15:25:17 Total number of equality constraints.................:        1
15:25:17 Total number of inequality constraints...............:        1
15:25:17         inequality constraints with only lower bounds:        0
15:25:17    inequality constraints with lower and upper bounds:        0
15:25:17         inequality constraints with only upper bounds:        1
15:25:17 
15:25:17 iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
15:25:17    0 -5.0400000e+02 5.00e+00 2.17e+01  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
15:25:17 
15:25:17 Number of Iterations....: 0
15:25:17 
15:25:17                                    (scaled)                 (unscaled)
15:25:17 Objective...............:  -8.7500000000000000e+01   -5.0400000000000000e+02
15:25:17 Dual infeasibility......:   2.1710422009792488e+01    1.2505203077640472e+02
15:25:17 Constraint violation....:   5.0000000000000000e+00    5.0000000000000000e+00
15:25:17 Complementarity.........:   4.0000000099999999e+00    2.3040000057600000e+01
15:25:17 Overall NLP error.......:   2.1710422009792488e+01    1.2505203077640472e+02
15:25:17 
15:25:17 
15:25:17 Number of objective function evaluations             = 1
15:25:17 Number of objective gradient evaluations             = 1
15:25:17 Number of equality constraint evaluations            = 1
15:25:17 Number of inequality constraint evaluations          = 1
15:25:17 Number of equality constraint Jacobian evaluations   = 1
15:25:17 Number of inequality constraint Jacobian evaluations = 1
15:25:17 Number of Lagrangian Hessian evaluations             = 0
15:25:17 Total CPU secs in IPOPT (w/o function evaluations)   =      0.002
15:25:17 Total CPU secs in NLP function evaluations           =      0.000
15:25:17 
15:25:17 EXIT: Stopping optimization at current point as requested by user.
15:25:17 ----------------------------- Captured stderr call -----------------------------
15:25:17 Exceptions caught in Qt event loop:
15:25:17 ________________________________________________________________________________
15:25:17 Traceback (most recent call last):
15:25:17   File ".../python310/pyomo/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py", line 500, in intermediate
15:25:17     return self._intermediate_callback(
15:25:17   File ".../python310/pyomo/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py", line 396, in intermediate
15:25:17     iterate = problem.get_current_iterate()
15:25:17   File "cyipopt/cython/ipopt_wrapper.pyx", line 701, in ipopt_wrapper.Problem.get_current_iterate
15:25:17 RuntimeError: get_current_iterate only supports Ipopt version >=3.14.0 CyIpopt is compiled with version 3.13.2
15:25:17 =========================== short test summary info ============================
15:25:17 FAILED pyomo/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py::TestCyIpoptSolver::test_solve_13arg_callback - RuntimeError: Solver failed to return an optimal solution. Solver status: aborted, Termination condition: userInterrupt
codecov[bot] commented 1 month ago

Codecov Report

Attention: Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.

Project coverage is 88.54%. Comparing base (404fd6d) to head (671d8c6). Report is 603 commits behind head on main.

Files Patch % Lines
...o/contrib/pynumero/interfaces/cyipopt_interface.py 93.33% 1 Missing :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #3289 +/- ## ======================================= Coverage 88.53% 88.54% ======================================= Files 868 868 Lines 98495 98509 +14 ======================================= + Hits 87206 87221 +15 + Misses 11289 11288 -1 ``` | [Flag](https://app.codecov.io/gh/Pyomo/pyomo/pull/3289/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | Coverage Δ | | |---|---|---| | [linux](https://app.codecov.io/gh/Pyomo/pyomo/pull/3289/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | `86.05% <93.33%> (+<0.01%)` | :arrow_up: | | [osx](https://app.codecov.io/gh/Pyomo/pyomo/pull/3289/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | `75.62% <6.66%> (-0.01%)` | :arrow_down: | | [other](https://app.codecov.io/gh/Pyomo/pyomo/pull/3289/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | `86.55% <93.33%> (+<0.01%)` | :arrow_up: | | [win](https://app.codecov.io/gh/Pyomo/pyomo/pull/3289/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | `83.86% <93.33%> (-0.01%)` | :arrow_down: | Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo#carryforward-flags-in-the-pull-request-comment) to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.