Skip to content

Implement arbitrary-precision numerical data in (B)AGGs - #1029

Merged
tturocy merged 15 commits into
masterfrom
issue_879
Aug 18, 2026
Merged

Implement arbitrary-precision numerical data in (B)AGGs#1029
tturocy merged 15 commits into
masterfrom
issue_879

Conversation

@tturocy

@tturocy tturocy commented Aug 6, 2026

Copy link
Copy Markdown
Member

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!)

@tturocy tturocy linked an issue Aug 6, 2026 that may be closed by this pull request
@tturocy tturocy added this to the gambit-17.0.0.alpha.2 milestone Aug 6, 2026
@tturocy
tturocy requested a review from rahulsavani August 6, 2026 00:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 double and Rational.
  • 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 the getMixedPayoff<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.

Comment thread src/games/agg/agg.cc Outdated
Comment thread src/games/agg/bagg.cc Outdated
Comment on lines +283 to +289
// 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
Comment thread tests/test_agg.py
Comment on lines +51 to +52
2x2_fraction_payoffs.agg is 2x2.agg with two of its four payoffs replaced: 35 -> 1/3 and
95 -> 0.123456789012345.
Comment thread src/games/gameagg.cc Outdated
Comment on lines +218 to +221
// 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.
Comment thread src/games/gamebagg.cc Outdated
Comment on lines +290 to +293
// 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.
Comment thread tests/test_nash.py Outdated
Comment on lines +931 to +934
# 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).
Comment thread tests/test_agg.py Outdated
Comment on lines +75 to +77
"""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.
Comment thread src/games/agg/agg.h
Comment on lines +218 to +222
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.
@tturocy
tturocy marked this pull request as ready for review August 14, 2026 18:24
@tturocy

tturocy commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.123456789012345 payoff: every assertion currently observes only 1/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_exactState eagerly constructs another numActionNodes × numPlayers matrix of trie_map objects for every AGG, and each empty trie_map heap-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 first state<Rational>() call.
  ConvolutionState<double> m_state;
  ConvolutionState<Rational> m_exactState;

@tturocy
tturocy merged commit bfaf102 into master Aug 18, 2026
26 checks passed
@tturocy
tturocy deleted the issue_879 branch August 18, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENH]: Clarify typing of numerical data in (B)AGGs

3 participants