Skip to main content

GPGI

PyPI uv

Fast particle deposition at post-processing time

This small Python library implements fundamental grid deposition algorithms to analyse (rectilinear) grid + particle datasets, with an emphasize on performance. Core algorithms are implemented as Cython extensions.

GPGI stands for Generic Particle + Grid data Interface

Table of Contents

Installation

python -m pip install --upgrade pip
python -m pip install gpgi

Supported applications

A rectilinear grid is defined as 1D arrays representing cell left edges in each directions. Note that the last point of such an array is interpreted as the right edge of the rightmost cell, so for instance, a 1D grid containing 100 cells is defined by 101 edges.

Particles are defined as points that live within the grid's bounds.

Deposition is the action of going from particle description to a grid description of a field. It is useful to analyze, compare and combine simulation data that exists in a combination of the two formalisms. This process is not reversible as it degrades information.

For instance, here's a simple overlay of a particle set (red dots) against a background that represents the deposited particle count.

This example illustrates the simplest possible deposition method, "Nearest Grid Point" (NGP), in which each particle contributes only to the cell that contains it.

More refined methods are also available.

Builtin deposition methods

method name abbreviated name order
Nearest Grid Point NGP 0
Cloud in Cell CIC 1
Triangular Shaped Cloud TSC 2

new in gpgi 0.12.0 User-defined alternative methods may be provided to Dataset.deposit as method=my_func. Their signature need to be compatible with gpgi.typing.DepositionMethodT or gpgi.typing.DepositionMethodWithMetadataT .

Supported geometries

geometry name axes order
cartesian x, y, z
polar radius, z, azimuth
cylindrical radius, azimuth, z
spherical radius, colatitude, azimuth
equatorial radius, azimuth, latitude

Time complexity

An important step in performing deposition is to associate particle indices to cell indices. This step is called "particle indexing". In directions where the grid is uniformly stepped (if any), indexing a particle is an O(1) operation. In the more general case, indexing is performed by bisection, which is a O(log(nx)) operation (where nx represents the number of cells in the direction of interest).

Usage

The API consists in a load function, which returns a Dataset object.

Load data

import numpy as np
import gpgi

nx = ny = 64
nparticles = 600_000

prng = np.random.RandomState(0)
ds = gpgi.load(
    geometry="cartesian",
    grid={
        "cell_edges": {
            "x": np.linspace(-1, 1, nx),
            "y": np.linspace(-1, 1, ny),
        },
    },
    particles={
        "coordinates": {
            "x": 2 * (prng.normal(0.5, 0.25, nparticles) % 1 - 0.5),
            "y": 2 * (prng.normal(0.5, 0.25, nparticles) % 1 - 0.5),
        },
        "fields": {
            "mass": np.ones(nparticles),
        },
    },
)

The Dataset object holds a grid and a particles attribute, which both hold a fields attribute for accessing their data. But more importantly, the Dataset has a deposit method to translate particle fields to the grid formalism.

Deposit Particle fields on the grid

particle_mass = ds.deposit("mass", method="nearest_grid_point")  # or "ngp" for shorts

Visualize In this example we'll use matplotlib for rendering, but note that matplotlib is not a dependency to gpgi

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.set(aspect=1, xlabel="x", ylabel="y")

im = ax.pcolormesh(
    "x",
    "y",
    particle_mass.T,
    data=ds.grid.cell_edges,
    cmap="viridis",
)
fig.colorbar(im, ax=ax)

The example script given here takes about a second (top to bottom).

Supplying arbitrary metadata

new in gpgi 0.4.0

Dataset objects have a special attribute metadata which is a dictionary with string keys. This attribute is meant to hold any special metadata that may be relevant for labelling or processing (e.g. simulation time, author, ...). Metadata can be supplied at load time as

ds = gpgi.load(
    geometry="cartesian",
    grid=...,
    particles=...,
    metadata={"simulation_time": 12.5, "author": "Clément Robert"}
)

Boundary conditions

new in gpgi 0.5.0

With CIC and TSC deposition, particles contribute to cells neighbouring the one that contains them. For particles that live in the outermost layer of the domain, this means some of their contribution is lost. This behavior corresponds to the default 'open' boundary condition, but gpgi has builtin support for more conservative boundary conditions.

Boundary conditions can be selected per field, per axis and per side. Builtin recipes all perform linear combinations of ghost layers (same-side and opposite side) and active domain layers (same-side and opposite side), and replace the same-side active layer with the result.

User-selected boundary conditions take the form of an optional argument to Dataset.deposit, as a dictionary with keys being axes names, and values being 2-tuples of boundary condition names (left and right side respectively). For instance, here's how one would require periodic boundary conditions on all axes:

ds.deposit(
    "mass",
    method="cic",
    boundaries={
        "x": ("periodic", "periodic"),
        "y": ("periodic", "periodic"),
    }
)

Unspecified axes will use the default 'open' boundary.

Builtin recipes

boundary conditions description conservative ?
open (default) no special treatment no
periodic add opposite ghost layer to the active domain yes
wall add same-side ghost layer to the active domain yes
antisymmetric subtract same-side ghost layer from the active domain no

Define custom recipes

gpgi's boundary recipes can be customized. Let's illustrate this feature with a simple example. Say we want to fix the value of the deposited field in some outer layer. This is done by defining a new function on the user side:

def ones(
    same_side_active_layer,
    same_side_ghost_layer,
    opposite_side_active_layer,
    opposite_side_ghost_layer,
    weight_same_side_active_layer,
    weight_same_side_ghost_layer,
    weight_opposite_side_active_layer,
    weight_opposite_side_ghost_layer,
    side,
    metadata,
):
   return 1.0

where all first eight arguments are numpy.ndarray objects with the same shape (which includes ghost padding !), to which the return value must be broadcastable, side can only be either "left" or "right", and metadata is the special Dataset.metadata attribute. Not all arguments need be used in the body of the function, but this signature is required.

The method must then be registered as a boundary condition recipe as

ds.boundary_recipes.register("ones", ones)

where the associated key (here "ones") is arbitrary. The recipe can now be used exactly as builtin ones, and all of them can be mixed arbitrarily.

ds.deposit(
    "mass",
    method="cic",
    boundaries={
        "x": ("ones", "wall"),
        "y": ("periodic", "periodic"),
    }
)

Note that all first eight arguments in a boundary recipe function should represent an extensive physical quantity (as opposed to intensive). When depositing an intensive quantity u, a weight field w should be supplied (see next section), in which case, the first four arguments represent u*w and the following four represent w, so that u can still be obtained within the function as a ratio if needed.

Weight fields (Depositing intensive quantities)

new in gpgi 0.7.0

Fundamentally, deposition algorithms construct on-grid fields by performing summations. An implication is that the physical quantities being deposited are required to be extensive (like mass or momentum). Intensive quantities (like velocity or temperature) require additional operations, and necessitate the use of an additional weight field.

This section provides showcases their usage. For a detailed explanation of the deposition algorithm for intensive quantities, see Deposition algorithm.

In order to deposit an intensive field (e.g., vx), an additional weight_field argument must be provided as

ds.deposit(
    "vx",
    method="cic",
    boundaries={
        "y": ("periodic", "periodic"),
        "x": ("antisymmetric", "antisymmetric"),
    },
    weight_field="mass",
    weight_field_boundaries={
        "y": ("periodic", "periodic"),
        "x": ("open", "open"),
    },
)

Boundary recipes may be also associated to the weight field with the weight_field_boundaries argument. This arguments becomes required if boundaries and weight_field are both provided.

Call help(ds.deposit) for more detail.

Count Sorting

new in gpgi 0.14.0

gpgi can load arbitrarily ordered particle sets, though deposition algorithms perform better when the in-memory position of particles correlates with their physical positions relative to the grid, since such a state minimizes the number of cache misses.

Particles may be sorted by a counting sort algorithm, as

ds = ds.sorted()

Note that this method returns a copy of the dataset, so it will best perform for datasets that, at most, fit in half your RAM.

This operation is costly in itself, so there may be a trade-off depending on how many depositions one needs to perform on a given dataset before tossing it out.

By default, axes are weighted in the order that's optimal for gpgi's deposition routines, but arbitrary priority order may be specified as, for instance

ds = ds.sorted(axes=(1, 0))

Use the Dataset.is_sorted method to check whether particles are already sorted without performing the sort. Dataset.is_sorted accepts an axes argument just like Dataset.sorted. This is useful for testing and comparative purposes.

Deposition algorithm

This section provides details on the general deposition algorithm, as implemented in gpgi.

Without loss of generality, we will illustrate how an intensive field (v) is deposited, since this case requires the most computational steps. As it happens, depositing an extensive field (w) separately is actually part of the algorithm.

Definitions

  • v is an intensive field that we want to deposit on the grid
  • w is an extensive field that will be used as weights
  • u = v * w is an extensive equivalent to v (conceptually, if v is a velocity and w is a mass, u corresponds to a momentum)

u(i), v(i) and w(i) are defined for each particle i.

We note U(x), V(x) and W(x) the corresponding on-grid fields, where V(x) is the final output of the algorithm. These are defined at grid cell centers x, within the active domain.

Last, we note U'(x), V'(x) and W'(x) the raw deposited fields, meaning no special treatment is applied to the outermost layers (boundary conditions). These are defined at grid cell centers, including one ghost layer that will be used to apply boundary conditions.

Algorithm

  1. W' and U' are computed as
W'(x) = Σ c(i,x) w(i)
U'(x) = Σ c(i,x) w(i) v(i)

where c(i,x) are geometric coefficients associated with the deposition method. Taking the nearest grid point (NGP) method for illustration, c(i,x) = 1 if particle i is contained in the cell whose center is x, and c(i,x) = 0 elsewhere.

  1. boundary conditions are applied
W(x) = W_BCO(W', 1, metadata)
U(x) = U_BCO(U', W', metadata)

where W_BCO and U_BCO denote arbitrary boundary condition operators associated with W and U respectively, and which take 3 arguments, representing the field to be transformed, its associated weight field and a wildcard metadata argument which may contain any additional data relevant to the operator.

Note 1 is used a placeholder "weight" for W, for symmetry reasons: all boundary condition operators must expose a similar interface, as explained in Define custom recipes.

  1. Finally, V(x) is obtained as
V(x) = (U/W)(x)

Thread safety

Starting in gpgi 2.0.0, thread safety is guaranteed in Dataset.host_cell_index computation and Dataset.deposit, and both operations release the GIL (Global Interpreter Lock) around their respective hotloops.

Note that, by default, Dataset.deposit still uses a lock per Dataset instance, which in the most general case is preferable since concurrently depositing many fields can cause catastrophic degradations of performances as it encourages cache misses. Optimal performance is however application-specific, so this strategy can be overridden using the lock parameter:

  • using lock=None will not use any lock, which in restricted conditions leads to better walltime performances
  • alternatively, an externally managed threading.Lock instance may be supplied

Dataset.boundary_recipes.register is also thread-safe: registering a shared function multiple times is supported, but an error is raised in case one attempts registering a different function under an existing key.

Semi-private, low level deposition functions included in gpgi._lib, are not thread safe, as they explicitly mutate their out argument.

Free-threaded CPython compatibility

As of gpgi 2.0.0, thread safety is tested against the experimental free-threaded build of CPython, however, pre-compiled binaries are not provided yet.

Release files for gpgi 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gpgi 2.1.0
File Size Uploaded
gpgi-2.1.0.tar.gz 57.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for gpgi 2.1.0
File
gpgi-2.1.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
gpgi-2.1.0-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details
gpgi-2.1.0-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details
gpgi-2.1.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64, Linux glibc 2.28+ x86-64 Details
gpgi-2.1.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl CPython 3.11 abi3 Linux glibc 2.28+ ARM64, Linux glibc 2.17+ ARM64 Details
gpgi-2.1.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
gpgi-2.1.0-cp311-abi3-macosx_10_9_x86_64.whl CPython 3.11 abi3 macOS 10.9+ x86-64 Details

Total release size: 1.4 MB

Release files / gpgi-2.1.0.tar.gz

Download URL gpgi-2.1.0.tar.gz
Size 57.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3baec9e34d6f05848ff2449b27284379c8d0823e4421b7e007043a96f69cc61d
BLAKE2b-256 checksum
How to use checksums
95d5d2b0f8442e3af28a5553e2926f8237d4adec9d888639288811be559c3189
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-win_amd64.whl

Download URL gpgi-2.1.0-cp311-abi3-win_amd64.whl
Size 196.6 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
05ba1ac2688235c961f528eea4a94d59198ebdb28ce2eacb0e17be26da1d8c58
BLAKE2b-256 checksum
How to use checksums
c184eb2350f8cd53cc32c280a635839cab32dc69bee12c434a1501e4ee0b0058
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-musllinux_1_2_x86_64.whl

Download URL gpgi-2.1.0-cp311-abi3-musllinux_1_2_x86_64.whl
Size 207.8 kB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
76f1dc7c998ce788f23c1654f9d92df0fc06367d068bbf5a468ce311a1bb3d9b
BLAKE2b-256 checksum
How to use checksums
1176674990f577f80f996ddd4e8fcfb1c29a559cab6cec00924da7457b0cfba5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-musllinux_1_2_aarch64.whl

Download URL gpgi-2.1.0-cp311-abi3-musllinux_1_2_aarch64.whl
Size 185.8 kB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
7d9b64f3f0222da3de2a315e093aa9cdbe7d145c74ab0d2031b14751566a9594
BLAKE2b-256 checksum
How to use checksums
6eb513597455ac76d6e6cff6b380aabd2534ad9c3f8b22c773d350139a3c3141
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL gpgi-2.1.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Size 205.1 kB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
9085f11639be33944b73604ed3c967711b690b770e84b67b062ae0f79e46d07f
BLAKE2b-256 checksum
How to use checksums
0e3e8a38ec07c8d1e2807ced07bf8d1ad7b27fc80f820722a4efcb4d57fbf1a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl

Download URL gpgi-2.1.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Size 188.3 kB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
75926f35810d492eacfe1331ba9812688eba0c0eaec685671c5a20881099a21a
BLAKE2b-256 checksum
How to use checksums
cdbaf7e4f8304843a0eb0579bc36dcab0d2d6f7c2530268353063842685f4256
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL gpgi-2.1.0-cp311-abi3-macosx_11_0_arm64.whl
Size 152.2 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
e846d17ff1c1ced9dc5bf8067b41ced7e881b22714ee7e56c149d76a36537ccb
BLAKE2b-256 checksum
How to use checksums
fb46ea5c23c1bf6a08fb161efbf1f1e67ead767cd0b5ef86d5b1e4146373807a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release files / gpgi-2.1.0-cp311-abi3-macosx_10_9_x86_64.whl

Download URL gpgi-2.1.0-cp311-abi3-macosx_10_9_x86_64.whl
Size 182.2 kB
Tags CPython 3.11 abi3 macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
1b2ac7fc4f0b5b0ab897760deaf90030dc37c8f33ac31c0d2441dbfcdca6e93b
BLAKE2b-256 checksum
How to use checksums
b2cc381267253d2646b4780b22260e03793c94e445ef72214613f40465f0451f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

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 Jul 24, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

2.1.0 This release

8 release files

1.0.0

16 release files

0.7.2

13 release files

0.7.1

13 release files

0.6.0

13 release files

0.5.0

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page