Skip to main content

Quickstart

Install​

pip install -e .          # core (torch, warp-lang, numpy)
# or with test tooling:
pip install -e ".[dev]"

Precision is chosen before first import through an environment variable (it is baked into every Warp kernel at import time and cannot change mid-process):

export warpSPHCore_PRECISION=float32   # default; float64 for high-accuracy runs
# export warpSPHCore_DIM=3 # optional: pin the spatial dimension

A minimal run: density and gradient​

import torch
import warp as wp
import warpSPHCore as sph
from warpSPHCore import (
ParticleState, OperationProperties, DomainDescription,
WarpOperation, KernelFunctions, GradientScheme, SupportScheme,
OperationDirection,
)
from warpSPHCore.util import generateNeighborTestData
from warpSPHCore.renorm import computeRenormalizationMatrices

wp.init()
device = "cuda"

# 1. Build a state: an nx^d lattice in [-1, 1]^d with ~55 neighbors per particle.
nx, target_neighbors, dim, periodic = 32, 55, 2, True
positions, supports, n, domain, dx = generateNeighborTestData(
nx, target_neighbors, dim, periodic, device
)
masses = torch.full_like(supports, dx**dim) # unit nominal density
kinds = torch.zeros(n, dtype=torch.int32, device=device)
particles = ParticleState(positions, supports, masses, kinds)

# 2. The one entry point. `warpOperation` dispatches on
# OperationProperties.operation; `adjacency=None` builds a
# CompactHashMap on the fly (pass an AdjacencyList or a
# CompactHashMap to reuse one across many calls).
properties = OperationProperties(
kernel=KernelFunctions.Wendland2,
operation=WarpOperation.Density,
supportMode=SupportScheme.Gather,
operationMode=OperationDirection.AllToAll,
)
rho = sph.warpOperation(particles, properties, domain)
particles.densities = rho
density_properties = properties
print("density:", rho.shape) # [N]

# 3. A gradient of a vector field, Difference scheme.
vec = torch.sin(torch.pi * positions) # smooth on the periodic domain
properties = OperationProperties(
kernel=KernelFunctions.Wendland2,
operation=WarpOperation.Gradient,
supportMode=SupportScheme.Gather,
operationMode=OperationDirection.AllToAll,
gradientMode=GradientScheme.Difference,
)
grad = sph.warpOperation(
particles, properties, domain,
queryValues=vec, referenceValues=vec,
)
print("gradient:", grad.shape) # [N, 2] -> [N, 2, 2]

The same call pattern covers every operator — see API reference and the per-operator pages for which OperationProperties fields and which value/volume/correction arguments each one consumes.

Adding corrections​

from warpSPHCore.crk import computeCRKFactors

# CRK correction (moment-based linear reproduction):
apparent_area, crk_density, crk_state = computeCRKFactors(
particles, domain, KernelFunctions.Wendland2,
)

# Gradient renormalization (De Courcy et al. 2024 style):
renorm = computeRenormalizationMatrices(
particles, properties, domain,
referenceVolumes=apparent_area,
) # -> (C, eigVals, RenormalizationState)

grad_crk = sph.warpOperation(
particles, properties, domain,
queryValues=vec, referenceValues=vec,
referenceVolumes=apparent_area,
crkState=crk_state, # CRK-corrected kernel gradient
renormalizationState=renorm[2], # ... then renormalized
)

A correction is "on" exactly when its state object is passed — there are no boolean flags in the state API (the flat sphOperation_warp keeps useCRK/useVolume/... flags for manual callers, validated against the matching tensors).

Differentiating an operator​

Reverse mode — gradients w.r.t. positions, supports, masses, densities and the field values:

positions.requires_grad_(True)
rho = sph.warpOperation(particles, density_properties, domain)
rho.sum().backward()
print(positions.grad.shape)

Forward mode — a JVP in any combination of value and geometry directions, through the same public entry point:

from warpSPHCore import ParticleTangentState

dpositions = torch.randn_like(positions)
tangent = ParticleTangentState(positions=dpositions, supports=None, masses=None)
drho = sph.warpOperationJVP(
particles, density_properties, domain,
queryTangentState=tangent,
referenceTangentState=tangent, # move both roles of this physical cloud
)

Every operator's JVP scope — which tangents it accepts, which combinations raise NotImplementedError, and why — is documented on its operator page under JVP / HVP and summarized in Autodiff machinery.

What the notebooks show​

The notebooks/ directory ships Jupyter notebooks that exercise each operator on a lattice: warp_density.ipynb, warp_gradient.ipynb, warp_divergence.ipynb, warp_curl.ipynb, warp_laplacian.ipynb, warp_interpolate.ipynb, warp_renorm.ipynb, warp_profile.ipynb. They use the same warpOperation / warpOperationJVP entry points as this quickstart.

Running the tests​

pytest tests/operations            # CPU + CUDA (CUDA skipped if absent)

The gradcheck suites run as subprocesses at float64 (tests/operations/test_gradcheck_scripts.py) because precision is baked in at import time.

For complete checked compositions, including the density/correction/gradient derivative chain, start with Runnable examples. The density derivative snippets above differentiate the density operator alone; cached correction tensors must be rebuilt inside a differentiated composition.