Core primitives: Domain, Geometry, Mesh, Field¶
pinneapple_core (also pp.core) is the shared vocabulary of tensors, geometry and topology. One object
answers the four questions every physics workflow asks of a quantity: its derivative, its divergence, its value
at an arbitrary point, and its integral.
| Class | What it is |
|---|---|
Domain |
A box in any dimension, or a 2D CSG shape. contains, sdf, sample_interior, sample_boundary(return_normals=True), volume. |
Geometry |
A Domain with named boundaries (x_min, x_max, ... for boxes) and mesh(h). |
Mesh |
Simplex mesh (interval, triangle, tetrahedron). Exact P1 gradient, integrate, interpolate, boundary_nodes. |
Field |
Values on a Grid, a Mesh or a PointCloud. |
FunctionField |
A torch callable with the same calls, derivatives by autograd. |
import numpy as np
from pinneapple_core import Field, Geometry
geom = Geometry.box([0, 0], [1, 1])
mesh = geom.mesh(h=0.05)
u = Field.on_mesh(mesh, lambda p: np.sin(np.pi * p[:, 0]) * p[:, 1])
u.gradient() # Field with values (N, 2)
u.integrate() # exact for the piecewise-linear interpolant
u.interpolate(np.array([[0.3, 0.4]]))
Values have shape (N, *components): (N,) scalar, (N, d) vector, (N, c, d) gradient of a vector.
gradient() of a scalar gives (N, d); divergence() needs a vector with d components.
How each support computes the four operations¶
| Support | gradient | interpolate | integrate |
|---|---|---|---|
Grid |
central differences (second-order edges), non-uniform axes allowed | multilinear | trapezoid |
Mesh |
volume-weighted average of the exact cell gradients (exact for linear data) | barycentric (NaN outside, or fill="nearest") |
exact for P1 |
PointCloud |
weighted local least-squares plane over the k nearest neighbours | Delaunay-linear in d >= 2, np.interp in 1D |
weights, else Monte Carlo over a Domain |
FunctionField |
autograd, graph kept (create_graph=True) |
evaluation | Monte Carlo over the Domain, or points + weights |
Notes and limits¶
Meshstores simplices only; surface meshes embedded in 3D (MeshData) are a different object.Geometry.meshis a structured mesh for boxes and a Delaunay mesh of a lattice plus boundary samples for 2D CSG shapes. Good for prototyping; use gmsh for production quality.- The point-cloud Monte Carlo integral assumes the points are uniform samples of the domain.
Operators¶
pinneapple_core.operators (also pp.grad, pp.div, ...) gives one function per operator, the
torch.nn.functional of physics. The same call works on a grid, mesh or point-cloud Field
(no point argument, returns a Field) and on a continuous field (a torch callable or FunctionField,
takes the points x, returns a tensor that keeps the autograd graph).
| Operator | Result |
|---|---|
grad(f) |
(N, d) scalar, (N, c, d) vector |
div(f) |
scalar, from a vector field |
curl(f) |
(N, 3) in 3D, scalar dv/dx - du/dy in 2D |
laplacian(f) |
scalar, or one per component of a vector |
jacobian(f) |
(N, c, d), J[n, i, j] = d f_i / d x_j (c = 1 for a scalar) |
hessian(f) |
(N, d, d) of a scalar |
integrate(f) |
number; continuous fields take n= (Monte Carlo) or points=, weights= |
flux(f, where=None) |
outward flux integral f.n dS; where is a box face ("x_max") or a predicate |
import pinneapple as pp
u = Field.on_mesh(mesh, lambda p: p[:, 0] ** 2 + p[:, 1])
pp.laplacian(u) # Field of 2.0
v = Field.on_mesh(mesh, lambda p: p)
pp.flux(v) == pp.integrate(pp.div(v)) # divergence theorem, exact for P1 data
Accuracy against manufactured solutions (tests/pinneapple_core/test_operators.py, interior of the unit
square, max error relative to the max of the exact value): grid 81x81 ~ 4e-4, mesh 40x40 ~ 3e-3,
4000 scattered points ~ 4e-2 for second derivatives and 3e-3 for first, continuous exact to round-off.
On a mesh the second derivatives are two rounds of gradient recovery, so they are accurate in the
interior and degrade at the boundary; a point cloud uses a local quadratic fit. flux is exact on a
mesh for P1 fields, trapezoid on a grid, and Monte Carlo (error ~ 1/sqrt(n)) on a point cloud or a
continuous field.
Derivatives through solvers: pp.func¶
pinneapple_core.func (pp.func) wraps torch.func (grad, jacobian, jacrev, jacfwd, hessian, vmap)
and adds wrt, the argument to differentiate by name or position:
from pinneapple_core import func
func.jacobian(model, wrt="geometry")(geometry, mu) # d model / d geometry
func.implicit_solve(residual, x0, *params, solver=None)solvesresidual(x, *params) = 0and differentiates the solution with the implicit function theorem,dx/dp = -(dF/dx)^-1 dF/dp. The backward pass is one adjoint solve, independent of the forward iterations, andsolver=accepts any external code (never differentiated). The JacobiandF/dxis dense; second derivatives through the solve ignore its dependence onparams.pinneapple_core.femis a differentiable P1 finite-element Poisson solver (dense matrices, up to a few thousand nodes). Because the vertex coordinates are torch tensors,loss.backward()returns the gradient with respect to the geometry: writepoints = f(design_parameters)and differentiate. Tested against central finite differences of the same pipeline (relative agreement 1e-6 or better for a 1D length and for 2D width, height and bump parameters).
Examples: examples/core_primitives/01_poisson_pinn.py (PINN) and 02_operator_poisson_1d.py (neural operator).