Fix: reject spurious subgame roots on single-action chains in GameTreeRep::BuildSubgameRoots#911
Conversation
…xist in the test suite
|
|
||
| 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. |
There was a problem hiding this comment.
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....
There was a problem hiding this comment.
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).
Description of the changes in this PR
In the subgame roots detector
GameTreeRep::BuildSubgameRoots, the testlow == discis 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.