Reading MHD Data: Units, Meshes, and Value-Space Slicing

Reading MHD Data: Units, Meshes, and Value-Space Slicing#

Demonstrate the full read and vslice API of PsiData(): loading data in different unit systems, remeshing from the half-mesh to the main mesh, and slicing by physical coordinate value using Quantity.

This example demonstrates:

  1. Reading the full dataset in code units, physical CGS units, and a specific unit.

  2. Remeshing a half-mesh field to the main mesh via the mesh keyword.

  3. Reading a radial subset by index.

  4. Slicing by physical coordinate value (vslice) with a bare scalar and with an Quantity object.

  5. Combining index-space and value-space arguments in a single vslice call.

Note

All positional arguments to read() and vslice() are interpreted in physical (r, θ, φ) order, even though the HDF storage order is (Nφ, Nθ, Nr). The returned data array shape always reflects the HDF storage order.

import astropy.units as u
import numpy as np
from psi_data import fetch_mas_data
from psi_io.mhd_io import PsiData

Create a reader

Open the example radial magnetic field file. Metadata — quantity name, physical unit, and mesh stagger — is resolved from the filename and HDF attributes. No array data is transferred from disk at this step.

br_filepath = fetch_mas_data(domains="cor", variables="br").cor_br
reader = PsiData(br_filepath, model='mas')
print(f"name  : {reader.name!r}")
print(f"shape : {reader.shape}  (Nr × Nθ × Nφ in physical order)")
name  : 'br'
shape : (255, 142, 299)  (Nr × Nθ × Nφ in physical order)

Reading in code (native) units

By default, read() returns the dataset in MAS code units. The values stored on disk are wrapped in the normalization unit object so the result is a Quantity. Passing unit='native' (aliases: 'code', 'model', 'psi') is equivalent.

data_code, r, t, p = reader.read()
print(f"unit (code) : {data_code.unit}")
print(f"shape       : {data_code.shape}")
unit (code) : MAS_b
shape       : (299, 142, 255)

Reading in physical (CGS) units

Passing unit='physical' (aliases: 'real', 'phys', 'cgs') decomposes the data into CGS base units. Any Unit-compatible string is also accepted — the example below converts directly to Gauss:

data_phys, r, t, p = reader.read(unit='physical')
print(f"unit (phys) : {data_phys.unit}")

data_gauss, r, t, p = reader.read(unit='Gauss')
print(f"unit (Gauss): {data_gauss.unit}")
unit (phys) : G
unit (Gauss): G

Remeshing to the main mesh

br is stored on the half-mesh in the radial direction (Yee face-centred). Passing mesh='main' shifts every half-mesh axis to the main mesh by averaging adjacent array elements; the radial axis shrinks by one element. The mesh stagger for each quantity is encoded in its ModelProps descriptor (see psi_io.models), and the averaging itself is performed by remesh_array() from psi_io.mesh.

data_main, r_main, t_main, p_main = reader.read(mesh='main', unit='Gauss')
print(f"original r points  : {r.shape[0]}")
print(f"main-mesh r points : {r_main.shape[0]}")
print(f"data shape (main)  : {data_main.shape}")
original r points  : 255
main-mesh r points : 254
data shape (main)  : (299, 142, 254)

Reading a subset by index

Positional arguments to read() are index-space slice specifiers in physical (r, θ, φ) order. Here we read only the first ten radial grid points (all θ and φ):

data_rslice, r_rslice, t_rslice, p_rslice = reader.read(slice(0, 10), unit='Gauss')
print(f"r-slice shape : {data_rslice.shape}")
print(f"r scale range : [{r_rslice[0]:.3f}, {r_rslice[-1]:.3f}]")
r-slice shape : (299, 142, 10)
r scale range : [1.000 PSI_rsun, 1.005 PSI_rsun]

Value-space slicing with vslice

vslice() accepts physical coordinate values as positional arguments alongside the usual index-space arguments. A bare scalar is interpreted in the native coordinate unit (solar radii for r, radians for θ and φ).

We first inspect the radial scale to pick a coordinate safely within the domain:

r_scale = reader.scales.r.read()
print(f"r domain : [{r_scale[0]:.5f}, {r_scale[-1]:.5f}]")

r_target = float(r_scale.value[len(r_scale) // 2])
print(f"r target : {r_target:.5f} {r_scale.unit}")

data_vs, r_vs, t_vs, p_vs = reader.vslice(r_target, unit='Gauss')
print(f"vslice shape : {data_vs.shape}  (r axis collapsed to 1)")
print(f"r value      : {r_vs[0]:.5f}")
r domain : [0.99971 PSI_rsun, 30.41903 PSI_rsun]
r target : 1.72158 PSI_rsun
vslice shape : (299, 142, 1)  (r axis collapsed to 1)
r value      : 1.72158 PSI_rsun

Passing an Quantity allows specifying the coordinate in any compatible unit. Here we use PSI_rsun, the solar radius constant used internally by MAS coordinate scales:

r_qty = r_scale[len(r_scale) // 2].to(u.cm)
data_vsq, r_vsq, t_vsq, p_vsq = reader.vslice(r_qty, unit='Gauss')
print(f"input (Qty) r  : {r_qty:.4e}")
print(f"vslice (Qty) r : {r_vsq[0]:.4f}")
np.testing.assert_allclose(data_vs.value, data_vsq.value, rtol=1e-5, atol=1e-6)
print("Scalar and Quantity results match.")
input (Qty) r  : 1.1982e+11 cm
vslice (Qty) r : 1.7216 PSI_rsun
Scalar and Quantity results match.

Mixing value-space and index-space arguments

vslice accepts any combination of value-space (Quantity, bare scalar, or int) and index-space (slice or None) arguments, one per spatial axis in (r, θ, φ) order. Note that — unlike read() — a bare int is interpreted as a coordinate value, not an array index; use a slice to select by index. The example below fixes the radial coordinate at the chosen target value and reads only the first five co-latitude grid points:

data_mixed, r_mixed, t_mixed, p_mixed = reader.vslice(
    r_qty,           # r: value-space (PSI_rsun) → collapses to 1
    slice(0, 5),     # θ: index-space → first 5 points
    None,            # φ: all points
    unit='Gauss',
)
print(f"mixed shape : {data_mixed.shape}  (r=1; θ=5; φ=full)")
print(f"r           : {r_mixed[0]:.4f}")
print(f"θ range     : [{t_mixed[0]:.4f}, {t_mixed[-1]:.4f}]")
mixed shape : (299, 5, 1)  (r=1; θ=5; φ=full)
r           : 1.7216 PSI_rsun
θ range     : [0.0000 PSI_angle, 0.1480 PSI_angle]

Total running time of the script: (0 minutes 0.088 seconds)

Gallery generated by Sphinx-Gallery