Fourier analysis
The Fourier subsystem exposes asymmetry.core.fourier.fft for
direct FFT-based spectra of any time-domain dataset, with the
WiMDA-compatible filter / apodisation model documented in
Fourier analysis, and
asymmetry.core.fourier.window for the windowing primitives the
FFT path uses. asymmetry.core.fourier.maxent is a placeholder
for the maximum-entropy reconstruction currently on the roadmap;
calling it raises NotImplementedError in the current release. The
GUI Fourier panel does not call this API directly — it computes
grouped-count FFTs from the current grouping payload — but the two
share the same windowing primitives.
FFT
FFT helpers for μSR asymmetry and grouped detector signals.
- asymmetry.core.fourier.fft.canonical_fourier_display_mode(display: str) str[source]
Return the canonical Fourier display-mode key for display.
- asymmetry.core.fourier.fft.fourier_mode_uses_phase_correction(display: str) bool[source]
Return whether display consumes the phase-corrected spectrum.
- asymmetry.core.fourier.fft.fourier_mode_uses_entropy_optimizer(display: str) bool[source]
Return whether display requires the entropy-based phase optimizer.
The
phaseOptRealmode runs musrfit’sPFTPhaseCorrectionalgorithm (entropy + penalty minimisation) on the averaged complex spectrum rather than consuming a manually supplied or table-driven phase correction.
- class asymmetry.core.fourier.fft.PreparedFFTSignal(signal: ndarray[tuple[int, ...], dtype[float64]], dt: float, window_sum: float, fractional_baseline: float | None = None, fractional_applied: bool = False)[source]
Preprocessed FFT time-domain input plus the calibration it enables.
window_sumis the coherent gain of the apodisation actually applied over thenpopulated (unpadded) samples —Σ wₙ— used by the amplitude calibration (2 / Σw); with no apodisation it is exactlyn. When fractional footing runs,fractional_baselinerecords the error-weighted baselineN₀the signal was divided by, andfractional_appliedis true; the degenerate guard (non-positive/non-finite baseline) leaves the signal on its raw footing withfractional_appliedfalse.
- asymmetry.core.fourier.fft.prepare_fft_time_signal(dataset: MuonDataset, *, window: str = 'none', t_min: float | None = None, t_max: float | None = None, subtract_average_signal: bool = True, filter_start_us: float = 0.0, filter_time_constant_us: float = 1.5, fractional: bool = False) PreparedFFTSignal[source]
Return the preprocessed real time-domain signal and its calibration.
Applies (in order) the time crop, optional fractional footing, WiMDA-style average subtraction, and the apodisation filter/window — the exact preprocessing
fft_complex_asymmetry()feeds to the FFT, so an all-poles (Burg) estimate can share the same input.When
fractionalis set the signal (a lifetime-corrected count scale) and its error are divided by the error-weighted baselineN₀so aN₀·(1 + A·cos)signal becomes1 + A·cos; the subsequent average subtraction then removes the residual DC, leavingA·cos. This puts the spectrum on a fractional-asymmetry footing invariant to counting statistics.
- asymmetry.core.fourier.fft.fft_complex_asymmetry(dataset: MuonDataset, window: str = 'none', padding_factor: int = 1, t_min: float | None = None, t_max: float | None = None, phase_degrees: float = 0.0, t0_offset_us: float = 0.0, subtract_average_signal: bool = True, filter_start_us: float = 0.0, filter_time_constant_us: float = 1.5, fractional: bool = False, amplitude_calibration: bool = False, diagnostics: dict | None = None) tuple[ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[complex128]]][source]
Compute the phase-rotated complex FFT of the asymmetry signal.
- Parameters:
dataset – The time-domain data.
window – Apodization window name (
"none","gaussian","hann","cosine").padding_factor – Zero-pad the signal to
padding_factor × N.t_min – Restrict the time range before transforming.
t_max – Restrict the time range before transforming.
phase_degrees – Manual phase correction in degrees. A positive value applies the usual phase-correction rotation
exp(-i * phi)to the complex spectrum.t0_offset_us – Additional WiMDA-style time-zero offset in microseconds. This applies a frequency-dependent phase term
exp(-i * 2π f t0)after the FFT.subtract_average_signal – When true, subtract the WiMDA-style pre-FFT average signal before any window is applied.
filter_start_us – WiMDA-style filter start time in microseconds for
"gaussian"and"lorentzian"FFT filtering.filter_time_constant_us – WiMDA-style filter time constant in microseconds for
"gaussian"and"lorentzian"FFT filtering.fractional – When true, put the signal on a fractional-asymmetry footing by dividing it (and its error) by the error-weighted baseline before averaging and the FFT (see
prepare_fft_time_signal()).amplitude_calibration – When true, multiply the complex spectrum by the coherent-gain / percent factor
100 · 2 / Σw(Σwthe apodisation coherent gain over the unpadded samples), so a pure cosine of fractional amplitudeApeaks at100·Ain the magnitude spectrum — invariant to counting statistics, window length, apodisation, and zero padding.diagnostics – Optional mutable dict; when supplied it is populated with the
window_sum,fractional_baseline,fractional_appliedandamplitude_calibratedused, so callers can stamp provenance/guard notes without a second pass.
- Returns:
Frequency axis (MHz) and the complex, optionally phase-rotated spectrum.
- Return type:
frequencies, spectrum
- asymmetry.core.fourier.fft.fft_asymmetry(dataset: MuonDataset, window: str = 'none', padding_factor: int = 1, t_min: float | None = None, t_max: float | None = None, phase_degrees: float = 0.0, t0_offset_us: float = 0.0, subtract_average_signal: bool = True, filter_start_us: float = 0.0, filter_time_constant_us: float = 1.5, fractional: bool = False, amplitude_calibration: bool = False) tuple[ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[float64]]][source]
Compute the FFT of the asymmetry signal.
- Parameters:
dataset – The time-domain data.
window – Apodization window name (
"none","gaussian","hann","cosine").padding_factor – Zero-pad the signal to
padding_factor × N.t_min – Restrict the time range before transforming.
t_max – Restrict the time range before transforming.
phase_degrees – Manual phase correction in degrees applied to the complex spectrum before the real and magnitude outputs are derived.
t0_offset_us – Additional WiMDA-style time-zero offset in microseconds. This adds the frequency-dependent phase term before the real and magnitude outputs are derived.
subtract_average_signal – When true, subtract the WiMDA-style pre-FFT average signal before any window is applied.
filter_start_us – WiMDA-style filter start time in microseconds for
"gaussian"and"lorentzian"FFT filtering.filter_time_constant_us – WiMDA-style filter time constant in microseconds for
"gaussian"and"lorentzian"FFT filtering.
- Returns:
Frequency axis (MHz) and the real and magnitude spectra.
- Return type:
frequencies, real_part, magnitude
- asymmetry.core.fourier.fft.estimate_fft_phase(freqs: ndarray[tuple[int, ...], dtype[float64]], spectrum: ndarray[tuple[int, ...], dtype[complex128]], *, method: str = 'peak', min_frequency: float = 0.0, max_frequency: float | None = None) float[source]
Estimate the phase, in degrees, that best projects a spectrum onto the real axis.
- Parameters:
freqs – Frequency axis in MHz.
spectrum – Complex FFT spectrum.
method –
"peak"uses the dominant non-zero frequency bin."average"uses a power-weighted circular mean across the selected spectrum.min_frequency – Ignore bins below this frequency threshold when estimating the phase.
max_frequency – Ignore bins above this frequency threshold when estimating the phase.
- asymmetry.core.fourier.fft.optimize_phase_entropy(spectrum: ndarray[tuple[int, ...], dtype[complex128]], *, min_bin: int = 1, max_bin: int | None = None, gamma: float = 1.0) tuple[ndarray[tuple[int, ...], dtype[float64]], float, float][source]
Find the linear phase (c₀ + c₁·i/N) that maximises spectral compactness.
Implements musrfit’s
PFTPhaseCorrectionalgorithm: minimise an entropy + penalty functional over the phase-corrected real spectrum using iminuit.The phase model is
φ(i) = c₀ + c₁ · (i − min_bin) / spanwherespan = max_bin − min_bin. The optimisation targets the bins in the range[min_bin, max_bin]but the returned real spectrum covers all input bins.- Parameters:
spectrum – Complex FFT spectrum, not pre-rotated.
min_bin – First bin index to include in the optimisation (default 1 to skip DC).
max_bin – Last bin index to include (default: last bin).
gamma – Weight of the negativity penalty relative to the entropy term.
- Returns:
Optimised real-valued display spectrum and the phase parameters in radians.
- Return type:
real_values, c0_rad, c1_rad
- asymmetry.core.fourier.fft.fourier_display_values(spectrum: ndarray[tuple[int, ...], dtype[complex128]], *, display: str = 'Real') ndarray[tuple[int, ...], dtype[float64]][source]
Return one real-valued display channel derived from a complex spectrum.
For
phaseOptRealmode the caller must first runoptimize_phase_entropy()and pass the resulting already-optimised real spectrum wrapped in a complex array (imaginary part zero), or pass the real array directly as a complex dtype. The function simply returns the real part in that case.
- asymmetry.core.fourier.fft.exclude_frequency_ranges(freqs: ndarray[tuple[int, ...], dtype[float64]], values: ndarray[tuple[int, ...], dtype[float64]], exclusion_ranges: list[tuple[float, float]] | tuple[tuple[float, float], ...]) ndarray[tuple[int, ...], dtype[float64]][source]
Zero spectral values inside one or more symmetric exclusion ranges.
- Parameters:
freqs – Frequency axis in MHz.
values – Real-valued Fourier display channel to be filtered.
exclusion_ranges – Iterable of
(center_mhz, half_width_mhz)pairs.
- asymmetry.core.fourier.fft.average_fourier_display_values(display_channels: list[ndarray[tuple[int, ...], dtype[float64]]] | tuple[ndarray[tuple[int, ...], dtype[float64]], ...], *, estimate_error: bool = False) tuple[ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[float64]]][source]
Average real-valued Fourier display channels across groups.
When
estimate_erroris true, the returned error follows the same WiMDA-style grouped-average estimate used for averaged FFT spectra, computed from the per-bin second moment and mean across the selected groups.
Maximum entropy
Compatibility wrapper for grouped MaxEnt spectral reconstruction.
- asymmetry.core.fourier.maxent.maxent(dataset: MuonDataset | Run, n_freq: int = 512, f_max: float | None = None) tuple[ndarray[tuple[int, ...], dtype[float64]], ndarray[tuple[int, ...], dtype[float64]]][source]
Return a MaxEnt spectrum as
(frequencies, values).This preserves the historical Fourier-module import path while delegating to
asymmetry.core.maxent. MaxEnt is a grouped raw-count algorithm; callers passing aMuonDatasetmust provide one withdataset.run.
Windowing
Apodization / window functions for Fourier analysis.
- asymmetry.core.fourier.window.apply_window(signal: ndarray[tuple[int, ...], dtype[float64]], name: str) ndarray[tuple[int, ...], dtype[float64]][source]
Apply a named window function to signal.
Supported names:
"gaussian","hann","cosine","lorentzian".
- asymmetry.core.fourier.window.apply_fft_filter(signal: ndarray[tuple[int, ...], dtype[float64]], time_us: ndarray[tuple[int, ...], dtype[float64]], *, mode: str = 'none', start_time_us: float = 0.0, time_constant_us: float = 1.5) ndarray[tuple[int, ...], dtype[float64]][source]
Apply WiMDA-style FFT apodisation to a time-domain signal.
- Parameters:
signal – Time-domain signal values.
time_us – Matching time axis in microseconds.
mode – One of
"none","lorentzian", or"gaussian".start_time_us – Filter start time. A value above zero creates WiMDA’s softened step at the chosen start time.
time_constant_us – Filter time constant
tauin microseconds.