Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_SpMatrix.H
Go to the documentation of this file.
1#ifndef AMREX_SP_MATRIX_H_
2#define AMREX_SP_MATRIX_H_
3#include <AMReX_Config.H>
4
6#include <AMReX_AlgVector.H>
7#include <AMReX_CSR.H>
8#include <AMReX_BLProfiler.H>
9#include <AMReX_Gpu.H>
10#include <AMReX_Scan.H>
11
12#if defined(AMREX_USE_CUDA)
13# include <cusparse.h>
14#elif defined(AMREX_USE_HIP)
15# include <rocsparse/rocsparse.h>
16#elif defined(AMREX_USE_SYCL)
17# include <mkl_version.h>
18# include <oneapi/mkl/spblas.hpp>
19#endif
20
21#include <fstream>
22#include <numeric>
23#include <string>
24#include <type_traits>
25#include <utility>
26
27namespace amrex {
28
38template <typename T>
39struct ParCsr {
40 CsrView<T,int> csr0; // diagonal part
41 CsrView<T,int> csr1; // off-diagonal part
42 Long row_begin = 0; // global row index begin
43 Long col_begin = 0; // global col index begin
44 Long const* AMREX_RESTRICT row_map = nullptr; // mapping from csr0's row to csr1
45 Long const* AMREX_RESTRICT col_map = nullptr; // mapping from csr1's col to global
46};
47
49struct CsrSorted {
50 bool b = true;
51 explicit operator bool() const { return b; }
52};
53
55struct CsrValid {
56 bool b = true;
57 explicit operator bool() const { return b; }
58};
59
63template <typename T, template<typename> class Allocator = DefaultAllocator>
65{
66public:
67 using value_type = T;
68 template <class U> using allocator_type = Allocator<U>;
69 template <class U> using container_type = PODVector<U,Allocator<U> >;
70 using csr_type = CSR<T,container_type>; // global Long columns (unsplit)
71 using local_csr_type = CSR<T,container_type,int>; // split blocks, 32-bit indices
72 using AllocT = Allocator<T>;
73
74 SpMatrix () = default;
75
94 SpMatrix (AlgPartition partition, int nnz_per_row);
95
107
108 SpMatrix (SpMatrix const&) = delete;
109 SpMatrix& operator= (SpMatrix const&) = delete;
110
111 SpMatrix (SpMatrix &&) = default;
113
114 ~SpMatrix () = default;
115
130 void define (AlgPartition partition, int nnz_per_row);
131
164 void define (AlgPartition partition, T const* mat, Long const* col_index,
165 Long nentries, Long const* row_offset, CsrSorted is_sorted,
166 CsrValid is_valid);
167
180
182 [[nodiscard]] AlgPartition const& partition () const { return m_partition; }
183
191 [[nodiscard]] AlgPartition const& columnPartition () const { return m_col_partition; }
192
194 [[nodiscard]] Long numLocalRows () const { return m_row_end - m_row_begin; }
196 [[nodiscard]] Long numGlobalRows () const { return m_partition.numGlobalRows(); }
198 [[nodiscard]] Long numLocalNonZeros () const { return m_nnz; }
199
201 [[nodiscard]] Long globalRowBegin () const { return m_row_begin; }
203 [[nodiscard]] Long globalRowEnd () const { return m_row_end; }
204
206 [[nodiscard]] T* data () {
207 AMREX_ALWAYS_ASSERT(m_split == false);
208 return m_csr.mat.data();
209 }
210
212 [[nodiscard]] Long* columnIndex () {
213 AMREX_ALWAYS_ASSERT(m_split == false);
214 return m_csr.col_index.data();
215 }
216
218 [[nodiscard]] Long* rowOffset () {
219 AMREX_ALWAYS_ASSERT(m_split == false);
220 return m_csr.row_offset.data();
221 }
222
223 /*
224 * \brief Print the matrix to files.
225 *
226 * Each process writes its local portion of the matrix to a separate
227 * file. This function is provided for debugging purpose. Using it in
228 * large-scale runs may overwhelm the file system.
229 *
230 * File format:
231 * - The first line contains three integers describing the local matrix:
232 * the global row begin index, the global row end index, and the
233 * number of local nonzeros.
234 * - This is followed by one line per nonzero entry. Each line contains:
235 * the global row index, global column index, and matrix value.
236 *
237 * \param file Base file name. The full name on process `i` is `{file}.{i}`.
238 */
239 void printToFile (std::string const& file) const;
240
264 template <typename F>
265 void setVal (F const& f, CsrSorted is_sorted);
266
269 void sortCSR ();
270
278 void setColumnPartition (AlgPartition const& col_partition);
279
281 [[nodiscard]] AlgVector<T,AllocT> const& diagonalVector () const;
282
289 [[nodiscard]] AlgVector<T,AllocT> rowSum () const;
290
296 [[nodiscard]] ParCsr<T > parcsr () ;
298 [[nodiscard]] ParCsr<T const> parcsr () const;
300 [[nodiscard]] ParCsr<T const> const_parcsr () const;
301
302 template <typename U, template<typename> class M, typename N> friend
304
305 template <typename U, template<typename> class M> friend
306 SpMatrix<U,M> transpose (SpMatrix<U,M> const& A, AlgPartition const& col_partition);
307
308 template <typename U, template<typename> class M> friend
310 AlgPartition const& col_partition);
311
312 template <typename U, template<typename> class M, typename F> friend
314 AlgPartition const& col_partition, F const& row_post);
315
316 template <typename U, template<typename> class M> friend
318 SpMatrix<U,M> const& P, AlgPartition const& col_partition);
319
321 void define_doit (int nnz_per_row);
322
332 template <typename I>
333 void define_and_filter_doit (T const* mat, Long const* col_index,
334 Long nentries, Long const* row_offset);
335
344
354 template <typename U>
357 template <typename U>
359
361 void startComm_tr (AlgPartition const& col_partition);
364
365public: // NOLINT Private, but public for CUDA
367 void split_csr (AlgPartition const& col_partition);
368
375 void define_split (AlgPartition partition, AlgPartition const& col_partition,
376 local_csr_type&& compact, Long nlocal,
377 Long const* remote_cols, Long nremote);
378
379private:
380
381 void set_num_neighbors ();
382
383 AlgPartition m_partition;
384 AlgPartition m_col_partition;
385 Long m_row_begin = 0;
386 Long m_row_end = 0;
387 Long m_col_begin = 0;
388 Long m_col_end = 0;
389 Long m_nnz = 0;
390 csr_type m_csr; // unsplit form, empty once split
391 local_csr_type m_csr_local; // diagonal block after the split
392
393 mutable AlgVector<T,AllocT> m_diagonal;
394
395 bool m_split = false; // Has the matrix been split into diagonal and off-diagonal parts?
396
397#ifdef AMREX_USE_MPI
398 local_csr_type m_csr_remote;
399
400 // The `row_offset` in m_csr_remote does not contain empty rows. The
401 // full list of row offsets is available in m_remote_row_offset, which
402 // is built on demand by expand_remote_row_offset.
403 mutable container_type<int> m_remote_row_offset;
404
405 // It should be noted that m_csr and m_csr_remote may have different
406 // number of rows, because some rows may be purely local and they do not
407 // appear in m_csr_remote. Thus, we need a mapping from local row index
408 // in m_csr_remote to local row index in m_csr. Then we will be able to
409 // know its global row index by adding m_row_begin. For example,
410 // m_row_begin + m_ri_rtol[i] is the global index for local row i in
411 // m_csr_remote.
412 container_type<Long> m_ri_rtol; // size: m_csr_remote.nrows()
413
414 // This is local row index mapping from m_csr to m_csr_remote. -1 means
415 // the row does not exist in m_csr_remote.
416 container_type<Long> m_ri_ltor; // size: numLocalRows()
417
418 // For column index, we also need to be careful with local vs. global,
419 // and there two types of locals: local in m_csr and local in
420 // m_csr_remote. For m_csr, col_index is the global index if m_split is
421 // false, and it becomes the local index if m_split is true. The
422 // conversion is global_col_index = local_col_index + m_col_begin.
423 //
424 // For m_csr_remote, col_index is also local. For a give col_index j,
425 // m_remote_cols_v[j] gives us the global index.
426 Vector<Long> m_remote_cols_v;
427#ifdef AMREX_USE_GPU
428 container_type<Long> m_remote_cols_dv;
429#endif
430 // The size of outer vector is # of procs. The indices are global.
431 Vector<Vector<Long>> m_remote_cols_vv;
432
433 // No. of other processes involved in communication.
434 //
435 // For matrix-vector multiplication, this is the number of processes
436 // that require this process' data in the vector. This variable is
437 // stored in the matrix and it's set by calling its member function
438 // set_num_neighbors(). However, the data here are the vector's data.
439 //
440 // For matrix transpose, this is the number of processes that will send
441 // us their transposed data.
442 int m_num_neighbors = -1;
443
444public: // NOLINT Private functions, but public for cuda
445
464
465 struct CommTR {
466 CsrView<T> csrt; // Own the memory inside
467
471
475
476 std::array<Long,2> total_counts_recv = {0,0};
478 // The raw pointers below are owning.
479 // xxxxx TODO GPU: Currently we use pinned memory. In the future, we
480 // may explore device memory for GPU aware MPI.
481 T* recv_buffer_mat = nullptr;
484 Long* recv_buffer_idx_map = nullptr; // local -> global for row of A^T.
485
487
490 template <typename VL, typename VI>
491 void update_remote_col_index (VL const& gcols, VI& lcols, bool in_device_memory);
495 void prepare_comm_mv (AlgPartition const& col_partition);
500 void unpack_buffer_tr (CommTR const& ctr, AlgPartition const& col_partition);
504
508 Long* col_index = nullptr; // pinned memory
509 T* mat = nullptr; // comms arena
512
513 RemoteRowsMM () = default;
515 RemoteRowsMM (RemoteRowsMM const&) = delete;
517 RemoteRowsMM (RemoteRowsMM&& rhs) noexcept
518 : row_offset(std::move(rhs.row_offset)),
519 col_index(std::exchange(rhs.col_index, nullptr)),
520 mat(std::exchange(rhs.mat, nullptr)),
521 nrows(rhs.nrows), nnz(rhs.nnz) {}
523 if (this != &rhs) {
524 clear();
525 row_offset = std::move(rhs.row_offset);
526 col_index = std::exchange(rhs.col_index, nullptr);
527 mat = std::exchange(rhs.mat, nullptr);
528 nrows = rhs.nrows;
529 nnz = rhs.nnz;
530 }
531 return *this;
532 }
533 void clear () {
534 if (col_index || mat) {
537 if (mat) { The_Comms_Arena()->free(mat); }
538 col_index = nullptr;
539 mat = nullptr;
540 }
541 row_offset.clear();
542 nrows = 0;
543 nnz = 0;
544 }
545 };
546
550
551#endif
552};
553
554namespace detail {
555
557template <typename T>
559bool has_remote_row (ParCsr<T> const& a, Long i)
560{
561 return a.csr1.nnz > 0 && a.row_map[i] >= 0;
562}
563
564// Communication may be skipped only when all rows and all columns live on
565// one common rank. Two partitions with a single active rank each may still
566// name different ranks.
567inline bool spmat_comm_is_local (AlgPartition const& row_partition,
568 AlgPartition const& col_partition)
569{
570 int const rp = row_partition.singleActiveProc();
571 int const cp = col_partition.singleActiveProc();
572 return row_partition.numActiveProcs() <= 1
573 && col_partition.numActiveProcs() <= 1
574 && (rp < 0 || cp < 0 || rp == cp);
575}
576
577// Diagonal of a square matrix: entry with column index == row index + offset.
578template <typename T, typename I>
579void extract_diagonal (T* p, CsrView<T const,I> const& csr, Long offset)
580{
581 auto const* AMREX_RESTRICT mat = csr.mat;
582 auto const* AMREX_RESTRICT col = csr.col_index;
583 auto const* AMREX_RESTRICT row = csr.row_offset;
584 ParallelForOMP(csr.nrows, [=] AMREX_GPU_DEVICE (Long i)
585 {
586 T d = 0;
587 for (Long j = row[i]; j < row[i+1]; ++j) {
588 if (i == Long(col[j]) - offset) {
589 d = mat[j];
590 break;
591 }
592 }
593 p[i] = d;
594 });
595}
596
597template <typename T, typename IO, typename II>
598void transpose (CsrView<T,IO> const& csrt, CsrView<T const,II> const& csr)
599{
600 Long nrows = csr.nrows;
601 Long ncols = csrt.nrows;
602 Long nnz = csr.nnz;
603
604 if (nrows <= 0 || ncols <= 0 || nnz <= 0) {
605 auto* p = csrt.row_offset;
606 ParallelForOMP(ncols+1, [=] AMREX_GPU_DEVICE (Long i) { p[i] = 0; });
607 return;
608 }
609
610 // The row indices become the column indices of the transpose, and its
611 // row offsets go up to nnz.
612 if constexpr (!std::is_same_v<IO,Long>) {
613 AMREX_ALWAYS_ASSERT(nrows < Long(std::numeric_limits<IO>::max()) &&
614 nnz < Long(std::numeric_limits<IO>::max()));
615 }
616
617#ifdef AMREX_USE_GPU
618
619#if defined(AMREX_USE_CUDA)
620
621 cusparseHandle_t handle;
622 AMREX_CUSPARSE_SAFE_CALL(cusparseCreate(&handle));
623 AMREX_CUSPARSE_SAFE_CALL(cusparseSetStream(handle, Gpu::gpuStream()));
624
625 cudaDataType data_type;
626 if constexpr (std::is_same_v<T,float>) {
627 data_type = CUDA_R_32F;
628 } else if constexpr (std::is_same_v<T,double>) {
629 data_type = CUDA_R_64F;
630 } else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
631 data_type = CUDA_C_32F;
632 } else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
633 data_type = CUDA_C_64F;
634 } else {
635 amrex::Abort("SpMatrix transpose: unsupported data type");
636 }
637
638 AMREX_ALWAYS_ASSERT(nrows < Long(std::numeric_limits<int>::max()) &&
639 ncols < Long(std::numeric_limits<int>::max()) &&
640 nnz < Long(std::numeric_limits<int>::max()));
641
642 // cuSPARSE wants 32-bit indices; convert only if needed.
643 constexpr bool in32 = std::is_same_v<II,int>;
644 constexpr bool out32 = std::is_same_v<IO,int>;
645 CsrIndex<int,Gpu::AsyncVector> ci, cit;
646 int const* csr_col_index;
647 int const* csr_row_offset;
648 int* csrt_col_index;
649 int* csrt_row_offset;
650 if constexpr (in32) {
651 csr_col_index = csr.col_index;
652 csr_row_offset = csr.row_offset;
653 } else {
654 ci.copyFrom(csr);
655 csr_col_index = ci.col_index.data();
656 csr_row_offset = ci.row_offset.data();
657 }
658 if constexpr (out32) {
659 csrt_col_index = csrt.col_index;
660 csrt_row_offset = csrt.row_offset;
661 } else {
662 cit.col_index.resize(csrt.nnz);
663 cit.row_offset.resize(csrt.nrows+1);
664 csrt_col_index = cit.col_index.data();
665 csrt_row_offset = cit.row_offset.data();
666 }
667
668 std::size_t buffer_size;
670 cusparseCsr2cscEx2_bufferSize(handle, int(nrows), int(ncols), int(nnz),
671 csr.mat, csr_row_offset, csr_col_index,
672 csrt.mat, csrt_row_offset, csrt_col_index,
673 data_type, CUSPARSE_ACTION_NUMERIC,
674 CUSPARSE_INDEX_BASE_ZERO,
675 CUSPARSE_CSR2CSC_ALG1,
676 &buffer_size));
677
678 auto* pbuffer = (void*)The_Async_Arena()->alloc(buffer_size);
679
681 cusparseCsr2cscEx2(handle, int(nrows), int(ncols), int(nnz),
682 csr.mat, csr_row_offset, csr_col_index,
683 csrt.mat, csrt_row_offset, csrt_col_index,
684 data_type, CUSPARSE_ACTION_NUMERIC,
685 CUSPARSE_INDEX_BASE_ZERO,
686 CUSPARSE_CSR2CSC_ALG1,
687 pbuffer));
688
689 if constexpr (!out32) {
690 cit.copyTo(csrt);
691 }
692
693 // No sync needed: the descriptors are host objects, cusparseDestroy defers
694 // the release of GPU resources, and the buffer is freed in stream order.
695 AMREX_CUSPARSE_SAFE_CALL(cusparseDestroy(handle));
696 The_Async_Arena()->free(pbuffer);
697
698#elif defined(AMREX_USE_HIP)
699
700 rocsparse_handle handle;
701 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_create_handle(&handle));
702 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_set_stream(handle, Gpu::gpuStream()));
703
704 constexpr bool same_int = (sizeof(rocsparse_int) == sizeof(II)) &&
705 (sizeof(rocsparse_int) == sizeof(IO));
706
707 rocsparse_int const* csr_col_index;
708 rocsparse_int const* csr_row_offset;
709 rocsparse_int* csrt_col_index;
710 rocsparse_int* csrt_row_offset;
711 CsrIndex<rocsparse_int,Gpu::AsyncVector> ci, cit;
712 if constexpr (same_int) {
713 csr_col_index = reinterpret_cast<rocsparse_int const*>(csr.col_index);
714 csr_row_offset = reinterpret_cast<rocsparse_int const*>(csr.row_offset);
715 csrt_col_index = reinterpret_cast<rocsparse_int*>(csrt.col_index);
716 csrt_row_offset = reinterpret_cast<rocsparse_int*>(csrt.row_offset);
717 } else {
718 AMREX_ALWAYS_ASSERT(ncols < Long(std::numeric_limits<rocsparse_int>::max()));
719 ci.copyFrom(csr);
720 cit.col_index.resize(csrt.nnz);
721 cit.row_offset.resize(csrt.nrows+1);
722 csr_col_index = ci.col_index.data();
723 csr_row_offset = ci.row_offset.data();
724 csrt_col_index = cit.col_index.data();
725 csrt_row_offset = cit.row_offset.data();
726 }
727
728 std::size_t buffer_size;
729 AMREX_ROCSPARSE_SAFE_CALL(
730 rocsparse_csr2csc_buffer_size(handle, rocsparse_int(nrows),
731 rocsparse_int(ncols), rocsparse_int(nnz),
732 csr_row_offset, csr_col_index,
733 rocsparse_action_numeric,
734 &buffer_size));
735
736 auto* pbuffer = (void*)The_Async_Arena()->alloc(buffer_size);
737
738 if constexpr (std::is_same_v<T,float>) {
739 AMREX_ROCSPARSE_SAFE_CALL(
740 rocsparse_scsr2csc(handle, rocsparse_int(nrows),
741 rocsparse_int(ncols), rocsparse_int(nnz),
742 csr.mat, csr_row_offset, csr_col_index,
743 csrt.mat, csrt_col_index, csrt_row_offset,
744 rocsparse_action_numeric,
745 rocsparse_index_base_zero,
746 pbuffer));
747 } else if constexpr (std::is_same_v<T,double>) {
748 AMREX_ROCSPARSE_SAFE_CALL(
749 rocsparse_dcsr2csc(handle, rocsparse_int(nrows),
750 rocsparse_int(ncols), rocsparse_int(nnz),
751 csr.mat, csr_row_offset, csr_col_index,
752 csrt.mat, csrt_col_index, csrt_row_offset,
753 rocsparse_action_numeric,
754 rocsparse_index_base_zero,
755 pbuffer));
756 } else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
757 AMREX_ROCSPARSE_SAFE_CALL(
758 rocsparse_ccsr2csc(handle, rocsparse_int(nrows),
759 rocsparse_int(ncols), rocsparse_int(nnz),
760 (rocsparse_float_complex*)csr.mat, csr_row_offset, csr_col_index,
761 (rocsparse_float_complex*)csrt.mat, csrt_col_index, csrt_row_offset,
762 rocsparse_action_numeric,
763 rocsparse_index_base_zero,
764 pbuffer));
765 } else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
766 AMREX_ROCSPARSE_SAFE_CALL(
767 rocsparse_zcsr2csc(handle, rocsparse_int(nrows),
768 rocsparse_int(ncols), rocsparse_int(nnz),
769 (rocsparse_double_complex*)csr.mat, csr_row_offset, csr_col_index,
770 (rocsparse_double_complex*)csrt.mat, csrt_col_index, csrt_row_offset,
771 rocsparse_action_numeric,
772 rocsparse_index_base_zero,
773 pbuffer));
774 } else {
775 amrex::Abort("SpMatrix transpose: unsupported data type");
776 }
777
778 if constexpr (!same_int) {
779 cit.copyTo(csrt);
780 }
781
783 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_destroy_handle(handle));
784 The_Async_Arena()->free(pbuffer);
785
786#elif defined(AMREX_USE_SYCL)
787
788 mkl::sparse::matrix_handle_t handle_in{};
789 mkl::sparse::matrix_handle_t handle_out{};
790 mkl::sparse::init_matrix_handle(&handle_in);
791 mkl::sparse::init_matrix_handle(&handle_out);
792
793 // oneMKL needs one index type per handle: Long copies unless already Long.
794 Gpu::DeviceVector<Long> lrow_in, lcol_in, lrow_out, lcol_out;
795 Long const* prow_in;
796 Long const* pcol_in;
797 Long* prow_out;
798 Long* pcol_out;
799 if constexpr (std::is_same_v<II,Long>) {
800 prow_in = csr.row_offset;
801 pcol_in = csr.col_index;
802 } else {
803 lrow_in.resize(nrows+1);
804 lcol_in.resize(nnz);
805 auto* pr = lrow_in.data(); auto const* sr = csr.row_offset;
806 auto* pc = lcol_in.data(); auto const* sc = csr.col_index;
807 ParallelFor(std::max(nrows+1,nnz), [=] AMREX_GPU_DEVICE (Long i) {
808 if (i < nrows+1) { pr[i] = Long(sr[i]); }
809 if (i < nnz) { pc[i] = Long(sc[i]); }
810 });
811 prow_in = lrow_in.data();
812 pcol_in = lcol_in.data();
813 }
814 if constexpr (std::is_same_v<IO,Long>) {
815 prow_out = csrt.row_offset;
816 pcol_out = csrt.col_index;
817 } else {
818 lrow_out.resize(ncols+1);
819 lcol_out.resize(nnz);
820 prow_out = lrow_out.data();
821 pcol_out = lcol_out.data();
822 }
823#if defined(INTEL_MKL_VERSION) && (INTEL_MKL_VERSION < 20250300)
824 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_in, nrows, ncols,
825 mkl::index_base::zero, (Long*)prow_in, (Long*)pcol_in,
826 (T*)csr.mat);
827 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_out, ncols, nrows,
828 mkl::index_base::zero, prow_out, pcol_out, (T*)csrt.mat);
829#else
830 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_in, nrows, ncols, nnz,
831 mkl::index_base::zero, (Long*)prow_in, (Long*)pcol_in,
832 (T*)csr.mat);
833 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_out, ncols, nrows, nnz,
834 mkl::index_base::zero, prow_out, pcol_out, (T*)csrt.mat);
835#endif
836
837 mkl::sparse::omatcopy(Gpu::Device::streamQueue(), mkl::transpose::trans,
838 handle_in, handle_out);
839
840 mkl::sparse::release_matrix_handle(Gpu::Device::streamQueue(), &handle_in);
841 auto ev = mkl::sparse::release_matrix_handle(Gpu::Device::streamQueue(), &handle_out);
842 ev.wait();
843 if constexpr (!std::is_same_v<IO,Long>) {
844 auto* po = csrt.col_index; auto const* so = lcol_out.data();
845 auto* pr = csrt.row_offset; auto const* sr = lrow_out.data();
846 ParallelFor(std::max(nnz,ncols+1), [=] AMREX_GPU_DEVICE (Long i) {
847 if (i < nnz) { po[i] = IO(so[i]); }
848 if (i < ncols+1) { pr[i] = IO(sr[i]); }
849 });
851 }
852
853#endif
854
856
857#else
858
859 auto* p = csrt.row_offset;
860
861 ParallelForOMP(ncols+1, [=] AMREX_GPU_DEVICE (Long i) { p[i] = 0; });
862
863 // nonzeros per column
864#ifdef AMREX_USE_OMP
865#pragma omp parallel for
866#endif
867 for (Long i = 0; i < nnz; ++i) {
868 auto col = csr.col_index[i];
869#ifdef AMREX_USE_OMP
870#pragma omp atomic update
871#endif
872 ++p[col+1];
873 }
874
875 // build row_offset for transposed matrix. Also save a copy.
876 Vector<Long> current_pos(ncols+1);
877 current_pos[0] = 0;
878 for (Long i = 0; i < ncols; ++i) {
879 p[i+1] += p[i];
880 current_pos[i+1] = p[i+1];
881 }
882
883 // The following code is not OMP safe. It's difficult to use OMP and
884 // still keep CSR sorted.
885 for (Long i = 0; i < nrows; ++i) {
886 for (Long idx = csr.row_offset[i]; idx < csr.row_offset[i+1]; ++idx) {
887 auto col = csr.col_index[idx];
888 Long dest = current_pos[col]++;
889 csrt.mat[dest] = csr.mat[idx];
890 csrt.col_index[dest] = IO(i);
891 }
892 }
893
894#endif
895}
896}
897
898template <typename T, template<typename> class Allocator>
900 : m_partition(std::move(partition)),
901 m_row_begin(m_partition[ParallelContext::MyProcSub()]),
902 m_row_end(m_partition[ParallelContext::MyProcSub()+1])
903{
904 define_doit(nnz_per_row);
905}
906
907template <typename T, template<typename> class Allocator>
909 : m_partition(std::move(partition)),
910 m_row_begin(m_partition[ParallelContext::MyProcSub()]),
911 m_row_end(m_partition[ParallelContext::MyProcSub()+1]),
912 m_nnz(csr.nnz),
913 m_csr(std::move(csr))
914{}
915
916template <typename T, template<typename> class Allocator>
917void SpMatrix<T,Allocator>::define (AlgPartition partition, int nnz_per_row)
918{
919 m_partition = std::move(partition);
920 m_row_begin = m_partition[ParallelContext::MyProcSub()];
921 m_row_end = m_partition[ParallelContext::MyProcSub()+1];
922 m_diagonal = AlgVector<T,AllocT>{};
923 define_doit(nnz_per_row);
924}
925
926template <typename T, template<typename> class Allocator>
928 CsrSorted is_sorted)
929{
930 AMREX_ALWAYS_ASSERT(m_split == false);
931
932 m_partition = std::move(partition);
933 m_row_begin = m_partition[ParallelContext::MyProcSub()];
934 m_row_end = m_partition[ParallelContext::MyProcSub()+1];
935 m_nnz = csr.nnz;
936 m_csr = std::move(csr);
937 m_diagonal = AlgVector<T,AllocT>{};
938 if (! is_sorted) { m_csr.sort(); }
939}
940
941template <typename T, template<typename> class Allocator>
942void
944{
945 AMREX_ALWAYS_ASSERT(m_split == false);
946
947 nnz_per_row = std::max(nnz_per_row, 0);
948 Long nlocalrows = this->numLocalRows();
949 m_nnz = nlocalrows*nnz_per_row;
950 m_csr.mat.resize(m_nnz);
951 m_csr.col_index.resize(m_nnz);
952 m_csr.row_offset.resize(nlocalrows+1);
953 m_csr.nnz = m_nnz;
954
955 auto* poffset = m_csr.row_offset.data();
956 ParallelForOMP(nlocalrows+1, [=] AMREX_GPU_DEVICE (Long lrow) noexcept
957 {
958 poffset[lrow] = lrow*nnz_per_row;
959 });
960}
961
962template <typename T, template<typename> class Allocator>
963void
965 Long const* col_index, Long nentries,
966 Long const* row_offset, CsrSorted is_sorted,
967 CsrValid is_valid)
968{
969 AMREX_ALWAYS_ASSERT(m_split == false);
970
971 m_partition = std::move(partition);
972 m_row_begin = m_partition[ParallelContext::MyProcSub()];
973 m_row_end = m_partition[ParallelContext::MyProcSub()+1];
974 m_diagonal = AlgVector<T,AllocT>{};
975
976 bool synced = false;
977
978 if (is_valid) {
979 m_nnz = nentries;
980 Long nlocalrows = this->numLocalRows();
981 m_csr.mat.resize(nentries);
982 m_csr.col_index.resize(nentries);
983 m_csr.row_offset.resize(nlocalrows+1);
984 m_csr.nnz = nentries;
985 Gpu::copyAsync(Gpu::deviceToDevice, mat, mat+nentries, m_csr.mat.begin());
986 Gpu::copyAsync(Gpu::deviceToDevice, col_index, col_index+nentries,
987 m_csr.col_index.begin());
988 Gpu::copyAsync(Gpu::deviceToDevice, row_offset, row_offset+nlocalrows+1,
989 m_csr.row_offset.begin());
990 } else {
991 if (nentries < Long(std::numeric_limits<int>::max())) {
992 define_and_filter_doit<int>(mat, col_index, nentries, row_offset);
993 } else {
994 define_and_filter_doit<Long>(mat, col_index, nentries, row_offset);
995 }
996 synced = true;
997 }
998
999 if (! is_sorted) {
1000 m_csr.sort();
1001 synced = true;
1002 }
1003
1004 if (! synced) {
1006 }
1007}
1008
1009template <typename T, template<typename> class Allocator>
1010void
1012{
1013 AMREX_ALWAYS_ASSERT(m_split == false);
1014 m_csr.sort();
1015}
1016
1017template <typename T, template<typename> class Allocator>
1018void
1020{
1021 split_csr(col_partition);
1022}
1023
1024template <typename T, template<typename> class Allocator>
1025template <typename I>
1026void
1028 Long nentries, Long const* row_offset)
1029{
1030 Gpu::DeviceVector<I> psum(nentries);
1031 auto* ps = psum.data();
1032 m_nnz = Scan::PrefixSum<I>(I(nentries),
1033 [=] AMREX_GPU_DEVICE (I i) -> I {
1034 return col_index[i] >= 0 && mat[i] != 0; },
1035 [=] AMREX_GPU_DEVICE (I i, I x) {
1036 ps[i] = x; },
1038 Long nlocalrows = this->numLocalRows();
1039 m_csr.mat.resize(m_nnz);
1040 m_csr.col_index.resize(m_nnz);
1041 m_csr.row_offset.resize(nlocalrows+1);
1042 m_csr.nnz = m_nnz;
1043 auto* pmat = m_csr.mat.data();
1044 auto* pcol = m_csr.col_index.data();
1045 auto* prow = m_csr.row_offset.data();
1046 auto actual_nnz = m_nnz;
1047 ParallelFor(std::max(nentries,nlocalrows+1), [=] AMREX_GPU_DEVICE (Long i)
1048 {
1049 if (i < nentries) {
1050 if (col_index[i] >= 0 && mat[i] != 0) {
1051 pmat[ps[i]] = mat[i];
1052 pcol[ps[i]] = col_index[i];
1053 }
1054 }
1055 if (i <= nlocalrows) {
1056 prow[i] = (i < nlocalrows && row_offset[i] < nentries)
1057 ? Long(ps[row_offset[i]]) : actual_nnz;
1058 }
1059 });
1061}
1062
1063template <typename T, template<typename> class Allocator>
1064void
1065SpMatrix<T,Allocator>::printToFile (std::string const& file) const
1066{
1067 std::ofstream ofs(file+"."+std::to_string(ParallelContext::MyProcSub()));
1068 ofs << m_row_begin << " " << m_row_end << " " << m_nnz << "\n";
1069 Long const nrows = numLocalRows();
1070
1071 if (!m_split) {
1072#ifdef AMREX_USE_GPU
1076#else
1077 auto const& csr = m_csr;
1078#endif
1079 for (Long i = 0; i < nrows; ++i) {
1080 for (Long j = csr.row_offset[i]; j < csr.row_offset[i+1]; ++j) {
1081 ofs << i+m_row_begin << " " << csr.col_index[j] << " " << csr.mat[j] << "\n";
1082 }
1083 }
1084 return;
1085 }
1086
1087#ifdef AMREX_USE_GPU
1089 amrex::duplicateCSR(Gpu::deviceToHost, csr, m_csr_local);
1090# ifdef AMREX_USE_MPI
1092 amrex::duplicateCSR(Gpu::deviceToHost, csr_r, m_csr_remote);
1093 Gpu::PinnedVector<Long> ri_ltor(m_ri_ltor.size());
1094 Gpu::copyAsync(Gpu::deviceToHost, m_ri_ltor.begin(), m_ri_ltor.end(), ri_ltor.begin());
1095# endif
1097#else
1098 auto const& csr = m_csr_local;
1099# ifdef AMREX_USE_MPI
1100 auto const& csr_r = m_csr_remote;
1101 auto const& ri_ltor = m_ri_ltor;
1102# endif
1103#endif
1104
1105 for (Long i = 0; i < nrows; ++i) {
1106 for (Long j = csr.row_offset[i]; j < csr.row_offset[i+1]; ++j) {
1107 ofs << i+m_row_begin << " " << Long(csr.col_index[j])+m_col_begin
1108 << " " << csr.mat[j] << "\n";
1109 }
1110#ifdef AMREX_USE_MPI
1111 if (i < Long(ri_ltor.size()) && ri_ltor[i] >= 0) {
1112 Long ii = ri_ltor[i];
1113 for (Long j = csr_r.row_offset[ii]; j < csr_r.row_offset[ii+1]; ++j) {
1114 ofs << i+m_row_begin << " " << m_remote_cols_v[csr_r.col_index[j]]
1115 << " " << csr_r.mat[j] << "\n";
1116 }
1117 }
1118#endif
1119 }
1120}
1121
1122template <typename T, template<typename> class Allocator>
1123template <typename F>
1125{
1126 // xxxxx TODO: We can try to optimize this later by using shared memory.
1127
1128 AMREX_ALWAYS_ASSERT(m_split == false);
1129 m_diagonal = AlgVector<T,AllocT>{};
1130
1131 Long nlocalrows = this->numLocalRows();
1132 Long rowbegin = this->globalRowBegin();
1133 auto* pmat = m_csr.mat.data();
1134 auto* pcolindex = m_csr.col_index.data();
1135 auto* prowoffset = m_csr.row_offset.data();
1136 ParallelForOMP(nlocalrows, [=] AMREX_GPU_DEVICE (int lrow) noexcept
1137 {
1138 f(rowbegin+lrow, pcolindex+prowoffset[lrow], pmat+prowoffset[lrow]);
1139 });
1140
1141 if (! is_sorted) { m_csr.sort(); }
1142}
1143
1144template <typename T, template<typename> class Allocator>
1146{
1147 if (m_diagonal.empty()) {
1148 // Square matrix: global columns before the split, local block after.
1149 if (m_split) {
1150 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_col_partition == this->partition(),
1151 "SpMatrix::diagonalVector: column partition differs from row partition");
1152 }
1153 m_diagonal.define(this->partition());
1154 if (m_split) {
1155 detail::extract_diagonal(m_diagonal.data(), m_csr_local.const_view(), Long(0));
1156 } else {
1157 detail::extract_diagonal(m_diagonal.data(), m_csr.const_view(), m_row_begin);
1158 }
1159 }
1160 return m_diagonal;
1161}
1162
1163template <typename T, template<typename> class Allocator>
1165{
1166 AlgVector<T,Allocator<T>> r(this->partition());
1167 auto* p = r.data();
1168 if (!m_split) {
1169 auto const c = m_csr.const_view();
1171 {
1172 T s = 0;
1173 for (auto idx = c.row_offset[i]; idx < c.row_offset[i+1]; ++idx) {
1174 s += c.mat[idx];
1175 }
1176 p[i] = s;
1177 });
1178 return r;
1179 }
1180 auto const& a = this->const_parcsr();
1182 {
1183 T s = 0;
1184 for (auto idx = a.csr0.row_offset[i];
1185 idx < a.csr0.row_offset[i+1]; ++idx) {
1186 s += a.csr0.mat[idx];
1187 }
1188 if (detail::has_remote_row(a, i)) {
1189 auto ii = a.row_map[i];
1190 for (auto idx = a.csr1.row_offset[ii];
1191 idx < a.csr1.row_offset[ii+1]; ++idx) {
1192 s += a.csr1.mat[idx];
1193 }
1194 }
1195 p[i] = s;
1196 });
1197 return r;
1198}
1199
1200template <typename T, template<typename> class Allocator>
1202{
1203 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_split, "SpMatrix::parcsr: column partition not set");
1204 return ParCsr<T>{m_csr_local.view(),
1205#ifdef AMREX_USE_MPI
1206 m_csr_remote.view(),
1207#else
1209#endif
1210 m_row_begin,
1211 m_col_begin,
1212#ifdef AMREX_USE_MPI
1213 m_ri_ltor.data(),
1214# ifdef AMREX_USE_GPU
1215 m_remote_cols_dv.data()
1216# else
1217 m_remote_cols_v.data()
1218# endif
1219#else
1220 nullptr, nullptr
1221#endif
1222 };
1223}
1224
1225template <typename T, template<typename> class Allocator>
1227{
1228 using U = T const;
1229 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_split, "SpMatrix::parcsr: column partition not set");
1230 return ParCsr<U>{m_csr_local.const_view(),
1231#ifdef AMREX_USE_MPI
1232 m_csr_remote.const_view(),
1233#else
1235#endif
1236 m_row_begin,
1237 m_col_begin,
1238#ifdef AMREX_USE_MPI
1239 m_ri_ltor.data(),
1240# ifdef AMREX_USE_GPU
1241 m_remote_cols_dv.data()
1242# else
1243 m_remote_cols_v.data()
1244# endif
1245#else
1246 nullptr, nullptr
1247#endif
1248 };
1249}
1250
1251template <typename T, template<typename> class Allocator>
1253{
1254 return this->const_parcsr();
1255}
1256
1257template <typename T, template<typename> class Allocator>
1259{
1260 BL_PROFILE("SpMatrix::startComm_mv");
1261#ifndef AMREX_USE_MPI
1262 amrex::ignore_unused(this, x);
1263#else
1264 if (detail::spmat_comm_is_local(this->partition(), x.partition())) { return; }
1265
1266 this->prepare_comm_mv(x.partition());
1267
1268 auto const mpi_tag = ParallelDescriptor::SeqNum();
1269 auto const mpi_t_type = ParallelDescriptor::Mpi_typemap<T>::type();
1270 auto const mpi_comm = ParallelContext::CommunicatorSub();
1271
1272 auto const nrecvs = int(m_comm_mv.recv_from.size());
1273 if (nrecvs > 0) {
1274 m_comm_mv.recv_buffer = (T*)The_Comms_Arena()->alloc(sizeof(T)*m_comm_mv.total_counts_recv);
1275 m_comm_mv.recv_reqs.resize(nrecvs, MPI_REQUEST_NULL);
1276 auto* p_recv = m_comm_mv.recv_buffer;
1277 for (int irecv = 0; irecv < nrecvs; ++irecv) {
1278 BL_MPI_REQUIRE(MPI_Irecv(p_recv,
1279 m_comm_mv.recv_counts[irecv], mpi_t_type,
1280 m_comm_mv.recv_from[irecv], mpi_tag, mpi_comm,
1281 &(m_comm_mv.recv_reqs[irecv])));
1282 p_recv += m_comm_mv.recv_counts[irecv];
1283 }
1284 AMREX_ASSERT(p_recv == m_comm_mv.recv_buffer + m_comm_mv.total_counts_recv);
1285 }
1286
1287 auto const nsends = int(m_comm_mv.send_to.size());
1288 if (nsends > 0) {
1289 m_comm_mv.send_buffer = (T*)The_Comms_Arena()->alloc(sizeof(T)*m_comm_mv.total_counts_send);
1290
1291 pack_buffer_mv(x);
1293
1294 m_comm_mv.send_reqs.resize(nsends, MPI_REQUEST_NULL);
1295 auto* p_send = m_comm_mv.send_buffer;
1296 for (int isend = 0; isend < nsends; ++isend) {
1297 auto count = m_comm_mv.send_counts[isend];
1298 BL_MPI_REQUIRE(MPI_Isend(p_send, count, mpi_t_type, m_comm_mv.send_to[isend],
1299 mpi_tag, mpi_comm, &(m_comm_mv.send_reqs[isend])));
1300 p_send += count;
1301 }
1302 AMREX_ASSERT(p_send == m_comm_mv.send_buffer + m_comm_mv.total_counts_send);
1303 }
1304#endif
1305}
1306
1307template <typename T, template<typename> class Allocator>
1309{
1310 BL_PROFILE("SpMatrix::finishComm_mv");
1311#ifndef AMREX_USE_MPI
1313#else
1314 if (detail::spmat_comm_is_local(this->partition(), m_col_partition)) { return; }
1315
1316 if ( ! m_comm_mv.recv_reqs.empty()) {
1317 Vector<MPI_Status> mpi_statuses(m_comm_mv.recv_reqs.size());
1318 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_mv.recv_reqs.size()),
1319 m_comm_mv.recv_reqs.data(),
1320 mpi_statuses.data()));
1321 }
1322
1323 unpack_buffer_mv(y);
1324
1325 if ( ! m_comm_mv.send_reqs.empty()) {
1326 Vector<MPI_Status> mpi_statuses(m_comm_mv.send_reqs.size());
1327 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_mv.send_reqs.size()),
1328 m_comm_mv.send_reqs.data(),
1329 mpi_statuses.data()));
1330 }
1331
1333 The_Comms_Arena()->free(m_comm_mv.send_buffer);
1334 The_Comms_Arena()->free(m_comm_mv.recv_buffer);
1335 m_comm_mv.send_reqs.clear();
1336 m_comm_mv.recv_reqs.clear();
1337#endif
1338}
1339
1340template <typename T, template<typename> class Allocator>
1341template <typename U>
1343{
1345 gatherRemote(x, r);
1346 return r;
1347}
1348
1349template <typename T, template<typename> class Allocator>
1350template <typename U>
1352{
1353 BL_PROFILE("SpMatrix::gatherRemote");
1354#ifndef AMREX_USE_MPI
1355 amrex::ignore_unused(this, x);
1356 r.clear();
1357#else
1358 AMREX_ALWAYS_ASSERT(m_split);
1359 if (detail::spmat_comm_is_local(this->partition(), m_col_partition)) { r.clear(); return; }
1360
1361 this->prepare_comm_mv(m_col_partition);
1362
1363 auto const mpi_tag = ParallelDescriptor::SeqNum();
1364 auto const mpi_type = ParallelDescriptor::Mpi_typemap<U>::type();
1365 auto const mpi_comm = ParallelContext::CommunicatorSub();
1366
1367 auto const nrecvs = int(m_comm_mv.recv_from.size());
1368 U* recv_buffer = nullptr;
1369 Vector<MPI_Request> recv_reqs(nrecvs, MPI_REQUEST_NULL);
1370 if (nrecvs > 0) {
1371 recv_buffer = (U*)The_Comms_Arena()->alloc(sizeof(U)*m_comm_mv.total_counts_recv);
1372 auto* p_recv = recv_buffer;
1373 for (int irecv = 0; irecv < nrecvs; ++irecv) {
1374 BL_MPI_REQUIRE(MPI_Irecv(p_recv, m_comm_mv.recv_counts[irecv], mpi_type,
1375 m_comm_mv.recv_from[irecv], mpi_tag, mpi_comm,
1376 &recv_reqs[irecv]));
1377 p_recv += m_comm_mv.recv_counts[irecv];
1378 }
1379 }
1380
1381 auto const nsends = int(m_comm_mv.send_to.size());
1382 U* send_buffer = nullptr;
1383 Vector<MPI_Request> send_reqs(nsends, MPI_REQUEST_NULL);
1384 if (nsends > 0) {
1385 send_buffer = (U*)The_Comms_Arena()->alloc(sizeof(U)*m_comm_mv.total_counts_send);
1386 auto* AMREX_RESTRICT pdst = send_buffer;
1387 auto const* AMREX_RESTRICT pidx = m_comm_mv.send_indices.data();
1388 auto const col_begin = m_col_begin;
1389 ParallelForOMP(m_comm_mv.total_counts_send, [=] AMREX_GPU_DEVICE (Long i)
1390 {
1391 pdst[i] = x[pidx[i]-col_begin];
1392 });
1394
1395 auto* p_send = send_buffer;
1396 for (int isend = 0; isend < nsends; ++isend) {
1397 auto count = m_comm_mv.send_counts[isend];
1398 BL_MPI_REQUIRE(MPI_Isend(p_send, count, mpi_type, m_comm_mv.send_to[isend],
1399 mpi_tag, mpi_comm, &send_reqs[isend]));
1400 p_send += count;
1401 }
1402 }
1403
1404 r.resize(m_comm_mv.total_counts_recv);
1405 if (nrecvs > 0) {
1406 Vector<MPI_Status> mpi_statuses(nrecvs);
1407 BL_MPI_REQUIRE(MPI_Waitall(nrecvs, recv_reqs.data(), mpi_statuses.data()));
1408 // The buffer may be pinned host memory, so copy with a kernel.
1409 auto* AMREX_RESTRICT pr = r.data();
1410 auto const* AMREX_RESTRICT pb = recv_buffer;
1411 ParallelForOMP(m_comm_mv.total_counts_recv, [=] AMREX_GPU_DEVICE (Long i)
1412 {
1413 pr[i] = pb[i];
1414 });
1415 }
1416 if (nsends > 0) {
1417 Vector<MPI_Status> mpi_statuses(nsends);
1418 BL_MPI_REQUIRE(MPI_Waitall(nsends, send_reqs.data(), mpi_statuses.data()));
1419 }
1420
1422 The_Comms_Arena()->free(send_buffer);
1423 The_Comms_Arena()->free(recv_buffer);
1424#endif
1425}
1426
1427template <typename T, template<typename> class Allocator>
1429{
1430 BL_PROFILE("SpMatrix::startComm_tr");
1431#ifdef AMREX_USE_MPI
1432 if (detail::spmat_comm_is_local(this->partition(), col_partition)) { return; }
1433
1434 this->split_csr(col_partition);
1435
1436 int const nprocs = ParallelContext::NProcsSub();
1437 auto const mpi_tag = ParallelDescriptor::SeqNum();
1438 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
1439 auto const mpi_t = ParallelDescriptor::Mpi_typemap<T>::type();
1440 auto const mpi_comm = ParallelContext::CommunicatorSub();
1441
1442 // transpose the off-diagonal part
1443 if (m_csr_remote.nnz > 0) {
1444 m_comm_tr.csrt.nnz = m_csr_remote.nnz;
1445 m_comm_tr.csrt.nrows = m_remote_cols_v.size();
1446 m_comm_tr.csrt.mat = (T*)The_Pinned_Arena()->alloc
1447 (sizeof(T)*m_comm_tr.csrt.nnz);
1448 m_comm_tr.csrt.col_index = (Long*)The_Pinned_Arena()->alloc
1449 (sizeof(Long)*m_comm_tr.csrt.nnz);
1450 m_comm_tr.csrt.row_offset = (Long*)The_Pinned_Arena()->alloc
1451 (sizeof(Long)*(m_comm_tr.csrt.nrows+1));
1452#ifdef AMREX_USE_GPU
1453 csr_type csr_comm;
1454 csr_comm.resize(m_comm_tr.csrt.nrows, m_comm_tr.csrt.nnz);
1455 auto const& csrv_comm = csr_comm.view();
1456#else
1457 auto const& csrv_comm = m_comm_tr.csrt;
1458#endif
1459 detail::transpose(csrv_comm, m_csr_remote.const_view());
1460 auto row_begin = m_row_begin;
1461 auto ri_rtol = m_ri_rtol.data();
1462 auto* col_index = csrv_comm.col_index;
1463 ParallelForOMP(csrv_comm.nnz, [=] AMREX_GPU_DEVICE (Long idx)
1464 {
1465 auto gjt =ri_rtol[col_index[idx]] + row_begin;
1466 col_index[idx] = gjt; // global index
1467 });
1468#ifdef AMREX_USE_GPU
1470 csrv_comm. mat,
1471 csrv_comm. mat + csrv_comm.nnz,
1472 m_comm_tr.csrt.mat);
1474 csrv_comm. col_index,
1475 csrv_comm. col_index + csrv_comm.nnz,
1476 m_comm_tr.csrt.col_index);
1478 csrv_comm. row_offset,
1479 csrv_comm. row_offset + csrv_comm.nrows+1,
1480 m_comm_tr.csrt.row_offset);
1482#endif
1483 }
1484
1485 if (m_num_neighbors < 0) { set_num_neighbors(); }
1486
1487 // As a sender, I need to let other processes know that how many
1488 // elements I will send them.
1489
1490 Vector<MPI_Request> mpi_requests;
1491 mpi_requests.reserve(nprocs);
1492 if (m_csr_remote.nnz > 0) {
1493 Long it = 0;
1494 for (int iproc = 0; iproc < nprocs; ++iproc) {
1495 Long n = 0;
1496 for (Long i = 0; i < Long(m_remote_cols_vv[iproc].size()); ++i) {
1497 n += m_comm_tr.csrt.row_offset[it+1] - m_comm_tr.csrt.row_offset[it];
1498 ++it;
1499 }
1500 if (n > 0) {
1501 mpi_requests.push_back(MPI_REQUEST_NULL);
1502 AMREX_ALWAYS_ASSERT(n < std::numeric_limits<int>::max());
1503 std::array<int,2> nn{int(n), int(m_remote_cols_vv[iproc].size())};
1504 BL_MPI_REQUIRE(MPI_Isend(nn.data(), 2, MPI_INT, iproc, mpi_tag,
1505 mpi_comm, &(mpi_requests.back())));
1506 m_comm_tr.send_to.push_back(iproc);
1507 m_comm_tr.send_counts.push_back(nn);
1508 }
1509 }
1510 }
1511
1512 // As a receiver, m_num_neighbors is the number of processes from which
1513 // I will receive data.
1514
1515 for (int irecv = 0; irecv < m_num_neighbors; ++irecv) {
1516 MPI_Status mpi_status;
1517 BL_MPI_REQUIRE(MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &mpi_status));
1518 int sender = mpi_status.MPI_SOURCE;
1519 std::array<int,2> nn;
1520 BL_MPI_REQUIRE(MPI_Recv(nn.data(), 2, MPI_INT, sender, mpi_tag,
1521 mpi_comm, &mpi_status));
1522 m_comm_tr.recv_from.push_back(sender);
1523 m_comm_tr.recv_counts.push_back(nn);
1524 m_comm_tr.total_counts_recv[0] += nn[0];
1525 m_comm_tr.total_counts_recv[1] += nn[1];
1526 }
1527
1528 if (! mpi_requests.empty()) {
1529 Vector<MPI_Status> mpi_statuses(mpi_requests.size());
1530 BL_MPI_REQUIRE(MPI_Waitall(int(mpi_requests.size()), mpi_requests.data(),
1531 mpi_statuses.data()));
1532 }
1533
1534 auto const mpi_tag_m = ParallelDescriptor::SeqNum();
1535 auto const mpi_tag_c = ParallelDescriptor::SeqNum();
1536 auto const mpi_tag_r = ParallelDescriptor::SeqNum();
1537 auto const mpi_tag_p = ParallelDescriptor::SeqNum();
1538
1539 // We need to send m_comm_tr.csrt.mat, col_index & row_offset. We also
1540 // need to send m_remote_cols_vv, which maps row index (in transposed
1541 // matrix) form local to global.
1542
1543 auto const nrecvs = int(m_comm_tr.recv_from.size());
1544 if (nrecvs > 0) {
1545 m_comm_tr.recv_buffer_mat = (T*) The_Pinned_Arena()->alloc
1546 (sizeof(T) * m_comm_tr.total_counts_recv[0]);
1547 m_comm_tr.recv_buffer_col_index = (Long*) The_Pinned_Arena()->alloc
1548 (sizeof(Long) * m_comm_tr.total_counts_recv[0]);
1549 m_comm_tr.recv_buffer_row_offset = (Long*) The_Pinned_Arena()->alloc
1550 (sizeof(Long) * (m_comm_tr.total_counts_recv[1]+nrecvs));
1551 m_comm_tr.recv_buffer_idx_map = (Long*) The_Pinned_Arena()->alloc
1552 (sizeof(Long) * m_comm_tr.total_counts_recv[1]);
1553 m_comm_tr.recv_buffer_offset.push_back({0,0,0,0});
1554 m_comm_tr.recv_reqs.resize(4*nrecvs, MPI_REQUEST_NULL);
1555 for (int irecv = 0; irecv < nrecvs; ++irecv) {
1556 auto [os0, os1, os2, os3] = m_comm_tr.recv_buffer_offset.back();
1557 auto [n0, n1] = m_comm_tr.recv_counts[irecv];
1558 auto recv_from_rank = m_comm_tr.recv_from[irecv];
1559 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_mat + os0,
1560 n0,
1561 mpi_t,
1562 recv_from_rank,
1563 mpi_tag_m,
1564 mpi_comm,
1565 &(m_comm_tr.recv_reqs[irecv*4])));
1566 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_col_index + os1,
1567 n0,
1568 mpi_long,
1569 recv_from_rank,
1570 mpi_tag_c,
1571 mpi_comm,
1572 &(m_comm_tr.recv_reqs[irecv*4+1])));
1573 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_row_offset + os2,
1574 n1+1,
1575 mpi_long,
1576 recv_from_rank,
1577 mpi_tag_r,
1578 mpi_comm,
1579 &(m_comm_tr.recv_reqs[irecv*4+2])));
1580 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_idx_map + os3,
1581 n1,
1582 mpi_long,
1583 recv_from_rank,
1584 mpi_tag_p,
1585 mpi_comm,
1586 &(m_comm_tr.recv_reqs[irecv*4+3])));
1587 m_comm_tr.recv_buffer_offset.push_back({os0 + n0,
1588 os1 + n0,
1589 os2 + n1+1,
1590 os3 + n1});
1591 }
1592 }
1593
1594 auto const nsends = int(m_comm_tr.send_to.size());
1595 if (nsends > 0) {
1596 m_comm_tr.send_reqs.resize(4*nsends, MPI_REQUEST_NULL);
1597 Long os0 = 0, os1 = 0;
1598 for (int isend = 0; isend < nsends; ++isend) {
1599 auto [n0, n1] = m_comm_tr.send_counts[isend];
1600 auto send_to_rank = m_comm_tr.send_to[isend];
1601 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.mat + os0,
1602 n0,
1603 mpi_t,
1604 send_to_rank,
1605 mpi_tag_m,
1606 mpi_comm,
1607 &(m_comm_tr.send_reqs[isend*4])));
1608 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.col_index + os0,
1609 n0,
1610 mpi_long,
1611 send_to_rank,
1612 mpi_tag_c,
1613 mpi_comm,
1614 &(m_comm_tr.send_reqs[isend*4+1])));
1615 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.row_offset + os1,
1616 n1+1,
1617 mpi_long,
1618 send_to_rank,
1619 mpi_tag_r,
1620 mpi_comm,
1621 &(m_comm_tr.send_reqs[isend*4+2])));
1622 BL_MPI_REQUIRE(MPI_Isend(m_remote_cols_vv[send_to_rank].data(),
1623 n1,
1624 mpi_long,
1625 send_to_rank,
1626 mpi_tag_p,
1627 mpi_comm,
1628 &(m_comm_tr.send_reqs[isend*4+3])));
1629 os0 += n0;
1630 os1 += n1;
1631 }
1632 }
1633#else
1634 amrex::ignore_unused(col_partition);
1635#endif
1636}
1637
1638template <typename T, template<typename> class Allocator>
1640{
1641 BL_PROFILE("SpMatrix::finishComm_tr");
1642#ifdef AMREX_USE_MPI
1643 if (detail::spmat_comm_is_local(this->partition(), AT.partition())) { return; }
1644
1645 this->comm_tr_recv_wait();
1646
1647 AT.unpack_buffer_tr(m_comm_tr, this->m_partition);
1648
1649 this->comm_tr_clear();
1650#else
1652#endif
1653}
1654
1655#ifdef AMREX_USE_MPI
1656
1657template <typename T, template<typename> class Allocator>
1659{
1660 if (! m_comm_tr.recv_reqs.empty()) {
1661 Vector<MPI_Status> mpi_statuses(m_comm_tr.recv_reqs.size());
1662 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_tr.recv_reqs.size()),
1663 m_comm_tr.recv_reqs.data(),
1664 mpi_statuses.data()));
1665 }
1666}
1667
1668template <typename T, template<typename> class Allocator>
1670{
1671 if (! m_comm_tr.send_reqs.empty()) {
1672 Vector<MPI_Status> mpi_statuses(m_comm_tr.send_reqs.size());
1673 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_tr.send_reqs.size()),
1674 m_comm_tr.send_reqs.data(),
1675 mpi_statuses.data()));
1676 }
1677
1678 if (m_comm_tr.csrt.nnz > 0) {
1679 The_Pinned_Arena()->free(m_comm_tr.csrt.mat);
1680 The_Pinned_Arena()->free(m_comm_tr.csrt.col_index);
1681 The_Pinned_Arena()->free(m_comm_tr.csrt.row_offset);
1682 }
1683 if (m_comm_tr.recv_buffer_mat) {
1684 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_mat);
1685 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_col_index);
1686 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_row_offset);
1687 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_idx_map);
1688 }
1689 m_comm_tr = CommTR{};
1690}
1691
1692#endif
1693
1694template <typename T, template<typename> class Allocator>
1696 AlgPartition const& col_partition,
1697 local_csr_type&& compact, Long nlocal,
1698 Long const* remote_cols, Long nremote)
1699{
1700 BL_PROFILE("SpMatrix::define_split");
1701 AMREX_ALWAYS_ASSERT(m_split == false && m_col_partition.empty());
1702 amrex::ignore_unused(nremote);
1703
1704 m_partition = std::move(partition);
1705 m_row_begin = m_partition[ParallelContext::MyProcSub()];
1706 m_row_end = m_partition[ParallelContext::MyProcSub()+1];
1707 AMREX_ASSERT(compact.nrows() == this->numLocalRows());
1708 AMREX_ASSERT(nremote >= 0 && (nremote == 0 || remote_cols != nullptr));
1709 m_diagonal = AlgVector<T,AllocT>{};
1710 m_col_partition = col_partition;
1711 m_col_begin = col_partition[ParallelContext::MyProcSub()];
1712 m_col_end = col_partition[ParallelContext::MyProcSub()+1];
1713 AMREX_ALWAYS_ASSERT(m_col_end - m_col_begin == nlocal);
1714 m_nnz = compact.nnz;
1715 m_csr = csr_type{};
1716
1717 Long const nlocalrows = this->numLocalRows();
1718 Long const nnz = compact.nnz;
1719
1720#if defined(AMREX_USE_MPI) && !defined(AMREX_USE_GPU)
1721 if (nlocal < col_partition.numGlobalRows()) {
1722 // Host: the remote entries are copied out and the local ones are
1723 // moved forward in place, keeping the order of both.
1724 AMREX_ALWAYS_ASSERT(nlocal < Long(std::numeric_limits<int>::max()));
1725 int const nl = int(nlocal);
1726 int* AMREX_RESTRICT pro = compact.row_offset.data();
1727 int* AMREX_RESTRICT pcol = compact.col_index.data();
1728 T* AMREX_RESTRICT pmat = compact.mat.data();
1729 Long remote_nnz = 0;
1730 for (Long i = 0; i < nnz; ++i) { remote_nnz += (pcol[i] >= nl); }
1731 container_type<Long> remote_gcols(remote_nnz);
1732 container_type<int> remote_row_offset(nlocalrows+1);
1733 auto* AMREX_RESTRICT pcol_r = remote_gcols.data();
1734 auto* AMREX_RESTRICT pro_r = remote_row_offset.data();
1735 m_csr_remote.mat.resize(remote_nnz);
1736 auto* AMREX_RESTRICT pmat_r = m_csr_remote.mat.data();
1737 Long nloc = 0, nrem = 0;
1738 for (Long i = 0; i < nlocalrows; ++i) {
1739 Long const b = pro[i];
1740 Long const e = pro[i+1];
1741 pro[i] = int(nloc);
1742 pro_r[i] = int(nrem);
1743 for (Long idx = b; idx < e; ++idx) {
1744 int const c = pcol[idx];
1745 if (c < nl) {
1746 pcol[nloc] = c;
1747 pmat[nloc] = pmat[idx];
1748 ++nloc;
1749 } else {
1750 pcol_r[nrem] = remote_cols[c - nl];
1751 pmat_r[nrem] = pmat[idx];
1752 ++nrem;
1753 }
1754 }
1755 }
1756 pro[nlocalrows] = int(nloc);
1757 pro_r[nlocalrows] = int(nrem);
1758 compact.col_index.resize(nloc);
1759 compact.mat.resize(nloc);
1760 compact.nnz = nloc;
1761 m_csr_local = std::move(compact);
1762 if (remote_nnz > 0) {
1763 m_csr_remote.col_index.resize(remote_nnz);
1764 m_csr_remote.row_offset = std::move(remote_row_offset);
1765 m_csr_remote.nnz = remote_nnz;
1766 trim_remote_rows();
1767 } else {
1768 m_csr_remote.mat = container_type<T>{};
1769 }
1770 update_remote_col_index(remote_gcols, m_csr_remote.col_index, true);
1771 m_split = true;
1772 return;
1773 }
1774#endif
1775
1776 // Entries with a compact column below nlocal are local; within a row
1777 // they come first. No scan is needed if all columns are local.
1779 auto const* pcol = compact.col_index.data();
1780 AMREX_ALWAYS_ASSERT(nlocal < Long(std::numeric_limits<int>::max()));
1781 int const nl = int(nlocal);
1782 Long local_nnz = nnz;
1783 if (nlocal < col_partition.numGlobalRows()) {
1784 pfsum.resize(nnz);
1785 auto* p_pfsum = pfsum.data();
1786 local_nnz = Scan::PrefixSum<Long>(nnz,
1787 [=] AMREX_GPU_DEVICE (Long i) -> Long { return pcol[i] < nl; },
1788 [=] AMREX_GPU_DEVICE (Long i, Long const& x) { p_pfsum[i] = x; },
1790 }
1791 auto const* p_pfsum = pfsum.data();
1792 Long const remote_nnz = nnz - local_nnz;
1793
1794#ifndef AMREX_USE_MPI
1795 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(remote_nnz == 0,
1796 "SpMatrix::define_split: remote columns without MPI");
1797#endif
1798
1799 if (remote_nnz == 0) {
1800 m_csr_local = std::move(compact);
1801 } else {
1802 m_csr_local.resize(nlocalrows, local_nnz);
1803 container_type<Long> remote_gcols(remote_nnz);
1804 container_type<T> remote_mat(remote_nnz);
1805 container_type<int> remote_row_offset(nlocalrows+1);
1806 auto const* pmat = compact.mat.data();
1807 auto* pmat_l = m_csr_local.mat.data();
1808 auto* pcol_l = m_csr_local.col_index.data();
1809 auto* pmat_r = remote_mat.data();
1810 auto* pcol_r = remote_gcols.data();
1812 {
1813 auto ps = p_pfsum[i];
1814 if (pcol[i] < nl) {
1815 pmat_l[ps] = pmat[i];
1816 pcol_l[ps] = pcol[i];
1817 } else {
1818 pmat_r[i-ps] = pmat[i];
1819 pcol_r[i-ps] = remote_cols[pcol[i] - nl];
1820 }
1821 });
1822 auto const noffset = nlocalrows+1;
1823 auto const* pro = compact.row_offset.data();
1824 auto* pro_l = m_csr_local.row_offset.data();
1825 auto* pro_r = remote_row_offset.data();
1826 ParallelForOMP(noffset, [=] AMREX_GPU_DEVICE (Long i)
1827 {
1828 Long ro_l = (i < noffset-1)
1829 ? ((pro[i] < nnz) ? p_pfsum[pro[i]] : local_nnz) : local_nnz;
1830 pro_l[i] = int(ro_l);
1831 pro_r[i] = int(pro[i] - ro_l);
1832 });
1834 compact = local_csr_type{};
1835#ifdef AMREX_USE_MPI
1836 m_csr_remote.mat = std::move(remote_mat);
1837 m_csr_remote.col_index.resize(remote_nnz);
1838 m_csr_remote.row_offset = std::move(remote_row_offset);
1839 m_csr_remote.nnz = remote_nnz;
1840 trim_remote_rows();
1841 update_remote_col_index(remote_gcols, m_csr_remote.col_index, true);
1842#endif
1843 }
1844#ifdef AMREX_USE_MPI
1845 if (remote_nnz == 0) {
1846 // Keep the remote bookkeeping consistent with an empty block.
1847 update_remote_col_index(container_type<Long>{}, m_csr_remote.col_index, true);
1848 }
1849#endif
1850
1851 m_split = true;
1852}
1853
1854template <typename T, template<typename> class Allocator>
1856{
1857 BL_PROFILE("SpMatrix::split_csr");
1858 if (m_split) {
1860 (m_col_begin == col_partition[ParallelContext::MyProcSub()] &&
1861 m_col_end == col_partition[ParallelContext::MyProcSub()+1]);
1862 return;
1863 }
1864
1865 AMREX_ALWAYS_ASSERT(m_col_partition.empty());
1866
1867 m_col_partition = col_partition;
1868 m_col_begin = col_partition[ParallelContext::MyProcSub()];
1869 m_col_end = col_partition[ParallelContext::MyProcSub()+1];
1871 (m_col_end - m_col_begin < Long(std::numeric_limits<int>::max()),
1872 "SpMatrix: the local column block is too large for 32-bit indices");
1873
1874 // This function needs to be safe when nnz is zero.
1875
1876 // Split into a diagonal block with 32-bit local column indices and,
1877 // with MPI, an off-diagonal block with 32-bit indices into the sorted
1878 // list of remote columns.
1879
1880 Long const nlocalrows = this->numLocalRows();
1881
1882 if (m_col_begin == 0 && m_col_end == col_partition.numGlobalRows()) {
1883 // All columns are local: no scan, and the values are moved.
1884 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_nnz < Long(std::numeric_limits<int>::max()),
1885 "SpMatrix: too many local nonzeros for 32-bit row offsets");
1886 Long const nnz = m_nnz;
1887 Long const noffset = Long(m_csr.row_offset.size());
1888 m_csr_local.col_index.resize(nnz);
1889 m_csr_local.row_offset.resize(nlocalrows+1);
1890 auto const* pcol = m_csr.col_index.data();
1891 auto const* pro = m_csr.row_offset.data();
1892 auto* pcol_l = m_csr_local.col_index.data();
1893 auto* pro_l = m_csr_local.row_offset.data();
1894 ParallelForOMP(std::max(nnz, nlocalrows+1), [=] AMREX_GPU_DEVICE (Long i)
1895 {
1896 if (i < nnz) { pcol_l[i] = int(pcol[i]); }
1897 if (i <= nlocalrows) { pro_l[i] = (i < noffset) ? int(pro[i]) : 0; }
1898 });
1900 m_csr_local.mat = std::move(m_csr.mat);
1901 m_csr_local.nnz = m_nnz;
1902 m_csr = csr_type{};
1903#ifdef AMREX_USE_MPI
1904 update_remote_col_index(container_type<Long>{}, m_csr_remote.col_index, true);
1905#endif
1906 m_split = true;
1907 return;
1908 }
1909
1910#if defined(AMREX_USE_MPI) && !defined(AMREX_USE_GPU)
1911 {
1912 // Host: one pass per row; the local values are moved forward in place.
1913 Long const nnz = m_nnz;
1914 Long const noffset = Long(m_csr.row_offset.size());
1915 auto const* AMREX_RESTRICT pcol = m_csr.col_index.data();
1916 auto const* AMREX_RESTRICT pro = m_csr.row_offset.data();
1917 T* AMREX_RESTRICT pmat = m_csr.mat.data();
1918 Long remote_nnz = 0;
1919 for (Long i = 0; i < nnz; ++i) {
1920 remote_nnz += (pcol[i] < m_col_begin || pcol[i] >= m_col_end);
1921 }
1923 (nnz - remote_nnz < Long(std::numeric_limits<int>::max()) &&
1924 remote_nnz < Long(std::numeric_limits<int>::max()),
1925 "SpMatrix: too many local nonzeros for 32-bit row offsets");
1926 m_csr_local.col_index.resize(nnz - remote_nnz);
1927 m_csr_local.row_offset.resize(nlocalrows+1);
1928 container_type<Long> remote_gcols(remote_nnz);
1929 container_type<int> remote_row_offset(nlocalrows+1);
1930 container_type<T> remote_mat(remote_nnz);
1931 auto* AMREX_RESTRICT pcol_l = m_csr_local.col_index.data();
1932 auto* AMREX_RESTRICT pro_l = m_csr_local.row_offset.data();
1933 auto* AMREX_RESTRICT pcol_r = remote_gcols.data();
1934 auto* AMREX_RESTRICT pro_r = remote_row_offset.data();
1935 auto* AMREX_RESTRICT pmat_r = remote_mat.data();
1936 Long nloc = 0, nrem = 0;
1937 for (Long i = 0; i < nlocalrows; ++i) {
1938 pro_l[i] = int(nloc);
1939 pro_r[i] = int(nrem);
1940 if (i+1 >= noffset) { continue; }
1941 for (Long idx = pro[i]; idx < pro[i+1]; ++idx) {
1942 Long const c = pcol[idx];
1943 if (c >= m_col_begin && c < m_col_end) {
1944 pcol_l[nloc] = int(c - m_col_begin);
1945 pmat[nloc] = pmat[idx];
1946 ++nloc;
1947 } else {
1948 pcol_r[nrem] = c;
1949 pmat_r[nrem] = pmat[idx];
1950 ++nrem;
1951 }
1952 }
1953 }
1954 pro_l[nlocalrows] = int(nloc);
1955 pro_r[nlocalrows] = int(nrem);
1956 m_csr.mat.resize(nloc);
1957 m_csr_local.mat = std::move(m_csr.mat);
1958 m_csr_local.nnz = nloc;
1959 m_csr = csr_type{};
1960 if (remote_nnz > 0) {
1961 m_csr_remote.mat = std::move(remote_mat);
1962 m_csr_remote.col_index.resize(remote_nnz);
1963 m_csr_remote.row_offset = std::move(remote_row_offset);
1964 m_csr_remote.nnz = remote_nnz;
1965 trim_remote_rows();
1966 }
1967 update_remote_col_index(remote_gcols, m_csr_remote.col_index, true);
1968 m_split = true;
1969 return;
1970 }
1971#endif
1972
1973 Long local_nnz;
1974 Gpu::DeviceVector<Long> pfsum(m_nnz);
1975 auto* p_pfsum = pfsum.data();
1976 auto col_begin = m_col_begin;
1977 auto col_end = m_col_end;
1978 auto const* pcol = m_csr.col_index.data();
1979 if (m_csr.nnz < Long(std::numeric_limits<int>::max())) {
1980 local_nnz = Scan::PrefixSum<int>(int(m_nnz),
1981 [=] AMREX_GPU_DEVICE (int i) -> int {
1982 return (pcol[i] >= col_begin &&
1983 pcol[i] < col_end); },
1984 [=] AMREX_GPU_DEVICE (int i, int const& x) {
1985 p_pfsum[i] = x; },
1987 } else {
1988 local_nnz = Scan::PrefixSum<Long>(m_nnz,
1989 [=] AMREX_GPU_DEVICE (Long i) -> Long {
1990 return (pcol[i] >= col_begin &&
1991 pcol[i] < col_end); },
1992 [=] AMREX_GPU_DEVICE (Long i, Long const& x) {
1993 p_pfsum[i] = x; },
1995 }
1996 Long const remote_nnz = m_nnz - local_nnz;
1998 (local_nnz < Long(std::numeric_limits<int>::max()) &&
1999 remote_nnz < Long(std::numeric_limits<int>::max()),
2000 "SpMatrix: too many local nonzeros for 32-bit row offsets");
2001
2002 m_csr_local.resize(nlocalrows, local_nnz);
2003 container_type<Long> remote_gcols;
2004 container_type<T> remote_mat;
2005 container_type<int> remote_row_offset;
2006#ifdef AMREX_USE_MPI
2007 remote_gcols.resize(remote_nnz);
2008 remote_mat.resize(remote_nnz);
2009 remote_row_offset.resize(nlocalrows+1);
2010#else
2011 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(remote_nnz == 0,
2012 "SpMatrix: column index outside the column partition");
2013#endif
2014 {
2015 auto const* pmat = m_csr.mat.data();
2016 auto* pmat_l = m_csr_local.mat.data();
2017 auto* pcol_l = m_csr_local.col_index.data();
2018 auto* pmat_r = remote_mat.data();
2019 auto* pcol_r = remote_gcols.data();
2020 ParallelForOMP(m_nnz, [=] AMREX_GPU_DEVICE (Long i)
2021 {
2022 auto ps = p_pfsum[i];
2023 auto local = (pcol[i] >= col_begin && pcol[i] < col_end);
2024 if (local) {
2025 pmat_l[ps] = pmat[i];
2026 pcol_l[ps] = int(pcol[i] - col_begin);
2027 } else if (pmat_r) {
2028 pmat_r[i-ps] = pmat[i];
2029 pcol_r[i-ps] = pcol[i];
2030 }
2031 });
2032 auto const noffset = Long(m_csr.row_offset.size());
2033 auto const* pro = m_csr.row_offset.data();
2034 auto* pro_l = m_csr_local.row_offset.data();
2035 auto* pro_r = remote_row_offset.data();
2036 auto total_nnz = m_nnz;
2037 ParallelForOMP(noffset, [=] AMREX_GPU_DEVICE (Long i)
2038 {
2039 Long ro_l;
2040 if (i < noffset-1) {
2041 ro_l = (pro[i] < total_nnz) ? p_pfsum[pro[i]] : local_nnz;
2042 } else {
2043 ro_l = local_nnz;
2044 }
2045 pro_l[i] = int(ro_l);
2046 if (pro_r) { pro_r[i] = int(pro[i] - ro_l); }
2047 });
2049 }
2050 m_csr = csr_type{}; // the unsplit form is no longer needed
2051
2052#ifdef AMREX_USE_MPI
2053 // Without remote entries the remote block stays empty (no offsets).
2054 if (remote_nnz > 0) {
2055 m_csr_remote.mat = std::move(remote_mat);
2056 m_csr_remote.col_index.resize(remote_nnz);
2057 m_csr_remote.row_offset = std::move(remote_row_offset);
2058 m_csr_remote.nnz = remote_nnz;
2059 trim_remote_rows();
2060 }
2061 update_remote_col_index(remote_gcols, m_csr_remote.col_index, true);
2062#endif
2063
2064 m_split = true;
2065}
2066
2067#ifdef AMREX_USE_MPI
2068
2069template <typename T, template<typename> class Allocator>
2071{
2072 BL_PROFILE("SpMatrix::trim_remote_rows");
2073 // In the remote part, it's expected that some rows don't have
2074 // nonzeros. So we trim them off, and also save a full copy of the
2075 // row offsets.
2076 Long old_size = m_csr_remote.row_offset.size();
2077 m_ri_ltor.resize(old_size-1);
2078 m_ri_rtol.resize(old_size-1);
2079 auto* p_ltor = m_ri_ltor.data();
2080 auto* p_rtol = m_ri_rtol.data();
2081 container_type<int> trimmed_row_offset(old_size);
2082 auto const* p_ro = m_csr_remote.row_offset.data();
2083 auto* p_tro = trimmed_row_offset.data();
2084 // This is basically std::unique.
2085 Long new_size = Scan::PrefixSum<Long>(old_size,
2086 [=] AMREX_GPU_DEVICE (Long i) -> Long {
2087 if (i+1 < old_size) {
2088 return (p_ro[i+1] > p_ro[i]);
2089 } else {
2090 return 1;
2091 }
2092 },
2093 [=] AMREX_GPU_DEVICE (Long i, Long const& x) {
2094 if (i == 0) {
2095 p_tro[0] = 0;
2096 } else if (p_ro[i] > p_ro[i-1]) {
2097 p_tro[x] = p_ro[i];
2098 }
2099 if (i+1 < old_size) {
2100 if (p_ro[i+1] > p_ro[i]) {
2101 p_rtol[x] = i;
2102 p_ltor[i] = x;
2103 } else {
2104 p_ltor[i] = -1;
2105 }
2106 }
2107 },
2109 m_ri_rtol.resize(new_size-1);
2110 trimmed_row_offset.resize(new_size);
2111#ifdef AMREX_USE_GPU
2112 m_ri_rtol.shrink_to_fit();
2113 trimmed_row_offset.shrink_to_fit();
2114#endif
2115 m_remote_row_offset = std::move(trimmed_row_offset);
2116 std::swap(m_csr_remote.row_offset, m_remote_row_offset);
2117}
2118
2119template <typename T, template<typename> class Allocator>
2120template <typename VL, typename VI>
2121void SpMatrix<T,Allocator>::update_remote_col_index (VL const& gcols, VI& lcols,
2122 bool in_device_memory)
2123{
2124 int const nprocs = ParallelContext::NProcsSub();
2125
2126 // This function also needs to update m_remote_cols_*.
2127
2128 m_remote_cols_v.clear();
2129 m_remote_cols_vv.clear();
2130 m_remote_cols_vv.resize(nprocs);
2131#ifdef AMREX_USE_GPU
2132 m_remote_cols_dv.clear();
2133#endif
2134
2135 Long const n = Long(gcols.size());
2136 lcols.resize(n);
2137 if (n == 0) { return; }
2138
2139 amrex::ignore_unused(in_device_memory);
2140
2141 Vector<Long> h_gcols;
2142#ifdef AMREX_USE_GPU
2143 if (in_device_memory) {
2144 h_gcols.resize(n);
2145 Gpu::copyAsync(Gpu::deviceToHost, gcols.begin(), gcols.end(), h_gcols.begin());
2147 } else
2148#endif
2149 {
2150 h_gcols.assign(gcols.begin(), gcols.end());
2151 }
2152
2153 m_remote_cols_v = h_gcols;
2154 amrex::RemoveDuplicates(m_remote_cols_v);
2156 (Long(m_remote_cols_v.size()) < Long(std::numeric_limits<int>::max()),
2157 "SpMatrix: too many remote columns for 32-bit indices");
2158
2159#ifdef AMREX_USE_GPU
2160 m_remote_cols_dv.resize(m_remote_cols_v.size());
2162 m_remote_cols_v.begin(),
2163 m_remote_cols_v.end(),
2164 m_remote_cols_dv.data());
2165#endif
2166
2167 // Note that amrex::RemoveDuplicates sorts the data.
2168 auto const& cp = this->m_col_partition.dataVector();
2169 AMREX_ALWAYS_ASSERT(m_remote_cols_v.front() >= cp.front() &&
2170 m_remote_cols_v.back() < cp.back());
2171 auto it = cp.cbegin();
2172 for (auto c : m_remote_cols_v) {
2173 it = std::find_if(it, cp.cend(), [&] (auto x) { return x > c; });
2174 if (it != cp.cend()) {
2175 int iproc = int(std::distance(cp.cbegin(),it)) - 1;
2176 m_remote_cols_vv[iproc].push_back(c);
2177 } else {
2178 amrex::Abort("SpMatrix::update_remote_col_index: how did this happen?");
2179 }
2180 }
2181
2182 // Now we convert the remote indices from global to local.
2183 Gpu::PinnedVector<int> h_lcols(n);
2184 for (Long i = 0; i < n; ++i) {
2185 h_lcols[i] = int(std::lower_bound(m_remote_cols_v.begin(), m_remote_cols_v.end(),
2186 h_gcols[i]) - m_remote_cols_v.begin());
2187 }
2188
2189#ifdef AMREX_USE_GPU
2190 if (in_device_memory) {
2191 Gpu::copyAsync(Gpu::hostToDevice, h_lcols.begin(), h_lcols.end(), lcols.begin());
2193 } else
2194#endif
2195 {
2196 std::copy(h_lcols.begin(), h_lcols.end(), lcols.begin());
2197 }
2198}
2199
2200template <typename T, template<typename> class Allocator>
2202{
2203 if (m_num_neighbors >= 0) { return; }
2204
2205 int const nprocs = ParallelContext::NProcsSub();
2206 auto const mpi_int = ParallelDescriptor::Mpi_typemap<int>::type();
2207 auto const mpi_comm = ParallelContext::CommunicatorSub();
2208
2209 amrex::Vector<int> connection(nprocs);
2210 for (int iproc = 0; iproc < nprocs; ++iproc) {
2211 connection[iproc] = m_remote_cols_vv[iproc].empty() ? 0 : 1;
2212 }
2213 amrex::Vector<int> reduce_scatter_counts(nprocs,1);
2214 m_num_neighbors = 0;
2215 BL_MPI_REQUIRE(MPI_Reduce_scatter
2216 (connection.data(), &m_num_neighbors, reduce_scatter_counts.data(),
2217 mpi_int, MPI_SUM, mpi_comm));
2218}
2219
2220template <typename T, template<typename> class Allocator>
2222{
2223 BL_PROFILE("SpMatrix::prepare_comm_mv");
2224 if (m_comm_mv.prepared) { return; }
2225
2226 // This function needs to be safe when nnz is zero.
2227
2228 this->split_csr(col_partition);
2229
2230 int const nprocs = ParallelContext::NProcsSub();
2231 auto const mpi_tag = ParallelDescriptor::SeqNum();
2232 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
2233 auto const mpi_comm = ParallelContext::CommunicatorSub();
2234
2235 if (m_num_neighbors < 0) { set_num_neighbors(); }
2236
2237 Vector<MPI_Request> mpi_requests;
2238 mpi_requests.reserve(nprocs);
2239 for (int iproc = 0; iproc < nprocs; ++iproc) {
2240 if ( ! m_remote_cols_vv[iproc].empty()) {
2241 mpi_requests.push_back(MPI_REQUEST_NULL);
2242 auto const sz = m_remote_cols_vv[iproc].size();
2243 if (sz > static_cast<Long>(std::numeric_limits<int>::max())) {
2244 amrex::Abort("SpMatrix::prepare_comm_mv: remote column payload exceeds MPI int count range.");
2245 }
2246 auto const msg_count = static_cast<int>(sz);
2247 // I need to let other processes know what I need from them.
2248 BL_MPI_REQUIRE(MPI_Isend(m_remote_cols_vv[iproc].data(),
2249 msg_count,
2250 mpi_long, iproc, mpi_tag, mpi_comm,
2251 &(mpi_requests.back())));
2252 m_comm_mv.recv_from.push_back(iproc);
2253 m_comm_mv.recv_counts.push_back(msg_count);
2254 }
2255 }
2256
2257 m_comm_mv.total_counts_recv = Long(m_remote_cols_v.size());
2258
2259 Vector<Vector<Long>> send_indices(m_num_neighbors);
2260 m_comm_mv.total_counts_send = 0;
2261 for (int isend = 0; isend < m_num_neighbors; ++isend) {
2262 MPI_Status mpi_status;
2263 BL_MPI_REQUIRE(MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &mpi_status));
2264 int receiver = mpi_status.MPI_SOURCE;
2265 int count;
2266 BL_MPI_REQUIRE(MPI_Get_count(&mpi_status, mpi_long, &count));
2267 m_comm_mv.send_to.push_back(receiver);
2268 m_comm_mv.send_counts.push_back(count);
2269 send_indices[isend].resize(count);
2270 BL_MPI_REQUIRE(MPI_Recv(send_indices[isend].data(), count, mpi_long,
2271 receiver, mpi_tag, mpi_comm, &mpi_status));
2272 m_comm_mv.total_counts_send += count;
2273 }
2274
2275 m_comm_mv.send_indices.resize(m_comm_mv.total_counts_send);
2276 Gpu::PinnedVector<Long> send_indices_all;
2277 send_indices_all.reserve(m_comm_mv.total_counts_send);
2278 for (auto const& vl : send_indices) {
2279 for (auto x : vl) {
2280 send_indices_all.push_back(x);
2281 }
2282 }
2283 Gpu::copyAsync(Gpu::hostToDevice, send_indices_all.begin(), send_indices_all.end(),
2284 m_comm_mv.send_indices.begin());
2286
2287 if (! mpi_requests.empty()) {
2288 Vector<MPI_Status> mpi_statuses(mpi_requests.size());
2289 BL_MPI_REQUIRE(MPI_Waitall(int(mpi_requests.size()), mpi_requests.data(),
2290 mpi_statuses.data()));
2291 }
2292
2293 m_comm_mv.prepared = true;
2294}
2295
2296template <typename T, template<typename> class Allocator>
2298{
2299 auto* pdst = m_comm_mv.send_buffer;
2300 auto* pidx = m_comm_mv.send_indices.data();
2301 auto const& vv = v.view();
2302 auto const nsends = Long(m_comm_mv.send_indices.size());
2303 ParallelForOMP(nsends, [=] AMREX_GPU_DEVICE (Long i)
2304 {
2305 pdst[i] = vv(pidx[i]);
2306 });
2307}
2308
2309template <typename T, template<typename> class Allocator>
2311{
2312 auto const& csr = m_csr_remote;
2313 if (csr.nnz > 0) {
2314 T const* AMREX_RESTRICT mat = csr.mat.data();
2315 auto const* AMREX_RESTRICT col = csr.col_index.data();
2316 auto const* AMREX_RESTRICT row = csr.row_offset.data();
2317
2318 auto const* rtol = m_ri_rtol.data();
2319
2320 auto const* AMREX_RESTRICT px = m_comm_mv.recv_buffer;
2321 auto * AMREX_RESTRICT py = v.data();
2322
2323 auto const nrr = Long(csr.row_offset.size())-1;
2325 {
2326 T r = 0;
2327 for (Long j = row[i]; j < row[i+1]; ++j) {
2328 r += mat[j] * px[col[j]];
2329 }
2330 py[rtol[i]] += r;
2331 });
2332 }
2333}
2334
2335template <typename T, template<typename> class Allocator>
2337 AlgPartition const& col_partition)
2338{
2339 m_split = true;
2340 m_col_partition = col_partition;
2341 m_col_begin = m_col_partition[ParallelContext::MyProcSub() ];
2342 m_col_end = m_col_partition[ParallelContext::MyProcSub()+1];
2343
2344 m_ri_ltor.resize(numLocalRows(), -1);
2345 m_remote_cols_vv.resize(ParallelContext::NProcsSub());
2346
2347 auto nnz = ctr.total_counts_recv[0];
2348 if (nnz == 0) { return; }
2349 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(nnz < Long(std::numeric_limits<int>::max()),
2350 "SpMatrix: too many received nonzeros for 32-bit row offsets");
2351
2352 m_nnz += nnz;
2353 auto nb = int(ctr.recv_from.size()); // # of blocked CSRs to be merged
2354 auto total_local_rows = ctr.total_counts_recv[1];
2355
2356 // Build compressed row index map
2358 ctr.recv_buffer_idx_map + total_local_rows);
2359 RemoveDuplicates(ri_map);
2360 Long nrows = ri_map.size(); // # of unique rows.
2361
2362 // Assembled on the host with global columns, then converted to 32-bit
2363 // remote indices.
2364#ifdef AMREX_USE_GPU
2366#else
2367 auto& csrr = m_csr_remote;
2368#endif
2369 Vector<Long> gcols(nnz);
2370 csrr.mat.resize(nnz);
2371 csrr.col_index.resize(nnz);
2372 csrr.row_offset.resize(nrows+1);
2373 csrr.nnz = nnz;
2374
2375 // Merge blocks in sender-rank order so that rows stay sorted. Each
2376 // block covers a contiguous global column range.
2377 Vector<int> order(nb);
2378 std::iota(order.begin(), order.end(), 0);
2379 std::sort(order.begin(), order.end(), [&] (int a, int b) {
2380 return ctr.recv_from[a] < ctr.recv_from[b]; });
2381
2382 // Count nnz per compressed row
2383 Vector<int> row_nnz(nrows, 0);
2384 for (int i : order) {
2385 auto nrow_i = ctr.recv_counts[i][1];
2386 Long const* row_offset = ctr.recv_buffer_row_offset
2387 + ctr.recv_buffer_offset[i][2];
2388 Long const* idx_map = ctr.recv_buffer_idx_map
2389 + ctr.recv_buffer_offset[i][3];
2390 AMREX_ASSERT((row_offset[nrow_i] - row_offset[0]) == ctr.recv_counts[i][0]);
2391
2392 Long p = 0; // index into ri_map
2393 for (int lr = 0; lr < nrow_i; ++lr) {
2394 Long const gr = idx_map[lr];
2395 while (p < nrows && ri_map[p] < gr) { ++p; }
2396 AMREX_ASSERT(p < nrows && ri_map[p] == gr);
2397 // p is now compressed row index
2398 row_nnz[p] += int(row_offset[lr+1] - row_offset[lr]);
2399 }
2400 }
2401
2402 std::exclusive_scan(row_nnz.begin(), row_nnz.end(), csrr.row_offset.begin(), 0);
2403 csrr.row_offset.back() = int(csrr.nnz);
2404
2405 auto rowpos = csrr.row_offset; // make a copy to keep track of offset
2406
2407 for (int i : order) {
2408 auto nrow_i = ctr.recv_counts[i][1];
2409 T const* mat = ctr.recv_buffer_mat
2410 + ctr.recv_buffer_offset[i][0];
2411 Long const* col_index = ctr.recv_buffer_col_index
2412 + ctr.recv_buffer_offset[i][1];
2413 Long const* row_offset = ctr.recv_buffer_row_offset
2414 + ctr.recv_buffer_offset[i][2];
2415 Long const* idx_map = ctr.recv_buffer_idx_map
2416 + ctr.recv_buffer_offset[i][3];
2417
2418 Long p = 0; // index into ri_map
2419 for (int lr = 0; lr < nrow_i; ++lr) {
2420 Long const gr = idx_map[lr];
2421 while (p < nrows && ri_map[p] < gr) { ++p; }
2422 AMREX_ASSERT(p < nrows && ri_map[p] == gr);
2423 // p is now compressed row index
2424 auto os_src = row_offset[lr] - row_offset[0];
2425 auto nvals = row_offset[lr+1] - row_offset[lr];
2426 auto os_dst = rowpos[p];
2427 std::memcpy(csrr.mat.data()+os_dst, mat+os_src, sizeof(T)*nvals);
2428 std::memcpy(gcols.data()+os_dst, col_index+os_src, sizeof(Long)*nvals);
2429 rowpos[p] += int(nvals);
2430 }
2431 }
2432
2433 m_ri_rtol.resize(nrows);
2434 Gpu::copyAsync(Gpu::hostToDevice, ri_map.begin(), ri_map.end(), m_ri_rtol.begin());
2435 {
2436 auto row_begin = m_row_begin;
2437 auto* AMREX_RESTRICT ltor = m_ri_ltor.data();
2438 auto* AMREX_RESTRICT rtol = m_ri_rtol.data();
2439 ParallelForOMP(nrows, [=] AMREX_GPU_DEVICE (Long i) {
2440 rtol[i] -= row_begin;
2441 ltor[rtol[i]] = i;
2442 });
2443 }
2444
2445 update_remote_col_index(gcols, csrr.col_index, false);
2446
2447#ifdef AMREX_USE_GPU
2448 amrex::duplicateCSR(Gpu::hostToDevice, m_csr_remote, csrr);
2450#endif
2451}
2452
2453template <typename T, template<typename> class Allocator>
2455 -> RemoteRowsMM
2456{
2457 // this = A, split by B's row partition. m_comm_mv tells which rows of B
2458 // other ranks need from us and which we need from them.
2459 this->prepare_comm_mv(B.partition());
2460
2461 auto const& cm = m_comm_mv;
2462 auto const nrecvs = int(cm.recv_from.size());
2463 auto const nsends = int(cm.send_to.size());
2464 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
2465 auto const mpi_t = ParallelDescriptor::Mpi_typemap<T>::type();
2466 auto const mpi_comm = ParallelContext::CommunicatorSub();
2467
2468 RemoteRowsMM ext;
2469 ext.nrows = cm.total_counts_recv; // == m_remote_cols_v.size()
2470 ext.row_offset.resize(ext.nrows+1);
2471
2472 auto const b0 = B.m_csr_local.const_view();
2473 auto const b1 = B.remote_full_const_view();
2474 Long const b_row_begin = B.m_row_begin;
2475 Long const nsend_rows = cm.total_counts_send;
2476 auto const* AMREX_RESTRICT send_idx = cm.send_indices.data();
2477
2478 // Round 1: number of nonzeros in each requested row.
2479 Gpu::PinnedVector<Long> h_send(nsend_rows+1);
2480 {
2481 Gpu::DeviceVector<Long> d_cnt(nsend_rows);
2482 auto* pcnt = d_cnt.data();
2483 ParallelForOMP(nsend_rows, [=] AMREX_GPU_DEVICE (Long i)
2484 {
2485 Long const lr = send_idx[i] - b_row_begin;
2486 pcnt[i] = (b0.row_offset[lr+1] - b0.row_offset[lr])
2487 + (b1.row_offset[lr+1] - b1.row_offset[lr]);
2488 });
2489 Gpu::copyAsync(Gpu::deviceToHost, d_cnt.begin(), d_cnt.end(), h_send.begin());
2491 }
2492
2493 Gpu::PinnedVector<Long> h_recv(ext.nrows+1);
2494 {
2495 auto const tag = ParallelDescriptor::SeqNum();
2497 reqs.reserve(nrecvs+nsends);
2498 Long os = 0;
2499 for (int i = 0; i < nrecvs; ++i) {
2500 reqs.push_back(MPI_REQUEST_NULL);
2501 BL_MPI_REQUIRE(MPI_Irecv(h_recv.data()+os, cm.recv_counts[i], mpi_long,
2502 cm.recv_from[i], tag, mpi_comm, &reqs.back()));
2503 os += cm.recv_counts[i];
2504 }
2505 os = 0;
2506 for (int i = 0; i < nsends; ++i) {
2507 reqs.push_back(MPI_REQUEST_NULL);
2508 BL_MPI_REQUIRE(MPI_Isend(h_send.data()+os, cm.send_counts[i], mpi_long,
2509 cm.send_to[i], tag, mpi_comm, &reqs.back()));
2510 os += cm.send_counts[i];
2511 }
2512 if (! reqs.empty()) {
2513 Vector<MPI_Status> stats(reqs.size());
2514 BL_MPI_REQUIRE(MPI_Waitall(int(reqs.size()), reqs.data(), stats.data()));
2515 }
2516 }
2517
2518 // Counts to offsets, in place; per-rank totals.
2519 auto to_offsets = [] (Gpu::PinnedVector<Long>& v, Long n) {
2520 Long s = 0;
2521 for (Long i = 0; i < n; ++i) {
2522 Long const c = v[i];
2523 v[i] = s;
2524 s += c;
2525 }
2526 v[n] = s;
2527 };
2528 to_offsets(h_recv, ext.nrows);
2529 to_offsets(h_send, nsend_rows);
2530 ext.nnz = h_recv[ext.nrows];
2531 Long const send_nnz = h_send[nsend_rows];
2532
2533 Vector<Long> recv_nnz(nrecvs), send_nnz_v(nsends);
2534 for (int i = 0, r = 0; i < nrecvs; ++i) {
2535 recv_nnz[i] = h_recv[r+cm.recv_counts[i]] - h_recv[r];
2536 r += cm.recv_counts[i];
2537 if (recv_nnz[i] >= Long(std::numeric_limits<int>::max())) {
2538 amrex::Abort("SpMatrix::fetch_remote_rows_mm: message exceeds MPI int count range.");
2539 }
2540 }
2541 for (int i = 0, r = 0; i < nsends; ++i) {
2542 send_nnz_v[i] = h_send[r+cm.send_counts[i]] - h_send[r];
2543 r += cm.send_counts[i];
2544 if (send_nnz_v[i] >= Long(std::numeric_limits<int>::max())) {
2545 amrex::Abort("SpMatrix::fetch_remote_rows_mm: message exceeds MPI int count range.");
2546 }
2547 }
2548
2549 Gpu::copyAsync(Gpu::hostToDevice, h_recv.begin(), h_recv.end(), ext.row_offset.begin());
2550 Gpu::DeviceVector<Long> d_send_off(nsend_rows+1);
2551 Gpu::copyAsync(Gpu::hostToDevice, h_send.begin(), h_send.end(), d_send_off.begin());
2552
2553 // Round 2: column indices and values.
2554 auto const tag_c = ParallelDescriptor::SeqNum();
2555 auto const tag_m = ParallelDescriptor::SeqNum();
2556
2557 Vector<MPI_Request> rreqs, sreqs;
2558 if (ext.nnz > 0) {
2559 ext.col_index = (Long*)The_Pinned_Arena()->alloc(sizeof(Long)*ext.nnz);
2560 ext.mat = (T*)The_Comms_Arena()->alloc(sizeof(T)*ext.nnz);
2561 rreqs.reserve(2*nrecvs);
2562 Long os = 0;
2563 for (int i = 0; i < nrecvs; ++i) {
2564 if (recv_nnz[i] > 0) {
2565 auto n = int(recv_nnz[i]);
2566 rreqs.push_back(MPI_REQUEST_NULL);
2567 BL_MPI_REQUIRE(MPI_Irecv(ext.col_index+os, n, mpi_long, cm.recv_from[i],
2568 tag_c, mpi_comm, &rreqs.back()));
2569 rreqs.push_back(MPI_REQUEST_NULL);
2570 BL_MPI_REQUIRE(MPI_Irecv(ext.mat+os, n, mpi_t, cm.recv_from[i],
2571 tag_m, mpi_comm, &rreqs.back()));
2572 os += recv_nnz[i];
2573 }
2574 }
2575 }
2576
2577 Long* send_col = nullptr;
2578 T* send_mat = nullptr;
2579 if (send_nnz > 0) {
2580 send_col = (Long*)The_Comms_Arena()->alloc(sizeof(Long)*send_nnz);
2581 send_mat = (T*)The_Comms_Arena()->alloc(sizeof(T)*send_nnz);
2582 auto const* poff = d_send_off.data();
2583 Long const b_col_begin = B.m_col_begin;
2584#ifdef AMREX_USE_GPU
2585 auto const* b_rcols = B.m_remote_cols_dv.data();
2586#else
2587 auto const* b_rcols = B.m_remote_cols_v.data();
2588#endif
2589 // Merge the diagonal and off-diagonal parts so that each packed
2590 // row is sorted by global column index.
2591 ParallelForOMP(nsend_rows, [=] AMREX_GPU_DEVICE (Long i)
2592 {
2593 constexpr Long gmax = std::numeric_limits<Long>::max();
2594 Long const lr = send_idx[i] - b_row_begin;
2595 Long p = poff[i];
2596 Long q0 = b0.row_offset[lr];
2597 Long q1 = b1.row_offset[lr];
2598 Long const e0 = b0.row_offset[lr+1];
2599 Long const e1 = b1.row_offset[lr+1];
2600 while (q0 < e0 || q1 < e1) {
2601 Long const g0 = (q0 < e0) ? b0.col_index[q0] + b_col_begin : gmax;
2602 Long const g1 = (q1 < e1) ? b_rcols[b1.col_index[q1]] : gmax;
2603 if (g0 < g1) {
2604 send_col[p] = g0;
2605 send_mat[p] = b0.mat[q0];
2606 ++q0;
2607 } else {
2608 send_col[p] = g1;
2609 send_mat[p] = b1.mat[q1];
2610 ++q1;
2611 }
2612 ++p;
2613 }
2614 });
2616
2617 sreqs.reserve(2*nsends);
2618 Long os = 0;
2619 for (int i = 0; i < nsends; ++i) {
2620 if (send_nnz_v[i] > 0) {
2621 auto n = int(send_nnz_v[i]);
2622 sreqs.push_back(MPI_REQUEST_NULL);
2623 BL_MPI_REQUIRE(MPI_Isend(send_col+os, n, mpi_long, cm.send_to[i],
2624 tag_c, mpi_comm, &sreqs.back()));
2625 sreqs.push_back(MPI_REQUEST_NULL);
2626 BL_MPI_REQUIRE(MPI_Isend(send_mat+os, n, mpi_t, cm.send_to[i],
2627 tag_m, mpi_comm, &sreqs.back()));
2628 os += send_nnz_v[i];
2629 }
2630 }
2631 }
2632
2633 if (! rreqs.empty()) {
2634 Vector<MPI_Status> stats(rreqs.size());
2635 BL_MPI_REQUIRE(MPI_Waitall(int(rreqs.size()), rreqs.data(), stats.data()));
2636 }
2637 if (! sreqs.empty()) {
2638 Vector<MPI_Status> stats(sreqs.size());
2639 BL_MPI_REQUIRE(MPI_Waitall(int(sreqs.size()), sreqs.data(), stats.data()));
2640 }
2642 if (send_col) { The_Comms_Arena()->free(send_col); }
2643 if (send_mat) { The_Comms_Arena()->free(send_mat); }
2644
2645 return ext;
2646}
2647
2648template <typename T, template<typename> class Allocator>
2650{
2651 if (! m_remote_row_offset.empty()) { return; }
2652
2653 AMREX_ASSERT(m_split);
2654
2655 auto nrows = numLocalRows();
2656 m_remote_row_offset.resize(nrows+1);
2657 auto* pro_full = m_remote_row_offset.data();
2658 auto const* pro_comp = m_csr_remote.row_offset.data();
2659 auto const* ri_ltor = m_ri_ltor.data();
2660 auto nnz_r = m_csr_remote.nnz;
2661 if (nnz_r == 0 || m_ri_ltor.empty()) {
2662 // split_csr only builds m_ri_ltor when remote entries exist.
2663 ParallelForOMP(nrows+1, [=] AMREX_GPU_DEVICE (Long i) { pro_full[i] = 0; });
2665 } else {
2666 Scan::PrefixSum<int>(nrows,
2667 [=] AMREX_GPU_DEVICE (Long i) -> int {
2668 Long rrow = ri_ltor[i];
2669 if (rrow == -1) {
2670 return int(0);
2671 } else {
2672 return int(pro_comp[rrow+1]-pro_comp[rrow]);
2673 }},
2674 [=] AMREX_GPU_DEVICE (Long i, int x) {
2675 if (i == 0) {
2676 pro_full[0] = 0;
2677 }
2678 pro_full[i+1] = x;
2679 },
2681 }
2682}
2683
2684template <typename T, template<typename> class Allocator>
2686{
2687 if (m_remote_row_offset.empty()) {
2688 expand_remote_row_offset();
2689 }
2690 auto csr_view = m_csr_remote.const_view();
2691 csr_view.row_offset = m_remote_row_offset.data();
2692 csr_view.nrows = numLocalRows();
2693 return csr_view;
2694}
2695
2696#endif
2697
2698}
2699
2700#endif
#define BL_PROFILE(a)
Definition AMReX_BLProfiler.H:562
#define AMREX_ALWAYS_ASSERT_WITH_MESSAGE(EX, MSG)
Definition AMReX_BLassert.H:49
#define AMREX_ASSERT(EX)
Definition AMReX_BLassert.H:38
#define AMREX_ALWAYS_ASSERT(EX)
Definition AMReX_BLassert.H:50
#define AMREX_FORCE_INLINE
Definition AMReX_Extension.H:124
#define AMREX_RESTRICT
Definition AMReX_Extension.H:37
#define AMREX_CUSPARSE_SAFE_CALL(call)
Definition AMReX_GpuError.H:101
#define AMREX_GPU_ERROR_CHECK()
Definition AMReX_GpuError.H:151
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
#define AMREX_GPU_HOST_DEVICE
Definition AMReX_GpuQualifiers.H:20
Convenience header for the core AMReX GPU facilities.
Array4< int const > offset
Definition AMReX_HypreMLABecLap.cpp:1139
Real * pdst
Definition AMReX_HypreMLABecLap.cpp:1140
GpuArray< MultiArray4< Real const >, 3 > s
Definition AMReX_MLEBNodeFDLaplacian.cpp:214
Definition AMReX_AlgPartition.H:26
Long numGlobalRows() const
Total number of rows covered by the partition.
Definition AMReX_AlgPartition.H:55
bool empty() const
True if the partition contains no rows.
Definition AMReX_AlgPartition.H:45
Vector< Long > const & dataVector() const
Underlying array describing row offsets (size nproc+1).
Definition AMReX_AlgPartition.H:72
Distributed dense vector that mirrors the layout of an AlgPartition.
Definition AMReX_AlgVector.H:29
Long numLocalRows() const
Number of entries stored on this rank.
Definition AMReX_AlgVector.H:74
bool empty() const
True if the local storage holds zero entries.
Definition AMReX_AlgVector.H:68
T const * data() const
Definition AMReX_AlgVector.H:85
void define(Long global_size)
Resize/repartition the vector to span global_size rows.
Definition AMReX_AlgVector.H:255
Table1D< T const, Long > view() const
Definition AMReX_AlgVector.H:94
virtual void free(void *pt)=0
Free a previously allocated block pointed to by pt.
virtual void * alloc(std::size_t sz)=0
Allocate sz bytes from this arena.
Dynamically allocated vector for trivially copyable data.
Definition AMReX_PODVector.H:308
void reserve(size_type a_capacity, GrowthStrategy strategy=GrowthStrategy::Poisson)
Definition AMReX_PODVector.H:819
size_type size() const noexcept
Definition AMReX_PODVector.H:654
void shrink_to_fit()
Definition AMReX_PODVector.H:826
iterator begin() noexcept
Definition AMReX_PODVector.H:680
void resize(size_type a_new_size, GrowthStrategy strategy=GrowthStrategy::Poisson)
Definition AMReX_PODVector.H:734
iterator end() noexcept
Definition AMReX_PODVector.H:684
void clear() noexcept
Definition AMReX_PODVector.H:652
T * data() noexcept
Definition AMReX_PODVector.H:672
bool empty() const noexcept
Definition AMReX_PODVector.H:658
void push_back(const T &a_value)
Definition AMReX_PODVector.H:633
Distributed CSR matrix that manages storage and GPU-friendly partitions.
Definition AMReX_SpMatrix.H:65
void finishComm_tr(SpMatrix< T, Allocator > &AT)
Complete transpose communication, writing the assembled matrix into AT.
Definition AMReX_SpMatrix.H:1639
void split_csr(AlgPartition const &col_partition)
Split into local and remote blocks with 32-bit column indices.
Definition AMReX_SpMatrix.H:1855
void setColumnPartition(AlgPartition const &col_partition)
Set the column partition and split the matrix into local and remote blocks with 32-bit column indices...
Definition AMReX_SpMatrix.H:1019
Long globalRowBegin() const
Inclusive global index begin on this process.
Definition AMReX_SpMatrix.H:201
void define_and_filter_doit(T const *mat, Long const *col_index, Long nentries, Long const *row_offset)
Private helper (exposed for CUDA) that copies/filters CSR arrays into device storage.
Definition AMReX_SpMatrix.H:1027
Long * rowOffset()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:218
CsrView< T const, int > remote_full_const_view() const
Off-diagonal part with the full (untrimmed) row offsets.
Definition AMReX_SpMatrix.H:2685
void sortCSR()
Definition AMReX_SpMatrix.H:1011
void pack_buffer_mv(AlgVector< T, AllocT > const &v)
Definition AMReX_SpMatrix.H:2297
void unpack_buffer_mv(AlgVector< T, AllocT > &v)
Definition AMReX_SpMatrix.H:2310
void startComm_tr(AlgPartition const &col_partition)
Initiate communication required to build the transpose with column partition col_partition.
Definition AMReX_SpMatrix.H:1428
void comm_tr_clear()
Definition AMReX_SpMatrix.H:1669
void define_doit(int nnz_per_row)
Private helper (exposed for CUDA) that allocates fixed-connectivity matrices with nnz_per_row entries...
Definition AMReX_SpMatrix.H:943
T * data()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:206
Long numGlobalRows() const
Global row count.
Definition AMReX_SpMatrix.H:196
Gpu::DeviceVector< U > gatherRemote(U const *x)
Gather values at the columns of the off-diagonal block.
Definition AMReX_SpMatrix.H:1342
SpMatrix & operator=(SpMatrix const &)=delete
~SpMatrix()=default
T value_type
Definition AMReX_SpMatrix.H:67
RemoteRowsMM fetch_remote_rows_mm(SpMatrix< T, Allocator > const &B)
Definition AMReX_SpMatrix.H:2454
friend SpMatrix< U, M > SpGEMM(SpMatrix< U, M > const &A, SpMatrix< U, M > const &B, AlgPartition const &col_partition, F const &row_post)
Allocator< U > allocator_type
Definition AMReX_SpMatrix.H:68
Long globalRowEnd() const
Exclusive global index end on this process.
Definition AMReX_SpMatrix.H:203
void trim_remote_rows()
Definition AMReX_SpMatrix.H:2070
Long numLocalNonZeros() const
Number of nonzeros stored locally.
Definition AMReX_SpMatrix.H:198
struct amrex::SpMatrix::CommMV m_comm_mv
AlgVector< T, AllocT > rowSum() const
Sum the values in each local row and return the result as an AlgVector.
Definition AMReX_SpMatrix.H:1164
void define_split(AlgPartition partition, AlgPartition const &col_partition, local_csr_type &&compact, Long nlocal, Long const *remote_cols, Long nremote)
Define directly in split form from a CSR with compact 32-bit columns: c < nlocal is the local column ...
Definition AMReX_SpMatrix.H:1695
void update_remote_col_index(VL const &gcols, VI &lcols, bool in_device_memory)
Definition AMReX_SpMatrix.H:2121
AlgPartition const & columnPartition() const
Return the column partition used for matrix-vector and matrix-matrix multiplications.
Definition AMReX_SpMatrix.H:191
void printToFile(std::string const &file) const
Definition AMReX_SpMatrix.H:1065
ParCsr< T const > const_parcsr() const
Const-qualified alias of parcsr() for convenience.
Definition AMReX_SpMatrix.H:1226
SpMatrix()=default
SpMatrix(SpMatrix const &)=delete
ParCsr< T > parcsr()
Build GPU-friendly CSR views split into diagonal/off-diagonal blocks.
Definition AMReX_SpMatrix.H:1201
SpMatrix(SpMatrix &&)=default
Long numLocalRows() const
Number of rows owned by this rank.
Definition AMReX_SpMatrix.H:194
void define(AlgPartition partition, csr_type csr, CsrSorted is_sorted)
Define a default-constructed matrix from a given CSR.
Definition AMReX_SpMatrix.H:927
AlgPartition const & partition() const
Row partition describing how matrix rows are distributed across ranks.
Definition AMReX_SpMatrix.H:182
SpMatrix(AlgPartition partition, csr_type csr)
Construct a sparse matrix from a given Partition and CSR.
Definition AMReX_SpMatrix.H:908
friend SpMatrix< U, M > RAP(SpMatrix< U, M > const &R, SpMatrix< U, M > const &A, SpMatrix< U, M > const &P, AlgPartition const &col_partition)
AlgVector< T, AllocT > const & diagonalVector() const
Return diagonal elements in a square matrix.
Definition AMReX_SpMatrix.H:1145
Long * columnIndex()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:212
void comm_tr_recv_wait()
Definition AMReX_SpMatrix.H:1658
void define(AlgPartition partition, T const *mat, Long const *col_index, Long nentries, Long const *row_offset, CsrSorted is_sorted, CsrValid is_valid)
Define a default-constructed matrix from given CSR arrays.
Definition AMReX_SpMatrix.H:964
void finishComm_mv(AlgVector< T, AllocT > &y)
Finish halo exchanges and accumulate contributions into y.
Definition AMReX_SpMatrix.H:1308
Allocator< T > AllocT
Definition AMReX_SpMatrix.H:72
friend SpMatrix< U, M > SpGEMM(SpMatrix< U, M > const &A, SpMatrix< U, M > const &B, AlgPartition const &col_partition)
struct amrex::SpMatrix::CommTR m_comm_tr
void expand_remote_row_offset() const
Definition AMReX_SpMatrix.H:2649
void gatherRemote(U const *x, Gpu::DeviceVector< U > &r)
As above, into r (reused across calls).
Definition AMReX_SpMatrix.H:1351
void prepare_comm_mv(AlgPartition const &col_partition)
Definition AMReX_SpMatrix.H:2221
ParCsr< T const > parcsr() const
Const variant of parcsr().
Definition AMReX_SpMatrix.H:1252
void startComm_mv(AlgVector< T, AllocT > const &x)
Prepare halo exchanges for a subsequent SpMV using x as the source vector.
Definition AMReX_SpMatrix.H:1258
friend SpMatrix< U, M > transpose(SpMatrix< U, M > const &A, AlgPartition const &col_partition)
void unpack_buffer_tr(CommTR const &ctr, AlgPartition const &col_partition)
Definition AMReX_SpMatrix.H:2336
friend void SpMV(AlgVector< U, N > &y, SpMatrix< U, M > const &A, AlgVector< U, N > const &x)
SpMatrix(AlgPartition partition, int nnz_per_row)
Construct a sparse matrix with a fixed number of nonzeros per row.
Definition AMReX_SpMatrix.H:899
void setVal(F const &f, CsrSorted is_sorted)
Initialize matrix entries using a row-wise functor.
Definition AMReX_SpMatrix.H:1124
void define(AlgPartition partition, int nnz_per_row)
Allocate storage for a default-constructed matrix with a fixed number of nonzeros per row.
Definition AMReX_SpMatrix.H:917
This class is a thin wrapper around std::vector. Unlike vector, Vector::operator[] provides bound che...
Definition AMReX_Vector.H:29
Long size() const noexcept
Definition AMReX_Vector.H:54
amrex_long Long
Definition AMReX_INT.H:30
void ParallelForOMP(T n, L const &f) noexcept
Performance-portable kernel launch function with optional OpenMP threading.
Definition AMReX_GpuLaunch.H:328
Arena * The_Comms_Arena()
Definition AMReX_Arena.cpp:889
Arena * The_Pinned_Arena()
Definition AMReX_Arena.cpp:869
Arena * The_Async_Arena()
Definition AMReX_Arena.cpp:839
void copyAsync(HostToDevice, InIter begin, InIter end, OutIter result) noexcept
A host-to-device copy routine. Note this is just a wrapper around memcpy, so it assumes contiguous st...
Definition AMReX_GpuContainers.H:228
static constexpr DeviceToDevice deviceToDevice
Definition AMReX_GpuContainers.H:107
static constexpr DeviceToHost deviceToHost
Definition AMReX_GpuContainers.H:106
static constexpr HostToDevice hostToDevice
Definition AMReX_GpuContainers.H:105
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:310
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:291
MPI_Comm CommunicatorSub() noexcept
sub-communicator for current frame
Definition AMReX_ParallelContext.H:70
int MyProcSub() noexcept
my sub-rank in current frame
Definition AMReX_ParallelContext.H:76
int NProcsSub() noexcept
number of ranks in current frame
Definition AMReX_ParallelContext.H:74
int SeqNum() noexcept
Returns sequential message sequence numbers, usually used as tags for send/recv.
Definition AMReX_ParallelDescriptor.H:678
static constexpr struct amrex::Scan::Type::Exclusive exclusive
static constexpr struct amrex::Scan::Type::Inclusive inclusive
static constexpr RetSum noRetSum
Definition AMReX_Scan.H:35
static constexpr RetSum retSum
Definition AMReX_Scan.H:34
static constexpr int MPI_REQUEST_NULL
Definition AMReX_ccse-mpi.H:57
Definition AMReX_Amr.cpp:50
__host__ __device__ void ignore_unused(const Ts &...)
No-op helper that marks variables as intentionally unused.
Definition AMReX.H:273
amrex::ArenaAllocator< T > DefaultAllocator
Definition AMReX_GpuAllocators.H:205
void duplicateCSR(C c, CSR< T, AD, I > &dst, CSR< T, AS, I > const &src)
Definition AMReX_CSR.H:125
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
void Abort(const std::string &msg)
Print a fatal-error message to stderr and abort execution.
Definition AMReX.cpp:244
const int[]
Definition AMReX_BLProfiler.cpp:1665
void RemoveDuplicates(Vector< T > &vec)
Definition AMReX_Vector.H:210
CsrView< T const, I > const_view() const
Convenience alias for view() const.
Definition AMReX_CSR.H:97
V< I > row_offset
Definition AMReX_CSR.H:57
CsrView< T, I > view()
Mutable view of the underlying buffers.
Definition AMReX_CSR.H:82
Long nnz
Definition AMReX_CSR.H:58
V< I > col_index
Definition AMReX_CSR.H:56
void resize(Long num_rows, Long num_non_zeros)
Resize the storage to accommodate num_rows and num_non_zeros entries.
Definition AMReX_CSR.H:74
void sort()
Sort each row by column index. Uses GPU acceleration when possible.
Definition AMReX_CSR.H:146
V< T > mat
Definition AMReX_CSR.H:55
Sorted CSR means for each row the column indices are sorted.
Definition AMReX_SpMatrix.H:49
bool b
Definition AMReX_SpMatrix.H:50
Valid CSR means all entries are valid. It may be sorted ro unsorted.
Definition AMReX_SpMatrix.H:55
bool b
Definition AMReX_SpMatrix.H:56
Lightweight non-owning CSR view that can point to host or device buffers.
Definition AMReX_CSR.H:35
Long nnz
Definition AMReX_CSR.H:41
Definition AMReX_SpMatrix.H:39
Long const *__restrict__ col_map
Definition AMReX_SpMatrix.H:45
CsrView< T, int > csr1
Definition AMReX_SpMatrix.H:41
Long const *__restrict__ row_map
Definition AMReX_SpMatrix.H:44
Long col_begin
Definition AMReX_SpMatrix.H:43
Long row_begin
Definition AMReX_SpMatrix.H:42
CsrView< T, int > csr0
Definition AMReX_SpMatrix.H:40
Definition AMReX_SpMatrix.H:446
T * send_buffer
Definition AMReX_SpMatrix.H:455
bool prepared
Definition AMReX_SpMatrix.H:462
Vector< int > recv_counts
Definition AMReX_SpMatrix.H:452
Long total_counts_recv
Definition AMReX_SpMatrix.H:460
Vector< int > recv_from
Definition AMReX_SpMatrix.H:451
T * recv_buffer
Definition AMReX_SpMatrix.H:459
Vector< int > send_counts
Definition AMReX_SpMatrix.H:448
Long total_counts_send
Definition AMReX_SpMatrix.H:456
Gpu::DeviceVector< Long > send_indices
Definition AMReX_SpMatrix.H:449
Vector< MPI_Request > recv_reqs
Definition AMReX_SpMatrix.H:458
Vector< int > send_to
Definition AMReX_SpMatrix.H:447
Vector< MPI_Request > send_reqs
Definition AMReX_SpMatrix.H:454
Definition AMReX_SpMatrix.H:465
Vector< std::array< int, 2 > > send_counts
Definition AMReX_SpMatrix.H:469
Long * recv_buffer_col_index
Definition AMReX_SpMatrix.H:482
Vector< MPI_Request > send_reqs
Definition AMReX_SpMatrix.H:470
Vector< MPI_Request > recv_reqs
Definition AMReX_SpMatrix.H:474
Vector< int > send_to
Definition AMReX_SpMatrix.H:468
std::array< Long, 2 > total_counts_recv
Definition AMReX_SpMatrix.H:476
Long * recv_buffer_row_offset
Definition AMReX_SpMatrix.H:483
Vector< std::array< int, 2 > > recv_counts
Definition AMReX_SpMatrix.H:473
CsrView< T > csrt
Definition AMReX_SpMatrix.H:466
T * recv_buffer_mat
Definition AMReX_SpMatrix.H:481
Vector< std::array< Long, 4 > > recv_buffer_offset
Definition AMReX_SpMatrix.H:477
Vector< int > recv_from
Definition AMReX_SpMatrix.H:472
Long * recv_buffer_idx_map
Definition AMReX_SpMatrix.H:484
Rows of another matrix fetched for SpGEMM. Column indices are global.
Definition AMReX_SpMatrix.H:506
container_type< Long > row_offset
Definition AMReX_SpMatrix.H:507
RemoteRowsMM(RemoteRowsMM &&rhs) noexcept
Definition AMReX_SpMatrix.H:517
RemoteRowsMM(RemoteRowsMM const &)=delete
Long nnz
Definition AMReX_SpMatrix.H:511
~RemoteRowsMM()
Definition AMReX_SpMatrix.H:514
Long * col_index
Definition AMReX_SpMatrix.H:508
RemoteRowsMM & operator=(RemoteRowsMM const &)=delete
Long nrows
Definition AMReX_SpMatrix.H:510
void clear()
Definition AMReX_SpMatrix.H:533
T * mat
Definition AMReX_SpMatrix.H:509
Definition AMReX_ccse-mpi.H:55