def test_random_canongame(strats):
    """Test random canon games"""
    strats = np.array(strats)
    if np.all(strats == 1):
        return  # not a game
    game = gamegen.normal_aggfn(2, strats, strats.sum())
    cgame = canongame.canon(game)
    paygame.game_copy(cgame)
Beispiel #2
0
def test_random_canongame(strats):
    """Test random canon games"""
    strats = np.array(strats)
    if np.all(strats == 1):
        return  # not a game
    game = gamegen.normal_aggfn(2, strats, strats.sum())
    cgame = canongame.canon(game)
    paygame.game_copy(cgame)
Beispiel #3
0
def test_conv_game_gambit_inv(game, game_str):
    """Test game to gambit and back"""
    with stdin(game_str), stdout() as out, stderr() as err:
        assert run('conv', 'gambit'), err.getvalue()
    with stdin(out.getvalue()), stdout() as out, stderr() as err:
        assert run('conv', 'game'), err.getvalue()
    copy = gamereader.loads(out.getvalue())
    assert copy == paygame.game_copy(matgame.matgame_copy(game))
Beispiel #4
0
def test_random_min_max(strats):
    """Test min and max"""
    payoffs = rand.random(tuple(strats) + (len(strats), ))
    matg = matgame.matgame(payoffs)
    game = paygame.game_copy(matg)

    assert np.allclose(matg.min_strat_payoffs(), game.min_strat_payoffs())
    assert np.allclose(matg.max_strat_payoffs(), game.max_strat_payoffs())
Beispiel #5
0
def test_profiles_payoffs():
    """Test payoffs"""
    matg = matgame.matgame([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
    copy = paygame.game_copy(matg)
    profs = [[1, 0, 1, 0], [1, 0, 0, 1], [0, 1, 1, 0], [0, 1, 0, 1]]
    pays = [[1, 0, 2, 0], [3, 0, 0, 4], [0, 5, 6, 0], [0, 7, 0, 8]]
    game = paygame.game([1, 1], 2, profs, pays)
    assert copy == game
def test_random_min_max(strats):
    """Test min and max"""
    payoffs = rand.random(tuple(strats) + (len(strats),))
    matg = matgame.matgame(payoffs)
    game = paygame.game_copy(matg)

    assert np.allclose(matg.min_strat_payoffs(), game.min_strat_payoffs())
    assert np.allclose(matg.max_strat_payoffs(), game.max_strat_payoffs())
Beispiel #7
0
def verify_aggfn(game):
    """Verify that aggfn matches the expanded version"""
    payoff_game = paygame.game_copy(game)
    assert not game.is_empty()
    assert game.is_complete()

    ngame = game.normalize()
    assert np.all(
        np.isclose(ngame.max_role_payoffs(), 1) |
        np.isclose(ngame.max_role_payoffs(), 0))
    assert np.allclose(ngame.min_role_payoffs(), 0)

    # Check accuracy of min and max payoffs
    assert np.all(
        (payoff_game.payoffs() >= game.min_strat_payoffs() - 1e-6) |
        (payoff_game.profiles() == 0))
    assert np.all(
        (payoff_game.payoffs() <= game.max_strat_payoffs() + 1e-6) |
        (payoff_game.profiles() == 0))

    # Test that get payoffs works for multiple dimensions
    profiles = game.random_profiles(20).reshape((4, 5, -1))
    payoffs = game.get_payoffs(profiles)
    true_payoffs = payoff_game.get_payoffs(profiles)
    assert np.allclose(payoffs, true_payoffs)

    # Check that we get the same deviations if we construct the full game
    # game deviation payoff jacobian is inaccurate for sparse mixtures, so we
    # can't use it as ground truth
    for mix in game.random_mixtures(20):
        idev, ijac = game.deviation_payoffs(mix, jacobian=True)
        tdev, tjac = payoff_game.deviation_payoffs(mix, jacobian=True)
        assert np.allclose(idev, tdev)
        tjac -= np.repeat(np.add.reduceat(tjac, game.role_starts, 1) /
                          game.num_role_strats, game.num_role_strats, 1)
        ijac -= np.repeat(np.add.reduceat(ijac, game.role_starts, 1) /
                          game.num_role_strats, game.num_role_strats, 1)
        assert np.allclose(ijac, tjac)

    # Check that sparse mixtures produce correct deviations
    # TODO For some reason, the jacobians don't match with the jacobians for
    # the standard payoff game when the mixture is sparse. My hunch is that the
    # aggfn version has payoff effects clse to zero that aren't captured by
    # simply recording the payoffs. However, this doesn't make a whole lot of
    # sense.
    for mix in game.random_sparse_mixtures(20):
        dev = game.deviation_payoffs(mix)
        tdev = payoff_game.deviation_payoffs(mix)
        assert np.allclose(dev, tdev)

    # Check that it serializes properly
    jgame = json.dumps(game.to_json())
    copy = aggfn.aggfn_json(json.loads(jgame))
    assert game == copy
    # As does it's normalized version
    jgame = json.dumps(ngame.to_json())
    copy = aggfn.aggfn_json(json.loads(jgame))
    assert ngame == copy
Beispiel #8
0
def test_rbfgame_min_max_payoffs(players, strats):
    """Test min and max payoffs of rbf game"""
    game = gamegen.sparse_game(players, strats, 11)
    reggame = learning.rbfgame_train(game)
    full = paygame.game_copy(reggame)

    assert np.all(
        full.min_strat_payoffs() >= reggame.min_strat_payoffs() - 1e-4)
    assert np.all(
        full.max_strat_payoffs() <= reggame.max_strat_payoffs() + 1e-4)
def test_random_game_addition(strats):
    """Test random addition"""
    mpayoffs = rand.random(tuple(strats) + (len(strats),))
    matg = matgame.matgame(mpayoffs)
    payoffs = rand.random(matg.payoffs().shape)
    payoffs[matg.profiles() == 0] = 0
    game = paygame.game_replace(matg, matg.profiles(), payoffs)
    assert paygame.game_copy(matg + game) == game + matg

    empty = rsgame.empty_copy(matg)
    assert matg + empty == empty
Beispiel #10
0
def test_random_game_addition(strats):
    """Test random addition"""
    mpayoffs = rand.random(tuple(strats) + (len(strats), ))
    matg = matgame.matgame(mpayoffs)
    payoffs = rand.random(matg.payoffs().shape)
    payoffs[matg.profiles() == 0] = 0
    game = paygame.game_replace(matg, matg.profiles(), payoffs)
    assert paygame.game_copy(matg + game) == game + matg

    empty = rsgame.empty_copy(matg)
    assert matg + empty == empty
Beispiel #11
0
def test_random_matgame_hash_eq(strats):
    """Test hash and eq"""
    payoffs = rand.random(tuple(strats) + (len(strats), ))
    matg = matgame.matgame(payoffs)

    copy = matgame.matgame_copy(matg)
    assert hash(copy) == hash(matg)
    assert copy == matg

    game = paygame.game_copy(matg)
    copy = matgame.matgame_copy(game)
    assert hash(copy) == hash(matg)
    assert copy == matg
Beispiel #12
0
def test_random_matgame_hash_eq(strats):
    """Test hash and eq"""
    payoffs = rand.random(tuple(strats) + (len(strats),))
    matg = matgame.matgame(payoffs)

    copy = matgame.matgame_copy(matg)
    assert hash(copy) == hash(matg)
    assert copy == matg

    game = paygame.game_copy(matg)
    copy = matgame.matgame_copy(game)
    assert hash(copy) == hash(matg)
    assert copy == matg
Beispiel #13
0
def test_profiles_payoffs():
    """Test payoffs"""
    matg = matgame.matgame([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
    copy = paygame.game_copy(matg)
    profs = [[1, 0, 1, 0],
             [1, 0, 0, 1],
             [0, 1, 1, 0],
             [0, 1, 0, 1]]
    pays = [[1, 0, 2, 0],
            [3, 0, 0, 4],
            [0, 5, 6, 0],
            [0, 7, 0, 8]]
    game = paygame.game([1, 1], 2, profs, pays)
    assert copy == game
Beispiel #14
0
def reduce_game(full_game, red_players=None):
    """Return original game

    Parameters
    ----------
    full_game : Game
        The game to reduce.
    red_players : ndarray-like, optional
        If specified, this must match the number of players per role in
        full_game.
    """
    utils.check(
        red_players is None
        or np.all(full_game.num_role_players == red_players),
        'identity reduction must have same number of players')
    return paygame.game_copy(full_game)
Beispiel #15
0
def reduce_game(full_game, red_players=None):
    """Return original game

    Parameters
    ----------
    full_game : Game
        The game to reduce.
    red_players : ndarray-like, optional
        If specified, this must match the number of players per role in
        full_game.
    """
    utils.check(
        red_players is None or np.all(
            full_game.num_role_players == red_players),
        'identity reduction must have same number of players')
    return paygame.game_copy(full_game)
Beispiel #16
0
    def __init__(self, game, conf, zipf, *, max_procs=4, simultaneous_obs=1):
        super().__init__(game.role_names, game.strat_names,
                         game.num_role_players)
        self._game = paygame.game_copy(rsgame.empty_copy(game))
        self.conf = conf
        self.zipf = zipf

        self._extra_profs = {}
        self._base = {}
        self._count = simultaneous_obs
        self._is_open = False
        self._sim_dir = None
        self._prof_dir = None
        self._sim_root = None

        self._num = 0
        self._procs = asyncio.Semaphore(max_procs)
Beispiel #17
0
    def __init__(self, game, config, command, buff_size=65536):
        super().__init__(game.role_names, game.strat_names,
                         game.num_role_players)
        self._game = paygame.game_copy(rsgame.empty_copy(game))
        self._base = {"configuration": config}
        self.command = command
        self.buff_size = buff_size

        self._is_open = False
        self._proc = None
        self._reader = None
        self._read_queue = asyncio.Queue()
        self._write_lock = asyncio.Lock()
        self._buffer_empty = asyncio.Event()
        self._buffer_bytes = 0
        self._line_bytes = collections.deque()

        self._buffer_empty.set()
Beispiel #18
0
def test_random_deviations(strats):
    """Test random devs"""
    payoffs = rand.random(tuple(strats) + (len(strats), ))
    matg = matgame.matgame(payoffs)
    game = paygame.game_copy(matg)

    mix = matg.random_mixture()
    matdev = matg.deviation_payoffs(mix)
    gamedev = game.deviation_payoffs(mix)
    assert np.allclose(matdev, gamedev)

    matdev, matjac = matg.deviation_payoffs(mix, jacobian=True)
    gamedev, gamejac = game.deviation_payoffs(mix, jacobian=True)
    assert np.allclose(matdev, gamedev)
    assert np.allclose(matjac, gamejac)

    for mix in matg.random_mixtures(20):
        matdev, matjac = matg.deviation_payoffs(mix, jacobian=True)
        gamedev, gamejac = game.deviation_payoffs(mix, jacobian=True)
        assert np.allclose(matdev, gamedev)
        assert np.allclose(matjac, gamejac)
Beispiel #19
0
def test_random_deviations(strats):
    """Test random devs"""
    payoffs = rand.random(tuple(strats) + (len(strats),))
    matg = matgame.matgame(payoffs)
    game = paygame.game_copy(matg)

    mix = matg.random_mixture()
    matdev = matg.deviation_payoffs(mix)
    gamedev = game.deviation_payoffs(mix)
    assert np.allclose(matdev, gamedev)

    matdev, matjac = matg.deviation_payoffs(mix, jacobian=True)
    gamedev, gamejac = game.deviation_payoffs(mix, jacobian=True)
    assert np.allclose(matdev, gamedev)
    assert np.allclose(matjac, gamejac)

    for mix in matg.random_mixtures(20):
        matdev, matjac = matg.deviation_payoffs(mix, jacobian=True)
        gamedev, gamejac = game.deviation_payoffs(mix, jacobian=True)
        assert np.allclose(matdev, gamedev)
        assert np.allclose(matjac, gamejac)
Beispiel #20
0
def test_neighbor():  # pylint: disable=too-many-locals
    """Test neighbor games"""
    game = gamegen.sparse_game([2, 3], [3, 2], 10)
    model = gp.GaussianProcessRegressor(1.0 * gp.kernels.RBF(2, [1, 3]) +
                                        gp.kernels.WhiteKernel(1),
                                        normalize_y=True)
    learn = learning.neighbor(learning.sklgame_train(game, model))
    full = paygame.game_copy(learn)

    errors = np.zeros(game.num_strats)
    for i, mix in enumerate(
            itertools.chain(game.random_mixtures(20),
                            game.random_sparse_mixtures(20)), 1):
        tdev = full.deviation_payoffs(mix)
        dev, _ = learn.deviation_payoffs(mix, jacobian=True)
        err = (tdev - dev)**2 / (np.abs(dev) + 1e-5)
        errors += (err - errors) / i
    assert np.all(errors < 5)

    submask = game.random_restriction()
    sublearn = learn.restrict(submask)
    subfull = full.restrict(submask)
    assert np.allclose(sublearn.get_payoffs(subfull.profiles()),
                       subfull.payoffs())

    norm = learn.normalize()
    assert np.allclose(norm.min_role_payoffs(), 0)
    assert np.allclose(norm.max_role_payoffs(), 1)

    assert learning.neighbor(learn, learn.num_neighbors) == learn

    learn = learning.neighbor(learning.rbfgame_train(game))
    jgame = json.dumps(learn.to_json())
    copy = learning.neighbor_json(json.loads(jgame))
    assert hash(copy) == hash(learn)
    assert copy == learn
    assert learn + copy == copy + learn

    empty = rsgame.empty_copy(learn)
    assert learn + empty == empty
Beispiel #21
0
def test_continuous_approximation(base, num):  # pylint: disable=too-many-locals
    """Test continuous approximation"""
    # pylint: disable-msg=protected-access
    game = gamegen.gen_num_profiles(base, num)
    learn = learning.rbfgame_train(game)
    full = paygame.game_copy(learn)
    red = np.eye(game.num_roles).repeat(game.num_role_strats, 0)
    size = np.eye(game.num_strats).repeat(learn._sizes, 0)

    def devpays(mix):
        """Compute dev pays"""
        players = learn._dev_players.repeat(game.num_role_strats, 1)
        avg_prof = players * mix
        diag = 1 / (learn._lengths**2 + avg_prof)
        diag_sizes = anp.dot(size, diag)
        diff = learn._profiles - anp.dot(size, avg_prof)
        det = 1 / (1 - learn._dev_players * anp.dot(mix**2 * diag, red))
        det_sizes = anp.dot(size, det)
        cov_diag = anp.einsum('ij,ij,ij->i', diff, diff, diag_sizes)
        cov_outer = anp.dot(mix * diag_sizes * diff, red)
        sec_term = anp.einsum('ij,ij,ij,ij->i',
                              learn._dev_players.repeat(learn._sizes, 0),
                              det_sizes, cov_outer, cov_outer)
        exp = anp.exp(-(cov_diag + sec_term) / 2)
        coef = anp.prod(learn._lengths, 1) * anp.sqrt(
            anp.prod(diag, 1) * anp.prod(det, 1))
        avg = anp.dot(learn._alpha * exp, size)
        return learn._coefs * coef * avg + learn._offset

    devpays_jac = autograd.jacobian(devpays)  # pylint: disable=no-value-for-parameter

    for mix in itertools.chain(game.random_mixtures(20),
                               game.random_sparse_mixtures(20)):
        dev = full.deviation_payoffs(mix)
        adev, ajac = learn.deviation_payoffs(mix, jacobian=True)
        assert np.allclose(adev, dev, rtol=0.1, atol=0.2)
        tdev = devpays(mix)
        tjac = devpays_jac(mix)
        assert np.allclose(adev, tdev)
        assert np.allclose(ajac, tjac)
Beispiel #22
0
def test_payoff_vals():
    """Test payoff values"""
    profiles = [[2, 0, 1, 0, 0],
                [2, 0, 0, 1, 0],
                [2, 0, 0, 0, 1],
                [1, 1, 1, 0, 0],
                [1, 1, 0, 1, 0],
                [1, 1, 0, 0, 1],
                [0, 2, 1, 0, 0],
                [0, 2, 0, 1, 0],
                [0, 2, 0, 0, 1]]
    payoffs = [[4, 0, 8, 0, 0],
               [-1, 0, 0, 10, 0],
               [1, 0, 0, 0, 5],
               [1, 6, 6, 0, 0],
               [0, 3, 0, 5, 0],
               [-2, 3, 0, 0, 9],
               [0, 6, 11, 0, 0],
               [0, 3, 0, 6, 0],
               [0, 3, 0, 0, 10]]
    copy = paygame.game_replace(_GAME, profiles, payoffs)
    assert paygame.game_copy(_GAME) == copy
Beispiel #23
0
def test_random_identity(keep_prob, game_desc, _):
    """Test random identity"""
    players, strategies = game_desc
    # Create game and reduction
    game = gamegen.game(players, strategies, keep_prob)
    assert (paygame.game_copy(rsgame.empty(players, strategies)) ==
            ir.reduce_game(rsgame.empty(players, strategies)))

    # Try to reduce game
    red_game = ir.reduce_game(game)

    # Assert that reducing all profiles covers reduced game
    reduced_full_profiles = utils.axis_to_elem(
        ir.reduce_profiles(red_game, game.profiles()))
    reduced_profiles = utils.axis_to_elem(red_game.profiles())
    assert np.setxor1d(reduced_full_profiles, reduced_profiles).size == 0, \
        "reduced game didn't match full game"

    full_profiles = utils.axis_to_elem(game.profiles())
    full_reduced_profiles = utils.axis_to_elem(
        ir.expand_profiles(game, red_game.profiles()))
    assert np.setxor1d(full_profiles, full_reduced_profiles).size == 0, \
        'full game did not match reduced game'
Beispiel #24
0
def test_sample():  # pylint: disable=too-many-locals
    """Test sample game"""
    # pylint: disable-msg=protected-access
    game = gamegen.sparse_game([2, 3], [3, 2], 10)
    model = learning.sklgame_train(
        game,
        gp.GaussianProcessRegressor(1.0 * gp.kernels.RBF(2, [1, 3]) +
                                    gp.kernels.WhiteKernel(1),
                                    normalize_y=True))
    learn = learning.sample(model)
    full = paygame.game_copy(learn)

    @autograd.primitive
    def sample_profs(mix):
        """Sample profiles"""
        return game.random_role_deviation_profiles(learn.num_samples,
                                                   mix).astype(float)

    @autograd.primitive
    def model_pays(profs):
        """Get pays from model"""
        return model.get_dev_payoffs(profs)

    @autograd.primitive
    def const_weights(profs, mix):
        """Get the weights"""
        return np.prod(mix**profs, 2).repeat(game.num_role_strats, 1)

    @autograd.primitive
    def rep(probs):
        """Repeat an array"""
        return probs.repeat(game.num_role_strats, 1)

    def rep_vjp(_repd, _probs):
        """The jacobian of repeat"""
        return lambda grad: np.add.reduceat(grad, game.role_starts, 1)

    autograd.extend.defvjp(sample_profs, None)
    autograd.extend.defvjp(model_pays, None)
    autograd.extend.defvjp(const_weights, None, None)
    autograd.extend.defvjp(rep, rep_vjp)  # This is wrong in autograd

    def devpays(mix):
        """Compute the dev pays"""
        profs = sample_profs(mix)
        payoffs = model_pays(profs)
        numer = rep(anp.prod(mix**profs, 2))
        denom = const_weights(profs, mix)
        weights = numer / denom / learn.num_samples
        return anp.einsum('ij,ij->j', weights, payoffs)

    devpays_jac = autograd.jacobian(devpays)  # pylint: disable=no-value-for-parameter

    errors = np.zeros(game.num_strats)
    samp_errors = np.zeros(game.num_strats)
    for i, mix in enumerate(
            itertools.chain(game.random_mixtures(20),
                            game.random_sparse_mixtures(20)), 1):
        seed = random.randint(0, 10**9)
        fdev = full.deviation_payoffs(mix)
        np.random.seed(seed)
        dev, jac = learn.deviation_payoffs(mix, jacobian=True)
        avg_err = (fdev - dev)**2 / (np.abs(fdev) + 1e-5)
        errors += (avg_err - errors) / i
        samp_err = ((learn.deviation_payoffs(mix) - dev)**2 /
                    (np.abs(dev) + 1e-5))
        samp_errors += (samp_err - samp_errors) / i

        np.random.seed(seed)
        tdev = devpays(mix)
        assert np.allclose(dev, tdev)
        np.random.seed(seed)
        tjac = devpays_jac(mix)
        assert np.allclose(jac, tjac)
    assert np.all(errors <= 200 * (samp_errors + 1e-5))

    submask = game.random_restriction()
    sublearn = learn.restrict(submask)
    subfull = full.restrict(submask)
    assert np.allclose(sublearn.get_payoffs(subfull.profiles()),
                       subfull.payoffs())

    norm = learn.normalize()
    assert np.allclose(norm.min_role_payoffs(), 0)
    assert np.allclose(norm.max_role_payoffs(), 1)

    assert learning.sample(learn, learn.num_samples) == learn

    learn = learning.sample(learning.rbfgame_train(game))
    jgame = json.dumps(learn.to_json())
    copy = learning.sample_json(json.loads(jgame))
    assert hash(copy) == hash(learn)
    assert copy == learn
    assert learn + copy == copy + learn

    empty = rsgame.empty_copy(learn)
    assert learn + empty == empty
Beispiel #25
0
from gameanalysis import paygame
from gameanalysis import rsgame


_TYPES = {
    'emptygame': (
        ['empty'], 'Strip payoff data', """Strip all payoff data from a game
        and return only its base structure---role and strategy names and player
        counts.""",
        lambda game, out: json.dump(
            rsgame.empty_copy(game).to_json(), out)),
    'game': (
        [], 'Sparse payoff format', """Convert a game to a sparse mapping of
        profiles to their corresponding payoff data.""",
        lambda game, out: json.dump(
            paygame.game_copy(game).to_json(), out)),
    'samplegame': (
        ['samp'], 'Multiple payoffs per profile', """Convert a game to a format
        with a sparse mapping of profiles to potentially several samples of
        payoff data. There should be little reason to convert a non-samplegame
        to a samplegame as all profiles will have exactly one sample.""",
        lambda game, out: json.dump(
            paygame.samplegame_copy(game).to_json(), out)),
    'matgame': (
        ['mat'], 'Asymmetric format', """Convert a game to a compact
        representation for asymmetric games. If the input game is not
        asymmetric, role names will be duplicated and modified to allow for the
        conversion. This will only work if the input game is complete.""",
        lambda game, out: json.dump(
            matgame.matgame_copy(game).to_json(), out)),
    'norm': (
Beispiel #26
0
from gameanalysis import gambit
from gameanalysis import gamereader
from gameanalysis import matgame
from gameanalysis import paygame
from gameanalysis import rsgame

_TYPES = {
    'emptygame':
    (['empty'], 'Strip payoff data', """Strip all payoff data from a game
        and return only its base structure---role and strategy names and player
        counts.""",
     lambda game, out: json.dump(rsgame.empty_copy(game).to_json(), out)),
    'game':
    ([], 'Sparse payoff format', """Convert a game to a sparse mapping of
        profiles to their corresponding payoff data.""",
     lambda game, out: json.dump(paygame.game_copy(game).to_json(), out)),
    'samplegame':
    (['samp'], 'Multiple payoffs per profile', """Convert a game to a format
        with a sparse mapping of profiles to potentially several samples of
        payoff data. There should be little reason to convert a non-samplegame
        to a samplegame as all profiles will have exactly one sample.""",
     lambda game, out: json.dump(paygame.samplegame_copy(game).to_json(), out)
     ),
    'matgame':
    (['mat'], 'Asymmetric format', """Convert a game to a compact
        representation for asymmetric games. If the input game is not
        asymmetric, role names will be duplicated and modified to allow for the
        conversion. This will only work if the input game is complete.""",
     lambda game, out: json.dump(matgame.matgame_copy(game).to_json(), out)),
    'norm':
    ([], 'Normalize payoffs to [0, 1]', """Modify the input game by scaling
Beispiel #27
0
def test_point():  # pylint: disable=too-many-locals
    """Test point

    We increase player number so point is a more accurate estimator.
    """
    # pylint: disable-msg=protected-access
    game = gamegen.sparse_game(1000, 2, 10)
    model = learning.rbfgame_train(game)
    learn = learning.point(model)
    full = paygame.game_copy(learn)
    red = np.eye(game.num_roles).repeat(game.num_role_strats, 0)
    size = np.eye(game.num_strats).repeat(model._sizes, 0)

    def devpays(mix):
        """The deviation payoffs"""
        profile = learn._dev_players * mix
        dev_profiles = anp.dot(size, anp.dot(red, profile))
        vec = ((dev_profiles - model._profiles) /
               model._lengths.repeat(model._sizes, 0))
        rbf = anp.einsum('...ij,...ij->...i', vec, vec)
        exp = anp.exp(-rbf / 2) * model._alpha
        return model._offset + model._coefs * anp.dot(exp, size)

    devpays_jac = autograd.jacobian(devpays)  # pylint: disable=no-value-for-parameter

    errors = np.zeros(game.num_strats)
    for i, mix in enumerate(
            itertools.chain(game.random_mixtures(20),
                            game.random_sparse_mixtures(20)), 1):
        fdev = full.deviation_payoffs(mix)
        dev, jac = learn.deviation_payoffs(mix, jacobian=True)
        err = (fdev - dev)**2 / (np.abs(dev) + 1e-5)
        errors += (err - errors) / i
        tdev = devpays(mix)
        tjac = devpays_jac(mix)
        assert np.allclose(learn.deviation_payoffs(mix), dev)
        assert np.allclose(dev, tdev)
        assert np.allclose(jac, tjac)

    # Point is a very biased estimator, so errors are large
    assert np.all(errors < 10)

    submask = game.random_restriction()
    sublearn = learn.restrict(submask)
    subfull = full.restrict(submask)
    assert np.allclose(sublearn.get_payoffs(subfull.profiles()),
                       subfull.payoffs())

    norm = learn.normalize()
    assert np.allclose(norm.min_role_payoffs(), 0)
    assert np.allclose(norm.max_role_payoffs(), 1)

    assert learning.point(learn) == learn

    jgame = json.dumps(learn.to_json())
    copy = learning.point_json(json.loads(jgame))
    assert hash(copy) == hash(learn)
    assert copy == learn
    assert learn + copy == copy + learn

    empty = rsgame.empty_copy(learn)
    assert learn + empty == empty