Quick Start¶
Installation¶
pip install emout
PyVista-based 3D visualization is included in the standard install.
Dask-based remote execution is automatically available on Python 3.10+ (no extra install needed).
Loading Simulation Data¶
import emout
data = emout.Emout("output_dir")
Emout scans the directory for HDF5 files and the parameter file (plasma.inp or plasma.toml).
Variable names are resolved from the EMSES filename convention:
Attribute |
Source file pattern |
Description |
|---|---|---|
|
|
Electrostatic potential |
|
|
Species-1 number density |
|
|
Species-1 current density (x) |
|
|
Electric field (x) |
|
|
Magnetic field (z) |
|
relocated from |
Relocated electric field (x) |
|
|
2D vector (auto-combined) |
|
|
3D vector (auto-combined) |
|
|
Inward-current data (pandas DataFrame, SI conversion via |
|
|
Outward-current data (pandas DataFrame, SI conversion via |
|
|
Conductor-potential data (pandas DataFrame, SI conversion via |
Each HDF5-backed attribute is a time-series object. Indexing by timestep returns a NumPy-compatible array:
len(data.phisp) # Number of timesteps
data.phisp[0].shape # (nz, ny, nx)
data.phisp[-1] # Last timestep
Periodic Electric-Field Relocation¶
For electric fields whose last sample duplicates the first, e[N] = e[0],
the following option interpolates adjacent samples across the periodic seam.
It preserves the mean of nonuniform periodic fields and avoids using a
different interpolation stencil at the boundaries of rex / rey / rez.
data = emout.Emout("output_dir", electric_relocation="periodic")
ex_at_centres = data.rex[-1]
This mode builds independent caches such as .emout/relocated/periodic/rex00_0000.h5
from the original electric-field HDF5 files. It neither deletes nor overwrites
existing rex00_0000.h5 files, and the mode is retained when reopening remotely.
The default, electric_relocation="legacy", keeps the historical formula and caches.
This setting applies to emout’s relocated fields; it does not configure fields
that an external trace solver loads independently.
Your First Plot¶
# 2D color map of potential on the xz-plane (y = ny/2) at the last timestep
data.phisp[-1, :, data.inp.ny // 2, :].plot()
After slicing out a 2D or 1D array, call .plot() to visualize it with SI unit labels.
Note: slice axis order is
(t, z, y, x)— this is the reverse of the(x, y, z)convention you may be used to from NumPy. The example above reads ast=-1(last step),z=:(all),y=ny/2(fixed),x=:(all), which produces an xz-plane. Every slice expression inemoutuses this order, so rewrite slices copied in from other code before using them.
Vector Fields and Component Values¶
Choose a type according to the role of the data.
Type |
Role |
|---|---|
|
One component of grid data: a NumPy array with coordinate and unit metadata |
|
Two or three field components sharing a shape and grid coordinates; supports slicing, plotting, and component-wise arithmetic |
|
Point samples, reductions, or arrays without grid metadata; retains physical component names |
|
Element-wise operations on arbitrary objects, without physical component names or grid assumptions |
data.exz and data.exyz continue to return VectorData.
Slice axis order is (t, z, y, x). After slicing, index in the order of the
remaining axes; the two-dimensional field below uses (z, x).
import numpy as np
field = data.exz[-1, :, data.inp.ny // 2, :]
field.components["x"] # Physical x component
field.components["z"] # Physical z component
field.component_axes # ("x", "z")
field.to_numpy() # Shape: (component, z, x)
(-field).plot()
np.add(field, 1.0) # Component-wise NumPy arithmetic
sample = field[0, 0] # ComponentValues: one point
means = field.mean() # ComponentValues: per-component means
means.components["z"]
means.objs # Existing element-wise access remains available
Arithmetic between named operands matches physical components regardless of
storage order. For example, adding data.exz and data.ezx adds x to x and z to z.
Operations between fields with different component sets, array shapes, or grid
coordinates raise ValueError. Ordinary NumPy arrays broadcast to each component,
while Group operands retain positional matching. Operations between Group
objects of different lengths also raise ValueError, rather than silently dropping
trailing elements. Arithmetic operates on the stored values and does not
automatically convert between different unit systems.
Boolean or integer array indexing, or a new axis inserted with None, on a loaded
Data array returns an ordinary NumPy array without coordinate metadata. For a
vector field, the component arrays are returned together as ComponentValues.
To mask a plot region while keeping its grid, use .masked() as described in the
plotting guide.
VectorData2d and VectorData3d remain aliases for VectorData.
.objs, .attrs, .x_data, .y_data, and .z_data remain available.
The *_data aliases retain their first, second, and third storage positions:
for exz, .y_data is the z component. Use .components["z"] to select a physical
component explicitly. .components is a read-only mapping, but its arrays remain
accessible as before. Use .axis(i) to obtain the shared grid coordinates for one
current array axis.
Appended Simulation Outputs¶
If the simulation continued into additional directories:
# Automatic detection
data = emout.Emout("output_dir", ad="auto")
# Manual specification
data = emout.Emout("output_dir", append_directories=["output_dir_2", "output_dir_3"])
Indexing, iteration, and len() count each join once by skipping the first
frame of every appended grid series. For compatibility, time_series(x, y, z)
defaults to skip_overlap=False and concatenates all frames. Pass skip_overlap=True when extracting
values for statistics or FFTs. The same keyword also works on a single series.
values = data.phisp.time_series(32, 32, 10, skip_overlap=True)
# Equivalent values: data.phisp[:, 10, 32, 32].to_numpy()
Particle Data¶
EMSES particle outputs are automatically grouped by species:
p4 = data.p4 # Species 4
p4.x, p4.y, p4.z # Position time series
p4.vx, p4.vy, p4.vz # Velocity time series
p4.tid # Trace ID
# Convert to pandas Series
data.p4.vx[0].val_si.to_series().hist(bins=200)