Skip to content

Commit

Permalink
Add initialisation of mixed behavior profiles by specified distributi…
Browse files Browse the repository at this point in the history
…ons.
  • Loading branch information
Konstantinos Varsos authored and tturocy committed Feb 8, 2024
1 parent 717bb7d commit 13a6225
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 21 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added
- MixedStrategyProfile and MixedBehaviorProfile objects in pygambit can now be iterated in
various dict-like ways
- The MixedBehaviorProfile object can now be initialized on creation by a given distribution.

### Changed
- Functions to compute Nash equilibria now return a NashComputationResult object instead of a bare
Expand Down
74 changes: 59 additions & 15 deletions src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -747,15 +747,20 @@ class Game:
mspr[s] = Rational(v)
return mspr

def mixed_behavior_profile(self, rational=False) -> MixedBehaviorProfile:
"""Create a behavior strategy profile over the game.
def mixed_behavior_profile(self, data=None, rational=False) -> MixedBehaviorProfile:
"""Create a mixed behavior profile over the game.

The profile is initialized to uniform randomization for each player
over their actions at each information set.
If `data` is not specified, the profile is initialized to uniform randomization
at each information set.

Parameters
----------
rational
data : array_like of array_like of array_like, optional
A nested list (or compatible type) with the
same dimension as the action set of the game,
specifying the probabilities of the actions.

rational : bool, optional
If True, probabilities are represented using rational numbers; otherwise
double-precision floating point numbers are used.

Expand All @@ -764,19 +769,58 @@ class Game:
UndefinedOperationError
If the game does not have a tree representation.
"""
if self.is_tree:
if not rational:
mbpd = MixedBehaviorProfileDouble()
mbpd.profile = make_shared[c_MixedBehaviorProfileDouble](self.game)
return mbpd
else:
mbpr = MixedBehaviorProfileRational()
mbpr.profile = make_shared[c_MixedBehaviorProfileRational](self.game)
return mbpr
else:
if not self.is_tree:
raise UndefinedOperationError(
"Game must have a tree representation to create a mixed behavior profile"
)
if not rational:
mbpd = MixedBehaviorProfileDouble()
mbpd.profile = make_shared[c_MixedBehaviorProfileDouble](self.game)
if data is None:
return mbpd
if len(data) != len(self.players):
raise ValueError(
"Number of elements does not match number of players"
)
for (p, d) in zip(self.players, data):
if len(p.infosets) != len(d):
raise ValueError(
f"Number of elements does not match number of "
f"infosets for {p}"
)
for (i, v) in zip(p.infosets, d):
if len(i.actions) != len(v):
raise ValueError(
f"Number of elements does not match number of "
f"actions for the infoset {i} for {p}"
)
for (a, u) in zip(i.actions, v):
mbpd[a] = float(u)
return mbpd
else:
mbpr = MixedBehaviorProfileRational()
mbpr.profile = make_shared[c_MixedBehaviorProfileRational](self.game)
if data is None:
return mbpr
if len(data) != len(self.players):
raise ValueError(
"Number of elements does not match number of players"
)
for (p, d) in zip(self.players, data):
if len(p.infosets) != len(d):
raise ValueError(
f"Number of elements does not match number of "
f"infosets for {p}"
)
for (i, v) in zip(p.infosets, d):
if len(i.actions) != len(v):
raise ValueError(
f"Number of elements does not match number of "
f"actions for the infoset {i} for {p}"
)
for (a, u) in zip(i.actions, v):
mbpr[a] = Rational(u)
return mbpr

def support_profile(self):
return StrategySupportProfile(self)
Expand Down
88 changes: 82 additions & 6 deletions tests/test_behav.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
TOL = 1e-13



def _set_action_probs(profile: gbt.MixedBehaviorProfile, probs: list, rational_flag: bool):
"""Set the action probabilities in a behavior profile called ```profile``` according to a
list with probabilities in the order of ```profile.game.actions```
Expand Down Expand Up @@ -579,11 +578,16 @@ def test_as_strategy(game: gbt.Game, rational_flag: bool):

@pytest.mark.parametrize(
"game,tol,values,infoset_idx,member_idx,value,rational_flag",
[(games.create_mixed_behav_game(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 0, 0, 1.0, False),
(games.create_mixed_behav_game(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 0, 0.8, False),
(games.create_mixed_behav_game(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 1, 0.2, False),
(games.create_mixed_behav_game(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 0, 0.32, False),
(games.create_mixed_behav_game(), TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 1, 0.48, False),
[(games.create_mixed_behav_game(),
TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 0, 0, 1.0, False),
(games.create_mixed_behav_game(),
TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 0, 0.8, False),
(games.create_mixed_behav_game(),
TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 1, 1, 0.2, False),
(games.create_mixed_behav_game(),
TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 0, 0.32, False),
(games.create_mixed_behav_game(),
TOL, [0.8, 0.2, 0.4, 0.6, 0.0, 1.0], 2, 1, 0.48, False),
(games.create_mixed_behav_game(),
TOL, ["4/5", "1/5", "2/5", "3/5", "0", "1"], 0, 0, "1", True),
(games.create_mixed_behav_game(),
Expand Down Expand Up @@ -641,3 +645,75 @@ def test_action_value_with_chance_player_action(game: gbt.Game, rational_flag: b
chance_action = game.players.chance.infosets[0].actions[0]
with pytest.raises(ValueError):
game.mixed_behavior_profile(rational=rational_flag).action_value(chance_action)


@pytest.mark.parametrize(
"game,rational_flag,data",
[(games.create_mixed_behav_game(), True, [[[0, 1]], [[0, 1]], [[1, 0]]]),
(games.create_mixed_behav_game(), True, [[["1/5", "4/5"]], [["1/4", "3/4"]], [[1, 0]]]),
(games.create_complicated_extensive_game(), True, [[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4]]]),
(games.create_mixed_behav_game(), False, [[[0, 1]], [[1, 0]], [[1, 0]]]),
(games.create_mixed_behav_game(), False, [[[1/5, 4/5]], [[1/4, 3/4]], [[1, 0]]]),
(games.create_complicated_extensive_game(), False, [[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4]]])
]
)
def test_specific_profile(game: gbt.Game, rational_flag: bool, data: list):
"""Test that the mixed behavior profile is initialized from a specific distribution
for each player over his actions.
"""
profile = game.mixed_behavior_profile(rational=rational_flag, data=data)
for (action, prob) in zip(game.actions, [k for i in data for j in i for k in j]):
assert profile[action] == (gbt.Rational(prob) if rational_flag else prob)


@pytest.mark.parametrize(
"game,rational_flag,data",
[(games.create_mixed_behav_game(), True,
[[[0, 1, 0]], [[1, 0]], [["1/2", "1/2"]]]),
(games.create_mixed_behav_game(), True,
[[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]]),
(games.create_complicated_extensive_game(), True,
[[["1/5", "4/5"], ["3/5", "2/5"]], [["1/4", "3/4"], ["1/4", "3/4"]]]),
(games.create_el_farol_bar_game(), True,
[[4/9, 5/9], [0], [1/2, 1/2], [11/12, 1/12], [1/2, 1/2]]),
(games.create_el_farol_bar_game(), True,
[[1/2, 1/2]]),
(games.create_mixed_behav_game(), False,
[[[0, 1, 0]], [[1, 0]], [[1, 0]]]),
(games.create_mixed_behav_game(), False,
[[[0, 1]], [[1, 0]], [[1, 0]], [[0, 1]]]),
(games.create_complicated_extensive_game(), False,
[[[1/5, 4/5], [3/5, 2/5]], [[1/4, 3/4], [1/4, 3/4]]]),
(games.create_el_farol_bar_game(), False,
[[4/9, 5/9], [0], [1/2, 1/2], [11/12, 1/12], [1/2, 1/2]]),
(games.create_el_farol_bar_game(), False,
[[1/2, 1/2]])
]
)
def test_profile_data_error(game: gbt.Game, rational_flag: bool, data: list):
"""Test to ensure a pygambit.ValueError is raised when the data do not
match with the number of players, the number of the infosets, and the
number of actions per infoset.
"""
with pytest.raises(ValueError):
game.mixed_behavior_profile(rational=rational_flag, data=data)


@pytest.mark.parametrize(
"game,rational_flag,data",
[(games.create_coord_4x4_nfg(), True,
[["1/5", "2/5", 0, "2/5"], ["1/4", "3/8", "1/4", "3/8"]]),
(games.create_strategic_game(), True,
[["4/9", "5/9"], ["1/3", "2/3"]]),
(games.create_coord_4x4_nfg(), False,
[[1/5, 2/5, 0/5, 2/5], [1/4, 3/8, 1/4, 3/8]]),
(games.create_strategic_game(), False,
[[4/9, 5/9], [1/3, 2/3]])
]
)
def test_tree_representation_error(game: gbt.Game, rational_flag: bool, data: list):
"""Test to ensure a pygambit.UndefinedOperationError is raised when the game
to create a mixed behavior profile does not have a tree representation.
"""
with pytest.raises(gbt.UndefinedOperationError):
game.mixed_behavior_profile(rational=rational_flag, data=data)

0 comments on commit 13a6225

Please sign in to comment.