Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
sage.game_theory: Switch to new lrsnash input format
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthias Koeppe committed Jul 13, 2021
1 parent f6b4d50 commit d77acee
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 24 deletions.
98 changes: 88 additions & 10 deletions src/sage/game_theory/normal_form_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@
sage: A = matrix([[3,3],[2,5],[0,6]])
sage: B = matrix([[3,3],[2,6],[3,1]])
sage: degenerate_game = NormalFormGame([A,B])
sage: degenerate_game.obtain_nash(algorithm='lrs') # optional - lrslib
sage: degenerate_game.obtain_nash(algorithm='lrs') # random, optional - lrslib
[[(0, 1/3, 2/3), (1/3, 2/3)], [(1, 0, 0), (1/2, 3)], [(1, 0, 0), (1, 3)]]
sage: degenerate_game.obtain_nash(algorithm='LCP') # optional - gambit
[[(0.0, 0.3333333333, 0.6666666667), (0.3333333333, 0.6666666667)],
Expand Down Expand Up @@ -1752,17 +1752,14 @@ def _solve_lrs(self, maximization=True):
if maximization is False:
m1 = - m1
m2 = - m2
game1_str, game2_str = self._Hrepresentation(m1, m2)

g1_name = tmp_filename()
with open(g1_name, 'w') as g1_file:
g1_file.write(game1_str)
g2_name = tmp_filename()
with open(g2_name, 'w') as g2_file:
g2_file.write(game2_str)
game_str = self._lrs_nash_format(m1, m2)
game_name = tmp_filename()
with open(game_name, 'w') as game_file:
game_file.write(game_str)

try:
process = Popen(['lrsnash', g1_name, g2_name],
process = Popen(['lrsnash', game_name],
stdout=PIPE,
stderr=PIPE)
except OSError as e:
Expand Down Expand Up @@ -2243,14 +2240,22 @@ def _is_NE(self, a, b, p1_support, p2_support, M1, M2):

def _Hrepresentation(self, m1, m2):
r"""
Create the H-representation strings required to use lrs nash.
Create the H-representation strings required to use ``lrsnash``.
Since lrslib 6.1, this format is referred to as "legacy format".
This method is deprecated.
EXAMPLES::
sage: A = matrix([[1, 2], [3, 4]])
sage: B = matrix([[3, 3], [1, 4]])
sage: C = NormalFormGame([A, B])
sage: print(C._Hrepresentation(A, B)[0])
doctest:warning...
DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it
creates the legacy input format. Use NormalFormGame._lrs_nash_format instead
See https://trac.sagemath.org/27745 for details.
H-representation
linearity 1 5
begin
Expand All @@ -2276,6 +2281,12 @@ def _Hrepresentation(self, m1, m2):
<BLANKLINE>
"""
from sage.misc.superseded import deprecation
deprecation(27745,
"NormalFormGame._Hrepresentation is deprecated as it "
"creates the legacy input format. "
"Use NormalFormGame._lrs_nash_format instead")

from sage.geometry.polyhedron.misc import _to_space_separated_string
m = self.players[0].num_strategies
n = self.players[1].num_strategies
Expand Down Expand Up @@ -2311,6 +2322,73 @@ def _Hrepresentation(self, m1, m2):
t += 'end\n'
return s, t

def _lrs_nash_format(self, m1, m2):
"""
Create the input format for ``lrsnash``, version 6.1 or newer.
EXAMPLES:
An example from the ``lrsnash`` manual in the old and the format::
sage: A = matrix([[0, 6], [2, 5], [3, 3]])
sage: B = matrix([[1, 0], [0, 2], [4, 3]])
sage: C = NormalFormGame([A, B])
sage: print(C._lrs_nash_format(A, B))
3 2
<BLANKLINE>
0 6
2 5
3 3
<BLANKLINE>
1 0
0 2
4 3
<BLANKLINE>
sage: legacy_format = C._Hrepresentation(A, B)
doctest:warning...
DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it
creates the legacy input format. Use NormalFormGame._lrs_nash_format instead
See https://trac.sagemath.org/27745 for details.
sage: print('*game: player 1\n', legacy_format[0])
*game: player 1
H-representation
linearity 1 6
begin
6 5 rational
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 -1 0 -4 1
0 0 -2 -3 1
-1 1 1 1 0
end
<BLANKLINE>
sage: print('*game: player 2\n', legacy_format[1])
*game: player 2
H-representation
linearity 1 6
begin
6 4 rational
0 0 -6 1
0 -2 -5 1
0 -3 -3 1
0 1 0 0
0 0 1 0
-1 1 1 0
end
"""
from sage.geometry.polyhedron.misc import _to_space_separated_string
m = self.players[0].num_strategies
n = self.players[1].num_strategies
s = f'{m} {n}\n\n'
for r in m1.rows():
s += _to_space_separated_string(r) + '\n'
s += '\n'
for r in m2.rows():
s += _to_space_separated_string(r) + '\n'
return s

def is_degenerate(self, certificate=False):
"""
A function to check whether the game is degenerate or not.
Expand Down
41 changes: 27 additions & 14 deletions src/sage/game_theory/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def __init__(self, raw_string):
sage: A = matrix([[1, 2], [3, 2]])
sage: g = NormalFormGame([A])
sage: raw_string = g._Hrepresentation(A, -A)
doctest:warning...
DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it
creates the legacy input format. Use NormalFormGame._lrs_nash_format instead
See https://trac.sagemath.org/27745 for details.
sage: P = Parser(raw_string)
sage: print(P.raw_string[0])
H-representation
Expand Down Expand Up @@ -127,7 +131,7 @@ def __init__(self, raw_string):
"""
self.raw_string = raw_string

def format_lrs(self):
def format_lrs(self, legacy_format=False):
"""
Parses the output of lrs so as to return vectors
corresponding to equilibria.
Expand All @@ -140,6 +144,10 @@ def format_lrs(self):
sage: A = matrix([[1, 2], [3, 2]])
sage: g = NormalFormGame([A])
sage: game1_str, game2_str = g._Hrepresentation(A, -A)
doctest:warning...
DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it
creates the legacy input format. Use NormalFormGame._lrs_nash_format instead
See https://trac.sagemath.org/27745 for details.
sage: g1_name = tmp_filename()
sage: g2_name = tmp_filename()
sage: g1_file = open(g1_name, 'w')
Expand All @@ -148,14 +156,14 @@ def format_lrs(self):
sage: g1_file.close()
sage: _ = g2_file.write(game2_str)
sage: g2_file.close()
sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # optional - lrslib
sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib
sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # not tested, optional - lrslib
sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # not tested, optional - lrslib
The above creates a game, writes the H representation to
temporary files, calls lrs and stores the output in `lrs_output`
(here slicing to get rid of some system parameters that get returned)::
sage: lrs_output[:20] # optional - lrslib
sage: lrs_output[:20] # not tested, optional - lrslib
[...,
'***** 4 4 rational\n',
'2 0 1 2 \n',
Expand All @@ -172,8 +180,8 @@ def format_lrs(self):
The above is pretty messy, here is the output when we put it through
the parser::
sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib
sage: nasheq # optional - lrslib
sage: nasheq = Parser(lrs_output).format_lrs(legacy_format=True) # not tested, optional - lrslib
sage: nasheq # not tested, optional - lrslib
[[(1/2, 1/2), (0, 1)], [(0, 1), (0, 1)]]
Another game::
Expand All @@ -194,9 +202,9 @@ def format_lrs(self):
sage: g1_file.close()
sage: _ = g2_file.write(game2_str)
sage: g2_file.close()
sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # optional - lrslib
sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib
sage: print(lrs_output[:25]) # optional - lrslib
sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # not tested, optional - lrslib
sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # not tested, optional - lrslib
sage: print(lrs_output[:25]) # not tested, optional - lrslib
[...,
'***** 5 5 rational\n',
'2 1/7 0 6/7 23/7 \n',
Expand All @@ -214,19 +222,24 @@ def format_lrs(self):
'*Player 1: vertices=6 bases=7 pivots=10\n',
...]
sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib
sage: sorted(nasheq) # optional - lrslib
sage: nasheq = Parser(lrs_output).format_lrs(legacy_format=True) # not tested, optional - lrslib
sage: sorted(nasheq) # not tested, optional - lrslib
[[(0, 1, 0), (1, 0, 0)],
[(1/3, 2/3, 0), (0, 1/6, 5/6)],
[(1/3, 2/3, 0), (1/7, 0, 6/7)],
[(1, 0, 0), (0, 0, 1)]]
"""
equilibria = []
from sage.misc.sage_eval import sage_eval
from itertools import groupby
from itertools import groupby, dropwhile
lines = iter(self.raw_string)
while not next(lines).startswith("*****"):
pass
if legacy_format:
# Skip until the magic stars announce the beginning of the real output
while not next(lines).startswith("*****"):
pass
else:
# Skip comment lines starting with a single star
lines = dropwhile(lambda line: line.startswith('*'), lines)
for collection in [list(x[1]) for x in groupby(lines, lambda x: x == '\n')]:
if collection[0].startswith('2'):
s1 = tuple([sage_eval(k) for k in collection[-1].split()][1:-1])
Expand Down

0 comments on commit d77acee

Please sign in to comment.