diff --git a/src/ludics/fitness_functions.py b/src/ludics/fitness_functions.py index 13301b1..3c210fe 100644 --- a/src/ludics/fitness_functions.py +++ b/src/ludics/fitness_functions.py @@ -86,3 +86,35 @@ def general_four_state_fitness_function(state, **kwargs): return np.array( [sym.Function(f"f_{i + 1}")(state_symbol) for i, j in enumerate(state)] ) + +def pairwise_interaction_fitness_function(state,a,b,c,d): + """ + Returns the fitness of players in a symmetric pairwise interaction game. + The payoff matrix for the row player is given by: + + | | C | D | + |---|-----|-----| + | C | a_i | b_i | + | D | c_i | d_i | + + And a player's fitness is given by the mean payoff they receive when + interacting in a 2 player game with a random other member of the + population. + + Parameters: + ----------- + state: numpy.array, the ordered set of actions each player takes. 1 is a + cooperator, 0 is a defector, + + a,b,c,d: float numpy.array, the entries to go into the payoff matrix. Entry i is the + respective payoff for player i's payoff matrix. Float values indicate a symmetric game + + Returns: + --------- + numpy.array: an ordered array of each player's fitness + """ + N = len(state) + number_of_cooperators = np.sum(state) + cooperator_payoffs = ((number_of_cooperators-1) * a + (N - number_of_cooperators) * b)/(N-1) + defector_payoffs = ((number_of_cooperators) * c + (N - number_of_cooperators-1) * d)/(N-1) + return cooperator_payoffs * state + defector_payoffs * (1 - state) \ No newline at end of file diff --git a/tests/test_fitness_functions.py b/tests/test_fitness_functions.py index 08c048b..62915ac 100644 --- a/tests/test_fitness_functions.py +++ b/tests/test_fitness_functions.py @@ -243,4 +243,54 @@ def test_public_goods_game_fitness_function_for_homoogeneous_contribution_and_he expected_return = np.array([0,1,2]) - np.testing.assert_allclose(actual_return, expected_return) \ No newline at end of file + np.testing.assert_allclose(actual_return, expected_return) + +def test_pairwise_interaction_fitness_function_for_symmetric_prisoners_dilemma(): + """ + Tests that pairwise_interaction_fitness_function returns the correct + value for a symmetric, standard prisoner's dilemma""" + + state = np.array([0,1,1,0,1]) + a = np.array([3 for _ in state]) + b = np.array([0 for _ in state]) + c = np.array([5 for _ in state]) + d = np.array([1 for _ in state]) + + actual_value = ludics.fitness_functions.pairwise_interaction_fitness_function(state=state,a=a,b=b,c=c,d=d) + expected_value = np.array([4, 6/4, 6/4, 4, 6/4]) + + np.testing.assert_allclose(actual_value, expected_value) + +def test_pairwise_interaction_fitness_function_for_asymmetric_game(): + """ + Tests that pairwise_interaction_fitness_function returns the correct value for + an asymmetric game""" + + state = np.array([1,0,1,0]) + a = np.array([1,2,3,4]) + b = np.array([2,4,6,8]) + c = np.array([5,4,3,2]) + d = np.array([2,2,2,1]) + + actual_value = ludics.fitness_functions.pairwise_interaction_fitness_function(state=state,a=a,b=b,c=c,d=d) + + expected_value = np.array([5/3, 10/3, 5, 5/3]) + + np.testing.assert_allclose(actual_value, expected_value) + +def test_pairwise_interaction_fitness_function_for_mixed_parameters(): + """ + Tests that pairwise_interaction_fitness_function returns the correct value for + an asymmetric game where two parameters are symmetric""" + + state = np.array([1,0,0,1]) + a = np.array([1,2,3,4]) + b = np.array([2,4,6,4]) + c = 5 + d = 2 + + actual_value = ludics.fitness_functions.pairwise_interaction_fitness_function(state=state,a=a,b=b,c=c,d=d) + + expected_value = np.array([5/3, 4, 4, 4]) + + np.testing.assert_allclose(actual_value, expected_value) \ No newline at end of file