Skip to content

Commit

Permalink
Refactor and modernise behavior support and behavior contingency iter…
Browse files Browse the repository at this point in the history
…ation.

This addresses several items of techincal debt with respect to working with
behavior supports and iterating over behavior contingencies:

* Behavior contingencies is now an iterable class, and combines the functionality of
  several variations previously scattered in different locations
* Accessing elements by numeric indices has been removed in favour of
  consistently using game objects as appropriate
* The internal representation of behavior supports has been migrated to
  use STL containers
* Computation and elimination of dominated actions in extensive games
  is now always conditional on reaching the information set, and the
  implementation has been simplified.
  • Loading branch information
tturocy committed Nov 20, 2024
1 parent 25742d2 commit 405b14b
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 702 deletions.
2 changes: 0 additions & 2 deletions src/core/rational.cc
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,6 @@ bool Rational::operator>(const Rational &y) const { return compare(*this, y) > 0

bool Rational::operator>=(const Rational &y) const { return compare(*this, y) >= 0; }

int sign(const Rational &x) { return sign(x.num); }

void Rational::negate() { num.negate(); }

Rational &Rational::operator+=(const Rational &y)
Expand Down
2 changes: 2 additions & 0 deletions src/core/rational.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class Rational {
// Naming compatible with Boost's lexical_cast concept for potential future compatibility.
template <> Rational lexical_cast(const std::string &);

inline int sign(const Rational &x) { return sign(x.num); }

} // end namespace Gambit

#endif // LIBGAMBIT_RATIONAL_H
41 changes: 35 additions & 6 deletions src/games/behavmixed.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const Game &p_game)
: m_probs(p_game->NumActions()), m_support(BehaviorSupportProfile(p_game)),
m_gameversion(p_game->GetVersion())
{
int index = 1;
for (const auto &player : p_game->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
m_profileIndex[action] = index++;
}
}
}
SetCentroid();
}

Expand All @@ -46,6 +54,19 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const BehaviorSupportProfile &p_su
: m_probs(p_support.NumActions()), m_support(p_support),
m_gameversion(p_support.GetGame()->GetVersion())
{
int index = 1;
for (const auto &player : p_support.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
if (p_support.Contains(action)) {
m_profileIndex[action] = index++;
}
else {
m_profileIndex[action] = -1;
}
}
}
}
SetCentroid();
}

Expand Down Expand Up @@ -84,7 +105,7 @@ void MixedBehaviorProfile<T>::RealizationProbs(const MixedStrategyProfile<T> &mp
}
}
else if (GetSupport().Contains(node->GetInfoset()->GetAction(i))) {
int num_actions = GetSupport().NumActions(node->GetInfoset());
int num_actions = GetSupport().GetActions(node->GetInfoset()).size();
prob = T(1) / T(num_actions);
}
else {
Expand All @@ -109,6 +130,15 @@ MixedBehaviorProfile<T>::MixedBehaviorProfile(const MixedStrategyProfile<T> &p_p
: m_probs(p_profile.GetGame()->NumActions()), m_support(p_profile.GetGame()),
m_gameversion(p_profile.GetGame()->GetVersion())
{
int index = 1;
for (const auto &player : p_profile.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
m_profileIndex[action] = index++;
}
}
}

static_cast<Vector<T> &>(m_probs) = T(0);

GameTreeNodeRep *root =
Expand Down Expand Up @@ -172,8 +202,8 @@ template <class T> void MixedBehaviorProfile<T>::SetCentroid()
{
CheckVersion();
for (auto infoset : m_support.GetGame()->GetInfosets()) {
if (m_support.NumActions(infoset) > 0) {
T center = T(1) / T(m_support.NumActions(infoset));
if (!m_support.GetActions(infoset).empty()) {
T center = T(1) / T(m_support.GetActions(infoset).size());
for (auto act : m_support.GetActions(infoset)) {
(*this)[act] = center;
}
Expand All @@ -195,7 +225,7 @@ template <class T> void MixedBehaviorProfile<T>::UndefinedToCentroid()
[this](T total, GameAction act) { return total + GetActionProb(act); });
if (total == T(0)) {
for (auto act : actions) {
(*this)[act] = T(1) / T(m_support.NumActions(infoset));
(*this)[act] = T(1) / T(m_support.GetActions(infoset).size());
}
}
}
Expand Down Expand Up @@ -322,8 +352,7 @@ template <class T> T MixedBehaviorProfile<T>::GetActionProb(const GameAction &ac
return T(0);
}
else {
return m_probs(action->GetInfoset()->GetPlayer()->GetNumber(),
action->GetInfoset()->GetNumber(), m_support.GetIndex(action));
return m_probs[m_profileIndex.at(action)];
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/games/behavmixed.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ template <class T> class MixedBehaviorProfile {
protected:
DVector<T> m_probs;
BehaviorSupportProfile m_support;
/// The index into the action profile for a action (-1 if not in support)
std::map<GameAction, int> m_profileIndex;
unsigned int m_gameversion;

// structures for storing cached data: nodes
Expand Down Expand Up @@ -107,14 +109,12 @@ template <class T> class MixedBehaviorProfile {

const T &operator[](const GameAction &p_action) const
{
return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(),
p_action->GetInfoset()->GetNumber(), m_support.GetIndex(p_action));
return m_probs[m_profileIndex.at(p_action)];
}
T &operator[](const GameAction &p_action)
{
InvalidateCache();
return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(),
p_action->GetInfoset()->GetNumber(), m_support.GetIndex(p_action));
return m_probs[m_profileIndex.at(p_action)];
}

const T &operator[](int a) const { return m_probs[a]; }
Expand Down
173 changes: 61 additions & 112 deletions src/games/behavpure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,66 +32,50 @@ namespace Gambit {
// PureBehaviorProfile: Lifecycle
//------------------------------------------------------------------------

PureBehaviorProfile::PureBehaviorProfile(Game p_efg) : m_efg(p_efg), m_profile(m_efg->NumPlayers())
PureBehaviorProfile::PureBehaviorProfile(const Game &p_efg) : m_efg(p_efg)
{
for (int pl = 1; pl <= m_efg->NumPlayers(); pl++) {
GamePlayerRep *player = m_efg->GetPlayer(pl);
m_profile[pl] = Array<GameAction>(player->NumInfosets());
for (int iset = 1; iset <= player->NumInfosets(); iset++) {
m_profile[pl][iset] = player->GetInfoset(iset)->GetAction(1);
}
for (const auto &infoset : m_efg->GetInfosets()) {
m_profile[infoset] = infoset->GetActions().front();
}
}

//------------------------------------------------------------------------
// PureBehaviorProfile: Data access and manipulation
//------------------------------------------------------------------------

GameAction PureBehaviorProfile::GetAction(const GameInfoset &infoset) const
{
return m_profile[infoset->GetPlayer()->GetNumber()][infoset->GetNumber()];
}

void PureBehaviorProfile::SetAction(const GameAction &action)
{
m_profile[action->GetInfoset()->GetPlayer()->GetNumber()][action->GetInfoset()->GetNumber()] =
action;
}

template <class T> T PureBehaviorProfile::GetPayoff(const GameNode &p_node, int pl) const
template <class T>
T PureBehaviorProfile::GetPayoff(const GameNode &p_node, const GamePlayer &p_player) const
{
T payoff(0);

if (p_node->GetOutcome()) {
payoff += static_cast<T>(p_node->GetOutcome()->GetPayoff(pl));
payoff += static_cast<T>(p_node->GetOutcome()->GetPayoff(p_player));
}

if (!p_node->IsTerminal()) {
if (p_node->GetInfoset()->IsChanceInfoset()) {
for (int i = 1; i <= p_node->NumChildren(); i++) {
payoff += (static_cast<T>(p_node->GetInfoset()->GetActionProb(i)) *
GetPayoff<T>(p_node->GetChild(i), pl));
for (const auto &action : p_node->GetInfoset()->GetActions()) {
payoff += (static_cast<T>(p_node->GetInfoset()->GetActionProb(action)) *
GetPayoff<T>(p_node->GetChild(action), p_player));
}
}
else {
int player = p_node->GetPlayer()->GetNumber();
int iset = p_node->GetInfoset()->GetNumber();
payoff += GetPayoff<T>(p_node->GetChild(m_profile[player][iset]->GetNumber()), pl);
payoff += GetPayoff<T>(p_node->GetChild(m_profile.at(p_node->GetInfoset())), p_player);
}
}

return payoff;
}

// Explicit instantiations
template double PureBehaviorProfile::GetPayoff(const GameNode &, int pl) const;
template Rational PureBehaviorProfile::GetPayoff(const GameNode &, int pl) const;
template double PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const;
template Rational PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const;

template <class T> T PureBehaviorProfile::GetPayoff(const GameAction &p_action) const
{
PureBehaviorProfile copy(*this);
copy.SetAction(p_action);
return copy.GetPayoff<T>(p_action->GetInfoset()->GetPlayer()->GetNumber());
return copy.GetPayoff<T>(p_action->GetInfoset()->GetPlayer());
}

// Explicit instantiations
Expand All @@ -100,10 +84,10 @@ template Rational PureBehaviorProfile::GetPayoff(const GameAction &) const;

bool PureBehaviorProfile::IsAgentNash() const
{
for (auto player : m_efg->GetPlayers()) {
for (const auto &player : m_efg->GetPlayers()) {
auto current = GetPayoff<Rational>(player);
for (auto infoset : player->GetInfosets()) {
for (auto action : infoset->GetActions()) {
for (const auto &infoset : player->GetInfosets()) {
for (const auto &action : infoset->GetActions()) {
if (GetPayoff<Rational>(action) > current) {
return false;
}
Expand All @@ -117,110 +101,75 @@ MixedBehaviorProfile<Rational> PureBehaviorProfile::ToMixedBehaviorProfile() con
{
MixedBehaviorProfile<Rational> temp(m_efg);
temp = Rational(0);
for (auto player : m_efg->GetPlayers()) {
for (auto infoset : player->GetInfosets()) {
temp[GetAction(infoset)] = Rational(1);
for (const auto &player : m_efg->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
temp[m_profile.at(infoset)] = Rational(1);
}
}
return temp;
}

//========================================================================
// class BehaviorProfileIterator
// class BehaviorContingencies
//========================================================================

BehaviorProfileIterator::BehaviorProfileIterator(const Game &p_game)
: m_atEnd(false), m_support(p_game), m_currentBehav(p_game->NumInfosets()), m_profile(p_game),
m_frozenPlayer(0), m_frozenInfoset(0), m_numActiveInfosets(p_game->NumPlayers())
BehaviorContingencies::BehaviorContingencies(const BehaviorSupportProfile &p_support,
const std::set<GameInfoset> &p_reachable,
const std::vector<GameAction> &p_frozen)
: m_support(p_support), m_frozen(p_frozen)
{
for (int pl = 1; pl <= p_game->NumPlayers(); pl++) {
GamePlayer player = p_game->GetPlayer(pl);
m_numActiveInfosets[pl] = player->NumInfosets();
Array<bool> activeForPl(player->NumInfosets());
for (int iset = 1; iset <= player->NumInfosets(); iset++) {
activeForPl[iset] = true;
if (!p_reachable.empty()) {
for (const auto &infoset : p_reachable) {
m_activeInfosets.push_back(infoset);
}
m_isActive.push_back(activeForPl);
}
First();
}

BehaviorProfileIterator::BehaviorProfileIterator(const BehaviorSupportProfile &p_support,
const GameAction &p_action)
: m_atEnd(false), m_support(p_support), m_currentBehav(p_support.GetGame()->NumInfosets()),
m_profile(p_support.GetGame()),
m_frozenPlayer(p_action->GetInfoset()->GetPlayer()->GetNumber()),
m_frozenInfoset(p_action->GetInfoset()->GetNumber()),
m_numActiveInfosets(m_support.GetGame()->NumPlayers())
{
for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) {
GamePlayer player = m_support.GetGame()->GetPlayer(pl);
m_numActiveInfosets[pl] = 0;
Array<bool> activeForPl(player->NumInfosets());
for (int iset = 1; iset <= player->NumInfosets(); iset++) {
activeForPl[iset] = p_support.IsReachable(player->GetInfoset(iset));
m_numActiveInfosets[pl]++;
}
m_isActive.push_back(activeForPl);
}

m_currentBehav(m_frozenPlayer, m_frozenInfoset) = p_support.GetIndex(p_action);
m_profile.SetAction(p_action);
First();
}

void BehaviorProfileIterator::First()
{
for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) {
for (int iset = 1; iset <= m_support.GetGame()->GetPlayer(pl)->NumInfosets(); iset++) {
if (pl != m_frozenPlayer && iset != m_frozenInfoset) {
m_currentBehav(pl, iset) = 1;
if (m_isActive[pl][iset]) {
m_profile.SetAction(m_support.GetAction(pl, iset, 1));
else {
for (const auto &player : m_support.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
if (p_support.IsReachable(infoset)) {
m_activeInfosets.push_back(infoset);
}
}
}
}
for (const auto &action : m_frozen) {
m_activeInfosets.erase(std::find_if(
m_activeInfosets.begin(), m_activeInfosets.end(),
[action](const GameInfoset &infoset) { return infoset == action->GetInfoset(); }));
}
}

void BehaviorProfileIterator::operator++()
BehaviorContingencies::iterator::iterator(BehaviorContingencies *p_cont, bool p_end)
: m_cont(p_cont), m_atEnd(p_end), m_profile(p_cont->m_support.GetGame())
{
int pl = m_support.GetGame()->NumPlayers();
while (pl > 0 && m_numActiveInfosets[pl] == 0) {
--pl;
}
if (pl == 0) {
m_atEnd = true;
if (m_atEnd) {
return;
}

int iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets();

while (true) {
if (m_isActive[pl][iset] && (pl != m_frozenPlayer || iset != m_frozenInfoset)) {
if (m_currentBehav(pl, iset) < m_support.NumActions(pl, iset)) {
m_profile.SetAction(m_support.GetAction(pl, iset, ++m_currentBehav(pl, iset)));
return;
}
else {
m_currentBehav(pl, iset) = 1;
m_profile.SetAction(m_support.GetAction(pl, iset, 1));
}
for (const auto &player : m_cont->m_support.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
m_currentBehav[infoset] = m_cont->m_support.GetActions(infoset).begin();
m_profile.SetAction(*m_currentBehav[infoset]);
}
}
for (const auto &action : m_cont->m_frozen) {
m_profile.SetAction(action);
}
}

iset--;
if (iset == 0) {
do {
--pl;
} while (pl > 0 && m_numActiveInfosets[pl] == 0);

if (pl == 0) {
m_atEnd = true;
return;
}
iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets();
BehaviorContingencies::iterator &BehaviorContingencies::iterator::operator++()
{
for (auto infoset = m_cont->m_activeInfosets.crbegin();
infoset != m_cont->m_activeInfosets.crend(); ++infoset) {
++m_currentBehav[*infoset];
if (m_currentBehav.at(*infoset) != m_cont->m_support.GetActions(*infoset).end()) {
m_profile.SetAction(*m_currentBehav[*infoset]);
return *this;
}
m_currentBehav[*infoset] = m_cont->m_support.GetActions(*infoset).begin();
m_profile.SetAction(*m_currentBehav[*infoset]);
}
m_atEnd = true;
return *this;
}

} // end namespace Gambit
Loading

0 comments on commit 405b14b

Please sign in to comment.