Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
181d800
neo_bspline: add 1D B-spline basis and matrix-free CGLS LSQ with tests
krystophny Dec 2, 2025
f0b433f
Add matrix-free neo_bspline 3D and batched LSQ
krystophny Dec 2, 2025
ce14ff9
Parallelize matrix-free neo_bspline operators with OpenMP
krystophny Dec 2, 2025
48fe904
Shorten OpenMP directives to respect line-length
krystophny Dec 2, 2025
ed0fb8a
Add benchmarks comparing interpolate and neo_bspline runtimes
krystophny Dec 2, 2025
9850da5
Use nanosecond timer and separate create/eval benchmark plots
krystophny Dec 2, 2025
0ece3cd
Revert experimental 3D LSQ caching to preserve correctness
krystophny Dec 2, 2025
3821da7
Add 3D basis caching for 4x LSQ performance improvement
krystophny Dec 2, 2025
e89f310
Unify neo_bspline module with grid-optimized OpenMP parallelization
krystophny Dec 2, 2025
dd7a873
Split neo_bspline into base/interp/lsq submodules and add direct inte…
krystophny Dec 2, 2025
ddc05ba
Fix bspline benchmark timing and ignore generated plots
krystophny Dec 2, 2025
8c0daab
Plot direct bspline interpolation timings
krystophny Dec 2, 2025
309f2bf
Make direct bspline bench run all sizes and use band solver
krystophny Dec 2, 2025
d776fa9
Benchmark interpolate 3D at all sizes
krystophny Dec 2, 2025
d5355ef
Add 2D direct bspline analytical test
krystophny Dec 2, 2025
b4eb6bd
Merge branch 'main' into feature/neo_bspline
krystophny Jun 29, 2026
1da6d3e
Merge remote-tracking branch 'origin/main' into local-feature/neo_bsp…
krystophny Jun 30, 2026
79a2fce
Fix neo_bspline division-by-zero, missing interp tests, dense solves
krystophny Jun 30, 2026
be2a1be
Merge branch 'main' into feature/neo_bspline
krystophny Jul 1, 2026
314da3f
Merge branch 'main' into feature/neo_bspline
krystophny Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ lib/
.venv
dist/
test/**/*.png
bench_neo_bspline_*.png
version.f90
Testing/Temporary/CTestCostData.txt
4 changes: 4 additions & 0 deletions src/interpolate/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
add_library(interpolate STATIC
../spl_three_to_five.f90
acc_spline_build_order5.f90
neo_bspline_base.f90
neo_bspline_interp.f90
neo_bspline_lsq.f90
neo_bspline.f90
batch_interpolate_types.f90
batch_interpolate_1d.f90
batch_interpolate_2d.f90
Expand Down
11 changes: 11 additions & 0 deletions src/interpolate/neo_bspline.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module neo_bspline
!! Umbrella module for B-spline functionality.
!! Re-exports types and procedures from submodules:
!! - neo_bspline_base: Core types, init, eval, basis functions
!! - neo_bspline_interp: Direct interpolation (collocation solve)
!! - neo_bspline_lsq: Least-squares fitting (CGLS solver)
use neo_bspline_base
use neo_bspline_interp
use neo_bspline_lsq
implicit none
end module neo_bspline
218 changes: 218 additions & 0 deletions src/interpolate/neo_bspline_base.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
module neo_bspline_base
!! Core B-spline types and basis function evaluation.
!! No external dependencies beyond intrinsics.
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
private

type, public :: bspline_1d
integer :: degree
integer :: n_ctrl
real(dp), allocatable :: knots(:)
real(dp) :: x_min, x_max
end type bspline_1d

type, public :: bspline_2d
type(bspline_1d) :: sx, sy
end type bspline_2d

type, public :: bspline_3d
type(bspline_1d) :: sx, sy, sz
end type bspline_3d

public :: bspline_1d_init_uniform, bspline_2d_init_uniform, bspline_3d_init_uniform
public :: bspline_1d_eval, bspline_2d_eval, bspline_3d_eval
public :: find_span, basis_funs

contains

subroutine bspline_1d_init_uniform(spl, degree, n_ctrl, x_min, x_max)
type(bspline_1d), intent(out) :: spl
integer, intent(in) :: degree, n_ctrl
real(dp), intent(in) :: x_min, x_max
integer :: p, n, m, i
real(dp) :: h

p = degree
n = n_ctrl
m = n + p + 1
spl%degree = p
spl%n_ctrl = n
spl%x_min = x_min
spl%x_max = x_max
allocate(spl%knots(m))
h = (x_max - x_min)/real(n - p, dp)
do i = 1, m
if (i <= p + 1) then
spl%knots(i) = x_min
else if (i >= m - p) then
spl%knots(i) = x_max
else
spl%knots(i) = x_min + real(i - (p + 1), dp)*h
end if
end do
end subroutine bspline_1d_init_uniform


subroutine bspline_2d_init_uniform(spl, degree, n_ctrl, x_min, x_max)
type(bspline_2d), intent(out) :: spl
integer, intent(in) :: degree(2), n_ctrl(2)
real(dp), intent(in) :: x_min(2), x_max(2)

call bspline_1d_init_uniform(spl%sx, degree(1), n_ctrl(1), x_min(1), &
x_max(1))
call bspline_1d_init_uniform(spl%sy, degree(2), n_ctrl(2), x_min(2), &
x_max(2))
end subroutine bspline_2d_init_uniform


subroutine bspline_3d_init_uniform(spl, degree, n_ctrl, x_min, x_max)
type(bspline_3d), intent(out) :: spl
integer, intent(in) :: degree(3), n_ctrl(3)
real(dp), intent(in) :: x_min(3), x_max(3)

call bspline_1d_init_uniform(spl%sx, degree(1), n_ctrl(1), x_min(1), &
x_max(1))
call bspline_1d_init_uniform(spl%sy, degree(2), n_ctrl(2), x_min(2), &
x_max(2))
call bspline_1d_init_uniform(spl%sz, degree(3), n_ctrl(3), x_min(3), &
x_max(3))
end subroutine bspline_3d_init_uniform


subroutine find_span(spl, x, span)
type(bspline_1d), intent(in) :: spl
real(dp), intent(in) :: x
integer, intent(out) :: span
integer :: low, high, mid, p, n, m
real(dp) :: xx

p = spl%degree
n = spl%n_ctrl
m = n + p + 1
xx = min(max(x, spl%x_min), spl%x_max)
if (xx >= spl%knots(m - p)) then
span = n
return
end if
low = p + 1
high = n + 1
do
mid = (low + high)/2
if (xx < spl%knots(mid)) then
high = mid
else if (xx >= spl%knots(mid + 1)) then
low = mid
else
span = mid
exit
end if
end do
end subroutine find_span


subroutine basis_funs(spl, span, x, N)
type(bspline_1d), intent(in) :: spl
integer, intent(in) :: span
real(dp), intent(in) :: x
real(dp), intent(out) :: N(0:)
integer :: p, j, r
real(dp) :: left(0:spl%degree), right(0:spl%degree), saved, temp, denom

p = spl%degree
N(0) = 1.0_dp
do j = 1, p
left(j) = x - spl%knots(span + 1 - j)
right(j) = spl%knots(span + j) - x
saved = 0.0_dp
do r = 0, j - 1
denom = right(r + 1) + left(j - r)
if (denom == 0.0_dp) then
temp = 0.0_dp
else
temp = N(r)/denom
end if
N(r) = saved + right(r + 1)*temp
saved = left(j - r)*temp
end do
N(j) = saved
end do
end subroutine basis_funs


subroutine bspline_1d_eval(spl, coeff, x, y)
type(bspline_1d), intent(in) :: spl
real(dp), intent(in) :: coeff(:)
real(dp), intent(in) :: x
real(dp), intent(out) :: y
integer :: span, j, p
real(dp) :: N(0:spl%degree)

p = spl%degree
call find_span(spl, x, span)
call basis_funs(spl, span, x, N)
y = 0.0_dp
!$omp simd reduction(+:y)
do j = 0, p
y = y + N(j)*coeff(span - p + j)
end do
end subroutine bspline_1d_eval


subroutine bspline_2d_eval(spl, coeff, x, y)
type(bspline_2d), intent(in) :: spl
real(dp), intent(in) :: coeff(:,:)
real(dp), intent(in) :: x(2)
real(dp), intent(out) :: y
integer :: spanx, spany, px, py, a, b, ix, iy
real(dp) :: Nx(0:spl%sx%degree), Ny(0:spl%sy%degree)

px = spl%sx%degree
py = spl%sy%degree
call find_span(spl%sx, x(1), spanx)
call basis_funs(spl%sx, spanx, x(1), Nx)
call find_span(spl%sy, x(2), spany)
call basis_funs(spl%sy, spany, x(2), Ny)
y = 0.0_dp
do a = 0, px
ix = spanx - px + a
do b = 0, py
iy = spany - py + b
y = y + Nx(a)*Ny(b)*coeff(ix, iy)
end do
end do
end subroutine bspline_2d_eval


subroutine bspline_3d_eval(spl, coeff, x, y)
type(bspline_3d), intent(in) :: spl
real(dp), intent(in) :: coeff(:,:,:)
real(dp), intent(in) :: x(3)
real(dp), intent(out) :: y
integer :: spanx, spany, spanz, px, py, pz, a, b, c, ix, iy, iz
real(dp) :: Nx(0:spl%sx%degree), Ny(0:spl%sy%degree)
real(dp) :: Nz(0:spl%sz%degree)

px = spl%sx%degree
py = spl%sy%degree
pz = spl%sz%degree
call find_span(spl%sx, x(1), spanx)
call basis_funs(spl%sx, spanx, x(1), Nx)
call find_span(spl%sy, x(2), spany)
call basis_funs(spl%sy, spany, x(2), Ny)
call find_span(spl%sz, x(3), spanz)
call basis_funs(spl%sz, spanz, x(3), Nz)
y = 0.0_dp
do a = 0, px
ix = spanx - px + a
do b = 0, py
iy = spany - py + b
do c = 0, pz
iz = spanz - pz + c
y = y + Nx(a)*Ny(b)*Nz(c)*coeff(ix, iy, iz)
end do
end do
end do
end subroutine bspline_3d_eval

end module neo_bspline_base
Loading
Loading