Skip to content

Commit

Permalink
Merge branch 'implement-service-discipline-when-customer-arrives'
Browse files Browse the repository at this point in the history
  • Loading branch information
geraintpalmer committed Apr 4, 2024
2 parents 3695875 + db6e7f6 commit 22536cc
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 21 deletions.
9 changes: 6 additions & 3 deletions ciw/disciplines.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,47 @@
from ciw.auxiliary import random_choice


def FIFO(individuals: List[Individual]) -> Individual:
def FIFO(individuals: List[Individual], t: float) -> Individual:
"""
FIFO: First In, First Out (FIFO)
Returns the individual at the head of the queue.
Parameters:
- individuals (List[Individual]): List of individuals in the queue.
- t (float): The current simulation time
Returns:
- Individual: The individual at the head of the queue.
"""
return individuals[0]


def SIRO(individuals: List[Individual]) -> Individual:
def SIRO(individuals: List[Individual], t: float) -> Individual:
"""
SIRO: Service In Random Order (SIRO)
Returns a random individual from the queue.
Parameters:
- individuals (List[Individual]): List of individuals in the queue.
- t (float): The current simulation time
Returns:
- Individual: A randomly selected individual from the queue.
"""
return random_choice(individuals)


def LIFO(individuals: List[Individual]) -> Individual:
def LIFO(individuals: List[Individual], t: float) -> Individual:
"""
LIFO: Last In, First Out (LIFO)
Returns the individual who joined the queue most recently.
Parameters:
- individuals (List[Individual]): List of individuals in the queue.
- t (float): The current simulation time
Returns:
- Individual: The individual who joined the queue most recently.
Expand Down
34 changes: 20 additions & 14 deletions ciw/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,25 @@ def begin_service_if_possible_accept(self, next_individual):
next_individual.reneging_date = self.get_reneging_date(next_individual)
self.decide_class_change(next_individual)

free_server = self.find_free_server(next_individual)
if free_server is None and isinf(self.c) is False and self.c > 0:
self.decide_preempt(next_individual)
if free_server is not None or isinf(self.c):
if isinf(self.c) is False:
self.attach_server(free_server, next_individual)
next_individual.service_start_date = self.now
next_individual.service_time = self.get_service_time(next_individual)
next_individual.service_end_date = self.now + next_individual.service_time
self.number_in_service += 1
self.reset_class_change(next_individual)
if not isinf(self.c):
free_server.next_end_service_date = next_individual.service_end_date
if isinf(self.c):
ind = next_individual
else:
ind = self.choose_next_customer()

if ind is not None:
free_server = self.find_free_server(ind)
if free_server is None and isinf(self.c) is False and self.c > 0:
self.decide_preempt(ind)
if free_server is not None or isinf(self.c):
if isinf(self.c) is False:
self.attach_server(free_server, ind)
ind.service_start_date = self.now
ind.service_time = self.get_service_time(ind)
ind.service_end_date = self.now + ind.service_time
self.number_in_service += 1
self.reset_class_change(ind)
if not isinf(self.c):
free_server.next_end_service_date = ind.service_end_date

def begin_interrupted_individuals_service(self, srvr):
"""
Expand Down Expand Up @@ -344,7 +350,7 @@ def choose_next_customer(self):
for priority_individuals in self.individuals:
waiting_individuals = [ind for ind in priority_individuals if not ind.server]
if len(waiting_individuals) > 0:
return self.service_discipline(waiting_individuals)
return self.service_discipline(waiting_individuals, self.now)

def create_starting_servers(self):
"""
Expand Down
1 change: 1 addition & 0 deletions ciw/tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ def test_begin_service_if_possible_accept_method(self):
self.assertEqual(ind.service_start_date, False)
self.assertEqual(ind.service_end_date, False)
Q.current_time = 300
Q.transitive_nodes[0].individuals[0].append(ind)
Q.transitive_nodes[0].begin_service_if_possible_accept(ind)
self.assertEqual(ind.arrival_date, 300)
self.assertEqual(round(ind.service_time, 5), 0.03382)
Expand Down
64 changes: 62 additions & 2 deletions ciw/tests/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1172,8 +1172,8 @@ def test_service_in_random_order(self):
Q = ciw.Simulation(N)
Q.simulate_until_max_time(14)
recs = sorted(Q.get_all_records(), key=lambda dr: dr.service_start_date)
self.assertEqual([r.id_number for r in recs], [1, 2, 5, 4, 8])
self.assertEqual([r.arrival_date for r in recs], [1.0, 2.0, 5.0, 4.0, 8.0])
self.assertEqual([r.id_number for r in recs], [1, 2, 5, 6, 4])
self.assertEqual([r.arrival_date for r in recs], [1.0, 2.0, 5.0, 6.0, 4.0])
self.assertEqual([r.service_time for r in recs], [2.5, 2.5, 2.5, 2.5, 2.5])
self.assertEqual(
[r.service_end_date for r in recs],
Expand Down Expand Up @@ -1240,6 +1240,66 @@ def test_mixed_service_disciplines(self):
)


def test_custom_service_discipline(self):
"""
First test for a given linger time, no person waited less than the linger time.
"""
def linger(individuals, t):
individuals_who_lingered = [ind for ind in individuals if (t - ind.arrival_date) >= 3]
if len(individuals_who_lingered) == 0:
return None
return individuals_who_lingered[0]

N = ciw.create_network(
arrival_distributions=[ciw.dists.Exponential(3)],
service_distributions=[ciw.dists.Exponential(5)],
number_of_servers=[1],
service_disciplines=[linger]
)
Q = ciw.Simulation(N)
Q.simulate_until_max_time(100)
recs = Q.get_all_records()
waits = [r.waiting_time for r in recs]
self.assertTrue(min(waits) >= 3)

"""
Now test a specific example:
Customers arrive every 1.31
Services last 1.9 and 0.2 alternatively
Customers must linger for at least 3
ID Arrive Linger Start Service End
-----------------------------------------
1 01.31 04.31 05.24 1.9 07.14
2 02.62 05.62 07.14 0.2 07.34
3 03.93 06.93 07.34 1.9 09.24
4 05.24 08.24 09.24 0.2 09.44
5 06.55 09.55 10.48 1.9 12.38
6 07.86 10.86 12.38 0.2 12.58
7 09.17 12.17 12.58 1.9 14.48
8 10.48 13.48 14.48 0.2 14.68
-----------------------------------------
"""
N = ciw.create_network(
arrival_distributions=[ciw.dists.Deterministic(value=1.31)],
service_distributions=[ciw.dists.Sequential([1.9, 0.2])],
number_of_servers=[1],
service_disciplines=[linger]
)
Q = ciw.Simulation(N)
Q.simulate_until_max_customers(8, method='Finish')
recs = Q.get_all_records()
self.assertEqual(len(recs), 8)

arrivals = [round(r.arrival_date, 2) for r in recs]
start_dates = [round(r.service_start_date, 2) for r in recs]
end_dates = [round(r.service_end_date, 2) for r in recs]

self.assertEqual(arrivals, [1.31, 2.62, 3.93, 5.24, 6.55, 7.86, 9.17, 10.48])
self.assertEqual(start_dates, [5.24, 7.14, 7.34, 9.24, 10.48, 12.38, 12.58, 14.48])
self.assertEqual(end_dates, [7.14, 7.34, 9.24, 9.44, 12.38, 12.58, 14.48, 14.68])


def test_names_for_customer_classes(self):
N = ciw.create_network(
arrival_distributions={
Expand Down
4 changes: 2 additions & 2 deletions docs/Guides/service_disciplines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ As an example, say we have a three node network, and we want to use FIFO discipl
Custom Disciplines
------------------

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.
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.

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:

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

0 comments on commit 22536cc

Please sign in to comment.