Quickstart#

Struphy is a Python API for solving PDEs with structure-preserving discretizations. This quickstart shows how to solve a simple problem with minimal input: a 1D Poisson solve.

For interactive tutorials (no local install), use mybinder. For ready-to-run simulations of many models, see the struphy-examples repository (Ready-To-Run Examples: struphy-examples); for more on the API, see Userguide and the tutorial collection. An overview of the models, the numerical methods and a gallery of examples is on the Struphy website https://struphy-hub.github.io/.

Solve Poisson In A Few Steps#

Make sure that Struphy is installed and compiled (see Install and compile). Save the code below as params_poisson.py. By default, output is written to sim_1/ in the directory from which you launch the script (change this with EnvironmentOptions).

We search for a potential \(\phi(x, y)\) satisfying the Poisson equation

\[-\Delta \phi = \rho\]

for a given source term \(\rho(x)\) on a doubly periodic 2D domain.

  1. Import the API and choose a model.

import numpy as np
from matplotlib import pyplot as plt

from struphy import Simulation, domains, grids, perturbations
from struphy.models import Poisson
  1. Create the Poisson model.

model = Poisson()

3. This model features the Propagator PoissonSolve under propagators.poisson. For periodic boundary conditions we will stabilize via options.

stab_eps = 1e-8

model.propagators.poisson.options = model.propagators.poisson.Options(
    stab_eps=stab_eps,
)
  1. Add a manufactured term \(\rho(x) = (k^2 + \epsilon)\cos(kx)\) to the source variable.

Lx = 2.0 * np.pi
Ly = 4.0 * np.pi
mode = 2
k = mode * 2.0 * np.pi / Lx
source_amp = k**2 + stab_eps

fun = perturbations.ModesCos(ls=(mode,), amps=(source_amp,))

model.em_fields.source.add_perturbation(fun)
  1. Build domain and grid, then instantiate a simulation.

domain = domains.Cuboid(r1=Lx, l2=-Ly / 2, r2=Ly / 2)
grid = grids.TensorProductGrid(num_elements=(64, 64, 1))

sim = Simulation(model=model, domain=domain, grid=grid)
  1. Run the simulation. sim.run() returns an Output object, the entry point for all post-processing. For plots and diagnostics made for Struphy output (out.plot, out.analysis and .plasma.plot on every product), install the separate package plasma-plots with pip install "struphy[pproc]"; Output loads it automatically.

out = sim.run()
  1. Evaluate the potential on a line along \(\eta_1\) and compare to the exact solution. out.evaluate() takes a "species/variable" name, evaluates the saved spline field on the given logical coordinates, and returns a labeled xarray.DataArray. Scalars fix a direction (here \(\eta_2 = \eta_3 = 0.5\)); omitted directions stay on the cell centres of the simulation grid. t=-1 selects the last saved snapshot. The array also carries the physical coordinates X, Y, Z so that xarray’s .plot() can use either coordinate system.

fig, axs = plt.subplots(1, 2, figsize=(12, 4))

eta1 = np.linspace(0, 1, 100)
phi_1d = out.evaluate("em_fields/phi", eta1=eta1, eta2=0.5, eta3=0.5, t=-1)

x = phi_1d["X"]
phi_exact = np.cos(k * x)
phi_exact_logical = np.cos(Lx * k * eta1)

phi_1d.plot(ax=axs[0], label="Struphy")  # plot along eta1 (default)
phi_1d.plot(ax=axs[1], x="X", label="Struphy")  # plot along physical X
axs[0].plot(eta1, phi_exact_logical, "k--", lw=1.8, label="exact")
axs[1].plot(x, phi_exact, "k--", lw=1.8, label="exact")

for ax in axs:
    ax.legend()
    ax.grid(alpha=0.3)
fig.savefig("quickstart_poisson_phi.png", dpi=150)
plt.show()
Poisson quickstart comparison of exact and numerical solution

Exact (dashed) and Struphy solutions from Step 7, along \(\eta_1\) (left) and \(x\) (right).#

  1. Evaluate on a 2D logical grid and plot in physical coordinates.

eta = np.linspace(0, 1, 100)
phi_2d = out.evaluate("em_fields/phi", eta1=eta, eta2=eta, t=-1)
phi_2d.plot(x="X", y="Y")
plt.savefig("quickstart_poisson_phi_2d.png", dpi=150)
plt.show()
Poisson quickstart solution on the 2D domain

Struphy solution \(\phi(x, y)\) from Step 8 in physical coordinates.#

out can also be created later from the output folder alone, e.g. in a separate post-processing script, via out = Output("sim_1"); the model, domain and numerical options are reconstructed from run_metadata.json.

Full script (save as params_poisson.py and run with python params_poisson.py):

import numpy as np
from matplotlib import pyplot as plt

from struphy import Simulation, domains, grids, perturbations
from struphy.models import Poisson

model = Poisson()

stab_eps = 1e-8

model.propagators.poisson.options = model.propagators.poisson.Options(
    stab_eps=stab_eps,
)

Lx = 2.0 * np.pi
Ly = 4.0 * np.pi
mode = 2
k = mode * 2.0 * np.pi / Lx
source_amp = k**2 + stab_eps

fun = perturbations.ModesCos(ls=(mode,), amps=(source_amp,))

model.em_fields.source.add_perturbation(fun)

domain = domains.Cuboid(r1=Lx, l2=-Ly / 2, r2=Ly / 2)
grid = grids.TensorProductGrid(num_elements=(64, 64, 1))

sim = Simulation(model=model, domain=domain, grid=grid)

if __name__ == "__main__":
    # sim.run() returns an Output object for post-processing
    out = sim.run()

    # out.evaluate() evaluates a saved field on logical coordinates (eta1, eta2, eta3)
    # and returns a labeled xarray.DataArray; scalar etas fix a direction, omitted etas stay
    # on the simulation grid's cell centres, t=-1 selects the last snapshot. Physical coordinates X, Y, Z are attached.
    fig, axs = plt.subplots(1, 2, figsize=(12, 4))

    eta1 = np.linspace(0, 1, 100)
    phi_1d = out.evaluate("em_fields/phi", eta1=eta1, eta2=0.5, eta3=0.5, t=-1)

    x = phi_1d["X"]
    phi_exact = np.cos(k * x)
    phi_exact_logical = np.cos(Lx * k * eta1)

    # xarray plotting: along eta1 by default, or along any attached coordinate
    phi_1d.plot(ax=axs[0], label="Struphy")
    phi_1d.plot(x="X", ax=axs[1], label="Struphy")
    axs[0].plot(eta1, phi_exact_logical, "k--", lw=1.8, label="exact")
    axs[1].plot(x, phi_exact, "k--", lw=1.8, label="exact")

    for ax in axs:
        ax.legend()
        ax.grid(alpha=0.3)
    fig.savefig("quickstart_poisson_phi.png", dpi=150)
    plt.show()

    # 2D evaluation on a logical tensor-product grid, plotted in physical coordinates
    eta = np.linspace(0, 1, 100)
    phi_2d = out.evaluate("em_fields/phi", eta1=eta, eta2=eta, t=-1)
    phi_2d.plot(x="X", y="Y")
    plt.savefig("quickstart_poisson_phi_2d.png", dpi=150)
    plt.show()

Same Workflow For All Models#

The same Simulation API is reused across models. For example, replace Poisson with Maxwell:

from pathlib import Path

from struphy import EnvironmentOptions, Simulation, perturbations
from struphy.models import Maxwell

model = Maxwell()
model.em_fields.e_field.add_perturbation(
    perturbations.ModesCos(ls=(1,), amps=(1e-2,), comp=1)
)

env = EnvironmentOptions(sim_folder="sim_data")
sim = Simulation(model=model, env=env, params_path=__file__)
if __name__ == "__main__":
    sim.run()

Check Models for more models and their specific options.

Generate A Default Parameter File#

You can generate a ready-to-edit parameter file for any model from the CLI:

struphy params Poisson

This writes params_Poisson.py in your current directory. You can open and edit it, then run with:

python params_Poisson.py

As all data structures in Struphy are written for MPI, you can run the same script with mpirun to use multiple processes:

mpirun -n 4 python params_Poisson.py

Ready-To-Run Examples: struphy-examples#

The repository struphy-examples collects simulations of physics scenarios and numerical test cases, for example Landau damping, the two-stream and Weibel instabilities, the ITPA TAE benchmark, the Cyclone base case or elliptic problems on tori. The folders are sorted by model and then by case; each case contains

  • params_*.py: the parameter file, which sets up and runs the simulation,

  • pproc_*.py: the post-processing script, which reads the output and creates the figures.

The physics and the expected results are described in the docstring at the top of each parameter file. The repository is updated with every Struphy release and tagged with its version, so clone the tag that matches your installation (struphy --version prints it):

git clone --branch vX.Y.Z https://github.com/struphy-hub/struphy-examples.git
cd struphy-examples/VlasovAmpereOneSpecies/two_stream
python params_two_stream.py              # or: mpirun -n 4 python params_two_stream.py
python pproc_two_stream.py

The output folder is created beside the scripts, and the post-processing script finds it there; both scripts accept --help. A good way to set up your own simulation is to copy the closest example and adapt it. To add an example, open a pull request into the devel branch of Struphy with your case in its examples/ folder.

Using Struphy With AI Coding Agents#

Struphy ships a usage guide for AI coding agents such as Claude Code, Codex, Copilot or Cursor. It is part of the installed package, so it always matches your Struphy version, and it covers setting up a parameter file, running it and post-processing the output. Print it with:

struphy agents

struphy agents --path prints only the folder that holds the guide (USAGE.md) and its skills.

To let an agent find the guide automatically in your own project, run in the project folder:

struphy agents --install

This

  • copies the skills setup-simulation and postprocess-simulation to .claude/skills/ (Claude Code loads them when a request matches, e.g. “set up a Maxwell simulation” or “plot the energy of sim_1”),

  • adds a short section to AGENTS.md (read by most agents) that tells the agent to run struphy agents before writing Struphy scripts,

  • creates CLAUDE.md importing AGENTS.md, or adds the same section to an existing CLAUDE.md.

Running it again replaces the skills by the current version and leaves the pointers unchanged, so repeat it after upgrading Struphy. Pass a folder to install elsewhere: struphy agents --install path/to/project.

You can then simply ask your agent, for example, “Set up a Struphy simulation of the two-stream instability with VlasovAmpereOneSpecies, run it on 4 processes and plot the electric energy.”

If you develop Struphy itself, the coding guidelines for agents are in AGENTS.md at the root of the repository.