gpgi


Namegpgi JSON
Version 2.0.0 PyPI version JSON
download
home_pageNone
SummaryA Generic Particle+Grid Interface
upload_time2024-10-09 09:27:53
maintainerNone
docs_urlNone
authorC.M.T. Robert
requires_python>=3.11
licenseGPL-3.0
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # GPGI
[![PyPI](https://img.shields.io/pypi/v/gpgi.svg?logo=pypi&logoColor=white&label=PyPI)](https://pypi.org/project/gpgi/)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/neutrinoceros/gpgi/main.svg)](https://results.pre-commit.ci/latest/github/neutrinoceros/gpgi/main)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)

***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 **G**eneric **P**article + **G**rid data **I**nterface

## Table of Contents

<!-- toc -->

- [Installation](#installation)
- [Supported applications](#supported-applications)
  * [Builtin deposition methods](#builtin-deposition-methods)
  * [Supported geometries](#supported-geometries)
- [Time complexity](#time-complexity)
- [Usage](#usage)
  * [Supplying arbitrary metadata](#supplying-arbitrary-metadata)
  * [Boundary conditions](#boundary-conditions)
    + [Builtin recipes](#builtin-recipes)
    + [Define custom recipes](#define-custom-recipes)
  * [Weight fields (Depositing intensive quantities)](#weight-fields-depositing-intensive-quantities)
  * [Count Sorting](#count-sorting)
- [Deposition algorithm](#deposition-algorithm)
- [Thread safety](#thread-safety)

<!-- tocstop -->

## Installation

```shell
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.
<p align="center">
    <img src="https://raw.githubusercontent.com/neutrinoceros/gpgi/main/tests/pytest_mpl_baseline/test_2D_deposit_ngp.png" width="600"></a>
</p>

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

```python
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**

```python
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`
```python
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)
```

<p align="center">
    <img src="https://raw.githubusercontent.com/neutrinoceros/gpgi/main/tests/pytest_mpl_baseline/test_readme_example.png" width="600"></a>
</p>

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
```python
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 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 dictionary with keys being axes names, and values being
2-tuples of boundary conditions names (for left and right side respectively).
For instance, here's how one would require periodic boundary conditions on all axes:

```python
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:

```python
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
```python
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.
```python
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](#deposition-algorithm).

In order to deposit an *intensive* field (e.g., `vx`), an additional `weight_field` argument must be provided as
```python
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](https://en.wikipedia.org/wiki/Counting_sort),
as

```python
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
```python
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.

2) 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](#define-custom-recipes).

3) 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. Thread safety is
also tested against the experimental free-threaded build of Python 3.13.

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.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "gpgi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": null,
    "author": "C.M.T. Robert",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/9d/48/6c8833406d5cc8d1ec6d85302d73acdad1d1776c30076d6adb3ca784d5f9/gpgi-2.0.0.tar.gz",
    "platform": null,
    "description": "# GPGI\n[![PyPI](https://img.shields.io/pypi/v/gpgi.svg?logo=pypi&logoColor=white&label=PyPI)](https://pypi.org/project/gpgi/)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/neutrinoceros/gpgi/main.svg)](https://results.pre-commit.ci/latest/github/neutrinoceros/gpgi/main)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)\n\n***Fast particle deposition at post-processing time***\n\nThis small Python library implements fundamental grid deposition algorithms to\nanalyse (rectilinear) grid + particle datasets, with an emphasize on\nperformance. Core algorithms are implemented as Cython extensions.\n\nGPGI stands for **G**eneric **P**article + **G**rid data **I**nterface\n\n## Table of Contents\n\n<!-- toc -->\n\n- [Installation](#installation)\n- [Supported applications](#supported-applications)\n  * [Builtin deposition methods](#builtin-deposition-methods)\n  * [Supported geometries](#supported-geometries)\n- [Time complexity](#time-complexity)\n- [Usage](#usage)\n  * [Supplying arbitrary metadata](#supplying-arbitrary-metadata)\n  * [Boundary conditions](#boundary-conditions)\n    + [Builtin recipes](#builtin-recipes)\n    + [Define custom recipes](#define-custom-recipes)\n  * [Weight fields (Depositing intensive quantities)](#weight-fields-depositing-intensive-quantities)\n  * [Count Sorting](#count-sorting)\n- [Deposition algorithm](#deposition-algorithm)\n- [Thread safety](#thread-safety)\n\n<!-- tocstop -->\n\n## Installation\n\n```shell\npython -m pip install --upgrade pip\npython -m pip install gpgi\n```\n\n## Supported applications\n\nA 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.\n\nParticles are defined as points that live within the grid's bounds.\n\nDeposition is the action of going from particle description\nto a grid description of a field.\nIt is useful to analyze, compare and combine simulation data that exists in a combination of the two formalisms.\nThis process is not reversible as it degrades information.\n\nFor instance, here's a simple overlay of a particle set (red dots)\nagainst a background that represents the deposited particle count.\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/neutrinoceros/gpgi/main/tests/pytest_mpl_baseline/test_2D_deposit_ngp.png\" width=\"600\"></a>\n</p>\n\nThis example illustrates the simplest possible deposition method, \"Nearest Grid Point\" (NGP), in which each particle contributes only to the cell\nthat contains it.\n\nMore refined methods are also available.\n\n### Builtin deposition methods\n| method name             | abbreviated name | order |\n|-------------------------|:----------------:|:-----:|\n| Nearest Grid Point      | NGP              | 0     |\n| Cloud in Cell           | CIC              | 1     |\n| Triangular Shaped Cloud | TSC              | 2     |\n\n*new in gpgi 0.12.0*\nUser-defined alternative methods may be provided to `Dataset.deposit` as `method=my_func`.\nTheir signature need to be compatible with `gpgi.typing.DepositionMethodT` or `gpgi.typing.DepositionMethodWithMetadataT` .\n\n### Supported geometries\n| geometry name | axes order                  |\n|---------------|-----------------------------|\n| cartesian     | x, y, z                     |\n| polar         | radius, z, azimuth          |\n| cylindrical   | radius, azimuth, z          |\n| spherical     | radius, colatitude, azimuth |\n| equatorial    | radius, azimuth, latitude   |\n\n## Time complexity\n\nAn important step in performing deposition is to associate particle indices to cell indices. This step is called \"particle indexing\".\nIn directions where the grid is uniformly stepped (if any), indexing a particle is an O(1) operation.\nIn 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).\n\n\n## Usage\n\nThe API consists in a `load` function, which returns a `Dataset` object.\n\n**Load** data\n\n```python\nimport numpy as np\nimport gpgi\n\nnx = ny = 64\nnparticles = 600_000\n\nprng = np.random.RandomState(0)\nds = gpgi.load(\n    geometry=\"cartesian\",\n    grid={\n        \"cell_edges\": {\n            \"x\": np.linspace(-1, 1, nx),\n            \"y\": np.linspace(-1, 1, ny),\n        },\n    },\n    particles={\n        \"coordinates\": {\n            \"x\": 2 * (prng.normal(0.5, 0.25, nparticles) % 1 - 0.5),\n            \"y\": 2 * (prng.normal(0.5, 0.25, nparticles) % 1 - 0.5),\n        },\n        \"fields\": {\n            \"mass\": np.ones(nparticles),\n        },\n    },\n)\n```\nThe `Dataset` object holds a `grid` and a `particles` attribute,\nwhich both hold a `fields` attribute for accessing their data.\nBut more importantly, the `Dataset` has a `deposit` method to\ntranslate particle fields to the grid formalism.\n\n**Deposit Particle fields on the grid**\n\n```python\nparticle_mass = ds.deposit(\"mass\", method=\"nearest_grid_point\")  # or \"ngp\" for shorts\n```\n\n**Visualize**\nIn this example we'll use `matplotlib` for rendering, but note that `matplotlib` is not a dependency to `gpgi`\n```python\nimport matplotlib.pyplot as plt\n\nfig, ax = plt.subplots()\nax.set(aspect=1, xlabel=\"x\", ylabel=\"y\")\n\nim = ax.pcolormesh(\n    \"x\",\n    \"y\",\n    particle_mass.T,\n    data=ds.grid.cell_edges,\n    cmap=\"viridis\",\n)\nfig.colorbar(im, ax=ax)\n```\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/neutrinoceros/gpgi/main/tests/pytest_mpl_baseline/test_readme_example.png\" width=\"600\"></a>\n</p>\n\nThe example script given here takes about a second (top to bottom).\n\n\n\n### Supplying arbitrary metadata\n*new in gpgi 0.4.0*\n\n`Dataset` objects have a special attribute `metadata` which is a dictionary with string keys.\nThis attribute is meant to hold any special metadata that may be relevant for labelling or processing (e.g. simulation time, author, ...).\nMetadata can be supplied at load time as\n```python\nds = gpgi.load(\n    geometry=\"cartesian\",\n    grid=...,\n    particles=...,\n    metadata={\"simulation_time\": 12.5, \"author\": \"Cl\u00e9ment Robert\"}\n)\n```\n\n\n### Boundary conditions\n*new in gpgi 0.5.0*\n\nWith CIC and TSC deposition, particles contribute to cells neighbouring the one\nthat contains them. For particles that live in the outermost layer of the\ndomain, this means some of their contribution is lost. This behavior\ncorresponds to the default `'open'` boundary condition, but `gpgi` has builtin\nsupport for more conservative boundary conditions.\n\nBoundary conditions can selected per field, per axis and per side. Builtin\nrecipes all perform linear combinations of ghost layers (same-side and opposite\nside) and active domain layers (same-side and opposite side), and replace the\nsame-side active layer with the result.\n\nUser-selected boundary conditions take the form of an optional argument to\n`Dataset.deposit`, as dictionary with keys being axes names, and values being\n2-tuples of boundary conditions names (for left and right side respectively).\nFor instance, here's how one would require periodic boundary conditions on all axes:\n\n```python\nds.deposit(\n    \"mass\",\n    method=\"cic\",\n    boundaries={\n        \"x\": (\"periodic\", \"periodic\"),\n        \"y\": (\"periodic\", \"periodic\"),\n    }\n)\n```\nUnspecified axes will use the default `'open'` boundary.\n\n\n#### Builtin recipes\n\n| boundary conditions     | description                                           | conservative ? |\n|-------------------------|-------------------------------------------------------|:--------------:|\n| open (default)          | no special treatment                                  | no             |\n| periodic                | add opposite ghost layer to the active domain         | yes            |\n| wall                    | add same-side ghost layer to the active domain        | yes            |\n| antisymmetric           | subtract same-side ghost layer from the active domain | no             |\n\n\n#### Define custom recipes\n\n`gpgi`'s boundary recipes can be customized. Let's illustrate this feature with a simple example.\nSay we want to fix the value of the deposited field in some outer layer.\nThis is done by defining a new function on the user side:\n\n```python\ndef ones(\n    same_side_active_layer,\n    same_side_ghost_layer,\n    opposite_side_active_layer,\n    opposite_side_ghost_layer,\n    weight_same_side_active_layer,\n    weight_same_side_ghost_layer,\n    weight_opposite_side_active_layer,\n    weight_opposite_side_ghost_layer,\n    side,\n    metadata,\n):\n   return 1.0\n```\nwhere all first eight arguments are `numpy.ndarray` objects with the same shape (which includes ghost padding !),\nto which the return value must be broadcastable, `side` can only be either\n`\"left\"` or `\"right\"`, and `metadata` is the special `Dataset.metadata`\nattribute. Not all arguments need be used in the body of the function, but this\nsignature is required.\n\nThe method must then be registered as a boundary condition recipe as\n```python\nds.boundary_recipes.register(\"ones\", ones)\n```\nwhere the associated key (here `\"ones\"`) is arbitrary. The recipe can now be\nused exactly as builtin ones, and all of them can be mixed arbitrarily.\n```python\nds.deposit(\n    \"mass\",\n    method=\"cic\",\n    boundaries={\n        \"x\": (\"ones\", \"wall\"),\n        \"y\": (\"periodic\", \"periodic\"),\n    }\n)\n```\n\nNote that all first eight arguments in a boundary recipe function should\nrepresent an *extensive* physical quantity (as opposed to *intensive*). When\ndepositing an *intensive* quantity `u`, a weight field `w` should be supplied\n(see next section), in which case, the first four arguments represent `u*w` and\nthe following four represent *w*, so that *u* can still be obtained within the\nfunction as a ratio if needed.\n\n\n### Weight fields (Depositing intensive quantities)\n*new in gpgi 0.7.0*\n\nFundamentally, deposition algorithms construct on-grid fields by performing\n*summations*. An implication is that the physical quantities being deposited are\nrequired to be *extensive* (like mass or momentum). *Intensive* quantities (like\nvelocity or temperature) require additional operations, and necessitate the use\nof an additional *weight field*.\n\nThis section provides showcases their *usage*. For a detailed explanation of the deposition algorithm for intensive quantities, see [Deposition algorithm](#deposition-algorithm).\n\nIn order to deposit an *intensive* field (e.g., `vx`), an additional `weight_field` argument must be provided as\n```python\nds.deposit(\n    \"vx\",\n    method=\"cic\",\n    boundaries={\n        \"y\": (\"periodic\", \"periodic\"),\n        \"x\": (\"antisymmetric\", \"antisymmetric\"),\n    },\n    weight_field=\"mass\",\n    weight_field_boundaries={\n        \"y\": (\"periodic\", \"periodic\"),\n        \"x\": (\"open\", \"open\"),\n    },\n)\n```\n\nBoundary recipes may be also associated to the weight field with the\n`weight_field_boundaries` argument. This arguments becomes *required* if\n`boundaries` and `weight_field` are both provided.\n\nCall `help(ds.deposit)` for more detail.\n\n\n### Count Sorting\n*new in gpgi 0.14.0*\n\ngpgi can load arbitrarily ordered particle sets, though deposition algorithms\nperform 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.\n\nParticles may be sorted by a [counting sort algorithm](https://en.wikipedia.org/wiki/Counting_sort),\nas\n\n```python\nds = ds.sorted()\n```\nNote that this method returns a *copy* of the dataset, so it will best perform for datasets that, at most, fit in half your RAM.\n\nThis operation is costly in itself, so there may be a trade-off depending on how many\ndepositions one needs to perform on a given dataset before tossing it out.\n\nBy default, axes are weighted in the order that's optimal for gpgi's deposition routines,\nbut arbitrary priority order may be specified as, for instance\n```python\nds = ds.sorted(axes=(1, 0))\n```\n\nUse the `Dataset.is_sorted` method to check whether particles are already sorted without\nperforming the sort. `Dataset.is_sorted` accepts an `axes` argument just like `Dataset.sorted`. This is useful for testing and comparative purposes.\n\n\n## Deposition algorithm\n\nThis section provides details on the general deposition algorithm, as\nimplemented in `gpgi`.\n\nWithout loss of generality, we will illustrate how  an *intensive* field (`v`)\nis deposited, since this case requires the most computational steps. As it\nhappens, depositing an *extensive* field (`w`) separately is actually part of\nthe algorithm.\n\n**Definitions**\n\n- `v` is an intensive field that we want to deposit on the grid\n- `w` is an extensive field that will be used as weights\n- `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)\n\n`u(i)`, `v(i)` and `w(i)` are defined for each particle `i`.\n\nWe note `U(x)`, `V(x)` and `W(x)` the corresponding on-grid fields, where `V(x)`\nis the final output of the algorithm. These are defined at grid cell centers\n`x`, within the *active domain*.\n\nLast, we note `U'(x)`, `V'(x)` and `W'(x)` the *raw* deposited fields, meaning\nno special treatment is applied to the outermost layers (boundary conditions).\nThese are defined at grid cell centers, *including one ghost layer* that will be\nused to apply boundary conditions.\n\n**Algorithm**\n\n1) `W'` and `U'` are computed as\n```\nW'(x) = \u03a3 c(i,x) w(i)\nU'(x) = \u03a3 c(i,x) w(i) v(i)\n```\nwhere `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.\n\n2) boundary conditions are applied\n```\nW(x) = W_BCO(W', 1, metadata)\nU(x) = U_BCO(U', W', metadata)\n```\nwhere `W_BCO` and `U_BCO` denote arbitrary boundary condition operators\nassociated with `W` and `U` respectively, and which take 3 arguments,\nrepresenting the field to be transformed, its associated weight field and a\nwildcard `metadata` argument which may contain any additional data relevant to\nthe operator.\n\nNote `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](#define-custom-recipes).\n\n3) Finally, `V(x)` is obtained as\n```\nV(x) = (U/W)(x)\n```\n\n\n## Thread safety\n\nStarting in gpgi 2.0.0, thread safety is guaranteed in `Dataset.host_cell_index`\ncomputation and `Dataset.deposit`, and both operations release the\nGIL (Global Interpreter Lock) around their respective hotloops. Thread safety is\nalso tested against the experimental free-threaded build of Python 3.13.\n\nNote that, by default, `Dataset.deposit` still uses a lock per `Dataset`\ninstance, which in the most general case is preferable since concurrently\ndepositing many fields can cause catastrophic degradations of performances as\nit encourages cache misses. Optimal performance is however application-specific,\nso this strategy can be overridden using the `lock` parameter:\n- using `lock=None` will not use any lock, which in restricted conditions\n  leads to better walltime performances\n- alternatively, an externally managed `threading.Lock` instance may be supplied\n\n`Dataset.boundary_recipes.register` is also thread-safe: registering a shared\nfunction multiple times is supported, but an error is raised in case one\nattempts registering a different function under an existing key.\n",
    "bugtrack_url": null,
    "license": "GPL-3.0",
    "summary": "A Generic Particle+Grid Interface",
    "version": "2.0.0",
    "project_urls": {
        "Changelog": "https://github.com/neutrinoceros/gpgi/blob/main/CHANGELOG.md",
        "Homepage": "https://github.com/neutrinoceros/gpgi"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c34dd91caf2551457a706b5d6dc5b13f79dbe3656cc3976d2497b51f0eb7871f",
                "md5": "507cf3288faba1bcf635eee1a9a70f40",
                "sha256": "cee4af4a456520cbdccc82ecf8c63ab9c5c04feac508fd773cd1f046c2b42037"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "507cf3288faba1bcf635eee1a9a70f40",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.11",
            "size": 198241,
            "upload_time": "2024-10-09T09:27:29",
            "upload_time_iso_8601": "2024-10-09T09:27:29.242490Z",
            "url": "https://files.pythonhosted.org/packages/c3/4d/d91caf2551457a706b5d6dc5b13f79dbe3656cc3976d2497b51f0eb7871f/gpgi-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ec9ddd2a1df050a3018e8e046047f461c76945455fb0a946a746163210f4231c",
                "md5": "e5f9ca1d5c8388e421ecdc807a518296",
                "sha256": "e30acd3055355ede823b2cdbe8f47056ad94a1f857c68d056fb284beeb4e4002"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "e5f9ca1d5c8388e421ecdc807a518296",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.11",
            "size": 169634,
            "upload_time": "2024-10-09T09:27:30",
            "upload_time_iso_8601": "2024-10-09T09:27:30.770271Z",
            "url": "https://files.pythonhosted.org/packages/ec/9d/dd2a1df050a3018e8e046047f461c76945455fb0a946a746163210f4231c/gpgi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fe32ee62eb3dc10b9854729ae25a98d312ef25ae3d3e3f8feb4f36dae6ce7659",
                "md5": "fb8f1f31b1c27e127e703f6f7a1859fd",
                "sha256": "71a835ff50819db7b2b2c2b011ff332e04cb0e1761f7efb185b4625da6429d17"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "fb8f1f31b1c27e127e703f6f7a1859fd",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.11",
            "size": 1211343,
            "upload_time": "2024-10-09T09:27:32",
            "upload_time_iso_8601": "2024-10-09T09:27:32.593907Z",
            "url": "https://files.pythonhosted.org/packages/fe/32/ee62eb3dc10b9854729ae25a98d312ef25ae3d3e3f8feb4f36dae6ce7659/gpgi-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7c6c96e92ce029d7acefeed8dfe6d341f834910beb98e6cb734568ec9a1be4f3",
                "md5": "6587d66bcaa63a1bc119f59e030bc3be",
                "sha256": "d6efd1dbdb56bcabd80bf3d2d0ecafaf78d76b165e04c71dcef3fcf426486a33"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "6587d66bcaa63a1bc119f59e030bc3be",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.11",
            "size": 1263404,
            "upload_time": "2024-10-09T09:27:34",
            "upload_time_iso_8601": "2024-10-09T09:27:34.295669Z",
            "url": "https://files.pythonhosted.org/packages/7c/6c/96e92ce029d7acefeed8dfe6d341f834910beb98e6cb734568ec9a1be4f3/gpgi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "38996018cb2ee81b7f995950628c868151a8b93f0fba30cb1ccbf55c9704e9cf",
                "md5": "b10a6da0955d78ceb5ad527e3a9c4d4b",
                "sha256": "922c6654fc4afb569a961f8f21f9c8fe2fbbbc179dd2fc122a06b8d67cb69d15"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "b10a6da0955d78ceb5ad527e3a9c4d4b",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.11",
            "size": 179601,
            "upload_time": "2024-10-09T09:27:36",
            "upload_time_iso_8601": "2024-10-09T09:27:36.741879Z",
            "url": "https://files.pythonhosted.org/packages/38/99/6018cb2ee81b7f995950628c868151a8b93f0fba30cb1ccbf55c9704e9cf/gpgi-2.0.0-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bb9dda89253d3b4490cb55ee4132ceb635c9f73627214b66dc27b83a3b2a7b47",
                "md5": "d03f001ee27fbb7fdd6b141aee977fe7",
                "sha256": "65c62c94fd9f0f23110d76be08249577a422710aa5b206c627d6e3ca8420f868"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d03f001ee27fbb7fdd6b141aee977fe7",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.11",
            "size": 206007,
            "upload_time": "2024-10-09T09:27:37",
            "upload_time_iso_8601": "2024-10-09T09:27:37.888618Z",
            "url": "https://files.pythonhosted.org/packages/bb/9d/da89253d3b4490cb55ee4132ceb635c9f73627214b66dc27b83a3b2a7b47/gpgi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a4e4d180868da265d7c7d8298b0a72d435bdbcc281356852517dfb670cbbec52",
                "md5": "fe2bd9acbd065353f31037fd89d049a9",
                "sha256": "1cd229779fa042752027c80521a22f58ef336396357efc2567ce61002394a39e"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "fe2bd9acbd065353f31037fd89d049a9",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.11",
            "size": 171884,
            "upload_time": "2024-10-09T09:27:39",
            "upload_time_iso_8601": "2024-10-09T09:27:39.635297Z",
            "url": "https://files.pythonhosted.org/packages/a4/e4/d180868da265d7c7d8298b0a72d435bdbcc281356852517dfb670cbbec52/gpgi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "084557c3c65dfc79d75980c02e431c9525ab7b79b8afae80b814fc4195b894e8",
                "md5": "b5ea2c9a3297adfc6b663897eb698454",
                "sha256": "baf7dbdfe42ba8aabd638456dd5f8dfb6a90e816b9f4a3d10e1c4f0cc8559fa6"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "b5ea2c9a3297adfc6b663897eb698454",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.11",
            "size": 1223490,
            "upload_time": "2024-10-09T09:27:41",
            "upload_time_iso_8601": "2024-10-09T09:27:41.387288Z",
            "url": "https://files.pythonhosted.org/packages/08/45/57c3c65dfc79d75980c02e431c9525ab7b79b8afae80b814fc4195b894e8/gpgi-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d585ed2dedcae8ce175c21001bea8da28bbd2bf36c417914720a898a2fac87f5",
                "md5": "d12f14b6d137c4314be132da6ac8237e",
                "sha256": "033bca7b96540db659fcbc3f48da242332a672a3af0e8f04bb4f1f24a6873360"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d12f14b6d137c4314be132da6ac8237e",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.11",
            "size": 1228482,
            "upload_time": "2024-10-09T09:27:42",
            "upload_time_iso_8601": "2024-10-09T09:27:42.717989Z",
            "url": "https://files.pythonhosted.org/packages/d5/85/ed2dedcae8ce175c21001bea8da28bbd2bf36c417914720a898a2fac87f5/gpgi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "215afe1b9ddc01d3e89e2c2a8f8aeb2ec9efee40ef314e4fcd06e5da9e5d2fa5",
                "md5": "07277be0a58c24514048b701ebe74d79",
                "sha256": "8682653f00e8459b16d3b8c3ea1b0a3a3738ae389f63c1160fe50b520882de2a"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp312-cp312-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "07277be0a58c24514048b701ebe74d79",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.11",
            "size": 182418,
            "upload_time": "2024-10-09T09:27:44",
            "upload_time_iso_8601": "2024-10-09T09:27:44.534551Z",
            "url": "https://files.pythonhosted.org/packages/21/5a/fe1b9ddc01d3e89e2c2a8f8aeb2ec9efee40ef314e4fcd06e5da9e5d2fa5/gpgi-2.0.0-cp312-cp312-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8e58b1721124999309de28a6bc072821f73a08107b2047b338740a1a06433095",
                "md5": "999f9e98759e33897009f082ae75123f",
                "sha256": "5578ea252fa9f9c79b6f1cbcce30ba7797125f2d15e304fb9ae626d4816446d6"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl",
            "has_sig": false,
            "md5_digest": "999f9e98759e33897009f082ae75123f",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.11",
            "size": 203313,
            "upload_time": "2024-10-09T09:27:45",
            "upload_time_iso_8601": "2024-10-09T09:27:45.645293Z",
            "url": "https://files.pythonhosted.org/packages/8e/58/b1721124999309de28a6bc072821f73a08107b2047b338740a1a06433095/gpgi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "14af464bf1bd0ecc96fb6fe59742234281c26cdb2557709e91661bde2735c9fb",
                "md5": "a6a6cda7269554a6025790000dd56892",
                "sha256": "35957754e69034bf5c89a0a18fd1c2b88e00fdf130bcb15d2709c1b18a543994"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "a6a6cda7269554a6025790000dd56892",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.11",
            "size": 171336,
            "upload_time": "2024-10-09T09:27:47",
            "upload_time_iso_8601": "2024-10-09T09:27:47.389665Z",
            "url": "https://files.pythonhosted.org/packages/14/af/464bf1bd0ecc96fb6fe59742234281c26cdb2557709e91661bde2735c9fb/gpgi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d85fd4a37dea0c94a0ad1cc585a3d89707f39d11e3851821c0145630966142e5",
                "md5": "76b88aca16dce5ca74e8386ca35b0fa0",
                "sha256": "b4e3c7c59f4afd133af39ca1df30314b96dd9cec1889df434b64115b9b374591"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "76b88aca16dce5ca74e8386ca35b0fa0",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.11",
            "size": 1199056,
            "upload_time": "2024-10-09T09:27:48",
            "upload_time_iso_8601": "2024-10-09T09:27:48.589298Z",
            "url": "https://files.pythonhosted.org/packages/d8/5f/d4a37dea0c94a0ad1cc585a3d89707f39d11e3851821c0145630966142e5/gpgi-2.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dbcd4b8dbf747490e215dcbdd082acbfcc2046daf33d41f56221612a7b94b0ae",
                "md5": "5b758f443211c98591c124291a4f78af",
                "sha256": "6f3a1b5dcdb32052149fa2bfeac1c840bd95bd13ee32495da70646bdd3180872"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "5b758f443211c98591c124291a4f78af",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.11",
            "size": 1220799,
            "upload_time": "2024-10-09T09:27:49",
            "upload_time_iso_8601": "2024-10-09T09:27:49.999333Z",
            "url": "https://files.pythonhosted.org/packages/db/cd/4b8dbf747490e215dcbdd082acbfcc2046daf33d41f56221612a7b94b0ae/gpgi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "65c1b2726435e304ea3d86ec7b8240a4a6f366df5c43899487c657c1533eae86",
                "md5": "f0bb670c9c564bb376144adad0f206ac",
                "sha256": "d6e7197bc9aa52f9654cf736e4bbbb56f0d67fb2ac7f6d1c4b198e75cf2d722b"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0-cp313-cp313-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "f0bb670c9c564bb376144adad0f206ac",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.11",
            "size": 182517,
            "upload_time": "2024-10-09T09:27:51",
            "upload_time_iso_8601": "2024-10-09T09:27:51.815162Z",
            "url": "https://files.pythonhosted.org/packages/65/c1/b2726435e304ea3d86ec7b8240a4a6f366df5c43899487c657c1533eae86/gpgi-2.0.0-cp313-cp313-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9d486c8833406d5cc8d1ec6d85302d73acdad1d1776c30076d6adb3ca784d5f9",
                "md5": "2f13a61378f691e92a3d39cbd6ecb1f4",
                "sha256": "c98e6b06dd42e5ce333e6346ca113ca5f6316457f85e7bd78ebe763441825d67"
            },
            "downloads": -1,
            "filename": "gpgi-2.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "2f13a61378f691e92a3d39cbd6ecb1f4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 46415,
            "upload_time": "2024-10-09T09:27:53",
            "upload_time_iso_8601": "2024-10-09T09:27:53.325212Z",
            "url": "https://files.pythonhosted.org/packages/9d/48/6c8833406d5cc8d1ec6d85302d73acdad1d1776c30076d6adb3ca784d5f9/gpgi-2.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-09 09:27:53",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "neutrinoceros",
    "github_project": "gpgi",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "gpgi"
}
        
Elapsed time: 0.35812s