feat: polyharmonic spline (PHS) N-dimensional interpolation#136
feat: polyharmonic spline (PHS) N-dimensional interpolation#136twilsonco wants to merge 139 commits into
Conversation
FastInterpolations.jl BenchmarksAll benchmarks (65 total, click to expand)
This comment was automatically generated by Benchmark workflow. |
|
@twilsonco This is a fairly large change, so I’ll need some time to digest it and review it properly. I’m also in the middle of major refactoring for the next minor release, so I’ll probably finish that first, and then review and merge this separately for a following version. I likely won’t be able to review this properly over the next few days, but I’ll get back to it as soon as I can. Sorry for the delay, and thanks again for the contribution! A couple of quick notes for now:
|
|
Hi. Great project here. I'm happy to contribute.
Yes. Fortunately, all the changes are self-contained (except for the addition of PrecompileTools).
That's fine. Once you're done with the refactor, comment hear and I'll make any necessary changes to the PR.
I'll make sure it's applied.
Yes. It looks like it's the formatting that's the failing step.
Once the PR is ready again after you finish the refactor, I'll resubmit. At that point, of course you're welcome to make edits. |
Implements C2-continuous blended PHS interpolation via local RBF stencils with polynomial augmentation. Zero-allocation hot path via AdaptiveArrayPools, multithreaded batch evaluation, full first+second derivative support. New files in src/phs/: - phs_kernels.jl: radial kernel phi(r)=r^K, blend weight w(d,a) with derivs - phs_stencil.jl: stencil selection + Phi-inv precomputation (boundary-safe) - phs_types.jl: PHSInterpolantND struct + PHSLogTransform - phs_eval.jl: 3-layer evaluation engine (value/deriv1/deriv2, quotient rule) - phs_interpolant.jl: constructor + callable protocol - phs_oneshot.jl: one-shot scalar and batch forms - phs.jl: aggregator include New test file: test/test_phs_nd.jl (115 tests, all passing) Exports added: phs_interp, phs_interp!, PHSInterpolantND, PHSLogTransform
For r^K PHS the augmentation must have degree ≥ (K-1)÷2: - K=3 → linear (N+1 terms) — was already correct - K=5 → quadratic C(N+2,2) terms — was WRONG (only used N+1) - K=7 → cubic C(N+3,3) terms — was WRONG Changes: - phs_kernels.jl: add _phs_n_poly, _phs_all_exponents, @generated _phs_poly_exps_tuple, _phs_eval_poly, _phs_eval_poly_deriv1, _phs_eval_poly_deriv2 - phs_stencil.jl: _phs_build_phi_inv now computes poly_deg=(degree-1)÷2, uses _phs_all_exponents for C block; M = ns + binomial(N+poly_deg,poly_deg) - phs_eval.jl: all three _phs_eval_coeffs_* use _phs_poly_exps_tuple for correct compile-time polynomial evaluation; buffer size M taken from actual phi_inv matrix size instead of hardcoded ns+N+1 - scripts/update_readme_2d_comparison.jl: drop PeriodicBC from cubic for fairer comparison; use PHS degree=3 All 115 PHS tests pass.
Use 15x15 regular spacing so PHS (0.3% error) and cubic with PeriodicBC (0.0% error) both show near-perfect results, while constant (18%) and linear (3.5%) illustrate the progression.
…d PHS interpolation title Co-authored-by: Copilot <copilot@github.com>
…d boundary shift cache
…lations in density comparison Co-authored-by: Copilot <copilot@github.com>
…la and improve derivative calculations
This reverts commit 348e7b3.
…ConstantRef tests
…plot example - Create docs/src/interpolation/phs.md with mathematical foundation, usage, and quantum chemistry example - Link to paper (https://doi.org/10.1063/5.0090232) - Add auto-download for critic2 wavefunction files in phs_density_comparison.jl - Add scripts/dat/wfc/ to .gitignore to avoid committing large downloaded files - Remove redundant examples/promolecular_density.jl (consolidate with validation script) - Update README link to point to full PHS documentation
The ensure_wfc_files() function had an invalid 'import JSON' statement inside it, which causes a syntax error in Julia. Fixed by switching to regex-based URL parsing instead of JSON parsing. This removes the JSON.jl dependency while maintaining full functionality for downloading wavefunction files.
…chmarks to run e.g. `julia --project=benchmark benchmark/ci_benchmark.jl 15` will only run benchmark group `15_phs_eval`
This reverts commit 1af8ec3.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #136 +/- ##
==========================================
+ Coverage 96.56% 96.75% +0.18%
==========================================
Files 148 154 +6
Lines 12272 13540 +1268
==========================================
+ Hits 11851 13101 +1250
- Misses 421 439 +18
🚀 New features to boost your workflow:
|
|
Note that the missing lines in test coverage CodeCov is reporting are false-positives. The PR has 100% coverage. |
Add test/test_phs_broken_pins.jl with 20 @test_broken pins for issues found in the PR ProjectTorreyPines#136 PHS review. Each pin flips to an Unexpected Pass once the underlying bug is fixed, signalling promotion to @test, so follow-up PRs have ready-made TDD targets. All 20 currently record as Broken (verified: 0 Error), so the suite stays green. Value pins (correct value known): - node-derivative d≈0 bug: 1D, 2D, and log-transform path (drops the w·f' term at a grid node) - gradient/hessian/laplacian agreement with the deriv keyword path - Complex-valued data evaluation - Clamp/Wrap extrapolation returning the boundary/wrapped value - non-uniform grid node + linear reproduction - derivative order >= 3 matching a finite-difference reference (nonzero: PHS is transcendental via the exp blend, not polynomial) - 1D bare-vector construction phs_interp(x, y) Throw pins (refusal is the intended fix): - batch out-of-domain query under NoExtrap - blend_factor validation - log-transform positivity (reject non-positive data) Tracking pin: - ExtendExtrap far-OOB collapse to 0.0 (compact-support blend) Add PHSBrokenHelpers test snippet (is_throwing) to test/setup.jl.
Move the PHS density-comparison scripts and their data out of the shared scripts/ root into a dedicated scripts/phs/ folder: - scripts/phs_density_comparison.jl -> scripts/phs/ - scripts/phs_density_comparison_simplified.jl -> scripts/phs/ - scripts/dat/ -> scripts/phs/dat/ Resolve the .pkl/.csv/.xyz data paths via @__DIR__ instead of a CWD-relative "dat/...", so the scripts run from any working directory after the move. The wfc/ wavefunction files keep auto-downloading from critic2 (verified: ensure_wfc_files URLs return HTTP 200) and stay gitignored. The DFT grid (.pkl) and line cut (.csv) have no public download source and are required to run the script, so they remain committed under scripts/phs/dat/ (relocated as renames — no new blobs). Update docs/src/interpolation/phs.md run instructions to the new path and point the source link at master instead of the stale feat branch. The non-PHS update_readme_2d_comparison.jl and the shared scripts/Project.toml stay at scripts/ root.
The plot output path was CWD-relative ("../docs/images/...") and broke once
the scripts moved to scripts/phs/. Resolve it through @__DIR__ like the data
paths, so the figure lands in docs/images/ regardless of working directory.
Verified by running scripts/phs/phs_density_comparison_simplified.jl end to
end (with the local dev FastInterpolations): wfc auto-download, .pkl/.csv/.xyz
load, PHS-3 build, and the benchmark/profile all succeed from the new layout.
Correct factually-wrong comments/docstrings found while auditing src/phs
against the actual code. Comment-only (plus removing one dead constant);
no behavior change, package precompiles cleanly.
Threading / memory locality:
- The coefficient cache is per-thread (a Vector{Dict} indexed by
Threads.threadid()), not task-local: drop the false "task_local_storage"
and "objectid sub-Dict" claims and the dead _PHS_COEFF_CACHE_TKEY constant.
- AdaptiveArrayPools scratch buffers are task-local (task_local_storage),
not thread-local: fix the two comments that mislabeled them.
- Drop the "immutable after construction / all workspace in pools" claim
(the coeff cache is a mutated struct field).
Math / structure:
- Φ is symmetric but indefinite (saddle-point), not positive definite.
- Kernel guards fire at r ≤ 0 (r <= zero(T)), not at a nonexistent r ≤ ε;
for K=1 it is φ'(r)/r that is singular, while φ'(0)=1 is finite.
Out-of-sync API/struct docs:
- Remove the nonexistent `spacings` field from the struct docstring.
- Fix the _phs_build_stencil signature (no `spacings` argument).
- Correct the boundary left-clip formula to include stencil_lo[d].
- Drop the false "cache always built for stencil_size<=6, N<=3" guarantee
(it is skipped when the estimate exceeds the 100 MB limit).
- The one-shot batch builds a full temporary interpolant, not just the
output vector.
- `_phs_blend_params` computes the mean grid spacing per axis, not the max.
The PHS interpolation page (docs/src/interpolation/phs.md) was never added to the `pages` list, so it built as an orphan and was unreachable from the site navigation. Add it under the Interpolation section, after Local Cubic Hermite.
|
Hi @twilsonco, Thank you again for this valuable contribution, First, the PHS code is nicely done. Small fixes I made
Some issues I noticed The core interpolation works well. Plan I don't think we need to fix all the broken tests at once. |
Add N-Dimensional Polyharmonic Spline (PHS) Interpolation
This Pull Request introduces a high-performance, general-dimension Polyharmonic Spline (PHS) Radial Basis Function (RBF) interpolant to
FastInterpolations.jl, based on the implementation described here.Excerpt from Otero-de-la-Roza - 2022 - Finding critical points and reconstruction of electron densities on grids
PHS is particularly effective for smooth interpolation of multidimensional gridded physical data (e.g., electron densities, potential energy surfaces), especially when combined with log-density transforms and custom reference functions (physics-informed interpolation).
Key Features
N-Dimensional PHS Interpolation (
PHSInterpolantND)Float32,Float64) and value type (retaining duck-typed scalar support).Log-Density Smoothing Transform & Custom References
PHSLogTransformto interpolate smooth log-transformed functions of the formConstantRef(val)(for standard log-space interpolation) or other existing interpolants.Weighted Blending for$C^2$ Continuity
blend_factorconstructor argument.Robust Precompilation & JIT Warmup
PrecompileToolsinsidesrc/phs/phs.jlto precompile the most commonFloat64/3D patterns. This completely eliminates first-call latency (JIT compilation overhead) for standard value and derivative queries.Zero-Allocation Hot Path & Performance Optimizations
Achieving competitive evaluation speeds for stencil-based RBFs required eliminating all sources of heap allocations on the hot path. Through rigorous profiling and micro-optimizations, the query hot path is 100% allocation-free for both values and derivatives.
Key Optimizations:
shift_cache.coeff_cacheswithinPHSInterpolantNDandAdaptiveArrayPoolsfor thread workspaces), ensuring thread-safety without dynamic memory allocations under parallel batch queries.r^Kexponentiation with explicit, compiler-friendly multiplications.r_invpatterns and@simdloops in radial evaluation. Redundant divisions in blending functions were fused into a single reciprocal division followed by multiplications.@generatedfunction dispatch, allowing the compiler to fully unroll evaluation loops.Real-World Validation: Phenol Dimer (Electron Density)
To validate correctness and showcase the interpolation accuracy, we included a real-world quantum chemistry benchmark in$75 \times 113 \times 70$ grid).
scripts/phs_density_comparison.jl(phenol dimer, DFT-computed electron density on aThe benchmark compares PHS (using a log-density transform and a promolecular density reference constructed analytically from critic2 wavefunctions) against standard interpolation methods (Nearest, Linear, Cubic, Cardinal).
Accuracy Comparison (Relative Error Statistics)
1. Charge Density ($\rho$ )
2. Gradient Magnitude ($|\nabla \rho|$ )
3. Laplacian Magnitude ($|\nabla^2 \rho|$ )
Note: Relative error ratios in parentheses indicate how many times larger the error of the given method is compared to PHS.
Here's a set of plots that better captures the comparison (this plot is generated using the new script with the PR, and is included in the PHS docs):
Evaluation Timings & Allocation Verification
Evaluation times are measured for 1000 query points along a phenol-dimer hydrogen-bond path (runs executed twice to isolate JIT and stencil caches):
Extensive optimization was performed after the original implementation, reducing the runtime for this from approx.
30sdown to0.042son a M3 Ultra Mac Studio, a ~715X speedup.While PHS has a higher baseline computational cost due to localized blending and radial evaluations, it provides multiple orders of magnitude better accuracy, particularly in derivative fields (gradient & Hessian/Laplacian), with completely stable memory usage.
Codebase Modifications & Added Files
The main addition of the PHS feature-set lives in the
src/phs/directory:src/phs/phs.jl: Features aggregator and compile workload definition.src/phs/phs_kernels.jl: Core radial basis functions and their specialized first/second-order derivatives (src/phs/phs_stencil.jl: Matrix formulation, canonical stencil offset solver, and boundary shift cached variants.src/phs/phs_types.jl: Type definitions (PHSInterpolantND,PHSLogTransform,ConstantRef).src/phs/phs_eval.jl: Thread-safe coefficient evaluations, blend function kernels, and evaluation pipelines (including value, gradient, Hessian, and log-transform chain-rule solvers).src/phs/phs_interpolant.jl: Public constructorphs_interpand user-facing callable entrypoints.src/phs/phs_oneshot.jl: Non-allocating batch evaluation methods.New Documentation & Testing:
docs/src/interpolation/phs.md: Mathematical foundations, full API specifications, parameters tuning, and performance guides.test/test_phs_nd.jl: Exhaustive unit tests checking multidimensional interpolation correctness, degree scaling, log-transform safety, allocations, and type stability.scripts/phs_density_comparison.jl: Full-scale validation and timing comparison script for phenol-dimer electron density.scripts/update_readme_2d_comparison.jl: Scripts to plot multi-panel 2D validation results on regular and irregular grids.Future Improvements
As I integrate this PHS implementation into my other project (the chemistry density-analysis tool for which PHS was originally implemented), further changes, potential API polish, or performance-related improvements may be PR'd back to this repository.
Thank you for the reviews! Let me know if you would like any specific changes or further tests.