# 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`: ```toml [diagnostics] quantities = ["energy", "mode_growth", "divergence_error"] mode = [1, 1] fit_time_window = [0.02, 0.1] ``` Inspect registered diagnostics from the CLI: ```bash 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](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: - [Critical-point implementation](https://github.com/uwplasma/MHX/blob/main/src/mhx/diagnostics/critical_points.py) - [Critical-point tests](https://github.com/uwplasma/MHX/blob/main/tests/test_critical_points.py) ## Python extension API Use `DiagnosticSpec` and `DiagnosticsRegistry` for new diagnostics: ```python 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: ```python 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: ```python 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: ```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: ```toml [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 ` 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 ` writes the files under `figures/diagnostics/`. `mhx report ` 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. ## Source links - [Diagnostics implementation](https://github.com/uwplasma/MHX/blob/main/src/mhx/diagnostics/reduced_mhd.py) - [Diagnostic figure dispatch](https://github.com/uwplasma/MHX/blob/main/src/mhx/plotting/diagnostics.py) - [Diagnostics tests](https://github.com/uwplasma/MHX/blob/main/tests/test_reduced_mhd.py) - [Plugin-module tests](https://github.com/uwplasma/MHX/blob/main/tests/test_plugin_modules.py) - [Local plugin example](https://github.com/uwplasma/MHX/blob/main/examples/local_extension_plugin.py) - [Run integration](https://github.com/uwplasma/MHX/blob/main/src/mhx/benchmarks/tearing.py)