Continuous Model Runner¶
Often, a model is not only executed once for n amount of iterations. Most of the time meaningful conclusions can be drawn after simulating the model multiple times and even using different inputs.
This is made possible using the ContinuousModelRunner
.
It takes as input a ContinuousModel
object and a Configuration
object (created by using ModelConfig
).
After instantiating the runner object, two functions can be used; one runs the model multiple times for n amount of iterations using different parameters, the other performs sensitivity analysis based on specified measures.
Runner¶
The model can be executed N amount with different parameters using the
run(N, iterations_list, initial_statuses, constants_list=None)
function.
If the length of a list is not equal to the amount of simulations, this is no problem,
as the index of the list will be selected using iteration number % list_length
.
This means if you want to only use one value for every simulation, simply provide a list as argument with only one value.
Run parameters | Value Type | Default | Mandatory | Description |
---|---|---|---|---|
N | number | True | The amount of times to run the simulation | |
iterations_list | list[number] | True | A list containing the amount of iterations to use per simulation | |
initial_statuses | list[dictionary] | True | A list containing initial_status dictionaries to use per simulation | |
constants_list | list[dictionary] | None | False | A list containing constants dictionaries to use per simulation |
Example:
import networkx as nx
import numpy as np
from ndlib.models.ContinuousModel import ContinuousModel
from ndlib.models.ContinuousModelRunner import ContinuousModelRunner
from ndlib.models.compartments.NodeStochastic import NodeStochastic
import ndlib.models.ModelConfig as mc
g = nx.erdos_renyi_graph(n=1000, p=0.1)
def initial_status_1(node, graph, status, constants):
return np.random.uniform(0, 0.5)
def initial_status_2(node, graph, status, constants):
return status['status_1'] + np.random.uniform(0.5, 1)
initial_status = {
'status_1': initial_status_1,
'status_2': initial_status_2,
}
model = ContinuousModel(g)
model.add_status('status_1')
model.add_status('status_2')
# Compartments
condition = NodeStochastic(1)
# Update functions
def update_1(node, graph, status, attributes, constants):
return status[node]['status_2'] + 0.1
def update_2(node, graph, status, attributes, constants):
return status[node]['status_1'] + 0.5
# Rules
model.add_rule('status_1', update_1, condition)
model.add_rule('status_2', update_2, condition)
config = mc.Configuration()
model.set_initial_status(initial_status, config)
# Simulation
runner = ContinuousModelRunner(model, config)
# Simulate the model 10 times with 100 iterations
results = runner.run(10, [100], [initial_status])
Sensitivity Analysis¶
Another important part of analysing a model is sensitivity analysis.
Custom analysis can be done using the run function, but an integrated SALib version is included
and can be ran using the analyze_sensitivity(sa_type, initial_status, bounds, n, iterations, second_order=True)
function.
It requires the following parameters:
parameters | Value Type | Default | Mandatory | Description |
---|---|---|---|---|
sa_type | SAType | True | SAType enumerated value indicating what metric to use for sensitivity analysis | |
initial_status | dictionary | True | A dictionary containing the initial status per state | |
bounds | dictionary{status => (lower, upper) | True | A dictionary mapping a status string to a tuple in the form of [lower, upper] | |
n | integer | True | The amount of samples to get from the SALib saltelli sampler | |
iterations | integer | True | A list containing constants dictionaries to use per simulation | |
second_order | boolean | True | False | Boolean indicating whether to include second order indices |
At the moment, after every simulation, the mean value for a state is taken over all the nodes, which is seen as one output for the model.
After running the analysis, a dictionary is returned, mapping a state to a dictionary with the keys “S1”, “S2”, “ST”, “S1_conf”, “S2_conf”, and “ST_conf”
which is acquired by using sobol.analyze()
from SALib.
Note
Currently, the following sensitivity analysis metrics can be passed for the sa_type parameter (use the SAType enum):
- SAType.MEAN
Example:
import networkx as nx
import numpy as np
from ndlib.models.ContinuousModel import ContinuousModel
from ndlib.models.ContinuousModelRunner import ContinuousModelRunner
from ndlib.models.compartments.NodeStochastic import NodeStochastic
from ndlib.models.compartments.enums.SAType import SAType
import ndlib.models.ModelConfig as mc
g = nx.erdos_renyi_graph(n=1000, p=0.1)
constants = {
'constant_1': 0.5,
'constant_2': 0.8
}
def initial_status_1(node, graph, status, constants):
return np.random.uniform(0, 0.5)
def initial_status_2(node, graph, status, constants):
return status['status_1'] + np.random.uniform(0.5, 1)
initial_status = {
'status_1': initial_status_1,
'status_2': initial_status_2,
}
model = ContinuousModel(g, constants=constants)
model.add_status('status_1')
model.add_status('status_2')
# Compartments
condition = NodeStochastic(1)
# Update functions
def update_1(node, graph, status, attributes, constants):
return status[node]['status_2'] * constants['constant_1']
def update_2(node, graph, status, attributes, constants):
return status[node]['status_1'] + constants['constant_2']
# Rules
model.add_rule('status_1', update_1, condition)
model.add_rule('status_2', update_2, condition)
config = mc.Configuration()
model.set_initial_status(initial_status, config)
# Simulation
runner = ContinuousModelRunner(model, config)
analysis = runner.analyze_sensitivity(SAType.MEAN, initial_status, {'constant_1': (0, 1), 'constant_2': (-1, 1)}, 100, 50)