Vivarium Interface Demo¶
This notebook demonstrates the usage of the Vivarium Python API for building, verifying, and saving composite process-bigraph simulations. It includes examples of initializing a simulation, running it, retrieving results, and generating visual diagrams.
In [44]:
import random
from vivarium import Vivarium
from vivarium.tests import DEMO_PROCESSES # toy processes
In [45]:
# initialize a Vivarium object
v = Vivarium(processes=DEMO_PROCESSES,
# require=['units', 'sbml', 'sedml=0.1.0']
)
In [46]:
# view the available types
v.get_types()
Out[46]:
['number', '', 'length*mass/current^2*time^2', 'substance/length^3', 'current', 'time/length', 'substance/time', 'length^2*mass/current*time^2', 'time^2/length', 'boolean', 'substance', 'length*time/mass', '/printing_unit', 'current*length*time', 'length*temperature', 'printing_unit', 'mass/length', 'length^3', 'path', 'length^0_5*mass^0_5/time', 'length^2*mass/time', 'luminosity/length^2', 'quote', 'length^2', 'tuple', 'wires', 'mass^0_5/length^1_5', 'mass/temperature^4*time^3', 'current*time', 'length/mass', '/temperature*time', 'length^2/time', 'step', 'temperature', 'length^2*mass/temperature*time^2', 'luminosity', 'length', 'length^2*mass/current*time^3', 'length^1_5*mass^0_5/time^2', 'length^2*mass/current^2*time^3', '/time', '/length', 'length*mass/time^2', 'map', 'length/time', 'mass', 'integer', 'maybe', 'mass/current*time^2', 'emitter_mode', 'mass/length^3', 'float', 'protocol', 'current^2*time^3/length^2*mass', 'length^2*mass/substance*temperature*time^2', 'length^0_5*mass^0_5', 'length^2*mass/time^2', 'schema', 'current*time^2/length^2*mass', 'length^3/time', 'current*length^2*time', 'union', 'list', 'any', 'composite', 'time', 'array', 'mass/time^3', 'mass/length*time', 'mass^0_5/length^0_5*time', 'length^4*mass/current*time^3', 'current*time/substance', 'length^3/mass*time^2', 'length^1_5*mass^0_5/time', 'mass/time^2', 'current*time/mass', 'tree', 'current^2*time^4/length^3*mass', 'interval', 'length^2*mass/time^3', 'mass/length*time^2', 'edge', 'length*mass/current*time^3', 'current^2*time^4/length^2*mass', 'printing_unit/length', 'current*length^2', 'length^2/time^2', 'length/time^2', '/substance', 'string', 'process', 'length^4*mass/time^3', 'length^2*mass/current^2*time^2', 'enum', 'length^3*mass/current^2*time^4']
In [47]:
# view the type schema for 'string'
v.get_type('length')
Out[47]:
Attribute | Value | |
---|---|---|
0 | default | 1 dimensionless |
1 | description | type to represent values with scientific units |
In [48]:
# view the available processes
v.get_processes()
Out[48]:
['ram-emitter', 'increase float', 'json-emitter', 'increase mass', 'console-emitter', 'composite']
In [49]:
# inspect the config schema for the 'increase' process
v.process_schema('increase float')
/var/folders/vy/vr0_ytms6m95qrnk7xnh0bth0000gp/T/ipykernel_30535/2849076076.py:2: DeprecationWarning: process_schema() is deprecated and will be removed in a future release. Use process_config() instead. v.process_schema('increase float')
In [50]:
# # TODO -- this could look better. Maybe get the dataclass so it can be passed back into the process?
# schema = v.process_schema('increase float')
# d = v.core.dataclass(schema)
# d.rate = 0.1
# # d can be passed into the process
In [51]:
# inspect the process interface (inputs/outputs) for the 'increase' process
# TODO -- fix this output. interface might need a config
v.process_interface('increase mass')
Out[51]:
default | description | Type | ||
---|---|---|---|---|
Inputs | mass | 1.0 gram | type to represent values with scientific units | NaN |
In [52]:
# add an increase process called 'increase process'
v.add_process(name='increase',
process_id='increase float', # this is the process id
config={'rate': 0.1}, # set according to the config schema. TODO Could this accept a dataclass?
)
# print the current state
v
Out[52]:
Vivarium( { 'global_time': 0.0, 'increase': { 'address': 'local:increase float', 'config': {'rate': 0.1}, 'inputs': {}, 'instance': <vivarium.tests.IncreaseFloat object at 0x116740fd0>, 'interval': 1.0, 'outputs': {}, 'shared': None}})
In [53]:
# display as a diagram
v.diagram(dpi='70', remove_nodes=['global_time'])
Out[53]:
In [54]:
# connect the 'increase' process to the state through its inputs and outputs
v.connect_process(
name='increase',
inputs={'amount': ['top', 'A']},
outputs={'amount': ['top', 'A']}
)
# display the current state
v
Out[54]:
Vivarium( { 'global_time': 0.0, 'increase': { 'address': 'local:increase float', 'config': {'rate': 0.1}, 'inputs': {'amount': ['top', 'A']}, 'instance': <vivarium.tests.IncreaseFloat object at 0x116740fd0>, 'interval': 1.0, 'outputs': {'amount': ['top', 'A']}, 'shared': None}, 'top': {'A': 1.0}})
In [55]:
# display the current state as a diagram
v.diagram(dpi='70', remove_nodes=['global_time', 'emitter'])
Out[55]:
In [56]:
# get the value of 'top.A'
v.get_value(path=['top', 'A'])
Out[56]:
1.0
In [57]:
# set value of 'top.A' to 100
v.set_value(path=['top', 'A'], value=100.0)
v.get_value(path=['top', 'A'])
Out[57]:
100.0
In [58]:
# add an emitter to save the history
# TODO -- improve emitter configurability
v.add_emitter()
# display the current state
v.diagram(dpi='70')
Out[58]:
In [59]:
# add more objects
v.add_object(name='B', path=['top'], value=1.0)
# add another increase process
v.add_process(name='increase2',
process_id='increase float',
config={'rate': -0.1},
inputs={'amount': ['top', 'B']},
outputs={'amount': ['top', 'B']}
)
# display the current state
v.diagram(dpi='70', size='30,15')
Out[59]:
In [60]:
# run the simulation for 10 time units
v.run(interval=10)
In [61]:
# get the timeseries results
timeseries = v.get_timeseries(decimal_places=6, as_dataframe=True)
timeseries
Out[61]:
/global_time | /top/A | /top/B | |
---|---|---|---|
0 | 0.0 | 100.000000 | 1.000000 |
1 | 1.0 | 110.000000 | 0.900000 |
2 | 2.0 | 121.000000 | 0.810000 |
3 | 3.0 | 133.100000 | 0.729000 |
4 | 4.0 | 146.410000 | 0.656100 |
5 | 5.0 | 161.051000 | 0.590490 |
6 | 6.0 | 177.156100 | 0.531441 |
7 | 7.0 | 194.871710 | 0.478297 |
8 | 8.0 | 214.358881 | 0.430467 |
9 | 9.0 | 235.794769 | 0.387420 |
10 | 10.0 | 259.374246 | 0.348678 |
In [62]:
v.plot_timeseries(
query=None,
subplot_size=(6, 3),
ncols=2,
# combined_vars=None
)
Out[62]:
Save and Load¶
In [63]:
# save the current state to a file
v.save(filename='demo1')
Saved file: out/demo1.json
In [64]:
# make a new Vivarium object (v2) from the saved file
v2 = Vivarium(document='out/demo1.json', processes=DEMO_PROCESSES)
n_new_nodes = 6
# add another object and process
for i in range(n_new_nodes):
v2.add_object(name=f'C{i}', path=['top'], value=1)
v2.add_process(name=f'increase{i}',
process_id='increase float',
config={'rate': random.uniform(-1, 1)},
inputs={'amount': ['top', f'C{i}']},
outputs={'amount': ['top', f'C{i}']}
)
# display the current state as a diagram
v2.diagram(dpi='70',
show_values=True,
show_types=True,
)
Out[64]:
In [65]:
v2.run(interval=10)
v2.plot_timeseries(subplot_size=(10, 3),
ncols=2,
combined_vars=[[f'/top/C{i}' for i in range(4)],
[f'/top/C{i}' for i in range(4, n_new_nodes)]])
Out[65]:
In [66]:
v2.diagram(dpi='70',
show_values=True,
# show_types=True,
significant_digits=4,
)
Out[66]:
In [67]:
v2.diagram(dpi='70',
show_values=True,
# show_types=True,
significant_digits=4,
rankdir='LR',
port_labels=False,
remove_nodes=['top.A', 'top.B', 'increase', 'emitter', 'global_time'],
node_border_colors={(f'increase{i}',): 'blue' for i in range(n_new_nodes)},
)
Out[67]:
In [ ]: