Skip to content

Add Game.is_degenerate for two-player games - #1201

Draft
UveysAlsalehi wants to merge 7 commits into
gambitproject:masterfrom
UveysAlsalehi:feature/is-degenerate
Draft

UveysAlsalehi wants to merge 7 commits into
gambitproject:masterfrom
UveysAlsalehi:feature/is-degenerate

Conversation

@UveysAlsalehi

Copy link
Copy Markdown

Follows discussion #961

Description of the changes in this PR

Added Game.is_degenerate(certificate=False) to check whether a two-player game is degenerate. A game is degenerate if there exists a mixed strategy with support size $k$ for which the opponent has more than $k$ pure best responses.
The function only works for two-player games; for other games it raises NotImplementedError.
If certificate is True, it also returns a witness: the player and an exact mixed strategy that make the game degenerate.

As in SageMath's implementation, we first check every pure strategy to see whether the opponent has more than one pure best response to it. If so, the game is degenerate.

Then, for every support size $k$ from 2 to $\min(m, n)$, we check every pair consisting of a support of size $k$ and a candidate set of $k+1$ pure best responses for the opponent. To check whether some mixed strategy on that support makes every strategy in the candidate set a best response, we solve a linear program (LP). To solve it exactly, in rational arithmetic, I used the pycddlib library.

I tested 22 degenerate and nondegenerate games from SageMath, the Gambit catalog and some of my own. The tests cover every exit of the function.

How to review this PR

  • The main code is Game.is_degenerate in src/pygambit/game.pxi.
  • The tests are in tests/test_degeneracy.py.
  • Other changes:
    • added pycddlib>=3.0 to pyproject.toml,
    • added Game.is_degenerate to the API docs in doc/pygambit.api.rst,
    • added a note to doc/developer.build.rst,
    • added cddlib and GMP to the CI for Linux and macOS, including the catalog lint job. Windows is not working yet.
  • I have some questions about the implementation, listed below.

Questions

This was my first time adding a dependency and changing the CI, so I am not sure I handled it correctly. My questions are:

  1. Should pycddlib be a required dependency or an optional extra?
  2. Windows CI fails on Python 3.14 because pycddlib does not provide a compatible wheel. How would you prefer to handle this?
  3. Should I also add a ChangeLog entry?
  4. With certificate=True, the witness is returned as (player, strategy). Should it be (strategy, player), like in SageMath?
  5. I fixed the macOS CI build of pycddlib (it needed Homebrew's include/library paths); it now passes on my fork.
  6. Gambit already has an exact LP solver in C++, SolveLP<Rational> in src/solvers/linalg/lpsolve.h. Would you prefer using it instead of pycddlib? It would avoid the new dependency and the installation problems.

Sorry for the late response. I hope this is close to what you had in mind, and I am happy to change anything. Thank you for the guidance in #961 and for the opportunity.

Uveys Al-Salehi and others added 7 commits October 23, 2026 22:37
Replace scipy's floating-point linprog with exact rational LP
(pycddlib/cdd.gmp) so ties are detected exactly, per maintainer
guidance. Pure-strategy ties are checked directly; LPs handle
supports of size >= 2.

- Raise NotImplementedError for games without exactly two players
- Return the witness strategy as a numpy array of Rational
- Add pycddlib>=3.0 as a dependency
- Check certificates in tests with exact Fraction arithmetic;
  add mixed-only, rational-witness and near-tie tests
- List Game.is_degenerate in the API docs; document the
  cddlib/GMP build requirement
- Install cddlib/GMP in Linux and macOS CI
…tproject#961)

Restructure tests/test_degeneracy.py to follow the style of the existing
suite: a parametrized table of degenerate and nondegenerate games (from
SageMath, the catalog and test_games) run with and without a certificate,
dataclass cases pinning unique witnesses, and tests for every exit of the
method, including faked LP statuses. Import cdd inside is_degenerate so
that importing pygambit does not require pycddlib, clarify the docstring
on the witness's support, and update examples to StrategicGame.
pycddlib has no macOS wheels, so it is compiled from source in CI. Add
Homebrew's include and library directories to the compiler and linker
search paths, and build for arm64 only, since Homebrew's cddlib and GMP
on the macOS runners are arm64-only.
The job installs pygambit, which builds pycddlib from source, so it
needs the cddlib and GMP development libraries like the Linux test job.
Give each game in the parametrized table a pytest id instead of a
trailing comment, build test games with gbt.Rational as the rest of
the test suite does, and store certificate test games directly rather
than through factory functions.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The mandatory dependency leaves supported Windows installations and documentation and wheel-test environments without a working installation path.

2 open issues
What changed in this PR

Adds exact degeneracy detection for two-player games to the Python API, with an optional mixed-strategy witness.

Changes:

  • Implements pure-strategy checks and exhaustive rational linear-programming checks using pycddlib.
  • Adds tests for degeneracy, exact witnesses, near-ties, and error handling.
  • Documents the API and native dependencies, and updates Linux/macOS CI setup.
File Description
tests/​test_degeneracy.py Tests results, witnesses, exact arithmetic, and error paths.
src/​pygambit/​game.pxi Implements Game.is_degenerate and documents its behavior.
pyproject.toml Adds pycddlib as a required dependency.
doc/​pygambit.api.rst Lists the new method in the API reference.
doc/​developer.build.rst Documents native build prerequisites.
.github/​workflows/​python.yml Adds Linux/macOS native dependencies and macOS build flags.
.github/​workflows/​lint.yml Installs native prerequisites for catalog validation.

Review effort: Balanced

Comment thread pyproject.toml
"scipy",
"pandas",
"click",
"pycddlib>=3.0",
Comment thread src/pygambit/game.pxi
Comment on lines +1125 to +1135
for S in itertools.combinations(range(m), k):
for T in itertools.combinations(range(n), k + 1):
strategy = tied_best_response_strategy(M2, S, T)
if strategy is not None:
if not certificate:
return True
return True, (0, strategy)

# Player 1 mixes over k columns; can player 0 have k + 1 best responses?
for S in itertools.combinations(range(n), k):
for T in itertools.combinations(range(m), k + 1):
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