Skip to content

Commit

Permalink
add flexible routing processor sharing docs
Browse files Browse the repository at this point in the history
  • Loading branch information
geraintpalmer committed May 1, 2024
1 parent 33bee5a commit b0f6fbc
Showing 1 changed file with 107 additions and 6 deletions.
113 changes: 107 additions & 6 deletions docs/Guides/Routing/process_based.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
How to Define Process-Based Routing
===================================

Ciw has the capability to run simulations with process-based routing. This means a customer's entire route is determined at the start and not determined probablistically as they progress through the network.
Ciw has the capability to run simulations with process-based routing. This means a customer's entire route is determined at the start and not determined probabilistically as they progress through the network.
This allows routes to account for an individuals history, for example, repeating nodes a certain number of times.

A customer's entire route is determined at the start, generated from a routing function, that takes in an individual and returns a route, which is a list containing the order of the nodes they are to visit. The function should also take in the simulation itself, allowing time and state-dependent routing. For example::
Expand Down Expand Up @@ -36,7 +36,7 @@ Let's run this and look at the routes of those that have left the system.
>>> Q = ciw.Simulation(N)
>>> Q.simulate_until_max_time(100.0)

>>> inds = Q.nodes[-1].all_individuals # Get's all individuals from exit node
>>> inds = Q.nodes[-1].all_individuals # Gets all individuals from exit node
>>> set([tuple(dr.node for dr in ind.data_records) for ind in inds]) # Get's all unique routes of completed individuals
{(1, 1, 1)}

Expand All @@ -58,7 +58,7 @@ Lets make a network with three nodes with the following routes:
+ have 50% chance of routing to Node 1, and then exit.
+ There are no arrivals at Node 3.

For this we will require two routing functions: :code:`routing_function_Node_1`, :code:`routing_function_Node_2`::
For this we will require a routing function that returns different things depending on the individual's starting node::

>>> import random
>>> def routing_function(ind, simulation):
Expand All @@ -71,15 +71,116 @@ For this we will require two routing functions: :code:`routing_function_Node_1`,
... return [2, 3]
... return [2, 1]

As there are no arrivals at Node 3, no customer will need routing assigned here. However, we need to use the placeholder function :code:`ciw.no_routing` to account for this::

>>> N = ciw.create_network(
... arrival_distributions=[ciw.dists.Exponential(rate=1),
... ciw.dists.Deterministic(value=1),
... None],
... service_distributions=[ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2)],
... number_of_servers=[1,1,1],
... number_of_servers=[1, 1, 1],
... routing=ciw.routing.ProcessBased(routing_function)
... )



Flexible Process Based Routing
------------------------------

In the examples above, once a route was sampled, the customer's entire journey was set out before them. However, with a :code:`FlexibleProcessBased` object, we can define sequences of sets of nodes that must be visited in order. Within a set of nodes, either the individual must visit at least one node here, or must visit all nodes here, but the order is irrelevant. This is defined with the :code:`rule` keyword.

Consider for example the following sequence of sets of destinations::

[[1, 2, 3], [4], [5, 6]]

There are three sets of nodes in the sequence, the set :code:`[1, 2, 3]`, followed by the set :code:`[4]`, followed by the set :code:`[5, 6]`. Routes are then determined by the :code:`rule` keyword:

+ :code:`rule='any'`: this means that at just one node from each set should be visited, in the order of the sets. The choice of which node is chosen from each set is set with the :code:`choice` keyword. Valid routes include (1, 4, 5), (2, 4, 5), and (3, 4, 6), amongst others.
+ :code:`rule='all'`: this means that every node in a set must be visited before moving on to the next set. The order at which a node is visited in a set is set with the :code:`choice` keyword. Valid routes include (1, 2, 3, 4, 5, 6), (3, 2, 1, 4, 6, 5), and (3, 1, 2, 4, 5, 6), amongst others.

The current options for choices are:
- :code:`'random'`: randomly chooses a node from the set.
- :code:`'jsq'`: chooses the node with the smallest queue from the set (like the :ref:`join-shortest-queue<jsq>` router).
- :code:`'lb'`: chooses the node with the least number of customers present from the set (like the :ref:`load-balancing<load_balancing>` router).

When all nodes in a set must be visited, these rules apply to choosing the next node from the set minus the nodes already visited, applied at the current time when the choice is made.

Example::

>>> def routing_function(ind, simulation):
... return [[1, 2], [3], [1, 2]]

A route where the first and third sets include nodes 1 and 2, and the second set only includes node 3. All customers arrive to node 4. Let's compare the :code:`'any'` and :code:`'all'` rules. First with :code:`'any'`::

>>> N = ciw.create_network(
... arrival_distributions=[
... None,
... None,
... None,
... ciw.dists.Exponential(rate=1)
... ],
... service_distributions=[
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ],
... number_of_servers=[1, 1, 1, 1],
... routing=ciw.routing.FlexibleProcessBased(
... route_function=routing_function,
... rule='any',
... choice='random'
... )
... )
>>> ciw.seed(0)
>>> Q = ciw.Simulation(N)
>>> Q.simulate_until_max_customers(6)
>>> routes = [[dr.node for dr in ind.data_records] for ind in Q.nodes[-1].all_individuals]
>>> for route in routes:
... print(route)
[4, 2, 3, 2]
[4, 1, 3, 1]
[4, 1, 3, 1]
[4, 1, 3, 1]
[4, 2, 3, 1]
[4, 2, 3, 1]

We see that all customers that completed their journey arrived at node 4, took either node 1 or 2 first, then node 3, then either node 1 or 2.

Now compare with :code:`'all'`::

>>> N = ciw.create_network(
... arrival_distributions=[
... None,
... None,
... None,
... ciw.dists.Exponential(rate=1)
... ],
... service_distributions=[
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ciw.dists.Exponential(rate=2),
... ],
... number_of_servers=[1, 1, 1, 1],
... routing=ciw.routing.FlexibleProcessBased(
... route_function=routing_function,
... rule='all',
... choice='random'
... )
... )
>>> ciw.seed(0)
>>> Q = ciw.Simulation(N)
>>> Q.simulate_until_max_customers(6)
>>> routes = [[dr.node for dr in ind.data_records] for ind in Q.nodes[-1].all_individuals]
>>> for route in routes:
... print(route)
[4, 2, 1, 3, 2, 1]
[4, 1, 2, 3, 2, 1]
[4, 2, 1, 3, 2, 1]
[4, 1, 2, 3, 2, 1]
[4, 1, 2, 3, 1, 2]
[4, 1, 2, 3, 2, 1]

We see that all customers that completed their journey arrived at node 4, took both node 1 or 2 in either order, then node 3, then both node 1 or 2 in either order.

0 comments on commit b0f6fbc

Please sign in to comment.