PeriX is a C++20 framework for multiphysics simulation with the Peridynamic Differential Operator (PDDO). It builds a cell-centred peridynamic mesh from a structured, circular-lattice, or imported Gmsh mesh, evaluates nonlocal derivative operators over each node family, and advances the published scalar, phase-field, and fracture models through a JSON input file.
The command-line JSON interface is the supported public interface. This repository does not currently install a C++ SDK or promise API stability for its internal classes.
This release contains only the element formulations and supporting systems needed by the accompanying manuscript:
PoissonElementDiffusionElementCahnHilliardElementPDDODynamicFracElementExplicitPDDOFracElementStressCahnHilliardElementFracStressCahnHilliardElement
Thermal transport and other unpublished element extensions are not included. The input schema rejects element, boundary-condition, initial-condition, and solver options outside the public allow-list.
AMGCL support is an optional public solver extension added for reproducible CPU-only builds. It is not one of the three linear solvers described in the current manuscript, which are the in-house profile LDU solver, Intel MKL PARDISO, and NVIDIA cuDSS.
The built-in circular lattice rounds each circumference-based ring count to a
positive multiple of four and uses a half-angular-step offset. Its
anchornodes group is therefore the four symmetric points of the innermost
ring.
All complete example decks shipped here are two-dimensional. Core mesh, operator, and selected element paths contain three-dimensional support, but this release does not yet provide a validated end-to-end 3D benchmark.
For a node (i), PeriX approximates a derivative of a field (q) by a weighted family sum of differences,
[ D^\alpha q(\mathbf{x}i) \approx \sum{j\in\mathcal{H}i} \left(q_j-q_i\right)g{ij}^{\alpha}V_j , ]
where (\mathcal{H}i) is the horizon family, (V_j) is the neighbour volume,
and (g{ij}^{\alpha}) is obtained from the local PDDO moment system. The
published element kernels require PDMesh.Order >= 2.
| JSON element type | Unknowns | Physics and strong form | Time/discrete treatment | Examples |
|---|---|---|---|---|
poisson |
(u) | (\sigma\nabla^2u+f=0) | Static, assembled nonlinear-driver path | poisson*.json |
diffusion |
(c) | (\dot c-D\nabla^2c-f=0) | Backward Euler | diffusion.json |
cahnhilliard |
(c,\mu) | (\dot c=\nabla\cdot(M(c)\nabla\mu)), (\mu=f'(c)-\kappa\nabla^2c), (f=c\ln c+(1-c)\ln(1-c)+\chi c(1-c)), (M(c)=M_0c(1-c)) | Split second-order system, backward Euler, Picard-frozen mobility, conservative antisymmetric bond flux | spinodal*.json |
pddo_dynamic_frac |
(\mathbf{u}) | (\rho\ddot{\mathbf{u}}=\nabla\cdot\boldsymbol{\sigma}+\mathbf{b}), small-strain isotropic elasticity with irreversible critical-stretch bond failure | Implicit backward-Euler velocity form; damage is committed between converged steps | tensile_plate.json |
explicit_pddo_frac |
(\mathbf{u}) | Same elastodynamic strong form and critical-stretch family as the implicit fracture element | Matrix-free central difference; fixed CFL-limited step | kalthoff_winkler*.json |
stress_cahnhilliard |
(c,\mu,\mathbf{u}) | Cahn-Hilliard transport coupled to (\boldsymbol{\epsilon}^*=\frac{\Omega}{3}(c-c_\mathrm{ref})\mathbf{I}), stress-dependent chemical potential, and small-strain mechanics | Backward-Euler transport with Picard-frozen mobility and conservative antisymmetric bond flux; quasi-static mechanics | stress_cahnhilliard.json |
frac_stress_cahnhilliard |
(c,\mu,\mathbf{u}) | Cahn-Hilliard transport coupled to (\boldsymbol{\epsilon}^*=\frac{\Omega}{3}(c-c_\mathrm{ref})\mathbf{I}), stress-dependent chemical potential, small-strain mechanics, and irreversible bond fracture | Backward Euler; quasi-static mechanics when (\rho=0), inertial mechanics when (\rho>0) | silicon_particle.json |
The implicit and explicit fracture elements share their spatial constitutive model, but their time integration and the instant at which damage is committed are different. Their transient histories should therefore be compared at the model level, not expected to be bitwise identical.
In the coupled fracture/Cahn-Hilliard element, broken-bond health gates the
mechanical terms but does not block or accelerate species transport. The
current implementation directly constructs the bulk-only species stencil at a
boundary; it does not apply a separate scalar surface-rescaling factor to that
coupled stencil. A positive rho, even a small one, selects the inertial
mechanics branch.
CPU, OpenMP, and CUDA paths solve the same public formulations. Floating-point reduction order and different solver algorithms may produce small tolerance-level differences; cross-backend results are not promised to be bitwise identical.
PeriX is currently oriented to Linux and other Unix-like build environments.
| Dependency | When needed | How it is supplied |
|---|---|---|
| CMake 3.21 or newer | Always | System package or CMake distribution |
| C++20 compiler | Always | GCC or Clang with C++20 support |
| Eigen | Always | Vendored snapshot in external/eigen |
| nlohmann/json 3.12.0 | Always | Vendored headers in external/nlohmann |
| OpenMP | Default CPU assembly; AMGCL | Compiler runtime; found by CMake |
| Intel oneAPI MKL | Optional PARDISO solver | USE_ONEAPI=ON; set ONEAPI_DIR or MKLROOT |
| AMGCL | Optional CPU iterative solver | Header-only source/install tree; USE_AMGCL=ON and AMGCL_DIR |
| CUDA Toolkit | Optional CUDA assembly or cuDSS | CUDA_ASSEMBLE=ON or USE_CUDSS=ON; set CUDA_DIR or CUDAToolkit_ROOT |
| NVIDIA cuDSS | Optional GPU direct solver | USE_CUDSS=ON; set CUDSS_DIR |
AMGCL is used through its compile-time builtin/OpenMP backend with
AMGCL_NO_BOOST, so Boost is not required. AMGCL itself is MIT licensed and
is not vendored in this repository.
Gmsh is not required to run the examples: the imported
examples/impact2D.msh mesh is committed. Gmsh is only needed to regenerate it
from examples/impact2D.geo. Python and NumPy are optional and used only by
examples/export_cell_ic.py; they are not build dependencies.
OpenMP assembly is enabled by default:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failureThis always builds the in-house profile LDU solver. Use
-DPARALLEL_ASSEMBLE=OFF for a serial-only build. The default solver is useful
for small problems; its skyline/profile storage can grow rapidly for large
nonlocal systems.
Load the oneAPI runtime environment before configuring and again in each new shell used to run PeriX:
source /path/to/oneapi/setvars.sh
cmake -S . -B build-oneapi \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON \
-DUSE_ONEAPI=ON \
-DONEAPI_DIR=/path/to/oneapi
cmake --build build-oneapi --parallel
ctest --test-dir build-oneapi --output-on-failureIf MKLROOT is already set correctly, ONEAPI_DIR may be omitted. A binary
linked to libmkl_rt still needs the oneAPI library paths at run time.
Point AMGCL_DIR to the directory that contains amgcl/amg.hpp:
cmake -S . -B build-amgcl \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON \
-DUSE_AMGCL=ON \
-DAMGCL_DIR=/path/to/amgcl
cmake --build build-amgcl --parallel
ctest --test-dir build-amgcl --output-on-failureThe public AMGCL configuration is intentionally narrow:
"LinearSolver": {
"type": "amgcl",
"params": {
"tol": 1.0e-10,
"maxiter": 200,
"verbose": false,
"iluk_level": 2,
"solver_threads": 0
}
}These five keys are the only accepted AMGCL parameters. Internally, PeriX uses BiCGSTAB, detects a node block size from one to five, and reuses the preconditioner. If the recomputed true residual misses the requested tolerance, it rebuilds once and then escalates the preconditioner along a fixed ladder:
- smoothed-aggregation AMG with ILU(0) relaxation (the default),
- single-level ILU(0),
- single-level ILU(k), with
iluk_level(default 2, range 1-6) levels of fill.
The escalation matters because the PDDO stencil is dense, non-symmetric and not an M-matrix: a zero-fill incomplete factorization of it can stagnate or even be exactly singular, while ILU(k>=1) converges. Each rung is built only after the cheaper one has been shown to fail on that very matrix, so well-conditioned problems pay nothing. A solve succeeds only when (\lVert b-Ax\rVert_2/\lVert b\rVert_2\leq\texttt{tol}).
solver_threads sizes the OpenMP team used for the AMGCL solve alone; 0 (the
default) derives it from the row count, allowing one thread per 20000 rows up to
eight. AMGCL's builtin backend opens a fresh parallel region for every sparse
kernel, so on a many-core node the barrier latency swamps the arithmetic long
before the cores are saturated: on a 96-core machine the 200x200 Poisson deck
takes 172 s on the default 96-wide team and 1.6 s once the team is sized to the
work. Set the key explicitly only to override that heuristic; it never changes
the assembly team, which always follows OMP_NUM_THREADS.
If a solve still fails, check first that every boundary-ghost row carries a
boundary condition. A ghost wall left without one keeps a one-sided PD equation,
which makes the Jacobian nearly rank deficient and progressively worse under
mesh refinement -- an iterative solve may not converge at all, and a direct
solve silently loses accuracy and reproducibility. PeriX reports such rows at
startup, naming the ghost groups and DoF slots involved; the cure is to state
the natural condition (a zero neumann or pdtraction BC) on the free walls.
PARDISO and AMGCL may be enabled in the same CPU build.
CUDA assembly and the cuDSS linear solver are independent options. Enable both for the complete implicit GPU path:
cmake -S . -B build-gpu \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON \
-DCUDA_ASSEMBLE=ON \
-DUSE_CUDSS=ON \
-DCUDA_DIR=/path/to/cuda \
-DCUDSS_DIR=/path/to/cudss \
-DCMAKE_CUDA_ARCHITECTURES=89
cmake --build build-gpu --parallel
ctest --test-dir build-gpu --output-on-failureReplace 89 with the compute capability of the target GPU. CMake validates
the CUDA toolkit, cuBLAS, cuDSS headers, and cuDSS library before enabling this
configuration.
The two JSON selections are also independent:
"JobSystem": {
"type": "static",
"assemble": "cuda"
},
"LinearSolver": {
"type": "cudss"
}assemble may be serial, openmp, or cuda when its build option is
available. An explicit fracture deck is matrix-free and must not contain
LinearSolver or NonlinearSolver blocks.
PeriX writes results next to the input JSON file. To keep the source tree clean, copy a deck and any relative assets to a separate run directory.
The smallest end-to-end example is the AMGCL Poisson deck:
mkdir -p /tmp/perix-poisson-amgcl
cp examples/poisson_amgcl.json /tmp/perix-poisson-amgcl/
cd /tmp/perix-poisson-amgcl
/path/to/PeriX/build-amgcl/perix -i poisson_amgcl.jsonFor an imported-mesh example, copy the mesh with the deck:
mkdir -p /tmp/perix-kalthoff
cp examples/kalthoff_winkler.json examples/impact2D.msh /tmp/perix-kalthoff/
cd /tmp/perix-kalthoff
/path/to/PeriX/build-oneapi/perix -i kalthoff_winkler.jsonThe command-line form is:
perix -i input.json [--read-only]
--read-only validates the public schema and builds or imports the base mesh;
it does not assemble or solve the numerical problem. It may still write a base
mesh if the deck sets "savemesh": true.
The normal VTU output set is:
<input>-pdmesh.vtu<input>-PD-results[-NNNNN].vtuand its.pvdtime-series index<input>-Element-results[-NNNNN].vtuand its.pvdtime-series index<input>-mesh.vtuwhenMesh.savemeshis enabled
OutputSystem.format may be vtu, exodus, or both. VTU is the default.
Transient output is written at step zero, at each
OutputSystem.interval, and at the final step. The VTU writer currently uses
ASCII arrays, which are easy to inspect but can be large.
| Deck | Model | Assembly | Linear solver |
|---|---|---|---|
poisson_amgcl.json |
Poisson, small public smoke case | Serial | AMGCL |
poisson.json |
Poisson, large CPU case | OpenMP | PARDISO |
poisson_gpu.json |
Poisson GPU case | CUDA | cuDSS |
diffusion.json |
Transient diffusion | OpenMP | PARDISO |
spinodal.json |
Spinodal decomposition | OpenMP | PARDISO |
spinodal_m1_kappa0p01.json |
Spinodal parameter study | OpenMP | PARDISO |
spinodal_m4_kappa0p001.json |
Spinodal parameter study | OpenMP | PARDISO |
tensile_plate.json |
Implicit dynamic fracture | OpenMP | PARDISO |
kalthoff_winkler.json |
Explicit impact fracture | OpenMP | Matrix-free |
kalthoff_winkler_3D.json |
Explicit 3D impact fracture, preset crack planes | OpenMP | Matrix-free |
kalthoff_winkler_gpu.json |
Explicit impact fracture | CUDA | Matrix-free |
silicon_particle.json |
Coupled species/stress/fracture | OpenMP | PARDISO |
examples/M4kappa0.001.i is retained as a legacy comparison input; it is not a
PeriX JSON deck and cannot be passed to perix -i.
The test suite always exercises unit tests, schema validation, and read-only
loading of every public JSON deck. With USE_AMGCL=ON, it also performs a
numerical solver test and a complete poisson_amgcl.json run. Read-only deck
tests alone are not evidence that the large manuscript simulations were
numerically executed.
The public boundary-condition types are:
dirichlet: prescribed value, with optional direct node pinning;neumann: homogeneous generic Neumann condition only;pdtraction: mechanical traction for the PDDO elasticity models;speciesflux: source-form flux on the concentration fieldc.
The public initial profiles are constant, linear, box, circle, ellipse, Gaussian, cosine, and seeded random. Explicit dynamics additionally accepts an initial velocity field. Random initial data must include a seed so manuscript runs remain reproducible.
Give every boundary-ghost row a boundary condition, including the walls that
carry no load. A ghost wall left free keeps a one-sided PD equation instead of a
boundary equation, which makes the Jacobian nearly rank deficient and steadily
worse as the mesh is refined. Stating the natural condition explicitly (a zero
neumann or pdtraction BC on the free walls) does not change the physics; it
is what makes an iterative solve converge and a direct solve reproducible. PeriX
prints a warning at startup naming any ghost group and DoF slot left uncovered.
MeshModify registers cracks before the peridynamic families are built. Cracks
can be listed explicitly as 2D segments under Cracks, or described
geometrically under Presets:
"MeshModify": {
"type": "pre_existing_cracks",
"treatment": "force_only",
"Presets": [
{
"type": "center_crack",
"label": "left_notch",
"center": [0.075, 0.075, 0.0045],
"normal": [1.0, 0.0, 0.0],
"length": 0.05,
"width": 0.009
},
{
"type": "edge_crack",
"side": "left",
"position": 0.05,
"length": 0.02
}
]
}center_cracktakescenter(orcenter_x/center_y/center_z) andlength, plus an optionalangle_degreesorangle_radians.edge_crackstarts on theleft,right,bottomortopwall of the mesh bounding box atpositionand grows inward bylength, unless an angle is given.- On a 2D mesh a preset resolves to a line segment. On a 3D mesh it resolves to
a bounded planar quadrilateral: with no orientation fields the 2D segment is
extruded through the full thickness, while
normal(and optionallyaxisandwidth) builds a general oriented rectangle.examples/kalthoff_winkler_3D.jsonuses the oriented form for the two Kalthoff-Winkler notches.
Cracks and Presets may be combined; at least one of them must be present.
- Configure
Release; debug builds are useful for checking but substantially slower. - Use
JobSystem.assemble: "openmp"for CPU assembly and setOMP_NUM_THREADSto physical cores. On dedicated nodes,OMP_PLACES=coresandOMP_PROC_BIND=closeoften improve locality. - Use PARDISO for robust large CPU implicit runs, cuDSS when the matrix and factorization fit GPU memory, or AMGCL when a lower-memory CPU iterative solve is suitable. The default profile LDU solver is primarily a dependency-free small-problem backend.
- For CUDA builds, compile for the actual GPU architecture. Avoid relying on a generic architecture list when benchmarking.
- Increasing
HorizonRadiusFactoror PDDOOrderenlarges each family, increases matrix density, and raises both operator and solver cost. - Explicit fracture avoids matrix assembly and factorization, but its time step must satisfy the reported CFL-scale stability limit. Reducing output frequency is often essential for long impact simulations.
- For implicit transients, adaptive time stepping can cut back after Newton or
linear-solver failure and grow after easy steps. Set physically meaningful
min_dt,max_dt, and output intervals. - Request only needed derived
OutputSystem.Fields, and increaseOutputSystem.intervalfor long runs. ASCII VTU output can otherwise dominate disk space and wall time. - CUDA operator caching chooses a default heuristic. Benchmark overrides with
PERIX_CUDA_OPCACHE=0orPERIX_CUDA_OPCACHE=1; do not assume one setting is fastest for both 2D and 3D kernels. PERIX_ASSEMBLE_TIMING=1prints CPU assembly breakdowns, andPERIX_CUDSS_TIMING=1prints cuDSS transfer/factor/solve timing. Disable diagnostic timing for production measurements.
This project is supported by:
- Start up fund of the Great Bay University
- General Program of Guangdong Natural Science Foundation, 2025-2027
- Youth Program of the National Natural Science Foundation of China, 2025-2027
PeriX is licensed under GNU GPLv3; see LICENSE. Vendored and optional
third-party dependencies retain their own licenses.