Skip to content

Commit 22536cc

Browse files
committed
Merge branch 'implement-service-discipline-when-customer-arrives'
2 parents 3695875 + db6e7f6 commit 22536cc

5 files changed

Lines changed: 91 additions & 21 deletions

File tree

ciw/disciplines.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,47 @@
44
from ciw.auxiliary import random_choice
55

66

7-
def FIFO(individuals: List[Individual]) -> Individual:
7+
def FIFO(individuals: List[Individual], t: float) -> Individual:
88
"""
99
FIFO: First In, First Out (FIFO)
1010
1111
Returns the individual at the head of the queue.
1212
1313
Parameters:
1414
- individuals (List[Individual]): List of individuals in the queue.
15+
- t (float): The current simulation time
1516
1617
Returns:
1718
- Individual: The individual at the head of the queue.
1819
"""
1920
return individuals[0]
2021

2122

22-
def SIRO(individuals: List[Individual]) -> Individual:
23+
def SIRO(individuals: List[Individual], t: float) -> Individual:
2324
"""
2425
SIRO: Service In Random Order (SIRO)
2526
2627
Returns a random individual from the queue.
2728
2829
Parameters:
2930
- individuals (List[Individual]): List of individuals in the queue.
31+
- t (float): The current simulation time
3032
3133
Returns:
3234
- Individual: A randomly selected individual from the queue.
3335
"""
3436
return random_choice(individuals)
3537

3638

37-
def LIFO(individuals: List[Individual]) -> Individual:
39+
def LIFO(individuals: List[Individual], t: float) -> Individual:
3840
"""
3941
LIFO: Last In, First Out (LIFO)
4042
4143
Returns the individual who joined the queue most recently.
4244
4345
Parameters:
4446
- individuals (List[Individual]): List of individuals in the queue.
47+
- t (float): The current simulation time
4548
4649
Returns:
4750
- Individual: The individual who joined the queue most recently.

ciw/node.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,25 @@ def begin_service_if_possible_accept(self, next_individual):
140140
next_individual.reneging_date = self.get_reneging_date(next_individual)
141141
self.decide_class_change(next_individual)
142142

143-
free_server = self.find_free_server(next_individual)
144-
if free_server is None and isinf(self.c) is False and self.c > 0:
145-
self.decide_preempt(next_individual)
146-
if free_server is not None or isinf(self.c):
147-
if isinf(self.c) is False:
148-
self.attach_server(free_server, next_individual)
149-
next_individual.service_start_date = self.now
150-
next_individual.service_time = self.get_service_time(next_individual)
151-
next_individual.service_end_date = self.now + next_individual.service_time
152-
self.number_in_service += 1
153-
self.reset_class_change(next_individual)
154-
if not isinf(self.c):
155-
free_server.next_end_service_date = next_individual.service_end_date
143+
if isinf(self.c):
144+
ind = next_individual
145+
else:
146+
ind = self.choose_next_customer()
147+
148+
if ind is not None:
149+
free_server = self.find_free_server(ind)
150+
if free_server is None and isinf(self.c) is False and self.c > 0:
151+
self.decide_preempt(ind)
152+
if free_server is not None or isinf(self.c):
153+
if isinf(self.c) is False:
154+
self.attach_server(free_server, ind)
155+
ind.service_start_date = self.now
156+
ind.service_time = self.get_service_time(ind)
157+
ind.service_end_date = self.now + ind.service_time
158+
self.number_in_service += 1
159+
self.reset_class_change(ind)
160+
if not isinf(self.c):
161+
free_server.next_end_service_date = ind.service_end_date
156162

157163
def begin_interrupted_individuals_service(self, srvr):
158164
"""
@@ -344,7 +350,7 @@ def choose_next_customer(self):
344350
for priority_individuals in self.individuals:
345351
waiting_individuals = [ind for ind in priority_individuals if not ind.server]
346352
if len(waiting_individuals) > 0:
347-
return self.service_discipline(waiting_individuals)
353+
return self.service_discipline(waiting_individuals, self.now)
348354

349355
def create_starting_servers(self):
350356
"""

ciw/tests/test_node.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ def test_begin_service_if_possible_accept_method(self):
628628
self.assertEqual(ind.service_start_date, False)
629629
self.assertEqual(ind.service_end_date, False)
630630
Q.current_time = 300
631+
Q.transitive_nodes[0].individuals[0].append(ind)
631632
Q.transitive_nodes[0].begin_service_if_possible_accept(ind)
632633
self.assertEqual(ind.arrival_date, 300)
633634
self.assertEqual(round(ind.service_time, 5), 0.03382)

ciw/tests/test_simulation.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,8 +1172,8 @@ def test_service_in_random_order(self):
11721172
Q = ciw.Simulation(N)
11731173
Q.simulate_until_max_time(14)
11741174
recs = sorted(Q.get_all_records(), key=lambda dr: dr.service_start_date)
1175-
self.assertEqual([r.id_number for r in recs], [1, 2, 5, 4, 8])
1176-
self.assertEqual([r.arrival_date for r in recs], [1.0, 2.0, 5.0, 4.0, 8.0])
1175+
self.assertEqual([r.id_number for r in recs], [1, 2, 5, 6, 4])
1176+
self.assertEqual([r.arrival_date for r in recs], [1.0, 2.0, 5.0, 6.0, 4.0])
11771177
self.assertEqual([r.service_time for r in recs], [2.5, 2.5, 2.5, 2.5, 2.5])
11781178
self.assertEqual(
11791179
[r.service_end_date for r in recs],
@@ -1240,6 +1240,66 @@ def test_mixed_service_disciplines(self):
12401240
)
12411241

12421242

1243+
def test_custom_service_discipline(self):
1244+
"""
1245+
First test for a given linger time, no person waited less than the linger time.
1246+
"""
1247+
def linger(individuals, t):
1248+
individuals_who_lingered = [ind for ind in individuals if (t - ind.arrival_date) >= 3]
1249+
if len(individuals_who_lingered) == 0:
1250+
return None
1251+
return individuals_who_lingered[0]
1252+
1253+
N = ciw.create_network(
1254+
arrival_distributions=[ciw.dists.Exponential(3)],
1255+
service_distributions=[ciw.dists.Exponential(5)],
1256+
number_of_servers=[1],
1257+
service_disciplines=[linger]
1258+
)
1259+
Q = ciw.Simulation(N)
1260+
Q.simulate_until_max_time(100)
1261+
recs = Q.get_all_records()
1262+
waits = [r.waiting_time for r in recs]
1263+
self.assertTrue(min(waits) >= 3)
1264+
1265+
"""
1266+
Now test a specific example:
1267+
Customers arrive every 1.31
1268+
Services last 1.9 and 0.2 alternatively
1269+
Customers must linger for at least 3
1270+
1271+
ID Arrive Linger Start Service End
1272+
-----------------------------------------
1273+
1 01.31 04.31 05.24 1.9 07.14
1274+
2 02.62 05.62 07.14 0.2 07.34
1275+
3 03.93 06.93 07.34 1.9 09.24
1276+
4 05.24 08.24 09.24 0.2 09.44
1277+
5 06.55 09.55 10.48 1.9 12.38
1278+
6 07.86 10.86 12.38 0.2 12.58
1279+
7 09.17 12.17 12.58 1.9 14.48
1280+
8 10.48 13.48 14.48 0.2 14.68
1281+
-----------------------------------------
1282+
"""
1283+
N = ciw.create_network(
1284+
arrival_distributions=[ciw.dists.Deterministic(value=1.31)],
1285+
service_distributions=[ciw.dists.Sequential([1.9, 0.2])],
1286+
number_of_servers=[1],
1287+
service_disciplines=[linger]
1288+
)
1289+
Q = ciw.Simulation(N)
1290+
Q.simulate_until_max_customers(8, method='Finish')
1291+
recs = Q.get_all_records()
1292+
self.assertEqual(len(recs), 8)
1293+
1294+
arrivals = [round(r.arrival_date, 2) for r in recs]
1295+
start_dates = [round(r.service_start_date, 2) for r in recs]
1296+
end_dates = [round(r.service_end_date, 2) for r in recs]
1297+
1298+
self.assertEqual(arrivals, [1.31, 2.62, 3.93, 5.24, 6.55, 7.86, 9.17, 10.48])
1299+
self.assertEqual(start_dates, [5.24, 7.14, 7.34, 9.24, 10.48, 12.38, 12.58, 14.48])
1300+
self.assertEqual(end_dates, [7.14, 7.34, 9.24, 9.44, 12.38, 12.58, 14.48, 14.68])
1301+
1302+
12431303
def test_names_for_customer_classes(self):
12441304
N = ciw.create_network(
12451305
arrival_distributions={

docs/Guides/service_disciplines.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ As an example, say we have a three node network, and we want to use FIFO discipl
3838
Custom Disciplines
3939
------------------
4040

41-
Other service disciplines can also be implemented by writing a custom service discipline function. These functions take in a list of individuals and returns an individual from that list that represents the next individual to be served. As this is a list of individuals, we can access the individuals' attributes when making the service discipline decision.
41+
Other service disciplines can also be implemented by writing a custom service discipline function. These functions take in a list of individuals, and the current time, and returns an individual from that list that represents the next individual to be served. As this is a list of individuals, we can access the individuals' attributes when making the service discipline decision.
4242

4343
For example, say we wish to implement a service discipline that chooses the customers randomly, but with probability proportional to their arrival order, we could write:
4444

45-
>>> def SIRO_proportional(individuals):
45+
>>> def SIRO_proportional(individuals, t):
4646
... n = len(inds)
4747
... denominator = (n * (n + 1)) / 2
4848
... probs = [(n - i) / denominator for i in range(n)]

0 commit comments

Comments
 (0)