afivo-streamer 1.1
1D/2D/3D streamer simulations with AMR
Loading...
Searching...
No Matches
m_spline_interp.f90
Go to the documentation of this file.
1!> Module for cubic spline interpolation
2!>
3!> Author: Jannis Teunissen
4!>
5!> This code is based on:
6!> 1. spline.f (https://www.netlib.org/fmm/spline.f)
7!> 2. spline.f90 (translation of spline.f,
8!> https://ww2.odu.edu/~agodunov/computing/programs/book2/Ch01/spline.f90)
9!>
10!> License: the above two files do not specify a license, so I assume they are
11!> in the public domain, as is this code.
13 implicit none
14 private
15
16 integer, parameter :: dp = kind(0.0d0)
17
19 !> Number of tabulated points
20 integer :: n
21 !> Tabulated x values
22 real(dp), allocatable :: x(:)
23 !> Tabulated y values
24 real(dp), allocatable :: y(:)
25 !> Spline coefficients such that s(x) = y(i) + b(i)*(x-x(i)) +
26 !> c(i)*(x-x(i))**2 + d(i)*(x-x(i))**3
27 real(dp), allocatable :: bcd(:, :)
28 !> Array to quickly get index for a coordinate
29 integer, allocatable :: lookup_index(:)
30 !> Inverse spacing in lookup array
31 real(dp) :: lookup_inv_dx
32 end type spline_t
33
34 public :: spline_t
35 public :: spline_set_coeffs
36 public :: spline_evaluate
37
38contains
39
40
41 !> Calculate coefficients for cubic spline interpolation
42 subroutine spline_set_coeffs(x, y, n, spl)
43 !> Size of tabulated data
44 integer, intent(in) :: n
45 !> Tabulated coordinates (in strictly increasing order)
46 real(dp), intent(in) :: x(n)
47 !> Tabulated values
48 real(dp), intent(in) :: y(n)
49 type(spline_t), intent(out) :: spl
50 real(dp), allocatable :: b(:), c(:), d(:)
51 integer :: i, j, lookup_size
52 real(dp) :: h, x_lookup, min_dx
53
54 if (n < 2) &
55 error stop "spline_set_coeffs requires n >= 2"
56 if (any(x(2:n) <= x(1:n-1))) &
57 error stop "spline_set_coeffs: x(:) not strictly increasing"
58
59 allocate(spl%x(n), spl%y(n), spl%bcd(3, n), b(n), c(n), d(n))
60 spl%n = n
61 spl%x = x
62 spl%y = y
63
64 if (n < 3) then
65 ! Handle special case by linear interpolation
66 b(1) = (y(2)-y(1))/(x(2)-x(1))
67 c(1) = 0
68 d(1) = 0
69 b(2) = b(1)
70 c(2) = 0
71 d(2) = 0
72 return
73 end if
74
75 ! set up tridiagonal system
76 ! b = diagonal, d = offdiagonal, c = right hand side.
77 d(1) = x(2) - x(1)
78 c(2) = (y(2) - y(1))/d(1)
79 do i = 2, n-1
80 d(i) = x(i+1) - x(i)
81 b(i) = 2*(d(i-1) + d(i))
82 c(i+1) = (y(i+1) - y(i))/d(i)
83 c(i) = c(i+1) - c(i)
84 end do
85
86 ! end conditions. third derivatives at x(1) and x(n)
87 ! obtained from divided differences
88 b(1) = -d(1)
89 b(n) = -d(n-1)
90 c(1) = 0
91 c(n) = 0
92 if(n /= 3) then
93 c(1) = c(3)/(x(4)-x(2)) - c(2)/(x(3)-x(1))
94 c(n) = c(n-1)/(x(n)-x(n-2)) - c(n-2)/(x(n-1)-x(n-3))
95 c(1) = c(1)*d(1)**2/(x(4)-x(1))
96 c(n) = -c(n)*d(n-1)**2/(x(n)-x(n-3))
97 end if
98
99 ! forward elimination
100 do i = 2, n
101 h = d(i-1)/b(i-1)
102 b(i) = b(i) - h*d(i-1)
103 c(i) = c(i) - h*c(i-1)
104 end do
105
106 ! back substitution
107 c(n) = c(n)/b(n)
108 do j = 1, n-1
109 i = n-j
110 c(i) = (c(i) - d(i)*c(i+1))/b(i)
111 end do
112
113 ! compute spline coefficients
114 b(n) = (y(n) - y(n-1))/d(n-1) + d(n-1)*(c(n-1) + 2*c(n))
115 do i = 1, n-1
116 b(i) = (y(i+1) - y(i))/d(i) - d(i)*(c(i+1) + 2*c(i))
117 d(i) = (c(i+1) - c(i))/d(i)
118 c(i) = 3.*c(i)
119 end do
120 c(n) = 3*c(n)
121 d(n) = d(n-1)
122
123 spl%bcd(1, :) = b
124 spl%bcd(2, :) = c
125 spl%bcd(3, :) = d
126
127 ! Create linear lookup table to find location between data points more
128 ! quickly. First determine good size for the lookup table.
129 min_dx = minval(x(2:n) - x(1:n-1))
130 lookup_size = min(4 * n, nint(1 + (x(n) - x(1))/min_dx))
131
132 ! The lookup table will have a regular (linear) spacing
133 h = (x(n) - x(1)) / (lookup_size - 1)
134 spl%lookup_inv_dx = 1/h
135 allocate(spl%lookup_index(lookup_size))
136
137 ! At location z, the table index is i = ceiling((z - x(1)) * inv_dx)
138 ! The tabulated points should then be x(spl%lookup_index(i))
139 spl%lookup_index(1) = 1
140 do i = 2, lookup_size
141 x_lookup = x(1) + (i-1) * h
142 spl%lookup_index(i) = spl%lookup_index(i-1)
143 do while (x_lookup > x(spl%lookup_index(i)+1))
144 spl%lookup_index(i) = spl%lookup_index(i) + 1
145 end do
146 end do
147
148 end subroutine spline_set_coeffs
149
150 ! Evaluate the cubic spline interpolation at point u
151 ! result = y(i)+b(i)*(u-x(i))+c(i)*(u-x(i))**2+d(i)*(u-x(i))**3
152 ! where x(i) <= u <= x(i+1)
153 pure elemental function spline_evaluate(u, spl) result(spline_value)
154 !> Evaluate at this coordinate
155 real(dp), intent(in) :: u
156 !> Spline data
157 type(spline_t), intent(in) :: spl
158 integer :: i, j
159 real(dp) :: spline_value, dx
160
161 associate(n=>spl%n, x=>spl%x, y=>spl%y, bcd=>spl%bcd)
162 if(u <= x(1)) then
163 spline_value = y(1)
164 return
165 else if(u >= x(n)) then
166 spline_value = y(n)
167 return
168 end if
169
170 j = ceiling(spl%lookup_inv_dx * (u - x(1)))
171
172 ! Even with the checks above, we probably have to check whether j < 1 or
173 ! j > size(spl%lookup_index) due to numerical round-off error
174 if (j < 1) then
175 j = 1
176 else if (j > size(spl%lookup_index)) then
177 j = size(spl%lookup_index)
178 end if
179
180 ! TODO: Not sure whether in practical applications it could be useful to
181 ! do a binary search here instead of a linear one
182
183 ! Find index i so that x(i) <= u <= x(i+1)
184 do i = spl%lookup_index(j), n-1
185 if (u <= x(i+1)) exit
186 end do
187
188 ! evaluate spline interpolation
189 dx = u - x(i)
190 spline_value = y(i) + dx*(bcd(1, i) + dx*(bcd(2, i) + dx*bcd(3, i)))
191 end associate
192 end function spline_evaluate
193
194end module m_spline_interp
Module for cubic spline interpolation.
pure elemental real(dp) function, public spline_evaluate(u, spl)
subroutine, public spline_set_coeffs(x, y, n, spl)
Calculate coefficients for cubic spline interpolation.