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/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 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 b6cc9fdf8..37619653a 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; } @@ -277,39 +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; -} - -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); -} - -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 //======================================================================== @@ -333,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(); @@ -377,16 +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); - // values of the player's strategies + 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); } } }; @@ -397,14 +362,47 @@ 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]) { - 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; } +template T MixedStrategyProfile::GetRegret(const GameStrategy &p_strategy) const +{ + 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.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 +{ + CheckVersion(); + ComputePayoffs(); + auto br_payoff = + maximize_function(p_player->GetStrategies(), [this, p_player](const auto &strategy) -> T { + return m_strategyValues.at(p_player).at(strategy); + }); + return br_payoff - m_payoffs.at(p_player); +} + +template T MixedStrategyProfile::GetMaxRegret() const +{ + CheckVersion(); + return maximize_function(GetGame()->GetPlayers(), + [this](const auto &player) -> T { return this->GetRegret(player); }); +} + template class MixedStrategyProfileRep; template class MixedStrategyProfileRep; 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 652b1062e..ad5412ba7 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 { @@ -73,10 +72,6 @@ template class MixedStrategyProfileRep { return GetPayoffDeriv(p_strategy->GetPlayer()->GetNumber(), p_strategy); } - T GetRegret(const GameStrategy &) const; - T GetRegret(const GamePlayer &) const; - T GetMaxRegret() const; - virtual void InvalidateCache() const {} }; @@ -86,10 +81,9 @@ 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; + mutable std::map m_payoffs; + mutable std::map> m_strategyValues; /// Check underlying game has not changed; raise exception if it has void CheckVersion() const @@ -104,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(); } @@ -119,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; @@ -143,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); @@ -219,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()); @@ -229,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 @@ -284,11 +276,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 @@ -298,21 +286,13 @@ 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. /// @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 @@ -347,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;