Skip to content

Commit

Permalink
implement rerouting as a preemptive interruption option
Browse files Browse the repository at this point in the history
  • Loading branch information
geraintpalmer committed Apr 29, 2024
1 parent 9ecf2e0 commit 126a143
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 36 deletions.
72 changes: 46 additions & 26 deletions ciw/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,33 +529,39 @@ def kill_server(self, srvr):
def next_node(self, ind):
"""
Finds the next node according the routing method:
- if not process-based then sample from transition matrix
- if process-based then take the next value from the predefined route,
removing the current node from the route
"""
return self.simulation.routers[ind.customer_class].next_node(ind, self.id_number)

def next_node_for_rerouting(self, ind):
"""
Finds the next node (for rerouting) according the routing method:
"""
return self.simulation.routers[ind.customer_class].next_node_for_rerouting(ind, self.id_number)

def preempt(self, individual_to_preempt, next_individual):
"""
Removes individual_to_preempt from service and replaces them with next_individual
"""
server = individual_to_preempt.server
individual_to_preempt.original_service_time = individual_to_preempt.service_time
self.write_interruption_record(individual_to_preempt)
individual_to_preempt.service_start_date = False
individual_to_preempt.time_left = individual_to_preempt.service_end_date - self.now
individual_to_preempt.service_time = self.priority_preempt
individual_to_preempt.service_end_date = False
self.detatch_server(server, individual_to_preempt)
self.decide_class_change(individual_to_preempt)
if self.priority_preempt == 'reroute':
self.reroute(individual_to_preempt)
else:
self.write_interruption_record(individual_to_preempt)
individual_to_preempt.service_start_date = False
individual_to_preempt.time_left = individual_to_preempt.service_end_date - self.now
individual_to_preempt.service_time = self.priority_preempt
individual_to_preempt.service_end_date = False
self.detatch_server(server, individual_to_preempt)
self.decide_class_change(individual_to_preempt)
self.attach_server(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.reset_class_change(next_individual)
server.next_end_service_date = next_individual.service_end_date

def release(self, next_individual, next_node):
def release(self, next_individual, next_node, reroute=False):
"""
Update node when an individual is released:
- find the individual to release
Expand All @@ -573,7 +579,8 @@ def release(self, next_individual, next_node):
self.number_in_service -= 1
next_individual.queue_size_at_departure = self.number_of_individuals
next_individual.exit_date = self.now
self.write_individual_record(next_individual)
if not reroute:
self.write_individual_record(next_individual)
newly_free_server = None
if not isinf(self.c) and not self.slotted:
newly_free_server = next_individual.server
Expand All @@ -584,9 +591,11 @@ def release(self, next_individual, next_node):
self.simulation.statetracker.change_state_release(
self, next_node, next_individual, next_individual.is_blocked
)
self.begin_service_if_possible_release(next_individual, newly_free_server)
if not reroute:
self.begin_service_if_possible_release(next_individual, newly_free_server)
next_node.accept(next_individual)
self.release_blocked_individual()
if not reroute:
self.release_blocked_individual()

def release_blocked_individual(self):
"""
Expand Down Expand Up @@ -687,24 +696,35 @@ def interrupt_service(self, individual):
Interrupts the service of an individual and places them in an
interrupted queue, and writes an interruption record for them.
"""
self.interrupted_individuals.append(individual)
individual.interrupted = True
self.number_interrupted_individuals += 1
individual.original_service_time = individual.service_time
self.write_interruption_record(individual)
individual.original_service_start_date = individual.service_start_date
individual.service_start_date = False
individual.time_left = individual.service_end_date - self.now
individual.service_time = self.schedule.preemption
individual.service_end_date = False
self.number_in_service -= 1
if self.schedule.preemption == 'reroute':
self.reroute(individual)
else:
self.interrupted_individuals.append(individual)
individual.interrupted = True
self.number_interrupted_individuals += 1
self.write_interruption_record(individual)
individual.original_service_start_date = individual.service_start_date
individual.service_start_date = False
individual.time_left = individual.service_end_date - self.now
individual.service_time = self.schedule.preemption
individual.service_end_date = False
self.number_in_service -= 1

def sort_interrupted_individuals(self):
"""
Sorts the list of interrupted individuals by priority class and arrival date.
"""
self.interrupted_individuals.sort(key=lambda x: (x.priority_class, x.arrival_date))

def reroute(self, individual):
"""
Rerouts a preempted individual
"""
next_node = self.next_node_for_rerouting(individual)
self.write_interruption_record(individual, destination=next_node.id_number)
self.release(individual, next_node, reroute=True)

def update_next_end_service_without_server(self):
"""
Updates the next end of a slotted service in the `possible_next_events` dictionary.
Expand Down Expand Up @@ -848,7 +868,7 @@ def write_individual_record(self, individual):
)
individual.data_records.append(record)

def write_interruption_record(self, individual):
def write_interruption_record(self, individual, destination=nan):
"""
Write a data record for an individual when being interrupted.
"""
Expand All @@ -869,7 +889,7 @@ def write_interruption_record(self, individual):
service_end_date=nan,
time_blocked=nan,
exit_date=self.now,
destination=nan,
destination=destination,
queue_size_at_arrival=individual.queue_size_at_arrival,
queue_size_at_departure=individual.queue_size_at_departure,
server_id=server_id,
Expand Down
18 changes: 18 additions & 0 deletions ciw/routing/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def next_node(self, ind, node_id):
"""
return self.routers[node_id - 1].next_node(ind)

def next_node_for_rerouting(self, ind, node_id):
"""
Chooses the next node when rerouting preempted customer.
"""
return self.routers[node_id - 1].next_node_for_rerouting(ind)


class TransitionMatrix(NetworkRouting):
"""
Expand Down Expand Up @@ -88,6 +94,12 @@ def next_node(self, ind, node_id):
node_index = ind.route.pop(0)
return self.simulation.nodes[node_index]

def next_node_for_rerouting(self, ind, node_id):
"""
Chooses the next node when rerouting preempted customer.
"""
return self.next_node(ind, node_id)


class NodeRouting:
"""
Expand All @@ -104,6 +116,12 @@ def initialise(self, simulation, node):
def error_check_at_initialise(self):
pass

def next_node_for_rerouting(self, ind):
"""
By default, the next node for rerouting uses the same method as next_node.
"""
return self.next_node(ind)


class Probabilistic(NodeRouting):
"""
Expand Down
4 changes: 2 additions & 2 deletions ciw/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def __init__(self, numbers_of_servers: List[int], shift_end_dates: List[float],
'resample', or False.
Default is False.
"""
if preemption not in [False, 'resume', 'restart', 'resample']:
raise ValueError("Pre-emption options should be either 'resume', 'restart', 'resample', or False.")
if preemption not in [False, 'resume', 'restart', 'resample', 'reroute']:
raise ValueError("Pre-emption options should be either 'resume', 'restart', 'resample', 'reroute', or False.")
if not isinstance(offset, float):
raise ValueError("Offset should be a positive float.")
if offset < 0.0:
Expand Down

0 comments on commit 126a143

Please sign in to comment.