!> !> @file tautsp.f90 !> !> @brief !> !> @copyright !> Copyright (©) 2021 EPFL (Ecole Polytechnique Fédérale de Lausanne) !> SPC (Swiss Plasma Center) !> !> SPClibs is free software: you can redistribute it and/or modify it under !> the terms of the GNU Lesser General Public License as published by the Free !> Software Foundation, either version 3 of the License, or (at your option) !> any later version. !> !> SPClibs is distributed in the hope that it will be useful, but WITHOUT ANY !> WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS !> FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. !> !> You should have received a copy of the GNU Lesser General Public License !> along with this program. If not, see . !> !> @author !> (in alphabetical order) !> @author Trach-Minh Tran !> subroutine tautsp ( tau, gtau, ntau, gamma, s, break, coef, l, k, iflag ) !************************************************************************* ! !! TAUTSP constructs a cubic spline interpolant to given data. ! ! Discussion: ! ! If 0 < GAMMA, additional knots are introduced where needed to ! make the interpolant more flexible locally. This avoids extraneous ! inflection points typical of cubic spline interpolation at knots to ! rapidly changing data. ! ! Method: ! ! On the I-th interval, (TAU(I), TAU(I+1)), the interpolant is of the ! form: ! ! (*) f(u(x))=a+b*u + c*h(u,z) + d*h(1-u,1-z) , ! ! with ! ! U = U(X) = ( X - TAU(I) ) / DTAU(I). ! ! Here, ! z=z(i) = addg(i+1)/(addg(i)+addg(i+1)) ! (= .5, in case the denominator vanishes). with ! addg(j)=abs(ddg(j)), ddg(j) = dg(j+1)-dg(j), ! dg(j)=divdif ( j) = (gtau(j+1)-gtau(j))/dtau(j) ! and ! h(u,z)=alpha*u**3+(1-alpha)*(max(((u-zeta)/(1-zeta)),0)**3 ! with ! alpha(z)=(1-gamma/3)/zeta ! zeta(z)=1-gamma*min((1 - z), 1/3) ! thus, for 1/3 <= z .le. 2/3, f is just a cubic polynomial on ! the interval i. otherwise, it has one additional knot, at ! tau(i)+zeta*dtau(i) . ! as z approaches 1, h(.,z) has an increasingly sharp bend near 1, ! thus allowing f to turn rapidly near the additional knot. ! in terms of f(j)=gtau(j) and ! fsecnd(j)= second derivative of f at tau(j), ! the coefficients for (*) are given as ! a=f(i)-d ! b=(f(i+1)-f(i)) - (c - d) ! c=fsecnd(i+1)*dtau(i)**2/hsecnd(1,z) ! d=fsecnd(i)*dtau(i)**2/hsecnd(1,1-z) ! hence can be computed once fsecnd(i),i=1,...,ntau, is fixed. ! ! F is automatically continuous and has a continuous second derivative ! (except when z=0 or 1 for some i). we determine fscnd(.) from ! the requirement that also the first derivative of F be continuous. ! ! In addition, we require that the third derivative be continuous ! across TAU(2) and across TAU(NTAU-1). This leads to a strictly ! diagonally dominant tridiagonal linear system for the fsecnd(i) ! which we solve by Gauss elimination without pivoting. ! ! Reference: ! ! Carl DeBoor, ! A Practical Guide to Splines, ! Springer Verlag. ! ! Parameters: ! ! Input, real ( kind = 8 ) TAU(NTAU), the sequence of data points. ! TAU must be strictly increasing. ! ! Input, real ( kind = 8 ) GTAU(NTAU), the corresponding sequence of ! function values. ! ! Input, integer NTAU, the number of data points. NTAU must be at least 4. ! ! Input, gamma indicates whether additional flexibility is desired. ! =0., no additional knots ! in (0.,3.), under certain conditions on the given data at ! points i-1, i, i+1, and i+2, a knot is added in the ! i-th interval, i=2,...,ntau-2. see description of meth- ! od below. the interpolant gets rounded with increasing ! gamma. a value of 2.5 for gamma is typical. ! in (3.,6.), same , except that knots might also be added in ! intervals in which an inflection point would be permit- ! ted. a value of 5.5 for gamma is typical. ! ! Output, break, coef, l, k give the pp-representation of the interpolant. ! specifically, for break(i) <= x .le. break(i+1), the ! interpolant has the form ! f(x)=coef(1,i) +dx(coef(2,i) +(dx/2)(coef(3,i) +(dx/3)coef(4,i))) ! with dx=x-break(i) and i=1,...,l . ! ! Output, iflag =1, ok ! =2, input was incorrect. a printout specifying the mistake ! was made. ! workspace ! ! Output, s is required, of size (ntau,6). the individual columns of this ! array contain the following quantities mentioned in the write- ! up and below. ! s(.,1)=dtau = tau(.+1)-tau ! s(.,2)=diag = diagonal in linear system ! s(.,3)=u = upper diagonal in linear system ! s(.,4)=r = right side for linear system (initially) ! = fsecnd = solution of linear system , namely the second ! derivatives of interpolant at tau ! s(.,5)=z = indicator of additional knots ! s(.,6)=1/hsecnd(1,x) with x = z or = 1-z. see below. ! implicit none integer ntau real ( kind = 8 ) alph real ( kind = 8 ) alpha real ( kind = 8 ) break(*) real ( kind = 8 ) c real ( kind = 8 ) coef(4,*) real ( kind = 8 ) d real ( kind = 8 ) del real ( kind = 8 ) denom real ( kind = 8 ) divdif real ( kind = 8 ) entry real ( kind = 8 ) entry3 real ( kind = 8 ) factor real ( kind = 8 ) factr2 real ( kind = 8 ) gam real ( kind = 8 ) gamma real ( kind = 8 ) gtau(ntau) integer i integer iflag integer k integer l integer method real ( kind = 8 ) onemg3 real ( kind = 8 ) onemzt real ( kind = 8 ) ratio real ( kind = 8 ) s(ntau,6) real ( kind = 8 ) sixth real ( kind = 8 ) tau(ntau) real ( kind = 8 ) temp real ( kind = 8 ) x real ( kind = 8 ) z real ( kind = 8 ) zeta real ( kind = 8 ) zt2 alph(x) = min ( 1.0D+00, onemg3 / x ) ! ! There must be at least 4 interpolation points. ! if ( ntau < 4 ) then write ( *, '(a)' ) ' ' write ( *, '(a)' ) 'TAUTSP - Fatal error!' write ( *, '(a)' ) ' Input NTAU must be at least 4.' write ( *, '(a,i6)' ) ' NTAU = ', ntau iflag = 2 stop end if ! ! Construct delta tau and first and second (divided) differences of data. ! do i = 1, ntau-1 s(i,1) = tau(i+1)-tau(i) if ( s(i,1) <= 0.0D+00 ) then write(*,30)i,tau(i),tau(i+1) 30 format (' point ',i3,' and the next',2e15.6,' are disordered') iflag=2 return end if s(i+1,4) = ( gtau(i+1) - gtau(i) ) / s(i,1) end do do i = 2, ntau-1 s(i,4) = s(i+1,4)-s(i,4) end do ! ! Construct system of equations for second derivatives at tau. at each ! interior data point, there is one continuity equation, at the first ! and the last interior data point there is an additional one for a ! total of NTAU equations in ntau unknowns. ! i = 2 s(2,2) = s(1,1) / 3.0D+00 sixth = 1.0D+00 / 6.0D+00 method = 2 gam = gamma if ( gam <= 0.0D+00 ) then method=1 end if if ( 3.0D+00 < gam ) then method = 3 gam = gam - 3.0D+00 end if onemg3 = 1.0D+00 - gam / 3.0D+00 ! ! loop over i ! 70 continue ! ! Construct z(i) and zeta(i) ! z = 0.5D+00 if ( method == 1) then go to 100 end if if ( method == 3) then go to 90 end if if ( s(i,4)*s(i+1,4) < 0.0D+00 ) then go to 100 end if 90 continue temp = abs ( s(i+1,4) ) denom = abs ( s(i,4) ) +temp if ( denom /= 0.0D+00 ) then z = temp/denom if ( abs ( z - 0.5D+00 ) <= sixth ) then z=0.5D+00 end if end if 100 continue s(i,5) = z ! ! Set up part of the i-th equation which depends on the i-th interval. ! if ( z < 0.5D+00 ) then zeta = gam*z onemzt = 1.0D+00 - zeta zt2 = zeta**2 alpha = alph(onemzt) factor = zeta/(alpha*(zt2-1.0D+00 ) + 1.0D+00 ) s(i,6) = zeta*factor / 6.0D+00 s(i,2) = s(i,2) + s(i,1) & * ( ( 1.0D+00 - alpha * onemzt ) * factor / 2.0D+00-s(i,6)) ! ! If z=0 and the previous z = 1, then d(i) = 0. since then ! also u(i-1)=l(i+1) = 0, its value does not matter. reset ! d(i)=1 to insure nonzero pivot in elimination. ! if ( s(i,2) <= 0.0D+00 ) then s(i,2) = 1.0D+00 end if s(i,3)=s(i,1) / 6.0D+00 else if ( z - 0.5D+00 == 0.0D+00 ) then s(i,2)=s(i,2)+s(i,1) / 3.0D+00 s(i,3)=s(i,1) / 6.0D+00 else if ( 0.0D+00 < z - 0.5D+00 ) then onemzt = gam*(1.0D+00 - z) zeta = 1.0D+00 - onemzt alpha = alph(zeta) factor = onemzt/(1.0D+00 - alpha * zeta * ( 1.0D+00 + onemzt ) ) s(i,6) = onemzt*factor / 6.0D+00 s(i,2) = s(i,2)+s(i,1) / 3.0D+00 s(i,3) = s(i,6)*s(i,1) end if if ( 2 < i ) then go to 190 end if s(1,5) = 0.5D+00 ! ! The first two equations enforce continuity of the first and of ! the third derivative across tau(2). ! s(1,2)=s(1,1) / 6.0D+00 s(1,3)=s(2,2) entry3=s(2,3) if ( z-0.5D+00) 150, 160, 170 150 continue factr2 = zeta * ( alpha * ( zt2 - 1.0D+00 ) + 1.0D+00 ) & / ( alpha * ( zeta * zt2 - 1.0D+00 ) + 1.0D+00 ) ratio=factr2*s(2,1)/s(1,2) s(2,2)=factr2*s(2,1)+s(1,1) s(2,3)=-factr2*s(1,1) go to 180 160 continue ratio=s(2,1)/s(1,2) s(2,2)=s(2,1)+s(1,1) s(2,3)=-s(1,1) go to 180 170 continue ratio=s(2,1)/s(1,2) s(2,2)=s(2,1)+s(1,1) s(2,3)=-s(1,1)*6.0D+00 * alpha * s(2,6) ! ! At this point, the first two equations read ! diag(1)*x1+u(1)*x2 + entry3*x3=r(2) ! -ratio*diag(1)*x1+diag(2)*x2 + u(2)*x3=0.0 ! Eliminate first unknown from second equation ! 180 continue s(2,2)=ratio*s(1,3)+s(2,2) s(2,3)=ratio*entry3+s(2,3) s(1,4)=s(2,4) s(2,4)=ratio*s(1,4) go to 200 190 continue ! ! The i-th equation enforces continuity of the first derivative ! across tau(i). it has been set up in statements 35 up to 40 ! and 21 up to 25 and reads now ! -ratio*diag(i-1)*xi-1+diag(i)*xi + u(i)*xi+1=r(i) . ! eliminate (i-1)st unknown from this equation ! s(i,2)=ratio*s(i-1,3)+s(i,2) s(i,4)=ratio*s(i-1,4)+s(i,4) ! ! Set up the part of the next equation which depends on the ! i-th interval. ! 200 continue if ( z- 0.5D+00 ) 210, 220, 230 210 continue ratio = -s(i,6) * s(i,1) / s(i,2) s(i+1,2)=s(i,1) / 3.0D+00 go to 240 220 continue ratio=-(s(i,1) / 6.0D+00 ) / s(i,2) s(i+1,2)=s(i,1) / 3.0D+00 go to 240 230 continue ratio=-( s(i,1) / 6.0D+00 )/s(i,2) s(i+1,2)=s(i,1)*((1.0D+00-zeta*alpha) * factor / 2.0D+00 - s(i,6) ) ! ! end of i loop ! 240 continue i=i+1 if ( i < ntau-1) then go to 70 end if s(i,5) = 0.5D+00 ! ! The last two equations enforce continuity of third derivative and ! of first derivative across tau(ntau-1). ! entry=ratio*s(i-1,3)+s(i,2)+s(i,1)/3.0D+00 s(i+1,2)=s(i,1)/6.0D+00 s(i+1,4)=ratio*s(i-1,4)+s(i,4) if ( z- 0.5D+00 ) 250, 260, 270 250 continue ratio = s(i,1) * 6.0D+00 * s(i-1,6) * alpha / s(i-1,2) s(i,2)=ratio*s(i-1,3)+s(i,1)+s(i-1,1) s(i,3)=-s(i-1,1) go to 280 260 continue ratio=s(i,1)/s(i-1,2) s(i,2)=ratio*s(i-1,3)+s(i,1)+s(i-1,1) s(i,3)=-s(i-1,1) go to 280 270 continue factr2=onemzt*(alpha*(onemzt**2-1.0D+00)+1.0D+00) & /(alpha*(onemzt**3-1.0D+00)+1.0D+00) ratio = factr2*s(i,1) / s(i-1,2) s(i,2)=ratio*s(i-1,3)+factr2*s(i-1,1)+s(i,1) s(i,3)=-factr2*s(i-1,1) ! ! At this point, the last two equations read: ! ! diag(i)*xi+ u(i)*xi+1=r(i) ! -ratio*diag(i)*xi+diag(i+1)*xi+1=r(i+1) ! ! Eliminate XI from the last equation. ! 280 continue s(i,4)=ratio*s(i-1,4) ratio=-entry/s(i,2) s(i+1,2)=ratio*s(i,3)+s(i+1,2) s(i+1,4)=ratio*s(i,4)+s(i+1,4) ! ! Back substitution. ! s(ntau,4) = s(ntau,4) / s(ntau,2) 290 continue s(i,4)=(s(i,4)-s(i,3)*s(i+1,4))/s(i,2) i=i-1 if ( 1 < i ) then go to 290 end if s(1,4)=(s(1,4)-s(1,3)*s(2,4)-entry3*s(3,4))/s(1,2) ! ! Construct polynomial pieces. ! break(1)=tau(1) l=1 do i=1, ntau-1 coef(1,l)=gtau(i) coef(3,l)=s(i,4) divdif=(gtau(i+1)-gtau(i))/s(i,1) z=s(i,5) if ( z- 0.5D+00 ) 300, 310, 320 300 continue if ( z == 0.0D+00 ) go to 330 zeta=gam*z onemzt=1.0D+00-zeta c=s(i+1,4) / 6.0D+00 d=s(i,4)*s(i,6) l=l+1 del=zeta*s(i,1) break(l)=tau(i)+del zt2=zeta**2 alpha=alph(onemzt) factor=onemzt**2*alpha coef(1,l)=gtau(i)+divdif*del+s(i,1)**2*(d*onemzt*(factor-1.0D+00) & +c*zeta*(zt2-1.0D+00)) coef(2,l)=divdif+s(i,1)*(d*(1.0D+00-3.0D+00*factor)+c*(3.0D+00*zt2-1.0D+00)) coef(3,l)=6.0D+00*(d*alpha*onemzt+c*zeta) coef(4,l)=6.0D+00*(c-d*alpha)/s(i,1) coef(4,l-1)=coef(4,l)-6.0D+00*d*(1.0D+00-alpha)/(del*zt2) coef(2,l-1)=coef(2,l)-del*(coef(3,l)-(del/2.0D+00)*coef(4,l-1)) go to 340 310 continue coef(2,l) = divdif - s(i,1) * ( 2.0D+00 * s(i,4) + s(i+1,4) ) / 6.0D+00 coef(4,l)=(s(i+1,4)-s(i,4))/s(i,1) go to 340 320 continue onemzt=gam*(1.0D+00-z) if ( onemzt == 0.0D+00 ) then go to 330 end if zeta = 1.0D+00 - onemzt alpha=alph(zeta) c=s(i+1,4)*s(i,6) d=s(i,4)/6.0D+00 del=zeta*s(i,1) break(l+1)=tau(i)+del coef(2,l)=divdif-s(i,1)*(2.0D+00*d+c) coef(4,l)=6.0D+00*(c*alpha-d)/s(i,1) l=l+1 coef(4,l)=coef(4,l-1)+6.0D+00*(1.0D+00-alpha)*c/(s(i,1)*onemzt**3) coef(3,l)=coef(3,l-1)+del*coef(4,l-1) coef(2,l)=coef(2,l-1)+del*(coef(3,l-1)+(del/2.0D+00)*coef(4,l-1)) coef(1,l)=coef(1,l-1)+del*(coef(2,l-1)+(del/2.0D+00)*(coef(3,l-1) & +(del/3.0D+00)*coef(4,l-1))) go to 340 330 continue coef(2,l) = divdif coef(3,l) = 0D+00 coef(4,l) = 0.0D+00 340 continue l = l + 1 break(l) = tau(i+1) end do l = l - 1 k = 4 iflag = 1 return end