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.