diff --git a/axelrod/ecosystem.py b/axelrod/ecosystem.py index 61f4d81cb..5df23453d 100644 --- a/axelrod/ecosystem.py +++ b/axelrod/ecosystem.py @@ -1,15 +1,47 @@ +"""Tools for simulating population dynamics of immutable players. + +An ecosystem runs in the context of a previous tournament, and takes the +results as input. That means no matches are run by the ecosystem, and a +tournament needs to happen before it is created. For example: + +players = [axelrod.Cooperator(), axlerod.Defector()] +tournament = axelrod.Tournament(players=players) +results = tournament.play() +ecosystem = axelrod.Ecosystem(results) +ecosystem.reproduce(100) +""" + import random -from axelrod.result_set import ResultSet + from typing import List, Callable +from axelrod.result_set import ResultSet + class Ecosystem(object): - """Create an ecosystem based on the payoff matrix from an Axelrod - tournament.""" + """An ecosystem based on the payoff matrix from a tournament. + + Attributes + ---------- + num_players: int + The number of players + """ def __init__(self, results: ResultSet, fitness: Callable[[float], float] = None, population: List[int] = None) -> None: + """Create a new ecosystem. + + Parameters + ---------- + results: ResultSet + The results of the tournament run beforehand to use. + fitness: List of callables + The reproduction rate at which populations reproduce. + population: List of ints. + The initial populations of the players, corresponding to the + payoff matrix in results. + """ self.results = results self.num_players = self.results.num_players @@ -45,7 +77,13 @@ def __init__(self, results: ResultSet, self.fitness = lambda p: p def reproduce(self, turns: int): - + """Reproduce populations according to the payoff matrix. + + Parameters + ---------- + turns: int + The number of turns to run. + """ for iturn in range(turns): plist = list(range(self.num_players)) pops = self.population_sizes[-1] diff --git a/axelrod/eigen.py b/axelrod/eigen.py index ef8a38b5f..139e2de0b 100644 --- a/axelrod/eigen.py +++ b/axelrod/eigen.py @@ -9,21 +9,21 @@ from typing import Tuple -def normalise(nvec: numpy.ndarray) -> numpy.ndarray: +def _normalise(nvec: numpy.ndarray) -> numpy.ndarray: """Normalises the given numpy array.""" with numpy.errstate(invalid='ignore'): result = nvec / numpy.sqrt(numpy.dot(nvec, nvec)) return result -def squared_error(vector_1: numpy.ndarray, vector_2: numpy.ndarray) -> float: +def _squared_error(vector_1: numpy.ndarray, vector_2: numpy.ndarray) -> float: """Computes the squared error between two numpy arrays.""" diff = vector_1 - vector_2 s = numpy.dot(diff, diff) return numpy.sqrt(s) -def power_iteration(mat: numpy.matrix, initial: numpy.ndarray) -> numpy.ndarray: +def _power_iteration(mat: numpy.matrix, initial: numpy.ndarray) -> numpy.ndarray: """ Generator of successive approximations. @@ -41,7 +41,7 @@ def power_iteration(mat: numpy.matrix, initial: numpy.ndarray) -> numpy.ndarray: vec = initial while True: - vec = normalise(numpy.dot(mat, vec)) + vec = _normalise(numpy.dot(mat, vec)) yield vec @@ -60,6 +60,13 @@ def principal_eigenvector(mat: numpy.matrix, maximum_iterations=1000, The maximum number of iterations of the approximation max_error: float, 1e-8 Exit criterion -- error threshold of the difference of successive steps + + Returns + ------- + ndarray + Eigenvector estimate for the input matrix + float + Eigenvalue corresonding to the returned eigenvector """ mat_ = numpy.matrix(mat) @@ -70,10 +77,10 @@ def principal_eigenvector(mat: numpy.matrix, maximum_iterations=1000, if not maximum_iterations: maximum_iterations = float('inf') last = initial - for i, vector in enumerate(power_iteration(mat, initial=initial)): + for i, vector in enumerate(_power_iteration(mat, initial=initial)): if i > maximum_iterations: break - if squared_error(vector, last) < max_error: + if _squared_error(vector, last) < max_error: break last = vector # Compute the eigenvalue (Rayleigh quotient) diff --git a/axelrod/tests/unit/test_deterministic_cache.py b/axelrod/tests/unit/test_deterministic_cache.py index 6e71ef27a..6f5ebd4cc 100644 --- a/axelrod/tests/unit/test_deterministic_cache.py +++ b/axelrod/tests/unit/test_deterministic_cache.py @@ -32,17 +32,15 @@ def setUp(self): self.cache = DeterministicCache() def test_basic_init(self): - cache = DeterministicCache() - self.assertTrue(cache.mutable) + self.assertTrue(self.cache.mutable) def test_init_from_file(self): - cache = DeterministicCache(file_name=self.test_load_file) - self.assertEqual(cache[self.test_key], self.test_value) + loaded_cache = DeterministicCache(file_name=self.test_load_file) + self.assertEqual(loaded_cache[self.test_key], self.test_value) def test_setitem(self): - cache = DeterministicCache() - cache[self.test_key] = self.test_value - self.assertEqual(cache[self.test_key], self.test_value) + self.cache[self.test_key] = self.test_value + self.assertEqual(self.cache[self.test_key], self.test_value) def test_setitem_invalid_key_not_tuple(self): invalid_key = 'test' @@ -87,28 +85,24 @@ def test_setitem_invalid_key_stochastic_player(self): self.cache[invalid_key] = self.test_value def test_setitem_invalid_value_not_list(self): - cache = DeterministicCache() with self.assertRaises(ValueError): - cache[self.test_key] = 5 + self.cache[self.test_key] = 5 def test_setitem_with_immutable_cache(self): - cache = DeterministicCache() - cache.mutable = False + self.cache.mutable = False with self.assertRaises(ValueError): - cache[self.test_key] = self.test_value + self.cache[self.test_key] = self.test_value def test_save(self): - cache = DeterministicCache() - cache[self.test_key] = self.test_value - cache.save(self.test_save_file) + self.cache[self.test_key] = self.test_value + self.cache.save(self.test_save_file) with open(self.test_save_file, 'rb') as f: text = f.read() self.assertEqual(text, self.test_pickle) def test_load(self): - cache = DeterministicCache() - cache.load(self.test_load_file) - self.assertEqual(cache[self.test_key], self.test_value) + self.cache.load(self.test_load_file) + self.assertEqual(self.cache[self.test_key], self.test_value) def test_load_error_for_inccorect_format(self): filename = "test_outputs/test.cache" @@ -116,12 +110,10 @@ def test_load_error_for_inccorect_format(self): pickle.dump(range(5), io) with self.assertRaises(ValueError): - cache = DeterministicCache() - cache.load(filename) + self.cache.load(filename) def test_del_item(self): - cache = DeterministicCache() - cache[self.test_key] = self.test_value - self.assertTrue(self.test_key in cache) - del cache[self.test_key] - self.assertFalse(self.test_key in cache) + self.cache[self.test_key] = self.test_value + self.assertTrue(self.test_key in self.cache) + del self.cache[self.test_key] + self.assertFalse(self.test_key in self.cache) diff --git a/axelrod/tests/unit/test_ecosystem.py b/axelrod/tests/unit/test_ecosystem.py index 67536f830..28c3caccd 100644 --- a/axelrod/tests/unit/test_ecosystem.py +++ b/axelrod/tests/unit/test_ecosystem.py @@ -24,10 +24,7 @@ def setUpClass(cls): cls.res_cooperators = cooperators.play() cls.res_defector_wins = defector_wins.play() - def test_init(self): - """Are the populations created correctly?""" - - # By default create populations of equal size + def test_default_population_sizes(self): eco = axelrod.Ecosystem(self.res_cooperators) pops = eco.population_sizes self.assertEqual(eco.num_players, 4) @@ -36,7 +33,7 @@ def test_init(self): self.assertAlmostEqual(sum(pops[0]), 1.0) self.assertEqual(list(set(pops[0])), [0.25]) - # Can pass list of initial population distributions + def test_non_default_population_sizes(self): eco = axelrod.Ecosystem(self.res_cooperators, population=[.7, .25, .03, .02]) pops = eco.population_sizes self.assertEqual(eco.num_players, 4) @@ -45,7 +42,7 @@ def test_init(self): self.assertAlmostEqual(sum(pops[0]), 1.0) self.assertEqual(pops[0], [.7, .25, .03, .02]) - # Distribution will automatically normalise + def test_population_normalization(self): eco = axelrod.Ecosystem(self.res_cooperators, population=[70, 25, 3, 2]) pops = eco.population_sizes self.assertEqual(eco.num_players, 4) @@ -54,22 +51,20 @@ def test_init(self): self.assertAlmostEqual(sum(pops[0]), 1.0) self.assertEqual(pops[0], [.7, .25, .03, .02]) - # If passed list is of incorrect size get error + def test_results_and_population_of_different_sizes(self): self.assertRaises(TypeError, axelrod.Ecosystem, self.res_cooperators, population=[.7, .2, .03, .1, .1]) - # If passed list has negative values + def test_negative_populations(self): self.assertRaises(TypeError, axelrod.Ecosystem, self.res_cooperators, population=[.7, -.2, .03, .2]) - def test_fitness(self): + def test_fitness_function(self): fitness = lambda p: 2 * p eco = axelrod.Ecosystem(self.res_cooperators, fitness=fitness) self.assertTrue(eco.fitness(10), 20) - def test_cooperators(self): - """Are cooperators stable over time?""" - + def test_cooperators_are_stable_over_time(self): eco = axelrod.Ecosystem(self.res_cooperators) eco.reproduce(100) pops = eco.population_sizes @@ -79,9 +74,7 @@ def test_cooperators(self): self.assertEqual(sum(p), 1.0) self.assertEqual(list(set(p)), [0.25]) - def test_defector_wins(self): - """Does one defector win over time?""" - + def test_defector_wins_with_only_cooperators(self): eco = axelrod.Ecosystem(self.res_defector_wins) eco.reproduce(1000) pops = eco.population_sizes diff --git a/axelrod/tests/unit/test_eigen.py b/axelrod/tests/unit/test_eigen.py index d27e86149..dc2f2ab6a 100644 --- a/axelrod/tests/unit/test_eigen.py +++ b/axelrod/tests/unit/test_eigen.py @@ -5,41 +5,37 @@ import numpy from numpy.testing import assert_array_almost_equal -from axelrod.eigen import normalise, principal_eigenvector +from axelrod.eigen import _normalise, principal_eigenvector class FunctionCases(unittest.TestCase): - def test_eigen_1(self): - # Test identity matrices + def test_identity_matrices(self): for size in range(2, 6): mat = numpy.identity(size) evector, evalue = principal_eigenvector(mat) self.assertAlmostEqual(evalue, 1) - assert_array_almost_equal(evector, normalise(numpy.ones(size))) + assert_array_almost_equal(evector, _normalise(numpy.ones(size))) - def test_eigen_2(self): - # Test a 2x2 matrix + def test_2x2_matrix(self): mat = [[2, 1], [1, 2]] evector, evalue = principal_eigenvector(mat) self.assertAlmostEqual(evalue, 3) assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) - assert_array_almost_equal(evector, normalise([1, 1])) + assert_array_almost_equal(evector, _normalise([1, 1])) - def test_eigen_3(self): - # Test a 3x3 matrix + def test_3x3_matrix(self): mat = [[1, 2, 0], [-2, 1, 2], [1, 3, 1]] evector, evalue = principal_eigenvector(mat, maximum_iterations=None, max_error=1e-10) self.assertAlmostEqual(evalue, 3) assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) - assert_array_almost_equal(evector, normalise([0.5, 0.5, 1])) + assert_array_almost_equal(evector, _normalise([0.5, 0.5, 1])) - def test_eigen_4(self): - # Test a 4x4 matrix + def test_4x4_matrix(self): mat = [[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]] evector, evalue = principal_eigenvector(mat, maximum_iterations=None, max_error=1e-10) self.assertAlmostEqual(evalue, 3, places=3) assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) - assert_array_almost_equal(evector, normalise([0, 0, 0, 1]), decimal=4) + assert_array_almost_equal(evector, _normalise([0, 0, 0, 1]), decimal=4)