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 phaseOptReal mode runs musrfit’s PFTPhaseCorrection algorithm (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_sum is the coherent gain of the apodisation actually applied over the n populated (unpadded) samples — Σ wₙ — used by the amplitude calibration (2 / Σw); with no apodisation it is exactly n. When fractional footing runs, fractional_baseline records the error-weighted baseline N₀ the signal was divided by, and fractional_applied is true; the degenerate guard (non-positive/non-finite baseline) leaves the signal on its raw footing with fractional_applied false.

signal: ndarray[tuple[int, ...], dtype[float64]]
dt: float
window_sum: float
fractional_baseline: float | None = None
fractional_applied: bool = False
__init__(signal: ndarray[tuple[int, ...], dtype[float64]], dt: float, window_sum: float, fractional_baseline: float | None = None, fractional_applied: bool = False) None
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 fractional is set the signal (a lifetime-corrected count scale) and its error are divided by the error-weighted baseline N₀ so a N₀·(1 + A·cos) signal becomes 1 + A·cos; the subsequent average subtraction then removes the residual DC, leaving A·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 * 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 (Σw the apodisation coherent gain over the unpadded samples), so a pure cosine of fractional amplitude A peaks at 100·A in 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_applied and amplitude_calibrated used, 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 PFTPhaseCorrection algorithm: minimise an entropy + penalty functional over the phase-corrected real spectrum using iminuit.

The phase model is φ(i) = c₀ + c₁ · (i min_bin) / span where span = 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 phaseOptReal mode the caller must first run optimize_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_error is 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 a MuonDataset must provide one with dataset.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 tau in microseconds.