Diagnostics registry

MHX diagnostics are centralized in mhx.diagnostics so runs, reports, benchmarks, and future scan/inverse-design workflows use the same definitions. TOML configs select diagnostics through [diagnostics].quantities:

[diagnostics]
quantities = ["energy", "mode_growth", "divergence_error"]
mode = [1, 1]
fit_time_window = [0.02, 0.1]

Inspect registered diagnostics from the CLI:

mhx diagnostics list
mhx diagnostics list-with-plugins --plugin-module examples.local_extension_plugin
mhx diagnostics list-with-plugins --entry-point-group mhx.diagnostics

Built-in diagnostics

Name

Output keys

energy

initial_total_energy, final_total_energy, final_magnetic_energy, final_kinetic_energy

mode_growth

diagnostic_mode, fit_time_window, fit_sample_count, initial_mode_amplitude, final_mode_amplitude, gamma_fit

divergence_error

final_magnetic_divergence_linf

Energy definitions

For the current 2D reduced-MHD state variables \(\psi\) and \(\omega=\nabla^2\phi\), MHX computes

\[ E_B = \frac{1}{2}\langle |\nabla\psi|^2\rangle, \qquad E_K = \frac{1}{2}\langle |\nabla\phi|^2\rangle, \qquad E = E_B + E_K. \]

The trajectory diagnostic reports final magnetic/kinetic energy and initial/final total energy. The benchmark validation report uses these fields to gate unphysical total-energy growth in the current dissipative FAST benchmark.

Mode growth

For a configured Fourier mode \((k_x,k_y)\), MHX stores

\[ A_k(t) = |\hat\psi_{k_x,k_y}(t)|. \]

The fitted growth/decay rate is the least-squares slope of \(\log A_k(t)\) against time over the configured inclusive window:

\[ \gamma_{\mathrm{fit}} = \frac{\sum_i (t_i-\bar t)(\log A_i-\overline{\log A})} {\sum_i (t_i-\bar t)^2}. \]

This scalar is a reproducibility diagnostic for individual saved trajectories. It is not, by itself, a calibrated FKR eigenmode claim. Calibrated tearing checks live in the separate Harris eigenvalue, dispersion, layer, and time-domain replay gates documented on validation.md.

Reconnected flux and island-width proxies

MHX exposes low-level island diagnostics as pure functions rather than default run-summary fields, because a credible nonlinear island claim also needs a resolved separatrix, a calibrated equilibrium shear, and a long enough time window. For a real single-harmonic perturbation

\[ \psi_1(x,y,t)=\tilde\psi_1(t)\cos(k\cdot x), \]

mode_amplitude returns \(|\hat\psi_k|=|\tilde\psi_1|/2\). The helper reconnected_flux_amplitude(state, mode=...) returns \(2|\hat\psi_k|\), so the cosine amplitude is recovered exactly for single-mode tests. Given local magnetic shear \(B_y'(0)\), the Rutherford full-width proxy is

\[ W = 4\sqrt{\frac{|\tilde\psi_1|}{|B_y'(0)|}}. \]

Use island_width_from_mode(state, mode=..., magnetic_shear=...) for quick post-processing, then gate any publication claim with the nonlinear duration audit and a resolution/time-step convergence study. The unit tests verify that the proxy recovers a known cosine amplitude and rejects nonpositive shear.

Divergence error

The reduced-MHD perpendicular magnetic field is represented as

\[ B_\perp = (\partial_y\psi,\,-\partial_x\psi), \]

so analytically \(\nabla\cdot B_\perp=0\). MHX reports the final spectral consistency check

\[ \|\nabla\cdot B_\perp\|_\infty = \|\partial_x\partial_y\psi - \partial_y\partial_x\psi\|_\infty. \]

For smooth periodic fields this should be near roundoff. The unit tests include a spectral-zero gate for this diagnostic.

Flux critical points

README media and turbulent-reconnection diagnostics use magnetic-flux critical points from mhx.diagnostics.detect_flux_critical_points and mhx.diagnostics.critical_points_by_kind. The detector finds local minima of \(|\nabla\psi|\) and classifies them with the Hessian determinant:

\[ D = \psi_{xx}\psi_{yy} - \psi_{xy}^2 . \]

Points with \(D<0\) are marked as X-points; points with \(D>0\) are marked as O-points. Passing refine=True applies one local quadratic Newton correction, which reduces grid-locking for smooth fields:

\[ \delta x = -H(\psi)^{-1}\nabla\psi,\qquad x_\mathrm{refined}=x_\mathrm{grid}+\delta x . \]

mhx.diagnostics.track_critical_points then greedily links detected points across frames by nearest neighbor and point kind. This is still a validation and visualization diagnostic rather than a full topological event solver: it does not model separatrix reconnection events, mergers, or bifurcations.

Source links:

Python extension API

Use DiagnosticSpec and DiagnosticsRegistry for new diagnostics:

from mhx.diagnostics import DiagnosticSpec, default_diagnostics_registry

def compute_my_metric(context):
    return {"my_metric": 0.0}

registry = default_diagnostics_registry()
registry.register(
    DiagnosticSpec(
        name="my_metric",
        description="Example user metric.",
        output_keys=("my_metric",),
        compute=compute_my_metric,
    )
)

The callable receives a DiagnosticContext with the saved trajectory, initial state, domain lengths, diagnostic Fourier mode, and fit-time window.

Diagnostics can also provide optional report figures by passing a figure callable. The hook receives the same DiagnosticContext, the scalar diagnostic dictionary, and an output directory. It should write deterministic files and return a mapping from figure key to path:

from pathlib import Path

def plot_my_metric(context, diagnostics, figure_dir: Path):
    path = figure_dir / "my_metric.png"
    # write figure to path
    return {"my_metric_history": path}

registry.register(
    DiagnosticSpec(
        name="my_metric",
        description="Example metric with a report figure.",
        output_keys=("my_metric",),
        compute=compute_my_metric,
        figure=plot_my_metric,
    )
)

Config-loaded diagnostics plugins

A local module can expose diagnostics without modifying MHX source code:

from mhx.diagnostics import DiagnosticSpec

def compute_metric(context):
    return {"my_metric": 0.0}

def register_diagnostics(registry):
    registry.register(
        DiagnosticSpec(
            name="my_metric",
            description="Example metric.",
            output_keys=("my_metric",),
            compute=compute_metric,
        )
    )

Then enable it in TOML:

[diagnostics]
quantities = ["energy", "mode_growth", "my_metric"]
plugin_modules = ["my_project.mhx_diagnostics"]

Installed packages can alternatively expose diagnostics through the mhx.diagnostics entry-point group:

[diagnostics]
quantities = ["energy", "mode_growth", "my_metric"]
plugin_entry_point_groups = ["mhx.diagnostics"]

The demo examples/linear_tearing_plugin_demo.toml loads examples.local_extension_plugin, which registers final_flux_l2. The output schema records diagnostic_plugin_modules and diagnostic_quantities to keep extension-derived figures auditable.

mhx report <run-dir> now includes an Additional scalar diagnostics section for scalar keys that come from plugin diagnostics, for example final_flux_l2 in the local plugin demo.

Reports also reconstruct diagnostic registry metadata from config_effective.json. When plugin modules or entry points are still importable at report time, report.json contains diagnostic_metadata, and report.md renders a table of selected diagnostic descriptions and output keys. If a plugin cannot be imported, the report still writes but records a warning.

If a selected diagnostic defines a figure hook, mhx figures <run-dir> writes the files under figures/diagnostics/. mhx report <run-dir> dispatches the same hooks and records them in report.json as diagnostic_figures. The local plugin example writes figures/diagnostics/final_flux_l2_history.png, which is checked in CI as part of the FAST artifact pipeline.