diff --git a/src/core/array.h b/src/core/array.h index f2f44af77..a7e2e294a 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -42,6 +42,7 @@ template class Array { public: using iterator = typename std::vector::iterator; using const_iterator = typename std::vector::const_iterator; + using value_type = typename std::vector::value_type; using reference = typename std::vector::reference; using const_reference = typename std::vector::const_reference; diff --git a/src/core/util.h b/src/core/util.h index 720b1b729..f5678c706 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -157,6 +157,15 @@ auto minimize_function(const Container &p_container, const Func &p_function) [](const T &a, const T &b) -> T { return std::min(a, b); }, p_function); } +/// @brief Returns the sum of the function over the container +template +auto sum_function(const Container &p_container, const Func &p_function) +{ + using T = decltype(p_function(*(p_container.begin()))); + return std::transform_reduce(p_container.begin(), p_container.end(), static_cast(0), + std::plus<>{}, p_function); +} + //======================================================================== // Exception classes //======================================================================== diff --git a/src/games/game.h b/src/games/game.h index 2c3f5579c..8a702800a 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -901,10 +901,12 @@ class GameRep : public std::enable_shared_from_this { /// @name Information sets //@{ + class Infosets; + /// Returns the iset'th information set in the game (numbered globally) virtual GameInfoset GetInfoset(int iset) const { throw UndefinedException(); } /// Returns the set of information sets in the game - virtual std::vector GetInfosets() const { throw UndefinedException(); } + virtual Infosets GetInfosets() const; /// Sort the information sets for each player in a canonical order virtual void SortInfosets() {} /// Returns the set of actions taken by the infoset's owner before reaching this infoset @@ -956,6 +958,83 @@ class GameRep : public std::enable_shared_from_this { virtual void BuildComputedValues() const {} }; +class GameRep::Infosets { + Players m_players; + +public: + explicit Infosets(const Players &outer) : m_players(outer) {} + + class iterator { + using OuterIter = Players::iterator; + using InnerIter = GamePlayerRep::Infosets::iterator; + + OuterIter m_playerIterator, m_playerEnd; + InnerIter m_infosetIterator, m_infosetEnd; + + void next() + { + while (m_playerIterator != m_playerEnd && m_infosetIterator == m_infosetEnd) { + ++m_playerIterator; + if (m_playerIterator != m_playerEnd) { + auto infosets = (*m_playerIterator)->GetInfosets(); + m_infosetIterator = infosets.begin(); + m_infosetEnd = infosets.end(); + } + } + } + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = GameInfoset; + using reference = GameInfoset; + using pointer = GameInfoset; + using difference_type = std::ptrdiff_t; + + iterator() = default; + + iterator(const OuterIter &p_playerIterator, const OuterIter &p_playerEnd) + : m_playerIterator(p_playerIterator), m_playerEnd(p_playerEnd) + { + if (m_playerIterator != m_playerEnd) { + const auto infosets = (*m_playerIterator)->GetInfosets(); + m_infosetIterator = infosets.begin(); + m_infosetEnd = infosets.end(); + } + next(); + } + + reference operator*() const { return *m_infosetIterator; } + pointer operator->() const { return *m_infosetIterator; } + + iterator &operator++() + { + ++m_infosetIterator; + next(); + return *this; + } + + iterator operator++(int) + { + iterator tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const iterator &a, const iterator &b) + { + return a.m_playerIterator == b.m_playerIterator && + (a.m_playerIterator == a.m_playerEnd || a.m_infosetIterator == b.m_infosetIterator); + } + + friend bool operator!=(const iterator &a, const iterator &b) { return !(a == b); } + }; + + iterator begin() const { return {m_players.begin(), m_players.end()}; } + iterator end() const { return {m_players.end(), m_players.end()}; } +}; + +inline GameRep::Infosets GameRep::GetInfosets() const { return Infosets(GetPlayers()); } + //======================================================================= // Inline members of game representation classes //======================================================================= diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 6a450fd46..33509228e 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1193,15 +1193,6 @@ GameInfoset GameTreeRep::GetInfoset(int p_index) const throw std::out_of_range("Infoset index out of range"); } -std::vector GameTreeRep::GetInfosets() const -{ - std::vector infosets; - for (const auto &player : m_players) { - std::copy(player->m_infosets.begin(), player->m_infosets.end(), std::back_inserter(infosets)); - } - return infosets; -} - //------------------------------------------------------------------------ // GameTreeRep: Outcomes //------------------------------------------------------------------------ diff --git a/src/games/gametree.h b/src/games/gametree.h index f62d26d4b..e77500964 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -126,8 +126,6 @@ class GameTreeRep : public GameExplicitRep { //@{ /// Returns the iset'th information set in the game (numbered globally) GameInfoset GetInfoset(int iset) const override; - /// Returns the set of information sets in the game - std::vector GetInfosets() const override; /// Sort the information sets for each player in a canonical order void SortInfosets() override; /// Returns the set of actions taken by the infoset's owner before reaching this infoset diff --git a/src/solvers/liap/efgliap.cc b/src/solvers/liap/efgliap.cc index 2add29501..4eb888a33 100644 --- a/src/solvers/liap/efgliap.cc +++ b/src/solvers/liap/efgliap.cc @@ -38,18 +38,10 @@ class AgentLyapunovFunction : public FunctionOnSimplices { : m_game(p_start.GetGame()), m_profile(p_start) { m_scale = m_game->GetMaxPayoff() - m_game->GetMinPayoff(); - if (m_scale == 0.0) { - m_scale = 1.0; - } - else { - m_scale = 1.0 / m_scale; - } - - for (const auto &player : m_game->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - m_shape.push_back(infoset->GetActions().size()); - } - } + m_scale = (m_scale == 0.0) ? 1.0 : 1.0 / m_scale; + std::transform( + m_game->GetInfosets().begin(), m_game->GetInfosets().end(), std::back_inserter(m_shape), + [](const auto &infoset) -> std::size_t { return infoset->GetActions().size(); }); } ~AgentLyapunovFunction() override = default; @@ -72,35 +64,29 @@ class AgentLyapunovFunction : public FunctionOnSimplices { inline double sum_infoset_probs(const MixedBehaviorProfile &p_profile, const GameInfoset &p_infoset) { - auto actions = p_infoset->GetActions(); - return std::accumulate(actions.begin(), actions.end(), 0.0, - [p_profile](double t, const GameAction &a) { return t + p_profile[a]; }); + return sum_function(p_infoset->GetActions(), + [&p_profile](const auto &action) { return p_profile[action]; }); } double AgentLyapunovFunction::PenalizedLiapValue(const MixedBehaviorProfile &p_profile) const { double value = 0.0; - // Liapunov function proper - should be replaced with call to profile once - // the penalty is removed from that implementation. - for (auto player : p_profile.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - for (auto action : infoset->GetActions()) { - value += sqr( - std::max(m_scale * (p_profile.GetPayoff(action) - p_profile.GetPayoff(infoset)), 0.0)); - } - } + // Liapunov function proper. + for (const auto &infoset : p_profile.GetGame()->GetInfosets()) { + double infosetValue = p_profile.GetPayoff(infoset); + value += sum_function(infoset->GetActions(), [&](const auto &action) -> double { + return sqr(std::max(m_scale * (p_profile.GetPayoff(action) - infosetValue), 0.0)); + }); } // Penalty function for non-negativity constraint for each action for (auto element : static_cast &>(p_profile)) { value += m_penalty * sqr(std::min(element, 0.0)); } // Penalty function for sum-to-one constraint for each action - for (auto player : p_profile.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - value += m_penalty * sqr(sum_infoset_probs(m_profile, infoset) - 1.0); - } - } + value += sum_function(p_profile.GetGame()->GetInfosets(), [&](const auto &infoset) -> double { + return m_penalty * sqr(sum_infoset_probs(m_profile, infoset) - 1.0); + }); return value; } @@ -131,11 +117,9 @@ namespace { MixedBehaviorProfile EnforceNonnegativity(const MixedBehaviorProfile &p_profile) { auto profile = p_profile; - for (auto player : p_profile.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - for (auto action : infoset->GetActions()) { - profile[action] = std::max(profile[action], 0.0); - } + for (const auto &infoset : p_profile.GetGame()->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + profile[action] = std::max(profile[action], 0.0); } } return profile.Normalize();