Source code for bnelearn.tests.test_matrix_games

import warnings
import pytest
import torch
from bnelearn.mechanism import PrisonersDilemma, RockPaperScissors, JordanGame

#Setup shared objects

gpu_device = 'cuda' if torch.cuda.is_available() else 'cpu'

# setup games
pd = PrisonersDilemma(cuda = True)
pd_cpu = PrisonersDilemma(cuda = False)

jordan = JordanGame()

# setup actions
actions_pd_cpu = torch.tensor([
        [[0], [0]],
        [[0], [1]],
        [[1], [0]],
        [[1], [1]]
    ])

actions_pd_gpu = actions_pd_cpu.to(gpu_device)

pd_allocation, pd_payments = pd.play(actions_pd_cpu)

[docs]def test_output_on_gpu(): """If the game has cuda=True, outputs should be on gpu regardless of input.""" if gpu_device == 'cpu': pytest.skip(msg='Cuda not available. skipping.') assert pd_allocation.device.type == 'cuda', "Result should be on GPU for cpu inputs!" assert pd_payments.device.type == 'cuda', "Result should be on GPU for cpu inputs!" # run again with gpu inputs allocation, payments = pd.play(actions_pd_gpu) assert allocation.device.type == 'cuda', "Result should be on GPU for gpu inputs!" assert payments.device.type == 'cuda', "Result should be on GPU for gpu inputs!"
[docs]def test_output_on_cpu(): """Test game that has cuda=False """ allocation, payments = pd_cpu.play(actions_pd_cpu) assert allocation.device.type == 'cpu', "Result should be on CPU for cpu inputs!" assert payments.device.type == 'cpu', "Result should be on CPU for cpu inputs!" allocation, payments = pd_cpu.play(actions_pd_gpu) assert allocation.device.type == 'cpu', "Result should be on CPU for gpu inputs!" assert payments.device.type == 'cpu', "Result should be on CPU for gpu inputs!"
[docs]def test_output_shapes(): assert pd_allocation.shape == torch.Size([4, 2, 1]), \ "Invalid allocation shape! Should be batch x n_players x items" assert pd_payments.shape == torch.Size([4, 2]), \ "Invalid payments shape. Should be batch x n_players"
[docs]def test_output_correctness_2x2(): assert torch.equal(pd_allocation, torch.zeros_like(pd_allocation)), \ "All allocations in matrix game should be 0" assert torch.equal(pd_payments, -pd.outcomes.view(4,2)), \ "incorrect payments. Should be negative of outcome."
[docs]def test_output_correctness_3x2(): """3 player 2 action game: Jordan Anticoordination game.""" actions = torch.tensor( [ [[0], [0], [0]], # LLL [[0], [1], [0]] # LRL ], device = jordan.device) allocation, payments = jordan.play(actions) assert torch.equal(allocation, torch.zeros(2, 3, 1, device=jordan.device)), \ "Found invalid allocation." assert torch.equal(payments, -torch.tensor( [ [0.,0, 0], [1, 1, 0]], device = jordan.device )), "Returned invalid payments!"
[docs]def test_output_correctness_2x3(): """ 2 player 3 action game: Rock Paper Scissors""" rps = RockPaperScissors(cuda = True) actions = torch.tensor( [ [[0], [0]], # rock = rock [[0], [2]], # rock > scissors [[1], [1]], # paper = paper [[2], [2]], # scissors = scissors [[1], [2]], # paper < scissors ], device = rps.device ) allocation, payments = rps.play(actions) assert torch.equal(allocation, torch.zeros(5, 2, 1, device=rps.device)), \ "Found invalid allocation." assert torch.equal( payments, -torch.tensor( [ [0.,0], [1, -1], [0,0], [0,0], [-1,1] ], device = rps.device ) ), "Returned invalid payments!"
[docs]def test_invalid_actions_float(): actions_float = torch.tensor([ [[.5], [.5]], [[0.], [1.]], [[.33], [.67]], [[1], [1.]] ]) with pytest.raises(AssertionError): pd.play(actions_float) pytest.fail("Game class should fail on invalid input: Float instead of Int")
[docs]def test_invalid_actions_shape(): actions_invalid_shape = torch.tensor([[1], [5], [3]]) with pytest.raises(AssertionError): pd.play(actions_invalid_shape) pytest.fail("Game class should validate input: Invalid action profile shape!")
[docs]def test_invalid_actions_out_of_bounds(): actions = torch.tensor([ [[0], [0]], [[0], [1]], [[2], [0]], [[1], [2]] ]) with pytest.raises(AssertionError): pd.play(actions) pytest.fail("Game should validate input: Action Index out of bounds!") actions = torch.tensor([ [[0], [0]], [[-1], [1]], [[1], [0]], [[1], [2]] ]) with pytest.raises(AssertionError): pd.play(actions) pytest.fail("Game should validate input: Action Index out of bounds!")
[docs]def test_mixed_strategy_playing_2p(): """Test in Prisoner's Dilemma""" # playing pure strategy expressed as mixed should give same results mixed_sp = [torch.tensor([0., 1.]), torch.tensor([0.,1.])] pure_payments = pd_payments[3] _, mixed_payments = pd.play_mixed(mixed_sp) assert pure_payments.shape == mixed_payments.shape assert torch.allclose(pure_payments, mixed_payments), "Mixed results differ from pure implementation!" # test purely mixed strategy mixed_sp = [torch.tensor([0.67, 0.33]), torch.tensor([0.5,0.5])] _, mixed_payments = pd.play_mixed(mixed_sp) assert mixed_payments.shape == torch.Size([2]) assert torch.allclose(-mixed_payments, torch.tensor([-1.67, -1.16], device = pd.device))
[docs]def test_mixed_strategy_playing_3p(): # playing pure strategy expressed as mixed should give same results # set pure action profile batch x n_player x items (here: 1 x n_player x 1) pure_sp = torch.tensor([0,1,1]).view(1, -1, 1) mixed_sp = [torch.tensor([1., 0.]), torch.tensor([0.,1.]), torch.tensor([0.,1.])] _, pure_payments = jordan.play(pure_sp) pure_payments.squeeze_() _, mixed_payments = jordan.play_mixed(mixed_sp) assert mixed_payments.shape == pure_payments.shape, "Mixed strategy playing returned invalid shape!" assert torch.allclose(pure_payments, mixed_payments), "Mixed results differ from pure implementation!" # test purely mixed strategy mixed_sp = [torch.tensor([0.5, 0.5]), torch.tensor([0.5,0.5]), torch.tensor([.33, .67])] _, mixed_payments = jordan.play_mixed(mixed_sp) assert mixed_payments.shape == torch.Size([3]) assert torch.allclose(-mixed_payments, torch.tensor([0.5, 0.5, 0.5], device = pd.device))
[docs]def test_mixed_strategy_invalid_action_shape(): # invalid number of players with pytest.raises(AssertionError): pd.play_mixed([torch.tensor([1., 0.]), torch.tensor([0.,1.]), torch.tensor([0.,1.])]) pytest.fail("Game should validate input: Invalid number of players!") #invalid number of actions with pytest.raises(AssertionError): pd.play_mixed([torch.tensor([1., 0., 0.]), torch.tensor([0.,1.])]) pytest.fail("Game should validate input: Invalid number of actions!")
[docs]def test_mixed_strategy_invalid_action_probabilities(): # invalid probabilities with pytest.raises(AssertionError): pd.play_mixed([torch.tensor([0.5, 0.]), torch.tensor([1.3,1.])]) pytest.fail("Game should not allow probabilities that don't sum to one!") with pytest.raises(AssertionError): pd.play_mixed([torch.tensor([0., 0.]), torch.tensor([0.,1.])]) pytest.fail("Game should not allow probabilities that don't sum to one!") with pytest.raises(AssertionError): pd.play_mixed([torch.tensor([1.5, -0.5]), torch.tensor([0., 1.])]) pytest.fail("Game should not allow negative probabilities!")