From 461cd9855d0a7cb6c17eb40ee824a4ff2564f252 Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 10:21:40 -0500 Subject: [PATCH 01/60] Create matrix2dict function with docstrings --- queueing_tool/graph/graph_functions.py | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/queueing_tool/graph/graph_functions.py b/queueing_tool/graph/graph_functions.py index f45eed0..b991257 100644 --- a/queueing_tool/graph/graph_functions.py +++ b/queueing_tool/graph/graph_functions.py @@ -86,3 +86,49 @@ def graph2dict(g, return_dict_of_dict=True): return dict_of_dicts else: return {k: list(val.keys()) for k, val in dict_of_dicts.items()} + + +def matrix2dict(matrix, graph): + """Takes an adjacency matrix and returns an adjacency list. + + Parameters + ---------- + graph : :any:`networkx.DiGraph`, :any:`networkx.Graph`, etc. + Any object that networkx can turn into a + :any:`DiGraph`. + matrix : :class:`~numpy.ndarray` + An matrix related to an adjacency list. The ``matrix[i, j]`` + represents some relationship between the vertex ``i`` and the + vertex ``j``. + + Returns + ------- + adj : dict + An adjacency representation of the matrix for the graph. + + Examples + -------- + >>> import queueing_tool as qt + >>> import networkx as nx + >>> adj = {0: [1, 2], 1: [0], 2: [0, 3], 3: [2]} + >>> g = nx.DiGraph(adj) + >>> mat = qt.generate_transition_matrix(g, seed=123) + >>> mat # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE + array([[ 0. , 0.707..., 0.292..., 0. ], + [ 1. , 0. , 0. , 0. ], + [ 0.291..., 0. , 0. , 0.708...], + [ 0. , 0. , 1. , 0. ]]) + >>> qt.matrix2dict(mat, g) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE + {0: {1: 0.707..., 2: 0.292...}, + 1: {0: 1.0}, + 2: {0: 0.291..., 3: 0.708...}, + 3: {2: 1.0}} + """ + result = {} + adjacency_graph = graph2dict(graph, return_dict_of_dict=False) + for source, adjacents in adjacency_graph.items(): + result[source] = {dest: matrix[source, dest] for dest in adjacents} + + return result From 70930c795015a2b1bbf0ae7f02bfe00438508d6c Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 18:33:04 -0500 Subject: [PATCH 02/60] simplify set_transitions function --- queueing_tool/network/queue_network.py | 46 +++++++++----------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index b993fea..9e47163 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -17,7 +17,7 @@ except ImportError: HAS_MATPLOTLIB = False -from queueing_tool.graph import _prepare_graph +from queueing_tool.graph import _prepare_graph, matrix2dict from queueing_tool.queues import ( NullQueue, QueueServer, @@ -1091,40 +1091,24 @@ def set_transitions(self, mat): :func:`.generate_transition_matrix` : Generate a random routing matrix. """ - if isinstance(mat, dict): - for key, value in mat.items(): - probs = list(value.values()) - - if key not in self.g.node: - msg = "One of the keys don't correspond to a vertex." - raise ValueError(msg) - elif len(self.out_edges[key]) > 0 and not np.isclose(sum(probs), 1): - msg = "Sum of transition probabilities at a vertex was not 1." - raise ValueError(msg) - elif (np.array(probs) < 0).any(): - msg = "Some transition probabilities were negative." - raise ValueError(msg) - - for k, e in enumerate(self.g.out_edges(key)): - self._route_probs[key][k] = value.get(e[1], 0) - - elif isinstance(mat, np.ndarray): - non_terminal = np.array([self.g.out_degree(v) > 0 for v in self.g.nodes()]) - if mat.shape != (self.nV, self.nV): - msg = ("Matrix is the wrong shape, should " - "be {0} x {1}.").format(self.nV, self.nV) + if isinstance(mat, np.ndarray): + mat = matrix2dict(mat) + + for key, value in mat.items(): + probs = list(value.values()) + + if key not in self.g.node: + msg = "One of the keys don't correspond to a vertex." raise ValueError(msg) - elif not np.allclose(np.sum(mat[non_terminal, :], axis=1), 1): + elif len(self.out_edges[key]) > 0 and not np.isclose(sum(probs), 1): msg = "Sum of transition probabilities at a vertex was not 1." raise ValueError(msg) - elif (mat < 0).any(): - raise ValueError("Some transition probabilities were negative.") + elif (np.array(probs) < 0).any(): + msg = "Some transition probabilities were negative." + raise ValueError(msg) - for k in range(self.nV): - for j, e in enumerate(self.g.out_edges(k)): - self._route_probs[k][j] = mat[k, e[1]] - else: - raise TypeError("mat must be a numpy array or a dict.") + for k, e in enumerate(self.g.out_edges(key)): + self._route_probs[key][k] = value.get(e[1], 0) def show_active(self, **kwargs): """Draws the network, highlighting active queues. From 9756fd0bc4a3ccce12a7b77262462473bedd1e98 Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 20:24:55 -0500 Subject: [PATCH 03/60] Deprecate AgentFactory --- queueing_tool/queues/queue_servers.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index b2626bc..54d43f5 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -119,6 +119,8 @@ class QueueServer(object): the edge type for this queue. This is automatically created when a :class:`.QueueNetwork` instance is created. AgentFactory : class (optional, default: the :class:`~Agent` class) + Deprecated. Use the :meth:`~QueueServer.agent_factory` method + instead. Any function that can create agents. Note that the function must take one parameter. active_cap : int (optional, default: ``infty``) @@ -303,7 +305,7 @@ def service_f(t): self.arrival_f = arrival_f self.service_f = service_f - self.AgentFactory = AgentFactory + self._agent_factory = AgentFactory self.collect_data = collect_data self.active_cap = active_cap self.deactive_t = deactive_t @@ -346,6 +348,11 @@ def current_time(self): def num_arrivals(self): return [self._num_arrivals, self._oArrivals] + @property + def AgentFactory(self): + # Deprecated + return self._agent_factory + def __repr__(self): my_str = ("QueueServer:{0}. Servers: {1}, queued: {2}, arrivals: {3}, " "departures: {4}, next time: {5}") @@ -366,7 +373,7 @@ def _add_arrival(self, agent=None): return self._num_total += 1 - new_agent = self.AgentFactory((self.edge[2], self._oArrivals)) + new_agent = self.agent_factory() new_agent._time = self._next_ct heappush(self._arrivals, new_agent) @@ -378,6 +385,9 @@ def _add_arrival(self, agent=None): if self._arrivals[0]._time < self._departures[0]._time: self._time = self._arrivals[0]._time + def agent_factory(self): + return self._agent_factory((self.edge[2], self._oArrivals), queue=self) + def at_capacity(self): """Returns whether the queue is at capacity or not. From d9417b7d1e7d63570fc8e5b1ef6d638597be617f Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 20:34:25 -0500 Subject: [PATCH 04/60] Add matrix2dict to __init__ file --- queueing_tool/graph/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/queueing_tool/graph/__init__.py b/queueing_tool/graph/__init__.py index 4d8c013..9f3c948 100644 --- a/queueing_tool/graph/__init__.py +++ b/queueing_tool/graph/__init__.py @@ -7,6 +7,7 @@ generate_pagerank_graph generate_transition_matrix graph2dict + matrix2dict minimal_random_graph set_types_rank set_types_random @@ -15,7 +16,8 @@ """ from queueing_tool.graph.graph_functions import ( - graph2dict + graph2dict, + matrix2dict ) from queueing_tool.graph.graph_generation import ( generate_random_graph, @@ -42,6 +44,7 @@ 'generate_pagerank_graph', 'generate_transition_matrix', 'graph2dict', + 'matrix2dict', 'minimal_random_graph', 'set_types_rank', 'set_types_random', From 91d67b63d2ebbd69ba9ccada4b9e42b434477b2e Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 20:58:54 -0500 Subject: [PATCH 05/60] Multiclass queue network prototype --- queueing_tool/network/multiclass_network.py | 76 +++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 queueing_tool/network/multiclass_network.py diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py new file mode 100644 index 0000000..a523d8f --- /dev/null +++ b/queueing_tool/network/multiclass_network.py @@ -0,0 +1,76 @@ +import copy + +import numpy as np + +from queueing_tool.network.queue_network import QueueNetwork +from queueing_tool.queues.agents import Agent +from queueing_tool.queues.choice import _choice + + +class MultiClassQueueNetwork(QueueNetwork): + + def __init__(self, *args, **kwargs): + super(MultiClassQueueNetwork, self).__init__(*args, **kwargs) + self._routing_transitions = {None: copy.deepcopy(self._route_probs)} + + def set_transitions(self, mat, category=None): + for key, value in mat.items(): + probs = list(value.values()) + + if key not in self.g.node: + msg = "One of the keys don't correspond to a vertex." + raise ValueError(msg) + elif len(self.out_edges[key]) > 0 and not np.isclose(sum(probs), 1): + msg = "Sum of transition probabilities at a vertex was not 1." + raise ValueError(msg) + elif (np.array(probs) < 0).any(): + msg = "Some transition probabilities were negative." + raise ValueError(msg) + + for k, e in enumerate(self.g.out_edges(key)): + self._routing_transitions[category][key][k] = value.get(e[1], 0) + + def set_categorical_transitions(self, adjacency_list): + for category, mat in adjacency_list.items(): + self.set_transitions(mat, category) + + def transitions(self): + mat = { + category: { + k: {e[1]: p for e, p in zip(self.g.out_edges(k), value)} + for k, value in enumerate(routing_probs) + } + for category, routing_probs in self._routing_transitions.items() + } + return mat + + def routing_transitions(self, destination, category=None): + if category not in self._routing_transitions: + category = None + + return self._routing_transitions[category][destination] + + def copy(self): + network = super(MultiClassQueueNetwork, self).copy() + network._routing_transitions = copy.deepcopy(self._routing_transitions) + + +class ClassedAgent(Agent): + def __init__(self, agent_id=(0, 0), category=None, **kwargs): + super(ClassedAgent, self).__init__(agent_id=agent_id, **kwargs) + self._category = category + + @property + def category(self): + return self._category or self.__class__.__name__ + + def desired_destination(self, network, edge): + + n = len(network.out_edges[edge[1]]) + if n <= 1: + return network.out_edges[edge[1]][0] + + pr = network.routing_transition(edge[1], self.category) + u = np.random.uniform() + k = _choice(pr, u, n) + return network.out_edges[edge[1]][k] From cd47da39eb1be9ac3574d81e698aad1d456a47e8 Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 12 Dec 2017 21:24:00 -0500 Subject: [PATCH 06/60] Fix routing_transition attribution with defaultdict --- queueing_tool/network/__init__.py | 2 ++ queueing_tool/network/multiclass_network.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/queueing_tool/network/__init__.py b/queueing_tool/network/__init__.py index 7f726f7..43e2df2 100644 --- a/queueing_tool/network/__init__.py +++ b/queueing_tool/network/__init__.py @@ -22,6 +22,7 @@ QueueNetwork.transitions """ +from queueing_tool.network.multiclass_network import MultiClassQueueNetwork from queueing_tool.network.priority_queue import PriorityQueue from queueing_tool.network.queue_network import ( QueueingToolError, @@ -29,6 +30,7 @@ ) __all__ = [ + 'MultiClassQueueNetwork', 'PriorityQueue', 'QueueingToolError', 'QueueNetwork' diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index a523d8f..06acaad 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -1,3 +1,4 @@ +import collections import copy import numpy as np @@ -11,7 +12,11 @@ class MultiClassQueueNetwork(QueueNetwork): def __init__(self, *args, **kwargs): super(MultiClassQueueNetwork, self).__init__(*args, **kwargs) - self._routing_transitions = {None: copy.deepcopy(self._route_probs)} + + def default_factory(): + return copy.deepcopy(self._route_probs) + + self._routing_transitions = collections.defaultdict(default_factory) def set_transitions(self, mat, category=None): for key, value in mat.items(): From 7dc731c186219dc89f7a68387f5714b784da548b Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 18:58:14 -0500 Subject: [PATCH 07/60] Add value checking, raises an error now --- queueing_tool/graph/graph_functions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/queueing_tool/graph/graph_functions.py b/queueing_tool/graph/graph_functions.py index b991257..9bd193b 100644 --- a/queueing_tool/graph/graph_functions.py +++ b/queueing_tool/graph/graph_functions.py @@ -126,6 +126,13 @@ def matrix2dict(matrix, graph): 2: {0: 0.291..., 3: 0.708...}, 3: {2: 1.0}} """ + num_columns, num_rows = matrix.shape + + if num_columns != num_rows or num_columns != graph.number_of_nodes(): + msg = "Matrix has wrong shape, must be {} x {}" + non = graph.number_of_nodes() + raise ValueError(msg.format(non, non)) + result = {} adjacency_graph = graph2dict(graph, return_dict_of_dict=False) for source, adjacents in adjacency_graph.items(): From 0de85decf45d4d379eab90939005dcc07e5a6932 Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 18:58:44 -0500 Subject: [PATCH 08/60] Fix a typo --- queueing_tool/network/queue_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 9e47163..e7aa572 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -1092,7 +1092,7 @@ def set_transitions(self, mat): matrix. """ if isinstance(mat, np.ndarray): - mat = matrix2dict(mat) + mat = matrix2dict(mat, self.g) for key, value in mat.items(): probs = list(value.values()) From 1b1aef75f6c8d861d7d71e265e55a1aaec92de97 Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 19:00:38 -0500 Subject: [PATCH 09/60] Add keyword args for agents --- queueing_tool/queues/agents.py | 3 --- queueing_tool/queues/queue_extentions.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/queueing_tool/queues/agents.py b/queueing_tool/queues/agents.py index 74435bc..f66f142 100644 --- a/queueing_tool/queues/agents.py +++ b/queueing_tool/queues/agents.py @@ -130,9 +130,6 @@ class GreedyAgent(Agent): with the shortest line (where the ordering is given by :class:`QueueNetwork's<.QueueNetwork>` ``out_edges`` attribute). """ - def __init__(self, agent_id=(0, 0)): - Agent.__init__(self, agent_id) - def __repr__(self): msg = "GreedyAgent; agent_id:{0}. time: {1}" return msg.format(self.agent_id, round(self._time, 3)) diff --git a/queueing_tool/queues/queue_extentions.py b/queueing_tool/queues/queue_extentions.py index 2a5150f..0404e4c 100644 --- a/queueing_tool/queues/queue_extentions.py +++ b/queueing_tool/queues/queue_extentions.py @@ -19,8 +19,8 @@ class ResourceAgent(Agent): a resource to that queue by increasing the number of servers there by one; the ``ResourceAgent`` is then deleted. """ - def __init__(self, agent_id=(0, 0)): - super(ResourceAgent, self).__init__(agent_id) + def __init__(self, agent_id=(0, 0) , **kwargs): + super(ResourceAgent, self).__init__(agent_id, **kwargs) self._has_resource = False self._had_resource = False From 5ef1362bd11cfb15a8ee4f089c00f264c352d4e5 Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 19:01:10 -0500 Subject: [PATCH 10/60] Update tests --- tests/test_network.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index 941c0f3..b009051 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -446,10 +446,6 @@ def test_QueueNetwork_set_transitions_Error(self): with self.assertRaises(ValueError): self.qn.set_transitions(mat) - mat = 1 - with self.assertRaises(TypeError): - self.qn.set_transitions(mat) - def test_QueueNetwork_simulate(self): g = qt.generate_pagerank_graph(50) From 612df7422e6503ab64db3bb0c3ca0e652fccc4f4 Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 19:02:10 -0500 Subject: [PATCH 11/60] Simplify queue network di graph, probably containts a bug though --- queueing_tool/graph/graph_wrapper.py | 36 ++++++++++------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 582180c..f728250 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -247,23 +247,23 @@ def __init__(self, data=None, **kwargs): else: self.pos = None - self.edge_color = None - self.vertex_color = None - self.vertex_fill_color = None - self._nE = self.number_of_edges() + @property + def edge_color(self): + return np.array([self.adj[e[0]][e[1]].get('edge_color') for e in self.edges()]) + + @property + def vertex_color(self): + return np.array([self.node[n].get('vertex_color') for n in self.nodes()]) + + @property + def vertex_fill_color(self): + return np.array([self.node[n].get('vertex_fill_color') for n in self.nodes()]) def freeze(self): nx.freeze(self) def is_edge(self, e): - return e in self.edge_index - - def add_edge(self, *args, **kwargs): - super(QueueNetworkDiGraph, self).add_edge(*args, **kwargs) - e = (args[0], args[1]) - if e not in self.edge_index: - self.edge_index[e] = self._nE - self._nE += 1 + return e[1] in self.adj[e[0]] def out_neighbours(self, v): return [e[1] for e in self.out_edges(v)] @@ -276,15 +276,9 @@ def vp(self, v, vertex_property): def set_ep(self, e, edge_property, value): self.adj[e[0]][e[1]][edge_property] = value - if hasattr(self, edge_property): - attr = getattr(self, edge_property) - attr[self.edge_index[e]] = value def set_vp(self, v, vertex_property, value): self.node[v][vertex_property] = value - if hasattr(self, vertex_property): - attr = getattr(self, vertex_property) - attr[v] = value def vertex_properties(self): props = set() @@ -301,16 +295,10 @@ def edge_properties(self): def new_vertex_property(self, name): values = {v: None for v in self.nodes()} nx.set_node_attributes(self, name=name, values=values) - if name == 'vertex_color': - self.vertex_color = [0 for v in range(self.number_of_nodes())] - if name == 'vertex_fill_color': - self.vertex_fill_color = [0 for v in range(self.number_of_nodes())] def new_edge_property(self, name): values = {e: None for e in self.edges()} nx.set_edge_attributes(self, name=name, values=values) - if name == 'edge_color': - self.edge_color = np.zeros((self.number_of_edges(), 4)) def set_pos(self, pos=None): if pos is None: From dd9a9d603e9ccc66348b9afbb66cb69ed2a13b70 Mon Sep 17 00:00:00 2001 From: djordon Date: Thu, 14 Dec 2017 19:33:40 -0500 Subject: [PATCH 12/60] Use dict for in_edges, out_edges and _route_probs attributes --- queueing_tool/network/queue_network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index e7aa572..52bc3de 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -353,9 +353,9 @@ def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, self.edge2queue = qs self.num_agents = np.zeros(g.number_of_edges(), int) - self.out_edges = [0 for v in range(self.nV)] - self.in_edges = [0 for v in range(self.nV)] - self._route_probs = [0 for v in range(self.nV)] + self.out_edges = {} + self.in_edges = {} + self._route_probs = {} for v in g.nodes(): vod = g.out_degree(v) From 17626b7aa9e1e2602d2e2cbdca528ad9fa4ecc7c Mon Sep 17 00:00:00 2001 From: djordon Date: Fri, 15 Dec 2017 17:04:18 -0500 Subject: [PATCH 13/60] Changed some documentation --- queueing_tool/queues/agents.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/queueing_tool/queues/agents.py b/queueing_tool/queues/agents.py index f66f142..b752fe9 100644 --- a/queueing_tool/queues/agents.py +++ b/queueing_tool/queues/agents.py @@ -114,8 +114,7 @@ def queue_action(self, queue, *args, **kwargs): ``args[0] == 0``), when service starts for the Agent (where ``args[0] == 1``), and when the Agent departs from the queue (where ``args[0] == 2``). By default, this method does nothing - to the queue, but is here if the Agent class is extended and - this method is overwritten. + to the queue. """ pass From 2fc9d85ffe4d0473cf5d9535fd12cf27c9aec1e1 Mon Sep 17 00:00:00 2001 From: djordon Date: Fri, 15 Dec 2017 17:05:36 -0500 Subject: [PATCH 14/60] Added a better named method as set_pos --- queueing_tool/graph/graph_wrapper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index f728250..9235e77 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -300,12 +300,15 @@ def new_edge_property(self, name): values = {e: None for e in self.edges()} nx.set_edge_attributes(self, name=name, values=values) - def set_pos(self, pos=None): + def set_node_positions(self, pos=None): if pos is None: pos = nx.spring_layout(self) nx.set_node_attributes(self, name='pos', values=pos) self.pos = np.array([pos[v] for v in self.nodes()]) + def set_pos(self, pos=None): + self.set_node_positions(pos=pos) + def get_edge_type(self, edge_type): """Returns all edges with the specified edge type. From 3cbd4e9ca369fc681f41579a108932a567b7c6c0 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 05:00:33 -0500 Subject: [PATCH 15/60] Convert tests to pytest tests --- tests/test_qndigraph.py | 47 ++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/tests/test_qndigraph.py b/tests/test_qndigraph.py index 0acae68..66e38a6 100644 --- a/tests/test_qndigraph.py +++ b/tests/test_qndigraph.py @@ -1,5 +1,4 @@ import os -import unittest try: import unittest.mock as mock except ImportError: @@ -7,6 +6,7 @@ import networkx as nx import numpy as np +import pytest import matplotlib import matplotlib.image @@ -29,36 +29,39 @@ } -class TestQueueNetworkDiGraph(unittest.TestCase): +@pytest.fixture(scope='module') +def queue_network_graph(): + np.random.seed(10) + return qt.QueueNetworkDiGraph(nx.krackhardt_kite_graph()) - @classmethod - def setUpClass(cls): - cls.g = qt.QueueNetworkDiGraph(nx.krackhardt_kite_graph()) - np.random.seed(10) + +class TestQueueNetworkDiGraph(object): @mock.patch.dict('sys.modules', matplotlib_mock) - def testlines_scatter_args(self): + def test_lines_scatter_args(self, queue_network_graph): + np.random.seed(10) ax = mock.Mock() ax.transData = mock.Mock() line_args = {'linewidths': 77, 'vmax': 107} scat_args = {'vmax': 107} - kwargs = {'pos': {v: (910, 10) for v in self.g.nodes()}} + kwargs = {'pos': {v: (910, 10) for v in queue_network_graph.nodes()}} - a, b = self.g.lines_scatter_args(line_args, scat_args, **kwargs) + a, b = queue_network_graph.lines_scatter_args(line_args, scat_args, **kwargs) - self.assertEqual(a['linewidths'], 77) - self.assertEqual(b['vmax'], 107) - self.assertTrue('beefy' not in a and 'beefy' not in b) + assert a['linewidths'] == 77 + assert b['vmax'] == 107 + assert 'beefy' not in a and 'beefy' not in b - def test_draw_graph(self): - pos = np.random.uniform(size=(self.g.number_of_nodes(), 2)) + def test_draw_graph(self, queue_network_graph): + np.random.seed(10) + pos = np.random.uniform(size=(queue_network_graph.number_of_nodes(), 2)) kwargs = { 'fname': 'test1.png', 'pos': pos } - self.g.draw_graph(scatter_kwargs={'s': 100}, **kwargs) + queue_network_graph.draw_graph(scatter_kwargs={'s': 100}, **kwargs) - version = 1 if matplotlib.__version__.startswith('1') else 2 + version = matplotlib.__version__[0] filename = 'test-mpl-{version}.x.png'.format(version=version) img0 = matplotlib.image.imread('tests/img/{filename}'.format(filename=filename)) @@ -69,15 +72,15 @@ def test_draw_graph(self): pixel_diff = (img0 != img1).flatten() num_pixels = pixel_diff.shape[0] + 0.0 - self.assertLess(pixel_diff.sum() / num_pixels, 0.0001) + assert pixel_diff.sum() / num_pixels < 0.0001 with mock.patch('queueing_tool.graph.graph_wrapper.HAS_MATPLOTLIB', False): - with self.assertRaises(ImportError): - self.g.draw_graph() + with pytest.raises(ImportError): + queue_network_graph.draw_graph() kwargs = {'pos': 1} - self.g.set_pos = mock.MagicMock() + queue_network_graph.set_pos = mock.MagicMock() with mock.patch.dict('sys.modules', matplotlib_mock): - self.g.draw_graph(**kwargs) + queue_network_graph.draw_graph(**kwargs) - self.g.set_pos.assert_called_once_with(1) + queue_network_graph.set_pos.assert_called_once_with(1) From dbb3e7d070f0d3d55bc4279b39d83d9ceab24b0f Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 05:01:35 -0500 Subject: [PATCH 16/60] Smarter graph property methods --- queueing_tool/graph/graph_wrapper.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 9235e77..88fb96a 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -1,3 +1,5 @@ +import itertools + import networkx as nx import numpy as np @@ -249,14 +251,20 @@ def __init__(self, data=None, **kwargs): @property def edge_color(self): + if 'edge_color' not in self.edge_properties(): + return None return np.array([self.adj[e[0]][e[1]].get('edge_color') for e in self.edges()]) @property def vertex_color(self): + if 'vertex_color' not in self.edge_properties(): + return None return np.array([self.node[n].get('vertex_color') for n in self.nodes()]) @property def vertex_fill_color(self): + if 'vertex_fill_color' not in self.edge_properties(): + return None return np.array([self.node[n].get('vertex_fill_color') for n in self.nodes()]) def freeze(self): @@ -282,13 +290,13 @@ def set_vp(self, v, vertex_property, value): def vertex_properties(self): props = set() - for v in self.nodes(): + for v in itertools.islice(self.nodes(), 1): props.update(self.node[v].keys()) return props def edge_properties(self): props = set() - for e in self.edges(): + for e in itertools.islice(self.edges(), 1): props.update(self.adj[e[0]][e[1]].keys()) return props From ba601e7fdcd28c8713b1a199ec6cd35fbb0adb0f Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 05:02:21 -0500 Subject: [PATCH 17/60] Add flake8 preferences to setup.cfg and ignore long lines --- queueing_tool/network/queue_network.py | 6 +++--- setup.cfg | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 52bc3de..a3ab5fd 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -266,7 +266,7 @@ class QueueNetwork(object): to have pygraphviz installed and your graph may be rotated): >>> net.simulate(n=500) - >>> pos = nx.nx_agraph.graphviz_layout(g.to_undirected(), prog='neato') # doctest: +SKIP + >>> pos = nx.nx_agraph.graphviz_layout(g.to_undirected(), prog='neato') # doctest: +SKIP # noqa: E501 >>> net.draw(pos=pos) # doctest: +SKIP <...> @@ -1197,7 +1197,7 @@ def show_type(self, edge_type, **kwargs): if self.g.is_edge(e) and self.g.ep(e, 'edge_type') == edge_type: ei = self.g.edge_index[e] self.g.set_vp(v, 'vertex_fill_color', self.colors['vertex_highlight']) - self.g.set_vp(v, 'vertex_color', self.edge2queue[ei].colors['vertex_color']) + self.g.set_vp(v, 'vertex_color', self.edge2queue[ei].colors['vertex_color']) # noqa: E501 else: self.g.set_vp(v, 'vertex_fill_color', self.colors['vertex_inactive']) self.g.set_vp(v, 'vertex_color', [0, 0, 0, 0.9]) @@ -1500,7 +1500,7 @@ def transitions(self, return_matrix=True): else: mat = { k: {e[1]: p for e, p in zip(self.g.out_edges(k), value)} - for k, value in enumerate(self._route_probs) + for k, value in self._route_probs.items() } return mat diff --git a/setup.cfg b/setup.cfg index 58f8c99..48a12d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,6 @@ addopts = --doctest-modules --color=yes --capture=no doctest_optionflags = NORMALIZE_WHITESPACE testpaths = tests queueing_tool + +[flake8] +max-line-length = 90 From 6abba51a542a8dddd91fa6b2f1a5a2872d565dec Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 05:34:59 -0500 Subject: [PATCH 18/60] Use pytests throughout queue network tests --- tests/test_network.py | 438 +++++++++++++++++++++--------------------- 1 file changed, 222 insertions(+), 216 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index b009051..79dcf27 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -1,5 +1,4 @@ import os -import unittest try: import unittest.mock as mock except ImportError: @@ -13,6 +12,7 @@ import networkx as nx import numpy as np +import pytest import queueing_tool as qt @@ -20,35 +20,41 @@ TRAVIS_TEST = os.environ.get('TRAVIS_TEST', False) -class TestQueueNetwork(unittest.TestCase): +@pytest.fixture(scope='module') +def queue_network(): + g = qt.generate_pagerank_graph(200) + qn = qt.QueueNetwork(g) + qn.g.draw_graph = mock.MagicMock() + qn.max_agents = 2000 + qn.initialize(50) + return qn - @classmethod - def setUpClass(cls): - cls.g = qt.generate_pagerank_graph(200) - cls.qn = qt.QueueNetwork(cls.g) - cls.qn.g.draw_graph = mock.MagicMock() - cls.qn.max_agents = 2000 - cls.qn.initialize(50) - def tearDown(self): - self.qn.clear() - self.qn.initialize(50) +@pytest.fixture +def clear_queue_network(queue_network): + yield + queue_network.clear() + queue_network.initialize(50) - def test_QueueNetwork_accounting(self): + +@pytest.mark.usefixtures('clear_queue_network') +class TestQueueNetwork(object): + + def test_QueueNetwork_accounting(self, queue_network): num_events = 2500 ans = np.zeros(num_events, bool) - na = np.zeros(self.qn.nE, int) - for q in self.qn.edge2queue: + na = np.zeros(queue_network.nE, int) + for q in queue_network.edge2queue: na[q.edge[2]] = len(q._arrivals) + len(q._departures) + len(q.queue) - 2 for k in range(num_events): - ans[k] = (self.qn.num_agents == na).all() - self.qn.simulate(n=1) - for q in self.qn.edge2queue: + ans[k] = (queue_network.num_agents == na).all() + queue_network.simulate(n=1) + for q in queue_network.edge2queue: na[q.edge[2]] = len(q._arrivals) + len(q._departures) + len(q.queue) - 2 - self.assertTrue(ans.all()) + assert ans.all() def test_QueueNetwork_add_arrival(self): @@ -71,16 +77,16 @@ def test_QueueNetwork_add_arrival(self): trans = qn.transitions(False) - self.assertAlmostEqual(trans[1][2], p0, 2) - self.assertAlmostEqual(trans[1][3], p1, 2) + assert np.isclose(trans[1][2], p0, atol=1e-2) + assert np.isclose(trans[1][3], p1, atol=1e-2) - def test_QueueNetwork_animate(self): + def test_QueueNetwork_animate(self, queue_network): if not HAS_MATPLOTLIB: with mock.patch('queueing_tool.network.queue_network.plt.show'): - self.qn.animate(frames=5) + queue_network.animate(frames=5) else: plt.switch_backend('Agg') - self.qn.animate(frames=5) + queue_network.animate(frames=5) def test_QueueNetwork_blocking(self): @@ -103,33 +109,33 @@ def test_QueueNetwork_blocking(self): qn = qt.QueueNetwork(g, q_classes=q_cls, q_args=q_arg, seed=17) qn.blocking = 'RS' - self.assertEqual(qn.blocking, 'RS') - self.assertEqual(qn._blocking, False) + assert qn.blocking == 'RS' + assert qn._blocking is False qn.clear() - self.assertEqual(qn._initialized, False) + assert qn._initialized is False - def test_QueueNetwork_blocking_setter_error(self): - self.qn.blocking = 'RS' - with self.assertRaises(TypeError): - self.qn.blocking = 2 + def test_QueueNetwork_blocking_setter_error(self, queue_network): + queue_network.blocking = 'RS' + with pytest.raises(TypeError): + queue_network.blocking = 2 - def test_QueueNetwork_closedness(self): + def test_QueueNetwork_closedness(self, queue_network): num_events = 2500 ans = np.zeros(num_events, bool) - na = np.zeros(self.qn.nE, int) - for q in self.qn.edge2queue: + na = np.zeros(queue_network.nE, int) + for q in queue_network.edge2queue: na[q.edge[2]] = len(q._arrivals) + len(q._departures) + len(q.queue) - 2 for k in range(num_events): - ans[k] = np.sum(self.qn.num_agents) >= np.sum(na) - for q in self.qn.edge2queue: + ans[k] = np.sum(queue_network.num_agents) >= np.sum(na) + for q in queue_network.edge2queue: na[q.edge[2]] = len(q._arrivals) + len(q._departures) + len(q.queue) - 2 - self.qn.simulate(n=1) + queue_network.simulate(n=1) - self.assertTrue(ans.all()) + assert ans.all() def test_QueueNetwork_copy(self): @@ -156,57 +162,57 @@ def test_QueueNetwork_copy(self): stamp = [(q.num_arrivals, q.time) for q in qn2.edge2queue] qn2.simulate(n=25000) - self.assertFalse(qn.current_time == qn2.current_time) - self.assertFalse(qn.time == qn2.time) + assert qn.current_time != qn2.current_time + assert qn.time != qn2.time ans = [] for k, q in enumerate(qn2.edge2queue): if stamp[k][1] != q.time: ans.append(q.time != qn.edge2queue[k].time) - self.assertTrue(np.array(ans).all()) + assert np.array(ans).all() @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_drawing(self): + def test_QueueNetwork_drawing(self, queue_network): scatter_kwargs = {'c': 'b'} kwargs = {'bgcolor': 'green'} - self.qn.draw(scatter_kwargs=scatter_kwargs, **kwargs) - self.qn.g.draw_graph.assert_called_with(scatter_kwargs=scatter_kwargs, - line_kwargs=None, **kwargs) + queue_network.draw(scatter_kwargs=scatter_kwargs, **kwargs) + queue_network.g.draw_graph.assert_called_with(scatter_kwargs=scatter_kwargs, + line_kwargs=None, **kwargs) - self.qn.draw(scatter_kwargs=scatter_kwargs) - bgcolor = self.qn.colors['bgcolor'] - self.qn.g.draw_graph.assert_called_with(scatter_kwargs=scatter_kwargs, - line_kwargs=None, bgcolor=bgcolor) + queue_network.draw(scatter_kwargs=scatter_kwargs) + bgcolor = queue_network.colors['bgcolor'] + queue_network.g.draw_graph.assert_called_with(scatter_kwargs=scatter_kwargs, + line_kwargs=None, bgcolor=bgcolor) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', False) - def test_QueueNetwork_drawing_importerror(self): - with self.assertRaises(ImportError): - self.qn.draw() + def test_QueueNetwork_drawing_importerror(self, queue_network): + with pytest.raises(ImportError): + queue_network.draw() - def test_QueueNetwork_drawing_animation_error(self): - self.qn.clear() - with self.assertRaises(qt.QueueingToolError): - self.qn.animate() + def test_QueueNetwork_drawing_animation_error(self, queue_network): + queue_network.clear() + with pytest.raises(qt.QueueingToolError): + queue_network.animate() - self.qn.initialize() + queue_network.initialize() with mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', False): - with self.assertRaises(ImportError): - self.qn.animate() + with pytest.raises(ImportError): + queue_network.animate() def test_QueueNetwork_init_error(self): g = qt.generate_pagerank_graph(7) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): qt.QueueNetwork(g, blocking=2) - def test_QueueNetwork_get_agent_data(self): + def test_QueueNetwork_get_agent_data(self, queue_network): - self.qn.clear() - self.qn.initialize(queues=1) - self.qn.start_collecting_data() - self.qn.simulate(n=20000) + queue_network.clear() + queue_network.initialize(queues=1) + queue_network.start_collecting_data() + queue_network.simulate(n=20000) - data = self.qn.get_agent_data() + data = queue_network.get_agent_data() dat0 = data[(1, 0)] a = dat0[:, 0] @@ -217,10 +223,10 @@ def test_QueueNetwork_get_agent_data(self): b.sort() c.sort() - self.assertTrue((a == dat0[:, 0]).all()) - self.assertTrue((b == dat0[dat0[:, 1] > 0, 1]).all()) - self.assertTrue((c == dat0[dat0[:, 2] > 0, 2]).all()) - self.assertTrue((dat0[1:, 0] == dat0[dat0[:, 2] > 0, 2]).all()) + assert (a == dat0[:, 0]).all() + assert (b == dat0[dat0[:, 1] > 0, 1]).all() + assert (c == dat0[dat0[:, 2] > 0, 2]).all() + assert (dat0[1:, 0] == dat0[dat0[:, 2] > 0, 2]).all() def test_QueueNetwork_get_queue_data(self): @@ -236,12 +242,12 @@ def test_QueueNetwork_get_queue_data(self): qn.simulate(n=k) data = qn.get_queue_data() - self.assertEqual(data.shape, (k, 6)) + assert data.shape == (k, 6) qn.stop_collecting_data() qn.clear_data() ans = np.array([q.data == {} for q in qn.edge2queue]) - self.assertTrue(ans.all()) + assert ans.all() def test_QueueNetwork_greedy_routing(self): @@ -296,155 +302,155 @@ def ser_id(t): qn.simulate(n=1) if qn.next_event_description() == ('Departure', e01): d0 = qn.edge2queue[e01]._departures[0].desired_destination(qn, edg) - a1 = np.argmin([qn.edge2queue[e].number_queued() for e in qn.out_edges[1]]) + a1 = np.argmin([qn.edge2queue[e].number_queued() for e in qn.out_edges[1]]) # noqa: E501 d1 = qn.out_edges[1][a1] ans[c] = d0 == d1 c += 1 - self.assertTrue(ans.all()) + assert ans.all() - def test_QueueNetwork_initialize_Error(self): - self.qn.clear() - with self.assertRaises(ValueError): - self.qn.initialize(nActive=0) + def test_QueueNetwork_initialize_Error(self, queue_network): + queue_network.clear() + with pytest.raises(ValueError): + queue_network.initialize(nActive=0) - with self.assertRaises(TypeError): - self.qn.initialize(nActive=1.6) + with pytest.raises(TypeError): + queue_network.initialize(nActive=1.6) _get_queues_mock = mock.Mock() _get_queues_mock.return_value = [] mock_location = 'queueing_tool.network.queue_network._get_queues' with mock.patch(mock_location, _get_queues_mock): - with self.assertRaises(qt.QueueingToolError): - self.qn.initialize(edge_type=1) + with pytest.raises(qt.QueueingToolError): + queue_network.initialize(edge_type=1) - def test_QueueNetwork_initialization(self): + def test_QueueNetwork_initialization(self, queue_network): # Single edge index - k = np.random.randint(0, self.qn.nE) - self.qn.clear() - self.qn.initialize(queues=k) + k = np.random.randint(0, queue_network.nE) + queue_network.clear() + queue_network.initialize(queues=k) - ans = [q.edge[2] for q in self.qn.edge2queue if q.active] - self.assertEqual(ans, [k]) + ans = [q.edge[2] for q in queue_network.edge2queue if q.active] + assert ans == [k] # Multiple edge indices - k = np.unique(np.random.randint(0, self.qn.nE, 5)) - self.qn.clear() - self.qn.initialize(queues=k) + k = np.unique(np.random.randint(0, queue_network.nE, 5)) + queue_network.clear() + queue_network.initialize(queues=k) - ans = np.array([q.edge[2] for q in self.qn.edge2queue if q.active]) + ans = np.array([q.edge[2] for q in queue_network.edge2queue if q.active]) ans.sort() - self.assertTrue((ans == k).all()) + assert (ans == k).all() # Single edge as edge - k = np.random.randint(0, self.qn.nE) - e = self.qn.edge2queue[k].edge[:2] - self.qn.clear() - self.qn.initialize(edges=e) + k = np.random.randint(0, queue_network.nE) + e = queue_network.edge2queue[k].edge[:2] + queue_network.clear() + queue_network.initialize(edges=e) - ans = [q.edge[2] for q in self.qn.edge2queue if q.active] - self.assertEqual(ans, [k]) + ans = [q.edge[2] for q in queue_network.edge2queue if q.active] + assert ans == [k] # Single edge as tuple - k = np.random.randint(0, self.qn.nE) - e = self.qn.edge2queue[k].edge[:2] - self.qn.clear() - self.qn.initialize(edges=e) + k = np.random.randint(0, queue_network.nE) + e = queue_network.edge2queue[k].edge[:2] + queue_network.clear() + queue_network.initialize(edges=e) - ans = [q.edge[2] for q in self.qn.edge2queue if q.active] - self.assertEqual(ans, [k]) + ans = [q.edge[2] for q in queue_network.edge2queue if q.active] + assert ans == [k] # Multiple edges as tuples - k = np.unique(np.random.randint(0, self.qn.nE, 5)) - es = [self.qn.edge2queue[i].edge[:2] for i in k] - self.qn.clear() - self.qn.initialize(edges=es) + k = np.unique(np.random.randint(0, queue_network.nE, 5)) + es = [queue_network.edge2queue[i].edge[:2] for i in k] + queue_network.clear() + queue_network.initialize(edges=es) - ans = [q.edge[2] for q in self.qn.edge2queue if q.active] - self.assertTrue((ans == k).all()) + ans = [q.edge[2] for q in queue_network.edge2queue if q.active] + assert (ans == k).all() # Multple edges as edges - k = np.unique(np.random.randint(0, self.qn.nE, 5)) - es = [self.qn.edge2queue[i].edge[:2] for i in k] - self.qn.clear() - self.qn.initialize(edges=es) + k = np.unique(np.random.randint(0, queue_network.nE, 5)) + es = [queue_network.edge2queue[i].edge[:2] for i in k] + queue_network.clear() + queue_network.initialize(edges=es) - ans = [q.edge[2] for q in self.qn.edge2queue if q.active] - self.assertTrue((ans == k).all()) + ans = [q.edge[2] for q in queue_network.edge2queue if q.active] + assert (ans == k).all() # Single edge_type k = np.random.randint(1, 4) - self.qn.clear() - self.qn.initialize(edge_type=k) + queue_network.clear() + queue_network.initialize(edge_type=k) - ans = np.array([q.edge[3] == k for q in self.qn.edge2queue if q.active]) - self.assertTrue(ans.all()) + ans = np.array([q.edge[3] == k for q in queue_network.edge2queue if q.active]) + assert ans.all() # Multiple edge_types k = np.unique(np.random.randint(1, 4, 3)) - self.qn.clear() - self.qn.initialize(edge_type=k) + queue_network.clear() + queue_network.initialize(edge_type=k) - ans = np.array([q.edge[3] in k for q in self.qn.edge2queue if q.active]) - self.assertTrue(ans.all()) + ans = np.array([q.edge[3] in k for q in queue_network.edge2queue if q.active]) + assert ans.all() - self.qn.clear() - self.qn.max_agents = 3 - self.qn.initialize(nActive=self.qn.num_edges) - ans = np.array([q.active for q in self.qn.edge2queue]) - self.assertEqual(ans.sum(), 3) + queue_network.clear() + queue_network.max_agents = 3 + queue_network.initialize(nActive=queue_network.num_edges) + ans = np.array([q.active for q in queue_network.edge2queue]) + assert ans.sum() == 3 - def test_QueueNetwork_max_agents(self): + def test_QueueNetwork_max_agents(self, queue_network): num_events = 1500 - self.qn.max_agents = 200 + queue_network.max_agents = 200 ans = np.zeros(num_events, bool) for k in range(num_events // 2): - ans[k] = np.sum(self.qn.num_agents) <= self.qn.max_agents - self.qn.simulate(n=1) + ans[k] = np.sum(queue_network.num_agents) <= queue_network.max_agents + queue_network.simulate(n=1) - self.qn.simulate(n=20000) + queue_network.simulate(n=20000) for k in range(num_events // 2, num_events): - ans[k] = np.sum(self.qn.num_agents) <= self.qn.max_agents - self.qn.simulate(n=1) + ans[k] = np.sum(queue_network.num_agents) <= queue_network.max_agents + queue_network.simulate(n=1) - self.assertTrue(ans.all()) + assert ans.all() - def test_QueueNetwork_properties(self): - self.qn.clear() - self.assertEqual(self.qn.time, np.infty) - self.assertEqual(self.qn.num_edges, self.qn.nE) - self.assertEqual(self.qn.num_vertices, self.qn.nV) - self.assertEqual(self.qn.num_nodes, self.qn.nV) + def test_QueueNetwork_properties(self, queue_network): + queue_network.clear() + assert queue_network.time == np.infty + assert queue_network.num_edges == queue_network.nE + assert queue_network.num_vertices == queue_network.nV + assert queue_network.num_nodes == queue_network.nV - def test_QueueNetwork_set_transitions_Error(self): - with self.assertRaises(ValueError): - self.qn.set_transitions({-1: {0: 0.75, 1: 0.25}}) + def test_QueueNetwork_set_transitions_Error(self, queue_network): + with pytest.raises(ValueError): + queue_network.set_transitions({-1: {0: 0.75, 1: 0.25}}) - with self.assertRaises(ValueError): - self.qn.set_transitions({self.qn.nV: {0: 0.75, 1: 0.25}}) + with pytest.raises(ValueError): + queue_network.set_transitions({queue_network.nV: {0: 0.75, 1: 0.25}}) - with self.assertRaises(ValueError): - self.qn.set_transitions({0: {0: 0.75, 1: -0.25}}) + with pytest.raises(ValueError): + queue_network.set_transitions({0: {0: 0.75, 1: -0.25}}) - with self.assertRaises(ValueError): - self.qn.set_transitions({0: {0: 1.25, 1: -0.25}}) + with pytest.raises(ValueError): + queue_network.set_transitions({0: {0: 1.25, 1: -0.25}}) mat = np.zeros((2, 2)) - with self.assertRaises(ValueError): - self.qn.set_transitions(mat) + with pytest.raises(ValueError): + queue_network.set_transitions(mat) - mat = np.zeros((self.qn.nV, self.qn.nV)) - with self.assertRaises(ValueError): - self.qn.set_transitions(mat) + mat = np.zeros((queue_network.nV, queue_network.nV)) + with pytest.raises(ValueError): + queue_network.set_transitions(mat) mat[0, 0] = -1 mat[0, 1] = 2 - with self.assertRaises(ValueError): - self.qn.set_transitions(mat) + with pytest.raises(ValueError): + queue_network.set_transitions(mat) def test_QueueNetwork_simulate(self): @@ -456,113 +462,113 @@ def test_QueueNetwork_simulate(self): qn.max_agents = 2000 qn.simulate(t=t0) - self.assertGreater(qn.current_time, t0) + assert qn.current_time > t0 - def test_QueueNetwork_simulate_error(self): - self.qn.clear() - with self.assertRaises(qt.QueueingToolError): - self.qn.simulate() + def test_QueueNetwork_simulate_error(self, queue_network): + queue_network.clear() + with pytest.raises(qt.QueueingToolError): + queue_network.simulate() - def test_QueueNetwork_simulate_slow(self): - e = self.qn._fancy_heap.array_edges[0] - edge = self.qn.edge2queue[e].edge + def test_QueueNetwork_simulate_slow(self, queue_network): + e = queue_network._fancy_heap.array_edges[0] + edge = queue_network.edge2queue[e].edge if edge[0] == edge[1]: - for q in self.qn.edge2queue: + for q in queue_network.edge2queue: if q.edge[0] != q.edge[1]: break - self.qn._simulate_next_event(slow=True) + queue_network._simulate_next_event(slow=True) else: - for q in self.qn.edge2queue: + for q in queue_network.edge2queue: if q.edge[0] == q.edge[1]: break - self.qn._simulate_next_event(slow=True) + queue_network._simulate_next_event(slow=True) - self.qn.clear() - self.qn.initialize(queues=[q.edge[2]]) - e = self.qn._fancy_heap.array_edges[0] - edge = self.qn.edge2queue[e].edge + queue_network.clear() + queue_network.initialize(queues=[q.edge[2]]) + e = queue_network._fancy_heap.array_edges[0] + edge = queue_network.edge2queue[e].edge loop = edge[0] == edge[1] - self.qn._simulate_next_event(slow=True) + queue_network._simulate_next_event(slow=True) while True: - e = self.qn._fancy_heap.array_edges[0] - edge = self.qn.edge2queue[e].edge + e = queue_network._fancy_heap.array_edges[0] + edge = queue_network.edge2queue[e].edge if (edge[0] != edge[1]) == loop: - self.qn._simulate_next_event(slow=True) + queue_network._simulate_next_event(slow=True) break else: - self.qn._simulate_next_event(slow=False) + queue_network._simulate_next_event(slow=False) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_show_type(self): + def test_QueueNetwork_show_type(self, queue_network): args = {'c': 'b', 'bgcolor': 'green'} - self.qn.show_type(edge_type=2, **args) - self.qn.g.draw_graph.assert_called_with(scatter_kwargs=None, - line_kwargs=None, **args) + queue_network.show_type(edge_type=2, **args) + queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, + line_kwargs=None, **args) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_show_active(self): + def test_QueueNetwork_show_active(self, queue_network): args = { 'fname': 'types.png', 'figsize': (3, 3), 'bgcolor': 'green' } - self.qn.show_active(**args) - self.qn.g.draw_graph.assert_called_with(scatter_kwargs=None, - line_kwargs=None, **args) + queue_network.show_active(**args) + queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, + line_kwargs=None, **args) - def test_QueueNetwork_sorting(self): + def test_QueueNetwork_sorting(self, queue_network): num_events = 2000 ans = np.zeros(num_events, bool) for k in range(num_events // 2): - queue_times = [q.time for q in self.qn.edge2queue] + queue_times = [q.time for q in queue_network.edge2queue] queue_times.sort() tmp = queue_times[0] - self.qn.simulate(n=1) - ans[k] = (tmp == self.qn._qkey[0]) + queue_network.simulate(n=1) + ans[k] = (tmp == queue_network._qkey[0]) - self.qn.simulate(n=10000) + queue_network.simulate(n=10000) for k in range(num_events // 2, num_events): - queue_times = [q.time for q in self.qn.edge2queue] + queue_times = [q.time for q in queue_network.edge2queue] queue_times.sort() tmp = queue_times[0] - self.qn.simulate(n=1) - ans[k] = (tmp == self.qn._qkey[0]) + queue_network.simulate(n=1) + ans[k] = (tmp == queue_network._qkey[0]) - self.assertTrue(ans.all()) + assert ans.all() - def test_QueueNetwork_transitions(self): + def test_QueueNetwork_transitions(self, queue_network): - degree = [len(self.qn.out_edges[k]) for k in range(self.qn.nV)] + degree = [len(queue_network.out_edges[k]) for k in range(queue_network.nV)] v, deg = np.argmax(degree), max(degree) trans = np.random.uniform(size=deg) trans = trans / sum(trans) - probs = {v: {e[1]: p for e, p in zip(self.qn.g.out_edges(v), trans)}} + probs = {v: {e[1]: p for e, p in zip(queue_network.g.out_edges(v), trans)}} - self.qn.set_transitions(probs) - mat = self.qn.transitions() - tra = mat[v, [e[1] for e in self.qn.g.out_edges(v)]] + queue_network.set_transitions(probs) + mat = queue_network.transitions() + tra = mat[v, [e[1] for e in queue_network.g.out_edges(v)]] - self.assertTrue((tra == trans).all()) + assert (tra == trans).all() - tra = self.qn.transitions(return_matrix=False) - tra = np.array([tra[v][e[1]] for e in self.qn.g.out_edges(v)]) - self.assertTrue((tra == trans).all()) + tra = queue_network.transitions(return_matrix=False) + tra = np.array([tra[v][e[1]] for e in queue_network.g.out_edges(v)]) + assert (tra == trans).all() - mat = qt.generate_transition_matrix(self.g) - self.qn.set_transitions(mat) - tra = self.qn.transitions() + mat = qt.generate_transition_matrix(queue_network.g) + queue_network.set_transitions(mat) + tra = queue_network.transitions() - self.assertTrue(np.allclose(tra, mat)) + assert np.allclose(tra, mat) - mat = qt.generate_transition_matrix(self.g) - self.qn.set_transitions({v: {e[1]: mat[e] for e in self.qn.g.out_edges(v)}}) - tra = self.qn.transitions() + mat = qt.generate_transition_matrix(queue_network.g) + queue_network.set_transitions({v: {e[1]: mat[e] for e in queue_network.g.out_edges(v)}}) # noqa: E501 + tra = queue_network.transitions() - self.assertTrue(np.allclose(tra[v], mat[v])) + assert np.allclose(tra[v], mat[v]) From 58b1ef8df6770ef1fc5e085590f3d69990a5310b Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 05:40:29 -0500 Subject: [PATCH 19/60] Make tests less costly, simplify naming --- tests/test_network.py | 76 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index 79dcf27..790d20f 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -40,9 +40,9 @@ def clear_queue_network(queue_network): @pytest.mark.usefixtures('clear_queue_network') class TestQueueNetwork(object): - def test_QueueNetwork_accounting(self, queue_network): + def test_accounting(self, queue_network): - num_events = 2500 + num_events = 1500 ans = np.zeros(num_events, bool) na = np.zeros(queue_network.nE, int) for q in queue_network.edge2queue: @@ -56,7 +56,7 @@ def test_QueueNetwork_accounting(self, queue_network): assert ans.all() - def test_QueueNetwork_add_arrival(self): + def test_add_arrival(self): adj = {0: [1], 1: [2, 3]} g = qt.adjacency2graph(adj) @@ -67,7 +67,7 @@ def test_QueueNetwork_add_arrival(self): qn.initialize(edges=(0, 1)) qn.start_collecting_data(edge=[(1, 2), (1, 3)]) - qn.simulate(150000) + qn.simulate(15000) data = qn.get_queue_data(edge=[(1, 2), (1, 3)]) e0, e1 = qn.out_edges[1] @@ -77,10 +77,10 @@ def test_QueueNetwork_add_arrival(self): trans = qn.transitions(False) - assert np.isclose(trans[1][2], p0, atol=1e-2) - assert np.isclose(trans[1][3], p1, atol=1e-2) + assert np.isclose(trans[1][2], p0, atol=1e-1) + assert np.isclose(trans[1][3], p1, atol=1e-1) - def test_QueueNetwork_animate(self, queue_network): + def test_animate(self, queue_network): if not HAS_MATPLOTLIB: with mock.patch('queueing_tool.network.queue_network.plt.show'): queue_network.animate(frames=5) @@ -88,7 +88,7 @@ def test_QueueNetwork_animate(self, queue_network): plt.switch_backend('Agg') queue_network.animate(frames=5) - def test_QueueNetwork_blocking(self): + def test_blocking(self): g = nx.random_geometric_graph(100, 0.2).to_directed() g = qt.set_types_random(g, proportions={k: 1.0 / 6 for k in range(1, 7)}) @@ -115,12 +115,12 @@ def test_QueueNetwork_blocking(self): qn.clear() assert qn._initialized is False - def test_QueueNetwork_blocking_setter_error(self, queue_network): + def test_blocking_setter_error(self, queue_network): queue_network.blocking = 'RS' with pytest.raises(TypeError): queue_network.blocking = 2 - def test_QueueNetwork_closedness(self, queue_network): + def test_closedness(self, queue_network): num_events = 2500 ans = np.zeros(num_events, bool) @@ -137,7 +137,7 @@ def test_QueueNetwork_closedness(self, queue_network): assert ans.all() - def test_QueueNetwork_copy(self): + def test_copy(self): g = nx.random_geometric_graph(100, 0.2).to_directed() g = qt.set_types_random(g, proportions={k: 0.2 for k in range(1, 6)}) @@ -150,17 +150,17 @@ def test_QueueNetwork_copy(self): } q_arg = {3: {'net_size': g.number_of_edges()}, - 4: {'num_servers': 500}} + 4: {'num_servers': 50}} qn = qt.QueueNetwork(g, q_classes=q_cls, q_args=q_arg, seed=17) qn.max_agents = np.infty qn.initialize(queues=range(g.number_of_edges())) - qn.simulate(n=50000) + qn.simulate(n=5000) qn2 = qn.copy() stamp = [(q.num_arrivals, q.time) for q in qn2.edge2queue] - qn2.simulate(n=25000) + qn2.simulate(n=2500) assert qn.current_time != qn2.current_time assert qn.time != qn2.time @@ -173,7 +173,7 @@ def test_QueueNetwork_copy(self): assert np.array(ans).all() @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_drawing(self, queue_network): + def test_drawing(self, queue_network): scatter_kwargs = {'c': 'b'} kwargs = {'bgcolor': 'green'} queue_network.draw(scatter_kwargs=scatter_kwargs, **kwargs) @@ -186,11 +186,11 @@ def test_QueueNetwork_drawing(self, queue_network): line_kwargs=None, bgcolor=bgcolor) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', False) - def test_QueueNetwork_drawing_importerror(self, queue_network): + def test_drawing_importerror(self, queue_network): with pytest.raises(ImportError): queue_network.draw() - def test_QueueNetwork_drawing_animation_error(self, queue_network): + def test_drawing_animation_error(self, queue_network): queue_network.clear() with pytest.raises(qt.QueueingToolError): queue_network.animate() @@ -200,17 +200,17 @@ def test_QueueNetwork_drawing_animation_error(self, queue_network): with pytest.raises(ImportError): queue_network.animate() - def test_QueueNetwork_init_error(self): + def test_init_error(self): g = qt.generate_pagerank_graph(7) with pytest.raises(TypeError): qt.QueueNetwork(g, blocking=2) - def test_QueueNetwork_get_agent_data(self, queue_network): + def test_get_agent_data(self, queue_network): queue_network.clear() queue_network.initialize(queues=1) queue_network.start_collecting_data() - queue_network.simulate(n=20000) + queue_network.simulate(n=2000) data = queue_network.get_agent_data() dat0 = data[(1, 0)] @@ -228,15 +228,15 @@ def test_QueueNetwork_get_agent_data(self, queue_network): assert (c == dat0[dat0[:, 2] > 0, 2]).all() assert (dat0[1:, 0] == dat0[dat0[:, 2] > 0, 2]).all() - def test_QueueNetwork_get_queue_data(self): + def test_get_queue_data(self): g = nx.random_geometric_graph(50, 0.5).to_directed() q_cls = {1: qt.QueueServer} qn = qt.QueueNetwork(g, q_classes=q_cls, seed=17) - k = np.random.randint(10000, 20000) + k = np.random.randint(1000, 2000) - qn.max_agents = 4000 + qn.max_agents = 400 qn.initialize(queues=range(qn.nE)) qn.start_collecting_data() qn.simulate(n=k) @@ -249,7 +249,7 @@ def test_QueueNetwork_get_queue_data(self): ans = np.array([q.data == {} for q in qn.edge2queue]) assert ans.all() - def test_QueueNetwork_greedy_routing(self): + def test_greedy_routing(self): lam = np.random.randint(1, 10) + 0.0 rho = np.random.uniform(0.75, 1) @@ -290,9 +290,9 @@ def ser_id(t): qn = qt.QueueNetwork(g, q_classes=qcl, q_args=arg) qn.initialize(edges=(0, 1)) - qn.max_agents = 5000 + qn.max_agents = 500 - num_events = 1000 + num_events = 100 ans = np.zeros(num_events, bool) e01 = qn.g.edge_index[(0, 1)] edg = qn.edge2queue[e01].edge @@ -309,7 +309,7 @@ def ser_id(t): assert ans.all() - def test_QueueNetwork_initialize_Error(self, queue_network): + def test_initialize_error(self, queue_network): queue_network.clear() with pytest.raises(ValueError): queue_network.initialize(nActive=0) @@ -325,7 +325,7 @@ def test_QueueNetwork_initialize_Error(self, queue_network): with pytest.raises(qt.QueueingToolError): queue_network.initialize(edge_type=1) - def test_QueueNetwork_initialization(self, queue_network): + def test_initialization(self, queue_network): # Single edge index k = np.random.randint(0, queue_network.nE) queue_network.clear() @@ -401,7 +401,7 @@ def test_QueueNetwork_initialization(self, queue_network): ans = np.array([q.active for q in queue_network.edge2queue]) assert ans.sum() == 3 - def test_QueueNetwork_max_agents(self, queue_network): + def test_max_agents(self, queue_network): num_events = 1500 queue_network.max_agents = 200 @@ -419,14 +419,14 @@ def test_QueueNetwork_max_agents(self, queue_network): assert ans.all() - def test_QueueNetwork_properties(self, queue_network): + def test_properties(self, queue_network): queue_network.clear() assert queue_network.time == np.infty assert queue_network.num_edges == queue_network.nE assert queue_network.num_vertices == queue_network.nV assert queue_network.num_nodes == queue_network.nV - def test_QueueNetwork_set_transitions_Error(self, queue_network): + def test_set_transitions_error(self, queue_network): with pytest.raises(ValueError): queue_network.set_transitions({-1: {0: 0.75, 1: 0.25}}) @@ -452,7 +452,7 @@ def test_QueueNetwork_set_transitions_Error(self, queue_network): with pytest.raises(ValueError): queue_network.set_transitions(mat) - def test_QueueNetwork_simulate(self): + def test_simulate(self): g = qt.generate_pagerank_graph(50) qn = qt.QueueNetwork(g) @@ -464,12 +464,12 @@ def test_QueueNetwork_simulate(self): assert qn.current_time > t0 - def test_QueueNetwork_simulate_error(self, queue_network): + def test_simulate_error(self, queue_network): queue_network.clear() with pytest.raises(qt.QueueingToolError): queue_network.simulate() - def test_QueueNetwork_simulate_slow(self, queue_network): + def test_simulate_slow(self, queue_network): e = queue_network._fancy_heap.array_edges[0] edge = queue_network.edge2queue[e].edge @@ -503,14 +503,14 @@ def test_QueueNetwork_simulate_slow(self, queue_network): queue_network._simulate_next_event(slow=False) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_show_type(self, queue_network): + def test_show_type(self, queue_network): args = {'c': 'b', 'bgcolor': 'green'} queue_network.show_type(edge_type=2, **args) queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, line_kwargs=None, **args) @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_QueueNetwork_show_active(self, queue_network): + def test_show_active(self, queue_network): args = { 'fname': 'types.png', 'figsize': (3, 3), @@ -520,7 +520,7 @@ def test_QueueNetwork_show_active(self, queue_network): queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, line_kwargs=None, **args) - def test_QueueNetwork_sorting(self, queue_network): + def test_sorting(self, queue_network): num_events = 2000 ans = np.zeros(num_events, bool) @@ -542,7 +542,7 @@ def test_QueueNetwork_sorting(self, queue_network): assert ans.all() - def test_QueueNetwork_transitions(self, queue_network): + def test_transitions(self, queue_network): degree = [len(queue_network.out_edges[k]) for k in range(queue_network.nV)] v, deg = np.argmax(degree), max(degree) From b0f44c809886b8219a6698eef72c8566fba15ac6 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 06:13:16 -0500 Subject: [PATCH 20/60] Use pytests some more in queue server test file --- tests/test_network.py | 58 ++++----------- tests/test_queue_server.py | 147 +++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 105 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index 790d20f..c4eb28e 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -325,7 +325,7 @@ def test_initialize_error(self, queue_network): with pytest.raises(qt.QueueingToolError): queue_network.initialize(edge_type=1) - def test_initialization(self, queue_network): + def test_initialization_single_edge_index(self, queue_network): # Single edge index k = np.random.randint(0, queue_network.nE) queue_network.clear() @@ -334,6 +334,7 @@ def test_initialization(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert ans == [k] + def test_initialization_multiple_edge_index(self, queue_network): # Multiple edge indices k = np.unique(np.random.randint(0, queue_network.nE, 5)) queue_network.clear() @@ -343,6 +344,7 @@ def test_initialization(self, queue_network): ans.sort() assert (ans == k).all() + def test_initialization_single_edge(self, queue_network): # Single edge as edge k = np.random.randint(0, queue_network.nE) e = queue_network.edge2queue[k].edge[:2] @@ -352,15 +354,7 @@ def test_initialization(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert ans == [k] - # Single edge as tuple - k = np.random.randint(0, queue_network.nE) - e = queue_network.edge2queue[k].edge[:2] - queue_network.clear() - queue_network.initialize(edges=e) - - ans = [q.edge[2] for q in queue_network.edge2queue if q.active] - assert ans == [k] - + def test_initialization_multiple_edges(self, queue_network): # Multiple edges as tuples k = np.unique(np.random.randint(0, queue_network.nE, 5)) es = [queue_network.edge2queue[i].edge[:2] for i in k] @@ -370,15 +364,7 @@ def test_initialization(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert (ans == k).all() - # Multple edges as edges - k = np.unique(np.random.randint(0, queue_network.nE, 5)) - es = [queue_network.edge2queue[i].edge[:2] for i in k] - queue_network.clear() - queue_network.initialize(edges=es) - - ans = [q.edge[2] for q in queue_network.edge2queue if q.active] - assert (ans == k).all() - + def test_initialization_single_edge_type(self, queue_network): # Single edge_type k = np.random.randint(1, 4) queue_network.clear() @@ -387,6 +373,7 @@ def test_initialization(self, queue_network): ans = np.array([q.edge[3] == k for q in queue_network.edge2queue if q.active]) assert ans.all() + def test_initialization_multiple_edge_types(self, queue_network): # Multiple edge_types k = np.unique(np.random.randint(1, 4, 3)) queue_network.clear() @@ -395,6 +382,7 @@ def test_initialization(self, queue_network): ans = np.array([q.edge[3] in k for q in queue_network.edge2queue if q.active]) assert ans.all() + def test_initialization_num_active_edges(self, queue_network): queue_network.clear() queue_network.max_agents = 3 queue_network.initialize(nActive=queue_network.num_edges) @@ -426,29 +414,15 @@ def test_properties(self, queue_network): assert queue_network.num_vertices == queue_network.nV assert queue_network.num_nodes == queue_network.nV - def test_set_transitions_error(self, queue_network): - with pytest.raises(ValueError): - queue_network.set_transitions({-1: {0: 0.75, 1: 0.25}}) - - with pytest.raises(ValueError): - queue_network.set_transitions({queue_network.nV: {0: 0.75, 1: 0.25}}) - - with pytest.raises(ValueError): - queue_network.set_transitions({0: {0: 0.75, 1: -0.25}}) - - with pytest.raises(ValueError): - queue_network.set_transitions({0: {0: 1.25, 1: -0.25}}) - - mat = np.zeros((2, 2)) - with pytest.raises(ValueError): - queue_network.set_transitions(mat) - - mat = np.zeros((queue_network.nV, queue_network.nV)) - with pytest.raises(ValueError): - queue_network.set_transitions(mat) - - mat[0, 0] = -1 - mat[0, 1] = 2 + @pytest.mark.parametrize('mat', [ + {-1: {0: 0.75, 1: 0.25}}, + {200: {0: 0.75, 1: 0.25}}, + {0: {0: 0.75, 1: -0.25}}, + {0: {0: 1.25, 1: -0.25}}, + np.zeros((2, 2)), + np.zeros((200, 200)), + ]) + def test_set_transitions_error(self, mat, queue_network): with pytest.raises(ValueError): queue_network.set_transitions(mat) diff --git a/tests/test_queue_server.py b/tests/test_queue_server.py index 2448b5a..7a3f333 100644 --- a/tests/test_queue_server.py +++ b/tests/test_queue_server.py @@ -1,26 +1,32 @@ import functools -import unittest import networkx as nx import numpy as np +import pytest import queueing_tool as qt -class TestQueueServers(unittest.TestCase): +@pytest.fixture +def lam(): + return float(np.random.randint(1, 10)) - def setUp(self): - self.lam = np.random.randint(1, 10) + 0.0 - self.rho = np.random.uniform(0.5, 1) - def test_QueueServer_init_errors(self): - with self.assertRaises(TypeError): +@pytest.fixture +def rho(): + return np.random.uniform(0.5, 1) + + +class TestQueueServer(object): + + def test_init_errors(self): + with pytest.raises(TypeError): qt.QueueServer(num_servers=3.0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): qt.QueueServer(num_servers=0) - def test_QueueServer_set_num_servers(self): + def test_set_num_servers(self): nSe = np.random.randint(1, 10) q = qt.QueueServer(num_servers=nSe) @@ -31,31 +37,31 @@ def test_QueueServer_set_num_servers(self): Se2 = q.num_servers q.set_num_servers(np.infty) - self.assertEqual(Se1, nSe) - self.assertEqual(Se2, 2 * nSe) - self.assertTrue(q.num_servers is np.inf) + assert Se1 == nSe + assert Se2 == 2 * nSe + assert q.num_servers is np.inf - def test_QueueServer_set_num_servers_errors(self): + def test_set_num_servers_errors(self): q = qt.QueueServer(num_servers=3) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): q.set_num_servers(3.0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): q.set_num_servers(0) - def test_QueueServer_set_inactive(self): + def test_set_inactive(self): q = qt.QueueServer() q.set_active() - a = q.active + was_active = q.active q.set_inactive() - self.assertTrue(a) - self.assertTrue(not q.active) + assert was_active + assert not q.active - def test_QueueServer_copy(self): + def test_copy(self): q1 = qt.QueueServer(seed=15) q1.set_active() @@ -65,9 +71,9 @@ def test_QueueServer_copy(self): t = q1.time q2.simulate(t=20) - self.assertTrue(t < q2.time) + assert t < q2.time - def test_QueueServer_active_cap(self): + def test_active_cap(self): def r(t): return 2 + np.sin(t) @@ -78,16 +84,16 @@ def r(t): q.set_active() q.simulate(n=3000) - self.assertEqual(q.num_departures, 1000) - self.assertEqual(q.num_arrivals, [1000, 1000]) + assert q.num_departures == 1000 + assert q.num_arrivals == [1000, 1000] - def test_QueueServer_accounting(self): + def test_accounting(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.exponential(1 / mu) @@ -106,22 +112,24 @@ def ser(t): ans[k, 2] = len(q._departures) - 1 <= q.num_servers q.simulate(n=1) - self.assertTrue(ans.all()) + assert ans.all() - def test_QueueServer_deactivate(self): + def test_deactivate(self): q = qt.QueueServer(num_servers=3, deactive_t=10) q.set_active() - self.assertTrue(q.active) + + assert q.active + q.simulate(t=10) - self.assertFalse(q.active) + assert not q.active - def test_QueueServer_simulation(self): + def test_simulation(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.exponential(1 / mu) @@ -153,15 +161,18 @@ def ser(t): q.simulate(t=t) ans[3] = q.current_time - t0 >= t - self.assertTrue(ans.all()) + assert ans.all() + + +class TestLossQueueServer(object): - def test_LossQueue_accounting(self): + def test_accounting(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.exponential(1 / mu) @@ -180,17 +191,17 @@ def ser(t): ans[k, 2] = len(q._departures) - 1 <= q.num_servers q.simulate(n=1) - self.assertTrue(ans.all()) + assert ans.all() - def test_LossQueue_blocking(self): + def test_blocking(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) k = np.random.randint(5, 15) scl = 1 / (mu * k) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.gamma(k, scl) @@ -210,9 +221,12 @@ def ser(t): else: q.simulate(n=1) - self.assertTrue(ans.all()) + assert ans.all() - def test_NullQueue_data_collection(self): + +class TestNullQueueServer(object): + + def test_data_collection(self): adj = { 0: {1: {'edge_type': 1}}, 1: {2: {'edge_type': 2}, @@ -226,16 +240,18 @@ def test_NullQueue_data_collection(self): qn = qt.QueueNetwork(g, q_classes=qcl) qn.initialize(edges=(0, 1)) qn.start_collecting_data(edge_type=2) - qn.max_agents = 5000 - qn.simulate(n=10000) + qn.max_agents = 500 + qn.simulate(n=1000) data = qn.get_queue_data() # Data collected by NullQueues do not have departure and # service start times in the data + assert not data[:, (1, 2)].any() + - self.assertFalse(data[:, (1, 2)].any()) +class TestResourceQueueServer(object): - def test_ResourceQueue_network(self): + def test_network(self): g = nx.random_geometric_graph(100, 0.2).to_directed() q_cls = {1: qt.ResourceQueue, 2: qt.ResourceQueue} @@ -248,9 +264,9 @@ def test_ResourceQueue_network(self): nServ = {1: 50, 2: 500} ans = np.array([q.num_servers != nServ[q.edge[3]] for q in qn.edge2queue]) - self.assertTrue(ans.any()) + assert ans.any() - def test_ResourceQueue_network_data_collection(self): + def test_network_data_collection(self): g = qt.generate_random_graph(100) q_cls = {1: qt.ResourceQueue, 2: qt.ResourceQueue} q_arg = {1: {'num_servers': 500}, @@ -264,27 +280,29 @@ def test_ResourceQueue_network_data_collection(self): qn.simulate(n=5000) data = qn.get_queue_data() - self.assertTrue(len(data) > 0) + assert len(data) > 0 - def test_ResourceQueue_network_current_color(self): + def test_network_current_color(self): q = qt.ResourceQueue(num_servers=50) ans = q._current_color(0) col = q.colors['vertex_fill_color'] col = [i * (0.9 - 1. / 6) / 0.9 for i in col] col[3] = 1.0 - self.assertEqual(ans, col) + assert ans == col ans = q._current_color(1) col = q.colors['edge_loop_color'] col = [i * (0.9 - 1. / 6) / 0.9 for i in col] col[3] = 0 - self.assertEqual(ans, col) + assert ans == col ans = q._current_color(2) col = q.colors['vertex_pen_color'] - self.assertEqual(ans, col) + assert ans == col - def test_InfoQueue_network(self): + +class TestInfoQueueServer(object): + def test_network(self): g = nx.random_geometric_graph(100, 0.2).to_directed() q_cls = {1: qt.InfoQueue} @@ -296,18 +314,21 @@ def test_InfoQueue_network(self): qn.simulate(n=2000) # Finish this - self.assertTrue(True) + assert True + + +class TestAgents(object): - def test_Agent_compare(self): + def test_compare(self): a0 = qt.Agent() a1 = qt.Agent() - self.assertEqual(a0, a1) + assert a0 == a1 a1._time = 10 - self.assertLessEqual(a0, a1) - self.assertLess(a0, a1) + assert a0 <= a1 + assert a0 < a1 a0._time = 20 - self.assertGreaterEqual(a0, a1) - self.assertGreater(a0, a1) + assert a0 >= a1 + assert a0 > a1 From 37b039cf9624dd5f005f8ebdc59161a552a21657 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 16 Dec 2017 06:23:06 -0500 Subject: [PATCH 21/60] Move doctest comment to next line --- queueing_tool/network/queue_network.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index a3ab5fd..823f019 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -266,8 +266,9 @@ class QueueNetwork(object): to have pygraphviz installed and your graph may be rotated): >>> net.simulate(n=500) - >>> pos = nx.nx_agraph.graphviz_layout(g.to_undirected(), prog='neato') # doctest: +SKIP # noqa: E501 - >>> net.draw(pos=pos) # doctest: +SKIP + >>> pos = nx.nx_agraph.graphviz_layout(g.to_undirected(), prog='neato') + ... # doctest: +SKIP + >>> net.draw(pos=pos) # doctest: +SKIP <...> .. figure:: my_network1.png From be0d3aa97d5d8dcbd59ae95fe5911be0a70a0973 Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 17 Dec 2017 11:39:54 -0500 Subject: [PATCH 22/60] Use pytest for testing --- tests/test_graph_generation.py | 64 +++++------ tests/test_statistical_properties.py | 160 +++++++++++++-------------- 2 files changed, 108 insertions(+), 116 deletions(-) diff --git a/tests/test_graph_generation.py b/tests/test_graph_generation.py index b6abfd1..8ac67b7 100644 --- a/tests/test_graph_generation.py +++ b/tests/test_graph_generation.py @@ -1,42 +1,25 @@ -import unittest - -from numpy.random import randint import networkx as nx import numpy as np +import pytest import queueing_tool as qt def generate_adjacency(a=3, b=25, c=6, n=12): ans = {} - for k in np.unique(randint(a, b, n)): - ans[k] = {j: {} for j in randint(a, b, randint(1, c))} + for k in np.unique(np.random.randint(a, b, n)): + ans[k] = {j: {} for j in np.random.randint(a, b, np.random.randint(1, c))} return ans -class TestGraphFunctions(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.expected_response0 = { - 0: {1: {'edge_type': 5}}, - 1: {2: {'edge_type': 9}, 3: {'edge_type': 14}}, - 2: {0: {'edge_type': 1}}, - 3: {3: {'edge_type': 0}} - } - cls.expected_response1 = { - 0: {1: {'edge_type': 5}}, - 1: {2: {'edge_type': 9}, 3: {'edge_type': 0}}, - 2: {0: {'edge_type': 1}}, - 3: {} - } +class TestGraphFunctions(object): def test_graph2dict(self): adj = generate_adjacency() g1 = qt.adjacency2graph(adj, adjust=2) aj1 = qt.graph2dict(g1) g2 = qt.adjacency2graph(aj1, adjust=2) - self.assertTrue(nx.is_isomorphic(g1, g2)) + assert nx.is_isomorphic(g1, g2) def test_add_edge_lengths(self): g1 = qt.generate_pagerank_graph(10) @@ -46,18 +29,18 @@ def test_add_edge_lengths(self): for key in g2.edge_properties(): edge_props.add(key) - self.assertTrue('edge_length' in edge_props) + assert 'edge_length' in edge_props def test_generate_transition(self): g = qt.generate_random_graph(20) mat = qt.generate_transition_matrix(g) ans = np.sum(mat, axis=1) - self.assertTrue(np.allclose(ans, 1)) + np.testing.assert_allclose(ans, 1) mat = qt.generate_transition_matrix(g, seed=10) ans = np.sum(mat, axis=1) - self.assertTrue(np.allclose(ans, 1)) + np.testing.assert_allclose(ans, 1) def test_adjacency2graph_matrix_adjacency(self): @@ -69,9 +52,14 @@ def test_adjacency2graph_matrix_adjacency(self): ety = {0: {1: 5}, 1: {2: 9, 3: 14}} g = qt.adjacency2graph(adj, edge_type=ety, adjust=2) - ans = qt.graph2dict(g) - - self.assertTrue(ans == self.expected_response1) + actual = qt.graph2dict(g) + expected = { + 0: {1: {'edge_type': 5}}, + 1: {2: {'edge_type': 9}, 3: {'edge_type': 0}}, + 2: {0: {'edge_type': 1}}, + 3: {} + } + assert actual == expected def test_adjacency2graph_matrix_etype(self): # Test adjacency argument using ndarrays work @@ -82,11 +70,17 @@ def test_adjacency2graph_matrix_etype(self): [0, 0, 0, 0]]) g = qt.adjacency2graph(adj, edge_type=ety, adjust=1) - ans = qt.graph2dict(g) - self.assertEqual(ans, self.expected_response0) + actual = qt.graph2dict(g) + expected = { + 0: {1: {'edge_type': 5}}, + 1: {2: {'edge_type': 9}, 3: {'edge_type': 14}}, + 2: {0: {'edge_type': 1}}, + 3: {3: {'edge_type': 0}} + } + assert expected == actual def test_adjacency2graph_errors(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): qt.adjacency2graph([]) def test_set_types_random(self): @@ -107,16 +101,16 @@ def test_set_types_random(self): props = (np.array(mat).sum(1) + 0.0) / len(non_loops) ps = np.array([pType[k] for k in eType]) - self.assertTrue(np.allclose(props, ps, atol=0.01)) + np.testing.assert_allclose(props, ps, atol=0.01) prob[-1] = 2 pType = {eType[k]: prob[k] for k in range(nT)} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): g = qt.set_types_random(g, proportions=pType, seed=10) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): g = qt.set_types_random(g, loop_proportions=pType, seed=10) def test_test_graph_importerror(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): qt.generate_transition_matrix(1) diff --git a/tests/test_statistical_properties.py b/tests/test_statistical_properties.py index f8c979b..ee13e0d 100644 --- a/tests/test_statistical_properties.py +++ b/tests/test_statistical_properties.py @@ -1,42 +1,43 @@ import functools import math -import os -import unittest import numpy as np +import pytest import queueing_tool as qt -TRAVIS_TEST = os.environ.get('TRAVIS_TEST', False) - - def empirical_cdf0(x, z, n): return np.sum(z <= x) / n + empirical_cdf = np.vectorize(empirical_cdf0, excluded={1, 2}) def chi2_cdf(q, k, n=1000000, ns=1): return np.mean([empirical_cdf(q, np.random.chisquare(k, n), n) for i in range(ns)]) -reason = "Test takes long." +@pytest.fixture +def lam(): + return float(np.random.randint(1, 10)) -class TestQueueServers(unittest.TestCase): - def setUp(self): - self.lam = np.random.randint(1, 10) + 0.0 - self.rho = np.random.uniform(0.5, 1) +@pytest.fixture +def rho(): + return np.random.uniform(0.5, 1) - @unittest.skipIf(TRAVIS_TEST, reason) - def test_Markovian_QueueServer(self): + +@pytest.mark.slow +class TestQueueServerStatistically(object): + + def test_markovian_property(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.exponential(1 / mu) @@ -70,23 +71,22 @@ def ser(t): x, y = dep[1:], dep[:-1] cc = np.corrcoef(x, y)[0, 1] - self.assertAlmostEqual(cc, 0, 1) - self.assertGreater(p1, 0.05) + assert np.isclose(cc, 0, atol=1e-1) + assert p1 > 0.05 - @unittest.skipIf(TRAVIS_TEST, reason) - def test_QueueServer_Littleslaw(self): + def test_littles_law(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.exponential(1 / mu) q = qt.QueueServer(num_servers=nSe, arrival_f=arr, service_f=ser) - n = 500000 + n = 250000 q.set_active() q.simulate(n=n) # Burn in period @@ -97,20 +97,19 @@ def ser(t): ind = data[:, 2] > 0 wait = data[ind, 1] - data[ind, 0] - ans = np.mean(wait) * self.lam - np.mean(data[:, 3]) * self.rho + ans = np.mean(wait) * lam - np.mean(data[:, 3]) * rho - self.assertAlmostEqual(ans, 0, 1) + assert np.isclose(ans, 0, atol=1e-1) - @unittest.skipIf(TRAVIS_TEST, reason) - def test_LossQueue_blocking(self): + def test_queue_blocking(self, lam, rho): nSe = np.random.randint(1, 10) - mu = self.lam / (self.rho * nSe) + mu = lam / (rho * nSe) k = np.random.randint(5, 15) scl = 1 / (mu * k) def arr(t): - return t + np.random.exponential(1 / self.lam) + return t + np.random.exponential(1 / lam) def ser(t): return t + np.random.gamma(k, scl) @@ -127,62 +126,61 @@ def ser(t): nA1 = q2.num_arrivals[1] nB1 = q2.num_blocked - a = self.lam / mu + a = lam / mu f = np.array([math.factorial(j) for j in range(nSe + 1)]) pois_pmf = np.exp(-a) * a**nSe / math.factorial(nSe) pois_cdf = np.sum(np.exp(-a) * a**np.arange(nSe + 1) / f) p_block = (nB1 - nB0 + 0.0) / (nA1 - nA0) - self.assertAlmostEqual(pois_pmf / pois_cdf, p_block, 2) - - -class TestRandomMeasure(unittest.TestCase): - - @unittest.skipIf(TRAVIS_TEST, reason) - def test_poisson_random_measure(self): - # This function tests to make sure the poisson_random_measure function - # actually simulates a Poisson random measure. It does so looking for - # Poisson random variables using a chi-squared test (testing the - # composite null hypothesis). It does not look for independence of the - # random variables. - # This test should fail some percentage of the time - - def rate(t): - return 0.5 + 4 * np.sin(np.pi * t / 12)**2 - - arr_f = functools.partial(qt.poisson_random_measure, rate=rate, rate_max=4.5) - - nSamp = 15000 - nArr = 1000 - arrival_times = np.zeros((nSamp, nArr)) - for k in range(nSamp): - t = 0 - for j in range(nArr): - t = arr_f(t) - arrival_times[k, j] = t - if t > 12: - break - - mu1 = 5 * np.sum(rate(np.linspace(3, 8, 200))) / 200 # or 2*(5 + (sqrt(3) + 2) * 3/pi) + 2.5 - mu2 = 4 * np.sum(rate(np.linspace(8, 12, 200))) / 200 # or 2*(4 - 3*sqrt(3)/pi) + 2 - mus = [mu1, mu2] - - rv1 = np.sum(np.logical_and(3 < arrival_times, arrival_times < 8), axis=1) - rv2 = np.sum(np.logical_and(8 < arrival_times, arrival_times < 12), axis=1) - rvs = [rv1, rv2] - df = [max(rv1) + 2, max(rv2) + 2] - - Q = np.zeros((max(df), len(rvs))) - - for i, sample in enumerate(rvs): - for k in range(df[i] - 1): - pi_hat = nSamp * np.exp(-mus[i]) * mus[i]**k / math.factorial(k) - Q[k, i] = (np.sum(sample == k) - pi_hat)**2 / pi_hat - - ans = np.array([math.factorial(j) for j in range(k + 1)]) - pois_cdf = np.sum(np.exp(-mus[i]) * mus[i]**np.arange(k + 1) / ans) - Q[k + 1, i] = nSamp * (1 - pois_cdf) - - Qs = np.sum(Q, axis=0) - p = np.array([1 - chi2_cdf(Qs[i], df[i] - 2) for i in range(len(rvs))]) - self.assertTrue((p > 0.1).any()) + + assert np.isclose(pois_pmf / pois_cdf, p_block, atol=1e-2) + + +@pytest.mark.slow +def test_poisson_random_measure(): + # This function tests to make sure the poisson_random_measure function + # actually simulates a Poisson random measure. It does so looking for + # Poisson random variables using a chi-squared test (testing the + # composite null hypothesis). It does not look for independence of the + # random variables. + # This test should fail some percentage of the time + + def rate(t): + return 0.5 + 4 * np.sin(np.pi * t / 12)**2 + + arr_f = functools.partial(qt.poisson_random_measure, rate=rate, rate_max=4.5) + + nSamp = 15000 + nArr = 1000 + arrival_times = np.zeros((nSamp, nArr)) + for k in range(nSamp): + t = 0 + for j in range(nArr): + t = arr_f(t) + arrival_times[k, j] = t + if t > 12: + break + + mu1 = 5 * np.sum(rate(np.linspace(3, 8, 200))) / 200 # or 2*(5 + (sqrt(3) + 2) * 3 / pi) + 2.5 + mu2 = 4 * np.sum(rate(np.linspace(8, 12, 200))) / 200 # or 2*(4 - 3*sqrt(3)/pi) + 2 + mus = [mu1, mu2] + + rv1 = np.sum(np.logical_and(3 < arrival_times, arrival_times < 8), axis=1) + rv2 = np.sum(np.logical_and(8 < arrival_times, arrival_times < 12), axis=1) + rvs = [rv1, rv2] + df = [max(rv1) + 2, max(rv2) + 2] + + Q = np.zeros((max(df), len(rvs))) + + for i, sample in enumerate(rvs): + for k in range(df[i] - 1): + pi_hat = nSamp * np.exp(-mus[i]) * mus[i]**k / math.factorial(k) + Q[k, i] = (np.sum(sample == k) - pi_hat)**2 / pi_hat + + ans = np.array([math.factorial(j) for j in range(k + 1)]) + pois_cdf = np.sum(np.exp(-mus[i]) * mus[i]**np.arange(k + 1) / ans) + Q[k + 1, i] = nSamp * (1 - pois_cdf) + + Qs = np.sum(Q, axis=0) + p = np.array([1 - chi2_cdf(Qs[i], df[i] - 2) for i in range(len(rvs))]) + assert (p > 0.1).any() From ba13c4be8fcf6672778dde52c9af439277acee1a Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 17 Dec 2017 11:40:09 -0500 Subject: [PATCH 23/60] Update travis tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9ef39ee..744ce7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,6 @@ before_script: - "sh -e /etc/init.d/xvfb start" - sleep 3 # give xvfb some time to start script: - - py.test --cov=queueing_tool --cov-report term-missing --doctest-modules + - pytest --cov=queueing_tool --cov-report term-missing --doctest-modules -k "not slow" after_success: - coveralls From 33e943a3b04cab8815b9897b7e439c9e1f332b2c Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 19 Dec 2017 13:22:56 -0500 Subject: [PATCH 24/60] Add ClassedAgent to network init file --- queueing_tool/network/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/queueing_tool/network/__init__.py b/queueing_tool/network/__init__.py index 43e2df2..6a49b98 100644 --- a/queueing_tool/network/__init__.py +++ b/queueing_tool/network/__init__.py @@ -22,7 +22,7 @@ QueueNetwork.transitions """ -from queueing_tool.network.multiclass_network import MultiClassQueueNetwork +from queueing_tool.network.multiclass_network import MultiClassQueueNetwork, ClassedAgent from queueing_tool.network.priority_queue import PriorityQueue from queueing_tool.network.queue_network import ( QueueingToolError, @@ -30,6 +30,7 @@ ) __all__ = [ + 'ClassedAgent', 'MultiClassQueueNetwork', 'PriorityQueue', 'QueueingToolError', From d3092d3e207fdc5de29250c598fa85ece7ce4092 Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 19 Dec 2017 16:59:45 -0500 Subject: [PATCH 25/60] Add multi class queue server --- queueing_tool/network/__init__.py | 7 +- queueing_tool/network/multiclass_network.py | 77 +++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/queueing_tool/network/__init__.py b/queueing_tool/network/__init__.py index 6a49b98..ad1843b 100644 --- a/queueing_tool/network/__init__.py +++ b/queueing_tool/network/__init__.py @@ -22,7 +22,11 @@ QueueNetwork.transitions """ -from queueing_tool.network.multiclass_network import MultiClassQueueNetwork, ClassedAgent +from queueing_tool.network.multiclass_network import ( + ClassedAgent, + MultiClassQueueNetwork, + MultiClassQueueServer +) from queueing_tool.network.priority_queue import PriorityQueue from queueing_tool.network.queue_network import ( QueueingToolError, @@ -32,6 +36,7 @@ __all__ = [ 'ClassedAgent', 'MultiClassQueueNetwork', + 'MultiClassQueueServer', 'PriorityQueue', 'QueueingToolError', 'QueueNetwork' diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index 06acaad..e0f57fd 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -1,11 +1,14 @@ import collections import copy +from heapq import heappush, heappop import numpy as np +from numpy import infty from queueing_tool.network.queue_network import QueueNetwork from queueing_tool.queues.agents import Agent from queueing_tool.queues.choice import _choice +from queueing_tool.queues.queue_server import QueueServer class MultiClassQueueNetwork(QueueNetwork): @@ -60,6 +63,80 @@ def copy(self): network._routing_transitions = copy.deepcopy(self._routing_transitions) +class MultiClassQueueServer(QueueServer): + + def next_event(self): + """Simulates the queue forward one event. + + Use :meth:`.simulate` instead. + + Returns + ------- + out : :class:`.Agent` (sometimes) + If the next event is a departure then the departing agent + is returned, otherwise nothing is returned. + + See Also + -------- + :meth:`.simulate` : Simulates the queue forward. + """ + if self._departures[0]._time < self._arrivals[0]._time: + new_depart = heappop(self._departures) + self._current_t = new_depart._time + self._num_total -= 1 + self.num_system -= 1 + self.num_departures += 1 + + if self.collect_data and new_depart.agent_id in self.data: + self.data[new_depart.agent_id][-1][2] = self._current_t + + if len(self.queue) > 0: + agent = self.queue.popleft() + if self.collect_data and agent.agent_id in self.data: + self.data[agent.agent_id][-1][1] = self._current_t + + agent._time = self.service_f(self._current_t, agent) + agent.queue_action(self, 1) + heappush(self._departures, agent) + + new_depart.queue_action(self, 2) + self._update_time() + return new_depart + + elif self._arrivals[0]._time < infty: + arrival = heappop(self._arrivals) + self._current_t = arrival._time + + if self._active: + self._add_arrival() + + self.num_system += 1 + self._num_arrivals += 1 + + if self.collect_data: + b = 0 if self.num_system <= self.num_servers else 1 + if arrival.agent_id not in self.data: + self.data[arrival.agent_id] = \ + [[arrival._time, 0, 0, len(self.queue) + b, self.num_system]] + else: + self.data[arrival.agent_id]\ + .append([arrival._time, 0, 0, len(self.queue) + b, self.num_system]) # noqa: E501 + + arrival.queue_action(self, 0) + + if self.num_system <= self.num_servers: + if self.collect_data: + self.data[arrival.agent_id][-1][1] = arrival._time + + arrival._time = self.service_f(arrival._time, arrival) + arrival.queue_action(self, 1) + heappush(self._departures, arrival) + else: + self.queue.append(arrival) + + self._update_time() + + class ClassedAgent(Agent): def __init__(self, agent_id=(0, 0), category=None, **kwargs): super(ClassedAgent, self).__init__(agent_id=agent_id, **kwargs) From 7810ef1d7661c98dcd22eb65781515eef6205470 Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 19 Dec 2017 17:03:24 -0500 Subject: [PATCH 26/60] Fixed a typo --- queueing_tool/network/multiclass_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index e0f57fd..c92e6cc 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -8,7 +8,7 @@ from queueing_tool.network.queue_network import QueueNetwork from queueing_tool.queues.agents import Agent from queueing_tool.queues.choice import _choice -from queueing_tool.queues.queue_server import QueueServer +from queueing_tool.queues.queue_servers import QueueServer class MultiClassQueueNetwork(QueueNetwork): From 976dda7c10996840ef4271a48274ecfc000da5db Mon Sep 17 00:00:00 2001 From: djordon Date: Tue, 19 Dec 2017 18:59:16 -0500 Subject: [PATCH 27/60] Typo with routing transition method --- queueing_tool/network/multiclass_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index c92e6cc..19fa74d 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -52,7 +52,7 @@ def transitions(self): } return mat - def routing_transitions(self, destination, category=None): + def routing_transition(self, destination, category=None): if category not in self._routing_transitions: category = None From fa9b9d16e8f1ae1e150a94ec938e5865e3d70d10 Mon Sep 17 00:00:00 2001 From: djordon Date: Wed, 20 Dec 2017 10:08:26 -0500 Subject: [PATCH 28/60] Add id namedtuple classes for agents and queues --- queueing_tool/common.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 queueing_tool/common.py diff --git a/queueing_tool/common.py b/queueing_tool/common.py new file mode 100644 index 0000000..4ed2165 --- /dev/null +++ b/queueing_tool/common.py @@ -0,0 +1,12 @@ +import collections + + +EdgeID = collections.namedtuple( + typename='EdgeID', + field_names=['source', 'target', 'edge_index', 'edge_index'] +) + +AgentID = collections.namedtuple( + typename='AgentID', + field_names=['edge_index', 'agent_qid'] +) From c0354f01d59b694e795a1f7b83ef81a3e01e02d6 Mon Sep 17 00:00:00 2001 From: djordon Date: Wed, 20 Dec 2017 10:14:32 -0500 Subject: [PATCH 29/60] Use namedtuples for ids --- queueing_tool/queues/agents.py | 5 +++-- queueing_tool/queues/queue_extentions.py | 2 +- queueing_tool/queues/queue_servers.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/queueing_tool/queues/agents.py b/queueing_tool/queues/agents.py index b752fe9..ad317d3 100644 --- a/queueing_tool/queues/agents.py +++ b/queueing_tool/queues/agents.py @@ -1,6 +1,7 @@ from numpy import infty from numpy.random import uniform +from queueing_tool.common import AgentID from queueing_tool.queues.choice import _choice, _argmin @@ -29,14 +30,14 @@ class Agent(object): Attributes ---------- - agent_id : tuple + agent_id : namedtuple A unique identifier for an agent. blocked : int Specifies how many times an agent has been blocked by a finite capacity queue. """ def __init__(self, agent_id=(0, 0), **kwargs): - self.agent_id = agent_id + self.agent_id = AgentID(*agent_id) self.blocked = 0 self._time = 0 # The agents arrival or departure time diff --git a/queueing_tool/queues/queue_extentions.py b/queueing_tool/queues/queue_extentions.py index 0404e4c..a0a77c4 100644 --- a/queueing_tool/queues/queue_extentions.py +++ b/queueing_tool/queues/queue_extentions.py @@ -19,7 +19,7 @@ class ResourceAgent(Agent): a resource to that queue by increasing the number of servers there by one; the ``ResourceAgent`` is then deleted. """ - def __init__(self, agent_id=(0, 0) , **kwargs): + def __init__(self, agent_id=(0, 0), **kwargs): super(ResourceAgent, self).__init__(agent_id, **kwargs) self._has_resource = False self._had_resource = False diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index 54d43f5..d1bd745 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -7,6 +7,7 @@ from numpy import infty import numpy as np +from queueing_tool.common import EdgeID from queueing_tool.queues.agents import Agent, InftyAgent @@ -288,7 +289,7 @@ def __init__(self, num_servers=1, arrival_f=None, msg = "num_servers must be a positive integer or infinity." raise ValueError(msg) - self.edge = edge + self.edge = EdgeID(*edge) self.num_servers = kwargs.get('nServers', num_servers) self.num_departures = 0 self.num_system = 0 From 0088347e97309f202cb0a9ec681f3bdacd02d54a Mon Sep 17 00:00:00 2001 From: djordon Date: Wed, 20 Dec 2017 10:23:02 -0500 Subject: [PATCH 30/60] Fixed a typo, changed agent repr --- queueing_tool/common.py | 2 +- queueing_tool/queues/agents.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/queueing_tool/common.py b/queueing_tool/common.py index 4ed2165..eab7b89 100644 --- a/queueing_tool/common.py +++ b/queueing_tool/common.py @@ -3,7 +3,7 @@ EdgeID = collections.namedtuple( typename='EdgeID', - field_names=['source', 'target', 'edge_index', 'edge_index'] + field_names=['source', 'target', 'edge_index', 'edge_type'] ) AgentID = collections.namedtuple( diff --git a/queueing_tool/queues/agents.py b/queueing_tool/queues/agents.py index ad317d3..0fae214 100644 --- a/queueing_tool/queues/agents.py +++ b/queueing_tool/queues/agents.py @@ -42,7 +42,7 @@ def __init__(self, agent_id=(0, 0), **kwargs): self._time = 0 # The agents arrival or departure time def __repr__(self): - return "Agent; agent_id:{0}. time: {1}".format(self.agent_id, round(self._time, 3)) + return "Agent; {0}. time: {1}".format(self.agent_id, round(self._time, 3)) def __lt__(self, b): return self._time < b._time From 383064edb7eb96abb766d4c38e8ccb2a38383203 Mon Sep 17 00:00:00 2001 From: djordon Date: Wed, 20 Dec 2017 18:48:30 -0500 Subject: [PATCH 31/60] Use named tuple for classed agent --- queueing_tool/network/multiclass_network.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index 19fa74d..2d345d0 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -137,10 +137,17 @@ def next_event(self): self._update_time() +ClassAgentID = collections.namedtuple( + typename='AgentID', + field_names=['edge_index', 'agent_qid', 'category'] +) + + class ClassedAgent(Agent): def __init__(self, agent_id=(0, 0), category=None, **kwargs): - super(ClassedAgent, self).__init__(agent_id=agent_id, **kwargs) self._category = category + super(ClassedAgent, self).__init__(agent_id=agent_id, **kwargs) + self.agent_id = ClassAgentID(agent_id[0], agent_id[1], self.category) @property def category(self): From 4f499900cd7caefbc221cec6faa24020128c56ae Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 24 Dec 2017 19:37:59 -0500 Subject: [PATCH 32/60] Use edgeid object in _update_graph_colors method --- queueing_tool/network/queue_network.py | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 823f019..c761233 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -1548,33 +1548,33 @@ def _update_vertex_color(self, v): self.g.set_vp(v, 'vertex_color', self.colors['vertex_color']) def _update_graph_colors(self, qedge): - e = qedge[:2] - v = qedge[1] if self._prev_edge is not None: - pe = self._prev_edge[:2] - pv = self._prev_edge[1] - q = self.edge2queue[self._prev_edge[2]] - - if pe[0] == pe[1]: - self.g.set_ep(pe, 'edge_color', q._current_color(1)) - self.g.set_vp(pv, 'vertex_color', q._current_color(2)) - if q.edge[3] != 0: - self.g.set_vp(v, 'vertex_fill_color', q._current_color()) + q = self.edge2queue[self._prev_edge.edge_index] + + if self._prev_edge.source == self._prev_edge.target: + self.g.set_ep(self._prev_edge, 'edge_color', q._current_color(1)) + self.g.set_vp(self._prev_edge.target, 'vertex_color', q._current_color(2)) + if q.edge.edge_type != 0: + self.g.set_vp( + v=self._prev_edge.target, + vertex_property='vertex_fill_color', + value=q._current_color() + ) else: - self.g.set_ep(pe, 'edge_color', q._current_color()) - self._update_vertex_color(pv) + self.g.set_ep(self._prev_edge, 'edge_color', q._current_color()) + self._update_vertex_color(self._prev_edge.target) - q = self.edge2queue[qedge[2]] - if qedge[0] == qedge[1]: - self.g.set_ep(e, 'edge_color', q._current_color(1)) - self.g.set_vp(v, 'vertex_color', q._current_color(2)) - if q.edge[3] != 0: - self.g.set_vp(v, 'vertex_fill_color', q._current_color()) + q = self.edge2queue[qedge.edge_index] + if qedge.source == qedge.target: + self.g.set_ep(qedge, 'edge_color', q._current_color(1)) + self.g.set_vp(qedge.target, 'vertex_color', q._current_color(2)) + if q.edge.edge_type != 0: + self.g.set_vp(qedge.target, 'vertex_fill_color', q._current_color()) else: - self.g.set_ep(e, 'edge_color', q._current_color()) - self._update_vertex_color(v) + self.g.set_ep(qedge, 'edge_color', q._current_color()) + self._update_vertex_color(qedge.target) def _get_queues(g, queues, edge, edge_type): From 128e50fe6b3d9adcc921d8d02d51b5400f7c25e1 Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 24 Dec 2017 19:42:39 -0500 Subject: [PATCH 33/60] Update _update_vertex_color --- queueing_tool/network/queue_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index c761233..3e047df 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -1531,7 +1531,7 @@ def _update_vertex_color(self, v): ee_is_edge = self.g.is_edge(ee) eei = self.g.edge_index[ee] if ee_is_edge else 0 - if not ee_is_edge or (ee_is_edge and self.edge2queue[eei].edge[3] == 0): + if not ee_is_edge or (ee_is_edge and self.edge2queue[eei].edge.edge_type == 0): nSy = 0 cap = 0 for ei in self.in_edges[v]: From f33268203f481d1c7262fb3c47c4bafc48ee2d86 Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 24 Dec 2017 19:45:49 -0500 Subject: [PATCH 34/60] Cleanup _update_all_colors, fix bug --- queueing_tool/network/queue_network.py | 30 ++++++++++++-------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 3e047df..2787839 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -1507,24 +1507,22 @@ def transitions(self, return_matrix=True): return mat def _update_all_colors(self): - do = [True for v in range(self.nV)] + do = {v: True for v in self.g.nodes()} for q in self.edge2queue: - e = q.edge[:2] - v = q.edge[1] - if q.edge[0] == q.edge[1]: - self.g.set_ep(e, 'edge_color', q._current_color(1)) - self.g.set_vp(v, 'vertex_color', q._current_color(2)) - if q.edge[3] != 0: - self.g.set_vp(v, 'vertex_fill_color', q._current_color()) - do[v] = False + if q.edge.source == q.edge.target: + self.g.set_ep(q.edge, 'edge_color', q._current_color(1)) + self.g.set_vp(q.edge.target, 'vertex_color', q._current_color(2)) + if q.edge.edge_type != 0: + self.g.set_vp(q.edge.target, 'vertex_fill_color', q._current_color()) + do[q.edge.target] = False else: - self.g.set_ep(e, 'edge_color', q._current_color()) - if do[v]: - self._update_vertex_color(v) - do[v] = False - if do[q.edge[0]]: - self._update_vertex_color(q.edge[0]) - do[q.edge[0]] = False + self.g.set_ep(q.edge, 'edge_color', q._current_color()) + if do[q.edge.target]: + self._update_vertex_color(q.edge.target) + do[q.edge.target] = False + if do[q.edge.source]: + self._update_vertex_color(q.edge.source) + do[q.edge.source] = False def _update_vertex_color(self, v): ee = (v, v) From 7ad8c32e4a7b1724e891a88c2c712a4209f3cc83 Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 24 Dec 2017 19:52:12 -0500 Subject: [PATCH 35/60] Spacing change --- queueing_tool/network/queue_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 2787839..7f8a07a 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -1552,13 +1552,13 @@ def _update_graph_colors(self, qedge): if self._prev_edge.source == self._prev_edge.target: self.g.set_ep(self._prev_edge, 'edge_color', q._current_color(1)) self.g.set_vp(self._prev_edge.target, 'vertex_color', q._current_color(2)) + if q.edge.edge_type != 0: self.g.set_vp( v=self._prev_edge.target, vertex_property='vertex_fill_color', value=q._current_color() ) - else: self.g.set_ep(self._prev_edge, 'edge_color', q._current_color()) self._update_vertex_color(self._prev_edge.target) From 0b71c07536aa1b756e873b509f3a6fdd72fba87c Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 24 Dec 2017 20:00:34 -0500 Subject: [PATCH 36/60] Moved comments around --- queueing_tool/queues/queue_servers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index d1bd745..c04c39e 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -312,15 +312,15 @@ def service_f(t): self.deactive_t = deactive_t inftyAgent = InftyAgent() - self._arrivals = [inftyAgent] # A list of arriving agents. - self._departures = [inftyAgent] # A list of departing agents. + self._arrivals = [inftyAgent] # A list of arriving agents. + self._departures = [inftyAgent] # A list of departing agents. self._num_arrivals = 0 self._oArrivals = 0 - self._num_total = 0 # The number of agents scheduled to arrive + num_system + self._num_total = 0 # noqa E501 The number of agents scheduled to arrive + num_system self._active = False - self._current_t = 0 # The time of the last event. - self._time = infty # The time of the next event. - self._next_ct = 0 # The next time an arrival from outside the network can arrive. + self._current_t = 0 # The time of the last event. + self._time = infty # The time of the next event. + self._next_ct = 0 # noqa E501 The next time an arrival from outside the network can arrive. self.coloring_sensitivity = coloring_sensitivity if isinstance(seed, numbers.Integral): From 8167d04eb55dc50ae5ff169efa5aa8082d629fb3 Mon Sep 17 00:00:00 2001 From: djordon Date: Mon, 25 Dec 2017 02:43:10 -0500 Subject: [PATCH 37/60] Remove sorting of edges when creating edge_index --- queueing_tool/graph/graph_wrapper.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 88fb96a..2b17cc8 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -1,4 +1,4 @@ -import itertools +©import itertools import networkx as nx import numpy as np @@ -239,13 +239,12 @@ def __init__(self, data=None, **kwargs): data = adjacency2graph(data, **kwargs) super(QueueNetworkDiGraph, self).__init__(data, **kwargs) - edges = sorted(self.edges()) - self.edge_index = {e: k for k, e in enumerate(edges)} + self.edge_index = {e: k for k, e in enumerate(self.edges())} pos = nx.get_node_attributes(self, name='pos') if len(pos) == self.number_of_nodes(): - self.pos = np.array([pos[v] for v in self.nodes()]) + self.pos = pos else: self.pos = None @@ -312,7 +311,7 @@ def set_node_positions(self, pos=None): if pos is None: pos = nx.spring_layout(self) nx.set_node_attributes(self, name='pos', values=pos) - self.pos = np.array([pos[v] for v in self.nodes()]) + self.pos = {v: pos[v] for v in self.nodes()} def set_pos(self, pos=None): self.set_node_positions(pos=pos) @@ -457,15 +456,10 @@ def lines_scatter_args(self, line_kwargs=None, scatter_kwargs=None, pos=None): If a specific keyword argument is not passed then the defaults are used. """ - if pos is not None: - self.set_pos(pos) - elif self.pos is None: - self.set_pos() + self.set_pos(pos) - edge_pos = [0 for e in self.edges()] - for e in self.edges(): - ei = self.edge_index[e] - edge_pos[ei] = (self.pos[e[0]], self.pos[e[1]]) + edge_pos = [(self.pos[e[0]], self.pos[e[1]]) for e in self.edges()] + node_pos = np.array([self.pos[v] for v in self.nodes()]) line_collecton_kwargs = { 'segments': edge_pos, @@ -484,8 +478,8 @@ def lines_scatter_args(self, line_kwargs=None, scatter_kwargs=None, pos=None): 'hatch': None, } scatter_kwargs_ = { - 'x': self.pos[:, 0], - 'y': self.pos[:, 1], + 'x': node_pos[:, 0], + 'y': node_pos[:, 1], 's': 50, 'c': self.vertex_fill_color, 'alpha': None, From 75f95cf543fbfdb21e2697077839d10a305613ee Mon Sep 17 00:00:00 2001 From: djordon Date: Mon, 25 Dec 2017 02:44:42 -0500 Subject: [PATCH 38/60] weird type --- queueing_tool/graph/graph_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 2b17cc8..5f8bade 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -1,4 +1,4 @@ -©import itertools +import itertools import networkx as nx import numpy as np From 6b55f777b72214b0c74680a3aa82504196651d54 Mon Sep 17 00:00:00 2001 From: djordon Date: Mon, 25 Dec 2017 02:51:21 -0500 Subject: [PATCH 39/60] Corrected a 'broken' test after recent changes --- queueing_tool/network/queue_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 7f8a07a..0d44932 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -260,7 +260,7 @@ class QueueNetwork(object): >>> nA = [(q.num_system, q.edge[2]) for q in net.edge2queue if q.edge[3] == 1] >>> nA.sort(reverse=True) >>> nA[:5] - [(4, 37), (4, 34), (3, 43), (3, 32), (3, 30)] + [(4, 37), (4, 33), (3, 43), (3, 32), (3, 30)] To view the state of the network do the following (note, you need to have pygraphviz installed and your graph may be rotated): From 5e89a18b31d23f39c3fc0286c4122f9717703c25 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 30 Dec 2017 02:27:38 -0500 Subject: [PATCH 40/60] pass the service function the queue --- queueing_tool/network/multiclass_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index 2d345d0..bab44c1 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -95,7 +95,7 @@ def next_event(self): if self.collect_data and agent.agent_id in self.data: self.data[agent.agent_id][-1][1] = self._current_t - agent._time = self.service_f(self._current_t, agent) + agent._time = self.service_f(self._current_t, self) agent.queue_action(self, 1) heappush(self._departures, agent) @@ -128,7 +128,7 @@ def next_event(self): if self.collect_data: self.data[arrival.agent_id][-1][1] = arrival._time - arrival._time = self.service_f(arrival._time, arrival) + arrival._time = self.service_f(arrival._time, self) arrival.queue_action(self, 1) heappush(self._departures, arrival) else: From 5ced4e5e4388f38bda95451b5f9e3ddaf68fe4c3 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 30 Dec 2017 02:51:07 -0500 Subject: [PATCH 41/60] Pass the queue and the agent to service_f --- queueing_tool/network/multiclass_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index bab44c1..ee4ae6b 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -95,7 +95,7 @@ def next_event(self): if self.collect_data and agent.agent_id in self.data: self.data[agent.agent_id][-1][1] = self._current_t - agent._time = self.service_f(self._current_t, self) + agent._time = self.service_f(self._current_t, agent, self) agent.queue_action(self, 1) heappush(self._departures, agent) @@ -128,7 +128,7 @@ def next_event(self): if self.collect_data: self.data[arrival.agent_id][-1][1] = arrival._time - arrival._time = self.service_f(arrival._time, self) + arrival._time = self.service_f(arrival._time, agent, self) arrival.queue_action(self, 1) heappush(self._departures, arrival) else: From a1c57d423e1acfce45528fece57742538fab9db6 Mon Sep 17 00:00:00 2001 From: djordon Date: Sat, 30 Dec 2017 02:52:57 -0500 Subject: [PATCH 42/60] Typo for agent name when passing to service_f --- queueing_tool/network/multiclass_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index ee4ae6b..42281ec 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -128,7 +128,7 @@ def next_event(self): if self.collect_data: self.data[arrival.agent_id][-1][1] = arrival._time - arrival._time = self.service_f(arrival._time, agent, self) + arrival._time = self.service_f(arrival._time, arrival, self) arrival.queue_action(self, 1) heappush(self._departures, arrival) else: From a2f744d890606b92d30fcf3a0c223bdb214cb2b2 Mon Sep 17 00:00:00 2001 From: djordon Date: Sun, 31 Dec 2017 21:33:41 -0500 Subject: [PATCH 43/60] Simplify category property --- queueing_tool/network/multiclass_network.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index 42281ec..a9dc2b4 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -138,21 +138,17 @@ def next_event(self): ClassAgentID = collections.namedtuple( - typename='AgentID', + typename='ClassAgentID', field_names=['edge_index', 'agent_qid', 'category'] ) class ClassedAgent(Agent): def __init__(self, agent_id=(0, 0), category=None, **kwargs): - self._category = category + self.category = category or self.__class__.__name__ super(ClassedAgent, self).__init__(agent_id=agent_id, **kwargs) self.agent_id = ClassAgentID(agent_id[0], agent_id[1], self.category) - @property - def category(self): - return self._category or self.__class__.__name__ - def desired_destination(self, network, edge): n = len(network.out_edges[edge[1]]) From 5e5aa6d9b9105d6ff744c7ed2d1019e08c5c9c55 Mon Sep 17 00:00:00 2001 From: djordon Date: Mon, 8 Jan 2018 12:15:43 -0500 Subject: [PATCH 44/60] Fix transitions function --- queueing_tool/network/multiclass_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index a9dc2b4..28b0cff 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -45,8 +45,8 @@ def set_categorical_transitions(self, adjacency_list): def transitions(self): mat = { category: { - k: {e[1]: p for e, p in zip(self.g.out_edges(k), value)} - for k, value in enumerate(routing_probs) + node: {e[1]: p for e, p in zip(self.g.out_edges(node), value)} + for node, value in routing_probs.values() } for category, routing_probs in self._routing_transitions.items() } From 47174bd58497c85e6e74eaffe97b6aff0d30e286 Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 10:25:22 -0500 Subject: [PATCH 45/60] Linting related changes --- queueing_tool/graph/graph_preparation.py | 2 +- queueing_tool/graph/graph_wrapper.py | 6 +++--- queueing_tool/network/multiclass_network.py | 6 ++++-- queueing_tool/network/queue_network.py | 12 ++++++++---- queueing_tool/queues/agents.py | 7 ++++--- queueing_tool/queues/queue_extentions.py | 2 +- queueing_tool/queues/queue_servers.py | 10 +++++----- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/queueing_tool/graph/graph_preparation.py b/queueing_tool/graph/graph_preparation.py index e751d00..e82dd1c 100644 --- a/queueing_tool/graph/graph_preparation.py +++ b/queueing_tool/graph/graph_preparation.py @@ -116,7 +116,7 @@ def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph): ans = nx.to_dict_of_dicts(g) g = adjacency2graph(ans, adjust=2, is_directed=g.is_directed()) g = QueueNetworkDiGraph(g) - if len(pos) > 0: + if pos: g.set_pos(pos) g.new_vertex_property('vertex_color') diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 5f8bade..ad45e6d 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -56,7 +56,7 @@ def _adjacency_adjust(adjacency, adjust, is_directed): null_nodes = set() for k, adj in adjacency.items(): - if len(adj) == 0: + if not adj: null_nodes.add(k) for k, adj in adjacency.items(): @@ -66,13 +66,13 @@ def _adjacency_adjust(adjacency, adjust, is_directed): else: for k, adj in adjacency.items(): - if len(adj) == 0: + if not adj: adj[k] = {'edge_type': 0} return adjacency -def adjacency2graph(adjacency, edge_type=None, adjust=1, **kwargs): +def adjacency2graph(adjacency, edge_type=None, adjust=1, **_kwargs): """Takes an adjacency list, dict, or matrix and returns a graph. The purpose of this function is take an adjacency list (or matrix) diff --git a/queueing_tool/network/multiclass_network.py b/queueing_tool/network/multiclass_network.py index 28b0cff..8a3e2f4 100644 --- a/queueing_tool/network/multiclass_network.py +++ b/queueing_tool/network/multiclass_network.py @@ -28,7 +28,7 @@ def set_transitions(self, mat, category=None): if key not in self.g.node: msg = "One of the keys don't correspond to a vertex." raise ValueError(msg) - elif len(self.out_edges[key]) > 0 and not np.isclose(sum(probs), 1): + elif self.out_edges[key] and not np.isclose(sum(probs), 1): msg = "Sum of transition probabilities at a vertex was not 1." raise ValueError(msg) elif (np.array(probs) < 0).any(): @@ -90,7 +90,7 @@ def next_event(self): if self.collect_data and new_depart.agent_id in self.data: self.data[new_depart.agent_id][-1][2] = self._current_t - if len(self.queue) > 0: + if self.queue: agent = self.queue.popleft() if self.collect_data and agent.agent_id in self.data: self.data[agent.agent_id][-1][1] = self._current_t @@ -136,6 +136,8 @@ def next_event(self): self._update_time() + return None + ClassAgentID = collections.namedtuple( typename='ClassAgentID', diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 0d44932..de4b0ef 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -309,6 +309,9 @@ def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, if not isinstance(blocking, str): raise TypeError("blocking must be a string") + # Used for testing + self._qkey = None + self._t = 0 self.num_events = 0 self.max_agents = max_agents @@ -528,7 +531,7 @@ def animate(self, out=None, t=None, line_kwargs=None, t = np.infty if t is None else t now = self._t - def update(frame_number): + def update(_frame_number): if t is not None: if self._t > now + t: return False @@ -536,6 +539,7 @@ def update(frame_number): lines.set_color(line_args['colors']) scatt.set_edgecolors(scat_args['edgecolors']) scatt.set_facecolor(scat_args['c']) + return None if hasattr(ax, 'set_facecolor'): ax.set_facecolor(kwargs['bgcolor']) @@ -894,7 +898,7 @@ def get_queue_data(self, queues=None, edge=None, edge_type=None, return_header=F for q in queues: dat = self.edge2queue[q].fetch_data() - if len(dat) > 0: + if dat: data = np.vstack((data, dat)) if return_header: @@ -969,7 +973,7 @@ def initialize(self, nActive=1, queues=None, edges=None, edge_type=None): queues = [e for e in queues if self.edge2queue[e].edge[3] != 0] - if len(queues) == 0: + if not queues: raise QueueingToolError("There were no queues to initialize.") if len(queues) > self.max_agents: @@ -1101,7 +1105,7 @@ def set_transitions(self, mat): if key not in self.g.node: msg = "One of the keys don't correspond to a vertex." raise ValueError(msg) - elif len(self.out_edges[key]) > 0 and not np.isclose(sum(probs), 1): + elif self.out_edges[key] and not np.isclose(sum(probs), 1): msg = "Sum of transition probabilities at a vertex was not 1." raise ValueError(msg) elif (np.array(probs) < 0).any(): diff --git a/queueing_tool/queues/agents.py b/queueing_tool/queues/agents.py index 0fae214..22ad55a 100644 --- a/queueing_tool/queues/agents.py +++ b/queueing_tool/queues/agents.py @@ -36,7 +36,7 @@ class Agent(object): Specifies how many times an agent has been blocked by a finite capacity queue. """ - def __init__(self, agent_id=(0, 0), **kwargs): + def __init__(self, agent_id=(0, 0), **_kwargs): self.agent_id = AgentID(*agent_id) self.blocked = 0 self._time = 0 # The agents arrival or departure time @@ -59,13 +59,14 @@ def __le__(self, b): def __ge__(self, b): return self._time >= b._time - def add_loss(self, *args, **kwargs): + def add_loss(self, *_args, **_kwargs): """Adds one to the number of times the agent has been blocked from entering a queue. """ self.blocked += 1 - def desired_destination(self, network, edge): + @staticmethod + def desired_destination(network, edge): """Returns the agents next destination given their current location on the network. diff --git a/queueing_tool/queues/queue_extentions.py b/queueing_tool/queues/queue_extentions.py index a0a77c4..3bb4d15 100644 --- a/queueing_tool/queues/queue_extentions.py +++ b/queueing_tool/queues/queue_extentions.py @@ -238,7 +238,7 @@ def __init__(self, agent_id=(0, 0), net_size=1, **kwargs): def __repr__(self): return "InfoAgent; agent_id:{0}. Time: {1}".format(self.agent_id, round(self._time, 3)) - def add_loss(self, qedge, *args, **kwargs): # qedge[2] is the edge_index of the queue + def add_loss(self, qedge, *_args, **_kwargs): # qedge[2] is the edge_index of the queue self.stats[qedge[2], 2] += 1 def get_beliefs(self): diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index c04c39e..504d89d 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -276,7 +276,7 @@ class QueueServer(object): 'vertex_color': [0.0, 0.5, 1.0, 1.0] } - def __init__(self, num_servers=1, arrival_f=None, + def __init__(self, num_servers=1, arrival_f=None, # pylint: disable=R0914 service_f=None, edge=(0, 0, 0, 1), AgentFactory=Agent, collect_data=False, active_cap=infty, deactive_t=infty, colors=None, seed=None, @@ -297,11 +297,11 @@ def __init__(self, num_servers=1, arrival_f=None, self.queue = collections.deque() if arrival_f is None: - def arrival_f(t): + def arrival_f(t): # pylint: disable=E0102 return t + exponential(1.0) if service_f is None: - def service_f(t): + def service_f(t): # pylint: disable=E0102 return t + exponential(0.9) self.arrival_f = arrival_f @@ -529,7 +529,7 @@ def fetch_data(self, return_header=False): qdata.extend(d) dat = np.zeros((len(qdata), 6)) - if len(qdata) > 0: + if qdata: dat[:, :5] = np.array(qdata) dat[:, 5] = self.edge[2] @@ -588,7 +588,7 @@ def next_event(self): if self.collect_data and new_depart.agent_id in self.data: self.data[new_depart.agent_id][-1][2] = self._current_t - if len(self.queue) > 0: + if self.queue: agent = self.queue.popleft() if self.collect_data and agent.agent_id in self.data: self.data[agent.agent_id][-1][1] = self._current_t From 0bcd52e46c2be93206175a1dffad89c7e69a98d4 Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 10:26:15 -0500 Subject: [PATCH 46/60] Don't remove attributes, keep the API the same --- queueing_tool/queues/queue_servers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index 504d89d..c588c74 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -306,6 +306,7 @@ def service_f(t): # pylint: disable=E0102 self.arrival_f = arrival_f self.service_f = service_f + self.AgentFactory = AgentFactory self._agent_factory = AgentFactory self.collect_data = collect_data self.active_cap = active_cap @@ -896,7 +897,7 @@ class NullQueue(QueueServer): 'vertex_color': [0.5, 0.5, 0.5, 0.5] } - def __init__(self, *args, **kwargs): + def __init__(self, *_args, **kwargs): if 'edge' not in kwargs: kwargs['edge'] = (0, 0, 0, 0) @@ -909,7 +910,7 @@ def __repr__(self): def initialize(self, *args, **kwargs): pass - def set_num_servers(self, *args, **kwargs): + def set_num_servers(self, n): pass def number_queued(self): @@ -922,7 +923,7 @@ def _add_arrival(self, agent=None): else: self.data[agent.agent_id].append([agent._time, 0, 0, 0, 0]) - def delay_service(self, *args, **kwargs): + def delay_service(self, t=None): pass def next_event_description(self): From fa090bbbf981647954167fd4a89db9be5137d6a2 Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 10:27:02 -0500 Subject: [PATCH 47/60] Out was previously unused, it should have been called filename. --- queueing_tool/network/queue_network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index de4b0ef..ac09067 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -410,7 +410,7 @@ def time(self): t = np.infty return t - def animate(self, out=None, t=None, line_kwargs=None, + def animate(self, filename=None, t=None, line_kwargs=None, scatter_kwargs=None, **kwargs): """Animates the network as it's simulating. @@ -423,7 +423,7 @@ def animate(self, out=None, t=None, line_kwargs=None, Parameters ---------- - out : str (optional) + filename : str (optional) The location where the frames for the images will be saved. If this parameter is not given, then the animation is shown in interactive mode. @@ -567,12 +567,12 @@ def update(_frame_number): animation_args[key] = value animation = FuncAnimation(**animation_args) - if 'filename' not in kwargs: + if filename is None: plt.ioff() plt.show() else: save_args = { - 'filename': None, + 'filename': filename, 'writer': None, 'fps': None, 'dpi': None, From d09ef9c96c563a9dd1b13b84bc73e3b75567f96e Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 10:27:30 -0500 Subject: [PATCH 48/60] Keep multiclass queues hush --- queueing_tool/network/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/queueing_tool/network/__init__.py b/queueing_tool/network/__init__.py index ad1843b..7f726f7 100644 --- a/queueing_tool/network/__init__.py +++ b/queueing_tool/network/__init__.py @@ -22,11 +22,6 @@ QueueNetwork.transitions """ -from queueing_tool.network.multiclass_network import ( - ClassedAgent, - MultiClassQueueNetwork, - MultiClassQueueServer -) from queueing_tool.network.priority_queue import PriorityQueue from queueing_tool.network.queue_network import ( QueueingToolError, @@ -34,9 +29,6 @@ ) __all__ = [ - 'ClassedAgent', - 'MultiClassQueueNetwork', - 'MultiClassQueueServer', 'PriorityQueue', 'QueueingToolError', 'QueueNetwork' From e96ae79d0d1a825a69e248c5094433e9d097c236 Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 10:51:26 -0500 Subject: [PATCH 49/60] Clean up tests, mainly linting --- tests/test_graph_generation.py | 24 ++++--- tests/test_network.py | 104 +++++++++++++++++---------- tests/test_qndigraph.py | 10 +-- tests/test_queue_server.py | 61 ++++++++++------ tests/test_statistical_properties.py | 23 +++--- 5 files changed, 140 insertions(+), 82 deletions(-) diff --git a/tests/test_graph_generation.py b/tests/test_graph_generation.py index 8ac67b7..0873beb 100644 --- a/tests/test_graph_generation.py +++ b/tests/test_graph_generation.py @@ -14,14 +14,16 @@ def generate_adjacency(a=3, b=25, c=6, n=12): class TestGraphFunctions(object): - def test_graph2dict(self): + @staticmethod + def test_graph2dict(): adj = generate_adjacency() g1 = qt.adjacency2graph(adj, adjust=2) aj1 = qt.graph2dict(g1) g2 = qt.adjacency2graph(aj1, adjust=2) assert nx.is_isomorphic(g1, g2) - def test_add_edge_lengths(self): + @staticmethod + def test_add_edge_lengths(): g1 = qt.generate_pagerank_graph(10) g2 = qt.add_edge_lengths(g1) @@ -31,7 +33,8 @@ def test_add_edge_lengths(self): assert 'edge_length' in edge_props - def test_generate_transition(self): + @staticmethod + def test_generate_transition(): g = qt.generate_random_graph(20) mat = qt.generate_transition_matrix(g) @@ -42,7 +45,8 @@ def test_generate_transition(self): ans = np.sum(mat, axis=1) np.testing.assert_allclose(ans, 1) - def test_adjacency2graph_matrix_adjacency(self): + @staticmethod + def test_adjacency2graph_matrix_adjacency(): # Test adjacency argument using ndarray work adj = np.array([[0, 1, 0, 0], @@ -61,7 +65,8 @@ def test_adjacency2graph_matrix_adjacency(self): } assert actual == expected - def test_adjacency2graph_matrix_etype(self): + @staticmethod + def test_adjacency2graph_matrix_etype(): # Test adjacency argument using ndarrays work adj = {0: {1: {}}, 1: {2: {}, 3: {}}, 2: {0: {}}, 3: {}} ety = np.array([[0, 5, 0, 0], @@ -79,11 +84,13 @@ def test_adjacency2graph_matrix_etype(self): } assert expected == actual - def test_adjacency2graph_errors(self): + @staticmethod + def test_adjacency2graph_errors(): with pytest.raises(TypeError): qt.adjacency2graph([]) - def test_set_types_random(self): + @staticmethod + def test_set_types_random(): nV = 1200 nT = np.random.randint(5, 10) @@ -111,6 +118,7 @@ def test_set_types_random(self): with pytest.raises(ValueError): g = qt.set_types_random(g, loop_proportions=pType, seed=10) - def test_test_graph_importerror(self): + @staticmethod + def test_test_graph_importerror(): with pytest.raises(TypeError): qt.generate_transition_matrix(1) diff --git a/tests/test_network.py b/tests/test_network.py index c4eb28e..0675c10 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -1,4 +1,3 @@ -import os try: import unittest.mock as mock except ImportError: @@ -17,11 +16,8 @@ import queueing_tool as qt -TRAVIS_TEST = os.environ.get('TRAVIS_TEST', False) - - -@pytest.fixture(scope='module') -def queue_network(): +@pytest.fixture(name='queue_network', scope='module') +def fixture_queue_network(): g = qt.generate_pagerank_graph(200) qn = qt.QueueNetwork(g) qn.g.draw_graph = mock.MagicMock() @@ -40,7 +36,8 @@ def clear_queue_network(queue_network): @pytest.mark.usefixtures('clear_queue_network') class TestQueueNetwork(object): - def test_accounting(self, queue_network): + @staticmethod + def test_accounting(queue_network): num_events = 1500 ans = np.zeros(num_events, bool) @@ -56,7 +53,8 @@ def test_accounting(self, queue_network): assert ans.all() - def test_add_arrival(self): + @staticmethod + def test_add_arrival(): adj = {0: [1], 1: [2, 3]} g = qt.adjacency2graph(adj) @@ -80,7 +78,8 @@ def test_add_arrival(self): assert np.isclose(trans[1][2], p0, atol=1e-1) assert np.isclose(trans[1][3], p1, atol=1e-1) - def test_animate(self, queue_network): + @staticmethod + def test_animate(queue_network): if not HAS_MATPLOTLIB: with mock.patch('queueing_tool.network.queue_network.plt.show'): queue_network.animate(frames=5) @@ -88,7 +87,8 @@ def test_animate(self, queue_network): plt.switch_backend('Agg') queue_network.animate(frames=5) - def test_blocking(self): + @staticmethod + def test_blocking(): g = nx.random_geometric_graph(100, 0.2).to_directed() g = qt.set_types_random(g, proportions={k: 1.0 / 6 for k in range(1, 7)}) @@ -115,12 +115,14 @@ def test_blocking(self): qn.clear() assert qn._initialized is False - def test_blocking_setter_error(self, queue_network): + @staticmethod + def test_blocking_setter_error(queue_network): queue_network.blocking = 'RS' with pytest.raises(TypeError): queue_network.blocking = 2 - def test_closedness(self, queue_network): + @staticmethod + def test_closedness(queue_network): num_events = 2500 ans = np.zeros(num_events, bool) @@ -137,7 +139,8 @@ def test_closedness(self, queue_network): assert ans.all() - def test_copy(self): + @staticmethod + def test_copy(): g = nx.random_geometric_graph(100, 0.2).to_directed() g = qt.set_types_random(g, proportions={k: 0.2 for k in range(1, 6)}) @@ -172,8 +175,9 @@ def test_copy(self): assert np.array(ans).all() + @staticmethod @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_drawing(self, queue_network): + def test_drawing(queue_network): scatter_kwargs = {'c': 'b'} kwargs = {'bgcolor': 'green'} queue_network.draw(scatter_kwargs=scatter_kwargs, **kwargs) @@ -185,12 +189,14 @@ def test_drawing(self, queue_network): queue_network.g.draw_graph.assert_called_with(scatter_kwargs=scatter_kwargs, line_kwargs=None, bgcolor=bgcolor) + @staticmethod @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', False) - def test_drawing_importerror(self, queue_network): + def test_drawing_importerror(queue_network): with pytest.raises(ImportError): queue_network.draw() - def test_drawing_animation_error(self, queue_network): + @staticmethod + def test_drawing_animation_error(queue_network): queue_network.clear() with pytest.raises(qt.QueueingToolError): queue_network.animate() @@ -200,12 +206,14 @@ def test_drawing_animation_error(self, queue_network): with pytest.raises(ImportError): queue_network.animate() - def test_init_error(self): + @staticmethod + def test_init_error(): g = qt.generate_pagerank_graph(7) with pytest.raises(TypeError): qt.QueueNetwork(g, blocking=2) - def test_get_agent_data(self, queue_network): + @staticmethod + def test_get_agent_data(queue_network): queue_network.clear() queue_network.initialize(queues=1) @@ -228,7 +236,8 @@ def test_get_agent_data(self, queue_network): assert (c == dat0[dat0[:, 2] > 0, 2]).all() assert (dat0[1:, 0] == dat0[dat0[:, 2] > 0, 2]).all() - def test_get_queue_data(self): + @staticmethod + def test_get_queue_data(): g = nx.random_geometric_graph(50, 0.5).to_directed() q_cls = {1: qt.QueueServer} @@ -249,7 +258,8 @@ def test_get_queue_data(self): ans = np.array([q.data == {} for q in qn.edge2queue]) assert ans.all() - def test_greedy_routing(self): + @staticmethod + def test_greedy_routing(): lam = np.random.randint(1, 10) + 0.0 rho = np.random.uniform(0.75, 1) @@ -309,7 +319,8 @@ def ser_id(t): assert ans.all() - def test_initialize_error(self, queue_network): + @staticmethod + def test_initialize_error(queue_network): queue_network.clear() with pytest.raises(ValueError): queue_network.initialize(nActive=0) @@ -325,7 +336,8 @@ def test_initialize_error(self, queue_network): with pytest.raises(qt.QueueingToolError): queue_network.initialize(edge_type=1) - def test_initialization_single_edge_index(self, queue_network): + @staticmethod + def test_initialization_single_edge_index(queue_network): # Single edge index k = np.random.randint(0, queue_network.nE) queue_network.clear() @@ -334,7 +346,8 @@ def test_initialization_single_edge_index(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert ans == [k] - def test_initialization_multiple_edge_index(self, queue_network): + @staticmethod + def test_initialization_multiple_edge_index(queue_network): # Multiple edge indices k = np.unique(np.random.randint(0, queue_network.nE, 5)) queue_network.clear() @@ -344,7 +357,8 @@ def test_initialization_multiple_edge_index(self, queue_network): ans.sort() assert (ans == k).all() - def test_initialization_single_edge(self, queue_network): + @staticmethod + def test_initialization_single_edge(queue_network): # Single edge as edge k = np.random.randint(0, queue_network.nE) e = queue_network.edge2queue[k].edge[:2] @@ -354,7 +368,8 @@ def test_initialization_single_edge(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert ans == [k] - def test_initialization_multiple_edges(self, queue_network): + @staticmethod + def test_initialization_multiple_edges(queue_network): # Multiple edges as tuples k = np.unique(np.random.randint(0, queue_network.nE, 5)) es = [queue_network.edge2queue[i].edge[:2] for i in k] @@ -364,7 +379,8 @@ def test_initialization_multiple_edges(self, queue_network): ans = [q.edge[2] for q in queue_network.edge2queue if q.active] assert (ans == k).all() - def test_initialization_single_edge_type(self, queue_network): + @staticmethod + def test_initialization_single_edge_type(queue_network): # Single edge_type k = np.random.randint(1, 4) queue_network.clear() @@ -373,7 +389,8 @@ def test_initialization_single_edge_type(self, queue_network): ans = np.array([q.edge[3] == k for q in queue_network.edge2queue if q.active]) assert ans.all() - def test_initialization_multiple_edge_types(self, queue_network): + @staticmethod + def test_initialization_multiple_edge_types(queue_network): # Multiple edge_types k = np.unique(np.random.randint(1, 4, 3)) queue_network.clear() @@ -382,14 +399,16 @@ def test_initialization_multiple_edge_types(self, queue_network): ans = np.array([q.edge[3] in k for q in queue_network.edge2queue if q.active]) assert ans.all() - def test_initialization_num_active_edges(self, queue_network): + @staticmethod + def test_initialization_num_active_edges(queue_network): queue_network.clear() queue_network.max_agents = 3 queue_network.initialize(nActive=queue_network.num_edges) ans = np.array([q.active for q in queue_network.edge2queue]) assert ans.sum() == 3 - def test_max_agents(self, queue_network): + @staticmethod + def test_max_agents(queue_network): num_events = 1500 queue_network.max_agents = 200 @@ -407,13 +426,15 @@ def test_max_agents(self, queue_network): assert ans.all() - def test_properties(self, queue_network): + @staticmethod + def test_properties(queue_network): queue_network.clear() assert queue_network.time == np.infty assert queue_network.num_edges == queue_network.nE assert queue_network.num_vertices == queue_network.nV assert queue_network.num_nodes == queue_network.nV + @staticmethod @pytest.mark.parametrize('mat', [ {-1: {0: 0.75, 1: 0.25}}, {200: {0: 0.75, 1: 0.25}}, @@ -422,11 +443,12 @@ def test_properties(self, queue_network): np.zeros((2, 2)), np.zeros((200, 200)), ]) - def test_set_transitions_error(self, mat, queue_network): + def test_set_transitions_error(mat, queue_network): with pytest.raises(ValueError): queue_network.set_transitions(mat) - def test_simulate(self): + @staticmethod + def test_simulate(): g = qt.generate_pagerank_graph(50) qn = qt.QueueNetwork(g) @@ -438,12 +460,14 @@ def test_simulate(self): assert qn.current_time > t0 - def test_simulate_error(self, queue_network): + @staticmethod + def test_simulate_error(queue_network): queue_network.clear() with pytest.raises(qt.QueueingToolError): queue_network.simulate() - def test_simulate_slow(self, queue_network): + @staticmethod + def test_simulate_slow(queue_network): e = queue_network._fancy_heap.array_edges[0] edge = queue_network.edge2queue[e].edge @@ -476,15 +500,17 @@ def test_simulate_slow(self, queue_network): else: queue_network._simulate_next_event(slow=False) + @staticmethod @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_show_type(self, queue_network): + def test_show_type(queue_network): args = {'c': 'b', 'bgcolor': 'green'} queue_network.show_type(edge_type=2, **args) queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, line_kwargs=None, **args) + @staticmethod @mock.patch('queueing_tool.network.queue_network.HAS_MATPLOTLIB', True) - def test_show_active(self, queue_network): + def test_show_active(queue_network): args = { 'fname': 'types.png', 'figsize': (3, 3), @@ -494,7 +520,8 @@ def test_show_active(self, queue_network): queue_network.g.draw_graph.assert_called_with(scatter_kwargs=None, line_kwargs=None, **args) - def test_sorting(self, queue_network): + @staticmethod + def test_sorting(queue_network): num_events = 2000 ans = np.zeros(num_events, bool) @@ -516,7 +543,8 @@ def test_sorting(self, queue_network): assert ans.all() - def test_transitions(self, queue_network): + @staticmethod + def test_transitions(queue_network): degree = [len(queue_network.out_edges[k]) for k in range(queue_network.nV)] v, deg = np.argmax(degree), max(degree) diff --git a/tests/test_qndigraph.py b/tests/test_qndigraph.py index 66e38a6..9e6d971 100644 --- a/tests/test_qndigraph.py +++ b/tests/test_qndigraph.py @@ -29,16 +29,17 @@ } -@pytest.fixture(scope='module') -def queue_network_graph(): +@pytest.fixture(name='queue_network_graph', scope='module') +def fixture_queue_network_graph(): np.random.seed(10) return qt.QueueNetworkDiGraph(nx.krackhardt_kite_graph()) class TestQueueNetworkDiGraph(object): + @staticmethod @mock.patch.dict('sys.modules', matplotlib_mock) - def test_lines_scatter_args(self, queue_network_graph): + def test_lines_scatter_args(queue_network_graph): np.random.seed(10) ax = mock.Mock() ax.transData = mock.Mock() @@ -52,7 +53,8 @@ def test_lines_scatter_args(self, queue_network_graph): assert b['vmax'] == 107 assert 'beefy' not in a and 'beefy' not in b - def test_draw_graph(self, queue_network_graph): + @staticmethod + def test_draw_graph(queue_network_graph): np.random.seed(10) pos = np.random.uniform(size=(queue_network_graph.number_of_nodes(), 2)) kwargs = { diff --git a/tests/test_queue_server.py b/tests/test_queue_server.py index 7a3f333..e62dda1 100644 --- a/tests/test_queue_server.py +++ b/tests/test_queue_server.py @@ -7,26 +7,28 @@ import queueing_tool as qt -@pytest.fixture -def lam(): +@pytest.fixture(name='lam') +def fixture_lam(): return float(np.random.randint(1, 10)) -@pytest.fixture -def rho(): +@pytest.fixture(name='rho') +def fixture_rho(): return np.random.uniform(0.5, 1) class TestQueueServer(object): - def test_init_errors(self): + @staticmethod + def test_init_errors(): with pytest.raises(TypeError): qt.QueueServer(num_servers=3.0) with pytest.raises(ValueError): qt.QueueServer(num_servers=0) - def test_set_num_servers(self): + @staticmethod + def test_set_num_servers(): nSe = np.random.randint(1, 10) q = qt.QueueServer(num_servers=nSe) @@ -41,7 +43,8 @@ def test_set_num_servers(self): assert Se2 == 2 * nSe assert q.num_servers is np.inf - def test_set_num_servers_errors(self): + @staticmethod + def test_set_num_servers_errors(): q = qt.QueueServer(num_servers=3) with pytest.raises(TypeError): @@ -50,7 +53,8 @@ def test_set_num_servers_errors(self): with pytest.raises(ValueError): q.set_num_servers(0) - def test_set_inactive(self): + @staticmethod + def test_set_inactive(): q = qt.QueueServer() q.set_active() @@ -61,7 +65,8 @@ def test_set_inactive(self): assert was_active assert not q.active - def test_copy(self): + @staticmethod + def test_copy(): q1 = qt.QueueServer(seed=15) q1.set_active() @@ -73,7 +78,8 @@ def test_copy(self): assert t < q2.time - def test_active_cap(self): + @staticmethod + def test_active_cap(): def r(t): return 2 + np.sin(t) @@ -87,7 +93,8 @@ def r(t): assert q.num_departures == 1000 assert q.num_arrivals == [1000, 1000] - def test_accounting(self, lam, rho): + @staticmethod + def test_accounting(lam, rho): nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -114,7 +121,8 @@ def ser(t): assert ans.all() - def test_deactivate(self): + @staticmethod + def test_deactivate(): q = qt.QueueServer(num_servers=3, deactive_t=10) q.set_active() @@ -123,7 +131,8 @@ def test_deactivate(self): q.simulate(t=10) assert not q.active - def test_simulation(self, lam, rho): + @staticmethod + def test_simulation(lam, rho): nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -166,7 +175,8 @@ def ser(t): class TestLossQueueServer(object): - def test_accounting(self, lam, rho): + @staticmethod + def test_accounting(lam, rho): nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -193,7 +203,8 @@ def ser(t): assert ans.all() - def test_blocking(self, lam, rho): + @staticmethod + def test_blocking(lam, rho): nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -226,7 +237,8 @@ def ser(t): class TestNullQueueServer(object): - def test_data_collection(self): + @staticmethod + def test_data_collection(): adj = { 0: {1: {'edge_type': 1}}, 1: {2: {'edge_type': 2}, @@ -251,7 +263,8 @@ def test_data_collection(self): class TestResourceQueueServer(object): - def test_network(self): + @staticmethod + def test_network(): g = nx.random_geometric_graph(100, 0.2).to_directed() q_cls = {1: qt.ResourceQueue, 2: qt.ResourceQueue} @@ -266,7 +279,8 @@ def test_network(self): ans = np.array([q.num_servers != nServ[q.edge[3]] for q in qn.edge2queue]) assert ans.any() - def test_network_data_collection(self): + @staticmethod + def test_network_data_collection(): g = qt.generate_random_graph(100) q_cls = {1: qt.ResourceQueue, 2: qt.ResourceQueue} q_arg = {1: {'num_servers': 500}, @@ -280,9 +294,10 @@ def test_network_data_collection(self): qn.simulate(n=5000) data = qn.get_queue_data() - assert len(data) > 0 + assert data.size > 0 - def test_network_current_color(self): + @staticmethod + def test_network_current_color(): q = qt.ResourceQueue(num_servers=50) ans = q._current_color(0) col = q.colors['vertex_fill_color'] @@ -302,7 +317,8 @@ def test_network_current_color(self): class TestInfoQueueServer(object): - def test_network(self): + @staticmethod + def test_network(): g = nx.random_geometric_graph(100, 0.2).to_directed() q_cls = {1: qt.InfoQueue} @@ -319,7 +335,8 @@ def test_network(self): class TestAgents(object): - def test_compare(self): + @staticmethod + def test_compare(): a0 = qt.Agent() a1 = qt.Agent() diff --git a/tests/test_statistical_properties.py b/tests/test_statistical_properties.py index ee13e0d..bec1502 100644 --- a/tests/test_statistical_properties.py +++ b/tests/test_statistical_properties.py @@ -18,20 +18,21 @@ def chi2_cdf(q, k, n=1000000, ns=1): return np.mean([empirical_cdf(q, np.random.chisquare(k, n), n) for i in range(ns)]) -@pytest.fixture -def lam(): +@pytest.fixture(name='lam') +def fixture_lam(): return float(np.random.randint(1, 10)) -@pytest.fixture -def rho(): +@pytest.fixture(name='rho') +def fixture_rho(): return np.random.uniform(0.5, 1) @pytest.mark.slow class TestQueueServerStatistically(object): - def test_markovian_property(self, lam, rho): + @staticmethod + def test_markovian_property(lam, rho): # pylint: disable=R0914 nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -74,7 +75,8 @@ def ser(t): assert np.isclose(cc, 0, atol=1e-1) assert p1 > 0.05 - def test_littles_law(self, lam, rho): + @staticmethod + def test_littles_law(lam, rho): nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -101,7 +103,8 @@ def ser(t): assert np.isclose(ans, 0, atol=1e-1) - def test_queue_blocking(self, lam, rho): + @staticmethod + def test_queue_blocking(lam, rho): # pylint: disable=R0914 nSe = np.random.randint(1, 10) mu = lam / (rho * nSe) @@ -137,7 +140,7 @@ def ser(t): @pytest.mark.slow -def test_poisson_random_measure(): +def test_poisson_random_measure(): # pylint: disable=R0914 # This function tests to make sure the poisson_random_measure function # actually simulates a Poisson random measure. It does so looking for # Poisson random variables using a chi-squared test (testing the @@ -165,8 +168,8 @@ def rate(t): mu2 = 4 * np.sum(rate(np.linspace(8, 12, 200))) / 200 # or 2*(4 - 3*sqrt(3)/pi) + 2 mus = [mu1, mu2] - rv1 = np.sum(np.logical_and(3 < arrival_times, arrival_times < 8), axis=1) - rv2 = np.sum(np.logical_and(8 < arrival_times, arrival_times < 12), axis=1) + rv1 = np.sum(np.logical_and(arrival_times > 3, arrival_times < 8), axis=1) + rv2 = np.sum(np.logical_and(arrival_times > 8, arrival_times < 12), axis=1) rvs = [rv1, rv2] df = [max(rv1) + 2, max(rv2) + 2] From 0b1b9848f6231a0d495ff7c830f07b015e9384d2 Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 11:25:24 -0500 Subject: [PATCH 50/60] Actually, I already sorted this agent factory stuff out --- queueing_tool/queues/queue_extentions.py | 2 +- queueing_tool/queues/queue_servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/queueing_tool/queues/queue_extentions.py b/queueing_tool/queues/queue_extentions.py index 3bb4d15..83f6e12 100644 --- a/queueing_tool/queues/queue_extentions.py +++ b/queueing_tool/queues/queue_extentions.py @@ -317,7 +317,7 @@ def _add_arrival(self, agent=None): return self._num_total += 1 - new_agent = self.AgentFactory((self.edge[2], self._oArrivals), len(self.net_data)) + new_agent = self._agent_factory((self.edge[2], self._oArrivals), len(self.net_data)) new_agent._time = self._next_ct heappush(self._arrivals, new_agent) diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index c588c74..b8a6436 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -306,7 +306,6 @@ def service_f(t): # pylint: disable=E0102 self.arrival_f = arrival_f self.service_f = service_f - self.AgentFactory = AgentFactory self._agent_factory = AgentFactory self.collect_data = collect_data self.active_cap = active_cap From 2b9c9cc7f3cc4cac140aa9f1afde9223c6f3a8ca Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 11:25:51 -0500 Subject: [PATCH 51/60] Use random state everywhere. Leads to more predictable simulations throughout --- queueing_tool/graph/graph_generation.py | 108 +++++++++++++++--------- queueing_tool/network/queue_network.py | 26 ++++-- queueing_tool/queues/queue_servers.py | 22 ++--- 3 files changed, 99 insertions(+), 57 deletions(-) diff --git a/queueing_tool/graph/graph_generation.py b/queueing_tool/graph/graph_generation.py index e027c79..3fa8593 100644 --- a/queueing_tool/graph/graph_generation.py +++ b/queueing_tool/graph/graph_generation.py @@ -1,14 +1,13 @@ -import numbers - import networkx as nx import numpy as np +from numpy.random import RandomState from queueing_tool.graph.graph_functions import _test_graph, _calculate_distance from queueing_tool.graph.graph_wrapper import QueueNetworkDiGraph from queueing_tool.union_find import UnionFind -def generate_transition_matrix(g, seed=None): +def generate_transition_matrix(g, seed=None, random_state=None): """Generates a random transition matrix for the graph ``g``. Parameters @@ -18,6 +17,10 @@ def generate_transition_matrix(g, seed=None): seed : int (optional) An integer used to initialize numpy's psuedo-random number generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. Returns ------- @@ -29,8 +32,8 @@ def generate_transition_matrix(g, seed=None): """ g = _test_graph(g) - if isinstance(seed, numbers.Integral): - np.random.seed(seed) + if random_state is None: + random_state = RandomState(seed) nV = g.number_of_nodes() mat = np.zeros((nV, nV)) @@ -41,16 +44,17 @@ def generate_transition_matrix(g, seed=None): if deg == 1: mat[v, ind] = 1 elif deg > 1: - probs = np.ceil(np.random.rand(deg) * 100) / 100. + probs = np.ceil(random_state.rand(deg) * 100) / 100. if np.isclose(np.sum(probs), 0): - probs[np.random.randint(deg)] = 1 + probs[random_state.randint(deg)] = 1 mat[v, ind] = probs / np.sum(probs) return mat -def generate_random_graph(num_vertices=250, prob_loop=0.5, **kwargs): +def generate_random_graph(num_vertices=250, prob_loop=0.5, + seed=None, random_state=None, **kwargs): """Creates a random graph where the edges have different types. This method calls :func:`.minimal_random_graph`, and then adds @@ -63,6 +67,13 @@ def generate_random_graph(num_vertices=250, prob_loop=0.5, **kwargs): The number of vertices in the graph. prob_loop : float (optional, default: 0.5) The probability that a loop gets added to a vertex. + seed : int (optional) + An integer used to initialize numpy's psuedo-random number + generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. **kwargs : Any parameters to send to :func:`.minimal_random_graph` or :func:`.set_types_random`. @@ -105,17 +116,20 @@ def generate_random_graph(num_vertices=250, prob_loop=0.5, **kwargs): recommended use edge type indices starting at 1, since 0 is typically used for terminal edges. """ - g = minimal_random_graph(num_vertices, **kwargs) + if random_state is None: + random_state = RandomState(seed) + + g = minimal_random_graph(num_vertices, random_state=random_state, **kwargs) for v in g.nodes(): e = (v, v) if not g.is_edge(e): - if np.random.uniform() < prob_loop: + if random_state.uniform() < prob_loop: g.add_edge(*e) - g = set_types_random(g, **kwargs) - return g + + return set_types_random(g, random_state=random_state, **kwargs) -def generate_pagerank_graph(num_vertices=250, **kwargs): +def generate_pagerank_graph(num_vertices=250, seed=None, random_state=None, **kwargs): """Creates a random graph where the vertex types are selected using their pagerank. @@ -127,6 +141,13 @@ def generate_pagerank_graph(num_vertices=250, **kwargs): ---------- num_vertices : int (optional, the default is 250) The number of vertices in the graph. + seed : int (optional) + An integer used to initialize numpy's psuedo-random number + generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. **kwargs : Any parameters to send to :func:`.minimal_random_graph` or :func:`.set_types_rank`. @@ -149,15 +170,18 @@ def generate_pagerank_graph(num_vertices=250, **kwargs): loops then have edge types that correspond to the vertices type. The rest of the edges are set to type 1. """ - g = minimal_random_graph(num_vertices, **kwargs) + if random_state is None: + random_state = RandomState(seed) + + g = minimal_random_graph(num_vertices, random_seed=random_state, **kwargs) r = np.zeros(num_vertices) for k, pr in nx.pagerank(g).items(): r[k] = pr - g = set_types_rank(g, rank=r, **kwargs) - return g + + return set_types_rank(g, rank=r, random_seed=random_state, **kwargs) -def minimal_random_graph(num_vertices, seed=None, **kwargs): +def minimal_random_graph(num_vertices, seed=None, random_state=None, **_kwargs): """Creates a connected graph with random vertex locations. Parameters @@ -165,10 +189,12 @@ def minimal_random_graph(num_vertices, seed=None, **kwargs): num_vertices : int The number of vertices in the graph. seed : int (optional) - An integer used to initialize numpy's psuedorandom number - generators. - **kwargs : - Unused. + An integer used to initialize numpy's psuedo-random number + generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. Returns ------- @@ -184,10 +210,10 @@ def minimal_random_graph(num_vertices, seed=None, **kwargs): ``r`` are connect by an edge --- where ``r`` is the smallest number such that the graph ends up connected. """ - if isinstance(seed, numbers.Integral): - np.random.seed(seed) + if random_state is None: + random_state = RandomState(seed) - points = np.random.random((num_vertices, 2)) * 10 + points = random_state.uniform(size=(num_vertices, 2)) * 10 edges = [] for k in range(num_vertices - 1): @@ -215,7 +241,7 @@ def minimal_random_graph(num_vertices, seed=None, **kwargs): def set_types_random(g, proportions=None, loop_proportions=None, seed=None, - **kwargs): + random_state=None, **_kwargs): """Randomly sets ``edge_type`` (edge type) properties of the graph. This function randomly assigns each edge a type. The probability of @@ -237,10 +263,12 @@ def set_types_random(g, proportions=None, loop_proportions=None, seed=None, that are expected to be of that type. The values can must sum to one. seed : int (optional) - An integer used to initialize numpy's psuedorandom number + An integer used to initialize numpy's psuedo-random number generator. - **kwargs : - Unused. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. Returns ------- @@ -266,8 +294,8 @@ def set_types_random(g, proportions=None, loop_proportions=None, seed=None, """ g = _test_graph(g) - if isinstance(seed, numbers.Integral): - np.random.seed(seed) + if random_state is None: + random_state = RandomState(seed) if proportions is None: proportions = {k: 1. / 3 for k in range(1, 4)} @@ -287,13 +315,13 @@ def set_types_random(g, proportions=None, loop_proportions=None, seed=None, eTypes = {} types = list(proportions.keys()) - values = np.random.choice(types, size=len(edges), replace=True, p=props) + values = random_state.choice(types, size=len(edges), replace=True, p=props) for k, e in enumerate(edges): eTypes[e] = values[k] types = list(loop_proportions.keys()) - values = np.random.choice(types, size=len(loops), replace=True, p=lprops) + values = random_state.choice(types, size=len(loops), replace=True, p=lprops) for k, e in enumerate(loops): eTypes[e] = values[k] @@ -305,7 +333,8 @@ def set_types_random(g, proportions=None, loop_proportions=None, seed=None, return g -def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs): +def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, + random_state=None, **_kwargs): """Creates a stylized graph. Sets edge and types using `pagerank`_. This function sets the edge types of a graph to be either 1, 2, or @@ -334,8 +363,10 @@ def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs): seed : int (optional) An integer used to initialize numpy's psuedo-random number generator. - **kwargs : - Unused. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. Returns ------- @@ -350,8 +381,8 @@ def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs): """ g = _test_graph(g) - if isinstance(seed, numbers.Integral): - np.random.seed(seed) + if random_state is None: + random_state = RandomState(seed) tmp = np.sort(np.array(rank)) nDests = int(np.ceil(g.number_of_nodes() * pType2)) @@ -365,7 +396,8 @@ def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs): min_g_dist = np.ones(nFCQ) * np.infty ind_g_dist = np.ones(nFCQ, int) - r, theta = np.random.random(nFCQ) / 500., np.random.random(nFCQ) * 360. + r = random_state.uniform(size=nFCQ) / 500.0 + theta = random_state.uniform(size=nFCQ) * 360.0 xy_pos = np.array([r * np.cos(theta), r * np.sin(theta)]).transpose() g_pos = xy_pos + dest_pos[np.array(np.mod(np.arange(nFCQ), nDests), int)] diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index ac09067..e8e3f59 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -4,7 +4,7 @@ import array import numpy as np -from numpy.random import uniform +from numpy.random import RandomState try: import matplotlib.pyplot as plt @@ -73,6 +73,10 @@ class QueueNetwork(object): seed : int (optional) An integer used to initialize numpy's psuedo-random number generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. colors : dict (optional) A dictionary of RGBA colors used to color the graph. The keys are specified in the Notes section. If this parameter is @@ -304,7 +308,7 @@ class QueueNetwork(object): } def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, - max_agents=1000, blocking='BAS', adjust_graph=True): + max_agents=1000, blocking='BAS', adjust_graph=True, random_state=None): if not isinstance(blocking, str): raise TypeError("blocking must be a string") @@ -319,7 +323,7 @@ def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, self._initialized = False self._prev_edge = None self._fancy_heap = PriorityQueue() - self._blocking = True if blocking.lower() != 'rs' else False + self._blocking = blocking.lower() != 'rs' if colors is None: colors = {} @@ -342,13 +346,18 @@ def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, for k in set(q_classes.keys()) - set(q_args.keys()): q_args[k] = {} + if random_state is None: + random_state = RandomState(seed) + + for kw in q_args.values(): + kw.setdefault('random_state', random_state) + + self.random_state = random_state + for key, args in q_args.items(): if 'colors' not in args: args['colors'] = self.default_q_colors.get(key, self.default_q_colors[1]) - if isinstance(seed, numbers.Integral): - np.random.seed(seed) - if g is not None: g, qs = _prepare_graph(g, self.colors, q_classes, q_args, adjust_graph) @@ -960,7 +969,7 @@ def initialize(self, nActive=1, queues=None, edges=None, edge_type=None): if nActive >= 1 and isinstance(nActive, numbers.Integral): qs = [q.edge[2] for q in self.edge2queue if q.edge[3] != 0] n = min(nActive, len(qs)) - queues = np.random.choice(qs, size=n, replace=False) + queues = self.random_state.choice(qs, size=n, replace=False) elif not isinstance(nActive, numbers.Integral): msg = "If queues is None, then nActive must be an integer." raise TypeError(msg) @@ -1303,7 +1312,8 @@ def _simulate_next_event(self, slow=True): q2.num_blocked += 1 q1._departures[0].blocked += 1 if self._blocking: - t = q2._departures[0]._time + EPS * uniform(0.33, 0.66) + jitter = EPS * self.random_state.uniform(0.33, 0.66) + t = q2._departures[0]._time + jitter q1.delay_service(t) else: q1.delay_service() diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index b8a6436..0384478 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -3,7 +3,7 @@ import numbers from heapq import heappush, heappop -from numpy.random import uniform, exponential +from numpy.random import uniform, exponential, RandomState from numpy import infty import numpy as np @@ -280,7 +280,7 @@ def __init__(self, num_servers=1, arrival_f=None, # pylint: disable=R0914 service_f=None, edge=(0, 0, 0, 1), AgentFactory=Agent, collect_data=False, active_cap=infty, deactive_t=infty, colors=None, seed=None, - coloring_sensitivity=2, **kwargs): + coloring_sensitivity=2, random_state=None, **kwargs): if not isinstance(num_servers, numbers.Integral) and num_servers is not infty: msg = "num_servers must be an integer or infinity." @@ -296,13 +296,16 @@ def __init__(self, num_servers=1, arrival_f=None, # pylint: disable=R0914 self.data = {} # times; agent_id : [arrival, service start, departure] self.queue = collections.deque() + if random_state is None: + random_state = RandomState(seed) + if arrival_f is None: def arrival_f(t): # pylint: disable=E0102 - return t + exponential(1.0) + return t + random_state.exponential(1.0) if service_f is None: def service_f(t): # pylint: disable=E0102 - return t + exponential(0.9) + return t + random_state.exponential(0.9) self.arrival_f = arrival_f self.service_f = service_f @@ -323,9 +326,6 @@ def service_f(t): # pylint: disable=E0102 self._next_ct = 0 # noqa E501 The next time an arrival from outside the network can arrive. self.coloring_sensitivity = coloring_sensitivity - if isinstance(seed, numbers.Integral): - np.random.seed(seed) - if colors is not None: self.colors = colors for col in set(self._default_colors.keys()) - set(self.colors.keys()): @@ -683,11 +683,11 @@ def set_num_servers(self, n): If ``n`` is not positive. """ if not isinstance(n, numbers.Integral) and n is not infty: - the_str = "n must be an integer or infinity.\n{0}" - raise TypeError(the_str.format(str(self))) + the_str = "n ({0}) must be an integer or infinity.\n{1}" + raise TypeError(the_str.format(n, str(self))) elif n <= 0: - the_str = "n must be a positive integer or infinity.\n{0}" - raise ValueError(the_str.format(str(self))) + the_str = "n ({0}) must be a positive integer or infinity.\n{1}" + raise ValueError(the_str.format(n, str(self))) else: self.num_servers = n From a3fdc02d7578982b3b4cbb4055a7dd26db9021ff Mon Sep 17 00:00:00 2001 From: Daniel Jordon Date: Sat, 22 Dec 2018 11:28:43 -0500 Subject: [PATCH 52/60] numpy arrays can't be falsy --- queueing_tool/network/queue_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index e8e3f59..0384472 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -907,7 +907,7 @@ def get_queue_data(self, queues=None, edge=None, edge_type=None, return_header=F for q in queues: dat = self.edge2queue[q].fetch_data() - if dat: + if dat.size > 0: data = np.vstack((data, dat)) if return_header: From 080890faa48056aacf5de6109f859daa7ebeb2c9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:30:43 -0500 Subject: [PATCH 53/60] Update doctest after setting the seed --- queueing_tool/queues/queue_servers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/queueing_tool/queues/queue_servers.py b/queueing_tool/queues/queue_servers.py index 0384478..2c4cca0 100644 --- a/queueing_tool/queues/queue_servers.py +++ b/queueing_tool/queues/queue_servers.py @@ -715,6 +715,7 @@ def simulate(self, n=1, t=None, nA=None, nD=None): >>> import queueing_tool as qt >>> import numpy as np + >>> np.random.seed(15) >>> rate = lambda t: 2 + 16 * np.sin(np.pi * t / 8)**2 >>> arr = lambda t: qt.poisson_random_measure(t, rate, 18) >>> ser = lambda t: t + np.random.gamma(4, 0.1) @@ -733,23 +734,23 @@ def simulate(self, n=1, t=None, nA=None, nD=None): >>> t0 = q.time >>> q.simulate(t=75) >>> round(float(q.time - t0), 1) - 75.1 + 75.2 >>> q.num_arrivals[1] + q.num_departures - num_events - 1597 + 1594 To simulate forward until 1000 new departures are observed run: >>> nA0, nD0 = q.num_arrivals[1], q.num_departures >>> q.simulate(nD=1000) >>> q.num_departures - nD0, q.num_arrivals[1] - nA0 - (1000, 983) + (1000, 1019) To simulate until 1000 new arrivals are observed run: >>> nA0, nD0 = q.num_arrivals[1], q.num_departures >>> q.simulate(nA=1000) >>> q.num_departures - nD0, q.num_arrivals[1] - nA0, - (987, 1000) + (1010, 1000) """ if t is None and nD is None and nA is None: for dummy in range(n): From f45f644bd79bb7ea338c30335a6fc4174a7fad59 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:34:02 -0500 Subject: [PATCH 54/60] Add RandomState variable to queuenetworkdigraph --- queueing_tool/graph/graph_wrapper.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index ad45e6d..7fbf5cf 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -2,6 +2,7 @@ import networkx as nx import numpy as np +from numpy.random import RandomState try: import matplotlib.pyplot as plt @@ -211,6 +212,13 @@ class QueueNetworkDiGraph(nx.DiGraph): data : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, etc. Any object that networkx can turn into a :any:`DiGraph`. + seed : int (optional) + An integer used to initialize numpy's psuedo-random number + generator. + random_state : :class:`~numpy.random.RandomState` (optional) + Used to initialize numpy's psuedo-random number generator. If + present, ``seed`` is ignored. If this is missing then the seed is + used to create a :class:`~numpy.random.RandomState`. kwargs : Any additional arguments for :any:`networkx.DiGraph`. @@ -234,12 +242,16 @@ class QueueNetworkDiGraph(nx.DiGraph): Not suitable for stand alone use; only use with a :class:`.QueueNetwork`. """ - def __init__(self, data=None, **kwargs): + def __init__(self, data=None, seed=None, random_state=None, **kwargs): if isinstance(data, dict): data = adjacency2graph(data, **kwargs) super(QueueNetworkDiGraph, self).__init__(data, **kwargs) + if random_state is None: + random_state = RandomState(seed) + + self.random_state = random_state self.edge_index = {e: k for k, e in enumerate(self.edges())} pos = nx.get_node_attributes(self, name='pos') @@ -309,7 +321,7 @@ def new_edge_property(self, name): def set_node_positions(self, pos=None): if pos is None: - pos = nx.spring_layout(self) + pos = nx.spring_layout(self, seed=self.random_state) nx.set_node_attributes(self, name='pos', values=pos) self.pos = {v: pos[v] for v in self.nodes()} From c83f2ccfdc180f1af9b6dba887761b79e6997b5e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:37:04 -0500 Subject: [PATCH 55/60] Patch up doctests --- queueing_tool/graph/graph_functions.py | 8 ++-- queueing_tool/graph/graph_generation.py | 6 +-- queueing_tool/graph/graph_wrapper.py | 20 +++++----- queueing_tool/network/queue_network.py | 52 +++++++++++++++---------- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/queueing_tool/graph/graph_functions.py b/queueing_tool/graph/graph_functions.py index 9bd193b..d0a53d7 100644 --- a/queueing_tool/graph/graph_functions.py +++ b/queueing_tool/graph/graph_functions.py @@ -115,10 +115,10 @@ def matrix2dict(matrix, graph): >>> mat = qt.generate_transition_matrix(g, seed=123) >>> mat # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE - array([[ 0. , 0.707..., 0.292..., 0. ], - [ 1. , 0. , 0. , 0. ], - [ 0.291..., 0. , 0. , 0.708...], - [ 0. , 0. , 1. , 0. ]]) + array([[0. , 0.70707071, 0.29292929, 0. ], + [1. , 0. , 0. , 0. ], + [0.29113924, 0. , 0. , 0.70886076], + [0. , 0. , 1. , 0. ]]) >>> qt.matrix2dict(mat, g) # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE {0: {1: 0.707..., 2: 0.292...}, diff --git a/queueing_tool/graph/graph_generation.py b/queueing_tool/graph/graph_generation.py index 3fa8593..2ee2bc7 100644 --- a/queueing_tool/graph/graph_generation.py +++ b/queueing_tool/graph/graph_generation.py @@ -96,13 +96,13 @@ def generate_random_graph(num_vertices=250, prob_loop=0.5, >>> non_loops = [e for e in g.edges() if e[0] != e[1]] >>> p1 = np.sum([g.ep(e, 'edge_type') == 1 for e in non_loops]) >>> float(p1) / len(non_loops) # doctest: +ELLIPSIS - 0.486... + 0.498... >>> p2 = np.sum([g.ep(e, 'edge_type') == 2 for e in non_loops]) >>> float(p2) / len(non_loops) # doctest: +ELLIPSIS - 0.249... + 0.260... >>> p3 = np.sum([g.ep(e, 'edge_type') == 3 for e in non_loops]) >>> float(p3) / len(non_loops) # doctest: +ELLIPSIS - 0.264... + 0.241... To make an undirected graph with 25 vertices where there are 4 different edge types with random proportions: diff --git a/queueing_tool/graph/graph_wrapper.py b/queueing_tool/graph/graph_wrapper.py index 7fbf5cf..4112416 100644 --- a/queueing_tool/graph/graph_wrapper.py +++ b/queueing_tool/graph/graph_wrapper.py @@ -117,6 +117,7 @@ def adjacency2graph(adjacency, edge_type=None, adjust=1, **_kwargs): a loop is added with edge type 0. >>> import queueing_tool as qt + >>> import pprint >>> adj = { ... 0: {1: {}}, ... 1: {2: {}, @@ -126,10 +127,9 @@ def adjacency2graph(adjacency, edge_type=None, adjust=1, **_kwargs): >>> # A loop will be added to vertex 2 >>> g = qt.adjacency2graph(adj, edge_type=eTy) >>> ans = qt.graph2dict(g) - >>> ans # doctest: +NORMALIZE_WHITESPACE + >>> pprint.pprint(ans) # doctest: +NORMALIZE_WHITESPACE {0: {1: {'edge_type': 1}}, - 1: {2: {'edge_type': 2}, - 3: {'edge_type': 4}}, + 1: {2: {'edge_type': 2}, 3: {'edge_type': 4}}, 2: {2: {'edge_type': 0}}, 3: {0: {'edge_type': 1}}} @@ -138,10 +138,9 @@ def adjacency2graph(adjacency, edge_type=None, adjust=1, **_kwargs): >>> adj = {0 : [1], 1: [2, 3], 3: [0]} >>> g = qt.adjacency2graph(adj, edge_type=eTy) >>> ans = qt.graph2dict(g) - >>> ans # doctest: +NORMALIZE_WHITESPACE + >>> pprint.pprint(ans) # doctest: +NORMALIZE_WHITESPACE {0: {1: {'edge_type': 1}}, - 1: {2: {'edge_type': 2}, - 3: {'edge_type': 4}}, + 1: {2: {'edge_type': 2}, 3: {'edge_type': 4}}, 2: {2: {'edge_type': 0}}, 3: {0: {'edge_type': 1}}} @@ -151,12 +150,11 @@ def adjacency2graph(adjacency, edge_type=None, adjust=1, **_kwargs): >>> # The graph is unaltered >>> g = qt.adjacency2graph(adj, edge_type=eTy, adjust=2) >>> ans = qt.graph2dict(g) - >>> ans # doctest: +NORMALIZE_WHITESPACE + >>> pprint.pprint(ans) # doctest: +NORMALIZE_WHITESPACE {0: {1: {'edge_type': 1}}, - 1: {2: {'edge_type': 0}, - 3: {'edge_type': 4}}, - 2: {}, - 3: {0: {'edge_type': 1}}} + 1: {2: {'edge_type': 0}, 3: {'edge_type': 4}}, + 2: {}, + 3: {0: {'edge_type': 1}}} """ if isinstance(adjacency, np.ndarray): diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 0384472..760fc20 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -240,11 +240,12 @@ class QueueNetwork(object): >>> import queueing_tool as qt >>> import networkx as nx >>> import numpy as np - >>> + + >>> rs = np.random.RandomState(seed=13) >>> g = nx.moebius_kantor_graph() >>> q_cl = {1: qt.QueueServer} - >>> def arr(t): return t + np.random.gamma(4, 0.0025) - >>> def ser(t): return t + np.random.exponential(0.025) + >>> def arr(t): return t + rs.gamma(4, 0.0025) + >>> def ser(t): return t + rs.exponential(0.025) >>> q_ar = { ... 1: { ... 'arrival_f': arr, @@ -252,7 +253,7 @@ class QueueNetwork(object): ... 'num_servers': 5 ... } ... } - >>> net = qt.QueueNetwork(g, q_classes=q_cl, q_args=q_ar, seed=13) + >>> net = qt.QueueNetwork(g=g, q_classes=q_cl, q_args=q_ar, random_state=rs) To specify that arrivals enter from type 1 edges and simulate run: @@ -263,8 +264,8 @@ class QueueNetwork(object): >>> nA = [(q.num_system, q.edge[2]) for q in net.edge2queue if q.edge[3] == 1] >>> nA.sort(reverse=True) - >>> nA[:5] - [(4, 37), (4, 33), (3, 43), (3, 32), (3, 30)] + >>> nA + ... # doctest: +SKIP To view the state of the network do the following (note, you need to have pygraphviz installed and your graph may be rotated): @@ -891,15 +892,16 @@ def get_queue_data(self, queues=None, edge=None, edge_type=None, return_header=F To get data from an edge connecting two vertices do the following: - >>> data = net.get_queue_data(edge=(1, 50)) + >>> edges = list(g.edges()) + >>> data = net.get_queue_data(edge=edges[0]) To get data from several edges do the following: - >>> data = net.get_queue_data(edge=[(1, 50), (10, 91), (99, 99)]) + >>> data = net.get_queue_data(edge=edges[:3]) You can specify the edge indices as well: - >>> data = net.get_queue_data(queues=(20, 14, 0, 4)) + >>> data = net.get_queue_data(queues=qt.EdgeID(20, 14, 0, 4)) """ queues = _get_queues(self.g, queues, edge, edge_type) @@ -1062,10 +1064,12 @@ def set_transitions(self, mat): likely: >>> import queueing_tool as qt + >>> import pprint >>> g = qt.generate_random_graph(5, seed=10) >>> net = qt.QueueNetwork(g) - >>> net.transitions(False) # doctest: +ELLIPSIS - ... # doctest: +NORMALIZE_WHITESPACE + >>> ans = net.transitions(False) + >>> pprint.pprint(ans) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE {0: {2: 1.0}, 1: {2: 0.5, 3: 0.5}, 2: {0: 0.25, 1: 0.25, 2: 0.25, 4: 0.25}, @@ -1076,8 +1080,9 @@ def set_transitions(self, mat): probabilities, you can do so with the following: >>> net.set_transitions({1 : {2: 0.75, 3: 0.25}}) - >>> net.transitions(False) # doctest: +ELLIPSIS - ... # doctest: +NORMALIZE_WHITESPACE + >>> ans = net.transitions(False) + >>> pprint.pprint(ans) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE {0: {2: 1.0}, 1: {2: 0.75, 3: 0.25}, 2: {0: 0.25, 1: 0.25, 2: 0.25, 4: 0.25}, @@ -1088,13 +1093,14 @@ def set_transitions(self, mat): :func:`.generate_transition_matrix`. You can change all transition probabilities with an :class:`~numpy.ndarray`: - >>> mat = qt.generate_transition_matrix(g, seed=10) + >>> mat = qt.generate_transition_matrix(g, seed=1234) >>> net.set_transitions(mat) - >>> net.transitions(False) # doctest: +ELLIPSIS - ... # doctest: +NORMALIZE_WHITESPACE + >>> ans = net.transitions(False) + >>> pprint.pprint(ans) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE {0: {2: 1.0}, - 1: {2: 0.962..., 3: 0.037...}, - 2: {0: 0.301..., 1: 0.353..., 2: 0.235..., 4: 0.108...}, + 1: {2: 0.240..., 3: 0.759...}, + 2: {0: 0.192..., 1: 0.344..., 2: 0.340..., 4: 0.122...}, 3: {1: 1.0}, 4: {2: 1.0}} @@ -1455,12 +1461,15 @@ def transitions(self, return_matrix=True): >>> import queueing_tool as qt >>> import networkx as nx + >>> import pprint >>> g = nx.sedgewick_maze_graph() >>> net = qt.QueueNetwork(g) Below is an adjacency list for the graph ``g``. - >>> qt.graph2dict(g, False) + >>> ans = qt.graph2dict(g, False) + >>> ans = dict([(k, sorted(v)) for k, v in ans.items()]) + >>> pprint.pprint(ans) ... # doctest: +NORMALIZE_WHITESPACE {0: [2, 5, 7], 1: [7], @@ -1489,8 +1498,9 @@ def transitions(self, return_matrix=True): >>> mat = qt.generate_transition_matrix(g, seed=96) >>> net.set_transitions(mat) - >>> net.transitions(False) # doctest: +ELLIPSIS - ... # doctest: +NORMALIZE_WHITESPACE + >>> ans = net.transitions(False) + >>> pprint.pprint(ans) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE {0: {2: 0.112..., 5: 0.466..., 7: 0.420...}, 1: {7: 1.0}, 2: {0: 0.561..., 6: 0.438...}, From d3d92efcd8201644282386162d97127f3af2442e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:39:13 -0500 Subject: [PATCH 56/60] Sorting edges, nodes, and related functions for consistent output --- queueing_tool/graph/graph_generation.py | 14 ++++++-------- queueing_tool/graph/graph_preparation.py | 6 +++--- queueing_tool/network/queue_network.py | 18 +++++++++--------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/queueing_tool/graph/graph_generation.py b/queueing_tool/graph/graph_generation.py index 2ee2bc7..b742be2 100644 --- a/queueing_tool/graph/graph_generation.py +++ b/queueing_tool/graph/graph_generation.py @@ -38,8 +38,8 @@ def generate_transition_matrix(g, seed=None, random_state=None): nV = g.number_of_nodes() mat = np.zeros((nV, nV)) - for v in g.nodes(): - ind = [e[1] for e in g.out_edges(v)] + for v in sorted(g.nodes()): + ind = [e[1] for e in sorted(g.out_edges(v))] deg = len(ind) if deg == 1: mat[v, ind] = 1 @@ -120,8 +120,9 @@ def generate_random_graph(num_vertices=250, prob_loop=0.5, random_state = RandomState(seed) g = minimal_random_graph(num_vertices, random_state=random_state, **kwargs) - for v in g.nodes(): + for v in sorted(g.nodes()): e = (v, v) + if not g.is_edge(e): if random_state.uniform() < prob_loop: g.add_edge(*e) @@ -412,7 +413,7 @@ def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, dests = set(dests) g.new_vertex_property('loop_type') - for v in g.nodes(): + for v in sorted(g.nodes()): if v in dests: g.set_vp(v, 'loop_type', 3) if not g.is_edge((v, v)): @@ -429,9 +430,6 @@ def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, for v in g.nodes(): if g.vp(v, 'loop_type') in [2, 3]: e = (v, v) - if g.vp(v, 'loop_type') == 2: - g.set_ep(e, 'edge_type', 2) - else: - g.set_ep(e, 'edge_type', 3) + g.set_ep(e, 'edge_type', g.vp(v, 'loop_type')) return g diff --git a/queueing_tool/graph/graph_preparation.py b/queueing_tool/graph/graph_preparation.py index e82dd1c..138d661 100644 --- a/queueing_tool/graph/graph_preparation.py +++ b/queueing_tool/graph/graph_preparation.py @@ -134,7 +134,7 @@ def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph): if 'pos' not in g.vertex_properties(): g.set_pos() - for k, e in enumerate(g.edges()): + for k, e in enumerate(sorted(g.edges())): g.set_ep(e, 'edge_pen_width', 1.25) g.set_ep(e, 'edge_marker_size', 8) if e[0] == e[1]: @@ -142,7 +142,7 @@ def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph): else: g.set_ep(e, 'edge_color', queues[k].colors['edge_color']) - for v in g.nodes(): + for v in sorted(g.nodes()): g.set_vp(v, 'vertex_pen_width', 1) g.set_vp(v, 'vertex_size', 8) e = (v, v) @@ -159,7 +159,7 @@ def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph): def _set_queues(g, q_cls, q_arg, has_cap): queues = [0 for k in range(g.number_of_edges())] - for e in g.edges(): + for e in sorted(g.edges()): eType = g.ep(e, 'edge_type') qedge = (e[0], e[1], g.edge_index[e], eType) diff --git a/queueing_tool/network/queue_network.py b/queueing_tool/network/queue_network.py index 760fc20..3c1dcec 100644 --- a/queueing_tool/network/queue_network.py +++ b/queueing_tool/network/queue_network.py @@ -371,11 +371,11 @@ def __init__(self, g, q_classes=None, q_args=None, seed=None, colors=None, self.in_edges = {} self._route_probs = {} - for v in g.nodes(): + for v in sorted(g.nodes()): vod = g.out_degree(v) probs = array.array('d', [1. / vod for i in range(vod)]) - self.out_edges[v] = [g.edge_index[e] for e in g.out_edges(v)] - self.in_edges[v] = [g.edge_index[e] for e in g.in_edges(v)] + self.out_edges[v] = [g.edge_index[e] for e in sorted(g.out_edges(v))] + self.in_edges[v] = [g.edge_index[e] for e in sorted(g.in_edges(v))] self._route_probs[v] = probs g.freeze() @@ -1118,8 +1118,8 @@ def set_transitions(self, mat): probs = list(value.values()) if key not in self.g.node: - msg = "One of the keys don't correspond to a vertex." - raise ValueError(msg) + msg = "One of the keys ({0}) doesn't correspond to a vertex." + raise ValueError(msg.format(key)) elif self.out_edges[key] and not np.isclose(sum(probs), 1): msg = "Sum of transition probabilities at a vertex was not 1." raise ValueError(msg) @@ -1127,7 +1127,7 @@ def set_transitions(self, mat): msg = "Some transition probabilities were negative." raise ValueError(msg) - for k, e in enumerate(self.g.out_edges(key)): + for k, e in enumerate(sorted(self.g.out_edges(key))): self._route_probs[key][k] = value.get(e[1], 0) def show_active(self, **kwargs): @@ -1520,11 +1520,11 @@ def transitions(self, return_matrix=True): if return_matrix: mat = np.zeros((self.nV, self.nV)) for v in self.g.nodes(): - ind = [e[1] for e in self.g.out_edges(v)] + ind = [e[1] for e in sorted(self.g.out_edges(v))] mat[v, ind] = self._route_probs[v] else: mat = { - k: {e[1]: p for e, p in zip(self.g.out_edges(k), value)} + k: {e[1]: p for e, p in zip(sorted(self.g.out_edges(k)), value)} for k, value in self._route_probs.items() } @@ -1625,7 +1625,7 @@ def _get_queues(g, queues, edge, edge_type): if g.ep(e, 'edge_type') in edge_type: tmp.append(g.edge_index[e]) - queues = np.array(tmp, int) + queues = np.array(sorted(tmp), int) if queues is None: queues = range(g.number_of_edges()) From c88e31b0913cca34fdeab9bfb81ce9d141064ae7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:39:25 -0500 Subject: [PATCH 57/60] Update tests --- tests/test_network.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index 0675c10..4bfadde 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -551,16 +551,17 @@ def test_transitions(queue_network): trans = np.random.uniform(size=deg) trans = trans / sum(trans) - probs = {v: {e[1]: p for e, p in zip(queue_network.g.out_edges(v), trans)}} + edges = sorted(queue_network.g.out_edges(v)) + probs = {v: {e[1]: p for e, p in zip(edges, trans)}} queue_network.set_transitions(probs) mat = queue_network.transitions() - tra = mat[v, [e[1] for e in queue_network.g.out_edges(v)]] + tra = mat[v, [e[1] for e in edges]] assert (tra == trans).all() tra = queue_network.transitions(return_matrix=False) - tra = np.array([tra[v][e[1]] for e in queue_network.g.out_edges(v)]) + tra = np.array([tra[v][e[1]] for e in edges]) assert (tra == trans).all() mat = qt.generate_transition_matrix(queue_network.g) @@ -570,7 +571,7 @@ def test_transitions(queue_network): assert np.allclose(tra, mat) mat = qt.generate_transition_matrix(queue_network.g) - queue_network.set_transitions({v: {e[1]: mat[e] for e in queue_network.g.out_edges(v)}}) # noqa: E501 + queue_network.set_transitions({v: {e[1]: mat[e] for e in edges}}) # noqa: E501 tra = queue_network.transitions() assert np.allclose(tra[v], mat[v]) From b700c2282f9af18f2c986dddd9146fecaa7b9387 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:39:53 -0500 Subject: [PATCH 58/60] Bump the version --- VERSION | 2 +- queueing_tool/__init__.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index 23aa839..f0bb29e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.2 +1.3.0 diff --git a/queueing_tool/__init__.py b/queueing_tool/__init__.py index 8e1d802..cc75220 100644 --- a/queueing_tool/__init__.py +++ b/queueing_tool/__init__.py @@ -1,20 +1,24 @@ from __future__ import absolute_import -from queueing_tool.queues import * -import queueing_tool.queues as queues +from queueing_tool.common import * +import queueing_tool.common as common + +from queueing_tool.graph import * +import queueing_tool.graph as graph from queueing_tool.network import * import queueing_tool.network as network -from queueing_tool.graph import * -import queueing_tool.graph as graph +from queueing_tool.queues import * +import queueing_tool.queues as queues + __all__ = [] -__version__ = '1.2.2' +__version__ = '1.3.0' -__all__.extend(['__version__']) -__all__.extend(queues.__all__) -__all__.extend(network.__all__) +__all__.extend(['__version__', 'EdgeID', 'AgentID']) __all__.extend(graph.__all__) +__all__.extend(network.__all__) +__all__.extend(queues.__all__) # del queues, network, generation, graph From 2d4055fbffe94b311b46aa7e9df4e457b2cd8ca3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:41:37 -0500 Subject: [PATCH 59/60] Suppose more versions of python --- .travis.yml | 2 ++ README.rst | 4 ++-- requirements.txt | 2 +- setup.py | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 744ce7c..ef83c88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ python: - 2.7 - 3.4 - 3.5 + - 3.6 + - 3.7 cache: pip env: global: diff --git a/README.rst b/README.rst index 3eba2fe..654f195 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ Installation ------------ -**Prerequisites:** Queueing-tool runs on Python 2.7 and 3.4-3.5 and it +**Prerequisites:** Queueing-tool runs on Python 2.7 and 3.4-3.7 and it requires `networkx `__ and `numpy `__. If you want to plot, you will need to install `matplotlib `__ as well. @@ -73,7 +73,7 @@ The issue tracker is at https://github.com/djordon/queueing-tool/issues. Please Copyright and license --------------------- -Code and documentation Copyright 2014-2016 Daniel Jordon. Code released +Code and documentation Copyright 2014-2019 Daniel Jordon. Code released under the `MIT license `__. diff --git a/requirements.txt b/requirements.txt index fafae50..43d6849 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Cython==0.23.4 -matplotlib==1.5.1 +matplotlib==2.3.3 mock==1.0.1 networkx==1.11 numpy==1.10.4 diff --git a/setup.py b/setup.py index 3ed100b..bac9a4f 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,8 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Cython', 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Scientific/Engineering :: Mathematics' From 6f38b5b018e7503730b38abff1be57e015687405 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 22 Dec 2018 21:51:57 -0500 Subject: [PATCH 60/60] whitespace --- queueing_tool/graph/graph_functions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/queueing_tool/graph/graph_functions.py b/queueing_tool/graph/graph_functions.py index d0a53d7..8c00e0a 100644 --- a/queueing_tool/graph/graph_functions.py +++ b/queueing_tool/graph/graph_functions.py @@ -115,10 +115,10 @@ def matrix2dict(matrix, graph): >>> mat = qt.generate_transition_matrix(g, seed=123) >>> mat # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE - array([[0. , 0.70707071, 0.29292929, 0. ], - [1. , 0. , 0. , 0. ], - [0.29113924, 0. , 0. , 0.70886076], - [0. , 0. , 1. , 0. ]]) + array([[ 0. , 0.70707071, 0.29292929, 0. ], + [ 1. , 0. , 0. , 0. ], + [ 0.29113924, 0. , 0. , 0.70886076], + [ 0. , 0. , 1. , 0. ]]) >>> qt.matrix2dict(mat, g) # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE {0: {1: 0.707..., 2: 0.292...},