diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 57e9bc0ef..79e3accdf 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -30,7 +30,7 @@ import pygambit.gambit as libgbt -from . import nashphc +from . import nashlrs, nashphc MixedStrategyEquilibriumSet = list[libgbt.MixedStrategyProfile] MixedBehaviorEquilibriumSet = list[libgbt.MixedBehaviorProfile] @@ -103,7 +103,7 @@ def enumpure_solve(game: libgbt.Game, use_strategic: bool = True) -> NashComputa def enummixed_solve( game: libgbt.Game, rational: bool = True, - use_lrs: bool = False + lrsnash_path: pathlib.Path | str | None = None, ) -> NashComputationResult: """Compute all :ref:`mixed-strategy Nash equilibria ` of a two-player game using the strategic representation. @@ -112,11 +112,23 @@ def enummixed_solve( ---------- game : Game The game to compute equilibria in. + rational : bool, default True Compute using rational numbers. If `False`, using floating-point arithmetic. Using rationals is more precise, but slower. - use_lrs : bool, default False - If `True`, use the implementation based on ``lrslib``. This is experimental. + + lrsnash_path : pathlib.Path | str | None = None, + If specified, use lrsnash [1]_ to solve the systems of equations. + This argument specifies the path to the lrsnash executable. + + .. versionadded:: 16.3.0 + + Returns + ------- + res : NashComputationResult + The result represented as a ``NashComputationResult`` object. + + .. [1] http://cgm.cs.mcgill.ca/~avis/C/lrs.html Returns ------- @@ -128,14 +140,24 @@ def enummixed_solve( RuntimeError If game has more than two players. """ - if use_lrs or rational: + if lrsnash_path is not None: + equilibria = nashlrs.lrsnash_solve(game, lrsnash_path=lrsnash_path) + return NashComputationResult( + game=game, + method="enummixed", + rational=True, + use_strategic=True, + parameters={"lrsnash_path": lrsnash_path}, + equilibria=equilibria, + ) + if rational: equilibria = libgbt._enummixed_strategy_solve_rational(game) else: equilibria = libgbt._enummixed_strategy_solve_double(game) return NashComputationResult( game=game, method="enummixed", - rational=use_lrs or rational, + rational=rational, use_strategic=True, equilibria=equilibria ) diff --git a/src/pygambit/nashlrs.py b/src/pygambit/nashlrs.py index 31bcfe1b5..7610f7fb8 100644 --- a/src/pygambit/nashlrs.py +++ b/src/pygambit/nashlrs.py @@ -2,6 +2,7 @@ """ import itertools +import pathlib import subprocess import sys @@ -33,9 +34,12 @@ def _parse_lrs_output(game: gbt.Game, txt: str) -> list[gbt.MixedStrategyProfile return eqa -def lrsnash_solve(game: gbt.Game) -> list[gbt.MixedStrategyProfileRational]: +def lrsnash_solve(game: gbt.Game, + lrsnash_path: pathlib.Path | str) -> list[gbt.MixedStrategyProfileRational]: + if len(game.players) != 2: + raise RuntimeError("Method only valid for two-player games.") with util.make_temporary(_generate_lrs_input(game)) as infn: - result = subprocess.run(["./lrsnash", infn], encoding="utf-8", + result = subprocess.run([lrsnash_path, infn], encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: raise ValueError(f"PHC run failed with return code {result.returncode}") @@ -44,7 +48,7 @@ def lrsnash_solve(game: gbt.Game) -> list[gbt.MixedStrategyProfileRational]: def main(): game = gbt.Game.parse_game(sys.stdin.read()) - eqa = lrsnash_solve(game) + eqa = lrsnash_solve(game, "./lrsnash") for eqm in eqa: print("NE," + ",".join(str(eqm[strat]) for player in game.players for strat in player.strategies))