From 9324864813f3c83c33d6986e7dd0012c4008a6f2 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Tue, 18 Nov 2025 19:40:14 +0000 Subject: [PATCH 01/36] Added GetFullSupport() to Game --- src/games/game.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/games/game.h b/src/games/game.h index f98827b57..1cf8fabbf 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -66,6 +66,7 @@ using Game = std::shared_ptr; class PureStrategyProfile; template class MixedStrategyProfile; class StrategySupportProfile; +class BehaviorSupportProfile; template class MixedBehaviorProfile; @@ -957,6 +958,11 @@ class GameRep : public std::enable_shared_from_this { /// Build any computed values anew virtual void BuildComputedValues() const {} + + virtual std::shared_ptr GetFullSupport() + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + }; }; //======================================================================= From 24261f5b32848aab575efbd1d6c0a479cf52a753 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Wed, 19 Nov 2025 11:19:13 +0000 Subject: [PATCH 02/36] Moved Sequences into Gambit namespace --- src/games/behavspt.cc | 61 +------------------------------------------ src/games/behavspt.h | 33 ----------------------- src/games/game.cc | 56 +++++++++++++++++++++++++++++++++++++++ src/games/game.h | 39 +++++++++++++++++++++++++++ src/games/gameseq.h | 1 + 5 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 81cd29c9f..71a35d46e 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -174,7 +174,7 @@ std::shared_ptr BehaviorSupportProfile::GetSequenceForm() cons return m_sequenceForm; } -BehaviorSupportProfile::Sequences BehaviorSupportProfile::GetSequences() const { return {this}; } +Sequences BehaviorSupportProfile::GetSequences() const { return {this}; } BehaviorSupportProfile::PlayerSequences BehaviorSupportProfile::GetSequences(GamePlayer &p_player) const @@ -220,65 +220,6 @@ BehaviorSupportProfile::ToMixedBehaviorProfile(const std::mapGetSequenceForm()->m_sequences.cbegin(), - m_support->GetSequenceForm()->m_sequences.cend(), 0, - [](int acc, const std::pair> &seq) { - return acc + seq.second.size(); - }); -} - -BehaviorSupportProfile::Sequences::iterator BehaviorSupportProfile::Sequences::begin() const -{ - return {m_support->GetSequenceForm(), false}; -} -BehaviorSupportProfile::Sequences::iterator BehaviorSupportProfile::Sequences::end() const -{ - return {m_support->GetSequenceForm(), true}; -} - -BehaviorSupportProfile::Sequences::iterator::iterator( - const std::shared_ptr p_sfg, bool p_end) - : m_sfg(p_sfg) -{ - if (p_end) { - m_currentPlayer = m_sfg->m_sequences.cend(); - } - else { - m_currentPlayer = m_sfg->m_sequences.cbegin(); - m_currentSequence = m_currentPlayer->second.cbegin(); - } -} - -BehaviorSupportProfile::Sequences::iterator & -BehaviorSupportProfile::Sequences::iterator::operator++() -{ - if (m_currentPlayer == m_sfg->m_sequences.cend()) { - return *this; - } - m_currentSequence++; - if (m_currentSequence != m_currentPlayer->second.cend()) { - return *this; - } - m_currentPlayer++; - if (m_currentPlayer != m_sfg->m_sequences.cend()) { - m_currentSequence = m_currentPlayer->second.cbegin(); - } - return *this; -} - -bool BehaviorSupportProfile::Sequences::iterator::operator==(const iterator &it) const -{ - if (m_sfg != it.m_sfg || m_currentPlayer != it.m_currentPlayer) { - return false; - } - if (m_currentPlayer == m_sfg->m_sequences.end()) { - return true; - } - return (m_currentSequence == it.m_currentSequence); -} - std::vector::const_iterator BehaviorSupportProfile::PlayerSequences::begin() const { return m_support->GetSequenceForm()->m_sequences.at(m_player).begin(); diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 59e89fbbc..166fb8a67 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -30,10 +30,6 @@ namespace Gambit { class GameSequenceForm; -class SequencesWrapper; -class PlayerSequencesWrapper; -class InfosetsWrapper; -class ContingenciesWrapper; /// This class represents a subset of the actions in an extensive game. /// It is enforced that each player has at least one action at each @@ -165,35 +161,6 @@ class BehaviorSupportProfile { } }; - class Sequences { - const BehaviorSupportProfile *m_support; - - public: - class iterator { - const std::shared_ptr m_sfg; - std::map>::const_iterator m_currentPlayer; - std::vector::const_iterator m_currentSequence; - - public: - iterator(const std::shared_ptr p_sfg, bool p_end); - - GameSequence operator*() const { return *m_currentSequence; } - GameSequence operator->() const { return *m_currentSequence; } - - iterator &operator++(); - - bool operator==(const iterator &it) const; - bool operator!=(const iterator &it) const { return !(*this == it); } - }; - - Sequences(const BehaviorSupportProfile *p_support) : m_support(p_support) {} - - size_t size() const; - - iterator begin() const; - iterator end() const; - }; - class PlayerSequences { const BehaviorSupportProfile *m_support; GamePlayer m_player; diff --git a/src/games/game.cc b/src/games/game.cc index 37619653a..b1fc99b33 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -32,6 +32,7 @@ // editing operations into the game itself instead of in the member-object // classes. #include "gametree.h" +#include "gameseq.h" namespace Gambit { @@ -409,4 +410,59 @@ template class MixedStrategyProfileRep; template class MixedStrategyProfile; template class MixedStrategyProfile; +//======================================================================== +// Sequences +//======================================================================== + +size_t Sequences::size() const +{ + return std::accumulate(m_support->GetSequenceForm()->m_sequences.cbegin(), + m_support->GetSequenceForm()->m_sequences.cend(), 0, + [](int acc, const std::pair> &seq) { + return acc + seq.second.size(); + }); +} + +Sequences::iterator Sequences::begin() const { return {m_support->GetSequenceForm(), false}; } +Sequences::iterator Sequences::end() const { return {m_support->GetSequenceForm(), true}; } + +Sequences::iterator::iterator(const std::shared_ptr p_sfg, bool p_end) + : m_sfg(p_sfg) +{ + if (p_end) { + m_currentPlayer = m_sfg->m_sequences.cend(); + } + else { + m_currentPlayer = m_sfg->m_sequences.cbegin(); + m_currentSequence = m_currentPlayer->second.cbegin(); + } +} + +Sequences::iterator &Sequences::iterator::operator++() +{ + if (m_currentPlayer == m_sfg->m_sequences.cend()) { + return *this; + } + m_currentSequence++; + if (m_currentSequence != m_currentPlayer->second.cend()) { + return *this; + } + m_currentPlayer++; + if (m_currentPlayer != m_sfg->m_sequences.cend()) { + m_currentSequence = m_currentPlayer->second.cbegin(); + } + return *this; +} + +bool Sequences::iterator::operator==(const iterator &it) const +{ + if (m_sfg != it.m_sfg || m_currentPlayer != it.m_currentPlayer) { + return false; + } + if (m_currentPlayer == m_sfg->m_sequences.end()) { + return true; + } + return (m_currentSequence == it.m_currentSequence); +} + } // end namespace Gambit diff --git a/src/games/game.h b/src/games/game.h index 1cf8fabbf..27d748515 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -67,6 +67,7 @@ class PureStrategyProfile; template class MixedStrategyProfile; class StrategySupportProfile; class BehaviorSupportProfile; +class GameSequenceForm; template class MixedBehaviorProfile; @@ -598,6 +599,35 @@ inline GameNodeRep::Actions::iterator::iterator(GameInfosetRep::Actions::iterato inline GameNode GameNodeRep::Actions::iterator::GetOwner() const { return m_child_it.GetOwner(); } +class Sequences { + const BehaviorSupportProfile *m_support; + +public: + class iterator { + const std::shared_ptr m_sfg; + std::map>::const_iterator m_currentPlayer; + std::vector::const_iterator m_currentSequence; + + public: + iterator(const std::shared_ptr p_sfg, bool p_end); + + GameSequence operator*() const { return *m_currentSequence; } + GameSequence operator->() const { return *m_currentSequence; } + + iterator &operator++(); + + bool operator==(const iterator &it) const; + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + Sequences(const BehaviorSupportProfile *p_support) : m_support(p_support) {} + + size_t size() const; + + iterator begin() const; + iterator end() const; +}; + /// This is the class for representing an arbitrary finite game. class GameRep : public std::enable_shared_from_this { friend class GameOutcomeRep; @@ -963,6 +993,15 @@ class GameRep : public std::enable_shared_from_this { { throw std::runtime_error("Sequence form can only be generated for extensive form games"); }; + + virtual const Sequences GetSequences() const {} + + virtual const Rational &GetPayoff(const std::map &p_profile, + const GamePlayer &p_player) const + { + } + + virtual const GameSequence GetCorrespondingSequence(const GameAction &p_action) {} }; //======================================================================= diff --git a/src/games/gameseq.h b/src/games/gameseq.h index bcf4fec70..d9efdbc31 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -30,6 +30,7 @@ namespace Gambit { class GameSequenceForm { friend class BehaviorSupportProfile; + friend class Sequences; BehaviorSupportProfile m_support; std::map> m_sequences; From 36f4040839790bd7baa60a0c00422d30cd864f63 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Wed, 19 Nov 2025 12:35:49 +0000 Subject: [PATCH 03/36] Added sequence form functionality to Game --- src/games/behavspt.cc | 11 +++++++++++ src/games/behavspt.h | 2 ++ src/games/game.h | 20 +++++++++++++++++--- src/games/gametree.h | 18 ++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 71a35d46e..89c856aca 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -176,6 +176,17 @@ std::shared_ptr BehaviorSupportProfile::GetSequenceForm() cons Sequences BehaviorSupportProfile::GetSequences() const { return {this}; } +GameSequence BehaviorSupportProfile::GetCorrespondingSequence(const GameAction &p_action) const +{ + return GetSequenceForm()->m_correspondence.at(p_action); +} + +int BehaviorSupportProfile::GetSequenceConstraintEntry(const GameInfoset &p_infoset, + const GameAction &p_action) const +{ + return GetSequenceForm()->GetConstraintEntry(p_infoset, p_action); +} + BehaviorSupportProfile::PlayerSequences BehaviorSupportProfile::GetSequences(GamePlayer &p_player) const { diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 166fb8a67..c14d4d411 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -221,6 +221,8 @@ class BehaviorSupportProfile { ToMixedBehaviorProfile(const std::map &) const; Infosets GetInfosets() const { return {this}; }; SequenceContingencies GetSequenceContingencies() const; + GameSequence GetCorrespondingSequence(const GameAction &p_action) const; + int GetSequenceConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) const; void FindReachableInfosets(GameNode p_node) const; std::shared_ptr> GetReachableInfosets() const; diff --git a/src/games/game.h b/src/games/game.h index 27d748515..0eec6c08c 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -989,19 +989,33 @@ class GameRep : public std::enable_shared_from_this { /// Build any computed values anew virtual void BuildComputedValues() const {} + // Sequence form methods + virtual std::shared_ptr GetFullSupport() { throw std::runtime_error("Sequence form can only be generated for extensive form games"); }; - virtual const Sequences GetSequences() const {} + virtual const Sequences GetSequences() + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + } virtual const Rational &GetPayoff(const std::map &p_profile, - const GamePlayer &p_player) const + const GamePlayer &p_player) + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + } + + virtual const GameSequence GetCorrespondingSequence(const GameAction &p_action) { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); } - virtual const GameSequence GetCorrespondingSequence(const GameAction &p_action) {} + virtual int GetSequenceConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + } }; //======================================================================= diff --git a/src/games/gametree.h b/src/games/gametree.h index e77500964..c36e27d62 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -48,6 +48,24 @@ class GameTreeRep : public GameExplicitRep { mutable std::shared_ptr m_ownPriorActionInfo; mutable std::unique_ptr> m_unreachableNodes; + const Sequences GetSequences() override { return GetFullSupport()->GetSequences(); } + + const Rational &GetPayoff(const std::map &p_profile, + const GamePlayer &p_player) override + { + return GetFullSupport()->GetPayoff(p_profile, p_player); + } + + const GameSequence GetCorrespondingSequence(const GameAction &p_action) override + { + return GetFullSupport()->GetCorrespondingSequence(p_action); + } + + int GetSequenceConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) override + { + return GetFullSupport()->GetSequenceConstraintEntry(p_infoset, p_action); + } + /// @name Private auxiliary functions //@{ void SortInfosets(GamePlayerRep *); From b252b872e0bd38189e7a57688e3727e2fd2690f7 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Wed, 19 Nov 2025 17:33:02 +0000 Subject: [PATCH 04/36] Made get_sequence_form_payoff function on pygambit game --- src/games/behavspt.cc | 2 +- src/games/behavspt.h | 2 +- src/games/game.h | 5 +++++ src/games/gametree.h | 5 +++++ src/pygambit/gambit.pyx | 5 ++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 89c856aca..a84128e29 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -188,7 +188,7 @@ int BehaviorSupportProfile::GetSequenceConstraintEntry(const GameInfoset &p_info } BehaviorSupportProfile::PlayerSequences -BehaviorSupportProfile::GetSequences(GamePlayer &p_player) const +BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) const { return {this, p_player}; } diff --git a/src/games/behavspt.h b/src/games/behavspt.h index c14d4d411..d5769be05 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -212,7 +212,7 @@ class BehaviorSupportProfile { std::shared_ptr GetSequenceForm() const; Sequences GetSequences() const; - PlayerSequences GetSequences(GamePlayer &p_player) const; + PlayerSequences GetSequences(const GamePlayer &p_player) const; int GetConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) const; const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const; diff --git a/src/games/game.h b/src/games/game.h index 0eec6c08c..e2cf53fe8 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1016,6 +1016,11 @@ class GameRep : public std::enable_shared_from_this { { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } + + virtual const GameSequence GetEmptySequence(const GamePlayer &p_player) + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + } }; //======================================================================= diff --git a/src/games/gametree.h b/src/games/gametree.h index c36e27d62..7582e8897 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -66,6 +66,11 @@ class GameTreeRep : public GameExplicitRep { return GetFullSupport()->GetSequenceConstraintEntry(p_infoset, p_action); } + const GameSequence GetEmptySequence(const GamePlayer &p_player) override + { + return *(GetFullSupport()->GetSequences(p_player).begin()); + } + /// @name Private auxiliary functions //@{ void SortInfosets(GamePlayerRep *); diff --git a/src/pygambit/gambit.pyx b/src/pygambit/gambit.pyx index ee31dde27..dc51547a6 100644 --- a/src/pygambit/gambit.pyx +++ b/src/pygambit/gambit.pyx @@ -50,7 +50,10 @@ class Rational(fractions.Fraction): @cython.cfunc def rat_to_py(r: c_Rational): """Convert a C++ Rational number to a Python Rational.""" - return Rational(to_string(r).decode("ascii")) + s = to_string(r).decode("ascii") + if not s: + return Rational(0) + return Rational(s) @cython.cfunc From 41e7e4390eeed6e6c9a94c6cb77d7a5abdbe9f20 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Thu, 20 Nov 2025 11:33:23 +0000 Subject: [PATCH 05/36] Made a Sequence class in cython --- src/pygambit/gambit.pxd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 50e56d7e9..a968abd5a 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -1,6 +1,6 @@ from libcpp cimport bool from libcpp.string cimport string -from libcpp.memory cimport shared_ptr, unique_ptr +from libcpp.memory cimport shared_ptr, unique_ptr, weak_ptr from libcpp.list cimport list as stdlist from libcpp.vector cimport vector as stdvector from libcpp.set cimport set as stdset @@ -93,6 +93,11 @@ cdef extern from "games/game.h": cdef cppclass c_PureBehaviorProfile "PureBehaviorProfile": c_PureBehaviorProfile(c_Game) except + + cdef cppclass c_GameSequenceRep "GameSequenceRep": + c_GamePlayer player + c_GameAction action + weak_ptr[c_GameSequenceRep] parent + cdef cppclass c_GameStrategyRep "GameStrategyRep": int GetNumber() except + int GetId() except + From 8a570b555b733516b00a7abdf6acaffa4d971df7 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 21 Nov 2025 11:47:31 +0000 Subject: [PATCH 06/36] Moved PlayerSequences into Gambit namespace and added GetSequences(player) to GameRep --- src/games/behavspt.cc | 18 +----------------- src/games/behavspt.h | 15 --------------- src/games/game.cc | 15 +++++++++++++++ src/games/game.h | 20 ++++++++++++++++++++ src/games/gameseq.h | 1 + src/games/gametree.h | 5 +++++ 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index a84128e29..dea79df61 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -187,8 +187,7 @@ int BehaviorSupportProfile::GetSequenceConstraintEntry(const GameInfoset &p_info return GetSequenceForm()->GetConstraintEntry(p_infoset, p_action); } -BehaviorSupportProfile::PlayerSequences -BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) const +PlayerSequences BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) const { return {this, p_player}; } @@ -231,21 +230,6 @@ BehaviorSupportProfile::ToMixedBehaviorProfile(const std::map::const_iterator BehaviorSupportProfile::PlayerSequences::begin() const -{ - return m_support->GetSequenceForm()->m_sequences.at(m_player).begin(); -} - -std::vector::const_iterator BehaviorSupportProfile::PlayerSequences::end() const -{ - return m_support->GetSequenceForm()->m_sequences.at(m_player).end(); -} - -size_t BehaviorSupportProfile::PlayerSequences::size() const -{ - return m_support->GetSequenceForm()->m_sequences.at(m_player).size(); -} - BehaviorSupportProfile::SequenceContingencies::iterator::iterator( const std::shared_ptr p_sfg, bool p_end) : m_sfg(p_sfg), m_end(p_end) diff --git a/src/games/behavspt.h b/src/games/behavspt.h index d5769be05..e1643f097 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -161,21 +161,6 @@ class BehaviorSupportProfile { } }; - class PlayerSequences { - const BehaviorSupportProfile *m_support; - GamePlayer m_player; - - public: - PlayerSequences(const BehaviorSupportProfile *p_support, const GamePlayer &p_player) - : m_support(p_support), m_player(p_player) - { - } - - size_t size() const; - std::vector::const_iterator begin() const; - std::vector::const_iterator end() const; - }; - class SequenceContingencies { const BehaviorSupportProfile *m_support; diff --git a/src/games/game.cc b/src/games/game.cc index b1fc99b33..eff072df6 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -465,4 +465,19 @@ bool Sequences::iterator::operator==(const iterator &it) const return (m_currentSequence == it.m_currentSequence); } +std::vector::const_iterator PlayerSequences::begin() const +{ + return m_support->GetSequenceForm()->m_sequences.at(m_player).begin(); +} + +std::vector::const_iterator PlayerSequences::end() const +{ + return m_support->GetSequenceForm()->m_sequences.at(m_player).end(); +} + +size_t PlayerSequences::size() const +{ + return m_support->GetSequenceForm()->m_sequences.at(m_player).size(); +} + } // end namespace Gambit diff --git a/src/games/game.h b/src/games/game.h index e2cf53fe8..ff326058e 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -628,6 +628,21 @@ class Sequences { iterator end() const; }; +class PlayerSequences { + const BehaviorSupportProfile *m_support; + GamePlayer m_player; + +public: + PlayerSequences(const BehaviorSupportProfile *p_support, const GamePlayer &p_player) + : m_support(p_support), m_player(p_player) + { + } + + size_t size() const; + std::vector::const_iterator begin() const; + std::vector::const_iterator end() const; +}; + /// This is the class for representing an arbitrary finite game. class GameRep : public std::enable_shared_from_this { friend class GameOutcomeRep; @@ -1001,6 +1016,11 @@ class GameRep : public std::enable_shared_from_this { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } + virtual const PlayerSequences GetSequences(const GamePlayer &p_player); + { + throw std::runtime_error("Sequence form can only be generated for extensive"); + } + virtual const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) { diff --git a/src/games/gameseq.h b/src/games/gameseq.h index d9efdbc31..9e3e414d9 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -31,6 +31,7 @@ namespace Gambit { class GameSequenceForm { friend class BehaviorSupportProfile; friend class Sequences; + friend class PlayerSequences; BehaviorSupportProfile m_support; std::map> m_sequences; diff --git a/src/games/gametree.h b/src/games/gametree.h index 7582e8897..b06f4ace4 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -50,6 +50,11 @@ class GameTreeRep : public GameExplicitRep { const Sequences GetSequences() override { return GetFullSupport()->GetSequences(); } + const PlayerSequences GetSequences(const GamePlayer &p_player) override + { + return GetFullSupport()->GetSequences(p_player); + } + const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) override { From 1ce3457a2ce188e4460ebcf763d29854e7ce49a9 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 22 Nov 2025 10:38:17 +0000 Subject: [PATCH 07/36] Exposed PlayerSequences --- src/games/game.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/game.h b/src/games/game.h index ff326058e..993a9f080 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1016,7 +1016,7 @@ class GameRep : public std::enable_shared_from_this { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } - virtual const PlayerSequences GetSequences(const GamePlayer &p_player); + virtual const PlayerSequences GetSequences(const GamePlayer &p_player) { throw std::runtime_error("Sequence form can only be generated for extensive"); } From de490db711a8749b5f93d9d8ff4d506e98dadc07 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 24 Nov 2025 12:40:24 +0000 Subject: [PATCH 08/36] Added MixedSequenceProfile to game.h --- src/games/game.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/games/game.h b/src/games/game.h index 993a9f080..39ce4d97f 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1165,6 +1165,44 @@ std::list UniformOnSimplex(int p_denom, size_t p_dim, Generator &gener return output; } +template class MixedSequenceProfile { + +private: + std::map probs; + Game m_game; + +public: + MixedSequenceProfile(const Game &p_game) { m_game = p_game; } + + const T &operator[](const GameSequence &p_key) const { return probs.at(p_key); } + + T &operator[](const GameSequence &p_key) { return probs[p_key]; } + + MixedBehaviorProfile GetMixedBehaviorProfile() const + { + MixedBehaviorProfile mbp(m_game); + for (const auto &[seq, prob] : probs) { + auto parent_seq = seq->parent.lock(); + if (parent_seq) { + T parent_prob = probs.at(parent_seq); + auto action = seq->action; + mbp[action] = (parent_prob > static_cast(0)) ? prob / parent_prob : static_cast(0); + } + } + return mbp; + } + + std::map GetPayoffs() const + { + std::map payoffs; + auto mbp = GetMixedBehaviorProfile(); + for (auto player : m_game->GetPlayers()) { + payoffs[player] = mbp.GetPayoff(player); + } + return payoffs; + } +}; + } // namespace Gambit #endif // LIBGAMBIT_GAME_H From ca7cadf3b4ca8532f19d9b92896fa582d810c015 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 24 Nov 2025 12:43:19 +0000 Subject: [PATCH 09/36] Added MixedSequenceProfile to game.h --- src/games/game.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/games/game.h b/src/games/game.h index 39ce4d97f..5f1396a0a 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1165,6 +1165,10 @@ std::list UniformOnSimplex(int p_denom, size_t p_dim, Generator &gener return output; } +//======================================================================= +// Mixed sequence profile class +//======================================================================= + template class MixedSequenceProfile { private: From e3cbb9d4915b732803bdcf325c01a02987559599 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 24 Nov 2025 14:39:04 +0000 Subject: [PATCH 10/36] Added failing LCP code --- src/solvers/lcp/efglcp.cc | 111 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index 9b44b08e9..71dc019a7 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -24,6 +24,7 @@ #include "gambit.h" #include "solvers/linalg/lemketab.h" #include "solvers/lcp/lcp.h" +#include "games/gameseq.h" namespace Gambit::Nash { @@ -45,10 +46,13 @@ template class NashLcpBehaviorSolver { class Solution; void FillTableau(Matrix &, const GameNode &, T, int, int, Solution &) const; + void FillTableauNew(Matrix &A, const Game &p_game, Solution &p_solution) const; void AllLemke(const Game &, int dup, linalg::LemkeTableau &B, int depth, Matrix &, Solution &) const; void GetProfile(const linalg::LemkeTableau &tab, MixedBehaviorProfile &, const Vector &, const GameNode &n, int, int, Solution &) const; + MixedBehaviorProfile GetProfileNew(const linalg::LemkeTableau &tab, const Vector &sol, + const Game &p_game, Solution &p_solution) const; }; template class NashLcpBehaviorSolver::Solution { @@ -126,7 +130,12 @@ std::list> NashLcpBehaviorSolver::Solve(const Game &p const int ntot = solution.ns1 + solution.ns2 + solution.ni1 + solution.ni2; Matrix A(1, ntot, 0, ntot); A = static_cast(0); - FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1, solution); + + // BehaviorSupportProfile full_support(p_game); + // Gambit::GameSequenceForm sequenceForm(full_support); + + // FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1, solution); + FillTableauNew(A, p_game, solution); for (int i = A.MinRow(); i <= A.MaxRow(); i++) { A(i, 0) = static_cast(-1); } @@ -158,8 +167,9 @@ std::list> NashLcpBehaviorSolver::Solve(const Game &p solution.AddBFS(tab); Vector sol(tab.MinRow(), tab.MaxRow()); tab.BasisVector(sol); - MixedBehaviorProfile profile(p_game); - GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution); + // MixedBehaviorProfile profile(p_game); + // GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution); + auto profile = GetProfileNew(tab, sol, p_game, solution); profile.UndefinedToCentroid(); solution.m_equilibria.push_back(profile); this->m_onEquilibrium(profile, "NE"); @@ -187,7 +197,7 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke } Vector sol(B.MinRow(), B.MaxRow()); - MixedBehaviorProfile profile(p_game); + // MixedBehaviorProfile profile(p_game); bool newsol = false; for (int i = B.MinRow(); i <= B.MaxRow() && !newsol; i++) { @@ -214,7 +224,10 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke if (BCopy.SF_LCPPath(-missing) == 1) { newsol = p_solution.AddBFS(BCopy); BCopy.BasisVector(sol); - GetProfile(BCopy, profile, sol, p_game->GetRoot(), 1, 1, p_solution); + // BehaviorSupportProfile full_support(p_game); + // Gambit::GameSequenceForm sequenceForm(full_support); + // GetProfile(BCopy, profile, sol, p_game->GetRoot(), 1, 1, p_solution); + auto profile = GetProfileNew(BCopy, sol, p_game, p_solution); profile.UndefinedToCentroid(); if (newsol) { m_onEquilibrium(profile, "NE"); @@ -288,6 +301,57 @@ void NashLcpBehaviorSolver::FillTableau(Matrix &A, const GameNode &n, T pr } } +template +void NashLcpBehaviorSolver::FillTableauNew(Matrix &A, const Game &p_game, + Solution &p_solution) const +{ + const int ns1 = p_solution.ns1; + const int ns2 = p_solution.ns2; + const int ni1 = p_solution.ni1; + auto players = p_game->GetPlayers(); + auto it = players.begin(); + auto player1 = *it; + ++it; + auto player2 = *it; + auto sequences1 = p_game->GetSequences(player1); + auto sequences2 = p_game->GetSequences(player2); + for (auto seq : sequences1) { + auto parentSeq = seq->parent.lock(); + if (parentSeq) { + const int infoset_idx = ns1 + ns2 + seq->GetInfoset()->GetNumber() + 1; + const int seq_idx = seq->number; + const int parent_idx = parentSeq->number; + A(parent_idx, infoset_idx) = static_cast(-1); + A(infoset_idx, parent_idx) = static_cast(1); + A(seq_idx, infoset_idx) = static_cast(1); + A(infoset_idx, seq_idx) = static_cast(-1); + } + } + for (auto seq : sequences2) { + auto parentSeq = seq->parent.lock(); + if (parentSeq) { + const int infoset_idx = ns1 + ns2 + ni1 + seq->GetInfoset()->GetNumber() + 1; + const int seq_idx = seq->number; + const int parent_idx = parentSeq->number; + A(ns1 + parent_idx, infoset_idx) = static_cast(-1); + A(infoset_idx, ns1 + parent_idx) = static_cast(1); + A(ns1 + seq_idx, infoset_idx) = static_cast(1); + A(infoset_idx, ns1 + seq_idx) = static_cast(-1); + } + } + for (auto seq1 : sequences1) { + for (auto seq2 : sequences2) { + const int s1 = seq1->number; + const int s2 = seq2->number; + std::map profile; + profile[player1] = seq1; + profile[player2] = seq2; + A(s1, ns1 + s2) = p_game->GetPayoff(profile, player1) - p_solution.maxpay; + A(ns1 + s2, s1) = p_game->GetPayoff(profile, player2) - p_solution.maxpay; + } + } +} + template void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, MixedBehaviorProfile &v, const Vector &sol, @@ -340,6 +404,43 @@ void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, } } +template +MixedBehaviorProfile +NashLcpBehaviorSolver::GetProfileNew(const linalg::LemkeTableau &tab, const Vector &sol, + const Game &p_game, Solution &p_solution) const +{ + const int ns1 = p_solution.ns1; + auto players = p_game->GetPlayers(); + auto it = players.begin(); + auto player1 = *it; + ++it; + auto player2 = *it; + auto sequences1 = p_game->GetSequences(player1); + auto sequences2 = p_game->GetSequences(player2); + Gambit::MixedSequenceProfile msp(p_game); + for (auto seq : sequences1) { + int seq_num = seq->number; + if (tab.Member(seq_num)) { + int index = tab.Find(seq_num); + msp[seq] = (sol[index] > p_solution.eps) ? sol[index] : static_cast(0); + } + else { + msp[seq] = static_cast(0); + } + } + for (auto seq : sequences2) { + int seq_num = seq->number; + if (tab.Member(ns1 + seq_num)) { + int index = tab.Find(ns1 + seq_num); + msp[seq] = (sol[index] > p_solution.eps) ? sol[index] : static_cast(0); + } + else { + msp[seq] = static_cast(0); + } + } + return msp.GetMixedBehaviorProfile(); +} + template std::list> LcpBehaviorSolve(const Game &p_game, int p_stopAfter, int p_maxDepth, From 936385c0ab484d58bf94d5edae8c06615bd7b1fd Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 24 Nov 2025 14:58:28 +0000 Subject: [PATCH 11/36] Added failing LP code --- src/solvers/lp/lp.cc | 79 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc index 9e0a5e467..af674c3c7 100644 --- a/src/solvers/lp/lp.cc +++ b/src/solvers/lp/lp.cc @@ -23,6 +23,7 @@ #include "gambit.h" #include "solvers/lp/lp.h" #include "solvers/linalg/lpsolve.h" +#include "games/gameseq.h" namespace Gambit::Nash { @@ -35,9 +36,12 @@ template class GameData { explicit GameData(const Game &); void FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2); + void FillTableauNew(Matrix &A, const Game &p_game); void GetBehavior(MixedBehaviorProfile &v, const Array &, const Array &, const GameNode &, int, int); + MixedBehaviorProfile GetBehaviorNew(const Array &p_primal, const Array &p_dual, + const Game &p_game); }; template GameData::GameData(const Game &p_game) : minpay(p_game->GetMinPayoff()) @@ -94,6 +98,47 @@ void GameData::FillTableau(Matrix &A, const GameNode &n, const T &prob, in } } +template void GameData::FillTableauNew(Matrix &A, const Game &p_game) +{ + auto players = p_game->GetPlayers(); + auto it = players.begin(); + auto player1 = *it; + ++it; + auto player2 = *it; + auto sequences1 = p_game->GetSequences(player1); + auto sequences2 = p_game->GetSequences(player2); + for (auto seq : sequences1) { + auto parentSeq = seq->parent.lock(); + if (parentSeq) { + const int col = ns2 + seq->GetInfoset()->GetNumber() + 1; + const int row = seq->number; + const int parentRow = parentSeq->number; + A(parentRow, col) = static_cast(1); + A(row, col) = static_cast(-1); + } + } + for (auto seq : sequences2) { + auto parentSeq = seq->parent.lock(); + if (parentSeq) { + const int row = ns1 + seq->GetInfoset()->GetNumber() + 1; + const int col = seq->number; + const int parentCol = parentSeq->number; + A(row, parentCol) = static_cast(-1); + A(row, col) = static_cast(1); + } + } + for (auto seq1 : sequences1) { + for (auto seq2 : sequences2) { + const int row = seq1->number; + const int col = seq2->number; + std::map profile; + profile[player1] = seq1; + profile[player2] = seq2; + A(row, col) = p_game->GetPayoff(profile, player1) - minpay; + } + } +} + // // Recursively construct the behavior profile from the sequence form // solution represented by 'p_primal' (containing player 2's @@ -133,6 +178,29 @@ void GameData::GetBehavior(MixedBehaviorProfile &v, const Array &p_prim } } +template +MixedBehaviorProfile GameData::GetBehaviorNew(const Array &p_primal, + const Array &p_dual, const Game &p_game) +{ + auto players = p_game->GetPlayers(); + auto it = players.begin(); + auto player1 = *it; + ++it; + auto player2 = *it; + auto sequences1 = p_game->GetSequences(player1); + auto sequences2 = p_game->GetSequences(player2); + Gambit::MixedSequenceProfile msp(p_game); + for (auto seq : sequences1) { + int index = seq->number; + msp[seq] = p_dual[index]; + } + for (auto seq : sequences2) { + int index = seq->number; + msp[seq] = p_primal[index]; + } + return msp.GetMixedBehaviorProfile(); +} + // // The routine to actually solve the LP // This routine takes an LP of the form @@ -187,7 +255,11 @@ std::list> LpBehaviorSolve(const Game &p_game, b = static_cast(0); c = static_cast(0); - data.FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1); + // BehaviorSupportProfile full_support(p_game); + // Gambit::GameSequenceForm sequenceForm(full_support); + + // data.FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1); + data.FillTableauNew(A, p_game); A(1, data.ns2 + 1) = static_cast(-1); A(data.ns1 + 1, 1) = static_cast(1); @@ -197,8 +269,9 @@ std::list> LpBehaviorSolve(const Game &p_game, Array primal(A.NumColumns()), dual(A.NumRows()); std::list> solution; SolveLP(A, b, c, p_game->GetPlayer(2)->GetInfosets().size() + 1, primal, dual); - MixedBehaviorProfile profile(p_game); - data.GetBehavior(profile, primal, dual, p_game->GetRoot(), 1, 1); + // MixedBehaviorProfile profile(p_game); + // data.GetBehavior(profile, primal, dual, p_game->GetRoot(), 1, 1); + MixedBehaviorProfile profile = data.GetBehaviorNew(primal, dual, p_game); profile.UndefinedToCentroid(); p_onEquilibrium(profile, "NE"); solution.push_back(profile); From e224052db77e76928446e3b9c97a4774dd9cb871 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 12:00:26 +0000 Subject: [PATCH 12/36] Fixed LP and LCP --- src/games/behavspt.cc | 6 ++++++ src/games/behavspt.h | 1 + src/games/game.h | 7 ++++++- src/games/gameseq.cc | 2 ++ src/games/gameseq.h | 6 ++++++ src/games/gametree.h | 5 +++++ src/solvers/lcp/efglcp.cc | 6 ++++-- src/solvers/lp/lp.cc | 4 +++- 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index dea79df61..90ab47d8d 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -198,6 +198,12 @@ int BehaviorSupportProfile::GetConstraintEntry(const GameInfoset &p_infoset, return GetSequenceForm()->GetConstraintEntry(p_infoset, p_action); } +uint8_t & +BehaviorSupportProfile::IsOutcome(const std::map &p_profile) const +{ + return GetSequenceForm()->IsOutcome(p_profile); +} + const Rational & BehaviorSupportProfile::GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const diff --git a/src/games/behavspt.h b/src/games/behavspt.h index e1643f097..bda5f7a85 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -201,6 +201,7 @@ class BehaviorSupportProfile { int GetConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) const; const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const; + uint8_t &IsOutcome(const std::map &p_profile) const; GameRep::Players GetPlayers() const { return GetGame()->GetPlayers(); } MixedBehaviorProfile ToMixedBehaviorProfile(const std::map &) const; diff --git a/src/games/game.h b/src/games/game.h index 5f1396a0a..b3b0cfe6d 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1016,9 +1016,14 @@ class GameRep : public std::enable_shared_from_this { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } + virtual const uint8_t &IsOutcome(const std::map &p_profile) + { + throw std::runtime_error("Sequence form can only be generated for extensive form games"); + } + virtual const PlayerSequences GetSequences(const GamePlayer &p_player) { - throw std::runtime_error("Sequence form can only be generated for extensive"); + throw std::runtime_error("Sequence form can only be generated for extensive form games"); } virtual const Rational &GetPayoff(const std::map &p_profile, diff --git a/src/games/gameseq.cc b/src/games/gameseq.cc index 367a7ec7c..18fa9f73e 100644 --- a/src/games/gameseq.cc +++ b/src/games/gameseq.cc @@ -70,6 +70,7 @@ void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, std::map &p_currentSequences) { if (n->GetOutcome()) { + IsOutcome(p_currentSequences) = 1; for (auto player : m_support.GetGame()->GetPlayers()) { GetPayoffEntry(p_currentSequences, player) += prob * n->GetOutcome()->GetPayoff(player); @@ -104,6 +105,7 @@ void GameSequenceForm::FillTableau() dim[player->GetNumber()] = m_sequences.at(player).size(); } m_payoffs = NDArray(dim, dim.size()); + m_isOutcome = NDArray(dim, dim.size()); std::map currentSequence; for (auto player : GetPlayers()) { diff --git a/src/games/gameseq.h b/src/games/gameseq.h index 9e3e414d9..98e7e0199 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -36,6 +36,7 @@ class GameSequenceForm { BehaviorSupportProfile m_support; std::map> m_sequences; NDArray m_payoffs; + NDArray m_isOutcome; std::map, int> m_constraints; // (sparse) constraint matrices std::set m_infosets; // infosets actually reachable given support std::map m_correspondence; @@ -54,6 +55,11 @@ class GameSequenceForm { return index; } + uint8_t &IsOutcome(const std::map &p_profile) + { + return m_isOutcome.at(ProfileToIndex(p_profile), 1); + } + Rational &GetPayoffEntry(const std::map &p_profile, const GamePlayer &p_player) { diff --git a/src/games/gametree.h b/src/games/gametree.h index b06f4ace4..d9425b246 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -55,6 +55,11 @@ class GameTreeRep : public GameExplicitRep { return GetFullSupport()->GetSequences(p_player); } + const uint8_t &IsOutcome(const std::map &p_profile) override + { + return GetFullSupport()->IsOutcome(p_profile); + } + const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) override { diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index 71dc019a7..bf22181db 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -346,8 +346,10 @@ void NashLcpBehaviorSolver::FillTableauNew(Matrix &A, const Game &p_game, std::map profile; profile[player1] = seq1; profile[player2] = seq2; - A(s1, ns1 + s2) = p_game->GetPayoff(profile, player1) - p_solution.maxpay; - A(ns1 + s2, s1) = p_game->GetPayoff(profile, player2) - p_solution.maxpay; + if (p_game->IsOutcome(profile)) { + A(s1, ns1 + s2) = p_game->GetPayoff(profile, player1) - p_solution.maxpay; + A(ns1 + s2, s1) = p_game->GetPayoff(profile, player2) - p_solution.maxpay; + } } } } diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc index af674c3c7..fde11a6c5 100644 --- a/src/solvers/lp/lp.cc +++ b/src/solvers/lp/lp.cc @@ -134,7 +134,9 @@ template void GameData::FillTableauNew(Matrix &A, const Game &p_ std::map profile; profile[player1] = seq1; profile[player2] = seq2; - A(row, col) = p_game->GetPayoff(profile, player1) - minpay; + if (p_game->IsOutcome(profile)) { + A(row, col) = p_game->GetPayoff(profile, player1) - minpay; + } } } } From 5fa380b4a42b1403e659e6b4087d3af3372c4732 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 12:24:37 +0000 Subject: [PATCH 13/36] Cleaned up LP --- src/solvers/lp/lp.cc | 119 +++++-------------------------------------- 1 file changed, 12 insertions(+), 107 deletions(-) diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc index fde11a6c5..eef21a59b 100644 --- a/src/solvers/lp/lp.cc +++ b/src/solvers/lp/lp.cc @@ -35,13 +35,10 @@ template class GameData { explicit GameData(const Game &); - void FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2); - void FillTableauNew(Matrix &A, const Game &p_game); + void FillTableau(Matrix &A, const Game &p_game); - void GetBehavior(MixedBehaviorProfile &v, const Array &, const Array &, - const GameNode &, int, int); - MixedBehaviorProfile GetBehaviorNew(const Array &p_primal, const Array &p_dual, - const Game &p_game); + MixedBehaviorProfile GetBehavior(const Array &p_primal, const Array &p_dual, + const Game &p_game); }; template GameData::GameData(const Game &p_game) : minpay(p_game->GetMinPayoff()) @@ -57,54 +54,10 @@ template GameData::GameData(const Game &p_game) : minpay(p_game->Ge } } -// -// Recursively fills the constraint matrix A for the subtree rooted at 'n'. -// -template -void GameData::FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2) +template void GameData::FillTableau(Matrix &A, const Game &p_game) { - const GameOutcome outcome = n->GetOutcome(); - if (outcome) { - A(s1, s2) += - Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(1)) - minpay); - } - if (n->IsTerminal()) { - return; - } - const GameInfoset infoset = n->GetInfoset(); - if (n->GetPlayer()->IsChance()) { - for (const auto &action : infoset->GetActions()) { - FillTableau(A, n->GetChild(action), prob * static_cast(infoset->GetActionProb(action)), - s1, s2); - } - } - else if (n->GetPlayer()->GetNumber() == 1) { - const int col = ns2 + infoset->GetNumber() + 1; - int snew = infosetOffset.at(infoset); - A(s1, col) = static_cast(1); - for (const auto &child : n->GetChildren()) { - A(++snew, col) = static_cast(-1); - FillTableau(A, child, prob, snew, s2); - } - } - else { - const int row = ns1 + infoset->GetNumber() + 1; - int snew = infosetOffset.at(infoset); - A(row, s2) = static_cast(-1); - for (const auto &child : n->GetChildren()) { - A(row, ++snew) = static_cast(1); - FillTableau(A, child, prob, s1, snew); - } - } -} - -template void GameData::FillTableauNew(Matrix &A, const Game &p_game) -{ - auto players = p_game->GetPlayers(); - auto it = players.begin(); - auto player1 = *it; - ++it; - auto player2 = *it; + auto player1 = p_game->GetPlayer(1); + auto player2 = p_game->GetPlayer(2); auto sequences1 = p_game->GetSequences(player1); auto sequences2 = p_game->GetSequences(player2); for (auto seq : sequences1) { @@ -141,54 +94,12 @@ template void GameData::FillTableauNew(Matrix &A, const Game &p_ } } -// -// Recursively construct the behavior profile from the sequence form -// solution represented by 'p_primal' (containing player 2's -// sequences) and 'p_dual' (containing player 1's sequences). -// -// Any information sets not reached with positive probability have -// their action probabilities set to zero. -// -template -void GameData::GetBehavior(MixedBehaviorProfile &v, const Array &p_primal, - const Array &p_dual, const GameNode &n, int s1, int s2) -{ - if (n->IsTerminal()) { - return; - } - if (n->GetPlayer()->IsChance()) { - for (const auto &child : n->GetChildren()) { - GetBehavior(v, p_primal, p_dual, child, s1, s2); - } - } - else if (n->GetPlayer()->GetNumber() == 2) { - int snew = infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = - (p_primal[s1] > static_cast(0)) ? p_primal[snew] / p_primal[s1] : static_cast(0); - GetBehavior(v, p_primal, p_dual, n->GetChild(action), snew, s2); - } - } - else { - int snew = infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = (p_dual[s2] > static_cast(0)) ? p_dual[snew] / p_dual[s2] : static_cast(0); - GetBehavior(v, p_primal, p_dual, n->GetChild(action), s1, snew); - } - } -} - template -MixedBehaviorProfile GameData::GetBehaviorNew(const Array &p_primal, - const Array &p_dual, const Game &p_game) +MixedBehaviorProfile GameData::GetBehavior(const Array &p_primal, const Array &p_dual, + const Game &p_game) { - auto players = p_game->GetPlayers(); - auto it = players.begin(); - auto player1 = *it; - ++it; - auto player2 = *it; + auto player1 = p_game->GetPlayer(1); + auto player2 = p_game->GetPlayer(2); auto sequences1 = p_game->GetSequences(player1); auto sequences2 = p_game->GetSequences(player2); Gambit::MixedSequenceProfile msp(p_game); @@ -257,11 +168,7 @@ std::list> LpBehaviorSolve(const Game &p_game, b = static_cast(0); c = static_cast(0); - // BehaviorSupportProfile full_support(p_game); - // Gambit::GameSequenceForm sequenceForm(full_support); - - // data.FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1); - data.FillTableauNew(A, p_game); + data.FillTableau(A, p_game); A(1, data.ns2 + 1) = static_cast(-1); A(data.ns1 + 1, 1) = static_cast(1); @@ -271,9 +178,7 @@ std::list> LpBehaviorSolve(const Game &p_game, Array primal(A.NumColumns()), dual(A.NumRows()); std::list> solution; SolveLP(A, b, c, p_game->GetPlayer(2)->GetInfosets().size() + 1, primal, dual); - // MixedBehaviorProfile profile(p_game); - // data.GetBehavior(profile, primal, dual, p_game->GetRoot(), 1, 1); - MixedBehaviorProfile profile = data.GetBehaviorNew(primal, dual, p_game); + MixedBehaviorProfile profile = data.GetBehavior(primal, dual, p_game); profile.UndefinedToCentroid(); p_onEquilibrium(profile, "NE"); solution.push_back(profile); From fbc88cc2a8b33725695f4e379a6c9550d1efdb91 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 13:09:28 +0000 Subject: [PATCH 14/36] Cleaned up LCP --- src/solvers/lcp/efglcp.cc | 149 ++++---------------------------------- 1 file changed, 13 insertions(+), 136 deletions(-) diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index bf22181db..0d86e32e6 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -45,14 +45,11 @@ template class NashLcpBehaviorSolver { class Solution; - void FillTableau(Matrix &, const GameNode &, T, int, int, Solution &) const; - void FillTableauNew(Matrix &A, const Game &p_game, Solution &p_solution) const; + void FillTableau(Matrix &A, const Game &p_game, Solution &p_solution) const; void AllLemke(const Game &, int dup, linalg::LemkeTableau &B, int depth, Matrix &, Solution &) const; - void GetProfile(const linalg::LemkeTableau &tab, MixedBehaviorProfile &, const Vector &, - const GameNode &n, int, int, Solution &) const; - MixedBehaviorProfile GetProfileNew(const linalg::LemkeTableau &tab, const Vector &sol, - const Game &p_game, Solution &p_solution) const; + MixedBehaviorProfile GetProfile(const linalg::LemkeTableau &tab, const Vector &sol, + const Game &p_game, Solution &p_solution) const; }; template class NashLcpBehaviorSolver::Solution { @@ -131,11 +128,7 @@ std::list> NashLcpBehaviorSolver::Solve(const Game &p Matrix A(1, ntot, 0, ntot); A = static_cast(0); - // BehaviorSupportProfile full_support(p_game); - // Gambit::GameSequenceForm sequenceForm(full_support); - - // FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1, solution); - FillTableauNew(A, p_game, solution); + FillTableau(A, p_game, solution); for (int i = A.MinRow(); i <= A.MaxRow(); i++) { A(i, 0) = static_cast(-1); } @@ -167,9 +160,7 @@ std::list> NashLcpBehaviorSolver::Solve(const Game &p solution.AddBFS(tab); Vector sol(tab.MinRow(), tab.MaxRow()); tab.BasisVector(sol); - // MixedBehaviorProfile profile(p_game); - // GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution); - auto profile = GetProfileNew(tab, sol, p_game, solution); + auto profile = GetProfile(tab, sol, p_game, solution); profile.UndefinedToCentroid(); solution.m_equilibria.push_back(profile); this->m_onEquilibrium(profile, "NE"); @@ -197,7 +188,6 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke } Vector sol(B.MinRow(), B.MaxRow()); - // MixedBehaviorProfile profile(p_game); bool newsol = false; for (int i = B.MinRow(); i <= B.MaxRow() && !newsol; i++) { @@ -224,10 +214,7 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke if (BCopy.SF_LCPPath(-missing) == 1) { newsol = p_solution.AddBFS(BCopy); BCopy.BasisVector(sol); - // BehaviorSupportProfile full_support(p_game); - // Gambit::GameSequenceForm sequenceForm(full_support); - // GetProfile(BCopy, profile, sol, p_game->GetRoot(), 1, 1, p_solution); - auto profile = GetProfileNew(BCopy, sol, p_game, p_solution); + auto profile = GetProfile(BCopy, sol, p_game, p_solution); profile.UndefinedToCentroid(); if (newsol) { m_onEquilibrium(profile, "NE"); @@ -250,69 +237,14 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke } template -void NashLcpBehaviorSolver::FillTableau(Matrix &A, const GameNode &n, T prob, int s1, int s2, +void NashLcpBehaviorSolver::FillTableau(Matrix &A, const Game &p_game, Solution &p_solution) const { const int ns1 = p_solution.ns1; const int ns2 = p_solution.ns2; const int ni1 = p_solution.ni1; - - const GameOutcome outcome = n->GetOutcome(); - if (outcome) { - A(s1, ns1 + s2) += Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(1)) - - p_solution.maxpay); - A(ns1 + s2, s1) += Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(2)) - - p_solution.maxpay); - } - if (n->IsTerminal()) { - return; - } - const GameInfoset infoset = n->GetInfoset(); - if (n->GetPlayer()->IsChance()) { - for (const auto &action : infoset->GetActions()) { - FillTableau(A, n->GetChild(action), - Rational(prob) * static_cast(infoset->GetActionProb(action)), s1, s2, - p_solution); - } - } - else if (n->GetPlayer()->GetNumber() == 1) { - const int infoset_idx = ns1 + ns2 + infoset->GetNumber() + 1; - A(s1, infoset_idx) = static_cast(-1); - A(infoset_idx, s1) = static_cast(1); - int snew = p_solution.infosetOffset.at(infoset); - for (const auto &child : n->GetChildren()) { - snew++; - A(snew, infoset_idx) = static_cast(1); - A(infoset_idx, snew) = static_cast(-1); - FillTableau(A, child, prob, snew, s2, p_solution); - } - } - else { - const int infoset_idx = ns1 + ns2 + ni1 + n->GetInfoset()->GetNumber() + 1; - A(ns1 + s2, infoset_idx) = static_cast(-1); - A(infoset_idx, ns1 + s2) = static_cast(1); - int snew = p_solution.infosetOffset.at(n->GetInfoset()); - for (const auto &child : n->GetChildren()) { - snew++; - A(ns1 + snew, infoset_idx) = static_cast(1); - A(infoset_idx, ns1 + snew) = static_cast(-1); - FillTableau(A, child, prob, s1, snew, p_solution); - } - } -} - -template -void NashLcpBehaviorSolver::FillTableauNew(Matrix &A, const Game &p_game, - Solution &p_solution) const -{ - const int ns1 = p_solution.ns1; - const int ns2 = p_solution.ns2; - const int ni1 = p_solution.ni1; - auto players = p_game->GetPlayers(); - auto it = players.begin(); - auto player1 = *it; - ++it; - auto player2 = *it; + auto player1 = p_game->GetPlayer(1); + auto player2 = p_game->GetPlayer(2); auto sequences1 = p_game->GetSequences(player1); auto sequences2 = p_game->GetSequences(player2); for (auto seq : sequences1) { @@ -354,69 +286,14 @@ void NashLcpBehaviorSolver::FillTableauNew(Matrix &A, const Game &p_game, } } -template -void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, - MixedBehaviorProfile &v, const Vector &sol, - const GameNode &n, int s1, int s2, - Solution &p_solution) const -{ - const int ns1 = p_solution.ns1; - - if (n->IsTerminal()) { - return; - } - if (n->GetPlayer()->IsChance()) { - for (const auto &child : n->GetChildren()) { - GetProfile(tab, v, sol, child, s1, s2, p_solution); - } - } - else if (n->GetPlayer()->GetNumber() == 1) { - int snew = p_solution.infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = static_cast(0); - if (tab.Member(s1)) { - const int ind = tab.Find(s1); - if (sol[ind] > p_solution.eps && tab.Member(snew)) { - const int ind2 = tab.Find(snew); - if (sol[ind2] > p_solution.eps) { - v[action] = sol[ind2] / sol[ind]; - } - } - } - GetProfile(tab, v, sol, n->GetChild(action), snew, s2, p_solution); - } - } - else { - int snew = p_solution.infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = static_cast(0); - if (tab.Member(ns1 + s2)) { - const int ind = tab.Find(ns1 + s2); - if (sol[ind] > p_solution.eps && tab.Member(ns1 + snew)) { - const int ind2 = tab.Find(ns1 + snew); - if (sol[ind2] > p_solution.eps) { - v[action] = sol[ind2] / sol[ind]; - } - } - } - GetProfile(tab, v, sol, n->GetChild(action), s1, snew, p_solution); - } - } -} - template MixedBehaviorProfile -NashLcpBehaviorSolver::GetProfileNew(const linalg::LemkeTableau &tab, const Vector &sol, - const Game &p_game, Solution &p_solution) const +NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, const Vector &sol, + const Game &p_game, Solution &p_solution) const { const int ns1 = p_solution.ns1; - auto players = p_game->GetPlayers(); - auto it = players.begin(); - auto player1 = *it; - ++it; - auto player2 = *it; + auto player1 = p_game->GetPlayer(1); + auto player2 = p_game->GetPlayer(2); auto sequences1 = p_game->GetSequences(player1); auto sequences2 = p_game->GetSequences(player2); Gambit::MixedSequenceProfile msp(p_game); From 408ac5d61760324a6635777dedde6489f9ac1bfb Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 13:18:18 +0000 Subject: [PATCH 15/36] Removed GetConstraintEntry from BSP (now just GetSequenceConstraintEntry) --- src/games/behavspt.cc | 6 ------ src/games/behavspt.h | 1 - src/solvers/enumpoly/efgpoly.cc | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 90ab47d8d..0a2ea839e 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -192,12 +192,6 @@ PlayerSequences BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) return {this, p_player}; } -int BehaviorSupportProfile::GetConstraintEntry(const GameInfoset &p_infoset, - const GameAction &p_action) const -{ - return GetSequenceForm()->GetConstraintEntry(p_infoset, p_action); -} - uint8_t & BehaviorSupportProfile::IsOutcome(const std::map &p_profile) const { diff --git a/src/games/behavspt.h b/src/games/behavspt.h index bda5f7a85..0586e8f46 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -198,7 +198,6 @@ class BehaviorSupportProfile { std::shared_ptr GetSequenceForm() const; Sequences GetSequences() const; PlayerSequences GetSequences(const GamePlayer &p_player) const; - int GetConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) const; const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const; uint8_t &IsOutcome(const std::map &p_profile) const; diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index e88cca353..02bd2fa92 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -72,7 +72,7 @@ Polynomial BuildSequenceVariable(ProblemData &p_data, const GameSequence continue; } if (const int constraint_coef = - p_data.m_support.GetConstraintEntry(p_sequence->GetInfoset(), seq->action)) { + p_data.m_support.GetSequenceConstraintEntry(p_sequence->GetInfoset(), seq->action)) { equation += BuildSequenceVariable(p_data, seq, var) * double(constraint_coef); } } From 811c8825e2cc9c1d6e574543a77eaad533b383ce Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 13:52:29 +0000 Subject: [PATCH 16/36] Changed uint8_t to int --- src/games/behavspt.cc | 3 +-- src/games/behavspt.h | 2 +- src/games/game.h | 2 +- src/games/gameseq.cc | 2 +- src/games/gameseq.h | 4 ++-- src/games/gametree.h | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 0a2ea839e..75a74f14b 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -192,8 +192,7 @@ PlayerSequences BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) return {this, p_player}; } -uint8_t & -BehaviorSupportProfile::IsOutcome(const std::map &p_profile) const +int &BehaviorSupportProfile::IsOutcome(const std::map &p_profile) const { return GetSequenceForm()->IsOutcome(p_profile); } diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 0586e8f46..ee8da3926 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -200,7 +200,7 @@ class BehaviorSupportProfile { PlayerSequences GetSequences(const GamePlayer &p_player) const; const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const; - uint8_t &IsOutcome(const std::map &p_profile) const; + int &IsOutcome(const std::map &p_profile) const; GameRep::Players GetPlayers() const { return GetGame()->GetPlayers(); } MixedBehaviorProfile ToMixedBehaviorProfile(const std::map &) const; diff --git a/src/games/game.h b/src/games/game.h index b3b0cfe6d..7478cb4c2 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1016,7 +1016,7 @@ class GameRep : public std::enable_shared_from_this { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } - virtual const uint8_t &IsOutcome(const std::map &p_profile) + virtual const int &IsOutcome(const std::map &p_profile) { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } diff --git a/src/games/gameseq.cc b/src/games/gameseq.cc index 18fa9f73e..cdb9971c9 100644 --- a/src/games/gameseq.cc +++ b/src/games/gameseq.cc @@ -105,7 +105,7 @@ void GameSequenceForm::FillTableau() dim[player->GetNumber()] = m_sequences.at(player).size(); } m_payoffs = NDArray(dim, dim.size()); - m_isOutcome = NDArray(dim, dim.size()); + m_isOutcome = NDArray(dim, dim.size()); std::map currentSequence; for (auto player : GetPlayers()) { diff --git a/src/games/gameseq.h b/src/games/gameseq.h index 98e7e0199..87ddfc55c 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -36,7 +36,7 @@ class GameSequenceForm { BehaviorSupportProfile m_support; std::map> m_sequences; NDArray m_payoffs; - NDArray m_isOutcome; + NDArray m_isOutcome; std::map, int> m_constraints; // (sparse) constraint matrices std::set m_infosets; // infosets actually reachable given support std::map m_correspondence; @@ -55,7 +55,7 @@ class GameSequenceForm { return index; } - uint8_t &IsOutcome(const std::map &p_profile) + int &IsOutcome(const std::map &p_profile) { return m_isOutcome.at(ProfileToIndex(p_profile), 1); } diff --git a/src/games/gametree.h b/src/games/gametree.h index d9425b246..317755c54 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -55,7 +55,7 @@ class GameTreeRep : public GameExplicitRep { return GetFullSupport()->GetSequences(p_player); } - const uint8_t &IsOutcome(const std::map &p_profile) override + const int &IsOutcome(const std::map &p_profile) override { return GetFullSupport()->IsOutcome(p_profile); } From 54bfe423b7f54aff9817bd6a9643632931065368 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 14:09:54 +0000 Subject: [PATCH 17/36] Used member initialiser to fix clang-tidy error --- src/games/game.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/game.h b/src/games/game.h index 7478cb4c2..8ecde84d8 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1181,7 +1181,7 @@ template class MixedSequenceProfile { Game m_game; public: - MixedSequenceProfile(const Game &p_game) { m_game = p_game; } + MixedSequenceProfile(const Game &p_game) : m_game(p_game) {} const T &operator[](const GameSequence &p_key) const { return probs.at(p_key); } From 0205b5c0172c564344ff4c60fe771987de992467 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 28 Nov 2025 15:07:50 +0000 Subject: [PATCH 18/36] Changed int to const int for clang-tidy --- src/solvers/lcp/efglcp.cc | 8 ++++---- src/solvers/lp/lp.cc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index 0d86e32e6..1e68c82c3 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -298,9 +298,9 @@ NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, const V auto sequences2 = p_game->GetSequences(player2); Gambit::MixedSequenceProfile msp(p_game); for (auto seq : sequences1) { - int seq_num = seq->number; + const int seq_num = seq->number; if (tab.Member(seq_num)) { - int index = tab.Find(seq_num); + const int index = tab.Find(seq_num); msp[seq] = (sol[index] > p_solution.eps) ? sol[index] : static_cast(0); } else { @@ -308,9 +308,9 @@ NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, const V } } for (auto seq : sequences2) { - int seq_num = seq->number; + const int seq_num = seq->number; if (tab.Member(ns1 + seq_num)) { - int index = tab.Find(ns1 + seq_num); + const int index = tab.Find(ns1 + seq_num); msp[seq] = (sol[index] > p_solution.eps) ? sol[index] : static_cast(0); } else { diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc index eef21a59b..e52a09c7e 100644 --- a/src/solvers/lp/lp.cc +++ b/src/solvers/lp/lp.cc @@ -104,11 +104,11 @@ MixedBehaviorProfile GameData::GetBehavior(const Array &p_primal, const auto sequences2 = p_game->GetSequences(player2); Gambit::MixedSequenceProfile msp(p_game); for (auto seq : sequences1) { - int index = seq->number; + const int index = seq->number; msp[seq] = p_dual[index]; } for (auto seq : sequences2) { - int index = seq->number; + const int index = seq->number; msp[seq] = p_primal[index]; } return msp.GetMixedBehaviorProfile(); From 2d88e5f78186862b5b19f08f107a79705e6c4a15 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Wed, 10 Dec 2025 13:28:12 +0000 Subject: [PATCH 19/36] Added terminal prob code --- src/games/behavspt.cc | 5 +++-- src/games/behavspt.h | 2 +- src/games/game.h | 2 +- src/games/gameseq.cc | 4 ++-- src/games/gameseq.h | 6 +++--- src/games/gametree.h | 4 ++-- src/solvers/lcp/efglcp.cc | 8 ++++---- src/solvers/lp/lp.cc | 5 ++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 75a74f14b..555016215 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -192,9 +192,10 @@ PlayerSequences BehaviorSupportProfile::GetSequences(const GamePlayer &p_player) return {this, p_player}; } -int &BehaviorSupportProfile::IsOutcome(const std::map &p_profile) const +const Rational & +BehaviorSupportProfile::GetTerminalProb(const std::map &p_profile) const { - return GetSequenceForm()->IsOutcome(p_profile); + return GetSequenceForm()->GetTerminalProb(p_profile); } const Rational & diff --git a/src/games/behavspt.h b/src/games/behavspt.h index ee8da3926..52c6a0da5 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -200,7 +200,7 @@ class BehaviorSupportProfile { PlayerSequences GetSequences(const GamePlayer &p_player) const; const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const; - int &IsOutcome(const std::map &p_profile) const; + const Rational &GetTerminalProb(const std::map &p_profile) const; GameRep::Players GetPlayers() const { return GetGame()->GetPlayers(); } MixedBehaviorProfile ToMixedBehaviorProfile(const std::map &) const; diff --git a/src/games/game.h b/src/games/game.h index 8ecde84d8..4d52df73f 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1016,7 +1016,7 @@ class GameRep : public std::enable_shared_from_this { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } - virtual const int &IsOutcome(const std::map &p_profile) + virtual const Rational &GetTerminalProb(const std::map &p_profile) { throw std::runtime_error("Sequence form can only be generated for extensive form games"); } diff --git a/src/games/gameseq.cc b/src/games/gameseq.cc index cdb9971c9..a95736cd1 100644 --- a/src/games/gameseq.cc +++ b/src/games/gameseq.cc @@ -70,13 +70,13 @@ void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, std::map &p_currentSequences) { if (n->GetOutcome()) { - IsOutcome(p_currentSequences) = 1; for (auto player : m_support.GetGame()->GetPlayers()) { GetPayoffEntry(p_currentSequences, player) += prob * n->GetOutcome()->GetPayoff(player); } } if (!n->GetInfoset()) { + GetTerminalProb(p_currentSequences) += prob; return; } if (n->GetPlayer()->IsChance()) { @@ -105,7 +105,7 @@ void GameSequenceForm::FillTableau() dim[player->GetNumber()] = m_sequences.at(player).size(); } m_payoffs = NDArray(dim, dim.size()); - m_isOutcome = NDArray(dim, dim.size()); + m_terminalProb = NDArray(dim, dim.size()); std::map currentSequence; for (auto player : GetPlayers()) { diff --git a/src/games/gameseq.h b/src/games/gameseq.h index 87ddfc55c..8441d9445 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -36,7 +36,7 @@ class GameSequenceForm { BehaviorSupportProfile m_support; std::map> m_sequences; NDArray m_payoffs; - NDArray m_isOutcome; + NDArray m_terminalProb; std::map, int> m_constraints; // (sparse) constraint matrices std::set m_infosets; // infosets actually reachable given support std::map m_correspondence; @@ -55,9 +55,9 @@ class GameSequenceForm { return index; } - int &IsOutcome(const std::map &p_profile) + Rational &GetTerminalProb(const std::map &p_profile) { - return m_isOutcome.at(ProfileToIndex(p_profile), 1); + return m_terminalProb.at(ProfileToIndex(p_profile), 1); } Rational &GetPayoffEntry(const std::map &p_profile, diff --git a/src/games/gametree.h b/src/games/gametree.h index 317755c54..e4819d417 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -55,9 +55,9 @@ class GameTreeRep : public GameExplicitRep { return GetFullSupport()->GetSequences(p_player); } - const int &IsOutcome(const std::map &p_profile) override + const Rational &GetTerminalProb(const std::map &p_profile) override { - return GetFullSupport()->IsOutcome(p_profile); + return GetFullSupport()->GetTerminalProb(p_profile); } const Rational &GetPayoff(const std::map &p_profile, diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index 1e68c82c3..e85347b72 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -278,10 +278,10 @@ void NashLcpBehaviorSolver::FillTableau(Matrix &A, const Game &p_game, std::map profile; profile[player1] = seq1; profile[player2] = seq2; - if (p_game->IsOutcome(profile)) { - A(s1, ns1 + s2) = p_game->GetPayoff(profile, player1) - p_solution.maxpay; - A(ns1 + s2, s1) = p_game->GetPayoff(profile, player2) - p_solution.maxpay; - } + A(s1, ns1 + s2) = p_game->GetPayoff(profile, player1) - + (p_solution.maxpay * p_game->GetTerminalProb(profile)); + A(ns1 + s2, s1) = p_game->GetPayoff(profile, player2) - + (p_solution.maxpay * p_game->GetTerminalProb(profile)); } } } diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc index e52a09c7e..3ade68f02 100644 --- a/src/solvers/lp/lp.cc +++ b/src/solvers/lp/lp.cc @@ -87,9 +87,8 @@ template void GameData::FillTableau(Matrix &A, const Game &p_gam std::map profile; profile[player1] = seq1; profile[player2] = seq2; - if (p_game->IsOutcome(profile)) { - A(row, col) = p_game->GetPayoff(profile, player1) - minpay; - } + A(row, col) = + p_game->GetPayoff(profile, player1) - (minpay * p_game->GetTerminalProb(profile)); } } } From 7537624eb28987f64c7bcc85ace772a9978b09a4 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Wed, 10 Dec 2025 16:30:39 +0000 Subject: [PATCH 20/36] resolved conflict in gametree.h --- src/games/gametree.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/games/gametree.h b/src/games/gametree.h index e4819d417..26778498c 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -47,6 +47,15 @@ class GameTreeRep : public GameExplicitRep { std::map> m_nodePlays; mutable std::shared_ptr m_ownPriorActionInfo; mutable std::unique_ptr> m_unreachableNodes; + mutable std::shared_ptr m_fullSupport; + + std::shared_ptr GetFullSupport() override + { + if (!m_fullSupport) { + m_fullSupport = std::make_shared(shared_from_this()); + } + return m_fullSupport; + } const Sequences GetSequences() override { return GetFullSupport()->GetSequences(); } From 36ccf6a3f25264deaaa5cfdcdd94ba446797afdf Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Thu, 11 Dec 2025 19:01:01 +0000 Subject: [PATCH 21/36] pushed payoffs to terminal nodes in sequence form object --- src/games/gameseq.cc | 19 +++++++++++++------ src/games/gameseq.h | 3 ++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/games/gameseq.cc b/src/games/gameseq.cc index a95736cd1..ce68b876c 100644 --- a/src/games/gameseq.cc +++ b/src/games/gameseq.cc @@ -67,23 +67,28 @@ void GameSequenceForm::BuildSequences() } void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, - std::map &p_currentSequences) + std::map &p_currentSequences, + std::map p_cumPayoff) { if (n->GetOutcome()) { for (auto player : m_support.GetGame()->GetPlayers()) { - GetPayoffEntry(p_currentSequences, player) += - prob * n->GetOutcome()->GetPayoff(player); + p_cumPayoff[player] += n->GetOutcome()->GetPayoff(player); + // GetPayoffEntry(p_currentSequences, player) += + // prob * n->GetOutcome()->GetPayoff(player); } } if (!n->GetInfoset()) { GetTerminalProb(p_currentSequences) += prob; + for (auto player : m_support.GetGame()->GetPlayers()) { + GetPayoffEntry(p_currentSequences, player) += prob * p_cumPayoff[player]; + } return; } if (n->GetPlayer()->IsChance()) { for (auto action : n->GetInfoset()->GetActions()) { FillTableau(n->GetChild(action), prob * static_cast(n->GetInfoset()->GetActionProb(action)), - p_currentSequences); + p_currentSequences, p_cumPayoff); } } else { @@ -92,7 +97,7 @@ void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, for (auto action : m_support.GetActions(n->GetInfoset())) { m_constraints[{n->GetInfoset(), action}] = -1; p_currentSequences[n->GetPlayer()] = m_correspondence.at(action); - FillTableau(n->GetChild(action), prob, p_currentSequences); + FillTableau(n->GetChild(action), prob, p_currentSequences, p_cumPayoff); } p_currentSequences[n->GetPlayer()] = tmp_sequence; } @@ -108,10 +113,12 @@ void GameSequenceForm::FillTableau() m_terminalProb = NDArray(dim, dim.size()); std::map currentSequence; + std::map cumPayoff; for (auto player : GetPlayers()) { currentSequence[player] = m_sequences[player].front(); + cumPayoff[player] = Rational(0); } - FillTableau(m_support.GetGame()->GetRoot(), Rational(1), currentSequence); + FillTableau(m_support.GetGame()->GetRoot(), Rational(1), currentSequence, cumPayoff); } } // end namespace Gambit diff --git a/src/games/gameseq.h b/src/games/gameseq.h index 8441d9445..8815c3571 100644 --- a/src/games/gameseq.h +++ b/src/games/gameseq.h @@ -44,7 +44,8 @@ class GameSequenceForm { void BuildSequences(); void BuildSequences(const GameNode &, std::map &); void FillTableau(); - void FillTableau(const GameNode &, const Rational &, std::map &); + void FillTableau(const GameNode &, const Rational &, std::map &, + std::map); Array ProfileToIndex(const std::map &p_profile) const { From b728f3a3f9d84e16ee5803580466fa6c20e0dadd Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 12 Dec 2025 15:41:35 +0000 Subject: [PATCH 22/36] updated nonterm_outcomes kuhn poker test so that it xpasses --- tests/test_nash.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index b436b79a9..7e9f294b5 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -530,12 +530,12 @@ def test_lp_behavior_double(): games.create_kuhn_poker_efg(nonterm_outcomes=True), [ [ - ["2/3", "1/3"], [1, 0], [1, 0], - ["1/3", "2/3"], + [1, 0], + ["2/3", "1/3"], + [1, 0], [0, 1], - ["1/2", "1/2"], ], [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], ], From ead0c23475e1df31d938aa0548c972744d1f7485 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 12 Dec 2025 15:57:53 +0000 Subject: [PATCH 23/36] set m_fullSupport to null when game changes --- src/games/gametree.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 33509228e..bfd43f6b2 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -871,6 +871,7 @@ void GameTreeRep::ClearComputedValues() const m_ownPriorActionInfo = nullptr; const_cast(this)->m_unreachableNodes = nullptr; m_computedValues = false; + m_fullSupport = nullptr; } void GameTreeRep::BuildComputedValues() const From 9204b87d38369417ac6083fcff3b987afb65abd6 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Fri, 12 Dec 2025 16:24:15 +0000 Subject: [PATCH 24/36] removed commented out code from gameseq.cc --- src/games/gameseq.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/games/gameseq.cc b/src/games/gameseq.cc index ce68b876c..24b33abd7 100644 --- a/src/games/gameseq.cc +++ b/src/games/gameseq.cc @@ -73,8 +73,6 @@ void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, if (n->GetOutcome()) { for (auto player : m_support.GetGame()->GetPlayers()) { p_cumPayoff[player] += n->GetOutcome()->GetPayoff(player); - // GetPayoffEntry(p_currentSequences, player) += - // prob * n->GetOutcome()->GetPayoff(player); } } if (!n->GetInfoset()) { From 710ff33c85db42fbd5c18a5611b976506cc16d75 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 09:58:52 +0000 Subject: [PATCH 25/36] added perfect info game with chance to tests --- tests/games.py | 23 +++++++++++++++++++++++ tests/test_nash.py | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/tests/games.py b/tests/games.py index e34591d4d..066286e9c 100644 --- a/tests/games.py +++ b/tests/games.py @@ -123,6 +123,29 @@ def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: return g +def create_perfect_info_with_chance_efg(): + # Tests case in which sequence profile probabilities don't sum to 1 + g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info with chance") + g.append_move(g.root, "1", ["a", "b"]) + g.append_move(g.root.children[0], g.players.chance, ["L", "R"]) + g.append_move(g.root.children[0].children[0], "2", ["A", "B"]) + g.append_move(g.root.children[0].children[1], "2", ["C", "D"]) + g.set_outcome( + g.root.children[0].children[0].children[0], g.add_outcome([-2, 2], label="aLA") + ) + g.set_outcome( + g.root.children[0].children[0].children[1], g.add_outcome([-2, 2], label="aLB") + ) + g.set_outcome( + g.root.children[0].children[1].children[0], g.add_outcome([-2, 2], label="aRC") + ) + g.set_outcome( + g.root.children[0].children[1].children[1], g.add_outcome([-2, 2], label="aRD") + ) + g.set_outcome(g.root.children[1], g.add_outcome([-1, 1], label="b")) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. diff --git a/tests/test_nash.py b/tests/test_nash.py index 7e9f294b5..2233a29ad 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -418,6 +418,11 @@ def test_lcp_behavior_double(): games.create_EFG_for_nxn_bimatrix_coordination_game(4), [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], ), + ( + games.create_perfect_info_with_chance_efg(), + [[[0, 1]], [[0, 1], [0, 1]]], + + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -548,6 +553,10 @@ def test_lp_behavior_double(): [["5/6", "1/6"], ["5/9", "4/9"]], ], ), + ( + games.create_perfect_info_with_chance_efg(), + [[[0, 1]], [[1, 0], [1, 0]]], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 1c0f0e772480c88c6323a6ebffced5684e9514fb Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 11:28:21 +0000 Subject: [PATCH 26/36] added one card poker with lacking outcome to tests --- tests/test_nash.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_nash.py b/tests/test_nash.py index 2233a29ad..0e0485300 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -423,6 +423,10 @@ def test_lcp_behavior_double(): [[[0, 1]], [[0, 1], [0, 1]]], ), + ( + games.create_one_card_poker_lacking_outcome(), + [[["2/3", "1/3"]], [[1, 0], ["1/3", "2/3"]]], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -557,6 +561,10 @@ def test_lp_behavior_double(): games.create_perfect_info_with_chance_efg(), [[[0, 1]], [[1, 0], [1, 0]]], ), + ( + games.create_one_card_poker_lacking_outcome(), + [[["2/3", "1/3"]], [[1, 0], ["1/3", "2/3"]]], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 7d04c1813aadd1ca7609b1d207e559570b286893 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 11:30:04 +0000 Subject: [PATCH 27/36] added one card poker with lacking outcome to tests --- tests/games.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/games.py b/tests/games.py index 066286e9c..21d039ef9 100644 --- a/tests/games.py +++ b/tests/games.py @@ -123,7 +123,7 @@ def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game: return g -def create_perfect_info_with_chance_efg(): +def create_perfect_info_with_chance_efg() -> gbt.Game: # Tests case in which sequence profile probabilities don't sum to 1 g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info with chance") g.append_move(g.root, "1", ["a", "b"]) @@ -146,6 +146,25 @@ def create_perfect_info_with_chance_efg(): return g +def create_one_card_poker_lacking_outcome() -> gbt.Game: + g = gbt.Game.new_tree(players=["Bob", "Alice"], + title="One card poker game, after Myerson (1991)") + g.append_move(g.root, g.players.chance, ["King", "Queen"]) + for node in g.root.children: + g.append_move(node, "Alice", ["Raise", "Fold"]) + g.append_move(g.root.children[0].children[0], "Bob", ["Meet", "Pass"]) + g.append_infoset(g.root.children[1].children[0], + g.root.children[0].children[0].infoset) + alice_winsbig = g.add_outcome([-1, 1], label="Alice wins big") + bob_winsbig = g.add_outcome([3, -3], label="Bob wins big") + bob_wins = g.add_outcome([2, -2], label="Bob wins") + g.set_outcome(g.root.children[0].children[0].children[0], alice_winsbig) + g.set_outcome(g.root.children[0].children[1], bob_wins) + g.set_outcome(g.root.children[1].children[0].children[0], bob_winsbig) + g.set_outcome(g.root.children[1].children[1], bob_wins) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. From 4ffc68e5581ba10fc52bd48c6ffbcc4646ffdd38 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 12:37:59 +0000 Subject: [PATCH 28/36] added one perfect info game with internal outcomes to tests --- tests/games.py | 21 ++++++++++++++++++++- tests/test_nash.py | 13 ++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/games.py b/tests/games.py index 21d039ef9..db376909f 100644 --- a/tests/games.py +++ b/tests/games.py @@ -146,7 +146,7 @@ def create_perfect_info_with_chance_efg() -> gbt.Game: return g -def create_one_card_poker_lacking_outcome() -> gbt.Game: +def create_one_card_poker_lacking_outcome_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["Bob", "Alice"], title="One card poker game, after Myerson (1991)") g.append_move(g.root, g.players.chance, ["King", "Queen"]) @@ -165,6 +165,25 @@ def create_one_card_poker_lacking_outcome() -> gbt.Game: return g +def create_perfect_info_internal_outcomes_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info win lose") + g.append_move(g.root, "2", ["a", "b"]) + g.append_move(g.root.children[0], "1", ["L", "R"]) + g.append_move(g.root.children[1], "1", ["L", "R"]) + g.append_move(g.root.children[0].children[0], "2", ["l", "r"]) + g.set_outcome(g.root.children[0], g.add_outcome([-100, 50], label="a")) + g.set_outcome( + g.root.children[0].children[0].children[0], g.add_outcome([101, -51], label="aLl") + ) + g.set_outcome( + g.root.children[0].children[0].children[1], g.add_outcome([99, -49], label="aLr") + ) + g.set_outcome(g.root.children[0].children[1], g.add_outcome([101, -51], label="aR")) + g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL")) + g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR")) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. diff --git a/tests/test_nash.py b/tests/test_nash.py index 0e0485300..662e31bac 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -421,12 +421,15 @@ def test_lcp_behavior_double(): ( games.create_perfect_info_with_chance_efg(), [[[0, 1]], [[0, 1], [0, 1]]], - ), ( - games.create_one_card_poker_lacking_outcome(), + games.create_one_card_poker_lacking_outcome_efg(), [[["2/3", "1/3"]], [[1, 0], ["1/3", "2/3"]]], ), + ( + games.create_perfect_info_internal_outcomes_efg(), + [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -562,9 +565,13 @@ def test_lp_behavior_double(): [[[0, 1]], [[1, 0], [1, 0]]], ), ( - games.create_one_card_poker_lacking_outcome(), + games.create_one_card_poker_lacking_outcome_efg(), [[["2/3", "1/3"]], [[1, 0], ["1/3", "2/3"]]], ), + ( + games.create_perfect_info_internal_outcomes_efg(), + [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 571c94e10532aa0da4ee742d1fcbc96922b45e36 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 16:22:11 +0000 Subject: [PATCH 29/36] added game testing multiple things to tests --- tests/games.py | 27 +++++++++++++++++++++++++++ tests/test_nash.py | 14 ++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/tests/games.py b/tests/games.py index db376909f..e91d24cb1 100644 --- a/tests/games.py +++ b/tests/games.py @@ -184,6 +184,33 @@ def create_perfect_info_internal_outcomes_efg() -> gbt.Game: return g +def create_three_action_internal_outcomes_efg() -> gbt.Game: + # Test 3 actions at infoset, internal outcomes and missing some outcomes at leaves + g = gbt.Game.new_tree(players=["1", "2"], + title="3 action, internal outcomes, lacking terminal outcomes") + g.append_move(g.root, g.players.chance, ["H", "L"]) + for i in range(2): + g.append_move(g.root.children[i], "1", ["A", "B", "C"]) + for i in range(3): + g.append_move(g.root.children[0].children[i], "2", ["X", "Y"]) + g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset) + o_1 = g.add_outcome([1, -1], label="1") + o_m1 = g.add_outcome([-1, 1], label="-1") + o_2 = g.add_outcome([2, -2], label="2") + o_m2 = g.add_outcome([-2, 2], label="-2") + g.set_outcome(g.root.children[0].children[0], o_1) + g.set_outcome(g.root.children[1].children[2], o_m1) + g.set_outcome(g.root.children[0].children[0].children[1], o_m2) + g.set_outcome(g.root.children[0].children[1].children[0], o_m1) + g.set_outcome(g.root.children[0].children[1].children[1], o_1) + g.set_outcome(g.root.children[0].children[2].children[0], o_1) + g.set_outcome(g.root.children[1].children[0].children[1], o_1) + g.set_outcome(g.root.children[1].children[1].children[0], o_1) + g.set_outcome(g.root.children[1].children[1].children[1], o_m1) + g.set_outcome(g.root.children[1].children[2].children[1], o_2) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. diff --git a/tests/test_nash.py b/tests/test_nash.py index 662e31bac..dd521b38d 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -430,6 +430,13 @@ def test_lcp_behavior_double(): games.create_perfect_info_internal_outcomes_efg(), [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), + ( + games.create_three_action_internal_outcomes_efg(), + [ + [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], + [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], + ], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -572,6 +579,13 @@ def test_lp_behavior_double(): games.create_perfect_info_internal_outcomes_efg(), [[[0, 1], [1, 0]], [[1, 0], [1, 0]]], ), + ( + games.create_three_action_internal_outcomes_efg(), + [ + [["1/3", 0, "2/3"], ["2/3", 0, "1/3"]], + [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], + ], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 5b98213761bd4feb86c3c638509dccc92b329bd7 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sat, 13 Dec 2025 19:27:50 +0000 Subject: [PATCH 30/36] added large payoff game to tests --- tests/games.py | 23 +++++++++++++++++++++++ tests/test_nash.py | 14 ++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tests/games.py b/tests/games.py index e91d24cb1..bf4120e5e 100644 --- a/tests/games.py +++ b/tests/games.py @@ -211,6 +211,29 @@ def create_three_action_internal_outcomes_efg() -> gbt.Game: return g +def create_large_payoff_game_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], title="Large payoff game") + g.append_move(g.root, g.players.chance, ["L", "R"]) + for i in range(2): + g.append_move(g.root.children[i], "1", ["A", "B"]) + for i in range(2): + g.append_move(g.root.children[0].children[i], "2", ["X", "Y"]) + g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset) + o_large = g.add_outcome([10000000000000000000, -10000000000000000000], label="large payoff") + o_1 = g.add_outcome([1, -1], label="1") + o_m1 = g.add_outcome([-1, 1], label="-1") + o_zero = g.add_outcome([0, 0], label="0") + g.set_outcome(g.root.children[0].children[0].children[0], o_large) + g.set_outcome(g.root.children[0].children[0].children[1], o_1) + g.set_outcome(g.root.children[0].children[1].children[0], o_m1) + g.set_outcome(g.root.children[0].children[1].children[1], o_zero) + g.set_outcome(g.root.children[1].children[0].children[0], o_m1) + g.set_outcome(g.root.children[1].children[0].children[1], o_1) + g.set_outcome(g.root.children[1].children[1].children[0], o_zero) + g.set_outcome(g.root.children[1].children[1].children[1], o_large) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. diff --git a/tests/test_nash.py b/tests/test_nash.py index dd521b38d..b08d10907 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -437,6 +437,13 @@ def test_lcp_behavior_double(): [["2/3", "1/3"], ["1/3", "2/3"], ["1/3", "2/3"]], ], ), + ( + games.create_large_payoff_game_efg(), + [ + [[1, 0], [1, 0]], + [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], + ], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -586,6 +593,13 @@ def test_lp_behavior_double(): [["2/3", "1/3"], ["2/3", "1/3"], ["1/3", "2/3"]], ], ), + ( + games.create_large_payoff_game_efg(), + [ + [[1, 0], [1, 0]], + [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], + ], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 917630bd2f45159ca59cb49e6c955832fac8425d Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sun, 14 Dec 2025 13:48:34 +0000 Subject: [PATCH 31/36] added chance in middle game --- tests/games.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/games.py b/tests/games.py index bf4120e5e..786b85dd7 100644 --- a/tests/games.py +++ b/tests/games.py @@ -211,6 +211,47 @@ def create_three_action_internal_outcomes_efg() -> gbt.Game: return g +def create_chance_in_middle_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], + title="Chance in middle game") + g.append_move(g.root, "1", ["A", "B"]) + g.append_move(g.root.children[0], g.players.chance, ["H", "L"]) + g.set_chance_probs(g.root.children[0].infoset, ["1/5", "4/5"]) + g.append_move(g.root.children[1], g.players.chance, ["H", "L"]) + g.set_chance_probs(g.root.children[1].infoset, ["7/10", "3/10"]) + g.set_outcome(g.root.children[0].children[0], g.add_outcome([-1, 1], label="a")) + for i in range(2): + g.append_move(g.root.children[0].children[i], "2", ["X", "Y"]) + ist = g.root.children[0].children[i].infoset + g.append_infoset(g.root.children[1].children[i], ist) + for i in range(2): + for j in range(2): + g.append_move(g.root.children[i].children[0].children[j], "1", ["C", "D"]) + ist = g.root.children[i].children[0].children[j].infoset + g.append_infoset(g.root.children[i].children[1].children[j], ist) + o_1 = g.add_outcome([1, -1], label="1") + o_m1 = g.add_outcome([-1, 1], label="-1") + o_h = g.add_outcome(["1/2", "-1/2"], label="0.5") + o_mh = g.add_outcome(["-1/2", "1/2"], label="-0.5") + g.set_outcome(g.root.children[0].children[0].children[0].children[0], o_1) + g.set_outcome(g.root.children[0].children[0].children[0].children[1], o_m1) + g.set_outcome(g.root.children[0].children[0].children[1].children[0], o_h) + g.set_outcome(g.root.children[0].children[0].children[1].children[1], o_mh) + g.set_outcome(g.root.children[0].children[1].children[0].children[0], o_h) + g.set_outcome(g.root.children[0].children[1].children[0].children[1], o_mh) + g.set_outcome(g.root.children[0].children[1].children[1].children[0], o_1) + g.set_outcome(g.root.children[0].children[1].children[1].children[1], o_m1) + g.set_outcome(g.root.children[1].children[0].children[0].children[0], o_h) + g.set_outcome(g.root.children[1].children[0].children[0].children[1], o_mh) + g.set_outcome(g.root.children[1].children[0].children[1].children[0], o_1) + g.set_outcome(g.root.children[1].children[0].children[1].children[1], o_m1) + g.set_outcome(g.root.children[1].children[1].children[0].children[0], o_1) + g.set_outcome(g.root.children[1].children[1].children[0].children[1], o_m1) + g.set_outcome(g.root.children[1].children[1].children[1].children[0], o_h) + g.set_outcome(g.root.children[1].children[1].children[1].children[1], o_mh) + return g + + def create_large_payoff_game_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["1", "2"], title="Large payoff game") g.append_move(g.root, g.players.chance, ["L", "R"]) From 9f0fe58074b1179f8e748a0bf56367c92713310f Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Sun, 14 Dec 2025 14:01:14 +0000 Subject: [PATCH 32/36] added chance in middle game --- tests/test_nash.py | 48 ++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index b08d10907..8ee922558 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -401,23 +401,6 @@ def test_lcp_behavior_double(): games.create_two_player_perfect_info_win_lose_efg(), [[[0, 1], [1, 0]], [[0, 1], ["1/2", "1/2"]]], ), - # Non-zero-sum games - ( - games.create_reduction_both_players_payoff_ties_efg(), - [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], - ), - ( - games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), - [ - [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"]], - [["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], - ], - ), - (games.create_EFG_for_nxn_bimatrix_coordination_game(3), [[[0, 0, 1]], [[0, 0, 1]]]), - ( - games.create_EFG_for_nxn_bimatrix_coordination_game(4), - [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], - ), ( games.create_perfect_info_with_chance_efg(), [[[0, 1]], [[0, 1], [0, 1]]], @@ -444,6 +427,30 @@ def test_lcp_behavior_double(): [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], ], ), + ( + games.create_chance_in_middle_efg(), + [ + [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], + [[1, 0], ["6/11", "5/11"]] + ], + ), + # Non-zero-sum games + ( + games.create_reduction_both_players_payoff_ties_efg(), + [[[0, 0, 1, 0], [1, 0]], [[0, 1], [0, 1], [0, 1], [0, 1]]], + ), + ( + games.create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq(), + [ + [["1/30", "1/6", "3/10", "3/10", "1/6", "1/30"]], + [["1/6", "1/30", "3/10", "3/10", "1/30", "1/6"]], + ], + ), + (games.create_EFG_for_nxn_bimatrix_coordination_game(3), [[[0, 0, 1]], [[0, 0, 1]]]), + ( + games.create_EFG_for_nxn_bimatrix_coordination_game(4), + [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): @@ -600,6 +607,13 @@ def test_lp_behavior_double(): [[0, 1], ["9999999999999999999/10000000000000000000", "1/10000000000000000000"]], ], ), + ( + games.create_chance_in_middle_efg(), + [ + [["3/11", "8/11"], [1, 0], [1, 0], [1, 0], [1, 0]], + [[1, 0], ["6/11", "5/11"]] + ], + ), ], ) def test_lp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 3a8c7419d139444e969e5ed978c9ee911cbfbe07 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 15 Dec 2025 11:06:56 +0000 Subject: [PATCH 33/36] added entry-accomodation game to tests --- tests/games.py | 17 +++++++++++++++++ tests/test_nash.py | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/tests/games.py b/tests/games.py index 786b85dd7..9ab8884f8 100644 --- a/tests/games.py +++ b/tests/games.py @@ -211,6 +211,23 @@ def create_three_action_internal_outcomes_efg() -> gbt.Game: return g +def create_entry_accomodation_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], + title="Entry-accomodation game with internal outcomes") + g.append_move(g.root, "1", ["S", "T"]) + g.append_move(g.root.children[0], "2", ["E", "O"]) + g.append_infoset(g.root.children[1], g.root.children[0].infoset) + g.append_move(g.root.children[0].children[0], "1", ["A", "F"]) + g.append_move(g.root.children[1].children[0], "1", ["A", "F"]) + g.set_outcome(g.root.children[0], g.add_outcome([3, 2])) + g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-3, -1])) + g.set_outcome(g.root.children[0].children[1], g.add_outcome([-2, 1])) + g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([2, 3])) + g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([1, 0])) + g.set_outcome(g.root.children[1].children[1], g.add_outcome([3, 1])) + return g + + def create_chance_in_middle_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["1", "2"], title="Chance in middle game") diff --git a/tests/test_nash.py b/tests/test_nash.py index 8ee922558..3a5341455 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -451,6 +451,10 @@ def test_lcp_behavior_double(): games.create_EFG_for_nxn_bimatrix_coordination_game(4), [[[0, 0, 0, 1]], [[0, 0, 0, 1]]], ), + ( + games.create_entry_accomodation_efg(), + [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 98ac78c27ab9381c6a03994e4a88387b26a8ef0d Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 15 Dec 2025 14:37:19 +0000 Subject: [PATCH 34/36] added non-zero sum game lacking outcome to tests --- tests/games.py | 20 ++++++++++++++++++++ tests/test_nash.py | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/tests/games.py b/tests/games.py index 9ab8884f8..d3fc86909 100644 --- a/tests/games.py +++ b/tests/games.py @@ -228,6 +228,26 @@ def create_entry_accomodation_efg() -> gbt.Game: return g +def create_non_zero_sum_lacking_outcome_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], title="Non constant-sum game lacking outcome") + g.append_move(g.root, g.players.chance, ["H", "T"]) + g.set_chance_probs(g.root.infoset, ["1/2", "1/2"]) + g.append_move(g.root.children[0], "1", ["A", "B"]) + g.append_infoset(g.root.children[1], g.root.children[0].infoset) + g.append_move(g.root.children[0].children[0], "2", ["X", "Y"]) + g.append_infoset(g.root.children[0].children[1], g.root.children[0].children[0].infoset) + g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset) + g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[0].infoset) + g.set_outcome(g.root.children[0].children[0].children[0], g.add_outcome([2, 1])) + g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-1, 2])) + g.set_outcome(g.root.children[0].children[1].children[0], g.add_outcome([1, -1])) + g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([1, 0])) + g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([0, 1])) + g.set_outcome(g.root.children[1].children[1].children[0], g.add_outcome([-1, 1])) + g.set_outcome(g.root.children[1].children[1].children[1], g.add_outcome([2, -1])) + return g + + def create_chance_in_middle_efg() -> gbt.Game: g = gbt.Game.new_tree(players=["1", "2"], title="Chance in middle game") diff --git a/tests/test_nash.py b/tests/test_nash.py index 3a5341455..11fea3f9c 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -455,6 +455,10 @@ def test_lcp_behavior_double(): games.create_entry_accomodation_efg(), [[["2/3", "1/3"], [1, 0], [1, 0]], [["2/3", "1/3"]]], ), + ( + games.create_non_zero_sum_lacking_outcome_efg(), + [[["1/3", "2/3"]], [["1/2", "1/2"]]], + ), ], ) def test_lcp_behavior_rational(game: gbt.Game, mixed_behav_prof_data: list): From 308ce80652f336bd1b005de7e35d2afaf9242b71 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 15 Dec 2025 16:15:49 +0000 Subject: [PATCH 35/36] added 3 player game with internal outcomes to tests --- tests/games.py | 35 +++++++++++++++++++++++++++++++++++ tests/test_nash.py | 8 ++++++++ 2 files changed, 43 insertions(+) diff --git a/tests/games.py b/tests/games.py index d3fc86909..7dfc4b8df 100644 --- a/tests/games.py +++ b/tests/games.py @@ -312,6 +312,41 @@ def create_large_payoff_game_efg() -> gbt.Game: return g +def create_3_player_with_internal_outcomes_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2", "3"], title="3 player game with internal outcomes") + g.append_move(g.root, g.players.chance, ["H", "T"]) + g.set_chance_probs(g.root.infoset, ["1/2", "1/2"]) + g.append_move(g.root.children[0], "1", ["a", "b"]) + g.append_move(g.root.children[1], "1", ["c", "d"]) + g.append_move(g.root.children[0].children[0], "2", ["A", "B"]) + g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset) + g.append_move(g.root.children[0].children[1], "3", ["W", "X"]) + g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[1].infoset) + g.append_move(g.root.children[0].children[0].children[0], "3", ["Y", "Z"]) + iset = g.root.children[0].children[0].children[0].infoset + g.append_infoset(g.root.children[0].children[0].children[1], iset) + g.append_move(g.root.children[0].children[1].children[1], "2", ["C", "D"]) + o = g.add_outcome([1, 2, 3]) + g.set_outcome(g.root.children[1], o) + o = g.add_outcome([3, 1, 4]) + g.set_outcome(g.root.children[0].children[0].children[0].children[0], o) + o = g.add_outcome([4, 0, 1]) + g.set_outcome(g.root.children[0].children[0].children[0].children[1], o) + o = g.add_outcome([1, 0, 1]) + g.set_outcome(g.root.children[1].children[0].children[0], o) + o = g.add_outcome([2, -1, -2]) + g.set_outcome(g.root.children[1].children[0].children[1], o) + o = g.add_outcome([1, 3, 2]) + g.set_outcome(g.root.children[0].children[1].children[0], o) + o = g.add_outcome([2, 4, 1]) + g.set_outcome(g.root.children[0].children[1].children[1].children[0], o) + o = g.add_outcome([4, 1, 3]) + g.set_outcome(g.root.children[0].children[1].children[1].children[1], o) + o = g.add_outcome([-1, 2, -1]) + g.set_outcome(g.root.children[1].children[1].children[0], o) + return g + + def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game: """ The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node. diff --git a/tests/test_nash.py b/tests/test_nash.py index 11fea3f9c..86a61ee4c 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -135,6 +135,14 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): # ], # 2, # 9 in total found by enumpoly (see unordered test) # ), + ( + games.create_3_player_with_internal_outcomes_efg(), + [ + [[[1.0, 0.0], [1.0, 0.0]], [[1.0, 0.0], [0.5, 0.5]], [[0.0, 1.0], [1.0, 0.0]]], + [[[1.0, 0.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 1.0]], + [[0.3333333333333333, 0.6666666666666667], [1.0, 0.0]]]], + 2, + ), ], ) def test_enumpoly_ordered_behavior( From ed9554c605928e3cdc0f9cc4c75a30401fa587b8 Mon Sep 17 00:00:00 2001 From: StephenPasteris Date: Mon, 15 Dec 2025 16:50:21 +0000 Subject: [PATCH 36/36] Added tests to enumpoly --- tests/test_nash.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/test_nash.py b/tests/test_nash.py index 86a61ee4c..4143f3f17 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -143,6 +143,21 @@ def test_enummixed_rational(game: gbt.Game, mixed_strategy_prof_data: list): [[0.3333333333333333, 0.6666666666666667], [1.0, 0.0]]]], 2, ), + ( + games.create_entry_accomodation_efg(), + [ + [[[0.6666666666666666, 0.33333333333333337], [1.0, 0.0], [1.0, 0.0]], + [[0.6666666666666666, 0.33333333333333337]]], + [[[0.0, 1.0], [0.0, 0.0], [0.3333333333333333, 0.6666666666666667]], [[0.0, 1.0]]], + [[[0.0, 1.0], [0.0, 0.0], [1.0, 0.0]], [[1.0, 0.0]]], + [[[0.0, 1.0], [0.0, 0.0], [0.0, 0.0]], [[0.0, 1.0]]]], + 4, + ), + ( + games.create_non_zero_sum_lacking_outcome_efg(), + [[[[0.33333333333333337, 0.6666666666666666]], [[0.5, 0.5]]]], + 1, + ), ], ) def test_enumpoly_ordered_behavior( @@ -358,21 +373,18 @@ def test_lcp_behavior_double(): ), pytest.param( games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - marks=pytest.mark.xfail(reason="Problem with missing terminal outcome in LP/LCP") + [[["1/2", "1/2"]], [["1/2", "1/2"]]] ), (games.create_matching_pennies_efg(), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), pytest.param( games.create_matching_pennies_efg(with_neutral_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - marks=pytest.mark.xfail(reason="Problem with nonterminal nodes in LP/LCP") + [[["1/2", "1/2"]], [["1/2", "1/2"]]] ), (games.create_stripped_down_poker_efg(), [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]]), pytest.param( games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - marks=pytest.mark.xfail(reason="Problem with missing terminal outcome in LP/LCP") + [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]] ), ( games.create_kuhn_poker_efg(), @@ -400,8 +412,7 @@ def test_lcp_behavior_double(): ["1/2", "1/2"], ], [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - marks=pytest.mark.xfail(reason="Problem with missing terminal outcome in LP/LCP") + ] ), # In the next test case: # 1/2-1/2 for l/r is determined by MixedBehaviorProfile.UndefinedToCentroid() @@ -549,15 +560,13 @@ def test_lp_behavior_double(): ), pytest.param( games.create_2x2_zero_sum_efg(missing_term_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - marks=pytest.mark.xfail(reason="Problem with missing terminal outcome in LP/LCP") + [[["1/2", "1/2"]], [["1/2", "1/2"]]] ), (games.create_matching_pennies_efg(with_neutral_outcome=False), [[["1/2", "1/2"]], [["1/2", "1/2"]]]), pytest.param( games.create_matching_pennies_efg(with_neutral_outcome=True), - [[["1/2", "1/2"]], [["1/2", "1/2"]]], - marks=pytest.mark.xfail(reason="Problem with nonterminal nodes in LP/LCP") + [[["1/2", "1/2"]], [["1/2", "1/2"]]] ), ( games.create_stripped_down_poker_efg(), @@ -565,8 +574,7 @@ def test_lp_behavior_double(): ), pytest.param( games.create_stripped_down_poker_efg(nonterm_outcomes=True), - [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]], - marks=pytest.mark.xfail(reason="Problem with nonterminal nodes in LP/LCP") + [[[1, 0], ["1/3", "2/3"]], [["2/3", "1/3"]]] ), ( games.create_kuhn_poker_efg(), @@ -587,8 +595,7 @@ def test_lp_behavior_double(): [0, 1], ], [[1, 0], ["2/3", "1/3"], [0, 1], [0, 1], ["2/3", "1/3"], [1, 0]], - ], - marks=pytest.mark.xfail(reason="Problem with nonterminal nodes in LP/LCP") + ] ), ( games.create_seq_form_STOC_paper_zero_sum_2_player_efg(),