From 980ebfbb0e64b579d37ea1baa11a99149feda0c0 Mon Sep 17 00:00:00 2001 From: drdkad Date: Thu, 22 May 2025 11:14:09 +0100 Subject: [PATCH 1/5] Add GameTreeRep::GetPower --- src/games/game.h | 3 +++ src/games/gametree.cc | 19 +++++++++++++++++++ src/games/gametree.h | 2 ++ 3 files changed, 24 insertions(+) diff --git a/src/games/game.h b/src/games/game.h index cd17b2789..68399b631 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -558,6 +558,9 @@ class GameRep : public BaseGameRep { /// Returns the set of terminal nodes which are descendants of members of an action virtual std::vector GetPlays(GameAction action) const { throw UndefinedException(); } + /// Returns the power of an action + virtual std::vector GetPower(GameAction action) const { throw UndefinedException(); } + /// Returns true if the game is perfect recall. If not, /// a pair of violating information sets is returned in the parameters. virtual bool IsPerfectRecall(GameInfoset &, GameInfoset &) const = 0; diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 7bb28e3f8..2d2f85851 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include "gambit.h" #include "gametree.h" @@ -1058,6 +1059,24 @@ std::vector GameTreeRep::GetPlays(GameAction action) const return plays; } +std::vector GameTreeRep::GetPower(GameAction action) const +{ + std::vector power; + + const std::vector plays = GetPlays(GetRoot()); + const std::vector aplays = GetPlays(action); + const std::vector iplays = GetPlays(action->GetInfoset()); + + for (const auto &node : plays) { + if ((std::find(iplays.cbegin(), iplays.cend(), node) == iplays.cend()) || + (std::find(aplays.cbegin(), aplays.cend(), node) != aplays.cend())) { + power.push_back(node); + } + } + + return power; +} + void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome) { IncrementVersion(); diff --git a/src/games/gametree.h b/src/games/gametree.h index f6c3f04b7..05a4ec988 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -147,6 +147,8 @@ class GameTreeRep : public GameExplicitRep { std::vector GetPlays(GameInfoset infoset) const override; std::vector GetPlays(GameAction action) const override; + std::vector GetPower(GameAction action) const override; + Game CopySubgame(GameNode) const override; //@} From 55dca4ad396413eef2ecc33c5e11d202a2791524 Mon Sep 17 00:00:00 2001 From: drdkad Date: Thu, 22 May 2025 11:14:50 +0100 Subject: [PATCH 2/5] Add Action.power and a test; update documentation --- ChangeLog | 7 +++++-- doc/pygambit.api.rst | 1 + src/pygambit/action.pxi | 11 ++++++++++- src/pygambit/gambit.pxd | 1 + tests/test_actions.py | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index f49d13a9f..d2b1b10f7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,8 +10,11 @@ been removed as planned. (#357) ### Added -- Implement `GetPlays()` (C++) and `get_plays` (Python) to compute the set of terminal nodes consistent - with a node, information set, or action (#517) +- Implement `GameRep::GetPlays` (C++) and `Node.plays`, `Infoset.plays`, and `Action.plays` (Python) + to compute the set of terminal nodes consistent with a node, information set, or action (#517) +- Implement `GameRep::GetPower` (C++) and `Action.power` (Python) to compute the power of an action, + that is, the set of plays (terminal nodes) consistent with passing throughn the action + and the plays that do not pass through the information set where this action is available. ## [16.3.1] - unreleased diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 50052a873..cc13a89ac 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -169,6 +169,7 @@ Information about the game Action.precedes Action.prob Action.plays + Action.power .. autosummary:: diff --git a/src/pygambit/action.pxi b/src/pygambit/action.pxi index 576834b54..b83cff635 100644 --- a/src/pygambit/action.pxi +++ b/src/pygambit/action.pxi @@ -109,9 +109,18 @@ class Action: @property def plays(self) -> typing.List[Node]: - """Returns a list of all terminal `Node` objects consistent with it. + """Returns a list of all terminal `Node` objects consistent with the action. """ return [ Node.wrap(n) for n in self.action.deref().GetInfoset().deref().GetGame().deref().GetPlays(self.action) ] + + @property + def power(self) -> typing.List[Node]: + """Returns power of the action. + """ + return [ + Node.wrap(n) for n in + self.action.deref().GetInfoset().deref().GetGame().deref().GetPower(self.action) + ] diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 4f518f3ad..e0a635994 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -282,6 +282,7 @@ cdef extern from "games/game.h": stdvector[c_GameNode] GetPlays(c_GameNode) except + stdvector[c_GameNode] GetPlays(c_GameInfoset) except + stdvector[c_GameNode] GetPlays(c_GameAction) except + + stdvector[c_GameNode] GetPower(c_GameAction) except + bool IsPerfectRecall() except + c_GameInfoset AppendMove(c_GameNode, c_GamePlayer, int) except +ValueError diff --git a/tests/test_actions.py b/tests/test_actions.py index 9b46e7d3d..9ac636834 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -143,3 +143,21 @@ def test_action_plays(): } # paths=[0, 1, 0], [0, 1] assert set(test_action.plays) == expected_set_of_plays + + +def test_action_power(): + """Verify `action.power` returns the action's power + (terminal `Node` objects that can be reached from this action + or that cannot be reached from the information set associated with this action). + """ + game = games.read_from_file("e01.efg") + list_nodes = list(game.nodes) + list_infosets = list(game.infosets) + + test_action = list_infosets[2].actions[0] # members' paths=[0, 1, 0], [0, 1] + + expected_set_of_plays = { + list_nodes[2], list_nodes[4], list_nodes[7] + } # paths=[0, 0], [0, 1, 0], [0, 1] + + assert set(test_action.power) == expected_set_of_plays From 16714f0bbee68748d3000aa8bddbae950de31d54 Mon Sep 17 00:00:00 2001 From: drdkad Date: Thu, 22 May 2025 12:53:47 +0100 Subject: [PATCH 3/5] Remove unused import of library --- src/games/gametree.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 2d2f85851..a7299fa72 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -23,7 +23,6 @@ #include #include #include -#include #include "gambit.h" #include "gametree.h" From efda6c3d17e27e93ab099c3accb1d9dcb395481c Mon Sep 17 00:00:00 2001 From: drdkad Date: Fri, 6 Jun 2025 09:05:52 +0100 Subject: [PATCH 4/5] Replace action.power with action.veto --- src/games/game.h | 2 +- src/games/gametree.cc | 17 ++++++----------- src/games/gametree.h | 2 +- src/pygambit/action.pxi | 4 ++-- src/pygambit/gambit.pxd | 2 +- tests/test_actions.py | 15 ++++++--------- 6 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/games/game.h b/src/games/game.h index 68399b631..a21838d4f 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -559,7 +559,7 @@ class GameRep : public BaseGameRep { virtual std::vector GetPlays(GameAction action) const { throw UndefinedException(); } /// Returns the power of an action - virtual std::vector GetPower(GameAction action) const { throw UndefinedException(); } + virtual std::vector GetVeto(GameAction action) const { throw UndefinedException(); } /// Returns true if the game is perfect recall. If not, /// a pair of violating information sets is returned in the parameters. diff --git a/src/games/gametree.cc b/src/games/gametree.cc index a7299fa72..411064b9b 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1058,22 +1058,17 @@ std::vector GameTreeRep::GetPlays(GameAction action) const return plays; } -std::vector GameTreeRep::GetPower(GameAction action) const +std::vector GameTreeRep::GetVeto(GameAction action) const { - std::vector power; - - const std::vector plays = GetPlays(GetRoot()); + std::vector veto; const std::vector aplays = GetPlays(action); - const std::vector iplays = GetPlays(action->GetInfoset()); - for (const auto &node : plays) { - if ((std::find(iplays.cbegin(), iplays.cend(), node) == iplays.cend()) || - (std::find(aplays.cbegin(), aplays.cend(), node) != aplays.cend())) { - power.push_back(node); + for (const auto &node : GetPlays(action->GetInfoset())) { + if (!contains(aplays, node)) { + veto.push_back(node); } } - - return power; + return veto; } void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome) diff --git a/src/games/gametree.h b/src/games/gametree.h index 05a4ec988..2dd2e6309 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -147,7 +147,7 @@ class GameTreeRep : public GameExplicitRep { std::vector GetPlays(GameInfoset infoset) const override; std::vector GetPlays(GameAction action) const override; - std::vector GetPower(GameAction action) const override; + std::vector GetVeto(GameAction action) const override; Game CopySubgame(GameNode) const override; //@} diff --git a/src/pygambit/action.pxi b/src/pygambit/action.pxi index b83cff635..6eb4e8ed7 100644 --- a/src/pygambit/action.pxi +++ b/src/pygambit/action.pxi @@ -117,10 +117,10 @@ class Action: ] @property - def power(self) -> typing.List[Node]: + def veto(self) -> typing.List[Node]: """Returns power of the action. """ return [ Node.wrap(n) for n in - self.action.deref().GetInfoset().deref().GetGame().deref().GetPower(self.action) + self.action.deref().GetInfoset().deref().GetGame().deref().GetVeto(self.action) ] diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index e0a635994..91db76a22 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -282,7 +282,7 @@ cdef extern from "games/game.h": stdvector[c_GameNode] GetPlays(c_GameNode) except + stdvector[c_GameNode] GetPlays(c_GameInfoset) except + stdvector[c_GameNode] GetPlays(c_GameAction) except + - stdvector[c_GameNode] GetPower(c_GameAction) except + + stdvector[c_GameNode] GetVeto(c_GameAction) except + bool IsPerfectRecall() except + c_GameInfoset AppendMove(c_GameNode, c_GamePlayer, int) except +ValueError diff --git a/tests/test_actions.py b/tests/test_actions.py index 9ac636834..a4600e1ac 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -145,19 +145,16 @@ def test_action_plays(): assert set(test_action.plays) == expected_set_of_plays -def test_action_power(): - """Verify `action.power` returns the action's power - (terminal `Node` objects that can be reached from this action - or that cannot be reached from the information set associated with this action). +def test_action_veto(): + """Verify `action.veto` returns the action's veto + (terminal `Node` objects contained in the information set associated with this action + that cannot be reached from this action). """ game = games.read_from_file("e01.efg") - list_nodes = list(game.nodes) list_infosets = list(game.infosets) test_action = list_infosets[2].actions[0] # members' paths=[0, 1, 0], [0, 1] - expected_set_of_plays = { - list_nodes[2], list_nodes[4], list_nodes[7] - } # paths=[0, 0], [0, 1, 0], [0, 1] + expected_set_of_plays = set(list_infosets[2].plays) - set(test_action.plays) - assert set(test_action.power) == expected_set_of_plays + assert set(test_action.veto) == expected_set_of_plays From 87cf05c5b657bd4294cbff5a3b2abfda834132c0 Mon Sep 17 00:00:00 2001 From: drdkad Date: Fri, 6 Jun 2025 09:14:00 +0100 Subject: [PATCH 5/5] Updated ChangeLog --- ChangeLog | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index d2b1b10f7..6ee63ffe9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,9 +12,7 @@ ### Added - Implement `GameRep::GetPlays` (C++) and `Node.plays`, `Infoset.plays`, and `Action.plays` (Python) to compute the set of terminal nodes consistent with a node, information set, or action (#517) -- Implement `GameRep::GetPower` (C++) and `Action.power` (Python) to compute the power of an action, - that is, the set of plays (terminal nodes) consistent with passing throughn the action - and the plays that do not pass through the information set where this action is available. +- Implement `GameRep::GetVeto` (C++) and `Action.veto` (Python) to compute the veto of an action (#522). ## [16.3.1] - unreleased