!>
!> @file tcdsmat_mod.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
!>
MODULE tcdsmat_mod
IMPLICIT NONE
!
INTERFACE
SUBROUTINE meshdist(coefs, x, nx)
DOUBLE PRECISION, INTENT(in) :: coefs(5)
INTEGER, INTENT(iN) :: nx
DOUBLE PRECISION, INTENT(inout) :: x(0:nx)
END SUBROUTINE meshdist
END INTERFACE
!
INTERFACE norm2
MODULE PROCEDURE norm2_1d, norm2_2d
END INTERFACE
CONTAINS
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SUBROUTINE mstruct(p, n, rank, dist)
!
! It is assumed that:
! . 2nd dimension is number first
! . 2nd dimension is periodic
!
INTEGER, INTENT(in) :: p(2), n(2)
INTEGER, INTENT(out) :: rank
INTEGER, ALLOCATABLE :: dist(:)
!
INTEGER, ALLOCATABLE :: pvect(:,:)
INTEGER :: kl, ku, i
!
rank = (n(1)+p(1))*n(2) ! Rank of the FE matrix
ku = (p(1)+1)*(2*p(2)+1)-1
kl = ku
ALLOCATE(pvect(0:2*p(2),0:p(1)))
IF( ALLOCATED(dist)) DEALLOCATE(dist)
ALLOCATE(dist(-kl:ku))
!
! Upper (North) points
pvect(0:p(2),0) = (/(i,i=0,p(2))/)
!
! Lower (South) points and periodicity of 2nd dim.
DO i=1,p(2)
pvect(p(2)+i,0) = n(2)-pvect(p(2)-i+1,0)
END DO
!
! Shift by N2 for points on the right (West) side
DO i=1,p(1)
pvect(:,i) = pvect(:,i-1)+n(2)
END DO
!
! Super-diagonals including the diagonal
dist(0:ku) = RESHAPE(pvect, (/ku+1/))
!
! Sub-diagonals
DO i=-1,-kl,-1
dist(i) = -dist(-i)
END DO
!
DEALLOCATE(pvect)
END SUBROUTINE mstruct
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SUBROUTINE dismat(spl, mat)
!
! Assembly of FE matrix mat using spline spl
!
USE bsplines
USE cds
!
TYPE(spline2d), INTENT(in) :: spl
TYPE(cds_mat), INTENT(inout) :: mat
!
INTEGER :: n1, nidbas1, ndim1, ng1
INTEGER :: n2, nidbas2, ndim2, ng2
INTEGER :: i, j, ig1, ig2
INTEGER :: iterm, iw1, iw2, igw1, igw2, it1, it2, igt1, igt2, irow, jcol
DOUBLE PRECISION, ALLOCATABLE :: xg1(:), wg1(:), fun1(:,:)
DOUBLE PRECISION, ALLOCATABLE :: xg2(:), wg2(:), fun2(:,:)
DOUBLE PRECISION:: contrib
!
INTEGER :: kterms ! Number of terms in weak form
INTEGER, DIMENSION(:,:), ALLOCATABLE :: idert, iderw ! Derivative order
DOUBLE PRECISION, DIMENSION(:), ALLOCATABLE :: coefs ! Terms in weak form
!===========================================================================
! 1.0 Prologue
!
! Properties of spline space
!
CALL get_dim(spl%sp1, ndim1, n1, nidbas1)
CALL get_dim(spl%sp2, ndim2, n2, nidbas2)
WRITE(*,'(/a, 5i6)') 'n1, nidbas1, ndim1 =', n1, nidbas1, ndim1
WRITE(*,'(a, 5i6)') 'n2, nidbas2, ndim2 =', n2, nidbas2, ndim2
!
! Weak form
!
kterms = mat%nterms
ALLOCATE(idert(kterms,2), iderw(kterms,2), coefs(kterms))
!
ALLOCATE(fun1(0:nidbas1,0:1)) ! Spline and 1st derivative
ALLOCATE(fun2(0:nidbas2,0:1)) !
!
! Gauss quadature
!
CALL get_gauss(spl%sp1, ng1)
CALL get_gauss(spl%sp2, ng2)
ALLOCATE(xg1(ng1), wg1(ng1))
ALLOCATE(xg2(ng1), wg2(ng1))
!===========================================================================
! 2.0 Assembly loop
!
DO i=1,n1
CALL get_gauss(spl%sp1, ng1, i, xg1, wg1)
DO ig1=1,ng1
CALL basfun(xg1(ig1), spl%sp1, fun1, i)
DO j=1,n2
CALL get_gauss(spl%sp2, ng2, j, xg2, wg2)
DO ig2=1,ng2
CALL basfun(xg2(ig2), spl%sp2, fun2, j)
CALL coefeq(xg1(ig1), xg2(ig2), idert, iderw, coefs)
DO iterm=1,kterms
DO iw1=0,nidbas1 ! Weight function in dir 1
igw1 = i+iw1
DO iw2=0,nidbas2 ! Weight function in dir 2
igw2 = MODULO(j+iw2-1, n2) + 1
irow = igw2 + (igw1-1)*n2
DO it1=0,nidbas1 ! Test function in dir 1
igt1 = i+it1
DO it2=0,nidbas2 ! Test function in dir 2
igt2 = MODULO(j+it2-1, n2) + 1
jcol = igt2 + (igt1-1)*n2
contrib = fun1(iw1,iderw(iterm,1)) * &
& fun2(iw2,iderw(iterm,2)) * &
& coefs(iterm) * &
& fun2(it2,idert(iterm,2)) * &
& fun1(it1,idert(iterm,1)) * &
& wg1(ig1) * wg2(ig2)
CALL updtmat(mat, irow, jcol, contrib)
END DO
END DO
END DO
END DO
END DO
END DO
END DO
END DO
END DO
!===========================================================================
! 9.0 Epilogue
!
DEALLOCATE(xg1, wg1, fun1)
DEALLOCATE(xg2, wg2, fun2)
DEALLOCATE(idert, iderw, coefs)
!
CONTAINS
SUBROUTINE coefeq(x, y, idt, idw, c)
DOUBLE PRECISION, INTENT(in) :: x, y
INTEGER, INTENT(out) :: idt(:,:), idw(:,:)
DOUBLE PRECISION, INTENT(out) :: c(SIZE(idt,1))
!
! Weak form = Int(x*dw/dx*dt/dx + 1/x*dw/dy*dt/dy)dxdy
!
c(1) = x !
idt(1,1) = 1
idt(1,2) = 0
idw(1,1) = 1
idw(1,2) = 0
!
c(2) = 1.d0/x
idt(2,1) = 0
idt(2,2) = 1
idw(2,1) = 0
idw(2,2) = 1
END SUBROUTINE coefeq
END SUBROUTINE dismat
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SUBROUTINE ibcmat(mat, ny, px)
!
! Apply BC on matrix
!
USE cds
IMPLICIT NONE
TYPE(cds_mat), INTENT(inout) :: mat
INTEGER, INTENT(in) :: ny, px
!
INTEGER :: kl, ku, n, bw0, i, j
DOUBLE PRECISION :: zsum(mat%rank), arr(mat%rank)
!===========================================================================
! 1.0 Prologue
!
kl = mat%kl
ku = mat%ku
n = mat%rank
mat%ny = ny
!
! Size of row ny and column ny
!
bw0 = SIZE(mat%rowv)
!===========================================================================
! 2.0 Unicity at the axis
!
! The vertical sum on the NY-th row
! Store the sums at row ny in MAT%ROWV
!
zsum(1:n) = 0.0d0
DO i=1,ny
CALL getrow(mat, i, arr)
zsum(1:bw0) = zsum(1:bw0) + arr(1:bw0)
IF( i .LE. ny ) THEN ! Clear rows 1:(ny-1)
arr = 0.0d0; arr(i) = 1.0d0
CALL putrow(mat, i, arr)
END IF
END DO
mat%rowv(1:bw0) = zsum(1:bw0) ! row ny
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'rowv', mat%rowv(1:130)
!
! The horizontal sum on the NY-th column
! The NY-th row of matrix was stored in mat%rowv
! Store the sums ar column ny at MAT%COLH
!
zsum(1:n) = 0.0d0
DO j=1,ny
CALL getcol(mat, j, arr)
zsum(ny) = zsum(ny) + mat%rowv(j)
zsum(ny+1:bw0) = zsum(ny+1:bw0) + arr(ny+1:bw0)
IF( j .NE. ny ) THEN ! Clear columns 1:(ny-1)
mat%rowv(j) = 0.0d0
arr = 0.0d0; arr(j) = 1.0d0
CALL putcol(mat, j, arr)
END IF
END DO
mat%rowv(ny) = 0.0d0 ! Its value is now in mat%colh(ny)
arr = 0.0d0
CALL putcol(mat, ny, arr)
CALL putrow(mat, ny, arr)
mat%colh(1:bw0) = zsum(1:bw0) ! column ny
!
! Move the diagonal term from mat%colh back to main diagonal
!
CALL putele(mat, ny, ny, mat%colh(ny))
mat%colh(ny) = 0.0d0
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'rowv', mat%rowv(1:130)
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'colh', mat%colh(1:130)
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'colh', mat%val(1:ny,0)
!===========================================================================
! 3.0 Dirichlet on right boundary
!
DO j = n, n-ny+1, -1
arr = 0.0d0; arr(j) = 1.0d0
CALL putcol(mat, j, arr)
END DO
!
DO i = n, n-ny+1, -1
arr = 0.0d0; arr(i) = 1.0d0
CALL putrow(mat, i, arr)
END DO
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'diag 1', mat%val(n-ny-1:n,1)
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'diag 0', mat%val(n-ny:n,0)
!!$ WRITE(*,'(/a,/(10(f8.3)))') 'diag -1', mat%val(n-ny-1:n,-1)
!===========================================================================
! 9.0 Epilogue
!
END SUBROUTINE ibcmat
!===========================================================================
SUBROUTINE disrhs(mbess, spl, rhs)
!
! Assembly the RHS using 2d spline spl
!
USE bsplines
INTEGER, INTENT(in) :: mbess
TYPE(spline2d), INTENT(in) :: spl
DOUBLE PRECISION, INTENT(out) :: rhs(:)
!
INTEGER :: n1, nidbas1, ndim1, ng1
INTEGER :: n2, nidbas2, ndim2, ng2
INTEGER :: i, j, ig1, ig2, k1, k2, i1, j2, ij, nrank
DOUBLE PRECISION, ALLOCATABLE :: xg1(:), wg1(:), fun1(:,:)
DOUBLE PRECISION, ALLOCATABLE :: xg2(:), wg2(:), fun2(:,:)
DOUBLE PRECISION:: contrib
!===========================================================================
! 1.0 Prologue
!
! Properties of spline space
!
CALL get_dim(spl%sp1, ndim1, n1, nidbas1)
CALL get_dim(spl%sp2, ndim2, n2, nidbas2)
!
ALLOCATE(fun1(0:nidbas1,1)) ! needs only basis functions (no derivatives)
ALLOCATE(fun2(0:nidbas2,1)) ! needs only basis functions (no derivatives)
!
! Gauss quadature
!
CALL get_gauss(spl%sp1, ng1)
CALL get_gauss(spl%sp2, ng2)
WRITE(*,'(/a, 2i3)') 'Gauss points and weights, ngauss =', ng1, ng2
ALLOCATE(xg1(ng1), wg1(ng1))
ALLOCATE(xg2(ng1), wg2(ng1))
!===========================================================================
! 2.0 Assembly loop
!
nrank = SIZE(rhs)
rhs(1:nrank) = 0.0d0
!
DO i=1,n1
CALL get_gauss(spl%sp1, ng1, i, xg1, wg1)
DO ig1=1,ng1
CALL basfun(xg1(ig1), spl%sp1, fun1, i)
DO j=1,n2
CALL get_gauss(spl%sp2, ng2, j, xg2, wg2)
DO ig2=1,ng2
CALL basfun(xg2(ig2), spl%sp2, fun2, j)
contrib = wg1(ig1)*wg2(ig2) * &
& rhseq(xg1(ig1),xg2(ig2), mbess)
DO k1=0,nidbas1
i1 = i+k1
DO k2=0,nidbas2
j2 = MODULO(j+k2-1,n2) + 1
ij = j2 + (i1-1)*n2
rhs(ij) = rhs(ij) + contrib*fun1(k1,1)*fun2(k2,1)
END DO
END DO
END DO
END DO
END DO
END DO
!===========================================================================
! 9.0 Epilogue
!
DEALLOCATE(xg1, wg1, fun1)
DEALLOCATE(xg2, wg2, fun2)
!
CONTAINS
DOUBLE PRECISION FUNCTION rhseq(x1, x2, m)
DOUBLE PRECISION, INTENT(in) :: x1, x2
INTEGER, INTENT(in) :: m
rhseq = REAL(4*(m+1),8)*x1**(m+1)*COS(REAL(m,8)*x2)
END FUNCTION rhseq
END SUBROUTINE disrhs
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SUBROUTINE ibcrhs(rhs, ny)
!
! Apply BC on RHS
!
DOUBLE PRECISION, INTENT(inout) :: rhs(:)
INTEGER, INTENT(in) :: ny
!
INTEGER :: nrank
DOUBLE PRECISION :: zsum
!===========================================================================
! 1.0 Prologue
!
nrank = SIZE(rhs,1)
!===========================================================================
! 2.0 Unicity at the axis
!
! The vertical sum
!
zsum = SUM(rhs(1:ny))
rhs(ny) = zsum
rhs(1:ny-1) = 0.0d0
!===========================================================================
! 3.0 Dirichlet on right boundary
!
rhs(nrank-ny+1:nrank) = 0.0d0
END SUBROUTINE ibcrhs
!===========================================================================
SUBROUTINE cg(mat, rhs, omega, nitmx, rtolmx, sol, resid, nit, verbose, &
& nssor)
!
! Preconditionned Conjugate Gradient solver
!
USE cds
TYPE(cds_mat) :: mat
DOUBLE PRECISION, INTENT(in) :: rhs(:), omega, rtolmx
INTEGER, INTENT(in) :: nitmx
DOUBLE PRECISION, INTENT(inout) :: sol(:)
DOUBLE PRECISION, OPTIONAL, INTENT(out) :: resid
INTEGER, OPTIONAL, INTENT(out) :: nit
INTEGER, OPTIONAL, INTENT(in) :: nssor
LOGICAL, OPTIONAL, INTENT(in) :: verbose
!
DOUBLE PRECISION, DIMENSION(SIZE(rhs,1)) :: wr, wz, wp, wq
DOUBLE PRECISION :: bnrm2, residue, rho0, rho1, alpha, beta
INTEGER :: it
!
bnrm2 = SQRT(DOT_PRODUCT(rhs,rhs)) ! Euclidian norm of RHS
it = 0
wr = rhs-vmx(mat,sol)
!
!... Iteration loop (see fig. 2.5, p.15 of "Templates...")
DO
it = it+1
IF( PRESENT(nssor) ) THEN
CALL psolve(mat, wz, wr, omega, nssor)
ELSE
wz = wr
END IF
rho1 = DOT_PRODUCT(wr,wz)
IF( it .EQ. 1 ) THEN
wp = wz
ELSE
beta = rho1/rho0
wp = wz + beta*wp
END IF
wq = vmx(mat,wp)
alpha = rho1 / DOT_PRODUCT(wp,wq)
sol = sol + alpha*wp
wr = wr - alpha*wq
residue = SQRT(DOT_PRODUCT(wr,wr)) / bnrm2
IF( PRESENT(verbose) ) THEN
IF(verbose) WRITE(*,'(a,i8,1pe12.3)') 'it, resid', it, residue
END IF
IF( residue .LE. rtolmx .OR. it .GE. nitmx) EXIT
rho0 = rho1
END DO
IF(PRESENT(resid)) resid = residue
IF(PRESENT(nit)) nit = it
END SUBROUTINE cg
!===========================================================================
SUBROUTINE psolve(mat, x, b, omega, niter_in)
!
! Preconditionners
!
USE cds
TYPE(cds_mat) :: mat
DOUBLE PRECISION, INTENT(out) :: x(:)
DOUBLE PRECISION, INTENT(in) :: b(:)
DOUBLE PRECISION, INTENT(in) :: omega
INTEGER, OPTIONAL, INTENT(in) :: niter_in
!
INTEGER :: niter
DOUBLE PRECISION :: rtolmx
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
! 1. No-preconditionning
!
IF( omega .LT. 0.0d0 ) THEN
x = b
RETURN
END IF
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
! 2. SSOR Preconditionner
!
niter = 1
rtolmx = 1.d-6
IF(PRESENT(niter_in)) THEN
niter = niter_in
END IF
CALL ssor(mat, b, omega, niter, rtolmx, x)
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
END SUBROUTINE psolve
!
!===========================================================================
SUBROUTINE ssor(mat, b, omega, nitmx, rtolmx, x, resid, nit, verbose)
!
! Solve Ax = b using SSOR method
!
USE cds
TYPE(cds_mat) :: mat
DOUBLE PRECISION, INTENT(out) :: x(:)
DOUBLE PRECISION, INTENT(in) :: b(:)
DOUBLE PRECISION, INTENT(in) :: omega, rtolmx
INTEGER, INTENT(in) :: nitmx
DOUBLE PRECISION, OPTIONAL, INTENT(out) :: resid
INTEGER, OPTIONAL, INTENT(out) :: nit
LOGICAL, OPTIONAL, INTENT(in) :: verbose
!
INTEGER :: n, iter
INTEGER :: k, i, j, d, bw0, ny
DOUBLE PRECISION :: omega1, bnrm2, residue
DOUBLE PRECISION :: rhs(SIZE(x))
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
! 1. Initialization
n = SIZE(x)
bw0 = SIZE(mat%rowv)
ny = mat%ny
bnrm2 = norm2(b) ! Euclidian norm of RHS
omega1 = 1.0d0-omega
iter = 0
DO
iter = iter+1
!
! 2. Forward SOR
! Set RHS
rhs = omega*b ! rhs <- omega*b
IF( iter .GT. 1 ) THEN
rhs = rhs + omega1*x ! rhs <- rhs + (1-omega)*x0
DO k=1,mat%ku ! rhs <- rhs - omega*U*x0
d = mat%dists(k)
DO i=MAX(1,1-d),MIN(n,n-d)
rhs(i) = rhs(i) - omega*mat%val(i,k)*x(i+d)
END DO
END DO
IF( ny .NE. 0 ) THEN ! Contributions from unicity BC
rhs(ny) = rhs(ny) - omega*DOT_PRODUCT(mat%rowv(ny+1:bw0),x(ny+1:bw0))
END IF
END IF
!
! Solve (1+omega*L) x = rhs
x = rhs
IF( ny .NE. 0 ) THEN ! Contributions from unicity BC
rhs(ny+1:bw0) = rhs(ny+1:bw0) - omega*mat%colh(ny:bw0)*x(ny)
END IF
DO i=ny+1,n
DO k=-1,-mat%kl,-1
d = mat%dists(k)
j=i+d
IF( j.LE.0 ) EXIT
x(i) = x(i) - omega*mat%val(i,k)*x(j)
END DO
END DO
!
! 3. Backward SOR
! Set RHS
rhs = omega*b + omega1*x ! rhs <- omega*b + (1-omega)*x0
IF( ny .NE. 0 ) THEN ! Contributions from unicity BC
rhs(ny+1:bw0) = rhs(ny+1:bw0) - omega*mat%colh(ny:bw0)*x(ny)
END IF
DO k=-mat%kl,-1 ! rhs <- rhs - omega*L*x0
d = mat%dists(k)
DO i=MAX(1,1-d),MIN(n,n-d)
rhs(i) = rhs(i) - omega*mat%val(i,k)*x(i+d)
END DO
END DO
!
! Solve (1+omega*U) x = rhs
x = rhs
DO i=n-1,ny+1,-1
DO k=1,mat%ku
d = mat%dists(k)
j = i+d
IF( j.GT.n ) EXIT
x(i) = x(i) - omega*mat%val(i,k)*x(j)
END DO
END DO
IF( ny .NE. 0 ) THEN ! Contributions from unicity BC
x(ny) = x(ny) - omega*DOT_PRODUCT(mat%rowv(ny+1:bw0),x(ny+1:bw0))
END IF
!
! 4. Compute residue
!
IF( PRESENT(resid) ) THEN
residue = norm2(b-vmx(mat,x)) / bnrm2
IF(PRESENT(verbose)) THEN
IF(verbose) WRITE(*,'(a,i8,1pe12.3)') 'it, resid', iter, residue
END IF
IF( residue .LT. rtolmx ) EXIT
END IF
!
IF( iter .GE. nitmx ) EXIT
END DO ! End of SSOR iterations
IF(PRESENT(nit)) nit = iter
IF(PRESENT(resid)) resid = residue
END SUBROUTINE ssor
!===========================================================================
SUBROUTINE diagbal(mat)
!
! Diagonal matrix balancing: store D^(-1/2) in mat%bal
!
USE cds
TYPE(cds_mat) :: mat
INTEGER :: n, bw0, ny, d, i, k
DOUBLE PRECISION :: diag(mat%rank)
!
n = mat%rank
ny = mat%ny
bw0 = SIZE(mat%colh)
diag(1:n) = mat%val(1:n,0)
IF( MINVAL(diag) .LE. 0.0d0 ) THEN
WRITE(*,'(a)') 'Diagonal elements of matrix are not stricly positive!'
STOP
END IF
diag(1:n) = 1.0d0/SQRT(diag(1:n))
!
! Scale the matrix
!$OMP parallel do private (k,d,i)
DO k=-mat%kl,mat%ku
d = mat%dists(k)
DO i=MAX(1,1-d),MIN(n,n-d)
mat%val(i,k) = diag(i)*diag(i+d)*mat%val(i,k)
END DO
END DO
!$OMP end parallel do
!
! The ny^th column and row
IF( ny.NE.0 ) THEN
mat%rowv(1:bw0) = diag(1:bw0)*diag(ny)*mat%rowv(1:bw0)
mat%colh(1:bw0) = diag(ny)*diag(1:bw0)*mat%colh(1:bw0)
END IF
!
! Save D^(-1/2)
mat%bal(:) = diag(:)
END SUBROUTINE diagbal
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
FUNCTION norm2_1d(x)
!
! Compute the 2-norm of 1d array
!
DOUBLE PRECISION :: x(:)
DOUBLE PRECISION :: norm2_1d
norm2_1d = SQRT(SUM(x*x))
END FUNCTION norm2_1d
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
FUNCTION norm2_2d(x)
!
! Compute the 2-norm of 2d array
!
DOUBLE PRECISION :: x(:,:)
DOUBLE PRECISION :: norm2_2d
norm2_2d = SQRT(SUM(x*x))
END FUNCTION norm2_2d
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
END MODULE tcdsmat_mod