From 955b1257dcb95665afcbeda5dbe2dcd3c8bb7d2d Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 4 Dec 2025 13:33:07 +0000 Subject: [PATCH 01/10] Move MaxRegret into mixedstrategyprofile --- src/games/game.cc | 27 ++++++++++++++++++--------- src/games/stratmixed.h | 6 +----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index b6cc9fdf8..eec8a8248 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -301,15 +301,6 @@ template T MixedStrategyProfileRep::GetRegret(const GamePlayer &p_p return br_payoff - GetPayoff(p_player); } -template T MixedStrategyProfileRep::GetMaxRegret() const -{ - auto players = m_support.GetGame()->GetPlayers(); - return std::accumulate(players.begin(), players.end(), T(0), - [this](const T &x, const GamePlayer &player) { - return std::max(x, this->GetRegret(player)); - }); -} - //======================================================================== // MixedStrategyProfile: Lifecycle //======================================================================== @@ -405,6 +396,24 @@ template T MixedStrategyProfile::GetLiapValue() const return liapValue; } +template +auto maximize_function(const Container &p_container, const Func &p_function) +{ + auto it = p_container.begin(); + using T = decltype(p_function(*it)); + return std::transform_reduce( + std::next(it), p_container.end(), p_function(*it), + [](const T &a, const T &b) { return std::max(a, b); }, p_function); +} + +template T MixedStrategyProfile::GetMaxRegret() const +{ + CheckVersion(); + return maximize_function(GetGame()->GetPlayers(), [this](const GamePlayer &p_player) -> T { + return this->m_rep->GetRegret(p_player); + }); +} + template class MixedStrategyProfileRep; template class MixedStrategyProfileRep; diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 652b1062e..1b4b8ba1e 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -308,11 +308,7 @@ template class MixedStrategyProfile { /// @details Computes the maximum of the regrets of the players in the profile. /// @sa GetRegret(const GamePlayer &) const; /// GetRegret(const GameStrategy &) const - T GetMaxRegret() const - { - CheckVersion(); - return m_rep->GetMaxRegret(); - } + T GetMaxRegret() const; /// @brief Computes the Lyapunov value of the profile /// @details Computes the Lyapunov value of the profile. This is a nonnegative From 0513e1aac138cce556354f3dda7963d13b7d2367 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 4 Dec 2025 13:44:52 +0000 Subject: [PATCH 02/10] Move player regret into mixedstrategyprofile --- src/games/game.cc | 22 ++++++++++------------ src/games/stratmixed.h | 8 +------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index eec8a8248..f2cf255f2 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -290,17 +290,6 @@ template T MixedStrategyProfileRep::GetRegret(const GameStrategy &p return brpayoff - payoff; } -template T MixedStrategyProfileRep::GetRegret(const GamePlayer &p_player) const -{ - auto strategies = p_player->GetStrategies(); - T br_payoff = std::accumulate(std::next(strategies.begin()), strategies.end(), - GetPayoff(*strategies.begin()), - [this](const T &x, const GameStrategy &strategy) { - return std::max(x, GetPayoff(strategy)); - }); - return br_payoff - GetPayoff(p_player); -} - //======================================================================== // MixedStrategyProfile: Lifecycle //======================================================================== @@ -406,11 +395,20 @@ auto maximize_function(const Container &p_container, const Func &p_function) [](const T &a, const T &b) { return std::max(a, b); }, p_function); } +template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const +{ + CheckVersion(); + auto br_payoff = + maximize_function(p_player->GetStrategies(), + [this](const GameStrategy &strategy) -> T { return GetPayoff(strategy); }); + return br_payoff - GetPayoff(p_player); +} + template T MixedStrategyProfile::GetMaxRegret() const { CheckVersion(); return maximize_function(GetGame()->GetPlayers(), [this](const GamePlayer &p_player) -> T { - return this->m_rep->GetRegret(p_player); + return this->GetRegret(p_player); }); } diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 1b4b8ba1e..9707215c5 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -74,8 +74,6 @@ template class MixedStrategyProfileRep { } T GetRegret(const GameStrategy &) const; - T GetRegret(const GamePlayer &) const; - T GetMaxRegret() const; virtual void InvalidateCache() const {} }; @@ -298,11 +296,7 @@ template class MixedStrategyProfile { /// @param[in] p_player The player to compute the regret for. /// @sa GetRegret(const GameStrategy &) const; /// GetMaxRegret() const - T GetRegret(const GamePlayer &p_player) const - { - CheckVersion(); - return m_rep->GetRegret(p_player); - } + T GetRegret(const GamePlayer &p_player) const; /// @brief Computes the maximum regret to any player in the profile /// @details Computes the maximum of the regrets of the players in the profile. From 8a8838c9e10d96aed4ab15986fdf37169f307121 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 4 Dec 2025 16:30:45 +0000 Subject: [PATCH 03/10] Develop filter iterator --- src/games/game.cc | 99 ++++++++++++++++++++++++++++++++++++------ src/games/gameobject.h | 2 +- src/games/stratmixed.h | 8 +--- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index f2cf255f2..fccbb8a4f 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -277,19 +277,6 @@ std::unique_ptr> MixedStrategyProfileRep::Normaliz return norm; } -template T MixedStrategyProfileRep::GetRegret(const GameStrategy &p_strategy) const -{ - const GamePlayer player = p_strategy->GetPlayer(); - T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); - T brpayoff = payoff; - for (auto strategy : player->GetStrategies()) { - if (strategy != p_strategy) { - brpayoff = std::max(brpayoff, GetPayoffDeriv(player->GetNumber(), strategy)); - } - } - return brpayoff - payoff; -} - //======================================================================== // MixedStrategyProfile: Lifecycle //======================================================================== @@ -395,6 +382,92 @@ auto maximize_function(const Container &p_container, const Func &p_function) [](const T &a, const T &b) { return std::max(a, b); }, p_function); } +template class filter_iterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + + filter_iterator(Iter current, Iter end, const T &value) + : m_current(current), m_end(end), m_value(value) + { + skip_if_value(); + } + + value_type operator*() const { return *m_current; } + pointer operator->() const { return std::addressof(*m_current); } + + filter_iterator &operator++() + { + ++m_current; + skip_if_value(); + return *this; + } + + filter_iterator operator++(int) + { + auto tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const filter_iterator &a, const filter_iterator &b) + { + return a.m_current == b.m_current; + } + + friend bool operator!=(const filter_iterator &a, const filter_iterator &b) + { + return a.m_current != b.m_current; + } + +private: + Iter m_current, m_end; + T m_value; + + void skip_if_value() + { + while (m_current != m_end && *m_current == m_value) { + ++m_current; + } + } +}; + +template class filter_range { +public: + using Iter = decltype(std::begin(std::declval())); + + filter_range(const Container &c, const T &value) + : m_begin(c.begin(), c.end(), value), m_end(c.end(), c.end(), value) + { + } + + filter_iterator begin() const { return m_begin; } + filter_iterator end() const { return m_end; } + +private: + filter_iterator m_begin, m_end; +}; + +template auto make_filter_iterator(const Container &c, const T &value) +{ + return filter_range(c, value); +} + +template T MixedStrategyProfile::GetRegret(const GameStrategy &p_strategy) const +{ + CheckVersion(); + const GamePlayer player = p_strategy->GetPlayer(); + T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); + T brpayoff = payoff; + for (const auto &strategy : make_filter_iterator(player->GetStrategies(), p_strategy)) { + brpayoff = std::max(brpayoff, GetPayoffDeriv(player->GetNumber(), strategy)); + } + return brpayoff - payoff; +} + template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const { CheckVersion(); diff --git a/src/games/gameobject.h b/src/games/gameobject.h index 9e9338cb7..a8328fe3c 100644 --- a/src/games/gameobject.h +++ b/src/games/gameobject.h @@ -188,7 +188,7 @@ template class ElementCollection { } value_type operator*() const { return m_container->at(m_index); } - inline const P &GetOwner() const { return m_owner; } + const P &GetOwner() const { return m_owner; } }; ElementCollection() = default; diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 9707215c5..1bd40fcc8 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -73,8 +73,6 @@ template class MixedStrategyProfileRep { return GetPayoffDeriv(p_strategy->GetPlayer()->GetNumber(), p_strategy); } - T GetRegret(const GameStrategy &) const; - virtual void InvalidateCache() const {} }; @@ -282,11 +280,7 @@ template class MixedStrategyProfile { /// @param[in] p_strategy The strategy to compute the regret for. /// @sa GetRegret(const GamePlayer &) const; /// GetMaxRegret() const - T GetRegret(const GameStrategy &p_strategy) const - { - CheckVersion(); - return m_rep->GetRegret(p_strategy); - } + T GetRegret(const GameStrategy &p_strategy) const; /// @brief Computes the regret for player \p p_player /// @details Computes the regret to the player of playing their mixed strategy From 61150acf4635431aad3f7c272186e4afc06ae015 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 4 Dec 2025 16:45:22 +0000 Subject: [PATCH 04/10] Refactor GetRegret of strategy --- src/games/game.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index fccbb8a4f..27b443244 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -461,11 +461,11 @@ template T MixedStrategyProfile::GetRegret(const GameStrategy &p_st CheckVersion(); const GamePlayer player = p_strategy->GetPlayer(); T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); - T brpayoff = payoff; - for (const auto &strategy : make_filter_iterator(player->GetStrategies(), p_strategy)) { - brpayoff = std::max(brpayoff, GetPayoffDeriv(player->GetNumber(), strategy)); - } - return brpayoff - payoff; + T best_other_payoff = maximize_function(filter_range(player->GetStrategies(), p_strategy), + [this, player](const GameStrategy &p_strategy) -> T { + return GetPayoffDeriv(player->GetNumber(), p_strategy); + }); + return std::max(best_other_payoff - payoff, static_cast(0)); } template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const From fe48dc98030a8c9d45f6dc671ce0b7a7ce4af0f1 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Thu, 4 Dec 2025 16:50:38 +0000 Subject: [PATCH 05/10] Use GetPayoff instead of deriv just for clarity --- src/games/game.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index 27b443244..26fc85ea2 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -459,12 +459,10 @@ template auto make_filter_iterator(const Container &c template T MixedStrategyProfile::GetRegret(const GameStrategy &p_strategy) const { CheckVersion(); - const GamePlayer player = p_strategy->GetPlayer(); - T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); - T best_other_payoff = maximize_function(filter_range(player->GetStrategies(), p_strategy), - [this, player](const GameStrategy &p_strategy) -> T { - return GetPayoffDeriv(player->GetNumber(), p_strategy); - }); + T payoff = GetPayoff(p_strategy); + T best_other_payoff = maximize_function( + filter_range(p_strategy->GetPlayer()->GetStrategies(), p_strategy), + [this](const GameStrategy &p_strategy) -> T { return GetPayoff(p_strategy); }); return std::max(best_other_payoff - payoff, static_cast(0)); } From fa5b1efd22279fc74bc3083fafe554516519c3d4 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 5 Dec 2025 09:20:24 +0000 Subject: [PATCH 06/10] Tidy some unused/redundant items --- src/games/file.cc | 3 +-- src/games/game.cc | 6 ++---- src/games/stratmixed.h | 26 +++++++++++--------------- src/pygambit/util.h | 1 + 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/games/file.cc b/src/games/file.cc index b1c0d9b1f..babcf925e 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -20,14 +20,13 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include -#include #include #include #include #include #include "gambit.h" +#include "gameagg.h" namespace { // This anonymous namespace encapsulates the file-parsing code diff --git a/src/games/game.cc b/src/games/game.cc index 26fc85ea2..e1206d5b5 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -251,7 +251,7 @@ MixedStrategyProfileRep::MixedStrategyProfileRep(const StrategySupportProfile template void MixedStrategyProfileRep::SetCentroid() { for (auto player : m_support.GetGame()->GetPlayers()) { - T center = T(1) / T(m_support.GetStrategies(player).size()); + T center = static_cast(1) / static_cast(m_support.GetStrategies(player).size()); for (auto strategy : m_support.GetStrategies(player)) { (*this)[strategy] = center; } @@ -300,8 +300,7 @@ MixedStrategyProfile::MixedStrategyProfile(const MixedBehaviorProfile &p_p } template -MixedStrategyProfile & -MixedStrategyProfile::operator=(const MixedStrategyProfile &p_profile) +MixedStrategyProfile &MixedStrategyProfile::operator=(const MixedStrategyProfile &p_profile) { if (this != &p_profile) { InvalidateCache(); @@ -351,7 +350,6 @@ template void MixedStrategyProfile::ComputePayoffs() const } for (const auto &player : m_rep->m_support.GetPlayers()) { map_profile_payoffs[player] = GetPayoff(player); - // values of the player's strategies for (const auto &strategy : m_rep->m_support.GetStrategies(player)) { map_strategy_payoffs[player][strategy] = GetPayoff(strategy); } diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 1bd40fcc8..d006e239e 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -24,7 +24,6 @@ #define LIBGAMBIT_MIXED_H #include "core/vector.h" -#include "games/gameagg.h" #include "games/gamebagg.h" namespace Gambit { @@ -39,10 +38,10 @@ template class MixedStrategyProfileRep { explicit MixedStrategyProfileRep(const StrategySupportProfile &); virtual ~MixedStrategyProfileRep() = default; - virtual std::unique_ptr> Copy() const = 0; + virtual std::unique_ptr Copy() const = 0; void SetCentroid(); - std::unique_ptr> Normalize() const; + std::unique_ptr Normalize() const; /// Returns the probability the strategy is played const T &operator[](const GameStrategy &p_strategy) const { @@ -82,7 +81,6 @@ template class MixedStrategyProfileRep { /// independently chooses from among his strategies with specified /// probabilities. template class MixedStrategyProfile { -private: std::unique_ptr> m_rep; mutable std::map> map_strategy_payoffs; mutable std::map map_profile_payoffs; @@ -115,20 +113,18 @@ template class MixedStrategyProfile { /// Convert a behavior strategy profile to a mixed strategy profile explicit MixedStrategyProfile(const MixedBehaviorProfile &); /// Make a copy of the mixed strategy profile - MixedStrategyProfile(const MixedStrategyProfile &p_profile) : m_rep(p_profile.m_rep->Copy()) - { - } + MixedStrategyProfile(const MixedStrategyProfile &p_profile) : m_rep(p_profile.m_rep->Copy()) {} /// Destructor ~MixedStrategyProfile() = default; - MixedStrategyProfile &operator=(const MixedStrategyProfile &); - MixedStrategyProfile &operator=(const Vector &v) + MixedStrategyProfile &operator=(const MixedStrategyProfile &); + MixedStrategyProfile &operator=(const Vector &v) { InvalidateCache(); m_rep->m_probs = v; return *this; } - MixedStrategyProfile &operator=(const T &c) + MixedStrategyProfile &operator=(const T &c) { InvalidateCache(); m_rep->m_probs = c; @@ -139,13 +135,13 @@ template class MixedStrategyProfile { /// @name Operator overloading //@{ /// Test for the equality of two profiles - bool operator==(const MixedStrategyProfile &p_profile) const + bool operator==(const MixedStrategyProfile &p_profile) const { return (m_rep->m_support == p_profile.m_rep->m_support && m_rep->m_probs == p_profile.m_rep->m_probs); } /// Test for the inequality of two profiles - bool operator!=(const MixedStrategyProfile &p_profile) const + bool operator!=(const MixedStrategyProfile &p_profile) const { return (m_rep->m_support != p_profile.m_rep->m_support || m_rep->m_probs != p_profile.m_rep->m_probs); @@ -215,7 +211,7 @@ template class MixedStrategyProfile { /// Create a new mixed strategy profile where strategies are played /// in the same proportions, but with probabilities for each player /// summing to one. - MixedStrategyProfile Normalize() const + MixedStrategyProfile Normalize() const { CheckVersion(); return MixedStrategyProfile(m_rep->Normalize()); @@ -225,7 +221,7 @@ template class MixedStrategyProfile { size_t MixedProfileLength() const { return m_rep->m_probs.size(); } /// Converts the profile to one on the full support of the game - MixedStrategyProfile ToFullSupport() const; + MixedStrategyProfile ToFullSupport() const; //@} /// @name Computation of interesting quantities @@ -331,7 +327,7 @@ MixedStrategyProfile GameRep::NewRandomStrategyProfile(int p_denom, auto prob = dist.cbegin(); for (auto strategy : player->GetStrategies()) { profile[strategy] = *prob; - prob++; + ++prob; } } return profile; diff --git a/src/pygambit/util.h b/src/pygambit/util.h index 233aa0c03..e40bc59e2 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -30,6 +30,7 @@ #include #include #include "gambit.h" +#include "games/gameagg.h" #include "games/nash.h" using namespace std; From 73ae3f06c8b5360b4bc7c7756ea49f8debdea821 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 5 Dec 2025 09:33:45 +0000 Subject: [PATCH 07/10] use cached payoffs with regret --- src/games/game.cc | 31 ++++++++++++++++++------------- src/games/stratmixed.h | 8 ++++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index e1206d5b5..c6ec8788d 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -343,15 +343,15 @@ template MixedStrategyProfile MixedStrategyProfile::ToFullSuppor template void MixedStrategyProfile::ComputePayoffs() const { - if (!map_profile_payoffs.empty()) { - // caches (map_profile_payoffs and map_strategy_payoffs) are valid, + if (!m_payoffs.empty()) { + // caches (m_payoffs and m_strategyValues) are valid, // so don't compute anything, simply return return; } for (const auto &player : m_rep->m_support.GetPlayers()) { - map_profile_payoffs[player] = GetPayoff(player); + m_payoffs[player] = GetPayoff(player); for (const auto &strategy : m_rep->m_support.GetStrategies(player)) { - map_strategy_payoffs[player][strategy] = GetPayoff(strategy); + m_strategyValues[player][strategy] = GetPayoff(strategy); } } }; @@ -362,8 +362,8 @@ template T MixedStrategyProfile::GetLiapValue() const ComputePayoffs(); auto liapValue = static_cast(0); - for (auto [player, payoff] : map_profile_payoffs) { - for (auto v : map_strategy_payoffs[player]) { + for (auto [player, payoff] : m_payoffs) { + for (auto v : m_strategyValues[player]) { liapValue += sqr(std::max(v.second - payoff, static_cast(0))); } } @@ -457,20 +457,25 @@ template auto make_filter_iterator(const Container &c template T MixedStrategyProfile::GetRegret(const GameStrategy &p_strategy) const { CheckVersion(); - T payoff = GetPayoff(p_strategy); - T best_other_payoff = maximize_function( - filter_range(p_strategy->GetPlayer()->GetStrategies(), p_strategy), - [this](const GameStrategy &p_strategy) -> T { return GetPayoff(p_strategy); }); + ComputePayoffs(); + auto player = p_strategy->GetPlayer(); + T payoff = m_strategyValues[player][p_strategy]; + T best_other_payoff = maximize_function(filter_range(player->GetStrategies(), p_strategy), + [this, &player](const GameStrategy &strategy) -> T { + return m_strategyValues[player][strategy]; + }); return std::max(best_other_payoff - payoff, static_cast(0)); } template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const { CheckVersion(); + ComputePayoffs(); auto br_payoff = - maximize_function(p_player->GetStrategies(), - [this](const GameStrategy &strategy) -> T { return GetPayoff(strategy); }); - return br_payoff - GetPayoff(p_player); + maximize_function(p_player->GetStrategies(), [this, p_player](const auto &strategy) -> T { + return m_strategyValues[p_player][strategy]; + }); + return br_payoff - m_payoffs[p_player]; } template T MixedStrategyProfile::GetMaxRegret() const diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index d006e239e..ad5412ba7 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -82,8 +82,8 @@ template class MixedStrategyProfileRep { /// probabilities. template class MixedStrategyProfile { std::unique_ptr> m_rep; - mutable std::map> map_strategy_payoffs; - mutable std::map map_profile_payoffs; + mutable std::map m_payoffs; + mutable std::map> m_strategyValues; /// Check underlying game has not changed; raise exception if it has void CheckVersion() const @@ -98,8 +98,8 @@ template class MixedStrategyProfile { /// Reset cache for payoffs and strategy values void InvalidateCache() const { - map_strategy_payoffs.clear(); - map_profile_payoffs.clear(); + m_strategyValues.clear(); + m_payoffs.clear(); m_rep->InvalidateCache(); } From c431247c7048829b56e921288d35fce4327ed093 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 5 Dec 2025 09:52:34 +0000 Subject: [PATCH 08/10] Move general templates to util header --- src/core/util.h | 94 ++++++++++++++++++++++++++++++++++++++++++++ src/games/game.cc | 99 +++-------------------------------------------- 2 files changed, 100 insertions(+), 93 deletions(-) diff --git a/src/core/util.h b/src/core/util.h index 10011af97..720b1b729 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace Gambit { @@ -63,6 +64,99 @@ template bool contains(const std::map &map, const K { return map.find(key) != map.end(); } + +/// @brief A container adaptor which skips over a given value when iterating +template class exclude_value { +public: + using Iter = decltype(std::begin(std::declval())); + + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + + iterator(Iter current, Iter end, const T &value) + : m_current(current), m_end(end), m_value(value) + { + skip_if_value(); + } + + value_type operator*() const { return *m_current; } + pointer operator->() const { return std::addressof(*m_current); } + + iterator &operator++() + { + ++m_current; + skip_if_value(); + return *this; + } + + iterator operator++(int) + { + auto tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const iterator &a, const iterator &b) + { + return a.m_current == b.m_current; + } + + friend bool operator!=(const iterator &a, const iterator &b) + { + return a.m_current != b.m_current; + } + + private: + Iter m_current, m_end; + T m_value; + + void skip_if_value() + { + while (m_current != m_end && *m_current == m_value) { + ++m_current; + } + } + }; + + exclude_value(const Container &c, const T &value) + : m_begin(c.begin(), c.end(), value), m_end(c.end(), c.end(), value) + { + } + + iterator begin() const { return m_begin; } + iterator end() const { return m_end; } + +private: + iterator m_begin, m_end; +}; + +/// @brief Returns the maximum value of the function over the *non-empty* container +template +auto maximize_function(const Container &p_container, const Func &p_function) +{ + auto it = p_container.begin(); + using T = decltype(p_function(*it)); + return std::transform_reduce( + std::next(it), p_container.end(), p_function(*it), + [](const T &a, const T &b) -> T { return std::max(a, b); }, p_function); +} + +/// @brief Returns the minimum value of the function over the *non-empty* container +template +auto minimize_function(const Container &p_container, const Func &p_function) +{ + auto it = p_container.begin(); + using T = decltype(p_function(*it)); + return std::transform_reduce( + std::next(it), p_container.end(), p_function(*it), + [](const T &a, const T &b) -> T { return std::min(a, b); }, p_function); +} + //======================================================================== // Exception classes //======================================================================== diff --git a/src/games/game.cc b/src/games/game.cc index c6ec8788d..603c64821 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -370,101 +370,15 @@ template T MixedStrategyProfile::GetLiapValue() const return liapValue; } -template -auto maximize_function(const Container &p_container, const Func &p_function) -{ - auto it = p_container.begin(); - using T = decltype(p_function(*it)); - return std::transform_reduce( - std::next(it), p_container.end(), p_function(*it), - [](const T &a, const T &b) { return std::max(a, b); }, p_function); -} - -template class filter_iterator { -public: - using iterator_category = std::forward_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = typename std::iterator_traits::difference_type; - using reference = typename std::iterator_traits::reference; - using pointer = typename std::iterator_traits::pointer; - - filter_iterator(Iter current, Iter end, const T &value) - : m_current(current), m_end(end), m_value(value) - { - skip_if_value(); - } - - value_type operator*() const { return *m_current; } - pointer operator->() const { return std::addressof(*m_current); } - - filter_iterator &operator++() - { - ++m_current; - skip_if_value(); - return *this; - } - - filter_iterator operator++(int) - { - auto tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const filter_iterator &a, const filter_iterator &b) - { - return a.m_current == b.m_current; - } - - friend bool operator!=(const filter_iterator &a, const filter_iterator &b) - { - return a.m_current != b.m_current; - } - -private: - Iter m_current, m_end; - T m_value; - - void skip_if_value() - { - while (m_current != m_end && *m_current == m_value) { - ++m_current; - } - } -}; - -template class filter_range { -public: - using Iter = decltype(std::begin(std::declval())); - - filter_range(const Container &c, const T &value) - : m_begin(c.begin(), c.end(), value), m_end(c.end(), c.end(), value) - { - } - - filter_iterator begin() const { return m_begin; } - filter_iterator end() const { return m_end; } - -private: - filter_iterator m_begin, m_end; -}; - -template auto make_filter_iterator(const Container &c, const T &value) -{ - return filter_range(c, value); -} - template T MixedStrategyProfile::GetRegret(const GameStrategy &p_strategy) const { CheckVersion(); ComputePayoffs(); auto player = p_strategy->GetPlayer(); - T payoff = m_strategyValues[player][p_strategy]; - T best_other_payoff = maximize_function(filter_range(player->GetStrategies(), p_strategy), - [this, &player](const GameStrategy &strategy) -> T { - return m_strategyValues[player][strategy]; - }); - return std::max(best_other_payoff - payoff, static_cast(0)); + T best_other_payoff = maximize_function( + exclude_value(player->GetStrategies(), p_strategy), + [this, &player](const auto &strategy) -> T { return m_strategyValues[player][strategy]; }); + return std::max(best_other_payoff - m_strategyValues[player][p_strategy], static_cast(0)); } template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const @@ -481,9 +395,8 @@ template T MixedStrategyProfile::GetRegret(const GamePlayer &p_play template T MixedStrategyProfile::GetMaxRegret() const { CheckVersion(); - return maximize_function(GetGame()->GetPlayers(), [this](const GamePlayer &p_player) -> T { - return this->GetRegret(p_player); - }); + return maximize_function(GetGame()->GetPlayers(), + [this](const auto &player) -> T { return this->GetRegret(player); }); } template class MixedStrategyProfileRep; From 3bcd4e107a7ec024b07efa4a4887ff0299494390 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 5 Dec 2025 10:08:50 +0000 Subject: [PATCH 09/10] Tidy up liapvalue implementation --- src/games/game.cc | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index 603c64821..37619653a 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -362,10 +362,12 @@ template T MixedStrategyProfile::GetLiapValue() const ComputePayoffs(); auto liapValue = static_cast(0); - for (auto [player, payoff] : m_payoffs) { - for (auto v : m_strategyValues[player]) { - liapValue += sqr(std::max(v.second - payoff, static_cast(0))); - } + for (auto p : m_payoffs) { + liapValue += std::transform_reduce( + m_strategyValues.at(p.first).begin(), m_strategyValues.at(p.first).end(), + static_cast(0), std::plus(), [&p](const auto &v) -> T { + return sqr(std::max(v.second - p.second, static_cast(0))); + }); } return liapValue; } @@ -375,10 +377,12 @@ template T MixedStrategyProfile::GetRegret(const GameStrategy &p_st CheckVersion(); ComputePayoffs(); auto player = p_strategy->GetPlayer(); - T best_other_payoff = maximize_function( - exclude_value(player->GetStrategies(), p_strategy), - [this, &player](const auto &strategy) -> T { return m_strategyValues[player][strategy]; }); - return std::max(best_other_payoff - m_strategyValues[player][p_strategy], static_cast(0)); + T best_other_payoff = maximize_function(exclude_value(player->GetStrategies(), p_strategy), + [this, &player](const auto &strategy) -> T { + return m_strategyValues.at(player).at(strategy); + }); + return std::max(best_other_payoff - m_strategyValues.at(player).at(p_strategy), + static_cast(0)); } template T MixedStrategyProfile::GetRegret(const GamePlayer &p_player) const @@ -387,9 +391,9 @@ template T MixedStrategyProfile::GetRegret(const GamePlayer &p_play ComputePayoffs(); auto br_payoff = maximize_function(p_player->GetStrategies(), [this, p_player](const auto &strategy) -> T { - return m_strategyValues[p_player][strategy]; + return m_strategyValues.at(p_player).at(strategy); }); - return br_payoff - m_payoffs[p_player]; + return br_payoff - m_payoffs.at(p_player); } template T MixedStrategyProfile::GetMaxRegret() const From fc4981eb9f674f8e59799c48e7075d27a0b15259 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 5 Dec 2025 10:23:36 +0000 Subject: [PATCH 10/10] Make regret and liap implementations in behaviour parallel to strategy --- src/games/behavmixed.cc | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index cf11abb6d..993d7be4b 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -266,12 +266,11 @@ template T MixedBehaviorProfile::GetLiapValue() const CheckVersion(); ComputeSolutionData(); - auto value = T(0); - for (auto player : m_support.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - for (auto action : m_support.GetActions(infoset)) { - value += sqr(std::max(GetPayoff(action) - GetPayoff(infoset), T(0))); - } + auto value = static_cast(0); + for (auto infoset : m_support.GetGame()->GetInfosets()) { + for (auto action : m_support.GetActions(infoset)) { + value += + sqr(std::max(map_actionValues[action] - map_infosetValues[infoset], static_cast(0))); } } return value; @@ -352,24 +351,23 @@ template const T &MixedBehaviorProfile::GetRegret(const GameAction { CheckVersion(); ComputeSolutionData(); - return map_regret[act]; + return map_regret.at(act); } template T MixedBehaviorProfile::GetRegret(const GameInfoset &p_infoset) const { - auto actions = p_infoset->GetActions(); - T br_payoff = std::accumulate( - std::next(actions.begin()), actions.end(), GetPayoff(*actions.begin()), - [this](const T &x, const GameAction &action) { return std::max(x, GetPayoff(action)); }); - return br_payoff - GetPayoff(p_infoset); + CheckVersion(); + ComputeSolutionData(); + T br_payoff = maximize_function(p_infoset->GetActions(), [this](const auto &action) -> T { + return map_actionValues.at(action); + }); + return br_payoff - map_infosetValues[p_infoset]; } template T MixedBehaviorProfile::GetMaxRegret() const { - auto infosets = m_support.GetGame()->GetInfosets(); - return std::accumulate( - infosets.begin(), infosets.end(), T(0), - [this](const T &x, const GameInfoset &infoset) { return std::max(x, GetRegret(infoset)); }); + return maximize_function(m_support.GetGame()->GetInfosets(), + [this](const auto &infoset) -> T { return this->GetRegret(infoset); }); } template