Unit-Aware Computations

How TVBO handles physical units across simulation components

TVBO uses SymPy’s units system to handle physical units automatically. This enables correct unit conversions across different components of a simulation experiment.

Overview

Physical quantities appear throughout simulations:

Component Quantities with Units
Network distances (mm, m), delays (ms, s), conduction speed (m/s)
Integrator step size (ms), duration (s), time scale
Dynamics time constants (ms), frequencies (Hz)
Monitors sampling period (ms), frequencies (Hz)

This notebook demonstrates unit handling, starting with Networks.


1. Network Units

Networks have two unit attributes that define the coordinate system:

  • distance_unit: Unit for edge distances/lengths (default: "mm")
  • time_unit: Default time unit for delays (default: "ms")
import numpy as np
from tvbo import Network
from tvbo.datamodel import Parameter

# Simple 3-node network
W = np.array([[0, 0.5, 0.3],
              [0.2, 0, 0.4],
              [0.1, 0.6, 0]])
L = np.array([[0, 50, 80],
              [50, 0, 30],
              [80, 30, 0]])  # distances

# Default units: mm and ms
net = Network.from_matrix(W, lengths=L)
print(f"Distance unit: {net.distance_unit}")
print(f"Time unit: {net.time_unit}")
Distance unit: mm
Time unit: ms

1.1 Computing Delays

The compute_delays() method calculates transmission delays from distances and conduction speed:

\[\text{delay} = \frac{\text{distance}}{\text{speed}}\]

Units are handled automatically via SymPy:

# Set conduction speed (with units)
net.conduction_speed = Parameter(name="v", value=3.0, unit="mm/ms")

# Compute delays (uses network's time_unit by default)
delays = net.compute_delays()
print(f"Delays (ms):\n{np.round(delays, 2)}")
Delays (ms):
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

1.2 Mixed Units

TVBO correctly handles mixed units. The conduction speed unit doesn’t need to match the network’s distance/time units:

# Speed in m/s, distances in mm, output in ms
net.conduction_speed = Parameter(name="v", value=3.0, unit="m/s")
delays = net.compute_delays()
print(f"With speed in m/s:\n{np.round(delays, 2)}")
With speed in m/s:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
# Override output unit
delays_seconds = net.compute_delays("s")
print(f"Delays in seconds:\n{delays_seconds}")
Delays in seconds:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

1.3 Custom Network Units

Create networks with non-default units:

# Network with distances in meters and time in seconds
net_si = Network.from_matrix(
    W,
    lengths=L / 1000,  # Convert mm to m
    distance_unit="m",
    time_unit="s"
)
net_si.conduction_speed = Parameter(name="v", value=3.0, unit="m/s")

delays_si = net_si.compute_delays()
print(f"SI units - delays in seconds:\n{delays_si}")
SI units - delays in seconds:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

1.4 Supported Units

Any unit recognized by SymPy can be used:

Category Examples
Length mm, m, cm, km, um (micrometer)
Time ms, s, us (microsecond), minute, hour
Speed m/s, mm/ms, km/hour, etc.
# Example: centimeters and microseconds
net_micro = Network.from_matrix(
    W,
    lengths=L * 10,  # mm to 0.1mm scale
    distance_unit="cm",
    time_unit="us"
)
net_micro.conduction_speed = Parameter(name="v", value=3000.0, unit="m/s")

delays_us = net_micro.compute_delays()
print(f"Delays in microseconds:\n{np.round(delays_us, 2)}")
Delays in microseconds:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

1.5 Edge-Level Parameters with Units

Individual edges can have parameters with units via the parameters slot:

from tvbo.datamodel import Edge, Node

# Create network with explicit edges
nodes = [
    Node(id=0, label="A"),
    Node(id=1, label="B"),
    Node(id=2, label="C"),
]

edges = [
    Edge(
        source=0, target=1,
        parameters=[
            Parameter(name="weight", value=0.5),
            Parameter(name="distance", value=50.0, unit="mm"),
            Parameter(name="delay", value=16.67, unit="ms"),
        ]
    ),
    Edge(
        source=1, target=2,
        parameters=[
            Parameter(name="weight", value=0.8),
            Parameter(name="distance", value=30.0, unit="mm"),
        ]
    ),
]

net_explicit = Network(nodes=nodes, edges=edges)
print(f"Number of edges: {len(net_explicit.edges)}")

# Access edge parameters
for edge in net_explicit.edges:
    print(f"  Edge {edge.source}{edge.target}:")
    for p in edge.parameters.values():
        unit_str = f" {p.unit}" if p.unit else ""
        print(f"    {p.name}: {p.value}{unit_str}")
Number of edges: 2
  Edge 0→1:
    weight: 0.5
    distance: 50.0 mm
    delay: 16.67 ms
  Edge 1→2:
    weight: 0.8
    distance: 30.0 mm

2. Integrator Units (Coming Soon)

The integrator’s step_size and duration will support unit-aware specification:

# Future API example
from tvbo.datamodel import Integrator

integrator = Integrator(
    method="heun",
    step_size=0.1,       # value
    time_unit="ms",      # unit for step_size
    duration=1000.0,     # in time_unit
)

3. Dynamics Units (Coming Soon)

Time constants and frequencies in dynamics models will be unit-aware:

# Future API example
from tvbo.datamodel import Parameter

tau = Parameter(name="tau", value=10.0, unit="ms")  # time constant
omega = Parameter(name="omega", value=10.0, unit="Hz")  # frequency

4. Monitor Units (Coming Soon)

Sampling periods and frequencies will support units:

# Future API example
from tvbo.datamodel import Monitor

monitor = Monitor(
    name="BOLD",
    period=2.0,
    period_unit="s",  # TR in seconds
)

Summary

  • Set distance_unit and time_unit on networks to define the unit system
  • compute_delays() automatically converts between units using SymPy
  • Edge parameters can include units for documentation and validation
  • Any SymPy-recognized unit string works (no hardcoded mappings)
  • Unit support will extend to Integrator, Dynamics, and Monitor components