calc
tit.calc ¶
Temporal interference field calculation utilities.
Vectorised NumPy implementations of the TI/mTI modulation-amplitude envelope from Grossman et al. (2017), extended to an arbitrary even number of electrode pairs (mTI).
Public API¶
get_TI_vectors
Modulation-amplitude vectors for K >= 1 carriers (K=1 exact closed
form; K>=2 direction search).
get_TI_avg
Direction-averaged modulation depth for K >= 1 carriers.
get_TI_dir
Modulation depth evaluated along a fixed per-element direction
(e.g. the cortical surface normal); backs TI_normal for mTI.
Attribution¶
The K >= 2 envelope and the _fibonacci_sphere /
_validate_field_list helpers originate from collaborator Larissa
Albantakis's branch alba/mTI_testing and are ported here with
attribution, not reimplemented.
get_TI_vectors ¶
Compute TI modulation-amplitude vectors for K >= 1 carriers.
fields is [E_1a, E_1b, ..., E_Ka, E_Kb], 2K arrays of shape
(N, 3) -- one per channel, paired positionally: each two
consecutive fields are the two channels sharing one carrier. K=1
(standard TI) is solved by an exact closed form (Grossman et al.
2017; Hirata et al. 2024) with no direction search; K>=2 (mTI)
returns best_direction * md from the verified
:func:_mti_modulation_depth envelope.
Parameters¶
fields : list of np.ndarray, each shape (N, 3)
Channel field vectors in V/m, consecutive fields sharing a
carrier: [E1, E2] for TI, [E1a, E1b, E2a, E2b] for
two-carrier mTI. The list length must be even and >= 2; all
arrays must have the same shape.
psi : array-like, shape (K,), or None
Per-carrier envelope phase offset (radians); None means
phase-aligned carriers (psi_k=0), the standard case. Ignored
at K=1 (phase-invariant there).
Returns¶
np.ndarray, shape (N, 3)
Modulation-amplitude vectors [V/m]; norm is the modulation depth
(TI_max).
Raises¶
ValueError
If fields has an odd length or fewer than two entries, the
arrays are not all (N, 3) of one shape, or psi is not
None/shape (K,).
Examples¶
Two collinear unit fields interfere fully: the envelope equals
2 * min(|E1|, |E2|) along the common axis.
import numpy as np from tit.calc import get_TI_vectors E1 = np.array([[1.0, 0.0, 0.0]]) E2 = np.array([[0.5, 0.0, 0.0]]) get_TI_vectors([E1, E2]) array([[1., 0., 0.]]) np.linalg.norm(get_TI_vectors([E1, E2, E1, E2]), axis=1).shape # mTI, K=2 (1,)
References¶
Grossman, N. et al. (2017). Cell, 169(6), 1029-1041 (K=1 closed form). Botzanowski, B. et al. (2025). Bioelectronic Medicine, 11(1), 7 -- multipolar TI (K carrier bands, phase-aligned envelopes); describes the square/low-pass/sqrt envelope procedure in prose (no published equation), which the (P, Q) form here formalizes.
Source code in tit/calc.py
get_TI_avg ¶
Direction-averaged modulation depth for K >= 1 electrode pairs.
TI_max (:func:get_TI_vectors) maximizes the envelope over
direction -- a best case for a neuron aligned with the optimal axis.
TI_avg instead averages the same coarse Fibonacci-sphere sweep
over all sampled directions, giving what a randomly-oriented neuron
sees on average. Local refinement (accurate for a single best
direction only) is skipped as irrelevant to an average.
Parameters¶
fields : list of np.ndarray, each shape (N, 3)
Channel field vectors, consecutive fields sharing a carrier.
psi : array-like, shape (K,), or None
Per-carrier envelope phase offset (radians); see
:func:get_TI_vectors.
Returns¶
np.ndarray, shape (N,)
Modulation depth [V/m], averaged over sampled directions
(TI_avg). Never exceeds |get_TI_vectors(...)|.
Raises¶
ValueError
Same conditions as :func:get_TI_vectors.
Examples¶
import numpy as np from tit.calc import get_TI_avg, get_TI_vectors E1 = np.array([[1.0, 0.0, 0.0]]) E2 = np.array([[0.5, 0.0, 0.0]]) avg = get_TI_avg([E1, E2]) avg.shape, bool(avg[0] <= np.linalg.norm(get_TI_vectors([E1, E2]))) ((1,), True)
Source code in tit/calc.py
get_TI_dir ¶
get_TI_dir(fields: Sequence[ArrayLike], directions: ArrayLike, psi: ArrayLike | None = None) -> NDArray[float64]
Modulation depth along a fixed per-element direction, K >= 1.
The multi-carrier analogue of SimNIBS's 2-field TI.get_dirTI:
instead of maximizing the envelope over direction
(:func:get_TI_vectors), evaluate it at a given direction per
element -- typically the cortical surface normal, which yields the
TI_normal quantity. Exact (no direction search) for any K.
Parameters¶
fields : list of np.ndarray, each shape (N, 3)
Field vectors ordered [E_1a, E_1b, E_2a, E_2b, ...], one per
channel; consecutive fields are the two channels of one carrier.
directions : np.ndarray, shape (N, 3)
Per-element direction to evaluate the envelope along. Need not be
unit length; zero vectors yield 0.
psi : array-like, shape (K,), or None
Per-carrier envelope phase offset (radians); see
:func:get_TI_vectors.
Returns¶
np.ndarray, shape (N,)
Modulation depth [V/m] along directions (TI_normal when
they are the cortical normals).
Raises¶
ValueError
Same conditions as :func:get_TI_vectors, or if directions is
not shape (N, 3).
Examples¶
import numpy as np from tit.calc import get_TI_dir E1 = np.array([[1.0, 0.0, 0.0]]) E2 = np.array([[0.5, 0.0, 0.0]]) get_TI_dir([E1, E2], directions=np.array([[1.0, 0.0, 0.0]])) array([1.]) get_TI_dir([E1, E2], directions=np.array([[0.0, 1.0, 0.0]])) array([0.])