Skip to content

Commit ae0cd3f

Browse files
committed
Return null/none for action value at unreached information set
1 parent adbe5c6 commit ae0cd3f

7 files changed

Lines changed: 34 additions & 14 deletions

File tree

src/games/behavmixed.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,13 @@ template <class T> T MixedBehaviorProfile<T>::GetActionProb(const GameAction &ac
331331
return m_probs[m_profileIndex.at(action)];
332332
}
333333

334-
template <class T> const T &MixedBehaviorProfile<T>::GetPayoff(const GameAction &act) const
334+
template <class T> std::optional<T> MixedBehaviorProfile<T>::GetPayoff(const GameAction &act) const
335335
{
336336
CheckVersion();
337337
EnsureActionValues();
338+
if (GetInfosetProb(act->GetInfoset()) == T{0}) {
339+
return std::nullopt;
340+
}
338341
return m_cache.m_actionValues[act];
339342
}
340343

src/games/behavmixed.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ template <class T> class MixedBehaviorProfile {
245245
Vector<T> GetPayoff(const GameNode &node) const;
246246
const T &GetPayoff(const GamePlayer &player, const GameNode &node) const;
247247
const T &GetPayoff(const GameInfoset &p_infoset) const;
248-
const T &GetPayoff(const GameAction &act) const;
248+
std::optional<T> GetPayoff(const GameAction &act) const;
249249
T GetActionProb(const GameAction &act) const;
250250

251251
/// @brief Computes the regret to playing \p p_action

src/gui/analysis.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,10 @@ std::string AnalysisProfileList<T>::GetActionValue(const GameNode &p_node, int p
367367
}
368368

369369
try {
370-
if (m_behavProfiles[index]->GetInfosetProb(p_node->GetInfoset()) > Rational(0)) {
371-
return lexical_cast<std::string>(
372-
m_behavProfiles[index]->GetPayoff(p_node->GetInfoset()->GetAction(p_act)),
373-
m_doc->GetStyle().NumDecimals());
370+
std::optional<T> actionValue =
371+
m_behavProfiles[index]->GetPayoff(p_node->GetInfoset()->GetAction(p_act));
372+
if (actionValue.has_value()) {
373+
return lexical_cast<std::string>(actionValue.value(), m_doc->GetStyle().NumDecimals());
374374
}
375375
// In the absence of beliefs, this is not well-defined
376376
return "*";

src/pygambit/behavmixed.pxi

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -686,10 +686,13 @@ class MixedBehaviorProfile:
686686
raise ValueError("infoset_value() is not defined for the chance player")
687687
return self._infoset_value(resolved_infoset)
688688

689-
def action_value(self, action: ActionReference) -> ProfileDType:
689+
def action_value(self, action: ActionReference) -> ProfileDType | None:
690690
"""Returns the expected payoff to the player of playing an action conditional on reaching
691691
its information set, if all players play according to the profile.
692692

693+
If the information set is not reachable, the expected payoff is not well-defined.
694+
In this case, the function returns `None`.
695+
693696
Parameters
694697
----------
695698
action : Action or str
@@ -704,6 +707,10 @@ class MixedBehaviorProfile:
704707
If `action` is a string and no action in the game has that label.
705708
ValueError
706709
If `action` resolves to an action that belongs to the chance player
710+
711+
See also
712+
--------
713+
MixedBehaviorProfile.infoset_prob
707714
"""
708715
self._check_validity()
709716
resolved_action = self.game._resolve_action(action, "action_value")
@@ -959,8 +966,11 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile):
959966
def _node_value(self, player: Player, node: Node) -> float:
960967
return deref(self.profile).GetPayoff(player.player, node.node)
961968

962-
def _action_value(self, action: Action) -> float:
963-
return deref(self.profile).GetPayoff(action.action)
969+
def _action_value(self, action: Action) -> float | None:
970+
cdef optional[float] value = deref(self.profile).GetPayoff(action.action)
971+
if value.has_value():
972+
return value.value()
973+
return None
964974

965975
def _action_regret(self, action: Action) -> float:
966976
return deref(self.profile).GetRegret(action.action)
@@ -1061,8 +1071,11 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile):
10611071
def _node_value(self, player: Player, node: Node) -> Rational:
10621072
return rat_to_py(deref(self.profile).GetPayoff(player.player, node.node))
10631073

1064-
def _action_value(self, action: Action) -> Rational:
1065-
return rat_to_py(deref(self.profile).GetPayoff(action.action))
1074+
def _action_value(self, action: Action) -> Rational | None:
1075+
cdef optional[c_Rational] value = deref(self.profile).GetPayoff(action.action)
1076+
if value.has_value():
1077+
return rat_to_py(value.value())
1078+
return None
10661079

10671080
def _action_regret(self, action: Action) -> Rational:
10681081
return rat_to_py(deref(self.profile).GetRegret(action.action))

src/pygambit/gambit.pxd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from libcpp.memory cimport shared_ptr, unique_ptr
44
from libcpp.list cimport list as stdlist
55
from libcpp.vector cimport vector as stdvector
66
from libcpp.set cimport set as stdset
7+
from libcpp.optional cimport optional
78

89

910
cdef extern from "gambit.h":
@@ -367,7 +368,7 @@ cdef extern from "games/behavmixed.h" namespace "Gambit":
367368
T GetInfosetProb(c_GameInfoset) except +
368369
T GetPayoff(c_GameInfoset) except +
369370
T GetPayoff(c_GamePlayer, c_GameNode) except +
370-
T GetPayoff(c_GameAction) except +
371+
optional[T] GetPayoff(c_GameAction) except +
371372
T GetRegret(c_GameAction) except +
372373
T GetRegret(c_GameInfoset) except +
373374
T GetAgentMaxRegret() except +

src/solvers/liap/efgliap.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ AgentLyapunovFunction::PenalizedLiapValue(const MixedBehaviorProfile<double> &p_
7676
for (const auto &infoset : p_profile.GetGame()->GetInfosets()) {
7777
double infosetValue = p_profile.GetPayoff(infoset);
7878
value += sum_function(infoset->GetActions(), [&](const auto &action) -> double {
79-
return sqr(std::max(m_scale * (p_profile.GetPayoff(action) - infosetValue), 0.0));
79+
return sqr(std::max(m_scale * (p_profile.GetPayoff(action).value() - infosetValue), 0.0));
8080
});
8181
}
8282
// Penalty function for non-negativity constraint for each action

src/tools/util.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@ void MixedBehaviorProfileDetailRenderer<T>::Render(const MixedBehaviorProfile<T>
229229
m_stream << lexical_cast<std::string>(p_profile[action], m_numDecimals);
230230
m_stream << " ";
231231
m_stream << std::setw(11);
232-
m_stream << lexical_cast<std::string>(p_profile.GetPayoff(action), m_numDecimals);
232+
std::optional<T> actionValue = p_profile.GetPayoff(action);
233+
if (actionValue.has_value()) {
234+
m_stream << lexical_cast<std::string>(actionValue.value(), m_numDecimals);
235+
}
233236
m_stream << std::endl;
234237
}
235238
}

0 commit comments

Comments
 (0)