diff --git a/src/core/array.h b/src/core/array.h index a7e2e294a..82836db5b 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -94,6 +94,7 @@ template class Array { void erase(iterator pos) { m_data.erase(pos); } void push_back(const T &value) { m_data.push_back(value); } void pop_back() { m_data.pop_back(); } + void reserve(size_t len) { m_data.reserve(len); } }; /// Convenience function to erase the element at `p_index` diff --git a/src/core/function.cc b/src/core/function.cc index 680bfa234..66ee8ebf8 100644 --- a/src/core/function.cc +++ b/src/core/function.cc @@ -43,7 +43,7 @@ using namespace Gambit; // vector perpendicular to the plane, then subtracting to compute the // component parallel to the plane.) // -void FunctionOnSimplices::Project(Vector &x, const Array &lengths) const +void FunctionOnSimplices::Project(Vector &x, const Array &lengths) const { int index = 1; for (size_t part = 1; part <= lengths.size(); part++) { diff --git a/src/core/function.h b/src/core/function.h index a3bc68302..fa2c73248 100644 --- a/src/core/function.h +++ b/src/core/function.h @@ -42,7 +42,7 @@ class FunctionOnSimplices : public Function { protected: // Project the gradient 'x' onto the plane of the product of simplices. - void Project(Vector &x, const Array &lengths) const; + void Project(Vector &x, const Array &lengths) const; }; class FunctionMinimizerError : public std::runtime_error { diff --git a/src/games/game.h b/src/games/game.h index 8a702800a..f98827b57 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -818,39 +818,6 @@ class GameRep : public std::enable_shared_from_this { throw UndefinedException(); } - /// @name Dimensions of the game - //@{ - /// The number of strategies for each player - virtual Array NumStrategies() const = 0; - /// Gets the i'th strategy in the game, numbered globally - virtual GameStrategy GetStrategy(int p_index) const = 0; - /// Creates a new strategy for the player - virtual GameStrategy NewStrategy(const GamePlayer &p_player, const std::string &p_label) - { - throw UndefinedException(); - } - /// Remove the strategy from the game - virtual void DeleteStrategy(const GameStrategy &p_strategy) { throw UndefinedException(); } - /// Returns the number of strategy contingencies in the game - int NumStrategyContingencies() const - { - BuildComputedValues(); - return std::transform_reduce( - m_players.begin(), m_players.end(), 0, std::multiplies<>(), - [](const std::shared_ptr &p) { return p->m_strategies.size(); }); - } - /// Returns the total number of actions in the game - virtual int BehavProfileLength() const = 0; - /// Returns the total number of strategies in the game - int MixedProfileLength() const - { - BuildComputedValues(); - return std::transform_reduce( - m_players.begin(), m_players.end(), 0, std::plus<>(), - [](const std::shared_ptr &p) { return p->m_strategies.size(); }); - } - //@} - virtual PureStrategyProfile NewPureStrategyProfile() const = 0; virtual MixedStrategyProfile NewMixedStrategyProfile(double) const = 0; virtual MixedStrategyProfile NewMixedStrategyProfile(const Rational &) const = 0; @@ -899,14 +866,48 @@ class GameRep : public std::enable_shared_from_this { virtual GamePlayer NewPlayer() = 0; //@} + /// @name Dimensions of the game + //@{ + using Strategies = + NestedElementCollection; + /// Returns the set of strategies in the game + Strategies GetStrategies() const + { + BuildComputedValues(); + return Strategies(std::const_pointer_cast(this->shared_from_this())); + } + /// Gets the i'th strategy in the game, numbered globally starting from 1 + GameStrategy GetStrategy(const std::size_t p_index) const + { + const auto strategies = GetStrategies(); + if (p_index < 1 || p_index > strategies.size()) { + throw std::out_of_range("Strategy index out of range"); + } + return *std::next(strategies.begin(), p_index - 1); + } + /// Creates a new strategy for the player + virtual GameStrategy NewStrategy(const GamePlayer &p_player, const std::string &p_label) + { + throw UndefinedException(); + } + /// Remove the strategy from the game + virtual void DeleteStrategy(const GameStrategy &p_strategy) { throw UndefinedException(); } + /// Returns the total number of actions in the game + virtual int BehavProfileLength() const = 0; + //@} + /// @name Information sets //@{ - class Infosets; + using Infosets = + NestedElementCollection; /// 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 Infosets GetInfosets() const; + virtual Infosets GetInfosets() const + { + return Infosets(std::const_pointer_cast(this->shared_from_this())); + } /// Sort the information sets for each player in a canonical order virtual void SortInfosets() {} /// Returns the set of actions taken by the infoset's owner before reaching this infoset @@ -958,83 +959,6 @@ 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/gameagg.cc b/src/games/gameagg.cc index 678a7b241..c92a7ef53 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -196,32 +196,6 @@ Game GameAGGRep::Copy() const return ReadAggFile(is); } -//------------------------------------------------------------------------ -// GameAGGRep: Dimensions of the game -//------------------------------------------------------------------------ - -Array GameAGGRep::NumStrategies() const -{ - Array ns; - for (const auto &player : m_players) { - ns.push_back(player->GetStrategies().size()); - } - return ns; -} - -GameStrategy GameAGGRep::GetStrategy(int p_index) const -{ - for (const auto &player : m_players) { - if (static_cast(player->GetStrategies().size()) >= p_index) { - return player->GetStrategy(p_index); - } - else { - p_index -= player->GetStrategies().size(); - } - } - throw std::out_of_range("Strategy index out of range"); -} - //------------------------------------------------------------------------ // GameAGGRep: Factory functions //------------------------------------------------------------------------ diff --git a/src/games/gameagg.h b/src/games/gameagg.h index 6ae344161..bd12f7da7 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -46,10 +46,6 @@ class GameAGGRep : public GameRep { std::shared_ptr GetUnderlyingAGG() const { return aggPtr; } /// @name Dimensions of the game //@{ - /// The number of strategies for each player - Array NumStrategies() const override; - /// Gets the i'th strategy in the game, numbered globally - GameStrategy GetStrategy(int p_index) const override; /// Returns the total number of actions in the game int BehavProfileLength() const override { throw UndefinedException(); } //@} diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 3155e69d8..5f63b4391 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -99,8 +99,8 @@ template class BAGGMixedStrategyProfileRep : public MixedStrategyProfi template T BAGGMixedStrategyProfileRep::GetPayoff(int pl) const { auto &g = dynamic_cast(*(this->m_support.GetGame())); - std::vector s(g.MixedProfileLength()); - Array ns = g.NumStrategies(); + std::vector s(g.GetStrategies().size()); + const auto ns = g.GetStrategies().shape(); int bplayer = -1, btype = -1; for (int i = 0, offs = 0; i < g.baggPtr->getNumPlayers(); ++i) { for (int tp = 0; tp < g.baggPtr->getNumTypes(i); ++tp) { @@ -108,7 +108,7 @@ template T BAGGMixedStrategyProfileRep::GetPayoff(int pl) const bplayer = i; btype = tp; } - for (int j = 0; j < ns[g.baggPtr->typeOffset[i] + tp + 1]; ++j, ++offs) { + for (int j = 0; j < ns[g.baggPtr->typeOffset[i] + tp]; ++j, ++offs) { const GameStrategy strategy = this->m_support.GetGame() ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) ->GetStrategy(j + 1); @@ -124,7 +124,7 @@ template T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps) const { auto &g = dynamic_cast(*(this->m_support.GetGame())); - std::vector s(g.MixedProfileLength()); + std::vector s(g.GetStrategies().size()); int bplayer = -1, btype = -1; for (int i = 0; i < g.baggPtr->getNumPlayers(); ++i) { for (int tp = 0; tp < g.baggPtr->getNumTypes(i); ++tp) { @@ -163,7 +163,7 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps1 } auto &g = dynamic_cast(*(this->m_support.GetGame())); - std::vector s(g.MixedProfileLength()); + std::vector s(g.GetStrategies().size()); int bplayer = -1, btype = -1; for (int i = 0; i < g.baggPtr->getNumPlayers(); ++i) { for (int tp = 0; tp < g.baggPtr->getNumTypes(i); ++tp) { @@ -236,15 +236,6 @@ Game GameBAGGRep::Copy() const // GameBAGGRep: Dimensions of the game //------------------------------------------------------------------------ -Array GameBAGGRep::NumStrategies() const -{ - Array ns; - for (const auto &player : m_players) { - ns.push_back(player->m_strategies.size()); - } - return ns; -} - PureStrategyProfile GameBAGGRep::NewPureStrategyProfile() const { return PureStrategyProfile(std::make_shared( diff --git a/src/games/gamebagg.h b/src/games/gamebagg.h index 1ea6eb722..3918a58d2 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -53,10 +53,6 @@ class GameBAGGRep : public GameRep { /// @name Dimensions of the game //@{ - /// The number of strategies for each player - Array NumStrategies() const override; - /// Gets the i'th strategy in the game, numbered globally - GameStrategy GetStrategy(int p_index) const override { throw UndefinedException(); } /// Returns the total number of actions in the game int BehavProfileLength() const override { throw UndefinedException(); } //@} diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index 7b1f6b6a1..b8b5ce8a3 100644 --- a/src/games/gameexpl.cc +++ b/src/games/gameexpl.cc @@ -54,34 +54,6 @@ Rational GameExplicitRep::GetMaxPayoff() const }); } -//------------------------------------------------------------------------ -// GameExplicitRep: Dimensions of the game -//------------------------------------------------------------------------ - -Array GameExplicitRep::NumStrategies() const -{ - BuildComputedValues(); - Array dim; - for (const auto &player : m_players) { - dim.push_back(player->m_strategies.size()); - } - return dim; -} - -GameStrategy GameExplicitRep::GetStrategy(int p_index) const -{ - BuildComputedValues(); - int i = 1; - for (const auto &player : m_players) { - for (const auto &strategy : player->m_strategies) { - if (p_index == i++) { - return strategy; - } - } - } - throw std::out_of_range("Strategy index out of range"); -} - //------------------------------------------------------------------------ // GameExplicitRep: Outcomes //------------------------------------------------------------------------ diff --git a/src/games/gameexpl.h b/src/games/gameexpl.h index 808ab50bb..52ac543fc 100644 --- a/src/games/gameexpl.h +++ b/src/games/gameexpl.h @@ -39,14 +39,6 @@ class GameExplicitRep : public GameRep { Rational GetMaxPayoff() const override; //@} - /// @name Dimensions of the game - //@{ - /// The number of strategies for each player - Array NumStrategies() const override; - /// Gets the i'th strategy in the game, numbered globally - GameStrategy GetStrategy(int p_index) const override; - //@} - /// @name Outcomes //@{ /// Creates a new outcome in the game diff --git a/src/games/gameobject.h b/src/games/gameobject.h index a8328fe3c..2f2f39214 100644 --- a/src/games/gameobject.h +++ b/src/games/gameobject.h @@ -214,6 +214,141 @@ template class ElementCollection { iterator cend() const { return {m_owner, m_container, (m_owner) ? m_container->size() : 0}; } }; +/// @brief A view on a nested collection of objects (e.g. infosets of players or strategies of +/// players) +template class NestedElementCollection { + T m_owner; + +public: + class iterator { + using OuterRange = decltype((m_owner.get()->*OuterMemFn)()); + using OuterIter = decltype(std::begin(std::declval())); + + using OuterElem = decltype(*std::declval()); + using InnerRange = decltype((std::declval().get()->*InnerMemFn)()); + using InnerIter = decltype(std::begin(std::declval())); + + T m_iterOwner; + OuterRange m_outerRange; + OuterIter m_outerIt, m_outerEnd; + InnerRange m_innerRange; + InnerIter m_innerIt, m_innerEnd; + + void update_inner() + { + if (m_outerIt != m_outerEnd) { + m_innerRange = ((*m_outerIt).get()->*InnerMemFn)(); + m_innerIt = m_innerRange.begin(); + m_innerEnd = m_innerRange.end(); + } + } + + void skip_empty() + { + while (m_outerIt != m_outerEnd && m_innerIt == m_innerEnd) { + ++m_outerIt; + if (m_outerIt != m_outerEnd) { + update_inner(); + } + } + } + + public: + using iterator_category = std::forward_iterator_tag; + using reference = decltype(*std::declval()); + using pointer = std::add_pointer_t; + using value_type = std::remove_reference_t; + using difference_type = std::ptrdiff_t; + + iterator() = default; + + iterator(T p_owner, const bool p_isEnd) + : m_iterOwner(p_owner), m_outerRange((m_iterOwner.get()->*OuterMemFn)()), + m_outerIt(std::begin(m_outerRange)), m_outerEnd(std::end(m_outerRange)) + { + if (p_isEnd) { + m_outerIt = m_outerEnd; + return; + } + if (m_outerIt != m_outerEnd) { + update_inner(); + } + skip_empty(); + } + + reference operator*() const { return *m_innerIt; } + pointer operator->() const { return *m_innerIt; } + + iterator &operator++() + { + ++m_innerIt; + skip_empty(); + return *this; + } + iterator operator++(int) + { + iterator tmp = *this; + ++(*this); + return tmp; + } + + bool operator==(const iterator &p_other) const + { + return m_outerIt == p_other.m_outerIt && + (m_outerIt == m_outerEnd || m_innerIt == p_other.m_innerIt); + } + bool operator!=(const iterator &p_other) const { return !(*this == p_other); } + }; + + explicit NestedElementCollection(T owner) : m_owner(owner) {} + + /// @brief Return the total number of elements summed across all inner ranges + std::size_t size() const + { + auto outer = (m_owner.get()->*OuterMemFn)(); + return std::accumulate(outer.begin(), outer.end(), static_cast(0), + [](std::size_t acc, const auto &element) { + return acc + (element.get()->*InnerMemFn)().size(); + }); + } + + /// @brief Returns the shape, a vector of the sizes of the inner ranges + std::vector shape() const + { + std::vector result; + auto outer = (m_owner.get()->*OuterMemFn)(); + result.reserve(outer.size()); + std::transform(outer.begin(), outer.end(), std::back_inserter(result), + [](const auto &element) { return (element.get()->*InnerMemFn)().size(); }); + return result; + } + + /// @brief Returns the shape, a Gambit Array of the sizes of the inner ranges + /// @deprecated This is for backwards compatibility; uses should be migrated to shape(). + Array shape_array() const + { + Array result; + auto outer = (m_owner.get()->*OuterMemFn)(); + result.reserve(outer.size()); + std::transform(outer.begin(), outer.end(), std::back_inserter(result), + [](const auto &element) { return (element.get()->*InnerMemFn)().size(); }); + return result; + } + + /// @brief Returns the Cartesian product of the sizes of the inner ranges + std::size_t extent_product() const + { + auto outer = (m_owner.get()->*OuterMemFn)(); + return std::accumulate(outer.begin(), outer.end(), static_cast(1), + [](std::size_t acc, const auto &element) { + return acc * (element.get()->*InnerMemFn)().size(); + }); + } + + iterator begin() const { return {m_owner, false}; } + iterator end() const { return {m_owner, true}; } +}; + } // end namespace Gambit #endif // GAMBIT_GAMES_GAMEOBJECT_H diff --git a/src/gui/dlnfglogit.cc b/src/gui/dlnfglogit.cc index 7cc9c2a04..66eab8fda 100644 --- a/src/gui/dlnfglogit.cc +++ b/src/gui/dlnfglogit.cc @@ -117,7 +117,7 @@ LogitMixedSheet::LogitMixedSheet(wxWindow *p_parent, GameDocument *p_doc, : wxSheet(p_parent, wxID_ANY, wxDefaultPosition, wxSize(800, 600)), m_doc(p_doc), m_branch(p_branch) { - CreateGrid(p_branch.NumPoints(), p_doc->GetGame()->MixedProfileLength() + 1); + CreateGrid(p_branch.NumPoints(), p_doc->GetGame()->GetStrategies().size() + 1); SetRowLabelWidth(40); SetColLabelHeight(25); } @@ -359,13 +359,13 @@ class LogitPlotStrategyList final : public wxSheet { LogitPlotStrategyList::LogitPlotStrategyList(wxWindow *p_parent, GameDocument *p_doc) : wxSheet(p_parent, wxID_ANY), m_doc(p_doc) { - CreateGrid(m_doc->GetGame()->MixedProfileLength(), 3); + CreateGrid(m_doc->GetGame()->GetStrategies().size(), 3); SetRowLabelWidth(0); SetColLabelHeight(0); SetGridLineColour(*wxWHITE); - for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) { + for (int st = 1; st <= m_doc->GetGame()->GetStrategies().size(); st++) { const GameStrategy strategy = m_doc->GetGame()->GetStrategy(st); const GamePlayer player = strategy->GetPlayer(); const wxColour color = m_doc->GetStyle().GetPlayerColor(player); @@ -476,7 +476,7 @@ void LogitPlotPanel::Plot() m_plotCtrl->DeleteCurve(-1); - for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) { + for (int st = 1; st <= m_doc->GetGame()->GetStrategies().size(); st++) { if (!m_plotStrategies->IsStrategyShown(st)) { continue; } diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index f6d356416..e66aa63f2 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -1111,14 +1111,15 @@ void GameFrame::OnViewStrategic(wxCommandEvent &p_event) return; } - const int ncont = m_doc->GetGame()->NumStrategyContingencies(); - if (!m_nfgPanel && ncont >= 50000) { - if (wxMessageBox( - wxString::Format(wxT("This game has %d contingencies in strategic form.\n"), ncont) + - wxT("Performance in browsing strategic form will be poor,\n") + - wxT("and may render the program nonresponsive.\n") + - wxT("Do you wish to continue?"), - _("Large strategic game warning"), wxOK | wxCANCEL | wxALIGN_CENTER, this) != wxOK) { + if (const size_t contingencies = m_doc->GetGame()->GetStrategies().extent_product(); + !m_nfgPanel && contingencies >= 50000) { + if (wxMessageBox(wxString::Format(wxT("This game has %d contingencies in strategic form.\n"), + contingencies) + + wxT("Performance in browsing strategic form will be poor,\n") + + wxT("and may render the program nonresponsive.\n") + + wxT("Do you wish to continue?"), + _("Large strategic game warning"), wxOK | wxCANCEL | wxALIGN_CENTER, + this) != wxOK) { return; } } @@ -1242,15 +1243,16 @@ void GameFrame::OnToolsEquilibrium(wxCommandEvent &) if (dialog.ShowModal() == wxID_OK) { if (dialog.UseStrategic()) { - const int ncont = m_doc->GetGame()->NumStrategyContingencies(); - if (ncont >= 50000) { - if (wxMessageBox(wxString::Format( - wxT("This game has %d contingencies in strategic form.\n"), ncont) + - wxT("Performance in solving strategic form will be poor,\n") + - wxT("and may render the program nonresponsive.\n") + - wxT("Do you wish to continue?"), - _("Large strategic game warning"), wxOK | wxCANCEL | wxALIGN_CENTER, - this) != wxOK) { + if (const int contingencies = m_doc->GetGame()->GetStrategies().extent_product(); + contingencies >= 50000) { + if (wxMessageBox( + wxString::Format(wxT("This game has %d contingencies in strategic form.\n"), + contingencies) + + wxT("Performance in solving strategic form will be poor,\n") + + wxT("and may render the program nonresponsive.\n") + + wxT("Do you wish to continue?"), + _("Large strategic game warning"), wxOK | wxCANCEL | wxALIGN_CENTER, + this) != wxOK) { return; } } diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index 705f45c40..3034849c4 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -188,7 +188,7 @@ void MixedProfileList::OnUpdate() DeleteRows(0, GetNumberRows()); InsertRows(0, newRows); - const int profileLength = m_doc->GetGame()->MixedProfileLength(); + const int profileLength = m_doc->GetGame()->GetStrategies().size(); const int newCols = profileLength; DeleteCols(0, GetNumberCols()); InsertCols(0, newCols); diff --git a/src/solvers/liap/efgliap.cc b/src/solvers/liap/efgliap.cc index 4eb888a33..a7ce04cd2 100644 --- a/src/solvers/liap/efgliap.cc +++ b/src/solvers/liap/efgliap.cc @@ -51,7 +51,7 @@ class AgentLyapunovFunction : public FunctionOnSimplices { private: Game m_game; mutable MixedBehaviorProfile m_profile; - Array m_shape; + Array m_shape; double m_scale, m_penalty{100.0}; double Value(const Vector &x) const override; diff --git a/src/solvers/liap/nfgliap.cc b/src/solvers/liap/nfgliap.cc index f1fe4a11a..58c4b73a1 100644 --- a/src/solvers/liap/nfgliap.cc +++ b/src/solvers/liap/nfgliap.cc @@ -38,12 +38,7 @@ class StrategicLyapunovFunction : 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; - } + m_scale = (m_scale == 0.0) ? 1.0 : 1.0 / m_scale; } ~StrategicLyapunovFunction() override = default; @@ -63,34 +58,31 @@ class StrategicLyapunovFunction : public FunctionOnSimplices { inline double sum_player_probs(const MixedStrategyProfile &p_profile, const GamePlayer &p_player) { - auto strategies = p_player->GetStrategies(); - return std::accumulate( - strategies.cbegin(), strategies.cend(), 0.0, - [p_profile](double t, const GameStrategy &s) { return t + p_profile[s]; }); + return sum_function(p_player->GetStrategies(), + [&](const auto &strategy) -> double { return p_profile[strategy]; }); } double StrategicLyapunovFunction::Value(const Vector &v) const { m_profile = v; double value = 0; - // Liapunov function proper - should be replaced with call to profile once - // the penalty is removed from that implementation. - for (auto player : m_profile.GetGame()->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - value += sqr( - std::max(m_scale * (m_profile.GetPayoff(strategy) - m_profile.GetPayoff(player)), 0.0)); - } - } - // Penalty function for non-negativity constraint for each strategy - for (auto player : m_profile.GetGame()->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - value += m_penalty * sqr(std::min(m_profile[strategy], 0.0)); - } - } - // Penalty function for sum-to-one constraint for each player - for (auto player : m_profile.GetGame()->GetPlayers()) { - value += m_penalty * sqr(sum_player_probs(m_profile, player) - 1.0); + // Liapunov function proper + for (const auto &player : m_profile.GetGame()->GetPlayers()) { + const double payoff = m_profile.GetPayoff(player); + value += sum_function(player->GetStrategies(), [&](const auto &strategy) -> double { + return sqr(std::max(m_scale * (m_profile.GetPayoff(strategy) - payoff), 0.0)); + }); } + // Penalty for non-negativity constraint for each strategy + value += m_penalty * + sum_function(m_profile.GetGame()->GetStrategies(), [&](const auto &strategy) -> double { + return sqr(std::min(m_profile[strategy], 0.0)); + }); + // Penalty for sum-to-one constraint for each player + value += m_penalty * + sum_function(m_profile.GetGame()->GetPlayers(), [&](const auto &player) -> double { + return sqr(sum_player_probs(m_profile, player) - 1.0); + }); return value; } @@ -98,10 +90,10 @@ double StrategicLyapunovFunction::LiapDerivValue(const MixedStrategyProfileGetPlayers()) { + for (const auto &player : m_game->GetPlayers()) { + const double payoff = p_profile.GetPayoff(player); for (auto strategy : player->GetStrategies()) { - const double loss = - sqr(m_scale) * (p_profile.GetPayoff(strategy) - p_profile.GetPayoff(player)); + const double loss = sqr(m_scale) * (p_profile.GetPayoff(strategy) - payoff); if (loss <= 0.0) { continue; } @@ -121,14 +113,10 @@ double StrategicLyapunovFunction::LiapDerivValue(const MixedStrategyProfile &v, Vector &d) const { m_profile = v; - auto element = d.begin(); - for (auto player : m_game->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - *element = LiapDerivValue(m_profile, strategy); - ++element; - } - } - Project(d, m_game->NumStrategies()); + std::transform( + m_game->GetStrategies().begin(), m_game->GetStrategies().end(), d.begin(), + [&](const auto &strategy) -> double { return LiapDerivValue(m_profile, strategy); }); + Project(d, m_game->GetStrategies().shape_array()); return true; } @@ -137,10 +125,8 @@ namespace { MixedStrategyProfile EnforceNonnegativity(const MixedStrategyProfile &p_profile) { auto profile = p_profile; - for (auto player : p_profile.GetGame()->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - profile[strategy] = std::max(profile[strategy], 0.0); - } + for (const auto &strategy : p_profile.GetGame()->GetStrategies()) { + profile[strategy] = std::max(profile[strategy], 0.0); } return profile.Normalize(); } diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc index 9f76fffd7..fa2d4eff1 100644 --- a/src/solvers/nashsupport/nfgsupport.cc +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -36,30 +36,29 @@ using StrategySupport = std::vector; using CallbackFunction = std::function &)>; class CartesianRange { -private: - Array m_sizes; + Array m_sizes; public: - CartesianRange(const Array &p_sizes) : m_sizes(p_sizes) {} + CartesianRange(const Array &p_sizes) : m_sizes(p_sizes) {} class iterator { private: - Array m_sizes; - Array m_indices; + Array m_sizes; + Array m_indices; bool m_end; public: using iterator_category = std::forward_iterator_tag; - iterator(const Array &p_sizes, bool p_end = false) + iterator(const Array &p_sizes, bool p_end = false) : m_sizes(p_sizes), m_indices(m_sizes.size()), m_end(p_end) { std::fill(m_indices.begin(), m_indices.end(), 1); } - const Array &operator*() const { return m_indices; } + const Array &operator*() const { return m_indices; } - const Array &operator->() const { return m_indices; } + const Array &operator->() const { return m_indices; } iterator &operator++() { @@ -205,7 +204,7 @@ void GenerateSupportProfiles(const Game &game, void GenerateSizeDiff(const Game &game, int size, int diff, CallbackFunction callback) { auto players = game->GetPlayers(); - CartesianRange range(game->NumStrategies()); + CartesianRange range(game->GetStrategies().shape_array()); for (auto size_profile : range) { if (*std::max_element(size_profile.cbegin(), size_profile.cend()) - *std::min_element(size_profile.cbegin(), size_profile.cend()) != @@ -243,14 +242,14 @@ std::shared_ptr PossibleNashStrategySupports(const Game &p_game) { auto result = std::make_shared(); - auto numActions = p_game->NumStrategies(); + auto numActions = p_game->GetStrategies().shape_array(); const int maxsize = std::accumulate(numActions.begin(), numActions.end(), 0) - p_game->NumPlayers() + 1; const int maxdiff = *std::max_element(numActions.cbegin(), numActions.cend()); const bool preferBalance = p_game->NumPlayers() == 2; - Array dim(2); + Array dim(2); dim[1] = (preferBalance) ? maxsize : maxdiff; dim[2] = (preferBalance) ? maxdiff : maxsize; diff --git a/src/solvers/simpdiv/simpdiv.cc b/src/solvers/simpdiv/simpdiv.cc index 6049924b9..b3710ffa5 100644 --- a/src/solvers/simpdiv/simpdiv.cc +++ b/src/solvers/simpdiv/simpdiv.cc @@ -29,11 +29,11 @@ namespace Gambit::Nash { template class PVector { private: Vector m_values; - Array m_offsets; - Array m_shape; + Array m_offsets; + Array m_shape; public: - explicit PVector(const Array &p_shape) + explicit PVector(const Array &p_shape) : m_values(std::accumulate(p_shape.begin(), p_shape.end(), 0)), m_offsets(p_shape.size()), m_shape(p_shape) { @@ -62,7 +62,7 @@ template class PVector { return *this; } - const Array &GetShape() const { return m_shape; } + const Array &GetShape() const { return m_shape; } explicit operator const Vector &() const { return m_values; } }; @@ -137,7 +137,7 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, const Game game = y.GetGame(); State state(m_leashLength); state.d = d; - Array nstrats(game->NumStrategies()); + Array nstrats(game->GetStrategies().shape_array()); Array ylabel(2); RectArray labels(y.MixedProfileLength(), 2), pi(y.MixedProfileLength(), 2); PVector U(nstrats), TT(nstrats);