Skip to content

Python Post-processing API Reference

The BEACH Python package (beach) is the post-processing layer for loading, analyzing, and visualizing Fortran simulation results. It reads files emitted by the Fortran runtime, such as summary.txt, charges.csv, and mesh_triangles.csv, and performs potential reconstruction, Coulomb force calculations, electric-field calculations, field-line tracing, and 3D visualization on the Python side.

If you only need the first plots, start with Post-processing Tutorial.

ModuleRole
beach.fortran_results.ioReads output directories (load_fortran_result, list_fortran_runs)
beach.fortran_results.facadeHigh-level facade class Beach
beach.fortran_results.potentialPotential reconstruction (compute_potential_mesh, compute_potential_points, compute_potential_slices)
beach.fortran_results.coulombCoulomb force/torque calculation (calc_coulomb)
beach.fortran_results.kernelShared-library calls to the Fortran FMM field kernel (FieldKernel, calc_object_forces_kernel)
beach.fortran_results.sceneTemporary object translation/rotation and field-kernel evaluation of edited scenes
beach.fortran_results.field_linesElectric-field calculation, field-line tracing, and 3D plotting (compute_electric_field_points, trace_field_lines, plot_field_lines_3d)
beach.fortran_results.mobilityCoulomb mobility analysis (analyze_coulomb_mobility)
beach.fortran_results.plottingPlotting utilities (plot_charge_mesh, plot_charges, plot_potential_mesh, etc.)
beach.fortran_results.animationHistory animation (animate_history_mesh)
beach.fortran_results.historyBatch-step access to charge_history.csv (FortranChargeHistory)
beach.fortran_results.typesPublic data types (FortranRunResult, CoulombInteraction, etc.)
beach.fortran_results.constantsPhysical constants (K_COULOMB)

All public symbols can be imported from the beach top level and from beach.fortran_results.

from beach import Beach, calc_coulomb, compute_electric_field_points, trace_field_lines

This high-level interface binds one output directory and provides the main analysis and visualization methods.

b = Beach("outputs/latest")

You can explicitly specify the configuration file as in Beach("outputs/latest", config_path="path/to/beach.toml"). When config_path=None, BEACH searches automatically in order through output_dir/beach.toml, the parent directory, and the grandparent directory. Config-aware object/kernel analyses resolve object kind/order, sim.softening, periodic2, and tree parameters from this beach.toml.

ParameterTypeDefaultDescription
output_dirstr | Path"outputs/latest"Fortran output directory
NameReturn typeDescription
resultFortranRunResultLoaded result, loaded lazily
mesh_idstuple[int, ...]Available mesh IDs
MethodDelegates toSummary
reload()load_fortran_resultReload from disk
get_mesh(*mesh_ids, step)InternalGet a MeshSelection by mesh ID
get_mesh_charge(*mesh_ids, step)InternalGet an element charge array by mesh ID
calc_coulomb(target, source, ...)calc_coulombCoulomb force/torque calculation
calc_object_forces_kernel(...)calc_object_forces_kernelPer-object resultant-force calculation using the Fortran field kernel
scene(step, ...)BeachScene.from_resultWhat-if scene with temporary object translation/rotation
analyze_coulomb_mobility(...)analyze_coulomb_mobilityPer-object mobility analysis
compute_potential(...)compute_potential_meshPotential reconstruction at centroids
compute_potential_points(points, ...)compute_potential_pointsPotential at arbitrary points
compute_potential_slices(...)compute_potential_slicesPotential on XY/YZ/XZ sections
compute_electric_field(points, ...)compute_electric_field_pointsElectric-field vectors at arbitrary points
trace_field_lines(seed_points, ...)trace_field_linesRK4 tracing of electric field lines
plot_mesh(...)plot_charge_mesh3D mesh plot of charge density
plot_potential(...)plot_potential_mesh3D mesh plot of potential
plot_potential_slices(...)plot_potential_slicesPotential-section plots
plot_field_lines(seed_points, ...)plot_field_lines_3d3D plot of electric field lines
plot_bar()plot_chargesBar chart of element charge
plot_mesh_source_boxplot(...)plot_mesh_source_boxplotBox plot by mesh source
plot_coulomb_force_matrix(...)plot_coulomb_force_matrixCoulomb force matrix plot
animate_mesh(...)animate_history_meshCharge/potential history animation

Loads a Fortran output directory and returns a FortranRunResult.

from beach import load_fortran_result
result = load_fortran_result("outputs/latest")
print(f"要素数: {result.mesh_nelem}, バッチ数: {result.batches}")
print(f"吸収: {result.absorbed}, 脱出: {result.escaped}")

Required files: summary.txt, charges.csv Optional files: mesh_triangles.csv, mesh_sources.csv, charge_history.csv, mesh_potential.csv

FieldTypeDescription
directoryPathOutput directory path
mesh_nelemintNumber of mesh elements
processed_particlesintNumber of processed particles
absorbedintNumber of absorbed particles
escapedintNumber of escaped particles
batchesintNumber of processed batches
escaped_boundaryintNumber of particles that escaped through boundaries
survived_max_stepintNumber of particles that reached max_step
last_rel_changefloatFinal relative charge change
chargesndarray (mesh_nelem,)Element charge array [C]
trianglesndarray (mesh_nelem, 3, 3) | NoneTriangle vertex coordinates [m]
mesh_idsndarray (mesh_nelem,) | NoneElement mesh IDs
mesh_sourcesdict[int, MeshSource] | NoneMesh kind, surface model, and epsilon_r metadata
mesh_potential_vndarray (mesh_nelem,) | NoneCentroid potentials output by Fortran [V]
historyFortranChargeHistory | NoneCharge history accessor

4.1 compute_potential_mesh(result, *, softening, self_term, periodic2, reference_point)

Section titled “4.1 compute_potential_mesh(result, *, softening, self_term, periodic2, reference_point)”

Reconstructs the potential at triangle centroids. If Fortran has already output mesh_potential.csv and the conditions match, that output is preferred.

ParameterTypeDefaultUnitDescription
resultFortranRunResult | object(required)-Result object
softeningfloat | NoneNonemWhen None, automatically references sim.softening
self_termstr"auto"-Self interaction: "auto" / "area_equivalent" / "exclude" / "softened_point"
periodic2Mapping | NoneNone-Two-axis periodic settings, described below. None enables automatic detection
reference_pointIterable[float] | str | NoneNonemReference potential point. "species1_injection_center" means the species 1 injection-face center

Return value: ndarray (mesh_nelem,) [V]

4.2 compute_potential_points(result, points, *, softening, chunk_size, periodic2, reference_point)

Section titled “4.2 compute_potential_points(result, points, *, softening, chunk_size, periodic2, reference_point)”

Computes the potential at arbitrary 3D points.

ParameterTypeDefaultUnitDescription
pointsndarray (n_points, 3)(required)mSampling point coordinates
softeningfloat | NoneNonemAutomatic when None
chunk_sizeint2048-Chunk size
periodic2Mapping | NoneNone-Automatic detection when None
reference_pointIterable[float] | str | NoneNonemReference potential point

Return value: ndarray (n_points,) [V]

4.3 compute_potential_slices(result, *, box_min, box_max, grid_n, xy_z, yz_x, xz_y, ...)

Section titled “4.3 compute_potential_slices(result, *, box_min, box_max, grid_n, xy_z, yz_x, xz_y, ...)”

Computes potential sections on the XY/YZ/XZ planes.

Return value: dict[str, PotentialSlice2D] (keys: "xy", "yz", "xz")

5.1 calc_coulomb(result, target, source, *, step, softening, torque_origin, periodic2)

Section titled “5.1 calc_coulomb(result, target, source, *, step, softening, torque_origin, periodic2)”

Computes the Coulomb force and torque that the target mesh group receives from the source mesh group.

ParameterTypeDefaultUnitDescription
resultFortranRunResult | object(required)-Result object
targetint | MeshSelection | Iterable(required)-Target mesh group (group A)
sourceint | MeshSelection | Iterable(required)-Source mesh group (group B)
stepint | None-1-History step. -1 means latest; None means final charge
softeningfloat0.0mSoftening length
torque_originstr"target_center"-Torque reference point: "target_center" / "source_center" / "origin"
periodic2Mapping | NoneNone-Two-axis periodic boundary settings. None enables automatic detection

Return value: CoulombInteraction

Periodic Coulomb Sum Using the periodic2 Parameter

Section titled “Periodic Coulomb Sum Using the periodic2 Parameter”

When periodic2 is specified, source-charge image shells ix in [-nimg, nimg], iy in [-nimg, nimg] are generated in the two periodic directions, and the Coulomb force/torque is computed as a nearest-cell sum.

When periodic2=None (default), BEACH searches for beach.toml near the output directory and automatically applies periodic settings if sim.field_bc_mode="periodic2" is set. This uses the same automatic-detection logic as functions such as compute_potential_mesh.

FieldTypeUnitDescription
group_a_mesh_idstuple[int, ...]-Target mesh IDs
group_b_mesh_idstuple[int, ...]-Source mesh IDs
stepint | None-History step used
softeningfloatmSoftening length used
torque_origin_mndarray (3,)mTorque reference point
force_on_a_Nndarray (3,)NNet force acting on group A
force_on_b_Nndarray (3,)NNet force acting on group B
torque_on_a_Nmndarray (3,)N mNet torque acting on group A
torque_on_b_Nmndarray (3,)N mNet torque acting on group B
mean_force_on_a_per_element_Nndarray (3,)NMean force per target element
mean_torque_on_a_per_element_Nmndarray (3,)N mMean torque per target element
from beach import Beach
b = Beach("outputs/latest")
# mesh_id=0 が mesh_id=1 から受ける Coulomb 力
interaction = b.calc_coulomb(target=0, source=1)
print(f"Force on target: {interaction.force_on_a_N} [N]")
print(f"Torque on target: {interaction.torque_on_a_Nm} [N m]")
# periodic2 を明示指定する場合
interaction_p = b.calc_coulomb(
target=0, source=1,
periodic2={"axes": [0, 1], "lengths": [0.01, 0.01], "image_layers": 2},
)

6.1 compute_electric_field_points(result, points, *, softening, chunk_size, periodic2)

Section titled “6.1 compute_electric_field_points(result, points, *, softening, chunk_size, periodic2)”

Computes electric-field vectors at arbitrary 3D points directly from surface charges using Coulomb’s law.

Formula: E(r) = K * sum_j q_j * (r - r_j) / |r - r_j|^3

Here, r_j is the centroid of triangle element j, and q_j is the element charge.

ParameterTypeDefaultUnitDescription
resultFortranRunResult | object(required)-Result object
pointsndarray (n_points, 3)(required)mSampling point coordinates
softeningfloat | NoneNonemSoftening length. When None, automatically references sim.softening
chunk_sizeint2048-Chunk size
periodic2Mapping | NoneNone-Two-axis periodic settings. None enables automatic detection

Return value: ndarray (n_points, 3) [V/m]

In periodic2 mode, sampling points are wrapped into the periodic cell, and source-charge image shells are superposed with ix in [-nimg, nimg], iy in [-nimg, nimg].

import numpy as np
from beach import Beach
b = Beach("outputs/latest")
# グリッド点での電場計算
x = np.linspace(0.0, 0.01, 50)
y = np.linspace(0.0, 0.01, 50)
xx, yy = np.meshgrid(x, y)
zz = np.full_like(xx, 0.005)
points = np.column_stack([xx.ravel(), yy.ravel(), zz.ravel()])
efield = b.compute_electric_field(points)
print(f"Shape: {efield.shape}") # (2500, 3)
print(f"E-field [V/m]: {efield[0]}")

7.1 trace_field_lines(result, seed_points, *, ds, max_steps, softening, periodic2, direction, box_min, box_max)

Section titled “7.1 trace_field_lines(result, seed_points, *, ds, max_steps, softening, periodic2, direction, box_min, box_max)”

Traces electric field lines from seed points in the electric-field direction, or in the opposite direction, using RK4 integration.

ParameterTypeDefaultUnitDescription
resultFortranRunResult | object(required)-Result object
seed_pointsndarray (n_seeds, 3)(required)mStarting point coordinates for field lines
dsfloat | NoneNonemIntegration step size. When None, automatically set from average mesh edge length x 0.5
max_stepsint500-Maximum number of integration steps in each direction
softeningfloat | NoneNonemSoftening length. Automatic when None
periodic2Mapping | NoneNone-Two-axis periodic settings. None enables automatic detection
directionstr"both"-Trace direction: "forward" (electric-field direction) / "backward" (opposite direction) / "both" (both directions)
box_minIterable[float] | NoneNonemLower boundary-box limit. Stop when a field line exits this box
box_maxIterable[float] | NoneNonemUpper boundary-box limit

Return value: list[ndarray] — each element is a field-line coordinate array with shape (n_points_i, 3) [m]

  • Advances by ds along the unit vector of the electric field using fourth-order Runge-Kutta (RK4)
  • Stops if the electric-field norm becomes smaller than 1e-30 at any RK4 stage
  • When direction="both", joins the forward and backward traces, removing the duplicated seed point
  • Stops once a trace exceeds box_min / box_max
  • This is a direct-sum calculation on the Python side, so large meshes with tens of thousands of elements or more can take a long time
  • Fortran treecode/fmm is not used. Python-side electric-field calculation is only a direct sum over point charges at element centroids
  • In periodic2, only explicit image shells are reconstructed; oracle residuals (Ewald far correction) are not reproduced on the Python side
import numpy as np
from beach import Beach
b = Beach("outputs/latest")
# シード点を手動指定
seeds = np.array([
[0.005, 0.005, 0.008],
[0.005, 0.005, 0.002],
])
lines = b.trace_field_lines(seeds, max_steps=1000)
print(f"力線数: {len(lines)}")
for i, line in enumerate(lines):
print(f" 力線 {i}: {line.shape[0]} 点")

7.2 plot_field_lines_3d(result, seed_points, *, ...)

Section titled “7.2 plot_field_lines_3d(result, seed_points, *, ...)”

Draws electric field lines in 3D, optionally overlaying the mesh surface colored by charge density.

ParameterTypeDefaultDescription
resultFortranRunResult | object(required)Result object
seed_pointsndarray (n_seeds, 3)(required)Seed points [m]
dsfloat | NoneNoneIntegration step size [m]
max_stepsint500Maximum number of steps
softeningfloat | NoneNoneSoftening length [m]
periodic2Mapping | NoneNonePeriodic settings
directionstr"both"Trace direction
box_minIterable[float] | NoneNoneLower boundary-box limit [m]
box_maxIterable[float] | NoneNoneUpper boundary-box limit [m]
show_meshboolTrueShow mesh overlay
mesh_alphafloat0.25Mesh-face transparency
mesh_cmapstr"coolwarm"Mesh surface charge density colormap
line_colorstr | NoneNoneFixed field-line color. None enables coloring by line_cmap
line_cmapstr"plasma"Field-line colormap, used when line_color=None
line_widthfloat1.2Field-line width
view_elevfloat24.0Elevation angle [deg]
view_azimfloat-58.0Azimuth angle [deg]
titlestr"Electric field lines"Plot title
figsizetuple[float, float](9, 7)Figure size [inch]

Return value: (figure, axes) — matplotlib Figure / Axes3D

  • Each field line is drawn as a line. When line_color=None, each line is colored using line_cmap
  • Direction arrows (quiver) are drawn near the midpoint of each field line
  • Seed points are drawn as red scatter points
  • When show_mesh=True, the triangle mesh is colored by surface charge density q / A and overlaid semi-transparently
import numpy as np
from beach import Beach
b = Beach("outputs/latest")
seeds = np.array([
[0.005, 0.005, 0.008],
[0.003, 0.007, 0.006],
[0.007, 0.003, 0.006],
])
fig, ax = b.plot_field_lines(
seeds,
max_steps=800,
direction="both",
show_mesh=True,
mesh_alpha=0.3,
line_cmap="viridis",
view_elev=30,
view_azim=-45,
)
fig.savefig("field_lines.png", dpi=150)

The common periodic2 parameter is available for potential reconstruction, Coulomb force calculation, electric-field calculation, and field-line tracing.

With the default None, BEACH searches for beach.toml near the output directory and automatically applies periodic boundary settings when sim.field_bc_mode="periodic2" is set. If no configuration file is found, or if field_bc_mode is not periodic2, calculations are performed in free-space mode.

Specify periodic2 as a Mapping with the following keys.

KeyTypeRequiredDefaultDescription
axeslist[int] (length 2)required-0-based indices of the periodic axes, for example [0, 1] for the x and y axes
lengthslist[float] (length 2)required-Box length on each periodic axis [m]. Must be positive
originslist[float] (length 2)-[0.0, 0.0]Box origin on each periodic axis [m]
box_minlist[float] (length 3)--Alternative to origins. Extracts periodic-axis origins from the lower limit of the 3D box
image_layersint-1Number of image-shell layers. Evaluates [-N, N] on each periodic axis
far_correctionstr-"none""auto" / "none" / "m2l_root_oracle". auto is treated as "none" for compatibility. Kept on the Python side for configuration compatibility, but the oracle residual itself is not reproduced
ewald_alphafloat-0.0Ewald decomposition parameter, reserved
ewald_layersint-4Ewald truncation depth, reserved

If both origins and box_min are specified, origins takes precedence.

p2 = {
"axes": [0, 1],
"lengths": [0.01, 0.01],
"image_layers": 2,
}
potential = b.compute_potential(periodic2=p2)
efield = b.compute_electric_field(points, periodic2=p2)
interaction = b.calc_coulomb(target=0, source=1, periodic2=p2)
lines = b.trace_field_lines(seeds, periodic2=p2)

8.3 Limitations of the Python-side periodic2 Implementation

Section titled “8.3 Limitations of the Python-side periodic2 Implementation”
  • On the Python side, periodic sums are reconstructed only by direct sums over explicit image shells
  • The Ewald far correction using explicit m2l_root_oracle in the Fortran FMM is not reproduced on the Python side. far_correction is kept for configuration compatibility, but it does not affect Python direct-sum calculations
  • Larger image_layers improves accuracy but increases cost by a factor of (2*N+1)^2

9.1 analyze_coulomb_mobility(result, *, step, softening, config_path, gravity, support_normal, ...)

Section titled “9.1 analyze_coulomb_mobility(result, *, step, softening, config_path, gravity, support_normal, ...)”

Analyzes the tendency of each object to slide, roll, or lift under Coulomb forces.

Return value: CoulombMobilityAnalysis (stores a tuple of CoulombMobilityRecord in .records)

Loads build/libbeach_field_kernel.so, generated by make build-kernel, through ctypes and evaluates electric fields and potentials using the same Fortran FMM core as the simulation. It reads softening, periodic2, and tree settings from config_path or an automatically discovered beach.toml.

from beach import Beach, FieldKernel
run = Beach("outputs/latest")
with FieldKernel.from_result(run) as kernel:
e = kernel.eval_e([[0.0, 0.0, 0.01]])

If the shared library is located elsewhere, specify library_path= or the environment variable BEACH_FIELD_KERNEL_LIB.

10.2 calc_object_forces_kernel(result, ...)

Section titled “10.2 calc_object_forces_kernel(result, ...)”

For each object, zeros its own source charges and calculates sum(q_i E_not_self(r_i)) and torque. This path uses the Fortran kernel and passes the periodic2 settings explicitly defined in beach.toml, including m2l_root_oracle, so it is a diagnostic closer to the simulation-side field definition than the Python direct-sum calc_coulomb.

from beach import Beach
run = Beach("outputs/latest")
records = run.calc_object_forces_kernel()
for record in records:
print(record.mesh_id, record.total_charge_C, record.force_N)

Beach.scene() is a what-if view that temporarily edits an already charged mesh on the Python side. move / rotate returns a new scene; each element keeps its charge attached to the same element while only centroids and vertices are rigidly transformed. Calling calc_object_forces_kernel afterward passes the edited geometry to the Fortran field kernel as source/target geometry.

from beach import Beach
run = Beach("outputs/latest", config_path="examples/beach.toml")
scene = run.scene()
moved = scene.move(2, by=[1.0e-3, 0.0, 0.0]).rotate(
2,
axis=[0.0, 0.0, 1.0],
angle_deg=15.0,
)
records = moved.calc_object_forces_kernel(target_mesh_ids=[2])
print(records[0].force_N, records[0].torque_Nm)

By default, rigid transformations on the Python side are processed with NumPy. To use Numba, install the optional dependency with pip install ".[accel]" and specify run.scene(transform_backend="numba"). The main calculation that defines the meaning of FMM, periodic2, and far correction is still performed by the Fortran kernel.

fig, ax = b.plot_mesh(cmap="coolwarm") # 電荷密度
fig, ax = b.plot_potential(reference_point="species1_injection_center") # 電位
fig, axes = b.plot_potential_slices(
box_min=[0, 0, 0], box_max=[0.01, 0.01, 0.01],
xy_z=0.005,
)
gif_path = b.animate_mesh("charge_animation.gif", quantity="charge", fps=10)
fig, ax = b.plot_coulomb_force_matrix(component="z")

Since v1.0.0, the unified beachx CLI is recommended.

CommandDescription
beachx inspect <output_dir>Show an output-directory summary
beachx animate <output_dir>Generate an animated GIF of charge/potential history
beachx workload <config.toml>Estimate workload
beachx slices <output_dir>Plot potential sections
beachx profile <output_dir>Plot a performance profile
beachx coulomb <output_dir>Plot a Coulomb force matrix
beachx mobility <output_dir>Run Coulomb mobility analysis
beachx kernel-forces <output_dir>Output per-object resultant-force CSV using the Fortran field kernel
beachx lint <config.toml>Check TOML / JSON Schema / BEACH constraints
beachx config validate <config.toml>Validate a configuration file
beachx model close-packGenerate a close-packed model

The legacy entry points below are retained for backward compatibility, but they may be removed in a future version.

CommandDescription
beach-inspect <output_dir>Show an output-directory summary
beach-animate-history <output_dir>Generate an animated GIF of charge/potential history
beach-estimate-workload <config.toml>Estimate workload
beach-plot-potential-slices <output_dir>Plot potential sections
beach-plot-performance-profile <output_dir>Plot a performance profile
beach-plot-coulomb-force-matrix <output_dir>Plot a Coulomb force matrix
SymbolValueUnitDescription
K_COULOMB8.9875517923e9N m^2 / C^2Coulomb constant