plasma_plots.pyvista_plots
Three-dimensional PyVista views of labeled Struphy output.
Every function returns a pyvista.Plotter without showing it: call .show() in an
interactive session or notebook, or .screenshot(path) (with pyvista.OFF_SCREEN = True
in batch jobs). Pass plotter= to draw several views into one scene.
Fields need their physical X, Y, Z coordinates attached, as every Struphy field
product has; orbits need physical positions x, y, z.
Attributes
| Name | Description |
|---|---|
ORBIT_CLASS_COLORS | No description. |
RENDERERS | No description. |
Functions
| Name | Description |
|---|---|
boundary_faces | Return the real boundary faces of a point array, see boundary_keys(). |
boundary_keys | Find the logical faces of a (n1, n2, n3, 3) point array that are real boundaries. |
is_flat | Whether a structured grid has a single point in one logical direction (a 2-D run or cut). |
orbit_polylines | Marker orbits as one pyvista.PolyData line per marker, with point data color_by. |
prepare_slices_3d | The logical cuts of a scalar (eta1, eta2, eta3) field that pyvista_slices() draws. |
push_forward | Cartesian components of a vector field given by contravariant logical components. |
pyvista_domain | The mapping of a Struphy domain as a wireframe of logical grid lines. |
pyvista_glyphs | Arrows of a selected (component, eta1, eta2, eta3) vector field, colored by magnitude. |
pyvista_isosurface | Contour surfaces of a selected scalar (eta1, eta2, eta3) field in physical space. |
pyvista_orbits | Marker orbits as 3-D lines (or tubes), colored by time, orbit class, or any variable. |
pyvista_slices | Surfaces of constant logical coordinate through a scalar field, drawn in physical space. |
pyvista_streamlines | Field lines of a selected vector field, e.g. magnetic field lines, colored by magnitude. |
save_movie | Render one 3-D view per sweep step into a GIF (.gif) or video (.mp4, ...). |
save_vtk | Write a field to VTK structured grids (.vts) on its physical points, for ParaView. |
structured_grid | A pyvista.StructuredGrid of a selected (eta1, eta2, eta3) field on its physical points. |
ORBIT_CLASS_COLORSattributemodule attribute#
ORBIT_CLASS_COLORS = {'passing': 'tab:blue', 'trapped': 'tab:orange', 'lost': 'grey'}RENDERERSattributemodule attribute#
RENDERERS = {
'isosurface': pyvista_isosurface,
'slices': pyvista_slices,
'glyphs': pyvista_glyphs,
'streamlines': pyvista_streamlines
}boundary_facesfunction#
def boundary_faces(points: np.ndarray) -> list[np.ndarray]Return the real boundary faces of a point array, see boundary_keys().
Parameters
| Name | Type | Description |
|---|---|---|
points | numpy.ndarray | Physical points, shape (n1, n2, n3, 3). |
Returns
list of numpy.ndarray- The points of each face, with a size-one axis in the face’s logical direction.
boundary_keysfunction#
def boundary_keys(points: np.ndarray) -> list[tuple[int, int]]Find the logical faces of a (n1, n2, n3, 3) point array that are real boundaries.
Faces that collapse to a line or point (a polar axis) and pairs of opposite faces that
coincide (the seam of a periodic direction, e.g. phi = 0 of a full torus) are dropped.
A grid that is flat in one direction (a 2-D run) is its own single face.
Parameters
| Name | Type | Description |
|---|---|---|
points | numpy.ndarray | Physical points, shape (n1, n2, n3, 3). |
Returns
list of (int, int)(axis, index)of each boundary face;indexis0or-1.
is_flatfunction#
def is_flat(grid) -> boolWhether a structured grid has a single point in one logical direction (a 2-D run or cut).
Parameters
| Name | Type | Description |
|---|---|---|
grid | pyvista.StructuredGrid | The grid, e.g. from structured_grid(). |
Returns
boolTrueif any of the grid’s dimensions is 1.
orbit_polylinesfunction#
def orbit_polylines(orbits: xr.Dataset, *, color_by: str = 't', max_markers: int = 200)Marker orbits as one pyvista.PolyData line per marker, with point data color_by.
Samples where a marker is lost (every quantity zero) are dropped. color_by is "t",
"classification" (see classify_orbits()), or the name of
any (t, marker) variable, e.g. "v_par" or "weight".
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
orbits | xarray.Dataset | required | Marker orbits with dims (t, marker) and physical positions x, y, z. |
color_by | str | 't' | The point data to attach: "t", "classification" or a variable name.
Default: "t". |
max_markers | int | 200 | At most this many markers are used. Default: 200. |
Returns
pyvista.PolyData- One line per marker with at least two samples left, and point data
color_by; empty if no marker has.
Raises
ValueError- If
color_byis neither"t","classification"nor a variable oforbits.
prepare_slices_3dfunction#
def prepare_slices_3d(data: xr.DataArray, *, cuts: dict | None = None) -> list[xr.DataArray]The logical cuts of a scalar (eta1, eta2, eta3) field that pyvista_slices() draws.
cuts maps eta1/eta2/eta3 to one position or a list: a float is the nearest
logical coordinate, an integer a grid index (-1 the last). The default is
the middle of every dimension with more than one point, or for a 2-D field (one dimension
with a single point) the whole plane. Each cut keeps its size-one dimension, so it still
maps onto a surface in physical space.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A scalar field with dims (eta1, eta2, eta3) and physical coordinates X, Y,
Z. |
cuts | dict | None | {dim: position or list of positions} along eta1, eta2, eta3. |
Returns
list of xarray.DataArray- One array per cut, in the order of
cuts.
Raises
ValueError- If a cut is along another dimension.
TypeError- If a position is neither an integer nor a float.
push_forwardfunction#
def push_forward(data: xr.DataArray) -> xr.DataArrayCartesian components of a vector field given by contravariant logical components.
data has dims (component, eta1, eta2, eta3), e.g. from
out.evaluate(name, eta1=..., eta2=..., eta3=..., representation="v"). The Cartesian
field is sum_i v^i dX/de_i, with the Jacobian of the mapping differentiated numerically
from the attached X, Y, Z coordinates, so every logical direction needs at least
two points.
Parameters
| Name | Type | Description |
|---|---|---|
data | xarray.DataArray | The vector field, dims (component, eta1, eta2, eta3) with three contravariant
components and physical coordinates X, Y, Z. |
Returns
xarray.DataArray- The Cartesian
x,y,zcomponents, same dims and coordinates, labeled"<label> (Cartesian)".
Raises
ValueError- If the field doesn’t have three components or a logical direction has fewer than two points.
Examples
>>> B_xyz = push_forward(... out.evaluate("em_fields/b_field", representation="v").isel(t=-1)... )pyvista_domainfunction#
def pyvista_domain(domain, *, n1: int = 8, n2: int = 32, n3: int = 32, resolution: int = 4, color='black', surface: bool = True, cross_section: bool = True, title: str | None = None, plotter=None)The mapping of a Struphy domain as a wireframe of logical grid lines.
Grid lines are drawn on the real boundary faces (see boundary_keys(); for a torus the
outer and inner surfaces) and, with cross_section, over the whole eta3 = 0 face (for a
torus a poloidal cross-section). n1, n2, n3 lines per direction, each sampled
resolution times finer so curved lines stay smooth. surface adds the translucent
boundary. Useful to check the geometry (and its orientation) of a run; n3=1 shows a
2-D run’s plane.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
domain | callable | required | A Struphy domain (mapping), e.g. out.domain, called as
domain(eta1, eta2, eta3, squeeze_out=False) to give x, y, z. |
n1 | int | 8 | Grid lines along eta1. Default: 8. |
n2 | int | 32 | Grid lines along eta2. Default: 32. |
n3 | int | 32 | Grid lines along eta3. Default: 32. |
resolution | int | 4 | Samples per line spacing, so curved lines stay smooth. Default: 4. |
color | str | 'black' | Color of the grid lines. Default: "black". |
surface | bool | True | Draw the translucent boundary surface. Default: True. |
cross_section | bool | True | Also draw grid lines over the eta3 = 0 face. Default: True. |
title | str | None | Text in the scene’s corner. Default: "Domain"; "" for none. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the domain when this function creates the plotter. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Examples
>>> pyvista_domain(out.domain).show()>>> pyvista_domain(out.domain, n3=1, surface=False).show()pyvista_glyphsfunction#
def pyvista_glyphs(data: xr.DataArray, *, components: Literal['cartesian', 'contravariant'] = 'cartesian', stride: int = 2, scale: float | None = None, cmap='viridis', show_domain: bool = True, title: str | None = None, plotter=None)Arrows of a selected (component, eta1, eta2, eta3) vector field, colored by magnitude.
components="cartesian" (default) for x/y/z components, e.g. a *_phy product;
"contravariant" for logical components, pushed forward by push_forward().
stride thins the grid in every direction; scale is the arrow length of the largest
vector (default: a tenth of the domain size).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A vector field with dims (component, eta1, eta2, eta3) (three components, every
other dimension selected) and physical coordinates X, Y, Z. |
components | ('cartesian', 'contravariant') | "cartesian" | How to read the components: Cartesian x/y/z, or contravariant logical components,
pushed forward. Default: "cartesian". |
stride | int | 2 | Draw an arrow at every stride-th point in every direction. Default: 2. |
scale | float | None | Arrow length of the largest vector, in physical units. Default: a tenth of the domain size. |
cmap | str or matplotlib colormap | 'viridis' | The colormap of the magnitude. Default: "viridis". |
show_domain | bool | True | Draw the domain’s outer surface translucently for context. Default: True. |
title | str | None | Text in the scene’s corner. Default: the field’s label; "" for none. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Raises
ValueError- If
strideis less than 1,componentsis unknown, or the field isn’t a three-component field over the logical dimensions with physical coordinates.
Examples
>>> pyvista_glyphs(B.isel(t=-1), stride=3).show()>>> pyvista_glyphs(B.isel(t=-1), components="contravariant", scale=0.2).show()pyvista_isosurfacefunction#
def pyvista_isosurface(data: xr.DataArray, *, values: int | list[float] = 5, cmap='viridis', opacity: float = 1.0, clim=None, show_domain: bool = True, title: str | None = None, symmetric: bool = False, robust: bool = False, plotter=None)Contour surfaces of a selected scalar (eta1, eta2, eta3) field in physical space.
values is the number of evenly spaced levels, or explicit levels (between the color
limits, which symmetric/robust set as in color_limits()).
show_domain draws
the domain’s outer surface translucently for context. For a 2-D field (one logical
direction with a single point) the levels are contour lines over the colored plane.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A scalar field with dims (eta1, eta2, eta3) (every other dimension selected; one
logical dimension may be selected away) and physical coordinates X, Y, Z. |
values | int or list of float | 5 | The number of evenly spaced levels strictly between the color limits, or explicit levels. Default: 5. |
cmap | str or matplotlib colormap | 'viridis' | The colormap. Default: "viridis". |
opacity | float | 1.0 | Opacity of the surfaces (of the plane for a 2-D field). Default: 1. |
clim | (float, float) | None | Color limits; default from the field’s values, see symmetric and robust. |
show_domain | bool | True | Draw the domain’s outer surface translucently for context (3-D fields only).
Default: True. |
title | str | None | Text in the scene’s corner. Default: the field’s label; "" for none. |
symmetric | bool | False | Color limits symmetric about zero. Default: False. |
robust | bool | False | Color limits from percentiles instead of the extremes, so outliers don’t wash out the
colors. Default: False. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Raises
ValueError- If other dimensions remain or the physical coordinates are missing.
Examples
>>> pyvista_isosurface(phi.isel(t=-1), values=[-0.1, 0.1]).show()>>> pyvista_isosurface(phi.isel(t=-1), symmetric=True, opacity=0.6).show()pyvista_orbitsfunction#
def pyvista_orbits(orbits: xr.Dataset, *, color_by: str = 't', max_markers: int = 200, tube_radius: float | None = None, cmap=None, domain: xr.DataArray | None = None, title: str | None = None, plotter=None)Marker orbits as 3-D lines (or tubes), colored by time, orbit class, or any variable.
domain is any field with physical coordinates whose outer surface is drawn translucently
for context. See orbit_polylines() for color_by.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
orbits | xarray.Dataset | required | Marker orbits with dims (t, marker) and physical positions x, y, z. |
color_by | str | 't' | "t", "classification" (passing, trapped, lost, with a legend) or the name of
any (t, marker) variable, e.g. "v_par". Default: "t". |
max_markers | int | 200 | At most this many markers are drawn. Default: 200. |
tube_radius | float | None | Draw the orbits as tubes of this radius. Default: plain lines. |
cmap | str or matplotlib colormap | None | The colormap (not used for "classification"). Default: "viridis". |
domain | xarray.DataArray | None | A field with physical coordinates whose outer surface is drawn translucently; other
than eta1, eta2, eta3, its dimensions are taken at their first position. |
title | str | None | Text in the scene’s corner. Default: "Marker orbits"; "" for none. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Raises
ValueError- If
color_byis neither"t","classification"nor a variable oforbits.
Examples
>>> pyvista_orbits(out.kinetic_ions.orbits, color_by="classification").show()>>> pyvista_orbits(... out.kinetic_ions.orbits, color_by="v_par", domain=phi, tube_radius=0.01... ).show()pyvista_slicesfunction#
def pyvista_slices(data: xr.DataArray, *, cuts: dict | None = None, cmap='viridis', clim=None, show_domain: bool = True, title: str | None = None, symmetric: bool = False, robust: bool = False, plotter=None)Surfaces of constant logical coordinate through a scalar field, drawn in physical space.
On a mapped domain these are the natural cuts: cuts={"eta3": [0, 0.25]} gives poloidal
cross-sections of a torus, cuts={"eta1": 0.8} the field on one flux surface. See
prepare_slices_3d() for cuts; color limits are shared by every cut.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A scalar field with dims (eta1, eta2, eta3) (every other dimension selected; one
logical dimension may be selected away) and physical coordinates X, Y, Z. |
cuts | dict | None | {dim: position or list of positions} along eta1, eta2, eta3: a float is
the nearest logical coordinate, an integer a grid index (-1 the last). Default: the
middle of every dimension, or the whole plane of a 2-D field. |
cmap | str or matplotlib colormap | 'viridis' | The colormap. Default: "viridis". |
clim | (float, float) | None | Color limits, shared by every cut; default from the whole field’s values, see
symmetric and robust. |
show_domain | bool | True | Draw the domain’s outer surface translucently for context. Not drawn when the whole plane of a
2-D field is shown. Default: True. |
title | str | None | Text in the scene’s corner. Default: the field’s label; "" for none. |
symmetric | bool | False | Color limits symmetric about zero. Default: False. |
robust | bool | False | Color limits from percentiles instead of the extremes, so outliers don’t wash out the
colors. Default: False. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Raises
ValueError- If other dimensions remain, the physical coordinates are missing, or a cut is along another dimension.
Examples
>>> pyvista_slices(phi.isel(t=-1), cuts={"eta3": [0, 0.25]}).show()>>> pyvista_slices(phi.isel(t=-1), cuts={"eta1": 0.8}, symmetric=True).show()pyvista_streamlinesfunction#
def pyvista_streamlines(data: xr.DataArray, *, components: Literal['cartesian', 'contravariant'] = 'cartesian', n_points: int = 100, source_radius: float | None = None, source_center=None, max_length: float | None = None, tube_radius: float | None = None, cmap='viridis', show_domain: bool = True, title: str | None = None, plotter=None)Field lines of a selected vector field, e.g. magnetic field lines, colored by magnitude.
Lines are traced in both directions from n_points seeds: by default grid points drawn
at random (reproducibly) across the domain, or, given source_center and/or
source_radius, points in that sphere (radius default: a quarter of the domain size;
center default: the bounding-box center, which can lie outside a curved domain). For a 2-D
field the lines stay on the plane (the out-of-plane component is ignored). See
pyvista_glyphs() for components.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A vector field with dims (component, eta1, eta2, eta3) (three components, every
other dimension selected) and physical coordinates X, Y, Z. |
components | ('cartesian', 'contravariant') | "cartesian" | How to read the components: Cartesian x/y/z, or contravariant logical components,
pushed forward by push_forward(). Default: "cartesian". |
n_points | int | 100 | The number of seed points (at most the number of grid points when seeding on the grid). Default: 100. |
source_radius | float | None | Seed in a sphere of this radius instead of at grid points. Default (when
source_center is given): a quarter of the domain size. |
source_center | (float, float, float) | None | Seed in a sphere around this physical point instead of at grid points. Default (when
source_radius is given): the bounding-box center. |
max_length | float | None | Maximum length of each line. Default: four times the domain size. |
tube_radius | float | None | Draw the lines as tubes of this radius. Default: plain lines. |
cmap | str or matplotlib colormap | 'viridis' | The colormap of the magnitude. Default: "viridis". |
show_domain | bool | True | Draw the domain’s outer surface translucently for context. Default: True. |
title | str | None | Text in the scene’s corner. Default: "<label> field lines"; "" for none. |
plotter | pyvista.Plotter | None | Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter. |
Returns
pyvista.Plotter- The scene, not yet shown: call
.show()or.screenshot(path).
Raises
ValueError- If
componentsis unknown, or the field isn’t a three-component field over the logical dimensions with physical coordinates.
Examples
>>> pyvista_streamlines(B.isel(t=-1)).show()>>> pyvista_streamlines(... B.isel(t=-1),... source_center=(3.0, 0.0, 0.0),... source_radius=0.5,... tube_radius=0.01,... ).show()save_moviefunction#
def save_movie(data: xr.DataArray, path, *, kind: Literal['isosurface', 'slices', 'glyphs', 'streamlines'] = 'slices', sweep: str = 't', step: int = 1, framerate: int = 10, clim=None, window_size=(1024, 768), **options)Render one 3-D view per sweep step into a GIF (.gif) or video (.mp4, …).
kind picks the view and options are passed on to it. Scalar views share color
limits over the whole sweep (override with clim), and the camera is fixed after the
first frame. GIFs need imageio, videos imageio-ffmpeg.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A field with dimension sweep plus what the view kind needs (see
pyvista_slices(), pyvista_glyphs(), …). |
path | str or pathlib.Path | required | The output file: a GIF for .gif, a video (e.g. .mp4) for any other suffix. |
kind | ('isosurface', 'slices', 'glyphs', 'streamlines') | "isosurface" | The view of each frame. Default: "slices". |
sweep | str | 't' | The dimension to step through, one frame per step. Default: "t". |
step | int | 1 | Use every step-th position of sweep. Default: 1. |
framerate | int | 10 | Frames per second. Default: 10. |
clim | (float, float) | None | Color limits of the scalar views ("isosurface", "slices"). Default: from the
whole sweep, with the symmetric and robust options. |
window_size | (int, int) | (1024, 768) | Frame size in pixels. Default: (1024, 768). |
**options | {} | Passed on to the view, e.g. cuts or cmap. A title is prefixed to each
frame’s "<sweep> = <value>" label (default: the field’s label). |
Returns
str- The path of the written file.
Raises
ValueError- If
kindis unknown,stepisn’t a positive integer, ordatahas nosweepdimension.
Examples
>>> save_movie(phi, "phi.gif", cuts={"eta3": 0}, symmetric=True)>>> save_movie(B, "B.mp4", kind="glyphs", step=2)save_vtkfunction#
def save_vtk(data: xr.DataArray, path, *, name: str | None = None) -> list[str]Write a field to VTK structured grids (.vts) on its physical points, for ParaView.
With a t dimension, one file per time is written into the directory path, plus a
.pvd collection that ParaView opens as a time series; without one, path is a single
.vts file. Vector fields (component) become point vectors. Any array works, e.g. a
filter_time() result, so filtered modes can be inspected in
ParaView too.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A field over (eta1, eta2, eta3) (and optionally t and component) with
physical coordinates X, Y, Z; every other dimension selected. |
path | str or pathlib.Path | required | The .vts file (the suffix is set to .vts), or with a t dimension the
directory to write into (created if needed). |
name | str | None | The name of the point data, also the file stem in a time series. Default: the field’s label. |
Returns
list of str- The written paths: the
.vtsfile, or the.pvdcollection followed by one.vtsfile per time.
Examples
>>> save_vtk(phi, "vtk/phi")>>> save_vtk(phi.isel(t=-1), "phi_last.vts")structured_gridfunction#
def structured_grid(data: xr.DataArray, *, name: str | None = None)A pyvista.StructuredGrid of a selected (eta1, eta2, eta3) field on its physical points.
A scalar field becomes point data name (default: the field’s label); a vector field with
a component dimension of three Cartesian components becomes point vectors name, plus
their magnitude as "|name|". Periodic directions are closed, see plasma_plots.arrays.close_periodic().
The grid is useful directly for any PyVista filter.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
data | xarray.DataArray | required | A field with dims (eta1, eta2, eta3) (one of them may be selected away), or
(component, eta1, eta2, eta3) for a vector field, and physical coordinates X,
Y, Z. |
name | str | None | The name of the point data. Default: the field’s label. |
Returns
pyvista.StructuredGrid- The grid with the field as its active scalars or vectors.
Raises
ValueError- If other dimensions remain, the physical coordinates are missing, fewer than two logical dimensions are left, or a vector field doesn’t have three components.
Examples
>>> grid = structured_grid(phi.isel(t=-1))>>> grid.contour([0.0]).plot()