Skip to content

Commit 4ac9272

Browse files
committed
add NodeClassMatrix tracker
1 parent e05cbb1 commit 4ac9272

4 files changed

Lines changed: 301 additions & 7 deletions

File tree

ciw/tests/test_simulation.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,16 @@ def test_simulate_until_deadlock_method(self):
225225
Q.simulate_until_deadlock()
226226
self.assertEqual(round(Q.times_to_deadlock[(0, 0)], 8), 53.88526441)
227227

228+
# NodeClassMatrix tracker
229+
ciw.seed(3)
230+
Q = ciw.Simulation(ciw.create_network_from_yml(
231+
'ciw/tests/testing_parameters/params_deadlock.yml'),
232+
deadlock_detector=ciw.deadlock.StateDigraph(),
233+
tracker=ciw.trackers.NodeClassMatrix())
234+
Q.simulate_until_deadlock()
235+
self.assertEqual(round(Q.times_to_deadlock[((0,), (0,))], 8), 53.88526441)
236+
237+
228238
def test_detect_deadlock_method(self):
229239
Q = ciw.Simulation(ciw.create_network_from_yml(
230240
'ciw/tests/testing_parameters/params_deadlock.yml'),

ciw/tests/test_state_tracker.py

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,184 @@ def test_systempop_accept_method_within_simulation(self):
175175
self.assertEqual(Q.statetracker.state, 1)
176176

177177

178+
class TestNodePopulation(unittest.TestCase):
179+
def test_nodepop_init_method(self):
180+
Q = ciw.Simulation(ciw.create_network_from_yml(
181+
'ciw/tests/testing_parameters/params.yml'))
182+
B = ciw.trackers.NodePopulation()
183+
B.initialise(Q)
184+
self.assertEqual(B.simulation, Q)
185+
self.assertEqual(B.state, [0, 0, 0, 0])
186+
187+
def test_nodepop_change_state_accept_method(self):
188+
Q = ciw.Simulation(ciw.create_network_from_yml(
189+
'ciw/tests/testing_parameters/params.yml'))
190+
B = ciw.trackers.NodePopulation()
191+
B.initialise(Q)
192+
self.assertEqual(B.state, [0, 0, 0, 0])
193+
B.change_state_accept(1, 1)
194+
self.assertEqual(B.state, [1, 0, 0, 0])
195+
196+
def test_nodepop_change_state_block_method(self):
197+
Q = ciw.Simulation(ciw.create_network_from_yml(
198+
'ciw/tests/testing_parameters/params.yml'))
199+
B = ciw.trackers.NodePopulation()
200+
B.initialise(Q)
201+
B.state = [1, 0, 0, 0]
202+
B.change_state_block(1, 1, 2)
203+
self.assertEqual(B.state, [1, 0, 0, 0])
204+
205+
def test_nodepop_change_state_release_method(self):
206+
Q = ciw.Simulation(ciw.create_network_from_yml(
207+
'ciw/tests/testing_parameters/params.yml'))
208+
B = ciw.trackers.NodePopulation()
209+
B.initialise(Q)
210+
B.state = [3, 3, 1, 8]
211+
B.change_state_release(1, 1, 2, False)
212+
self.assertEqual(B.state, [2, 3, 1, 8])
213+
B.change_state_release(1, 1, 2, True)
214+
self.assertEqual(B.state, [1, 3, 1, 8])
215+
216+
def test_nodepop_hash_state_method(self):
217+
Q = ciw.Simulation(ciw.create_network_from_yml(
218+
'ciw/tests/testing_parameters/params.yml'))
219+
B = ciw.trackers.NodePopulation()
220+
B.initialise(Q)
221+
B.state = [7, 3, 1, 0]
222+
self.assertEqual(B.hash_state(), (7, 3, 1, 0))
223+
224+
def test_nodepop_release_method_within_simulation(self):
225+
params = ciw.create_network_from_yml(
226+
'ciw/tests/testing_parameters/params.yml')
227+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodePopulation())
228+
N = Q.transitive_nodes[2]
229+
inds = [ciw.Individual(i) for i in range(5)]
230+
N.individuals = [inds]
231+
for ind in N.individuals[0]:
232+
srvr = N.find_free_server()
233+
N.attach_server(srvr, ind)
234+
Q.statetracker.state = [5, 3, 6, 0]
235+
self.assertEqual(Q.statetracker.state, [5, 3, 6, 0])
236+
Q.current_time = 43.11
237+
N.release(0, Q.nodes[1])
238+
self.assertEqual(Q.statetracker.state, [6, 3, 5, 0])
239+
N.all_individuals[1].is_blocked = True
240+
Q.current_time = 46.72
241+
N.release(1, Q.nodes[1])
242+
self.assertEqual(Q.statetracker.state, [7, 3, 4, 0])
243+
N.release(1, Q.nodes[-1])
244+
self.assertEqual(Q.statetracker.state, [7, 3, 3, 0])
245+
246+
def test_nodepop_block_method_within_simulation(self):
247+
params = ciw.create_network_from_yml(
248+
'ciw/tests/testing_parameters/params.yml')
249+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodePopulation())
250+
N = Q.transitive_nodes[2]
251+
Q.statetracker.state = [5, 3, 6, 0]
252+
self.assertEqual(Q.statetracker.state, [5, 3, 6, 0])
253+
N.block_individual(ciw.Individual(1), Q.nodes[1])
254+
self.assertEqual(Q.statetracker.state, [5, 3, 6, 0])
255+
256+
def test_nodepop_accept_method_within_simulation(self):
257+
params = ciw.create_network_from_yml(
258+
'ciw/tests/testing_parameters/params.yml')
259+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodePopulation())
260+
N = Q.transitive_nodes[2]
261+
self.assertEqual(Q.statetracker.state, [0, 0, 0, 0])
262+
Q.current_time = 45.6
263+
N.accept(ciw.Individual(3, 2))
264+
self.assertEqual(Q.statetracker.state, [0, 0, 1, 0])
265+
266+
267+
class TestNodeClassMatrix(unittest.TestCase):
268+
def test_nodeclassmatrix_init_method(self):
269+
Q = ciw.Simulation(ciw.create_network_from_yml(
270+
'ciw/tests/testing_parameters/params.yml'))
271+
B = ciw.trackers.NodeClassMatrix()
272+
B.initialise(Q)
273+
self.assertEqual(B.simulation, Q)
274+
self.assertEqual(B.state, [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
275+
276+
def test_nodeclassmatrix_change_state_accept_method(self):
277+
Q = ciw.Simulation(ciw.create_network_from_yml(
278+
'ciw/tests/testing_parameters/params.yml'))
279+
B = ciw.trackers.NodeClassMatrix()
280+
B.initialise(Q)
281+
self.assertEqual(B.state, [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
282+
B.change_state_accept(1, 1)
283+
self.assertEqual(B.state, [[0, 1, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
284+
285+
def test_nodeclassmatrix_change_state_block_method(self):
286+
Q = ciw.Simulation(ciw.create_network_from_yml(
287+
'ciw/tests/testing_parameters/params.yml'))
288+
B = ciw.trackers.NodeClassMatrix()
289+
B.initialise(Q)
290+
B.state = [[0, 1, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
291+
B.change_state_block(1, 1, 1)
292+
self.assertEqual(B.state, [[0, 1, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
293+
294+
def test_nodeclassmatrix_change_state_release_method(self):
295+
Q = ciw.Simulation(ciw.create_network_from_yml(
296+
'ciw/tests/testing_parameters/params.yml'))
297+
B = ciw.trackers.NodeClassMatrix()
298+
B.initialise(Q)
299+
B.state = [[0, 1, 2], [1, 1, 1], [0, 1, 0], [4, 3, 1]]
300+
B.change_state_release(1, 1, 2, False)
301+
self.assertEqual(B.state, [[0, 1, 1], [1, 1, 1], [0, 1, 0], [4, 3, 1]])
302+
B.change_state_release(1, 1, 2, True)
303+
self.assertEqual(B.state, [[0, 1, 0], [1, 1, 1], [0, 1, 0], [4, 3, 1]])
304+
305+
def test_nodeclassmatrix_hash_state_method(self):
306+
Q = ciw.Simulation(ciw.create_network_from_yml(
307+
'ciw/tests/testing_parameters/params.yml'))
308+
B = ciw.trackers.NodeClassMatrix()
309+
B.initialise(Q)
310+
B.state = [[0, 2, 0], [1, 1, 1], [0, 1, 0], [4, 3, 1]]
311+
self.assertEqual(B.hash_state(), ((0, 2, 0), (1, 1, 1), (0, 1, 0), (4, 3, 1)))
312+
313+
def test_nodeclassmatrix_release_method_within_simulation(self):
314+
params = ciw.create_network_from_yml(
315+
'ciw/tests/testing_parameters/params.yml')
316+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodeClassMatrix())
317+
N = Q.transitive_nodes[2]
318+
inds = [ciw.Individual(i) for i in range(5)]
319+
N.individuals = [inds]
320+
for ind in N.individuals[0]:
321+
srvr = N.find_free_server()
322+
N.attach_server(srvr, ind)
323+
Q.statetracker.state = [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]]
324+
self.assertEqual(Q.statetracker.state, [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]])
325+
Q.current_time = 43.11
326+
N.release(0, Q.nodes[1])
327+
self.assertEqual(Q.statetracker.state, [[4, 2, 1], [1, 1, 1], [2, 1, 2], [0, 0, 0]])
328+
N.all_individuals[1].is_blocked = True
329+
Q.current_time = 46.72
330+
N.release(1, Q.nodes[1])
331+
self.assertEqual(Q.statetracker.state, [[5, 2, 1], [1, 1, 1], [1, 1, 2], [0, 0, 0]])
332+
N.release(1, Q.nodes[-1])
333+
self.assertEqual(Q.statetracker.state, [[5, 2, 1], [1, 1, 1], [0, 1, 2], [0, 0, 0]])
334+
335+
def test_nodeclassmatrix_block_method_within_simulation(self):
336+
params = ciw.create_network_from_yml(
337+
'ciw/tests/testing_parameters/params.yml')
338+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodeClassMatrix())
339+
N = Q.transitive_nodes[2]
340+
Q.statetracker.state = [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]]
341+
self.assertEqual(Q.statetracker.state, [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]])
342+
N.block_individual(ciw.Individual(1), Q.nodes[1])
343+
self.assertEqual(Q.statetracker.state, [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]])
344+
345+
def test_nodeclassmatrix_accept_method_within_simulation(self):
346+
params = ciw.create_network_from_yml(
347+
'ciw/tests/testing_parameters/params.yml')
348+
Q = ciw.Simulation(params, tracker=ciw.trackers.NodeClassMatrix())
349+
N = Q.transitive_nodes[2]
350+
self.assertEqual(Q.statetracker.state, [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
351+
Q.current_time = 45.6
352+
N.accept(ciw.Individual(3, 2))
353+
self.assertEqual(Q.statetracker.state, [[0, 0, 0], [0, 0, 0], [0, 0, 1], [0, 0, 0]])
354+
355+
178356
class TestNaiveBlocking(unittest.TestCase):
179357
def test_naive_init_method(self):
180358
Q = ciw.Simulation(ciw.create_network_from_yml(
@@ -264,7 +442,6 @@ def test_naive_accept_method_within_simulation(self):
264442
self.assertEqual(Q.statetracker.state, [[0, 0], [0, 0], [1, 0], [0, 0]])
265443

266444

267-
268445
class TestMatrixBlocking(unittest.TestCase):
269446
def test_matrix_init_method(self):
270447
Q = ciw.Simulation(ciw.create_network_from_yml(
@@ -558,3 +735,39 @@ def test_one_node_deterministic_nodepopulation(self):
558735
[Decimal('15.0'), (0,)]
559736
]
560737
self.assertEqual(Q.statetracker.history, expected_history)
738+
739+
def test_one_node_deterministic_nodeclassmatrix(self):
740+
N = ciw.create_network(
741+
arrival_distributions=[ciw.dists.Sequential([1.5, 0.3, 2.4, 1.1])],
742+
service_distributions=[ciw.dists.Sequential([1.8, 2.2, 0.2, 0.2, 0.2, 0.2])],
743+
number_of_servers=[1]
744+
)
745+
B = ciw.trackers.NodeClassMatrix()
746+
Q = ciw.Simulation(N, tracker=B, exact=26)
747+
Q.simulate_until_max_time(15.5)
748+
expected_history = [
749+
[Decimal('0.0'), ((0,),)],
750+
[Decimal('1.5'), ((1,),)],
751+
[Decimal('1.8'), ((2,),)],
752+
[Decimal('3.3'), ((1,),)],
753+
[Decimal('4.2'), ((2,),)],
754+
[Decimal('5.3'), ((3,),)],
755+
[Decimal('5.5'), ((2,),)],
756+
[Decimal('5.7'), ((1,),)],
757+
[Decimal('5.9'), ((0,),)],
758+
[Decimal('6.8'), ((1,),)],
759+
[Decimal('7.0'), ((0,),)],
760+
[Decimal('7.1'), ((1,),)],
761+
[Decimal('7.3'), ((0,),)],
762+
[Decimal('9.5'), ((1,),)],
763+
[Decimal('10.6'), ((2,),)],
764+
[Decimal('11.3'), ((1,),)],
765+
[Decimal('12.1'), ((2,),)],
766+
[Decimal('12.4'), ((3,),)],
767+
[Decimal('13.5'), ((2,),)],
768+
[Decimal('13.7'), ((1,),)],
769+
[Decimal('13.9'), ((0,),)],
770+
[Decimal('14.8'), ((1,),)],
771+
[Decimal('15.0'), ((0,),)]
772+
]
773+
self.assertEqual(Q.statetracker.history, expected_history)

ciw/trackers/state_tracker.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,54 @@ def hash_state(self):
128128
return tuple(self.state)
129129

130130

131+
class NodeClassMatrix(StateTracker):
132+
"""
133+
The node-class matrix tracker records the number of customers of each
134+
class at each node.
135+
136+
Example:
137+
((3, 1),
138+
(0, 1))
139+
This denotes 4 customers at the first node (3 of Class 0, 1 of
140+
Class 0), and 1 customer at the second node (0 of Class 0, 1 of
141+
Class 1).
142+
"""
143+
def initialise(self, simulation):
144+
"""
145+
Initialises the state tracker class.
146+
"""
147+
self.simulation = simulation
148+
self.state = [[0 for cls in range(
149+
self.simulation.network.number_of_classes)] for i in range(
150+
self.simulation.network.number_of_nodes)]
151+
self.history = []
152+
self.timestamp()
153+
154+
def change_state_accept(self, node_id, cust_clss):
155+
"""
156+
Changes the state of the system when a customer is accepted.
157+
"""
158+
self.state[node_id-1][cust_clss] += 1
159+
160+
def change_state_block(self, node_id, destination, cust_clss):
161+
"""
162+
Changes the state of the system when a customer gets blocked.
163+
"""
164+
pass
165+
166+
def change_state_release(self, node_id, destination, cust_clss, blocked):
167+
"""
168+
Changes the state of the system when a customer is released.
169+
"""
170+
self.state[node_id-1][cust_clss] -= 1
171+
172+
def hash_state(self):
173+
"""
174+
Returns a hashable state.
175+
"""
176+
return tuple(tuple(obs) for obs in self.state)
177+
178+
131179
class NaiveBlocking(StateTracker):
132180
"""
133181
The naive blocking tracker records the number of customers at each node,

docs/Reference/state_trackers.rst

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ Currently Ciw has the following state trackers:
88

99
- :ref:`population`
1010
- :ref:`nodepop`
11-
- :ref:`naive`
12-
- :ref:`matrix`
11+
- :ref:`nodeclssmatrix`
12+
- :ref:`naiveblock`
13+
- :ref:`matrixblock`
1314

1415

1516
.. _population:
@@ -32,9 +33,9 @@ The Simulation object takes in the optional argument :code:`tracker` used as fol
3233

3334
.. _nodepop:
3435

35-
----------------------------
36+
--------------------------
3637
The NodePopulation Tracker
37-
----------------------------
38+
--------------------------
3839

3940
The NodePopulation Tracker records the number of customers at each node.
4041
States take the form of list of numbers. An example for a three node queueing network is shown below::
@@ -48,8 +49,30 @@ The Simulation object takes in the optional argument :code:`tracker` used as fol
4849
>>> Q = ciw.Simulation(N, tracker=ciw.trackers.NodePopulation()) # doctest:+SKIP
4950

5051

52+
.. _nodeclssmatrix:
53+
54+
---------------------------
55+
The NodeClassMatrix Tracker
56+
---------------------------
57+
58+
The NodeClassPopulation Tracker records the number of customers at each node, split by customer class.
59+
States take the form of matrix, that is a list of lists, where the rows denote the nodes and the columns denote the customer classes. An example for a three node queueing network with two customer classes is shown below::
60+
61+
[[3, 0],
62+
[0, 1],
63+
[4, 1]]
64+
65+
This denotes that there are:
66+
+ Three customers at the first node - three of Class 0, and none of Class 1
67+
+ One customer at the second node - none of Class 0, and one of Class 1
68+
+ Five customers at the third node - four of Class 0, and one of Class 1.
69+
70+
The Simulation object takes in the optional argument :code:`tracker` used as follows::
71+
72+
>>> Q = ciw.Simulation(N, tracker=ciw.trackers.NodeClassMatrix()) # doctest:+SKIP
73+
5174

52-
.. _naive:
75+
.. _naiveblock:
5376

5477
-------------------------
5578
The NaiveBlocking Tracker
@@ -67,7 +90,7 @@ The Simulation object takes in the optional argument :code:`tracker` used as fol
6790
>>> Q = ciw.Simulation(N, tracker=ciw.trackers.NaiveBlocking()) # doctest:+SKIP
6891

6992

70-
.. _matrix:
93+
.. _matrixblock:
7194

7295
--------------------------
7396
The MatrixBlocking Tracker

0 commit comments

Comments
 (0)