From e07587239617d97553dae6a78b8de10995f549d1 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 29 Dec 2025 17:23:27 +0000 Subject: [PATCH] Develop concept of a Cartesian product space and apply to games in strategic form. This creates an abstraction, CartesianProductSpace, which aims to separate out the computations related to representing a Cartesian product. This has always been implicit in the strategic form, but the core ideas apply equally to other situations (sequence form, some applications of pure behavior profiles). Underpinning this is that any element of a cartesian product space can be represented by an integer computed using mixed radix arithmetic. This then leads to simplifications in the internal representation of several objects: * PureStrategyProfile, which is now be characterised simply by an integer index. * StrategySupportProfile, which is characterised by the set of "digits" which are valid for each dimension. * StrategyContingencies, which iterates over the valid digits for each dimension. Implement PureStrategyProfile using indexes only. This changes PureStrategyProfile so that it stores only to the index of the strategy profile in the cartesian product of pure strategy sets. A map from players to strategies is no longer needed. Implement CartesianProductSpace abstraction Implement StrategySupportProfile as vectors of legal "digits" in indexing the Cartesian product space. Default initialisations Make StrategyContingencies header-only to facilitate possible inlining eventually. Rewrite strategycontingencies to use "odometer digit" abstraction. Tidy up some uses of StrategyContingencies Remove "frozen" strategies in contingencies; handle in support profiles instead. Remember to add iterator attributes for StrategyContingencies --- Makefile.am | 1 - src/games/behavmixed.cc | 3 + src/games/file.cc | 10 +- src/games/game.cc | 28 ++++++ src/games/game.h | 24 ++++- src/games/gameagg.cc | 33 +++---- src/games/gamebagg.cc | 20 ++-- src/games/gametable.cc | 163 +++++++++++++------------------- src/games/gametable.h | 3 +- src/games/gametree.cc | 17 ++-- src/games/stratpure.cc | 95 ------------------- src/games/stratpure.h | 104 +++++++++++++++----- src/games/stratspt.cc | 96 ++++++++++--------- src/games/stratspt.h | 121 +++++++++++++++++++----- src/games/writer.cc | 12 ++- src/solvers/enumpoly/nfgpoly.cc | 2 +- src/solvers/enumpure/enumpure.h | 6 +- src/solvers/gtracer/gtracer.cc | 2 +- 18 files changed, 394 insertions(+), 346 deletions(-) delete mode 100644 src/games/stratpure.cc diff --git a/Makefile.am b/Makefile.am index fe3fc232f..d698afb1c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -299,7 +299,6 @@ game_SOURCES = \ src/games/behavmixed.h \ src/games/stratspt.cc \ src/games/stratspt.h \ - src/games/stratpure.cc \ src/games/stratpure.h \ src/games/stratmixed.h \ src/games/file.cc \ diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 7fb996ef0..36b0df22c 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -252,6 +252,7 @@ template MixedBehaviorProfile MixedBehaviorProfile::ToFullSuppor template T MixedBehaviorProfile::GetLiapValue() const { + m_support.GetGame()->BuildComputedValues(); return MixedStrategyProfile(*this).GetLiapValue(); } @@ -356,6 +357,7 @@ template T MixedBehaviorProfile::GetRegret(const GameInfoset &p_inf template T MixedBehaviorProfile::GetMaxRegret() const { + m_support.GetGame()->BuildComputedValues(); return MixedStrategyProfile(*this).GetMaxRegret(); } @@ -553,6 +555,7 @@ template bool MixedBehaviorProfile::IsDefinedAt(GameInfoset p_infos template MixedStrategyProfile MixedBehaviorProfile::ToMixedProfile() const { CheckVersion(); + m_support.GetGame()->BuildComputedValues(); return MixedStrategyProfile(*this); } diff --git a/src/games/file.cc b/src/games/file.cc index d9b374315..5ec77fec3 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -411,11 +411,10 @@ void ReadOutcomeList(GameFileLexer &p_parser, Game &p_nfg) void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) { ReadOutcomeList(p_parser, p_nfg); - const StrategySupportProfile profile(p_nfg); - for (auto iter : StrategyContingencies(profile)) { + for (const auto &profile : StrategyContingencies(p_nfg)) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "outcome index"); if (const int outcomeId = std::stoi(p_parser.GetLastText())) { - iter->SetOutcome(p_nfg->GetOutcome(outcomeId)); + profile->SetOutcome(p_nfg->GetOutcome(outcomeId)); } p_parser.GetNextToken(); } @@ -423,11 +422,10 @@ void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) void ParsePayoffBody(GameFileLexer &p_parser, Game &p_nfg) { - const StrategySupportProfile profile(p_nfg); - for (auto iter : StrategyContingencies(profile)) { + for (const auto &profile : StrategyContingencies(p_nfg)) { for (const auto &player : p_nfg->GetPlayers()) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); - iter->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); + profile->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); p_parser.GetNextToken(); } } diff --git a/src/games/game.cc b/src/games/game.cc index af6653395..c33e1840e 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -182,6 +182,24 @@ GameRep::~GameRep() } } +void GameRep::IndexStrategies() const +{ + const size_t n = m_players.size(); + m_pureStrategies.m_radices.resize(n); + m_pureStrategies.m_strides.resize(n); + + long stride = 1L; + for (size_t i = 0; i < n; ++i) { + const auto &player = m_players[i]; + m_pureStrategies.m_strides[i] = stride; + m_pureStrategies.m_radices[i] = player->m_strategies.size(); + for (auto [st, strategy] : enumerate(player->m_strategies)) { + strategy->m_number = st + 1; + } + stride *= m_pureStrategies.m_radices[i]; + } +} + //------------------------------------------------------------------------ // GameRep: Writing data files //------------------------------------------------------------------------ @@ -405,6 +423,16 @@ template T MixedStrategyProfile::GetMaxRegret() const [this](const auto &player) -> T { return this->GetRegret(player); }); } +MixedStrategyProfile PureStrategyProfileRep::ToMixedStrategyProfile() const +{ + auto temp = m_game->NewMixedStrategyProfile(Rational(0)); + temp = Rational(0); + for (const auto &player : m_game->GetPlayers()) { + temp[GetStrategy(player)] = Rational(1); + } + return temp; +} + template class MixedStrategyProfileRep; template class MixedStrategyProfileRep; diff --git a/src/games/game.h b/src/games/game.h index 66cf5ae00..420d072bd 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -267,6 +267,7 @@ class GameInfosetRep : public std::enable_shared_from_this { /// relatively efficient. class GameStrategyRep : public std::enable_shared_from_this { friend class GameExplicitRep; + friend class GameRep; friend class GameTreeRep; friend class GameTableRep; friend class GameAGGRep; @@ -283,7 +284,6 @@ class GameStrategyRep : public std::enable_shared_from_this { bool m_valid{true}; GamePlayerRep *m_player; int m_number; - long m_offset{-1L}; std::string m_label; std::map m_behav; @@ -360,6 +360,7 @@ class GamePlayerRep : public std::enable_shared_from_this { friend class GameInfosetRep; friend class GameNodeRep; friend class StrategySupportProfile; + friend class PureStrategyProfileRep; template friend class MixedBehaviorProfile; template friend class MixedStrategyProfile; @@ -594,6 +595,24 @@ inline GameNode GameNodeRep::Actions::iterator::GetOwner() const { return m_chil enum class TraversalOrder { Preorder, Postorder }; +class CartesianProductSpace { +public: + std::vector m_radices; + std::vector m_strides; +}; + +class CartesianSubset { +public: + const CartesianProductSpace *m_space{nullptr}; + std::vector> m_allowedDigits; +}; + +template class CartesianTensor { +public: + const CartesianProductSpace *m_space{nullptr}; + std::vector m_data; +}; + /// This is the class for representing an arbitrary finite game. class GameRep : public std::enable_shared_from_this { friend class GameOutcomeRep; @@ -602,6 +621,7 @@ class GameRep : public std::enable_shared_from_this { friend class GamePlayerRep; friend class PureStrategyProfileRep; friend class TablePureStrategyProfileRep; + friend class StrategySupportProfile; template friend class MixedBehaviorProfile; template friend class MixedStrategyProfile; template friend class TableMixedStrategyProfileRep; @@ -609,6 +629,7 @@ class GameRep : public std::enable_shared_from_this { protected: std::vector> m_players; std::vector> m_outcomes; + mutable CartesianProductSpace m_pureStrategies; std::string m_title, m_comment; unsigned int m_version{0}; @@ -618,6 +639,7 @@ class GameRep : public std::enable_shared_from_this { //@{ /// Mark that the content of the game has changed void IncrementVersion() { m_version++; } + void IndexStrategies() const; //@} /// Hooks for derived classes to update lazily-computed orderings if required diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 37b5551d0..ccdda7d17 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -51,10 +51,10 @@ class AGGPureStrategyProfileRep : public PureStrategyProfileRep { Rational AGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - const std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; + const std::shared_ptr aggPtr = dynamic_cast(*m_game).aggPtr; std::vector s(aggPtr->getNumPlayers()); for (int i = 1; i <= aggPtr->getNumPlayers(); i++) { - s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; + s[i - 1] = GetStrategy(m_game->GetPlayer(i))->GetNumber() - 1; } return Rational(aggPtr->getPurePayoff(p_player->GetNumber() - 1, s)); } @@ -62,10 +62,10 @@ Rational AGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const Rational AGGPureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { const int player = p_strategy->GetPlayer()->GetNumber(); - const std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; + const std::shared_ptr aggPtr = dynamic_cast(*m_game).aggPtr; std::vector s(aggPtr->getNumPlayers()); for (int i = 1; i <= aggPtr->getNumPlayers(); i++) { - s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; + s[i - 1] = GetStrategy(m_game->GetPlayer(i))->GetNumber() - 1; } s[player - 1] = p_strategy->GetNumber() - 1; return Rational(aggPtr->getPurePayoff(player - 1, s)); @@ -236,23 +236,14 @@ GameAGGRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProfi bool GameAGGRep::IsConstSum() const { - auto profile = NewPureStrategyProfile(); - Rational sum(0); - for (const auto &player : m_players) { - sum += profile->GetPayoff(player); - } - - for (auto iter : StrategyContingencies(std::const_pointer_cast(shared_from_this()))) { - Rational newsum(0); - for (const auto &player : m_players) { - newsum += iter->GetPayoff(player); - } - if (newsum != sum) { - return false; - } - } - - return true; + auto payoff_sum = [&](const PureStrategyProfile &p) { + return sum_function(m_players, [&](const auto &player) { return p->GetPayoff(player); }); + }; + const Rational sum = payoff_sum(NewPureStrategyProfile()); + + auto contingencies = StrategyContingencies(std::const_pointer_cast(shared_from_this())); + return std::all_of(contingencies.begin(), contingencies.end(), + [&](const PureStrategyProfile &p) { return payoff_sum(p) == sum; }); } //------------------------------------------------------------------------ diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index e369a15a7..30ee5848f 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -50,12 +50,12 @@ class BAGGPureStrategyProfileRep : public PureStrategyProfileRep { Rational BAGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - const std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; - std::vector s(m_nfg->NumPlayers()); - for (size_t i = 1; i <= m_nfg->NumPlayers(); i++) { - s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; + const std::shared_ptr baggPtr = dynamic_cast(*m_game).baggPtr; + std::vector s(m_game->NumPlayers()); + for (size_t i = 1; i <= m_game->NumPlayers(); i++) { + s[i - 1] = GetStrategy(m_game->GetPlayer(i))->GetNumber() - 1; } - const int bp = dynamic_cast(*m_nfg).agent2baggPlayer[p_player->GetNumber()]; + const int bp = dynamic_cast(*m_game).agent2baggPlayer[p_player->GetNumber()]; const int tp = p_player->GetNumber() - 1 - baggPtr->typeOffset[bp - 1]; return Rational(baggPtr->getPurePayoff(bp - 1, tp, s)); } @@ -63,13 +63,13 @@ Rational BAGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const Rational BAGGPureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { const int player = p_strategy->GetPlayer()->GetNumber(); - const std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; - std::vector s(m_nfg->NumPlayers()); - for (size_t i = 1; i <= m_nfg->NumPlayers(); i++) { - s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; + const std::shared_ptr baggPtr = dynamic_cast(*m_game).baggPtr; + std::vector s(m_game->NumPlayers()); + for (size_t i = 1; i <= m_game->NumPlayers(); i++) { + s[i - 1] = GetStrategy(m_game->GetPlayer(i))->GetNumber() - 1; } s[player - 1] = p_strategy->GetNumber() - 1; - const int bp = dynamic_cast(*m_nfg).agent2baggPlayer[player]; + const int bp = dynamic_cast(*m_game).agent2baggPlayer[player]; const int tp = player - 1 - baggPtr->typeOffset[bp - 1]; return Rational(baggPtr->getPurePayoff(bp - 1, tp, s)); } diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 7087e1cb2..09356d501 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -34,36 +34,19 @@ namespace Gambit { class TablePureStrategyProfileRep : public PureStrategyProfileRep { protected: - long m_index{0L}; - - std::shared_ptr Copy() const override; + std::shared_ptr Copy() const override + { + return std::make_shared(*this); + } public: - explicit TablePureStrategyProfileRep(const Game &p_game); - void SetStrategy(const GameStrategy &) override; + explicit TablePureStrategyProfileRep(const Game &p_game) : PureStrategyProfileRep(p_game) {} GameOutcome GetOutcome() const override; void SetOutcome(GameOutcome p_outcome) override; Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; }; -//------------------------------------------------------------------------ -// TablePureStrategyProfileRep: Lifecycle -//------------------------------------------------------------------------ - -TablePureStrategyProfileRep::TablePureStrategyProfileRep(const Game &p_nfg) - : PureStrategyProfileRep(p_nfg) -{ - for (auto [player, strategy] : m_profile) { - m_index += strategy->m_offset; - } -} - -std::shared_ptr TablePureStrategyProfileRep::Copy() const -{ - return std::make_shared(*this); -} - Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes /*= false*/) { return std::make_shared(p_dim, p_sparseOutcomes); @@ -73,15 +56,9 @@ Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes /*= false*/) // TablePureStrategyProfileRep: Data access and manipulation //------------------------------------------------------------------------ -void TablePureStrategyProfileRep::SetStrategy(const GameStrategy &s) -{ - m_index += s->m_offset - m_profile.at(s->GetPlayer())->m_offset; - m_profile[s->GetPlayer()] = s; -} - GameOutcome TablePureStrategyProfileRep::GetOutcome() const { - if (const auto outcome = dynamic_cast(*m_nfg).m_results[m_index]) { + if (const auto outcome = dynamic_cast(*m_game).m_results.at(m_index)) { return outcome->shared_from_this(); } return nullptr; @@ -89,12 +66,12 @@ GameOutcome TablePureStrategyProfileRep::GetOutcome() const void TablePureStrategyProfileRep::SetOutcome(GameOutcome p_outcome) { - dynamic_cast(*m_nfg).m_results[m_index] = p_outcome.get(); + dynamic_cast(*m_game).m_results[m_index] = p_outcome.get(); } Rational TablePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - if (const auto outcome = dynamic_cast(*m_nfg).m_results[m_index]) { + if (const auto outcome = dynamic_cast(*m_game).m_results.at(m_index)) { return outcome->GetPayoff(p_player); } return Rational(0); @@ -103,15 +80,16 @@ Rational TablePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) cons Rational TablePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { const auto &player = p_strategy->GetPlayer(); - const GameOutcomeRep *outcome = - dynamic_cast(*m_nfg) - .m_results[m_index - m_profile.at(player)->m_offset + p_strategy->m_offset]; - if (outcome) { + const auto &[m_radices, m_strides] = m_game->m_pureStrategies; + const size_t index = player->GetNumber() - 1; + const long stride = m_strides[index]; + const long digit_old = (m_index / stride) % m_radices[index]; + const long digit_new = p_strategy->GetNumber() - 1; + const long new_index = m_index + (digit_new - digit_old) * stride; + if (const auto outcome = dynamic_cast(*m_game).m_results[new_index]) { return outcome->GetPayoff(player); } - else { - return Rational(0); - } + return Rational(0); } PureStrategyProfile GameTableRep::NewPureStrategyProfile() const @@ -137,6 +115,14 @@ template class TableMixedStrategyProfileRep : public MixedStrategyProf T &value) const; //@} + long StrategyOffset(const GameStrategy &s) const + { + const auto &space = this->GetSupport().GetGame()->m_pureStrategies; + const auto &player = s->GetPlayer(); + const size_t i = player->GetNumber() - 1; + return (s->GetNumber() - 1) * space.m_strides[i]; + } + public: explicit TableMixedStrategyProfileRep(const StrategySupportProfile &p_support) : MixedStrategyProfileRep(p_support) @@ -172,7 +158,7 @@ T TableMixedStrategyProfileRep::GetPayoff(int pl, int index, int current) con for (auto s : this->GetSupport().GetStrategies(this->GetSupport().GetGame()->GetPlayer(current))) { if ((*this)[s] != T(0)) { - sum += ((*this)[s] * GetPayoff(pl, index + s->m_offset, current + 1)); + sum += ((*this)[s] * GetPayoff(pl, index + StrategyOffset(s), current + 1)); } } return sum; @@ -201,7 +187,8 @@ void TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, int const_pl, int c for (auto s : this->GetSupport().GetStrategies(this->GetSupport().GetGame()->GetPlayer(cur_pl))) { if ((*this)[s] > T(0)) { - GetPayoffDeriv(pl, const_pl, cur_pl + 1, index + s->m_offset, prob * (*this)[s], value); + GetPayoffDeriv(pl, const_pl, cur_pl + 1, index + StrategyOffset(s), prob * (*this)[s], + value); } } } @@ -211,7 +198,7 @@ template T TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &strategy) const { T value = T(0); - GetPayoffDeriv(pl, strategy->GetPlayer()->GetNumber(), 1, strategy->m_offset, T(1), value); + GetPayoffDeriv(pl, strategy->GetPlayer()->GetNumber(), 1, StrategyOffset(strategy), T(1), value); return value; } @@ -234,7 +221,7 @@ void TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, int const_pl1, int for (auto s : this->GetSupport().GetStrategies(this->GetSupport().GetGame()->GetPlayer(cur_pl))) { if ((*this)[s] > static_cast(0)) { - GetPayoffDeriv(pl, const_pl1, const_pl2, cur_pl + 1, index + s->m_offset, + GetPayoffDeriv(pl, const_pl1, const_pl2, cur_pl + 1, index + StrategyOffset(s), prob * (*this)[s], value); } } @@ -253,7 +240,7 @@ T TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &st T value = T(0); GetPayoffDeriv(pl, player1->GetNumber(), player2->GetNumber(), 1, - strategy1->m_offset + strategy2->m_offset, T(1), value); + StrategyOffset(strategy1) + StrategyOffset(strategy2), T(1), value); return value; } @@ -304,23 +291,14 @@ Game GameTableRep::Copy() const bool GameTableRep::IsConstSum() const { - auto profile = NewPureStrategyProfile(); - Rational sum(0); - for (const auto &player : m_players) { - sum += profile->GetPayoff(player); - } + auto payoff_sum = [&](const PureStrategyProfile &p) { + return sum_function(m_players, [&](const auto &player) { return p->GetPayoff(player); }); + }; + const Rational sum = payoff_sum(NewPureStrategyProfile()); - for (const auto iter : - StrategyContingencies(std::const_pointer_cast(shared_from_this()))) { - Rational newsum(0); - for (const auto &player : m_players) { - newsum += iter->GetPayoff(player); - } - if (newsum != sum) { - return false; - } - } - return true; + auto contingencies = StrategyContingencies(std::const_pointer_cast(shared_from_this())); + return std::all_of(contingencies.begin(), contingencies.end(), + [&](const PureStrategyProfile &p) { return payoff_sum(p) == sum; }); } Rational GameTableRep::GetPlayerMinPayoff(const GamePlayer &p_player) const @@ -431,9 +409,13 @@ GameStrategy GameTableRep::NewStrategy(const GamePlayer &p_player, const std::st throw MismatchException(); } IncrementVersion(); + std::vector old_radices; + for (const auto &player : m_players) { + old_radices.push_back(player->m_strategies.size()); + } p_player->m_strategies.push_back(std::make_shared( p_player.get(), p_player->m_strategies.size() + 1, p_label)); - RebuildTable(); + RebuildTable(old_radices); return p_player->m_strategies.back(); } @@ -493,52 +475,37 @@ GameTableRep::NewMixedStrategyProfile(const Rational &, const StrategySupportPro /// This rebuilds a new table of outcomes after the game has been /// redimensioned (change in the number of strategies). Strategies /// numbered -1 are identified as the new strategies. -void GameTableRep::RebuildTable() +void GameTableRep::RebuildTable(const std::vector &old_radices) { - long size = 1L; - std::vector offsets; - for (const auto &player : m_players) { - offsets.push_back(size); - size *= player->m_strategies.size(); + std::vector old_strides(old_radices.size()); + long stride = 1; + for (size_t i = 0; i < old_radices.size(); ++i) { + old_strides[i] = stride; + stride *= old_radices[i]; } + const long old_size = stride; - std::vector newResults(size); - std::fill(newResults.begin(), newResults.end(), nullptr); - - for (auto iter : StrategyContingencies( - StrategySupportProfile(std::const_pointer_cast(shared_from_this())))) { - long newindex = 0L; - for (const auto &player : m_players) { - if (iter->GetStrategy(player)->m_offset < 0) { - // This is a contingency involving a new strategy... skip - newindex = -1L; - break; - } - else { - newindex += (iter->GetStrategy(player)->m_number - 1) * offsets[player->m_number - 1]; - } - } + long new_size = 1; + std::vector new_strides(m_players.size()); + for (size_t i = 0; i < m_players.size(); ++i) { + new_strides[i] = new_size; + new_size *= m_players[i]->m_strategies.size(); + } - if (newindex >= 0 && iter->GetOutcome() != nullptr) { - newResults[newindex] = iter->GetOutcome().get(); + std::vector newResults(new_size, nullptr); + for (long old_index = 0; old_index < old_size; ++old_index) { + if (m_results[old_index] == nullptr) { + continue; + } + long new_index = 0; + for (size_t i = 0; i < m_players.size(); ++i) { + const long digit = (old_index / old_strides[i]) % old_radices[i]; + new_index += digit * new_strides[i]; } + newResults[new_index] = m_results[old_index]; } m_results.swap(newResults); IndexStrategies(); } -void GameTableRep::IndexStrategies() const -{ - long offset = 1L; - for (auto player : m_players) { - int st = 1; - for (auto strategy : player->m_strategies) { - strategy->m_number = st; - strategy->m_offset = (st - 1) * offset; - st++; - } - offset *= player->m_strategies.size(); - } -} - } // end namespace Gambit diff --git a/src/games/gametable.h b/src/games/gametable.h index db7149cf7..04facee7d 100644 --- a/src/games/gametable.h +++ b/src/games/gametable.h @@ -40,8 +40,7 @@ class GameTableRep : public GameExplicitRep { /// @name Private auxiliary functions //@{ - void IndexStrategies() const; - void RebuildTable(); + void RebuildTable(const std::vector &old_radices); //@} public: diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 3df2700c0..6092f138d 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -952,6 +952,7 @@ void GameTreeRep::BuildComputedValues() const std::map ptr, whichbranch; player->MakeReducedStrats(m_root.get(), nullptr, behav, ptr, whichbranch); } + IndexStrategies(); m_computedValues = true; } @@ -1384,7 +1385,7 @@ MixedStrategyProfile GameTreeRep::NewMixedStrategyProfile(double) const if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } - EnsureInfosetOrdering(); + BuildComputedValues(); return StrategySupportProfile(std::const_pointer_cast(shared_from_this())) .NewMixedStrategyProfile(); } @@ -1394,7 +1395,7 @@ MixedStrategyProfile GameTreeRep::NewMixedStrategyProfile(const Ration if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } - EnsureInfosetOrdering(); + BuildComputedValues(); return StrategySupportProfile(std::const_pointer_cast(shared_from_this())) .NewMixedStrategyProfile(); } @@ -1405,7 +1406,7 @@ GameTreeRep::NewMixedStrategyProfile(double, const StrategySupportProfile &spt) if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } - EnsureInfosetOrdering(); + BuildComputedValues(); return MixedStrategyProfile(std::make_unique>(spt)); } @@ -1415,7 +1416,7 @@ GameTreeRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProf if (!IsPerfectRecall()) { throw UndefinedException("Mixed strategies not supported for games with imperfect recall."); } - EnsureInfosetOrdering(); + BuildComputedValues(); return MixedStrategyProfile( std::make_unique>(spt)); } @@ -1445,7 +1446,7 @@ class TreePureStrategyProfileRep : public PureStrategyProfileRep { PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const { - EnsureInfosetOrdering(); + BuildComputedValues(); return PureStrategyProfile(std::make_shared( std::const_pointer_cast(shared_from_this()))); } @@ -1456,11 +1457,11 @@ PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const Rational TreePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - PureBehaviorProfile behav(m_nfg); - for (const auto &player : m_nfg->GetPlayers()) { + PureBehaviorProfile behav(m_game); + for (const auto &player : m_game->GetPlayers()) { for (const auto &infoset : player->GetInfosets()) { try { - behav.SetAction(infoset->GetAction(m_profile.at(player)->m_behav[infoset.get()])); + behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav[infoset.get()])); } catch (std::out_of_range &) { } diff --git a/src/games/stratpure.cc b/src/games/stratpure.cc deleted file mode 100644 index 0cedff018..000000000 --- a/src/games/stratpure.cc +++ /dev/null @@ -1,95 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/games/stratpure.cc -// Implementation of pure strategy profile -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gambit.h" -#include "stratpure.h" - -namespace Gambit { - -//======================================================================== -// class PureStrategyProfileRep -//======================================================================== - -PureStrategyProfileRep::PureStrategyProfileRep(const Game &p_game) : m_nfg(p_game) -{ - for (const auto &player : m_nfg->GetPlayers()) { - m_profile[player] = player->GetStrategies().front(); - } -} - -MixedStrategyProfile PureStrategyProfileRep::ToMixedStrategyProfile() const -{ - auto temp = m_nfg->NewMixedStrategyProfile(Rational(0)); - temp = Rational(0); - for (auto [player, strategy] : m_profile) { - temp[strategy] = Rational(1); - } - return temp; -} - -//=========================================================================== -// class StrategyContingencies -//=========================================================================== - -StrategyContingencies::StrategyContingencies(const StrategySupportProfile &p_support, - const std::vector &p_frozen) - : m_support(p_support), m_frozen(p_frozen) -{ - for (auto player : m_support.GetGame()->GetPlayers()) { - auto frozen = std::find_if(m_frozen.begin(), m_frozen.end(), [player](const GameStrategy &s) { - return s->GetPlayer() == player; - }); - if (frozen == m_frozen.end()) { - m_unfrozen.push_back(player); - } - } -} - -StrategyContingencies::iterator::iterator(StrategyContingencies *p_cont, bool p_end) - : m_cont(p_cont), m_atEnd(p_end), - m_profile(p_cont->m_support.GetGame()->NewPureStrategyProfile()) -{ - for (auto strategy : m_cont->m_frozen) { - m_profile->SetStrategy(strategy); - } - for (auto player : m_cont->m_unfrozen) { - m_currentStrat[player] = m_cont->m_support.GetStrategies(player).begin(); - m_profile->SetStrategy(*m_currentStrat[player]); - } -} - -StrategyContingencies::iterator &StrategyContingencies::iterator::operator++() -{ - for (auto player : m_cont->m_unfrozen) { - ++m_currentStrat[player]; - if (m_currentStrat[player] != m_cont->m_support.GetStrategies(player).end()) { - m_profile->SetStrategy(*m_currentStrat[player]); - return *this; - } - m_currentStrat[player] = m_cont->m_support.GetStrategies(player).begin(); - m_profile->SetStrategy(*m_currentStrat[player]); - } - m_atEnd = true; - return *this; -} - -} // namespace Gambit diff --git a/src/games/stratpure.h b/src/games/stratpure.h index ac1d99c4b..24596fe7f 100644 --- a/src/games/stratpure.h +++ b/src/games/stratpure.h @@ -36,11 +36,17 @@ class PureStrategyProfileRep { friend class PureStrategyProfile; protected: - Game m_nfg; - std::map m_profile; + Game m_game; + long m_index{0}; /// Construct a new strategy profile - explicit PureStrategyProfileRep(const Game &p_game); + explicit PureStrategyProfileRep(const Game &p_game) : m_game(p_game) + { + for (const auto &stride : p_game->m_pureStrategies.m_strides) { + constexpr long digit = 0; // The digit of the first strategy + m_index += digit * stride; + } + } public: virtual ~PureStrategyProfileRep() = default; @@ -50,18 +56,28 @@ class PureStrategyProfileRep { /// @name Data access and manipulation //@{ - const Game &GetGame() const { return m_nfg; } + const Game &GetGame() const { return m_game; } /// Get the strategy played by the player - const GameStrategy &GetStrategy(const GamePlayer &p_player) const + GameStrategy GetStrategy(const GamePlayer &p_player) const { - return m_profile.at(p_player); + const auto &[m_radices, m_strides] = m_game->m_pureStrategies; + const size_t index = p_player->m_number - 1; + const long stride = m_strides[index]; + const long radix = m_radices[index]; + const long digit = (m_index / stride) % radix; + return p_player->m_strategies[digit]; } /// Set the strategy for a player - virtual void SetStrategy(const GameStrategy &p_strategy) + void SetStrategy(const GameStrategy &p_strategy) { - m_profile[p_strategy->GetPlayer()] = p_strategy; + const auto &[m_radices, m_strides] = m_game->m_pureStrategies; + const size_t index = p_strategy->m_player->m_number - 1; + const long stride = m_strides[index]; + const long digit_old = (m_index / stride) % m_radices[index]; + const long digit_new = p_strategy->m_number - 1; + m_index += (digit_new - digit_old) * stride; } /// Get the outcome that results from the profile @@ -82,13 +98,14 @@ class PureStrategyProfileRep { }; class PureStrategyProfile { -private: std::shared_ptr rep; public: PureStrategyProfile(const PureStrategyProfile &r) : rep(r.rep->Copy()) {} - explicit PureStrategyProfile(std::shared_ptr p_rep) : rep(p_rep) {} + explicit PureStrategyProfile(const std::shared_ptr &p_rep) : rep(p_rep) + { + } ~PureStrategyProfile() = default; @@ -104,42 +121,77 @@ class PureStrategyProfile { }; class StrategyContingencies { -private: StrategySupportProfile m_support; - std::vector m_unfrozen; - std::vector m_frozen; public: class iterator { - private: StrategyContingencies *m_cont; - bool m_atEnd; - std::map m_currentStrat; + bool m_atEnd{false}; + std::vector m_pos; PureStrategyProfile m_profile; public: - iterator(StrategyContingencies *, bool p_end); + using iterator_category = std::forward_iterator_tag; + using value_type = PureStrategyProfile; + using difference_type = std::ptrdiff_t; + using pointer = PureStrategyProfile *; + using reference = PureStrategyProfile &; + + iterator(StrategyContingencies *cont, bool p_end) + : m_cont(cont), m_atEnd(p_end), + m_profile(cont->m_support.GetGame()->NewPureStrategyProfile()) + { + if (m_atEnd) { + return; + } + const size_t n = m_cont->m_support.GetPlayers().size(); + m_pos.assign(n, 0); + for (size_t i = 0; i < n; ++i) { + const GamePlayer player = m_cont->m_support.GetGame()->GetPlayer(i + 1); + auto support = m_cont->m_support.GetStrategies(player); + m_profile->SetStrategy(support[0]); + } + } - iterator &operator++(); + iterator &operator++() + { + const size_t n = m_pos.size(); + for (size_t i = 0; i < n; ++i) { + const GamePlayer player = m_cont->m_support.GetGame()->GetPlayer(i + 1); + auto support = m_cont->m_support.GetStrategies(player); + ++m_pos[i]; + if (m_pos[i] < support.size()) { + m_profile->SetStrategy(support[m_pos[i]]); + return *this; + } + m_pos[i] = 0; + m_profile->SetStrategy(support[0]); + } - bool operator==(const iterator &p_other) const + m_atEnd = true; + return *this; + } + + bool operator==(const iterator &other) const { - if (m_atEnd && p_other.m_atEnd && m_cont == p_other.m_cont) { - return true; + if (m_atEnd && other.m_atEnd) { + return m_cont == other.m_cont; } - if (m_atEnd != p_other.m_atEnd || m_cont != p_other.m_cont) { + if (m_atEnd != other.m_atEnd) { return false; } - return (m_profile.operator->() == p_other.m_profile.operator->()); + return m_profile.operator->() == other.m_profile.operator->(); } - bool operator!=(const iterator &p_other) const { return !(*this == p_other); } + + bool operator!=(const iterator &other) const { return !(*this == other); } PureStrategyProfile &operator*() { return m_profile; } const PureStrategyProfile &operator*() const { return m_profile; } }; - explicit StrategyContingencies(const StrategySupportProfile &, - const std::vector &p_frozen = {}); + explicit StrategyContingencies(const Game &p_game) : m_support(StrategySupportProfile(p_game)) {} + explicit StrategyContingencies(const StrategySupportProfile &support) : m_support(support) {} + iterator begin() { return {this, false}; } iterator end() { return {this, true}; } }; diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index fe818cafe..296d65af4 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -32,51 +32,48 @@ namespace Gambit { // class StrategySupportProfile //=========================================================================== -StrategySupportProfile::StrategySupportProfile(const Game &p_nfg) : m_nfg(p_nfg) +StrategySupportProfile::StrategySupportProfile(const Game &p_game) : m_game(p_game) { - for (auto player : m_nfg->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - m_support[player].push_back(strategy); - } + m_game->BuildComputedValues(); + m_strategies.m_space = &p_game->m_pureStrategies; + m_strategies.m_allowedDigits.resize(p_game->NumPlayers()); + + for (size_t i = 0; i < p_game->NumPlayers(); ++i) { + const int radix = p_game->m_pureStrategies.m_radices[i]; + auto &digits = m_strategies.m_allowedDigits[i]; + digits.resize(radix); + std::iota(digits.begin(), digits.end(), 0); } } -Array StrategySupportProfile::NumStrategies() const -{ - Array dim(m_support.size()); - std::transform( - m_support.cbegin(), m_support.cend(), dim.begin(), - [](std::pair> a) { return a.second.size(); }); - return dim; -} - int StrategySupportProfile::MixedProfileLength() const { - return std::accumulate(m_support.cbegin(), m_support.cend(), 0, - [](size_t tot, std::pair> a) { - return tot + a.second.size(); - }); + return sum_function(m_strategies.m_allowedDigits, [](const std::vector &digits) { + return static_cast(digits.size()); + }); } template <> MixedStrategyProfile StrategySupportProfile::NewMixedStrategyProfile() const { - return m_nfg->NewMixedStrategyProfile(0.0, *this); + return m_game->NewMixedStrategyProfile(0.0, *this); } template <> MixedStrategyProfile StrategySupportProfile::NewMixedStrategyProfile() const { - return m_nfg->NewMixedStrategyProfile(Rational(0), *this); + return m_game->NewMixedStrategyProfile(Rational(0), *this); } -bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_support) const +bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_other) const { - for (auto player : m_nfg->GetPlayers()) { - if (!std::includes(p_support.m_support.at(player).begin(), - p_support.m_support.at(player).end(), m_support.at(player).begin(), - m_support.at(player).end(), - [](const GameStrategy &s, const GameStrategy &t) { - return s->GetNumber() < t->GetNumber(); - })) { + if (m_game != p_other.m_game) { + return false; + } + const auto &A = m_strategies.m_allowedDigits; + const auto &B = p_other.m_strategies.m_allowedDigits; + const size_t n = A.size(); + + for (size_t i = 0; i < n; ++i) { + if (!std::includes(B[i].begin(), B[i].end(), A[i].begin(), A[i].end())) { return false; } } @@ -85,8 +82,8 @@ bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_support) void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const { - auto players = m_nfg->GetPlayers(); - p_file << "NFG 1 R " << std::quoted(m_nfg->GetTitle()) << ' ' + auto players = m_game->GetPlayers(); + p_file << "NFG 1 R " << std::quoted(m_game->GetTitle()) << ' ' << FormatList(players, [](const GamePlayer &p) { return QuoteString(p->GetLabel()); }) << std::endl << std::endl; @@ -97,9 +94,9 @@ void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const }) << std::endl; } p_file << "}" << std::endl; - p_file << std::quoted(m_nfg->GetComment()) << std::endl << std::endl; + p_file << std::quoted(m_game->GetComment()) << std::endl << std::endl; - for (auto iter : StrategyContingencies(*this)) { + for (const auto &iter : StrategyContingencies(*this)) { p_file << FormatList( players, [&iter](const GamePlayer &p) { @@ -112,25 +109,32 @@ void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const void StrategySupportProfile::AddStrategy(const GameStrategy &p_strategy) { - auto &support = m_support[p_strategy->GetPlayer()]; - auto pos = std::find_if(support.begin(), support.end(), [p_strategy](const GameStrategy &s) { - return s->GetNumber() >= p_strategy->GetNumber(); - }); - if (pos == support.end() || *pos != p_strategy) { - // Strategy is not in the support for the player; add at this location to keep sorted by number - support.insert(pos, p_strategy); + if (p_strategy->GetGame() != m_game) { + throw MismatchException(); + } + const size_t index = p_strategy->GetPlayer()->GetNumber() - 1; + const int digit = p_strategy->GetNumber() - 1; + auto &digits = m_strategies.m_allowedDigits[index]; + auto pos = std::lower_bound(digits.begin(), digits.end(), digit); + if (pos == digits.end() || *pos != digit) { + digits.insert(pos, digit); } } bool StrategySupportProfile::RemoveStrategy(const GameStrategy &p_strategy) { - auto &support = m_support[p_strategy->GetPlayer()]; - if (support.size() == 1) { + if (p_strategy->GetGame() != m_game) { + throw MismatchException(); + } + const size_t index = p_strategy->GetPlayer()->GetNumber() - 1; + const int digit = p_strategy->GetNumber() - 1; + auto &digits = m_strategies.m_allowedDigits[index]; + if (digits.size() == 1) { return false; } - auto pos = std::find(support.begin(), support.end(), p_strategy); - if (pos != support.end()) { - support.erase(pos); + auto pos = std::lower_bound(digits.begin(), digits.end(), digit); + if (pos != digits.end() && *pos == digit) { + digits.erase(pos); return true; } return false; @@ -145,7 +149,7 @@ bool StrategySupportProfile::Dominates(const GameStrategy &s, const GameStrategy { bool equal = true; - for (auto iter : StrategyContingencies(*this)) { + for (const auto &iter : StrategyContingencies(*this)) { const Rational ap = iter->GetStrategyValue(s); const Rational bp = iter->GetStrategyValue(t); if (p_strict && ap <= bp) { @@ -263,7 +267,7 @@ bool UndominatedForPlayer(const StrategySupportProfile &p_support, StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p_external) const { StrategySupportProfile newSupport(*this); - for (auto player : m_nfg->GetPlayers()) { + for (auto player : m_game->GetPlayers()) { UndominatedForPlayer(*this, newSupport, player, p_strict, p_external); } return newSupport; diff --git a/src/games/stratspt.h b/src/games/stratspt.h index bbae3eb3d..502bbbbd1 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -39,31 +39,89 @@ namespace Gambit { /// Within the support, strategies are maintained in the same order /// in which they appear in the underlying game. class StrategySupportProfile { -protected: - Game m_nfg; - std::map> m_support; + Game m_game; + CartesianSubset m_strategies; public: class Support { - private: const StrategySupportProfile *m_profile; - GamePlayer m_player; + size_t m_playerIndex; public: - using const_iterator = std::vector::const_iterator; + class const_iterator { + const StrategySupportProfile *m_profile{nullptr}; + size_t m_playerIndex{0}; + std::vector::const_iterator m_it; + + public: + using value_type = GameStrategy; + using reference = GameStrategy; + using pointer = void; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + const_iterator(const StrategySupportProfile *profile, const size_t playerIndex, + const std::vector::const_iterator it) + : m_profile(profile), m_playerIndex(playerIndex), m_it(it) + { + } + + GameStrategy operator*() const + { + const auto &player = m_profile->m_game->GetPlayer(m_playerIndex + 1); + return player->GetStrategy(*m_it + 1); + } + + const_iterator &operator++() + { + ++m_it; + return *this; + } + + bool operator==(const const_iterator &other) const { return m_it == other.m_it; } + + bool operator!=(const const_iterator &other) const { return !(*this == other); } + }; + + Support() : m_profile(nullptr), m_playerIndex(0) {} + + Support(const StrategySupportProfile *profile, GamePlayer player) + : m_profile(profile), m_playerIndex(player->GetNumber() - 1) + { + } + + size_t size() const { return m_profile->m_strategies.m_allowedDigits[m_playerIndex].size(); } + + GameStrategy operator[](const size_t index) const + { + const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex][index]; + return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1); + } - Support() : m_profile(nullptr), m_player(nullptr) {} - Support(const StrategySupportProfile *p_profile, GamePlayer p_player) - : m_profile(p_profile), m_player(p_player) + GameStrategy front() const { + const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex].front(); + return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1); } - size_t size() const { return m_profile->m_support.at(m_player).size(); } - GameStrategy front() const { return m_profile->m_support.at(m_player).front(); } - GameStrategy back() const { return m_profile->m_support.at(m_player).back(); } + GameStrategy back() const + { + const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex].back(); + return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1); + } + + const_iterator begin() const + { + const auto &digits = m_profile->m_strategies.m_allowedDigits[m_playerIndex]; + return {m_profile, m_playerIndex, digits.begin()}; + } - const_iterator begin() const { return m_profile->m_support.at(m_player).begin(); } - const_iterator end() const { return m_profile->m_support.at(m_player).end(); } + const_iterator end() const + { + const auto &digits = m_profile->m_strategies.m_allowedDigits[m_playerIndex]; + return {m_profile, m_playerIndex, digits.end()}; + } }; /// @name Lifecycle @@ -77,22 +135,21 @@ class StrategySupportProfile { /// Test for the equality of two supports (same strategies for all players) bool operator==(const StrategySupportProfile &p_support) const { - return (m_support == p_support.m_support); + return m_game == p_support.m_game && + m_strategies.m_allowedDigits == p_support.m_strategies.m_allowedDigits; } /// Test for the inequality of two supports bool operator!=(const StrategySupportProfile &p_support) const { - return (m_support != p_support.m_support); + return m_game != p_support.m_game || + m_strategies.m_allowedDigits != p_support.m_strategies.m_allowedDigits; } //@} /// @name General information //@{ /// Returns the game on which the support is defined. - Game GetGame() const { return m_nfg; } - - /// Returns the number of strategies in the support for all players. - Array NumStrategies() const; + Game GetGame() const { return m_game; } /// Returns the total number of strategies in the support. int MixedProfileLength() const; @@ -100,14 +157,19 @@ class StrategySupportProfile { template MixedStrategyProfile NewMixedStrategyProfile() const; /// Returns the number of players in the game - int NumPlayers() const { return m_nfg->NumPlayers(); } + int NumPlayers() const { return m_game->NumPlayers(); } /// Returns the set of players in the game - GameRep::Players GetPlayers() const { return m_nfg->GetPlayers(); } + GameRep::Players GetPlayers() const { return m_game->GetPlayers(); } /// Returns the set of strategies in the support for a player Support GetStrategies(const GamePlayer &p_player) const { return {this, p_player}; } /// Returns true exactly when the strategy is in the support. - bool Contains(const GameStrategy &s) const { return contains(m_support.at(s->GetPlayer()), s); } + bool Contains(const GameStrategy &s) const + { + const auto &digits = m_strategies.m_allowedDigits[s->GetPlayer()->GetNumber() - 1]; + const int digit = s->GetNumber() - 1; + return std::binary_search(digits.begin(), digits.end(), digit); + } /// Returns true iff this support is a (weak) subset of the specified support bool IsSubsetOf(const StrategySupportProfile &) const; @@ -131,6 +193,19 @@ class StrategySupportProfile { /// player, it is not removed. Returns true if the removal was /// executed, and false if not. bool RemoveStrategy(const GameStrategy &); + + StrategySupportProfile RestrictTo(const GameStrategy &p_strategy) const + { + if (p_strategy->GetGame() != m_game) { + throw MismatchException(); + } + const GamePlayer player = p_strategy->GetPlayer(); + const size_t player_index = player->GetNumber() - 1; + const int digit = p_strategy->GetNumber() - 1; + StrategySupportProfile restricted(*this); + restricted.m_strategies.m_allowedDigits[player_index].assign(1, digit); + return restricted; + } //@} /// @name Identification of dominated strategies diff --git a/src/games/writer.cc b/src/games/writer.cc index abffe1ad4..744f3efad 100644 --- a/src/games/writer.cc +++ b/src/games/writer.cc @@ -31,8 +31,10 @@ std::string WriteHTMLFile(const Game &p_game, const GamePlayer &p_rowPlayer, std::string theHtml; theHtml += "

" + p_game->GetTitle() + "

\n"; - for (auto iter : StrategyContingencies( - p_game, {p_rowPlayer->GetStrategies().front(), p_colPlayer->GetStrategies().front()})) { + auto support = StrategySupportProfile(p_game) + .RestrictTo(p_rowPlayer->GetStrategies().front()) + .RestrictTo(p_colPlayer->GetStrategies().front()); + for (const auto &iter : StrategyContingencies(support)) { if (p_game->NumPlayers() > 2) { theHtml += "
Subtable with strategies:
"; for (auto player : p_game->GetPlayers()) { @@ -99,8 +101,10 @@ std::string WriteLaTeXFile(const Game &p_game, const GamePlayer &p_rowPlayer, { std::string theHtml; - for (auto iter : StrategyContingencies( - p_game, {p_rowPlayer->GetStrategies().front(), p_colPlayer->GetStrategies().front()})) { + auto support = StrategySupportProfile(p_game) + .RestrictTo(p_rowPlayer->GetStrategies().front()) + .RestrictTo(p_colPlayer->GetStrategies().front()); + for (const auto &iter : StrategyContingencies(support)) { theHtml += "\\begin{game}{"; theHtml += std::to_string(p_rowPlayer->GetStrategies().size()); theHtml += "}{"; diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index aad63d3ea..3334b7de7 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -63,7 +63,7 @@ IndifferenceEquation(std::shared_ptr space, const StrategySupport { Polynomial equation(space); - for (auto iter : StrategyContingencies(support, {s1})) { + for (const auto &iter : StrategyContingencies(support.RestrictTo(s1))) { Polynomial term(space, 1); for (auto player : support.GetGame()->GetPlayers()) { if (player != s1->GetPlayer()) { diff --git a/src/solvers/enumpure/enumpure.h b/src/solvers/enumpure/enumpure.h index 6dcf4bb18..7b2563a02 100644 --- a/src/solvers/enumpure/enumpure.h +++ b/src/solvers/enumpure/enumpure.h @@ -53,9 +53,9 @@ inline std::list> EnumPureStrategySolve( "Computing equilibria of games with imperfect recall is not supported."); } std::list> solutions; - for (auto citer : StrategyContingencies(p_game)) { - if (IsNash(citer)) { - solutions.push_back(citer->ToMixedStrategyProfile()); + for (const auto &profile : StrategyContingencies(p_game)) { + if (IsNash(profile)) { + solutions.push_back(profile->ToMixedStrategyProfile()); p_onEquilibrium(solutions.back(), "NE"); } } diff --git a/src/solvers/gtracer/gtracer.cc b/src/solvers/gtracer/gtracer.cc index 3b505651e..164e98c97 100644 --- a/src/solvers/gtracer/gtracer.cc +++ b/src/solvers/gtracer/gtracer.cc @@ -44,7 +44,7 @@ std::shared_ptr BuildGame(const Game &p_game, bool p_scaled) std::shared_ptr A(new nfgame(actions)); std::vector profile(players.size()); - for (auto iter : StrategyContingencies(p_game)) { + for (const auto &iter : StrategyContingencies(p_game)) { std::transform(players.cbegin(), players.cend(), profile.begin(), [iter](const GamePlayer &p) { return iter->GetStrategy(p)->GetNumber() - 1; }); for (auto player : players) {