Skip to content

Commit

Permalink
Add section documenting numerical data in games to User Guide.
Browse files Browse the repository at this point in the history
  • Loading branch information
tturocy committed Sep 22, 2023
1 parent 5980ba7 commit 4a708db
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 41 deletions.
40 changes: 0 additions & 40 deletions doc/api.rst

This file was deleted.

102 changes: 102 additions & 0 deletions doc/pygambit.rst
Expand Up @@ -155,6 +155,108 @@ more directly, as in::
In [21]: g = gbt.Game.from_arrays(m, numpy.transpose(m))


Representation of numerical data of a game
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Payoffs to players and probabilities of actions at chance information sets are specified
as numbers. Gambit represents the numerical values in a game in exact precision,
using either decimal or rational representations.

To illustrate, we consider a trivial game which just has one move for the chance player::

In [1]: import pygambit as gbt

In [2]: g = gbt.Game.new_tree()

In [3]: g.append_move(g.root, g.players.chance, 3)

In [4]: [act.prob for act in g.root.infoset.actions]
Out [4]: [Rational(1, 3), Rational(1, 3), Rational(1, 3)]

The default when creating a new move for chance is that all actions are chosen with
equal probability. These probabilities are represented as rational numbers,
using `pygambit`'s :py:class:`.Rational` class, which is derived from Python's
`fractions.Fraction`. Numerical data can be set as rational numbers::

In [5]: g.set_chance_probs(g.root.infoset,
...: [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)])

In [6]: [act.prob for act in g.root.infoset.actions]
Out [6]: [Rational(1, 4), Rational(1, 2), Rational(1, 4)]

They can also be explicitly specified as decimal numbers::

In [7]: g.set_chance_probs(g.root.infoset,
...: [gbt.Decimal(".25"), gbt.Decimal(".50"), gbt.Decimal(".25")]

In [8]: [act.prob for act in g.root.infoset.actions]
Out [8]: [Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]

Although the two representations above are mathematically equivalent, `pygambit`
remembers the format in which the values were specified.

Expressing rational or decimal numbers as above is verbose and tedious.
`pygambit` offers a more concise way to express numerical data in games:
when setting numerical game data, `pygambit` will attempt to convert text strings to
their rational or decimal representation. The above can therefore be written
more compactly using string representations::

In [9]: g.set_chance_probs(g.root.infoset, ["1/4", "1/2", "1/4"])

In [10]: [act.prob for act in g.root.infoset.actions]
Out [10]: [Rational(1, 4), Rational(1, 2), Rational(1, 4)]

In [11]: g.set_chance_probs(g.root.infoset, [".25", ".50", ".25"])

In [12]: [act.prob for act in g.root.infoset.actions]
Out [12]: [Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]

As a further convenience, `pygambit` will accept Python `int` and `float` values.
`int` values are always interpreted as :py:class:`.Rational` values.
`pygambit` attempts to render `float` values in an appropriate :py:class:`.Decimal`
equivalent. In the majority of cases, this creates no problems.
For example,::

In [13]: g.set_chance_probs(g.root.infoset, [.25, .50, .25])

In [14]: [act.prob for act in g.root.infoset.actions]
Out [14]: [Decimal('0.25'), Decimal('0.5'), Decimal('0.25')]

However, rounding can cause difficulties when attempting to use `float`s to
represent values which do not have an exact decimal representation::

In [15]: g.set_chance_probs(g.root.infoset, [1/3, 1/3, 1/3])
ValueError: set_chance_probs(): must specify non-negative probabilities that sum to one

This behavior can be slightly surprising, especially in light of the fact that
in Python,::

In [16]: 1/3 + 1/3 + 1/3
Out [16]: 1.0

In checking whether these probabilities sum to one, `pygambit` first converts each
of the probabilities to a :py:class:`.Decimal` representation, via the following method::

In [17]: gbt.Decimal(str(1/3))
Out [17]: Decimal('0.3333333333333333')

and the sum-to-one check then fails because::

In [18]: gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3))
Out [18]: Decimal('0.9999999999999999')

Setting payoffs for players also follows the same rules. Representing probabilities
and payoffs exactly is essential, because `pygambit` offers (in particular for two-player
games) the possibility of computation of equilibria exactly, because the Nash equilibria
of any two-player game with rational payoffs and chance probabilities can be expressed exactly
in terms of rational numbers.

It is therefore advisable always to specify the numerical data of games either in terms
of :py:class:`.Decimal` or :py:class:`.Rational` values, or their string equivalents.
It is safe to use `int` values, but `float` values should be used with some care to ensure
the values are recorded as intended.


Reading a game from a file
~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
4 changes: 3 additions & 1 deletion src/pygambit/game.pxi
Expand Up @@ -527,7 +527,9 @@ class Game:
try:
self.game.deref().SetChanceProbs(cython.cast(Infoset, infoset).infoset, numbers)
except RuntimeError:
raise ValueError("set_chance_probs(): must specify non-negative probabilities that sum to one")
raise ValueError(
"set_chance_probs(): must specify non-negative probabilities that sum to one"
) from None

def _get_contingency(self, *args):
psp = cython.declare(shared_ptr[c_PureStrategyProfile])
Expand Down

0 comments on commit 4a708db

Please sign in to comment.