How to Simulate Reneging Customers¶
Ciw allows customers to renege, that is leave a queue after a certain amount of time waiting for service.
In Ciw, this works by sampling from a reneging_time_distribution
, giving the date that that customer will renege if their service has not started by that time.
For example, let’s say we have an M/M/1, \(\lambda = 5\) and \(\mu = 2\), queue where customers renege if they have spend more than 6 time units in the queue:
>>> import ciw
>>> N = ciw.create_network(
... arrival_distributions=[ciw.dists.Exponential(5)],
... service_distributions=[ciw.dists.Exponential(2)],
... number_of_servers=[1],
... reneging_time_distributions=[ciw.dists.Deterministic(6)]
... )
>>> ciw.seed(0)
>>> Q = ciw.Simulation(N, exact=5)
>>> Q.simulate_until_max_time(10)
Reneging events are recorded as DataRecords
and so are collected along with service records with the get_all_records
method. They are distinguished by the record_type
field (services have service
record type, while reneging events have renege
record types).
For the above example, we see that no customer receiving service waited longer than 6 time units, as expected. We also see that all four reneging events happened after the customer waited 6 time units:
>>> recs = Q.get_all_records()
>>> max([r.waiting_time for r in recs if r.record_type == 'service'])
Decimal('5.8880')
>>> [r.waiting_time for r in recs if r.record_type == 'renege']
[Decimal('6.0000'), Decimal('6.0000'), Decimal('6.0000'), Decimal('6.0000')]
By default, when a customer reneges, they leave the network, that is they are sent to the exit node.
Note: Similarly to all other customer-level keyword arguments, reneging_time_distributions
can take either a list of distributions indicating which reneging distribution to use at each node in the network; or dictionary mapping customer classes to these lists, allowing different reneging time distributions for each customer class.
Reneging Locations¶
After a customer reneges, by default they are sent to the exit node. However, it is also possible to send customers to any other node in the network, using the reneging_destinations
keyword argument.
Consider a two node network, the first node is an M/M/1, \(\lambda = 5\) and \(\mu = 2\), queue where customers renege if they have spend more than 6 time units in the queue. Upon reneging they are sent to the second node. The second node has no arrivals, one server with exponential services \(\mu = 4\), and no reneging:
>>> N = ciw.create_network(
... arrival_distributions=[ciw.dists.Exponential(5),
... None],
... service_distributions=[ciw.dists.Exponential(2),
... ciw.dists.Exponential(4)],
... number_of_servers=[1, 1],
... routing=[[0, 0],
... [0, 0]],
... reneging_time_distributions=[ciw.dists.Deterministic(6),
... None],
... reneging_destinations=[2, -1]
... )
>>> ciw.seed(0)
>>> Q = ciw.Simulation(N, exact=5)
>>> Q.simulate_until_max_time(11)
Now we will see that the id number and arrival dates of the customers served at Node 2 are identical to the reneging times of the reneging customers, as the only way to get a service at Node 2 is to renege there:
>>> recs = Q.get_all_records()
>>> [(r.id_number, r.exit_date) for r in recs if r.record_type == 'renege']
[(12, Decimal('8.0805')), (13, Decimal('8.1382')), (20, Decimal('10.441')), (21, Decimal('10.569')), (22, Decimal('10.758'))]
>>> [(r.id_number, r.arrival_date) for r in recs if r.node == 2]
[(12, Decimal('8.0805')), (13, Decimal('8.1382')), (20, Decimal('10.441')), (21, Decimal('10.569')), (22, Decimal('10.758'))]
Note: Similarly reneging_destinations
can take either a list of destination indicating which node to send reneging customer from node in the network; or dictionary mapping customer classes to these lists, allowing different reneging destinations for each customer class.