Skip to content
Merged
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
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
not a label of a child node. In addition, indexing by an action object is now supported. (#587)
- In `pygambit`, `min_payoff` and `max_payoff` (for both games and players) now refers to payoffs in
any play of the game; previously this referred only to the set of outcomes. (#498)
- In `pygambit`, calls to `sort_infosets` are no longer required to normalise the game representation.
Iteration ordering of information sets and their members is ensured internally. `sort_infosets`
is therefore now a no-op and is deprecated; it will be removed in a future version.

### Added
- Tests for EFG Nash solvers -- `enumpoly_solve`, `lp_solve`, `lcp_solve` -- in behavior stratgegies
Expand Down
3 changes: 3 additions & 0 deletions src/games/behavmixed.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const Game &p_game)
: m_probs(p_game->BehavProfileLength()), m_support(BehaviorSupportProfile(p_game)),
m_gameversion(p_game->GetVersion())
{
p_game->EnsureInfosetOrdering();
int index = 1;
for (const auto &infoset : p_game->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
Expand All @@ -51,6 +52,7 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const BehaviorSupportProfile &p_su
: m_probs(p_support.BehaviorProfileLength()), m_support(p_support),
m_gameversion(p_support.GetGame()->GetVersion())
{
m_support.GetGame()->EnsureInfosetOrdering();
int index = 1;
for (const auto &infoset : p_support.GetGame()->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
Expand Down Expand Up @@ -126,6 +128,7 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const MixedStrategyProfile<T> &p_p
: m_probs(p_profile.GetGame()->BehavProfileLength()), m_support(p_profile.GetGame()),
m_gameversion(p_profile.GetGame()->GetVersion())
{
m_support.GetGame()->EnsureInfosetOrdering();
int index = 1;
for (const auto &infoset : p_profile.GetGame()->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
Expand Down
1 change: 0 additions & 1 deletion src/games/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,6 @@ Game ReadEfgFile(std::istream &p_stream)
parser.GetNextToken();
}
ParseNode(parser, game, game->GetRoot(), treeData);
game->SortInfosets();
return game;
}

Expand Down
54 changes: 41 additions & 13 deletions src/games/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,8 @@ class GameInfosetRep : public std::enable_shared_from_this<GameInfosetRep> {
}
//@}

GameNode GetMember(int p_index) const { return m_members.at(p_index - 1); }
Members GetMembers() const
{
return Members(std::const_pointer_cast<GameInfosetRep>(shared_from_this()), &m_members);
}
GameNode GetMember(int p_index) const;
Members GetMembers() const;

bool Precedes(GameNode) const;

Expand Down Expand Up @@ -403,12 +400,9 @@ class GamePlayerRep : public std::enable_shared_from_this<GamePlayerRep> {
/// @name Information sets
//@{
/// Returns the p_index'th information set
GameInfoset GetInfoset(int p_index) const { return m_infosets.at(p_index - 1); }
GameInfoset GetInfoset(int p_index) const;
/// Returns the information sets for the player
Infosets GetInfosets() const
{
return Infosets(std::const_pointer_cast<GamePlayerRep>(shared_from_this()), &m_infosets);
}
Infosets GetInfosets() const;

/// @name Strategies
//@{
Expand Down Expand Up @@ -476,7 +470,7 @@ class GameNodeRep : public std::enable_shared_from_this<GameNodeRep> {
const std::string &GetLabel() const { return m_label; }
void SetLabel(const std::string &p_label) { m_label = p_label; }

int GetNumber() const { return m_number; }
int GetNumber() const;
GameNode GetChild(const GameAction &p_action)
{
if (p_action->GetInfoset().get() != m_infoset) {
Expand Down Expand Up @@ -603,6 +597,8 @@ enum class TraversalOrder { Preorder, Postorder };
class GameRep : public std::enable_shared_from_this<GameRep> {
friend class GameOutcomeRep;
friend class GameNodeRep;
friend class GameInfosetRep;
friend class GamePlayerRep;
friend class PureStrategyProfileRep;
friend class TablePureStrategyProfileRep;
template <class T> friend class MixedBehaviorProfile;
Expand All @@ -623,6 +619,10 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
void IncrementVersion() { m_version++; }
//@}

/// Hooks for derived classes to update lazily-computed orderings if required
virtual void EnsureNodeOrdering() const {}
virtual void EnsureInfosetOrdering() const {}

public:
using Players = ElementCollection<Game, GamePlayerRep>;
using Outcomes = ElementCollection<Game, GameOutcomeRep>;
Expand Down Expand Up @@ -986,8 +986,7 @@ class GameRep : public std::enable_shared_from_this<GameRep> {
{
return Infosets(std::const_pointer_cast<GameRep>(this->shared_from_this()));
}
/// Sort the information sets for each player in a canonical order
virtual void SortInfosets() {}

/// Returns the set of actions taken by the infoset's owner before reaching this infoset
virtual std::set<GameAction> GetOwnPriorActions(const GameInfoset &p_infoset) const
{
Expand Down Expand Up @@ -1111,6 +1110,35 @@ inline GamePlayerRep::Strategies GamePlayerRep::GetStrategies() const
}

inline Game GameNodeRep::GetGame() const { return m_game->shared_from_this(); }
inline int GameNodeRep::GetNumber() const
{
m_game->EnsureNodeOrdering();
return m_number;
}

inline GameNode GameInfosetRep::GetMember(int p_index) const
{
m_game->EnsureInfosetOrdering();
return m_members.at(p_index - 1);
}

inline GameInfosetRep::Members GameInfosetRep::GetMembers() const
{
m_game->EnsureInfosetOrdering();
return Members(std::const_pointer_cast<GameInfosetRep>(shared_from_this()), &m_members);
}

inline GameInfoset GamePlayerRep::GetInfoset(int p_index) const
{
m_game->EnsureInfosetOrdering();
return m_infosets.at(p_index - 1);
}

inline GamePlayerRep::Infosets GamePlayerRep::GetInfosets() const
{
m_game->EnsureInfosetOrdering();
return Infosets(std::const_pointer_cast<GamePlayerRep>(shared_from_this()), &m_infosets);
}

//=======================================================================

Expand Down
39 changes: 35 additions & 4 deletions src/games/gametree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ void GameTreeRep::DeleteAction(GameAction p_action)
member->m_children.erase(it);
}
ClearComputedValues();
InvalidateNodeOrdering();
}

GameInfoset GameActionRep::GetInfoset() const { return m_infoset->shared_from_this(); }
Expand Down Expand Up @@ -192,6 +193,7 @@ void GameTreeRep::SetPlayer(GameInfoset p_infoset, GamePlayer p_player)
p_player->m_infosets.push_back(p_infoset);

ClearComputedValues();
InvalidateNodeOrdering();
}

bool GameInfosetRep::Precedes(GameNode p_node) const
Expand Down Expand Up @@ -235,6 +237,7 @@ GameAction GameTreeRep::InsertAction(GameInfoset p_infoset, GameAction p_action
m_numNodes += p_infoset->m_members.size();
// m_numNonterminalNodes stays unchanged when an action is appended to an information set
ClearComputedValues();
InvalidateNodeOrdering();
return action;
}

Expand All @@ -250,6 +253,7 @@ void GameTreeRep::RemoveMember(GameInfosetRep *p_infoset, GameNodeRep *p_node)
player->m_infosets.begin(), player->m_infosets.end(), p_infoset->shared_from_this()));
RenumberInfosets(player);
}
InvalidateNodeOrdering();
}

void GameTreeRep::Reveal(GameInfoset p_atInfoset, GamePlayer p_player)
Expand Down Expand Up @@ -278,6 +282,7 @@ void GameTreeRep::Reveal(GameInfoset p_atInfoset, GamePlayer p_player)
}

ClearComputedValues();
InvalidateNodeOrdering();
}

//========================================================================
Expand Down Expand Up @@ -437,6 +442,7 @@ void GameTreeRep::DeleteParent(GameNode p_node)

oldParent->Invalidate();
ClearComputedValues();
InvalidateNodeOrdering();
}

void GameTreeRep::DeleteTree(GameNode p_node)
Expand All @@ -463,6 +469,7 @@ void GameTreeRep::DeleteTree(GameNode p_node)
node->m_label = "";

ClearComputedValues();
InvalidateNodeOrdering();
}

void GameTreeRep::CopySubtree(GameNodeRep *dest, GameNodeRep *src, GameNodeRep *stop)
Expand Down Expand Up @@ -504,6 +511,7 @@ void GameTreeRep::CopyTree(GameNode p_dest, GameNode p_src)
CopySubtree(dest_child->get(), src_child->get(), dest);
}
ClearComputedValues();
InvalidateNodeOrdering();
}
}

Expand All @@ -527,6 +535,7 @@ void GameTreeRep::MoveTree(GameNode p_dest, GameNode p_src)
dest->m_outcome = nullptr;

ClearComputedValues();
InvalidateNodeOrdering();
}

Game GameTreeRep::CopySubgame(GameNode p_root) const
Expand Down Expand Up @@ -558,6 +567,7 @@ void GameTreeRep::SetInfoset(GameNode p_node, GameInfoset p_infoset)
node->m_infoset = p_infoset.get();

ClearComputedValues();
InvalidateInfosetOrdering();
}

GameInfoset GameTreeRep::LeaveInfoset(GameNode p_node)
Expand Down Expand Up @@ -588,6 +598,7 @@ GameInfoset GameTreeRep::LeaveInfoset(GameNode p_node)
(*new_act)->SetLabel((*old_act)->GetLabel());
}
ClearComputedValues();
InvalidateInfosetOrdering();
return node->m_infoset->shared_from_this();
}

Expand Down Expand Up @@ -633,6 +644,7 @@ GameInfoset GameTreeRep::AppendMove(GameNode p_node, GameInfoset p_infoset)
});
m_numNonterminalNodes++;
ClearComputedValues();
InvalidateNodeOrdering();
return node->m_infoset->shared_from_this();
}

Expand Down Expand Up @@ -689,6 +701,7 @@ GameInfoset GameTreeRep::InsertMove(GameNode p_node, GameInfoset p_infoset)
m_numNodes += newNode->m_infoset->m_actions.size();
m_numNonterminalNodes++;
ClearComputedValues();
InvalidateNodeOrdering();
return p_infoset;
}

Expand Down Expand Up @@ -852,23 +865,36 @@ void GameTreeRep::SortInfosets(GamePlayerRep *p_player)
});
RenumberInfosets(p_player);
}

void GameTreeRep::RenumberInfosets(GamePlayerRep *p_player)
{
std::for_each(
p_player->m_infosets.begin(), p_player->m_infosets.end(),
[iset = 1](const std::shared_ptr<GameInfosetRep> &s) mutable { s->m_number = iset++; });
}

void GameTreeRep::SortInfosets()
void GameTreeRep::EnsureNodeOrdering() const
{
if (m_nodesOrdered) {
return;
}
int nodeindex = 1;
for (const auto &node : GetNodes()) {
node->m_number = nodeindex++;
}
SortInfosets(m_chance.get());
for (auto player : m_players) {
m_nodesOrdered = true;
}

void GameTreeRep::EnsureInfosetOrdering() const
{
if (m_infosetsOrdered) {
return;
}
EnsureNodeOrdering();
for (auto player : GetPlayersWithChance()) {
SortInfosets(player.get());
}
m_infosetsOrdered = true;
}

void GameTreeRep::ClearComputedValues() const
Expand All @@ -891,7 +917,7 @@ void GameTreeRep::BuildComputedValues() const
if (m_computedValues) {
return;
}
const_cast<GameTreeRep *>(this)->SortInfosets();
EnsureInfosetOrdering();
for (const auto &player : m_players) {
std::map<GameInfosetRep *, int> behav;
std::map<GameNodeRep *, GameNodeRep *> ptr, whichbranch;
Expand Down Expand Up @@ -1329,6 +1355,7 @@ MixedStrategyProfile<double> GameTreeRep::NewMixedStrategyProfile(double) const
if (!IsPerfectRecall()) {
throw UndefinedException("Mixed strategies not supported for games with imperfect recall.");
}
EnsureInfosetOrdering();
return StrategySupportProfile(std::const_pointer_cast<GameRep>(shared_from_this()))
.NewMixedStrategyProfile<double>();
}
Expand All @@ -1338,6 +1365,7 @@ MixedStrategyProfile<Rational> GameTreeRep::NewMixedStrategyProfile(const Ration
if (!IsPerfectRecall()) {
throw UndefinedException("Mixed strategies not supported for games with imperfect recall.");
}
EnsureInfosetOrdering();
return StrategySupportProfile(std::const_pointer_cast<GameRep>(shared_from_this()))
.NewMixedStrategyProfile<Rational>();
}
Expand All @@ -1348,6 +1376,7 @@ GameTreeRep::NewMixedStrategyProfile(double, const StrategySupportProfile &spt)
if (!IsPerfectRecall()) {
throw UndefinedException("Mixed strategies not supported for games with imperfect recall.");
}
EnsureInfosetOrdering();
return MixedStrategyProfile<double>(std::make_unique<TreeMixedStrategyProfileRep<double>>(spt));
}

Expand All @@ -1357,6 +1386,7 @@ GameTreeRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProf
if (!IsPerfectRecall()) {
throw UndefinedException("Mixed strategies not supported for games with imperfect recall.");
}
EnsureInfosetOrdering();
return MixedStrategyProfile<Rational>(
std::make_unique<TreeMixedStrategyProfileRep<Rational>>(spt));
}
Expand Down Expand Up @@ -1386,6 +1416,7 @@ class TreePureStrategyProfileRep : public PureStrategyProfileRep {

PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const
{
EnsureInfosetOrdering();
return PureStrategyProfile(std::make_shared<TreePureStrategyProfileRep>(
std::const_pointer_cast<GameRep>(shared_from_this())));
}
Expand Down
18 changes: 12 additions & 6 deletions src/games/gametree.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,18 @@

namespace Gambit {

class GameTreeRep : public GameExplicitRep {
class GameTreeRep final : public GameExplicitRep {
friend class GameNodeRep;
friend class GameInfosetRep;
friend class GameActionRep;

private:
struct OwnPriorActionInfo {
std::map<GameNodeRep *, GameActionRep *> node_map;
std::map<GameInfosetRep *, std::set<GameActionRep *>> infoset_map;
};

protected:
mutable bool m_computedValues{false};
mutable bool m_computedValues{false}, m_nodesOrdered{false}, m_infosetsOrdered{false};
std::shared_ptr<GameNodeRep> m_root;
std::shared_ptr<GamePlayerRep> m_chance;
std::size_t m_numNodes = 1;
Expand All @@ -51,14 +50,23 @@ class GameTreeRep : public GameExplicitRep {

/// @name Private auxiliary functions
//@{
void SortInfosets(GamePlayerRep *);
static void SortInfosets(GamePlayerRep *);
static void RenumberInfosets(GamePlayerRep *);
/// Normalize the probability distribution of actions at a chance node
Game NormalizeChanceProbs(GameInfosetRep *);
//@}

/// @name Managing the representation
//@{
void InvalidateNodeOrdering() const
{
m_nodesOrdered = false;
m_infosetsOrdered = false;
}
void InvalidateInfosetOrdering() const { m_infosetsOrdered = false; }
void EnsureNodeOrdering() const override;
void EnsureInfosetOrdering() const override;

void BuildComputedValues() const override;
void BuildConsistentPlays();
void ClearComputedValues() const;
Expand Down Expand Up @@ -128,8 +136,6 @@ class GameTreeRep : public GameExplicitRep {
//@{
/// Returns the iset'th information set in the game (numbered globally)
GameInfoset GetInfoset(int iset) const override;
/// Sort the information sets for each player in a canonical order
void SortInfosets() override;
/// Returns the set of actions taken by the infoset's owner before reaching this infoset
std::set<GameAction> GetOwnPriorActions(const GameInfoset &p_infoset) const override;
//@}
Expand Down
Loading