calc
tit.calc ¶
Temporal-interference envelope and carrier-field metrics.
Fields are zero-to-peak amplitudes, (N, 3) arrays in V/m for zero-to-peak
channel currents, one array per channel (electrodes Ek+ and Ek-). The
envelope functions take fields = [E1, E2, ..., E2K] in channel order, paired
positionally: channels 2k-1 and 2k form beating pair k (carriers
f_k and f_k + df, beating at the common difference frequency df), all
K beats phase-aligned (K = 1 is TI, K >= 2 is mTI). In the formulas E_ka and
E_kb are pair k's two channel fields. The carrier metrics take the
fields of any number >= 1 of channels, one carrier each.
A channel with no beating partner enters the envelope functions with a zero
field as its partner, [E1, E2, Eu, np.zeros_like(Eu)]: it adds to the
carrier power but not to the beat, so it lowers the envelope. :func:hf_peak
and :func:hf_power take it as one more channel field in the list.
- :func:
envelope-- envelope amplitude along a direction (TI_normal) - :func:
envelope_max-- its maximum over all directions (TI_max,mTI_max) or within a plane (tangential maximum) - :func:
envelope_mean-- its mean over all directions (TI_avg) - :func:
hf_peak-- peak instantaneous carrier field - :func:
hf_power-- time-averaged mean-square carrier field,E_rms^2
The math and the accuracy table are in docs/wiki/analyzer.md. The K >= 2
sphere maximum runs in the numba kernel :mod:tit._calc_kernel; numba is a
hard dependency (the image ships it).
References
Grossman et al. 2017, Cell 169(6):1029-1041, STAR Methods (K = 1 envelope). Botzanowski et al. 2025, Bioelectron Med 11:7, doi:10.1186/s42234-025-00169-6, pp. 4-5 (K >= 2 envelope, extending Grossman's: square, low-pass, square root). Hirata et al. 2024, Comput Biol Med 178:108697, Eq. 2 (K = 1 maximum). Cassarà et al. 2025, Bioelectromagnetics, Part I 46(2):e22542 and Part II 46(1):e22536 (carrier metrics); IEEE C95.1-2019 p. 35 (SAR).
The K >= 2 envelope model and the Fibonacci direction grid behind
_hemisphere_directions are ported from Larissa Albantakis's branch
alba/mTI_testing (the solvers here are new); the unpaired-channel
convention follows Michael Sutherland's feature/uni_HF work.
envelope ¶
Envelope modulation amplitude along a direction.
E_AM(n) = | |S n| - |D n| | = 4 |sum_k a_k b_k| / (|S n| + |D n|), with
a_k = E_ka . n, b_k = E_kb . n and S, D the K x 3 matrices
of rows E_ka + E_kb, E_ka - E_kb; the right-hand form (no
cancellation) is evaluated. K = 1 gives 2 min(|a|, |b|).
Method: closed form, exact for every K. Literature: Grossman 2017's
"envelope modulation amplitude along the direction n" (SimNIBS
get_dirTI); along the cortical normal it is TI_normal.
Defined edge cases: a zero direction row and a null field (every
projection 0) give 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Sequence[ArrayLike]
|
|
required |
direction
|
ArrayLike
|
(3,) or (N, 3); normalised internally, a zero row gives 0. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float64]
|
(N,) peak-to-trough envelope amplitude in V/m. |
Raises:
| Type | Description |
|---|---|
ValueError
|
odd or fewer than two fields, shapes not all one (N, 3),
NaN or inf, or |
Source code in tit/calc.py
envelope_max ¶
envelope_max(fields: Sequence[ArrayLike], *, perpendicular_to: ArrayLike | None = None, return_direction: bool = False) -> NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
Maximum of the envelope over all directions, or within a plane.
max_n E_AM(n) over unit n (:func:envelope), or over the unit n
perpendicular to perpendicular_to, which equals the sphere maximum of
the fields projected onto that plane.
Method: K = 1, sphere or plane, is an exact closed form (SimNIBS
get_maxTI to ~1e-15; 0 on null fields). K >= 2 is exact in a plane
(polynomial roots); on the sphere, an eigen-branch root-find plus Newton
polish, never measured more than 1e-6 relative below a dense direction
search. Literature: Grossman 2017's "maximal modulation
amplitude along any orientation" (SimNIBS TImax), K = 1 in the form of
Hirata 2024 Eq. 2. Outputs TI_max/mTI_max; the plane form is the
tangential maximum.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Sequence[ArrayLike]
|
|
required |
perpendicular_to
|
ArrayLike | None
|
plane normal(s), (3,) or (N, 3); |
None
|
return_direction
|
bool
|
also return the maximising unit direction. |
False
|
Returns:
| Type | Description |
|---|---|
NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
|
|
NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
|
|
NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
|
for |
NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
|
is the Grossman TI vector. Where |
NDArray[float64] | tuple[NDArray[float64], NDArray[float64]]
|
or an in-plane unit vector for a plane. |
Raises:
| Type | Description |
|---|---|
ValueError
|
the :func: |
RuntimeError
|
K >= 2 on the sphere, a 3x3 eigen-solve in the kernel did not converge (seen only for fields that overflow float64 once squared). |
Source code in tit/calc.py
envelope_mean ¶
Mean of the envelope over all directions (TI_avg).
The mean of E_AM(n) over the unit sphere; at K = 1 exactly
|E1| + |E2| - (|E1 + E2| + |E1 - E2|) / 2. Never exceeds
:func:envelope_max.
Method: K = 1 closed form, exact. K >= 2: equal-area quadrature on 2048
hemisphere directions (E_AM(n) = E_AM(-n), so a 4096-point sphere
rule), ~7e-4 relative error at worst. Literature: none, TI_avg is
defined by this toolbox.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Sequence[ArrayLike]
|
|
required |
Returns:
| Type | Description |
|---|---|
NDArray[float64]
|
(N,) direction-averaged envelope amplitude in V/m. |
Raises:
| Type | Description |
|---|---|
ValueError
|
as :func: |
Source code in tit/calc.py
hf_peak ¶
Peak instantaneous carrier field, max_s |sum_c s_c E_c| over s_c = +-1.
Equal to max_n sum_c |E_c . n|; two channels give max(|E1 + E2|,
|E1 - E2|). The default is the worst case over channel phases (reached
by incommensurate frequencies), the safety-relevant value. coherent=True
gives |sum_c E_c|, the phase-aligned sum some scripts call "peak HF".
Method: exact for any number of channels; closed form up to two, every
sign pattern from three to eight (2^(F-1) of them), zonotope vertices
from nine (cost ~F^3 per element for F fields, 3.5 ms at 52).
Literature: Cassarà et al. 2025 Part I Eq. 3.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Sequence[ArrayLike]
|
one or more channel fields (N, 3) in V/m, one per channel. |
required |
coherent
|
bool
|
carrier-only control (all channels at one frequency, in phase, ΔF = 0): the fields add as vectors (Cassarà 2025 Part II p. 8). |
False
|
Returns:
| Type | Description |
|---|---|
NDArray[float64]
|
(N,) peak carrier field magnitude in V/m (zero-to-peak). |
Raises:
| Type | Description |
|---|---|
ValueError
|
no fields, shapes not all one (N, 3), or NaN or inf. |
Source code in tit/calc.py
hf_power ¶
Time-averaged mean-square carrier field, (1/2) sum_c |E_c|^2.
Channel fields at distinct frequencies add in power (their cross terms
average out). The saved hf_rms field is sqrt(hf_power), and
sigma * hf_power (isotropic sigma) the absorbed (Joule) power density
hf_power_density in W/m^3. Unlike every other output this is a time
average (an RMS squared), not a zero-to-peak amplitude.
Method: closed form, exact for any number of channels. Literature: Cassarà et al. 2025 Part I p. 11 and Part II Eq. 1; IEEE C95.1-2019 p. 35.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Sequence[ArrayLike]
|
one or more zero-to-peak channel fields (N, 3) in V/m. |
required |
coherent
|
bool
|
carrier-only control (all channels at one frequency, in phase,
ΔF = 0): one sinusoid of amplitude |
False
|
Returns:
| Type | Description |
|---|---|
NDArray[float64]
|
(N,) mean-square carrier field in (V/m)^2. |
Raises:
| Type | Description |
|---|---|
ValueError
|
as :func: |