Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/games/behavmixed.cc
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,7 @@ template <class T> T MixedBehaviorProfile<T>::GetInfosetProb(const GameInfoset &
{
CheckVersion();
EnsureRealizations();
return sum_function(p_infoset->GetMembers(),
[&](const auto &node) -> T { return m_cache.m_realizProbs[node]; });
return m_cache.m_infosetProbs[p_infoset];
}

template <class T>
Expand Down Expand Up @@ -471,6 +470,7 @@ T MixedBehaviorProfile<T>::DiffNodeValue(const GameNode &p_node, const GamePlaye
template <class T> void MixedBehaviorProfile<T>::ComputeRealizationProbs() const
{
m_cache.m_realizProbs.clear();
m_cache.m_infosetProbs.clear();

const auto &game = m_support.GetGame();
m_cache.m_realizProbs[game->GetRoot()] = static_cast<T>(1);
Expand All @@ -480,6 +480,21 @@ template <class T> void MixedBehaviorProfile<T>::ComputeRealizationProbs() const
m_cache.m_realizProbs[child] = incomingProb * GetActionProb(action);
}
}

for (const auto &infoset : game->GetInfosets()) {
if (game->IsAbsentMinded(infoset)) {
m_cache.m_infosetProbs[infoset] =
sum_function(infoset->GetMembers(), [&](const auto &node) -> T {
return game->IsAbsentMindedReentry(node) ? static_cast<T>(0)
: m_cache.m_realizProbs[node];
});
}
else {
m_cache.m_infosetProbs[infoset] =
sum_function(infoset->GetMembers(),
[&](const auto &node) -> T { return m_cache.m_realizProbs[node]; });
}
}
}

template <class T> void MixedBehaviorProfile<T>::ComputeBeliefs() const
Expand Down
2 changes: 2 additions & 0 deletions src/games/behavmixed.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ template <class T> class MixedBehaviorProfile {

Level m_level{Level::None};
std::map<GameNode, T> m_realizProbs, m_beliefs;
std::map<GameInfoset, T> m_infosetProbs;
std::map<GameNode, std::map<GamePlayer, T>> m_nodeValues;
std::map<GameInfoset, T> m_infosetValues;
std::map<GameAction, T> m_actionValues;
Expand All @@ -60,6 +61,7 @@ template <class T> class MixedBehaviorProfile {
{
m_level = Level::None;
m_realizProbs.clear();
m_infosetProbs.clear();
m_beliefs.clear();
m_nodeValues.clear();
m_infosetValues.clear();
Expand Down
8 changes: 8 additions & 0 deletions src/games/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,14 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
}
return false;
}
/// Returns whether the path from the root to p_node passes through its infoset more than once
virtual bool IsAbsentMindedReentry(const GameNode &p_node) const
{
if (p_node->GetGame().get() != this) {
throw MismatchException();
}
return false;
}
/// Returns a list of all subgame roots in the game
virtual std::vector<GameNode> GetSubgames() const { throw UndefinedException(); }

Expand Down
19 changes: 19 additions & 0 deletions src/games/gametree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,23 @@ bool GameTreeRep::IsAbsentMinded(const GameInfoset &p_infoset) const
return contains(m_absentMindedInfosets, p_infoset.get());
}

bool GameTreeRep::IsAbsentMindedReentry(const GameNode &p_node) const
{
if (p_node->GetGame().get() != this) {
throw MismatchException();
}

if (!m_unreachableNodes && !m_root->IsTerminal()) {
BuildUnreachableNodes();
}

auto it = m_absentMindedReentries.find(p_node->m_infoset);
if (it == m_absentMindedReentries.end()) {
return false;
}
return contains(it->second, p_node.get());
}

//------------------------------------------------------------------------
// GameTreeRep: Managing the representation
//------------------------------------------------------------------------
Expand Down Expand Up @@ -924,6 +941,7 @@ void GameTreeRep::ClearComputedValues() const
m_ownPriorActionInfo = nullptr;
const_cast<GameTreeRep *>(this)->m_unreachableNodes = nullptr;
m_absentMindedInfosets.clear();
m_absentMindedReentries.clear();
m_subgames.clear();
m_computedValues = false;
}
Expand Down Expand Up @@ -1100,6 +1118,7 @@ void GameTreeRep::BuildUnreachableNodes() const
// Check for Absent-Minded Re-entry of the infoset
if (path_choices.find(child->m_infoset->shared_from_this()) != path_choices.end()) {
m_absentMindedInfosets.insert(child->m_infoset);
m_absentMindedReentries[child->m_infoset].insert(child.get());
const GameAction replay_action = path_choices.at(child->m_infoset->shared_from_this());
position.emplace(AbsentMindedEdge{replay_action, child});

Expand Down
3 changes: 3 additions & 0 deletions src/games/gametree.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class GameTreeRep final : public GameExplicitRep {
mutable std::shared_ptr<OwnPriorActionInfo> m_ownPriorActionInfo;
mutable std::unique_ptr<std::set<GameNodeRep *>> m_unreachableNodes;
mutable std::set<GameInfosetRep *> m_absentMindedInfosets;
mutable std::map<GameInfosetRep *, std::set<GameNodeRep *>> m_absentMindedReentries;
mutable std::vector<GameNodeRep *> m_subgames;

/// @name Private auxiliary functions
Expand Down Expand Up @@ -99,6 +100,8 @@ class GameTreeRep final : public GameExplicitRep {
/// Returns the largest payoff to the player in any play of the game
Rational GetPlayerMaxPayoff(const GamePlayer &) const override;
bool IsAbsentMinded(const GameInfoset &p_infoset) const override;
/// Returns whether the path from the root to p_node passes through its infoset more than once
bool IsAbsentMindedReentry(const GameNode &p_node) const override;
std::vector<GameNode> GetSubgames() const override;
//@}

Expand Down
31 changes: 31 additions & 0 deletions tests/test_behav.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,37 @@ def test_infoset_prob_by_label_reference(
assert profile.infoset_prob(label) == (gbt.Rational(prob) if rational_flag else prob)


@pytest.mark.parametrize(
"game,player_idx,infoset_idx,prob,rational_flag",
[
# P1 infoset 1 is absent-minded (root + one reentry)
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 0, 1.0, False),
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 1, 0.5, False),
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 2, 0.125, False),
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 0, "1", True),
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 1, "1/2", True),
(games.read_from_file("noPR-AM-driver-one-player.efg"), 0, 2, "1/8", True),
# P1 infoset 1 has 3 members (root + both children are reentries)
(games.read_from_file("noPR-action-AM.efg"), 0, 0, 1.0, False),
(games.read_from_file("noPR-action-AM.efg"), 1, 0, 0.25, False),
(games.read_from_file("noPR-action-AM.efg"), 1, 1, 0.25, False),
(games.read_from_file("noPR-action-AM.efg"), 1, 2, 0.25, False),
(games.read_from_file("noPR-action-AM.efg"), 1, 3, 0.25, False),
(games.read_from_file("noPR-action-AM.efg"), 0, 0, "1", True),
(games.read_from_file("noPR-action-AM.efg"), 1, 0, "1/4", True),
(games.read_from_file("noPR-action-AM.efg"), 1, 1, "1/4", True),
(games.read_from_file("noPR-action-AM.efg"), 1, 2, "1/4", True),
(games.read_from_file("noPR-action-AM.efg"), 1, 3, "1/4", True),
],
)
def test_absent_minded_infoset_prob(
game: gbt.Game, player_idx: int, infoset_idx: int, prob: str | float, rational_flag: bool
):
profile = game.mixed_behavior_profile(rational=rational_flag)
ip = profile.infoset_prob(game.players[player_idx].infosets[infoset_idx])
assert ip == (gbt.Rational(prob) if rational_flag else prob)


@pytest.mark.parametrize(
"game,player_idx,infoset_idx,payoff,rational_flag",
[
Expand Down
Loading