diff --git a/src/games/behav.h b/src/games/behav.h index 00e4a0efd..9accfc58e 100644 --- a/src/games/behav.h +++ b/src/games/behav.h @@ -172,7 +172,7 @@ template class MixedBehaviorProfile : public DVector { T GetLiapValue(bool p_definedOnly = false) const; const T &GetRealizProb(const GameNode &node) const; - T GetRealizProb(const GameInfoset &iset) const; + T GetInfosetProb(const GameInfoset &iset) const; const T &GetBeliefProb(const GameNode &node) const; Vector GetPayoff(const GameNode &node) const; const T &GetPayoff(const GamePlayer &player, const GameNode &node) const; @@ -195,4 +195,4 @@ template class MixedBehaviorProfile : public DVector { } // end namespace Gambit -#endif // LIBGAMBIT_BEHAV_H +#endif // LIBGAMBIT_BEHAV_H \ No newline at end of file diff --git a/src/games/behav.imp b/src/games/behav.imp index 7ae58f9df..1e4f7a5ea 100644 --- a/src/games/behav.imp +++ b/src/games/behav.imp @@ -245,7 +245,7 @@ void MixedBehaviorProfile::UndefinedToCentroid() GamePlayer player = efg->GetPlayer(pl); for (int iset = 1; iset <= player->NumInfosets(); iset++) { GameInfoset infoset = player->GetInfoset(iset); - if (GetRealizProb(infoset) > (T) 0) { + if (GetInfosetProb(infoset) > (T) 0) { continue; } T total = (T) 0; @@ -270,7 +270,7 @@ MixedBehaviorProfile MixedBehaviorProfile::Normalize() const GamePlayer player = efg->GetPlayer(pl); for (int iset = 1; iset <= player->NumInfosets(); iset++) { GameInfoset infoset = player->GetInfoset(iset); - if (GetRealizProb(infoset) == (T) 0) { + if (GetInfosetProb(infoset) == (T) 0) { continue; } T total = (T) 0; @@ -416,7 +416,7 @@ const T &MixedBehaviorProfile::GetRealizProb(const GameNode &node) const } template -T MixedBehaviorProfile::GetRealizProb(const GameInfoset &iset) const +T MixedBehaviorProfile::GetInfosetProb(const GameInfoset &iset) const { ComputeSolutionData(); T prob = (T) 0; @@ -555,7 +555,7 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, DiffNodeValue(member->GetChild(p_action->GetNumber()), player, p_oppAction); } - return deriv / GetRealizProb(p_action->GetInfoset()); + return deriv / GetInfosetProb(p_action->GetInfoset()); } template diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index 09f2bad95..58422db77 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -260,7 +260,7 @@ gbtAnalysisProfileList::GetBeliefProb(const GameNode &p_node, if (!p_node->GetPlayer()) return ""; try { - if (m_behavProfiles[index].GetRealizProb(p_node->GetInfoset()) > Rational(0)) { + if (m_behavProfiles[index].GetInfosetProb(p_node->GetInfoset()) > Rational(0)) { return lexical_cast(m_behavProfiles[index].GetBeliefProb(p_node), m_doc->GetStyle().NumDecimals()); } @@ -298,7 +298,7 @@ gbtAnalysisProfileList::GetInfosetProb(const GameNode &p_node, if (!p_node->GetPlayer()) return ""; try { - return lexical_cast(m_behavProfiles[index].GetRealizProb(p_node->GetInfoset()), + return lexical_cast(m_behavProfiles[index].GetInfosetProb(p_node->GetInfoset()), m_doc->GetStyle().NumDecimals()); } catch (IndexException &) { @@ -315,7 +315,7 @@ gbtAnalysisProfileList::GetInfosetValue(const GameNode &p_node, if (!p_node->GetPlayer() || p_node->GetPlayer()->IsChance()) return ""; try { - if (m_behavProfiles[index].GetRealizProb(p_node->GetInfoset()) > Rational(0)) { + if (m_behavProfiles[index].GetInfosetProb(p_node->GetInfoset()) > Rational(0)) { return lexical_cast(m_behavProfiles[index].GetPayoff(p_node->GetInfoset()), m_doc->GetStyle().NumDecimals()); } @@ -385,7 +385,7 @@ gbtAnalysisProfileList::GetActionValue(const GameNode &p_node, int p_act, if (!p_node->GetPlayer() || p_node->GetPlayer()->IsChance()) return ""; try { - if (m_behavProfiles[index].GetRealizProb(p_node->GetInfoset()) > Rational(0)) { + if (m_behavProfiles[index].GetInfosetProb(p_node->GetInfoset()) > Rational(0)) { return lexical_cast(m_behavProfiles[index].GetPayoff(p_node->GetInfoset()->GetAction(p_act)), m_doc->GetStyle().NumDecimals()); } diff --git a/src/pygambit/behav.pxi b/src/pygambit/behav.pxi index e90e9b42e..4b63f9e01 100644 --- a/src/pygambit/behav.pxi +++ b/src/pygambit/behav.pxi @@ -376,6 +376,24 @@ class MixedBehaviorProfile: raise ValueError("action_value() is not defined for the chance player") return self._action_value(resolved_action) + def realiz_prob(self, node: typing.Union[Node, str]): + """Returns the probability with which an node is reached. + + Parameters + ---------- + node : Node or str + The node to get the payoff for. If a string is passed, the + node is determined by finding the node with that label, if any. + + Raises + ------ + MismatchError + If `node` is an `Node` from a different game. + KeyError + If `node` is a string and no node in the game has that label. + """ + return self._realiz_prob(self.game._resolve_node(node, 'realiz_prob')) + def infoset_prob(self, infoset: typing.Union[Infoset, str]): """Returns the probability with which an information set is reached. @@ -477,8 +495,11 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): def _belief(self, node: Node) -> float: return deref(self.profile).GetBeliefProb(node.node) + def _realiz_prob(self, node: Node) -> float: + return deref(self.profile).GetRealizProb(node.node) + def _infoset_prob(self, infoset: Infoset) -> float: - return deref(self.profile).GetRealizProb(infoset.infoset) + return deref(self.profile).GetInfosetProb(infoset.infoset) def _infoset_value(self, infoset: Infoset) -> float: return deref(self.profile).GetPayoff(infoset.infoset) @@ -555,8 +576,11 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): def _belief(self, node: Node) -> Rational: return rat_to_py(deref(self.profile).GetBeliefProb(node.node)) + def _realiz_prob(self, node: Node) -> Rational: + return rat_to_py(deref(self.profile).GetRealizProb(node.node)) + def _infoset_prob(self, infoset: Infoset) -> Rational: - return rat_to_py(deref(self.profile).GetRealizProb(infoset.infoset)) + return rat_to_py(deref(self.profile).GetInfosetProb(infoset.infoset)) def _infoset_value(self, infoset: Infoset) -> Rational: return rat_to_py(deref(self.profile).GetPayoff(infoset.infoset)) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 26a474e30..dd266977f 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -284,7 +284,8 @@ cdef extern from "games/behav.h": double getaction "operator()"(c_GameAction) except +IndexError double GetPayoff(int) double GetBeliefProb(c_GameNode) - double GetRealizProb(c_GameInfoset) + double GetRealizProb(c_GameNode) + double GetInfosetProb(c_GameInfoset) double GetPayoff(c_GameInfoset) double GetPayoff(c_GamePlayer, c_GameNode) double GetPayoff(c_GameAction) @@ -308,7 +309,8 @@ cdef extern from "games/behav.h": c_Rational getaction "operator()"(c_GameAction) except +IndexError c_Rational GetPayoff(int) c_Rational GetBeliefProb(c_GameNode) - c_Rational GetRealizProb(c_GameInfoset) + c_Rational GetRealizProb(c_GameNode) + c_Rational GetInfosetProb(c_GameInfoset) c_Rational GetPayoff(c_GameInfoset) c_Rational GetPayoff(c_GamePlayer, c_GameNode) c_Rational GetPayoff(c_GameAction) diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 7c318c52c..58f667c1e 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -672,16 +672,15 @@ class Game: mspr[s] = Rational(v) return mspr - def mixed_behavior_profile(self, rational=False) -> MixedBehaviorProfile: - """Create a behavior strategy profile over the game. - - The profile is initialized to uniform randomization for each player - over their actions at each information set. + def mixed_behavior_profile(self, data=None, rational=False) -> MixedBehaviorProfile: + """Returns a behavior strategy profile `MixedBehaviorProfile` over the game, + initialized to uniform randomization for each player over his actions at each + specifying the probabilities of the strategies. Parameters ---------- rational - If True, probabilities are represented using rational numbers; otherwise + If 'True', probabilities are represented using rational numbers; otherwise double-precision floating point numbers are used. Raises @@ -693,10 +692,50 @@ class Game: if not rational: mbpd = MixedBehaviorProfileDouble() mbpd.profile = make_shared[c_MixedBehaviorProfileDouble](self.game) + if data is None: + return mbpd + if len(data) != len(self.players): + raise ValueError( + "Number of elements does not match number of players" + ) + for (p, d) in zip(self.players, data): + if len(p.infosets) != len(d): + raise ValueError( + f"Number of elements does not match number of " + f"infosets for {p}" + ) + for (i, v) in zip(p.infosets, d): + if len(i.actions) != len(v): + raise ValueError( + f"Number of elements does not match number of " + f"actions for the infoset {i} for {p}" + ) + for (a, u) in zip(i.actions, v): + mbpd[a] = float(u) return mbpd else: mbpr = MixedBehaviorProfileRational() mbpr.profile = make_shared[c_MixedBehaviorProfileRational](self.game) + if data is None: + return mbpr + if len(data) != len(self.players): + raise ValueError( + "Number of elements does not match number of players" + ) + for (p, d) in zip(self.players, data): + if len(p.infosets) != len(d): + raise ValueError( + f"Number of elements does not match number of " + f"infosets for {p}" + ) + for (i, v) in zip(p.infosets, d): + if len(i.actions) != len(v): + raise ValueError( + f"Number of elements does not match number of " + f"actions for the infoset {i} for {p}" + ) + for (a, u) in zip(i.actions, v): + mbpr[a] = float(u) return mbpr else: raise UndefinedOperationError( @@ -1447,3 +1486,4 @@ class Game: if len(resolved_strategy.player.strategies) == 1: raise UndefinedOperationError("Cannot delete the only strategy for a player") resolved_strategy.strategy.deref().DeleteStrategy() + diff --git a/src/pygambit/tests/test_behav.py b/src/pygambit/tests/test_behav.py index 03ca179ea..94d669267 100644 --- a/src/pygambit/tests/test_behav.py +++ b/src/pygambit/tests/test_behav.py @@ -8,14 +8,14 @@ def setUp(self): self.game = gbt.Game.read_game( "test_games/mixed_behavior_game.efg" ) - self.profile_double = self.game.mixed_behavior_profile() - self.profile_rational = self.game.mixed_behavior_profile(True) + self.profile_double = self.game.mixed_behavior_profile(None, False) + self.profile_rational = self.game.mixed_behavior_profile(None, True) self.game_with_chance = gbt.Game.read_game( "test_games/complicated_extensive_game.efg" ) - self.profile_double_with_chance = self.game_with_chance.mixed_behavior_profile() - self.profile_rational_with_chance = self.game_with_chance.mixed_behavior_profile(True) + self.profile_double_w_chance = self.game_with_chance.mixed_behavior_profile(None, False) + self.profile_rational_w_chance = self.game_with_chance.mixed_behavior_profile(None, True) def tearDown(self): del self.game @@ -430,6 +430,11 @@ def test_set_probabilities_player_by_string(self): [[gbt.Rational("1/98"), gbt.Rational("97/98")]] ) + def test_realiz_prob(self): + """Test realization probability on node.""" + assert self.profile_double.realiz_prob(self.game.root) == 1 + assert self.profile_rational.realiz_prob(self.game.root) == 1 + def test_infoset_prob(self): """Test to retrieve the probability an information set is reached.""" assert (self.profile_double.infoset_prob(self.game.players[0].infosets[0]) == 1.0) @@ -670,29 +675,3 @@ def test_infoset_belief(self): self.profile_rational.belief(self.game.infosets[0].members[0]) == gbt.Rational(1, 1) ) - - def test_payoff_with_chance_player(self): - """Test to ensure that payoff called with the chance player raises a ValueError""" - chance_player = self.game_with_chance.players.chance - self.assertRaises(ValueError, self.profile_double_with_chance.payoff, chance_player) - self.assertRaises(ValueError, self.profile_rational_with_chance.payoff, chance_player) - - def test_infoset_value_with_chance_player_infoset(self): - """Test to ensure that infoset_value called with an infoset of the chance player - raises a ValueError - """ - chance_infoset = self.game_with_chance.players.chance.infosets[0] - self.assertRaises(ValueError, self.profile_double_with_chance.infoset_value, - chance_infoset) - self.assertRaises(ValueError, self.profile_rational_with_chance.infoset_value, - chance_infoset) - - def test_action_value_with_chance_player_action(self): - """Test to ensure that action_value called with an action of the chance player - raises a ValueError - """ - chance_action = self.game_with_chance.players.chance.infosets[0].actions[0] - self.assertRaises(ValueError, self.profile_double_with_chance.action_value, - chance_action) - self.assertRaises(ValueError, self.profile_rational_with_chance.action_value, - chance_action)