Skip to main content

atomkit

Atom-level analysis toolkit for molecular dynamics trajectories.

Install

pip install atomkit
# or
uv pip install atomkit

Features

SpatialGrid

4D CSR-indexed spatial grid (space + time) for fast region queries on LAMMPS trajectories. Stores as HDF5 with zstd compression and lazy loading (mmap).

CLI:

# Convert LAMMPS trajectory to HDF5 (all timesteps)
atomkit convert simulation.lammpstrj output.h5

# Options
atomkit convert simulation.lammpstrj -c 4.0              # cell size 4Å
atomkit convert simulation.lammpstrj -t 0:100            # timesteps 0-99
atomkit convert simulation.lammpstrj -t 0:1000:10        # every 10th timestep
atomkit convert simulation.lammpstrj -t 0,50,100         # specific timesteps
atomkit convert simulation.lammpstrj --coords unwrapped  # use xu,yu,zu columns
atomkit convert simulation.lammpstrj --coords wrapped    # use x,y,z columns

# Field filtering (smaller files, faster conversion)
atomkit convert traj.lammpstrj out.h5 -f stress_xx,stress_yy,stress_zz  # only these
atomkit convert traj.lammpstrj out.h5 --exclude-fields vx,vy,vz         # exclude these

# Inspect files
atomkit info output.h5                         # show HDF5 grid info
atomkit info simulation.lammpstrj --list-fields  # list fields in LAMMPS file

Python:

from atomkit import SpatialGrid, Region

# Create from LAMMPS file (loads all timesteps by default)
grid = SpatialGrid.from_lammps('simulation.lammpstrj', cell_size=4.0)
grid.save('data.h5')

# Coordinate type: "auto" (default), "unwrapped", "wrapped", or "scaled"
# - unwrapped (xu,yu,zu): actual positions, tracks displacement outside box
# - wrapped (x,y,z): positions wrapped into simulation box
# - scaled (xs,ys,zs): fractional coordinates (0-1)
grid = SpatialGrid.from_lammps('traj.lammpstrj', coord_type='unwrapped')

# Query with 4D regions (returns read-only numpy arrays)
with SpatialGrid.load('data.h5') as grid:
    # Region bounds: (min, max) tuple, single value, or omit for unbounded

    # Single timestep (t=100 means timestep VALUE 100)
    data = grid.query(Region(t=100))

    # Spatial box, all timesteps
    data = grid.query(Region(x=(0, 50), y=(0, 50), z=(0, 50)))

    # Full 4D query
    data = grid.query(Region(x=(0, 50), y=(0, 50), z=(0, 50), t=(0, 1000)))

    # Slice at a point (all cells containing x=25)
    data = grid.query(Region(x=25.0, t=100))

    # Everything
    data = grid.query()  # or Region()

    # Access fields
    data['coords']       # (N, 3) atom positions
    data['stress']       # (N,) stress values
    data['_timestep']    # (N,) which timestep each atom belongs to
    data['_source_idx']  # (N,) original file indices

    # Per-timestep analysis
    for t in np.unique(data['_timestep']):
        mask = data['_timestep'] == t
        print(f"t={t}: mean stress = {data['stress'][mask].mean()}")

    # Fast approximate count (no field reads)
    n_approx = grid.count(Region(x=(0, 50), y=(0, 50), z=(0, 50)))

    # Exact vs cell-level query
    data = grid.query(region)                  # default, exact bounds
    data = grid.query(region, cell_level=True) # faster, includes full boundary cells

    # Add fields later
    grid.add_field('velocity', vel_array)

Region

4D axis-aligned bounding box for space-time queries:

from atomkit import Region

# Flexible bounds specification:
Region(x=(0, 10), y=(0, 10), z=(0, 10))  # Spatial box, all timesteps
Region(t=100)                             # Single timestep, all space
Region(x=5.0)                             # YZ plane at x=5 (slice query)
Region()                                  # Everything (unbounded)

# Region operations
region = Region(x=(0, 100), y=(0, 100), z=(0, 100), t=(0, 1000))
region.volume()                           # Spatial volume
region.subdivide(nx=10, ny=10, nz=10)     # Split into sub-regions
region.with_time(500)                     # Same space, different time
region.expand(padding=5.0)                # Grow bounds

Grid dimensions:

  • grid.n_timesteps - number of timesteps
  • grid.n_atoms - atoms per timestep
  • grid.grid_shape - (nx, ny, nz) spatial cells
  • grid.timestep_values - actual timestep values from trajectory

SourceBox

Original simulation box metadata (bounds, tilt, boundary conditions):

# Consolidated box info from LAMMPS
grid.source_box.bounds      # (xlo, xhi, ylo, yhi, zlo, zhi)
grid.source_box.tilt        # (xy, xz, yz) for triclinic boxes
grid.source_box.boundary    # "pp pp pp" (periodic)
grid.source_box.is_triclinic
grid.source_box.is_valid
grid.source_box.contains(x, y, z)  # handles sheared boxes

Cell Aggregates (CellsView)

O(1) cell-level queries using precomputed cumsum arrays:

# Field selection and slicing (both orderings work)
grid.cells["stress"]                    # Select field
grid.cells["stress"][0, :, :, 5]        # Select + slice
grid.cells[0, :, :, 5]["stress"]        # Slice + select

# O(1) reductions (uses cumsum internally)
grid.cells["stress"].sum()              # Total sum (scalar)
grid.cells["stress"].sum("t", "z")      # Sum over t,z → 2D array (x, y)
grid.cells["stress"].mean()             # Mean = sum/count
grid.cells["stress"].count()            # Atom count in region
grid.cells.count("z")                   # Count projection along z

# Sliced queries (O(1) box sums)
view = grid.cells["stress"][0:5, 10:20, :, 5:15]
view.sum()                              # Sum in this 4D box
view.mean()                             # Mean in this box
view.count()                            # Atoms in this box

# Raw arrays (for custom analysis)
grid.cells["stress"].np                 # Raw sum array (t, nx, ny, nz)
grid.cells.counts                       # Atom counts array
grid.cells.fields                       # List of available fields

# Multiple fields at once
grid.cells.sum()                        # {"stress": ..., "velocity": ...}

See docs/cells-api.md for detailed documentation.

GridView

Create views into subregions (shares underlying data, no copy):

# View by coordinate bounds
view = grid.view(x=(0, 50), z=(10, 100))
view.counts       # sliced counts array
view.box_bounds   # adjusted bounds
view.grid_shape   # subset shape

# View constrained to source box
view = grid.view_source_box()

Trimming

Filter atoms outside source box during load:

# Trim atoms outside LAMMPS box bounds (useful for unwrapped coords)
grid = SpatialGrid.from_lammps('traj.dump', trim_to_source_box=True)

Future Extensions

Marching Cubes / Void Visualization

For visualizing empty space (cracks, voids, delamination) and computing volumes:

# Potential future API
verts, faces = grid.void_mesh(Region(t=100), threshold=0.5)
volume = grid.void_volume(Region(t=100), threshold=0.5)

Grid Alignment

For snapping user-selected regions to grid cell boundaries:

# Potential future API
aligned = AlignedRegion.snap(region, grid, mode='enclosing')

Metadata

Release files for atomkit 0.3.1

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

Source distribution (sdist)

Source distribution for atomkit 0.3.1
File Size Uploaded Provenance
atomkit-0.3.1.tar.gz 93.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for atomkit 0.3.1
File Interpreter ABI Platform
atomkit-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 162.1 kB

Release files / atomkit-0.3.1.tar.gz

Download URL atomkit-0.3.1.tar.gz
Size 93.7 kB
Tags Source
SHA-256 checksum
How to use checksums
745c4d95b2e4a2b07fddb1933d485e5a96bb1d93e8d5ba2d604acc41d359b4f2
BLAKE2b-256 checksum
How to use checksums
e4bffdcc0a75c33a9fd78dd3dd30467568e8b9f5127b1a5a8ad754778a5b0fdf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.6

Release files / atomkit-0.3.1-py3-none-any.whl

Download URL atomkit-0.3.1-py3-none-any.whl
Size 68.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
67412b34d352418007c0d6654958eba32845b64f54e1a31726eafd7b4379fbae
BLAKE2b-256 checksum
How to use checksums
aef3b1e7907d0544370aca59918e751a983d801e9d1b73451a5d2b56a4c3e44d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.6

Security for atomkit 0.3.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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