Pipeline

Pipelines allow to arbitrarly interconnect generators and resources by chaining them. Messages belonging to a pipeline automatically flow from a resource to another without the needs of explicitly defining the hops. Please note that a generator (and their messages) belong only to a single pipeline. If multiple pipelines are needed simultaneously, they need to be merged fisrt. See Branching pipeline for more details.

Single pipeline

Let’s start with a simulation where messages are generated by Generator #0 and are served by Resource #0 and Resource #1.

|Generator #0| -> |Resource #0| -> |Resource #1|

The SimPype code would hence be:

import simpype
import random

sim = simpype.Simulation(id = 'single')
gen0 = sim.add_generator(id = 'gen0')
gen0.random['arrival'] = {0: lambda: random.expovariate(1.0)}
res0 = sim.add_resource(id = 'res0')
res0.random['service'] = {0: lambda: random.expovariate(2.0)}
res1 = sim.add_resource(id = 'res1')
res1.random['service'] = {0: lambda: random.expovariate(2.0)}

p0 = sim.add_pipeline(gen0, res0, res1)

sim.run(until = 5)

As it can be noticed in sim.log file, messages generated by Generator #0 automatically flow through Resource #0 and Resource #1 without the needs of explicitly defining the next hop:

timestamp,message,seq_num,resource,event
0.000000000,gen0,0,res0,pipe.in
0.000000000,gen0,0,res0,pipe.out
0.044474460,gen0,0,res0,resource.serve
0.044474460,gen0,0,res1,pipe.in
0.044474460,gen0,0,res1,pipe.out
0.867233916,gen0,1,res0,pipe.in
0.867233916,gen0,1,res0,pipe.out
1.099185483,gen0,0,res1,resource.serve
1.876512438,gen0,1,res0,resource.serve
1.876512438,gen0,1,res1,pipe.in
1.876512438,gen0,1,res1,pipe.out
2.873364054,gen0,1,res1,resource.serve

Overlapping pipelines

Noe let’s continue with a simulation scenarios like the following:

|Generator #0| -\                    /-> |Resource #1|
                 )-> |Resource #0| -(
|Generator #1| -/                    \-> |Resource #2|

In this scenario we want to reproduce the following interconnection:

|Generator #0| -> |Resource #0| -> |Resource #1|

|Generator #1| -> |Resource #0| -> |Resource #2|

As it can be noticed, there are two dinstinct paths/pipelines that overlap at Resource #0. However, any messages generated by Generator #0 should end to Resource #1. Similarly, any messages generated by Generator #1 should end to Resource #2.

In this scenario, Resource #0 is hence shared between the two pipelines. The SimPype code would hence be:

import simpype
import random

sim = simpype.Simulation(id = 'overlap')
gen0 = sim.add_generator(id = 'gen0')
gen0.random['arrival'] = {0: lambda: random.expovariate(1.0)}
gen1 = sim.add_generator(id = 'gen1')
gen1.random['arrival'] = {0: lambda: random.expovariate(1.0)}
res0 = sim.add_resource(id = 'res0')
res0.random['service'] = {0: lambda: random.expovariate(4.0)}
res1 = sim.add_resource(id = 'res1')
res1.random['service'] = {0: lambda: random.expovariate(2.0)}
res2 = sim.add_resource(id = 'res2')
res2.random['service'] = {0: lambda: random.expovariate(2.0)}

p0 = sim.add_pipeline(gen0, res0, res1)
p1 = sim.add_pipeline(gen1, res0, res2)

sim.run(until = 2.5)

As it can be noticed in sim.log file, messages generated by Generator #0 automatically flow through Resource #0 and Resource #1 and messages generated by Generator #1 automatically flow through Resource #0 and Resource #2. Moreover, Resource #0 is shared between the two pipelines:

timestamp,message,seq_num,resource,event
0.000000000,gen0,0,res0,pipe.in
0.000000000,gen1,0,res0,pipe.in
0.000000000,gen0,0,res0,pipe.out
0.372608250,gen0,0,res0,resource.serve
0.372608250,gen0,0,res1,pipe.in
0.372608250,gen0,0,res1,pipe.out
0.372608250,gen1,0,res0,pipe.out
0.515112655,gen0,1,res0,pipe.in
0.636849329,gen1,0,res0,resource.serve
0.636849329,gen1,0,res2,pipe.in
0.636849329,gen1,0,res2,pipe.out
0.636849329,gen0,1,res0,pipe.out
0.653319564,gen0,1,res0,resource.serve
0.653319564,gen0,1,res1,pipe.in
0.684766776,gen1,1,res0,pipe.in
0.684766776,gen1,1,res0,pipe.out
0.851617505,gen0,0,res1,resource.serve
0.851617505,gen0,1,res1,pipe.out
0.921614468,gen1,2,res0,pipe.in
0.949578262,gen1,1,res0,resource.serve
0.949578262,gen1,1,res2,pipe.in
0.949578262,gen1,2,res0,pipe.out
1.052881475,gen1,2,res0,resource.serve
1.052881475,gen1,2,res2,pipe.in
1.079748898,gen0,1,res1,resource.serve
1.245866822,gen1,3,res0,pipe.in
1.245866822,gen1,3,res0,pipe.out
1.352498249,gen1,0,res2,resource.serve
1.352498249,gen1,1,res2,pipe.out
1.369990105,gen1,4,res0,pipe.in
1.384336838,gen1,1,res2,resource.serve
1.384336838,gen1,2,res2,pipe.out
1.385217621,gen1,5,res0,pipe.in
1.418331444,gen1,2,res2,resource.serve
1.582122574,gen1,3,res0,resource.serve
1.582122574,gen1,3,res2,pipe.in
1.582122574,gen1,3,res2,pipe.out
1.582122574,gen1,4,res0,pipe.out
2.028251841,gen1,4,res0,resource.serve
2.028251841,gen1,4,res2,pipe.in
2.028251841,gen1,5,res0,pipe.out
2.148959938,gen1,6,res0,pipe.in

Branching pipeline

Now let’s continue with a pipeline having a branching point with one generator and three resources:

                                  /-> |Resource #1|
|Generator #0| -> |Resource #0| -(
                                  \-> |Resource #2|

There are two possible options at this stage:

  • Serve a copy of the same message to both Resource #1 and Resource #2;
  • Either serve a message to Resource #1 or to Resource #2.

Automatic copy

In case of serving a copy of the same message to both Resource #1 and Resource #2, the SimPype code would hence be:

import simpype
import random

sim = simpype.Simulation(id = 'single')
gen0 = sim.add_generator(id = 'gen0')
gen0.random['arrival'] = {0: lambda: random.expovariate(1.0)}
res0 = sim.add_resource(id = 'res0')
res0.random['service'] = {0: lambda: random.expovariate(2.0)}
res1 = sim.add_resource(id = 'res1')
res1.random['service'] = {0: lambda: random.expovariate(2.0)}
res2 = sim.add_resource(id = 'res2')
res2.random['service'] = {0: lambda: random.expovariate(2.0)}

p0 = sim.add_pipeline(gen0, res0, res1)
p1 = sim.add_pipeline(gen0, res0, res2)
pM = sim.merge_pipeline(p0, p1)

sim.run(until = 5)

Please note the use of merge_pipeline(). This function merges multiple pipelines into a single one, thus creating the branching point. Withouth calling the merge_pipeline() function, the only active pipeline would have been p1.

As it can be noticed in sim.log file, messages are automatically copied and served to both Resource #1 and Resource #2 after being served by Resource #0:

timestamp,message,seq_num,resource,event
0.000000000,gen0,0,res0,pipe.in
0.000000000,gen0,0,res0,pipe.out
0.412762064,gen0,0,res0,resource.serve
0.412762064,gen0,0,res2,pipe.in
0.412762064,gen0,0,res1,pipe.in
0.412762064,gen0,0,res2,pipe.out
0.412762064,gen0,0,res1,pipe.out
0.631472230,gen0,0,res1,resource.serve
0.989221320,gen0,0,res2,resource.serve
2.545794865,gen0,1,res0,pipe.in
2.545794865,gen0,1,res0,pipe.out
2.572402316,gen0,1,res0,resource.serve
2.572402316,gen0,1,res2,pipe.in
2.572402316,gen0,1,res1,pipe.in
2.572402316,gen0,1,res2,pipe.out
2.572402316,gen0,1,res1,pipe.out
2.602942195,gen0,1,res1,resource.serve
4.163453623,gen0,2,res0,pipe.in
4.163453623,gen0,2,res0,pipe.out
4.222865258,gen0,2,res0,resource.serve
4.222865258,gen0,2,res2,pipe.in
4.222865258,gen0,2,res1,pipe.in
4.222865258,gen0,2,res1,pipe.out
4.270038314,gen0,1,res2,resource.serve
4.270038314,gen0,2,res2,pipe.out
4.360461106,gen0,2,res2,resource.serve
4.551208266,gen0,2,res1,resource.serve

Next hop selection

Please refer to Next in Message section to understand how the next hop of the messages can be dynamically changed.

Miscellaneous

add_pipeline() admits both Resource and Pipeline objects as arguments as shown in this examples:

import simpype

sim = simpype.Simulation(id = 'single')
gen0 = sim.add_generator(id = 'gen0')
gen1 = sim.add_generator(id = 'gen1')
res0 = sim.add_resource(id = 'res0')
res1 = sim.add_resource(id = 'res1')
res2 = sim.add_resource(id = 'res2')
res3 = sim.add_resource(id = 'res3')
res4 = sim.add_resource(id = 'res4')
res5 = sim.add_resource(id = 'res5')
res6 = sim.add_resource(id = 'res6')

# Only resources
p0 = sim.add_pipeline(res0, res1, res2)
p1 = sim.add_pipeline(res3, res4, res5)
# Mixed pipeline and resources
p2 = sim.add_pipeline(gen0, p0)
p3 = sim.add_pipeline(gen1, p1)
p4 = sim.add_pipeline(p3, res6)
# Only pipelines
# Equivalent to sim.add_pipeline(res0, res1, res2, res3, res4, res5)
p4 = sim.add_pipeline(p0, p1)

Instead, merge_pipeline() only admits Pipeline objects as arguments.