Conversation
There was a problem hiding this comment.
Pull request overview
Adds exact rational storage and computation for AGG/BAGG numerical data while retaining double-precision solver paths.
Changes:
- Preserves original payoff and type-probability representations.
- Generalizes payoff convolution to
doubleandRational. - Adds exactness fixtures and rational-solver tests.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_nash.py |
Documents rational AGG solver coverage. |
tests/test_games/2x2_fraction_types.bagg |
Adds fractional BAGG probabilities. |
tests/test_games/2x2_fraction_payoffs.agg |
Adds exact fractional/decimal payoffs. |
tests/test_agg.py |
Tests exact parsing and rational calculations. |
src/solvers/gtracer/aggame.h |
Adapts GameTracer declarations to typed distributions. |
src/solvers/gtracer/aggame.cc |
Keeps GameTracer computations double-based. |
src/games/number.h |
Adds original-text stream serialization. |
src/games/gamebagg.h |
Uses exact BAGG payoff extrema. |
src/games/gamebagg.cc |
Enables rational BAGG profiles and payoffs. |
src/games/gameagg.h |
Uses exact AGG payoff extrema. |
src/games/gameagg.cc |
Enables rational AGG profiles and payoffs. |
src/games/agg/trie_map.imp |
Makes constants compatible with templated values. |
src/games/agg/trie_map.cc |
Instantiates double and rational tries. |
src/games/agg/bagg.h |
Defines exact BAGG storage and APIs. |
src/games/agg/bagg.cc |
Implements exact BAGG parsing and computation. |
src/games/agg/agg.h |
Defines typed convolution state and exact tables. |
src/games/agg/agg.cc |
Implements exact AGG parsing, lookup, and convolution. |
Suppressed comments (4)
src/games/agg/agg.cc:967
- The double branch has the same incomplete scan: only
payoffs[0].begin()is considered for node 0, so this can return a value below the true maximum. Include node 0 in the loop.
for (int i = 1; i < numActionNodes; i++) {
src/games/agg/agg.cc:981
- This omits every node-0 payoff except the first entry, so an exact minimum located elsewhere in
exactPayoffs[0]is missed. Include node 0 in the scan.
for (int i = 1; i < numActionNodes; i++) {
src/games/agg/agg.cc:990
- The double minimum scan also skips all but the first entry of node 0, which can return an incorrect minimum. Include node 0 in the loop.
for (int i = 1; i < numActionNodes; i++) {
tests/test_agg.py:98
- There is no
agg::AGG::getExactMixedPayoff; the exact engine is thegetMixedPayoff<Rational>specialization. The docstring should name the real API.
"""AGG/BAGG mixed-strategy payoffs support exact (rational) computation: the convolution
algorithm (agg::AGG::getMixedPayoff et al.) is generic in its numeric type, so it also runs
with Rational arithmetic throughout (agg::AGG::getExactMixedPayoff), not just double.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Unlike getPurePayoff() above, this does NOT delegate to the general mixed-payoff convolution | ||
| // engine. A BAGG pure profile only leaves the OTHER players' realized types uncertain (each | ||
| // independent, per exactIndepTypeDist), not their actions -- so the expectation is a small, | ||
| // exact, finite sum over the Cartesian product of the other players' types (bounded by the | ||
| // product of their type counts, not their action counts), each term an exact AGG-level pure | ||
| // payoff via AGG::getPurePayoff<Rational>. | ||
| Rational BAGG::getExactPurePayoff(int player, int tp, const std::vector<int> &ps) const |
| 2x2_fraction_payoffs.agg is 2x2.agg with two of its four payoffs replaced: 35 -> 1/3 and | ||
| 95 -> 0.123456789012345. |
| // Constructing the profile itself is fine (e.g. to exactly represent a pure-strategy | ||
| // profile as a degenerate mixed one, as EnumPureStrategySolve does) -- it's specifically | ||
| // *payoff computation* on it that AGG cannot do exactly; see | ||
| // AGGMixedStrategyProfileRep<Rational>::GetPayoff. |
| // Constructing the profile itself is fine (e.g. to exactly represent a pure-strategy | ||
| // profile as a degenerate mixed one, as EnumPureStrategySolve does) -- it's specifically | ||
| // *payoff computation* on it that BAGG cannot do exactly; see | ||
| // BAGGMixedStrategyProfileRep<Rational>::GetPayoff. |
| # Action graph game. Exact throughout: pure-strategy payoffs via | ||
| # agg::AGG::getExactPurePayoff, and the genuinely mixed equilibrium found here via | ||
| # agg::AGG::getExactMixedPayoff (Rational arithmetic through the same convolution | ||
| # algorithm the double engine uses). |
| """BAGG type-distribution probabilities written as a fraction ("1/3") are parsed exactly | ||
| (agg::BAGG::exactIndepTypeDist) and the exact weighted-sum payoff computation over them | ||
| (agg::BAGG::getExactMixedPayoff) agrees with the double engine on the same profile. |
| s << exactPayoffs.at(node).size() << std::endl; | ||
| for (const auto &entry : exactPayoffs.at(node)) { | ||
| s << "[ "; | ||
| std::copy(entry.first.begin(), entry.first.end(), std::ostream_iterator<int>(s, " ")); | ||
| s << "] " << entry.second << std::endl; |
Both loops started at node 1 after seeding `result` from only the first entry of node 0's payoff map, so any larger/smaller value elsewhere in that same map was never considered. Scan node 0 in full like every other node.
|
@rahulsavani Those were good suggestions; I've pushed changes which address them, excepting the idea of the round-trip test. I rejected that originally because we don't yet expose (B)AGG writing and that's a whole piece of work in itself. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/test_agg.py:52
- This test never reads the
0.123456789012345payoff: every assertion currently observes only1/3,-10, or derived player extrema. A regression that still rounds long decimals would therefore pass despite the test name and fixture. Assert the(1, 1)contingency explicitly.
def test_agg_fraction_and_long_decimal_payoffs_parsed_exactly():
"""Payoffs written as a fraction ("1/3") or a decimal too precise for a double to round-trip
are parsed into exact Rationals, not silently rounded through a bare double read.
2x2_fraction_payoffs.agg is 2x2.agg with two of its four payoffs replaced: 35 -> 1/3 and
95 -> 0.123456789012345.
src/games/agg/agg.h:251
m_exactStateeagerly constructs anothernumActionNodes × numPlayersmatrix oftrie_mapobjects for every AGG, and each emptytrie_mapheap-allocates a root. Thus double-only workflows—an important use case for large AGGs—now pay roughly twice the working-state allocations even if rational arithmetic is never requested. Please initialize the rational convolution state lazily on the firststate<Rational>()call.
ConvolutionState<double> m_state;
ConvolutionState<Rational> m_exactState;
This implements a solution to #879, ensuring that numerical data in (Bayesian) action graph games are all stored with their original representation (as all other Gambit games).
This ensures no loss of precision if computing with rational numbers, or exporting to other game formats. (Even the export back to the AGG format actually lost precision!)