Skip to content

Commit

Permalink
Update documentation and Python tests to reflect new conventions.
Browse files Browse the repository at this point in the history
  • Loading branch information
tturocy committed Sep 8, 2023
1 parent 5c2523a commit 0996e36
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 54 deletions.
12 changes: 9 additions & 3 deletions src/games/gametree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1034,18 +1034,24 @@ int GameTreeRep::NumNodes() const

Game GameTreeRep::SetChanceProbs(const GameInfoset &p_infoset, const Array<Number> &p_probs)
{
if (p_infoset->GetGame() != this) {
throw MismatchException();
}
if (!p_infoset->IsChanceInfoset()) {
throw UndefinedException();
throw UndefinedException("Action probabilities can only be specified for chance information sets");
}
if (p_infoset->NumActions() != p_probs.size()) {
throw DimensionException();
throw DimensionException("The number of probabilities given must match the number of actions");
}
Rational sum(0);
for (auto prob : p_probs) {
if (static_cast<Rational>(prob) < Rational(0)) {
throw ValueException("Probabilities must be non-negative numbers");
}
sum += static_cast<Rational>(prob);
}
if (sum != Rational(1)) {
throw ValueException();
throw ValueException("Probabilities must sum to exactly one");
}
for (int act = 1; act <= p_infoset->NumActions(); act++) {
dynamic_cast<GameTreeInfosetRep &>(*p_infoset).m_probs[act] = p_probs[act];
Expand Down
19 changes: 10 additions & 9 deletions src/pygambit/action.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,19 @@ class Action:
@property
def prob(self) -> typing.Union[decimal.Decimal, Rational]:
"""
Get or set the probability a chance action is played.
When setting the probability, the value can be any numeric type, or any object that
has a string representation which can be interpreted as an integer,
decimal, or rational number.
Get the probability a chance action is played.

.. deprecated:: 16.1.0
Setting individual chance action probabilities is no longer supported.
Use `Game.set_chance_probs()` instead.

Raises
------
ValueError
If `value` cannot be interpreted as a decimal or rational number.
UndefinedOperationError
If the action does not belong to the chance player.
"""
if not self.infoset.is_chance:
raise UndefinedOperationError("action probabilities are only defined at chance information sets")
py_string = cython.cast(
string,
self.action.deref().GetInfoset().deref().GetActionProb(self.action.deref().GetNumber())
Expand All @@ -110,6 +113,4 @@ class Action:

@prob.setter
def prob(self, value: typing.Any) -> None:
self.action.deref().GetInfoset().deref().SetActionProb(
self.action.deref().GetNumber(), _to_number(value)
)
raise UndefinedOperationError("use Game.set_chance_probs() to set probabilities at chance information sets")
9 changes: 6 additions & 3 deletions src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,8 @@ class Game:
IndexError
If the length of `probs` is not the same as the number of actions at the information set
ValueError
If any of the elements of `probs` are not interpretable as numbers, or the values of `probs` do not
sum to one.
If any of the elements of `probs` are not interpretable as numbers, or the values of `probs` are not
nonnegative numbers that sum to exactly one.
"""
if infoset.game != self:
raise MismatchError("set_chance_probs() first argument must be an infoset in the same game")
Expand All @@ -527,7 +527,10 @@ class Game:
numbers = Array[c_Number](len(probs))
for i in range(1, len(probs)+1):
setitem_array_number(numbers, i, _to_number(probs[i-1]))
self.game.deref().SetChanceProbs(infoset.infoset, numbers)
try:
self.game.deref().SetChanceProbs(infoset.infoset, numbers)
except RuntimeError:
raise ValueError("set_chance_probs(): must specify non-negative probabilities that sum to one")
return self

def _get_contingency(self, *args):
Expand Down
62 changes: 23 additions & 39 deletions src/pygambit/tests/test_actions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pygambit
import decimal
import fractions
import unittest

Expand All @@ -22,53 +21,38 @@ def test_action_set_label(self):
"action label"
)

def test_action_probability(self):
"""Test to ensure action probabilities work"""
assert (
self.extensive_game.root.infoset.actions[0].prob ==
decimal.Decimal('0.500000')
)
def test_action_probability_chance(self):
"""Test setting action probabilities at chance information sets"""
assert (self.extensive_game.root.infoset.actions[0].prob == 0.5)

self.extensive_game.root.infoset.actions[0].prob = 2.0
assert (
self.extensive_game.root.infoset.actions[0].prob ==
fractions.Fraction(2)
)
self.extensive_game.set_chance_probs(self.extensive_game.root.infoset, [0.75, 0.25])
assert (self.extensive_game.root.infoset.actions[0].prob == 0.75)
assert (self.extensive_game.root.infoset.actions[1].prob == 0.25)

self.extensive_game.root.infoset.actions[0].prob = (
decimal.Decimal('0.97300')
)
assert (
self.extensive_game.root.infoset.actions[0].prob ==
decimal.Decimal('0.97300')
)
self.extensive_game.set_chance_probs(self.extensive_game.root.infoset, ["1/17", "16/17"])
assert (self.extensive_game.root.infoset.actions[0].prob == fractions.Fraction("1/17"))
assert (self.extensive_game.root.infoset.actions[1].prob == fractions.Fraction("16/17"))

self.extensive_game.root.infoset.actions[0].prob = (
fractions.Fraction('1/17')
self.assertRaises(
ValueError, self.extensive_game.set_chance_probs, self.extensive_game.root.infoset, [0.75, 0.10]
)
assert (
self.extensive_game.root.infoset.actions[0].prob ==
fractions.Fraction('1/17')
self.assertRaises(
ValueError, self.extensive_game.set_chance_probs, self.extensive_game.root.infoset, [1.1, -0.1]
)

self.extensive_game.root.infoset.actions[0].prob = 2
assert self.extensive_game.root.infoset.actions[0].prob == 2

self.extensive_game.root.infoset.actions[0].prob = "1/7"
assert (
self.extensive_game.root.infoset.actions[0].prob ==
fractions.Fraction('1/7')
self.assertRaises(
IndexError, self.extensive_game.set_chance_probs, self.extensive_game.root.infoset, [0.75, 0.10, 0.15]
)

self.extensive_game.root.infoset.actions[0].prob = "2.7"
assert (
self.extensive_game.root.infoset.actions[0].prob ==
decimal.Decimal('2.7')
self.assertRaises(
ValueError, setattr, self.extensive_game.root.infoset.actions[0], "prob", "test"
)

self.extensive_game.set_chance_probs(self.extensive_game.root.infoset, [0.50, 0.50])

def test_action_probability_nonchance(self):
"""Test attempts to set action probabilities at non-chance information sets."""
self.assertRaises(
ValueError, setattr, self.extensive_game.root.infoset.actions[0],
"prob", "test"
pygambit.UndefinedOperationError, self.extensive_game.set_chance_probs,
self.extensive_game.players[0].infosets[0], [0.75, 0.25]
)

def test_action_precedes(self):
Expand Down

0 comments on commit 0996e36

Please sign in to comment.