Skip to content

Commit b5a9a88

Browse files
committed
Merge branch 'master' into issue_483
# Conflicts: # src/games/game.h # src/games/gametree.cc # src/games/gametree.h
2 parents fbda433 + a9fbcf1 commit b5a9a88

36 files changed

Lines changed: 398 additions & 387 deletions

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@v4
1313
- name: Run clang-format style check for C/C++
14-
uses: jidicula/clang-format-action@v4.14.0
14+
uses: jidicula/clang-format-action@v4.15.0
1515
with:
1616
clang-format-version: '17'
1717
check-path: 'src'

.github/workflows/python.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,52 @@ jobs:
3232
- name: Run tests
3333
run: pytest
3434

35+
macos-13:
36+
runs-on: macos-13
37+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
38+
strategy:
39+
matrix:
40+
python-version: ['3.12']
41+
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: Set up Python ${{ matrix.python-version }}
45+
uses: actions/setup-python@v5
46+
with:
47+
python-version: ${{ matrix.python-version }}
48+
- name: Set up dependencies
49+
run: |
50+
python -m pip install --upgrade pip
51+
pip install cython pytest wheel lxml numpy scipy
52+
- name: Build extension
53+
run: |
54+
python -m pip install -v .
55+
- name: Run tests
56+
run: pytest
57+
58+
macos-14:
59+
runs-on: macos-14
60+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
61+
strategy:
62+
matrix:
63+
python-version: ['3.12']
64+
65+
steps:
66+
- uses: actions/checkout@v4
67+
- name: Set up Python ${{ matrix.python-version }}
68+
uses: actions/setup-python@v5
69+
with:
70+
python-version: ${{ matrix.python-version }}
71+
- name: Set up dependencies
72+
run: |
73+
python -m pip install --upgrade pip
74+
pip install cython pytest wheel lxml numpy scipy
75+
- name: Build extension
76+
run: |
77+
python -m pip install -v .
78+
- name: Run tests
79+
run: pytest
80+
3581
windows:
3682
runs-on: windows-latest
3783
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
been removed as planned. (#357)
99

1010

11+
## [16.3.1] - unreleased
12+
13+
### Fixed
14+
- Corrected a regression in which information sets were prematurely invalidated (and therefore
15+
`delete this` called on them) when removing the last node from an information set.
16+
17+
1118
## [16.3.0] - 2025-01-13
1219

1320
### General

doc/pygambit.api.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ Probability distributions over strategies
212212
MixedStrategyProfile.strategy_value
213213
MixedStrategyProfile.strategy_regret
214214
MixedStrategyProfile.player_regret
215-
MixedStrategyProfile.max_regret
216215
MixedStrategyProfile.strategy_value_deriv
216+
MixedStrategyProfile.max_regret
217217
MixedStrategyProfile.liap_value
218218
MixedStrategyProfile.as_behavior
219219
MixedStrategyProfile.normalize
@@ -239,14 +239,16 @@ Probability distributions over behavior
239239
MixedBehaviorProfile.__getitem__
240240
MixedBehaviorProfile.__setitem__
241241
MixedBehaviorProfile.payoff
242-
MixedBehaviorProfile.action_regret
243242
MixedBehaviorProfile.action_value
243+
MixedBehaviorProfile.action_regret
244244
MixedBehaviorProfile.infoset_value
245+
MixedBehaviorProfile.infoset_regret
245246
MixedBehaviorProfile.node_value
246247
MixedBehaviorProfile.realiz_prob
247248
MixedBehaviorProfile.infoset_prob
248249
MixedBehaviorProfile.belief
249250
MixedBehaviorProfile.is_defined_at
251+
MixedBehaviorProfile.max_regret
250252
MixedBehaviorProfile.liap_value
251253
MixedBehaviorProfile.as_strategy
252254
MixedBehaviorProfile.normalize

src/games/behavmixed.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void MixedBehaviorProfile<T>::RealizationProbs(const MixedStrategyProfile<T> &mp
9494
{
9595
T prob;
9696

97-
for (int i = 1; i <= node->children.size(); i++) {
97+
for (int i = 1; i <= node->m_children.size(); i++) {
9898
if (node->GetPlayer() && !node->GetPlayer()->IsChance()) {
9999
if (node->GetPlayer() == player) {
100100
if (actions[node->GetInfoset()->GetNumber()] == i) {
@@ -113,10 +113,10 @@ void MixedBehaviorProfile<T>::RealizationProbs(const MixedStrategyProfile<T> &mp
113113
}
114114
}
115115
else { // n.GetPlayer() == 0
116-
prob = T(node->infoset->GetActionProb(i));
116+
prob = T(node->m_infoset->GetActionProb(node->m_infoset->GetAction(i)));
117117
}
118118

119-
GameTreeNodeRep *child = node->children[i];
119+
GameTreeNodeRep *child = node->m_children[i];
120120

121121
map_bvals[child] = prob * map_bvals[node];
122122
map_nvals[child] += map_bvals[child];
@@ -335,7 +335,7 @@ template <class T> T MixedBehaviorProfile<T>::GetActionProb(const GameAction &ac
335335
if (action->GetInfoset()->GetPlayer()->IsChance()) {
336336
GameTreeInfosetRep *infoset =
337337
dynamic_cast<GameTreeInfosetRep *>(action->GetInfoset().operator->());
338-
return static_cast<T>(infoset->GetActionProb(action->GetNumber()));
338+
return static_cast<T>(infoset->GetActionProb(action));
339339
}
340340
else if (!m_support.Contains(action)) {
341341
return T(0);
@@ -381,7 +381,7 @@ void MixedBehaviorProfile<T>::GetPayoff(const GameNode &node, const T &prob,
381381
const GamePlayer &player, T &value) const
382382
{
383383
if (node->GetOutcome()) {
384-
value += prob * static_cast<T>(node->GetOutcome()->GetPayoff(player));
384+
value += prob * node->GetOutcome()->GetPayoff<T>(player);
385385
}
386386

387387
if (!node->IsTerminal()) {
@@ -520,7 +520,7 @@ void MixedBehaviorProfile<T>::ComputePass2_beliefs_nodeValues_actionValues(
520520
if (node->GetOutcome()) {
521521
GameOutcome outcome = node->GetOutcome();
522522
for (auto player : m_support.GetGame()->GetPlayers()) {
523-
map_nodeValues[node][player] += static_cast<T>(outcome->GetPayoff(player));
523+
map_nodeValues[node][player] += outcome->GetPayoff<T>(player);
524524
}
525525
}
526526

src/games/behavpure.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ T PureBehaviorProfile::GetPayoff(const GameNode &p_node, const GamePlayer &p_pla
4949
T payoff(0);
5050

5151
if (p_node->GetOutcome()) {
52-
payoff += static_cast<T>(p_node->GetOutcome()->GetPayoff(p_player));
52+
payoff += p_node->GetOutcome()->GetPayoff<T>(p_player);
5353
}
5454

5555
if (!p_node->IsTerminal()) {

src/games/game.cc

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ namespace Gambit {
4141
// class GameOutcomeRep
4242
//========================================================================
4343

44-
GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number)
45-
: m_game(p_game), m_number(p_number), m_payoffs(m_game->NumPlayers())
44+
GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number) : m_game(p_game), m_number(p_number)
4645
{
46+
for (const auto &player : p_game->GetPlayers()) {
47+
m_payoffs[player] = Number();
48+
}
4749
}
4850

4951
//========================================================================
@@ -121,8 +123,8 @@ void GamePlayerRep::MakeStrategy()
121123
Array<int> c(NumInfosets());
122124

123125
for (int i = 1; i <= NumInfosets(); i++) {
124-
if (m_infosets[i]->flag == 1) {
125-
c[i] = m_infosets[i]->whichbranch;
126+
if (m_infosets[i - 1]->flag == 1) {
127+
c[i] = m_infosets[i - 1]->whichbranch;
126128
}
127129
else {
128130
c[i] = 0;
@@ -161,33 +163,33 @@ void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn)
161163
}
162164

163165
if (n->NumChildren() > 0) {
164-
if (n->infoset->m_player == this) {
165-
if (n->infoset->flag == 0) {
166+
if (n->m_infoset->m_player == this) {
167+
if (n->m_infoset->flag == 0) {
166168
// we haven't visited this infoset before
167-
n->infoset->flag = 1;
169+
n->m_infoset->flag = 1;
168170
for (i = 1; i <= n->NumChildren(); i++) {
169-
GameTreeNodeRep *m = n->children[i];
171+
GameTreeNodeRep *m = n->m_children[i];
170172
n->whichbranch = m;
171-
n->infoset->whichbranch = i;
173+
n->m_infoset->whichbranch = i;
172174
MakeReducedStrats(m, nn);
173175
}
174-
n->infoset->flag = 0;
176+
n->m_infoset->flag = 0;
175177
}
176178
else {
177179
// we have visited this infoset, take same action
178-
MakeReducedStrats(n->children[n->infoset->whichbranch], nn);
180+
MakeReducedStrats(n->m_children[n->m_infoset->whichbranch], nn);
179181
}
180182
}
181183
else {
182184
n->ptr = nullptr;
183185
if (nn != nullptr) {
184186
n->ptr = nn->m_parent;
185187
}
186-
n->whichbranch = n->children[1];
187-
if (n->infoset) {
188-
n->infoset->whichbranch = 0;
188+
n->whichbranch = n->m_children[1];
189+
if (n->m_infoset) {
190+
n->m_infoset->whichbranch = 0;
189191
}
190-
MakeReducedStrats(n->children[1], n->children[1]);
192+
MakeReducedStrats(n->m_children[1], n->m_children[1]);
191193
}
192194
}
193195
else if (nn) {
@@ -217,7 +219,7 @@ void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn)
217219
}
218220
}
219221

220-
GameInfoset GamePlayerRep::GetInfoset(int p_index) const { return m_infosets[p_index]; }
222+
GameInfoset GamePlayerRep::GetInfoset(int p_index) const { return m_infosets[p_index - 1]; }
221223

222224
Array<GameInfoset> GamePlayerRep::GetInfosets() const
223225
{
@@ -398,46 +400,31 @@ template <class T>
398400
MixedStrategyProfile<T>::MixedStrategyProfile(const MixedBehaviorProfile<T> &p_profile)
399401
: m_rep(new TreeMixedStrategyProfileRep<T>(p_profile))
400402
{
401-
Game game = p_profile.GetGame();
402-
auto *efg = dynamic_cast<GameTreeRep *>(game.operator->());
403-
for (int pl = 1; pl <= m_rep->m_support.GetGame()->NumPlayers(); pl++) {
404-
GamePlayer player = m_rep->m_support.GetGame()->GetPlayer(pl);
405-
for (int st = 1; st <= player->NumStrategies(); st++) {
406-
T prob = (T)1;
407-
408-
for (int iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) {
409-
if (efg->m_players[pl]->m_strategies[st]->m_behav[iset] > 0) {
410-
GameInfoset infoset = player->GetInfoset(iset);
411-
prob *=
412-
p_profile[infoset->GetAction(efg->m_players[pl]->m_strategies[st]->m_behav[iset])];
403+
auto *efg = dynamic_cast<GameTreeRep *>(p_profile.GetGame().operator->());
404+
for (const auto &player : efg->m_players) {
405+
for (const auto &strategy : player->m_strategies) {
406+
auto prob = static_cast<T>(1);
407+
for (const auto &infoset : player->m_infosets) {
408+
if (strategy->m_behav[infoset->GetNumber()] > 0) {
409+
prob *= p_profile[infoset->GetAction(strategy->m_behav[infoset->GetNumber()])];
413410
}
414411
}
415-
(*this)[m_rep->m_support.GetGame()->GetPlayer(pl)->GetStrategy(st)] = prob;
412+
(*m_rep)[strategy] = prob;
416413
}
417414
}
418415
}
419416

420-
template <class T>
421-
MixedStrategyProfile<T>::MixedStrategyProfile(const MixedStrategyProfile<T> &p_profile)
422-
: m_rep(p_profile.m_rep->Copy())
423-
{
424-
InvalidateCache();
425-
}
426-
427417
template <class T>
428418
MixedStrategyProfile<T> &
429419
MixedStrategyProfile<T>::operator=(const MixedStrategyProfile<T> &p_profile)
430420
{
431421
if (this != &p_profile) {
432422
InvalidateCache();
433-
delete m_rep;
434-
m_rep = p_profile.m_rep->Copy();
423+
m_rep.reset(p_profile.m_rep->Copy());
435424
}
436425
return *this;
437426
}
438427

439-
template <class T> MixedStrategyProfile<T>::~MixedStrategyProfile() { delete m_rep; }
440-
441428
//========================================================================
442429
// MixedStrategyProfile<T>: General data access
443430
//========================================================================
@@ -447,10 +434,8 @@ template <class T> Vector<T> MixedStrategyProfile<T>::operator[](const GamePlaye
447434
CheckVersion();
448435
auto strategies = m_rep->m_support.GetStrategies(p_player);
449436
Vector<T> probs(strategies.size());
450-
int st = 1;
451-
for (auto strategy : strategies) {
452-
probs[st] = (*this)[strategy];
453-
}
437+
std::transform(strategies.begin(), strategies.end(), probs.begin(),
438+
[this](const GameStrategy &s) { return (*m_rep)[s]; });
454439
return probs;
455440
}
456441

@@ -459,9 +444,10 @@ template <class T> MixedStrategyProfile<T> MixedStrategyProfile<T>::ToFullSuppor
459444
CheckVersion();
460445
MixedStrategyProfile<T> full(m_rep->m_support.GetGame()->NewMixedStrategyProfile(T(0)));
461446

462-
for (auto player : m_rep->m_support.GetGame()->GetPlayers()) {
463-
for (auto strategy : player->GetStrategies()) {
464-
full[strategy] = (m_rep->m_support.Contains(strategy)) ? (*this)[strategy] : T(0);
447+
for (const auto &player : m_rep->m_support.GetGame()->GetPlayers()) {
448+
for (const auto &strategy : player->GetStrategies()) {
449+
full[strategy] =
450+
(m_rep->m_support.Contains(strategy)) ? (*m_rep)[strategy] : static_cast<T>(0);
465451
}
466452
}
467453
return full;
@@ -470,17 +456,18 @@ template <class T> MixedStrategyProfile<T> MixedStrategyProfile<T>::ToFullSuppor
470456
//========================================================================
471457
// MixedStrategyProfile<T>: Computation of interesting quantities
472458
//========================================================================
459+
473460
template <class T> void MixedStrategyProfile<T>::ComputePayoffs() const
474461
{
475462
if (!map_profile_payoffs.empty()) {
476463
// caches (map_profile_payoffs and map_strategy_payoffs) are valid,
477464
// so don't compute anything, simply return
478465
return;
479466
}
480-
for (auto player : m_rep->m_support.GetPlayers()) {
467+
for (const auto &player : m_rep->m_support.GetPlayers()) {
481468
map_profile_payoffs[player] = GetPayoff(player);
482469
// values of the player's strategies
483-
for (auto strategy : m_rep->m_support.GetStrategies(player)) {
470+
for (const auto &strategy : m_rep->m_support.GetStrategies(player)) {
484471
map_strategy_payoffs[player][strategy] = GetPayoff(strategy);
485472
}
486473
}
@@ -491,13 +478,10 @@ template <class T> T MixedStrategyProfile<T>::GetLiapValue() const
491478
CheckVersion();
492479
ComputePayoffs();
493480

494-
T liapValue = T(0);
495-
for (auto player : m_rep->m_support.GetPlayers()) {
481+
auto liapValue = static_cast<T>(0);
482+
for (auto [player, payoff] : map_profile_payoffs) {
496483
for (auto v : map_strategy_payoffs[player]) {
497-
T regret = v.second - map_profile_payoffs[player];
498-
if (regret > T(0)) {
499-
liapValue += regret * regret; // penalty if not best response
500-
}
484+
liapValue += sqr(std::max(v.second - payoff, static_cast<T>(0)));
501485
}
502486
}
503487
return liapValue;

0 commit comments

Comments
 (0)