Cross-diagnostic fluctuation coherence

Cross-diagnostic fluctuation coherence

The fluctuation tutorial uses VEST shot 45531 to compare Mirnov, soft X-ray and H-alpha records. Its camera comparison uses shot 40600: no 45531 camera frames or interferometer record are supplied by that archive. Those missing diagnostics must not enter a 45531 matrix as zeros or as another shot’s signals.

The processing functions work on plain scalar records. vaft.omas.select_fluctuation_records extracts one explicitly named representative per diagnostic from an ODS or native IMAS entry; vaft.process.fluctuation aligns and analyses the resulting records; vaft.plot draws the maps. The selection API accepts Mirnov field only after calibration and integration of pickup voltage, a named SXR chord and explicit brightness/power energy band, an interferometer line integral, a camera ROI or a provenance-labelled temporal component, and a unique UV emission identity. SXR and UV units must be supplied because calibrated IMAS values and legacy proxy voltages/intensities differ. The selection source survives in every FluctuationRecord, CrossSpectralMatrix, and CoherentComponents result. One representative per diagnostic prevents a 52-chord array from receiving 52 times the weight of a single-channel diagnostic.

Estimation and units

For two records, cross_spectrogram returns one-sided auto and cross spectral densities, magnitude-squared coherence and the phase of y relative to x. Each output time window averages at least two independent inner Fourier segments before dividing cross-power by the auto PSDs. A single outer product would make coherence identically one and has no such interpretation. The default averages four non-overlapping 256-sample inner segments; the outer window advances by half its length. State the segment count and frequency resolution alongside any coherence result.

For several records, the raw matrix has

\[S_{ij}(f,t)=\langle X_i(f,t)X_j^*(f,t)\rangle.\]

Its entry has units (units_i × units_j)/Hz. PSD normalization divides by $\sqrt{S_{ii}S_{jj}}$, yielding a dimensionless Hermitian matrix with unit diagonal where power is positive. Other explicit policies are none, whole-record variance, and user scales. The raw physical-unit matrix remains in raw_csd. The default averaging count grows to at least N+1 inner segments for N selected diagnostics; fewer explicitly requested segments leave component metrics undefined when the sample matrix is rank deficient by construction.

The dominant eigenvalue divided by the matrix trace is a leading spectral fraction, not a probability or a significance level. Finite averaging raises it above 1/N even for independent signals. Compare a candidate with a null distribution using the same window, number of averages, diagnostic count and selection procedure. A tied leading eigenvalue has no unique eigenvector; participation and phase are then NaN. A missing diagnostic, out-of-band diagnostic, or zero loading also has undefined participation or phase, never an invented zero response.

Time, bandwidth and provenance

Records meet only on their common time interval. Rate reduction uses an anti-alias filter before projection onto the requested grid. Declare physical usable bandwidths where known; otherwise the result records nyquist_only, meaning the sampling ceiling is known but the sensor and analogue passband are not. A requested band above a declared limit or conservative anti-alias passband is rejected. Each record’s source rate, target rate, operation, filter cutoff and effective usable band are in resampling. The set of valid diagnostics can vary with frequency; the valid mask and n_diagnostics record that partial overlap.

The 25 kHz grid in the 45531 tutorial examines only 2–8 kHz. It cannot establish that its H-alpha record follows a 14 kHz branch: that frequency exceeds the grid’s 12.5 kHz Nyquist. The Mirnov record in the archive is pickup voltage (proportional to $dB/dt$), and its raw PSD is V²/Hz. Without a calibrated integration it is not a magnetic-field PSD in T²/Hz. PSD normalization supports dimensionless comparison but does not restore a physical field amplitude.

What the result says

Observation Supported claim
Simultaneous onset Activity shares a time; a common plasma evolution can cause it.
Same frequency Records contain power in one band; separate sources can do that.
Pairwise coherence with stable phase Two selected records maintain a spectral relation, subject to pickup and selection controls.
Dominant multi-diagnostic component Selected records concentrate normalized spectral power in one direction, with measurable participation and relative phase.
Physical MHD mode identification Needs spatial phase, mode-number and diagnostic transfer-function evidence beyond the spectral decomposition.

Use the canonical maps plot_cross_diagnostic_coherence_spectrogram, plot_multi_diagnostic_coherent_spectrogram, plot_multi_diagnostic_coherent_fraction, plot_multi_diagnostic_participation, and plot_multi_diagnostic_phase to inspect these quantities on their recorded time and frequency coordinates. The phase map is in degrees, the process result in radians. The leading component is named a dominant coherent spectral component until independent spatial evidence supports a physical mode identification.

Small synthetic check

This demonstrates a known positive phase of y relative to x without relying on one discharge. The independent noise makes the averaged estimate meaningful; two signals on the same frequency alone would not establish coherent observation.

import numpy as np
from vaft.process.fluctuation import (
    FluctuationRecord, coherent_components, cross_spectral_matrix,
)

fs = 40_000
t = np.arange(8_000) / fs
rng = np.random.default_rng(1611)
x = np.sin(2 * np.pi * 2_000 * t) + 0.1 * rng.normal(size=t.size)
y = np.sin(2 * np.pi * 2_000 * t + 0.4) + 0.1 * rng.normal(size=t.size)
records = [FluctuationRecord("reference", t, x, "V"),
           FluctuationRecord("second", t, y, "V")]
matrix = cross_spectral_matrix(records, nperseg=200, frequency_range=(1_000, 3_000))
component = coherent_components(matrix, reference="reference")
at_2khz = np.argmin(abs(component.frequency - 2_000))
print(np.nanmedian(component.phase[at_2khz, :, 1]))  # approximately +0.4 rad

results matching ""

    No results matching ""