Networks and Noise

Authors
Affiliations

Marius Pille

Berlin Institute of Health at Charité University Medicine

Leon Martin

Berlin Institute of Health at Charité University Medicine

Leon Stefanovski

Charité University Medicine Berlin

import bsplot
import matplotlib.pyplot as plt

from tvbo import Dynamics, Network, Noise, SimulationExperiment

bsplot.style.use("tvbo")

1 From a single node to a brain network

Chapter 1 worked with a single dynamical system in isolation. The TVBO state equation has more structure than that:

\[ dS_i = \left[f_d(S_i, \theta^d, C_i, I_i)\right]dt + g(S_i, \theta^g)\,dW_i. \]

Two terms bring brains into the picture:

  • Coupling \(C_i\) through a network of regions.
  • Noise \(g(S_i, \theta^g)\,dW_i\) that turns the equation into an SDE.

This notebook introduces both with the native TVBO API and minimal code.

1.1 A toy network

Networks in TVBO are first-class metadata objects. A small connectome can be written inline as YAML; a real one is loaded from the database.

toy = Network.from_string("""
label: ToyChain
nodes:
  - {id: 0, label: A}
  - {id: 1, label: B}
  - {id: 2, label: C}
edges:
  - {source: 0, target: 1, parameters: {weight: {value: 2}, distance: {value: 30, unit: mm}}}
  - {source: 1, target: 2, parameters: {weight: {value: 0.5}, distance: {value: 40, unit: mm}}}
  - {source: 0, target: 2, parameters: {weight: {value: 0.3}, distance: {value: 60, unit: mm}}}
""")
toy.plot_graph()

The Desikan-Killiany connectome ships with TVBO and is loaded the same way:

sc = Network.from_db(rec="avgMatrix", atlas="DesikanKilliany")
print(sc.label, "—", sc.number_of_nodes, "regions")
sc.plot_overview()
DesikanKilliany (avgMatrix SC+FC) — 84 regions

1.2 The Coupling Function

from tvbo import Coupling
C = Coupling.from_db('Linear')
C
Coupling({
  'name': 'Linear',
  'parameters': {'b': Parameter({
      'name': 'b',
      'value': 0.0,
      'description': ('Shifts the base of the connection strength while maintaining the absolute '
         'difference between different values.')
    }),
    'a': Parameter({
      'name': 'a',
      'value': 0.00390625,
      'description': 'Linear scaling factor of the coupled (delayed) input.'
    })},
  'sparse': False,
  'pre_expression': Equation({'rhs': 'x_j', 'latex': False}),
  'post_expression': Equation({'rhs': 'a*gx + b', 'latex': False}),
  'delayed': True,
  'symmetry': 'directed'
})
C.equation

\(\displaystyle b + a \sum_{j=0}^{-1 + N} {x}_{j} {g}_{i,j}\)

C.plot()

1.3 Simulating a coupled network

SimulationExperiment accepts a Dynamics, a Network, and a coupling kernel. We use the generic 2D oscillator coupled linearly across the toy connectome and run with the JAX-based tvboptim backend.

model = Dynamics.from_db("Generic2dOscillator")

toy.coupling['c_glob'] = C

C.parameters['a'].value=1.5


exp = SimulationExperiment(
    dynamics=model,
    network=toy,
)
exp.integration.duration = 1000      # ms
exp.integration.step_size = 0.1      # ms
exp.integration.method = "Heun"

result = exp.run("tvboptim")
result.integration.data

============================================================
STEP 1: Running simulation...
============================================================
  Simulation period: 1000.0 ms, dt: 0.1 ms
  Transient period: 0.0 ms
  Simulation complete.

============================================================
Experiment complete.
============================================================
<xarray.DataArray (time: 10000, variable: 2, node: 3)> Size: 480kB
array([[[ 0.10094234,  0.10100238,  0.10049208],
        [ 0.09379672,  0.09379612,  0.09380122]],

       [[ 0.10187335,  0.10199349,  0.10097233],
        [ 0.08758712,  0.08758472,  0.08760511]],

       [[ 0.10279301,  0.10297331,  0.10144073],
        [ 0.08137144,  0.08136604,  0.08141191]],

       ...,

       [[-0.26858695, -0.27299324, -0.21705416],
        [ 0.65534673,  0.69881663,  0.16184414]],

       [[-0.26861707, -0.27302391, -0.21706705],
        [ 0.65540802,  0.69887911,  0.16186165]],

       [[-0.26864716, -0.27305454, -0.21707993],
        [ 0.65546979,  0.69894208,  0.16187938]]], shape=(10000, 2, 3))
Coordinates:
  * time      (time) float64 80kB 0.1 0.2 0.3 0.4 ... 999.7 999.8 999.9 1e+03
  * variable  (variable) <U1 8B 'V' 'W'
  * node      (node) <U1 12B 'A' 'B' 'C'

The result is an xarray.DataArray with named axes. We select the first state variable and let TVBO plot all regions.

result.integration.sel(variable='V').plot()

Fully declarative wiring can also be expressed with an explicit mapping layer: network-level coupling aliases are declared once, then each model coupling input points to its source.

import copy

model_decl = Dynamics.from_db("Generic2dOscillator")
toy_decl = copy.deepcopy(toy)

toy_decl.coupling["global_linear"] = Coupling.from_db("Linear")

model_decl.coupling_inputs["c_glob"].source = "global_linear"

exp_decl = SimulationExperiment(
    dynamics=model_decl,
    network=toy_decl,
)
exp_decl.integration.duration = 1000
exp_decl.integration.step_size = 0.1
exp_decl.integration.method = "Heun"

result_decl = exp_decl.run("tvboptim")
result_decl.integration.sel(variable='V').plot()

============================================================
STEP 1: Running simulation...
============================================================
  Simulation period: 1000.0 ms, dt: 0.1 ms
  Transient period: 0.0 ms
  Simulation complete.

============================================================
Experiment complete.
============================================================

1.4 Adding noise

Noise enters the equation through the diffusion term \(g(S, \theta^g)\,dW\). TVBO exposes it as a metadata object that we attach to integration. Two flavours are built in.

1.4.1 Gaussian (white) noise

exp.integration.noise = Noise(
    noise_type="gaussian",
    parameters={"sigma": {"value": 0.01}},
)
result_white = exp.run("tvboptim")
result_white.integration.sel(variable='V').plot()

============================================================
STEP 1: Running simulation...
============================================================
  Simulation period: 1000.0 ms, dt: 0.1 ms
  Transient period: 0.0 ms
  Simulation complete.

============================================================
Experiment complete.
============================================================

exp.integration.noise = Noise(
    noise_type="gaussian",
    parameters={"sigma": {"value": 0.01}},
)
exp.integration.transient = 200
result_white = exp.run("tvboptim")
result_white.integration.sel(variable='V').plot()

============================================================
STEP 1: Running simulation...
============================================================
  Simulation period: 1000.0 ms, dt: 0.1 ms
  Transient period: 0.0 ms
  Simulation complete.

============================================================
Experiment complete.
============================================================

1.5 All together

from tvbo import SimulationExperiment, Dynamics, Network, Coupling, Noise

network = Network.from_db(atlas="DesikanKilliany", rec="avgMatrix")

network.coupling["Linear"] = Coupling.from_db("Linear")
network.normalize()

exp = SimulationExperiment(
    dynamics=Dynamics.from_db("Generic2dOscillator"), network=network
)

exp.dynamics.state_variables["V"].noise = Noise(parameters={"sigma": {"value": 0.1}})

exp.network.coupling["Linear"].parameters["a"].value = 0.5

res = exp.run()

res.sel(variable="V").data.T.plot()

============================================================
STEP 1: Running simulation...
============================================================
  Simulation period: 1000.0 ms, dt: 0.01220703125 ms
  Transient period: 0.0 ms
  Simulation complete.

============================================================
Experiment complete.
============================================================

1.6 What we have so far

  • A Network is metadata, just like a Dynamics.
  • SimulationExperiment(dynamics, network, coupling) is the canonical container.
  • integration.noise = Noise(...) turns the deterministic ODE into an SDE.
  • The result is the same xarray-backed object as before, regardless of network size.

The next chapter takes this same experiment and starts to vary the parameters systematically.

Back to top