Skip to content

Commit 2b7ad90

Browse files
Merge pull request #187 from CiwPython/server-priorities
Server priorities
2 parents 4cff461 + bdcefb1 commit 2b7ad90

File tree

10 files changed

+246
-21
lines changed

10 files changed

+246
-21
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
History
22
-------
33

4+
v2.2.3 (2021-01-27)
5+
~~~~~~~~~~~~~~~~~~~
6+
- Server priority functions implemented.
7+
48
v2.2.2 (2021-12-17)
59
~~~~~~~~~~~~~~~~~~~
610
- State trackers now take objects not indices

ciw/import_params.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def create_network(arrival_distributions=None,
4242
service_distributions=None,
4343
routing=None,
4444
batching_distributions=None,
45-
ps_thresholds=None):
45+
ps_thresholds=None,
46+
server_priority_functions=None):
4647
"""
4748
Takes in kwargs, creates dictionary.
4849
"""
@@ -69,6 +70,8 @@ def create_network(arrival_distributions=None,
6970
params['batching_distributions'] = batching_distributions
7071
if ps_thresholds != None:
7172
params['ps_thresholds'] = ps_thresholds
73+
if server_priority_functions != None:
74+
params['server_priority_functions'] = server_priority_functions
7275

7376
return create_network_from_dictionary(params)
7477

@@ -160,7 +163,8 @@ def create_network_from_dictionary(params_input):
160163
class_change_matrices['Node ' + str(nd + 1)],
161164
schedules[nd],
162165
preempts[nd],
163-
params['ps_thresholds'][nd]))
166+
params['ps_thresholds'][nd],
167+
params['server_priority_functions'][nd]))
164168
for clss in range(number_of_classes):
165169
if all(isinstance(f, types.FunctionType) for f in params['routing']):
166170
classes.append(CustomerClass(
@@ -229,7 +233,10 @@ def fill_out_dictionary(params_input):
229233
len(params['number_of_servers']))] for i in range(
230234
len(params['arrival_distributions']))},
231235
'ps_thresholds': [1 for _ in range(len(
232-
params['number_of_servers']))]
236+
params['number_of_servers']))],
237+
'server_priority_functions' : [
238+
None for _ in range(len(params['number_of_servers']))
239+
]
233240
}
234241

235242
for a in default_dict:

ciw/network.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ def __init__(self,
1313
class_change_matrix=None,
1414
schedule=None,
1515
preempt=False,
16-
ps_threshold=1):
16+
ps_threshold=1,
17+
server_priority_function=None):
1718
"""
1819
Initialises the ServiceCentre object.
1920
"""
@@ -23,6 +24,7 @@ def __init__(self,
2324
self.schedule = schedule
2425
self.preempt = preempt
2526
self.ps_threshold = ps_threshold
27+
self.server_priority_function = server_priority_function
2628

2729

2830
class CustomerClass(object):

ciw/node.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def __init__(self, id_, simulation):
2121
"""
2222
self.simulation = simulation
2323
node = self.simulation.network.service_centres[id_ - 1]
24+
self.server_priority_function = node.server_priority_function
2425
if node.schedule:
2526
raw_schedule = node.schedule
2627
self.cyclelength = self.increment_time(0, raw_schedule[-1][1])
@@ -124,7 +125,7 @@ def begin_service_if_possible_accept(self, next_individual):
124125
- Update the server's end date (only when servers are not infinite)
125126
"""
126127
next_individual.arrival_date = self.get_now()
127-
free_server = self.find_free_server()
128+
free_server = self.find_free_server(next_individual)
128129
if free_server is not None or isinf(self.c):
129130
if not isinf(self.c):
130131
self.attach_server(free_server, next_individual)
@@ -178,7 +179,7 @@ def begin_service_if_possible_change_shift(self):
178179
srvr.next_end_service_date = ind.service_end_date
179180
break
180181

181-
def begin_service_if_possible_release(self):
182+
def begin_service_if_possible_release(self, next_individual):
182183
"""
183184
Begins the service of the next individual (at point
184185
of previous individual's release)
@@ -188,7 +189,7 @@ def begin_service_if_possible_release(self):
188189
- give a start date and end date
189190
- attach server to individual
190191
"""
191-
srvr = self.find_free_server()
192+
srvr = self.find_free_server(next_individual)
192193
if srvr is not None:
193194
if self.number_interrupted_individuals > 0:
194195
self.begin_interrupted_individuals_service(srvr)
@@ -287,17 +288,27 @@ def detatch_server(self, server, individual):
287288
if server.offduty:
288289
self.kill_server(server)
289290

290-
def find_free_server(self):
291+
def find_free_server(self, ind):
291292
"""
292293
Finds a free server.
293294
"""
294295
if isinf(self.c):
295296
return None
296-
for svr in self.servers:
297+
298+
if self.server_priority_function is None:
299+
all_servers = self.servers
300+
else:
301+
all_servers = sorted(
302+
self.servers, key=lambda srv: self.server_priority_function(srv, ind)
303+
)
304+
305+
for svr in all_servers:
297306
if not svr.busy:
298307
return svr
299308
return None
300309

310+
311+
301312
def find_next_individual(self):
302313
"""
303314
Finds the next individual that should now finish service.
@@ -418,7 +429,7 @@ def release(self, next_individual_index, next_node):
418429
self.write_individual_record(next_individual)
419430
self.simulation.statetracker.change_state_release(self,
420431
next_node, next_individual, next_individual.is_blocked)
421-
self.begin_service_if_possible_release()
432+
self.begin_service_if_possible_release(next_individual)
422433
next_node.accept(next_individual)
423434
self.release_blocked_individual()
424435

ciw/processor_sharing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def begin_service_if_possible_accept(self, next_individual):
6464
next_individual.with_server = True
6565
self.update_all_service_end_dates()
6666

67-
def begin_service_if_possible_release(self):
67+
def begin_service_if_possible_release(self, ind=None):
6868
"""
6969
Begins the service of the next individual (at point
7070
of previous individual's release)

ciw/tests/test_node.py

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def test_begin_service_if_possible_release_method(self):
251251
self.assertEqual(ind.service_start_date, False)
252252
self.assertEqual(ind.service_end_date, False)
253253
Q.current_time = 200.0
254-
Q.transitive_nodes[0].begin_service_if_possible_release()
254+
Q.transitive_nodes[0].begin_service_if_possible_release(ind)
255255
self.assertEqual(ind.arrival_date, 100.0)
256256
self.assertEqual(round(ind.service_time, 5), 0.03382)
257257
self.assertEqual(ind.service_start_date, 200.0)
@@ -266,9 +266,9 @@ def test_release_blocked_individual_method(self):
266266
N1.individuals = [[ciw.Individual(i) for i in range(N1.c + 3)]]
267267
N2.individuals = [[ciw.Individual(i + 100) for i in range(N2.c + 4)]]
268268
for ind in N1.all_individuals[:2]:
269-
N1.attach_server(N1.find_free_server(), ind)
269+
N1.attach_server(N1.find_free_server(ind), ind)
270270
for ind in N2.all_individuals[:1]:
271-
N2.attach_server(N2.find_free_server(), ind)
271+
N2.attach_server(N2.find_free_server(ind), ind)
272272

273273
self.assertEqual([str(obs) for obs in N1.all_individuals],
274274
['Individual 0',
@@ -722,3 +722,76 @@ def event_and_return_nextnode(simself, next_active_node):
722722
'ciw/tests/testing_parameters/params.yml')
723723
Q = AssertSim(N)
724724
Q.simulate_until_max_time(100)
725+
726+
def test_server_priority_function_allocate_to_less_busy(self):
727+
"""
728+
Test the server priority function when we prioritise the server that was
729+
less busy throughout the simulation.
730+
"""
731+
def get_server_busy_time(server, ind):
732+
return server.busy_time
733+
734+
ciw.seed(0)
735+
Q = ciw.Simulation(ciw.create_network(
736+
arrival_distributions=[ciw.dists.Exponential(1)],
737+
service_distributions=[ciw.dists.Exponential(2)],
738+
number_of_servers=[2],
739+
server_priority_functions=[get_server_busy_time]
740+
)
741+
)
742+
Q.simulate_until_max_time(1000)
743+
744+
expected_times = [245.07547532640024, 244.68396417751663]
745+
for i, srv in enumerate(Q.nodes[1].servers):
746+
self.assertEqual(srv.busy_time, expected_times[i])
747+
748+
749+
def test_server_priority_function_allocate_to_last_server_first(self):
750+
"""
751+
Test the server priority function when we prioritise the server with the
752+
highest id number.
753+
"""
754+
def get_server_busy_time(server, ind):
755+
return -server.id_number
756+
757+
ciw.seed(0)
758+
Q = ciw.Simulation(ciw.create_network(
759+
arrival_distributions=[ciw.dists.Exponential(1)],
760+
service_distributions=[ciw.dists.Exponential(2)],
761+
number_of_servers=[2],
762+
server_priority_functions=[get_server_busy_time]
763+
)
764+
)
765+
Q.simulate_until_max_time(1000)
766+
767+
expected_times = [158.68745586286119, 331.0719836410557]
768+
for i, srv in enumerate(Q.nodes[1].servers):
769+
self.assertEqual(srv.busy_time, expected_times[i])
770+
771+
def test_server_priority_function_two_nodes(self):
772+
"""
773+
Test the server priority function with two nodes that each has a
774+
different priority rule.
775+
"""
776+
def prioritise_less_busy(srv, ind):
777+
return srv.busy_time
778+
779+
def prioritise_highest_id(srv, ind):
780+
return -srv.id_number
781+
782+
ciw.seed(0)
783+
Q = ciw.Simulation(ciw.create_network(
784+
arrival_distributions=[ciw.dists.Exponential(1), ciw.dists.Exponential(1)],
785+
service_distributions=[ciw.dists.Exponential(2), ciw.dists.Exponential(2)],
786+
number_of_servers=[2, 2],
787+
routing=[[0, 0], [0, 0]],
788+
server_priority_functions=[prioritise_less_busy, prioritise_highest_id]
789+
)
790+
)
791+
Q.simulate_until_max_time(1000)
792+
expected_times_node_1 = [256.2457715650031, 257.59339967047254]
793+
expected_times_node_2 = [157.35577182806387, 356.41473247082365]
794+
795+
for i, (srv_1, srv_2) in enumerate(zip(Q.nodes[1].servers, Q.nodes[2].servers)):
796+
self.assertEqual(srv_1.busy_time, expected_times_node_1[i])
797+
self.assertEqual(srv_2.busy_time, expected_times_node_2[i])

ciw/tests/test_state_tracker.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_base_release_method_within_simulation(self):
5959
inds = [ciw.Individual(i) for i in range(5)]
6060
N.individuals = [inds]
6161
for ind in N.all_individuals:
62-
srvr = N.find_free_server()
62+
srvr = N.find_free_server(ind)
6363
N.attach_server(srvr, ind)
6464
self.assertEqual(Q.statetracker.state, None)
6565
Q.current_time = 43.11
@@ -154,7 +154,7 @@ def test_systempop_release_method_within_simulation(self):
154154
inds = [ciw.Individual(i) for i in range(5)]
155155
N.individuals = [inds]
156156
for ind in N.individuals[0]:
157-
srvr = N.find_free_server()
157+
srvr = N.find_free_server(ind)
158158
N.attach_server(srvr, ind)
159159
Q.statetracker.state = 14
160160
self.assertEqual(Q.statetracker.state, 14)
@@ -252,7 +252,7 @@ def test_nodepop_release_method_within_simulation(self):
252252
inds = [ciw.Individual(i) for i in range(5)]
253253
N.individuals = [inds]
254254
for ind in N.individuals[0]:
255-
srvr = N.find_free_server()
255+
srvr = N.find_free_server(ind)
256256
N.attach_server(srvr, ind)
257257
Q.statetracker.state = [5, 3, 6, 0]
258258
self.assertEqual(Q.statetracker.state, [5, 3, 6, 0])
@@ -348,7 +348,7 @@ def test_nodeclassmatrix_release_method_within_simulation(self):
348348
inds = [ciw.Individual(i) for i in range(5)]
349349
N.individuals = [inds]
350350
for ind in N.individuals[0]:
351-
srvr = N.find_free_server()
351+
srvr = N.find_free_server(ind)
352352
N.attach_server(srvr, ind)
353353
Q.statetracker.state = [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]]
354354
self.assertEqual(Q.statetracker.state, [[3, 2, 1], [1, 1, 1], [3, 1, 2], [0, 0, 0]])
@@ -443,7 +443,7 @@ def test_naive_release_method_within_simulation(self):
443443
inds = [ciw.Individual(i) for i in range(5)]
444444
N.individuals = [inds]
445445
for ind in N.individuals[0]:
446-
srvr = N.find_free_server()
446+
srvr = N.find_free_server(ind)
447447
N.attach_server(srvr, ind)
448448
Q.statetracker.state = [[4, 1], [3, 0], [5, 1], [0, 0]]
449449
self.assertEqual(Q.statetracker.state, [[4, 1], [3, 0], [5, 1], [0, 0]])
@@ -595,7 +595,7 @@ def test_matrix_release_method_within_simulation(self):
595595
inds = [ciw.Individual(i) for i in range(5)]
596596
N.individuals = [inds]
597597
for ind in N.individuals[0]:
598-
srvr = N.find_free_server()
598+
srvr = N.find_free_server(ind)
599599
N.attach_server(srvr, ind)
600600
Q.statetracker.state = [[[[], [2], [], []],
601601
[[], [], [], []],

ciw/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.2.2"
1+
__version__ = "2.2.3"

docs/Guides/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Contents:
2020
batching.rst
2121
baulking.rst
2222
server_schedule.rst
23+
server_priority.rst
2324
dynamic_customerclasses.rst
2425
state_trackers.rst
2526
deadlock.rst

0 commit comments

Comments
 (0)