Skip to content

Introduce callback functions for printing in lemke.py - #17

Open
nataliemes wants to merge 9 commits into
gambitproject:mainfrom
nataliemes:refactor/callbacks-lemke
Open

Introduce callback functions for printing in lemke.py#17
nataliemes wants to merge 9 commits into
gambitproject:mainfrom
nataliemes:refactor/callbacks-lemke

Conversation

@nataliemes

@nataliemes nataliemes commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

This PR decouples runlemke() from the tableau class and introduces callbacks to print progress.

Changes in detail

  • runlemke() function is separated from the tableau class:

    • Callers have to pass lcp object as an argument.
    • Callers receive the solution as a result (a simple list for now), since they no longer have direct access to tableau to read it from there. When a solution is not found, RayTermination error is raised and None is returned to indicate failure (later changes will instead return a proper result object in both success and failure cases, with a status field).
    • tableau instance is created inside runlemke() and used there.
  • All printing calls are removed from the tableau class and runlemke():

    • Instead of printing error messages with printout() and then calling exit(), errors are raised.
    • docupivot(), outstatistics(), outsol() are separated from the tableau class.
    • LemkeCallback base 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 receive tableau and other relevant arguments.
      PrintingCallback class is a concrete callback implementation that prints the information during the execution of the algorithm to track its progress. It has a stream parameter to change the destination stream (sys.stdout by default).

@rahulsavani
rahulsavani self-requested a review July 24, 2026 04:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the tableau class into a module-level function that returns a solution list (or None on ray termination).
  • Introduced LemkeCallback and PrintingCallback to centralize progress/diagnostic output outside the core algorithm/tableau.
  • Updated tests and callers to use runlemke() and to assert exceptions instead of SystemExit.

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 return None on ray termination, but runtrace() slices the result unconditionally. This will raise a TypeError and obscure the solver failure. Handle the None case 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 return None on ray termination, but solve_via_sequence_form() indexes/slices sol unconditionally. If the solver fails, this will raise a TypeError instead of surfacing a clear, domain-relevant error. Check for None and 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.

Comment thread src/lemke/lemke.py Outdated
Comment on lines +587 to +590
runlemke(
lcp=m,
callback=PrintingCallback(stream=sys.stdout, verbose=verbose, z0=z0),
)
Comment thread src/lemke/bimatrix.py
Comment on lines 251 to +256
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nataliemes This is not a problem because LH should never fail with ray termination, do you agree?

@rahulsavani

Copy link
Copy Markdown
Member

@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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/lemke/bimatrix.py
Comment on lines 251 to +256
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nataliemes This is not a problem because LH should never fail with ray termination, do you agree?

Comment thread tests/test_lcp.py
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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be returning None, no raising SystemExit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants