Skip to content

Commit 93dbcd5

Browse files
committed
Merge branch 'fix-floatingpoint' into london_tidyingup
Incorporates the fix-floatingpoints bug fix. Now has ExactNode and ExactArrivalNode objects, and and 'Exact' kwarg.
2 parents 2f395ca + b8d77ee commit 93dbcd5

9 files changed

Lines changed: 182 additions & 39 deletions

File tree

112 Bytes
Binary file not shown.

ciw/arrival_node.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,18 @@ def have_event(self):
5656
self.rejection_dict[next_node.id_number][
5757
self.next_class].append(self.next_event_date)
5858
self.event_dates_dict[self.next_node][
59-
self.next_class] += self.inter_arrival(
60-
self.next_node, self.next_class)
59+
self.next_class] = self.increment_time(
60+
self.event_dates_dict[self.next_node][
61+
self.next_class], self.inter_arrival(
62+
self.next_node, self.next_class))
6163
self.find_next_event_date()
6264

65+
def increment_time(self, original, increment):
66+
"""
67+
Increments the original time by the increment
68+
"""
69+
return original + increment
70+
6371
def initialise_event_dates_dict(self):
6472
"""
6573
Initialises the next event dates dictionary

ciw/data_record.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def __init__(self,
2424
self.customer_class = customer_class
2525
self.queue_size_at_arrival = queue_size_at_arrival
2626
self.queue_size_at_departure = queue_size_at_departure
27-
self.service_end_date = service_start_date + service_time
28-
self.wait = service_start_date - arrival_date
29-
self.blocked = exit_date - self.service_end_date
27+
self.service_end_date = self.service_start_date + self.service_time
28+
self.wait = self.service_start_date - self.arrival_date
29+
self.blocked = self.exit_date - self.service_end_date
3030
self.node = node
3131
self.destination = destination
3232

ciw/exactnode.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from node import Node
2+
from arrival_node import ArrivalNode
3+
from decimal import Decimal, getcontext
4+
5+
class ExactNode(Node):
6+
"""
7+
Inherits from the Node class, implements a more
8+
precise version of addition to fix discrepencies
9+
with floating point numbers.
10+
"""
11+
def increment_time(self, original, increment):
12+
"""
13+
Increments the original time by the increment
14+
"""
15+
return Decimal(str(original)) + Decimal(str(increment))
16+
17+
def get_service_time(self, cls):
18+
"""
19+
Returns a service time for the given customer class
20+
"""
21+
return Decimal(str(self.simulation.service_times[self.id_number][cls]()))
22+
23+
def get_now(self, current_time):
24+
"""
25+
Gets the current time
26+
"""
27+
return Decimal(str(current_time))
28+
29+
30+
class ExactArrivalNode(ArrivalNode):
31+
"""
32+
Inherits from the ArrivalNode class, implements a
33+
more precise version of addition to fix discrepencies
34+
with floating point numbers.
35+
"""
36+
def increment_time(self, original, increment):
37+
"""
38+
Increments the original time by the increment
39+
"""
40+
return Decimal(str(original)) + Decimal(str(increment))
41+
42+
def inter_arrival(self, nd, cls):
43+
"""
44+
Samples the inter-arrival time for next class and node.
45+
"""
46+
return Decimal(str(self.simulation.inter_arrival_times[nd][cls]()))

ciw/node.py

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,32 @@ def __init__(self, id_, simulation):
1919
Initialise a node.
2020
"""
2121
self.simulation = simulation
22-
self.mu = [self.simulation.mu[cls][id_-1]
22+
self.mu = [self.simulation.mu[cls][id_ - 1]
2323
for cls in xrange(len(self.simulation.mu))]
24-
self.scheduled_servers = self.simulation.schedules[id_-1]
24+
self.scheduled_servers = self.simulation.schedules[id_ - 1]
2525
if self.scheduled_servers:
2626
self.schedule = self.simulation.parameters[
27-
self.simulation.c[id_-1]]
27+
self.simulation.c[id_ - 1]]
2828
self.cyclelength = self.simulation.parameters[
2929
'Cycle_length']
3030
self.c = self.schedule[0][1]
31-
self.masterschedule = [i*self.cyclelength + obs
32-
for i in xrange(int(
31+
self.masterschedule = [self.increment_time(i*self.cyclelength,
32+
obs) for i in xrange(int(
3333
self.simulation.max_simulation_time//self.cyclelength
3434
) + 2) for obs in [t[0] for t in self.schedule]][1:]
3535
else:
36-
self.c = self.simulation.c[id_-1]
37-
if self.simulation.queue_capacities[id_-1] == "Inf":
36+
self.c = self.simulation.c[id_ - 1]
37+
if self.simulation.queue_capacities[id_ - 1] == "Inf":
3838
self.node_capacity = "Inf"
3939
else:
4040
self.node_capacity = self.simulation.queue_capacities[
41-
id_-1] + self.c
41+
id_ - 1] + self.c
4242
self.transition_row = [self.simulation.transition_matrix[j][
43-
id_-1] for j in xrange(len(
43+
id_ - 1] for j in xrange(len(
4444
self.simulation.transition_matrix))]
4545
if self.simulation.class_change_matrix != 'NA':
4646
self.class_change = self.simulation.class_change_matrix[
47-
id_-1]
47+
id_ - 1]
4848
self.individuals = []
4949
self.id_number = id_
5050
if self.scheduled_servers:
@@ -53,7 +53,7 @@ def __init__(self, id_, simulation):
5353
self.next_event_date = "Inf"
5454
self.blocked_queue = []
5555
if self.c < 'Inf':
56-
self.servers = [Server(self, i+1) for i in xrange(self.c)]
56+
self.servers = [Server(self, i + 1) for i in xrange(self.c)]
5757
if simulation.detecting_deadlock:
5858
self.simulation.digraph.add_nodes_from([str(s)
5959
for s in self.servers])
@@ -110,16 +110,16 @@ def begin_service_if_possible_accept(self,
110110
Begins the service of the next individual, giving
111111
that customer a service time, end date and node.
112112
"""
113-
next_individual.arrival_date = current_time
114-
next_individual.service_time = self.simulation.service_times[
115-
self.id_number][next_individual.customer_class]()
113+
next_individual.arrival_date = self.get_now(current_time)
114+
next_individual.service_time = self.get_service_time(
115+
next_individual.customer_class)
116116
if self.free_server():
117117
if self.c < 'Inf':
118118
self.attach_server(self.find_free_server(),
119119
next_individual)
120-
next_individual.service_start_date = current_time
121-
next_individual.service_end_date = (current_time +
122-
next_individual.service_time)
120+
next_individual.service_start_date = self.get_now(current_time)
121+
next_individual.service_end_date = self.increment_time(
122+
current_time, next_individual.service_time)
123123

124124
def begin_service_if_possible_change_shift(self, current_time):
125125
"""
@@ -131,9 +131,9 @@ def begin_service_if_possible_change_shift(self, current_time):
131131
if len([i for i in self.individuals if not i.server]) > 0:
132132
ind = [i for i in self.individuals if not i.server][0]
133133
self.attach_server(srvr, ind)
134-
ind.service_start_date = current_time
135-
ind.service_end_date = (ind.service_start_date +
136-
ind.service_time)
134+
ind.service_start_date = self.get_now(current_time)
135+
ind.service_end_date = self.increment_time(
136+
ind.service_start_date, ind.service_time)
137137

138138
def begin_service_if_possible_release(self, current_time):
139139
"""
@@ -145,9 +145,9 @@ def begin_service_if_possible_release(self, current_time):
145145
if len([i for i in self.individuals if not i.server]) > 0:
146146
ind = [i for i in self.individuals if not i.server][0]
147147
self.attach_server(srvr, ind)
148-
ind.service_start_date = current_time
149-
ind.service_end_date = (ind.service_start_date +
150-
ind.service_time)
148+
ind.service_start_date = self.get_now(current_time)
149+
ind.service_end_date = self.increment_time(
150+
ind.service_start_date, ind.service_time)
151151

152152
def block_individual(self, individual, next_node):
153153
"""
@@ -170,10 +170,10 @@ def change_customer_class(self,individual):
170170
according to a probability distribution.
171171
"""
172172
if self.simulation.class_change_matrix != 'NA':
173-
individual.previous_class=individual.customer_class
174-
individual.customer_class=nprandom.choice(
173+
individual.previous_class = individual.customer_class
174+
individual.customer_class = nprandom.choice(
175175
xrange(len(self.class_change)),
176-
p=self.class_change[individual.previous_class])
176+
p = self.class_change[individual.previous_class])
177177

178178
def change_shift(self):
179179
"""
@@ -188,7 +188,7 @@ def change_shift(self):
188188
try: inx = self.schedule.index(shift)
189189
except:
190190
tms = [obs[0] for obs in self.schedule]
191-
diffs = [abs(x-shift) for x in tms]
191+
diffs = [abs(x-float(shift)) for x in tms]
192192
indx = diffs.index(min(diffs))
193193

194194
self.take_servers_off_duty()
@@ -261,6 +261,12 @@ def finish_service(self):
261261
else:
262262
self.block_individual(next_individual, next_node)
263263

264+
def get_now(self, current_time):
265+
"""
266+
Gets the current time
267+
"""
268+
return current_time
269+
264270
def have_event(self):
265271
"""
266272
Has an event
@@ -270,6 +276,12 @@ def have_event(self):
270276
else:
271277
self.finish_service()
272278

279+
def increment_time(self, original, increment):
280+
"""
281+
Increments the original time by the increment
282+
"""
283+
return original + increment
284+
273285
def kill_server(self,srvr):
274286
"""
275287
Kills server.
@@ -282,7 +294,7 @@ def next_node(self, customer_class):
282294
Finds the next node according the random distribution.
283295
"""
284296
return nprandom.choice(self.simulation.nodes[1:],
285-
p=self.transition_row[customer_class]+[1.0-sum(
297+
p = self.transition_row[customer_class] + [1.0 - sum(
286298
self.transition_row[customer_class])])
287299

288300
def release(self, next_individual_index, next_node, current_time):
@@ -319,6 +331,12 @@ def release_blocked_individual(self, current_time):
319331
node_to_receive_from.release(individual_to_receive_index,
320332
self, current_time)
321333

334+
def get_service_time(self, cls):
335+
"""
336+
Returns a service time for the given customer class
337+
"""
338+
return self.simulation.service_times[self.id_number][cls]()
339+
322340
def take_servers_off_duty(self):
323341
"""
324342
Gathers servers that should be deleted.
@@ -339,7 +357,7 @@ def update_next_event_date(self, current_time):
339357
next_end_service = min([ind.service_end_date
340358
for ind in self.individuals
341359
if not ind.is_blocked
342-
if ind.service_end_date>=current_time] + ["Inf"])
360+
if ind.service_end_date >= current_time] + ["Inf"])
343361
if self.scheduled_servers:
344362
next_shift_change = self.masterschedule[0]
345363
self.next_event_date = min(

ciw/simulation.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
gammavariate, gauss, lognormvariate, weibullvariate, choice)
55
from csv import writer, reader
66
import copy
7+
from decimal import Decimal, getcontext
78

89
import yaml
910
import networkx as nx
1011
import numpy.random as nprandom
1112

1213
from node import Node
14+
from exactnode import ExactNode, ExactArrivalNode
1315
from arrival_node import ArrivalNode
1416
from exit_node import ExitNode
1517
from server import Server
@@ -31,6 +33,13 @@ def __init__(self, *args, **kwargs):
3133
parameters = kwargs
3234
self.parameters = self.build_parameters(parameters)
3335
self.check_valid_parameters()
36+
if not self.parameters['Exact']:
37+
NodeType = Node
38+
ArrivalNodeType = ArrivalNode
39+
else:
40+
NodeType = ExactNode
41+
ArrivalNodeType = ExactArrivalNode
42+
getcontext().prec = self.parameters['Exact']
3443
self.name = self.parameters['Name']
3544
self.c = self.parameters['Number_of_servers']
3645
self.number_of_nodes = self.parameters['Number_of_nodes']
@@ -59,9 +68,9 @@ def __init__(self, *args, **kwargs):
5968
self.max_simulation_time = self.parameters['Simulation_time']
6069
self.inter_arrival_times = self.find_times_dict(self.lmbda)
6170
self.service_times = self.find_times_dict(self.mu)
62-
self.transitive_nodes = [Node(i + 1, self)
71+
self.transitive_nodes = [NodeType(i + 1, self)
6372
for i in xrange(len(self.c))]
64-
self.nodes = ([ArrivalNode(self)] +
73+
self.nodes = ([ArrivalNodeType(self)] +
6574
self.transitive_nodes +
6675
[ExitNode("Inf")])
6776
self.statetracker = self.choose_tracker()
@@ -98,7 +107,8 @@ def build_parameters(self, params):
98107
'Queue_capacities': ['Inf' for _ in xrange(len(
99108
params['Number_of_servers']))],
100109
'Detect_deadlock': False,
101-
'Simulation_time': None
110+
'Simulation_time': None,
111+
'Exact': False
102112
}
103113

104114
for a in default_dict:

ciw/tests/test_simulation.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import copy
88
from numpy import random as nprandom
9+
from decimal import Decimal
910

1011

1112
def set_seed(x):
@@ -354,6 +355,7 @@ def test_init_method_from_kws(self):
354355
'Number_of_nodes': 4,
355356
'Simulation_time': 2500,
356357
'Detect_deadlock': False,
358+
'Exact': False,
357359
'Name': 'Simulation',
358360
'Number_of_servers': [9, 10, 8, 8],
359361
'Queue_capacities': [20, 'Inf', 30, 'Inf'],
@@ -415,7 +417,8 @@ def test_simple_init_method(self,
415417
'Name': 'Simulation',
416418
'Queue_capacities': ['Inf'],
417419
'Simulation_time': Simulation_time,
418-
'Detect_deadlock': False
420+
'Detect_deadlock': False,
421+
'Exact': False
419422
}
420423
self.assertEqual(Q.parameters, expected_dictionary)
421424
self.assertEqual(Q.lmbda, [[['Exponential', arrival_rate]]])
@@ -455,6 +458,7 @@ def test_build_mmc_parameters(self,
455458
'Queue_capacities': ['Inf'],
456459
'Simulation_time': Simulation_time,
457460
'Detect_deadlock': False,
461+
'Exact': False,
458462
'Name': 'Simulation'
459463
}
460464
self.assertEqual(Q.build_parameters(params), expected_dictionary)
@@ -680,3 +684,46 @@ def test_simultaneous_events_example(self):
680684
self.assertEqual(len(inds), 3)
681685
self.assertTrue(all([x[6]==5.0 for x in recs[1:]]))
682686

687+
def test_exactness(self):
688+
set_seed(777)
689+
Arrival_distributions = [['Exponential', 20]]
690+
Service_distributions = [['Deterministic', 0.01]]
691+
Transition_matrices = [[0.0]]
692+
Simulation_time = 10
693+
Number_of_servers = ['server_schedule']
694+
Cycle_length = 3
695+
server_schedule = [[0.0, 0], [0.5, 1], [0.55, 0]]
696+
Q = ciw.Simulation(Arrival_distributions=Arrival_distributions,
697+
Service_distributions=Service_distributions,
698+
Transition_matrices=Transition_matrices,
699+
Simulation_time=Simulation_time,
700+
Number_of_servers=Number_of_servers,
701+
Cycle_length=Cycle_length,
702+
server_schedule=server_schedule)
703+
Q.simulate_until_max_time()
704+
recs = Q.get_all_records(headers=False)
705+
mod_service_starts = [obs%Cycle_length for obs in [r[5] for r in recs]]
706+
self.assertNotEqual(set(mod_service_starts), set([0.50, 0.51, 0.52, 0.53, 0.54]))
707+
708+
set_seed(777)
709+
Arrival_distributions = [['Exponential', 20]]
710+
Service_distributions = [['Deterministic', 0.01]]
711+
Transition_matrices = [[0.0]]
712+
Simulation_time = 10
713+
Number_of_servers = ['server_schedule']
714+
Cycle_length = 3
715+
server_schedule = [[0.0, 0], [0.5, 1], [0.55, 0]]
716+
Q = ciw.Simulation(Arrival_distributions=Arrival_distributions,
717+
Service_distributions=Service_distributions,
718+
Transition_matrices=Transition_matrices,
719+
Simulation_time=Simulation_time,
720+
Number_of_servers=Number_of_servers,
721+
Cycle_length=Cycle_length,
722+
server_schedule=server_schedule,
723+
Exact=14)
724+
Q.simulate_until_max_time()
725+
recs = Q.get_all_records(headers=False)
726+
mod_service_starts = [obs%Cycle_length for obs in [r[5] for r in recs]]
727+
self.assertEqual(set(mod_service_starts), set([Decimal(k) for k in ['0.50', '0.51', '0.52', '0.53', '0.54']]))
728+
729+

0 commit comments

Comments
 (0)