diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 993d7be4b..678194f6c 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -25,7 +25,6 @@ #include "gambit.h" #include "behavmixed.h" -#include "gametree.h" namespace Gambit { @@ -39,11 +38,9 @@ MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) m_gameversion(p_game->GetVersion()) { int index = 1; - for (const auto &player : p_game->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - for (const auto &action : infoset->GetActions()) { - m_profileIndex[action] = index++; - } + for (const auto &infoset : p_game->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + m_profileIndex[action] = index++; } } SetCentroid(); @@ -55,15 +52,13 @@ MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_su m_gameversion(p_support.GetGame()->GetVersion()) { int index = 1; - for (const auto &player : p_support.GetGame()->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - for (const auto &action : infoset->GetActions()) { - if (p_support.Contains(action)) { - m_profileIndex[action] = index++; - } - else { - m_profileIndex[action] = -1; - } + for (const auto &infoset : p_support.GetGame()->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + if (p_support.Contains(action)) { + m_profileIndex[action] = index++; + } + else { + m_profileIndex[action] = -1; } } } @@ -71,13 +66,13 @@ MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_su } template -void MixedBehaviorProfile::BehaviorStrat(GamePlayer &player, GameNode &p_node, +void MixedBehaviorProfile::BehaviorStrat(const GamePlayer &player, const GameNode &p_node, std::map &map_nvals, std::map &map_bvals) { - for (auto child : p_node->GetChildren()) { + for (const auto &child : p_node->GetChildren()) { if (p_node->GetPlayer() == player) { - if (map_nvals[p_node] > T(0) && map_nvals[child] > T(0)) { + if (map_nvals[p_node] > static_cast(0) && map_nvals[child] > static_cast(0)) { (*this)[child->GetPriorAction()] = map_nvals[child] / map_nvals[p_node]; } } @@ -87,7 +82,7 @@ void MixedBehaviorProfile::BehaviorStrat(GamePlayer &player, GameNode &p_node template void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp, - GamePlayer &player, + const GamePlayer &player, const std::map &actions, GameNodeRep *node, std::map &map_nvals, std::map &map_bvals) @@ -99,22 +94,22 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp if (node->GetPlayer() == player) { if (contains(actions, node->m_infoset) && actions.at(node->GetInfoset().get()) == static_cast(i)) { - prob = T(1); + prob = static_cast(1); } else { - prob = T(0); + prob = static_cast(0); } } else if (GetSupport().Contains(node->GetInfoset()->GetAction(i))) { const int num_actions = GetSupport().GetActions(node->GetInfoset()).size(); - prob = T(1) / T(num_actions); + prob = static_cast(1) / static_cast(num_actions); } else { - prob = T(0); + prob = static_cast(0); } } - else { // n.GetPlayer() == 0 - prob = T(node->m_infoset->GetActionProb(node->m_infoset->GetAction(i))); + else { + prob = static_cast(node->m_infoset->GetActionProb(node->m_infoset->GetAction(i))); } auto child = node->m_children[i - 1]; @@ -132,12 +127,10 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p m_gameversion(p_profile.GetGame()->GetVersion()) { int index = 1; - for (const auto &player : p_profile.GetGame()->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - for (const auto &action : infoset->GetActions()) { - m_profileIndex[action] = index; - m_probs[index++] = static_cast(0); - } + for (const auto &infoset : p_profile.GetGame()->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + m_profileIndex[action] = index; + m_probs[index++] = static_cast(0); } } @@ -146,18 +139,17 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p const StrategySupportProfile &support = p_profile.GetSupport(); GameRep *game = m_support.GetGame().get(); - for (auto player : game->GetPlayers()) { + for (const auto &player : game->GetPlayers()) { std::map map_nvals, map_bvals; - for (auto strategy : support.GetStrategies(player)) { - if (p_profile[strategy] > T(0)) { + for (const auto &strategy : support.GetStrategies(player)) { + if (p_profile[strategy] > static_cast(0)) { const auto &actions = strategy->m_behav; map_bvals[root->shared_from_this()] = p_profile[strategy]; RealizationProbs(p_profile, player, actions, root, map_nvals, map_bvals); } } - map_nvals[root->shared_from_this()] = T(1); // set the root nval - auto root = m_support.GetGame()->GetRoot(); - BehaviorStrat(player, root, map_nvals, map_bvals); + map_nvals[root->shared_from_this()] = static_cast(1); + BehaviorStrat(player, m_support.GetGame()->GetRoot(), map_nvals, map_bvals); } } @@ -171,15 +163,9 @@ MixedBehaviorProfile::operator=(const MixedBehaviorProfile &p_profile) if (m_support != p_profile.m_support) { throw MismatchException(); } - InvalidateCache(); m_probs = p_profile.m_probs; m_gameversion = p_profile.m_gameversion; - map_realizProbs = p_profile.map_realizProbs; - map_beliefs = p_profile.map_beliefs; - map_nodeValues = p_profile.map_nodeValues; - map_infosetValues = p_profile.map_infosetValues; - map_actionValues = p_profile.map_actionValues; - map_regret = p_profile.map_regret; + m_cache = p_profile.m_cache; return *this; } @@ -264,13 +250,12 @@ template MixedBehaviorProfile MixedBehaviorProfile::ToFullSuppor template T MixedBehaviorProfile::GetLiapValue() const { CheckVersion(); - ComputeSolutionData(); - + EnsureRegrets(); 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))); + value += sqr(std::max(m_cache.m_actionValues[action] - m_cache.m_infosetValues[infoset], + static_cast(0))); } } return value; @@ -279,36 +264,33 @@ template T MixedBehaviorProfile::GetLiapValue() const template const T &MixedBehaviorProfile::GetRealizProb(const GameNode &node) const { CheckVersion(); - ComputeSolutionData(); - return map_realizProbs[node]; + EnsureRealizations(); + return m_cache.m_realizProbs[node]; } -template T MixedBehaviorProfile::GetInfosetProb(const GameInfoset &iset) const +template T MixedBehaviorProfile::GetInfosetProb(const GameInfoset &p_infoset) const { CheckVersion(); - ComputeSolutionData(); - T prob = T(0); - for (auto member : iset->GetMembers()) { - prob += map_realizProbs[member]; - } - return prob; + EnsureRealizations(); + return sum_function(p_infoset->GetMembers(), + [&](const auto &node) -> T { return m_cache.m_realizProbs[node]; }); } template const T &MixedBehaviorProfile::GetBeliefProb(const GameNode &node) const { CheckVersion(); - ComputeSolutionData(); - return map_beliefs[node]; + EnsureBeliefs(); + return m_cache.m_beliefs[node]; } template Vector MixedBehaviorProfile::GetPayoff(const GameNode &node) const { CheckVersion(); - ComputeSolutionData(); + EnsureNodeValues(); Vector ret(node->GetGame()->NumPlayers()); auto players = node->GetGame()->GetPlayers(); std::transform(players.begin(), players.end(), ret.begin(), - [this, node](GamePlayer player) { return map_nodeValues[node][player]; }); + [this, node](GamePlayer player) { return m_cache.m_nodeValues[node][player]; }); return ret; } @@ -317,15 +299,15 @@ const T &MixedBehaviorProfile::GetPayoff(const GamePlayer &p_player, const GameNode &p_node) const { CheckVersion(); - ComputeSolutionData(); - return map_nodeValues[p_node][p_player]; + EnsureNodeValues(); + return m_cache.m_nodeValues[p_node][p_player]; } -template const T &MixedBehaviorProfile::GetPayoff(const GameInfoset &iset) const +template const T &MixedBehaviorProfile::GetPayoff(const GameInfoset &p_infoset) const { CheckVersion(); - ComputeSolutionData(); - return map_infosetValues[iset]; + EnsureRegrets(); + return m_cache.m_infosetValues[p_infoset]; } template T MixedBehaviorProfile::GetActionProb(const GameAction &action) const @@ -343,25 +325,25 @@ template T MixedBehaviorProfile::GetActionProb(const GameAction &ac template const T &MixedBehaviorProfile::GetPayoff(const GameAction &act) const { CheckVersion(); - ComputeSolutionData(); - return map_actionValues[act]; + EnsureActionValues(); + return m_cache.m_actionValues[act]; } template const T &MixedBehaviorProfile::GetRegret(const GameAction &act) const { CheckVersion(); - ComputeSolutionData(); - return map_regret.at(act); + EnsureRegrets(); + return m_cache.m_regret.at(act); } template T MixedBehaviorProfile::GetRegret(const GameInfoset &p_infoset) const { CheckVersion(); - ComputeSolutionData(); + EnsureRegrets(); T br_payoff = maximize_function(p_infoset->GetActions(), [this](const auto &action) -> T { - return map_actionValues.at(action); + return m_cache.m_actionValues.at(action); }); - return br_payoff - map_infosetValues[p_infoset]; + return br_payoff - m_cache.m_infosetValues[p_infoset]; } template T MixedBehaviorProfile::GetMaxRegret() const @@ -370,40 +352,6 @@ template T MixedBehaviorProfile::GetMaxRegret() const [this](const auto &infoset) -> T { return this->GetRegret(infoset); }); } -template -void MixedBehaviorProfile::GetPayoff(const GameNode &node, const T &prob, - const GamePlayer &player, T &value) const -{ - if (node->GetOutcome()) { - value += prob * node->GetOutcome()->GetPayoff(player); - } - - if (!node->IsTerminal()) { - if (node->GetPlayer()->IsChance()) { - // chance player - for (auto child : node->GetChildren()) { - GetPayoff(child, prob * static_cast(GetActionProb(child->GetPriorAction())), player, - value); - } - } - else { - for (auto child : node->GetChildren()) { - GetPayoff(child, prob * GetActionProb(child->GetPriorAction()), player, value); - } - } - } -} - -template T MixedBehaviorProfile::GetPayoff(int pl) const -{ - CheckVersion(); - T value = T(0); - auto rootNode = m_support.GetGame()->GetRoot(); - auto player = m_support.GetGame()->GetPlayer(pl); - GetPayoff(rootNode, T(1), player, value); - return value; -} - // // The following routines compute the derivatives of quantities as // the probability of the action 'p_oppAction' is changed. @@ -418,8 +366,8 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); - T deriv = T(0); + EnsureActionValues(); + T deriv = static_cast(0); const GameInfoset infoset = p_action->GetInfoset(); const GamePlayer player = p_action->GetInfoset()->GetPlayer(); @@ -427,9 +375,9 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, const GameNode child = member->GetChild(p_action); deriv += DiffRealizProb(member, p_oppAction) * - (map_nodeValues[child][player] - map_actionValues[p_action]); - deriv += - map_realizProbs[member] * DiffNodeValue(member->GetChild(p_action), player, p_oppAction); + (m_cache.m_nodeValues[child][player] - m_cache.m_actionValues[p_action]); + deriv += m_cache.m_realizProbs[member] * + DiffNodeValue(member->GetChild(p_action), player, p_oppAction); } return deriv / GetInfosetProb(p_action->GetInfoset()); @@ -440,13 +388,12 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); - T deriv = T(1); + EnsureActionValues(); + T deriv = static_cast(1); bool isPrec = false; GameNode node = p_node; while (node->GetParent()) { - const GameAction prevAction = node->GetPriorAction(); - if (prevAction != p_oppAction) { + if (const GameAction prevAction = node->GetPriorAction(); prevAction != p_oppAction) { deriv *= GetActionProb(prevAction); } else { @@ -455,7 +402,7 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, node = node->GetParent(); } - return (isPrec) ? deriv : T(0); + return (isPrec) ? deriv : static_cast(0); } template @@ -463,126 +410,120 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); + EnsureNodeValues(); if (p_node->IsTerminal()) { // If we reach a terminal node and haven't encountered p_oppAction, // derivative wrt this path is zero. - return T(0); + return static_cast(0); } if (p_node->GetInfoset() == p_oppAction->GetInfoset()) { // We've encountered the action; since we assume perfect recall, // we won't encounter it again, and the downtree value must // be the same. - return map_nodeValues[p_node->GetChild(p_oppAction)][p_player]; - } - else { - T deriv = T(0); - for (auto action : p_node->GetInfoset()->GetActions()) { - deriv += - (DiffNodeValue(p_node->GetChild(action), p_player, p_oppAction) * GetActionProb(action)); - } - return deriv; + return m_cache.m_nodeValues[p_node->GetChild(p_oppAction)][p_player]; } + return sum_function(p_node->GetActions(), [&](auto action_child) -> T { + return DiffNodeValue(action_child.second, p_player, p_oppAction) * + GetActionProb(action_child.first); + }); } //======================================================================== // MixedBehaviorProfile: Cached profile information //======================================================================== -// compute realization probabilities for nodes and isets. -template -void MixedBehaviorProfile::ComputePass1_realizProbs(const GameNode &node) const +template void MixedBehaviorProfile::ComputeRealizationProbs() const { - map_realizProbs[node] = (node->GetParent()) ? map_realizProbs[node->GetParent()] * - GetActionProb(node->GetPriorAction()) - : T(1); + m_cache.m_realizProbs.clear(); - for (auto childNode : node->GetChildren()) { - ComputePass1_realizProbs(childNode); + const auto &game = m_support.GetGame(); + m_cache.m_realizProbs[game->GetRoot()] = static_cast(1); + for (const auto &node : game->GetNodes()) { + const T incomingProb = m_cache.m_realizProbs[node]; + for (auto [action, child] : node->GetActions()) { + m_cache.m_realizProbs[child] = incomingProb * GetActionProb(action); + } } } -template -void MixedBehaviorProfile::ComputePass2_beliefs_nodeValues_actionValues( - const GameNode &node) const +template void MixedBehaviorProfile::ComputeBeliefs() const { - if (node->GetOutcome()) { - const GameOutcome outcome = node->GetOutcome(); - for (auto player : m_support.GetGame()->GetPlayers()) { - map_nodeValues[node][player] += outcome->GetPayoff(player); - } - } + m_cache.m_beliefs.clear(); - if (node->IsTerminal()) { - return; - } - - const GameInfoset iset = node->GetInfoset(); - auto nodes = iset->GetMembers(); - T infosetProb = - std::accumulate(nodes.begin(), nodes.end(), T(0), - [this](T total, GameNode node) { return total + map_realizProbs[node]; }); - - if (infosetProb != T(0)) { - map_beliefs[node] = map_realizProbs[node] / infosetProb; - } - - // push down payoffs from outcomes attached to non-terminal nodes - for (auto child : node->GetChildren()) { - map_nodeValues[child] = map_nodeValues[node]; - } - - for (auto player : m_support.GetGame()->GetPlayers()) { - map_nodeValues[node][player] = T(0); + for (const auto &infoset : m_support.GetGame()->GetInfosets()) { + const T infosetProb = sum_function( + infoset->GetMembers(), [&](const auto &node) -> T { return m_cache.m_realizProbs[node]; }); + if (infosetProb == static_cast(0)) { + continue; + } + for (const auto &node : infoset->GetMembers()) { + m_cache.m_beliefs[node] = m_cache.m_realizProbs[node] / infosetProb; + } } +} - for (auto child : node->GetChildren()) { - ComputePass2_beliefs_nodeValues_actionValues(child); - - const GameAction act = child->GetPriorAction(); +template void MixedBehaviorProfile::ComputeNodeValues() const +{ + const auto &game = m_support.GetGame(); + m_cache.m_nodeValues.clear(); - for (auto player : m_support.GetGame()->GetPlayers()) { - map_nodeValues[node][player] += GetActionProb(act) * map_nodeValues[child][player]; + for (const auto &node : game->GetNodes(TraversalOrder::Postorder)) { + auto &vals = m_cache.m_nodeValues[node]; + for (const auto &player : game->GetPlayers()) { + vals[player] = static_cast(0); } - - if (!iset->IsChanceInfoset()) { - map_actionValues[act] += (infosetProb != T(0)) - ? map_beliefs[node] * map_nodeValues[child][iset->GetPlayer()] - : T(0); + if (node->GetOutcome()) { + const GameOutcome &outcome = node->GetOutcome(); + for (const auto &player : game->GetPlayers()) { + vals[player] += outcome->GetPayoff(player); + } + } + for (auto [action, child] : node->GetActions()) { + const T p = GetActionProb(action); + for (const auto &player : game->GetPlayers()) { + vals[player] += p * m_cache.m_nodeValues[child][player]; + } } } } -template void MixedBehaviorProfile::ComputePass3_infosetValues_regret() const +template void MixedBehaviorProfile::ComputeActionValues() const { - // Populate - for (auto infoset : m_support.GetGame()->GetInfosets()) { - map_infosetValues[infoset] = T(0); - for (auto action : infoset->GetActions()) { - map_infosetValues[infoset] += GetActionProb(action) * map_actionValues[action]; - } - auto actions = infoset->GetActions(); - T brpayoff = map_actionValues[actions.front()]; - for (auto action : infoset->GetActions()) { - brpayoff = std::max(brpayoff, map_actionValues[action]); - } - for (auto action : infoset->GetActions()) { - map_regret[action] = brpayoff - map_actionValues[action]; + const auto &game = m_support.GetGame(); + m_cache.m_actionValues.clear(); + + for (const auto &infoset : game->GetInfosets()) { + const auto &player = infoset->GetPlayer(); + for (const auto &node : infoset->GetMembers()) { + T belief = m_cache.m_beliefs[node]; + if (belief == static_cast(0)) { + continue; + } + for (auto [action, child] : node->GetActions()) { + m_cache.m_actionValues[action] += belief * m_cache.m_nodeValues[child][player]; + } } } } -template void MixedBehaviorProfile::ComputeSolutionData() const +template void MixedBehaviorProfile::ComputeActionRegrets() const { - auto rootNode = m_support.GetGame()->GetRoot(); - if (contains(map_realizProbs, rootNode)) { - // cache is valid, don't compute anything, simply return - return; + for (const auto &infoset : m_support.GetGame()->GetInfosets()) { + m_cache.m_infosetValues[infoset] = + sum_function(infoset->GetActions(), [&](const auto &action) -> T { + return GetActionProb(action) * m_cache.m_actionValues[action]; + }); + + auto actions = infoset->GetActions(); + const T brpayoff = maximize_function(infoset->GetActions(), [&](const auto &action) -> T { + return m_cache.m_actionValues[action]; + }); + for (const auto &action : infoset->GetActions()) { + m_cache.m_regret[action] = + std::max(brpayoff - m_cache.m_actionValues[action], static_cast(0)); + } } - ComputePass1_realizProbs(rootNode); - ComputePass2_beliefs_nodeValues_actionValues(rootNode); - ComputePass3_infosetValues_regret(); } template bool MixedBehaviorProfile::IsDefinedAt(GameInfoset p_infoset) const diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index 239d237db..6cf265455 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -41,30 +41,102 @@ template class MixedBehaviorProfile { std::map m_profileIndex; unsigned int m_gameversion; - // structures for storing cached data: nodes - mutable std::map map_realizProbs, map_beliefs; - mutable std::map> map_nodeValues; - - // structures for storing cached data: information sets - mutable std::map map_infosetValues; + struct Cache { + enum class Level { None, Realizations, Beliefs, NodeValues, ActionValues, Regrets }; + + Level m_level{Level::None}; + std::map m_realizProbs, m_beliefs; + std::map> m_nodeValues; + std::map m_infosetValues; + std::map m_actionValues; + std::map m_regret; + + Cache() = default; + Cache(const Cache &) = default; + Cache &operator=(const Cache &) = default; + ~Cache() = default; + + void Clear() + { + m_level = Level::None; + m_realizProbs.clear(); + m_beliefs.clear(); + m_nodeValues.clear(); + m_infosetValues.clear(); + m_actionValues.clear(); + m_regret.clear(); + } + }; - // structures for storing cached data: actions - mutable std::map map_actionValues; // aka conditional payoffs - mutable std::map map_regret; + mutable Cache m_cache; - /// @name Auxiliary functions for computation of interesting values + /// @name Auxiliary functions for cached computation of interesting values //@{ - void GetPayoff(const GameNode &, const T &, const GamePlayer &, T &) const; - void ComputePass1_realizProbs(const GameNode &node) const; - void ComputePass2_beliefs_nodeValues_actionValues(const GameNode &node) const; - void ComputePass3_infosetValues_regret() const; - void ComputeSolutionData() const; + /// Compute the realisation probabilities of all nodes + void ComputeRealizationProbs() const; + /// Compute the realisation probabilities of information sets, and beliefs at + /// information sets reached with positive probability + void ComputeBeliefs() const; + /// Compute the expected payoffs conditional on reaching each node + void ComputeNodeValues() const; + /// Compute the expected (conditional) payoffs to each action + void ComputeActionValues() const; + /// Compute the conditional value of being at an information set, and corresponding + /// (agent) action regrets + void ComputeActionRegrets() const; + + void EnsureRealizations() const + { + if (m_cache.m_level >= Cache::Level::Realizations) { + return; + } + ComputeRealizationProbs(); + m_cache.m_level = Cache::Level::Realizations; + } + void EnsureBeliefs() const + { + EnsureRealizations(); + if (m_cache.m_level >= Cache::Level::Beliefs) { + return; + } + ComputeBeliefs(); + m_cache.m_level = Cache::Level::Beliefs; + } + void EnsureNodeValues() const + { + EnsureBeliefs(); + if (m_cache.m_level >= Cache::Level::NodeValues) { + return; + } + ComputeNodeValues(); + m_cache.m_level = Cache::Level::NodeValues; + } + void EnsureActionValues() const + { + EnsureNodeValues(); + if (m_cache.m_level >= Cache::Level::ActionValues) { + return; + } + ComputeActionValues(); + m_cache.m_level = Cache::Level::ActionValues; + } + void EnsureRegrets() const + { + EnsureActionValues(); + if (m_cache.m_level >= Cache::Level::Regrets) { + return; + } + ComputeActionRegrets(); + m_cache.m_level = Cache::Level::Regrets; + } + //@} /// @name Converting mixed strategies to behavior //@{ - void BehaviorStrat(GamePlayer &, GameNode &, std::map &, std::map &); - void RealizationProbs(const MixedStrategyProfile &, GamePlayer &, + void BehaviorStrat(const GamePlayer &, const GameNode &, std::map &, + std::map &); + void RealizationProbs(const MixedStrategyProfile &, const GamePlayer &, const std::map &, GameNodeRep *, std::map &, std::map &); //@} @@ -89,13 +161,13 @@ template class MixedBehaviorProfile { MixedBehaviorProfile &operator=(const MixedBehaviorProfile &); MixedBehaviorProfile &operator=(const Vector &p) { - InvalidateCache(); + m_cache.Clear(); m_probs = p; return *this; } MixedBehaviorProfile &operator=(const T &x) { - InvalidateCache(); + m_cache.Clear(); m_probs = x; return *this; } @@ -119,14 +191,14 @@ template class MixedBehaviorProfile { } T &operator[](const GameAction &p_action) { - InvalidateCache(); + m_cache.Clear(); return m_probs[m_profileIndex.at(p_action)]; } const T &operator[](int a) const { return m_probs[a]; } T &operator[](int a) { - InvalidateCache(); + m_cache.Clear(); return m_probs[a]; } @@ -135,20 +207,6 @@ template class MixedBehaviorProfile { /// @name Initialization, validation //@{ - /// Force recomputation of stored quantities - /// The validity of all caches is determined by the existence of the root node in the - /// primary cache (first to be computed) map_realizProbs - /// We also clear - /// map_nodeValues, map_actionValues - /// as otherwise we would need to reset them to 0 while populating them - void InvalidateCache() const - { - map_realizProbs.clear(); - map_nodeValues.clear(); - map_actionValues.clear(); - } - /// Reset certain cached values - /// Set the profile to the centroid void SetCentroid(); /// Set the behavior at any undefined information set to the centroid @@ -172,16 +230,20 @@ template class MixedBehaviorProfile { /// @name Computation of interesting quantities //@{ - T GetPayoff(int p_player) const; - T GetPayoff(const GamePlayer &p_player) const { return GetPayoff(p_player->GetNumber()); } + T GetPayoff(const GamePlayer &p_player) const + { + CheckVersion(); + EnsureNodeValues(); + return m_cache.m_nodeValues[m_support.GetGame()->GetRoot()][p_player]; + } T GetLiapValue() const; const T &GetRealizProb(const GameNode &node) const; - T GetInfosetProb(const GameInfoset &iset) const; + T GetInfosetProb(const GameInfoset &p_infoset) const; const T &GetBeliefProb(const GameNode &node) const; Vector GetPayoff(const GameNode &node) const; const T &GetPayoff(const GamePlayer &player, const GameNode &node) const; - const T &GetPayoff(const GameInfoset &iset) const; + const T &GetPayoff(const GameInfoset &p_infoset) const; const T &GetPayoff(const GameAction &act) const; T GetActionProb(const GameAction &act) const; diff --git a/src/games/gametree.cc b/src/games/gametree.cc index ba0b51a17..c630c50a2 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -71,7 +71,7 @@ template void TreeMixedStrategyProfileRep::InvalidateCache() const template T TreeMixedStrategyProfileRep::GetPayoff(int pl) const { MakeBehavior(); - return mixed_behav_profile_sptr->GetPayoff(pl); + return mixed_behav_profile_sptr->GetPayoff(mixed_behav_profile_sptr->GetGame()->GetPlayer(pl)); } template diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index ad5412ba7..0baf680ac 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -231,7 +231,8 @@ template class MixedStrategyProfile { T GetPayoff(int pl) const { CheckVersion(); - return m_rep->GetPayoff(pl); + return m_rep->GetPayoff(GetGame()->GetPlayer(pl)); + ; } /// Computes the payoff of the profile to the player diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index d03d6a84a..536dd6406 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -203,11 +203,13 @@ template std::string AnalysisProfileList::GetPayoff(int pl, int p_i try { if (m_doc->IsTree()) { - return lexical_cast(m_behavProfiles[index]->GetPayoff(pl), - m_doc->GetStyle().NumDecimals()); + return lexical_cast( + m_behavProfiles[index]->GetPayoff(m_doc->GetGame()->GetPlayer(pl)), + m_doc->GetStyle().NumDecimals()); } - return lexical_cast(m_mixedProfiles[index]->GetPayoff(pl), - m_doc->GetStyle().NumDecimals()); + return lexical_cast( + m_mixedProfiles[index]->GetPayoff(m_doc->GetGame()->GetPlayer(pl)), + m_doc->GetStyle().NumDecimals()); } catch (std::out_of_range &) { return ""; diff --git a/src/pygambit/behavmixed.pxi b/src/pygambit/behavmixed.pxi index 7a34bdd4d..8d7d0699b 100644 --- a/src/pygambit/behavmixed.pxi +++ b/src/pygambit/behavmixed.pxi @@ -895,7 +895,7 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): setitem_mbpd_action(deref(self.profile), index.action, value) def _payoff(self, player: Player) -> float: - return deref(self.profile).GetPayoff(player.player.deref().GetNumber()) + return deref(self.profile).GetPayoff(player.player) def _belief(self, node: Node) -> float: return deref(self.profile).GetBeliefProb(node.node) @@ -991,7 +991,7 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): to_rational(str(value).encode("ascii"))) def _payoff(self, player: Player) -> Rational: - return rat_to_py(deref(self.profile).GetPayoff(player.player.deref().GetNumber())) + return rat_to_py(deref(self.profile).GetPayoff(player.player)) def _belief(self, node: Node) -> Rational: return rat_to_py(deref(self.profile).GetBeliefProb(node.node)) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 335705471..0234f7920 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -368,7 +368,7 @@ cdef extern from "games/behavmixed.h" namespace "Gambit": c_MixedBehaviorProfile[T] Normalize() # except + doesn't compile T getitem "operator[]"(int) except +IndexError T getaction "operator[]"(c_GameAction) except +IndexError - T GetPayoff(int) except + + T GetPayoff(c_GamePlayer) except + T GetBeliefProb(c_GameNode) except + T GetRealizProb(c_GameNode) except + T GetInfosetProb(c_GameInfoset) except +