Skip to content

fields

tit.fields

Carrier-derived high-frequency field metrics — single source of truth.

Safety metrics computed from N per-pair carrier E-field vector arrays (shape (..., 3) each, one per electrode pair), following Cassarà et al. 2025, Safety Recommendations for Temporal Interference Stimulation in the Brain, Part I (Bioelectromagnetics 46(2), doi:10.1002/bem.22542):

hf_peak(*fields) — peak carrier field. Carriers run at mutually incommensurate frequencies, so every relative phase combination occurs over time; the true worst-case instantaneous magnitude is the max over sign choices, max_s |sum_i s_i * E_i| for s_i in {+1,-1}. At N=2 this is exactly max(|E1+E2|, |E1-E2|) (Cassarà Eq. 3).

hf_sar(*fields) — heating driver, proportional to SAR: carriers are incoherent, so power adds rather than amplitude, giving sum_i |E_i|^2.

Both are distinct from the stimulation-relevant modulation envelope (TI_max / TI_normal), computed in :mod:tit.calc.

See Also

tit.sim.TI : Writes hf_peak / hf_sar as volume fields on the TI mesh. tit.sim.mTI : Writes hf_peak / hf_sar as volume fields on the mTI mesh. tit.source.fsaverage : Projects hf_peak / hf_sar onto fsaverage.

hf_peak

hf_peak(*fields) -> ndarray

Peak carrier field: max over sign choices of the vector sum (Cassarà 2025, Eq. 3).

Exact sign enumeration (2**(N-1) combinations) is used up to EXACT_SIGN_ENUM_MAX_FIELDS fields. Above that, a Fibonacci-sphere direction sweep picks the best-sampled direction and evaluates the exact, realizable vector sum for the sign pattern it implies -- tighter than a raw support-function value, but still a lower bound (hence slightly non-conservative) since only sampled directions' sign patterns are tried.

Parameters

*fields : array-like, shape (..., 3) Two or more carrier E-field vectors (one per electrode pair), all the same shape.

Returns

numpy.ndarray, shape (...,) The worst-case peak carrier field magnitude.

Source code in tit/fields.py
def hf_peak(*fields) -> np.ndarray:
    """Peak carrier field: max over sign choices of the vector sum (Cassarà 2025, Eq. 3).

    Exact sign enumeration (``2**(N-1)`` combinations) is used up to
    `EXACT_SIGN_ENUM_MAX_FIELDS` fields. Above that, a Fibonacci-sphere
    direction sweep picks the best-sampled direction and evaluates the exact,
    realizable vector sum for the sign pattern it implies -- tighter than a
    raw support-function value, but still a lower bound (hence slightly
    non-conservative) since only sampled directions' sign patterns are tried.

    Parameters
    ----------
    *fields : array-like, shape ``(..., 3)``
        Two or more carrier E-field vectors (one per electrode pair), all
        the same shape.

    Returns
    -------
    numpy.ndarray, shape ``(...,)``
        The worst-case peak carrier field magnitude.
    """
    stack, shape = _stack_fields(fields)
    n = stack.shape[0]
    if n <= EXACT_SIGN_ENUM_MAX_FIELDS:
        flat = _hf_peak_exact(stack)
    else:
        flat = _hf_peak_sweep(stack)
    return flat.reshape(shape[:-1])

hf_sar

hf_sar(*fields) -> ndarray

Incoherent carrier heating driver, proportional to SAR: sum_i |E_i|^2.

Carriers sit at different, incommensurate frequencies, so their SAR/power adds rather than their amplitudes. This is a field-domain proxy in (V/m)^2, not calibrated SAR: the latter is (sigma / 2 rho) * hf_sar and needs per-tissue conductivity and density.

Parameters

*fields : array-like, shape (..., 3) Two or more carrier E-field vectors (one per electrode pair), all the same shape.

Returns

numpy.ndarray, shape (...,) sum_i |E_i|^2 in (V/m)^2 — proportional to tissue heating.

Source code in tit/fields.py
def hf_sar(*fields) -> np.ndarray:
    """Incoherent carrier heating driver, proportional to SAR: ``sum_i |E_i|^2``.

    Carriers sit at different, incommensurate frequencies, so their SAR/power
    adds rather than their amplitudes. This is a field-domain proxy in
    ``(V/m)^2``, **not** calibrated SAR: the latter is ``(sigma / 2 rho) *
    hf_sar`` and needs per-tissue conductivity and density.

    Parameters
    ----------
    *fields : array-like, shape ``(..., 3)``
        Two or more carrier E-field vectors (one per electrode pair), all
        the same shape.

    Returns
    -------
    numpy.ndarray, shape ``(...,)``
        ``sum_i |E_i|^2`` in ``(V/m)^2`` — proportional to tissue heating.
    """
    stack, shape = _stack_fields(fields)
    flat = np.sum(np.linalg.norm(stack, axis=-1) ** 2, axis=0)
    return flat.reshape(shape[:-1])