Skip to content

Commit

Permalink
Make interface to mixed profiles uniform.
Browse files Browse the repository at this point in the history
This creates a uniform approach to implementing mixed strategy and mixed behavior profiles.
In all cases, member functions are defined in the base (precision-agnostic) class, and then the
implementation is delegated to a private member in the
derived class.

The documentation index has also been updated to include all public members
of the class API.
  • Loading branch information
tturocy committed Oct 13, 2023
1 parent b07f68f commit 8c6b8c7
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 145 deletions.
29 changes: 12 additions & 17 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,21 +192,18 @@ Probability distributions over strategies
:toctree: api/

MixedStrategyProfile
MixedStrategyProfile.game
MixedStrategyProfile.__getitem__
MixedStrategyProfile.__setitem__
MixedStrategyProfile.payoff
MixedStrategyProfile.regret
MixedStrategyProfile.strategy_value
MixedStrategyProfile.strategy_value_deriv
MixedStrategyProfileDouble.game
MixedStrategyProfileDouble.liap_value
MixedStrategyProfileDouble.as_behavior
MixedStrategyProfileDouble.randomize
MixedStrategyProfileRational.game
MixedStrategyProfileRational.liap_value
MixedStrategyProfileRational.as_behavior
MixedStrategyProfileRational.randomize

MixedStrategyProfile.liap_value
MixedStrategyProfile.as_behavior
MixedStrategyProfile.randomize
MixedStrategyProfile.normalize
MixedStrategyProfile.copy


Probability distributions over behavior
Expand All @@ -216,6 +213,7 @@ Probability distributions over behavior
:toctree: api/

MixedBehaviorProfile
MixedBehaviorProfile.game
MixedBehaviorProfile.__getitem__
MixedBehaviorProfile.__setitem__
MixedBehaviorProfile.payoff
Expand All @@ -226,14 +224,11 @@ Probability distributions over behavior
MixedBehaviorProfile.infoset_prob
MixedBehaviorProfile.belief
MixedBehaviorProfile.is_defined_at
MixedBehaviorProfileDouble.game
MixedBehaviorProfileDouble.liap_value
MixedBehaviorProfileDouble.as_strategy
MixedBehaviorProfileDouble.randomize
MixedBehaviorProfileRational.game
MixedBehaviorProfileRational.liap_value
MixedBehaviorProfileRational.as_strategy
MixedBehaviorProfileRational.randomize
MixedBehaviorProfile.liap_value
MixedBehaviorProfile.as_strategy
MixedBehaviorProfile.randomize
MixedBehaviorProfile.normalize
MixedBehaviorProfile.copy


Computation on supports
Expand Down
104 changes: 55 additions & 49 deletions src/pygambit/behav.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ class MixedBehaviorProfile:
+ r"\right]$"
)

@property
def game(self) -> Game:
"""The game on which this mixed behaviour profile is defined."""
return self._game

def __getitem__(self, index: typing.Union[Player, Infoset, Action, str]):
"""Returns a probability, mixed agent strategy, or mixed behavior strategy.
Expand Down Expand Up @@ -413,6 +418,42 @@ class MixedBehaviorProfile:
"""
return self._regret(self.game._resolve_action(action, 'regret'))

def liap_value(self):
"""Returns the Lyapunov value (see [McK91]_) of the strategy profile.
The Lyapunov value is a non-negative number which is zero exactly at
Nash equilibria.
"""
return self._liap_value()

def as_strategy(self) -> MixedStrategyProfile:
"""Returns a `MixedStrategyProfile` which is equivalent
to the profile.
"""
return self._as_strategy()

def randomize(self, denom: typing.Optional[int] = None) -> None:
"""Randomizes the probabilities in the profile. These are
generated as uniform distributions over the actions at each
information set. If
``denom`` is specified, all probabilities are divisible by
``denom``, that is, the distribution is uniform over a discrete
grid of mixed strategies.
"""
if denom is not None and denom <= 0:
raise ValueError("randomize(): denominator must be a positive integer")
self._randomize(denom)

def normalize(self) -> MixedBehaviorProfile:
"""Create a profile with the same action proportions as this
one, but normalised so probabilities for each infoset sum to one.
"""
return self._normalize()

def copy(self) -> MixedBehaviorProfile:
"""Creates a copy of the behavior strategy profile."""
return self._copy()


@cython.cclass
class MixedBehaviorProfileDouble(MixedBehaviorProfile):
Expand Down Expand Up @@ -457,52 +498,32 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile):
deref(self.profile) == deref(cython.cast(MixedBehaviorProfileDouble, other).profile)
)

def copy(self) -> MixedBehaviorProfileDouble:
"""Creates a copy of the behavior strategy profile."""
def _copy(self) -> MixedBehaviorProfileDouble:
behav = MixedBehaviorProfileDouble()
behav.profile = make_shared[c_MixedBehaviorProfileDouble](deref(self.profile))
return behav

def as_strategy(self) -> MixedStrategyProfileDouble:
"""Returns a `MixedStrategyProfile` which is equivalent
to the profile.
"""
def _as_strategy(self) -> MixedStrategyProfileDouble:
mixed = MixedStrategyProfileDouble()
mixed.profile = make_shared[c_MixedStrategyProfileDouble](deref(self.profile).ToMixedProfile())
return mixed

def liap_value(self) -> float:
"""Returns the Lyapunov value (see [McK91]_) of the strategy profile. The
Lyapunov value is a non-negative number which is zero exactly at
Nash equilibria.
"""
def _liap_value(self) -> float:
return deref(self.profile).GetLiapValue()

def normalize(self) -> MixedBehaviorProfileDouble:
"""Create a profile with the same action proportions as this
one, but normalised so probabilities for each infoset sum to one.
"""
def _normalize(self) -> MixedBehaviorProfileDouble:
profile = MixedBehaviorProfileDouble()
profile.profile = make_shared[c_MixedBehaviorProfileDouble](deref(self.profile).Normalize())
return profile

def randomize(self, denom=None) -> None:
"""Randomizes the probabilities in the profile. These are
generated as uniform distributions over the actions at each
information set. If
``denom`` is specified, all probabilities are divisible by
``denom``, that is, the distribution is uniform over a discrete
grid of mixed strategies.
"""
def _randomize(self, denom: typing.Optional[int] = None) -> None:
if denom is None:
deref(self.profile).Randomize()
else:
deref(self.profile).Randomize(denom)

@property
def game(self) -> Game:
"""The game on which this mixed behaviour profile is defined.
"""
def _game(self) -> Game:
g = Game()
g.game = deref(self.profile).GetGame()
return g
Expand Down Expand Up @@ -555,46 +576,31 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile):
deref(self.profile) == deref(cython.cast(MixedBehaviorProfileRational, other).profile)
)

def copy(self) -> MixedBehaviorProfileRational:
"""Creates a copy of the behavior strategy profile."""
def _copy(self) -> MixedBehaviorProfileRational:
behav = MixedBehaviorProfileRational()
behav.profile = make_shared[c_MixedBehaviorProfileRational](deref(self.profile))
return behav

def as_strategy(self) -> MixedStrategyProfileRational:
"""Returns a `MixedStrategyProfile` which is equivalent
to the profile.
"""
def _as_strategy(self) -> MixedStrategyProfileRational:
mixed = MixedStrategyProfileRational()
mixed.profile = make_shared[c_MixedStrategyProfileRational](deref(self.profile).ToMixedProfile())
return mixed

def liap_value(self) -> Rational:
"""Returns the Lyapunov value (see [McK91]_) of the strategy profile. The
Lyapunov value is a non-negative number which is zero exactly at
Nash equilibria.
"""
def _liap_value(self) -> Rational:
return rat_to_py(deref(self.profile).GetLiapValue())

def normalize(self) -> MixedBehaviorProfileRational:
"""Create a profile with the same action proportions as this
one, but normalised so probabilites for each infoset sum to one.
"""
def _normalize(self) -> MixedBehaviorProfileRational:
profile = MixedBehaviorProfileRational()
profile.profile = make_shared[c_MixedBehaviorProfileRational](deref(self.profile).Normalize())
return profile

def randomize(self, denom) -> None:
"""Randomizes the probabilities in the profile. These are
generated as uniform distributions over the actions at each
information set.
"""
def _randomize(self, denom: typing.Optional[int] = None) -> None:
if denom is None:
raise ValueError("randomize() on rational-precision profiles requires a denominator")
deref(self.profile).Randomize(denom)

@property
def game(self) -> Game:
"""The game on which this mixed behaviour profile is defined.
"""
def _game(self) -> Game:
g = Game()
g.game = deref(self.profile).GetGame()
return g
Loading

0 comments on commit 8c6b8c7

Please sign in to comment.