diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index d16229b5e..b5c56336f 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -157,10 +157,12 @@ wxString ExternalCommand(const NashComputationSpec &p_spec) return prefix + wxT("enummixed"); } else if constexpr (std::is_same_v) { - return prefix + - wxString::Format("enumpoly -d 10 -e %d -m %.17g", method.stopAfter, - method.maxRegret) + - strategic; + wxString command = wxString::Format("enumpoly -d 10 -m %.17g", method.maxRegret); + if (method.stopAfter.has_value()) { + command += wxString::Format(" -e %llu", + static_cast(method.stopAfter.value())); + } + return prefix + command + strategic; } else if constexpr (std::is_same_v) { return prefix + wxString::Format("gnm -d 10 -n %d -m %.17g -c %d -f %d -i %d", @@ -175,8 +177,12 @@ wxString ExternalCommand(const NashComputationSpec &p_spec) return prefix + wxT("lp") + strategic; } else if constexpr (std::is_same_v) { - return prefix + wxString::Format("lcp -e %d -r %d", method.stopAfter, method.maxDepth) + - strategic; + wxString command = wxString::Format("lcp -r %d", method.maxDepth); + if (method.stopAfter.has_value()) { + command += wxString::Format(" -e %llu", + static_cast(method.stopAfter.value())); + } + return prefix + command + strategic; } else if constexpr (std::is_same_v) { return prefix + wxString::Format("liap -d 10 -n %d -i %d -m %.17g", @@ -244,7 +250,7 @@ wxString ParameterDescription(const NashMethodSpec &p_method) return std::visit( [](const Method &method) { if constexpr (std::is_same_v) { - if (method.stopAfter == 1) { + if (method.stopAfter.has_value() && method.stopAfter.value() == 1) { return wxString::Format(" (stop after one equilibrium; maximum regret %.4g)", method.maxRegret); } diff --git a/src/gui/nashspec.h b/src/gui/nashspec.h index a9319b38c..50af9a87d 100644 --- a/src/gui/nashspec.h +++ b/src/gui/nashspec.h @@ -72,7 +72,7 @@ struct EnumMixedNashSpec { }; struct EnumPolyNashSpec { - int stopAfter{0}; + std::optional stopAfter{std::nullopt}; double maxRegret{1.0e-4}; std::optional MakeSolver(NashRepresentation p_representation) const; @@ -99,7 +99,7 @@ struct LPNashSpec { }; struct LCPNashSpec { - int stopAfter{0}; + std::optional stopAfter{std::nullopt}; int maxDepth{0}; std::optional MakeSolver(NashRepresentation p_representation) const; diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 29ece38f0..320ed09a3 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -554,7 +554,7 @@ cdef extern from "solvers/enummixed/enummixed.h": cdef extern from "solvers/lcp/lcp.h": stdlist[c_MixedStrategyProfile[T]] LcpStrategySolve[T]( - c_Game, int p_stopAfter, int p_maxDepth + c_Game, optional[size_t] p_stopAfter, int p_maxDepth ) except +RuntimeError stdlist[c_MixedBehaviorProfile[T]] LcpBehaviorSolve[T](c_Game) except +RuntimeError @@ -594,10 +594,10 @@ cdef extern from "solvers/nashsupport/nashsupport.h": cdef extern from "solvers/enumpoly/enumpoly.h": stdlist[c_MixedStrategyProfile[double]] EnumPolyStrategySolve( - c_Game, int, float, size_t + c_Game, optional[size_t], float, size_t ) except +RuntimeError stdlist[c_MixedBehaviorProfile[double]] EnumPolyBehaviorSolve( - c_Game, int, float, size_t + c_Game, optional[size_t], float, size_t ) except +RuntimeError cdef extern from "solvers/logit/logit.h": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 64fffa696..4a5a14d00 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -86,15 +86,21 @@ def _lcp_behavior_solve_rational( def _lcp_strategy_solve_double( - game: Game, stop_after: int, max_depth: int + game: Game, stop_after, max_depth: int ) -> list[MixedStrategyProfileDouble]: - return _convert_mspd(LcpStrategySolve[double](game.game, stop_after, max_depth)) + cdef optional[size_t] c_stop_after + if stop_after is not None: + c_stop_after = stop_after + return _convert_mspd(LcpStrategySolve[double](game.game, c_stop_after, max_depth)) def _lcp_strategy_solve_rational( - game: Game, stop_after: int, max_depth: int + game: Game, stop_after, max_depth: int ) -> list[MixedStrategyProfileRational]: - return _convert_mspr(LcpStrategySolve[c_Rational](game.game, stop_after, max_depth)) + cdef optional[size_t] c_stop_after + if stop_after is not None: + c_stop_after = stop_after + return _convert_mspr(LcpStrategySolve[c_Rational](game.game, c_stop_after, max_depth)) def _lp_behavior_solve_double(game: Game) -> list[MixedBehaviorProfileDouble]: @@ -178,20 +184,26 @@ def _nashsupport_strategy_solve( def _enumpoly_strategy_solve( game: Game, - stop_after: int, + stop_after, maxregret: float, max_rectangles: int, ) -> list[MixedStrategyProfileDouble]: - return _convert_mspd(EnumPolyStrategySolve(game.game, stop_after, maxregret, max_rectangles)) + cdef optional[size_t] c_stop_after + if stop_after is not None: + c_stop_after = stop_after + return _convert_mspd(EnumPolyStrategySolve(game.game, c_stop_after, maxregret, max_rectangles)) def _enumpoly_behavior_solve( game: Game, - stop_after: int, + stop_after, maxregret: float, max_rectangles: int, ) -> list[MixedBehaviorProfileDouble]: - return _convert_mbpd(EnumPolyBehaviorSolve(game.game, stop_after, maxregret, max_rectangles)) + cdef optional[size_t] c_stop_after + if stop_after is not None: + c_stop_after = stop_after + return _convert_mbpd(EnumPolyBehaviorSolve(game.game, c_stop_after, maxregret, max_rectangles)) def _logit_strategy_solve( diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 11639dad6..ad0bb12f4 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -27,6 +27,7 @@ from __future__ import annotations import dataclasses +import math import pathlib from collections.abc import Iterator @@ -242,15 +243,21 @@ def lcp_solve( raise ValueError( "lcp_solve(): max_depth can only be used on the strategic representation" ) - if stop_after is not None and stop_after < 0: + if stop_after is not None and stop_after <= 0: raise ValueError( - f"lcp_solve(): stop_after argument must be a non-negative number; got {stop_after}" + f"lcp_solve(): stop_after argument must be a positive integer; got {stop_after}" + ) + if stop_after is not None and ( + not math.isfinite(stop_after) or int(stop_after) != stop_after + ): + raise ValueError( + f"lcp_solve(): stop_after argument must be a positive integer; got {stop_after}" ) if not game.is_tree or use_strategic: if rational: - equilibria = libgbt._lcp_strategy_solve_rational(game, stop_after or 0, max_depth or 0) + equilibria = libgbt._lcp_strategy_solve_rational(game, stop_after, max_depth or 0) else: - equilibria = libgbt._lcp_strategy_solve_double(game, stop_after or 0, max_depth or 0) + equilibria = libgbt._lcp_strategy_solve_double(game, stop_after, max_depth or 0) elif rational: equilibria = libgbt._lcp_behavior_solve_rational(game) else: @@ -709,12 +716,17 @@ def enumpoly_solve( ----- PHCpack is available at https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html """ - if stop_after is None: - stop_after = 0 - elif stop_after < 0: + if stop_after is not None and stop_after <= 0: + raise ValueError( + f"enumpoly_solve(): " + f"stop_after argument must be a positive integer; got {stop_after}" + ) + if stop_after is not None and ( + not math.isfinite(stop_after) or int(stop_after) != stop_after + ): raise ValueError( f"enumpoly_solve(): " - f"stop_after argument must be a non-negative number; got {stop_after}" + f"stop_after argument must be a positive integer; got {stop_after}" ) if maxregret <= 0.0: raise ValueError( diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index b8b51645c..d78225f1f 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -183,8 +183,8 @@ FindNashExtension(const MixedBehaviorProfile &p_baseProfile, double p_ma std::list> SolveSupport(const BehaviorSupportProfile &p_support, bool &p_isSingular, bool &p_budgetExceeded, - int p_stopAfter, double p_maxRegret, - size_t p_maxRectangles, + std::optional p_stopAfter, + double p_maxRegret, size_t p_maxRectangles, const CancelToken &p_cancel) { ProblemData data(p_support); @@ -200,9 +200,9 @@ std::list> SolveSupport(const BehaviorSupportProfil PolynomialSystemSolver solver(equations); std::list> roots; try { - roots = solver.FindRoots({bottoms, tops}, - (p_stopAfter > 0) ? p_stopAfter : std::numeric_limits::max(), - p_maxRectangles, p_budgetExceeded, p_cancel); + roots = + solver.FindRoots({bottoms, tops}, p_stopAfter.value_or(std::numeric_limits::max()), + p_maxRectangles, p_budgetExceeded, p_cancel); } catch (const SingularMatrixException &) { p_isSingular = true; @@ -228,7 +228,7 @@ std::list> SolveSupport(const BehaviorSupportProfil namespace Gambit::Nash { std::list> -EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, +EnumPolyBehaviorSolve(const Game &p_game, std::optional p_stopAfter, double p_maxregret, size_t p_maxRectangles, BehaviorCallbackType p_onEquilibrium, EnumPolyEventCallbackType p_onEvent, const CancelToken &p_cancel) @@ -253,8 +253,11 @@ EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, bool budgetExceeded = false; for (const auto &solution : SolveSupport(support, isSingular, budgetExceeded, - std::max(p_stopAfter - static_cast(ret.size()), 0), p_maxregret, - p_maxRectangles, p_cancel)) { + p_stopAfter.has_value() + ? std::optional(p_stopAfter.value() - + std::min(ret.size(), p_stopAfter.value())) + : std::nullopt, + p_maxregret, p_maxRectangles, p_cancel)) { p_onEquilibrium(solution); ret.push_back(solution); } @@ -264,7 +267,7 @@ EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, if (budgetExceeded) { p_onEvent(EnumPolyBudgetExceededSupportEvent{support}); } - if (p_stopAfter > 0 && static_cast(ret.size()) >= p_stopAfter) { + if (p_stopAfter.has_value() && ret.size() >= p_stopAfter.value()) { break; } } diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index f148a1e3e..03f37781b 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -24,6 +24,7 @@ #ifndef GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H #define GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H +#include #include #include "solvers/nash.h" @@ -62,7 +63,7 @@ template void NullEnumPolyEventCallback(const EnumPolyEvent> -EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, +EnumPolyStrategySolve(const Game &p_game, std::optional p_stopAfter, double p_maxregret, size_t p_maxRectangles = kDefaultEnumPolyMaxRectangles, StrategyCallbackType p_onEquilibrium = NullStrategyCallback, EnumPolyEventCallbackType p_onEvent = @@ -70,7 +71,7 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, const CancelToken &p_cancel = CancelToken()); std::list> -EnumPolyBehaviorSolve(const Game &, int p_stopAfter, double p_maxregret, +EnumPolyBehaviorSolve(const Game &, std::optional p_stopAfter, double p_maxregret, size_t p_maxRectangles = kDefaultEnumPolyMaxRectangles, BehaviorCallbackType p_onEquilibrium = NullBehaviorCallback, EnumPolyEventCallbackType p_onEvent = diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index d257e23ed..6c9957439 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -103,8 +103,8 @@ namespace Gambit::Nash { std::list> EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, - bool &p_budgetExceeded, int p_stopAfter, size_t p_maxRectangles, - const CancelToken &p_cancel) + bool &p_budgetExceeded, std::optional p_stopAfter, + size_t p_maxRectangles, const CancelToken &p_cancel) { auto space = std::make_shared(support.MixedProfileLength() - support.GetGame()->NumPlayers()); @@ -119,9 +119,9 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin is_singular = false; std::list> roots; try { - roots = solver.FindRoots({bottoms, tops}, - (p_stopAfter > 0) ? p_stopAfter : std::numeric_limits::max(), - p_maxRectangles, p_budgetExceeded, p_cancel); + roots = + solver.FindRoots({bottoms, tops}, p_stopAfter.value_or(std::numeric_limits::max()), + p_maxRectangles, p_budgetExceeded, p_cancel); } catch (const SingularMatrixException &) { is_singular = true; @@ -142,7 +142,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin } std::list> -EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, +EnumPolyStrategySolve(const Game &p_game, std::optional p_stopAfter, double p_maxregret, size_t p_maxRectangles, StrategyCallbackType p_onEquilibrium, EnumPolyEventCallbackType p_onEvent, const CancelToken &p_cancel) @@ -167,7 +167,11 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, bool budget_exceeded; for (auto solution : EnumPolyStrategySupportSolve( support, is_singular, budget_exceeded, - std::max(p_stopAfter - static_cast(ret.size()), 0), p_maxRectangles, p_cancel)) { + p_stopAfter.has_value() + ? std::optional(p_stopAfter.value() - + std::min(ret.size(), p_stopAfter.value())) + : std::nullopt, + p_maxRectangles, p_cancel)) { const MixedStrategyProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); @@ -181,7 +185,7 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, if (budget_exceeded) { p_onEvent(EnumPolyBudgetExceededSupportEvent{support}); } - if (p_stopAfter > 0 && static_cast(ret.size()) >= p_stopAfter) { + if (p_stopAfter.has_value() && ret.size() >= p_stopAfter.value()) { break; } } diff --git a/src/solvers/enumpoly/polysolver.cc b/src/solvers/enumpoly/polysolver.cc index 69983f44a..8bd709ad5 100644 --- a/src/solvers/enumpoly/polysolver.cc +++ b/src/solvers/enumpoly/polysolver.cc @@ -181,7 +181,7 @@ Vector PolynomialSystemSolver::ImprovingNewtonStep(const Vector } std::list> PolynomialSystemSolver::FindRoots(const Rectangle &r, - const int max_roots, + const size_t max_roots, const size_t max_rectangles, bool &p_budgetExceeded, const CancelToken &p_cancel) diff --git a/src/solvers/enumpoly/polysolver.h b/src/solvers/enumpoly/polysolver.h index 7d1cda68b..1e427a795 100644 --- a/src/solvers/enumpoly/polysolver.h +++ b/src/solvers/enumpoly/polysolver.h @@ -105,7 +105,7 @@ class PolynomialSystemSolver { // Find up to `max_roots` roots inside rectangle `r`, examining at most // `max_rectangles` subdivisions. Sets `p_budgetExceeded` if that budget // runs out first, in which case the roots returned may be incomplete. - std::list> FindRoots(const Rectangle &r, int max_roots, + std::list> FindRoots(const Rectangle &r, size_t max_roots, size_t max_rectangles, bool &p_budgetExceeded, const CancelToken &p_cancel = CancelToken()); }; diff --git a/src/solvers/lcp/lcp.h b/src/solvers/lcp/lcp.h index 2e21b1c5a..328fc4a26 100644 --- a/src/solvers/lcp/lcp.h +++ b/src/solvers/lcp/lcp.h @@ -23,13 +23,15 @@ #ifndef GAMBIT_SOLVERS_LCP_LCP_H #define GAMBIT_SOLVERS_LCP_LCP_H +#include + #include "solvers/nash.h" namespace Gambit::Nash { template std::list> -LcpStrategySolve(const Game &p_game, int p_stopAfter, int p_maxDepth, +LcpStrategySolve(const Game &p_game, std::optional p_stopAfter, int p_maxDepth, StrategyCallbackType p_onEquilibrium = NullStrategyCallback, const CancelToken &p_cancel = CancelToken()); diff --git a/src/solvers/lcp/nfglcp.cc b/src/solvers/lcp/nfglcp.cc index c7887e424..874bd8150 100644 --- a/src/solvers/lcp/nfglcp.cc +++ b/src/solvers/lcp/nfglcp.cc @@ -113,7 +113,7 @@ template Vector Make_b2(const Game &p_game) template class NashLcpStrategySolver { public: - NashLcpStrategySolver(int p_stopAfter, int p_maxDepth, + NashLcpStrategySolver(std::optional p_stopAfter, int p_maxDepth, StrategyCallbackType p_onEquilibrium = NullStrategyCallback, const CancelToken &p_cancel = CancelToken()) : m_onEquilibrium(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth), @@ -128,7 +128,8 @@ template class NashLcpStrategySolver { enum class SearchResult { Continue, PruneBranch, LimitReached }; StrategyCallbackType m_onEquilibrium; - int m_stopAfter, m_maxDepth; + std::optional m_stopAfter; + int m_maxDepth; CancelToken m_cancel; class Solution; @@ -216,7 +217,8 @@ NashLcpStrategySolver::OnBFS(const Game &p_game, linalg::LHTableau &p_tabl m_onEquilibrium(profile); p_solution.m_equilibria.push_back(profile); - if (m_stopAfter > 0 && p_solution.EquilibriumCount() >= m_stopAfter) { + if (m_stopAfter.has_value() && + static_cast(p_solution.EquilibriumCount()) >= m_stopAfter.value()) { return SearchResult::LimitReached; } @@ -283,7 +285,7 @@ std::list> NashLcpStrategySolver::Solve(const Game &p const Vector b2 = Make_b2(p_game); linalg::LHTableau B(A1, A2, b1, b2); - if (m_stopAfter != 1) { + if (!m_stopAfter.has_value() || m_stopAfter.value() != 1) { AllLemke(p_game, 0, B, solution, 0); } else { @@ -295,16 +297,20 @@ std::list> NashLcpStrategySolver::Solve(const Game &p template std::list> -LcpStrategySolve(const Game &p_game, int p_stopAfter, int p_maxDepth, +LcpStrategySolve(const Game &p_game, std::optional p_stopAfter, int p_maxDepth, StrategyCallbackType p_onEquilibrium, const CancelToken &p_cancel) { return NashLcpStrategySolver(p_stopAfter, p_maxDepth, p_onEquilibrium, p_cancel) .Solve(p_game); } -template std::list> -LcpStrategySolve(const Game &, int, int, StrategyCallbackType, const CancelToken &); -template std::list> -LcpStrategySolve(const Game &, int, int, StrategyCallbackType, const CancelToken &); +template std::list> LcpStrategySolve(const Game &, + std::optional, int, + StrategyCallbackType, + const CancelToken &); +template std::list> LcpStrategySolve(const Game &, + std::optional, int, + StrategyCallbackType, + const CancelToken &); } // end namespace Gambit::Nash diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index e43e9d792..0539629ff 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -20,10 +20,13 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#include +#include #include #include #include #include +#include #include #include "games.h" #include "solvers/enumpoly/enumpoly.h" @@ -147,7 +150,7 @@ int main(int argc, char *argv[]) bool quiet = false; bool useStrategic = false; double maxregret = 1.0e-8; - int stopAfter = 0; + std::optional stopAfter; size_t maxRectangles = Nash::kDefaultEnumPolyMaxRectangles; int long_opt_index = 0; @@ -173,9 +176,19 @@ int main(int argc, char *argv[]) case 'm': maxregret = atof(optarg); break; - case 'e': - stopAfter = atoi(optarg); + case 'e': { + size_t parsed; + const auto *begin = optarg; + const auto *end = optarg + std::strlen(optarg); + const auto result = std::from_chars(begin, end, parsed); + 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); + } + stopAfter = parsed; break; + } case 'r': maxRectangles = std::strtoull(optarg, nullptr, 10); break; diff --git a/src/tools/lcp/lcp.cc b/src/tools/lcp/lcp.cc index 5671f603d..37de3ed05 100644 --- a/src/tools/lcp/lcp.cc +++ b/src/tools/lcp/lcp.cc @@ -20,8 +20,12 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#include +#include +#include #include #include +#include #include #include #include "games.h" @@ -70,7 +74,8 @@ int main(int argc, char *argv[]) int c; bool useFloat = false, useStrategic = false, quiet = false; bool printDetail = false; - int numDecimals = 6, stopAfter = 0, maxDepth = 0; + int numDecimals = 6, maxDepth = 0; + std::optional stopAfter; int long_opt_index = 0; option long_options[] = { @@ -87,9 +92,19 @@ int main(int argc, char *argv[]) case 'D': printDetail = true; break; - case 'e': - stopAfter = atoi(optarg); + case 'e': { + size_t parsed; + const auto *begin = optarg; + const auto *end = optarg + std::strlen(optarg); + const auto result = std::from_chars(begin, end, parsed); + 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); + } + stopAfter = parsed; break; + } case 'h': PrintHelp(argv[0]); break; diff --git a/tests/test_nash.py b/tests/test_nash.py index 607bdce1e..84ac275e0 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -3421,6 +3421,34 @@ def test_qre_solver(test_case: QREquilibriumTestCase, subtests) -> None: ################################################################################################## +def test_enumpoly_solve_error_with_invalid_stop_after(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.enumpoly_solve(game, stop_after=0) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.enumpoly_solve(game, stop_after=-1) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.enumpoly_solve(game, stop_after=0.5) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.enumpoly_solve(game, stop_after=1.2) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.enumpoly_solve(game, stop_after=float("inf")) + + +def test_lcp_solve_error_with_invalid_stop_after(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.lcp_solve(game, stop_after=0) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.lcp_solve(game, stop_after=-1) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.lcp_solve(game, stop_after=0.5) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.lcp_solve(game, stop_after=1.2) + with pytest.raises(ValueError, match="must be a positive integer"): + gbt.nash.lcp_solve(game, stop_after=float("inf")) + + def test_logit_solve_branch_error_with_invalid_maxregret(): game = games.read_from_file("const_sum_game.nfg") with pytest.raises(ValueError, match="must be positive"):