Skip to content

Commit b0f6fbc

Browse files
committed
add flexible routing processor sharing docs
1 parent 33bee5a commit b0f6fbc

1 file changed

Lines changed: 107 additions & 6 deletions

File tree

docs/Guides/Routing/process_based.rst

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
How to Define Process-Based Routing
55
===================================
66

7-
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.
7+
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.
88
This allows routes to account for an individuals history, for example, repeating nodes a certain number of times.
99

1010
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::
@@ -36,7 +36,7 @@ Let's run this and look at the routes of those that have left the system.
3636
>>> Q = ciw.Simulation(N)
3737
>>> Q.simulate_until_max_time(100.0)
3838

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

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

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

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

74-
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::
75-
7674
>>> N = ciw.create_network(
7775
... arrival_distributions=[ciw.dists.Exponential(rate=1),
7876
... ciw.dists.Deterministic(value=1),
7977
... None],
8078
... service_distributions=[ciw.dists.Exponential(rate=2),
8179
... ciw.dists.Exponential(rate=2),
8280
... ciw.dists.Exponential(rate=2)],
83-
... number_of_servers=[1,1,1],
81+
... number_of_servers=[1, 1, 1],
8482
... routing=ciw.routing.ProcessBased(routing_function)
8583
... )
84+
85+
86+
87+
Flexible Process Based Routing
88+
------------------------------
89+
90+
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.
91+
92+
Consider for example the following sequence of sets of destinations::
93+
94+
[[1, 2, 3], [4], [5, 6]]
95+
96+
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:
97+
98+
+ :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.
99+
+ :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.
100+
101+
The current options for choices are:
102+
- :code:`'random'`: randomly chooses a node from the set.
103+
- :code:`'jsq'`: chooses the node with the smallest queue from the set (like the :ref:`join-shortest-queue<jsq>` router).
104+
- :code:`'lb'`: chooses the node with the least number of customers present from the set (like the :ref:`load-balancing<load_balancing>` router).
105+
106+
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.
107+
108+
Example::
109+
110+
>>> def routing_function(ind, simulation):
111+
... return [[1, 2], [3], [1, 2]]
112+
113+
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'`::
114+
115+
>>> N = ciw.create_network(
116+
... arrival_distributions=[
117+
... None,
118+
... None,
119+
... None,
120+
... ciw.dists.Exponential(rate=1)
121+
... ],
122+
... service_distributions=[
123+
... ciw.dists.Exponential(rate=2),
124+
... ciw.dists.Exponential(rate=2),
125+
... ciw.dists.Exponential(rate=2),
126+
... ciw.dists.Exponential(rate=2),
127+
... ],
128+
... number_of_servers=[1, 1, 1, 1],
129+
... routing=ciw.routing.FlexibleProcessBased(
130+
... route_function=routing_function,
131+
... rule='any',
132+
... choice='random'
133+
... )
134+
... )
135+
>>> ciw.seed(0)
136+
>>> Q = ciw.Simulation(N)
137+
>>> Q.simulate_until_max_customers(6)
138+
>>> routes = [[dr.node for dr in ind.data_records] for ind in Q.nodes[-1].all_individuals]
139+
>>> for route in routes:
140+
... print(route)
141+
[4, 2, 3, 2]
142+
[4, 1, 3, 1]
143+
[4, 1, 3, 1]
144+
[4, 1, 3, 1]
145+
[4, 2, 3, 1]
146+
[4, 2, 3, 1]
147+
148+
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.
149+
150+
Now compare with :code:`'all'`::
151+
152+
>>> N = ciw.create_network(
153+
... arrival_distributions=[
154+
... None,
155+
... None,
156+
... None,
157+
... ciw.dists.Exponential(rate=1)
158+
... ],
159+
... service_distributions=[
160+
... ciw.dists.Exponential(rate=2),
161+
... ciw.dists.Exponential(rate=2),
162+
... ciw.dists.Exponential(rate=2),
163+
... ciw.dists.Exponential(rate=2),
164+
... ],
165+
... number_of_servers=[1, 1, 1, 1],
166+
... routing=ciw.routing.FlexibleProcessBased(
167+
... route_function=routing_function,
168+
... rule='all',
169+
... choice='random'
170+
... )
171+
... )
172+
>>> ciw.seed(0)
173+
>>> Q = ciw.Simulation(N)
174+
>>> Q.simulate_until_max_customers(6)
175+
>>> routes = [[dr.node for dr in ind.data_records] for ind in Q.nodes[-1].all_individuals]
176+
>>> for route in routes:
177+
... print(route)
178+
[4, 2, 1, 3, 2, 1]
179+
[4, 1, 2, 3, 2, 1]
180+
[4, 2, 1, 3, 2, 1]
181+
[4, 1, 2, 3, 2, 1]
182+
[4, 1, 2, 3, 1, 2]
183+
[4, 1, 2, 3, 2, 1]
184+
185+
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.
186+

0 commit comments

Comments
 (0)