Skip to content

Commit

Permalink
Migrate agame and outcome to pure-python cython.
Browse files Browse the repository at this point in the history
  • Loading branch information
tturocy committed Sep 1, 2023
1 parent dcc665e commit 977e397
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 91 deletions.
100 changes: 47 additions & 53 deletions src/pygambit/core/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import pygambit.gte
import pygambit.gameiter


cdef class Outcomes(Collection):
@cython.cclass
class Outcomes(Collection):
"""Represents a collection of outcomes in a game."""
cdef c_Game game
game = cython.declare(c_Game)

def __len__(self):
"""The number of outcomes in the game."""
Expand All @@ -39,24 +40,26 @@ cdef class Outcomes(Collection):
def __getitem__(self, outc):
if not isinstance(outc, int):
return Collection.__getitem__(self, outc)
cdef Outcome c
c = cython.declare(Outcome)
c = Outcome()
c.outcome = self.game.deref().GetOutcome(outc+1)
return c

def add(self, label=""):
"""Add a new outcome to the game."""
cdef Outcome c
c = cython.declare(Outcome)
c = Outcome()
c.outcome = self.game.deref().NewOutcome()
if label != "": c.label = str(label)
if label != "":
c.label = str(label)
return c


cdef class Players(Collection):
@cython.cclass
class Players(Collection):
"""Represents a collection of players in a game."""
cdef c_Game game
cdef StrategicRestriction restriction
game = cython.declare(c_Game)
restriction = cython.declare(StrategicRestriction)

def __len__(self):
"""Returns the number of players in the game."""
Expand All @@ -65,7 +68,7 @@ cdef class Players(Collection):
def __getitem__(self, pl):
if not isinstance(pl, int):
return Collection.__getitem__(self, pl)
cdef Player p
p = cython.declare(Player)
p = Player()
p.player = self.game.deref().GetPlayer(pl+1)
if self.restriction is not None:
Expand All @@ -74,18 +77,19 @@ cdef class Players(Collection):

def add(self, label="") -> Player:
"""Adds a new player to the game."""
cdef Player p
if self.restriction is not None:
raise UndefinedOperationError("Changing objects in a restriction is not supported")
p = cython.declare(Player)
p = Player()
p.player = self.game.deref().NewPlayer()
if label != "": p.label = str(label)
if label != "":
p.label = str(label)
return p

@property
def chance(self) -> Player:
"""Returns the chance player associated with the game."""
cdef Player p
p = cython.declare(Player)
p = Player()
p.player = self.game.deref().GetChance()
p.restriction = self.restriction
Expand Down Expand Up @@ -170,7 +174,7 @@ cdef class Game:
Game
The newly-created extensive game.
"""
cdef Game g
g = cython.declare(Game)
g = cls()
g.game = NewTree()
g.title = title
Expand All @@ -196,7 +200,7 @@ cdef class Game:
Game
The newly-created strategic game.
"""
cdef Game g
g = cython.declare(Game)
cdef Array[int] *d
d = new Array[int](len(dim))
for i in range(1, len(dim)+1):
Expand Down Expand Up @@ -232,7 +236,7 @@ cdef class Game:
Game
The newly-created strategic game.
"""
cdef Game g
g = cython.declare(Game)
arrays = [np.array(a) for a in arrays]
if len(set(a.shape for a in arrays)) > 1:
raise ValueError("All specified arrays must have the same shape")
Expand Down Expand Up @@ -268,7 +272,7 @@ cdef class Game:
Game
The newly-created strategic game.
"""
cdef Game g
g = cython.declare(Game)
payoffs = {k: np.array(v) for k, v in payoffs.items()}
if len(set(a.shape for a in payoffs.values())) > 1:
raise ValueError("All specified arrays must have the same shape")
Expand Down Expand Up @@ -310,7 +314,7 @@ cdef class Game:
--------
parse_game : Constructs a game from a text string.
"""
cdef Game g
g = cython.declare(Game)
g = cls()
with open(filepath, "rb") as f:
data = f.read()
Expand Down Expand Up @@ -343,7 +347,7 @@ cdef class Game:
--------
read_game : Constructs a game from a representation in a file.
"""
cdef Game g
g = cython.declare(Game)
g = cls()
try:
g.game = ParseGame(text.encode('ascii'))
Expand All @@ -363,21 +367,15 @@ cdef class Game:
else:
return self.write('html')

def __richcmp__(self, other, whichop) -> bool:
def __eq__(self, other: typing.Any) -> bool:
if isinstance(other, Game):
if whichop == 2:
return self.game.deref() == (<Game>other).game.deref()
elif whichop == 3:
return self.game.deref() != (<Game>other).game.deref()
else:
raise NotImplementedError
else:
if whichop == 2:
return False
elif whichop == 3:
return True
else:
raise NotImplementedError
return self.game.deref() == (<Game> other).game.deref()
return False

def __ne__(self, other: typing.Any) -> bool:
if isinstance(other, Game):
return self.game.deref() != (<Game> other).game.deref()
return True

def __hash__(self):
return long(<long>self.game.deref())
Expand Down Expand Up @@ -416,14 +414,12 @@ cdef class Game:
UndefinedOperationError
If the game does not have a tree representation.
"""
cdef GameActions a
if self.is_tree:
a = GameActions()
a.game = self.game
return a
raise UndefinedOperationError(
"Operation only defined for games with a tree representation"
)
if not self.is_tree:
raise UndefinedOperationError("Operation only defined for games with a tree representation")
a = cython.declare(GameActions)
a = GameActions()
a.game = self.game
return a

@property
def infosets(self) -> GameInfosets:
Expand All @@ -434,35 +430,33 @@ cdef class Game:
UndefinedOperationError
If the game does not have a tree representation.
"""
cdef GameInfosets i
if self.is_tree:
i = GameInfosets()
i.game = self.game
return i
raise UndefinedOperationError(
"Operation only defined for games with a tree representation"
)
if not self.is_tree:
raise UndefinedOperationError("Operation only defined for games with a tree representation")
i = cython.declare(GameInfosets)
i = GameInfosets()
i.game = self.game
return i

@property
def players(self) -> Players:
"""Return the set of players in the game."""
cdef Players p
p = cython.declare(Players)
p = Players()
p.game = self.game
return p

@property
def strategies(self) -> GameStrategies:
"""Return the set of strategies in the game."""
cdef GameStrategies s
s = cython.declare(GameStrategies)
s = GameStrategies()
s.game = self.game
return s

@property
def outcomes(self) -> Outcomes:
"""Return the set of outcomes in the game."""
cdef Outcomes c
c = cython.declare(Outcomes)
c = Outcomes()
c.game = self.game
return c
Expand Down Expand Up @@ -577,8 +571,8 @@ cdef class Game:
----------
data
A nested list (or compatible type) with the
same dimension as the strategy set of the game,
specifying the probabilities of the strategies.
same dimension as the strategy set of the game,
specifying the probabilities of the strategies.

rational
If `True`, probabilities are represented using rational numbers;
Expand Down
55 changes: 18 additions & 37 deletions src/pygambit/core/outcome.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,23 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
cdef class Outcome:
@cython.cclass
class Outcome:
"""An outcome in a `Game`."""
cdef c_GameOutcome outcome
cdef StrategicRestriction restriction
outcome = cython.declare(c_GameOutcome)
restriction = cython.declare(StrategicRestriction)

def __repr__(self):
return (
f"<Outcome [{self.outcome.deref().GetNumber()-1}] '{self.label}' "
f"in game '{self.game.title}'>"
)

def __richcmp__(self, other, whichop) -> bool:
if isinstance(other, Outcome):
if whichop == 2:
return self.outcome.deref() == (<Outcome>other).outcome.deref()
elif whichop == 3:
return self.outcome.deref() != (<Outcome>other).outcome.deref()
else:
raise NotImplementedError
else:
if whichop == 2:
return False
elif whichop == 3:
return True
else:
raise NotImplementedError
def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, Outcome) and self.outcome.deref() == (<Outcome> other).outcome.deref()

def __ne__(self, other: typing.Any) -> bool:
return not isinstance(other, Outcome) or self.outcome.deref() != (<Outcome> other).outcome.deref()

def __hash__(self):
return long(<long>self.outcome.deref())
Expand Down Expand Up @@ -89,18 +80,18 @@ cdef class Outcome:
as its label. If an integer, returns the payoff to player
number ``player``.
"""
cdef bytes py_string
py_string = cython.declare(bytes)
if isinstance(player, Player):
py_string = self.outcome.deref().GetPayoff(player.number+1).as_string().c_str()
elif isinstance(player, str):
number = self.game.players[player].number
py_string = self.outcome.deref().GetPayoff(number+1).as_string().c_str()
elif isinstance(player, int):
py_string = self.outcome.deref().GetPayoff(player+1).as_string().c_str()
if "." in py_string.decode('ascii'):
return decimal.Decimal(py_string.decode('ascii'))
if "." in py_string.decode(b'ascii'):
return decimal.Decimal(py_string.decode(b'ascii'))
else:
return Rational(py_string.decode('ascii'))
return Rational(py_string.decode(b'ascii'))

def __setitem__(self, pl: int, value: typing.Any) -> None:
"""
Expand Down Expand Up @@ -152,21 +143,11 @@ cdef class TreeGameOutcome:
def __repr__(self):
return f"<Outcome '{self.label}' in game '{self.game.title}'>"

def __richcmp__(TreeGameOutcome self, other, whichop):
if isinstance(other, TreeGameOutcome):
if whichop == 2:
return self.psp.deref() == (<TreeGameOutcome>other).psp.deref()
elif whichop == 3:
return self.psp.deref() != (<TreeGameOutcome>other).psp.deref()
else:
raise NotImplementedError
else:
if whichop == 2:
return False
elif whichop == 3:
return True
else:
raise NotImplementedError
def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, TreeGameOutcome) and self.psp.deref() == (<TreeGameOutcome> other).psp.deref()

def __ne__(self, other: typing.Any) -> bool:
return not isinstance(other, TreeGameOutcome) or self.psp.deref() != (<TreeGameOutcome> other).psp.deref()

def __getitem__(self, player):
if isinstance(player, Player):
Expand Down
2 changes: 1 addition & 1 deletion src/pygambit/gambit.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ cdef extern from "util.h":
c_MixedBehaviorProfileRational *copyitem_list_mbpr "copyitem"(c_List[c_MixedBehaviorProfileRational], int)


cdef class Collection(object):
cdef class Collection:
"Represents a collection of related objects in a game."
def __repr__(self): return str(list(self))

Expand Down

0 comments on commit 977e397

Please sign in to comment.