From 5332637219fb3f0284e76ed7022aaa28d667a928 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 1 Jan 2017 14:32:49 -0800 Subject: [PATCH] Graph tests --- axelrod/graph.py | 66 +++++++++++----------- axelrod/moran.py | 5 ++ axelrod/tests/unit/test_graph.py | 96 ++++++++++++++++++++++++++++++++ docs/reference/bibliography.rst | 1 + 4 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 axelrod/tests/unit/test_graph.py diff --git a/axelrod/graph.py b/axelrod/graph.py index 22531fc66..b826f9ebc 100644 --- a/axelrod/graph.py +++ b/axelrod/graph.py @@ -18,33 +18,37 @@ class Graph(object): [[node1, node2, weights], ...] Weights can be omitted for an undirected graph. - For efficiency, neighbors are cached in dictionaries. + For efficiency, neighbors are cached in dictionaries. Undirected graphs + are implemented as directed graphs in which every edge (s, t) has the + opposite edge (t, s). """ def __init__(self, edges=None, directed=False): + self.directed = directed + self.original_edges = edges self.out_mapping = defaultdict(lambda: defaultdict(float)) self.in_mapping = defaultdict(lambda: defaultdict(float)) - self.directed = directed + self._edges = [] if edges: self.add_edges(edges) - def add_vertex(self, label): - self._vertices.add(label) - - def add_edge(self, source, target, weight=1.): - self.out_mapping[source][target] = weight - self.in_mapping[target][source] = weight - if not self.directed: + def add_edge(self, source, target, weight=None): + if (source, target) not in self._edges: + self._edges.append((source, target)) + self.out_mapping[source][target] = weight + self.in_mapping[target][source] = weight + if not self.directed and (source != target) and \ + (target, source) not in self._edges: + self._edges.append((target, source)) self.out_mapping[target][source] = weight self.in_mapping[source][target] = weight def add_edges(self, edges): - try: - for source, target, weight in edges: - self.add_edge(source, target, weight) - except ValueError: - for source, target in edges: - self.add_edge(source, target, 1.0) + for edge in edges: + self.add_edge(*edge) + + def edges(self): + return self._edges def vertices(self): """Returns the set of vertices of the graph.""" @@ -62,20 +66,13 @@ def in_dict(self, target): """Returns a dictionary of the incoming edges of source with weights.""" return self.in_mapping[target] - def normalize_weights(self): - """Normalizes the weights coming out of each vertex to be probability - distributions.""" - new_edges = [] - for source in self.out_mapping.keys(): - total = float(sum(out_mapping[source].values())) - for target, weight in self.out_mapping.items(): - self.out_mapping[target] = weight / total - self._edges = new_edges + def in_vertices(self, source): + """Returns a list of the outgoing vertices.""" + return list(self.in_mapping[source].keys()) - def __getitem__(self, k): - """Returns the dictionary of outgoing edges. You can access the weight - of an edge with g[source][target].""" - return self.out_mapping[k] + def __repr__(self): + s = "".format(repr(self.original_edges)) + return s ## Example Graphs @@ -104,9 +101,11 @@ def cycle(length, directed=False): return graph -def complete_graph(length, directed=False): +def complete_graph(length, loops=True): """ - Produces a complete graph of size `length`. + Produces a complete graph of size `length`, with loops. + https://en.wikipedia.org/wiki/Complete_graph + Parameters ---------- length: int @@ -117,10 +116,13 @@ def complete_graph(length, directed=False): ------- a Graph object """ - graph = Graph(directed=directed) + offset = 1 + if loops: + offset = 0 + graph = Graph(directed=False) edges = [] for i in range(length): - for j in range(length): + for j in range(i + offset, length): edges.append((i, j)) graph.add_edges(edges) return graph diff --git a/axelrod/moran.py b/axelrod/moran.py index 7ceb3d561..44f3e5df4 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -271,6 +271,11 @@ def __init__(self, players, interaction_graph, reproduction_graph=None, population. This is not the only method yet emulates the common method in the literature. + Note: the weighted graph case is not yet implemented. + + See [Shakarian2013]_ for more detail on the process and different + updating modes. + Parameters ---------- players, iterable of axelrod.Player subclasses diff --git a/axelrod/tests/unit/test_graph.py b/axelrod/tests/unit/test_graph.py new file mode 100644 index 000000000..51e63d3d9 --- /dev/null +++ b/axelrod/tests/unit/test_graph.py @@ -0,0 +1,96 @@ +import unittest + +from axelrod import graph + +class TestGraph(unittest.TestCase): + + def test_cycle(self): + g = graph.cycle(1, directed=False) + self.assertEqual(g.vertices(), [0]) + self.assertEqual(g.edges(), [(0, 0)]) + self.assertEqual(g.directed, False) + g = graph.cycle(1, directed=True) + self.assertEqual(g.vertices(), [0]) + self.assertEqual(g.edges(), [(0, 0)]) + self.assertEqual(g.directed, True) + g = graph.cycle(2, directed=True) + self.assertEqual(g.vertices(), [0, 1]) + self.assertEqual(g.edges(), [(0, 1), (1, 0)]) + g = graph.cycle(2, directed=False) + self.assertEqual(g.vertices(), [0, 1]) + self.assertEqual(g.edges(), [(0, 1), (1, 0)]) + g = graph.cycle(3, directed=True) + self.assertEqual(g.vertices(), [0, 1, 2]) + self.assertEqual(g.edges(), [(0, 1), (1, 2), (2, 0)]) + g = graph.cycle(3, directed=False) + edges = [(0, 1), (1, 0), (1, 2), (2, 1), (2, 0), (0, 2)] + self.assertEqual(g.vertices(), [0, 1, 2]) + self.assertEqual(g.edges(), edges) + g = graph.cycle(4, directed=True) + self.assertEqual(g.vertices(), [0, 1, 2, 3]) + self.assertEqual(g.edges(), [(0, 1), (1, 2), (2, 3), (3, 0)]) + self.assertEqual(g.out_vertices(0), [1]) + self.assertEqual(g.out_vertices(1), [2]) + self.assertEqual(g.out_vertices(2), [3]) + self.assertEqual(g.out_vertices(3), [0]) + self.assertEqual(g.in_vertices(0), [3]) + self.assertEqual(g.in_vertices(1), [0]) + self.assertEqual(g.in_vertices(2), [1]) + self.assertEqual(g.in_vertices(3), [2]) + g = graph.cycle(4, directed=False) + edges = [(0, 1), (1, 0), (1, 2), (2, 1), + (2, 3), (3, 2), (3, 0), (0, 3)] + self.assertEqual(g.vertices(), [0, 1, 2, 3]) + self.assertEqual(g.edges(), edges) + for vertex, neighbors in [ + (0, (1, 3)), (1, (0, 2)), (2, (1, 3)), (3, (0, 2))]: + self.assertEqual(set(g.out_vertices(0)), set(neighbors)) + for vertex, neighbors in [ + (0, (1, 3)), (1, (0, 2)), (2, (1, 3)), (3, (0, 2))]: + self.assertEqual(set(g.in_vertices(0)), set(neighbors)) + + def test_complete(self): + g = graph.complete_graph(2, loops=False) + self.assertEqual(g.vertices(), [0, 1]) + self.assertEqual(g.edges(), [(0, 1), (1, 0)]) + self.assertEqual(g.directed, False) + g = graph.complete_graph(3, loops=False) + self.assertEqual(g.vertices(), [0, 1, 2]) + edges = [(0, 1), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1)] + self.assertEqual(g.edges(), edges) + self.assertEqual(g.directed, False) + g = graph.complete_graph(4, loops=False ) + self.assertEqual(g.vertices(), [0, 1, 2, 3]) + edges = [(0, 1), (1, 0), (0, 2), (2, 0), (0, 3), (3, 0), + (1, 2), (2, 1), (1, 3), (3, 1), (2, 3), (3, 2)] + self.assertEqual(g.edges(), edges) + self.assertEqual(g.directed, False) + for vertex, neighbors in [ + (0, (1, 2, 3)), (1, (0, 2, 3)), (2, (0, 1, 3)), (3, (0, 1, 2))]: + self.assertEqual(set(g.out_vertices(0)), set(neighbors)) + for vertex, neighbors in [ + (0, (1, 2, 3)), (1, (0, 2, 3)), (2, (0, 1, 3)), (3, (0, 1, 2))]: + self.assertEqual(set(g.in_vertices(0)), set(neighbors)) + + def test_complete_with_loops(self): + g = graph.complete_graph(2, loops=True) + self.assertEqual(g.vertices(), [0, 1]) + self.assertEqual(g.edges(), [(0, 0), (0, 1), (1, 0), (1, 1)]) + self.assertEqual(g.directed, False) + g = graph.complete_graph(3, loops=True) + self.assertEqual(g.vertices(), [0, 1, 2]) + edges = [(0, 0), (0, 1), (1, 0), (0, 2), (2, 0), (1, 1), + (1, 2), (2, 1), (2, 2)] + self.assertEqual(g.edges(), edges) + self.assertEqual(g.directed, False) + g = graph.complete_graph(4, loops=True) + self.assertEqual(g.vertices(), [0, 1, 2, 3]) + edges = [(0, 0), (0, 1), (1, 0), (0, 2), (2, 0), (0, 3), (3, 0), + (1, 1), (1, 2), (2, 1), (1, 3), (3, 1), + (2, 2), (2, 3), (3, 2), (3, 3)] + self.assertEqual(g.edges(), edges) + self.assertEqual(g.directed, False) + neighbors = range(4) + for vertex in range(4): + self.assertEqual(set(g.out_vertices(vertex)), set(neighbors)) + self.assertEqual(set(g.in_vertices(vertex)), set(neighbors)) diff --git a/docs/reference/bibliography.rst b/docs/reference/bibliography.rst index 1f90eb28d..01c828dd4 100644 --- a/docs/reference/bibliography.rst +++ b/docs/reference/bibliography.rst @@ -26,6 +26,7 @@ documentation. .. [PRISON1998] LIFL (1998) PRISON. Available at: http://www.lifl.fr/IPD/ipd.frame.html (Accessed: 19 September 2016). .. [Robson1989] Robson, Arthur, (1989), EFFICIENCY IN EVOLUTIONARY GAMES: DARWIN, NASH AND SECRET HANDSHAKE, Working Papers, Michigan - Center for Research on Economic & Social Theory, http://EconPapers.repec.org/RePEc:fth:michet:89-22. .. [Singer-Clark2014] Singer-Clark, T. (2014). Morality Metrics On Iterated Prisoner’s Dilemma Players. +.. [Shakarian2013] Shakarian, P., Roos, P. & Moores, G. A Novel Analytical Method for Evolutionary Graph Theory Problems. .. [Slany2007] Slany W. and Kienreich W., On some winning strategies for the iterated prisoner’s dilemma, in Kendall G., Yao X. and Chong S. (eds.) The iterated prisoner’s dilemma: 20 years on. World Scientific, chapter 8, pp. 171-204, 2007. .. [Stewart2012] Stewart, a. J., & Plotkin, J. B. (2012). Extortion and cooperation in the Prisoner’s Dilemma. Proceedings of the National Academy of Sciences, 109(26), 10134–10135. http://doi.org/10.1073/pnas.1208087109 .. [Szabó1992] Szabó, G., & Fáth, G. (2007). Evolutionary games on graphs. Physics Reports, 446(4-6), 97–216. http://doi.org/10.1016/j.physrep.2007.04.004