jpolyd is a C++17 / Python library for Jacobi polynomial approximation on simplices, with a focus on high-order operator construction and hierarchical Poincaré–Steklov (HPS) solvers for elliptic PDEs on simplicial meshes.
The core numerical library supports Jacobi bases on the ctypes wrappers expose the same functionality for validation and higher-level mesh experiments.
The persistent HPS solver supports variable-coefficient elliptic operators in both non-divergence and divergence form. The divergence-form path uses physical co-normal fluxes for interface conservation and supports scalar or facewise polynomial Robin boundary conditions, mixed exterior boundary conditions, and pure Neumann problems with compatibility checking and a configurable boundary-mean gauge. Elementwise polynomial diffusion tensors may be anisotropic or discontinuous across interfaces.
The reference solver backend is dense and precomputed: local elliptic operators are materialized once, the tau-stabilized leaf least-squares problem is factorized with dense QR, and reusable leaf response maps are retained for subsequent boundary data and source terms. Matrix-free and dense/sparse variants remain available for the non-divergence-form solver as alternative research backends; the divergence-form implementation currently requires the Dense backend.
For the underlying simplex Jacobi definitions and notation, see Dunkl & Xu.
Let
denote polynomials of total degree at most
We use the reference simplex in Cartesian coordinates,
The Jacobi parameter vector has length
with componentwise admissibility
Under this convention:
kappa = 0gives the Dirichlet-half / Chebyshev-type simplex weight
kappa = 1/2gives the unweighted simplex measure.
For second-order PDEs, derivative outputs are promoted into the common residual Jacobi family
The variable-coefficient elliptic path currently uses the full trial-degree residual space
The library provides:
-
$D$ -simplex Jacobi basis evaluation in graded total-degree ordering; - multi-index and tail-degree tables;
- tensor-product / collapsed-coordinate simplex quadrature;
- weighted basis evaluation and projection;
- face quadrature and canonical face bases;
- affine reference-to-physical simplex geometry.
Relevant headers include:
include/jbasis.hhinclude/jweight.hhinclude/jquad_tprod.hhinclude/jquad_optim.hhinclude/jgeom.hh
Differentiation changes both polynomial degree and Jacobi parameters. The library therefore separates:
- differentiation operators (
DMat); - Jacobi-family promotion operators (
KMat); - coordinate/Jacobi operators (
JMat).
Known sparse stencils are used when constructing these operators. RefSimplexPrecomp builds and caches the derivative/promotion DAG used by the PDE solvers while retaining dense compatibility maps for validation.
Relevant headers:
include/jdmat.hhinclude/jkmat.hhinclude/jmat.hhinclude/jprecomp.hh
For a coefficient field
using quadrature chosen to resolve the corresponding polynomial triple products. No separate over-integration/de-aliasing stage is added beyond this construction.
A lifted operator-valued Clenshaw implementation is also retained. It is used by the matrix-free backend and as an algebraic reference for verification.
Relevant header:
include/jmult.hh
The library constructs modal trace and normal-derivative maps on every simplex face, including the face permutations/orientations required by a simplicial mesh. The divergence-form solver additionally constructs the physical co-normal flux
The HPS skeleton and prescribed boundary right-hand sides are stored as physical-face-scaled Jacobi moments. Facewise polynomial Robin coefficients, by contrast, are supplied as modal expansion coefficients in the common canonical face basis, without the physical face-measure scaling. This distinction also enters the pure-Neumann gauge construction.
Relevant headers:
include/jtrace.hhinclude/jflux.hhinclude/jconormal.hhinclude/jperms.hhinclude/jgeom.hh
The Poisson path solves constant-coefficient problems on affine simplicial meshes with Robin boundary data
For Poisson,
The local PDE residual lies naturally in
Relevant headers:
include/jlaplace.hhinclude/jleaf.hhinclude/jnode.hhinclude/jmerge.hh
The existing expanded elliptic operator is written in non-divergence form,
In this expanded-form interface, coefficient fields are represented elementwise in the residual Jacobi family. Principal, first-order, and zero-order terms are assembled after derivative/promotion into the common residual space.
The current elliptic residual policy is
so
after projection.
Relevant header:
include/jelliptic.hh
The persistent divergence-form solver implements
on an affine simplicial mesh, with physical co-normal flux
Here
The local diffusion tensor may be anisotropic and discontinuous between elements. The HPS merge enforces cancellation of the two outward co-normal numerical fluxes across an interface; it does not impose continuity of ordinary normal derivatives. The established tau-stabilized merge algebra is reused unchanged.
The divergence-form implementation currently uses the Dense leaf backend only. The polynomial coefficient degrees are specified as p_A, p_b, and p_c; a degree of -1 for p_b or p_c disables that term.
Relevant headers:
include/jdivergence.hhinclude/jconormal.hhinclude/jelliptic.hhinclude/jnode.hhinclude/jmerge.hh
The persistent elliptic solvers support the exterior condition
where update_variable_robin(), using the same canonical face basis for every face; this also accommodates mixed Dirichlet, Neumann, and Robin conditions by choosing the coefficients on each exterior face.
For variable
with a corresponding source-dependent right-hand-side correction
Both source data and variable Robin coefficients can be updated without rebuilding the tree. Root refactorizations for Robin updates are transactional: an invalid update does not overwrite the previously usable root factorization.
A globally pure-Neumann boundary condition is specified by
The default is set_neumann_gauge(eta) and can be changed between solves without rebuilding or refactorizing the HPS tree. The gauge value persists across Robin/Neumann boundary updates and is ignored when the root operator is nonsingular. Changing
The compatibility check uses the left null vector of the assembled root operator, so it applies to nonsymmetric problems with drift; simply checking
Physical face scaling is included in the constant-trace vector and in the unweighted boundary-mean functional, even for arbitrary admissible Jacobi weights. The root implementation applies a safeguarded rank-one correction for the small discrete constant-mode defect when
With a nonsingular pure-flux root operator (for example, a coercive positive-reaction problem), the usual root LU solve is used without imposing a gauge. More general spectral resonances, which may occur for certain reaction coefficients, are not addressed by this one-dimensional constant-nullspace branch.
The compatibility/gauge implementation is in the persistent elliptic/divergence solver APIs; the legacy one-shot Poisson and elliptic mesh-tree entry points still reject globally pure-Neumann problems.
Relevant header:
include/jneumann_root.hh
Each leaf combines the interior PDE equations with trace penalty rows. With trace map
For divergence form,
These maps are then merged hierarchically. Source-transfer maps are retained through the tree so the expensive leaf factorization/materialization can be reused for new source terms and boundary data. The high-order exterior co-normal response is propagated separately when needed for variable-$\beta$ boundary equations.
The elliptic tau parameter is interpreted as a base constant and rescaled for the enlarged residual space,
before the usual face-size/degree scaling is applied. The base constant remains user-configurable; Robin data with nonzero
Relevant headers:
include/jleaf.hhinclude/jnode.hhinclude/jmerge.hhinclude/jmesh.hh
The HPS implementation currently exposes several leaf backends.
This is the current main path and the required backend for divergence form and its high-order co-normal Robin correction.
- direct-quadrature multiplication-matrix materialization;
- dense local elliptic operator;
- tau-stabilized dense stacked system;
- one QR factorization per leaf;
- batched construction of
[U_lambda U_f]; - reusable
SandG_fmaps.
A hybrid experimental path retaining a dense interior operator with sparse/CSC trace and flux maps. It was useful for measuring sparsity and memory tradeoffs, but on the current CPU/OpenBLAS target the dense path is generally faster.
An apply-only path using the derivative DAG and lifted Clenshaw multiplication without materializing the full elliptic operator. This is substantially slower for the tested low/moderate-dimensional CPU cases because multiplication is repeated many times inside LSMR, but it remains useful for:
- high-dimensional problems where dense storage becomes prohibitive;
- memory-constrained settings;
- architectures where sparse/apply-only kernels may be more competitive;
- verification against the dense operator.
Builds/uses both representations and checks dense versus matrix-free actions.
| Solver path | Interior operator | Exterior boundary support | Leaf backend |
|---|---|---|---|
| Poisson / legacy one-shot mesh-tree | Constant-coefficient Laplacian | Scalar Robin; legacy one-shot pure Neumann not enabled | Existing Poisson modes |
Persistent expanded elliptic (HpsEllipticSolver) |
Polynomial non-divergence operator | Scalar and facewise polynomial Robin, mixed conditions, and constant-nullspace Neumann gauge | Dense, with alternative experimental modes |
Persistent divergence form (HpsDivergenceSolver) |
Physical co-normal scalar/variable Robin, mixed conditions, and pure Neumann compatibility/gauge | Dense only |
The numerical implementation assumes affine simplices and the chosen polynomial truncation and quadrature. It does not automatically certify uniform ellipticity of a supplied diffusion tensor, uniqueness of every reaction/convection problem, or well-posedness at exceptional spectral resonances. The current pure-Neumann gauge is specifically the single constant-mode case, not a general multi-nullspace Fredholm solver.
include/ Header-only C++ numerical core
bindings/c_api/include/ C API headers
bindings/c_api/src/ C/C++ wrappers around the template library
bindings/lsmr/ Fortran/C ABI glue for reverse-communication LSMR
third_party/lsmr/ SOL LSMR Fortran sources
python/ Thin ctypes-based Python wrappers
testing/ Python regression, manufactured-solution, and research tests
tex/ Mathematical notes / derivations
src/ C++ regression tests and Jacobi-codec experiments
The numerical-library regression executables in src/ include tests for:
JMat,DMat, andKMatstencil construction/caching;- derivative/promotion DAG compatibility;
- direct-quadrature multiplication matrices versus lifted Clenshaw;
- dense versus matrix-free elliptic actions;
- leaf operator modes;
- Poisson HPS leaf modes;
- thread-safe LSMR reverse communication.
The remaining non-library-test programs in src/ are experimental programs for a Jacobi codec and related transform/quantization/video experiments. They are not part of the PDE solver API and should be treated as research code.
The Python files in python/ are thin wrappers around libjpolyd rather than a separate reimplementation.
Examples include:
jbasis.pyjdmat.pyjkmat.pyjmat.pyjmult.pyjgeom.pyjtrace.pyjflux.pyjlaplace.pyjelliptic.pyjprecomp.pyjhps.py
python/libjpolyd_loader.py loads the shared library from the same installed directory.
The higher-level HPS convergence and mesh experiments under testing/ use these bindings.
jhps.py provides HpsDivergenceSolver, which inherits update_variable_robin(), set_neumann_gauge(), and solve() from HpsEllipticSolver. It holds the precomputed HPS tree and root factorization across repeated solves. A representative workflow is:
from jhps import HpsDivergenceSolver, HpsLeafOperatorMode
# pc: RefSimplexPrecomp; mesh and modal coefficient arrays already prepared.
# A_coeffs: (nelem, D, D, dimPi(D, p_A)), in base kappa.
# b_coeffs: (nelem, D, dimPi(D, p_b)), or None when p_b == -1.
# f_int: (nelem, pc.M), physical-source moments for the PDE.
# g_*: (nboundary_faces, pc.kf), physical-face-scaled moments.
with HpsDivergenceSolver(
pc, vertex_ids, coords, simplices, merge_pairs,
A_coeffs, b_coeffs, None, boundary_face_keys,
p_A=3, p_b=2, p_c=-1,
alpha=1.0, beta=0.0,
leaf_operator_mode=HpsLeafOperatorMode.DENSE,
) as solver:
# Initial Dirichlet solve.
u_dirichlet = solver.solve(f_int, g_dirichlet)
# Facewise polynomial alpha=0 and beta(x) != 0: pure Neumann.
# These arrays are unscaled modal expansion coefficients.
solver.update_variable_robin(
alpha_zero_faces, beta_variable_faces,
p_alpha=2, p_beta=2,
)
solver.set_neumann_gauge(0.75)
u_neumann = solver.solve(f_int, g_neumann)The coefficient arrays for update_variable_robin() have shapes (nboundary_faces, dimPi(D-1, p_alpha)) and (nboundary_faces, dimPi(D-1, p_beta)), respectively, with degrees in [0,n]. They use the original boundary-face order passed to the constructor; the C++ solver performs the root-face permutation internally. The source and boundary RHS arrays are modal moments, not pointwise values. For nonzero reaction, pass the corresponding c_coeffs and p_c>=0 instead of None and -1.
For set_neumann_gauge() select a different physical boundary mean without another factorization. For nonsingular Robin or pure-flux problems, the gauge is stored but not imposed. An exception from an incompatible Neumann right-hand side does not invalidate the persistent solver.
The current CMake project requires:
- CMake 3.16 or newer;
- a C++17 compiler;
- C and Fortran compilers;
- OpenBLAS / BLAS;
- LAPACK;
- LAPACKE;
- OpenMP;
- NLopt.
The current top-level CMake configuration also requires OpenCV because the repository still builds the Jacobi-codec experiment targets.
Some experimental executables additionally use SuiteSparse/SPQR.
Python validation scripts generally require:
- Python 3;
- NumPy;
and, depending on the script:
- SciPy;
- SymPy;
- Matplotlib;
- PyMetis.
A normal release build is:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DJPOLYD_BUILD_CAPI=ON \
-DJPOLYD_ENABLE_TEST=ON
cmake --build build -jThe current CMake configuration writes:
lib/ shared/static libraries
bin/ executables
under the repository root.
Release mode is the default when CMAKE_BUILD_TYPE is not specified.
Detailed elliptic/HPS timing counters can be enabled with:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DTIMING=ON
cmake --build build -jBy default, the install target places the Python wrappers and libjpolyd into the user's Python site-packages directory:
cmake --install buildThe default is equivalent to:
-DJPOLYD_PY_INSTALL_USER=ON
Set
-DJPOLYD_PY_INSTALL_USER=OFFto target the system site-packages location instead.
After installation, a simple import check is:
python3 - <<'PY'
import jbasis
import jelliptic
import jhps
print("jpolyd Python bindings loaded")
print("Dense backend:", jhps.HpsLeafOperatorMode.DENSE)
PYRun the CMake-registered tests with:
ctest --test-dir build --output-on-failureUseful C++ regression executables include:
./bin/kmat_stencil_test
./bin/dmat_kmat_stencil_cache_test
./bin/test_jmat_stencil_cache
./bin/test_jprecomp_partial_dag_compat
./bin/test_jleaf_dag_elliptic_compat
./bin/test_jelliptic_matrix_free_actions
./bin/test_jmult_quadrature_matrix
./bin/test_jleaf_operator_modes
./bin/test_jhps_poisson_leaf_operator_modes
./bin/test_lsmr_shim_threadsafeThe testing/ directory contains larger Python validation programs, including:
- basis/quadrature checks;
- manufactured-solution tests;
- Poisson Robin convergence;
- single- and multi-simplex trace/flux tests;
- HPS merge-algebra tests;
- arbitrary-mesh HPS convergence studies;
- dense, dense/sparse, and matrix-free elliptic comparisons;
- variable-coefficient non-polynomial convergence tests;
- divergence-form manufactured solutions and co-normal flux tests;
- scalar and facewise polynomial Robin, mixed Dirichlet/Neumann/Robin data;
- pure-Neumann compatibility, boundary-mean gauge, and persistent Robin/Neumann transitions;
- high-order exterior co-normal flux corrections for variable Robin coefficients.
For example, after building and installing the updated Python wrappers:
python testing/test_hps_divergence_variable_robin_polynomial.py
python testing/test_hps_divergence_mixed_boundary_polynomial.py
python testing/test_hps_divergence_pure_neumann.py --D 1,2,3 --n 4
python testing/test_hps_divergence_neumann_robin_transitions.py --D 1,2,3 --n 4
python testing/test_hps_divergence_neumann_high_conormal_transitions.py --D 2,3 --n 5,6
python testing/test_hps_divergence_neumann_nonzero_gauge.py --D 1,2,3 --n 4
python testing/test_hps_divergence_neumann_nonzero_gauge.py --D 2,3 --n 5 --high-orderMany of these are research/regression drivers rather than a stable command-line interface; individual tests may require additional Python dependencies and runtime settings.
The current solver stack has been exercised with:
- polynomial manufactured solutions recovering to roundoff;
- smooth non-polynomial manufactured solutions showing spectral convergence on fixed affine simplex meshes;
- full HPS mesh trees in dimensions
$D=1,\ldots,4$ in the current elliptic convergence studies; - dense versus matrix-free operator-action comparisons;
- direct-quadrature multiplication matrices versus lifted Clenshaw;
- multiple leaf operator backends;
- source and boundary response-map reuse;
- tau-stabilized HPS merge residual checks;
- elementwise polynomial divergence-form operators with co-normal interface conservation;
- polynomial manufactured-solution recovery for cubic anisotropic diffusion, polynomial drift/reaction, and variable Robin coefficients on
$D=1,2,3$ meshes; - mixed boundary conditions and persistent, source-independent Robin updates;
- scalar pure-Neumann compatibility/gauge tests in
$D=1,2,3$ ,$n=3,4,5,6$ ; - Robin
$\leftrightarrow$ variable-$\beta$ Neumann transitions with rollback after invalid data/updates; - nonzero physical boundary-mean gauges, including nonzero drift and variable diffusion;
- nontrivial high-order co-normal coupling
$M_\beta^{LH}q_H$ for cubic diffusion in$D=2,3$ at$n=5,6$ .
The recent polynomial Neumann/Robin tests generally recover solutions near floating-point accuracy (roughly
The tex/ directory contains derivations and working notes for the simplex Jacobi and HPS constructions. In particular:
tex/jsimplex.textex/steklov.textex/steklov.pdf
These files are useful references for the operator-valued Jacobi/Clenshaw formulation, trace/Steklov constructions, and solver derivations.
This repository is research software. The current emphasis is:
- preserve mathematically explicit simplex/Jacobi operators;
- validate each operator against manufactured solutions or independent constructions;
- materialize reusable local HPS maps when doing so is faster and memory-feasible;
- keep matrix-free/sparse alternatives available where dimensionality or hardware may favor them;
- separate solver mathematics from experimental optimization paths.
The completed divergence-form/co-normal Robin/Neumann extension is now part of the persistent solver. Future work includes broader physical-problem validation and performance/scaling studies; general resonant Neumann/Fredholm problems remain outside the present constant-gauge treatment.
If jpolyd contributes to published research, please cite the software.
The project-wide DOI is 10.5281/zenodo.22137636, which always resolves to the latest archived release.
For reproducible research, please cite the DOI of the specific release used.
The preferred citation metadata is provided in
CITATION.cff.