from tvbo import Dynamics, SimulationExperiment, Network
dyn = Dynamics.from_db("Generic2dOscillator")
print({sv: dyn.state_variables[sv].initial_value for sv in dyn.state_variables}){'V': 0.1, 'W': 0.1}
Part of the running example, where stage 3 adds noise on one state variable.
The initial state of a simulation is the value of every state variable on every node where the integration opens. TVBO resolves it from three layered sources, each overriding the previous:
state_variables.<name>.initial_value in the dynamics YAML.state: blocks on individual nodes.distribution on a state variable, activated for trial-based explorations.A declared integration.transient_time is not one of these. It does not replace the initial condition: the run opens from the IC resolved above at \(t = -\text{transient\_time}\) and settles in-band, so \(t = 0\) is simply where the settle ends and the measured window begins. To start from a state some other run already settled, see Seeding from a settled state.
The shape of state.initial_state.dynamics in the generated tvboptim code is (n_states, n_nodes).
Each state variable in the model YAML may carry initial_value. It is broadcast across all nodes.
Attach a state: block to any node to override that node’s IC. Other nodes keep the model default.
net = Network.from_string("""
label: Visual Pathway
nodes:
- id: 0
label: V1
state:
V: {value: 0.5}
W: {value: -0.2}
- id: 1
label: V2
state:
V: {value: 0.0}
- {id: 2, label: MT}
edges: []
""")Programmatic equivalent:
from tvbo.datamodel.tvbopydantic import StateVariable
exp.network.nodes[0].state = {
'V': StateVariable(name='V', value=0.5),
'W': StateVariable(name='W', value=-0.2),
}The tvboptim template emits one at[<sv_idx>].set(jnp.array([...])) per overridden state variable.
Add a distribution to a state variable; tvboptim then samples ICs per trial when an exploration with n_trials > 1 is active.
state_variables:
V:
initial_value: 0.1
distribution:
name: Uniform # Uniform | Gaussian (a.k.a. Normal)
seed: 42
domain: { lo: -2.0, hi: 2.0 }For Gaussian, mean = \((lo+hi)/2\) and std = \((hi-lo)/4\) (clamped to the domain).
from tvbo.datamodel.tvbopydantic import Distribution, Domain, Exploration
exp.dynamics.state_variables['V'].distribution = Distribution(
name='Uniform', domain=Domain(lo=-2.0, hi=2.0), seed=42
)
exp.dynamics.state_variables['W'].distribution = Distribution(
name='Uniform', domain=Domain(lo=-6.0, hi=6.0), seed=42
)
# Activate sampling: n_trials samples are drawn via jax.vmap
exp.explorations['ic_trials'] = Exploration(name='ic_trials', n_trials=10)
res = exp.run()Each trial draws an independent IC of shape (n_states, n_nodes) and runs the simulation in parallel via jax.vmap. Use average: trials on the exploration to reduce across trials.
A plain exp.run() without an exploration ignores the distribution. Sampling only happens when at least one Exploration requests n_trials > 1. To add a random_initial_conditions flag for single runs, open an issue.
integration.transient_time marks a settling window at the head of one integration. It is not a second simulation and it does not touch the initial condition; the scan simply opens earlier:
exp.integration.transient_time = 2000 # ms
res = exp.run()
res.integration.data # the measured window, t in (0, duration]
res.integration.transient # the settle, t in (-2000, 0]
res.integration.full # both, one bufferBecause the settle is part of the same window, an observation kernel that needs warm-up (a BOLD HRF, a phase memory, a running correlation) gets it from real signal, and its own settle-side output samples are dropped for you. Nothing needs to be declared for that to happen.
A settle that many runs reuse, the shared operating point a fit evaluates hundreds of candidates from, is a different thing, and it is declared as one:
initial_state:
method: time_integration # integrate forward to a settled start
duration: 120000 # how long to settle forThat state is computed once and reused, where transient_time is re-integrated by every run that declares it. from_working_point and from_experiment are the other two ways to say “start from a state something else reached”.
| Source | Scope | Triggered by |
|---|---|---|
state_variables.initial_value |
global | always (default) |
network.nodes[i].state |
per-node | state: block on a node |
state_variables.distribution |
per-trial | Exploration(n_trials=N) with \(N > 1\) |
initial_state.method |
global | a declared seed (see above) |
Search the output for initial_state.dynamics to see exactly how the IC is assembled for your experiment.