Skip to content

Fix: reject spurious subgame roots on single-action chains in GameTreeRep::BuildSubgameRoots#911

Merged
tturocy merged 6 commits into
gambitproject:masterfrom
d-kad:tarjan
Jun 4, 2026
Merged

Fix: reject spurious subgame roots on single-action chains in GameTreeRep::BuildSubgameRoots#911
tturocy merged 6 commits into
gambitproject:masterfrom
d-kad:tarjan

Conversation

@d-kad

@d-kad d-kad commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Description of the changes in this PR

In the subgame roots detector GameTreeRep::BuildSubgameRoots, the test low == disc is exact only when distinct nodes have distinct terminal spans, which holds under the traditional multi-child assumption of the Extensive Form model. On a single-action unary chain the spans collapse, so an absent-minded information set can straddle a node's subtree through a member on the chain above it, and the node is emitted as a spurious subgame root.

This PR adds a check to reject those false positives, since we allow both single-action nodes and absent-mindedness in Gambit. When a node satisfies low == disc, it walks the single-action ancestors and discards the candidate if any of their information sets contains a member inside the node's subtree.

Checks relying on the candidate's own information set only (e.g. its absent-mindedness, and if so finding the frontier) would miss situations like the ones added to the test suite: a node sitting between two members of a different absent-minded infoset, and a multi-action node straddled by an off-chain member below it.

@d-kad d-kad added this to the gambit-16.7.0 milestone Jun 2, 2026
@d-kad d-kad requested review from rahulsavani and tturocy June 2, 2026 17:43
@d-kad d-kad self-assigned this Jun 2, 2026
@d-kad d-kad added c++ Items which involve writing in C++ bug labels Jun 2, 2026
Comment thread src/games/gametree.cc Outdated
Comment thread tests/test_games/AM-unary-branches.efg Outdated
Comment thread src/games/gametree.cc Outdated

if (low == m_disc.at(node)) {
m_subgames.push_back(node);
// low == disc is an exact bridge test only when nodes have distinct terminal spans.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused here, because this is doing something different than what I remember the previous version doing.

I was under the impression that the claim is that the false positives are only in the case of a single-action absent-minded information set. Further, then, if we have a single-action information set, it would then be enough to ensure that none of the other members of the information set are ancestors of the current node.

Is that claim not correct?

If it is correct then a single std::any_of call iterating over the information set members and testing predecessor/successor would suffice.

If it is not correct then we need a more precise statement of the problem....

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the comment was not precise, so I improved it.

The cause is exactly as we discussed: a single-action absent-minded infoset.

But the false-positive node that passes low == disc is not always caught by looking at its own infoset, so an any_of over the candidate's members isn't enough on its own. Both cases already live in AM-unary-hops.efg:

Own infoset straddles (infoset 1:1). 1:1 is the absent-minded infoset with both members on the unary chain. Its lower member passes low == disc but its subtree is straddled by 1:1 itself. Here the candidate's own infoset is the straddling set, and the reflexive IsSuccessorOf() catches it. This is the case where any_of over the node's own infoset would catch.

Different infoset straddles (infoset 1:3). 1:3 is a singleton; it is not a subgame root as its subtree is straddled by a different infoset, 1:2. One member of 1:2 lies above 1:3 on the chain; the other lies inside 1:3's subtree. The upper member has the same span as 1:3 and the subtree member has a strictly smaller span, so low(1:3) == disc(1:3). An any_of over 1:3's own members never sees 1:2, so it cannot catch this one.
That's why the check needs to climb the single-action ancestors and test their infosets for a member in the node's subtree.

A precise statement of the false positive:

Given that x passes low == disc, the node x is not a subgame root IFF some infoset I has a member y' in subtree(x) and a member y outside subtree(x).
Any such y is then a strict single-action ancestor of x with D(y) = D(x), so the path from y down to the parent of x is unary (though x itself may branch) and Hull(I) = D(x).

@tturocy tturocy merged commit aa4f804 into gambitproject:master Jun 4, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug c++ Items which involve writing in C++

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants