Skip to content

Commit 7df3d34

Browse files
d-kadtturocy
authored andcommitted
Implement plays consistent with a node, information set, or action
This provides computation of the set of plays (terminal nodes) consistent with passing through a specified node, information set, or action: * `GameRep.GetPlays()` in C++ * Properties `Node.plays`, `Infoset.plays`, `Action,plays` in Python. Closes #517.
1 parent 229f495 commit 7df3d34

13 files changed

Lines changed: 157 additions & 1 deletion

File tree

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
- The deprecated functions `Game.read_game`, `Game.parse_game` and `Game.write` functions have
1010
been removed as planned. (#357)
1111

12+
### Added
13+
- Implement `GetPlays()` (C++) and `get_plays` (Python) to compute the set of terminal nodes consistent
14+
with a node, information set, or action (#517)
15+
1216

1317
## [16.3.1] - unreleased
1418

doc/pygambit.api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ Information about the game
145145
Node.infoset
146146
Node.player
147147
Node.is_successor_of
148+
Node.plays
148149

149150
.. autosummary::
150151

@@ -157,6 +158,7 @@ Information about the game
157158
Infoset.actions
158159
Infoset.members
159160
Infoset.precedes
161+
Infoset.plays
160162

161163
.. autosummary::
162164

@@ -166,6 +168,7 @@ Information about the game
166168
Action.infoset
167169
Action.precedes
168170
Action.prob
171+
Action.plays
169172

170173
.. autosummary::
171174

src/games/game.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,13 @@ class GameRep : public BaseGameRep {
551551
/// Returns the largest payoff to the player in any outcome of the game
552552
virtual Rational GetMaxPayoff(const GamePlayer &p_player) const = 0;
553553

554+
/// Returns the set of terminal nodes which are descendants of node
555+
virtual std::vector<GameNode> GetPlays(GameNode node) const { throw UndefinedException(); }
556+
/// Returns the set of terminal nodes which are descendants of members of an infoset
557+
virtual std::vector<GameNode> GetPlays(GameInfoset infoset) const { throw UndefinedException(); }
558+
/// Returns the set of terminal nodes which are descendants of members of an action
559+
virtual std::vector<GameNode> GetPlays(GameAction action) const { throw UndefinedException(); }
560+
554561
/// Returns true if the game is perfect recall. If not,
555562
/// a pair of violating information sets is returned in the parameters.
556563
virtual bool IsPerfectRecall(GameInfoset &, GameInfoset &) const = 0;

src/games/gametree.cc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ void GameTreeRep::ClearComputedValues() const
854854
}
855855
player->m_strategies.clear();
856856
}
857+
const_cast<GameTreeRep *>(this)->m_nodePlays.clear();
857858
m_computedValues = false;
858859
}
859860

@@ -869,6 +870,29 @@ void GameTreeRep::BuildComputedValues() const
869870
m_computedValues = true;
870871
}
871872

873+
void GameTreeRep::BuildConsistentPlays()
874+
{
875+
m_nodePlays.clear();
876+
BuildConsistentPlaysRecursiveImpl(m_root);
877+
}
878+
879+
std::vector<GameNodeRep *> GameTreeRep::BuildConsistentPlaysRecursiveImpl(GameNodeRep *node)
880+
{
881+
std::vector<GameNodeRep *> consistent_plays;
882+
if (node->IsTerminal()) {
883+
consistent_plays = std::vector<GameNodeRep *>{node};
884+
}
885+
else {
886+
for (GameNodeRep *child : node->GetChildren()) {
887+
auto child_consistent_plays = BuildConsistentPlaysRecursiveImpl(child);
888+
consistent_plays.insert(consistent_plays.end(), child_consistent_plays.begin(),
889+
child_consistent_plays.end());
890+
}
891+
}
892+
m_nodePlays[node] = consistent_plays;
893+
return consistent_plays;
894+
}
895+
872896
//------------------------------------------------------------------------
873897
// GameTreeRep: Writing data files
874898
//------------------------------------------------------------------------
@@ -997,6 +1021,43 @@ std::vector<GameInfoset> GameTreeRep::GetInfosets() const
9971021
// GameTreeRep: Outcomes
9981022
//------------------------------------------------------------------------
9991023

1024+
std::vector<GameNode> GameTreeRep::GetPlays(GameNode node) const
1025+
{
1026+
const_cast<GameTreeRep *>(this)->BuildConsistentPlays();
1027+
1028+
const std::vector<GameNodeRep *> &consistent_plays = m_nodePlays.at(node);
1029+
std::vector<GameNode> consistent_plays_copy;
1030+
consistent_plays_copy.reserve(consistent_plays.size());
1031+
1032+
std::transform(consistent_plays.cbegin(), consistent_plays.cend(),
1033+
std::back_inserter(consistent_plays_copy),
1034+
[](GameNodeRep *rep_ptr) -> GameNode { return {rep_ptr}; });
1035+
1036+
return consistent_plays_copy;
1037+
}
1038+
1039+
std::vector<GameNode> GameTreeRep::GetPlays(GameInfoset infoset) const
1040+
{
1041+
std::vector<GameNode> plays;
1042+
1043+
for (const auto &node : infoset->GetMembers()) {
1044+
std::vector<GameNode> member_plays = GetPlays(node);
1045+
plays.insert(plays.end(), member_plays.begin(), member_plays.end());
1046+
}
1047+
return plays;
1048+
}
1049+
1050+
std::vector<GameNode> GameTreeRep::GetPlays(GameAction action) const
1051+
{
1052+
std::vector<GameNode> plays;
1053+
1054+
for (const auto &node : action->GetInfoset()->GetMembers()) {
1055+
std::vector<GameNode> child_plays = GetPlays(node->GetChild(action));
1056+
plays.insert(plays.end(), child_plays.begin(), child_plays.end());
1057+
}
1058+
return plays;
1059+
}
1060+
10001061
void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome)
10011062
{
10021063
IncrementVersion();

src/games/gametree.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class GameTreeRep : public GameExplicitRep {
3838
GamePlayerRep *m_chance;
3939
std::size_t m_numNodes = 1;
4040
std::size_t m_numNonterminalNodes = 0;
41+
std::map<GameNodeRep *, std::vector<GameNodeRep *>> m_nodePlays;
4142

4243
/// @name Private auxiliary functions
4344
//@{
@@ -50,6 +51,7 @@ class GameTreeRep : public GameExplicitRep {
5051
//@{
5152
void Canonicalize();
5253
void BuildComputedValues() const override;
54+
void BuildConsistentPlays();
5355
void ClearComputedValues() const;
5456

5557
/// Removes the node from the information set, invalidating if emptied
@@ -141,6 +143,10 @@ class GameTreeRep : public GameExplicitRep {
141143
void DeleteAction(GameAction) override;
142144
void SetOutcome(GameNode, const GameOutcome &p_outcome) override;
143145

146+
std::vector<GameNode> GetPlays(GameNode node) const override;
147+
std::vector<GameNode> GetPlays(GameInfoset infoset) const override;
148+
std::vector<GameNode> GetPlays(GameAction action) const override;
149+
144150
Game CopySubgame(GameNode) const override;
145151
//@}
146152

@@ -151,6 +157,9 @@ class GameTreeRep : public GameExplicitRep {
151157
NewMixedStrategyProfile(double, const StrategySupportProfile &) const override;
152158
MixedStrategyProfile<Rational>
153159
NewMixedStrategyProfile(const Rational &, const StrategySupportProfile &) const override;
160+
161+
private:
162+
std::vector<GameNodeRep *> BuildConsistentPlaysRecursiveImpl(GameNodeRep *node);
154163
};
155164

156165
template <class T> class TreeMixedStrategyProfileRep : public MixedStrategyProfileRep<T> {

src/pygambit/action.pxi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,12 @@ class Action:
106106
return decimal.Decimal(py_string.decode("ascii"))
107107
else:
108108
return Rational(py_string.decode("ascii"))
109+
110+
@property
111+
def plays(self) -> typing.List[Node]:
112+
"""Returns a list of all terminal `Node` objects consistent with it.
113+
"""
114+
return [
115+
Node.wrap(n) for n in
116+
self.action.deref().GetInfoset().deref().GetGame().deref().GetPlays(self.action)
117+
]

src/pygambit/gambit.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ cdef extern from "games/game.h":
279279
c_Rational GetMinPayoff(c_GamePlayer) except +
280280
c_Rational GetMaxPayoff() except +
281281
c_Rational GetMaxPayoff(c_GamePlayer) except +
282+
stdvector[c_GameNode] GetPlays(c_GameNode) except +
283+
stdvector[c_GameNode] GetPlays(c_GameInfoset) except +
284+
stdvector[c_GameNode] GetPlays(c_GameAction) except +
282285
bool IsPerfectRecall() except +
283286

284287
c_GameInfoset AppendMove(c_GameNode, c_GamePlayer, int) except +ValueError

src/pygambit/game.pxi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import io
2323
import itertools
2424
import pathlib
25-
import warnings
2625

2726
import numpy as np
2827
import scipy.stats

src/pygambit/infoset.pxi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,11 @@ class Infoset:
175175
def player(self) -> Player:
176176
"""The player who has the move at this information set."""
177177
return Player.wrap(self.infoset.deref().GetPlayer())
178+
179+
@property
180+
def plays(self) -> typing.List[Node]:
181+
"""Returns a list of all terminal `Node` objects consistent with it.
182+
"""
183+
return [
184+
Node.wrap(n) for n in self.infoset.deref().GetGame().deref().GetPlays(self.infoset)
185+
]

src/pygambit/node.pxi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,9 @@ class Node:
207207
if self.node.deref().GetOutcome() == cython.cast(c_GameOutcome, NULL):
208208
return None
209209
return Outcome.wrap(self.node.deref().GetOutcome())
210+
211+
@property
212+
def plays(self) -> typing.List[Node]:
213+
"""Returns a list of all terminal `Node` objects consistent with it.
214+
"""
215+
return [Node.wrap(n) for n in self.node.deref().GetGame().deref().GetPlays(self.node)]

0 commit comments

Comments
 (0)