diff --git a/ChangeLog b/ChangeLog index 5b014fd15..7b2151654 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,9 @@ not a label of a child node. In addition, indexing by an action object is now supported. (#587) - In `pygambit`, `min_payoff` and `max_payoff` (for both games and players) now refers to payoffs in any play of the game; previously this referred only to the set of outcomes. (#498) +- In `pygambit`, calls to `sort_infosets` are no longer required to normalise the game representation. + Iteration ordering of information sets and their members is ensured internally. `sort_infosets` + is therefore now a no-op and is deprecated; it will be removed in a future version. ### Added - Tests for EFG Nash solvers -- `enumpoly_solve`, `lp_solve`, `lcp_solve` -- in behavior stratgegies diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index b8ff11a8d..f5749c9cc 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -37,6 +37,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) : m_probs(p_game->BehavProfileLength()), m_support(BehaviorSupportProfile(p_game)), m_gameversion(p_game->GetVersion()) { + p_game->EnsureInfosetOrdering(); int index = 1; for (const auto &infoset : p_game->GetInfosets()) { for (const auto &action : infoset->GetActions()) { @@ -51,6 +52,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_su : m_probs(p_support.BehaviorProfileLength()), m_support(p_support), m_gameversion(p_support.GetGame()->GetVersion()) { + m_support.GetGame()->EnsureInfosetOrdering(); int index = 1; for (const auto &infoset : p_support.GetGame()->GetInfosets()) { for (const auto &action : infoset->GetActions()) { @@ -126,6 +128,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p : m_probs(p_profile.GetGame()->BehavProfileLength()), m_support(p_profile.GetGame()), m_gameversion(p_profile.GetGame()->GetVersion()) { + m_support.GetGame()->EnsureInfosetOrdering(); int index = 1; for (const auto &infoset : p_profile.GetGame()->GetInfosets()) { for (const auto &action : infoset->GetActions()) { diff --git a/src/games/file.cc b/src/games/file.cc index babcf925e..f6ba3634f 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -737,7 +737,6 @@ Game ReadEfgFile(std::istream &p_stream) parser.GetNextToken(); } ParseNode(parser, game, game->GetRoot(), treeData); - game->SortInfosets(); return game; } diff --git a/src/games/game.h b/src/games/game.h index 7cb3a315f..800230231 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -239,11 +239,8 @@ class GameInfosetRep : public std::enable_shared_from_this { } //@} - GameNode GetMember(int p_index) const { return m_members.at(p_index - 1); } - Members GetMembers() const - { - return Members(std::const_pointer_cast(shared_from_this()), &m_members); - } + GameNode GetMember(int p_index) const; + Members GetMembers() const; bool Precedes(GameNode) const; @@ -403,12 +400,9 @@ class GamePlayerRep : public std::enable_shared_from_this { /// @name Information sets //@{ /// Returns the p_index'th information set - GameInfoset GetInfoset(int p_index) const { return m_infosets.at(p_index - 1); } + GameInfoset GetInfoset(int p_index) const; /// Returns the information sets for the player - Infosets GetInfosets() const - { - return Infosets(std::const_pointer_cast(shared_from_this()), &m_infosets); - } + Infosets GetInfosets() const; /// @name Strategies //@{ @@ -476,7 +470,7 @@ class GameNodeRep : public std::enable_shared_from_this { const std::string &GetLabel() const { return m_label; } void SetLabel(const std::string &p_label) { m_label = p_label; } - int GetNumber() const { return m_number; } + int GetNumber() const; GameNode GetChild(const GameAction &p_action) { if (p_action->GetInfoset().get() != m_infoset) { @@ -603,6 +597,8 @@ enum class TraversalOrder { Preorder, Postorder }; class GameRep : public std::enable_shared_from_this { friend class GameOutcomeRep; friend class GameNodeRep; + friend class GameInfosetRep; + friend class GamePlayerRep; friend class PureStrategyProfileRep; friend class TablePureStrategyProfileRep; template friend class MixedBehaviorProfile; @@ -623,6 +619,10 @@ class GameRep : public std::enable_shared_from_this { void IncrementVersion() { m_version++; } //@} + /// Hooks for derived classes to update lazily-computed orderings if required + virtual void EnsureNodeOrdering() const {} + virtual void EnsureInfosetOrdering() const {} + public: using Players = ElementCollection; using Outcomes = ElementCollection; @@ -986,8 +986,7 @@ class GameRep : public std::enable_shared_from_this { { return Infosets(std::const_pointer_cast(this->shared_from_this())); } - /// Sort the information sets for each player in a canonical order - virtual void SortInfosets() {} + /// Returns the set of actions taken by the infoset's owner before reaching this infoset virtual std::set GetOwnPriorActions(const GameInfoset &p_infoset) const { @@ -1111,6 +1110,35 @@ inline GamePlayerRep::Strategies GamePlayerRep::GetStrategies() const } inline Game GameNodeRep::GetGame() const { return m_game->shared_from_this(); } +inline int GameNodeRep::GetNumber() const +{ + m_game->EnsureNodeOrdering(); + return m_number; +} + +inline GameNode GameInfosetRep::GetMember(int p_index) const +{ + m_game->EnsureInfosetOrdering(); + return m_members.at(p_index - 1); +} + +inline GameInfosetRep::Members GameInfosetRep::GetMembers() const +{ + m_game->EnsureInfosetOrdering(); + return Members(std::const_pointer_cast(shared_from_this()), &m_members); +} + +inline GameInfoset GamePlayerRep::GetInfoset(int p_index) const +{ + m_game->EnsureInfosetOrdering(); + return m_infosets.at(p_index - 1); +} + +inline GamePlayerRep::Infosets GamePlayerRep::GetInfosets() const +{ + m_game->EnsureInfosetOrdering(); + return Infosets(std::const_pointer_cast(shared_from_this()), &m_infosets); +} //======================================================================= diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 0f85a16b7..38ab36245 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -145,6 +145,7 @@ void GameTreeRep::DeleteAction(GameAction p_action) member->m_children.erase(it); } ClearComputedValues(); + InvalidateNodeOrdering(); } GameInfoset GameActionRep::GetInfoset() const { return m_infoset->shared_from_this(); } @@ -192,6 +193,7 @@ void GameTreeRep::SetPlayer(GameInfoset p_infoset, GamePlayer p_player) p_player->m_infosets.push_back(p_infoset); ClearComputedValues(); + InvalidateNodeOrdering(); } bool GameInfosetRep::Precedes(GameNode p_node) const @@ -235,6 +237,7 @@ GameAction GameTreeRep::InsertAction(GameInfoset p_infoset, GameAction p_action m_numNodes += p_infoset->m_members.size(); // m_numNonterminalNodes stays unchanged when an action is appended to an information set ClearComputedValues(); + InvalidateNodeOrdering(); return action; } @@ -250,6 +253,7 @@ void GameTreeRep::RemoveMember(GameInfosetRep *p_infoset, GameNodeRep *p_node) player->m_infosets.begin(), player->m_infosets.end(), p_infoset->shared_from_this())); RenumberInfosets(player); } + InvalidateNodeOrdering(); } void GameTreeRep::Reveal(GameInfoset p_atInfoset, GamePlayer p_player) @@ -278,6 +282,7 @@ void GameTreeRep::Reveal(GameInfoset p_atInfoset, GamePlayer p_player) } ClearComputedValues(); + InvalidateNodeOrdering(); } //======================================================================== @@ -437,6 +442,7 @@ void GameTreeRep::DeleteParent(GameNode p_node) oldParent->Invalidate(); ClearComputedValues(); + InvalidateNodeOrdering(); } void GameTreeRep::DeleteTree(GameNode p_node) @@ -463,6 +469,7 @@ void GameTreeRep::DeleteTree(GameNode p_node) node->m_label = ""; ClearComputedValues(); + InvalidateNodeOrdering(); } void GameTreeRep::CopySubtree(GameNodeRep *dest, GameNodeRep *src, GameNodeRep *stop) @@ -504,6 +511,7 @@ void GameTreeRep::CopyTree(GameNode p_dest, GameNode p_src) CopySubtree(dest_child->get(), src_child->get(), dest); } ClearComputedValues(); + InvalidateNodeOrdering(); } } @@ -527,6 +535,7 @@ void GameTreeRep::MoveTree(GameNode p_dest, GameNode p_src) dest->m_outcome = nullptr; ClearComputedValues(); + InvalidateNodeOrdering(); } Game GameTreeRep::CopySubgame(GameNode p_root) const @@ -558,6 +567,7 @@ void GameTreeRep::SetInfoset(GameNode p_node, GameInfoset p_infoset) node->m_infoset = p_infoset.get(); ClearComputedValues(); + InvalidateInfosetOrdering(); } GameInfoset GameTreeRep::LeaveInfoset(GameNode p_node) @@ -588,6 +598,7 @@ GameInfoset GameTreeRep::LeaveInfoset(GameNode p_node) (*new_act)->SetLabel((*old_act)->GetLabel()); } ClearComputedValues(); + InvalidateInfosetOrdering(); return node->m_infoset->shared_from_this(); } @@ -633,6 +644,7 @@ GameInfoset GameTreeRep::AppendMove(GameNode p_node, GameInfoset p_infoset) }); m_numNonterminalNodes++; ClearComputedValues(); + InvalidateNodeOrdering(); return node->m_infoset->shared_from_this(); } @@ -689,6 +701,7 @@ GameInfoset GameTreeRep::InsertMove(GameNode p_node, GameInfoset p_infoset) m_numNodes += newNode->m_infoset->m_actions.size(); m_numNonterminalNodes++; ClearComputedValues(); + InvalidateNodeOrdering(); return p_infoset; } @@ -852,6 +865,7 @@ void GameTreeRep::SortInfosets(GamePlayerRep *p_player) }); RenumberInfosets(p_player); } + void GameTreeRep::RenumberInfosets(GamePlayerRep *p_player) { std::for_each( @@ -859,16 +873,28 @@ void GameTreeRep::RenumberInfosets(GamePlayerRep *p_player) [iset = 1](const std::shared_ptr &s) mutable { s->m_number = iset++; }); } -void GameTreeRep::SortInfosets() +void GameTreeRep::EnsureNodeOrdering() const { + if (m_nodesOrdered) { + return; + } int nodeindex = 1; for (const auto &node : GetNodes()) { node->m_number = nodeindex++; } - SortInfosets(m_chance.get()); - for (auto player : m_players) { + m_nodesOrdered = true; +} + +void GameTreeRep::EnsureInfosetOrdering() const +{ + if (m_infosetsOrdered) { + return; + } + EnsureNodeOrdering(); + for (auto player : GetPlayersWithChance()) { SortInfosets(player.get()); } + m_infosetsOrdered = true; } void GameTreeRep::ClearComputedValues() const @@ -891,7 +917,7 @@ void GameTreeRep::BuildComputedValues() const if (m_computedValues) { return; } - const_cast(this)->SortInfosets(); + EnsureInfosetOrdering(); for (const auto &player : m_players) { std::map behav; std::map ptr, whichbranch; @@ -1329,6 +1355,7 @@ MixedStrategyProfile GameTreeRep::NewMixedStrategyProfile(double) const if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } + EnsureInfosetOrdering(); return StrategySupportProfile(std::const_pointer_cast(shared_from_this())) .NewMixedStrategyProfile(); } @@ -1338,6 +1365,7 @@ MixedStrategyProfile GameTreeRep::NewMixedStrategyProfile(const Ration if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } + EnsureInfosetOrdering(); return StrategySupportProfile(std::const_pointer_cast(shared_from_this())) .NewMixedStrategyProfile(); } @@ -1348,6 +1376,7 @@ GameTreeRep::NewMixedStrategyProfile(double, const StrategySupportProfile &spt) if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } + EnsureInfosetOrdering(); return MixedStrategyProfile(std::make_unique>(spt)); } @@ -1357,6 +1386,7 @@ GameTreeRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProf if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } + EnsureInfosetOrdering(); return MixedStrategyProfile( std::make_unique>(spt)); } @@ -1386,6 +1416,7 @@ class TreePureStrategyProfileRep : public PureStrategyProfileRep { PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const { + EnsureInfosetOrdering(); return PureStrategyProfile(std::make_shared( std::const_pointer_cast(shared_from_this()))); } diff --git a/src/games/gametree.h b/src/games/gametree.h index 798da92fc..f7e3e9172 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -27,19 +27,18 @@ namespace Gambit { -class GameTreeRep : public GameExplicitRep { +class GameTreeRep final : public GameExplicitRep { friend class GameNodeRep; friend class GameInfosetRep; friend class GameActionRep; -private: struct OwnPriorActionInfo { std::map node_map; std::map> infoset_map; }; protected: - mutable bool m_computedValues{false}; + mutable bool m_computedValues{false}, m_nodesOrdered{false}, m_infosetsOrdered{false}; std::shared_ptr m_root; std::shared_ptr m_chance; std::size_t m_numNodes = 1; @@ -51,7 +50,7 @@ class GameTreeRep : public GameExplicitRep { /// @name Private auxiliary functions //@{ - void SortInfosets(GamePlayerRep *); + static void SortInfosets(GamePlayerRep *); static void RenumberInfosets(GamePlayerRep *); /// Normalize the probability distribution of actions at a chance node Game NormalizeChanceProbs(GameInfosetRep *); @@ -59,6 +58,15 @@ class GameTreeRep : public GameExplicitRep { /// @name Managing the representation //@{ + void InvalidateNodeOrdering() const + { + m_nodesOrdered = false; + m_infosetsOrdered = false; + } + void InvalidateInfosetOrdering() const { m_infosetsOrdered = false; } + void EnsureNodeOrdering() const override; + void EnsureInfosetOrdering() const override; + void BuildComputedValues() const override; void BuildConsistentPlays(); void ClearComputedValues() const; @@ -128,8 +136,6 @@ class GameTreeRep : public GameExplicitRep { //@{ /// Returns the iset'th information set in the game (numbered globally) GameInfoset GetInfoset(int iset) const override; - /// Sort the information sets for each player in a canonical order - void SortInfosets() override; /// Returns the set of actions taken by the infoset's owner before reaching this infoset std::set GetOwnPriorActions(const GameInfoset &p_infoset) const override; //@} diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 06b3fdf5b..225ea0b2f 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -465,21 +465,18 @@ void GameDocument::DoSetActionProbs(GameInfoset p_infoset, const Array & void GameDocument::DoSetInfoset(GameNode p_node, GameInfoset p_infoset) { m_game->SetInfoset(p_node, p_infoset); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoLeaveInfoset(GameNode p_node) { m_game->LeaveInfoset(p_node); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoRevealAction(GameInfoset p_infoset, GamePlayer p_player) { m_game->Reveal(p_infoset, p_player); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -490,7 +487,6 @@ void GameDocument::DoInsertAction(GameNode p_node) } const GameAction action = m_game->InsertAction(p_node->GetInfoset()); action->SetLabel(std::to_string(action->GetNumber())); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -503,35 +499,30 @@ void GameDocument::DoSetNodeLabel(GameNode p_node, const wxString &p_label) void GameDocument::DoAppendMove(GameNode p_node, GameInfoset p_infoset) { m_game->AppendMove(p_node, p_infoset); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoInsertMove(GameNode p_node, GamePlayer p_player, unsigned int p_actions) { m_game->InsertMove(p_node, p_player, p_actions, true); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoInsertMove(GameNode p_node, GameInfoset p_infoset) { m_game->InsertMove(p_node, p_infoset); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoCopyTree(GameNode p_destNode, GameNode p_srcNode) { m_game->CopyTree(p_destNode, p_srcNode); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoMoveTree(GameNode p_destNode, GameNode p_srcNode) { m_game->MoveTree(p_destNode, p_srcNode); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -541,14 +532,12 @@ void GameDocument::DoDeleteParent(GameNode p_node) return; } m_game->DeleteParent(p_node); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } void GameDocument::DoDeleteTree(GameNode p_node) { m_game->DeleteTree(p_node); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -557,7 +546,6 @@ void GameDocument::DoSetPlayer(GameInfoset p_infoset, GamePlayer p_player) if (!p_player->IsChance() && !p_infoset->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player m_game->SetPlayer(p_infoset, p_player); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } } @@ -567,7 +555,6 @@ void GameDocument::DoSetPlayer(GameNode p_node, GamePlayer p_player) if (!p_player->IsChance() && !p_node->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player m_game->SetPlayer(p_node->GetInfoset(), p_player); - m_game->SortInfosets(); UpdateViews(GBT_DOC_MODIFIED_GAME); } } diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 43a0b717b..1c9750265 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -280,7 +280,6 @@ cdef extern from "games/game.h": c_GameInfoset GetInfoset(int) except +IndexError Array[int] NumInfosets() except + - void SortInfosets() except + c_GameAction GetAction(int) except +IndexError int BehavProfileLength() except + diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 0b1735c0f..c8337d0e1 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -1844,30 +1844,25 @@ class Game: def sort_infosets(self) -> None: """Sort information sets into a standard order. - When iterating the information sets of a player, the order in which information - sets are visited may be dependent on the order of the operations used to build - the extensive game. This function sorts each player's information sets into - a standard order, namely, the order in which they are encountered in a depth-first - traversal of the tree, and likewise sorts the members of each information set - by the order in which they are encountered in a depth-first traversal. - - .. versionadded:: 16.4.0 + .. deprecated:: 16.5.0 + This operation is deprecated as efficient management of the iteration orders of + information sets and their members is now handled by the representation objects. Raises ------ UndefinedOperationError If the game does not have a tree representation. - - See also - -------- - Player.infosets - Infoset.members """ + warnings.warn( + "sort_infosets() is deprecated; This operation is now done automatically when" + " required. " + "This function will be removed in a future release.", + FutureWarning + ) if not self.is_tree: raise UndefinedOperationError( "Operation only defined for games with a tree representation" ) - self.game.deref().SortInfosets() def add_player(self, label: str = "") -> Player: """Add a new player to the game. diff --git a/src/pygambit/infoset.pxi b/src/pygambit/infoset.pxi index 82b341654..c85889b3e 100644 --- a/src/pygambit/infoset.pxi +++ b/src/pygambit/infoset.pxi @@ -206,19 +206,13 @@ class Infoset: def members(self) -> InfosetMembers: """The set of nodes which are members of the information set. - The order in which members are iterated is dependent on the order of - operations used to define the game. A standard ordering, in which members - are iterated in the order encountered in a depth-first traversal of the tree, - can be obtained by calling `Game.sort_infosets` on the game after construction. + The iteration order of information set members is the order in which they + are encountered in the pre-order depth first traversal of the game tree. - .. versionchanged:: 16.4.0 - The ordering of members is now dependent on the order of operations; - previously, members sets were (expensively) re-sorted after every change - to the game tree. + .. versionchanged:: 16.5.0 + It is no longer necessary to call `Game.sort_infosets` to standardise + iteration order. - See also - -------- - Game.sort_infosets """ return InfosetMembers.wrap(self.infoset) diff --git a/src/pygambit/player.pxi b/src/pygambit/player.pxi index d6f026328..0e7a289d2 100644 --- a/src/pygambit/player.pxi +++ b/src/pygambit/player.pxi @@ -218,24 +218,17 @@ class Player: def infosets(self) -> PlayerInfosets: """Returns the set of information sets at which the player has the decision. - The order in which information sets are iterated is dependent on the order of - operations used to define the game. A standard ordering, in which information - sets are iterated in the order encountered in a depth-first traversal of the tree, - can be obtained by calling `Game.sort_infosets` on the game after construction. + The iteration order of information sets is the order in which they + are encountered in the pre-order depth first traversal of the game tree. - .. versionchanged:: 16.4.0 - The ordering of information sets is now dependent on the order of operations; - previously, information sets were (expensively) re-sorted after every change - to the game tree. + .. versionchanged:: 16.5.0 + It is no longer necessary to call `Game.sort_infosets` to standardise + iteration order. Raises ------ UndefinedOperationError If the game does not have a tree representation. - - See also - -------- - Game.sort_infosets """ if not self.game.is_tree: raise UndefinedOperationError( diff --git a/tests/games.py b/tests/games.py index 1a382079d..32acea8e2 100644 --- a/tests/games.py +++ b/tests/games.py @@ -5,6 +5,7 @@ from abc import ABC, abstractmethod import numpy as np +import pytest import pygambit as gbt @@ -651,7 +652,8 @@ def create_kuhn_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game: g = _create_kuhn_poker_efg_only_term_outcomes() # Ensure infosets are in the same order as if game was written to efg and read back in - g.sort_infosets() + with pytest.warns(FutureWarning): + g.sort_infosets() return g diff --git a/tests/test_nash.py b/tests/test_nash.py index adf5552df..5f9b6d7b0 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -138,17 +138,17 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): ( games.create_3_player_with_internal_outcomes_efg(), [ - [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[0, 1], [1, 0]]], + [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], [[[1, 0], [1, 0]], [[1, 0], [0, 1]], - [["1/3", "2/3"], [1, 0]]]], + [[1, 0], ["1/3", "2/3"]]]], 2, ), ( games.create_3_player_with_internal_outcomes_efg(nonterm_outcomes=True), [ - [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[0, 1], [1, 0]]], + [[[1, 0], [1, 0]], [[1, 0], ["1/2", "1/2"]], [[1, 0], [0, 1]]], [[[1, 0], [1, 0]], [[1, 0], [0, 1]], - [["1/3", "2/3"], [1, 0]]]], + [[1, 0], ["1/3", "2/3"]]]], 2, ), ( @@ -683,7 +683,7 @@ def test_lp_behavior_double(): ( games.create_seq_form_STOC_paper_zero_sum_2_player_efg(), [ - [[0, 1], ["1/3", "2/3"], ["2/3", "1/3"]], + [[0, 1], ["2/3", "1/3"], ["1/3", "2/3"]], [["5/6", "1/6"], ["5/9", "4/9"]], ], ), diff --git a/tests/test_strategic.py b/tests/test_strategic.py index f6c87e60d..b59c4af6d 100644 --- a/tests/test_strategic.py +++ b/tests/test_strategic.py @@ -43,7 +43,7 @@ def test_strategic_game_nodes(): def test_strategic_game_sort_infosets(): game = gbt.Game.new_table([2, 2]) - with pytest.raises(gbt.UndefinedOperationError): + with pytest.warns(FutureWarning), pytest.raises(gbt.UndefinedOperationError): _ = game.sort_infosets()