Skip to content

plasma_plots.arrays

Small xarray metadata helpers used by plasma_plots.

They intentionally live here rather than in Struphy so the plotting package can operate on labeled xarray data from any producer.

Attributes

NameDescription
DIM_LABELSNo description.
LOGICAL_DIMSNo description.
SCALARS_EXCLUDENo description.

Functions

NameDescription
angle_periodThe period of an angle coordinate, from its period attribute.
axis_labelThe axis label of a dimension, with its units.
close_periodicRepeat the first slice at the end of every direction that wraps around in physical space.
logical_derivativeThe derivative ∂ values / ∂ coordinate along axis.
logical_dimsThe names of the three logical dimensions of data, radial first.
map_coordinateReplace a coordinate by a function of it, e.g. a logical one by a physical length.
mapping_jacobianThe Jacobian J[a, i] = ∂X_a / ∂η_i of the mapping, shape (3, 3, n1, n2, n3).
periodicityHow a direction of a (..., 3) array of physical points wraps around, if it does.
save_scalarsSave scalar time series to a CSV or NPZ file; under MPI only rank 0 writes it.
scalar_namesThe names of the scalar time series to use from a collection.
validate_arrayCheck that data is an xarray.DataArray with the required dimensions.
value_labelThe label of an array's values, with their units.

DIM_LABELSattributemodule attribute#

DIM_LABELS = {
  't': '$t$',
  'eta1': '$\\eta_1$',
  'eta2': '$\\eta_2$',
  'eta3': '$\\eta_3$',
  'v1': '$v_1$',
  'v2': '$v_2$',
  'v3': '$v_3$',
  'x': '$x$',
  'y': '$y$',
  'z': '$z$',
  'R': '$R$',
  'Z': '$Z$',
  'component': 'component',
  'marker': 'marker',
  'quantity': 'quantity',
  'rho': '$\\rho$',
  'theta': '$\\theta$',
  'zeta': '$\\zeta$',
  'theta_B': '$\\theta_B$',
  'zeta_B': '$\\zeta_B$',
  'theta_P': '$\\theta_P$'
}

LOGICAL_DIMSattributemodule attribute#

LOGICAL_DIMS = (
  ('eta1', 'eta2', 'eta3'),
  ('rho', 'theta', 'zeta'),
  ('rho', 'theta_B', 'zeta_B'),
  ('rho', 'theta_P', 'zeta')
)

SCALARS_EXCLUDEattributemodule attribute#

SCALARS_EXCLUDE = ('time')

angle_periodfunction#

def angle_period(data: xr.DataArray | xr.Dataset, dim: str) -> float | None

The period of an angle coordinate, from its period attribute.

GVEC’s angles carry one (2π for the poloidal angles, 2π/nfp for the toroidal ones, see plasma_plots.gvec.from_gvec()); Struphy’s logical coordinates have none.

Parameters

NameTypeDescription
dataxarray.DataArray or xarray.DatasetThe field.
dimstrOne of its coordinates.

Returns

float or None
The period, or None if dim has no period attribute (or is no coordinate).

axis_labelfunction#

def axis_label(data: xr.DataArray, dim: str) -> str

The axis label of a dimension, with its units.

The label is the coordinate’s label attribute, else its long_name, else a default for the known dimensions (matplotlib mathtext such as $\eta_1$ for eta1), else the dimension name.

Parameters

NameTypeDescription
dataxarray.DataArrayThe array.
dimstrOne of its dimensions.

Returns

str
The label, followed by [units] when the coordinate has a units attribute.

Raises

KeyError
If dim is not a dimension of data.

close_periodicfunction#

def close_periodic(data: xr.DataArray, dims=None) -> xr.DataArray

Repeat the first slice at the end of every direction that wraps around in physical space.

Struphy evaluates fields at cell centers, which leave out the seam of a periodic direction (e.g. the poloidal angle), so a surface or pcolormesh drawn through the points has a gap there. A direction counts as periodic when its last slice lies about one grid step from its first one ("open" in periodicity()); a radius, or the ends of a torus sector, do not. The repeated slice gets the logical coordinate one step past the last.

Parameters

NameTypeDefaultDescription
dataxarray.DataArrayrequiredThe field, with the physical X, Y, Z coordinates; without them data comes back unchanged.
dimssequence of strNoneThe directions to close, if periodic. Default: those of the logical dimensions (see logical_dims()) that data has.

Returns

xarray.DataArray
data, one point longer along each periodic direction in dims.

logical_derivativefunction#

def logical_derivative(values: np.ndarray, coordinate: np.ndarray, axis: int, kind: str | None) -> np.ndarray

The derivative ∂ values / ∂ coordinate along axis.

Around a periodic direction on a uniform grid (kind "open" or "closed", see periodicity()) the derivative is spectral (an FFT), which is exact for the smooth, periodic angles of Struphy’s mappings; the Nyquist mode of an even number of points is dropped. Elsewhere numpy’s second-order differences (one-sided at the ends; first order with only two points).

Parameters

NameTypeDefaultDescription
valuesnumpy.ndarrayrequiredThe values to differentiate.
coordinatenumpy.ndarrayrequiredThe coordinate along axis, one value per point.
axisintrequiredThe axis of values to differentiate along.
kind('open', 'closed', None)"open"How the direction wraps around, from periodicity(): "open" (the seam is left out), "closed" (the last point repeats the first) or None (not periodic).

Returns

numpy.ndarray
The derivative, of the same shape as values.

logical_dimsfunction#

def logical_dims(data: xr.DataArray | xr.Dataset) -> tuple[str, str, str]

The names of the three logical dimensions of data, radial first.

One of LOGICAL_DIMS: the one with the most of its names among the dimensions of data (then among its coordinates, for a slice that selected some away), Struphy’s ("eta1", "eta2", "eta3") when none match.

Parameters

NameTypeDescription
dataxarray.DataArray or xarray.DatasetThe field.

Returns

tuple of str
Three dimension names, e.g. ("eta1", "eta2", "eta3") or ("rho", "theta_B", "zeta_B"); data need not have all three.

Examples

>>> logical_dims(phi) # Struphy
('eta1', 'eta2', 'eta3')
>>> # GVEC, Boozer
>>> logical_dims(ev.mod_B.plasma.data.slice(x="zeta_B", y="theta_B", rho=0.5))
('rho', 'theta_B', 'zeta_B')

map_coordinatefunction#

def map_coordinate(data: xr.DataArray, dim: str, mapping, *, name: str | None = None, units: str | None = None, label: str | None = None) -> xr.DataArray

Replace a coordinate by a function of it, e.g. a logical one by a physical length.

Every plot then draws and labels the new coordinate, e.g. the minor radius r = a1 + (a2 - a1) * eta1 in meters instead of eta1. With name, the dimension is renamed too, so r is what selections (r=0.5) and plots (x="r") name; without it, dim keeps its name and only its values, label and units change.

Parameters

NameTypeDefaultDescription
dataxarray.DataArrayrequiredThe array.
dimstrrequiredThe dimension whose coordinate is mapped, e.g. "eta1".
mappingcallable or floatrequiredA function of the coordinate’s values (lambda eta1: 0.1 + 0.9 * eta1), or a factor (a length, for length * eta1).
namestrNoneThe name of the new dimension, e.g. "r". Default: keep dim.
unitsstrNoneThe new coordinate’s units, e.g. "m". Default: none.
labelstrNoneThe new coordinate’s axis label (its long_name), mathtext allowed. Default: name.

Returns

xarray.DataArray
The array over the new coordinate; the data itself is unchanged.

Raises

KeyError
If dim is not a dimension of data.

Examples

>>> map_coordinate(
... T, "eta1", lambda eta1: 0.1 + 0.9 * eta1, name="r", units="m"
... ).plasma.plot.lineout(x="r", t=-1)
>>> # a length of 2π along eta1
>>> map_coordinate(n, "eta1", 2 * np.pi, units="m")

mapping_jacobianfunction#

def mapping_jacobian(data: xr.DataArray) -> np.ndarray

The Jacobian J[a, i] = ∂X_a / ∂η_i of the mapping, shape (3, 3, n1, n2, n3).

Differentiated numerically from the X, Y, Z coordinates of data on its logical grid ((eta1, eta2, eta3), or GVEC’s, see logical_dims()): spectrally around periodic directions (see periodicity()), second order elsewhere (see logical_derivative()).

Parameters

NameTypeDescription
dataxarray.DataArrayAn array with the three logical dimensions, each with at least two points, and the X, Y, Z coordinates over them.

Returns

numpy.ndarray
J of shape (3, 3, n1, n2, n3): the Cartesian component a first, the logical direction i second.

Raises

ValueError
If a logical dimension is missing or has fewer than two points (pass a struphy domain to the calling function instead).

periodicityfunction#

def periodicity(points: np.ndarray, axis: int) -> str | None

How a direction of a (..., 3) array of physical points wraps around, if it does.

The direction is "closed" when its last slice coincides with its first (to 1e-9 of the median step), and "open" when the seam between them is at most 1.5 times the last grid step everywhere.

Parameters

NameTypeDescription
pointsnumpy.ndarrayPhysical points, with the Cartesian coordinates (X, Y, Z) along the last axis.
axisintThe axis of points to examine; it needs at least three points to be periodic.

Returns

{'closed', 'open', None}
"closed" (the last slice repeats the first), "open" (it stops one step short of the seam, as on Struphy’s cell centers), or None (not periodic, e.g. a radius or the ends of a torus sector).

save_scalarsfunction#

def save_scalars(scalars: xr.Dataset | Mapping, path: str, *, names=None, exclude=SCALARS_EXCLUDE, fmt=None) -> str

Save scalar time series to a CSV or NPZ file; under MPI only rank 0 writes it.

Parameters

NameTypeDefaultDescription
scalarsxarray.Dataset or mappingrequiredThe scalars, e.g. out.scalars. Each selected one must have t as its only dimension, with identical time coordinates.
pathstrrequiredThe file to write.
namessequence of strNoneThe scalars to save. Default: every one not in exclude; see scalar_names().
excludesequence of strSCALARS_EXCLUDENames left out when names is not given. Default: ("time",).
fmt('csv', 'npz')"csv"The file format. Default: from the extension of path, else "csv". A CSV has a header line t,<names> and one row per time; an NPZ has the arrays t and one per name.

Returns

str
path, on every rank.

Raises

ValueError
If a scalar has dimensions other than t, the time coordinates differ, or the format is unknown.
KeyError
If one of names is not in scalars.

Examples

>>> save_scalars(out.scalars, "scalars.csv", names=["en_E", "en_tot"])

scalar_namesfunction#

def scalar_names(scalars: xr.Dataset | Mapping, *, names=None, exclude=SCALARS_EXCLUDE) -> list[str]

The names of the scalar time series to use from a collection.

Parameters

NameTypeDefaultDescription
scalarsxarray.Dataset or mappingrequiredThe scalars, e.g. out.scalars.
namessequence of strNoneExplicit names, which must all be present; exclude then does not apply. Default: every variable not in exclude.
excludesequence of strSCALARS_EXCLUDENames left out when names is not given. Default: ("time",).

Returns

list of str
The selected names, in order.

Raises

KeyError
If one of names is not in scalars.

validate_arrayfunction#

def validate_array(data: xr.DataArray, *, required_dims: Sequence[str] = ()) -> xr.DataArray

Check that data is an xarray.DataArray with the required dimensions.

Parameters

NameTypeDefaultDescription
dataxarray.DataArrayrequiredThe array to check.
required_dimssequence of str()Dimensions data must have. Default: none.

Returns

xarray.DataArray
data itself, unchanged.

Raises

TypeError
If data is not an xarray.DataArray.
ValueError
If one of required_dims is missing.

value_labelfunction#

def value_label(data: xr.DataArray) -> str

The label of an array’s values, with their units.

Parameters

NameTypeDescription
dataxarray.DataArrayThe array. The label is its label attribute, else long_name, else its name.

Returns

str
"label [units]", with the units attribute or a.u.; only "[units]" when there is no label.