jax-pme: Particle-mesh based calculations of long-range interactions in JAX
This is an experimental version of the torch-pme package, written using jax instead of torch. It currently offers a subset of its features. Note that the API is not yet finalised -- we appreciate any feedback! There will be breaking changes without announcement.
To learn more about Ewald summation and its particle-mesh variants, which this package implements, please have a look at our preprint:
Title: Fast and flexible range-separated models for atomistic machine learning
Authors: Philip Loche, Kevin K. Huguenin-Dumittan, Melika Honarmand, Qianjun Xu, Egor Rumiantsev, Wei Bin How, Marcel F. Langer, Michele Ceriotti
Preprint: arXiv:2412.03281 (2024)
You should also check the torch-pme documentation!
Installation
This package requires jax. Please make sure to install the appropriate version for your setup.
Once this has been done, you can install the latest release from PyPI:
pip install jax-pme
For development, clone this repository and run pip install -e . inside the folder. The dev extra contains development dependencies, and can be installed by adding .[dev] to the command.
Usage
The interface of this package is not yet fully designed. Please file an issue or get in touch via marcel.langer@epfl.ch if you have a particular use-case in mind and would like to chat about how to best support it.
Currently, the high-level API of this package is designed to compute (a) potentials, (b) total energy, (c) forces, and (d) stress for pairwise potentials of the form 1/r**p, with p=1 defining the important case of Coulomb interactions, i.e., electrostatics. The API is designed for standalone computation of these quantities, and is not particularly optimised for integration into machine learning model architectures yet. For example, it expects to be given positions and the cell as inputs, as opposed to graph edges, and it expects a half neighborlist by default (full_neighbor_list=False).
Design
This package has to respect the constraints of jax, and is therefore designed differently than its torch counterpart. The relevant issue here is that jax is not designed to manage stateful classes, and it requires array shapes to be known ahead of time. Preprocessing steps (k-grid shape computation, charge handling) use plain numpy to avoid unnecessary JAX device allocation. For example, the shape of the reciprocal-space grid (the $k$-grid), which depends both on convergence parameters like lr_wavelength (the cutoff in reciprocal space, i.e., the minimum wavelength) and the periodic cell of the system, has to be known ahead of time. This is different to the torch version, where we can just compute it in forward. We also need to be careful to ensure that all operations relevant to differentiation happen inside the scope of the calculation function, since jax traces the computation, and doesn't track the arrays across compuations like torch.
jax-pme is designed accordingly: The actual compute functions are pure functions that accept the relevant arguments for differentiation (positions, charges, cell) as well as information like the shape of the k-grid. As a consequence, they can be traced and transformed by jax, for instance with jax.grad and jax.jit. Provided inputs are padded appropriately, even vmap and scan can be used.
For convenience, we also provide helper functions that transform more conventional inputs, together with the relevant convergence parameters, into the inputs required by jax-pme. We currently expect the user to take care of padding inputs to common shapes, but we support masking out padded inputs to enable this.
API
The high-level API is provided by Calculator classes, which are simple namedtuples of functions:
Calculator = namedtuple(
"Calculator",
("prepare", "potentials", "energy", "energy_forces", "energy_forces_stress"),
)
They are instantiated just like any other class, by calling
from jaxpme import Ewald, PME, P3M
calculator = Ewald(
exponent=1, # p in 1/r^p, integer 1-6; 1 corresponds to electrostatics
exclusion_radius=None, # if this is not None, purely long-range potentials are computed (see preprint)
prefactor=1.0, # default to Gauss units. jaxpme.prefactors.eV_A for standard ase units
custom_potential=None, # mostly for testing -- you can define custom potential functions
full_neighbor_list=False, # set True if your neighborlist includes both i->j and j->i
)
calculator = PME(
exponent=1, # p in 1/r^p, integer 1-6; 1 corresponds to electrostatics
exclusion_radius=None, # if this is not None, purely long-range potentials are computed (see preprint)
prefactor=1.0, # default to Gauss units. jaxpme.prefactors.eV_A for standard ase units
interpolation_nodes=4, # currently only 4 is supported
custom_potential=None, # mostly for testing -- you can define custom potential functions
full_neighbor_list=False, # set True if your neighborlist includes both i->j and j->i
)
calculator = P3M(
exponent=1, # p in 1/r^p, integer 1-6; 1 corresponds to electrostatics
exclusion_radius=None, # if this is not None, purely long-range potentials are computed (see preprint)
prefactor=1.0, # default to Gauss units. jaxpme.prefactors.eV_A for standard ase units
interpolation_nodes=4, # B-spline interpolation, supports 1-5
custom_potential=None, # mostly for testing -- you can define custom potential functions
full_neighbor_list=False, # set True if your neighborlist includes both i->j and j->i
)
# -> calculator.prepare, .energy, etc ... can be called
The functions exposed by Calculator consist of a prepare function that arranges all the inputs required for calculations of some input structure, including determining the shape of the reciprocal-space grid, and a bundle of functions that then execute different calculations.
prepare expects the arguments atoms (ase.Atoms instance), charges, cutoff (for the real-space neighborlist), mesh_spacing (PME/P3M) or lr_wavelength (Ewald) (defining the resolution/cutoff in reciprocal space), smearing (range separation parameter, related to cutoff). The parameters can be tuned with torch-pme or set heuristically (see torch-pme docs). It returns a tuple of inputs charges, *graph, k_grid, smearing, where *graph collects cell, positions, neighbor indices i and j, and cell_shifts. k_grid is a dummy array that defines the shape of the reciprocal-space grid via its shape, its values are not used. prepare is not jax.jit-able as it returns variable-shape output. (Ewald additionally appends pbc to the tuple, used to route non-periodic structures; see below.)
Non-periodic structures (pbc=[False, False, False]) are supported by Ewald: there is no periodic image to sum, so prepare builds the all-pairs list (half by default, both directions with full_neighbor_list=True) and potentials evaluates a bare $1/r^p$ sum (no real-space cutoff, no range separation). Serial PME/P3M do not yet support non-PBC inputs (their reciprocal block divides by the zero cell volume) — use Ewald or a batched calculator instead.
The following calculation functions are implemented:
potentials: Accepts the above inputs and returns the potential values at each position.energy: Computes the total energy obtained by multplying the charge at each position with the potential at each positions and summing up.energy_forces: Energy as above, and its derivative with respect to positions.energy_forces_stress: The above, with additionally the stress.
All these other functions can be jit-ed and support function transformations like vmap and grad. They optionally accept boolean mask arrays atom_mask and pair_mask to exclude irrelevant inputs from the output, typically introduced by padding. We currently do not support padding the $k$-grid, you should simply use the biggest grid consistently. Make sure that padding indices do not connect non-padded edges. Additionally, Ewald's potentials_fn accepts optional distances (to skip recomputing pairwise distances) and pbc kwargs; PME and P3M accept pbc.
The low-level API is not yet ready for public consumption. We split the calculation task into sub-problems: solvers.py defines the actual implementations of the Ewald and PME method, potentials.py defines the actual potential functions, and the other files implement various helper functionality.
Recommendations
We find in benchmarks that for moderately-sized systems up to a few thousand atoms, the asymptotically less efficient Ewald method works best. For large systems, PME and P3M are preferable, as they scale $O(N \log N)$. Note that PME is not smooth in the forces -- be careful when using it for dynamics. P3M uses B-spline interpolation (with interpolation_nodes 1-5) and an influence function correction, giving smoother forces and better accuracy. Use P3M for molecular dynamics.
It is highly recommended to tune convergence parameters for your specific system. torchpme.utils.tune_ewald (and its pme version) exists for this purpose. Paramters can be used directly in jax-pme. You should typically tune the parameters for the largest system in a given dataset.
Batched computation
Warning: The batched API is highly experimental and subject to breaking changes. Use at your own risk.
For computing energies/forces across multiple structures (e.g. for training), batched implementations are available:
from jaxpme.batched_mixed import Ewald # rectangular max-padding, supports cutoff-only API
from jaxpme.batched_tiled import Ewald # per-system sum-padding + tile dispatch (see below)
from jaxpme.batched_flat import Ewald # alternative flat padding strategy
All three accept lists of ase.Atoms in prepare and handle padding/masking internally. Currently, only batched Ewald is implemented. 2D PBC (slab geometries) is supported for arbitrary triclinic cells; large vacuum gaps are automatically shrunk to keep the k-grid efficient. The shrunk cell travels separately from the raw Batch.cell and the calculators compose the two at entry — see jaxpme.utils.compose_cell for the mechanism and gradient policy (2D-slab stress no longer includes the shrink's artifact gradient). Consequence: for 2D PBC the stress components touching the non-periodic direction carry only the per-atom term and should be treated as meaningless; the in-plane block is correct.
batched_tiled is a second Ewald backend designed for heterogeneous batches: atoms are sum-padded per system (each system padded to ⌈N_b/BM⌉·BM atoms, concatenated into one flat array) rather than max-padded to the batch's largest system. The reciprocal sum runs through a pure-JAX tile-dispatched kernel over fixed-size (BM × BK) work tiles — vmap + segment_sum for pass 1's structure factors, vmap + reshape-sum for pass 2's per-atom potential. Trade-offs vs batched_mixed:
num_kis required onprepareand fixes the reciprocal grid (per-cell K target vialr_wavelength_for_num_k; the K axis stays rectangular so all systems shareK_pad). The real-spacecutofffollows it: omit it and it is derived aslr_wavelength · 8(withsmearing = lr_wavelength · 2), matchingbatched_mixed, so real and reciprocal space stay balanced. An explicitcutoffoverrides only the real-space radius (smearingstill tracksnum_k).- Tile sizes
(BM, BK)are fixed at prepare time (defaultsBM=32, BK=128). They drive both the per-system atom padding and the kernel tile dimensions. - Lower memory and faster on heterogeneous batches where system sizes vary by a lot (small molecules + larger crystals/MOFs in the same batch).
- Host-side batching is exposed for external pipelines:
batched_tiled.batching.sample_shapesgives the per-sample size accountingget_batchitself uses (for batch-size planners), andget_batch(samples=[], dtype=...)builds a pure-padding batch at explicit sizes;int_dtype=sets the neighbor-list index dtype (default int64).
Development
The package uses ruff for linting and formatting and pytest for testing. Please run ruff format . && ruff check --fix . before every commit or set up a commit hook to do it. Tests can be run in the tests/ folder with pytest. Be aware that the test suite can take a few minutes to run.
To release a new version, push a v* tag. This triggers a workflow that builds the package and publishes it to PyPI; the version is taken from the tag via setuptools_scm.
This project is maintained by @E-Rum, and @sirmarcel, who will reply to issues and pull requests opened on this repository as soon as possible. You can mention them directly if you did not receive an answer after a couple of days.
Release files for jax-pme 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jax_pme-0.1.0.tar.gz | 84.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jax_pme-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 135.4 kB
Release files / jax_pme-0.1.0.tar.gz
| Download URL | jax_pme-0.1.0.tar.gz |
|---|---|
| Size | 84.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6a9362ad2d5339ce1984f30abd8379950a8a5759cb4af8e36c3dc8c26a02aad5
|
|
BLAKE2b-256 checksum How to use checksums |
7577a73be1fe74bfc4e5290175e07f8299564ec10cf3359cbb3933b7c678c559
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / jax_pme-0.1.0-py3-none-any.whl
| Download URL | jax_pme-0.1.0-py3-none-any.whl |
|---|---|
| Size | 51.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
69829eccdb2822c19f697426500851ab5e878c8e6e2cb4193bc754abf4f5caf2
|
|
BLAKE2b-256 checksum How to use checksums |
7c023108d7ce0deff65bd739d8405d792b9c002ec0fbf87994f3d33ee10b80fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log