! This file is part of mctc-lib. ! ! Licensed under the Apache License, Version 2.0 (the "License"); ! you may not use this file except in compliance with the License. ! You may obtain a copy of the License at ! ! http://www.apache.org/licenses/LICENSE-2.0 ! ! Unless required by applicable law or agreed to in writing, software ! distributed under the License is distributed on an "AS IS" BASIS, ! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ! See the License for the specific language governing permissions and ! limitations under the License. !> @file mctc/csrlist/linal.f90 !> Sparse matrix-vector and matrix-matrix routines for CSR compressed matrices. !> The routines follow the argument conventions of the MKL sparse BLAS. module mctc_csrlist_linal use mctc_csrlist_type, only : csr_list use mctc_env, only : wp, i8 implicit none private public :: spgemv_csr, spsymv_csr, spmm_csr, spmspv_csr !> Performs the general Compressed Sparse Row (CSR) matrix-vector operation !> !> y := A*x or y := A^T*x !> !> where A is a square m-by-m matrix. The operation selector transa !> is an optional trailing argument and defaults to 'N'. interface spgemv_csr !> Standard CSR matrix-vector multiplication. module procedure spgemv_csr !> CSR matrix-vector multiplication with separate diagonal elements. module procedure spgemv_csr_sepdiag !> CSR matrix-vector multiplication with a CSR-indexed full matrix. module procedure gemv_csr end interface spgemv_csr !> Performs the symmetric Compressed Sparse Row (CSR) matrix-vector operation !> !> y := A*x !> !> where A is a symmetric m-by-m matrix of which only the upper or lower !> triangle is referenced. The triangle selector uplo is !> an optional trailing argument and defaults to 'U'. interface spsymv_csr !> Standard CSR matrix-vector multiplication. module procedure spsymv_csr !> CSR matrix-vector multiplication with separate diagonal elements. module procedure spsymv_csr_sepdiag !> CSR matrix-vector multiplication with a CSR-indexed full matrix. module procedure dspsymv_csr end interface spsymv_csr !> Performs the sparse matrix - sparse vector operation !> !> y := alpha*A*x !> !> where alpha is a scalar, A is a matrix given by a complete CSR list and !> x is a sparse vector given as a pair of index/value arrays, holding the !> positions and values of its non-zero elements. The resulting vector y is !> returned in the same sparse representation, its index/value arrays are !> allocated by the routine. interface spmspv_csr !> Sparse matrix - sparse vector multiplication, complete CSR storage. module procedure spmspv_csr end interface spmspv_csr !> Performs the Compressed Sparse Row (CSR) matrix-matrix operations !> !> C := op(A)*B (sparse B and C) !> C := alpha*op(A)*B + beta*C (dense B and C) !> !> with op(A) = A or A^T. interface spmm_csr !> Sparse-sparse product with a sparse result. module procedure spspmm_csr !> Sparse-dense product for a dense right-hand side block. module procedure spgemm_csr end interface spmm_csr contains !> Multiply a general CSR matrix by a vector subroutine spgemv_csr(m, a, ia, ja, x, y, transa) !> Number of rows of the matrix A integer, intent(in) :: m !> Non-zero elements of the matrix A real(wp), intent(in) :: a(:) !> Row offsets of A into a and ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Operation selector, 'N' for A*x (default), 'T' or 'C' for A^T*x character(len=1), intent(in), optional :: transa integer :: i, j integer(i8) :: k logical :: trans real(wp) :: y_tmp_i real(wp), allocatable :: y_priv(:) trans = .false. if (present(transa)) trans = scan(transa, "TtCc") > 0 if (.not. trans) then !$omp parallel do default(none) schedule(guided) & !$omp& private(i, k, y_tmp_i) shared(m, a, ia, ja, x, y) do i = 1, m y_tmp_i = 0.0_wp do k = ia(i), ia(i+1) - 1 y_tmp_i = y_tmp_i + a(k) * x(ja(k)) end do y(i) = y_tmp_i end do !$omp end parallel do else y(:m) = 0.0_wp !$omp parallel default(none) & !$omp& private(y_priv, i, k, j) shared(m, a, ia, ja, x, y) allocate(y_priv(m), source=0.0_wp) !$omp do schedule(guided) do i = 1, m do k = ia(i), ia(i+1) - 1 j = ja(k) y_priv(j) = y_priv(j) + a(k) * x(i) end do end do !$omp end do !$omp critical y(:m) = y(:m) + y_priv !$omp end critical deallocate(y_priv) !$omp end parallel end if end subroutine spgemv_csr !> Multiply a general CSR matrix with separate diagonal elements by a vector subroutine spgemv_csr_sepdiag(m, a, diag, ia, ja, x, y, transa) !> Number of rows of the matrix A integer, intent(in) :: m !> Non-zero elements of the matrix A real(wp), intent(in) :: a(:) !> Diagonal elements, added to the diagonal stored in a real(wp), intent(in) :: diag(:) !> Row offsets of A into a and ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Operation selector, 'N' for A*x (default), 'T' or 'C' for A^T*x character(len=1), intent(in), optional :: transa call spgemv_csr(m, a, ia, ja, x, y, transa) y(:m) = y(:m) + diag(:m) * x(:m) end subroutine spgemv_csr_sepdiag !> Multiply a CSR-indexed full matrix by a vector subroutine gemv_csr(m, a, ia, ja, x, y, transa) !> Number of rows of the matrix A integer, intent(in) :: m !> Full matrix indexed by neighbouring and central atoms real(wp), intent(in) :: a(:, :) !> Row offsets of A into ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Operation selector, 'N' for A*x (default), 'T' or 'C' for A^T*x character(len=1), intent(in), optional :: transa integer :: i, j integer(i8) :: k logical :: trans real(wp) :: y_tmp_i real(wp), allocatable :: y_priv(:) trans = .false. if (present(transa)) trans = scan(transa, "TtCc") > 0 if (.not. trans) then !$omp parallel do default(none) schedule(guided) & !$omp& private(i, j, k, y_tmp_i) shared(m, a, ia, ja, x, y) do i = 1, m y_tmp_i = 0.0_wp do k = ia(i), ia(i+1) - 1 j = ja(k) y_tmp_i = y_tmp_i + a(j, i) * x(j) end do y(i) = y_tmp_i end do !$omp end parallel do else y(:m) = 0.0_wp !$omp parallel default(none) & !$omp& private(y_priv, i, k, j) shared(m, a, ia, ja, x, y) allocate(y_priv(m), source=0.0_wp) !$omp do schedule(guided) do i = 1, m do k = ia(i), ia(i+1) - 1 j = ja(k) y_priv(j) = y_priv(j) + a(j, i) * x(i) end do end do !$omp end do !$omp critical y(:m) = y(:m) + y_priv !$omp end critical deallocate(y_priv) !$omp end parallel end if end subroutine gemv_csr !> Multiply a symmetric CSR matrix by a vector subroutine spsymv_csr(m, a, ia, ja, x, y, uplo) !> Number of rows of the matrix A integer, intent(in) :: m !> Non-zero elements of the matrix A real(wp), intent(in) :: a(:) !> Row offsets of A into a and ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Triangle selector, 'U' for the upper (default), 'L' for the lower one character(len=1), intent(in), optional :: uplo integer :: i, j integer(i8) :: k logical :: upper real(wp) :: y_tmp_i real(wp), allocatable :: y_priv(:) upper = .true. if (present(uplo)) upper = scan(uplo, "Uu") > 0 y(:m) = 0.0_wp !$omp parallel default(none) & !$omp& private(y_priv, i, k, j, y_tmp_i) shared(m, a, ia, ja, x, y, upper) allocate(y_priv(m), source=0.0_wp) !$omp do schedule(guided) do i = 1, m y_tmp_i = 0.0_wp do k = ia(i), ia(i+1) - 1 j = ja(k) if (j == i) then y_tmp_i = y_tmp_i + a(k) * x(i) else if ((j > i) .eqv. upper) then ! Contributions to row i and to the mirrored row j y_tmp_i = y_tmp_i + a(k) * x(j) y_priv(j) = y_priv(j) + a(k) * x(i) end if end do y_priv(i) = y_priv(i) + y_tmp_i end do !$omp end do !$omp critical y(:m) = y(:m) + y_priv !$omp end critical deallocate(y_priv) !$omp end parallel end subroutine spsymv_csr !> Multiply a symmetric CSR matrix with separate diagonal elements by a vector subroutine spsymv_csr_sepdiag(m, a, diag, ia, ja, x, y, uplo) !> Number of rows of the matrix A integer, intent(in) :: m !> Non-zero elements of the matrix A real(wp), intent(in) :: a(:) !> Diagonal elements, added to the diagonal stored in a real(wp), intent(in) :: diag(:) !> Row offsets of A into a and ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Triangle selector, 'U' for the upper (default), 'L' for the lower one character(len=1), intent(in), optional :: uplo call spsymv_csr(m, a, ia, ja, x, y, uplo) y(:m) = y(:m) + diag(:m) * x(:m) end subroutine spsymv_csr_sepdiag !> Multiply a symmetric CSR-indexed full matrix by a vector subroutine dspsymv_csr(m, a, ia, ja, x, y, uplo) !> Number of rows of the matrix A integer, intent(in) :: m !> Full matrix indexed by neighbouring and central atoms real(wp), intent(in) :: a(:, :) !> Row offsets of A into ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Input vector real(wp), intent(in) :: x(:) !> Output vector, its first m elements are overwritten real(wp), intent(inout) :: y(:) !> Triangle selector, 'U' for the upper (default), 'L' for the lower one character(len=1), intent(in), optional :: uplo integer :: i, j integer(i8) :: k logical :: upper real(wp) :: y_tmp_i real(wp), allocatable :: y_priv(:) upper = .true. if (present(uplo)) upper = scan(uplo, "Uu") > 0 y(:m) = 0.0_wp !$omp parallel default(none) & !$omp& private(y_priv, i, k, j, y_tmp_i) & !$omp& shared(m, a, ia, ja, x, y, upper) allocate(y_priv(m), source=0.0_wp) !$omp do schedule(guided) do i = 1, m y_tmp_i = 0.0_wp do k = ia(i), ia(i+1) - 1 j = ja(k) if (j == i) then y_tmp_i = y_tmp_i + a(i, i) * x(i) else if ((j > i) .eqv. upper) then ! Contributions to row i and to the mirrored row j y_tmp_i = y_tmp_i + a(j, i) * x(j) y_priv(j) = y_priv(j) + a(j, i) * x(i) end if end do y_priv(i) = y_priv(i) + y_tmp_i end do !$omp end do !$omp critical y(:m) = y(:m) + y_priv !$omp end critical deallocate(y_priv) !$omp end parallel end subroutine dspsymv_csr !> Multiply a CSR matrix given in complete storage by a sparse vector subroutine spmspv_csr(list, mlist, xptr, xval, yptr, yval, alpha) !> CSR neighbour-list structure type(csr_list), intent(in) :: list !> Matrix elements in CSR order real(wp), intent(in) :: mlist(:) !> Row indices of the non-zero elements of the input vector integer, intent(in) :: xptr(:) !> Values of the non-zero elements of the input vector real(wp), intent(in) :: xval(:) !> Row indices of the non-zero elements of the product vector, allocated here integer, allocatable, intent(out) :: yptr(:) !> Values of the non-zero elements of the product vector, allocated here real(wp), allocatable, intent(out) :: yval(:) !> Matrix scaling factor, defaults to one real(wp), intent(in), optional :: alpha integer :: i, j, n, m, nnz integer(i8) :: k real(wp) :: a, y_tmp_i real(wp), allocatable :: xdense(:), ydense(:) logical, allocatable :: yflag(:) a = 1.0_wp if (present(alpha)) a = alpha n = size(list%inl) - 1 if (size(mlist) /= size(list%nlat) .or. n < 0) then allocate(yptr(0), yval(0)) return end if allocate(xdense(n), source=0.0_wp) m = min(size(xptr), size(xval)) do i = 1, m j = xptr(i) if (j >= 1 .and. j <= n) xdense(j) = xdense(j) + xval(i) end do allocate(ydense(n)) allocate(yflag(n), source=.false.) !$omp parallel do default(none) schedule(guided) & !$omp& private(i, k, j, y_tmp_i) & !$omp& shared(list, mlist, xdense, ydense, yflag, a, n) do i = 1, n y_tmp_i = 0.0_wp do k = list%inl(i), list%inl(i+1) - 1 j = list%nlat(k) y_tmp_i = y_tmp_i + mlist(k) * xdense(j) end do ydense(i) = a * y_tmp_i yflag(i) = ydense(i) /= 0.0_wp end do !$omp end parallel do nnz = count(yflag) allocate(yptr(nnz)) allocate(yval(nnz)) nnz = 0 do i = 1, n if (yflag(i)) then nnz = nnz + 1 yptr(nnz) = i yval(nnz) = ydense(i) end if end do end subroutine spmspv_csr !> Multiply two CSR matrices, C := op(A)*B, with a CSR result. The columns !> of each row of C are returned in ascending order. recursive subroutine spspmm_csr(trans, request, sort, m, n, k, a, ja, ia, & & b, jb, ib, c, jc, ic, nzmax, info) !> Operation selector, 'N' for A*B, 'T' or 'C' for A^T*B character(len=1), intent(in) :: trans !> Computation stage, 0 computes ic, jc and c within nzmax elements, !> 1 computes only ic, 2 computes jc and c from an ic of a previous call integer, intent(in) :: request !> Reordering selector, accepted for compatibility only since the inputs !> need no sorting and the result is always sorted integer, intent(in) :: sort !> Number of rows of the matrix A integer, intent(in) :: m !> Number of columns of the matrix A integer, intent(in) :: n !> Number of columns of the matrix B integer, intent(in) :: k !> Non-zero elements of the matrix A real(wp), intent(in) :: a(:) !> Column indices of the non-zero elements of A integer, intent(in) :: ja(:) !> Row offsets of A into a and ja, size m + 1 integer(i8), intent(in) :: ia(:) !> Non-zero elements of the matrix B real(wp), intent(in) :: b(:) !> Column indices of the non-zero elements of B integer, intent(in) :: jb(:) !> Row offsets of B into b and jb, size n + 1 for 'N' and m + 1 for 'T' integer(i8), intent(in) :: ib(:) !> Non-zero elements of the matrix C, not referenced for request 1 real(wp), intent(inout) :: c(:) !> Column indices of the non-zero elements of C, not referenced for request 1 integer, intent(inout) :: jc(:) !> Row offsets of C into c and jc, size m + 1 for 'N' and n + 1 for 'T' integer(i8), intent(inout) :: ic(:) !> Maximum number of non-zero elements of C, only referenced for request 0 integer(i8), intent(in) :: nzmax !> Exit status, zero on success, or the row of C exceeding nzmax for request 0 integer, intent(out) :: info integer :: i, j, jcol, ntouch, it, jt integer(i8) :: ka, kb, kc real(wp) :: aij integer(i8), allocatable :: iat(:), pos(:) integer, allocatable :: jat(:) real(wp), allocatable :: at(:) ! Thread-private sparse accumulator for a single row of the product real(wp), allocatable :: acc(:) logical, allocatable :: flag(:) integer, allocatable :: touch(:) info = 0 ! Transposed product, form A^T explicitly and evaluate A^T*B if (scan(trans, "TtCc") > 0) then allocate(iat(n + 1), source=0_i8) do ka = 1, ia(m+1) - 1 iat(ja(ka) + 1) = iat(ja(ka) + 1) + 1 end do iat(1) = 1 do j = 1, n iat(j+1) = iat(j+1) + iat(j) end do allocate(at(ia(m+1) - 1), jat(ia(m+1) - 1)) allocate(pos(n), source=iat(:n)) do i = 1, m do ka = ia(i), ia(i+1) - 1 j = ja(ka) at(pos(j)) = a(ka) jat(pos(j)) = i pos(j) = pos(j) + 1 end do end do call spspmm_csr("N", request, sort, n, m, k, at, jat, iat, & & b, jb, ib, c, jc, ic, nzmax, info) return end if if (request /= 2) then !$omp parallel default(none) & !$omp& private(i, j, jcol, ka, kb, ntouch, it, flag, touch) & !$omp& shared(m, k, ia, ja, ib, jb, ic) allocate(flag(k), source=.false.) allocate(touch(k)) !$omp do schedule(guided) do i = 1, m ntouch = 0 do ka = ia(i), ia(i+1) - 1 j = ja(ka) do kb = ib(j), ib(j+1) - 1 jcol = jb(kb) if (.not. flag(jcol)) then flag(jcol) = .true. ntouch = ntouch + 1 touch(ntouch) = jcol end if end do end do ic(i+1) = ntouch do it = 1, ntouch flag(touch(it)) = .false. end do end do !$omp end do deallocate(flag, touch) !$omp end parallel ic(1) = 1 do i = 1, m ic(i+1) = ic(i+1) + ic(i) end do if (request == 1) return if (ic(m+1) - 1 > nzmax) then do i = 1, m if (ic(i+1) - 1 > nzmax) exit end do info = i return end if end if !$omp parallel default(none) & !$omp& private(i, j, jcol, ka, kb, kc, aij, ntouch, it, jt, acc, flag, touch) & !$omp& shared(m, k, a, ia, ja, b, ib, jb, c, ic, jc) allocate(acc(k), source=0.0_wp) allocate(flag(k), source=.false.) allocate(touch(k)) !$omp do schedule(guided) do i = 1, m ntouch = 0 do ka = ia(i), ia(i+1) - 1 j = ja(ka) aij = a(ka) do kb = ib(j), ib(j+1) - 1 jcol = jb(kb) if (.not. flag(jcol)) then flag(jcol) = .true. ntouch = ntouch + 1 touch(ntouch) = jcol acc(jcol) = 0.0_wp end if acc(jcol) = acc(jcol) + aij * b(kb) end do end do do it = 2, ntouch jcol = touch(it) jt = it - 1 do while (jt >= 1) if (touch(jt) <= jcol) exit touch(jt+1) = touch(jt) jt = jt - 1 end do touch(jt+1) = jcol end do kc = ic(i) do it = 1, ntouch jcol = touch(it) jc(kc) = jcol c(kc) = acc(jcol) flag(jcol) = .false. kc = kc + 1 end do end do !$omp end do deallocate(acc, flag, touch) !$omp end parallel end subroutine spspmm_csr !> Multiply a CSR matrix by a dense matrix, C := alpha*op(A)*B + beta*C. subroutine spgemm_csr(transa, m, n, k, alpha, matdescra, val, indx, pntrb, & & pntre, b, ldb, beta, c, ldc) !> Operation selector, 'N' for A*B, 'T' or 'C' for A^T*B character(len=1), intent(in) :: transa !> Number of rows of the matrix A integer, intent(in) :: m !> Number of columns of the matrices B and C integer, intent(in) :: n !> Number of columns of the matrix A integer, intent(in) :: k !> Matrix scaling factor real(wp), intent(in) :: alpha !> Matrix descriptor, structure in the first and triangle in the second !> character, the triangle is only referenced for symmetric matrices character(len=*), intent(in) :: matdescra !> Non-zero elements of the matrix A real(wp), intent(in) :: val(:) !> Column indices of the non-zero elements of A integer, intent(in) :: indx(:) !> Offsets of the first element of each row of A, size m integer(i8), intent(in) :: pntrb(:) !> Offsets past the last element of each row of A, size m integer(i8), intent(in) :: pntre(:) !> Leading dimension of B integer, intent(in) :: ldb !> Dense right-hand side block, k-by-n for 'N' and m-by-n for 'T' real(wp), intent(in) :: b(ldb, n) !> Existing-matrix scaling factor real(wp), intent(in) :: beta !> Leading dimension of C integer, intent(in) :: ldc !> Dense product block, m-by-n for 'N' and k-by-n for 'T' real(wp), intent(inout) :: c(ldc, n) integer :: i, j, nrow integer(i8) :: kk logical :: trans, sym, upper real(wp) :: v real(wp), allocatable :: c_tmp(:), c_priv(:, :) trans = scan(transa, "TtCc") > 0 sym = scan(matdescra(1:1), "SsHh") > 0 upper = .false. if (sym .and. len(matdescra) > 1) upper = scan(matdescra(2:2), "Uu") > 0 nrow = m if (trans .and. .not. sym) nrow = k if (beta == 0.0_wp) then c(:nrow, :n) = 0.0_wp else if (beta /= 1.0_wp) then c(:nrow, :n) = beta * c(:nrow, :n) end if if (.not. (trans .or. sym)) then ! General non-transposed product, rows of C are independent !$omp parallel default(none) private(i, j, kk, c_tmp) & !$omp& shared(m, n, alpha, val, indx, pntrb, pntre, b, c) allocate(c_tmp(n)) !$omp do schedule(guided) do i = 1, m c_tmp(:) = 0.0_wp do kk = pntrb(i), pntre(i) - 1 j = indx(kk) c_tmp(:) = c_tmp(:) + val(kk) * b(j, :n) end do c(i, :n) = c(i, :n) + alpha * c_tmp(:) end do !$omp end do deallocate(c_tmp) !$omp end parallel else ! Transposed or symmetric product, scatter into private blocks !$omp parallel default(none) private(i, j, kk, v, c_priv) & !$omp& shared(m, n, nrow, alpha, val, indx, pntrb, pntre, b, c, sym, upper) allocate(c_priv(nrow, n), source=0.0_wp) !$omp do schedule(guided) do i = 1, m do kk = pntrb(i), pntre(i) - 1 j = indx(kk) v = alpha * val(kk) if (.not. sym) then c_priv(j, :) = c_priv(j, :) + v * b(i, :n) else if (j == i) then c_priv(i, :) = c_priv(i, :) + v * b(i, :n) else if ((j > i) .eqv. upper) then c_priv(i, :) = c_priv(i, :) + v * b(j, :n) c_priv(j, :) = c_priv(j, :) + v * b(i, :n) end if end do end do !$omp end do !$omp critical c(:nrow, :n) = c(:nrow, :n) + c_priv !$omp end critical deallocate(c_priv) !$omp end parallel end if end subroutine spgemm_csr end module mctc_csrlist_linal