diff --git a/doc/specs/stdlib_linalg_iterative_solvers.md b/doc/specs/stdlib_linalg_iterative_solvers.md new file mode 100644 index 000000000..c262efcdb --- /dev/null +++ b/doc/specs/stdlib_linalg_iterative_solvers.md @@ -0,0 +1,249 @@ +--- +title: linalg_iterative_solvers +--- + +# The `stdlib_linalg_iterative_solvers` module + +[TOC] + +## Introduction + +The `stdlib_linalg_iterative_solvers` module provides base implementations for known iterative solver methods. Each method is exposed with two procedure flavors: + +* A `solve__kernel` which holds the method's base implementation. The linear system argument is defined through a `linop` derived type which enables extending the method for implicit or unknown (by `stdlib`) matrices or to complex scenarios involving distributed parallelism for which the user shall extend the `inner_product` and/or matrix-vector product to account for parallel syncrhonization. + +* A `solve_` which proposes an off-the-shelf ready to use interface for `dense` and `CSR__type` matrices for all `real` kinds. + + +### The `linop` derived type + +The `linop__type` derive type is an auxiliary class enabling to abstract the definition of the linear system and the actual implementation of the solvers. + +#### Type-bound procedures + +The following type-bound procedures pointer enable customization of the solver: + +##### `apply` + +Proxy procedure for the matrix-vector product $y = alpha * M * x + beta * y$. + +#### Syntax + +`call ` [[stdlib_iterative_solvers(module):apply(interface)]] ` (x,y,alpha,beta)` + +###### Class + +Subroutine + +###### Argument(s) + +`x`: 1-D array of `real()`. This argument is `intent(in)`. + +`y`: 1-D array of `real()`. This argument is `intent(inout)`. + +`alpha`: scalar of `real()`. This argument is `intent(in)`. + +`beta`: scalar of `real()`. This argument is `intent(in)`. + +##### `inner_product` + +Proxy procedure for the `dot_product`. + +#### Syntax + +`res = ` [[stdlib_iterative_solvers(module):inner_product(interface)]] ` (x,y)` + +###### Class + +Function + +###### Argument(s) + +`x`: 1-D array of `real()`. This argument is `intent(in)`. + +`y`: 1-D array of `real()`. This argument is `intent(in)`. + +###### Output value or Result value + +The output is a scalar of `type` and `kind` same as to that of `x` and `y`. + + +### The `solver_workspace` derived type + +The `solver_workspace__type` derive type is an auxiliary class enabling to hold the data associated to the working arrays needed by the solvers to operate. + +#### Type-bound procedures + +- `callback`: null pointer procedure enabling to pass a callback at each iteration to check on the solvers status. + +##### Class + +Subroutine + +##### Argument(s) + +`x`: 1-D array of `real()` type with the current state of the solution vector. This argument is `intent(in)` as it should not be modified by the callback. + +`norm_sq`: scalar of `real()` type representing the squared norm of the residual at the current iteration. This argument is `intent(in)`. + +`iter`: scalar of `integer` type giving the current iteration counter. This argument is `intent(in)`. + + +### `solve_cg_kernel` subroutine + +#### Description + +Implements the Conjugate Gradient (CG) method for solving the linear system \( Ax = b \), where \( A \) is a symmetric positive-definite linear operator defined via the `linop` type. This is the core implementation, allowing flexibility for custom matrix types or parallel environments. + +#### Syntax + +`call ` [[stdlib_iterative_solvers(module):solve_cg_kernel(interface)]] ` (A, b, x, tol, maxiter, workspace)` + +#### Status + +Experimental + +#### Class + +Subroutine + +#### Argument(s) + +`A`: `class(linop__type)` defining the linear operator. This argument is `intent(in)`. + +`b`: 1-D array of `real()` defining the loading conditions of the linear system. This argument is `intent(in)`. + +`x`: 1-D array of `real()` which serves as the input initial guess and the output solution. This argument is `intent(inout)`. + +`tol`: scalar of type `real()` specifying the requested tolerance with respect to the relative residual vector norm. This argument is `intent(in)`. + +`maxiter`: scalar of type `integer` defining the maximum allowed number of iterations. This argument is `intent(in)`. + +`workspace`: `type(solver_workspace__type)` holding the work temporal array for the solver. This argument is `intent(inout)`. + + +### `solve_cg` subroutine + +#### Description + +Provides a user-friendly interface to the CG method for solving \( Ax = b \), supporting `dense` and `CSR__type` matrices. It handles workspace allocation and optional parameters for customization. + +#### Syntax + +`call ` [[stdlib_iterative_solvers(module):solve_cg(interface)]] ` (A, b, x [, di, tol, maxiter, restart, workspace])` + +#### Status + +Experimental + +#### Class + +Subroutine + +#### Argument(s) + +`A`: `dense` or `CSR__type` matrix defining the linear system. This argument is `intent(in)`. + +`b`: 1-D array of `real()` defining the loading conditions of the linear system. This argument is `intent(in)`. + +`x`: 1-D array of `real()` which serves as the input initial guess and the output solution. This argument is `intent(inout)`. + +`di` (optional): 1-D mask array of type `logical(1)` defining the degrees of freedom subject to dirichlet boundary conditions. The actual boundary conditions values should be stored in the `b` load array. This argument is `intent(in)`. + +`tol` (optional): scalar of type `real()` specifying the requested tolerance with respect to the relative residual vector norm. If no value is given, a default value of `1.e-4` is set. This argument is `intent(in)`. + +`maxiter` (optional): scalar of type `integer` defining the maximum allowed number of iterations. If no value is given, a default of `N` is set, where `N = size(b)`. This argument is `intent(in)`. + +`workspace` (optional): `type(solver_workspace__type)` holding the work temporal array for the solver. If the user passes its own `workspace`, then internally a pointer is set to it, otherwise, memory will be internally allocated and deallocated before exiting the procedure. This argument is `intent(inout)`. + +#### Example + +```fortran +{!example/linalg/example_solve_cg.f90!} +``` + + +### `solve_pcg_kernel` subroutine + +#### Description + +Implements the Preconditioned Conjugate Gradient (PCG) method for solving the linear system \( Ax = b \), where \( A \) is a symmetric positive-definite linear operator defined via the `linop` type. This is the core implementation, allowing flexibility for custom matrix types or parallel environments. + +#### Syntax + +`call ` [[stdlib_iterative_solvers(module):solve_cg_kernel(interface)]] ` (A, M, b, x, tol, maxiter, workspace)` + +#### Status + +Experimental + +#### Class + +Subroutine + +#### Argument(s) + +`A`: `class(linop__type)` defining the linear operator. This argument is `intent(in)`. + +`M`: `class(linop__type)` defining the preconditioner linear operator. This argument is `intent(in)`. + +`b`: 1-D array of `real()` defining the loading conditions of the linear system. This argument is `intent(in)`. + +`x`: 1-D array of `real()` which serves as the input initial guess and the output solution. This argument is `intent(inout)`. + +`tol`: scalar of type `real()` specifying the requested tolerance with respect to the relative residual vector norm. This argument is `intent(in)`. + +`maxiter`: scalar of type `integer` defining the maximum allowed number of iterations. This argument is `intent(in)`. + +`workspace`: `type(solver_workspace__type)` holding the work temporal array for the solver. This argument is `intent(inout)`. + +#### Example + +```fortran +{!example/linalg/example_solve_custom.f90!} +``` + + +### `solve_pcg` subroutine + +#### Description + +Provides a user-friendly interface to the PCG method for solving \( Ax = b \), supporting `dense` and `CSR__type` matrices. It supports optional preconditioners and handles workspace allocation. + +#### Syntax + +`call ` [[stdlib_iterative_solvers(module):solve_pcg(interface)]] ` (A, b, x [, di, tol, maxiter, restart, precond, M, workspace])` + +#### Status + +Experimental + +#### Class + +Subroutine + +#### Argument(s) + +`A`: `dense` or `CSR__type` matrix defining the linear system. This argument is `intent(in)`. + +`b`: 1-D array of `real()` defining the loading conditions of the linear system. This argument is `intent(in)`. + +`x`: 1-D array of `real()` which serves as the input initial guess and the output solution. This argument is `intent(inout)`. + +`di` (optional): 1-D mask array of type `logical(1)` defining the degrees of freedom subject to dirichlet boundary conditions. The actual boundary conditions values should be stored in the `b` load array. This argument is `intent(in)`. + +`tol` (optional): scalar of type `real()` specifying the requested tolerance with respect to the relative residual vector norm. If no value is given, a default value of `1.e-4` is set. This argument is `intent(in)`. + +`maxiter` (optional): scalar of type `integer` defining the maximum allowed number of iterations. If no value is given, a default of `N` is set, where `N = size(b)`. This argument is `intent(in)`. + +`precond` (optional): scalar of type `integer` enabling to switch among the default preconditioners available. If no value is given, no preconditionning will be applied. This argument is `intent(in)`. + +`M` (optional): `class(linop__type)` defining a custom preconditioner linear operator. If given, `precond` will have no effect, a pointer is set to this custom preconditioner. + +`workspace` (optional): `type(solver_workspace__type)` holding the work temporal array for the solver. If the user passes its own `workspace`, then internally a pointer is set to it, otherwise, memory will be internally allocated and deallocated before exiting the procedure. This argument is `intent(inout)`. + +#### Example + +```fortran +{!example/linalg/example_solve_pcg.f90!} +``` \ No newline at end of file diff --git a/example/linalg/CMakeLists.txt b/example/linalg/CMakeLists.txt index 10f982a02..2aa9ca2e8 100644 --- a/example/linalg/CMakeLists.txt +++ b/example/linalg/CMakeLists.txt @@ -41,6 +41,9 @@ ADD_EXAMPLE(get_norm) ADD_EXAMPLE(solve1) ADD_EXAMPLE(solve2) ADD_EXAMPLE(solve3) +ADD_EXAMPLE(solve_cg) +ADD_EXAMPLE(solve_pcg) +ADD_EXAMPLE(solve_custom) ADD_EXAMPLE(sparse_from_ijv) ADD_EXAMPLE(sparse_data_accessors) ADD_EXAMPLE(sparse_spmv) diff --git a/example/linalg/example_solve_cg.f90 b/example/linalg/example_solve_cg.f90 new file mode 100644 index 000000000..b4e7058e3 --- /dev/null +++ b/example/linalg/example_solve_cg.f90 @@ -0,0 +1,17 @@ +program example_solve_cg + use stdlib_kinds, only: dp + use stdlib_linalg_iterative_solvers, only: solve_cg + + real(dp) :: matrix(2,2) + real(dp) :: x(2), load(2) + + matrix = reshape( [4, 1,& + 1, 3] , [2,2]) + + x = dble( [2,1] ) !> initial guess + load = dble( [1,2] ) !> load vector + + call solve_cg(matrix, load, x, restart=.false.) + print *, x !> solution: [0.0909, 0.6364] + +end program \ No newline at end of file diff --git a/example/linalg/example_solve_custom.f90 b/example/linalg/example_solve_custom.f90 new file mode 100644 index 000000000..fb6f2f16c --- /dev/null +++ b/example/linalg/example_solve_custom.f90 @@ -0,0 +1,132 @@ +module custom_solver + use stdlib_kinds, only: dp + use stdlib_sparse + use stdlib_linalg_iterative_solvers, only: linop_dp_type, & + solver_workspace_dp_type, & + solve_pcg_kernel, & + stdlib_size_wksp_pcg + implicit none +contains + subroutine solve_pcg_custom(A,b,x,di,tol,maxiter,restart,workspace) + type(CSR_dp_type), intent(in) :: A + real(dp), intent(in) :: b(:) + real(dp), intent(inout) :: x(:) + real(dp), intent(in), optional :: tol + logical(1), intent(in), optional, target :: di(:) + integer, intent(in), optional :: maxiter + logical, intent(in), optional :: restart + type(solver_workspace_dp_type), optional, intent(inout), target :: workspace + !------------------------- + type(linop_dp_type) :: op + type(linop_dp_type) :: M + type(solver_workspace_dp_type), pointer :: workspace_ + integer :: n, maxiter_ + real(dp) :: tol_ + logical :: restart_ + logical(1), pointer :: di_(:) + real(dp), allocatable :: diagonal(:) + real(dp) :: norm_sq0 + !------------------------- + n = size(b) + maxiter_ = n; if(present(maxiter)) maxiter_ = maxiter + restart_ = .true.; if(present(restart)) restart_ = restart + tol_ = 1.e-4_dp; if(present(tol)) tol_ = tol + norm_sq0 = 0.d0 + !------------------------- + ! internal memory setup + op%apply => my_apply + op%inner_product => my_dot + M%apply => my_jacobi_preconditioner + if(present(di))then + di_ => di + else + allocate(di_(n),source=.false._1) + end if + + if(present(workspace)) then + workspace_ => workspace + else + allocate( workspace_ ) + end if + if(.not.allocated(workspace_%tmp)) allocate( workspace_%tmp(n,stdlib_size_wksp_pcg) , source = 0.d0 ) + workspace_%callback => my_logger + !------------------------- + ! Jacobi preconditioner factorization + call diag(A,diagonal) + where(abs(diagonal)>epsilon(0.d0)) diagonal = 1._dp/diagonal + !------------------------- + ! main call to the solver + call solve_pcg_kernel(op,M,b,x,tol_,maxiter_,workspace_) + + !------------------------- + ! internal memory cleanup + if(.not.present(di)) deallocate(di_) + di_ => null() + + if(.not.present(workspace)) then + deallocate( workspace_%tmp ) + deallocate( workspace_ ) + end if + workspace_ => null() + contains + + subroutine my_apply(x,y,alpha,beta) + real(dp), intent(in) :: x(:) + real(dp), intent(inout) :: y(:) + real(dp), intent(in) :: alpha + real(dp), intent(in) :: beta + call spmv( A , x, y , alpha, beta ) + y = merge( 0._dp, y, di_ ) + end subroutine + subroutine my_jacobi_preconditioner(x,y,alpha,beta) + real(dp), intent(in) :: x(:) + real(dp), intent(inout) :: y(:) + real(dp), intent(in) :: alpha + real(dp), intent(in) :: beta + y = merge( 0._dp, diagonal * x , di_ ) + end subroutine + pure real(dp) function my_dot(x,y) result(r) + real(dp), intent(in) :: x(:) + real(dp), intent(in) :: y(:) + r = dot_product(x,y) + end function + subroutine my_logger(x,norm_sq,iter) + real(dp), intent(in) :: x(:) + real(dp), intent(in) :: norm_sq + integer, intent(in) :: iter + if(iter == 0) norm_sq0 = norm_sq + print *, "Iteration: ", iter, " Residual: ", sqrt(norm_sq), " Relative: ", sqrt(norm_sq)/sqrt(norm_sq0) + end subroutine + end subroutine + +end module custom_solver + + +program example_solve_custom + use custom_solver + implicit none + + type(CSR_dp_type) :: laplacian_csr + type(COO_dp_type) :: COO + real(dp) :: laplacian(5,5) + real(dp) :: x(5), load(5) + logical(1) :: dirichlet(5) + + laplacian = reshape( [1, -1, 0, 0, 0,& + -1, 2, -1, 0, 0,& + 0, -1, 2, -1, 0,& + 0, 0, -1, 2, -1,& + 0, 0, 0, -1, 1] , [5,5]) + call dense2coo(laplacian,COO) + call coo2csr(COO,laplacian_csr) + + x = 0._dp + load = dble( [0,0,5,0,0] ) + + dirichlet = .false._1 + dirichlet([1,5]) = .true._1 + + call solve_pcg_custom(laplacian_csr, load, x, tol=1.d-6, di=dirichlet) + print *, x !> solution: [0.0, 2.5, 5.0, 2.5, 0.0] + +end program example_solve_custom \ No newline at end of file diff --git a/example/linalg/example_solve_pcg.f90 b/example/linalg/example_solve_pcg.f90 new file mode 100644 index 000000000..289fc0f05 --- /dev/null +++ b/example/linalg/example_solve_pcg.f90 @@ -0,0 +1,32 @@ +program example_solve_pcg + use stdlib_kinds, only: dp + use stdlib_sparse + use stdlib_linalg_iterative_solvers, only: solve_pcg + + type(CSR_dp_type) :: laplacian_csr + type(COO_dp_type) :: COO + real(dp) :: laplacian(5,5) + real(dp) :: x(5), load(5) + logical(1) :: dirichlet(5) + + laplacian = reshape( [1, -1, 0, 0, 0,& + -1, 2, -1, 0, 0,& + 0, -1, 2, -1, 0,& + 0, 0, -1, 2, -1,& + 0, 0, 0, -1, 1] , [5,5]) + call dense2coo(laplacian,COO) + call coo2csr(COO,laplacian_csr) + + x = 0._dp + load = dble( [0,0,5,0,0] ) + + dirichlet = .false._1 + dirichlet([1,5]) = .true._1 + + call solve_pcg(laplacian, load, x, tol=1.d-6, di=dirichlet) + print *, x !> solution: [0.0, 2.5, 5.0, 2.5, 0.0] + x = 0._dp + + call solve_pcg(laplacian_csr, load, x, tol=1.d-6, di=dirichlet) + print *, x !> solution: [0.0, 2.5, 5.0, 2.5, 0.0] +end program \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3cd99120..54a17721e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -94,6 +94,9 @@ set(cppFiles stdlib_linalg_blas.fypp stdlib_linalg_lapack.fypp + stdlib_linalg_iterative_solvers.fypp + stdlib_linalg_iterative_solvers_cg.fypp + stdlib_linalg_iterative_solvers_pcg.fypp ) add_subdirectory(blas) diff --git a/src/stdlib_linalg_iterative_solvers.fypp b/src/stdlib_linalg_iterative_solvers.fypp new file mode 100644 index 000000000..51cc05d45 --- /dev/null +++ b/src/stdlib_linalg_iterative_solvers.fypp @@ -0,0 +1,166 @@ +#:include "common.fypp" +#:set R_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES, REAL_SUFFIX)) +#:set C_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES, CMPLX_SUFFIX)) +#:set MATRIX_TYPES = ["dense", "CSR"] +#:set RANKS = range(1, 2+1) +!! The `stdlib_linalg_iterative_solvers` module provides interfaces for iterative solvers. +!! +module stdlib_linalg_iterative_solvers + use stdlib_kinds + use stdlib_sparse + implicit none + private + + !! workspace sizes: defined by the number of vectors used by the iterative solver. + enum, bind(c) + enumerator :: stdlib_size_wksp_cg = 3 + enumerator :: stdlib_size_wksp_pcg = 4 + end enum + public :: stdlib_size_wksp_cg, stdlib_size_wksp_pcg + + !! version: experimental + !! + !! linop type holding the linear operator and its associated methods. + !! The `linop` type is used to define the linear operator for the iterative solvers. + #:for k, t, s in R_KINDS_TYPES + type, public :: linop_${s}$_type + procedure(vector_sub_${s}$), nopass, pointer :: apply => null() + procedure(reduction_sub_${s}$), nopass, pointer :: inner_product => default_dot_${s}$ + end type + #:endfor + + !! version: experimental + !! + !! solver_workspace type holding temporal array data for the iterative solvers. + #:for k, t, s in R_KINDS_TYPES + type, public :: solver_workspace_${s}$_type + ${t}$, allocatable :: tmp(:,:) + procedure(logger_sub_${s}$), pointer, nopass :: callback => null() + end type + + #:endfor + + abstract interface + #:for k, t, s in R_KINDS_TYPES + subroutine vector_sub_${s}$(x,y,alpha,beta) + import :: ${k}$ + ${t}$, intent(in) :: x(:) + ${t}$, intent(inout) :: y(:) + ${t}$, intent(in) :: alpha + ${t}$, intent(in) :: beta + end subroutine + pure ${t}$ function reduction_sub_${s}$(x,y) result(r) + import :: ${k}$ + ${t}$, intent(in) :: x(:) + ${t}$, intent(in) :: y(:) + end function + subroutine logger_sub_${s}$(x,norm_sq,iter) + import :: ${k}$ + ${t}$, intent(in) :: x(:) + ${t}$, intent(in) :: norm_sq + integer, intent(in) :: iter + end subroutine + #:endfor + end interface + + !! version: experimental + !! + !! solve_cg_kernel interface for the conjugate gradient method. + !! [Specifications](../page/specs/stdlib_linalg_iterative_solvers.html#solve_cg_kernel) + interface solve_cg_kernel + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_cg_kernel_${s}$(A,b,x,tol,maxiter,workspace) + class(linop_${s}$_type), intent(in) :: A !! linear operator + ${t}$, intent(in) :: b(:) !! right-hand side vector + ${t}$, intent(inout) :: x(:) !! solution vector and initial guess + ${t}$, intent(in) :: tol !! tolerance for convergence + integer, intent(in) :: maxiter !! maximum number of iterations + type(solver_workspace_${s}$_type), intent(inout) :: workspace !! workspace for the solver + end subroutine + #:endfor + end interface + public :: solve_cg_kernel + + interface solve_cg + #:for matrix in MATRIX_TYPES + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_cg_${matrix}$_${s}$(A,b,x,di,tol,maxiter,restart,workspace) + !! linear operator matrix + #:if matrix == "dense" + ${t}$, intent(in) :: A(:,:) + #:else + type(${matrix}$_${s}$_type), intent(in) :: A + #:endif + ${t}$, intent(in) :: b(:) !! right-hand side vector + ${t}$, intent(inout) :: x(:) !! solution vector and initial guess + ${t}$, intent(in), optional :: tol !! tolerance for convergence + logical(1), intent(in), optional, target :: di(:) !! dirichlet conditions mask + integer, intent(in), optional :: maxiter !! maximum number of iterations + logical, intent(in), optional :: restart !! restart flag + type(solver_workspace_${s}$_type), optional, intent(inout), target :: workspace !! workspace for the solver + end subroutine + #:endfor + #:endfor + end interface + public :: solve_cg + + !! version: experimental + !! + !! solve_pcg_kernel interface for the preconditionned conjugate gradient method. + !! [Specifications](../page/specs/stdlib_linalg_iterative_solvers.html#solve_pcg_kernel) + interface solve_pcg_kernel + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_pcg_kernel_${s}$(A,M,b,x,tol,maxiter,workspace) + class(linop_${s}$_type), intent(in) :: A !! linear operator + class(linop_${s}$_type), intent(in) :: M !! preconditioner linear operator + ${t}$, intent(in) :: b(:) !! right-hand side vector + ${t}$, intent(inout) :: x(:) !! solution vector and initial guess + ${t}$, intent(in) :: tol !! tolerance for convergence + integer, intent(in) :: maxiter !! maximum number of iterations + type(solver_workspace_${s}$_type), intent(inout) :: workspace !! workspace for the solver + end subroutine + #:endfor + end interface + public :: solve_pcg_kernel + + interface solve_pcg + #:for matrix in MATRIX_TYPES + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_pcg_${matrix}$_${s}$(A,b,x,di,tol,maxiter,restart,precond,M,workspace) + !! linear operator matrix + #:if matrix == "dense" + ${t}$, intent(in) :: A(:,:) + #:else + type(${matrix}$_${s}$_type), intent(in) :: A + #:endif + ${t}$, intent(in) :: b(:) !! right-hand side vector + ${t}$, intent(inout) :: x(:) !! solution vector and initial guess + ${t}$, intent(in), optional :: tol !! tolerance for convergence + logical(1), intent(in), optional, target :: di(:) !! dirichlet conditions mask + integer, intent(in), optional :: maxiter !! maximum number of iterations + logical, intent(in), optional :: restart !! restart flag + integer, intent(in), optional :: precond !! preconditioner method enumerator + class(linop_${s}$_type), optional , intent(in), target :: M !! preconditioner linear operator + type(solver_workspace_${s}$_type), optional, intent(inout), target :: workspace !! workspace for the solver + end subroutine + #:endfor + #:endfor + end interface + public :: solve_pcg + +contains + + !------------------------------------------------------------------ + ! defaults + !------------------------------------------------------------------ + #:for k, t, s in R_KINDS_TYPES + pure ${t}$ function default_dot_${s}$(x,y) result(r) + use stdlib_intrinsics, only: stdlib_dot_product + ${t}$, intent(in) :: x(:) + ${t}$, intent(in) :: y(:) + r = stdlib_dot_product(x,y) + end function + + #:endfor + +end module stdlib_linalg_iterative_solvers diff --git a/src/stdlib_linalg_iterative_solvers_cg.fypp b/src/stdlib_linalg_iterative_solvers_cg.fypp new file mode 100644 index 000000000..e28e94e61 --- /dev/null +++ b/src/stdlib_linalg_iterative_solvers_cg.fypp @@ -0,0 +1,146 @@ +#:include "common.fypp" +#:set R_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES, REAL_SUFFIX)) +#:set C_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES, CMPLX_SUFFIX)) +#:set MATRIX_TYPES = ["dense", "CSR"] +#:set RANKS = range(1, 2+1) + +submodule(stdlib_linalg_iterative_solvers) stdlib_linalg_iterative_cg + use stdlib_kinds + use stdlib_sparse + use stdlib_constants + use stdlib_linalg_iterative_solvers + implicit none + +contains + + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_cg_kernel_${s}$(A,b,x,tol,maxiter,workspace) + class(linop_${s}$_type), intent(in) :: A + ${t}$, intent(in) :: b(:), tol + ${t}$, intent(inout) :: x(:) + integer, intent(in) :: maxiter + type(solver_workspace_${s}$_type), intent(inout) :: workspace + !------------------------- + integer :: iter + ${t}$ :: norm_sq, norm_sq_old, norm_sq0 + ${t}$ :: alpha, beta, tolsq + !------------------------- + iter = 0 + associate( P => workspace%tmp(:,1), & + R => workspace%tmp(:,2), & + Ap => workspace%tmp(:,3)) + + norm_sq0 = A%inner_product(B, B) + if(associated(workspace%callback)) call workspace%callback(x, norm_sq0, iter) + + R = B + call A%apply(X, R, alpha= -one_${s}$, beta=one_${s}$) ! R = B - A*X + norm_sq = A%inner_product(R, R) + + P = R + + tolsq = tol*tol + beta = zero_${s}$ + if(associated(workspace%callback)) call workspace%callback(x, norm_sq, iter) + do while( norm_sq > tolsq * norm_sq0 .and. iter < maxiter) + call A%apply(P,Ap, alpha= one_${s}$, beta=zero_${s}$) ! Ap = A*P + + alpha = norm_sq / A%inner_product(P, Ap) + + X = X + alpha * P + R = R - alpha * Ap + + norm_sq_old = norm_sq + norm_sq = A%inner_product(R, R) + beta = norm_sq / norm_sq_old + + P = R + beta * P + + iter = iter + 1 + + if(associated(workspace%callback)) call workspace%callback(x, norm_sq, iter) + end do + end associate + end subroutine + #:endfor + + #:for matrix in MATRIX_TYPES + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_cg_${matrix}$_${s}$(A,b,x,di,tol,maxiter,restart,workspace) + #:if matrix == "dense" + ${t}$, intent(in) :: A(:,:) + #:else + type(${matrix}$_${s}$_type), intent(in) :: A + #:endif + ${t}$, intent(in) :: b(:) + ${t}$, intent(inout) :: x(:) + ${t}$, intent(in), optional :: tol + logical(1), intent(in), optional, target :: di(:) + integer, intent(in), optional :: maxiter + logical, intent(in), optional :: restart + type(solver_workspace_${s}$_type), optional, intent(inout), target :: workspace + !------------------------- + type(linop_${s}$_type) :: op + type(solver_workspace_${s}$_type), pointer :: workspace_ + integer :: n, maxiter_ + ${t}$ :: tol_ + logical :: restart_ + logical(1), pointer :: di_(:) + !------------------------- + n = size(b) + maxiter_ = n; if(present(maxiter)) maxiter_ = maxiter + restart_ = .true.; if(present(restart)) restart_ = restart + tol_ = 1.e-4_${s}$; if(present(tol)) tol_ = tol + + !------------------------- + ! internal memory setup + op%apply => default_matvec + ! op%inner_product => default_dot + if(present(di))then + di_ => di + else + allocate(di_(n),source=.false._1) + end if + + if(present(workspace)) then + workspace_ => workspace + else + allocate( workspace_ ) + end if + if(.not.allocated(workspace_%tmp)) allocate( workspace_%tmp(n,stdlib_size_wksp_cg), source = zero_${s}$ ) + !------------------------- + ! main call to the solver + if(restart_) x = zero_${s}$ + x = merge( b, x, di_ ) ! copy dirichlet load conditions encoded in B and indicated by di + call solve_cg_kernel(op,b,x,tol_,maxiter_,workspace_) + + !------------------------- + ! internal memory cleanup + if(.not.present(di)) deallocate(di_) + di_ => null() + + if(.not.present(workspace)) then + deallocate( workspace_%tmp ) + deallocate( workspace_ ) + end if + workspace_ => null() + contains + + subroutine default_matvec(x,y,alpha,beta) + ${t}$, intent(in) :: x(:) + ${t}$, intent(inout) :: y(:) + ${t}$, intent(in) :: alpha + ${t}$, intent(in) :: beta + #:if matrix == "dense" + y = alpha * matmul(A,x) + beta * y + #:else + call spmv( A , x, y , alpha, beta ) + #:endif + y = merge( zero_${s}$, y, di_ ) + end subroutine + end subroutine + + #:endfor + #:endfor + +end submodule stdlib_linalg_iterative_cg \ No newline at end of file diff --git a/src/stdlib_linalg_iterative_solvers_pcg.fypp b/src/stdlib_linalg_iterative_solvers_pcg.fypp new file mode 100644 index 000000000..efa462b51 --- /dev/null +++ b/src/stdlib_linalg_iterative_solvers_pcg.fypp @@ -0,0 +1,211 @@ +#:include "common.fypp" +#:set R_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES, REAL_SUFFIX)) +#:set C_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES, CMPLX_SUFFIX)) +#:set MATRIX_TYPES = ["dense", "CSR"] +#:set RANKS = range(1, 2+1) + +submodule(stdlib_linalg_iterative_solvers) stdlib_linalg_iterative_pcg + use stdlib_kinds + use stdlib_sparse + use stdlib_constants + use stdlib_linalg_iterative_solvers + implicit none + + enum, bind(c) + enumerator :: pc_none = 0 + enumerator :: pc_jacobi + end enum + +contains + + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_pcg_kernel_${s}$(A,M,b,x,tol,maxiter,workspace) + class(linop_${s}$_type), intent(in) :: A + class(linop_${s}$_type), intent(in) :: M + ${t}$, intent(in) :: b(:), tol + ${t}$, intent(inout) :: x(:) + integer, intent(in) :: maxiter + type(solver_workspace_${s}$_type), intent(inout) :: workspace + !------------------------- + integer :: iter + ${t}$ :: norm_sq, norm_sq0, norm_sq_old + ${t}$ :: zr1, zr2, zv2, alpha, beta, tolsq + !------------------------- + iter = 0 + associate( R => workspace%tmp(:,1), & + S => workspace%tmp(:,2), & + P => workspace%tmp(:,3), & + Q => workspace%tmp(:,4)) + norm_sq = A%inner_product( b, b ) + norm_sq0 = norm_sq + if(associated(workspace%callback)) call workspace%callback(x, norm_sq, iter) + + if ( norm_sq0 > zero_${s}$ ) then + + R = B + call A%apply(X, R, alpha= -one_${s}$, beta=one_${s}$) ! R = B - A*X + + call M%apply(R,P, alpha= one_${s}$, beta=zero_${s}$) ! P = M^{-1}*R + + tolsq = tol*tol + + zr1 = zero_${s}$ + zr2 = one_${s}$ + do while ( (iter < maxiter) .AND. (norm_sq > tolsq * norm_sq0) ) + + call M%apply(R,S, alpha= one_${s}$, beta=zero_${s}$) ! S = M^{-1}*R + zr2 = A%inner_product( R, S ) + + if (iter>0) then + beta = zr2 / zr1 + P = S + beta * P + end if + + call A%apply(P, Q, alpha= one_${s}$, beta=zero_${s}$) ! Q = A*P + zv2 = A%inner_product( P, Q ) + + alpha = zr2 / zv2 + + X = X + alpha * P + R = R - alpha * Q + + norm_sq = A%inner_product( R, R ) + norm_sq_old = norm_sq + + zr1 = zr2 + iter = iter + 1 + if(associated(workspace%callback)) call workspace%callback(x, norm_sq, iter) + end do + end if + end associate + end subroutine + #:endfor + + #:for matrix in MATRIX_TYPES + #:for k, t, s in R_KINDS_TYPES + module subroutine solve_pcg_${matrix}$_${s}$(A,b,x,di,tol,maxiter,restart,precond,M,workspace) + #:if matrix == "dense" + use stdlib_linalg, only: diag + ${t}$, intent(in) :: A(:,:) + #:else + type(${matrix}$_${s}$_type), intent(in) :: A + #:endif + ${t}$, intent(in) :: b(:) + ${t}$, intent(inout) :: x(:) + ${t}$, intent(in), optional :: tol + logical(1), intent(in), optional, target :: di(:) + integer, intent(in), optional :: maxiter + logical, intent(in), optional :: restart + integer, intent(in), optional :: precond + class(linop_${s}$_type), optional , intent(in), target :: M + type(solver_workspace_${s}$_type), optional, intent(inout), target :: workspace + !------------------------- + type(linop_${s}$_type) :: op + type(linop_${s}$_type), pointer :: M_ => null() + type(solver_workspace_${s}$_type), pointer :: workspace_ + integer :: n, maxiter_ + ${t}$ :: tol_ + logical :: restart_ + logical(1), pointer :: di_(:) + !------------------------- + ! working data for preconditioner + integer :: precond_ + ${t}$, allocatable :: diagonal(:) + + !------------------------- + n = size(b) + maxiter_ = n; if(present(maxiter)) maxiter_ = maxiter + restart_ = .true.; if(present(restart)) restart_ = restart + tol_ = 1.e-4_${s}$; if(present(tol)) tol_ = tol + precond_ = pc_none; if(present(precond)) precond_ = precond + !------------------------- + ! internal memory setup + ! preconditioner + if(present(M)) then + M_ => M + else + allocate( M_ ) + allocate(diagonal(n),source=zero_${s}$) + + select case(precond_) + case(pc_jacobi) + #:if matrix == "dense" + diagonal = diag(A) + #:else + call diag(A,diagonal) + #:endif + M_%apply => precond_jacobi + case default + M_%apply => precond_none + end select + where(abs(diagonal)>epsilon(zero_${s}$)) diagonal = one_${s}$/diagonal + end if + ! matvec for the operator + op%apply => matvec + + ! direchlet boundary conditions mask + if(present(di))then + di_ => di + else + allocate(di_(n),source=.false._1) + end if + + ! workspace for the solver + if(present(workspace)) then + workspace_ => workspace + else + allocate( workspace_ ) + end if + if(.not.allocated(workspace_%tmp)) allocate( workspace_%tmp(n,stdlib_size_wksp_pcg) , source = zero_${s}$ ) + !------------------------- + ! main call to the solver + if(restart_) x = zero_${s}$ + x = merge( b, x, di_ ) ! copy dirichlet load conditions encoded in B and indicated by di + call solve_pcg_kernel(op,M_,b,x,tol_,maxiter_,workspace_) + + !------------------------- + ! internal memory cleanup + if(.not.present(di)) deallocate(di_) + di_ => null() + + if(.not.present(workspace)) then + deallocate( workspace_%tmp ) + deallocate( workspace_ ) + end if + M_ => null() + workspace_ => null() + contains + + subroutine matvec(x,y,alpha,beta) + ${t}$, intent(in) :: x(:) + ${t}$, intent(inout) :: y(:) + ${t}$, intent(in) :: alpha + ${t}$, intent(in) :: beta + #:if matrix == "dense" + y = alpha * matmul(A,x) + beta * y + #:else + call spmv( A , x, y , alpha, beta ) + #:endif + y = merge( zero_${s}$, y, di_ ) + end subroutine + + subroutine precond_none(x,y,alpha,beta) + ${t}$, intent(in) :: x(:) + ${t}$, intent(inout) :: y(:) + ${t}$, intent(in) :: alpha + ${t}$, intent(in) :: beta + y = merge( zero_${s}$, x, di_ ) + end subroutine + subroutine precond_jacobi(x,y,alpha,beta) + ${t}$, intent(in) :: x(:) + ${t}$, intent(inout) :: y(:) + ${t}$, intent(in) :: alpha + ${t}$, intent(in) :: beta + y = merge( zero_${s}$, diagonal * x, di_ ) ! inverted diagonal + end subroutine + end subroutine + + #:endfor + #:endfor + +end submodule stdlib_linalg_iterative_pcg \ No newline at end of file diff --git a/src/stdlib_sparse_constants.fypp b/src/stdlib_sparse_constants.fypp index 1e8374bd9..7b5dc4497 100644 --- a/src/stdlib_sparse_constants.fypp +++ b/src/stdlib_sparse_constants.fypp @@ -3,7 +3,7 @@ #:set C_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES, CMPLX_SUFFIX)) module stdlib_sparse_constants use stdlib_kinds, only: int8, int16, int32, int64, sp, dp, xdp, qp - + use stdlib_constants implicit none public @@ -20,13 +20,4 @@ module stdlib_sparse_constants ! Integer size support for ILP64 builds should be done here integer, parameter :: ilp = int32 - #:for k1, t1, s1 in (R_KINDS_TYPES) - ${t1}$, parameter :: zero_${s1}$ = 0._${k1}$ - ${t1}$, parameter :: one_${s1}$ = 1._${k1}$ - #:endfor - #:for k1, t1, s1 in (C_KINDS_TYPES) - ${t1}$, parameter :: zero_${s1}$ = (0._${k1}$,0._${k1}$) - ${t1}$, parameter :: one_${s1}$ = (1._${k1}$,1._${k1}$) - #:endfor - end module stdlib_sparse_constants diff --git a/test/linalg/CMakeLists.txt b/test/linalg/CMakeLists.txt index 0745aa3cb..9b33f100c 100644 --- a/test/linalg/CMakeLists.txt +++ b/test/linalg/CMakeLists.txt @@ -11,6 +11,7 @@ set( "test_linalg_determinant.fypp" "test_linalg_qr.fypp" "test_linalg_schur.fypp" + "test_linalg_solve_iterative.fypp" "test_linalg_svd.fypp" "test_linalg_matrix_property_checks.fypp" "test_linalg_sparse.fypp" @@ -39,6 +40,7 @@ ADDTEST(linalg_solve) ADDTEST(linalg_lstsq) ADDTEST(linalg_qr) ADDTEST(linalg_schur) +ADDTEST(linalg_solve_iterative) ADDTEST(linalg_svd) ADDTEST(linalg_sparse) ADDTESTPP(blas_lapack) diff --git a/test/linalg/test_linalg_solve_iterative.fypp b/test/linalg/test_linalg_solve_iterative.fypp new file mode 100644 index 000000000..e7d4acde4 --- /dev/null +++ b/test/linalg/test_linalg_solve_iterative.fypp @@ -0,0 +1,107 @@ +#:include "common.fypp" +#:set R_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES, REAL_SUFFIX)) +#:set MATRIX_TYPES = ["dense", "CSR"] +! Test linear system iterative solvers +module test_linalg_solve_iterative + use stdlib_kinds + use stdlib_sparse + use stdlib_linalg_iterative_solvers + use testdrive, only: error_type, check, new_unittest, unittest_type + + implicit none + private + + public :: test_linear_systems + + contains + + subroutine test_linear_systems(tests) + !> Collection of tests + type(unittest_type), allocatable, intent(out) :: tests(:) + + allocate(tests(0)) + + tests = [ new_unittest("solve_cg",test_solve_cg), & + new_unittest("solve_pcg",test_solve_pcg) ] + + end subroutine test_linear_systems + + subroutine test_solve_cg(error) + type(error_type), allocatable, intent(out) :: error + + #:for k, t, s in R_KINDS_TYPES + block + ${t}$, parameter :: tol = 1000*epsilon(0.0_${k}$) + ${t}$ :: A(2,2) = reshape([${t}$ :: 4, 1, & + 1, 3], [2,2]) + ${t}$ :: x(2), load(2), xref(2) + + xref = [0.0909, 0.6364] + x = real( [2,1] , kind = ${k}$ ) ! initial guess + load = real( [1,2] , kind = ${k}$ ) ! load vector + + call solve_cg(A, load, x) + + call check(error, norm2(x-xref)<1.e-4_${k}$, 'error in conjugate gradient solver') + if (allocated(error)) return + end block + + #:endfor + end subroutine test_solve_cg + + subroutine test_solve_pcg(error) + type(error_type), allocatable, intent(out) :: error + + #:for k, t, s in R_KINDS_TYPES + block + ${t}$, parameter :: tol = 1000*epsilon(0.0_${k}$) + ${t}$ :: A(5,5) = reshape([${t}$ :: 1, -1, 0, 0, 0,& + -1, 2, -1, 0, 0,& + 0, -1, 2, -1, 0,& + 0, 0, -1, 2, -1,& + 0, 0, 0, -1, 1] , [5,5]) + ${t}$ :: x(5), load(5), xref(5) + logical(1) :: dirichlet(5) + + xref = [0.0_${k}$,2.5_${k}$,5.0_${k}$,2.5_${k}$,0.0_${k}$] + x = 0.0_${k}$ + load = real( [0,0,5,0,0] , kind = ${k}$ ) ! load vector + dirichlet = .false._1 + dirichlet([1,5]) = .true._1 + + call solve_pcg(A, load, x, di=dirichlet, tol=1.e-6_${k}$) + + call check(error, norm2(x-xref)<1.e-6_${k}$*norm2(xref), 'error in preconditionned conjugate gradient solver') + if (allocated(error)) return + end block + + #:endfor + end subroutine test_solve_pcg + +end module test_linalg_solve_iterative + +program test_solve_iterative + use, intrinsic :: iso_fortran_env, only : error_unit + use testdrive, only : run_testsuite, new_testsuite, testsuite_type + use test_linalg_solve_iterative, only : test_linear_systems + implicit none + integer :: stat, is + type(testsuite_type), allocatable :: testsuites(:) + character(len=*), parameter :: fmt = '("#", *(1x, a))' + + stat = 0 + + testsuites = [ & + new_testsuite("linalg_solve_iterative", test_linear_systems) & + ] + + do is = 1, size(testsuites) + write(error_unit, fmt) "Testing:", testsuites(is)%name + call run_testsuite(testsuites(is)%collect, error_unit, stat) + end do + + if (stat > 0) then + write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!" + error stop + end if +end program test_solve_iterative