!>
!> @file test_jacobi.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
!>
!
! Test 2D parallel Jacobi using STENCIL_2D matrix-free structure.
!
MODULE mod
USE iso_fortran_env, ONLY : rkind => real64
IMPLICIT NONE
!
LOGICAL, PARAMETER :: nldebug=.FALSE.
REAL(rkind), PARAMETER :: PI=4.d0*ATAN(1.0d0)
CONTAINS
END MODULE mod
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
PROGRAM main
USE mpi
USE pputils2, ONLY : dist1d, exchange, norm2_vec=>ppnorm2, timera, hostlist
USE parmg, ONLY : jacobi, get_resids
USE stencil, ONLY : stencil_2d, init, laplacian, putmat
USE mod
IMPLICIT NONE
!
INTEGER, PARAMETER :: ndims=2
!
INTEGER :: me, neighs(4), npes, ierr
INTEGER, DIMENSION(ndims) :: dims=[0,0]
INTEGER, DIMENSION(ndims) :: coords, comm1d
LOGICAL, DIMENSION(ndims) :: periods=[.FALSE.,.FALSE.]
LOGICAL :: reorder =.FALSE.
INTEGER :: comm_cart
!
INTEGER :: nx=4, ny=4 ! Number of intervals
INTEGER, DIMENSION(ndims) :: e, s, lb, ub, npt_glob, npt_loc
!
REAL(rkind), ALLOCATABLE :: xgrid(:), ygrid(:)
REAL(rkind) :: dx, dy
INTEGER, DIMENSION(5,2) :: id ! 5-point stencil
INTEGER :: npoints
TYPE(stencil_2d) :: mat
INTEGER :: i
!
REAL(rkind), ALLOCATABLE :: f(:,:), v(:,:), u(:,:)
REAL(rkind), ALLOCATABLE :: resids(:,:), errs(:,:)
REAL(rkind), ALLOCATABLE :: resid_it(:), err_it(:)
REAL(rkind) :: omega=1.0d0, resid
INTEGER :: it, it_skip, nits=100
!
NAMELIST /in/ nx, ny, omega, nits
!================================================================================
! 1.0 Prologue
!
! 2D process grid
CALL mpi_init(ierr)
CALL mpi_comm_rank(MPI_COMM_WORLD, me, ierr)
CALL mpi_comm_size(MPI_COMM_WORLD, npes, ierr)
CALL mpi_dims_create(npes, ndims, dims, ierr)
CALL mpi_cart_create(MPI_COMM_WORLD, ndims, dims, periods, reorder, comm_cart,&
& ierr)
!
CALL mpi_comm_rank(comm_cart, me, ierr)
CALL mpi_cart_coords(comm_cart, me, ndims, coords, ierr)
CALL mpi_cart_shift(comm_cart, 0, 1, neighs(1), neighs(2), ierr)
CALL mpi_cart_shift(comm_cart, 1, 1, neighs(3), neighs(4), ierr)
!
CALL mpi_cart_sub(comm_cart, [.TRUE.,.FALSE.], comm1d(1), ierr)
CALL mpi_cart_sub(comm_cart, [.FALSE.,.TRUE.], comm1d(2), ierr)
!
CALL hostlist(comm_cart)
IF(me.EQ.0) THEN
WRITE(*,'(a,i0,a,i0/)') "Process grid: ", dims(1), " X ", dims(2)
END IF
!
! Read problem inputs
IF(me.EQ.0) THEN
READ(*,in)
WRITE(*,in)
END IF
!
CALL mpi_bcast(nx, 1, MPI_INTEGER, 0, comm_cart, ierr)
CALL mpi_bcast(ny, 1, MPI_INTEGER, 0, comm_cart, ierr)
CALL mpi_bcast(nits, 1, MPI_INTEGER, 0, comm_cart, ierr)
CALL mpi_bcast(omega, 1, MPI_DOUBLE_PRECISION, 0, comm_cart, ierr)
!================================================================================
! 2.0 2d Grid construction
!
! Partition 2D grid
CALL timera(0, 'Grid_construction')
npt_glob(1) = nx+1
npt_glob(2) = ny+1
DO i=1,ndims
CALL dist1d(comm1d(i), 0, npt_glob(i), s(i), npt_loc(i))
e(i) = s(i) + npt_loc(i) - 1
END DO
WRITE(*,'(a,i3.3,a,2(3i4,", "))') 'PE', me, ' coords, s,e:', &
& (coords(i),s(i),e(i),i=1,ndims)
!
! Global mesh
dx = 1.0d0/REAL(nx)
dy = 1.0d0/REAL(ny)
ALLOCATE(xgrid(0:nx))
ALLOCATE(ygrid(0:ny))
xgrid = [ (i*dx, i=0,nx) ]
ygrid = [ (i*dy, i=0,ny) ]
CALL timera(1, 'Grid_construction')
!================================================================================
! 3.0 FD Laplacian
!
CALL timera(0, 'Laplacian')
id=RESHAPE([ 0, -1, 0, 1, 0, &
0, 0,-1, 0, 1], &
[5,2])
npoints = 5
CALL init(s, e, id, .FALSE., mat, comm_cart)
!
CALL laplacian(dx, dy, mat)
CALL timera(1, 'Laplacian')
!================================================================================
! 4.0 Test parallel Jacobi with \nabla u(x,y) = f(x,y)
!
! Problem definition
!
s = mat%s
e = mat%e
lb = s-1
ub = e+1
ALLOCATE(f(lb(1):ub(1),lb(2):ub(2))) ! RHS
ALLOCATE(v(lb(1):ub(1),lb(2):ub(2))) ! Exact solutions
ALLOCATE(u(lb(1):ub(1),lb(2):ub(2))) ! Computed solutions
ALLOCATE(resids(lb(1):ub(1),lb(2):ub(2))) ! Residuals
ALLOCATE(errs(lb(1):ub(1),lb(2):ub(2))) ! Errors
ALLOCATE(resid_it(0:nits))
ALLOCATE(err_it(0:nits))
!
f(s(1):e(1),s(2):e(2)) = rhs(xgrid(s(1):e(1)),ygrid(s(2):e(2)))
v(s(1):e(1),s(2):e(2)) = exact(xgrid(s(1):e(1)),ygrid(s(2):e(2)))
CALL exchange(comm_cart, f)
CALL exchange(comm_cart, v)
!
! Residuals of exact solutions
resids = get_resids(mat,v,f)
resid = norm2_vec(resids, comm_cart)
!
! Jacobi iteration loop
!
IF(me.EQ.0) WRITE(*,'(/a6,t14,a,t34,a)') 'it', 'residual norm', 'discretization error'
u = 0.0d0
CALL exchange(comm_cart, u)
resids = get_resids(mat,u,f)
errs = u-v
resid_it(0) = norm2_vec(resids, comm_cart)
err_it(0) = norm2_vec(errs, comm_cart)
it_skip = MAX(1,nits/10)
!
CALL timera(0, 'Jacobi')
DO it=1,nits
CALL jacobi(mat, omega, 1, u, f)
CALL exchange(comm_cart, u)
resids = get_resids(mat,u,f)
errs = u-v
resid_it(it) = norm2_vec(resids, comm_cart)
err_it(it) = norm2_vec(errs, comm_cart)
IF(me.EQ.0 .AND. MODULO(it,it_skip).EQ.0 ) THEN
WRITE(*,'(i6,4(1pe12.3))') it, resid_it(it), resid_it(it)/resid_it(it-1),&
& err_it(it), err_it(it)/err_it(it-1)
END IF
END DO
CALL timera(1, 'Jacobi')
!================================================================================
! 9.0 Epilogue
CALL h5file
!
CALL timera(9, '')
CALL MPI_FINALIZE(ierr)
CONTAINS
SUBROUTINE disp(str, arr)
CHARACTER(len=*), INTENT(in) :: str
REAL(rkind), INTENT(in) :: arr(:,:)
INTEGER :: j
WRITE(*,'(/a)') str
DO j=1,SIZE(arr,2)
WRITE(*,'(10f8.3)') arr(:,j)
END DO
END SUBROUTINE disp
!
SUBROUTINE h5file
USE futils
CHARACTER(len=128) :: file='test_jacobi.h5'
INTEGER :: fid
CALL creatf(file, fid, real_prec='d', mpicomm=comm_cart)
CALL putarr(fid, '/xgrid', xgrid, ionode=0) ! only rank 0 does IO
CALL putarr(fid, '/ygrid', ygrid, ionode=0) ! only rank 0 does IO
!
CALL putarrnd(fid, '/f', f, (/1,2/), garea=(/1,1/))
CALL putarrnd(fid, '/v', v, (/1,2/), garea=(/1,1/))
CALL putarrnd(fid, '/u', u, (/1,2/), garea=(/1,1/))
CALL putarrnd(fid, '/errs', errs, (/1,2/), garea=(/1,1/))
CALL putarrnd(fid, '/resids', resids,(/1,2/), garea=(/1,1/))
!
CALL putarr(fid, '/resid', resid_it, ionode=0)
CALL putarr(fid, '/error', err_it, ionode=0)
!
CALL putmat(fid, '/MAT', mat)
CALL closef(fid)
END SUBROUTINE h5file
!
FUNCTION outerprod(x, y) RESULT(r)
!
! outer product
!
REAL(rkind), INTENT(in) :: x(:), y(:)
REAL(rkind) :: r(SIZE(x),SIZE(y))
INTEGER :: i, j
DO j=1,SIZE(y)
DO i=1,SIZE(x)
r(i,j) = x(i)*y(j)
END DO
END DO
END FUNCTION outerprod
!
FUNCTION rhs(x,y)
REAL(rkind), INTENT(in) :: x(:), y(:)
REAL(rkind) :: rhs(SIZE(x),SIZE(y))
rhs = -10.d0*pi**2 * outerprod(SIN(pi*x), SIN(3.d0*pi*y))
END FUNCTION rhs
!
FUNCTION exact(x,y)
REAL(rkind), INTENT(in) :: x(:), y(:)
REAL(rkind) :: exact(SIZE(x),SIZE(y))
exact = outerprod(SIN(pi*x), SIN(3.d0*pi*y))
END FUNCTION exact
END PROGRAM main