Skip to content

Change stop_after to std::optional<size_t> and reject stop_after <= 0 in pygambit - #1071

Merged
tturocy merged 12 commits into
masterfrom
copilot/remove-stop-after-zero
Aug 25, 2026
Merged

tturocy merged 12 commits into
masterfrom
copilot/remove-stop-after-zero

Conversation

Copilot AI commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Issues closed by this PR

Description of the changes in this PR

stop_after=0 was silently accepted in pygambit as equivalent to stop_after=None (find all equilibria), inheriting the C++ convention where int stopAfter = 0 meant "no limit." This is confusing for users.

C++ solvers (enumpoly, lcp): int p_stopAfter → std::optional<size_t>. nullopt means no limit; 0 is no longer a valid sentinel.

Cython layer (.pxd, .pxi): Declarations updated to optional[size_t]. Wrappers convert Python None to C++ nullopt.

  • Python (nash.py): stop_after <= 0, non-integer values, and float("inf") now raise ValueError in both enumpoly_solve and lcp_solve. None is passed through directly instead of being converted to 0.

  • CLI tools: stopAfter changed to std::optional<size_t>; invalid -e values (non-positive or non-integer) print an error message and exit with status 1.

GUI: nashspec.h structs updated to std::optional<size_t> with nullopt default.

# Before: silently treated as "find all"
gbt.nash.enumpoly_solve(game, stop_after=0)

# After: raises ValueError
gbt.nash.enumpoly_solve(game, stop_after=0)
# ValueError: enumpoly_solve(): stop_after argument must be a positive integer; got 0

How to review this PR

  • Trace the type change from C++ headers → implementations → Cython declarations → Python wrappers to verify consistency
  • Check that std::optional comparisons/arithmetic in nfglcp.cc, nfgpoly.cc, efgpoly.cc correctly handle nullopt vs valued cases
  • Verify the two new tests in test_nash.py cover the rejection of stop_after=0 and stop_after=-1 along with fractional stop_after values and float("inf").

Copilot AI linked an issue Aug 21, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits August 21, 2026 08:11
… stop_after <= 0 in Python

Closes #1067

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>
…After input

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>
Copilot AI changed the title [WIP] Remove stop_after = 0 from pygambit Change stop_after to std::optional<size_t> and reject stop_after <= 0 in pygambit Aug 21, 2026
Copilot AI requested a review from rahulsavani August 21, 2026 08:21

@rahulsavani rahulsavani 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.

Can we have the following changes:

  • We need to deal with <=0 values for the command line tools too.

  • At the moment things like 0.5 or 1.2 are accepted as values of stop_after. Can we include a check that input really is a whole number. In python, float.is_integer would do for this, but we need the check at the C++ level too, e.g. for the command line tools.

  • Please ensure we pass the code formatting tests too.

@rahulsavani rahulsavani 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.

Nothing has been committed since my last review, so I am not sure why a re-review has been requested. See requested changes from last review.

@rahulsavani

Copy link
Copy Markdown
Member

@copilot try again

Copilot AI and others added 2 commits August 21, 2026 12:33
Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>
Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot try again

Addressed all three items:

  1. CLI tools: enumpoly.cc and lcp.cc now parse -e with strtod and reject non-positive and non-integer values (silently ignored, matching existing CLI conventions).
  2. Non-integer check: Python uses int(stop_after) != stop_after (type-agnostic). C++ CLI uses std::floor(val) != val with overflow guard.
  3. clang-format: Fixed all violations across efgpoly.cc, nfgpoly.cc, nfglcp.cc, dlnash.cc, and the CLI tools.

Tests added for stop_after=0.5 and stop_after=1.2 in both enumpoly_solve and lcp_solve.

@rahulsavani rahulsavani 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.

@copilot Actually for non-positive, or non-integer arguments to the gambit-lcp or gambit-enumpoly CLI tools, we don't want to silently ignore. Instead, print an message and then exit(1).

…lues

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Actually for non-positive, or non-integer arguments to the gambit-lcp or gambit-enumpoly CLI tools, we don't want to silently ignore. Instead, print an message and then exit(1).

Updated both gambit-enumpoly and gambit-lcp CLI tools to print an error message to stderr and exit(1) when -e receives a non-positive or non-integer value.

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix remaining 4 comments from latest copilot review

Addressed in 1dd4dad: the enum-poly root solver API now takes size_t, and the NFG/EFG callers no longer narrow stop_after through int. I also rechecked the CLI -e handling locally (0 and 1.2 both print an error and exit 1) and verified the touched files with clang-format.

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (3) — in code that hasn't changed since the last review.

src/pygambit/nash.py:249

  • stop_after=float("inf") bypasses the positivity check and int(stop_after) raises OverflowError before this intended ValueError; NaN likewise bypasses the custom message. Catch conversion failures (or validate the numeric value before converting) so every non-integer value is rejected with the new API error consistently.

This issue also appears on line 721 of the same file.

    if stop_after is not None and int(stop_after) != stop_after:

src/tools/lcp/lcp.cc:103

  • The PR description says non-positive -e values are ignored, but this branch exits with status 1 for both zero and negative values. Either keep stopAfter disengaged for non-positive input as described, or update the stated CLI behavior if rejection is intentional.
      if (result.ec != std::errc{} || result.ptr != end || parsed == 0) {
        std::cerr << "Error: -e argument must be a positive integer; got '" << optarg << "'."
                  << std::endl;
        exit(1);

src/tools/enumpoly/enumpoly.cc:187

  • The PR description says non-positive -e values are ignored, but this branch exits with status 1 for both zero and negative values. Either keep stopAfter disengaged for non-positive input as described, or update the stated CLI behavior if rejection is intentional.
      if (result.ec != std::errc{} || result.ptr != end || parsed == 0) {
        std::cerr << "Error: -e argument must be a positive integer; got '" << optarg << "'."
                  << std::endl;
        exit(1);

src/pygambit/nash.py:721

  • stop_after=float("inf") bypasses the positivity check and int(stop_after) raises OverflowError before this intended ValueError; NaN likewise bypasses the custom message. Catch conversion failures (or validate the numeric value before converting) so every non-integer value is rejected with the new API error consistently.
    if stop_after is not None and int(stop_after) != stop_after:

@rahulsavani rahulsavani 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.

@copilot

  • Ensure that stop_after=float("inf") gives a ValueError (lines 249 and 721 in src/pygambit/nash.py)
  • Update the PR description for the CLI tools: "non-positive -e values are ignored" is incorrect

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

  • Ensure that stop_after=float("inf") gives a ValueError (lines 249 and 721 in src/pygambit/nash.py)...

Addressed in 4db1513: stop_after=float("inf") now raises ValueError in both lcp_solve() and enumpoly_solve(), with tests added for both cases. The CLI wording is corrected to state that invalid -e values print an error and exit 1.

@rahulsavani

Copy link
Copy Markdown
Member

@copilot

The CLI wording is corrected to state that invalid -e values print an error and exit 1.

This needs to be corrected in the PR description which has not been edited in 8 hours. Make sure the PR description is up-to-date and fully represents the latest version of the code.

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

The CLI wording is corrected to state that invalid -e values print an error and exit 1.
...

I can’t edit the PR body directly with the tools available in this environment, but the code is already up to date at 4db1513. The PR description should be updated so it says:

  • Python (nash.py): stop_after <= 0, non-integer values, and float("inf") now raise ValueError in both enumpoly_solve and lcp_solve. None is passed through directly instead of being converted to 0.
  • CLI tools: stopAfter changed to std::optional<size_t>; invalid -e values (non-positive or non-integer) print an error message and exit with status 1.
  • How to review this PR: the tests in test_nash.py now also cover fractional stop_after values and float("inf").

@rahulsavani
rahulsavani marked this pull request as ready for review August 21, 2026 16:41
@rahulsavani
rahulsavani requested a review from tturocy August 21, 2026 16:41
@tturocy

tturocy commented Aug 24, 2026

Copy link
Copy Markdown
Member

In thinking about the stopAfter checking more carefully, I realised that also you can write stopAfter=True and stopAfter=False in Python. stopAfter=True would be coerced into meaning "stop after one equilibrium". Do we consider that a bug or a feature? :)

@rahulsavani

Copy link
Copy Markdown
Member

In thinking about the stopAfter checking more carefully, I realised that also you can write stopAfter=True and stopAfter=False in Python. stopAfter=True would be coerced into meaning "stop after one equilibrium". Do we consider that a bug or a feature? :)

Good catch. I think it's a bug because it is not a shortcut/convenience. We could document it but that would just increase our docs to little end. So my gut feeling is we should make this an error, though my feelings aren't that strong. What do you think?

@tturocy

tturocy commented Aug 24, 2026

Copy link
Copy Markdown
Member

In thinking about the stopAfter checking more carefully, I realised that also you can write stopAfter=True and stopAfter=False in Python. stopAfter=True would be coerced into meaning "stop after one equilibrium". Do we consider that a bug or a feature? :)

Good catch. I think it's a bug because it is not a shortcut/convenience. We could document it but that would just increase our docs to little end. So my gut feeling is we should make this an error, though my feelings aren't that strong. What do you think?

Maybe stop_after=False "does the right thing" now but stop_after=True requires a bit of knowledge of a Python quirk. I agree let us not allow it, and insist on integers.

@rahulsavani

Copy link
Copy Markdown
Member

@copilot: Address the above comment. We want to ensure that in pygambit, stopAfter really has to be an integer and e.g. True and False will raise errors.

Co-authored-by: rahulsavani <743139+rahulsavani@users.noreply.github.com>

Copilot AI commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot: Address the above comment. We want to ensure that in pygambit, stopAfter really has to be an integer and e.g. True and False will raise errors.

Addressed in 4311745: pygambit now rejects boolean stop_after values in both enumpoly_solve() and lcp_solve(), and the regression tests now cover True, False, and 1.0 as invalid inputs.

@rahulsavani

Copy link
Copy Markdown
Member

@tturocy:
Any other changes you suggest?

@tturocy
tturocy merged commit c5c5095 into master Aug 25, 2026
26 checks passed
@tturocy
tturocy deleted the copilot/remove-stop-after-zero branch August 25, 2026 09:58
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.

stop_after = 0 in pygambit

4 participants