Skip to content

Add a result class for LCPs - #18

Open
nataliemes wants to merge 8 commits into
gambitproject:mainfrom
nataliemes:refactor/lcp-result
Open

Add a result class for LCPs#18
nataliemes wants to merge 8 commits into
gambitproject:mainfrom
nataliemes:refactor/lcp-result

Conversation

@nataliemes

@nataliemes nataliemes commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Changes in detail:

  • A new LcpResult class is added to encapsulate the output of Lemke's algorithm. It contains:

    • success: indicates whether a solution was found (True), or whether the algorithm terminated on a secondary ray (False).
    • num_pivots: the number of pivots performed during the algorithm.
    • basis: the set of basic variables at termination (e.g. {'w1', 'z2'}).
    • z0: the final artificial variable value.
    • z: the final values of the z variables.
    • w: the final values of the w variables.
    • ray_entering_variable: the entering variable that led to the secondary ray termination
      (None if a solution is found).

    When success=True, LcpResult stores the final LCP solution.
    When success=False, it stores the last state reached before the algorithm terminated.

    A standalone function result_from_tableau() creates a result object from a tableau, instead of tableau.createsol().
    And LcpResult.__str__() creates the output string for the result object, instead of outsol().

  • If $q \geq 0$, runlemke() now exits early without pivoting and returns the trivial solution: $z = 0, w = q$.

@nataliemes
nataliemes requested a review from stengel August 5, 2026 14:16

@stengel stengel left a comment

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.

Question: how will you use the result class for an LCP?
At the moment the purpose seems to be for output only.
It should at some point be used to generate an equilibrium solution, which will come from the z output field.
When the LCP terminates, is the tableau still accessible? Because that will be needed for running the LCP solver backwards from an existing solution.
Finally, the value of z0 should only matter when there is ray termination. Will the values of z and w then be returned with their current values? And will there be information about the entering variable that leads to the infinite ray? (For this we should try to find instances of ray termination, also as test cases - in the bimatrix application this case should not arise and hence was not covered.)

@nataliemes

Copy link
Copy Markdown
Collaborator Author

Question: how will you use the result class for an LCP? At the moment the purpose seems to be for output only.
It should at some point be used to generate an equilibrium solution, which will come from the z output field.

Do you mean that this LCP result object should be used within bimatrix.py? If so, that is already done - runLH() and runtrace() both call runlemke() and then get the equilibrium from result.z (lines 166 and 196).

When the LCP terminates, is the tableau still accessible? Because that will be needed for running the LCP solver backwards from an existing solution.

Right now tableau is only used within runlemke() and won't be accessible outside of it. I think it may also be possible to access it using callbacks, but maybe adding it to the result class here will be better. I guess we can update/change this when implementing the backwards search after seeing which approach works better?

Finally, the value of z0 should only matter when there is ray termination. Will the values of z and w then be returned with their current values? And will there be information about the entering variable that leads to the infinite ray?

When runlemke() finishes successfully, z0 is 0, ray_entering_variable is None, and w, z, basis is the solution.
Otherwise, z0, w, z, basis contain the last values that runlemke() calculated and ray_entering_variable is whichever entering variable led to the secondary ray termination.

So, in case of success, we get this kind of object:

LcpResult(
    success=True,
    num_pivots=3,
    basis=frozenset({'z1', 'z2'}),
    z0=Fraction(0, 1),
    z=(Fraction(2, 1), Fraction(1, 1)),
    w=(Fraction(0, 1), Fraction(0, 1)),
    ray_entering_variable=None
)

which is printed as:

Process finished successfully after 3 pivots.
Solution found:
basis=    z1 z2
    z=  0  2  1
    w=     0  0

And in case of failure:

LcpResult(
    success=False,
    num_pivots=2,
    basis=frozenset({'z0', 'z1'}),
    z0=Fraction(1, 1),
    z=(Fraction(2, 1), Fraction(0, 1)),
    w=(Fraction(0, 1), Fraction(0, 1)),
    ray_entering_variable='z2'
)

which is printed as:

Terminated on a secondary ray after 2 pivots, when trying to enter z2.
Current basis not an LCP solution:
basis= z0 z1   
    z=  1  2  0
    w=     0  0

(For this we should try to find instances of ray termination, also as test cases - in the bimatrix application this case should not arise and hence was not covered.)

Right now there are 2 LCPs that terminate on a secondary ray in tests\fixtures\lcp: failure_after_first_pivot and failure_after_several_pivots. And the tests check if success in the returned result is False.

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.

2 participants