From 97237e709bdf3a42f442ecd7416dea48c6cd0008 Mon Sep 17 00:00:00 2001 From: Salvatore Filippone Date: Tue, 6 Apr 2021 17:02:42 +0200 Subject: [PATCH 1/6] New tests/gpu example program --- tests/gpu/Makefile | 52 ++ tests/gpu/amg_d_genpde_mod.f90 | 931 ++++++++++++++++++++++++++++ tests/gpu/amg_d_pde3d.f90 | 671 ++++++++++++++++++++ tests/gpu/amg_d_pde3d_base_mod.f90 | 65 ++ tests/gpu/amg_d_pde3d_exp_mod.f90 | 65 ++ tests/gpu/amg_d_pde3d_gauss_mod.f90 | 65 ++ tests/gpu/runs/amg_gpu_pde3d.inp | 55 ++ 7 files changed, 1904 insertions(+) create mode 100644 tests/gpu/Makefile create mode 100644 tests/gpu/amg_d_genpde_mod.f90 create mode 100644 tests/gpu/amg_d_pde3d.f90 create mode 100644 tests/gpu/amg_d_pde3d_base_mod.f90 create mode 100644 tests/gpu/amg_d_pde3d_exp_mod.f90 create mode 100644 tests/gpu/amg_d_pde3d_gauss_mod.f90 create mode 100644 tests/gpu/runs/amg_gpu_pde3d.inp diff --git a/tests/gpu/Makefile b/tests/gpu/Makefile new file mode 100644 index 00000000..5e12f775 --- /dev/null +++ b/tests/gpu/Makefile @@ -0,0 +1,52 @@ +AMGDIR=../.. +AMGINCDIR=$(AMGDIR)/include +include $(AMGINCDIR)/Make.inc.amg4psblas +AMGMODDIR=$(AMGDIR)/modules +AMGLIBDIR=$(AMGDIR)/lib +AMG_LIBS=-L$(AMGLIBDIR) -lpsb_krylov -lamg_prec -lpsb_prec +FINCLUDES=$(FMFLAG). $(FMFLAG)$(AMGMODDIR) $(FMFLAG)$(AMGINCDIR) $(PSBLAS_INCLUDES) $(FIFLAG). + +LINKOPT= +EXEDIR=./runs + +all: amg_s_pde3d amg_d_pde3d amg_s_pde2d amg_d_pde2d + +amg_d_pde3d: amg_d_pde3d.o amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o data_input.o + $(FLINK) $(LINKOPT) amg_d_pde3d.o amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o data_input.o -o amg_d_pde3d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_d_pde3d $(EXEDIR) + +amg_s_pde3d: amg_s_pde3d.o amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o data_input.o + $(FLINK) $(LINKOPT) amg_s_pde3d.o amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o data_input.o -o amg_s_pde3d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_s_pde3d $(EXEDIR) + +amg_d_pde2d: amg_d_pde2d.o amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o data_input.o + $(FLINK) $(LINKOPT) amg_d_pde2d.o amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o data_input.o -o amg_d_pde2d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_d_pde2d $(EXEDIR) + +amg_s_pde2d: amg_s_pde2d.o amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o data_input.o + $(FLINK) $(LINKOPT) amg_s_pde2d.o amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o data_input.o -o amg_s_pde2d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_s_pde2d $(EXEDIR) + +amg_d_pde3d_rebld: amg_d_pde3d_rebld.o data_input.o + $(FLINK) $(LINKOPT) amg_d_pde3d_rebld.o data_input.o -o amg_d_pde3d_rebld $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_d_pde3d_rebld $(EXEDIR) + +amg_d_pde3d.o amg_s_pde3d.o amg_d_pde2d.o amg_s_pde2d.o: data_input.o + +amg_d_pde3d.o: amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o +amg_s_pde3d.o: amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o +amg_d_pde2d.o: amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o +amg_s_pde2d.o: amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o + +check: all + cd runs && ./amg_d_pde2d f + else + f_ => d_null_func_3d + end if + + if (present(partition)) then + if ((1<= partition).and.(partition <= 3)) then + partition_ = partition + else + write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' + partition_ = 3 + end if + else + partition_ = 3 + end if + deltah = done/(idim+2) + sqdeltah = deltah*deltah + deltah2 = 2.0_psb_dpk_* deltah + + if (present(partition)) then + if ((1<= partition).and.(partition <= 3)) then + partition_ = partition + else + write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' + partition_ = 3 + end if + else + partition_ = 3 + end if + + ! initialize array descriptor and sparse matrix storage. provide an + ! estimate of the number of non zeroes + + m = (1_psb_lpk_*idim)*idim*idim + n = m + nnz = 7*((n+np-1)/np) + if(iam == psb_root_) write(psb_out_unit,'("Generating Matrix (size=",i0,")...")')n + t0 = psb_wtime() + select case(partition_) + case(1) + ! A BLOCK partition + if (present(nrl)) then + nr = nrl + else + ! + ! Using a simple BLOCK distribution. + ! + nt = (m+np-1)/np + nr = max(0,min(nt,m-(iam*nt))) + end if + + nt = nr + call psb_sum(ctxt,nt) + if (nt /= m) then + write(psb_err_unit,*) iam, 'Initialization error ',nr,nt,m + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + + ! + ! First example of use of CDALL: specify for each process a number of + ! contiguous rows + ! + call psb_cdall(ctxt,desc_a,info,nl=nr) + myidx = desc_a%get_global_indices() + nlr = size(myidx) + + case(2) + ! A partition defined by the user through IV + + if (present(iv)) then + if (size(iv) /= m) then + write(psb_err_unit,*) iam, 'Initialization error: wrong IV size',size(iv),m + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + else + write(psb_err_unit,*) iam, 'Initialization error: IV not present' + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + + ! + ! Second example of use of CDALL: specify for each row the + ! process that owns it + ! + call psb_cdall(ctxt,desc_a,info,vg=iv) + myidx = desc_a%get_global_indices() + nlr = size(myidx) + + case(3) + ! A 3-dimensional partition + + ! A nifty MPI function will split the process list + npdims = 0 + call mpi_dims_create(np,3,npdims,info) + npx = npdims(1) + npy = npdims(2) + npz = npdims(3) + + allocate(bndx(0:npx),bndy(0:npy),bndz(0:npz)) + ! We can reuse idx2ijk for process indices as well. + call idx2ijk(iamx,iamy,iamz,iam,npx,npy,npz,base=0) + ! Now let's split the 3D cube in hexahedra + call dist1Didx(bndx,idim,npx) + mynx = bndx(iamx+1)-bndx(iamx) + call dist1Didx(bndy,idim,npy) + myny = bndy(iamy+1)-bndy(iamy) + call dist1Didx(bndz,idim,npz) + mynz = bndz(iamz+1)-bndz(iamz) + + ! How many indices do I own? + nlr = mynx*myny*mynz + allocate(myidx(nlr)) + ! Now, let's generate the list of indices I own + nr = 0 + do i=bndx(iamx),bndx(iamx+1)-1 + do j=bndy(iamy),bndy(iamy+1)-1 + do k=bndz(iamz),bndz(iamz+1)-1 + nr = nr + 1 + call ijk2idx(myidx(nr),i,j,k,idim,idim,idim) + end do + end do + end do + if (nr /= nlr) then + write(psb_err_unit,*) iam,iamx,iamy,iamz, 'Initialization error: NR vs NLR ',& + & nr,nlr,mynx,myny,mynz + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + end if + + ! + ! Third example of use of CDALL: specify for each process + ! the set of global indices it owns. + ! + call psb_cdall(ctxt,desc_a,info,vl=myidx) + + ! + ! Specify process topology + ! + block + ! + ! Use adjcncy methods + ! + integer(psb_mpk_), allocatable :: neighbours(:) + integer(psb_mpk_) :: cnt + logical, parameter :: debug_adj=.true. + if (debug_adj.and.(np > 1)) then + cnt = 0 + allocate(neighbours(np)) + if (iamx < npx-1) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx+1,iamy,iamz,npx,npy,npz,base=0) + end if + if (iamy < npy-1) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy+1,iamz,npx,npy,npz,base=0) + end if + if (iamz < npz-1) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy,iamz+1,npx,npy,npz,base=0) + end if + if (iamx >0) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx-1,iamy,iamz,npx,npy,npz,base=0) + end if + if (iamy >0) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy-1,iamz,npx,npy,npz,base=0) + end if + if (iamz >0) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy,iamz-1,npx,npy,npz,base=0) + end if + call psb_realloc(cnt, neighbours,info) + call desc_a%set_p_adjcncy(neighbours) + !write(0,*) iam,' Check on neighbours: ',desc_a%get_p_adjcncy() + end if + end block + + case default + write(psb_err_unit,*) iam, 'Initialization error: should not get here' + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end select + + + if (info == psb_success_) call psb_spall(a,desc_a,info,nnz=nnz) + ! define rhs from boundary conditions; also build initial guess + if (info == psb_success_) call psb_geall(xv,desc_a,info) + if (info == psb_success_) call psb_geall(bv,desc_a,info) + + call psb_barrier(ctxt) + talc = psb_wtime()-t0 + + if (info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='allocation rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + ! we build an auxiliary matrix consisting of one row at a + ! time; just a small matrix. might be extended to generate + ! a bunch of rows per call. + ! + allocate(val(20*nb),irow(20*nb),& + &icol(20*nb),stat=info) + if (info /= psb_success_ ) then + info=psb_err_alloc_dealloc_ + call psb_errpush(info,name) + goto 9999 + endif + + + ! loop over rows belonging to current process in a block + ! distribution. + + call psb_barrier(ctxt) + t1 = psb_wtime() + do ii=1, nlr,nb + ib = min(nb,nlr-ii+1) + icoeff = 1 + do k=1,ib + i=ii+k-1 + ! local matrix pointer + glob_row=myidx(i) + ! compute gridpoint coordinates + call idx2ijk(ix,iy,iz,glob_row,idim,idim,idim) + ! x, y, z coordinates + x = (ix-1)*deltah + y = (iy-1)*deltah + z = (iz-1)*deltah + zt(k) = f_(x,y,z) + ! internal point: build discretization + ! + ! term depending on (x-1,y,z) + ! + val(icoeff) = -a1(x,y,z)/sqdeltah-b1(x,y,z)/deltah2 + if (ix == 1) then + zt(k) = g(dzero,y,z)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix-1,iy,iz,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x,y-1,z) + val(icoeff) = -a2(x,y,z)/sqdeltah-b2(x,y,z)/deltah2 + if (iy == 1) then + zt(k) = g(x,dzero,z)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy-1,iz,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x,y,z-1) + val(icoeff)=-a3(x,y,z)/sqdeltah-b3(x,y,z)/deltah2 + if (iz == 1) then + zt(k) = g(x,y,dzero)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy,iz-1,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + + ! term depending on (x,y,z) + val(icoeff)=(2*done)*(a1(x,y,z)+a2(x,y,z)+a3(x,y,z))/sqdeltah & + & + c(x,y,z) + call ijk2idx(icol(icoeff),ix,iy,iz,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + ! term depending on (x,y,z+1) + val(icoeff)=-a3(x,y,z)/sqdeltah+b3(x,y,z)/deltah2 + if (iz == idim) then + zt(k) = g(x,y,done)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy,iz+1,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x,y+1,z) + val(icoeff)=-a2(x,y,z)/sqdeltah+b2(x,y,z)/deltah2 + if (iy == idim) then + zt(k) = g(x,done,z)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy+1,iz,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x+1,y,z) + val(icoeff)=-a1(x,y,z)/sqdeltah+b1(x,y,z)/deltah2 + if (ix==idim) then + zt(k) = g(done,y,z)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix+1,iy,iz,idim,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + + end do + call psb_spins(icoeff-1,irow,icol,val,a,desc_a,info) + if(info /= psb_success_) exit + call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),bv,desc_a,info) + if(info /= psb_success_) exit + zt(:)=dzero + call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),xv,desc_a,info) + if(info /= psb_success_) exit + end do + + tgen = psb_wtime()-t1 + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='insert rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + deallocate(val,irow,icol) + + call psb_barrier(ctxt) + t1 = psb_wtime() + call psb_cdasb(desc_a,info) + tcdasb = psb_wtime()-t1 + call psb_barrier(ctxt) + t1 = psb_wtime() + if (info == psb_success_) then + if (present(amold)) then + call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,mold=amold) + else + call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,afmt=afmt) + end if + end if + call psb_barrier(ctxt) + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='asb rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + if (info == psb_success_) call psb_geasb(xv,desc_a,info,mold=vmold) + if (info == psb_success_) call psb_geasb(bv,desc_a,info,mold=vmold) + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='asb rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + tasb = psb_wtime()-t1 + call psb_barrier(ctxt) + ttot = psb_wtime() - t0 + + call psb_amx(ctxt,talc) + call psb_amx(ctxt,tgen) + call psb_amx(ctxt,tasb) + call psb_amx(ctxt,ttot) + if(iam == psb_root_) then + tmpfmt = a%get_fmt() + write(psb_out_unit,'("The matrix has been generated and assembled in ",a3," format.")')& + & tmpfmt + write(psb_out_unit,'("-allocation time : ",es12.5)') talc + write(psb_out_unit,'("-coeff. gen. time : ",es12.5)') tgen + write(psb_out_unit,'("-desc asbly time : ",es12.5)') tcdasb + write(psb_out_unit,'("- mat asbly time : ",es12.5)') tasb + write(psb_out_unit,'("-total time : ",es12.5)') ttot + + end if + call psb_erractionrestore(err_act) + return + +9999 call psb_error_handler(ctxt,err_act) + + return + end subroutine amg_d_gen_pde3d + + + + ! + ! subroutine to allocate and fill in the coefficient matrix and + ! the rhs. + ! + subroutine amg_d_gen_pde2d(ctxt,idim,a,bv,xv,desc_a,afmt,& + & a1,a2,b1,b2,c,g,info,f,amold,vmold,partition, nrl,iv) + use psb_base_mod + use psb_util_mod + ! + ! Discretizes the partial differential equation + ! + ! d d(u) d d(u) b1 d(u) b2 d(u) + ! - -- a1 ---- - -- a1 ---- + ----- + ------ + c u = f + ! dx dx dy dy dx dy + ! + ! with Dirichlet boundary conditions + ! u = g + ! + ! on the unit square 0<=x,y<=1. + ! + ! + ! Note that if b1=b2=c=0., the PDE is the Laplace equation. + ! + implicit none + procedure(d_func_2d) :: b1,b2,c,a1,a2,g + integer(psb_ipk_) :: idim + type(psb_dspmat_type) :: a + type(psb_d_vect_type) :: xv,bv + type(psb_desc_type) :: desc_a + integer(psb_ipk_) :: info + type(psb_ctxt_type) :: ctxt + character :: afmt*5 + procedure(d_func_2d), optional :: f + class(psb_d_base_sparse_mat), optional :: amold + class(psb_d_base_vect_type), optional :: vmold + integer(psb_ipk_), optional :: partition, nrl,iv(:) + ! Local variables. + + integer(psb_ipk_), parameter :: nb=20 + type(psb_d_csc_sparse_mat) :: acsc + type(psb_d_coo_sparse_mat) :: acoo + type(psb_d_csr_sparse_mat) :: acsr + real(psb_dpk_) :: zt(nb),x,y,z,xph,xmh,yph,ymh,zph,zmh + integer(psb_ipk_) :: nnz,nr,nlr,i,j,ii,ib,k, partition_ + integer(psb_lpk_) :: m,n,glob_row,nt + integer(psb_ipk_) :: ix,iy,iz,ia,indx_owner + ! For 2D partition + ! Note: integer control variables going directly into an MPI call + ! must be 4 bytes, i.e. psb_mpk_ + integer(psb_mpk_) :: npdims(2), npp, minfo + integer(psb_ipk_) :: npx,npy,iamx,iamy,mynx,myny + integer(psb_ipk_), allocatable :: bndx(:),bndy(:) + ! Process grid + integer(psb_ipk_) :: np, iam + integer(psb_ipk_) :: icoeff + integer(psb_lpk_), allocatable :: irow(:),icol(:),myidx(:) + real(psb_dpk_), allocatable :: val(:) + ! deltah dimension of each grid cell + ! deltat discretization time + real(psb_dpk_) :: deltah, sqdeltah, deltah2, dd + real(psb_dpk_), parameter :: rhs=0.d0,one=done,zero=0.d0 + real(psb_dpk_) :: t0, t1, t2, t3, tasb, talc, ttot, tgen, tcdasb + integer(psb_ipk_) :: err_act + procedure(d_func_2d), pointer :: f_ + character(len=20) :: name, ch_err,tmpfmt + + info = psb_success_ + name = 'create_matrix' + call psb_erractionsave(err_act) + + call psb_info(ctxt, iam, np) + + + if (present(f)) then + f_ => f + else + f_ => d_null_func_2d + end if + + deltah = done/(idim+2) + sqdeltah = deltah*deltah + deltah2 = 2.0_psb_dpk_* deltah + + + if (present(partition)) then + if ((1<= partition).and.(partition <= 3)) then + partition_ = partition + else + write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' + partition_ = 3 + end if + else + partition_ = 3 + end if + + ! initialize array descriptor and sparse matrix storage. provide an + ! estimate of the number of non zeroes + + m = (1_psb_lpk_)*idim*idim + n = m + nnz = 7*((n+np-1)/np) + if(iam == psb_root_) write(psb_out_unit,'("Generating Matrix (size=",i0,")...")')n + t0 = psb_wtime() + select case(partition_) + case(1) + ! A BLOCK partition + if (present(nrl)) then + nr = nrl + else + ! + ! Using a simple BLOCK distribution. + ! + nt = (m+np-1)/np + nr = max(0,min(nt,m-(iam*nt))) + end if + + nt = nr + call psb_sum(ctxt,nt) + if (nt /= m) then + write(psb_err_unit,*) iam, 'Initialization error ',nr,nt,m + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + + ! + ! First example of use of CDALL: specify for each process a number of + ! contiguous rows + ! + call psb_cdall(ctxt,desc_a,info,nl=nr) + myidx = desc_a%get_global_indices() + nlr = size(myidx) + + case(2) + ! A partition defined by the user through IV + + if (present(iv)) then + if (size(iv) /= m) then + write(psb_err_unit,*) iam, 'Initialization error: wrong IV size',size(iv),m + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + else + write(psb_err_unit,*) iam, 'Initialization error: IV not present' + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end if + + ! + ! Second example of use of CDALL: specify for each row the + ! process that owns it + ! + call psb_cdall(ctxt,desc_a,info,vg=iv) + myidx = desc_a%get_global_indices() + nlr = size(myidx) + + case(3) + ! A 2-dimensional partition + + ! A nifty MPI function will split the process list + npdims = 0 + call mpi_dims_create(np,2,npdims,info) + npx = npdims(1) + npy = npdims(2) + + allocate(bndx(0:npx),bndy(0:npy)) + ! We can reuse idx2ijk for process indices as well. + call idx2ijk(iamx,iamy,iam,npx,npy,base=0) + ! Now let's split the 2D square in rectangles + call dist1Didx(bndx,idim,npx) + mynx = bndx(iamx+1)-bndx(iamx) + call dist1Didx(bndy,idim,npy) + myny = bndy(iamy+1)-bndy(iamy) + + ! How many indices do I own? + nlr = mynx*myny + allocate(myidx(nlr)) + ! Now, let's generate the list of indices I own + nr = 0 + do i=bndx(iamx),bndx(iamx+1)-1 + do j=bndy(iamy),bndy(iamy+1)-1 + nr = nr + 1 + call ijk2idx(myidx(nr),i,j,idim,idim) + end do + end do + if (nr /= nlr) then + write(psb_err_unit,*) iam,iamx,iamy, 'Initialization error: NR vs NLR ',& + & nr,nlr,mynx,myny + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + end if + + ! + ! Third example of use of CDALL: specify for each process + ! the set of global indices it owns. + ! + call psb_cdall(ctxt,desc_a,info,vl=myidx) + + ! + ! Specify process topology + ! + block + ! + ! Use adjcncy methods + ! + integer(psb_mpk_), allocatable :: neighbours(:) + integer(psb_mpk_) :: cnt + logical, parameter :: debug_adj=.true. + if (debug_adj.and.(np > 1)) then + cnt = 0 + allocate(neighbours(np)) + if (iamx < npx-1) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx+1,iamy,npx,npy,base=0) + end if + if (iamy < npy-1) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy+1,npx,npy,base=0) + end if + if (iamx >0) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx-1,iamy,npx,npy,base=0) + end if + if (iamy >0) then + cnt = cnt + 1 + call ijk2idx(neighbours(cnt),iamx,iamy-1,npx,npy,base=0) + end if + call psb_realloc(cnt, neighbours,info) + call desc_a%set_p_adjcncy(neighbours) + !write(0,*) iam,' Check on neighbours: ',desc_a%get_p_adjcncy() + end if + end block + + case default + write(psb_err_unit,*) iam, 'Initialization error: should not get here' + info = -1 + call psb_barrier(ctxt) + call psb_abort(ctxt) + return + end select + + + if (info == psb_success_) call psb_spall(a,desc_a,info,nnz=nnz) + ! define rhs from boundary conditions; also build initial guess + if (info == psb_success_) call psb_geall(xv,desc_a,info) + if (info == psb_success_) call psb_geall(bv,desc_a,info) + + call psb_barrier(ctxt) + talc = psb_wtime()-t0 + + if (info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='allocation rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + ! we build an auxiliary matrix consisting of one row at a + ! time; just a small matrix. might be extended to generate + ! a bunch of rows per call. + ! + allocate(val(20*nb),irow(20*nb),& + &icol(20*nb),stat=info) + if (info /= psb_success_ ) then + info=psb_err_alloc_dealloc_ + call psb_errpush(info,name) + goto 9999 + endif + + + ! loop over rows belonging to current process in a block + ! distribution. + + call psb_barrier(ctxt) + t1 = psb_wtime() + do ii=1, nlr,nb + ib = min(nb,nlr-ii+1) + icoeff = 1 + do k=1,ib + i=ii+k-1 + ! local matrix pointer + glob_row=myidx(i) + ! compute gridpoint coordinates + call idx2ijk(ix,iy,glob_row,idim,idim) + ! x, y coordinates + x = (ix-1)*deltah + y = (iy-1)*deltah + + zt(k) = f_(x,y) + ! internal point: build discretization + ! + ! term depending on (x-1,y) + ! + val(icoeff) = -a1(x,y)/sqdeltah-b1(x,y)/deltah2 + if (ix == 1) then + zt(k) = g(dzero,y)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix-1,iy,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x,y-1) + val(icoeff) = -a2(x,y)/sqdeltah-b2(x,y)/deltah2 + if (iy == 1) then + zt(k) = g(x,dzero)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy-1,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + + ! term depending on (x,y) + val(icoeff)=(2*done)*(a1(x,y) + a2(x,y))/sqdeltah + c(x,y) + call ijk2idx(icol(icoeff),ix,iy,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + ! term depending on (x,y+1) + val(icoeff)=-a2(x,y)/sqdeltah+b2(x,y)/deltah2 + if (iy == idim) then + zt(k) = g(x,done)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix,iy+1,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + ! term depending on (x+1,y) + val(icoeff)=-a1(x,y)/sqdeltah+b1(x,y)/deltah2 + if (ix==idim) then + zt(k) = g(done,y)*(-val(icoeff)) + zt(k) + else + call ijk2idx(icol(icoeff),ix+1,iy,idim,idim) + irow(icoeff) = glob_row + icoeff = icoeff+1 + endif + + end do + call psb_spins(icoeff-1,irow,icol,val,a,desc_a,info) + if(info /= psb_success_) exit + call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),bv,desc_a,info) + if(info /= psb_success_) exit + zt(:)=dzero + call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),xv,desc_a,info) + if(info /= psb_success_) exit + end do + + tgen = psb_wtime()-t1 + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='insert rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + deallocate(val,irow,icol) + + call psb_barrier(ctxt) + t1 = psb_wtime() + call psb_cdasb(desc_a,info) + tcdasb = psb_wtime()-t1 + call psb_barrier(ctxt) + t1 = psb_wtime() + if (info == psb_success_) then + if (present(amold)) then + call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,mold=amold) + else + call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,afmt=afmt) + end if + end if + call psb_barrier(ctxt) + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='asb rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + if (info == psb_success_) call psb_geasb(xv,desc_a,info,mold=vmold) + if (info == psb_success_) call psb_geasb(bv,desc_a,info,mold=vmold) + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='asb rout.' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + tasb = psb_wtime()-t1 + call psb_barrier(ctxt) + ttot = psb_wtime() - t0 + + call psb_amx(ctxt,talc) + call psb_amx(ctxt,tgen) + call psb_amx(ctxt,tasb) + call psb_amx(ctxt,ttot) + if(iam == psb_root_) then + tmpfmt = a%get_fmt() + write(psb_out_unit,'("The matrix has been generated and assembled in ",a3," format.")')& + & tmpfmt + write(psb_out_unit,'("-allocation time : ",es12.5)') talc + write(psb_out_unit,'("-coeff. gen. time : ",es12.5)') tgen + write(psb_out_unit,'("-desc asbly time : ",es12.5)') tcdasb + write(psb_out_unit,'("- mat asbly time : ",es12.5)') tasb + write(psb_out_unit,'("-total time : ",es12.5)') ttot + + end if + call psb_erractionrestore(err_act) + return + +9999 continue + call psb_erractionrestore(err_act) + if (err_act == psb_act_abort_) then + call psb_error(ctxt) + return + end if + return + end subroutine amg_d_gen_pde2d +end module amg_d_genpde_mod diff --git a/tests/gpu/amg_d_pde3d.f90 b/tests/gpu/amg_d_pde3d.f90 new file mode 100644 index 00000000..c7526f80 --- /dev/null +++ b/tests/gpu/amg_d_pde3d.f90 @@ -0,0 +1,671 @@ +! +! +! AMG4PSBLAS version 1.0 +! Algebraic Multigrid Package +! based on PSBLAS (Parallel Sparse BLAS version 3.7) +! +! (C) Copyright 2021 +! +! Salvatore Filippone +! Pasqua D'Ambra +! Fabio Durastante +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions +! are met: +! 1. Redistributions of source code must retain the above copyright +! notice, this list of conditions and the following disclaimer. +! 2. Redistributions in binary form must reproduce the above copyright +! notice, this list of conditions, and the following disclaimer in the +! documentation and/or other materials provided with the distribution. +! 3. The name of the AMG4PSBLAS group or the names of its contributors may +! not be used to endorse or promote products derived from this +! software without specific written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +! ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +! TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +! PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AMG4PSBLAS GROUP OR ITS CONTRIBUTORS +! BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +! CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +! SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +! CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +! ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +! POSSIBILITY OF SUCH DAMAGE. +! +! +! +! File: amg_d_pde3d.f90 +! +! Program: amg_d_pde3d +! This sample program solves a linear system obtained by discretizing a +! PDE with Dirichlet BCs. +! +! +! The PDE is a general second order equation in 3d +! +! a1 dd(u) a2 dd(u) a3 dd(u) b1 d(u) b2 d(u) b3 d(u) +! - ------ - ------ - ------ + ----- + ------ + ------ + c u = f +! dxdx dydy dzdz dx dy dz +! +! with Dirichlet boundary conditions +! u = g +! +! on the unit cube 0<=x,y,z<=1. +! +! +! Note that if b1=b2=b3=c=0., the PDE is the Laplace equation. +! +! There are three choices available for data distribution: +! 1. A simple BLOCK distribution +! 2. A ditribution based on arbitrary assignment of indices to processes, +! typically from a graph partitioner +! 3. A 3D distribution in which the unit cube is partitioned +! into subcubes, each one assigned to a process. +! +program amg_d_pde3d + use psb_base_mod + use amg_prec_mod + use psb_krylov_mod + use psb_util_mod + use psb_gpu_mod + use data_input + use amg_d_pde3d_base_mod + use amg_d_pde3d_exp_mod + use amg_d_pde3d_gauss_mod + use amg_d_genpde_mod + implicit none + + ! input parameters + character(len=20) :: kmethd, ptype + character(len=5) :: afmt, pdecoeff + integer(psb_ipk_) :: idim + integer(psb_epk_) :: system_size + + ! miscellaneous + real(psb_dpk_) :: t1, t2, tprec, thier, tslv + + ! sparse matrix and preconditioner + type(psb_dspmat_type) :: a + type(amg_dprec_type) :: prec + ! GPU variables + type(psb_d_hlg_sparse_mat) :: agmold + type(psb_d_vect_gpu) :: vgmold + type(psb_i_vect_gpu) :: igmold + + ! descriptor + type(psb_desc_type) :: desc_a + ! dense vectors + type(psb_d_vect_type) :: x,b,r + ! parallel environment + type(psb_ctxt_type) :: ctxt + integer(psb_ipk_) :: iam, np + + ! solver parameters + integer(psb_ipk_) :: iter, itmax,itrace, istopc, irst, nlv + integer(psb_epk_) :: amatsize, precsize, descsize + real(psb_dpk_) :: err, resmx, resmxp + + ! Krylov solver data + type solverdata + character(len=40) :: kmethd ! Krylov solver + integer(psb_ipk_) :: istopc ! stopping criterion + integer(psb_ipk_) :: itmax ! maximum number of iterations + integer(psb_ipk_) :: itrace ! tracing + integer(psb_ipk_) :: irst ! restart + real(psb_dpk_) :: eps ! stopping tolerance + end type solverdata + type(solverdata) :: s_choice + + ! preconditioner data + type precdata + + ! preconditioner type + character(len=40) :: descr ! verbose description of the prec + character(len=10) :: ptype ! preconditioner type + + integer(psb_ipk_) :: outer_sweeps ! number of outer sweeps: sweeps for 1-level, + ! AMG cycles for ML + ! general AMG data + character(len=16) :: mlcycle ! AMG cycle type + integer(psb_ipk_) :: maxlevs ! maximum number of levels in AMG preconditioner + + ! AMG aggregation + character(len=16) :: aggr_prol ! aggregation type: SMOOTHED, NONSMOOTHED + character(len=16) :: par_aggr_alg ! parallel aggregation algorithm: DEC, SYMDEC + character(len=16) :: aggr_ord ! ordering for aggregation: NATURAL, DEGREE + character(len=16) :: aggr_filter ! filtering: FILTER, NO_FILTER + real(psb_dpk_) :: mncrratio ! minimum aggregation ratio + real(psb_dpk_), allocatable :: athresv(:) ! smoothed aggregation threshold vector + integer(psb_ipk_) :: thrvsz ! size of threshold vector + real(psb_dpk_) :: athres ! smoothed aggregation threshold + integer(psb_ipk_) :: csizepp ! minimum size of coarsest matrix per process + + ! AMG smoother or pre-smoother; also 1-lev preconditioner + character(len=16) :: smther ! (pre-)smoother type: BJAC, AS + integer(psb_ipk_) :: jsweeps ! (pre-)smoother / 1-lev prec. sweeps + integer(psb_ipk_) :: novr ! number of overlap layers + character(len=16) :: restr ! restriction over application of AS + character(len=16) :: prol ! prolongation over application of AS + character(len=16) :: solve ! local subsolver type: ILU, MILU, ILUT, + ! UMF, MUMPS, SLU, FWGS, BWGS, JAC + character(len=16) :: variant ! AINV variant: LLK, etc + integer(psb_ipk_) :: fill ! fill-in for incomplete LU factorization + integer(psb_ipk_) :: invfill ! Inverse fill-in for INVK + real(psb_dpk_) :: thr ! threshold for ILUT factorization + + ! AMG post-smoother; ignored by 1-lev preconditioner + character(len=16) :: smther2 ! post-smoother type: BJAC, AS + integer(psb_ipk_) :: jsweeps2 ! post-smoother sweeps + integer(psb_ipk_) :: novr2 ! number of overlap layers + character(len=16) :: restr2 ! restriction over application of AS + character(len=16) :: prol2 ! prolongation over application of AS + character(len=16) :: solve2 ! local subsolver type: ILU, MILU, ILUT, + ! UMF, MUMPS, SLU, FWGS, BWGS, JAC + character(len=16) :: variant2 ! AINV variant: LLK, etc + integer(psb_ipk_) :: fill2 ! fill-in for incomplete LU factorization + integer(psb_ipk_) :: invfill2 ! Inverse fill-in for INVK + real(psb_dpk_) :: thr2 ! threshold for ILUT factorization + + ! coarsest-level solver + character(len=16) :: cmat ! coarsest matrix layout: REPL, DIST + character(len=16) :: csolve ! coarsest-lev solver: BJAC, SLUDIST (distr. + ! mat.); UMF, MUMPS, SLU, ILU, ILUT, MILU + ! (repl. mat.) + character(len=16) :: csbsolve ! coarsest-lev local subsolver: ILU, ILUT, + ! MILU, UMF, MUMPS, SLU + integer(psb_ipk_) :: cfill ! fill-in for incomplete LU factorization + real(psb_dpk_) :: cthres ! threshold for ILUT factorization + integer(psb_ipk_) :: cjswp ! sweeps for GS or JAC coarsest-lev subsolver + + end type precdata + type(precdata) :: p_choice + + ! other variables + integer(psb_ipk_) :: info, i, k + character(len=20) :: name,ch_err + + info=psb_success_ + + + call psb_init(ctxt) + call psb_info(ctxt,iam,np) + ! + ! BEWARE: if you have NGPUS per node, the default is to + ! attach to mod(IAM,NGPUS) + ! + call psb_gpu_init(ictxt) + + if (iam < 0) then + ! This should not happen, but just in case + call psb_exit(ctxt) + stop + endif + if(psb_get_errstatus() /= 0) goto 9999 + name='amg_d_pde3d' + call psb_set_errverbosity(itwo) + ! + ! Hello world + ! + if (iam == psb_root_) then + write(*,*) 'Welcome to AMG4PSBLAS version: ',amg_version_string_ + write(*,*) 'This is the ',trim(name),' sample program' + end if + write(*,*) 'Process ',iam,' running on device: ', psb_cuda_getDevice(),' out of', psb_cuda_getDeviceCount() + write(*,*) 'Process ',iam,' device ', psb_cuda_getDevice(),' is a: ', trim(psb_gpu_DeviceName()) + + + ! + ! get parameters + ! + call get_parms(ctxt,afmt,idim,s_choice,p_choice,pdecoeff) + + ! + ! allocate and fill in the coefficient matrix, rhs and initial guess + ! + + call psb_barrier(ctxt) + t1 = psb_wtime() + select case(psb_toupper(trim(pdecoeff))) + case("CONST") + call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& + & a1,a2,a3,b1,b2,b3,c,g,info) + case("EXP") + call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& + & a1_exp,a2_exp,a3_exp,b1_exp,b2_exp,b3_exp,c_exp,g_exp,info) + case("GAUSS") + call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& + & a1_gauss,a2_gauss,a3_gauss,b1_gauss,b2_gauss,b3_gauss,c_gauss,g_gauss,info) + case default + info=psb_err_from_subroutine_ + ch_err='amg_gen_pdecoeff' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end select + + + call psb_barrier(ctxt) + t2 = psb_wtime() - t1 + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='amg_gen_pde3d' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + if (iam == psb_root_) & + & write(psb_out_unit,'("PDE Coefficients : ",a)')pdecoeff + if (iam == psb_root_) & + & write(psb_out_unit,'("Overall matrix creation time : ",es12.5)')t2 + if (iam == psb_root_) & + & write(psb_out_unit,'(" ")') + ! + ! initialize the preconditioner + ! + call prec%init(ctxt,p_choice%ptype,info) + select case(trim(psb_toupper(p_choice%ptype))) + case ('NONE','NOPREC') + ! Do nothing, keep defaults + + case ('JACOBI','L1-JACOBI','GS','FWGS','FBGS') + ! 1-level sweeps from "outer_sweeps" + call prec%set('smoother_sweeps', p_choice%jsweeps, info) + + case ('BJAC') + call prec%set('smoother_sweeps', p_choice%jsweeps, info) + call prec%set('sub_solve', p_choice%solve, info) + call prec%set('sub_fillin', p_choice%fill, info) + call prec%set('sub_iluthrs', p_choice%thr, info) + + case('AS') + call prec%set('smoother_sweeps', p_choice%jsweeps, info) + call prec%set('sub_ovr', p_choice%novr, info) + call prec%set('sub_restr', p_choice%restr, info) + call prec%set('sub_prol', p_choice%prol, info) + call prec%set('sub_solve', p_choice%solve, info) + call prec%set('sub_fillin', p_choice%fill, info) + call prec%set('sub_iluthrs', p_choice%thr, info) + + case ('ML') + ! multilevel preconditioner + + call prec%set('ml_cycle', p_choice%mlcycle, info) + call prec%set('outer_sweeps', p_choice%outer_sweeps,info) + if (p_choice%csizepp>0)& + & call prec%set('min_coarse_size_per_process', p_choice%csizepp, info) + if (p_choice%mncrratio>1)& + & call prec%set('min_cr_ratio', p_choice%mncrratio, info) + if (p_choice%maxlevs>0)& + & call prec%set('max_levs', p_choice%maxlevs, info) + if (p_choice%athres >= dzero) & + & call prec%set('aggr_thresh', p_choice%athres, info) + if (p_choice%thrvsz>0) then + do k=1,min(p_choice%thrvsz,size(prec%precv)-1) + call prec%set('aggr_thresh', p_choice%athresv(k), info,ilev=(k+1)) + end do + end if + + call prec%set('aggr_prol', p_choice%aggr_prol, info) + call prec%set('par_aggr_alg', p_choice%par_aggr_alg, info) + call prec%set('aggr_ord', p_choice%aggr_ord, info) + call prec%set('aggr_filter', p_choice%aggr_filter,info) + + + call prec%set('smoother_type', p_choice%smther, info) + call prec%set('smoother_sweeps', p_choice%jsweeps, info) + + select case (psb_toupper(p_choice%smther)) + case ('GS','BWGS','FBGS','JACOBI','L1-JACOBI','L1-FBGS') + ! do nothing + case default + call prec%set('sub_ovr', p_choice%novr, info) + call prec%set('sub_restr', p_choice%restr, info) + call prec%set('sub_prol', p_choice%prol, info) + select case(trim(psb_toupper(p_choice%solve))) + case('INVK') + call prec%set('sub_solve', p_choice%solve, info) + case('INVT') + call prec%set('sub_solve', p_choice%solve, info) + case('AINV') + call prec%set('sub_solve', p_choice%solve, info) + call prec%set('ainv_alg', p_choice%variant, info) + case default + call prec%set('sub_solve', p_choice%solve, info) + end select + + call prec%set('sub_fillin', p_choice%fill, info) + call prec%set('inv_fillin', p_choice%invfill, info) + call prec%set('sub_iluthrs', p_choice%thr, info) + end select + + if (psb_toupper(p_choice%smther2) /= 'NONE') then + call prec%set('smoother_type', p_choice%smther2, info,pos='post') + call prec%set('smoother_sweeps', p_choice%jsweeps2, info,pos='post') + select case (psb_toupper(p_choice%smther2)) + case ('GS','BWGS','FBGS','JACOBI','L1-JACOBI','L1-FBGS') + ! do nothing + case default + call prec%set('sub_ovr', p_choice%novr2, info,pos='post') + call prec%set('sub_restr', p_choice%restr2, info,pos='post') + call prec%set('sub_prol', p_choice%prol2, info,pos='post') + select case(trim(psb_toupper(p_choice%solve2))) + case('INVK') + call prec%set('sub_solve', p_choice%solve, info) + case('INVT') + call prec%set('sub_solve', p_choice%solve, info) + case('AINV') + call prec%set('sub_solve', p_choice%solve, info) + call prec%set('ainv_alg', p_choice%variant, info) + case default + call prec%set('sub_solve', p_choice%solve2, info, pos='post') + end select + + call prec%set('sub_fillin', p_choice%fill2, info,pos='post') + call prec%set('inv_fillin', p_choice%invfill2, info,pos='post') + call prec%set('sub_iluthrs', p_choice%thr2, info,pos='post') + end select + end if + + call prec%set('coarse_solve', p_choice%csolve, info) + if (psb_toupper(p_choice%csolve) == 'BJAC') & + & call prec%set('coarse_subsolve', p_choice%csbsolve, info) + call prec%set('coarse_mat', p_choice%cmat, info) + call prec%set('coarse_fillin', p_choice%cfill, info) + call prec%set('coarse_iluthrs', p_choice%cthres, info) + call prec%set('coarse_sweeps', p_choice%cjswp, info) + + end select + + ! build the preconditioner + call psb_barrier(ctxt) + t1 = psb_wtime() + call prec%hierarchy_build(a,desc_a,info) + thier = psb_wtime()-t1 + if (info /= psb_success_) then + call psb_errpush(psb_err_from_subroutine_,name,a_err='amg_hierarchy_bld') + goto 9999 + end if + call psb_barrier(ctxt) + t1 = psb_wtime() + call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold) + tprec = psb_wtime()-t1 + if (info /= psb_success_) then + call psb_errpush(psb_err_from_subroutine_,name,a_err='amg_smoothers_bld') + goto 9999 + end if + + call psb_amx(ctxt, thier) + call psb_amx(ctxt, tprec) + + if(iam == psb_root_) then + write(psb_out_unit,'(" ")') + write(psb_out_unit,'("Preconditioner: ",a)') trim(p_choice%descr) + write(psb_out_unit,'("Preconditioner time: ",es12.5)')thier+tprec + write(psb_out_unit,'(" ")') + end if + call desc_a%cnv(mold=igmold) + call a%cscnv(info,mold=agmold) + call psb_geasb(x,desc_a,info,mold=vgmold) + call psb_geasb(b,desc_a,info,mold=vgmold) + + ! + ! iterative method parameters + ! + call psb_barrier(ctxt) + call prec%allocate_wrk(info) + t1 = psb_wtime() + call psb_krylov(s_choice%kmethd,a,prec,b,x,s_choice%eps,& + & desc_a,info,itmax=s_choice%itmax,iter=iter,err=err,itrace=s_choice%itrace,& + & istop=s_choice%istopc,irst=s_choice%irst) + call prec%deallocate_wrk(info) + call psb_barrier(ctxt) + tslv = psb_wtime() - t1 + + call psb_amx(ctxt,tslv) + + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='solver routine' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + + call psb_barrier(ctxt) + tslv = psb_wtime() - t1 + call psb_amx(ctxt,tslv) + + ! compute residual norms + call psb_geall(r,desc_a,info) + call r%zero() + call psb_geasb(r,desc_a,info) + call psb_geaxpby(done,b,dzero,r,desc_a,info) + call psb_spmm(-done,a,x,done,r,desc_a,info) + resmx = psb_genrm2(r,desc_a,info) + resmxp = psb_geamax(r,desc_a,info) + + amatsize = a%sizeof() + descsize = desc_a%sizeof() + precsize = prec%sizeof() + system_size = desc_a%get_global_rows() + call psb_sum(ctxt,amatsize) + call psb_sum(ctxt,descsize) + call psb_sum(ctxt,precsize) + call prec%descr(iout=psb_out_unit) + if (iam == psb_root_) then + write(psb_out_unit,'("Computed solution on ",i8," processors")') np + write(psb_out_unit,'("Linear system size : ",i12)') system_size + write(psb_out_unit,'("PDE Coefficients : ",a)') trim(pdecoeff) + write(psb_out_unit,'("Krylov method : ",a)') trim(s_choice%kmethd) + write(psb_out_unit,'("Preconditioner : ",a)') trim(p_choice%descr) + write(psb_out_unit,'("Iterations to convergence : ",i12)') iter + write(psb_out_unit,'("Relative error estimate on exit : ",es12.5)') err + write(psb_out_unit,'("Number of levels in hierarchy : ",i12)') prec%get_nlevs() + write(psb_out_unit,'("Time to build hierarchy : ",es12.5)') thier + write(psb_out_unit,'("Time to build smoothers : ",es12.5)') tprec + write(psb_out_unit,'("Total time for preconditioner : ",es12.5)') tprec+thier + write(psb_out_unit,'("Time to solve system : ",es12.5)') tslv + write(psb_out_unit,'("Time per iteration : ",es12.5)') tslv/iter + write(psb_out_unit,'("Total time : ",es12.5)') tslv+tprec+thier + write(psb_out_unit,'("Residual 2-norm : ",es12.5)') resmx + write(psb_out_unit,'("Residual inf-norm : ",es12.5)') resmxp + write(psb_out_unit,'("Total memory occupation for A : ",i12)') amatsize + write(psb_out_unit,'("Total memory occupation for DESC_A : ",i12)') descsize + write(psb_out_unit,'("Total memory occupation for PREC : ",i12)') precsize + write(psb_out_unit,'("Storage format for A : ",a )') a%get_fmt() + write(psb_out_unit,'("Storage format for DESC_A : ",a )') desc_a%get_fmt() + + end if + + ! + ! cleanup storage and exit + ! + call psb_gefree(b,desc_a,info) + call psb_gefree(x,desc_a,info) + call psb_spfree(a,desc_a,info) + call prec%free(info) + call psb_cdfree(desc_a,info) + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + ch_err='free routine' + call psb_errpush(info,name,a_err=ch_err) + goto 9999 + end if + call psb_gpu_exit() + call psb_exit(ctxt) + stop + +9999 continue + call psb_error(ctxt) + +contains + ! + ! get iteration parameters from standard input + ! + ! + ! get iteration parameters from standard input + ! + subroutine get_parms(ctxt,afmt,idim,solve,prec,pdecoeff) + + implicit none + + type(psb_ctxt_type) :: ctxt + integer(psb_ipk_) :: idim + character(len=*) :: afmt + type(solverdata) :: solve + type(precdata) :: prec + character(len=*) :: pdecoeff + integer(psb_ipk_) :: iam, nm, np, inp_unit + character(len=1024) :: filename + + call psb_info(ctxt,iam,np) + + if (iam == psb_root_) then + if (command_argument_count()>0) then + call get_command_argument(1,filename) + inp_unit = 30 + open(inp_unit,file=filename,action='read',iostat=info) + if (info /= 0) then + write(psb_err_unit,*) 'Could not open file ',filename,' for input' + call psb_abort(ctxt) + stop + else + write(psb_err_unit,*) 'Opened file ',trim(filename),' for input' + end if + else + inp_unit=psb_inp_unit + end if + ! read input data + ! + call read_data(afmt,inp_unit) ! matrix storage format + call read_data(idim,inp_unit) ! Discretization grid size + call read_data(pdecoeff,inp_unit) ! PDE Coefficients + ! Krylov solver data + call read_data(solve%kmethd,inp_unit) ! Krylov solver + call read_data(solve%istopc,inp_unit) ! stopping criterion + call read_data(solve%itmax,inp_unit) ! max num iterations + call read_data(solve%itrace,inp_unit) ! tracing + call read_data(solve%irst,inp_unit) ! restart + call read_data(solve%eps,inp_unit) ! tolerance + ! preconditioner type + call read_data(prec%descr,inp_unit) ! verbose description of the prec + call read_data(prec%ptype,inp_unit) ! preconditioner type + ! First smoother / 1-lev preconditioner + call read_data(prec%smther,inp_unit) ! smoother type + call read_data(prec%jsweeps,inp_unit) ! (pre-)smoother / 1-lev prec sweeps + call read_data(prec%novr,inp_unit) ! number of overlap layers + call read_data(prec%restr,inp_unit) ! restriction over application of AS + call read_data(prec%prol,inp_unit) ! prolongation over application of AS + call read_data(prec%solve,inp_unit) ! local subsolver + call read_data(prec%variant,inp_unit) ! AINV variant + call read_data(prec%fill,inp_unit) ! fill-in for incomplete LU + call read_data(prec%invfill,inp_unit) !Inverse fill-in for INVK + call read_data(prec%thr,inp_unit) ! threshold for ILUT + ! Second smoother/ AMG post-smoother (if NONE ignored in main) + call read_data(prec%smther2,inp_unit) ! smoother type + call read_data(prec%jsweeps2,inp_unit) ! (post-)smoother sweeps + call read_data(prec%novr2,inp_unit) ! number of overlap layers + call read_data(prec%restr2,inp_unit) ! restriction over application of AS + call read_data(prec%prol2,inp_unit) ! prolongation over application of AS + call read_data(prec%solve2,inp_unit) ! local subsolver + call read_data(prec%variant2,inp_unit) ! AINV variant + call read_data(prec%fill2,inp_unit) ! fill-in for incomplete LU + call read_data(prec%invfill2,inp_unit) !Inverse fill-in for INVK + call read_data(prec%thr2,inp_unit) ! threshold for ILUT + ! general AMG data + call read_data(prec%mlcycle,inp_unit) ! AMG cycle type + call read_data(prec%outer_sweeps,inp_unit) ! number of 1lev/outer sweeps + call read_data(prec%maxlevs,inp_unit) ! max number of levels in AMG prec + call read_data(prec%csizepp,inp_unit) ! min size coarsest mat + ! aggregation + call read_data(prec%aggr_prol,inp_unit) ! aggregation type + call read_data(prec%par_aggr_alg,inp_unit) ! parallel aggregation alg + call read_data(prec%aggr_ord,inp_unit) ! ordering for aggregation + call read_data(prec%aggr_filter,inp_unit) ! filtering + call read_data(prec%mncrratio,inp_unit) ! minimum aggregation ratio + call read_data(prec%thrvsz,inp_unit) ! size of aggr thresh vector + if (prec%thrvsz > 0) then + call psb_realloc(prec%thrvsz,prec%athresv,info) + call read_data(prec%athresv,inp_unit) ! aggr thresh vector + else + read(inp_unit,*) ! dummy read to skip a record + end if + call read_data(prec%athres,inp_unit) ! smoothed aggr thresh + ! coasest-level solver + call read_data(prec%csolve,inp_unit) ! coarsest-lev solver + call read_data(prec%csbsolve,inp_unit) ! coarsest-lev subsolver + call read_data(prec%cmat,inp_unit) ! coarsest mat layout + call read_data(prec%cfill,inp_unit) ! fill-in for incompl LU + call read_data(prec%cthres,inp_unit) ! Threshold for ILUT + call read_data(prec%cjswp,inp_unit) ! sweeps for GS/JAC subsolver + if (inp_unit /= psb_inp_unit) then + close(inp_unit) + end if + end if + + call psb_bcast(ctxt,afmt) + call psb_bcast(ctxt,idim) + call psb_bcast(ctxt,pdecoeff) + + call psb_bcast(ctxt,solve%kmethd) + call psb_bcast(ctxt,solve%istopc) + call psb_bcast(ctxt,solve%itmax) + call psb_bcast(ctxt,solve%itrace) + call psb_bcast(ctxt,solve%irst) + call psb_bcast(ctxt,solve%eps) + + call psb_bcast(ctxt,prec%descr) + call psb_bcast(ctxt,prec%ptype) + + ! broadcast first (pre-)smoother / 1-lev prec data + call psb_bcast(ctxt,prec%smther) + call psb_bcast(ctxt,prec%jsweeps) + call psb_bcast(ctxt,prec%novr) + call psb_bcast(ctxt,prec%restr) + call psb_bcast(ctxt,prec%prol) + call psb_bcast(ctxt,prec%solve) + call psb_bcast(ctxt,prec%variant) + call psb_bcast(ctxt,prec%fill) + call psb_bcast(ctxt,prec%invfill) + call psb_bcast(ctxt,prec%thr) + ! broadcast second (post-)smoother + call psb_bcast(ctxt,prec%smther2) + call psb_bcast(ctxt,prec%jsweeps2) + call psb_bcast(ctxt,prec%novr2) + call psb_bcast(ctxt,prec%restr2) + call psb_bcast(ctxt,prec%prol2) + call psb_bcast(ctxt,prec%solve2) + call psb_bcast(ctxt,prec%variant2) + call psb_bcast(ctxt,prec%fill2) + call psb_bcast(ctxt,prec%invfill2) + call psb_bcast(ctxt,prec%thr2) + + ! broadcast AMG parameters + call psb_bcast(ctxt,prec%mlcycle) + call psb_bcast(ctxt,prec%outer_sweeps) + call psb_bcast(ctxt,prec%maxlevs) + + call psb_bcast(ctxt,prec%aggr_prol) + call psb_bcast(ctxt,prec%par_aggr_alg) + call psb_bcast(ctxt,prec%aggr_ord) + call psb_bcast(ctxt,prec%aggr_filter) + call psb_bcast(ctxt,prec%mncrratio) + call psb_bcast(ctxt,prec%thrvsz) + if (prec%thrvsz > 0) then + if (iam /= psb_root_) call psb_realloc(prec%thrvsz,prec%athresv,info) + call psb_bcast(ctxt,prec%athresv) + end if + call psb_bcast(ctxt,prec%athres) + + call psb_bcast(ctxt,prec%csizepp) + call psb_bcast(ctxt,prec%cmat) + call psb_bcast(ctxt,prec%csolve) + call psb_bcast(ctxt,prec%csbsolve) + call psb_bcast(ctxt,prec%cfill) + call psb_bcast(ctxt,prec%cthres) + call psb_bcast(ctxt,prec%cjswp) + + + end subroutine get_parms + +end program amg_d_pde3d diff --git a/tests/gpu/amg_d_pde3d_base_mod.f90 b/tests/gpu/amg_d_pde3d_base_mod.f90 new file mode 100644 index 00000000..76aad5cd --- /dev/null +++ b/tests/gpu/amg_d_pde3d_base_mod.f90 @@ -0,0 +1,65 @@ +module amg_d_pde3d_base_mod + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_), save, private :: epsilon=done/80 +contains + subroutine pde_set_parm(dat) + real(psb_dpk_), intent(in) :: dat + epsilon = dat + end subroutine pde_set_parm + ! + ! functions parametrizing the differential equation + ! + function b1(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b1 + real(psb_dpk_), intent(in) :: x,y,z + b1=done/sqrt(3.0_psb_dpk_) + end function b1 + function b2(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b2 + real(psb_dpk_), intent(in) :: x,y,z + b2=done/sqrt(3.0_psb_dpk_) + end function b2 + function b3(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b3 + real(psb_dpk_), intent(in) :: x,y,z + b3=done/sqrt(3.0_psb_dpk_) + end function b3 + function c(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: c + real(psb_dpk_), intent(in) :: x,y,z + c=dzero + end function c + function a1(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a1 + real(psb_dpk_), intent(in) :: x,y,z + a1=epsilon + end function a1 + function a2(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a2 + real(psb_dpk_), intent(in) :: x,y,z + a2=epsilon + end function a2 + function a3(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a3 + real(psb_dpk_), intent(in) :: x,y,z + a3=epsilon + end function a3 + function g(x,y,z) + use psb_base_mod, only : psb_dpk_, done, dzero + real(psb_dpk_) :: g + real(psb_dpk_), intent(in) :: x,y,z + g = dzero + if (x == done) then + g = done + else if (x == dzero) then + g = done + end if + end function g +end module amg_d_pde3d_base_mod diff --git a/tests/gpu/amg_d_pde3d_exp_mod.f90 b/tests/gpu/amg_d_pde3d_exp_mod.f90 new file mode 100644 index 00000000..fdbb2970 --- /dev/null +++ b/tests/gpu/amg_d_pde3d_exp_mod.f90 @@ -0,0 +1,65 @@ +module amg_d_pde3d_exp_mod + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_), save, private :: epsilon=done/160 +contains + subroutine pde_set_parm(dat) + real(psb_dpk_), intent(in) :: dat + epsilon = dat + end subroutine pde_set_parm + ! + ! functions parametrizing the differential equation + ! + function b1_exp(x,y,z) + use psb_base_mod, only : psb_dpk_, dzero + real(psb_dpk_) :: b1_exp + real(psb_dpk_), intent(in) :: x,y,z + b1_exp=dzero/sqrt(3.0_psb_dpk_) + end function b1_exp + function b2_exp(x,y,z) + use psb_base_mod, only : psb_dpk_, dzero + real(psb_dpk_) :: b2_exp + real(psb_dpk_), intent(in) :: x,y,z + b2_exp=dzero/sqrt(3.0_psb_dpk_) + end function b2_exp + function b3_exp(x,y,z) + use psb_base_mod, only : psb_dpk_, dzero + real(psb_dpk_) :: b3_exp + real(psb_dpk_), intent(in) :: x,y,z + b3_exp=dzero/sqrt(3.0_psb_dpk_) + end function b3_exp + function c_exp(x,y,z) + use psb_base_mod, only : psb_dpk_, dzero + real(psb_dpk_) :: c_exp + real(psb_dpk_), intent(in) :: x,y,z + c_exp=dzero + end function c_exp + function a1_exp(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a1_exp + real(psb_dpk_), intent(in) :: x,y,z + a1_exp=epsilon*exp(-(x+y+z)) + end function a1_exp + function a2_exp(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a2_exp + real(psb_dpk_), intent(in) :: x,y,z + a2_exp=epsilon*exp(-(x+y+z)) + end function a2_exp + function a3_exp(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a3_exp + real(psb_dpk_), intent(in) :: x,y,z + a3_exp=epsilon*exp(-(x+y+z)) + end function a3_exp + function g_exp(x,y,z) + use psb_base_mod, only : psb_dpk_, done, dzero + real(psb_dpk_) :: g_exp + real(psb_dpk_), intent(in) :: x,y,z + g_exp = dzero + if (x == done) then + g_exp = done + else if (x == dzero) then + g_exp = done + end if + end function g_exp +end module amg_d_pde3d_exp_mod diff --git a/tests/gpu/amg_d_pde3d_gauss_mod.f90 b/tests/gpu/amg_d_pde3d_gauss_mod.f90 new file mode 100644 index 00000000..3787c76a --- /dev/null +++ b/tests/gpu/amg_d_pde3d_gauss_mod.f90 @@ -0,0 +1,65 @@ +module amg_d_pde3d_gauss_mod + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_), save, private :: epsilon=done/80 +contains + subroutine pde_set_parm(dat) + real(psb_dpk_), intent(in) :: dat + epsilon = dat + end subroutine pde_set_parm + ! + ! functions parametrizing the differential equation + ! + function b1_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b1_gauss + real(psb_dpk_), intent(in) :: x,y,z + b1_gauss=done/sqrt(3.0_psb_dpk_)-2*x*exp(-(x**2+y**2+z**2)) + end function b1_gauss + function b2_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b2_gauss + real(psb_dpk_), intent(in) :: x,y,z + b2_gauss=done/sqrt(3.0_psb_dpk_)-2*y*exp(-(x**2+y**2+z**2)) + end function b2_gauss + function b3_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_, done + real(psb_dpk_) :: b3_gauss + real(psb_dpk_), intent(in) :: x,y,z + b3_gauss=done/sqrt(3.0_psb_dpk_)-2*z*exp(-(x**2+y**2+z**2)) + end function b3_gauss + function c_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_, dzero + real(psb_dpk_) :: c_gauss + real(psb_dpk_), intent(in) :: x,y,z + c=dzero + end function c_gauss + function a1_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a1_gauss + real(psb_dpk_), intent(in) :: x,y,z + a1_gauss=epsilon*exp(-(x**2+y**2+z**2)) + end function a1_gauss + function a2_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a2_gauss + real(psb_dpk_), intent(in) :: x,y,z + a2_gauss=epsilon*exp(-(x**2+y**2+z**2)) + end function a2_gauss + function a3_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_ + real(psb_dpk_) :: a3_gauss + real(psb_dpk_), intent(in) :: x,y,z + a3_gauss=epsilon*exp(-(x**2+y**2+z**2)) + end function a3_gauss + function g_gauss(x,y,z) + use psb_base_mod, only : psb_dpk_, done, dzero + real(psb_dpk_) :: g_gauss + real(psb_dpk_), intent(in) :: x,y,z + g_gauss = dzero + if (x == done) then + g_gauss = done + else if (x == dzero) then + g_gauss = done + end if + end function g_gauss +end module amg_d_pde3d_gauss_mod diff --git a/tests/gpu/runs/amg_gpu_pde3d.inp b/tests/gpu/runs/amg_gpu_pde3d.inp new file mode 100644 index 00000000..a819710f --- /dev/null +++ b/tests/gpu/runs/amg_gpu_pde3d.inp @@ -0,0 +1,55 @@ +%%%%%%%%%%% General arguments % Lines starting with % are ignored. +CSR ! Storage format CSR COO JAD +0080 ! IDIM; domain size. Linear system size is IDIM**3 +CONST ! PDECOEFF: CONST, EXP, GAUSS Coefficients of the PDE +FCG ! Iterative method: BiCGSTAB BiCGSTABL BiCG CG CGS FCG GCR RGMRES +2 ! ISTOPC +00500 ! ITMAX +1 ! ITRACE +30 ! IRST (restart for RGMRES and BiCGSTABL) +1.d-6 ! EPS +%%%%%%%%%%% Main preconditioner choices %%%%%%%%%%%%%%%% +ML-VCYCLE-BJAC-D-BJAC ! Longer descriptive name for preconditioner (up to 20 chars) +ML ! Preconditioner type: NONE JACOBI GS FBGS BJAC AS ML +%%%%%%%%%%% First smoother (for all levels but coarsest) %%%%%%%%%%%%%%%% +BJAC ! Smoother type JACOBI FBGS GS BWGS BJAC AS. For 1-level, repeats previous. +1 ! Number of sweeps for smoother +0 ! Number of overlap layers for AS preconditioner +HALO ! AS restriction operator: NONE HALO +NONE ! AS prolongation operator: NONE SUM AVG +INVK ! Subdomain solver for BJAC/AS: JACOBI GS BGS ILU ILUT MILU MUMPS SLU UMF +LLK ! AINV variant +0 ! Fill level P for ILU(P) and ILU(T,P) +1 ! Inverse Fill level P for INVK +1.d-4 ! Threshold T for ILU(T,P) +%%%%%%%%%%% Second smoother, always ignored for non-ML %%%%%%%%%%%%%%%% +NONE ! Second (post) smoother, ignored if NONE +1 ! Number of sweeps for (post) smoother +0 ! Number of overlap layers for AS preconditioner +HALO ! AS restriction operator: NONE HALO +NONE ! AS prolongation operator: NONE SUM AVG +ILU ! Subdomain solver for BJAC/AS: JACOBI GS BGS ILU ILUT MILU MUMPS SLU UMF +LLK ! AINV variant +0 ! Fill level P for ILU(P) and ILU(T,P) +8 ! Inverse Fill level P for INVK +1.d-4 ! Threshold T for ILU(T,P) +%%%%%%%%%%% Multilevel parameters %%%%%%%%%%%%%%%% +VCYCLE ! Type of multilevel CYCLE: VCYCLE WCYCLE KCYCLE MULT ADD +1 ! Number of outer sweeps for ML +-3 ! Max Number of levels in a multilevel preconditioner; if <0, lib default +-3 ! Target coarse matrix size per process; if <0, lib default +SMOOTHED ! Type of aggregation: SMOOTHED UNSMOOTHED +DEC ! Parallel aggregation: DEC, SYMDEC +NATURAL ! Ordering of aggregation NATURAL DEGREE +NOFILTER ! Filtering of matrix: FILTER NOFILTER +-1.5 ! Coarsening ratio, if < 0 use library default +-2 ! Number of thresholds in vector, next line ignored if <= 0 +0.05 0.025 ! Thresholds +-0.0100d0 ! Smoothed aggregation threshold, ignored if < 0 +%%%%%%%%%%% Coarse level solver %%%%%%%%%%%%%%%% +BJAC ! Coarsest-level solver: MUMPS UMF SLU SLUDIST JACOBI GS BJAC +INVK ! Coarsest-level subsolver for BJAC: ILU ILUT MILU UMF MUMPS SLU +DIST ! Coarsest-level matrix distribution: DIST REPL +1 ! Coarsest-level fillin P for ILU(P) and ILU(T,P) +1.d-4 ! Coarsest-level threshold T for ILU(T,P) +1 ! Number of sweeps for JACOBI/GS/BJAC coarsest-level solver From 47acde313f06a9966e73c07a60988485be7e0de9 Mon Sep 17 00:00:00 2001 From: Salvatore Filippone Date: Tue, 6 Apr 2021 17:03:01 +0200 Subject: [PATCH 2/6] New GPU comments and sample program in docs. --- amgprec/amg_base_prec_type.F90 | 9 +- docs/amg4psblas_1.0-guide.pdf | Bin 1819190 -> 1829393 bytes docs/html/index.html | 30 +++--- docs/html/userhtml.html | 30 +++--- docs/html/userhtmlli2.html | 10 +- docs/html/userhtmlse6.html | 2 +- docs/html/userhtmlse8.html | 6 +- docs/html/userhtmlsu7.html | 157 +++++++++++++++++++++++++---- docs/src/gettingstarted.tex | 177 +++++++++++++++++++++++++++++---- 9 files changed, 344 insertions(+), 77 deletions(-) diff --git a/amgprec/amg_base_prec_type.F90 b/amgprec/amg_base_prec_type.F90 index dbe5fe5f..e9eb8cab 100644 --- a/amgprec/amg_base_prec_type.F90 +++ b/amgprec/amg_base_prec_type.F90 @@ -266,15 +266,18 @@ module amg_base_prec_type ! integer(psb_ipk_), parameter :: amg_dec_aggr_ = 0 integer(psb_ipk_), parameter :: amg_sym_dec_aggr_ = 1 - integer(psb_ipk_), parameter :: amg_ext_aggr_ = 2 - integer(psb_ipk_), parameter :: amg_max_par_aggr_alg_ = amg_ext_aggr_ + integer(psb_ipk_), parameter :: amg_ext_aggr_ = 2 + integer(psb_ipk_), parameter :: amg_coupled_aggr_ = 3 + integer(psb_ipk_), parameter :: amg_max_par_aggr_alg_ = amg_coupled_aggr_ ! ! Legal values for entry: amg_aggr_type_ ! integer(psb_ipk_), parameter :: amg_noalg_ = 0 integer(psb_ipk_), parameter :: amg_soc1_ = 1 integer(psb_ipk_), parameter :: amg_soc2_ = 2 + integer(psb_ipk_), parameter :: amg_matchboxp_ = 3 ! + ! Legal values for entry: amg_aggr_prol_ ! integer(psb_ipk_), parameter :: amg_no_smooth_ = 0 @@ -506,7 +509,7 @@ contains val = amg_soc2_ case('SOC1') val = amg_soc1_ - case('DEC') + case('DEC', 'DECOUPLED') val = amg_dec_aggr_ case('SYMDEC') val = amg_sym_dec_aggr_ diff --git a/docs/amg4psblas_1.0-guide.pdf b/docs/amg4psblas_1.0-guide.pdf index 6e6f8b214e5be7ba20ba086853d3d32ca138b8fd..090666a9750b5000c387db047e95f2f19d2cd3c4 100644 GIT binary patch delta 84128 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 delta 78918 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 n5b387xptvCMx0*5f)O`l$v4AGPU example
5 User Interface
 5.1 Method init
 5.2 Method set
 5.3 Method hierarchy_build
 5.4 Method smoothers_build
 5.5 Method build
 5.6 Method apply
 5.7 Method free
 5.8 Method descr
 5.9 Auxiliary Methods
6 Adding new smoother and solver objects to AMG4PSBLAS
7 Error Handling
A License
B Contributor Covenant Code of Conduct
References diff --git a/docs/html/userhtml.html b/docs/html/userhtml.html index 18ae1e4d..f1d24e40 100644 --- a/docs/html/userhtml.html +++ b/docs/html/userhtml.html @@ -112,73 +112,73 @@ href="userhtmlsu7.html#x16-150004.2" id="QQ2-16-21">GPU example
5 User Interface
 5.1 Method init
 5.2 Method set
 5.3 Method hierarchy_build
 5.4 Method smoothers_build
 5.5 Method build
 5.6 Method apply
 5.7 Method free
 5.8 Method descr
 5.9 Auxiliary Methods
6 Adding new smoother and solver objects to AMG4PSBLAS
7 Error Handling
A License
B Contributor Covenant Code of Conduct
References diff --git a/docs/html/userhtmlli2.html b/docs/html/userhtmlli2.html index f7e08c84..1d6a77b7 100644 --- a/docs/html/userhtmlli2.html +++ b/docs/html/userhtmlli2.html @@ -137,32 +137,32 @@ class="cmr-12">Auxiliary Methods class="cmr-12">  5.9.1 Method: dump
  5.9.2 Method: clone
  5.9.3 Method: sizeof
  5.9.4 Method: allocate_wrk
  5.9.5 Method: free_wrk
pass it as follows: -

+   
   ! sparse matrix and preconditioner
   type(psb_dspmat_type) :: a
   type(amg_dprec_type)  :: prec
diff --git a/docs/html/userhtmlse8.html b/docs/html/userhtmlse8.html
index b840cd20..028b6cbf 100644
--- a/docs/html/userhtmlse8.html
+++ b/docs/html/userhtmlse8.html
@@ -36,7 +36,7 @@ class="cmr-12">AMG4PSBLAS is freely distributable under the following copyright
                                                                                
 
                                                                                
-   
+   
                            AMG4PSBLAS  version 1.0
               Algebraic MultiGrid Preconditioners Package
              based on PSBLAS (Parallel Sparse BLAS version 3.7)
@@ -78,7 +78,7 @@ class="cmr-12">abide by its terms:
                                                                                
 
                                                                                
-   
+   
                            MLD2P4  version 2.2
   MultiLevel Domain Decomposition Parallel Preconditioners Package
              based on PSBLAS (Parallel Sparse BLAS version 3.5)
@@ -127,7 +127,7 @@ class="cmr-12">here.
                                                                                
 
                                                                                
-   
+   
 // ***********************************************************************
 //
 //        MatchboxP: A C++ library for approximate weighted matching
diff --git a/docs/html/userhtmlsu7.html b/docs/html/userhtmlsu7.html
index 93848cfa..fbd845fb 100644
--- a/docs/html/userhtmlsu7.html
+++ b/docs/html/userhtmlsu7.html
@@ -29,17 +29,21 @@ class="cmr-12">4.2    GPU example
 

The code reported in Figure 5 shows how to set up a program exploiting the combined +class="cmr-12">The code discussed here shows how to set up a program exploiting the combined GPU GPU capabilities of PSBLAS and AMG4PSBLAS. +class="cmr-12">capabilities of PSBLAS and AMG4PSBLAS. The code example is availabile in the +source distribution directory amg4psblas/tests/gpu. +

First of all, we need to include the appropriate modules and declare some auxiliary +variables: -


@@ -47,24 +51,143 @@ class="cmr-12">GPU capabilities of PSBLAS and AMG4PSBLAS.
-

+

-! build a one-level RAS with overlap 2 and ILU(0) on the local blocks.
-call P%init(’AS’,info)
-call P%set(’SUB_OVR’,2,info)
-call P%build(A,desc_A,info)
-... ...
-! solve Ax=b with preconditioned BiCGSTAB
-  call psb_krylov(’BICGSTAB’,A,P,b,x,tol,desc_A,info)
+program amg_d_pde3d
+  use psb_base_mod
+  use amg_prec_mod
+  use psb_krylov_mod
+  use psb_util_mod
+  use psb_gpu_mod
+  use data_input
+  use amg_d_pde3d_base_mod
+  use amg_d_pde3d_exp_mod
+  use amg_d_pde3d_gauss_mod
+  use amg_d_genpde_mod
+  implicit none
+  .......
+  ! GPU variables
+  type(psb_d_hlg_sparse_mat) :: agmold
+  type(psb_d_vect_gpu)       :: vgmold
+  type(psb_i_vect_gpu)       :: igmold
+ 
 
-

+


Listing 5: setup of a one-level Schwarz preconditioner.
+class="content">setup of a GPU-enabled test program part one.
+

We then have to initialize the GPU environment, and pass the appropriate MOLD +variables to the build methods + + + +


+ + + +
+

+

+  call psb_init(ctxt)
+  call psb_info(ctxt,iam,np)
+  !
+  ! BEWARE: if you have NGPUS  per node, the default is to
+  ! attach to mod(IAM,NGPUS)
+  !
+  call psb_gpu_init(ictxt)
+  ......
+  t1 = psb_wtime()
+  call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold)
+ 
+
+

+
Listing 6: setup of a GPU-enabled test program part two.
+ + + +

+

Finally, we convert the input matrix, the descriptor and the vectors, then +preallocate the preconditioner workspace before entering the Krylov method. At the +end of the code, we close the GPU environment + + + +


+ + + +
+

+

+  call desc_a%cnv(mold=igmold)
+  call a%cscnv(info,mold=agmold)
+  call psb_geasb(x,desc_a,info,mold=vgmold)
+  call psb_geasb(b,desc_a,info,mold=vgmold)
+  !
+  ! iterative method parameters
+  !
+  call psb_barrier(ctxt)
+  call prec%allocate_wrk(info)
+  t1 = psb_wtime()
+  call psb_krylov(s_choice%kmethd,a,prec,b,x,s_choice%eps,&
+       & desc_a,info,itmax=s_choice%itmax,iter=iter,err=err,itrace=s_choice%itrace,&
+       & istop=s_choice%istopc,irst=s_choice%irst)
+  call prec%deallocate_wrk(info)
+  call psb_barrier(ctxt)
+  tslv = psb_wtime() - t1
+  ......
+  call psb_gpu_exit()
+  call psb_exit(ctxt)
+  stop
+ 
+
+

+
Listing 7: setup of a GPU-enabled test program part three.
+ + + +

+

It is very important to employ solvers that are suited to the GPU, i.e. solvers that +do NOT employ triangular system solve kernels. Solvers that satisfy this constraint +include: +

    +
  • JACOBI +
  • +
  • INVK +
  • +
  • INVT +
  • +
  • AINV
+

and their L1 variants. diff --git a/docs/src/gettingstarted.tex b/docs/src/gettingstarted.tex index e4322ce0..7e5e0568 100644 --- a/docs/src/gettingstarted.tex +++ b/docs/src/gettingstarted.tex @@ -419,41 +419,182 @@ call P%build(A,desc_A,info) \subsection{GPU example\label{sec:gpu-example}} -The code reported in Figure~\ref{fig:gpu-ex1} shows how to set up a +The code discussed here shows how to set up a program exploiting the combined GPU capabilities of PSBLAS and -AMG4PSBLAS. +AMG4PSBLAS. The code example is availabile in the source distribution +directory \verb|amg4psblas/tests/gpu|. + +First of all, we need to include the appropriate modules and +declare some auxiliary variables: \begin{listing}[h!] \ifpdf \begin{minted}[breaklines=true,bgcolor=bg,fontsize=\small]{fortran} -! build a one-level RAS with overlap 2 and ILU(0) on the local blocks. -call P%init('AS',info) -call P%set('SUB_OVR',2,info) -call P%build(A,desc_A,info) -... ... -! solve Ax=b with preconditioned BiCGSTAB - call psb_krylov('BICGSTAB',A,P,b,x,tol,desc_A,info) +program amg_d_pde3d + use psb_base_mod + use amg_prec_mod + use psb_krylov_mod + use psb_util_mod + use psb_gpu_mod + use data_input + use amg_d_pde3d_base_mod + use amg_d_pde3d_exp_mod + use amg_d_pde3d_gauss_mod + use amg_d_genpde_mod + implicit none + ....... + ! GPU variables + type(psb_d_hlg_sparse_mat) :: agmold + type(psb_d_vect_gpu) :: vgmold + type(psb_i_vect_gpu) :: igmold \end{minted} \else \begin{center} \begin{minipage}{.90\textwidth} {\small \begin{verbatim} -! build a one-level RAS with overlap 2 and ILU(0) on the local blocks. -call P%init('AS',info) -call P%set('SUB_OVR',2,info) -call P%build(A,desc_A,info) -... ... -! solve Ax=b with preconditioned BiCGSTAB - call psb_krylov('BICGSTAB',A,P,b,x,tol,desc_A,info) -\end{verbatim} +program amg_d_pde3d + use psb_base_mod + use amg_prec_mod + use psb_krylov_mod + use psb_util_mod + use psb_gpu_mod + use data_input + use amg_d_pde3d_base_mod + use amg_d_pde3d_exp_mod + use amg_d_pde3d_gauss_mod + use amg_d_genpde_mod + implicit none + ....... + ! GPU variables + type(psb_d_hlg_sparse_mat) :: agmold + type(psb_d_vect_gpu) :: vgmold + type(psb_i_vect_gpu) :: igmold + + \end{verbatim} +} +\end{minipage} +\end{center} +\fi +\caption{setup of a GPU-enabled test program part one.\label{fig:gpu-ex1}} +\end{listing} + +We then have to initialize the GPU environment, and pass the +appropriate MOLD variables to the build methods +\begin{listing}[h!] +\ifpdf +\begin{minted}[breaklines=true,bgcolor=bg,fontsize=\small]{fortran} + call psb_init(ctxt) + call psb_info(ctxt,iam,np) + ! + ! BEWARE: if you have NGPUS per node, the default is to + ! attach to mod(IAM,NGPUS) + ! + call psb_gpu_init(ictxt) + ...... + t1 = psb_wtime() + call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold) + +\end{minted} +\else +\begin{center} +\begin{minipage}{.90\textwidth} +{\small +\begin{verbatim} + call psb_init(ctxt) + call psb_info(ctxt,iam,np) + ! + ! BEWARE: if you have NGPUS per node, the default is to + ! attach to mod(IAM,NGPUS) + ! + call psb_gpu_init(ictxt) + ...... + t1 = psb_wtime() + call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold) + + \end{verbatim} } \end{minipage} \end{center} \fi -\caption{setup of a one-level Schwarz preconditioner.\label{fig:gpu-ex1}} +\caption{setup of a GPU-enabled test program part two.\label{fig:gpu-ex2}} \end{listing} +Finally, we convert the input matrix, the descriptor and the vectors, +then preallocate the preconditioner workspace before entering the +Krylov method. At the end of the code, we close the GPU environment +\begin{listing}[h!] +\ifpdf +\begin{minted}[breaklines=true,bgcolor=bg,fontsize=\small]{fortran} + call desc_a%cnv(mold=igmold) + call a%cscnv(info,mold=agmold) + call psb_geasb(x,desc_a,info,mold=vgmold) + call psb_geasb(b,desc_a,info,mold=vgmold) + + ! + ! iterative method parameters + ! + call psb_barrier(ctxt) + call prec%allocate_wrk(info) + t1 = psb_wtime() + call psb_krylov(s_choice%kmethd,a,prec,b,x,s_choice%eps,& + & desc_a,info,itmax=s_choice%itmax,iter=iter,err=err,itrace=s_choice%itrace,& + & istop=s_choice%istopc,irst=s_choice%irst) + call prec%deallocate_wrk(info) + call psb_barrier(ctxt) + tslv = psb_wtime() - t1 + ...... + call psb_gpu_exit() + call psb_exit(ctxt) + stop + + +\end{minted} +\else +\begin{center} +\begin{minipage}{.90\textwidth} +{\small +\begin{verbatim} + call desc_a%cnv(mold=igmold) + call a%cscnv(info,mold=agmold) + call psb_geasb(x,desc_a,info,mold=vgmold) + call psb_geasb(b,desc_a,info,mold=vgmold) + + ! + ! iterative method parameters + ! + call psb_barrier(ctxt) + call prec%allocate_wrk(info) + t1 = psb_wtime() + call psb_krylov(s_choice%kmethd,a,prec,b,x,s_choice%eps,& + & desc_a,info,itmax=s_choice%itmax,iter=iter,err=err,itrace=s_choice%itrace,& + & istop=s_choice%istopc,irst=s_choice%irst) + call prec%deallocate_wrk(info) + call psb_barrier(ctxt) + tslv = psb_wtime() - t1 + + ...... + call psb_gpu_exit() + call psb_exit(ctxt) + stop + + \end{verbatim} +} +\end{minipage} +\end{center} +\fi +\caption{setup of a GPU-enabled test program part three.\label{fig:gpu-ex3}} +\end{listing} +It is very important to employ solvers that are suited +to the GPU, i.e. solvers that do NOT employ triangular +system solve kernels. Solvers that satisfy this constraint include: +\begin{itemize} +\item \verb|JACOBI| +\item \verb|INVK| +\item \verb|INVT| +\item \verb|AINV| +\end{itemize} +and their \verb|L1| variants. %%% Local Variables: %%% mode: latex From 8bf1e30d66909583315fd8182450beb70f6093b4 Mon Sep 17 00:00:00 2001 From: Salvatore Filippone Date: Wed, 7 Apr 2021 10:24:58 +0200 Subject: [PATCH 3/6] Doc fixes --- docs/amg4psblas_1.0-guide.pdf | Bin 1829393 -> 1829608 bytes docs/html/userhtmlsu7.html | 32 ++++++++++++++++++-------------- docs/html/userhtmlsu9.html | 4 ++-- docs/src/gettingstarted.tex | 10 ++++++---- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/amg4psblas_1.0-guide.pdf b/docs/amg4psblas_1.0-guide.pdf index 090666a9750b5000c387db047e95f2f19d2cd3c4..16f3f3d6995c772faa917b7552eb89eb5e4fe8e4 100644 GIT binary patch delta 2359 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 HH3t6yHxo9X diff --git a/docs/html/userhtmlsu7.html b/docs/html/userhtmlsu7.html index fbd845fb..52ce6525 100644 --- a/docs/html/userhtmlsu7.html +++ b/docs/html/userhtmlsu7.html @@ -31,7 +31,7 @@ class="cmr-12">GPU example

The code discussed here shows how to set up a program exploiting the combined GPU capabilities of PSBLAS and AMG4PSBLAS. The code example is availabile in the +class="cmr-12">capabilities of PSBLAS and AMG4PSBLAS. The code example is available in the source distribution directory amg4psblas/tests/gpusetup of a GPU-enabled test program part one.

We then have to initialize the GPU environment, and pass the appropriate MOLD variables to the build methods +class="cmr-12">variables to the build methods (see also the PSBLAS and PSBLAS-EXT users’ +guides). -


@@ -95,7 +97,7 @@ class="cmr-12">variables to the build methods
-

+

   call psb_init(ctxt)
   call psb_info(ctxt,iam,np)
@@ -109,7 +111,7 @@ class="cmr-12">variables to the build methods
   call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold)
  
 
-

+


Listing 6: setup of a GPU-enabled test program part two.
@@ -117,16 +119,18 @@ class="content">setup of a GPU-enabled test program part two.

Finally, we convert the input matrix, the descriptor and the vectors, then +

Finally, we convert the input matrix, the descriptor and the vectors to use a +GPU-enabled internal storage format. We then preallocate the preconditioner preallocate the preconditioner workspace before entering the Krylov method. At the +class="cmr-12">workspace before entering the Krylov method. At the end of the code, we close the end of the code, we close the GPU environment +class="cmr-12">GPU environment -


@@ -134,7 +138,7 @@ class="cmr-12">end of the code, we close the GPU environment
-

+

   call desc_a%cnv(mold=igmold)
   call a%cscnv(info,mold=agmold)
@@ -158,7 +162,7 @@ class="cmr-12">end of the code, we close the GPU environment
   stop
  
 
-

+


Listing 7: setup of a GPU-enabled test program part three.
@@ -166,7 +170,7 @@ class="content">setup of a GPU-enabled test program part three.

It is very important to employ solvers that are suited to the GPU, i.e. solvers that do NOT employ triangular system solve kernels. Solvers that satisfy this constraint @@ -184,7 +188,7 @@ class="cmtt-12">INVT

  • AINV
  • -

    and their L1 variants. diff --git a/docs/html/userhtmlsu9.html b/docs/html/userhtmlsu9.html index 17335fac..a6891350 100644 --- a/docs/html/userhtmlsu9.html +++ b/docs/html/userhtmlsu9.html @@ -324,8 +324,8 @@ class="td11">

    An auxiliary input argument that can be passed to the underlying objects. - +class="cmr-12">objects. +

    A variety of preconditioners can be obtained by setting the appropriate diff --git a/docs/src/gettingstarted.tex b/docs/src/gettingstarted.tex index 7e5e0568..2ca9b6f7 100644 --- a/docs/src/gettingstarted.tex +++ b/docs/src/gettingstarted.tex @@ -421,7 +421,7 @@ call P%build(A,desc_A,info) The code discussed here shows how to set up a program exploiting the combined GPU capabilities of PSBLAS and -AMG4PSBLAS. The code example is availabile in the source distribution +AMG4PSBLAS. The code example is available in the source distribution directory \verb|amg4psblas/tests/gpu|. First of all, we need to include the appropriate modules and @@ -479,7 +479,8 @@ program amg_d_pde3d \end{listing} We then have to initialize the GPU environment, and pass the -appropriate MOLD variables to the build methods +appropriate MOLD variables to the build methods (see also the PSBLAS +and PSBLAS-EXT users' guides). \begin{listing}[h!] \ifpdf \begin{minted}[breaklines=true,bgcolor=bg,fontsize=\small]{fortran} @@ -518,8 +519,9 @@ appropriate MOLD variables to the build methods \fi \caption{setup of a GPU-enabled test program part two.\label{fig:gpu-ex2}} \end{listing} -Finally, we convert the input matrix, the descriptor and the vectors, -then preallocate the preconditioner workspace before entering the +Finally, we convert the input matrix, the descriptor and the vectors +to use a GPU-enabled internal storage format. +We then preallocate the preconditioner workspace before entering the Krylov method. At the end of the code, we close the GPU environment \begin{listing}[h!] \ifpdf From 094999a7f0c9761c7c44cc9a97d34aaf86ed6eb5 Mon Sep 17 00:00:00 2001 From: pasquadambra Date: Wed, 7 Apr 2021 12:04:53 +0200 Subject: [PATCH 4/6] update --- docs/src/gettingstarted.tex | 2 +- docs/src/userguide.pdf | 1 + docs/src/userinterface.tex | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 120000 docs/src/userguide.pdf diff --git a/docs/src/gettingstarted.tex b/docs/src/gettingstarted.tex index 2ca9b6f7..5d35fe7c 100644 --- a/docs/src/gettingstarted.tex +++ b/docs/src/gettingstarted.tex @@ -596,7 +596,7 @@ system solve kernels. Solvers that satisfy this constraint include: \item \verb|INVT| \item \verb|AINV| \end{itemize} -and their \verb|L1| variants. +and their $\ell_1$ variants. %%% Local Variables: %%% mode: latex diff --git a/docs/src/userguide.pdf b/docs/src/userguide.pdf new file mode 120000 index 00000000..7b032aa3 --- /dev/null +++ b/docs/src/userguide.pdf @@ -0,0 +1 @@ +tmp/userguide.pdf \ No newline at end of file diff --git a/docs/src/userinterface.tex b/docs/src/userinterface.tex index 7c3d2c16..2a496079 100644 --- a/docs/src/userinterface.tex +++ b/docs/src/userinterface.tex @@ -202,7 +202,11 @@ been selected, the coarsest-level solver is changed to block-Jacobi, with the previously chosen solver applied to the local blocks. Likewise, the replicated layout can be used with any solver but SuperLu\_Dist and KRM; therefore, if SuperLu\_Dist or KRM have been previously set, the coarsest-level -solver is changed to the default sequential solver. +solver is changed to the default sequential solver. + +In a parallel setting with many cores, we suggest to the users to change the default +coarsest solver for using the KRM choice, i.e. a parallel distributed iterative solution of the +coarsest system based on Krylov methods. \textbf{Remark 4.} The argument \fortinline|idx| can be used to allow finer control for those solvers; for instance, by specifying the keyword From 76eedf43bd67dbdae32f6627e1553b5de2c679f4 Mon Sep 17 00:00:00 2001 From: Salvatore Filippone Date: Wed, 7 Apr 2021 12:58:41 +0200 Subject: [PATCH 5/6] New GPU example --- examples/gpu/Makefile | 31 + examples/gpu/amg_dexample_gpu.f90 | 353 +++++++++++ examples/gpu/data_input.f90 | 182 ++++++ examples/gpu/runs/ml.inp | 4 + tests/gpu/Makefile | 52 -- tests/gpu/amg_d_genpde_mod.f90 | 931 ---------------------------- tests/gpu/amg_d_pde3d.f90 | 671 -------------------- tests/gpu/amg_d_pde3d_base_mod.f90 | 65 -- tests/gpu/amg_d_pde3d_exp_mod.f90 | 65 -- tests/gpu/amg_d_pde3d_gauss_mod.f90 | 65 -- tests/gpu/runs/amg_gpu_pde3d.inp | 55 -- 11 files changed, 570 insertions(+), 1904 deletions(-) create mode 100644 examples/gpu/Makefile create mode 100644 examples/gpu/amg_dexample_gpu.f90 create mode 100644 examples/gpu/data_input.f90 create mode 100644 examples/gpu/runs/ml.inp delete mode 100644 tests/gpu/Makefile delete mode 100644 tests/gpu/amg_d_genpde_mod.f90 delete mode 100644 tests/gpu/amg_d_pde3d.f90 delete mode 100644 tests/gpu/amg_d_pde3d_base_mod.f90 delete mode 100644 tests/gpu/amg_d_pde3d_exp_mod.f90 delete mode 100644 tests/gpu/amg_d_pde3d_gauss_mod.f90 delete mode 100644 tests/gpu/runs/amg_gpu_pde3d.inp diff --git a/examples/gpu/Makefile b/examples/gpu/Makefile new file mode 100644 index 00000000..fd3e1fad --- /dev/null +++ b/examples/gpu/Makefile @@ -0,0 +1,31 @@ +AMGDIR=../.. +AMGINCDIR=$(AMGDIR)/include +include $(AMGINCDIR)/Make.inc.amg4psblas +AMGMODDIR=$(AMGDIR)/modules +AMGLIBDIR=$(AMGDIR)/lib +AMG_LIBS=-L$(AMGLIBDIR) -lpsb_krylov -lamg_prec -lpsb_prec +FINCLUDES=$(FMFLAG). $(FMFLAG)$(AMGMODDIR) $(FMFLAG)$(AMGINCDIR) $(PSBLAS_INCLUDES) $(FIFLAG). +LINKOPT= + +DGOBJS=amg_dexample_gpu.o data_input.o amg_dpde_mod.o + +EXEDIR=./runs + +all: amg_dexample_gpu + + +amg_dexample_gpu: $(DGOBJS) + $(FLINK) $(LINKOPT) $(DGOBJS) -o amg_dexample_gpu \ + $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) + /bin/mv amg_dexample_gpu $(EXEDIR) + +amg_dexample_gpu.o: data_input.o amg_dpde_mod.o + +clean: + /bin/rm -f $(DGOBJS) *$(.mod) $(EXEDIR)/amg_dexample_gpu + +lib: + (cd ../../; make library) +verycleanlib: + (cd ../../; make veryclean) + diff --git a/examples/gpu/amg_dexample_gpu.f90 b/examples/gpu/amg_dexample_gpu.f90 new file mode 100644 index 00000000..142dabe0 --- /dev/null +++ b/examples/gpu/amg_dexample_gpu.f90 @@ -0,0 +1,353 @@ +! +! +! AMG4PSBLAS version 1.0 +! Algebraic Multigrid Package +! based on PSBLAS (Parallel Sparse BLAS version 3.7) +! +! (C) Copyright 2021 +! +! Salvatore Filippone +! Pasqua D'Ambra +! Fabio Durastante +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions +! are met: +! 1. Redistributions of source code must retain the above copyright +! notice, this list of conditions and the following disclaimer. +! 2. Redistributions in binary form must reproduce the above copyright +! notice, this list of conditions, and the following disclaimer in the +! documentation and/or other materials provided with the distribution. +! 3. The name of the AMG4PSBLAS group or the names of its contributors may +! not be used to endorse or promote products derived from this +! software without specific written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +! ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +! TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +! PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AMG4PSBLAS GROUP OR ITS CONTRIBUTORS +! BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +! CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +! SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +! CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +! ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +! POSSIBILITY OF SUCH DAMAGE. +! +! +! File: amg_dexample_gpu.f90 +! +! This sample program solves a linear system obtained by discretizing a +! PDE with Dirichlet BCs. The solver is CG, coupled with one of the +! following multi-level preconditioner, as explained in Section 4.1 of +! the AMG4PSBLAS User's and Reference Guide: +! +! - choice = 1, the default multi-level preconditioner solver, i.e., +! V-cycle with decoupled smoothed aggregation, 1 hybrid forward/backward +! GS sweep as pre/post-smoother and UMFPACK as coarsest-level +! solver (Sec. 4.1, Listing 1) +! +! - choice = 2, a V-cycle preconditioner with 1 block-Jacobi sweep +! (with ILU(0) on the blocks) as pre- and post-smoother, and 8 block-Jacobi +! sweeps (with ILU(0) on the blocks) as coarsest-level solver (Sec. 4.1, Listing 2) +! +! - choice = 3, W-cycle preconditioner based on the coupled aggregation relying +! on matching, with maximum size of aggregates equal to 8 and smoothed prolongators, +! 2 hybrid forward/backward GS sweeps as pre/post-smoother, a distributed coarsest +! matrix, and preconditioned Flexible Conjugate Gradient as coarsest-level solver +! (Sec. 4.1, Listing 3) +! +! The matrix and the rhs are read from files (if an rhs is not available, the +! unit rhs is set). +! +! +! The PDE is a general second order equation in 3d +! +! a1 dd(u) a2 dd(u) a3 dd(u) b1 d(u) b2 d(u) b3 d(u) +! - ------ - ------ - ------ + ----- + ------ + ------ + c u = f +! dxdx dydy dzdz dx dy dz +! +! with Dirichlet boundary conditions +! u = g +! +! on the unit cube 0<=x,y,z<=1. +! +! +! Note that if b1=b2=b3=c=0., the PDE is the Laplace equation. +! +! In this sample program the index space of the discretized +! computational domain is first numbered sequentially in a standard way, +! then the corresponding vector is distributed according to a BLOCK +! data distribution. +! +program amg_dexample_gpu + use psb_base_mod + use amg_prec_mod + use psb_krylov_mod + use psb_util_mod + use psb_gpu_mod + use data_input + use amg_d_pde_mod + implicit none + + ! input parameters + + ! sparse matrices + type(psb_dspmat_type) :: A + + ! sparse matrices descriptor + type(psb_desc_type):: desc_A + + ! preconditioner + type(amg_dprec_type) :: P + + ! right-hand side, solution and residual vectors + type(psb_d_vect_type) :: x, b, r + ! GPU variables + type(psb_d_hlg_sparse_mat) :: agmold + type(psb_d_vect_gpu) :: vgmold + type(psb_i_vect_gpu) :: igmold + + ! solver and preconditioner parameters + real(psb_dpk_) :: tol, err + integer :: itmax, iter, istop + integer :: nlev + + ! parallel environment parameters + type(psb_ctxt_type) :: ctxt + integer :: iam, np + + ! other variables + integer :: choice + integer :: i,info,j + integer(psb_epk_) :: amatsize, precsize, descsize + integer(psb_epk_) :: system_size + integer :: idim, ierr, ircode + real(psb_dpk_) :: resmx, resmxp + real(psb_dpk_) :: t1, t2, tprec + character(len=5) :: afmt='CSR' + character(len=20) :: name, kmethod + + ! initialize the parallel environment + + call psb_init(ctxt) + call psb_info(ctxt,iam,np) + ! + ! BEWARE: if you have NGPUS per node, the default is to + ! attach to mod(IAM,NGPUS) + ! + call psb_gpu_init(ictxt) + + if (iam < 0) then + ! This should not happen, but just in case + call psb_exit(ctxt) + stop + endif + + name='amg_dexample_gpu' + if(psb_get_errstatus() /= 0) goto 9999 + info=psb_success_ + call psb_set_errverbosity(2) + ! + ! Hello world + ! + if (iam == psb_root_) then + write(*,*) 'Welcome to AMG4PSBLAS version: ',amg_version_string_ + write(*,*) 'This is the ',trim(name),' sample program' + end if + write(*,*) 'Process ',iam,' running on device: ', psb_cuda_getDevice(),' out of', psb_cuda_getDeviceCount() + write(*,*) 'Process ',iam,' device ', psb_cuda_getDevice(),' is a: ', trim(psb_gpu_DeviceName()) + + ! get parameters + + call get_parms(ctxt,choice,idim,itmax,tol) + + ! allocate and fill in the coefficient matrix, rhs and initial guess + + call psb_barrier(ctxt) + t1 = psb_wtime() + call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& + & a1,a2,a3,b1,b2,b3,c,g,info) + call psb_barrier(ctxt) + t2 = psb_wtime() - t1 + if(info /= psb_success_) then + info=psb_err_from_subroutine_ + call psb_errpush(info,name) + goto 9999 + end if + + if (iam == psb_root_) write(*,'("Overall matrix creation time : ",es12.5)')t2 + if (iam == psb_root_) write(*,'(" ")') + + select case(choice) + + case(1) + + ! initialize a V-cycle preconditioner with 4 Jacobi sweep + ! and 8 Jacobi sweeps as coarsest-level solver + + call P%init(ctxt,'ML',info) + call P%set('SMOOTHER_TYPE','JACOBI',info) + call P%set('SMOOTHER_SWEEPS',4,info) + call P%set('COARSE_SOLVE','JACOBI',info) + call P%set('COARSE_SWEEPS',8,info) + kmethod = 'CG' + + case(2) + + ! initialize a V-cycle preconditioner based on the coupled aggregation relying on matching, + ! with maximum size of aggregates equal to 8 and smoothed prolongators, + ! Block-Jacobi smoother using approximate inverse INVK and + ! and 4 sweeps of INVK on he coarsest level + + call P%init(ctxt,'ML',info) + call P%set('PAR_AGGR_ALG','COUPLED',info) + call P%set('AGGR_TYPE','MATCHBOXP',info) + call P%set('AGGR_SIZE',8,info) + call P%set('ML_CYCLE','WCYCLE',info) + call P%set('SMOOTHER_SWEEPS',2,info) + call P%set('SUB_SOLVE','INVK',info) + call P%set('COARSE_SOLVE','INVK',info) + call P%set('COARSE_MAT','DIST',info) + kmethod = 'CG' + + end select + + call psb_barrier(ctxt) + t1 = psb_wtime() + + ! build the preconditioner + call P%hierarchy_build(A,desc_A,info) + call P%smoothers_build(A,desc_A,info, amold=agmold, vmold=vgmold, imold=igmold) + + tprec = psb_wtime()-t1 + call psb_amx(ctxt, tprec) + + if (info /= psb_success_) then + call psb_errpush(psb_err_from_subroutine_,name,a_err='amg_precbld') + goto 9999 + end if + + ! set the solver parameters and the initial guess + + call psb_geall(x,desc_A,info) + call x%zero() + call psb_geasb(x,desc_A,info) + + ! Convert A, DESC_A,X,B to a GPU-enabled format + call desc_a%cnv(mold=igmold) + call a%cscnv(info,mold=agmold) + call psb_geasb(x,desc_a,info,mold=vgmold) + call psb_geasb(b,desc_a,info,mold=vgmold) + + + ! solve Ax=b with preconditioned Krylov method + + call psb_barrier(ctxt) + call prec%allocate_wrk(info) + t1 = psb_wtime() + + call psb_krylov(kmethod,A,P,b,x,tol,desc_A,info,itmax,iter,err,itrace=1,istop=2) + + t2 = psb_wtime() - t1 + call psb_amx(ctxt,t2) + call prec%deallocate_wrk(info) + + call psb_geall(r,desc_A,info) + call r%zero() + call psb_geasb(r,desc_A,info) + call psb_geaxpby(done,b,dzero,r,desc_A,info) + call psb_spmm(-done,A,x,done,r,desc_A,info) + resmx = psb_genrm2(r,desc_A,info) + resmxp = psb_geamax(r,desc_A,info) + + amatsize = a%sizeof() + descsize = desc_a%sizeof() + precsize = p%sizeof() + system_size = desc_a%get_global_rows() + call psb_sum(ctxt,amatsize) + call psb_sum(ctxt,descsize) + call psb_sum(ctxt,precsize) + + call P%descr(info) + + if (iam == psb_root_) then + write(*,'(" ")') + write(*,'("Matrix from PDE example")') + write(*,'("Computed solution on ",i8," processors")')np + write(*,'("Linear system size : ",i12)') system_size + write(*,'("Krylov method : ",a)') kmethod + write(*,'("Iterations to convergence : ",i6)')iter + write(*,'("Error estimate on exit : ",es12.5)')err + write(*,'("Time to build prec. : ",es12.5)')tprec + write(*,'("Time to solve system : ",es12.5)')t2 + write(*,'("Time per iteration : ",es12.5)')t2/(iter) + write(*,'("Total time : ",es12.5)')t2+tprec + write(*,'("Residual 2-norm : ",es12.5)')resmx + write(*,'("Residual inf-norm : ",es12.5)')resmxp + write(*,'("Total memory occupation for A : ",i12)')amatsize + write(*,'("Total memory occupation for DESC_A : ",i12)')descsize + write(*,'("Total memory occupation for PREC : ",i12)')precsize + end if + + call psb_gefree(b, desc_A,info) + call psb_gefree(x, desc_A,info) + call psb_spfree(A, desc_A,info) + call P%free(info) + call psb_cdfree(desc_A,info) + call psb_gpu_exit() + call psb_exit(ctxt) + stop + +9999 continue + call psb_error(ctxt) + +contains + ! + ! get parameters from standard input + ! + subroutine get_parms(ctxt,choice,idim,itmax,tol) + + implicit none + + type(psb_ctxt_type) :: ctxt + integer :: choice, idim, itmax + real(psb_dpk_) :: tol + integer :: iam, np, inp_unit + character(len=1024) :: filename + + call psb_info(ctxt,iam,np) + + if (iam == psb_root_) then + if (command_argument_count()>0) then + call get_command_argument(1,filename) + inp_unit = 30 + open(inp_unit,file=filename,action='read',iostat=info) + if (info /= 0) then + write(psb_err_unit,*) 'Could not open file ',filename,' for input' + call psb_abort(ctxt) + stop + else + write(psb_err_unit,*) 'Opened file ',trim(filename),' for input' + end if + else + inp_unit=psb_inp_unit + end if + ! read input parameters + call read_data(choice,inp_unit) + call read_data(idim,inp_unit) + call read_data(itmax,inp_unit) + call read_data(tol,inp_unit) + if (inp_unit /= psb_inp_unit) then + close(inp_unit) + end if + end if + + call psb_bcast(ctxt,choice) + call psb_bcast(ctxt,idim) + call psb_bcast(ctxt,itmax) + call psb_bcast(ctxt,tol) + + end subroutine get_parms + +end program amg_dexample_gpu diff --git a/examples/gpu/data_input.f90 b/examples/gpu/data_input.f90 new file mode 100644 index 00000000..fe95b557 --- /dev/null +++ b/examples/gpu/data_input.f90 @@ -0,0 +1,182 @@ +! +! AMG4PSBLAS version 1.0 +! Algebraic Multigrid Package +! based on PSBLAS (Parallel Sparse BLAS version 3.7) +! +! (C) Copyright 2020 +! +! Salvatore Filippone +! Pasqua D'Ambra +! Fabio Durastante +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions +! are met: +! 1. Redistributions of source code must retain the above copyright +! notice, this list of conditions and the following disclaimer. +! 2. Redistributions in binary form must reproduce the above copyright +! notice, this list of conditions, and the following disclaimer in the +! documentation and/or other materials provided with the distribution. +! 3. The name of the AMG4PSBLAS group or the names of its contributors may +! not be used to endorse or promote products derived from this +! software without specific written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +! ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +! TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +! PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AMG4PSBLAS GROUP OR ITS CONTRIBUTORS +! BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +! CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +! SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +! CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +! ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +! POSSIBILITY OF SUCH DAMAGE. +module data_input + + interface read_data + module procedure read_char, read_int,& + & read_double, read_single,& + & string_read_char, string_read_int,& + & string_read_double, string_read_single + end interface read_data + interface trim_string + module procedure trim_string + end interface + + character(len=4096), private :: charbuf + character, private, parameter :: def_marker="!" + +contains + + subroutine read_char(val,file,marker) + character(len=*), intent(out) :: val + integer, intent(in) :: file + character(len=1), optional, intent(in) :: marker + + read(file,'(a)')charbuf + call read_data(val,charbuf,marker) + + end subroutine read_char + + subroutine read_int(val,file,marker) + integer, intent(out) :: val + integer, intent(in) :: file + character(len=1), optional, intent(in) :: marker + + read(file,'(a)')charbuf + call read_data(val,charbuf,marker) + + end subroutine read_int + subroutine read_single(val,file,marker) + use psb_base_mod + real(psb_spk_), intent(out) :: val + integer, intent(in) :: file + character(len=1), optional, intent(in) :: marker + + read(file,'(a)')charbuf + call read_data(val,charbuf,marker) + + end subroutine read_single + subroutine read_double(val,file,marker) + use psb_base_mod + real(psb_dpk_), intent(out) :: val + integer, intent(in) :: file + character(len=1), optional, intent(in) :: marker + + read(file,'(a)')charbuf + call read_data(val,charbuf,marker) + + end subroutine read_double + + subroutine string_read_char(val,file,marker) + character(len=*), intent(out) :: val + character(len=*), intent(in) :: file + character(len=1), optional, intent(in) :: marker + character(len=1) :: marker_ + character(len=1024) :: charbuf + integer :: idx + if (present(marker)) then + marker_ = marker + else + marker_ = def_marker + end if + read(file,'(a)')charbuf + charbuf = adjustl(charbuf) + idx=index(charbuf,marker_) + if (idx == 0) idx = len(charbuf)+1 + read(charbuf(1:idx-1),'(a)') val + end subroutine string_read_char + + subroutine string_read_int(val,file,marker) + integer, intent(out) :: val + character(len=*), intent(in) :: file + character(len=1), optional, intent(in) :: marker + character(len=1) :: marker_ + character(len=1024) :: charbuf + integer :: idx + if (present(marker)) then + marker_ = marker + else + marker_ = def_marker + end if + read(file,'(a)')charbuf + charbuf = adjustl(charbuf) + idx=index(charbuf,marker_) + if (idx == 0) idx = len(charbuf)+1 + read(charbuf(1:idx-1),*) val + end subroutine string_read_int + subroutine string_read_single(val,file,marker) + use psb_base_mod + real(psb_spk_), intent(out) :: val + character(len=*), intent(in) :: file + character(len=1), optional, intent(in) :: marker + character(len=1) :: marker_ + character(len=1024) :: charbuf + integer :: idx + if (present(marker)) then + marker_ = marker + else + marker_ = def_marker + end if + read(file,'(a)')charbuf + charbuf = adjustl(charbuf) + idx=index(charbuf,marker_) + if (idx == 0) idx = len(charbuf)+1 + read(charbuf(1:idx-1),*) val + end subroutine string_read_single + subroutine string_read_double(val,file,marker) + use psb_base_mod + real(psb_dpk_), intent(out) :: val + character(len=*), intent(in) :: file + character(len=1), optional, intent(in) :: marker + character(len=1) :: marker_ + character(len=1024) :: charbuf + integer :: idx + if (present(marker)) then + marker_ = marker + else + marker_ = def_marker + end if + read(file,'(a)')charbuf + charbuf = adjustl(charbuf) + idx=index(charbuf,marker_) + if (idx == 0) idx = len(charbuf)+1 + read(charbuf(1:idx-1),*) val + end subroutine string_read_double + + function trim_string(string,marker) + character(len=*), intent(in) :: string + character(len=1), optional, intent(in) :: marker + character(len=len(string)) :: trim_string + character(len=1) :: marker_ + integer :: idx + if (present(marker)) then + marker_ = marker + else + marker_ = def_marker + end if + idx=index(string,marker_) + trim_string = adjustl(string(idx:)) + end function trim_string +end module data_input diff --git a/examples/gpu/runs/ml.inp b/examples/gpu/runs/ml.inp new file mode 100644 index 00000000..d51ba703 --- /dev/null +++ b/examples/gpu/runs/ml.inp @@ -0,0 +1,4 @@ +1 ! Preconditioner choice +10 ! IDIM; domain size is idim**3 +500 ! ITMAX +1.d-6 ! EPS diff --git a/tests/gpu/Makefile b/tests/gpu/Makefile deleted file mode 100644 index 5e12f775..00000000 --- a/tests/gpu/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -AMGDIR=../.. -AMGINCDIR=$(AMGDIR)/include -include $(AMGINCDIR)/Make.inc.amg4psblas -AMGMODDIR=$(AMGDIR)/modules -AMGLIBDIR=$(AMGDIR)/lib -AMG_LIBS=-L$(AMGLIBDIR) -lpsb_krylov -lamg_prec -lpsb_prec -FINCLUDES=$(FMFLAG). $(FMFLAG)$(AMGMODDIR) $(FMFLAG)$(AMGINCDIR) $(PSBLAS_INCLUDES) $(FIFLAG). - -LINKOPT= -EXEDIR=./runs - -all: amg_s_pde3d amg_d_pde3d amg_s_pde2d amg_d_pde2d - -amg_d_pde3d: amg_d_pde3d.o amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o data_input.o - $(FLINK) $(LINKOPT) amg_d_pde3d.o amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o data_input.o -o amg_d_pde3d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) - /bin/mv amg_d_pde3d $(EXEDIR) - -amg_s_pde3d: amg_s_pde3d.o amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o data_input.o - $(FLINK) $(LINKOPT) amg_s_pde3d.o amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o data_input.o -o amg_s_pde3d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) - /bin/mv amg_s_pde3d $(EXEDIR) - -amg_d_pde2d: amg_d_pde2d.o amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o data_input.o - $(FLINK) $(LINKOPT) amg_d_pde2d.o amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o data_input.o -o amg_d_pde2d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) - /bin/mv amg_d_pde2d $(EXEDIR) - -amg_s_pde2d: amg_s_pde2d.o amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o data_input.o - $(FLINK) $(LINKOPT) amg_s_pde2d.o amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o data_input.o -o amg_s_pde2d $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) - /bin/mv amg_s_pde2d $(EXEDIR) - -amg_d_pde3d_rebld: amg_d_pde3d_rebld.o data_input.o - $(FLINK) $(LINKOPT) amg_d_pde3d_rebld.o data_input.o -o amg_d_pde3d_rebld $(AMG_LIBS) $(PSBLAS_LIBS) $(LDLIBS) - /bin/mv amg_d_pde3d_rebld $(EXEDIR) - -amg_d_pde3d.o amg_s_pde3d.o amg_d_pde2d.o amg_s_pde2d.o: data_input.o - -amg_d_pde3d.o: amg_d_genpde_mod.o amg_d_pde3d_base_mod.o amg_d_pde3d_exp_mod.o amg_d_pde3d_gauss_mod.o -amg_s_pde3d.o: amg_s_genpde_mod.o amg_s_pde3d_base_mod.o amg_s_pde3d_exp_mod.o amg_s_pde3d_gauss_mod.o -amg_d_pde2d.o: amg_d_genpde_mod.o amg_d_pde2d_base_mod.o amg_d_pde2d_exp_mod.o amg_d_pde2d_box_mod.o -amg_s_pde2d.o: amg_s_genpde_mod.o amg_s_pde2d_base_mod.o amg_s_pde2d_exp_mod.o amg_s_pde2d_box_mod.o - -check: all - cd runs && ./amg_d_pde2d f - else - f_ => d_null_func_3d - end if - - if (present(partition)) then - if ((1<= partition).and.(partition <= 3)) then - partition_ = partition - else - write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' - partition_ = 3 - end if - else - partition_ = 3 - end if - deltah = done/(idim+2) - sqdeltah = deltah*deltah - deltah2 = 2.0_psb_dpk_* deltah - - if (present(partition)) then - if ((1<= partition).and.(partition <= 3)) then - partition_ = partition - else - write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' - partition_ = 3 - end if - else - partition_ = 3 - end if - - ! initialize array descriptor and sparse matrix storage. provide an - ! estimate of the number of non zeroes - - m = (1_psb_lpk_*idim)*idim*idim - n = m - nnz = 7*((n+np-1)/np) - if(iam == psb_root_) write(psb_out_unit,'("Generating Matrix (size=",i0,")...")')n - t0 = psb_wtime() - select case(partition_) - case(1) - ! A BLOCK partition - if (present(nrl)) then - nr = nrl - else - ! - ! Using a simple BLOCK distribution. - ! - nt = (m+np-1)/np - nr = max(0,min(nt,m-(iam*nt))) - end if - - nt = nr - call psb_sum(ctxt,nt) - if (nt /= m) then - write(psb_err_unit,*) iam, 'Initialization error ',nr,nt,m - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - - ! - ! First example of use of CDALL: specify for each process a number of - ! contiguous rows - ! - call psb_cdall(ctxt,desc_a,info,nl=nr) - myidx = desc_a%get_global_indices() - nlr = size(myidx) - - case(2) - ! A partition defined by the user through IV - - if (present(iv)) then - if (size(iv) /= m) then - write(psb_err_unit,*) iam, 'Initialization error: wrong IV size',size(iv),m - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - else - write(psb_err_unit,*) iam, 'Initialization error: IV not present' - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - - ! - ! Second example of use of CDALL: specify for each row the - ! process that owns it - ! - call psb_cdall(ctxt,desc_a,info,vg=iv) - myidx = desc_a%get_global_indices() - nlr = size(myidx) - - case(3) - ! A 3-dimensional partition - - ! A nifty MPI function will split the process list - npdims = 0 - call mpi_dims_create(np,3,npdims,info) - npx = npdims(1) - npy = npdims(2) - npz = npdims(3) - - allocate(bndx(0:npx),bndy(0:npy),bndz(0:npz)) - ! We can reuse idx2ijk for process indices as well. - call idx2ijk(iamx,iamy,iamz,iam,npx,npy,npz,base=0) - ! Now let's split the 3D cube in hexahedra - call dist1Didx(bndx,idim,npx) - mynx = bndx(iamx+1)-bndx(iamx) - call dist1Didx(bndy,idim,npy) - myny = bndy(iamy+1)-bndy(iamy) - call dist1Didx(bndz,idim,npz) - mynz = bndz(iamz+1)-bndz(iamz) - - ! How many indices do I own? - nlr = mynx*myny*mynz - allocate(myidx(nlr)) - ! Now, let's generate the list of indices I own - nr = 0 - do i=bndx(iamx),bndx(iamx+1)-1 - do j=bndy(iamy),bndy(iamy+1)-1 - do k=bndz(iamz),bndz(iamz+1)-1 - nr = nr + 1 - call ijk2idx(myidx(nr),i,j,k,idim,idim,idim) - end do - end do - end do - if (nr /= nlr) then - write(psb_err_unit,*) iam,iamx,iamy,iamz, 'Initialization error: NR vs NLR ',& - & nr,nlr,mynx,myny,mynz - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - end if - - ! - ! Third example of use of CDALL: specify for each process - ! the set of global indices it owns. - ! - call psb_cdall(ctxt,desc_a,info,vl=myidx) - - ! - ! Specify process topology - ! - block - ! - ! Use adjcncy methods - ! - integer(psb_mpk_), allocatable :: neighbours(:) - integer(psb_mpk_) :: cnt - logical, parameter :: debug_adj=.true. - if (debug_adj.and.(np > 1)) then - cnt = 0 - allocate(neighbours(np)) - if (iamx < npx-1) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx+1,iamy,iamz,npx,npy,npz,base=0) - end if - if (iamy < npy-1) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy+1,iamz,npx,npy,npz,base=0) - end if - if (iamz < npz-1) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy,iamz+1,npx,npy,npz,base=0) - end if - if (iamx >0) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx-1,iamy,iamz,npx,npy,npz,base=0) - end if - if (iamy >0) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy-1,iamz,npx,npy,npz,base=0) - end if - if (iamz >0) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy,iamz-1,npx,npy,npz,base=0) - end if - call psb_realloc(cnt, neighbours,info) - call desc_a%set_p_adjcncy(neighbours) - !write(0,*) iam,' Check on neighbours: ',desc_a%get_p_adjcncy() - end if - end block - - case default - write(psb_err_unit,*) iam, 'Initialization error: should not get here' - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end select - - - if (info == psb_success_) call psb_spall(a,desc_a,info,nnz=nnz) - ! define rhs from boundary conditions; also build initial guess - if (info == psb_success_) call psb_geall(xv,desc_a,info) - if (info == psb_success_) call psb_geall(bv,desc_a,info) - - call psb_barrier(ctxt) - talc = psb_wtime()-t0 - - if (info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='allocation rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - ! we build an auxiliary matrix consisting of one row at a - ! time; just a small matrix. might be extended to generate - ! a bunch of rows per call. - ! - allocate(val(20*nb),irow(20*nb),& - &icol(20*nb),stat=info) - if (info /= psb_success_ ) then - info=psb_err_alloc_dealloc_ - call psb_errpush(info,name) - goto 9999 - endif - - - ! loop over rows belonging to current process in a block - ! distribution. - - call psb_barrier(ctxt) - t1 = psb_wtime() - do ii=1, nlr,nb - ib = min(nb,nlr-ii+1) - icoeff = 1 - do k=1,ib - i=ii+k-1 - ! local matrix pointer - glob_row=myidx(i) - ! compute gridpoint coordinates - call idx2ijk(ix,iy,iz,glob_row,idim,idim,idim) - ! x, y, z coordinates - x = (ix-1)*deltah - y = (iy-1)*deltah - z = (iz-1)*deltah - zt(k) = f_(x,y,z) - ! internal point: build discretization - ! - ! term depending on (x-1,y,z) - ! - val(icoeff) = -a1(x,y,z)/sqdeltah-b1(x,y,z)/deltah2 - if (ix == 1) then - zt(k) = g(dzero,y,z)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix-1,iy,iz,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x,y-1,z) - val(icoeff) = -a2(x,y,z)/sqdeltah-b2(x,y,z)/deltah2 - if (iy == 1) then - zt(k) = g(x,dzero,z)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy-1,iz,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x,y,z-1) - val(icoeff)=-a3(x,y,z)/sqdeltah-b3(x,y,z)/deltah2 - if (iz == 1) then - zt(k) = g(x,y,dzero)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy,iz-1,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - - ! term depending on (x,y,z) - val(icoeff)=(2*done)*(a1(x,y,z)+a2(x,y,z)+a3(x,y,z))/sqdeltah & - & + c(x,y,z) - call ijk2idx(icol(icoeff),ix,iy,iz,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - ! term depending on (x,y,z+1) - val(icoeff)=-a3(x,y,z)/sqdeltah+b3(x,y,z)/deltah2 - if (iz == idim) then - zt(k) = g(x,y,done)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy,iz+1,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x,y+1,z) - val(icoeff)=-a2(x,y,z)/sqdeltah+b2(x,y,z)/deltah2 - if (iy == idim) then - zt(k) = g(x,done,z)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy+1,iz,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x+1,y,z) - val(icoeff)=-a1(x,y,z)/sqdeltah+b1(x,y,z)/deltah2 - if (ix==idim) then - zt(k) = g(done,y,z)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix+1,iy,iz,idim,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - - end do - call psb_spins(icoeff-1,irow,icol,val,a,desc_a,info) - if(info /= psb_success_) exit - call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),bv,desc_a,info) - if(info /= psb_success_) exit - zt(:)=dzero - call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),xv,desc_a,info) - if(info /= psb_success_) exit - end do - - tgen = psb_wtime()-t1 - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='insert rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - deallocate(val,irow,icol) - - call psb_barrier(ctxt) - t1 = psb_wtime() - call psb_cdasb(desc_a,info) - tcdasb = psb_wtime()-t1 - call psb_barrier(ctxt) - t1 = psb_wtime() - if (info == psb_success_) then - if (present(amold)) then - call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,mold=amold) - else - call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,afmt=afmt) - end if - end if - call psb_barrier(ctxt) - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='asb rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - if (info == psb_success_) call psb_geasb(xv,desc_a,info,mold=vmold) - if (info == psb_success_) call psb_geasb(bv,desc_a,info,mold=vmold) - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='asb rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - tasb = psb_wtime()-t1 - call psb_barrier(ctxt) - ttot = psb_wtime() - t0 - - call psb_amx(ctxt,talc) - call psb_amx(ctxt,tgen) - call psb_amx(ctxt,tasb) - call psb_amx(ctxt,ttot) - if(iam == psb_root_) then - tmpfmt = a%get_fmt() - write(psb_out_unit,'("The matrix has been generated and assembled in ",a3," format.")')& - & tmpfmt - write(psb_out_unit,'("-allocation time : ",es12.5)') talc - write(psb_out_unit,'("-coeff. gen. time : ",es12.5)') tgen - write(psb_out_unit,'("-desc asbly time : ",es12.5)') tcdasb - write(psb_out_unit,'("- mat asbly time : ",es12.5)') tasb - write(psb_out_unit,'("-total time : ",es12.5)') ttot - - end if - call psb_erractionrestore(err_act) - return - -9999 call psb_error_handler(ctxt,err_act) - - return - end subroutine amg_d_gen_pde3d - - - - ! - ! subroutine to allocate and fill in the coefficient matrix and - ! the rhs. - ! - subroutine amg_d_gen_pde2d(ctxt,idim,a,bv,xv,desc_a,afmt,& - & a1,a2,b1,b2,c,g,info,f,amold,vmold,partition, nrl,iv) - use psb_base_mod - use psb_util_mod - ! - ! Discretizes the partial differential equation - ! - ! d d(u) d d(u) b1 d(u) b2 d(u) - ! - -- a1 ---- - -- a1 ---- + ----- + ------ + c u = f - ! dx dx dy dy dx dy - ! - ! with Dirichlet boundary conditions - ! u = g - ! - ! on the unit square 0<=x,y<=1. - ! - ! - ! Note that if b1=b2=c=0., the PDE is the Laplace equation. - ! - implicit none - procedure(d_func_2d) :: b1,b2,c,a1,a2,g - integer(psb_ipk_) :: idim - type(psb_dspmat_type) :: a - type(psb_d_vect_type) :: xv,bv - type(psb_desc_type) :: desc_a - integer(psb_ipk_) :: info - type(psb_ctxt_type) :: ctxt - character :: afmt*5 - procedure(d_func_2d), optional :: f - class(psb_d_base_sparse_mat), optional :: amold - class(psb_d_base_vect_type), optional :: vmold - integer(psb_ipk_), optional :: partition, nrl,iv(:) - ! Local variables. - - integer(psb_ipk_), parameter :: nb=20 - type(psb_d_csc_sparse_mat) :: acsc - type(psb_d_coo_sparse_mat) :: acoo - type(psb_d_csr_sparse_mat) :: acsr - real(psb_dpk_) :: zt(nb),x,y,z,xph,xmh,yph,ymh,zph,zmh - integer(psb_ipk_) :: nnz,nr,nlr,i,j,ii,ib,k, partition_ - integer(psb_lpk_) :: m,n,glob_row,nt - integer(psb_ipk_) :: ix,iy,iz,ia,indx_owner - ! For 2D partition - ! Note: integer control variables going directly into an MPI call - ! must be 4 bytes, i.e. psb_mpk_ - integer(psb_mpk_) :: npdims(2), npp, minfo - integer(psb_ipk_) :: npx,npy,iamx,iamy,mynx,myny - integer(psb_ipk_), allocatable :: bndx(:),bndy(:) - ! Process grid - integer(psb_ipk_) :: np, iam - integer(psb_ipk_) :: icoeff - integer(psb_lpk_), allocatable :: irow(:),icol(:),myidx(:) - real(psb_dpk_), allocatable :: val(:) - ! deltah dimension of each grid cell - ! deltat discretization time - real(psb_dpk_) :: deltah, sqdeltah, deltah2, dd - real(psb_dpk_), parameter :: rhs=0.d0,one=done,zero=0.d0 - real(psb_dpk_) :: t0, t1, t2, t3, tasb, talc, ttot, tgen, tcdasb - integer(psb_ipk_) :: err_act - procedure(d_func_2d), pointer :: f_ - character(len=20) :: name, ch_err,tmpfmt - - info = psb_success_ - name = 'create_matrix' - call psb_erractionsave(err_act) - - call psb_info(ctxt, iam, np) - - - if (present(f)) then - f_ => f - else - f_ => d_null_func_2d - end if - - deltah = done/(idim+2) - sqdeltah = deltah*deltah - deltah2 = 2.0_psb_dpk_* deltah - - - if (present(partition)) then - if ((1<= partition).and.(partition <= 3)) then - partition_ = partition - else - write(*,*) 'Invalid partition choice ',partition,' defaulting to 3' - partition_ = 3 - end if - else - partition_ = 3 - end if - - ! initialize array descriptor and sparse matrix storage. provide an - ! estimate of the number of non zeroes - - m = (1_psb_lpk_)*idim*idim - n = m - nnz = 7*((n+np-1)/np) - if(iam == psb_root_) write(psb_out_unit,'("Generating Matrix (size=",i0,")...")')n - t0 = psb_wtime() - select case(partition_) - case(1) - ! A BLOCK partition - if (present(nrl)) then - nr = nrl - else - ! - ! Using a simple BLOCK distribution. - ! - nt = (m+np-1)/np - nr = max(0,min(nt,m-(iam*nt))) - end if - - nt = nr - call psb_sum(ctxt,nt) - if (nt /= m) then - write(psb_err_unit,*) iam, 'Initialization error ',nr,nt,m - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - - ! - ! First example of use of CDALL: specify for each process a number of - ! contiguous rows - ! - call psb_cdall(ctxt,desc_a,info,nl=nr) - myidx = desc_a%get_global_indices() - nlr = size(myidx) - - case(2) - ! A partition defined by the user through IV - - if (present(iv)) then - if (size(iv) /= m) then - write(psb_err_unit,*) iam, 'Initialization error: wrong IV size',size(iv),m - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - else - write(psb_err_unit,*) iam, 'Initialization error: IV not present' - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end if - - ! - ! Second example of use of CDALL: specify for each row the - ! process that owns it - ! - call psb_cdall(ctxt,desc_a,info,vg=iv) - myidx = desc_a%get_global_indices() - nlr = size(myidx) - - case(3) - ! A 2-dimensional partition - - ! A nifty MPI function will split the process list - npdims = 0 - call mpi_dims_create(np,2,npdims,info) - npx = npdims(1) - npy = npdims(2) - - allocate(bndx(0:npx),bndy(0:npy)) - ! We can reuse idx2ijk for process indices as well. - call idx2ijk(iamx,iamy,iam,npx,npy,base=0) - ! Now let's split the 2D square in rectangles - call dist1Didx(bndx,idim,npx) - mynx = bndx(iamx+1)-bndx(iamx) - call dist1Didx(bndy,idim,npy) - myny = bndy(iamy+1)-bndy(iamy) - - ! How many indices do I own? - nlr = mynx*myny - allocate(myidx(nlr)) - ! Now, let's generate the list of indices I own - nr = 0 - do i=bndx(iamx),bndx(iamx+1)-1 - do j=bndy(iamy),bndy(iamy+1)-1 - nr = nr + 1 - call ijk2idx(myidx(nr),i,j,idim,idim) - end do - end do - if (nr /= nlr) then - write(psb_err_unit,*) iam,iamx,iamy, 'Initialization error: NR vs NLR ',& - & nr,nlr,mynx,myny - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - end if - - ! - ! Third example of use of CDALL: specify for each process - ! the set of global indices it owns. - ! - call psb_cdall(ctxt,desc_a,info,vl=myidx) - - ! - ! Specify process topology - ! - block - ! - ! Use adjcncy methods - ! - integer(psb_mpk_), allocatable :: neighbours(:) - integer(psb_mpk_) :: cnt - logical, parameter :: debug_adj=.true. - if (debug_adj.and.(np > 1)) then - cnt = 0 - allocate(neighbours(np)) - if (iamx < npx-1) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx+1,iamy,npx,npy,base=0) - end if - if (iamy < npy-1) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy+1,npx,npy,base=0) - end if - if (iamx >0) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx-1,iamy,npx,npy,base=0) - end if - if (iamy >0) then - cnt = cnt + 1 - call ijk2idx(neighbours(cnt),iamx,iamy-1,npx,npy,base=0) - end if - call psb_realloc(cnt, neighbours,info) - call desc_a%set_p_adjcncy(neighbours) - !write(0,*) iam,' Check on neighbours: ',desc_a%get_p_adjcncy() - end if - end block - - case default - write(psb_err_unit,*) iam, 'Initialization error: should not get here' - info = -1 - call psb_barrier(ctxt) - call psb_abort(ctxt) - return - end select - - - if (info == psb_success_) call psb_spall(a,desc_a,info,nnz=nnz) - ! define rhs from boundary conditions; also build initial guess - if (info == psb_success_) call psb_geall(xv,desc_a,info) - if (info == psb_success_) call psb_geall(bv,desc_a,info) - - call psb_barrier(ctxt) - talc = psb_wtime()-t0 - - if (info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='allocation rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - ! we build an auxiliary matrix consisting of one row at a - ! time; just a small matrix. might be extended to generate - ! a bunch of rows per call. - ! - allocate(val(20*nb),irow(20*nb),& - &icol(20*nb),stat=info) - if (info /= psb_success_ ) then - info=psb_err_alloc_dealloc_ - call psb_errpush(info,name) - goto 9999 - endif - - - ! loop over rows belonging to current process in a block - ! distribution. - - call psb_barrier(ctxt) - t1 = psb_wtime() - do ii=1, nlr,nb - ib = min(nb,nlr-ii+1) - icoeff = 1 - do k=1,ib - i=ii+k-1 - ! local matrix pointer - glob_row=myidx(i) - ! compute gridpoint coordinates - call idx2ijk(ix,iy,glob_row,idim,idim) - ! x, y coordinates - x = (ix-1)*deltah - y = (iy-1)*deltah - - zt(k) = f_(x,y) - ! internal point: build discretization - ! - ! term depending on (x-1,y) - ! - val(icoeff) = -a1(x,y)/sqdeltah-b1(x,y)/deltah2 - if (ix == 1) then - zt(k) = g(dzero,y)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix-1,iy,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x,y-1) - val(icoeff) = -a2(x,y)/sqdeltah-b2(x,y)/deltah2 - if (iy == 1) then - zt(k) = g(x,dzero)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy-1,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - - ! term depending on (x,y) - val(icoeff)=(2*done)*(a1(x,y) + a2(x,y))/sqdeltah + c(x,y) - call ijk2idx(icol(icoeff),ix,iy,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - ! term depending on (x,y+1) - val(icoeff)=-a2(x,y)/sqdeltah+b2(x,y)/deltah2 - if (iy == idim) then - zt(k) = g(x,done)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix,iy+1,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - ! term depending on (x+1,y) - val(icoeff)=-a1(x,y)/sqdeltah+b1(x,y)/deltah2 - if (ix==idim) then - zt(k) = g(done,y)*(-val(icoeff)) + zt(k) - else - call ijk2idx(icol(icoeff),ix+1,iy,idim,idim) - irow(icoeff) = glob_row - icoeff = icoeff+1 - endif - - end do - call psb_spins(icoeff-1,irow,icol,val,a,desc_a,info) - if(info /= psb_success_) exit - call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),bv,desc_a,info) - if(info /= psb_success_) exit - zt(:)=dzero - call psb_geins(ib,myidx(ii:ii+ib-1),zt(1:ib),xv,desc_a,info) - if(info /= psb_success_) exit - end do - - tgen = psb_wtime()-t1 - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='insert rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - deallocate(val,irow,icol) - - call psb_barrier(ctxt) - t1 = psb_wtime() - call psb_cdasb(desc_a,info) - tcdasb = psb_wtime()-t1 - call psb_barrier(ctxt) - t1 = psb_wtime() - if (info == psb_success_) then - if (present(amold)) then - call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,mold=amold) - else - call psb_spasb(a,desc_a,info,dupl=psb_dupl_err_,afmt=afmt) - end if - end if - call psb_barrier(ctxt) - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='asb rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - if (info == psb_success_) call psb_geasb(xv,desc_a,info,mold=vmold) - if (info == psb_success_) call psb_geasb(bv,desc_a,info,mold=vmold) - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='asb rout.' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - tasb = psb_wtime()-t1 - call psb_barrier(ctxt) - ttot = psb_wtime() - t0 - - call psb_amx(ctxt,talc) - call psb_amx(ctxt,tgen) - call psb_amx(ctxt,tasb) - call psb_amx(ctxt,ttot) - if(iam == psb_root_) then - tmpfmt = a%get_fmt() - write(psb_out_unit,'("The matrix has been generated and assembled in ",a3," format.")')& - & tmpfmt - write(psb_out_unit,'("-allocation time : ",es12.5)') talc - write(psb_out_unit,'("-coeff. gen. time : ",es12.5)') tgen - write(psb_out_unit,'("-desc asbly time : ",es12.5)') tcdasb - write(psb_out_unit,'("- mat asbly time : ",es12.5)') tasb - write(psb_out_unit,'("-total time : ",es12.5)') ttot - - end if - call psb_erractionrestore(err_act) - return - -9999 continue - call psb_erractionrestore(err_act) - if (err_act == psb_act_abort_) then - call psb_error(ctxt) - return - end if - return - end subroutine amg_d_gen_pde2d -end module amg_d_genpde_mod diff --git a/tests/gpu/amg_d_pde3d.f90 b/tests/gpu/amg_d_pde3d.f90 deleted file mode 100644 index c7526f80..00000000 --- a/tests/gpu/amg_d_pde3d.f90 +++ /dev/null @@ -1,671 +0,0 @@ -! -! -! AMG4PSBLAS version 1.0 -! Algebraic Multigrid Package -! based on PSBLAS (Parallel Sparse BLAS version 3.7) -! -! (C) Copyright 2021 -! -! Salvatore Filippone -! Pasqua D'Ambra -! Fabio Durastante -! -! Redistribution and use in source and binary forms, with or without -! modification, are permitted provided that the following conditions -! are met: -! 1. Redistributions of source code must retain the above copyright -! notice, this list of conditions and the following disclaimer. -! 2. Redistributions in binary form must reproduce the above copyright -! notice, this list of conditions, and the following disclaimer in the -! documentation and/or other materials provided with the distribution. -! 3. The name of the AMG4PSBLAS group or the names of its contributors may -! not be used to endorse or promote products derived from this -! software without specific written permission. -! -! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -! ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -! TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -! PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AMG4PSBLAS GROUP OR ITS CONTRIBUTORS -! BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -! CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -! SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -! CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -! ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -! POSSIBILITY OF SUCH DAMAGE. -! -! -! -! File: amg_d_pde3d.f90 -! -! Program: amg_d_pde3d -! This sample program solves a linear system obtained by discretizing a -! PDE with Dirichlet BCs. -! -! -! The PDE is a general second order equation in 3d -! -! a1 dd(u) a2 dd(u) a3 dd(u) b1 d(u) b2 d(u) b3 d(u) -! - ------ - ------ - ------ + ----- + ------ + ------ + c u = f -! dxdx dydy dzdz dx dy dz -! -! with Dirichlet boundary conditions -! u = g -! -! on the unit cube 0<=x,y,z<=1. -! -! -! Note that if b1=b2=b3=c=0., the PDE is the Laplace equation. -! -! There are three choices available for data distribution: -! 1. A simple BLOCK distribution -! 2. A ditribution based on arbitrary assignment of indices to processes, -! typically from a graph partitioner -! 3. A 3D distribution in which the unit cube is partitioned -! into subcubes, each one assigned to a process. -! -program amg_d_pde3d - use psb_base_mod - use amg_prec_mod - use psb_krylov_mod - use psb_util_mod - use psb_gpu_mod - use data_input - use amg_d_pde3d_base_mod - use amg_d_pde3d_exp_mod - use amg_d_pde3d_gauss_mod - use amg_d_genpde_mod - implicit none - - ! input parameters - character(len=20) :: kmethd, ptype - character(len=5) :: afmt, pdecoeff - integer(psb_ipk_) :: idim - integer(psb_epk_) :: system_size - - ! miscellaneous - real(psb_dpk_) :: t1, t2, tprec, thier, tslv - - ! sparse matrix and preconditioner - type(psb_dspmat_type) :: a - type(amg_dprec_type) :: prec - ! GPU variables - type(psb_d_hlg_sparse_mat) :: agmold - type(psb_d_vect_gpu) :: vgmold - type(psb_i_vect_gpu) :: igmold - - ! descriptor - type(psb_desc_type) :: desc_a - ! dense vectors - type(psb_d_vect_type) :: x,b,r - ! parallel environment - type(psb_ctxt_type) :: ctxt - integer(psb_ipk_) :: iam, np - - ! solver parameters - integer(psb_ipk_) :: iter, itmax,itrace, istopc, irst, nlv - integer(psb_epk_) :: amatsize, precsize, descsize - real(psb_dpk_) :: err, resmx, resmxp - - ! Krylov solver data - type solverdata - character(len=40) :: kmethd ! Krylov solver - integer(psb_ipk_) :: istopc ! stopping criterion - integer(psb_ipk_) :: itmax ! maximum number of iterations - integer(psb_ipk_) :: itrace ! tracing - integer(psb_ipk_) :: irst ! restart - real(psb_dpk_) :: eps ! stopping tolerance - end type solverdata - type(solverdata) :: s_choice - - ! preconditioner data - type precdata - - ! preconditioner type - character(len=40) :: descr ! verbose description of the prec - character(len=10) :: ptype ! preconditioner type - - integer(psb_ipk_) :: outer_sweeps ! number of outer sweeps: sweeps for 1-level, - ! AMG cycles for ML - ! general AMG data - character(len=16) :: mlcycle ! AMG cycle type - integer(psb_ipk_) :: maxlevs ! maximum number of levels in AMG preconditioner - - ! AMG aggregation - character(len=16) :: aggr_prol ! aggregation type: SMOOTHED, NONSMOOTHED - character(len=16) :: par_aggr_alg ! parallel aggregation algorithm: DEC, SYMDEC - character(len=16) :: aggr_ord ! ordering for aggregation: NATURAL, DEGREE - character(len=16) :: aggr_filter ! filtering: FILTER, NO_FILTER - real(psb_dpk_) :: mncrratio ! minimum aggregation ratio - real(psb_dpk_), allocatable :: athresv(:) ! smoothed aggregation threshold vector - integer(psb_ipk_) :: thrvsz ! size of threshold vector - real(psb_dpk_) :: athres ! smoothed aggregation threshold - integer(psb_ipk_) :: csizepp ! minimum size of coarsest matrix per process - - ! AMG smoother or pre-smoother; also 1-lev preconditioner - character(len=16) :: smther ! (pre-)smoother type: BJAC, AS - integer(psb_ipk_) :: jsweeps ! (pre-)smoother / 1-lev prec. sweeps - integer(psb_ipk_) :: novr ! number of overlap layers - character(len=16) :: restr ! restriction over application of AS - character(len=16) :: prol ! prolongation over application of AS - character(len=16) :: solve ! local subsolver type: ILU, MILU, ILUT, - ! UMF, MUMPS, SLU, FWGS, BWGS, JAC - character(len=16) :: variant ! AINV variant: LLK, etc - integer(psb_ipk_) :: fill ! fill-in for incomplete LU factorization - integer(psb_ipk_) :: invfill ! Inverse fill-in for INVK - real(psb_dpk_) :: thr ! threshold for ILUT factorization - - ! AMG post-smoother; ignored by 1-lev preconditioner - character(len=16) :: smther2 ! post-smoother type: BJAC, AS - integer(psb_ipk_) :: jsweeps2 ! post-smoother sweeps - integer(psb_ipk_) :: novr2 ! number of overlap layers - character(len=16) :: restr2 ! restriction over application of AS - character(len=16) :: prol2 ! prolongation over application of AS - character(len=16) :: solve2 ! local subsolver type: ILU, MILU, ILUT, - ! UMF, MUMPS, SLU, FWGS, BWGS, JAC - character(len=16) :: variant2 ! AINV variant: LLK, etc - integer(psb_ipk_) :: fill2 ! fill-in for incomplete LU factorization - integer(psb_ipk_) :: invfill2 ! Inverse fill-in for INVK - real(psb_dpk_) :: thr2 ! threshold for ILUT factorization - - ! coarsest-level solver - character(len=16) :: cmat ! coarsest matrix layout: REPL, DIST - character(len=16) :: csolve ! coarsest-lev solver: BJAC, SLUDIST (distr. - ! mat.); UMF, MUMPS, SLU, ILU, ILUT, MILU - ! (repl. mat.) - character(len=16) :: csbsolve ! coarsest-lev local subsolver: ILU, ILUT, - ! MILU, UMF, MUMPS, SLU - integer(psb_ipk_) :: cfill ! fill-in for incomplete LU factorization - real(psb_dpk_) :: cthres ! threshold for ILUT factorization - integer(psb_ipk_) :: cjswp ! sweeps for GS or JAC coarsest-lev subsolver - - end type precdata - type(precdata) :: p_choice - - ! other variables - integer(psb_ipk_) :: info, i, k - character(len=20) :: name,ch_err - - info=psb_success_ - - - call psb_init(ctxt) - call psb_info(ctxt,iam,np) - ! - ! BEWARE: if you have NGPUS per node, the default is to - ! attach to mod(IAM,NGPUS) - ! - call psb_gpu_init(ictxt) - - if (iam < 0) then - ! This should not happen, but just in case - call psb_exit(ctxt) - stop - endif - if(psb_get_errstatus() /= 0) goto 9999 - name='amg_d_pde3d' - call psb_set_errverbosity(itwo) - ! - ! Hello world - ! - if (iam == psb_root_) then - write(*,*) 'Welcome to AMG4PSBLAS version: ',amg_version_string_ - write(*,*) 'This is the ',trim(name),' sample program' - end if - write(*,*) 'Process ',iam,' running on device: ', psb_cuda_getDevice(),' out of', psb_cuda_getDeviceCount() - write(*,*) 'Process ',iam,' device ', psb_cuda_getDevice(),' is a: ', trim(psb_gpu_DeviceName()) - - - ! - ! get parameters - ! - call get_parms(ctxt,afmt,idim,s_choice,p_choice,pdecoeff) - - ! - ! allocate and fill in the coefficient matrix, rhs and initial guess - ! - - call psb_barrier(ctxt) - t1 = psb_wtime() - select case(psb_toupper(trim(pdecoeff))) - case("CONST") - call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& - & a1,a2,a3,b1,b2,b3,c,g,info) - case("EXP") - call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& - & a1_exp,a2_exp,a3_exp,b1_exp,b2_exp,b3_exp,c_exp,g_exp,info) - case("GAUSS") - call amg_gen_pde3d(ctxt,idim,a,b,x,desc_a,afmt,& - & a1_gauss,a2_gauss,a3_gauss,b1_gauss,b2_gauss,b3_gauss,c_gauss,g_gauss,info) - case default - info=psb_err_from_subroutine_ - ch_err='amg_gen_pdecoeff' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end select - - - call psb_barrier(ctxt) - t2 = psb_wtime() - t1 - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='amg_gen_pde3d' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - if (iam == psb_root_) & - & write(psb_out_unit,'("PDE Coefficients : ",a)')pdecoeff - if (iam == psb_root_) & - & write(psb_out_unit,'("Overall matrix creation time : ",es12.5)')t2 - if (iam == psb_root_) & - & write(psb_out_unit,'(" ")') - ! - ! initialize the preconditioner - ! - call prec%init(ctxt,p_choice%ptype,info) - select case(trim(psb_toupper(p_choice%ptype))) - case ('NONE','NOPREC') - ! Do nothing, keep defaults - - case ('JACOBI','L1-JACOBI','GS','FWGS','FBGS') - ! 1-level sweeps from "outer_sweeps" - call prec%set('smoother_sweeps', p_choice%jsweeps, info) - - case ('BJAC') - call prec%set('smoother_sweeps', p_choice%jsweeps, info) - call prec%set('sub_solve', p_choice%solve, info) - call prec%set('sub_fillin', p_choice%fill, info) - call prec%set('sub_iluthrs', p_choice%thr, info) - - case('AS') - call prec%set('smoother_sweeps', p_choice%jsweeps, info) - call prec%set('sub_ovr', p_choice%novr, info) - call prec%set('sub_restr', p_choice%restr, info) - call prec%set('sub_prol', p_choice%prol, info) - call prec%set('sub_solve', p_choice%solve, info) - call prec%set('sub_fillin', p_choice%fill, info) - call prec%set('sub_iluthrs', p_choice%thr, info) - - case ('ML') - ! multilevel preconditioner - - call prec%set('ml_cycle', p_choice%mlcycle, info) - call prec%set('outer_sweeps', p_choice%outer_sweeps,info) - if (p_choice%csizepp>0)& - & call prec%set('min_coarse_size_per_process', p_choice%csizepp, info) - if (p_choice%mncrratio>1)& - & call prec%set('min_cr_ratio', p_choice%mncrratio, info) - if (p_choice%maxlevs>0)& - & call prec%set('max_levs', p_choice%maxlevs, info) - if (p_choice%athres >= dzero) & - & call prec%set('aggr_thresh', p_choice%athres, info) - if (p_choice%thrvsz>0) then - do k=1,min(p_choice%thrvsz,size(prec%precv)-1) - call prec%set('aggr_thresh', p_choice%athresv(k), info,ilev=(k+1)) - end do - end if - - call prec%set('aggr_prol', p_choice%aggr_prol, info) - call prec%set('par_aggr_alg', p_choice%par_aggr_alg, info) - call prec%set('aggr_ord', p_choice%aggr_ord, info) - call prec%set('aggr_filter', p_choice%aggr_filter,info) - - - call prec%set('smoother_type', p_choice%smther, info) - call prec%set('smoother_sweeps', p_choice%jsweeps, info) - - select case (psb_toupper(p_choice%smther)) - case ('GS','BWGS','FBGS','JACOBI','L1-JACOBI','L1-FBGS') - ! do nothing - case default - call prec%set('sub_ovr', p_choice%novr, info) - call prec%set('sub_restr', p_choice%restr, info) - call prec%set('sub_prol', p_choice%prol, info) - select case(trim(psb_toupper(p_choice%solve))) - case('INVK') - call prec%set('sub_solve', p_choice%solve, info) - case('INVT') - call prec%set('sub_solve', p_choice%solve, info) - case('AINV') - call prec%set('sub_solve', p_choice%solve, info) - call prec%set('ainv_alg', p_choice%variant, info) - case default - call prec%set('sub_solve', p_choice%solve, info) - end select - - call prec%set('sub_fillin', p_choice%fill, info) - call prec%set('inv_fillin', p_choice%invfill, info) - call prec%set('sub_iluthrs', p_choice%thr, info) - end select - - if (psb_toupper(p_choice%smther2) /= 'NONE') then - call prec%set('smoother_type', p_choice%smther2, info,pos='post') - call prec%set('smoother_sweeps', p_choice%jsweeps2, info,pos='post') - select case (psb_toupper(p_choice%smther2)) - case ('GS','BWGS','FBGS','JACOBI','L1-JACOBI','L1-FBGS') - ! do nothing - case default - call prec%set('sub_ovr', p_choice%novr2, info,pos='post') - call prec%set('sub_restr', p_choice%restr2, info,pos='post') - call prec%set('sub_prol', p_choice%prol2, info,pos='post') - select case(trim(psb_toupper(p_choice%solve2))) - case('INVK') - call prec%set('sub_solve', p_choice%solve, info) - case('INVT') - call prec%set('sub_solve', p_choice%solve, info) - case('AINV') - call prec%set('sub_solve', p_choice%solve, info) - call prec%set('ainv_alg', p_choice%variant, info) - case default - call prec%set('sub_solve', p_choice%solve2, info, pos='post') - end select - - call prec%set('sub_fillin', p_choice%fill2, info,pos='post') - call prec%set('inv_fillin', p_choice%invfill2, info,pos='post') - call prec%set('sub_iluthrs', p_choice%thr2, info,pos='post') - end select - end if - - call prec%set('coarse_solve', p_choice%csolve, info) - if (psb_toupper(p_choice%csolve) == 'BJAC') & - & call prec%set('coarse_subsolve', p_choice%csbsolve, info) - call prec%set('coarse_mat', p_choice%cmat, info) - call prec%set('coarse_fillin', p_choice%cfill, info) - call prec%set('coarse_iluthrs', p_choice%cthres, info) - call prec%set('coarse_sweeps', p_choice%cjswp, info) - - end select - - ! build the preconditioner - call psb_barrier(ctxt) - t1 = psb_wtime() - call prec%hierarchy_build(a,desc_a,info) - thier = psb_wtime()-t1 - if (info /= psb_success_) then - call psb_errpush(psb_err_from_subroutine_,name,a_err='amg_hierarchy_bld') - goto 9999 - end if - call psb_barrier(ctxt) - t1 = psb_wtime() - call prec%smoothers_build(a,desc_a,info, amold=agmold, vmold=vgmold, imold=igmold) - tprec = psb_wtime()-t1 - if (info /= psb_success_) then - call psb_errpush(psb_err_from_subroutine_,name,a_err='amg_smoothers_bld') - goto 9999 - end if - - call psb_amx(ctxt, thier) - call psb_amx(ctxt, tprec) - - if(iam == psb_root_) then - write(psb_out_unit,'(" ")') - write(psb_out_unit,'("Preconditioner: ",a)') trim(p_choice%descr) - write(psb_out_unit,'("Preconditioner time: ",es12.5)')thier+tprec - write(psb_out_unit,'(" ")') - end if - call desc_a%cnv(mold=igmold) - call a%cscnv(info,mold=agmold) - call psb_geasb(x,desc_a,info,mold=vgmold) - call psb_geasb(b,desc_a,info,mold=vgmold) - - ! - ! iterative method parameters - ! - call psb_barrier(ctxt) - call prec%allocate_wrk(info) - t1 = psb_wtime() - call psb_krylov(s_choice%kmethd,a,prec,b,x,s_choice%eps,& - & desc_a,info,itmax=s_choice%itmax,iter=iter,err=err,itrace=s_choice%itrace,& - & istop=s_choice%istopc,irst=s_choice%irst) - call prec%deallocate_wrk(info) - call psb_barrier(ctxt) - tslv = psb_wtime() - t1 - - call psb_amx(ctxt,tslv) - - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='solver routine' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - - call psb_barrier(ctxt) - tslv = psb_wtime() - t1 - call psb_amx(ctxt,tslv) - - ! compute residual norms - call psb_geall(r,desc_a,info) - call r%zero() - call psb_geasb(r,desc_a,info) - call psb_geaxpby(done,b,dzero,r,desc_a,info) - call psb_spmm(-done,a,x,done,r,desc_a,info) - resmx = psb_genrm2(r,desc_a,info) - resmxp = psb_geamax(r,desc_a,info) - - amatsize = a%sizeof() - descsize = desc_a%sizeof() - precsize = prec%sizeof() - system_size = desc_a%get_global_rows() - call psb_sum(ctxt,amatsize) - call psb_sum(ctxt,descsize) - call psb_sum(ctxt,precsize) - call prec%descr(iout=psb_out_unit) - if (iam == psb_root_) then - write(psb_out_unit,'("Computed solution on ",i8," processors")') np - write(psb_out_unit,'("Linear system size : ",i12)') system_size - write(psb_out_unit,'("PDE Coefficients : ",a)') trim(pdecoeff) - write(psb_out_unit,'("Krylov method : ",a)') trim(s_choice%kmethd) - write(psb_out_unit,'("Preconditioner : ",a)') trim(p_choice%descr) - write(psb_out_unit,'("Iterations to convergence : ",i12)') iter - write(psb_out_unit,'("Relative error estimate on exit : ",es12.5)') err - write(psb_out_unit,'("Number of levels in hierarchy : ",i12)') prec%get_nlevs() - write(psb_out_unit,'("Time to build hierarchy : ",es12.5)') thier - write(psb_out_unit,'("Time to build smoothers : ",es12.5)') tprec - write(psb_out_unit,'("Total time for preconditioner : ",es12.5)') tprec+thier - write(psb_out_unit,'("Time to solve system : ",es12.5)') tslv - write(psb_out_unit,'("Time per iteration : ",es12.5)') tslv/iter - write(psb_out_unit,'("Total time : ",es12.5)') tslv+tprec+thier - write(psb_out_unit,'("Residual 2-norm : ",es12.5)') resmx - write(psb_out_unit,'("Residual inf-norm : ",es12.5)') resmxp - write(psb_out_unit,'("Total memory occupation for A : ",i12)') amatsize - write(psb_out_unit,'("Total memory occupation for DESC_A : ",i12)') descsize - write(psb_out_unit,'("Total memory occupation for PREC : ",i12)') precsize - write(psb_out_unit,'("Storage format for A : ",a )') a%get_fmt() - write(psb_out_unit,'("Storage format for DESC_A : ",a )') desc_a%get_fmt() - - end if - - ! - ! cleanup storage and exit - ! - call psb_gefree(b,desc_a,info) - call psb_gefree(x,desc_a,info) - call psb_spfree(a,desc_a,info) - call prec%free(info) - call psb_cdfree(desc_a,info) - if(info /= psb_success_) then - info=psb_err_from_subroutine_ - ch_err='free routine' - call psb_errpush(info,name,a_err=ch_err) - goto 9999 - end if - call psb_gpu_exit() - call psb_exit(ctxt) - stop - -9999 continue - call psb_error(ctxt) - -contains - ! - ! get iteration parameters from standard input - ! - ! - ! get iteration parameters from standard input - ! - subroutine get_parms(ctxt,afmt,idim,solve,prec,pdecoeff) - - implicit none - - type(psb_ctxt_type) :: ctxt - integer(psb_ipk_) :: idim - character(len=*) :: afmt - type(solverdata) :: solve - type(precdata) :: prec - character(len=*) :: pdecoeff - integer(psb_ipk_) :: iam, nm, np, inp_unit - character(len=1024) :: filename - - call psb_info(ctxt,iam,np) - - if (iam == psb_root_) then - if (command_argument_count()>0) then - call get_command_argument(1,filename) - inp_unit = 30 - open(inp_unit,file=filename,action='read',iostat=info) - if (info /= 0) then - write(psb_err_unit,*) 'Could not open file ',filename,' for input' - call psb_abort(ctxt) - stop - else - write(psb_err_unit,*) 'Opened file ',trim(filename),' for input' - end if - else - inp_unit=psb_inp_unit - end if - ! read input data - ! - call read_data(afmt,inp_unit) ! matrix storage format - call read_data(idim,inp_unit) ! Discretization grid size - call read_data(pdecoeff,inp_unit) ! PDE Coefficients - ! Krylov solver data - call read_data(solve%kmethd,inp_unit) ! Krylov solver - call read_data(solve%istopc,inp_unit) ! stopping criterion - call read_data(solve%itmax,inp_unit) ! max num iterations - call read_data(solve%itrace,inp_unit) ! tracing - call read_data(solve%irst,inp_unit) ! restart - call read_data(solve%eps,inp_unit) ! tolerance - ! preconditioner type - call read_data(prec%descr,inp_unit) ! verbose description of the prec - call read_data(prec%ptype,inp_unit) ! preconditioner type - ! First smoother / 1-lev preconditioner - call read_data(prec%smther,inp_unit) ! smoother type - call read_data(prec%jsweeps,inp_unit) ! (pre-)smoother / 1-lev prec sweeps - call read_data(prec%novr,inp_unit) ! number of overlap layers - call read_data(prec%restr,inp_unit) ! restriction over application of AS - call read_data(prec%prol,inp_unit) ! prolongation over application of AS - call read_data(prec%solve,inp_unit) ! local subsolver - call read_data(prec%variant,inp_unit) ! AINV variant - call read_data(prec%fill,inp_unit) ! fill-in for incomplete LU - call read_data(prec%invfill,inp_unit) !Inverse fill-in for INVK - call read_data(prec%thr,inp_unit) ! threshold for ILUT - ! Second smoother/ AMG post-smoother (if NONE ignored in main) - call read_data(prec%smther2,inp_unit) ! smoother type - call read_data(prec%jsweeps2,inp_unit) ! (post-)smoother sweeps - call read_data(prec%novr2,inp_unit) ! number of overlap layers - call read_data(prec%restr2,inp_unit) ! restriction over application of AS - call read_data(prec%prol2,inp_unit) ! prolongation over application of AS - call read_data(prec%solve2,inp_unit) ! local subsolver - call read_data(prec%variant2,inp_unit) ! AINV variant - call read_data(prec%fill2,inp_unit) ! fill-in for incomplete LU - call read_data(prec%invfill2,inp_unit) !Inverse fill-in for INVK - call read_data(prec%thr2,inp_unit) ! threshold for ILUT - ! general AMG data - call read_data(prec%mlcycle,inp_unit) ! AMG cycle type - call read_data(prec%outer_sweeps,inp_unit) ! number of 1lev/outer sweeps - call read_data(prec%maxlevs,inp_unit) ! max number of levels in AMG prec - call read_data(prec%csizepp,inp_unit) ! min size coarsest mat - ! aggregation - call read_data(prec%aggr_prol,inp_unit) ! aggregation type - call read_data(prec%par_aggr_alg,inp_unit) ! parallel aggregation alg - call read_data(prec%aggr_ord,inp_unit) ! ordering for aggregation - call read_data(prec%aggr_filter,inp_unit) ! filtering - call read_data(prec%mncrratio,inp_unit) ! minimum aggregation ratio - call read_data(prec%thrvsz,inp_unit) ! size of aggr thresh vector - if (prec%thrvsz > 0) then - call psb_realloc(prec%thrvsz,prec%athresv,info) - call read_data(prec%athresv,inp_unit) ! aggr thresh vector - else - read(inp_unit,*) ! dummy read to skip a record - end if - call read_data(prec%athres,inp_unit) ! smoothed aggr thresh - ! coasest-level solver - call read_data(prec%csolve,inp_unit) ! coarsest-lev solver - call read_data(prec%csbsolve,inp_unit) ! coarsest-lev subsolver - call read_data(prec%cmat,inp_unit) ! coarsest mat layout - call read_data(prec%cfill,inp_unit) ! fill-in for incompl LU - call read_data(prec%cthres,inp_unit) ! Threshold for ILUT - call read_data(prec%cjswp,inp_unit) ! sweeps for GS/JAC subsolver - if (inp_unit /= psb_inp_unit) then - close(inp_unit) - end if - end if - - call psb_bcast(ctxt,afmt) - call psb_bcast(ctxt,idim) - call psb_bcast(ctxt,pdecoeff) - - call psb_bcast(ctxt,solve%kmethd) - call psb_bcast(ctxt,solve%istopc) - call psb_bcast(ctxt,solve%itmax) - call psb_bcast(ctxt,solve%itrace) - call psb_bcast(ctxt,solve%irst) - call psb_bcast(ctxt,solve%eps) - - call psb_bcast(ctxt,prec%descr) - call psb_bcast(ctxt,prec%ptype) - - ! broadcast first (pre-)smoother / 1-lev prec data - call psb_bcast(ctxt,prec%smther) - call psb_bcast(ctxt,prec%jsweeps) - call psb_bcast(ctxt,prec%novr) - call psb_bcast(ctxt,prec%restr) - call psb_bcast(ctxt,prec%prol) - call psb_bcast(ctxt,prec%solve) - call psb_bcast(ctxt,prec%variant) - call psb_bcast(ctxt,prec%fill) - call psb_bcast(ctxt,prec%invfill) - call psb_bcast(ctxt,prec%thr) - ! broadcast second (post-)smoother - call psb_bcast(ctxt,prec%smther2) - call psb_bcast(ctxt,prec%jsweeps2) - call psb_bcast(ctxt,prec%novr2) - call psb_bcast(ctxt,prec%restr2) - call psb_bcast(ctxt,prec%prol2) - call psb_bcast(ctxt,prec%solve2) - call psb_bcast(ctxt,prec%variant2) - call psb_bcast(ctxt,prec%fill2) - call psb_bcast(ctxt,prec%invfill2) - call psb_bcast(ctxt,prec%thr2) - - ! broadcast AMG parameters - call psb_bcast(ctxt,prec%mlcycle) - call psb_bcast(ctxt,prec%outer_sweeps) - call psb_bcast(ctxt,prec%maxlevs) - - call psb_bcast(ctxt,prec%aggr_prol) - call psb_bcast(ctxt,prec%par_aggr_alg) - call psb_bcast(ctxt,prec%aggr_ord) - call psb_bcast(ctxt,prec%aggr_filter) - call psb_bcast(ctxt,prec%mncrratio) - call psb_bcast(ctxt,prec%thrvsz) - if (prec%thrvsz > 0) then - if (iam /= psb_root_) call psb_realloc(prec%thrvsz,prec%athresv,info) - call psb_bcast(ctxt,prec%athresv) - end if - call psb_bcast(ctxt,prec%athres) - - call psb_bcast(ctxt,prec%csizepp) - call psb_bcast(ctxt,prec%cmat) - call psb_bcast(ctxt,prec%csolve) - call psb_bcast(ctxt,prec%csbsolve) - call psb_bcast(ctxt,prec%cfill) - call psb_bcast(ctxt,prec%cthres) - call psb_bcast(ctxt,prec%cjswp) - - - end subroutine get_parms - -end program amg_d_pde3d diff --git a/tests/gpu/amg_d_pde3d_base_mod.f90 b/tests/gpu/amg_d_pde3d_base_mod.f90 deleted file mode 100644 index 76aad5cd..00000000 --- a/tests/gpu/amg_d_pde3d_base_mod.f90 +++ /dev/null @@ -1,65 +0,0 @@ -module amg_d_pde3d_base_mod - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_), save, private :: epsilon=done/80 -contains - subroutine pde_set_parm(dat) - real(psb_dpk_), intent(in) :: dat - epsilon = dat - end subroutine pde_set_parm - ! - ! functions parametrizing the differential equation - ! - function b1(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b1 - real(psb_dpk_), intent(in) :: x,y,z - b1=done/sqrt(3.0_psb_dpk_) - end function b1 - function b2(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b2 - real(psb_dpk_), intent(in) :: x,y,z - b2=done/sqrt(3.0_psb_dpk_) - end function b2 - function b3(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b3 - real(psb_dpk_), intent(in) :: x,y,z - b3=done/sqrt(3.0_psb_dpk_) - end function b3 - function c(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: c - real(psb_dpk_), intent(in) :: x,y,z - c=dzero - end function c - function a1(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a1 - real(psb_dpk_), intent(in) :: x,y,z - a1=epsilon - end function a1 - function a2(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a2 - real(psb_dpk_), intent(in) :: x,y,z - a2=epsilon - end function a2 - function a3(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a3 - real(psb_dpk_), intent(in) :: x,y,z - a3=epsilon - end function a3 - function g(x,y,z) - use psb_base_mod, only : psb_dpk_, done, dzero - real(psb_dpk_) :: g - real(psb_dpk_), intent(in) :: x,y,z - g = dzero - if (x == done) then - g = done - else if (x == dzero) then - g = done - end if - end function g -end module amg_d_pde3d_base_mod diff --git a/tests/gpu/amg_d_pde3d_exp_mod.f90 b/tests/gpu/amg_d_pde3d_exp_mod.f90 deleted file mode 100644 index fdbb2970..00000000 --- a/tests/gpu/amg_d_pde3d_exp_mod.f90 +++ /dev/null @@ -1,65 +0,0 @@ -module amg_d_pde3d_exp_mod - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_), save, private :: epsilon=done/160 -contains - subroutine pde_set_parm(dat) - real(psb_dpk_), intent(in) :: dat - epsilon = dat - end subroutine pde_set_parm - ! - ! functions parametrizing the differential equation - ! - function b1_exp(x,y,z) - use psb_base_mod, only : psb_dpk_, dzero - real(psb_dpk_) :: b1_exp - real(psb_dpk_), intent(in) :: x,y,z - b1_exp=dzero/sqrt(3.0_psb_dpk_) - end function b1_exp - function b2_exp(x,y,z) - use psb_base_mod, only : psb_dpk_, dzero - real(psb_dpk_) :: b2_exp - real(psb_dpk_), intent(in) :: x,y,z - b2_exp=dzero/sqrt(3.0_psb_dpk_) - end function b2_exp - function b3_exp(x,y,z) - use psb_base_mod, only : psb_dpk_, dzero - real(psb_dpk_) :: b3_exp - real(psb_dpk_), intent(in) :: x,y,z - b3_exp=dzero/sqrt(3.0_psb_dpk_) - end function b3_exp - function c_exp(x,y,z) - use psb_base_mod, only : psb_dpk_, dzero - real(psb_dpk_) :: c_exp - real(psb_dpk_), intent(in) :: x,y,z - c_exp=dzero - end function c_exp - function a1_exp(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a1_exp - real(psb_dpk_), intent(in) :: x,y,z - a1_exp=epsilon*exp(-(x+y+z)) - end function a1_exp - function a2_exp(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a2_exp - real(psb_dpk_), intent(in) :: x,y,z - a2_exp=epsilon*exp(-(x+y+z)) - end function a2_exp - function a3_exp(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a3_exp - real(psb_dpk_), intent(in) :: x,y,z - a3_exp=epsilon*exp(-(x+y+z)) - end function a3_exp - function g_exp(x,y,z) - use psb_base_mod, only : psb_dpk_, done, dzero - real(psb_dpk_) :: g_exp - real(psb_dpk_), intent(in) :: x,y,z - g_exp = dzero - if (x == done) then - g_exp = done - else if (x == dzero) then - g_exp = done - end if - end function g_exp -end module amg_d_pde3d_exp_mod diff --git a/tests/gpu/amg_d_pde3d_gauss_mod.f90 b/tests/gpu/amg_d_pde3d_gauss_mod.f90 deleted file mode 100644 index 3787c76a..00000000 --- a/tests/gpu/amg_d_pde3d_gauss_mod.f90 +++ /dev/null @@ -1,65 +0,0 @@ -module amg_d_pde3d_gauss_mod - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_), save, private :: epsilon=done/80 -contains - subroutine pde_set_parm(dat) - real(psb_dpk_), intent(in) :: dat - epsilon = dat - end subroutine pde_set_parm - ! - ! functions parametrizing the differential equation - ! - function b1_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b1_gauss - real(psb_dpk_), intent(in) :: x,y,z - b1_gauss=done/sqrt(3.0_psb_dpk_)-2*x*exp(-(x**2+y**2+z**2)) - end function b1_gauss - function b2_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b2_gauss - real(psb_dpk_), intent(in) :: x,y,z - b2_gauss=done/sqrt(3.0_psb_dpk_)-2*y*exp(-(x**2+y**2+z**2)) - end function b2_gauss - function b3_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_, done - real(psb_dpk_) :: b3_gauss - real(psb_dpk_), intent(in) :: x,y,z - b3_gauss=done/sqrt(3.0_psb_dpk_)-2*z*exp(-(x**2+y**2+z**2)) - end function b3_gauss - function c_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_, dzero - real(psb_dpk_) :: c_gauss - real(psb_dpk_), intent(in) :: x,y,z - c=dzero - end function c_gauss - function a1_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a1_gauss - real(psb_dpk_), intent(in) :: x,y,z - a1_gauss=epsilon*exp(-(x**2+y**2+z**2)) - end function a1_gauss - function a2_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a2_gauss - real(psb_dpk_), intent(in) :: x,y,z - a2_gauss=epsilon*exp(-(x**2+y**2+z**2)) - end function a2_gauss - function a3_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_ - real(psb_dpk_) :: a3_gauss - real(psb_dpk_), intent(in) :: x,y,z - a3_gauss=epsilon*exp(-(x**2+y**2+z**2)) - end function a3_gauss - function g_gauss(x,y,z) - use psb_base_mod, only : psb_dpk_, done, dzero - real(psb_dpk_) :: g_gauss - real(psb_dpk_), intent(in) :: x,y,z - g_gauss = dzero - if (x == done) then - g_gauss = done - else if (x == dzero) then - g_gauss = done - end if - end function g_gauss -end module amg_d_pde3d_gauss_mod diff --git a/tests/gpu/runs/amg_gpu_pde3d.inp b/tests/gpu/runs/amg_gpu_pde3d.inp deleted file mode 100644 index a819710f..00000000 --- a/tests/gpu/runs/amg_gpu_pde3d.inp +++ /dev/null @@ -1,55 +0,0 @@ -%%%%%%%%%%% General arguments % Lines starting with % are ignored. -CSR ! Storage format CSR COO JAD -0080 ! IDIM; domain size. Linear system size is IDIM**3 -CONST ! PDECOEFF: CONST, EXP, GAUSS Coefficients of the PDE -FCG ! Iterative method: BiCGSTAB BiCGSTABL BiCG CG CGS FCG GCR RGMRES -2 ! ISTOPC -00500 ! ITMAX -1 ! ITRACE -30 ! IRST (restart for RGMRES and BiCGSTABL) -1.d-6 ! EPS -%%%%%%%%%%% Main preconditioner choices %%%%%%%%%%%%%%%% -ML-VCYCLE-BJAC-D-BJAC ! Longer descriptive name for preconditioner (up to 20 chars) -ML ! Preconditioner type: NONE JACOBI GS FBGS BJAC AS ML -%%%%%%%%%%% First smoother (for all levels but coarsest) %%%%%%%%%%%%%%%% -BJAC ! Smoother type JACOBI FBGS GS BWGS BJAC AS. For 1-level, repeats previous. -1 ! Number of sweeps for smoother -0 ! Number of overlap layers for AS preconditioner -HALO ! AS restriction operator: NONE HALO -NONE ! AS prolongation operator: NONE SUM AVG -INVK ! Subdomain solver for BJAC/AS: JACOBI GS BGS ILU ILUT MILU MUMPS SLU UMF -LLK ! AINV variant -0 ! Fill level P for ILU(P) and ILU(T,P) -1 ! Inverse Fill level P for INVK -1.d-4 ! Threshold T for ILU(T,P) -%%%%%%%%%%% Second smoother, always ignored for non-ML %%%%%%%%%%%%%%%% -NONE ! Second (post) smoother, ignored if NONE -1 ! Number of sweeps for (post) smoother -0 ! Number of overlap layers for AS preconditioner -HALO ! AS restriction operator: NONE HALO -NONE ! AS prolongation operator: NONE SUM AVG -ILU ! Subdomain solver for BJAC/AS: JACOBI GS BGS ILU ILUT MILU MUMPS SLU UMF -LLK ! AINV variant -0 ! Fill level P for ILU(P) and ILU(T,P) -8 ! Inverse Fill level P for INVK -1.d-4 ! Threshold T for ILU(T,P) -%%%%%%%%%%% Multilevel parameters %%%%%%%%%%%%%%%% -VCYCLE ! Type of multilevel CYCLE: VCYCLE WCYCLE KCYCLE MULT ADD -1 ! Number of outer sweeps for ML --3 ! Max Number of levels in a multilevel preconditioner; if <0, lib default --3 ! Target coarse matrix size per process; if <0, lib default -SMOOTHED ! Type of aggregation: SMOOTHED UNSMOOTHED -DEC ! Parallel aggregation: DEC, SYMDEC -NATURAL ! Ordering of aggregation NATURAL DEGREE -NOFILTER ! Filtering of matrix: FILTER NOFILTER --1.5 ! Coarsening ratio, if < 0 use library default --2 ! Number of thresholds in vector, next line ignored if <= 0 -0.05 0.025 ! Thresholds --0.0100d0 ! Smoothed aggregation threshold, ignored if < 0 -%%%%%%%%%%% Coarse level solver %%%%%%%%%%%%%%%% -BJAC ! Coarsest-level solver: MUMPS UMF SLU SLUDIST JACOBI GS BJAC -INVK ! Coarsest-level subsolver for BJAC: ILU ILUT MILU UMF MUMPS SLU -DIST ! Coarsest-level matrix distribution: DIST REPL -1 ! Coarsest-level fillin P for ILU(P) and ILU(T,P) -1.d-4 ! Coarsest-level threshold T for ILU(T,P) -1 ! Number of sweeps for JACOBI/GS/BJAC coarsest-level solver From b5c301eb05e2a3ac7ef8d9970e349e6e0cee5417 Mon Sep 17 00:00:00 2001 From: Salvatore Filippone Date: Wed, 7 Apr 2021 13:10:40 +0200 Subject: [PATCH 6/6] New GPU example --- docs/amg4psblas_1.0-guide.pdf | Bin 1829608 -> 1830328 bytes docs/html/userhtmlsu10.html | 38 +- docs/html/userhtmlsu11.html | 62 +-- docs/html/userhtmlsu12.html | 64 +-- docs/html/userhtmlsu13.html | 62 +-- docs/html/userhtmlsu14.html | 22 +- docs/html/userhtmlsu15.html | 46 +-- docs/html/userhtmlsu16.html | 114 +++--- docs/html/userhtmlsu7.html | 46 ++- docs/html/userhtmlsu9.html | 706 +++++++++++++++++----------------- docs/src/gettingstarted.tex | 19 +- 11 files changed, 594 insertions(+), 585 deletions(-) diff --git a/docs/amg4psblas_1.0-guide.pdf b/docs/amg4psblas_1.0-guide.pdf index 16f3f3d6995c772faa917b7552eb89eb5e4fe8e4..bba1106850f96b27612f9917df3c9d114f9f73db 100644 GIT binary patch delta 3565 zcmai0eQaCR74LKFq&THb8{gQ;Ym*oH!3!m?_w#*}s5DIzD0FKVis;5F<|b}p6xXTk zgmn^_YOXSMt5zi1!ynVwKCUU;x-RAbeJ)kW2KwV~QrU0l7Uny5BamsGEQjMF)6wBSr&J9nFXNtA0 z`q8z;ec9SS3Yl-$7sF$PLMgX@w0l*}TcK{4pFmgRg@bhy=sTb4B%VW+_+(hOZifTC zNdolRbdxWc>jd2Yq{c|5oT+&!m5bfJJvm&S#0Y|`5$r1z!T3127j}Lz8DA-da<~^* z!})~Hg=E~3idoDPj)1;K;=wBx*|fq#C2s8+!Ng1y3!}wM9u8efCYv+)(R)Vfx4vg| zViHcp$PJL|B4hBK<)jhrT|-P*`PbyV;hyWrJ_G)_hPaU=+_07$H%_cUorxpqG`w^l ziHCFR$gEM}|E-F&gfAWL%UVrJnDSDTNNVRZ-&ol_?P~Qu8-#}Iv4IXzCbD^-F9BBxA$CU!Qnn(xGOUc0~oyRtiHaKx9 z*#V2+O)6OSx8!bcddOq2*&)}!*&Z@_;%NFnc*-HaF`#f0X$%ija=?I&FSRX!htDKe zz)?XSg6|zmUJoldAq(9=L8hx^2$trPug5)oj}t>}Rv1WP7>$y0jGjTCB1hsLb38Bn zts-~UN*l%Ukw=!mkVn3*3*QOPI9O@JC*L)|YQi-Jz6id(iKO7On~77?nK+t`ht5XQ zXTSp#Bb>U49FM`&rKA$sl8itHMJ%2aD_0y~{y|0A4)@d5JD z4#N)@9w#%gii^`hh2?v`Kn6i0U>-8c3aA@c3jZ0`5{``L;v`Wk5a>!$-wLG55|X-B z;9~hDK7ptGF;ot4r2_nSwQgWBN}04I=g2X%mAGP*uM4!kLRLyV24RX(6`(VWf>BS= z#9B*G4}SItSqa-8CF$^`N65C$IA@OVVd-hY8aQ`6CBlKzq#@Q2AYyt!xbt~(a{VG9 zsG7&w6`?;%E;m#xM99GXRI|G11aYU<*fmUKc7!)LNtt41d^|f|o4-=FT-HDr4dZ>e z@>q?_XZA-R$L!(yT$XOs5^mS$FnJY@xLw$%2Mk`m zjKKL1GnEphgQapduPv#)7m+G*Lgx|h_Ts_u!csM1bZViWCl<^cXTYGt%`e;o=ZU9AV)J92S|5aWb#a{0 z%(YyGpCfv-U2GBDK?a9XA8lb7&?*QlR|V)AU(X0xXoY&NudVPS!K1j1cd|TAb-)rC zj7(8#c^ZXO1bo-2B9O%R62vG{l z=4&97m^WYPyD*K48tEiQ29Z{wQ{P8F_@+Xflr*7O1yPXV(E{TPG_EPkX~48cF0FuT z48@!Vl4C9z;-SKtD2^cNptlXK>Vg>4JY!Jp=^go}^D2dL0`s9$@S)|Z>^&}6C6cE@|r zbJ0x2iqFE}MRVKG;nk6jFxs9?Mw~R-Ui~oQl+pH{9dWMF-f}SFJfppEPsI5~`@v%o z7Z~lYY>c>#hAF=W=PzP#KlwXcxoC!sXYN{Hm~T7?FJ3aOCj1uViM4A7b_^!|10!mb A>Hq)$ delta 2678 zcmah~ZERI%9p|}l@1?h;PqpvIRNb>bGm%^fB>*!IQEkWGUxfe;uB3I&7`gPI8Q_guF5(KP4Z z-}C(6-=2Hu-kUzu8&R%xhAuQXPQ zFZ0talS~pOB$n~T>1>bZGubeR6H7rWe=}ErHaK)Qcc(QS{8@HK zarDV@Yx+hfzrafN!^B0X0;M8fsW>o+pzqJ;X%1BgwWPIo@dG&_0-dO|p^HyvStEi_ z(EM)xM9$hkC`+&R@V4}C-TYUKdaoAiX>Y(cQrG*%8ftsGTu;C3;C=LN9WPTjR-BY> zTfpCD%%q9nUt;uw3w$!If2Q0@Ust?oVyqI{FspGDT~~aJu5E0ttxHr8`8L#W718e{ zC%XC9a(y96!ob89ZA!0dK8fX390!q-^e=m-*h5Wl2dcf)8YEpWB5nsw^UN2w# zKQrkQz5E51Q&C`rriDxRk(^Y4hzzlQ{^J~GBN9=%zn}kw(N}ht>!<1{Fe*mzS8wvy z&Rb4@94R&Bkzb5SOivH+TPvAOPd&@u&kUQy*P-%b8%qGpXd)s2<0oMh`v(7obQ}p_ z4F)pw69uBf*iUp67BnU~i9mE{{lq$Z0>V)DffKO6NqiB8LirlDL?|Gsg$9!F2WSAb zh8y@6p-Es&SRkvDbp1a5cw1J)0h)915Er>n1XA0y?Ny%3WMhfJO1lp8pLR`@aY(Pv zZEB%+PZvAV#!-GPM>ntWHTg)&Al8CDd!6@wd^m7}w>~$mTsu{23?jn#Nb2}?{uG`1 zJD);B*#yec-_b=H+cHWji#~R zFhM_hv2`J336F%s61_GN;~=}7tt(+_~H5OM4)6Z%S4Zxldc3L5oSDuKEM%Y;z3 z0szKzWPKG0fg9~Bt58@AgXo>FHJTJE6j-B*G7N`-a8h_hw8fPPWkFlq7=x3ADsu3( zYrjR&wTAiF3CG$up$j@T00>57e2kEemH0*)2xC2ra$fNnqijK=QU)sy7N#fC+8@?< z&Wu&?p`jMT2x9P~P{TDk9f`zwRqk1Ih5%3*=h~07(@M}9lfX3Ck#$-|`Vk&y0ENAU zF@p)V62w)>y2wlz`qs^<8O9)5H_+OEXE4#o+_%;xjSAQ}QU!yS#G%VRaR6mDP>6*@ z9H??EF~kN6vD*U;^RYph3`90~T>}y~`3B>T zC4)tnPoY(=6a7DixW3|eU<*33#IgmNda4d7NI#1(oEU~R$RfMrs!gbBbhq3cFE zuG?rMV<2Pt5SDcN=YDbwghd!T=SuhjcFFJ}}-^xpBtR@!>Jv3BJ4;S#FvW_4}MUk z!gGK3Yu?N|FkSzeH|+QK4fI9*ef?V61aE?l%(5j)NVOz3%LZiFzjTqm@c)UVvrl`S zxy&b~u8J;`Na%3iJV$*5+MfjL=g7#eWIy0ZVMZdo2 z&GI&Wrz$I)m~z`T -