Source code for gameanalysis.regret
"""A module for computing regret and social welfare of profiles"""
import numpy as np
[docs]def pure_strategy_deviation_pays(game, profile):
"""Returns the pure strategy deviation payoffs
The result is a compact array of deviation payoffs. Each element
corresponds to the payoff of deviating to strategy i from strategy j for
all valid deviations."""
profile = np.asarray(profile, int)
pays = game.get_payoffs(profile)
devs = np.empty(game.num_devs)
for dev_ind, (from_ind, to_ind) in enumerate(
zip(game.dev_from_indices, game.dev_to_indices)
):
if profile[from_ind] == 0:
devs[dev_ind] = 0
elif from_ind == to_ind:
devs[dev_ind] = pays[from_ind]
else:
prof_copy = profile.copy()
prof_copy[from_ind] -= 1
prof_copy[to_ind] += 1
devs[dev_ind] = game.get_payoffs(prof_copy)[to_ind]
return devs
[docs]def pure_strategy_deviation_gains(game, profile):
"""Returns the pure strategy deviations gains"""
profile = np.asarray(profile, int)
pays = game.get_payoffs(profile)
devs = pure_strategy_deviation_pays(game, profile)
return devs - pays.repeat(game.num_strat_devs)
[docs]def pure_strategy_regret(game, prof):
"""Returns the regret of a pure strategy profile
If prof has more than one dimension, the last dimension is taken as a set
of profiles and returned as a new array."""
with np.errstate(invalid="ignore"): # keep nans
return pure_strategy_deviation_gains(game, prof).max()
[docs]def mixture_deviation_gains(game, mix):
"""Returns all the gains from deviation from a mixed strategy
The result is ordered by role, then strategy."""
mix = np.asarray(mix, float)
strategy_evs = game.deviation_payoffs(mix)
# strategy_evs is nan where there's no data, however, if it's not played in
# the mix, it doesn't effect the role_evs
masked = strategy_evs.copy()
masked[mix == 0] = 0
role_evs = np.add.reduceat(masked * mix, game.role_starts).repeat(
game.num_role_strats
)
return strategy_evs - role_evs
[docs]def mixture_regret(game, mix):
"""Return the regret of a mixture profile"""
mix = np.asarray(mix, float)
return mixture_deviation_gains(game, mix).max()
[docs]def pure_social_welfare(game, profile):
"""Returns the social welfare of a pure strategy profile in game"""
profile = np.asarray(profile, int)
return game.get_payoffs(profile).dot(profile)
[docs]def mixed_social_welfare(game, mix):
"""Returns the social welfare of a mixed strategy profile"""
return game.expected_payoffs(mix).dot(game.num_role_players)
[docs]def max_pure_social_welfare(game, *, by_role=False):
"""Returns the maximum social welfare over the known profiles.
If by_role is specified, then max social welfare applies to each role
independently. If there are no profiles with full payoff data for a role,
an arbitrary profile will be returned."""
if by_role: # pylint: disable=no-else-return
if game.num_profiles: # pylint: disable=no-else-return
welfares = np.add.reduceat(
game.profiles() * game.payoffs(), game.role_starts, 1
)
prof_inds = np.nanargmax(welfares, 0)
return (
welfares[prof_inds, np.arange(game.num_roles)],
game.profiles()[prof_inds],
)
else:
welfares = np.full(game.num_roles, np.nan)
profiles = np.full(game.num_roles, None)
return welfares, profiles
else:
if game.num_complete_profiles: # pylint: disable=no-else-return
welfares = np.einsum("ij,ij->i", game.profiles(), game.payoffs())
prof_ind = np.nanargmax(welfares)
return welfares[prof_ind], game.profiles()[prof_ind]
else:
return np.nan, None