funkelab / ilpy

Unified python wrappers for popular ILP solvers
https://funkelab.github.io/ilpy/
MIT License
3 stars 2 forks source link

feat: Add event callback to solver backend to monitor progress #56

Closed tlambert03 closed 5 months ago

tlambert03 commented 5 months ago

This pull request adds an event callback feature to the SolverBackend class, and exposes it on the ilpy.Solver object with set_event_callback(). (currently only implemented for the SCIP backend)

This takes a python callable that will be called with a mapping of key/value metadata for each event that occurs during the solving process. Structure of that payload, and exact events that occur remain to be seen. However, as a current example, the following script:

import ilpy
import numpy as np

N = 100

solution = ilpy.solve(
    objective=list(range(1, N + 1)),
    constraints=(
        [(row.tolist(), "<=", 1) for row in np.eye(N)] + [([1] * N, "=", N // 2)]
    ),
    sense=ilpy.Sense.Minimize,
    variable_type=ilpy.VariableType.Binary,
    preference=ilpy.Preference.Scip,
    on_event=print,
)

would print out

{'dualbound': -1e+20, 'event_type': 'PRESOLVEROUND', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'PRESOLVEROUND', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'PRESOLVEROUND', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'PRESOLVEROUND', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'PRESOLVEROUND', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'NODEDELETE', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': -1e+20, 'event_type': 'NODEFOCUSED', 'gap': 1e+20, 'primalbound': 1e+20}
{'dualbound': 0.0, 'event_type': 'BESTSOLFOUND', 'gap': 1e+20, 'primalbound': 1985.0}
{'dualbound': 0.0, 'event_type': 'FIRSTLPSOLVED', 'gap': 1e+20, 'primalbound': 1985.0}
{'dualbound': 1275.0, 'event_type': 'BESTSOLFOUND', 'gap': 0.0, 'primalbound': 1275.0}
{'dualbound': 1275.0, 'event_type': 'NODEINFEASIBLE', 'gap': 0.0, 'primalbound': 1275.0}
{'dualbound': 1275.0, 'event_type': 'NODEINFEASIBLE', 'gap': 0.0, 'primalbound': 1275.0}
{'dualbound': 1275.0, 'event_type': 'NODEDELETE', 'gap': 0.0, 'primalbound': 1275.0}

( not a very interesting one since it gets solved in one step)...

tlambert03 commented 5 months ago

@funkey @cmalinmayor curious to get your initial thoughts on this

codecov[bot] commented 5 months ago

Codecov Report

Attention: Patch coverage is 24.47552% with 216 lines in your changes are missing coverage. Please review.

Project coverage is 64.83%. Comparing base (0fdfae7) to head (0b3248c). Report is 1 commits behind head on main.

:exclamation: Current head 0b3248c differs from pull request most recent head 289f60c. Consider uploading reports for the commit 289f60c to get more accurate results

Files Patch % Lines
ilpy/impl/solvers/GurobiBackend.cpp 7.74% 128 Missing and 3 partials :warning:
ilpy/impl/solvers/ScipEventHandler.h 50.00% 44 Missing :warning:
ilpy/impl/solvers/SolverBackend.h 16.66% 38 Missing and 2 partials :warning:
ilpy/wrapper.pyx 80.00% 1 Missing :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #56 +/- ## =========================================== - Coverage 75.72% 64.83% -10.90% =========================================== Files 17 18 +1 Lines 828 944 +116 Branches 154 202 +48 =========================================== - Hits 627 612 -15 - Misses 136 316 +180 + Partials 65 16 -49 ```

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

tlambert03 commented 5 months ago

callbacks working for gurobi now as well, with dualbound, primalbound and gap reported for each backend. The remainder of the work now is in

tlambert03 commented 5 months ago

note also @funkey , I've bumped this to c++17 to use std::variant ... but can find an alternative if you'd prefer to keep it at c++11

funkey commented 5 months ago

Wonderful, this is great! :)

No problem with C++17. Looks all good to me. Thanks a lot!