Skip to content

Commit 1155647

Browse files
Merge pull request #195 from CiwPython/reneging
Reneging
2 parents 300c8d4 + 78d270b commit 1155647

25 files changed

+1533
-152
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ A number of other features are also implemented, including:
8282
+ `Time-dependent and state-dependent distributions <https://ciw.readthedocs.io/en/latest/Guides/time_dependent.html>`_
8383
+ `Batch arrivals <https://ciw.readthedocs.io/en/latest/Guides/batching.html>`_
8484
+ `Baulking customers <https://ciw.readthedocs.io/en/latest/Guides/baulking.html>`_
85+
+ `Reneging customers <https://ciw.readthedocs.io/en/latest/Guides/reneging.html>`_
8586
+ `Processor sharing <https://ciw.readthedocs.io/en/latest/Guides/processor-sharing.html>`_
8687
+ `Multiple customer classes <https://ciw.readthedocs.io/en/latest/Tutorial-II/tutorial_vii.html>`_
8788
+ `Priorities <https://ciw.readthedocs.io/en/latest/Guides/priority.html>`_

ciw/data_record.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
DataRecord = namedtuple('Record', [
44
'id_number',
55
'customer_class',
6+
'original_customer_class',
67
'node',
78
'arrival_date',
89
'waiting_time',
@@ -14,5 +15,6 @@
1415
'destination',
1516
'queue_size_at_arrival',
1617
'queue_size_at_departure',
17-
'server_id'
18+
'server_id',
19+
'record_type'
1820
])

ciw/import_params.py

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ def get_distribution(dist):
1111
Returns instances of the distribution classes that
1212
correspond to the indicator string in the .yml file.
1313
"""
14+
if (dist is None) or (dist == 'None'):
15+
return None
1416
if dist[0] == 'Uniform':
1517
return ciw.dists.Uniform(dist[1], dist[2])
1618
if dist[0] == 'Deterministic':
@@ -36,14 +38,18 @@ def get_distribution(dist):
3638
def create_network(arrival_distributions=None,
3739
baulking_functions=None,
3840
class_change_matrices=None,
41+
class_change_time_distributions=None,
3942
number_of_servers=None,
4043
priority_classes=None,
4144
queue_capacities=None,
4245
service_distributions=None,
4346
routing=None,
4447
batching_distributions=None,
4548
ps_thresholds=None,
46-
server_priority_functions=None):
49+
server_priority_functions=None,
50+
reneging_time_distributions=None,
51+
reneging_destinations=None,
52+
):
4753
"""
4854
Takes in kwargs, creates dictionary.
4955
"""
@@ -60,6 +66,8 @@ def create_network(arrival_distributions=None,
6066
params['baulking_functions'] = baulking_functions
6167
if class_change_matrices != None:
6268
params['class_change_matrices'] = class_change_matrices
69+
if class_change_time_distributions is not None:
70+
params['class_change_time_distributions'] = class_change_time_distributions
6371
if priority_classes != None:
6472
params['priority_classes'] = priority_classes
6573
if queue_capacities != None:
@@ -72,6 +80,10 @@ def create_network(arrival_distributions=None,
7280
params['ps_thresholds'] = ps_thresholds
7381
if server_priority_functions != None:
7482
params['server_priority_functions'] = server_priority_functions
83+
if reneging_time_distributions is not None:
84+
params['reneging_time_distributions'] = reneging_time_distributions
85+
if reneging_destinations is not None:
86+
params['reneging_destinations'] = reneging_destinations
7587

7688
return create_network_from_dictionary(params)
7789

@@ -105,6 +117,13 @@ def create_network_from_yml(directory_name):
105117
for dist in params['service_distributions'][clss]:
106118
dists.append(get_distribution(dist))
107119
params['service_distributions'][clss] = dists
120+
for clss in params['reneging_time_distributions']:
121+
dists = [get_distribution(dist) for dist in params['reneging_time_distributions'][clss]]
122+
params['reneging_time_distributions'][clss] = dists
123+
if 'class_change_time_distributions' in params:
124+
for clss, dist_original in enumerate(params['class_change_time_distributions']):
125+
dists = [get_distribution(dist) for dist in dist_original]
126+
params['class_change_time_distributions'][clss] = dists
108127
validify_dictionary(params)
109128
return create_network_from_dictionary(params)
110129

@@ -116,6 +135,8 @@ def create_network_from_dictionary(params_input):
116135
params = fill_out_dictionary(params_input)
117136
validify_dictionary(params)
118137
# Then make the Network object
138+
number_of_classes = params['number_of_classes']
139+
number_of_nodes = params['number_of_nodes']
119140
arrivals = [params['arrival_distributions']['Class ' + str(clss)]
120141
for clss in range(len(params['arrival_distributions']))]
121142
services = [params['service_distributions']['Class ' + str(clss)]
@@ -125,17 +146,23 @@ def create_network_from_dictionary(params_input):
125146
else:
126147
routing = [params['routing']['Class ' + str(clss)]
127148
for clss in range(len(params['routing']))]
128-
priorities = [params['priority_classes']['Class ' + str(clss)]
129-
for clss in range(len(params['priority_classes']))]
149+
if isinstance(params['priority_classes'], dict):
150+
priorities = [params['priority_classes']['Class ' + str(clss)]
151+
for clss in range(len(params['priority_classes']))]
152+
preempt_priorities = [False for _ in range(number_of_nodes)]
153+
if isinstance(params['priority_classes'], tuple):
154+
priorities = [params['priority_classes'][0]['Class ' + str(clss)]
155+
for clss in range(len(params['priority_classes'][0]))]
156+
preempt_priorities = params['priority_classes'][1]
130157
baulking_functions = [params['baulking_functions']['Class ' + str(clss)]
131158
for clss in range(len(params['baulking_functions']))]
132159
batches = [params['batching_distributions']['Class ' + str(clss)]
133160
for clss in range(len(params['batching_distributions']))]
134-
number_of_classes = params['number_of_classes']
135-
number_of_nodes = params['number_of_nodes']
136161
queueing_capacities = [float(i) if i == "Inf" else i for i in params['queue_capacities']]
137162
class_change_matrices = params.get('class_change_matrices',
138163
{'Node ' + str(nd + 1): None for nd in range(number_of_nodes)})
164+
class_change_time_distributions = params.get('class_change_time_distributions',
165+
[[None for clss1 in range(number_of_classes)] for clss2 in range(number_of_classes)])
139166
number_of_servers, schedules, nodes, classes, preempts = [], [], [], [], []
140167
for c in params['number_of_servers']:
141168
if isinstance(c, (tuple, list)):
@@ -163,6 +190,7 @@ def create_network_from_dictionary(params_input):
163190
class_change_matrices['Node ' + str(nd + 1)],
164191
schedules[nd],
165192
preempts[nd],
193+
preempt_priorities[nd],
166194
params['ps_thresholds'][nd],
167195
params['server_priority_functions'][nd]))
168196
for clss in range(number_of_classes):
@@ -173,15 +201,22 @@ def create_network_from_dictionary(params_input):
173201
routing,
174202
priorities[clss],
175203
baulking_functions[clss],
176-
batches[clss]))
204+
batches[clss],
205+
params['reneging_time_distributions']['Class ' + str(clss)],
206+
params['reneging_destinations']['Class ' + str(clss)],
207+
class_change_time_distributions[clss]))
177208
else:
178209
classes.append(CustomerClass(
179210
arrivals[clss],
180211
services[clss],
181212
routing[clss],
182213
priorities[clss],
183214
baulking_functions[clss],
184-
batches[clss]))
215+
batches[clss],
216+
params['reneging_time_distributions']['Class ' + str(clss)],
217+
params['reneging_destinations']['Class ' + str(clss)],
218+
class_change_time_distributions[clss]))
219+
185220
n = Network(nodes, classes)
186221
if all(isinstance(f, types.FunctionType) for f in params['routing']):
187222
n.process_based = True
@@ -214,6 +249,14 @@ def fill_out_dictionary(params_input):
214249
if isinstance(params['batching_distributions'], list):
215250
btch_dists = params['batching_distributions']
216251
params['batching_distributions'] = {'Class 0': btch_dists}
252+
if 'reneging_time_distributions' in params:
253+
if isinstance(params['reneging_time_distributions'], list):
254+
reneging_dists = params['reneging_time_distributions']
255+
params['reneging_time_distributions'] = {'Class 0': reneging_dists}
256+
if 'reneging_destinations' in params:
257+
if isinstance(params['reneging_destinations'], list):
258+
reneging_dests = params['reneging_destinations']
259+
params['reneging_destinations'] = {'Class 0': reneging_dests}
217260

218261
default_dict = {
219262
'name': 'Simulation',
@@ -235,8 +278,13 @@ def fill_out_dictionary(params_input):
235278
'ps_thresholds': [1 for _ in range(len(
236279
params['number_of_servers']))],
237280
'server_priority_functions' : [
238-
None for _ in range(len(params['number_of_servers']))
239-
]
281+
None for _ in range(len(params['number_of_servers']))],
282+
'reneging_time_distributions': {'Class ' + str(i): [
283+
None for _ in range(len(params['number_of_servers']))]
284+
for i in range(len(params['arrival_distributions']))},
285+
'reneging_destinations': {'Class ' + str(i): [
286+
-1 for _ in range(len(params['number_of_servers']))]
287+
for i in range(len(params['arrival_distributions']))},
240288
}
241289

242290
for a in default_dict:
@@ -254,28 +302,38 @@ def validify_dictionary(params):
254302
params['number_of_classes'] ==
255303
len(params['arrival_distributions']) ==
256304
len(params['service_distributions']) ==
257-
len(params['batching_distributions']))
305+
len(params['batching_distributions']) ==
306+
len(params['reneging_time_distributions']) ==
307+
len(params['reneging_destinations'])
308+
)
258309
else:
259310
consistant_num_classes = (
260311
params['number_of_classes'] ==
261312
len(params['arrival_distributions']) ==
262313
len(params['service_distributions']) ==
263314
len(params['routing']) ==
264-
len(params['batching_distributions']))
315+
len(params['batching_distributions']) ==
316+
len(params['reneging_time_distributions']) ==
317+
len(params['reneging_destinations'])
318+
)
265319
if not consistant_num_classes:
266320
raise ValueError('Ensure consistant number of classes is used throughout.')
267321
if all(isinstance(f, types.FunctionType) for f in params['routing']):
268322
consistant_class_names = (
269323
set(params['arrival_distributions']) ==
270324
set(params['service_distributions']) ==
271325
set(params['batching_distributions']) ==
326+
set(params['reneging_time_distributions']) ==
327+
set(params['reneging_destinations']) ==
272328
set(['Class ' + str(i) for i in range(params['number_of_classes'])]))
273329
else:
274330
consistant_class_names = (
275331
set(params['arrival_distributions']) ==
276332
set(params['service_distributions']) ==
277333
set(params['routing']) ==
278334
set(params['batching_distributions']) ==
335+
set(params['reneging_time_distributions']) ==
336+
set(params['reneging_destinations']) ==
279337
set(['Class ' + str(i) for i in range(params['number_of_classes'])]))
280338
if not consistant_class_names:
281339
raise ValueError('Ensure correct names for customer classes.')
@@ -285,8 +343,11 @@ def validify_dictionary(params):
285343
len(obs) for obs in params['arrival_distributions'].values()] + [
286344
len(obs) for obs in params['service_distributions'].values()] + [
287345
len(obs) for obs in params['batching_distributions'].values()] + [
346+
len(obs) for obs in params['reneging_time_distributions'].values()] + [
347+
len(obs) for obs in params['reneging_destinations'].values()] + [
288348
len(params['routing'])] + [
289349
len(params['number_of_servers'])] + [
350+
len(params['server_priority_functions'])] + [
290351
len(params['queue_capacities'])]
291352
else:
292353
num_nodes_count = [
@@ -295,12 +356,15 @@ def validify_dictionary(params):
295356
len(obs) for obs in params['service_distributions'].values()] + [
296357
len(obs) for obs in params['routing'].values()] + [
297358
len(obs) for obs in params['batching_distributions'].values()] + [
359+
len(obs) for obs in params['reneging_time_distributions'].values()] + [
360+
len(obs) for obs in params['reneging_destinations'].values()] + [
298361
len(row) for row in [obs for obs in params['routing'].values()][0]] + [
299362
len(params['number_of_servers'])] + [
363+
len(params['server_priority_functions'])] + [
300364
len(params['queue_capacities'])]
301365
if len(set(num_nodes_count)) != 1:
302366
raise ValueError('Ensure consistant number of nodes is used throughout.')
303-
if not all(isinstance(f, types.FunctionType) for f in params['routing']):
367+
if not all(isinstance(f, types.FunctionType) for f in params['routing']):
304368
for clss in params['routing'].values():
305369
for row in clss:
306370
if sum(row) > 1.0 or min(row) < 0.0 or max(row) > 1.0:
@@ -320,7 +384,16 @@ def validify_dictionary(params):
320384
for row in nd:
321385
if sum(row) > 1.0 or min(row) < 0.0 or max(row) > 1.0:
322386
raise ValueError('Ensure that class change matrix is valid.')
387+
if 'class_change_time_distributions' in params:
388+
wrong_num_classes = any(len(row) != params['number_of_classes'] for row in params['class_change_time_distributions']) or (len(params['class_change_time_distributions']) != params['number_of_classes'])
389+
if wrong_num_classes:
390+
raise ValueError('Ensure correct number of customer classes used in class_change_time_distributions.')
323391
for n in params['number_of_servers']:
324392
if isinstance(n, str) and n != 'Inf':
325393
if n not in params:
326394
raise ValueError('No schedule ' + str(n) + ' defined.')
395+
possible_destinations = list(range(1, params['number_of_nodes'] + 1)) + [-1]
396+
for dests in params['reneging_destinations']:
397+
correct_destinations = all(d in possible_destinations for d in params['reneging_destinations'][dests])
398+
if not correct_destinations:
399+
raise ValueError('Ensure all reneging destinations are possible.')

ciw/individual.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __init__(self, id_number, customer_class=0, priority_class=0, simulation=Fal
1919
self.previous_class = customer_class
2020
self.priority_class = priority_class
2121
self.prev_priority_class = priority_class
22+
self.original_class = customer_class
2223
self.is_blocked = False
2324
self.server = False
2425
self.queue_size_at_arrival = False

ciw/network.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def __init__(self,
1212
queueing_capacity,
1313
class_change_matrix=None,
1414
schedule=None,
15-
preempt=False,
15+
schedule_preempt=False,
16+
priority_preempt=False,
1617
ps_threshold=1,
1718
server_priority_function=None):
1819
"""
@@ -22,10 +23,11 @@ def __init__(self,
2223
self.queueing_capacity = queueing_capacity
2324
self.class_change_matrix = class_change_matrix
2425
self.schedule = schedule
25-
self.preempt = preempt
26+
self.schedule_preempt = schedule_preempt
27+
self.priority_preempt = priority_preempt
2628
self.ps_threshold = ps_threshold
2729
self.server_priority_function = server_priority_function
28-
30+
self.class_change_time = False
2931

3032
class CustomerClass(object):
3133
"""
@@ -44,7 +46,10 @@ def __init__(self,
4446
routing,
4547
priority_class,
4648
baulking_functions,
47-
batching_distributions):
49+
batching_distributions,
50+
reneging_time_distributions,
51+
reneging_destinations,
52+
class_change_time_distributions):
4853
"""
4954
Initialises the CutomerCass object.
5055
"""
@@ -54,6 +59,9 @@ def __init__(self,
5459
self.routing = routing
5560
self.priority_class = priority_class
5661
self.baulking_functions = baulking_functions
62+
self.reneging_time_distributions = reneging_time_distributions
63+
self.reneging_destinations = reneging_destinations
64+
self.class_change_time_distributions = class_change_time_distributions
5765

5866
class Network(object):
5967
"""
@@ -71,4 +79,12 @@ def __init__(self, service_centres, customer_classes):
7179
self.number_of_nodes = len(service_centres)
7280
self.number_of_classes = len(customer_classes)
7381
self.number_of_priority_classes = len(set([clss.priority_class for clss in customer_classes]))
74-
self.priority_class_mapping = {i: clss.priority_class for i, clss in enumerate(customer_classes)}
82+
self.priority_class_mapping = {i: clss.priority_class for i, clss in enumerate(customer_classes)}
83+
for nd_id, node in enumerate(self.service_centres):
84+
if all(clss.reneging_time_distributions[nd_id] == None for clss in self.customer_classes):
85+
node.reneging = False
86+
else:
87+
node.reneging = True
88+
if any(dist is not None for clss in customer_classes for dist in clss.class_change_time_distributions):
89+
for node in self.service_centres:
90+
node.class_change_time = True

0 commit comments

Comments
 (0)