Open Jackson Network Model

In this example, the model represents a a classic queuing network problem that can be formulated as an Open Jackson Network.

The JSON for this built-in example can be loaded using json2ciw.datasets.load_jackson_network_model.

Jackson network

Imports

import json

import ciw
from rich import print

from json2ciw.datasets import load_jackson_network_model
from json2ciw.engine import CiwConverter, multiple_replications
from json2ciw.results import summarise_results, tidy_to_wide_format
from json2ciw.schema import ProcessModel

Load JSON

json_network = load_jackson_network_model()
print(json.dumps(json_network, indent=2))
{
  "name": "Open Jackson Network",
  "description": "A simple jackson network to convert to ciw",
  "activities": [
    {
      "name": "Service 1",
      "type": "activity",
      "resource": {
        "name": "Servers 1",
        "capacity": 1
      },
      "service_distribution": {
        "type": "exponential",
        "parameters": {
          "mean": 0.1
        }
      },
      "arrival_distribution": {
        "type": "exponential",
        "parameters": {
          "rate": 1.0
        }
      }
    },
    {
      "name": "Service 2",
      "type": "activity",
      "resource": {
        "name": "Servers 2",
        "capacity": 2
      },
      "service_distribution": {
        "type": "exponential",
        "parameters": {
          "mean": 0.1
        }
      },
      "arrival_distribution": {
        "type": "exponential",
        "parameters": {
          "rate": 4.0
        }
      }
    },
    {
      "name": "Service 3",
      "type": "activity",
      "resource": {
        "name": "Servers 3",
        "capacity": 1
      },
      "service_distribution": {
        "type": "exponential",
        "parameters": {
          "mean": 0.1
        }
      },
      "arrival_distribution": {
        "type": "exponential",
        "parameters": {
          "rate": 3.0
        }
      }
    }
  ],
  "transitions": [
    {
      "from": "Service 1",
      "to": "Exit",
      "probability": 0.1
    },
    {
      "from": "Service 1",
      "to": "Service 2",
      "probability": 0.6
    },
    {
      "from": "Service 1",
      "to": "Service 3",
      "probability": 0.3
    },
    {
      "from": "Service 2",
      "to": "Exit",
      "probability": 0.6
    },
    {
      "from": "Service 2",
      "to": "Service 1",
      "probability": 0.1
    },
    {
      "from": "Service 2",
      "to": "Service 3",
      "probability": 0.3
    },
    {
      "from": "Service 3",
      "to": "Exit",
      "probability": 0.2
    },
    {
      "from": "Service 3",
      "to": "Service 2",
      "probability": 0.4
    },
    {
      "from": "Service 3",
      "to": "Service 1",
      "probability": 0.4
    }
  ]
}

Validate with ProcessModel

model_instance = ProcessModel(**json_network)
print(model_instance)
ProcessModel(
    name='Open Jackson Network',
    description='A simple jackson network to convert to ciw',
    activities=[
        Activity(
            name='Service 1',
            type='activity',
            resource=Resource(name='Servers 1', capacity=1),
            service_distribution=Distribution(type='exponential', parameters={'mean': 0.1}),
            arrival_distribution=Distribution(type='exponential', parameters={'rate': 1.0}),
            renege_distribution=None
        ),
        Activity(
            name='Service 2',
            type='activity',
            resource=Resource(name='Servers 2', capacity=2),
            service_distribution=Distribution(type='exponential', parameters={'mean': 0.1}),
            arrival_distribution=Distribution(type='exponential', parameters={'rate': 4.0}),
            renege_distribution=None
        ),
        Activity(
            name='Service 3',
            type='activity',
            resource=Resource(name='Servers 3', capacity=1),
            service_distribution=Distribution(type='exponential', parameters={'mean': 0.1}),
            arrival_distribution=Distribution(type='exponential', parameters={'rate': 3.0}),
            renege_distribution=None
        )
    ],
    transitions=[
        Transition(source='Service 1', target='Exit', probability=0.1),
        Transition(source='Service 1', target='Service 2', probability=0.6),
        Transition(source='Service 1', target='Service 3', probability=0.3),
        Transition(source='Service 2', target='Exit', probability=0.6),
        Transition(source='Service 2', target='Service 1', probability=0.1),
        Transition(source='Service 2', target='Service 3', probability=0.3),
        Transition(source='Service 3', target='Exit', probability=0.2),
        Transition(source='Service 3', target='Service 2', probability=0.4),
        Transition(source='Service 3', target='Service 1', probability=0.4)
    ]
)
model_instance.save_diagram("jackson.mmd")

flowchart TD
    Arrivals_Service_1("Time between arrivals<br/>Exponential(λ=1.0)")
    Arrivals_Service_2("Time between arrivals<br/>Exponential(λ=4.0)")
    Arrivals_Service_3("Time between arrivals<br/>Exponential(λ=3.0)")
    Service_1["Service 1<br/>Exponential(mean=0.1)"]
    Service_2["Service 2<br/>Exponential(mean=0.1)"]
    Service_3["Service 3<br/>Exponential(mean=0.1)"]
    Resource_Servers_1(("Servers 1<br/>(1)"))
    Resource_Servers_2(("Servers 2<br/>(2)"))
    Resource_Servers_3(("Servers 3<br/>(1)"))
    Exit(["Exit"])

    Arrivals_Service_1 --> Service_1
    Arrivals_Service_2 --> Service_2
    Arrivals_Service_3 --> Service_3
    Resource_Servers_1 -.Seize.-> Service_1
    Service_1 -.Release.-> Resource_Servers_1
    Resource_Servers_2 -.Seize.-> Service_2
    Service_2 -.Release.-> Resource_Servers_2
    Resource_Servers_3 -.Seize.-> Service_3
    Service_3 -.Release.-> Resource_Servers_3
    Service_1 -->|10%| Exit
    Service_1 -->|60%| Service_2
    Service_1 -->|30%| Service_3
    Service_2 -->|60%| Exit
    Service_2 -->|10%| Service_1
    Service_2 -->|30%| Service_3
    Service_3 -->|20%| Exit
    Service_3 -->|40%| Service_2
    Service_3 -->|40%| Service_1

model_instance.display_diagram(include_resources=True)

flowchart TD Arrivals_Service_1(“Time between arrivals
Exponential(λ=1.0)”) Arrivals_Service_2(“Time between arrivals
Exponential(λ=4.0)”) Arrivals_Service_3(“Time between arrivals
Exponential(λ=3.0)”) Service_1[“Service 1
Exponential(mean=0.1)”] Service_2[“Service 2
Exponential(mean=0.1)”] Service_3[“Service 3
Exponential(mean=0.1)”] Resource_Servers_1((“Servers 1
(1)”)) Resource_Servers_2((“Servers 2
(2)”)) Resource_Servers_3((“Servers 3
(1)”)) Exit([“Exit”])

Arrivals_Service_1 --> Service_1
Arrivals_Service_2 --> Service_2
Arrivals_Service_3 --> Service_3
Resource_Servers_1 -.Seize.-> Service_1
Service_1 -.Release.-> Resource_Servers_1
Resource_Servers_2 -.Seize.-> Service_2
Service_2 -.Release.-> Resource_Servers_2
Resource_Servers_3 -.Seize.-> Service_3
Service_3 -.Release.-> Resource_Servers_3
Service_1 -->|10%| Exit
Service_1 -->|60%| Service_2
Service_1 -->|30%| Service_3
Service_2 -->|60%| Exit
Service_2 -->|10%| Service_1
Service_2 -->|30%| Service_3
Service_3 -->|20%| Exit
Service_3 -->|40%| Service_2
Service_3 -->|40%| Service_1
model_instance.get_distributions_df()
Activity Phase Distribution Type Parameters
0 Service 1 Arrival Exponential rate=1.0
1 Service 1 Service Exponential mean=0.1
2 Service 2 Arrival Exponential rate=4.0
3 Service 2 Service Exponential mean=0.1
4 Service 3 Arrival Exponential rate=3.0
5 Service 3 Service Exponential mean=0.1
model_instance.get_routing_matrix_df()
Service 1 Service 2 Service 3 Exit
Source Activity
Service 1 0.0 0.6 0.3 0.1
Service 2 0.1 0.0 0.3 0.6
Service 3 0.4 0.4 0.0 0.2
model_instance.get_resources_df()
Resource Activity Count
0 Servers 1 Service 1 1
1 Servers 2 Service 2 2
2 Servers 3 Service 3 1

Convert to ciw parameters

adapter = CiwConverter(model_instance)
network_params = adapter.generate_params()
print(network_params)
{
    'number_of_servers': [1, 2, 1],
    'arrival_distributions': [Exponential(rate=1.0), Exponential(rate=4.0), Exponential(rate=3.0)],
    'service_distributions': [Exponential(rate=10.0), Exponential(rate=10.0), Exponential(rate=10.0)],
    'reneging_time_distributions': [None, None, None],
    'routing': [[0.0, 0.6, 0.3], [0.1, 0.0, 0.3], [0.4, 0.4, 0.0]]
}

Build and run the ciw model

network = ciw.create_network(**network_params)
sim = ciw.Simulation(network)
sim.simulate_until_max_time(50)
print("Quick simulation run worked!")
Quick simulation run worked!

Run the model for multiple replications

df_reps = multiple_replications(
    network,
    model_instance,
    num_reps=5,
    runtime=2880,
    warmup=1440,
    n_jobs=-1,
)

df_reps.head()
rep node_id activity_name resource_name resource_capacity n_service mean_wait mean_service utilisation mean_Lq
0 0 1 Service 1 Servers 1 1 7235 0.100526 0.101436 49.753370 0.505073
1 0 2 Service 2 Servers 2 2 14480 0.035334 0.101033 49.936752 0.355298
2 0 3 Service 3 Servers 3 1 10834 0.311941 0.101618 75.011634 2.346920
3 1 1 Service 1 Servers 1 1 7221 0.105431 0.100141 49.728757 0.528695
4 1 2 Service 2 Servers 2 2 14483 0.034044 0.100673 50.070456 0.342399

Convert to wide format

wide = tidy_to_wide_format(df_reps)
wide.head()
mean_Lq [Service 1] mean_Lq [Service 2] mean_Lq [Service 3] mean_service [Service 1] mean_service [Service 2] mean_service [Service 3] mean_wait [Service 1] mean_wait [Service 2] mean_wait [Service 3] n_service [Service 1] n_service [Service 2] n_service [Service 3] utilisation [Service 1] utilisation [Service 2] utilisation [Service 3]
rep
0 0.505073 0.355298 2.346920 0.101436 0.101033 0.101618 0.100526 0.035334 0.311941 7235 14480 10834 49.753370 49.936752 75.011634
1 0.528695 0.342399 2.633773 0.100141 0.100673 0.100782 0.105431 0.034044 0.345444 7221 14483 10979 49.728757 50.070456 76.194688
2 0.498286 0.343490 2.731786 0.099994 0.098454 0.101268 0.099478 0.034136 0.364441 7213 14490 10794 49.805521 49.213628 75.446480
3 0.568632 0.361709 2.589407 0.101253 0.099272 0.100922 0.110862 0.035452 0.337382 7386 14692 11052 51.395484 50.568808 77.015357
4 0.474180 0.314434 2.454758 0.100743 0.097752 0.101118 0.098716 0.031779 0.331041 6917 14248 10678 49.078224 48.828468 74.168355

Summarise results

summary = summarise_results(df_reps)
summary.round(1)
activity Metric Service 1 (Servers 1) Service 2 (Servers 2) Service 3 (Servers 3)
0 Mean completed services 7194.4 14478.6 10867.4
1 Mean waiting time 0.1 0.0 0.3
2 Mean service time 0.1 0.1 0.1
3 Mean utilisation 50.0 49.7 75.6
4 Mean queue length 0.5 0.3 2.6