A study is its experiments, the analyses that reduce them, and the figures that read both, as one object, one file and one run.
2Specify
Part of the running example, where stage 6 adds several experiments, their analyses and their figures.
A SimulationExperiment answers one question. Real work asks several, and the answers have to be compared: two coupling strengths, eleven driving frequencies, a model against its control. A SimulationStudy is the object that holds them together, and it holds three kinds of thing rather than one.
SimulationStudy
experiments → the runs
analyses → what reduces the runs to numbers
figures → what reads the runs and the analyses
That third field is what closes the loop. A study is not a folder of simulations with a plotting script beside it; the figures are part of the specification, so the thing that produced a panel is recoverable from the same file that produced the data underneath it.
A study with two experiments
The two runs differ in one parameter, so everything else is written once and referenced. &dyn names a value and *dyn reuses it, plain YAML covered in Writing TVB-O YAML.
result = study.run(root=root, figures=False)print(type(result).__name__, "->", list(result))
INFO [tvbo.run] [+0s] STEP 1: Running simulation...
INFO [tvbo.run] [+0s] Simulation period: 300.0 ms, dt: 0.1 ms
INFO [tvbo.run] [+0s] Simulation complete.
INFO [tvbo.run] [+0s] Experiment complete.
INFO [tvbo.cli] done: ExperimentResult
INFO [tvbo.cli] wrote ['/private/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/tmp1l1bse5k/derivatives/tvbo/exp-1_model-Generic2dOscillator_result.h5', '/private/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/tmp1l1bse5k/derivatives/tvbo/exp-1_model-Generic2dOscillator_result.yaml', '/private/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/tmp1l1bse5k/derivatives/tvbo/dataset_description.json']
INFO [tvbo.cli] running experiment: Strong coupling
INFO [tvbo.run] [+0s] STEP 1: Running simulation...
INFO [tvbo.run] [+0s] Simulation period: 300.0 ms, dt: 0.1 ms
INFO [tvbo.run] [+0s] Simulation complete.
INFO [tvbo.run] [+0s] Experiment complete.
INFO [tvbo.cli] done: ExperimentResult
INFO [tvbo.cli] wrote ['/private/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/tmp1l1bse5k/derivatives/tvbo/exp-2_model-Generic2dOscillator_result.h5', '/private/var/folders/ym/9kw1g21j1nd7kwfn8c0z3st40000gn/T/tmp1l1bse5k/derivatives/tvbo/exp-2_model-Generic2dOscillator_result.yaml']
StudyResult -> ['exp-1', 'exp-2']
tvbo run MiniStudy.yaml
What comes back is keyed by experiment, and each value is an xarray.DataTree, the same container a single experiment returns, so every axis is selected by name rather than by position:
V = result["exp-1"].integration.sel(variable="V")print("nodes:", V.sizes["node"], "| timepoints:", V.sizes["time"])
nodes: 4 | timepoints: 3000
The run also wrote a BIDS derivative tree beside the recipe. Each container has a .yaml sidecar holding the frozen, re-runnable spec that produced it, so a result can never drift away from the specification it came from:
for path insorted((root /"derivatives"/"tvbo").iterdir()):print(path.name)
That layout is not incidental: it is the study directory, which tvbo study init scaffolds and tvbo validate study checks.
The rest of the object
Two experiments and no analyses is the smallest useful study. The fields below are what a real one adds, and each is documented where it is specified rather than here.
Where the callables the recipe names by bare module: live.
requires
Packages this study’s own code needs beyond tvbo, with the version it was reproduced against.
workflow
Study-wide defaults for rendering sweeps into Slurm, Snakemake or Nextflow jobs.
sample
The subjects or cohort the study runs over.
members
Other studies this one aggregates, making it a study of studies.
results
The named numbers the prose cites, each bound to a computed value rather than typed into the text.
archive
How to package the run as a COMBINE/OMEX archive.
One result reads another
An analysis or a figure names the container it consumes with used:, and that binding is also the provenance edge: the record of which run a number came from.
analyses:-name: coupling_contrastlabel:"Synchrony gained by raising the coupling"callable:{name: contrast,module: mini_analysis}arguments:weak:{used:{experiment:1,output: V}}strong:{used:{experiment:2,output: V}}
Because an analysis is persisted like a run, a figure binds to it with used: {analysis: coupling_contrast} exactly as it binds to used: {experiment: 1}, and a second analysis can consume the first. Nothing in the chain is a script that has to be run in the right order by hand, because the used: edges are the order.