import json
import ciw
from rich import print
from json2ciw.datasets import load_three_node_network_model
from json2ciw.engine import CiwConverter, multiple_replications
from json2ciw.results import summarise_results, tidy_to_wide_format
from json2ciw.schema import ProcessModelOpen Network with lognormal service times
In this example, the model represents a simple three node network problem with lognormal service times.
The JSON for this built-in example can be loaded using json2ciw.datasets.load_three_node_network_model.
Imports
Load JSON
json_network = load_three_node_network_model()
print(json.dumps(json_network, indent=2)){ "name": "Three Node Network", "description": "A simple three node network to convert to ciw", "activities": [ { "name": "Node 1", "type": "activity", "resource": { "name": "Resource 1", "capacity": 18 }, "service_distribution": { "type": "lognormal", "parameters": { "mean": 1, "std": 0.1 } }, "arrival_distribution": { "type": "exponential", "parameters": { "rate": 1.0 } } }, { "name": "Node 2", "type": "activity", "resource": { "name": "Resource 2", "capacity": 12 }, "service_distribution": { "type": "normal", "parameters": { "mean": 1, "std": 0.1 } }, "arrival_distribution": { "type": "exponential", "parameters": { "rate": 4.0 } } }, { "name": "Node 3", "type": "activity", "resource": { "name": "Resource 3", "capacity": 25 }, "service_distribution": { "type": "lognormal", "parameters": { "mean": 1, "std": 0.1 } }, "arrival_distribution": { "type": "exponential", "parameters": { "rate": 3.0 } } } ], "transitions": [ { "from": "Node 1", "to": "Exit", "probability": 0.1 }, { "from": "Node 1", "to": "Node 2", "probability": 0.6 }, { "from": "Node 1", "to": "Node 3", "probability": 0.3 }, { "from": "Node 2", "to": "Exit", "probability": 0.6 }, { "from": "Node 2", "to": "Node 1", "probability": 0.1 }, { "from": "Node 2", "to": "Node 3", "probability": 0.3 }, { "from": "Node 3", "to": "Exit", "probability": 0.2 }, { "from": "Node 3", "to": "Node 2", "probability": 0.4 }, { "from": "Node 3", "to": "Node 1", "probability": 0.4 } ] }
Validate with ProcessModel
model_instance = ProcessModel(**json_network)print(model_instance)ProcessModel( name='Three Node Network', description='A simple three node network to convert to ciw', activities=[ Activity( name='Node 1', type='activity', resource=Resource(name='Resource 1', capacity=18), service_distribution=Distribution(type='lognormal', parameters={'mean': 1.0, 'std': 0.1}), arrival_distribution=Distribution(type='exponential', parameters={'rate': 1.0}), renege_distribution=None ), Activity( name='Node 2', type='activity', resource=Resource(name='Resource 2', capacity=12), service_distribution=Distribution(type='normal', parameters={'mean': 1.0, 'std': 0.1}), arrival_distribution=Distribution(type='exponential', parameters={'rate': 4.0}), renege_distribution=None ), Activity( name='Node 3', type='activity', resource=Resource(name='Resource 3', capacity=25), service_distribution=Distribution(type='lognormal', parameters={'mean': 1.0, 'std': 0.1}), arrival_distribution=Distribution(type='exponential', parameters={'rate': 3.0}), renege_distribution=None ) ], transitions=[ Transition(source='Node 1', target='Exit', probability=0.1), Transition(source='Node 1', target='Node 2', probability=0.6), Transition(source='Node 1', target='Node 3', probability=0.3), Transition(source='Node 2', target='Exit', probability=0.6), Transition(source='Node 2', target='Node 1', probability=0.1), Transition(source='Node 2', target='Node 3', probability=0.3), Transition(source='Node 3', target='Exit', probability=0.2), Transition(source='Node 3', target='Node 2', probability=0.4), Transition(source='Node 3', target='Node 1', probability=0.4) ] )
model_instance.save_diagram("urgentcare.mmd", include_resources=False)flowchart TD
Arrivals_Node_1("Time between arrivals<br/>Exponential(λ=1.0)")
Arrivals_Node_2("Time between arrivals<br/>Exponential(λ=4.0)")
Arrivals_Node_3("Time between arrivals<br/>Exponential(λ=3.0)")
Node_1["Node 1<br/>Lognormal(mean=1.0, stdev=0)"]
Node_2["Node 2<br/>Normal(mean=1.0, sd=0)"]
Node_3["Node 3<br/>Lognormal(mean=1.0, stdev=0)"]
Exit(["Exit"])
Arrivals_Node_1 --> Node_1
Arrivals_Node_2 --> Node_2
Arrivals_Node_3 --> Node_3
Node_1 -->|10%| Exit
Node_1 -->|60%| Node_2
Node_1 -->|30%| Node_3
Node_2 -->|60%| Exit
Node_2 -->|10%| Node_1
Node_2 -->|30%| Node_3
Node_3 -->|20%| Exit
Node_3 -->|40%| Node_2
Node_3 -->|40%| Node_1
model_instance.save_diagram("urgentcare_resources.mmd", include_resources=True)flowchart TD
Arrivals_Node_1("Time between arrivals<br/>Exponential(λ=1.0)")
Arrivals_Node_2("Time between arrivals<br/>Exponential(λ=4.0)")
Arrivals_Node_3("Time between arrivals<br/>Exponential(λ=3.0)")
Node_1["Node 1<br/>Lognormal(mean=1.0, stdev=0)"]
Node_2["Node 2<br/>Normal(mean=1.0, sd=0)"]
Node_3["Node 3<br/>Lognormal(mean=1.0, stdev=0)"]
Resource_Resource_1(("Resource 1<br/>(18)"))
Resource_Resource_2(("Resource 2<br/>(12)"))
Resource_Resource_3(("Resource 3<br/>(25)"))
Exit(["Exit"])
Arrivals_Node_1 --> Node_1
Arrivals_Node_2 --> Node_2
Arrivals_Node_3 --> Node_3
Resource_Resource_1 -.Seize.-> Node_1
Node_1 -.Release.-> Resource_Resource_1
Resource_Resource_2 -.Seize.-> Node_2
Node_2 -.Release.-> Resource_Resource_2
Resource_Resource_3 -.Seize.-> Node_3
Node_3 -.Release.-> Resource_Resource_3
Node_1 -->|10%| Exit
Node_1 -->|60%| Node_2
Node_1 -->|30%| Node_3
Node_2 -->|60%| Exit
Node_2 -->|10%| Node_1
Node_2 -->|30%| Node_3
Node_3 -->|20%| Exit
Node_3 -->|40%| Node_2
Node_3 -->|40%| Node_1
model_instance.get_distributions_df()| Activity | Phase | Distribution Type | Parameters | |
|---|---|---|---|---|
| 0 | Node 1 | Arrival | Exponential | rate=1.0 |
| 1 | Node 1 | Service | Lognormal | mean=1.0, std=0.1 |
| 2 | Node 2 | Arrival | Exponential | rate=4.0 |
| 3 | Node 2 | Service | Normal | mean=1.0, std=0.1 |
| 4 | Node 3 | Arrival | Exponential | rate=3.0 |
| 5 | Node 3 | Service | Lognormal | mean=1.0, std=0.1 |
model_instance.get_routing_matrix_df()| Node 1 | Node 2 | Node 3 | Exit | |
|---|---|---|---|---|
| Source Activity | ||||
| Node 1 | 0.0 | 0.6 | 0.3 | 0.1 |
| Node 2 | 0.1 | 0.0 | 0.3 | 0.6 |
| Node 3 | 0.4 | 0.4 | 0.0 | 0.2 |
model_instance.get_resources_df()| Resource | Activity | Count | |
|---|---|---|---|
| 0 | Resource 1 | Node 1 | 18 |
| 1 | Resource 2 | Node 2 | 12 |
| 2 | Resource 3 | Node 3 | 25 |
Convert to ciw parameters
adapter = CiwConverter(model_instance)
network_params = adapter.generate_params()
print(network_params){ 'number_of_servers': [18, 12, 25], 'arrival_distributions': [Exponential(rate=1.0), Exponential(rate=4.0), Exponential(rate=3.0)], 'service_distributions': [ Lognormal(mean=-0.0049751654265839124, sd=0.0997513451195916), Normal(mean=1.0, sd=0.1), Lognormal(mean=-0.0049751654265839124, sd=0.0997513451195916) ], '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 | Node 1 | Resource 1 | 18 | 7216 | 0.000000 | 1.000161 | 28.238367 | 0.000000 |
| 1 | 0 | 2 | Node 2 | Resource 2 | 12 | 14546 | 0.138830 | 1.000103 | 83.861598 | 1.402375 |
| 2 | 0 | 3 | Node 3 | Resource 3 | 25 | 10838 | 0.000000 | 0.999720 | 30.372911 | 0.000000 |
| 3 | 1 | 1 | Node 1 | Resource 1 | 18 | 7365 | 0.000000 | 1.001236 | 27.770561 | 0.000000 |
| 4 | 1 | 2 | Node 2 | Resource 2 | 12 | 14515 | 0.182148 | 1.000512 | 83.455292 | 1.836026 |
Convert to wide format
wide = tidy_to_wide_format(df_reps)
wide.head()| mean_Lq [Node 1] | mean_Lq [Node 2] | mean_Lq [Node 3] | mean_service [Node 1] | mean_service [Node 2] | mean_service [Node 3] | mean_wait [Node 1] | mean_wait [Node 2] | mean_wait [Node 3] | n_service [Node 1] | n_service [Node 2] | n_service [Node 3] | utilisation [Node 1] | utilisation [Node 2] | utilisation [Node 3] | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| rep | |||||||||||||||
| 0 | 0.0 | 1.402375 | 0.0 | 1.000161 | 1.000103 | 0.999720 | 0.0 | 0.138830 | 0.0 | 7216 | 14546 | 10838 | 28.238367 | 83.861598 | 30.372911 |
| 1 | 0.0 | 1.836026 | 0.0 | 1.001236 | 1.000512 | 1.001197 | 0.0 | 0.182148 | 0.0 | 7365 | 14515 | 10841 | 27.770561 | 83.455292 | 29.982137 |
| 2 | 0.0 | 1.806245 | 0.0 | 1.000565 | 1.000158 | 0.999756 | 0.0 | 0.178762 | 0.0 | 7210 | 14550 | 10678 | 27.542854 | 83.531804 | 29.792481 |
| 3 | 0.0 | 1.291956 | 0.0 | 0.998776 | 0.999506 | 1.001227 | 0.0 | 0.129872 | 0.0 | 7160 | 14325 | 10713 | 27.765163 | 83.631392 | 29.968283 |
| 4 | 0.0 | 1.483820 | 0.0 | 0.999830 | 1.000044 | 1.000370 | 0.0 | 0.148063 | 0.0 | 7056 | 14431 | 10793 | 27.990271 | 83.738731 | 30.265584 |
Summarise results
summary = summarise_results(df_reps)
summary.round(1)| activity | Metric | Node 1 (Resource 1) | Node 2 (Resource 2) | Node 3 (Resource 3) |
|---|---|---|---|---|
| 0 | Mean completed services | 7201.4 | 14473.4 | 10772.6 |
| 1 | Mean waiting time | 0.0 | 0.2 | 0.0 |
| 2 | Mean service time | 1.0 | 1.0 | 1.0 |
| 3 | Mean utilisation | 27.9 | 83.6 | 30.1 |
| 4 | Mean queue length | 0.0 | 1.6 | 0.0 |