Source code for tidal.measurement._effective_mass
"""Effective mass extraction from the dispersion relation.
Computes the Lorentz-invariant effective mass squared:
m²_eff = ω² - k²
from the dominant frequency ω(k) at each active wavenumber k. This is
the 4-momentum norm and is a **Lorentz scalar** — frame-independent by
construction.
The effective mass is extracted from the dispersion relation computed by
:func:`compute_dispersion`. For a free Klein-Gordon field, m²_eff
should match the parameter mass. Deviations indicate interaction or
medium effects (mass renormalization from coupling).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
import numpy as np
from tidal.measurement._dispersion import compute_dispersion
if TYPE_CHECKING:
from collections.abc import Sequence
from numpy.typing import NDArray
from tidal.measurement._io import SimulationData
[docs]
@dataclass(frozen=True)
class EffectiveMassResult:
"""Result of effective mass extraction.
Attributes
----------
m2_eff : float
Median effective mass squared across active modes.
m2_eff_std : float
Standard deviation of m² across active modes (spread indicator).
m2_eff_values : ndarray
Per-mode m² = ω² - k² for all active modes.
wavenumbers : ndarray
Wavenumber |k| for each active mode.
frequencies : ndarray
Dominant ω(k) for each active mode.
n_active_modes : int
Number of active k-modes used in the estimate.
field_name : str
Field group name (comma-joined).
"""
m2_eff: float
m2_eff_std: float
m2_eff_values: NDArray[np.float64]
wavenumbers: NDArray[np.float64]
frequencies: NDArray[np.float64]
n_active_modes: int
field_name: str
[docs]
def compute_effective_mass(
data: SimulationData,
field_names: str | Sequence[str],
*,
min_amplitude: float = 1e-12,
) -> EffectiveMassResult:
"""Extract effective mass from the dispersion relation.
Parameters
----------
data : SimulationData
Simulation output.
field_names : str or sequence of str
Field name(s) to analyze. Multiple fields are summed
(rotationally covariant within the group).
min_amplitude : float
Minimum Fourier amplitude for a mode to be considered active.
Returns
-------
EffectiveMassResult
Effective mass and per-mode breakdown.
Raises
------
ValueError
If no active modes are found or dispersion cannot be computed.
"""
disp = compute_dispersion(data, field_names, min_amplitude=min_amplitude)
# Extract active modes
active = disp.peak_frequencies > 0.0
if not np.any(active):
msg = f"No active modes found for {disp.field_name} — cannot extract effective mass"
raise ValueError(msg)
k_active = disp.wavenumbers[active]
omega_active = disp.peak_frequencies[active]
m2_values = omega_active**2 - k_active**2
return EffectiveMassResult(
m2_eff=float(np.median(m2_values)),
m2_eff_std=float(np.std(m2_values)),
m2_eff_values=m2_values,
wavenumbers=k_active,
frequencies=omega_active,
n_active_modes=int(active.sum()),
field_name=disp.field_name,
)