From 1dec220a3e8b547cefd674ae0f8bfccd1b757037 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 10 Dec 2025 16:58:28 +0000 Subject: [PATCH 1/5] Refactor MixedBehaviorProfile caching This re-writes the cached quantity calculations for MixedBehaviorProfile: * Uses the preorder and postorder traversal of nodes provided by the game class; * Thereby avoids recursion to descend the tree * Cleanly separates the computation of each vector of quantities (which will be a help for further optimisation in future when we look at the data structures used to represent them) * Because there is a dependency order in the cached quantities, implements a slightly more sophisticated cache to compute only what is truly needed. Introduce cache object Remove separate cache invalidation Ensure only as much information as is required. --- src/games/behavmixed.cc | 217 +++++++++++++++++++--------------------- src/games/behavmixed.h | 124 +++++++++++++++++------ 2 files changed, 193 insertions(+), 148 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 993d7be4b..e115b10fc 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 { @@ -171,15 +170,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 +257,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 +271,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 +306,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 +332,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 @@ -418,7 +407,7 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); + EnsureActionValues(); T deriv = T(0); const GameInfoset infoset = p_action->GetInfoset(); const GamePlayer player = p_action->GetInfoset()->GetPlayer(); @@ -427,9 +416,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,7 +429,7 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); + EnsureActionValues(); T deriv = T(1); bool isPrec = false; GameNode node = p_node; @@ -463,7 +452,7 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye const GameAction &p_oppAction) const { CheckVersion(); - ComputeSolutionData(); + EnsureActionValues(); if (p_node->IsTerminal()) { // If we reach a terminal node and haven't encountered p_oppAction, @@ -474,7 +463,7 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye // 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]; + return m_cache.m_nodeValues[p_node->GetChild(p_oppAction)][p_player]; } else { T deriv = T(0); @@ -490,99 +479,97 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye // 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); - } - } - - if (node->IsTerminal()) { - return; - } + m_cache.m_beliefs.clear(); - 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..f0737327a 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -41,24 +41,96 @@ 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 //@{ 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 @@ -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 @@ -177,11 +235,11 @@ template class MixedBehaviorProfile { 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; From 92fac010426b15fd8695ebe5c31200c262278d83 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 15 Dec 2025 16:45:41 +0000 Subject: [PATCH 2/5] GetPayoff now uses the cache --- src/games/behavmixed.cc | 31 ++----------------------------- src/games/behavmixed.h | 3 +-- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index e115b10fc..ddd64822f 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -359,38 +359,11 @@ 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; + EnsureNodeValues(); + return m_cache.m_nodeValues[m_support.GetGame()->GetRoot()][m_support.GetGame()->GetPlayer(pl)]; } // diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index f0737327a..af9c76c5d 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -70,9 +70,8 @@ template class MixedBehaviorProfile { 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; /// Compute the realisation probabilities of all nodes void ComputeRealizationProbs() const; /// Compute the realisation probabilities of information sets, and beliefs at From f438aa9fbd2b8ef2c83a268916d48da75afc3c80 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 15 Dec 2025 16:57:12 +0000 Subject: [PATCH 3/5] Remove payoff to player index from mixed behavior --- src/games/behavmixed.cc | 7 ------- src/games/behavmixed.h | 8 ++++++-- src/games/gametree.cc | 2 +- src/games/stratmixed.h | 3 ++- src/gui/analysis.cc | 10 ++++++---- src/pygambit/behavmixed.pxi | 4 ++-- src/pygambit/gambit.pxd | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index ddd64822f..3f18a05f5 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -359,13 +359,6 @@ template T MixedBehaviorProfile::GetMaxRegret() const [this](const auto &infoset) -> T { return this->GetRegret(infoset); }); } -template T MixedBehaviorProfile::GetPayoff(int pl) const -{ - CheckVersion(); - EnsureNodeValues(); - return m_cache.m_nodeValues[m_support.GetGame()->GetRoot()][m_support.GetGame()->GetPlayer(pl)]; -} - // // The following routines compute the derivatives of quantities as // the probability of the action 'p_oppAction' is changed. diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index af9c76c5d..351fc367f 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -229,8 +229,12 @@ 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; 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 + From e607ca8d7770746764c9aef9fde9acd81974fcc9 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 15 Dec 2025 17:09:14 +0000 Subject: [PATCH 4/5] Tidy diffnodevalue --- src/games/behavmixed.cc | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 3f18a05f5..02b0391de 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -374,7 +374,7 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, { CheckVersion(); EnsureActionValues(); - T deriv = T(0); + T deriv = static_cast(0); const GameInfoset infoset = p_action->GetInfoset(); const GamePlayer player = p_action->GetInfoset()->GetPlayer(); @@ -396,12 +396,11 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, { CheckVersion(); EnsureActionValues(); - T deriv = T(1); + 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 { @@ -410,7 +409,7 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, node = node->GetParent(); } - return (isPrec) ? deriv : T(0); + return (isPrec) ? deriv : static_cast(0); } template @@ -418,12 +417,12 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye const GameAction &p_oppAction) const { CheckVersion(); - EnsureActionValues(); + 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, @@ -431,14 +430,10 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye // be the same. return m_cache.m_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 sum_function(p_node->GetActions(), [&](auto action_child) -> T { + return DiffNodeValue(action_child.second, p_player, p_oppAction) * + GetActionProb(action_child.first); + }); } //======================================================================== From 32f76f023617c3fe0072ba06de63758d9fa2e2f1 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 15 Dec 2025 17:19:18 +0000 Subject: [PATCH 5/5] Miscellaneous consistency tidying --- src/games/behavmixed.cc | 65 ++++++++++++++++++----------------------- src/games/behavmixed.h | 5 ++-- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 02b0391de..678194f6c 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -38,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(); @@ -54,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; } } } @@ -70,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]; } } @@ -86,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) @@ -98,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]; @@ -131,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); } } @@ -145,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); } } diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index 351fc367f..6cf265455 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -134,8 +134,9 @@ template class MixedBehaviorProfile { /// @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 &); //@}