Skip to content

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

NameDescription
ORBIT_CLASS_COLORSNo description.
RENDERERSNo description.

Functions

NameDescription
boundary_facesReturn the real boundary faces of a point array, see boundary_keys().
boundary_keysFind the logical faces of a (n1, n2, n3, 3) point array that are real boundaries.
is_flatWhether a structured grid has a single point in one logical direction (a 2-D run or cut).
orbit_polylinesMarker orbits as one pyvista.PolyData line per marker, with point data color_by.
prepare_slices_3dThe logical cuts of a scalar (eta1, eta2, eta3) field that pyvista_slices() draws.
push_forwardCartesian components of a vector field given by contravariant logical components.
pyvista_domainThe mapping of a Struphy domain as a wireframe of logical grid lines.
pyvista_glyphsArrows of a selected (component, eta1, eta2, eta3) vector field, colored by magnitude.
pyvista_isosurfaceContour surfaces of a selected scalar (eta1, eta2, eta3) field in physical space.
pyvista_orbitsMarker orbits as 3-D lines (or tubes), colored by time, orbit class, or any variable.
pyvista_slicesSurfaces of constant logical coordinate through a scalar field, drawn in physical space.
pyvista_streamlinesField lines of a selected vector field, e.g. magnetic field lines, colored by magnitude.
save_movieRender one 3-D view per sweep step into a GIF (.gif) or video (.mp4, ...).
save_vtkWrite a field to VTK structured grids (.vts) on its physical points, for ParaView.
structured_gridA 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

NameTypeDescription
pointsnumpy.ndarrayPhysical 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

NameTypeDescription
pointsnumpy.ndarrayPhysical points, shape (n1, n2, n3, 3).

Returns

list of (int, int)
(axis, index) of each boundary face; index is 0 or -1.

is_flatfunction#

def is_flat(grid) -> bool

Whether a structured grid has a single point in one logical direction (a 2-D run or cut).

Parameters

NameTypeDescription
gridpyvista.StructuredGridThe grid, e.g. from structured_grid().

Returns

bool
True if 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

NameTypeDefaultDescription
orbitsxarray.DatasetrequiredMarker orbits with dims (t, marker) and physical positions x, y, z.
color_bystr't'The point data to attach: "t", "classification" or a variable name. Default: "t".
max_markersint200At 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_by is neither "t", "classification" nor a variable of orbits.

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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA scalar field with dims (eta1, eta2, eta3) and physical coordinates X, Y, Z.
cutsdictNone{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.DataArray

Cartesian 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

NameTypeDescription
dataxarray.DataArrayThe vector field, dims (component, eta1, eta2, eta3) with three contravariant components and physical coordinates X, Y, Z.

Returns

xarray.DataArray
The Cartesian x, y, z components, 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

NameTypeDefaultDescription
domaincallablerequiredA Struphy domain (mapping), e.g. out.domain, called as domain(eta1, eta2, eta3, squeeze_out=False) to give x, y, z.
n1int8Grid lines along eta1. Default: 8.
n2int32Grid lines along eta2. Default: 32.
n3int32Grid lines along eta3. Default: 32.
resolutionint4Samples per line spacing, so curved lines stay smooth. Default: 4.
colorstr'black'Color of the grid lines. Default: "black".
surfaceboolTrueDraw the translucent boundary surface. Default: True.
cross_sectionboolTrueAlso draw grid lines over the eta3 = 0 face. Default: True.
titlestrNoneText in the scene’s corner. Default: "Domain"; "" for none.
plotterpyvista.PlotterNoneDraw 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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA 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".
strideint2Draw an arrow at every stride-th point in every direction. Default: 2.
scalefloatNoneArrow length of the largest vector, in physical units. Default: a tenth of the domain size.
cmapstr or matplotlib colormap'viridis'The colormap of the magnitude. Default: "viridis".
show_domainboolTrueDraw the domain’s outer surface translucently for context. Default: True.
titlestrNoneText in the scene’s corner. Default: the field’s label; "" for none.
plotterpyvista.PlotterNoneDraw 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 stride is less than 1, components is 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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA scalar field with dims (eta1, eta2, eta3) (every other dimension selected; one logical dimension may be selected away) and physical coordinates X, Y, Z.
valuesint or list of float5The number of evenly spaced levels strictly between the color limits, or explicit levels. Default: 5.
cmapstr or matplotlib colormap'viridis'The colormap. Default: "viridis".
opacityfloat1.0Opacity of the surfaces (of the plane for a 2-D field). Default: 1.
clim(float, float)NoneColor limits; default from the field’s values, see symmetric and robust.
show_domainboolTrueDraw the domain’s outer surface translucently for context (3-D fields only). Default: True.
titlestrNoneText in the scene’s corner. Default: the field’s label; "" for none.
symmetricboolFalseColor limits symmetric about zero. Default: False.
robustboolFalseColor limits from percentiles instead of the extremes, so outliers don’t wash out the colors. Default: False.
plotterpyvista.PlotterNoneDraw 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

NameTypeDefaultDescription
orbitsxarray.DatasetrequiredMarker orbits with dims (t, marker) and physical positions x, y, z.
color_bystr't'"t", "classification" (passing, trapped, lost, with a legend) or the name of any (t, marker) variable, e.g. "v_par". Default: "t".
max_markersint200At most this many markers are drawn. Default: 200.
tube_radiusfloatNoneDraw the orbits as tubes of this radius. Default: plain lines.
cmapstr or matplotlib colormapNoneThe colormap (not used for "classification"). Default: "viridis".
domainxarray.DataArrayNoneA field with physical coordinates whose outer surface is drawn translucently; other than eta1, eta2, eta3, its dimensions are taken at their first position.
titlestrNoneText in the scene’s corner. Default: "Marker orbits"; "" for none.
plotterpyvista.PlotterNoneDraw 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_by is neither "t", "classification" nor a variable of orbits.

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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA scalar field with dims (eta1, eta2, eta3) (every other dimension selected; one logical dimension may be selected away) and physical coordinates X, Y, Z.
cutsdictNone{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.
cmapstr or matplotlib colormap'viridis'The colormap. Default: "viridis".
clim(float, float)NoneColor limits, shared by every cut; default from the whole field’s values, see symmetric and robust.
show_domainboolTrueDraw the domain’s outer surface translucently for context. Not drawn when the whole plane of a 2-D field is shown. Default: True.
titlestrNoneText in the scene’s corner. Default: the field’s label; "" for none.
symmetricboolFalseColor limits symmetric about zero. Default: False.
robustboolFalseColor limits from percentiles instead of the extremes, so outliers don’t wash out the colors. Default: False.
plotterpyvista.PlotterNoneDraw 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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA 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_pointsint100The number of seed points (at most the number of grid points when seeding on the grid). Default: 100.
source_radiusfloatNoneSeed 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)NoneSeed in a sphere around this physical point instead of at grid points. Default (when source_radius is given): the bounding-box center.
max_lengthfloatNoneMaximum length of each line. Default: four times the domain size.
tube_radiusfloatNoneDraw the lines as tubes of this radius. Default: plain lines.
cmapstr or matplotlib colormap'viridis'The colormap of the magnitude. Default: "viridis".
show_domainboolTrueDraw the domain’s outer surface translucently for context. Default: True.
titlestrNoneText in the scene’s corner. Default: "<label> field lines"; "" for none.
plotterpyvista.PlotterNoneDraw 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 components is 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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA field with dimension sweep plus what the view kind needs (see pyvista_slices(), pyvista_glyphs(), …).
pathstr or pathlib.PathrequiredThe 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".
sweepstr't'The dimension to step through, one frame per step. Default: "t".
stepint1Use every step-th position of sweep. Default: 1.
framerateint10Frames per second. Default: 10.
clim(float, float)NoneColor 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 kind is unknown, step isn’t a positive integer, or data has no sweep dimension.

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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA field over (eta1, eta2, eta3) (and optionally t and component) with physical coordinates X, Y, Z; every other dimension selected.
pathstr or pathlib.PathrequiredThe .vts file (the suffix is set to .vts), or with a t dimension the directory to write into (created if needed).
namestrNoneThe 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 .vts file, or the .pvd collection followed by one .vts file 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

NameTypeDefaultDescription
dataxarray.DataArrayrequiredA 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.
namestrNoneThe 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()