A GPU-accelerated finite element analysis framework with JAX.
Project description
FEAX
A simulation node in your JAX workflow
Documentation | Install guide | API
FEAX (Finite Element Analysis with JAX) is a pure, statically-composed functional finite element engine. The whole analysis — assembly, boundary conditions, linear/nonlinear solve, post-processing — is expressed as pure functions composed at trace time into a single static XLA program: no hidden state, no Python in the hot loop. That program is fully differentiable and composes with jax.jit, jax.grad, and jax.vmap, so a PDE solve becomes just another differentiable node in your JAX workflow.
Why FEAX?
- Pure & statically composed: the entire FE pipeline is built from pure functions traced into one static XLA graph — deterministic, side-effect-free, and free of per-iteration Python dispatch.
- JAX transformations: every stage works with
jax.jit,jax.grad, andjax.vmap, and arbitrary compositions such asjit(grad(vmap(...))), giving exact gradients through the whole pipeline with no approximation. - Solvers: GPU sparse direct via cuDSS (automatic General / Symmetric / SPD detection), matrix-free Krylov (CG / BiCGSTAB / GMRES), and algebraic multigrid (AMG) — smoothed aggregation through AMJax + PyAMG (
pip install feax[amg]). Optional CPU host-side direct (CHOLMOD / UMFPACK) too. - End-to-end differentiability: gradients flow through assembly, boundary conditions, linear/nonlinear solvers, and post-processing — enabling topology optimization, inverse problems, and design optimization.
- Built-in toolkits: a
genetopology-optimization toolkit (MMA, density filters, Heaviside continuation, adaptive remeshing) and aflatcomputational-homogenization toolkit for periodic unit cells.
Quick Example
A 3D cantilever beam under a surface traction — solved, then differentiated. We build it up block by block.
1. Imports and a mesh. A structured hex mesh of a 100×10×10 box, plus the material constants.
import feax as fe
import jax
import jax.numpy as np
mesh = fe.mesh.box_mesh((100, 10, 10), mesh_size=2)
E, nu = 70e3, 0.3
2. Define the physics. A Problem subclass declares the weak form through
pure callables: get_tensor_map returns the stress as a function of the
displacement gradient (the volume term), and get_surface_maps returns the
applied traction (the surface term). traction_mag is a traced parameter
supplied at solve time — that is what makes the load differentiable.
class LinearElasticity(fe.problem.Problem):
def get_tensor_map(self):
def stress(u_grad, *args):
mu = E / (2. * (1. + nu))
lmbda = E * nu / ((1 + nu) * (1 - 2 * nu))
eps = 0.5 * (u_grad + u_grad.T)
return lmbda * np.trace(eps) * np.eye(self.dim) + 2 * mu * eps
return stress
def get_surface_maps(self):
def surface_map(u, x, traction_mag):
return np.array([0., 0., traction_mag])
return [surface_map]
3. Instantiate the problem. Locator functions mark the boundary faces; the
traction is applied on the right face (location_fns=[right]), vec=3 is the
number of solution components (3D displacement).
left = lambda point: np.isclose(point[0], 0., atol=1e-5)
right = lambda point: np.isclose(point[0], 100., atol=1e-5)
problem = LinearElasticity(mesh, vec=3, dim=3, location_fns=[right])
4. Dirichlet boundary conditions. Clamp the left face (all components zero).
bc_config = fe.DCboundary.DirichletBCConfig([
fe.DCboundary.DirichletBCSpec(location=left, component="all", value=0.)
])
bc = bc_config.create_bc(problem)
5. Traced parameters. The runtime inputs you differentiate/vmap over —
here a uniform surface-traction magnitude. These flow in as the traction_mag
argument of the surface map above.
traction = fe.TracedParams.create_uniform_surface_var(problem, 1e-3)
traced_params = fe.TracedParams(volume_vars=(), surface_vars=[(traction,)])
6. Traced structure (recommended). Collects the mesh-sized structural arrays as traced leaves, so nothing mesh-sized is baked into the compiled program as a constant — important for compile time and memory on large meshes.
ts = fe.TracedStructure.from_problem(problem)
7. Build the solver. create_solver composes assembly + boundary conditions
- linear solve into one differentiable function. With
DirectSolverOptions()it auto-selects cuDSS on GPU (sparse direct on CPU); swap infe.KrylovSolverOptions()orfe.AMGSolverOptions()to change the linear solver with no other changes.
solver = fe.create_solver(problem, bc,
solver_options=fe.DirectSolverOptions(), linear=True,
traced_params=traced_params, traced_structure=ts)
initial = fe.zero_like_initial_guess(problem, bc)
8. Solve, then differentiate. The solver is an ordinary JAX function. Call it
to get the solution; or build a scalar objective that runs the solve and hand
that function to jax.grad — the gradient flows through the entire pipeline
(assembly → BCs → linear solve).
# Solve
sol = solver(traced_params, initial, traced_structure=ts)
# Build an objective on top of the solver, then differentiate it
def objective(params):
u = solver(params, initial, traced_structure=ts)
return np.sum(u ** 2)
grads = jax.grad(objective)(traced_params)
See examples/ for more, including topology optimization. Installation and solver setup are covered in the install guide.
License
FEAX is licensed under the GNU General Public License v3.0. See LICENSE for the full license text.
Acknowledgments
FEAX builds upon the excellent work of:
- JAX for automatic differentiation and compilation
- JAX-FEM for inspiration and reference implementations
- Spineax for cuDSS solver implementation
- AMJax and PyAMG for the algebraic multigrid (AMG) solver
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file feax-0.7.0.tar.gz.
File metadata
- Download URL: feax-0.7.0.tar.gz
- Upload date:
- Size: 310.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd27f53dedbe131914414493cbdfca2a56d3e8020c15a0dadd7616ad22dbce2e
|
|
| MD5 |
bc35b0ae825b2acad9b45cd31cdf5cdf
|
|
| BLAKE2b-256 |
7b2445761a44efd40c676be7b6e55a6414365b582256ed8a2786c74d98998377
|
File details
Details for the file feax-0.7.0-py3-none-any.whl.
File metadata
- Download URL: feax-0.7.0-py3-none-any.whl
- Upload date:
- Size: 294.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1f3a98eea2f0c6a5f4427667a110cc6e81227c81b578f7493ca7a8aea0b804a
|
|
| MD5 |
a9a26bdebb8e087631c622e2cbdb74c0
|
|
| BLAKE2b-256 |
66fd73a054f3925957415be461aed1f21ebb7dc2da6f5e6ffb910b2edd539a91
|