Measure a simulation the way an experiment would: BOLD, EEG, functional connectivity and power spectra, declared as observation models.
2Specify
Part of the running example, where stage 2 adds an observations block that declares what to measure.
Observation models define what to measure from a simulation: BOLD signals, functional connectivity, power spectral density and custom pipelines. Everything is defined in YAML: functions are declared in the functions: section and referenced by name in the observation pipeline:.
BOLD Signal
The Balloon-Windkessel hemodynamic model transforms neural activity into a BOLD fMRI signal. The pipeline is defined entirely in YAML, and each function is specified with its equation, callable, or time range, then chained together:
on stderr, controlled by TVBO_LOG_LEVEL (default INFO).
configure_logging()
_parser = argparse.ArgumentParser(description="Run JAX-generated TVBO simulation")
_parser.add_argument(
"--spec",
type=_Path,
default=None,
help="YAML experiment spec (default: ../spec/*.yaml next to this script)",
)
_parser.add_argument(
"-o",
"--output",
type=_Path,
default=None,
help="Output directory for the result",
)
_args = _parser.parse_args()
_spec = _args.spec
if _spec is None:
_candidates = sorted(
(_Path(__file__).resolve().parent.parent / "spec").glob("*.yaml")
)
if not _candidates:
raise SystemExit("No spec found; pass --spec PATH")
_spec = _candidates[0]
from tvbo import SimulationExperiment
_experiment = SimulationExperiment.from_yaml(str(_spec))
_state = _experiment.collect_state()
_result = kernel(_state)
logger.info(
"Done: %s, shape=%s", type(_result).__name__, getattr(_result, "shape", None)
)
if _args.output is not None:
_args.output.mkdir(parents=True, exist_ok=True)
if hasattr(_result, "save"):
_result.save(str(_args.output))
else:
import numpy as _np
_np.savez(
_args.output / "result.npz", data=getattr(_result, "data", _result)
)
logger.info("Wrote results to %s", _args.output)
The generated code shows each function definition followed by the pipeline composition. No Python functions are hardcoded: everything is generated from the YAML specification.
Note the emitted call subsample_bold(..., stepsize=20000). That count is not the value: 10000 written under arguments:, because sampling steps are resolved from the observation’s declared period (the BOLD TR, 2000 ms) divided by the integration step_size (0.1 ms). Declaring the TR in physical units keeps the pipeline correct when step_size changes; the literal under arguments: only supplies the function’s standalone default. Every backend routes through the same resolver, so the sample count is identical across jax, tvboptim, and tvb.
Pipeline Architecture
Observation pipelines chain functions sequentially. Each step’s output becomes the next step’s input automatically. Steps can be:
Type
YAML Field
Description
Equation
equation.rhs
Symbolic math expression, rendered to JAX
Callable
callable: {module, name}
Reference to an external library function
Kernel
time_range + equation
Generates a TimeSeries over a time range
Available Observation Models
Pre-defined observation models in tvbo/database/observation_models/:
Model
File
Description
BOLD (TVB)
bold_tvb.yaml
Balloon-Windkessel HRF with downsampling
FC
FC.yaml
Pearson correlation matrix
See Also
Model Fitting: use observations as targets for optimization