"""XY/YZ/XZ potential-slice figures."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Iterable, Mapping
import numpy as np
from beach.analysis.context import RunContext
from beach.analysis.potential import _resolve_reference_point, compute_potential_slices
from beach.io.types import RunResult
from ._defaults import DEFAULT_POTENTIAL_CMAP
from ._mesh3d import _resolve_axis_unit
if TYPE_CHECKING:
from matplotlib.figure import Figure
from numpy.typing import NDArray
from beach.analysis.field_kernel import FieldKernel
[ドキュメント]
def plot_potential_slices(
result: RunResult | object,
*,
box_min: Iterable[float],
box_max: Iterable[float],
grid_n: int = 200,
xy_z: float | None = None,
yz_x: float | None = None,
xz_y: float | None = None,
chunk_size: int = 2048,
cmap: str = DEFAULT_POTENTIAL_CMAP,
vmin: float | None = None,
vmax: float | None = None,
periodic2: Mapping[str, object] | None = None,
reference_point: Iterable[float] | str | None = None,
axis_unit: str = "m",
config_path: str | Path | None = None,
library_path: str | Path | None = None,
kernel: FieldKernel | None = None,
) -> tuple[Figure, NDArray[np.object_]]:
"""Plot XY/YZ/XZ potential slices in one figure (heavy: native kernel).
Parameters
----------
result : RunResult or Beach-like object
Run result or object exposing ``result`` as ``RunResult``.
box_min : iterable of float
Lower simulation-box corner ``[x, y, z]`` in meters.
box_max : iterable of float
Upper simulation-box corner ``[x, y, z]`` in meters.
grid_n : int, default 200
Grid size per slice axis.
xy_z : float or None, default None
Z coordinate of XY slice. ``None`` uses box center.
yz_x : float or None, default None
X coordinate of YZ slice. ``None`` uses box center.
xz_y : float or None, default None
Y coordinate of XZ slice. ``None`` uses box center.
chunk_size : int, default 2048
Sampling chunk size for potential computation.
cmap : str, default "viridis"
Matplotlib colormap name.
vmin : float or None, default None
Lower color limit. ``None`` selects automatically.
vmax : float or None, default None
Upper color limit. ``None`` selects automatically.
periodic2 : mapping or None, default None
Two-axis periodic setting. See
:func:`beach.compute_potential_points`.
reference_point : iterable of float, {"species1_injection_center"}, or None, default None
基準電位を差し引く参照点。``None`` は絶対電位、``"species1_injection_center"``
は species1 の注入面中心を基準にした電位差を描く。
axis_unit : {"m", "um", "nm"}, default "m"
Coordinate unit used for slice axes.
config_path, library_path : str, pathlib.Path, or None, default None
Explicit ``beach.toml`` and native kernel library.
kernel : FieldKernel or None, default None
Open kernel built from this run, used as given and not closed; see
:func:`beach.compute_potential_points`. ``None`` builds one for this call.
Returns
-------
tuple
``(figure, axes_array)`` from matplotlib.
Raises
------
ValueError
If potential sampling or color-limit arguments are invalid.
"""
import matplotlib.pyplot as plt
context = RunContext.from_value(result, config_path=config_path)
resolved = context.result
axis_label, axis_scale = _resolve_axis_unit(axis_unit)
resolved_reference = _resolve_reference_point(
resolved,
reference_point,
context=context,
)
slices = compute_potential_slices(
context,
box_min=box_min,
box_max=box_max,
grid_n=grid_n,
xy_z=xy_z,
yz_x=yz_x,
xz_y=xz_y,
chunk_size=chunk_size,
periodic2=periodic2,
reference_point=resolved_reference,
config_path=config_path,
library_path=library_path,
kernel=kernel,
)
ordered_planes = ("xy", "yz", "xz")
data_min = min(float(np.min(slices[name].potential_V)) for name in ordered_planes)
data_max = max(float(np.max(slices[name].potential_V)) for name in ordered_planes)
global_min, global_max = _resolve_slice_color_limits(
data_min=data_min,
data_max=data_max,
vmin=vmin,
vmax=vmax,
)
fig, axes = plt.subplots(1, 3, figsize=(15, 4.8), constrained_layout=True)
mappable = None
for ax, plane in zip(axes, ordered_planes):
slc = slices[plane]
mappable = ax.pcolormesh(
slc.u_values_m * axis_scale,
slc.v_values_m * axis_scale,
slc.potential_V,
shading="auto",
cmap=cmap,
vmin=global_min,
vmax=global_max,
)
ax.set_xlabel(f"{slc.axis_u} [{axis_label}]")
ax.set_ylabel(f"{slc.axis_v} [{axis_label}]")
ax.set_title(
f"{slc.plane.upper()} "
f"({slc.fixed_axis}={slc.fixed_value_m * axis_scale:.3g} {axis_label})"
)
ax.set_aspect("equal", adjustable="box")
if mappable is not None:
label = "potential [V]" if resolved_reference is None else "potential difference [V]"
fig.colorbar(mappable, ax=axes, shrink=0.9, label=label)
return fig, axes
def _resolve_slice_color_limits(
*,
data_min: float,
data_max: float,
vmin: float | None,
vmax: float | None,
) -> tuple[float, float]:
if vmin is None and vmax is None and np.isclose(data_min, data_max):
center = 0.5 * (data_min + data_max)
width = max(abs(center) * 0.05, 1.0)
return center - width, center + width
lower = data_min if vmin is None else float(vmin)
upper = data_max if vmax is None else float(vmax)
if not np.isfinite(lower) or not np.isfinite(upper):
raise ValueError("vmin/vmax must be finite values.")
if lower >= upper:
raise ValueError("vmin must be smaller than vmax.")
return lower, upper