Introduce callback functions for printing in lemke.py - #17
Conversation
65df7bb to
a3c76db
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors Lemke’s algorithm implementation to decouple runlemke() from the tableau class and to route progress/error reporting through a callback interface (with a provided PrintingCallback), replacing prior print(...); exit(1) flows with raised exceptions and return values.
Changes:
- Moved
runlemke()out of thetableauclass into a module-level function that returns a solution list (orNoneon ray termination). - Introduced
LemkeCallbackandPrintingCallbackto centralize progress/diagnostic output outside the core algorithm/tableau. - Updated tests and callers to use
runlemke()and to assert exceptions instead ofSystemExit.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_lemke.py | Updates tests to expect raised exceptions (e.g., ValueError, RuntimeError, RayTermination) instead of SystemExit. |
| tests/test_lcp.py | Switches tests from a local lemke_solver() wrapper to direct runlemke() usage and updates failure expectations to None. |
| tests/sequence_form_helper.py | Updates helper to call runlemke() instead of the removed test helper. |
| src/lemke/lemke.py | Core refactor: introduces callbacks, converts exits/prints to exceptions, adds module-level runlemke() and printing helpers. |
| src/lemke/bimatrix.py | Updates equilibrium-finding paths to use lemke.runlemke() instead of tableau.runlemke(). |
Comments suppressed due to low confidence (2)
src/lemke/bimatrix.py:283
lemke.runlemke()can returnNoneon ray termination, butruntrace()slices the result unconditionally. This will raise aTypeErrorand obscure the solver failure. Handle theNonecase explicitly and raise a clear error.
def runtrace(self, xprior, yprior):
lcp = self.createLCP()
Ay = self.A.negmatrix @ yprior
xB = xprior @ self.B.negmatrix
lcp.d = np.hstack((Ay, xB, [1, 1]))
equilibrium = lemke.runlemke(lcp=lcp)[1: lcp.n - 1]
return tuple(equilibrium)
tests/sequence_form_helper.py:232
runlemke()can returnNoneon ray termination, butsolve_via_sequence_form()indexes/slicessolunconditionally. If the solver fails, this will raise aTypeErrorinstead of surfacing a clear, domain-relevant error. Check forNoneand raise a meaningful exception (or propagate a solver-specific error).
lcp_instance = lcp_from_data(M, q, d)
sol = runlemke(lcp=lcp_instance)
# realization plans
x_y = sol[1:(ns1 + ns2 + 1)]
x = x_y[:ns1]
y = x_y[ns1:]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| runlemke( | ||
| lcp=m, | ||
| callback=PrintingCallback(stream=sys.stdout, verbose=verbose, z0=z0), | ||
| ) |
| def runLH(self, droppedlabel): | ||
| lcp = self.createLCP() | ||
| lcp.d[droppedlabel - 1] = 0 # subsidize this label | ||
| tabl = lemke.tableau(lcp) | ||
| # tabl.runlemke(verbose=True, lexstats=True, z0=gz0) | ||
| tabl.runlemke() | ||
| return tuple(getequil(tabl)) | ||
| equilibrium = lemke.runlemke(lcp=lcp)[1: lcp.n - 1] | ||
| return tuple(equilibrium) |
There was a problem hiding this comment.
@nataliemes This is not a problem because LH should never fail with ray termination, do you agree?
|
@nataliemes I am now ready for this one, so please fix the merge conflicts, cheers. |
# Conflicts: # src/lemke/lemke.py # tests/test_lcp_units.py
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
tests/sequence_form_helper.py:229
- runlemke() can return None on ray termination, but this helper immediately slices
sol, which would raise a TypeError and hide the underlying termination reason. Add an explicit check and raise a clear error (or otherwise handle the failure case).
lcp_instance = lcp_from_data(M, q, d)
sol = runlemke(lcp=lcp_instance)
# realization plans
x_y = sol[1:(ns1 + ns2 + 1)]
src/lemke/lemke.py:575
lcp.from_file()now raises ValueError for invalid input, but the CLI command doesn't catch it. This will surface as an unhandled exception (often a traceback) instead of a clean CLI error message. Convert it to a ClickException (exit code 1 with a friendly message).
m = lcp.from_file(lcpfilename)
result = runlemke(
lcp=m,
callback=PrintingCallback(stream=sys.stdout, verbose=verbose, z0=z0),
tests/test_lcp.py:242
- The docstring still says this test verifies SystemExit(1), but the assertion now checks that runlemke() returns None. Update the docstring so the test description matches the behavior under test.
by verifying that it raises SystemExit with code 1.
"""
lcp_instance = test_case.factory()
assert runlemke(lcp=lcp_instance) is None
| def runLH(self, droppedlabel): | ||
| lcp = self.createLCP() | ||
| lcp.d[droppedlabel - 1] = 0 # subsidize this label | ||
| tabl = lemke.tableau(lcp) | ||
| # tabl.runlemke(verbose=True, lexstats=True, z0=gz0) | ||
| tabl.runlemke() | ||
| return tuple(getequil(tabl)) | ||
| equilibrium = lemke.runlemke(lcp=lcp)[1: lcp.n - 1] | ||
| return tuple(equilibrium) |
There was a problem hiding this comment.
@nataliemes This is not a problem because LH should never fail with ray termination, do you agree?
| def test_failure(test_case: LCPTestCase): | ||
| """ | ||
| Test the Lemke solver on LCPs that terminate on a secondary ray | ||
| by verifying that it raises SystemExit with code 1. |
There was a problem hiding this comment.
Shouldn't this be returning None, no raising SystemExit?
This PR decouples
runlemke()from thetableauclass and introduces callbacks to print progress.Changes in detail
runlemke()function is separated from thetableauclass:lcpobject as an argument.tableauto read it from there. When a solution is not found,RayTerminationerror is raised andNoneis returned to indicate failure (later changes will instead return a proper result object in both success and failure cases, with astatusfield).tableauinstance is created insiderunlemke()and used there.All printing calls are removed from the
tableauclass andrunlemke():printout()and then callingexit(), errors are raised.docupivot(),outstatistics(),outsol()are separated from thetableauclass.LemkeCallbackbase class is introduced, which has callback functions at different steps of the algorithm (at the beginning, before a pivot, after a pivot, etc.). These functions receivetableauand other relevant arguments.PrintingCallbackclass is a concrete callback implementation that prints the information during the execution of the algorithm to track its progress. It has astreamparameter to change the destination stream (sys.stdoutby default).