Skip to content

Commit 12f563d

Browse files
tturocyrahulsavani
andauthored
New behaviour of GetMinPayoff and GetMaxPayoff to refer to plays of games (#655)
Previously, `GetMinPayoff` and `GetMaxPayoff` referred to the set of (non-null) outcomes in the game. This is often not what you want when analysing a game: * A terminal node with a null outcome is treated as a payoff of 0 and so should be taken into account; * A contingency in the strategic game with a null outcome is treated as a payoff of 0 and so likewise; * Outcomes at non-terminal nodes are supported; the "minimum" or "maximum" payoff in a game would be expected to be in reference to full plays of the game (cumulating payoffs). This changes the behaviour of these functions to refer always to plays of the game, and adds new tests for {min,max}_payoff, {lp,lcp}_solve with EFGs w/ non-terminal/missing terminal outcomes. --------- Co-authored-by: Rahul Savani <rahul.savani@gmail.com>
1 parent 3489b98 commit 12f563d

File tree

19 files changed

+610
-104
lines changed

19 files changed

+610
-104
lines changed

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
are always drawn and indicators are always drawn if an information set spans multiple levels.
88
- In `pygambit`, indexing the children of a node by a string inteprets the string as an action label,
99
not a label of a child node. In addition, indexing by an action object is now supported. (#587)
10+
- In `pygambit`, `min_payoff` and `max_payoff` (for both games and players) now refers to payoffs in
11+
any play of the game; previously this referred only to the set of outcomes. (#498)
1012

1113
### Added
1214
- Tests for EFG Nash solvers -- `enumpoly_solve`, `lp_solve`, `lcp_solve` -- in behavior stratgegies

src/games/game.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -738,14 +738,14 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
738738

739739
/// Returns true if the game is constant-sum
740740
virtual bool IsConstSum() const = 0;
741-
/// Returns the smallest payoff to any player in any outcome of the game
741+
/// Returns the smallest payoff to any player in any play of the game
742742
virtual Rational GetMinPayoff() const = 0;
743-
/// Returns the smallest payoff to the player in any outcome of the game
744-
virtual Rational GetMinPayoff(const GamePlayer &p_player) const = 0;
745-
/// Returns the largest payoff to any player in any outcome of the game
743+
/// Returns the smallest payoff to the player in any play of the game
744+
virtual Rational GetPlayerMinPayoff(const GamePlayer &p_player) const = 0;
745+
/// Returns the largest payoff to any player in any play of the game
746746
virtual Rational GetMaxPayoff() const = 0;
747-
/// Returns the largest payoff to the player in any outcome of the game
748-
virtual Rational GetMaxPayoff(const GamePlayer &p_player) const = 0;
747+
/// Returns the largest payoff to the player in any play of the game
748+
virtual Rational GetPlayerMaxPayoff(const GamePlayer &p_player) const = 0;
749749

750750
/// Returns the set of terminal nodes which are descendants of node
751751
virtual std::vector<GameNode> GetPlays(GameNode node) const { throw UndefinedException(); }

src/games/gameagg.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ class GameAGGRep : public GameRep {
8989
/// Returns the smallest payoff to any player in any outcome of the game
9090
Rational GetMinPayoff() const override { return Rational(aggPtr->getMinPayoff()); }
9191
/// Returns the smallest payoff to the player in any outcome of the game
92-
Rational GetMinPayoff(const GamePlayer &) const override { throw UndefinedException(); }
92+
Rational GetPlayerMinPayoff(const GamePlayer &) const override { throw UndefinedException(); }
9393
/// Returns the largest payoff to any player in any outcome of the game
9494
Rational GetMaxPayoff() const override { return Rational(aggPtr->getMaxPayoff()); }
9595
/// Returns the largest payoff to the player in any outcome of the game
96-
Rational GetMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); }
96+
Rational GetPlayerMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); }
9797
//@}
9898

9999
/// @name Modification

src/games/gamebagg.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ class GameBAGGRep : public GameRep {
9696
/// Returns the smallest payoff to any player in any outcome of the game
9797
Rational GetMinPayoff() const override { return Rational(baggPtr->getMinPayoff()); }
9898
/// Returns the smallest payoff to the player in any outcome of the game
99-
Rational GetMinPayoff(const GamePlayer &) const override { throw UndefinedException(); }
99+
Rational GetPlayerMinPayoff(const GamePlayer &) const override { throw UndefinedException(); }
100100
/// Returns the largest payoff to any player in any outcome of the game
101101
Rational GetMaxPayoff() const override { return Rational(baggPtr->getMaxPayoff()); }
102102
/// Returns the largest payoff to the player in any outcome of the game
103-
Rational GetMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); }
103+
Rational GetPlayerMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); }
104104
//@}
105105

106106
/// @name Writing data files

src/games/gameexpl.cc

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,39 +38,19 @@ namespace Gambit {
3838

3939
Rational GameExplicitRep::GetMinPayoff() const
4040
{
41-
return std::accumulate(
42-
std::next(m_players.begin()), m_players.end(), GetMinPayoff(m_players.front()),
43-
[this](const Rational &r, const GamePlayer &p) { return std::min(r, GetMinPayoff(p)); });
44-
}
45-
46-
Rational GameExplicitRep::GetMinPayoff(const GamePlayer &p_player) const
47-
{
48-
if (m_outcomes.empty()) {
49-
return Rational(0);
50-
}
51-
return std::accumulate(std::next(m_outcomes.begin()), m_outcomes.end(),
52-
m_outcomes.front()->GetPayoff<Rational>(p_player),
53-
[&p_player](const Rational &r, const std::shared_ptr<GameOutcomeRep> &c) {
54-
return std::min(r, c->GetPayoff<Rational>(p_player));
41+
return std::accumulate(std::next(m_players.begin()), m_players.end(),
42+
GetPlayerMinPayoff(m_players.front()),
43+
[this](const Rational &r, const GamePlayer &p) {
44+
return std::min(r, GetPlayerMinPayoff(p));
5545
});
5646
}
5747

5848
Rational GameExplicitRep::GetMaxPayoff() const
5949
{
60-
return std::accumulate(
61-
std::next(m_players.begin()), m_players.end(), GetMaxPayoff(m_players.front()),
62-
[this](const Rational &r, const GamePlayer &p) { return std::max(r, GetMaxPayoff(p)); });
63-
}
64-
65-
Rational GameExplicitRep::GetMaxPayoff(const GamePlayer &p_player) const
66-
{
67-
if (m_outcomes.empty()) {
68-
return Rational(0);
69-
}
70-
return std::accumulate(std::next(m_outcomes.begin()), m_outcomes.end(),
71-
m_outcomes.front()->GetPayoff<Rational>(p_player),
72-
[&p_player](const Rational &r, const std::shared_ptr<GameOutcomeRep> &c) {
73-
return std::max(r, c->GetPayoff<Rational>(p_player));
50+
return std::accumulate(std::next(m_players.begin()), m_players.end(),
51+
GetPlayerMaxPayoff(m_players.front()),
52+
[this](const Rational &r, const GamePlayer &p) {
53+
return std::max(r, GetPlayerMaxPayoff(p));
7454
});
7555
}
7656

src/games/gameexpl.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,10 @@ class GameExplicitRep : public GameRep {
3333
public:
3434
/// @name General data access
3535
//@{
36-
/// Returns the smallest payoff to any player in any outcome of the game
36+
/// Returns the smallest payoff to any player in any play of the game
3737
Rational GetMinPayoff() const override;
38-
/// Returns the smallest payoff to the player in any outcome of the game
39-
Rational GetMinPayoff(const GamePlayer &) const override;
40-
/// Returns the largest payoff to any player in any outcome of the game
38+
/// Returns the largest payoff to any player in any play of the game
4139
Rational GetMaxPayoff() const override;
42-
/// Returns the largest payoff to the player in any outcome of the game
43-
Rational GetMaxPayoff(const GamePlayer &) const override;
4440
//@}
4541

4642
/// @name Dimensions of the game

src/games/gametable.cc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ bool GameTableRep::IsConstSum() const
307307
sum += profile->GetPayoff(player);
308308
}
309309

310-
for (auto iter : StrategyContingencies(std::const_pointer_cast<GameRep>(shared_from_this()))) {
310+
for (const auto iter :
311+
StrategyContingencies(std::const_pointer_cast<GameRep>(shared_from_this()))) {
311312
Rational newsum(0);
312313
for (const auto &player : m_players) {
313314
newsum += iter->GetPayoff(player);
@@ -319,6 +320,26 @@ bool GameTableRep::IsConstSum() const
319320
return true;
320321
}
321322

323+
Rational GameTableRep::GetPlayerMinPayoff(const GamePlayer &p_player) const
324+
{
325+
Rational minpay = NewPureStrategyProfile()->GetPayoff(p_player);
326+
for (const auto &profile :
327+
StrategyContingencies(std::const_pointer_cast<GameRep>(shared_from_this()))) {
328+
minpay = std::min(minpay, profile->GetPayoff(p_player));
329+
}
330+
return minpay;
331+
}
332+
333+
Rational GameTableRep::GetPlayerMaxPayoff(const GamePlayer &p_player) const
334+
{
335+
Rational maxpay = NewPureStrategyProfile()->GetPayoff(p_player);
336+
for (const auto &profile :
337+
StrategyContingencies(std::const_pointer_cast<GameRep>(shared_from_this()))) {
338+
maxpay = std::max(maxpay, profile->GetPayoff(p_player));
339+
}
340+
return maxpay;
341+
}
342+
322343
//------------------------------------------------------------------------
323344
// GameTableRep: Writing data files
324345
//------------------------------------------------------------------------

src/games/gametable.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class GameTableRep : public GameExplicitRep {
5757
//@{
5858
bool IsTree() const override { return false; }
5959
bool IsConstSum() const override;
60+
61+
/// Returns the smallest payoff to the player in any play of the game
62+
Rational GetPlayerMinPayoff(const GamePlayer &) const override;
63+
/// Returns the largest payoff to the player in any play of the game
64+
Rational GetPlayerMaxPayoff(const GamePlayer &) const override;
65+
6066
bool IsPerfectRecall() const override { return true; }
6167
//@}
6268

src/games/gametree.cc

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include <iostream>
2424
#include <algorithm>
25+
#include <functional>
2526
#include <numeric>
2627
#include <stack>
2728
#include <set>
@@ -719,10 +720,10 @@ Rational SubtreeSum(GameNode p_node)
719720
Rational sum(0);
720721

721722
if (!p_node->IsTerminal()) {
722-
auto children = p_node->GetChildren();
723+
const auto children = p_node->GetChildren();
723724
sum = SubtreeSum(children.front());
724725
if (std::any_of(std::next(children.begin()), children.end(),
725-
[sum](GameNode n) { return SubtreeSum(n) != sum; })) {
726+
[sum](const GameNode &n) { return SubtreeSum(n) != sum; })) {
726727
throw NotZeroSumException();
727728
}
728729
}
@@ -735,6 +736,29 @@ Rational SubtreeSum(GameNode p_node)
735736
return sum;
736737
}
737738

739+
Rational
740+
AggregateSubtreePayoff(const GamePlayer &p_player, const GameNode &p_node,
741+
std::function<Rational(const Rational &, const Rational &)> p_aggregator)
742+
{
743+
if (p_node->IsTerminal()) {
744+
if (p_node->GetOutcome()) {
745+
return p_node->GetOutcome()->GetPayoff<Rational>(p_player);
746+
}
747+
return Rational(0);
748+
}
749+
const auto &children = p_node->GetChildren();
750+
auto subtree =
751+
std::accumulate(std::next(children.begin()), children.end(),
752+
AggregateSubtreePayoff(p_player, children.front(), p_aggregator),
753+
[&p_aggregator, &p_player](const Rational &r, const GameNode &c) {
754+
return p_aggregator(r, AggregateSubtreePayoff(p_player, c, p_aggregator));
755+
});
756+
if (p_node->GetOutcome()) {
757+
return subtree + p_node->GetOutcome()->GetPayoff<Rational>(p_player);
758+
}
759+
return subtree;
760+
}
761+
738762
} // end anonymous namespace
739763

740764
bool GameTreeRep::IsConstSum() const
@@ -748,6 +772,18 @@ bool GameTreeRep::IsConstSum() const
748772
}
749773
}
750774

775+
Rational GameTreeRep::GetPlayerMinPayoff(const GamePlayer &p_player) const
776+
{
777+
return AggregateSubtreePayoff(
778+
p_player, m_root, [](const Rational &a, const Rational &b) { return std::min(a, b); });
779+
}
780+
781+
Rational GameTreeRep::GetPlayerMaxPayoff(const GamePlayer &p_player) const
782+
{
783+
return AggregateSubtreePayoff(
784+
p_player, m_root, [](const Rational &a, const Rational &b) { return std::max(a, b); });
785+
}
786+
751787
bool GameTreeRep::IsPerfectRecall() const
752788
{
753789
if (m_infosetParents.empty() && !m_root->IsTerminal()) {

src/games/gametree.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class GameTreeRep : public GameExplicitRep {
7575
bool IsTree() const override { return true; }
7676
bool IsConstSum() const override;
7777
bool IsPerfectRecall() const override;
78+
79+
/// Returns the smallest payoff to the player in any play of the game
80+
Rational GetPlayerMinPayoff(const GamePlayer &) const override;
81+
/// Returns the largest payoff to the player in any play of the game
82+
Rational GetPlayerMaxPayoff(const GamePlayer &) const override;
7883
//@}
7984

8085
/// @name Players

0 commit comments

Comments
 (0)