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_Gpu.H>
9#include <AMReX_Scan.H>
10
11#if defined(AMREX_USE_CUDA)
12# include <cusparse.h>
13#elif defined(AMREX_USE_HIP)
14# include <rocsparse/rocsparse.h>
15#elif defined(AMREX_USE_SYCL)
16# include <mkl_version.h>
17# include <oneapi/mkl/spblas.hpp>
18#endif
19
20#include <fstream>
21#include <numeric>
22#include <string>
23#include <type_traits>
24#include <unordered_map>
25#include <utility>
26
27namespace amrex {
28
36template <typename T>
37struct ParCsr {
38 CsrView<T> csr0; // diagonal part
39 CsrView<T> csr1; // off-diagonal part
40 Long row_begin = 0; // global row index begin
41 Long col_begin = 0; // global col index begin
42 Long const* AMREX_RESTRICT row_map = nullptr; // mapping from csr0's row to csr1
43 Long const* AMREX_RESTRICT col_map = nullptr; // mapping from csr1's col to global
44};
45
47struct CsrSorted {
48 bool b = true;
49 explicit operator bool() const { return b; }
50};
51
53struct CsrValid {
54 bool b = true;
55 explicit operator bool() const { return b; }
56};
57
61template <typename T, template<typename> class Allocator = DefaultAllocator>
63{
64public:
65 using value_type = T;
66 template <class U> using allocator_type = Allocator<U>;
67 template <class U> using container_type = PODVector<U,Allocator<U> >;
69 using AllocT = Allocator<T>;
70
71 SpMatrix () = default;
72
91 SpMatrix (AlgPartition partition, int nnz_per_row);
92
104
105 SpMatrix (SpMatrix const&) = delete;
106 SpMatrix& operator= (SpMatrix const&) = delete;
107
108 SpMatrix (SpMatrix &&) = default;
110
111 ~SpMatrix () = default;
112
127 void define (AlgPartition partition, int nnz_per_row);
128
161 void define (AlgPartition partition, T const* mat, Long const* col_index,
162 Long nentries, Long const* row_offset, CsrSorted is_sorted,
163 CsrValid is_valid);
164
176 void define (AlgPartition partition, csr_type csr, CsrSorted is_sorted);
177
179 [[nodiscard]] AlgPartition const& partition () const { return m_partition; }
180
188 [[nodiscard]] AlgPartition const& columnPartition () const { return m_col_partition; }
189
191 [[nodiscard]] Long numLocalRows () const { return m_row_end - m_row_begin; }
193 [[nodiscard]] Long numGlobalRows () const { return m_partition.numGlobalRows(); }
195 [[nodiscard]] Long numLocalNonZeros () const { return m_nnz; }
196
198 [[nodiscard]] Long globalRowBegin () const { return m_row_begin; }
200 [[nodiscard]] Long globalRowEnd () const { return m_row_end; }
201
203 [[nodiscard]] T* data () {
204 AMREX_ALWAYS_ASSERT(m_split == false);
205 return m_csr.mat.data();
206 }
207
209 [[nodiscard]] Long* columnIndex () {
210 AMREX_ALWAYS_ASSERT(m_split == false);
211 return m_csr.col_index.data();
212 }
213
215 [[nodiscard]] Long* rowOffset () {
216 AMREX_ALWAYS_ASSERT(m_split == false);
217 return m_csr.row_offset.data();
218 }
219
220 /*
221 * \brief Print the matrix to files.
222 *
223 * Each process writes its local portion of the matrix to a separate
224 * file. This function is provided for debugging purpose. Using it in
225 * large-scale runs may overwhelm the file system.
226 *
227 * File format:
228 * - The first line contains three integers describing the local matrix:
229 * the global row begin index, the global row end index, and the
230 * number of local nonzeros.
231 * - This is followed by one line per nonzero entry. Each line contains:
232 * the global row index, global column index, and matrix value.
233 *
234 * \param file Base file name. The full name on process `i` is `{file}.{i}`.
235 */
236 void printToFile (std::string const& file) const;
237
261 template <typename F>
262 void setVal (F const& f, CsrSorted is_sorted);
263
266 void sortCSR ();
267
270 void setColumnPartition (AlgPartition const& col_partition);
271
273 [[nodiscard]] AlgVector<T,AllocT> const& diagonalVector () const;
274
281 [[nodiscard]] AlgVector<T,AllocT> rowSum () const;
282
288 [[nodiscard]] ParCsr<T > parcsr () ;
290 [[nodiscard]] ParCsr<T const> parcsr () const;
292 [[nodiscard]] ParCsr<T const> const_parcsr () const;
293
294 template <typename U, template<typename> class M, typename N> friend
296
297 template <typename U, template<typename> class M> friend
298 SpMatrix<U,M> transpose (SpMatrix<U,M> const& A, AlgPartition const& col_partition);
299
300 template <typename U, template<typename> class M> friend
302 AlgPartition const& col_partition);
303
304 template <typename U> friend class AMG;
305
307 void define_doit (int nnz_per_row);
308
318 template <typename I>
319 void define_and_filter_doit (T const* mat, Long const* col_index,
320 Long nentries, Long const* row_offset);
321
327 void startComm_mv (AlgVector<T,AllocT> const& x);
330
332 void startComm_tr (AlgPartition const& col_partition);
335
336private:
337
338 void set_num_neighbors ();
339
340 AlgPartition m_partition;
341 AlgPartition m_col_partition;
342 Long m_row_begin = 0;
343 Long m_row_end = 0;
344 Long m_col_begin = 0;
345 Long m_col_end = 0;
346 Long m_nnz = 0;
347 csr_type m_csr;
348
349 mutable AlgVector<T,AllocT> m_diagonal;
350
351 bool m_split = false; // Has the matrix been split into diagonal and off-diagonal parts?
352
353#ifdef AMREX_USE_MPI
354 csr_type m_csr_remote;
355
356 // The `row_offset` in m_csr_remote does not contain empty rows. The
357 // full list of row offsets is available in m_remote_row_offset, which
358 // is built on demand by expand_remote_row_offset.
359 mutable container_type<Long> m_remote_row_offset;
360
361 // It should be noted that m_csr and m_csr_remote may have different
362 // number of rows, because some rows may be purely local and they do not
363 // appear in m_csr_remote. Thus, we need a mapping from local row index
364 // in m_csr_remote to local row index in m_csr. Then we will be able to
365 // know its global row index by adding m_row_begin. For example,
366 // m_row_begin + m_ri_rtol[i] is the global index for local row i in
367 // m_csr_remote.
368 container_type<Long> m_ri_rtol; // size: m_csr_remote.nrows()
369
370 // This is local row index mapping from m_csr to m_csr_remote. -1 means
371 // the row does not exist in m_csr_remote.
372 container_type<Long> m_ri_ltor; // size: m_csr.nrows()
373
374 // For column index, we also need to be careful with local vs. global,
375 // and there two types of locals: local in m_csr and local in
376 // m_csr_remote. For m_csr, col_index is the global index if m_split is
377 // false, and it becomes the local index if m_split is true. The
378 // conversion is global_col_index = local_col_index + m_col_begin.
379 //
380 // For m_csr_remote, col_index is also local. For a give col_index j,
381 // m_remote_cols_v[j] gives us the global index.
382 Vector<Long> m_remote_cols_v;
383#ifdef AMREX_USE_GPU
384 container_type<Long> m_remote_cols_dv;
385#endif
386 // The size of outer vector is # of procs. The indices are global.
387 Vector<Vector<Long>> m_remote_cols_vv;
388
389 // No. of other processes involved in communication.
390 //
391 // For matrix-vector multiplication, this is the number of processes
392 // that require this process' data in the vector. This variable is
393 // stored in the matrix and it's set by calling its member function
394 // set_num_neighbors(). However, the data here are the vector's data.
395 //
396 // For matrix transpose, this is the number of processes that will send
397 // us their transposed data.
398 int m_num_neighbors = -1;
399
400public: // NOLINT Private functions, but public for cuda
401
420
421 struct CommTR {
422 CsrView<T> csrt; // Own the memory inside
423
427
431
432 std::array<Long,2> total_counts_recv = {0,0};
434 // The raw pointers below are owning.
435 // xxxxx TODO GPU: Currently we use pinned memory. In the future, we
436 // may explore device memory for GPU aware MPI.
437 T* recv_buffer_mat = nullptr;
440 Long* recv_buffer_idx_map = nullptr; // local -> global for row of A^T.
441
443
444 void split_csr (AlgPartition const& col_partition);
445 template <typename C>
446 void update_remote_col_index (C& csrr, bool in_device_memory);
447 void prepare_comm_mv (AlgPartition const& col_partition);
448 void pack_buffer_mv (AlgVector<T,AllocT> const& v);
450 void comm_tr_recv_wait();
451 void comm_tr_clear();
452 void unpack_buffer_tr (CommTR const& ctr, AlgPartition const& col_partition);
453 void expand_remote_row_offset () const;
456
460 Long* col_index = nullptr; // pinned memory
461 T* mat = nullptr; // comms arena
464
465 RemoteRowsMM () = default;
467 RemoteRowsMM (RemoteRowsMM const&) = delete;
469 RemoteRowsMM (RemoteRowsMM&& rhs) noexcept
470 : row_offset(std::move(rhs.row_offset)),
471 col_index(std::exchange(rhs.col_index, nullptr)),
472 mat(std::exchange(rhs.mat, nullptr)),
473 nrows(rhs.nrows), nnz(rhs.nnz) {}
475 if (this != &rhs) {
476 clear();
477 row_offset = std::move(rhs.row_offset);
478 col_index = std::exchange(rhs.col_index, nullptr);
479 mat = std::exchange(rhs.mat, nullptr);
480 nrows = rhs.nrows;
481 nnz = rhs.nnz;
482 }
483 return *this;
484 }
485 void clear () {
486 if (col_index || mat) {
489 if (mat) { The_Comms_Arena()->free(mat); }
490 col_index = nullptr;
491 mat = nullptr;
492 }
493 row_offset.clear();
494 nrows = 0;
495 nnz = 0;
496 }
497 };
498
501 RemoteRowsMM fetch_remote_rows_mm (SpMatrix<T,Allocator> const& B);
502
503#endif
504};
505
506namespace detail {
507
508// Communication may be skipped only when all rows and all columns live on
509// one common rank. Two partitions with a single active rank each may still
510// name different ranks.
511inline bool spmat_comm_is_local (AlgPartition const& row_partition,
512 AlgPartition const& col_partition)
513{
514 int const rp = row_partition.singleActiveProc();
515 int const cp = col_partition.singleActiveProc();
516 return row_partition.numActiveProcs() <= 1
517 && col_partition.numActiveProcs() <= 1
518 && (rp < 0 || cp < 0 || rp == cp);
519}
520
521template <typename T>
522void transpose (CsrView<T> const& csrt, CsrView<T const> const& csr)
523{
524 Long nrows = csr.nrows;
525 Long ncols = csrt.nrows;
526 Long nnz = csr.nnz;
527
528 if (nrows <= 0 || ncols <= 0 || nnz <= 0) {
529 auto* p = csrt.row_offset;
530 ParallelForOMP(ncols+1, [=] AMREX_GPU_DEVICE (Long i) { p[i] = 0; });
531 return;
532 }
533
534#ifdef AMREX_USE_GPU
535
536#if defined(AMREX_USE_CUDA)
537
538 cusparseHandle_t handle;
539 AMREX_CUSPARSE_SAFE_CALL(cusparseCreate(&handle));
540 AMREX_CUSPARSE_SAFE_CALL(cusparseSetStream(handle, Gpu::gpuStream()));
541
542 cudaDataType data_type;
543 if constexpr (std::is_same_v<T,float>) {
544 data_type = CUDA_R_32F;
545 } else if constexpr (std::is_same_v<T,double>) {
546 data_type = CUDA_R_64F;
547 } else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
548 data_type = CUDA_C_32F;
549 } else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
550 data_type = CUDA_C_64F;
551 } else {
552 amrex::Abort("SpMatrix transpose: unsupported data type");
553 }
554
555 AMREX_ALWAYS_ASSERT(ncols < Long(std::numeric_limits<int>::max()));
556
557 CsrIndex<int,Gpu::DeviceVector> ci, cit;
558 ci.copyFrom(csr);
559 cit.col_index.resize(csrt.nnz);
560 cit.row_offset.resize(csrt.nrows+1);
561 auto const* csr_col_index = ci.col_index.data();
562 auto const* csr_row_offset = ci.row_offset.data();
563 auto* csrt_col_index = cit.col_index.data();
564 auto* csrt_row_offset = cit.row_offset.data();
565
566 std::size_t buffer_size;
568 cusparseCsr2cscEx2_bufferSize(handle, int(nrows), int(ncols), int(nnz),
569 csr.mat, csr_row_offset, csr_col_index,
570 csrt.mat, csrt_row_offset, csrt_col_index,
571 data_type, CUSPARSE_ACTION_NUMERIC,
572 CUSPARSE_INDEX_BASE_ZERO,
573 CUSPARSE_CSR2CSC_ALG1,
574 &buffer_size));
575
576 auto* pbuffer = (void*)The_Arena()->alloc(buffer_size);
577
579 cusparseCsr2cscEx2(handle, int(nrows), int(ncols), int(nnz),
580 csr.mat, csr_row_offset, csr_col_index,
581 csrt.mat, csrt_row_offset, csrt_col_index,
582 data_type, CUSPARSE_ACTION_NUMERIC,
583 CUSPARSE_INDEX_BASE_ZERO,
584 CUSPARSE_CSR2CSC_ALG1,
585 pbuffer));
586
587 cit.copyTo(csrt);
588
590 AMREX_CUSPARSE_SAFE_CALL(cusparseDestroy(handle));
591 The_Arena()->free(pbuffer);
592
593#elif defined(AMREX_USE_HIP)
594
595 rocsparse_handle handle;
596 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_create_handle(&handle));
597 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_set_stream(handle, Gpu::gpuStream()));
598
599 constexpr bool same_int = (sizeof(rocsparse_int) == sizeof(Long));
600
601 rocsparse_int const* csr_col_index;
602 rocsparse_int const* csr_row_offset;
603 rocsparse_int* csrt_col_index;
604 rocsparse_int* csrt_row_offset;
605 CsrIndex<rocsparse_int,Gpu::DeviceVector> ci, cit;
606 if constexpr (same_int) {
607 csr_col_index = reinterpret_cast<rocsparse_int const*>(csr.col_index);
608 csr_row_offset = reinterpret_cast<rocsparse_int const*>(csr.row_offset);
609 csrt_col_index = reinterpret_cast<rocsparse_int*>(csrt.col_index);
610 csrt_row_offset = reinterpret_cast<rocsparse_int*>(csrt.row_offset);
611 } else {
612 AMREX_ALWAYS_ASSERT(ncols < Long(std::numeric_limits<rocsparse_int>::max()));
613 ci.copyFrom(csr);
614 cit.col_index.resize(csrt.nnz);
615 cit.row_offset.resize(csrt.nrows+1);
616 csr_col_index = ci.col_index.data();
617 csr_row_offset = ci.row_offset.data();
618 csrt_col_index = cit.col_index.data();
619 csrt_row_offset = cit.row_offset.data();
620 }
621
622 std::size_t buffer_size;
623 AMREX_ROCSPARSE_SAFE_CALL(
624 rocsparse_csr2csc_buffer_size(handle, rocsparse_int(nrows),
625 rocsparse_int(ncols), rocsparse_int(nnz),
626 csr_row_offset, csr_col_index,
627 rocsparse_action_numeric,
628 &buffer_size));
629
630 auto* pbuffer = (void*)The_Arena()->alloc(buffer_size);
631
632 if constexpr (std::is_same_v<T,float>) {
633 AMREX_ROCSPARSE_SAFE_CALL(
634 rocsparse_scsr2csc(handle, rocsparse_int(nrows),
635 rocsparse_int(ncols), rocsparse_int(nnz),
636 csr.mat, csr_row_offset, csr_col_index,
637 csrt.mat, csrt_col_index, csrt_row_offset,
638 rocsparse_action_numeric,
639 rocsparse_index_base_zero,
640 pbuffer));
641 } else if constexpr (std::is_same_v<T,double>) {
642 AMREX_ROCSPARSE_SAFE_CALL(
643 rocsparse_dcsr2csc(handle, rocsparse_int(nrows),
644 rocsparse_int(ncols), rocsparse_int(nnz),
645 csr.mat, csr_row_offset, csr_col_index,
646 csrt.mat, csrt_col_index, csrt_row_offset,
647 rocsparse_action_numeric,
648 rocsparse_index_base_zero,
649 pbuffer));
650 } else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
651 AMREX_ROCSPARSE_SAFE_CALL(
652 rocsparse_ccsr2csc(handle, rocsparse_int(nrows),
653 rocsparse_int(ncols), rocsparse_int(nnz),
654 (rocsparse_float_complex*)csr.mat, csr_row_offset, csr_col_index,
655 (rocsparse_float_complex*)csrt.mat, csrt_col_index, csrt_row_offset,
656 rocsparse_action_numeric,
657 rocsparse_index_base_zero,
658 pbuffer));
659 } else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
660 AMREX_ROCSPARSE_SAFE_CALL(
661 rocsparse_zcsr2csc(handle, rocsparse_int(nrows),
662 rocsparse_int(ncols), rocsparse_int(nnz),
663 (rocsparse_double_complex*)csr.mat, csr_row_offset, csr_col_index,
664 (rocsparse_double_complex*)csrt.mat, csrt_col_index, csrt_row_offset,
665 rocsparse_action_numeric,
666 rocsparse_index_base_zero,
667 pbuffer));
668 } else {
669 amrex::Abort("SpMatrix transpose: unsupported data type");
670 }
671
672 if constexpr (!same_int) {
673 cit.copyTo(csrt);
674 }
675
677 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_destroy_handle(handle));
678 The_Arena()->free(pbuffer);
679
680#elif defined(AMREX_USE_SYCL)
681
682 mkl::sparse::matrix_handle_t handle_in{};
683 mkl::sparse::matrix_handle_t handle_out{};
684 mkl::sparse::init_matrix_handle(&handle_in);
685 mkl::sparse::init_matrix_handle(&handle_out);
686
687#if defined(INTEL_MKL_VERSION) && (INTEL_MKL_VERSION < 20250300)
689 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_in, nrows, ncols,
690 mkl::index_base::zero, (Long*)csr.row_offset,
691 (Long*)csr.col_index, (T*)csr.mat);
692 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_out, ncols, nrows,
693 mkl::index_base::zero, (Long*)csrt.row_offset,
694 (Long*)csrt.col_index, (T*)csrt.mat);
695#else
696 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_in, nrows, ncols, nnz,
697 mkl::index_base::zero, (Long*)csr.row_offset,
698 (Long*)csr.col_index, (T*)csr.mat);
699 mkl::sparse::set_csr_data(Gpu::Device::streamQueue(), handle_out, ncols, nrows, nnz,
700 mkl::index_base::zero, (Long*)csrt.row_offset,
701 (Long*)csrt.col_index, (T*)csrt.mat);
702#endif
703
704 mkl::sparse::omatcopy(Gpu::Device::streamQueue(), mkl::transpose::trans,
705 handle_in, handle_out);
706
707 mkl::sparse::release_matrix_handle(Gpu::Device::streamQueue(), &handle_in);
708 auto ev = mkl::sparse::release_matrix_handle(Gpu::Device::streamQueue(), &handle_out);
709 ev.wait();
710
711#endif
712
714
715#else
716
717 auto* p = csrt.row_offset;
718
719 ParallelForOMP(ncols+1, [=] AMREX_GPU_DEVICE (Long i) { p[i] = 0; });
720
721 // nonzeros per column
722#ifdef AMREX_USE_OMP
723#pragma omp parallel for
724#endif
725 for (Long i = 0; i < nnz; ++i) {
726 auto col = csr.col_index[i];
727#ifdef AMREX_USE_OMP
728#pragma omp atomic update
729#endif
730 ++p[col+1];
731 }
732
733 // build row_offset for transposed matrix. Also save a copy.
734 Vector<Long> current_pos(ncols+1);
735 current_pos[0] = 0;
736 for (Long i = 0; i < ncols; ++i) {
737 p[i+1] += p[i];
738 current_pos[i+1] = p[i+1];
739 }
740
741 // The following code is not OMP safe. It's difficult to use OMP and
742 // still keep CSR sorted.
743 for (Long i = 0; i < nrows; ++i) {
744 for (Long idx = csr.row_offset[i]; idx < csr.row_offset[i+1]; ++idx) {
745 auto col = csr.col_index[idx];
746 Long dest = current_pos[col]++;
747 csrt.mat[dest] = csr.mat[idx];
748 csrt.col_index[dest] = i;
749 }
750 }
751
752#endif
753}
754}
755
756template <typename T, template<typename> class Allocator>
758 : m_partition(std::move(partition)),
759 m_row_begin(m_partition[ParallelDescriptor::MyProc()]),
760 m_row_end(m_partition[ParallelDescriptor::MyProc()+1])
761{
762 define_doit(nnz_per_row);
763}
764
765template <typename T, template<typename> class Allocator>
767 : m_partition(std::move(partition)),
768 m_row_begin(m_partition[ParallelDescriptor::MyProc()]),
769 m_row_end(m_partition[ParallelDescriptor::MyProc()+1]),
770 m_nnz(csr.nnz),
771 m_csr(std::move(csr))
772{}
773
774template <typename T, template<typename> class Allocator>
775void SpMatrix<T,Allocator>::define (AlgPartition partition, int nnz_per_row)
776{
777 m_partition = std::move(partition);
778 m_row_begin = m_partition[ParallelDescriptor::MyProc()];
779 m_row_end = m_partition[ParallelDescriptor::MyProc()+1];
780 m_diagonal = AlgVector<T,AllocT>{};
781 define_doit(nnz_per_row);
782}
783
784template <typename T, template<typename> class Allocator>
786 CsrSorted is_sorted)
787{
788 AMREX_ALWAYS_ASSERT(m_split == false);
789
790 m_partition = std::move(partition);
791 m_row_begin = m_partition[ParallelDescriptor::MyProc()];
792 m_row_end = m_partition[ParallelDescriptor::MyProc()+1];
793 m_nnz = csr.nnz;
794 m_csr = std::move(csr);
795 m_diagonal = AlgVector<T,AllocT>{};
796 if (! is_sorted) { m_csr.sort(); }
797}
798
799template <typename T, template<typename> class Allocator>
800void
802{
803 AMREX_ALWAYS_ASSERT(m_split == false);
804
805 nnz_per_row = std::max(nnz_per_row, 0);
806 Long nlocalrows = this->numLocalRows();
807 m_nnz = nlocalrows*nnz_per_row;
808 m_csr.mat.resize(m_nnz);
809 m_csr.col_index.resize(m_nnz);
810 m_csr.row_offset.resize(nlocalrows+1);
811 m_csr.nnz = m_nnz;
812
813 auto* poffset = m_csr.row_offset.data();
814 ParallelForOMP(nlocalrows+1, [=] AMREX_GPU_DEVICE (Long lrow) noexcept
815 {
816 poffset[lrow] = lrow*nnz_per_row;
817 });
818}
819
820template <typename T, template<typename> class Allocator>
821void
823 Long const* col_index, Long nentries,
824 Long const* row_offset, CsrSorted is_sorted,
825 CsrValid is_valid)
826{
827 AMREX_ALWAYS_ASSERT(m_split == false);
828
829 m_partition = std::move(partition);
830 m_row_begin = m_partition[ParallelDescriptor::MyProc()];
831 m_row_end = m_partition[ParallelDescriptor::MyProc()+1];
832 m_diagonal = AlgVector<T,AllocT>{};
833
834 bool synced = false;
835
836 if (is_valid) {
837 m_nnz = nentries;
838 Long nlocalrows = this->numLocalRows();
839 m_csr.mat.resize(nentries);
840 m_csr.col_index.resize(nentries);
841 m_csr.row_offset.resize(nlocalrows+1);
842 m_csr.nnz = nentries;
843 Gpu::copyAsync(Gpu::deviceToDevice, mat, mat+nentries, m_csr.mat.begin());
844 Gpu::copyAsync(Gpu::deviceToDevice, col_index, col_index+nentries,
845 m_csr.col_index.begin());
846 Gpu::copyAsync(Gpu::deviceToDevice, row_offset, row_offset+nlocalrows+1,
847 m_csr.row_offset.begin());
848 } else {
849 if (nentries < Long(std::numeric_limits<int>::max())) {
850 define_and_filter_doit<int>(mat, col_index, nentries, row_offset);
851 } else {
852 define_and_filter_doit<Long>(mat, col_index, nentries, row_offset);
853 }
854 synced = true;
855 }
856
857 if (! is_sorted) {
858 m_csr.sort();
859 synced = true;
860 }
861
862 if (! synced) {
864 }
865}
866
867template <typename T, template<typename> class Allocator>
868void
870{
871 m_csr.sort();
872}
873
874template <typename T, template<typename> class Allocator>
875void
877{
878#ifdef AMREX_USE_MPI
879 split_csr(col_partition);
880#else
881 // Column indices stay global. Like split_csr, allow only one partition.
882 AMREX_ALWAYS_ASSERT(m_col_partition.empty() || m_col_partition == col_partition);
883 m_col_partition = col_partition;
884#endif
885}
886
887template <typename T, template<typename> class Allocator>
888template <typename I>
889void
891 Long nentries, Long const* row_offset)
892{
893 Gpu::DeviceVector<I> psum(nentries);
894 auto* ps = psum.data();
895 m_nnz = Scan::PrefixSum<I>(I(nentries),
896 [=] AMREX_GPU_DEVICE (I i) -> I {
897 return col_index[i] >= 0 && mat[i] != 0; },
898 [=] AMREX_GPU_DEVICE (I i, I x) {
899 ps[i] = x; },
901 Long nlocalrows = this->numLocalRows();
902 m_csr.mat.resize(m_nnz);
903 m_csr.col_index.resize(m_nnz);
904 m_csr.row_offset.resize(nlocalrows+1);
905 m_csr.nnz = m_nnz;
906 auto* pmat = m_csr.mat.data();
907 auto* pcol = m_csr.col_index.data();
908 auto* prow = m_csr.row_offset.data();
909 auto actual_nnz = m_nnz;
910 ParallelFor(std::max(nentries,nlocalrows+1), [=] AMREX_GPU_DEVICE (Long i)
911 {
912 if (i < nentries) {
913 if (col_index[i] >= 0 && mat[i] != 0) {
914 pmat[ps[i]] = mat[i];
915 pcol[ps[i]] = col_index[i];
916 }
917 }
918 if (i <= nlocalrows) {
919 prow[i] = (i < nlocalrows && row_offset[i] < nentries)
920 ? Long(ps[row_offset[i]]) : actual_nnz;
921 }
922 });
924}
925
926template <typename T, template<typename> class Allocator>
927void
928SpMatrix<T,Allocator>::printToFile (std::string const& file) const
929{
930#ifdef AMREX_USE_GPU
933
934# ifdef AMREX_USE_MPI
936 if (m_split) {
937 amrex::duplicateCSR(Gpu::deviceToHost, csr_r, m_csr_remote);
938 }
939
940 Gpu::PinnedVector<Long> ri_ltor(m_ri_ltor.size());
941 if (m_split) {
943 m_ri_ltor.begin(),
944 m_ri_ltor.end(),
945 ri_ltor.begin());
946 }
947 auto const& remote_cols = m_remote_cols_v;
948# endif
949
951
952#else
953
954 auto const& csr = m_csr;
955# ifdef AMREX_USE_MPI
956 auto const& csr_r = m_csr_remote;
957 auto const& ri_ltor = m_ri_ltor;
958 auto const& remote_cols = m_remote_cols_v;
959# endif
960
961#endif
962
963 Long nnz = m_csr.nnz;
964#ifdef AMREX_USE_MPI
965 nnz += m_csr_remote.nnz;
966#endif
967
968 std::ofstream ofs(file+"."+std::to_string(ParallelDescriptor::MyProc()));
969 ofs << m_row_begin << " " << m_row_end << " " << nnz << "\n";
970 for (Long i = 0, nrows = numLocalRows(); i < nrows; ++i) {
971 Long nnz_row = csr.row_offset[i+1] - csr.row_offset[i];
972 T const* mat = csr.mat.data() + csr.row_offset[i];
973 Long const* col = csr.col_index.data() + csr.row_offset[i];
974 for (Long j = 0; j < nnz_row; ++j) {
975 ofs << i+m_row_begin << " " << col[j]+m_col_begin << " " << mat[j] << "\n";
976 }
977#ifdef AMREX_USE_MPI
978 if (i < Long(ri_ltor.size()) && ri_ltor[i] >= 0) {
979 Long ii = ri_ltor[i];
980 nnz_row = csr_r.row_offset[ii+1] - csr_r.row_offset[ii];
981 mat = csr_r.mat.data() + csr_r.row_offset[ii];
982 col = csr_r.col_index.data() + csr_r.row_offset[ii];
983 for (Long j = 0; j < nnz_row; ++j) {
984 ofs << i+m_row_begin << " " << remote_cols[col[j]] << " " << mat[j] << "\n";
985 }
986 }
987#endif
988 }
989}
990
991template <typename T, template<typename> class Allocator>
992template <typename F>
993void SpMatrix<T,Allocator>::setVal (F const& f, CsrSorted is_sorted)
994{
995 // xxxxx TODO: We can try to optimize this later by using shared memory.
996
997 AMREX_ALWAYS_ASSERT(m_split == false);
998 m_diagonal = AlgVector<T,AllocT>{};
999
1000 Long nlocalrows = this->numLocalRows();
1001 Long rowbegin = this->globalRowBegin();
1002 auto* pmat = m_csr.mat.data();
1003 auto* pcolindex = m_csr.col_index.data();
1004 auto* prowoffset = m_csr.row_offset.data();
1005 ParallelForOMP(nlocalrows, [=] AMREX_GPU_DEVICE (int lrow) noexcept
1006 {
1007 f(rowbegin+lrow, pcolindex+prowoffset[lrow], pmat+prowoffset[lrow]);
1008 });
1009
1010 if (! is_sorted) { m_csr.sort(); }
1011}
1012
1013template <typename T, template<typename> class Allocator>
1015{
1016 if (m_diagonal.empty()) {
1017 m_diagonal.define(this->partition());
1018 auto* AMREX_RESTRICT p = m_diagonal.data();
1019 auto const* AMREX_RESTRICT mat = m_csr.mat.data();
1020 auto const* AMREX_RESTRICT col = m_csr.col_index.data();
1021 auto const* AMREX_RESTRICT row = m_csr.row_offset.data();
1022 auto offset = m_split ? Long(0) : m_row_begin; // assuming square matrix
1023 Long nrows = this->numLocalRows();
1024 ParallelForOMP(nrows, [=] AMREX_GPU_DEVICE (Long i)
1025 {
1026 T d = 0;
1027 for (Long j = row[i]; j < row[i+1]; ++j) {
1028 if (i == col[j] - offset) {
1029 d = mat[j];
1030 break;
1031 }
1032 }
1033 p[i] = d;
1034 });
1035 }
1036 return m_diagonal;
1037}
1038
1039template <typename T, template<typename> class Allocator>
1041{
1042 AlgVector<T,Allocator<T>> r(this->partition());
1043 auto* p = r.data();
1044 auto const& a = this->const_parcsr();
1046 {
1047 T s = 0;
1048 for (auto idx = a.csr0.row_offset[i];
1049 idx < a.csr0.row_offset[i+1]; ++idx) {
1050 s += a.csr0.mat[idx];
1051 }
1052 if (a.csr1.nnz > 0 && a.row_map[i] >= 0) {
1053 auto ii = a.row_map[i];
1054 for (auto idx = a.csr1.row_offset[ii];
1055 idx < a.csr1.row_offset[ii+1]; ++idx) {
1056 s += a.csr1.mat[idx];
1057 }
1058 }
1059 p[i] = s;
1060 });
1061 return r;
1062}
1063
1064template <typename T, template<typename> class Allocator>
1066{
1067 return ParCsr<T>{m_csr.view(),
1068#ifdef AMREX_USE_MPI
1069 m_csr_remote.view(),
1070#else
1071 CsrView<T>{},
1072#endif
1073 m_row_begin,
1074 m_col_begin,
1075#ifdef AMREX_USE_MPI
1076 m_ri_ltor.data(),
1077# ifdef AMREX_USE_GPU
1078 m_remote_cols_dv.data()
1079# else
1080 m_remote_cols_v.data()
1081# endif
1082#else
1083 nullptr, nullptr
1084#endif
1085 };
1086}
1087
1088template <typename T, template<typename> class Allocator>
1090{
1091 using U = T const;
1092 return ParCsr<U>{m_csr.const_view(),
1093#ifdef AMREX_USE_MPI
1094 m_csr_remote.const_view(),
1095#else
1096 CsrView<U>{},
1097#endif
1098 m_row_begin,
1099 m_col_begin,
1100#ifdef AMREX_USE_MPI
1101 m_ri_ltor.data(),
1102# ifdef AMREX_USE_GPU
1103 m_remote_cols_dv.data()
1104# else
1105 m_remote_cols_v.data()
1106# endif
1107#else
1108 nullptr, nullptr
1109#endif
1110 };
1111}
1112
1113template <typename T, template<typename> class Allocator>
1115{
1116 return this->const_parcsr();
1117}
1118
1119template <typename T, template<typename> class Allocator>
1121{
1122#ifndef AMREX_USE_MPI
1123 amrex::ignore_unused(this, x);
1124#else
1125 if (detail::spmat_comm_is_local(this->partition(), x.partition())) { return; }
1126
1127 this->prepare_comm_mv(x.partition());
1128
1129 auto const mpi_tag = ParallelDescriptor::SeqNum();
1130 auto const mpi_t_type = ParallelDescriptor::Mpi_typemap<T>::type();
1131 auto const mpi_comm = ParallelContext::CommunicatorSub();
1132
1133 auto const nrecvs = int(m_comm_mv.recv_from.size());
1134 if (nrecvs > 0) {
1135 m_comm_mv.recv_buffer = (T*)The_Comms_Arena()->alloc(sizeof(T)*m_comm_mv.total_counts_recv);
1136 m_comm_mv.recv_reqs.resize(nrecvs, MPI_REQUEST_NULL);
1137 auto* p_recv = m_comm_mv.recv_buffer;
1138 for (int irecv = 0; irecv < nrecvs; ++irecv) {
1139 BL_MPI_REQUIRE(MPI_Irecv(p_recv,
1140 m_comm_mv.recv_counts[irecv], mpi_t_type,
1141 m_comm_mv.recv_from[irecv], mpi_tag, mpi_comm,
1142 &(m_comm_mv.recv_reqs[irecv])));
1143 p_recv += m_comm_mv.recv_counts[irecv];
1144 }
1145 AMREX_ASSERT(p_recv == m_comm_mv.recv_buffer + m_comm_mv.total_counts_recv);
1146 }
1147
1148 auto const nsends = int(m_comm_mv.send_to.size());
1149 if (nsends > 0) {
1150 m_comm_mv.send_buffer = (T*)The_Comms_Arena()->alloc(sizeof(T)*m_comm_mv.total_counts_send);
1151
1152 pack_buffer_mv(x);
1154
1155 m_comm_mv.send_reqs.resize(nsends, MPI_REQUEST_NULL);
1156 auto* p_send = m_comm_mv.send_buffer;
1157 for (int isend = 0; isend < nsends; ++isend) {
1158 auto count = m_comm_mv.send_counts[isend];
1159 BL_MPI_REQUIRE(MPI_Isend(p_send, count, mpi_t_type, m_comm_mv.send_to[isend],
1160 mpi_tag, mpi_comm, &(m_comm_mv.send_reqs[isend])));
1161 p_send += count;
1162 }
1163 AMREX_ASSERT(p_send == m_comm_mv.send_buffer + m_comm_mv.total_counts_send);
1164 }
1165#endif
1166}
1167
1168template <typename T, template<typename> class Allocator>
1170{
1171#ifndef AMREX_USE_MPI
1173#else
1174 if (detail::spmat_comm_is_local(this->partition(), m_col_partition)) { return; }
1175
1176 if ( ! m_comm_mv.recv_reqs.empty()) {
1177 Vector<MPI_Status> mpi_statuses(m_comm_mv.recv_reqs.size());
1178 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_mv.recv_reqs.size()),
1179 m_comm_mv.recv_reqs.data(),
1180 mpi_statuses.data()));
1181 }
1182
1183 unpack_buffer_mv(y);
1184
1185 if ( ! m_comm_mv.send_reqs.empty()) {
1186 Vector<MPI_Status> mpi_statuses(m_comm_mv.send_reqs.size());
1187 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_mv.send_reqs.size()),
1188 m_comm_mv.send_reqs.data(),
1189 mpi_statuses.data()));
1190 }
1191
1193 The_Comms_Arena()->free(m_comm_mv.send_buffer);
1194 The_Comms_Arena()->free(m_comm_mv.recv_buffer);
1195 m_comm_mv.send_reqs.clear();
1196 m_comm_mv.recv_reqs.clear();
1197#endif
1198}
1199
1200template <typename T, template<typename> class Allocator>
1202{
1203#ifdef AMREX_USE_MPI
1204 if (detail::spmat_comm_is_local(this->partition(), col_partition)) { return; }
1205
1206 this->split_csr(col_partition);
1207
1208 int const nprocs = ParallelContext::NProcsSub();
1209 auto const mpi_tag = ParallelDescriptor::SeqNum();
1210 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
1211 auto const mpi_t = ParallelDescriptor::Mpi_typemap<T>::type();
1212 auto const mpi_comm = ParallelContext::CommunicatorSub();
1213
1214 // transpose the off-diagonal part
1215 if (m_csr_remote.nnz > 0) {
1216 m_comm_tr.csrt.nnz = m_csr_remote.nnz;
1217 m_comm_tr.csrt.nrows = m_remote_cols_v.size();
1218 m_comm_tr.csrt.mat = (T*)The_Pinned_Arena()->alloc
1219 (sizeof(T)*m_comm_tr.csrt.nnz);
1220 m_comm_tr.csrt.col_index = (Long*)The_Pinned_Arena()->alloc
1221 (sizeof(Long)*m_comm_tr.csrt.nnz);
1222 m_comm_tr.csrt.row_offset = (Long*)The_Pinned_Arena()->alloc
1223 (sizeof(Long)*(m_comm_tr.csrt.nrows+1));
1224#ifdef AMREX_USE_GPU
1225 csr_type csr_comm;
1226 csr_comm.resize(m_comm_tr.csrt.nrows, m_comm_tr.csrt.nnz);
1227 auto const& csrv_comm = csr_comm.view();
1228#else
1229 auto const& csrv_comm = m_comm_tr.csrt;
1230#endif
1231 detail::transpose(csrv_comm, m_csr_remote.const_view());
1232 auto row_begin = m_row_begin;
1233 auto ri_rtol = m_ri_rtol.data();
1234 auto* col_index = csrv_comm.col_index;
1235 ParallelForOMP(csrv_comm.nnz, [=] AMREX_GPU_DEVICE (Long idx)
1236 {
1237 auto gjt =ri_rtol[col_index[idx]] + row_begin;
1238 col_index[idx] = gjt; // global index
1239 });
1240#ifdef AMREX_USE_GPU
1242 csrv_comm. mat,
1243 csrv_comm. mat + csrv_comm.nnz,
1244 m_comm_tr.csrt.mat);
1246 csrv_comm. col_index,
1247 csrv_comm. col_index + csrv_comm.nnz,
1248 m_comm_tr.csrt.col_index);
1250 csrv_comm. row_offset,
1251 csrv_comm. row_offset + csrv_comm.nrows+1,
1252 m_comm_tr.csrt.row_offset);
1254#endif
1255 }
1256
1257 if (m_num_neighbors < 0) { set_num_neighbors(); }
1258
1259 // As a sender, I need to let other processes know that how many
1260 // elements I will send them.
1261
1262 Vector<MPI_Request> mpi_requests;
1263 mpi_requests.reserve(nprocs);
1264 if (m_csr_remote.nnz > 0) {
1265 Long it = 0;
1266 for (int iproc = 0; iproc < nprocs; ++iproc) {
1267 Long n = 0;
1268 for (Long i = 0; i < Long(m_remote_cols_vv[iproc].size()); ++i) {
1269 n += m_comm_tr.csrt.row_offset[it+1] - m_comm_tr.csrt.row_offset[it];
1270 ++it;
1271 }
1272 if (n > 0) {
1273 mpi_requests.push_back(MPI_REQUEST_NULL);
1274 AMREX_ALWAYS_ASSERT(n < std::numeric_limits<int>::max());
1275 std::array<int,2> nn{int(n), int(m_remote_cols_vv[iproc].size())};
1276 BL_MPI_REQUIRE(MPI_Isend(nn.data(), 2, MPI_INT, iproc, mpi_tag,
1277 mpi_comm, &(mpi_requests.back())));
1278 m_comm_tr.send_to.push_back(iproc);
1279 m_comm_tr.send_counts.push_back(nn);
1280 }
1281 }
1282 }
1283
1284 // As a receiver, m_num_neighbors is the number of processes from which
1285 // I will receive data.
1286
1287 for (int irecv = 0; irecv < m_num_neighbors; ++irecv) {
1288 MPI_Status mpi_status;
1289 BL_MPI_REQUIRE(MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &mpi_status));
1290 int sender = mpi_status.MPI_SOURCE;
1291 std::array<int,2> nn;
1292 BL_MPI_REQUIRE(MPI_Recv(nn.data(), 2, MPI_INT, sender, mpi_tag,
1293 mpi_comm, &mpi_status));
1294 m_comm_tr.recv_from.push_back(sender);
1295 m_comm_tr.recv_counts.push_back(nn);
1296 m_comm_tr.total_counts_recv[0] += nn[0];
1297 m_comm_tr.total_counts_recv[1] += nn[1];
1298 }
1299
1300 if (! mpi_requests.empty()) {
1301 Vector<MPI_Status> mpi_statuses(mpi_requests.size());
1302 BL_MPI_REQUIRE(MPI_Waitall(int(mpi_requests.size()), mpi_requests.data(),
1303 mpi_statuses.data()));
1304 }
1305
1306 auto const mpi_tag_m = ParallelDescriptor::SeqNum();
1307 auto const mpi_tag_c = ParallelDescriptor::SeqNum();
1308 auto const mpi_tag_r = ParallelDescriptor::SeqNum();
1309 auto const mpi_tag_p = ParallelDescriptor::SeqNum();
1310
1311 // We need to send m_comm_tr.csrt.mat, col_index & row_offset. We also
1312 // need to send m_remote_cols_vv, which maps row index (in transposed
1313 // matrix) form local to global.
1314
1315 auto const nrecvs = int(m_comm_tr.recv_from.size());
1316 if (nrecvs > 0) {
1317 m_comm_tr.recv_buffer_mat = (T*) The_Pinned_Arena()->alloc
1318 (sizeof(T) * m_comm_tr.total_counts_recv[0]);
1319 m_comm_tr.recv_buffer_col_index = (Long*) The_Pinned_Arena()->alloc
1320 (sizeof(Long) * m_comm_tr.total_counts_recv[0]);
1321 m_comm_tr.recv_buffer_row_offset = (Long*) The_Pinned_Arena()->alloc
1322 (sizeof(Long) * (m_comm_tr.total_counts_recv[1]+nrecvs));
1323 m_comm_tr.recv_buffer_idx_map = (Long*) The_Pinned_Arena()->alloc
1324 (sizeof(Long) * m_comm_tr.total_counts_recv[1]);
1325 m_comm_tr.recv_buffer_offset.push_back({0,0,0,0});
1326 m_comm_tr.recv_reqs.resize(4*nrecvs, MPI_REQUEST_NULL);
1327 for (int irecv = 0; irecv < nrecvs; ++irecv) {
1328 auto [os0, os1, os2, os3] = m_comm_tr.recv_buffer_offset.back();
1329 auto [n0, n1] = m_comm_tr.recv_counts[irecv];
1330 auto recv_from_rank = m_comm_tr.recv_from[irecv];
1331 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_mat + os0,
1332 n0,
1333 mpi_t,
1334 recv_from_rank,
1335 mpi_tag_m,
1336 mpi_comm,
1337 &(m_comm_tr.recv_reqs[irecv*4])));
1338 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_col_index + os1,
1339 n0,
1340 mpi_long,
1341 recv_from_rank,
1342 mpi_tag_c,
1343 mpi_comm,
1344 &(m_comm_tr.recv_reqs[irecv*4+1])));
1345 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_row_offset + os2,
1346 n1+1,
1347 mpi_long,
1348 recv_from_rank,
1349 mpi_tag_r,
1350 mpi_comm,
1351 &(m_comm_tr.recv_reqs[irecv*4+2])));
1352 BL_MPI_REQUIRE(MPI_Irecv(m_comm_tr.recv_buffer_idx_map + os3,
1353 n1,
1354 mpi_long,
1355 recv_from_rank,
1356 mpi_tag_p,
1357 mpi_comm,
1358 &(m_comm_tr.recv_reqs[irecv*4+3])));
1359 m_comm_tr.recv_buffer_offset.push_back({os0 + n0,
1360 os1 + n0,
1361 os2 + n1+1,
1362 os3 + n1});
1363 }
1364 }
1365
1366 auto const nsends = int(m_comm_tr.send_to.size());
1367 if (nsends > 0) {
1368 m_comm_tr.send_reqs.resize(4*nsends, MPI_REQUEST_NULL);
1369 Long os0 = 0, os1 = 0;
1370 for (int isend = 0; isend < nsends; ++isend) {
1371 auto [n0, n1] = m_comm_tr.send_counts[isend];
1372 auto send_to_rank = m_comm_tr.send_to[isend];
1373 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.mat + os0,
1374 n0,
1375 mpi_t,
1376 send_to_rank,
1377 mpi_tag_m,
1378 mpi_comm,
1379 &(m_comm_tr.send_reqs[isend*4])));
1380 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.col_index + os0,
1381 n0,
1382 mpi_long,
1383 send_to_rank,
1384 mpi_tag_c,
1385 mpi_comm,
1386 &(m_comm_tr.send_reqs[isend*4+1])));
1387 BL_MPI_REQUIRE(MPI_Isend(m_comm_tr.csrt.row_offset + os1,
1388 n1+1,
1389 mpi_long,
1390 send_to_rank,
1391 mpi_tag_r,
1392 mpi_comm,
1393 &(m_comm_tr.send_reqs[isend*4+2])));
1394 BL_MPI_REQUIRE(MPI_Isend(m_remote_cols_vv[send_to_rank].data(),
1395 n1,
1396 mpi_long,
1397 send_to_rank,
1398 mpi_tag_p,
1399 mpi_comm,
1400 &(m_comm_tr.send_reqs[isend*4+3])));
1401 os0 += n0;
1402 os1 += n1;
1403 }
1404 }
1405#else
1406 amrex::ignore_unused(col_partition);
1407#endif
1408}
1409
1410template <typename T, template<typename> class Allocator>
1412{
1413#ifdef AMREX_USE_MPI
1414 if (detail::spmat_comm_is_local(this->partition(), AT.partition())) { return; }
1415
1416 this->comm_tr_recv_wait();
1417
1418 AT.unpack_buffer_tr(m_comm_tr, this->m_partition);
1419
1420 this->comm_tr_clear();
1421#else
1423#endif
1424}
1425
1426#ifdef AMREX_USE_MPI
1427
1428template <typename T, template<typename> class Allocator>
1430{
1431 if (! m_comm_tr.recv_reqs.empty()) {
1432 Vector<MPI_Status> mpi_statuses(m_comm_tr.recv_reqs.size());
1433 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_tr.recv_reqs.size()),
1434 m_comm_tr.recv_reqs.data(),
1435 mpi_statuses.data()));
1436 }
1437}
1438
1439template <typename T, template<typename> class Allocator>
1441{
1442 if (! m_comm_tr.send_reqs.empty()) {
1443 Vector<MPI_Status> mpi_statuses(m_comm_tr.send_reqs.size());
1444 BL_MPI_REQUIRE(MPI_Waitall(int(m_comm_tr.send_reqs.size()),
1445 m_comm_tr.send_reqs.data(),
1446 mpi_statuses.data()));
1447 }
1448
1449 if (m_comm_tr.csrt.nnz > 0) {
1450 The_Pinned_Arena()->free(m_comm_tr.csrt.mat);
1451 The_Pinned_Arena()->free(m_comm_tr.csrt.col_index);
1452 The_Pinned_Arena()->free(m_comm_tr.csrt.row_offset);
1453 }
1454 if (m_comm_tr.recv_buffer_mat) {
1455 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_mat);
1456 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_col_index);
1457 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_row_offset);
1458 The_Pinned_Arena()->free(m_comm_tr.recv_buffer_idx_map);
1459 }
1460 m_comm_tr = CommTR{};
1461}
1462
1463#endif
1464
1465#ifdef AMREX_USE_MPI
1466
1467template <typename T, template<typename> class Allocator>
1469{
1470 if (m_split) {
1472 (m_col_begin == col_partition[ParallelDescriptor::MyProc()] &&
1473 m_col_end == col_partition[ParallelDescriptor::MyProc()+1]);
1474 return;
1475 }
1476
1477 AMREX_ALWAYS_ASSERT(m_col_partition.empty());
1478
1479 m_col_partition = col_partition;
1480 m_col_begin = col_partition[ParallelDescriptor::MyProc()];
1481 m_col_end = col_partition[ParallelDescriptor::MyProc()+1];
1482
1483 // This function needs to be safe when nnz is zero.
1484
1485 // We need to split the matrix into two parts, a diagonal part for pure
1486 // local operations and another part for remote operations in
1487 // matrix-vector or matrix-matrix multiplication.
1488
1489 Long local_nnz;
1490 Gpu::DeviceVector<Long> pfsum(m_nnz);
1491 auto* p_pfsum = pfsum.data();
1492 auto col_begin = m_col_begin;
1493 auto col_end = m_col_end;
1494 if (m_csr.nnz < Long(std::numeric_limits<int>::max())) {
1495 auto const* pcol = m_csr.col_index.data();
1496 local_nnz = Scan::PrefixSum<int>(int(m_nnz),
1497 [=] AMREX_GPU_DEVICE (int i) -> int {
1498 return (pcol[i] >= col_begin &&
1499 pcol[i] < col_end); },
1500 [=] AMREX_GPU_DEVICE (int i, int const& x) {
1501 p_pfsum[i] = x; },
1503 } else {
1504 auto const* pcol = m_csr.col_index.data();
1505 local_nnz = Scan::PrefixSum<Long>(m_nnz,
1506 [=] AMREX_GPU_DEVICE (Long i) -> Long {
1507 return (pcol[i] >= col_begin &&
1508 pcol[i] < col_end); },
1509 [=] AMREX_GPU_DEVICE (Long i, Long const& x) {
1510 p_pfsum[i] = x; },
1512 }
1513
1514 m_csr.nnz = local_nnz;
1515 Long remote_nnz = m_nnz - local_nnz;
1516 m_csr_remote.nnz = remote_nnz;
1517
1518 if (local_nnz != m_nnz) {
1519 m_csr_remote.mat.resize(remote_nnz);
1520 m_csr_remote.col_index.resize(remote_nnz);
1521 container_type<T> new_mat(local_nnz);
1522 container_type<Long> new_col(local_nnz);
1523 auto const* pmat = m_csr.mat.data();
1524 auto const* pcol = m_csr.col_index.data();
1525 auto* pmat_l = new_mat.data();
1526 auto* pcol_l = new_col.data();
1527 auto* pmat_r = m_csr_remote.mat.data();
1528 auto* pcol_r = m_csr_remote.col_index.data();
1529 ParallelForOMP(m_nnz, [=] AMREX_GPU_DEVICE (Long i)
1530 {
1531 auto ps = p_pfsum[i];
1532 auto local = (pcol[i] >= col_begin &&
1533 pcol[i] < col_end);
1534 if (local) {
1535 pmat_l[ps] = pmat[i];
1536 pcol_l[ps] = pcol[i] - col_begin; // shift the column index to local
1537 } else {
1538 pmat_r[i-ps] = pmat[i];
1539 pcol_r[i-ps] = pcol[i];
1540 }
1541 });
1542 auto noffset = Long(m_csr.row_offset.size());
1543 auto* pro = m_csr.row_offset.data();
1544 m_csr_remote.row_offset.resize(noffset);
1545 auto* pro_r = m_csr_remote.row_offset.data();
1546 auto total_nnz = m_nnz;
1547 ParallelForOMP(noffset, [=] AMREX_GPU_DEVICE (Long i)
1548 {
1549 if (i < noffset-1) {
1550 auto ro_l = (pro[i] < total_nnz) ? p_pfsum[pro[i]] : local_nnz;
1551 pro_r[i] = pro[i] - ro_l;
1552 pro[i] = ro_l;
1553 } else {
1554 pro[i] = local_nnz;
1555 pro_r[i] = remote_nnz;
1556 }
1557 });
1559 m_csr.mat.swap(new_mat);
1560 m_csr.col_index.swap(new_col);
1561
1562 // In the remote part, it's expected that some rows don't have
1563 // nonzeros. So we trim them off, and also save a full copy of the
1564 // row offsets.
1565 {
1566 Long old_size = m_csr_remote.row_offset.size();
1567 m_ri_ltor.resize(old_size-1);
1568 m_ri_rtol.resize(old_size-1);
1569 auto* p_ltor = m_ri_ltor.data();
1570 auto* p_rtol = m_ri_rtol.data();
1571 container_type<Long> trimmed_row_offset(old_size);
1572 auto const* p_ro = m_csr_remote.row_offset.data();
1573 auto* p_tro = trimmed_row_offset.data();
1574 Long new_size;
1575 if (old_size < Long(std::numeric_limits<int>::max())) {
1576 // This is basically std::unique.
1577 new_size = Scan::PrefixSum<int>(int(old_size),
1578 [=] AMREX_GPU_DEVICE (int i) -> int {
1579 if (i+1 < old_size) {
1580 return (p_ro[i+1] > p_ro[i]);
1581 } else {
1582 return 1;
1583 }
1584 },
1585 [=] AMREX_GPU_DEVICE (int i, int const& x) {
1586 if (i == 0) {
1587 p_tro[0] = 0;
1588 } else if (p_ro[i] > p_ro[i-1]) {
1589 p_tro[x] = p_ro[i];
1590 }
1591 if (i+1 < old_size) {
1592 if (p_ro[i+1] > p_ro[i]) {
1593 p_rtol[x] = i;
1594 p_ltor[i] = x;
1595 } else {
1596 p_ltor[i] = -1;
1597 }
1598 }
1599 },
1601 } else {
1602 // This is basically std::unique.
1603 new_size = Scan::PrefixSum<Long>(old_size,
1604 [=] AMREX_GPU_DEVICE (Long i) -> Long {
1605 if (i+1 < old_size) {
1606 return (p_ro[i+1] > p_ro[i]);
1607 } else {
1608 return 1;
1609 }
1610 },
1611 [=] AMREX_GPU_DEVICE (Long i, Long const& x) {
1612 if (i == 0) {
1613 p_tro[0] = 0;
1614 } else if (p_ro[i] > p_ro[i-1]) {
1615 p_tro[x] = p_ro[i];
1616 }
1617 if (i+1 < old_size) {
1618 if (p_ro[i+1] > p_ro[i]) {
1619 p_rtol[x] = i;
1620 p_ltor[i] = x;
1621 } else {
1622 p_ltor[i] = -1;
1623 }
1624 }
1625 },
1627 }
1628
1629 m_ri_rtol.resize(new_size-1);
1630 trimmed_row_offset.resize(new_size);
1631#ifdef AMREX_USE_GPU
1632 m_ri_rtol.shrink_to_fit();
1633 trimmed_row_offset.shrink_to_fit();
1634#endif
1635 m_remote_row_offset = std::move(trimmed_row_offset);
1636 std::swap(m_csr_remote.row_offset, m_remote_row_offset);
1637 }
1638
1639 } else if (col_begin > 0) {
1640 auto* pcol = m_csr.col_index.data();
1641 ParallelForOMP(m_nnz, [=] AMREX_GPU_DEVICE (Long i) { pcol[i] -= col_begin; });
1642 }
1643
1644 update_remote_col_index(m_csr_remote, true);
1645
1646 m_split = true;
1647}
1648
1649template <typename T, template<typename> class Allocator>
1650template <typename C>
1651void SpMatrix<T,Allocator>::update_remote_col_index (C& csrr, bool in_device_memory)
1652{
1653 int const nprocs = ParallelContext::NProcsSub();
1654
1655 // This function also needs to update m_remote_cols_*.
1656
1657 m_remote_cols_v.clear();
1658 m_remote_cols_vv.clear();
1659 m_remote_cols_vv.resize(nprocs);
1660#ifdef AMREX_USE_GPU
1661 m_remote_cols_dv.clear();
1662#endif
1663
1664 if (csrr.nnz == 0) { return; }
1665
1666 amrex::ignore_unused(in_device_memory);
1667
1668#ifdef AMREX_USE_GPU
1669 if (in_device_memory) {
1670 m_remote_cols_v.resize(csrr.nnz);
1672 csrr.col_index.begin(),
1673 csrr.col_index.end(),
1674 m_remote_cols_v.begin());
1676 } else
1677#endif
1678 {
1679 m_remote_cols_v.assign(csrr.col_index.begin(),
1680 csrr.col_index.end());
1681 }
1682
1683 amrex::RemoveDuplicates(m_remote_cols_v);
1684
1685#ifdef AMREX_USE_GPU
1686 m_remote_cols_dv.resize(m_remote_cols_v.size());
1688 m_remote_cols_v.begin(),
1689 m_remote_cols_v.end(),
1690 m_remote_cols_dv.data());
1691#endif
1692
1693 // Note that amrex::RemoveDuplicates sorts the data.
1694 auto const& cp = this->m_col_partition.dataVector();
1695 AMREX_ALWAYS_ASSERT(m_remote_cols_v.front() >= cp.front() &&
1696 m_remote_cols_v.back() < cp.back());
1697 auto it = cp.cbegin();
1698 for (auto c : m_remote_cols_v) {
1699 it = std::find_if(it, cp.cend(), [&] (auto x) { return x > c; });
1700 if (it != cp.cend()) {
1701 int iproc = int(std::distance(cp.cbegin(),it)) - 1;
1702 m_remote_cols_vv[iproc].push_back(c);
1703 } else {
1704 amrex::Abort("SpMatrix::update_remote_col_index: how did this happen?");
1705 }
1706 }
1707
1708 // Now we convert the remote indices from global to local.
1709 std::map<Long,Long> gtol;
1710 for (Long i = 0, N = Long(m_remote_cols_v.size()); i < N; ++i) {
1711 gtol[m_remote_cols_v[i]] = i;
1712 }
1713
1714#ifdef AMREX_USE_GPU
1715 if (in_device_memory) {
1716 Gpu::PinnedVector<Long> host_col_index(csrr.nnz);
1718 csrr.col_index.begin(),
1719 csrr.col_index.end(),
1720 host_col_index.begin());
1722 for (auto& c : host_col_index) {
1723 c = gtol[c];
1724 }
1726 host_col_index.begin(),
1727 host_col_index.end(),
1728 csrr.col_index.begin());
1730 } else
1731#endif
1732 {
1733 for (auto& c : csrr.col_index) {
1734 c = gtol[c];
1735 }
1736 }
1737}
1738
1739template <typename T, template<typename> class Allocator>
1741{
1742 if (m_num_neighbors >= 0) { return; }
1743
1744 int const nprocs = ParallelContext::NProcsSub();
1745 auto const mpi_int = ParallelDescriptor::Mpi_typemap<int>::type();
1746 auto const mpi_comm = ParallelContext::CommunicatorSub();
1747
1748 amrex::Vector<int> connection(nprocs);
1749 for (int iproc = 0; iproc < nprocs; ++iproc) {
1750 connection[iproc] = m_remote_cols_vv[iproc].empty() ? 0 : 1;
1751 }
1752 amrex::Vector<int> reduce_scatter_counts(nprocs,1);
1753 m_num_neighbors = 0;
1754 BL_MPI_REQUIRE(MPI_Reduce_scatter
1755 (connection.data(), &m_num_neighbors, reduce_scatter_counts.data(),
1756 mpi_int, MPI_SUM, mpi_comm));
1757}
1758
1759template <typename T, template<typename> class Allocator>
1761{
1762 if (m_comm_mv.prepared) { return; }
1763
1764 // This function needs to be safe when nnz is zero.
1765
1766 this->split_csr(col_partition);
1767
1768 int const nprocs = ParallelContext::NProcsSub();
1769 auto const mpi_tag = ParallelDescriptor::SeqNum();
1770 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
1771 auto const mpi_comm = ParallelContext::CommunicatorSub();
1772
1773 if (m_num_neighbors < 0) { set_num_neighbors(); }
1774
1775 Vector<MPI_Request> mpi_requests;
1776 mpi_requests.reserve(nprocs);
1777 for (int iproc = 0; iproc < nprocs; ++iproc) {
1778 if ( ! m_remote_cols_vv[iproc].empty()) {
1779 mpi_requests.push_back(MPI_REQUEST_NULL);
1780 auto const sz = m_remote_cols_vv[iproc].size();
1781 if (sz > static_cast<Long>(std::numeric_limits<int>::max())) {
1782 amrex::Abort("SpMatrix::prepare_comm_mv: remote column payload exceeds MPI int count range.");
1783 }
1784 auto const msg_count = static_cast<int>(sz);
1785 // I need to let other processes know what I need from them.
1786 BL_MPI_REQUIRE(MPI_Isend(m_remote_cols_vv[iproc].data(),
1787 msg_count,
1788 mpi_long, iproc, mpi_tag, mpi_comm,
1789 &(mpi_requests.back())));
1790 m_comm_mv.recv_from.push_back(iproc);
1791 m_comm_mv.recv_counts.push_back(msg_count);
1792 }
1793 }
1794
1795 m_comm_mv.total_counts_recv = Long(m_remote_cols_v.size());
1796
1797 Vector<Vector<Long>> send_indices(m_num_neighbors);
1798 m_comm_mv.total_counts_send = 0;
1799 for (int isend = 0; isend < m_num_neighbors; ++isend) {
1800 MPI_Status mpi_status;
1801 BL_MPI_REQUIRE(MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &mpi_status));
1802 int receiver = mpi_status.MPI_SOURCE;
1803 int count;
1804 BL_MPI_REQUIRE(MPI_Get_count(&mpi_status, mpi_long, &count));
1805 m_comm_mv.send_to.push_back(receiver);
1806 m_comm_mv.send_counts.push_back(count);
1807 send_indices[isend].resize(count);
1808 BL_MPI_REQUIRE(MPI_Recv(send_indices[isend].data(), count, mpi_long,
1809 receiver, mpi_tag, mpi_comm, &mpi_status));
1810 m_comm_mv.total_counts_send += count;
1811 }
1812
1813 m_comm_mv.send_indices.resize(m_comm_mv.total_counts_send);
1814 Gpu::PinnedVector<Long> send_indices_all;
1815 send_indices_all.reserve(m_comm_mv.total_counts_send);
1816 for (auto const& vl : send_indices) {
1817 for (auto x : vl) {
1818 send_indices_all.push_back(x);
1819 }
1820 }
1821 Gpu::copyAsync(Gpu::hostToDevice, send_indices_all.begin(), send_indices_all.end(),
1822 m_comm_mv.send_indices.begin());
1824
1825 if (! mpi_requests.empty()) {
1826 Vector<MPI_Status> mpi_statuses(mpi_requests.size());
1827 BL_MPI_REQUIRE(MPI_Waitall(int(mpi_requests.size()), mpi_requests.data(),
1828 mpi_statuses.data()));
1829 }
1830
1831 m_comm_mv.prepared = true;
1832}
1833
1834template <typename T, template<typename> class Allocator>
1836{
1837 auto* pdst = m_comm_mv.send_buffer;
1838 auto* pidx = m_comm_mv.send_indices.data();
1839 auto const& vv = v.view();
1840 auto const nsends = Long(m_comm_mv.send_indices.size());
1841 ParallelForOMP(nsends, [=] AMREX_GPU_DEVICE (Long i)
1842 {
1843 pdst[i] = vv(pidx[i]);
1844 });
1845}
1846
1847template <typename T, template<typename> class Allocator>
1849{
1850 auto const& csr = m_csr_remote;
1851 if (csr.nnz > 0) {
1852 T const* AMREX_RESTRICT mat = csr.mat.data();
1853 auto const* AMREX_RESTRICT col = csr.col_index.data();
1854 auto const* AMREX_RESTRICT row = csr.row_offset.data();
1855
1856 auto const* rtol = m_ri_rtol.data();
1857
1858 auto const* AMREX_RESTRICT px = m_comm_mv.recv_buffer;
1859 auto * AMREX_RESTRICT py = v.data();
1860
1861 auto const nrr = Long(csr.row_offset.size())-1;
1863 {
1864 T r = 0;
1865 for (Long j = row[i]; j < row[i+1]; ++j) {
1866 r += mat[j] * px[col[j]];
1867 }
1868 py[rtol[i]] += r;
1869 });
1870 }
1871}
1872
1873template <typename T, template<typename> class Allocator>
1875 AlgPartition const& col_partition)
1876{
1877 m_split = true;
1878 m_col_partition = col_partition;
1879 m_col_begin = m_col_partition[ParallelDescriptor::MyProc() ];
1880 m_col_end = m_col_partition[ParallelDescriptor::MyProc()+1];
1881
1882 m_ri_ltor.resize(m_csr.nrows(), -1);
1883 m_remote_cols_vv.resize(ParallelDescriptor::NProcs());
1884
1885 auto nnz = ctr.total_counts_recv[0];
1886 if (nnz == 0) { return; }
1887
1888 m_nnz += nnz;
1889 auto nb = int(ctr.recv_from.size()); // # of blocked CSRs to be merged
1890 auto total_local_rows = ctr.total_counts_recv[1];
1891
1892 // Build compressed row index map
1894 ctr.recv_buffer_idx_map + total_local_rows);
1895 RemoveDuplicates(ri_map);
1896 Long nrows = ri_map.size(); // # of unique rows.
1897
1898#ifdef AMREX_USE_GPU
1900#else
1901 auto& csrr = m_csr_remote;
1902#endif
1903 csrr.mat.resize(nnz);
1904 csrr.col_index.resize(nnz);
1905 csrr.row_offset.resize(nrows+1);
1906 csrr.nnz = nnz;
1907
1908 // Merge blocks in sender-rank order so that rows stay sorted. Each
1909 // block covers a contiguous global column range.
1910 Vector<int> order(nb);
1911 std::iota(order.begin(), order.end(), 0);
1912 std::sort(order.begin(), order.end(), [&] (int a, int b) {
1913 return ctr.recv_from[a] < ctr.recv_from[b]; });
1914
1915 // Count nnz per compressed row
1916 Vector<int> row_nnz(nrows, 0);
1917 for (int i : order) {
1918 auto nrow_i = ctr.recv_counts[i][1];
1919 Long const* row_offset = ctr.recv_buffer_row_offset
1920 + ctr.recv_buffer_offset[i][2];
1921 Long const* idx_map = ctr.recv_buffer_idx_map
1922 + ctr.recv_buffer_offset[i][3];
1923 AMREX_ASSERT((row_offset[nrow_i] - row_offset[0]) == ctr.recv_counts[i][0]);
1924
1925 Long p = 0; // index into ri_map
1926 for (int lr = 0; lr < nrow_i; ++lr) {
1927 Long const gr = idx_map[lr];
1928 while (p < nrows && ri_map[p] < gr) { ++p; }
1929 AMREX_ASSERT(p < nrows && ri_map[p] == gr);
1930 // p is now compressed row index
1931 row_nnz[p] += int(row_offset[lr+1] - row_offset[lr]);
1932 }
1933 }
1934
1935 std::exclusive_scan(row_nnz.begin(), row_nnz.end(), csrr.row_offset.begin(),
1936 Long(0));
1937 csrr.row_offset.back() = csrr.nnz;
1938
1939 auto rowpos = csrr.row_offset; // make a copy to keep track of offset
1940
1941 for (int i : order) {
1942 auto nrow_i = ctr.recv_counts[i][1];
1943 T const* mat = ctr.recv_buffer_mat
1944 + ctr.recv_buffer_offset[i][0];
1945 Long const* col_index = ctr.recv_buffer_col_index
1946 + ctr.recv_buffer_offset[i][1];
1947 Long const* row_offset = ctr.recv_buffer_row_offset
1948 + ctr.recv_buffer_offset[i][2];
1949 Long const* idx_map = ctr.recv_buffer_idx_map
1950 + ctr.recv_buffer_offset[i][3];
1951
1952 Long p = 0; // index into ri_map
1953 for (int lr = 0; lr < nrow_i; ++lr) {
1954 Long const gr = idx_map[lr];
1955 while (p < nrows && ri_map[p] < gr) { ++p; }
1956 AMREX_ASSERT(p < nrows && ri_map[p] == gr);
1957 // p is now compressed row index
1958 auto os_src = row_offset[lr] - row_offset[0];
1959 auto nvals = row_offset[lr+1] - row_offset[lr];
1960 auto os_dst = rowpos[p];
1961 std::memcpy(csrr. mat.data()+os_dst, mat+os_src,
1962 sizeof(T) *nvals);
1963 std::memcpy(csrr.col_index.data()+os_dst, col_index+os_src,
1964 sizeof(Long)*nvals);
1965 rowpos[p] += nvals;
1966 }
1967 }
1968
1969 m_ri_rtol.resize(nrows);
1970 Gpu::copyAsync(Gpu::hostToDevice, ri_map.begin(), ri_map.end(), m_ri_rtol.begin());
1971 {
1972 auto row_begin = m_row_begin;
1973 auto* AMREX_RESTRICT ltor = m_ri_ltor.data();
1974 auto* AMREX_RESTRICT rtol = m_ri_rtol.data();
1975 ParallelForOMP(nrows, [=] AMREX_GPU_DEVICE (Long i) {
1976 rtol[i] -= row_begin;
1977 ltor[rtol[i]] = i;
1978 });
1979 }
1980
1981 // The column index in csrr is still global.
1982 update_remote_col_index(csrr, false);
1983
1984#ifdef AMREX_USE_GPU
1985 amrex::duplicateCSR(Gpu::hostToDevice, m_csr_remote, csrr);
1987#endif
1988}
1989
1990template <typename T, template<typename> class Allocator>
1992 -> RemoteRowsMM
1993{
1994 // this = A, split by B's row partition. m_comm_mv tells which rows of B
1995 // other ranks need from us and which we need from them.
1996 this->prepare_comm_mv(B.partition());
1997
1998 auto const& cm = m_comm_mv;
1999 auto const nrecvs = int(cm.recv_from.size());
2000 auto const nsends = int(cm.send_to.size());
2001 auto const mpi_long = ParallelDescriptor::Mpi_typemap<Long>::type();
2002 auto const mpi_t = ParallelDescriptor::Mpi_typemap<T>::type();
2003 auto const mpi_comm = ParallelContext::CommunicatorSub();
2004
2005 RemoteRowsMM ext;
2006 ext.nrows = cm.total_counts_recv; // == m_remote_cols_v.size()
2007 ext.row_offset.resize(ext.nrows+1);
2008
2009 auto const b0 = B.m_csr.const_view();
2010 auto const b1 = B.remote_full_const_view();
2011 Long const b_row_begin = B.m_row_begin;
2012 Long const nsend_rows = cm.total_counts_send;
2013 auto const* AMREX_RESTRICT send_idx = cm.send_indices.data();
2014
2015 // Round 1: number of nonzeros in each requested row.
2016 Gpu::PinnedVector<Long> h_send(nsend_rows+1);
2017 {
2018 Gpu::DeviceVector<Long> d_cnt(nsend_rows);
2019 auto* pcnt = d_cnt.data();
2020 ParallelForOMP(nsend_rows, [=] AMREX_GPU_DEVICE (Long i)
2021 {
2022 Long const lr = send_idx[i] - b_row_begin;
2023 pcnt[i] = (b0.row_offset[lr+1] - b0.row_offset[lr])
2024 + (b1.row_offset[lr+1] - b1.row_offset[lr]);
2025 });
2026 Gpu::copyAsync(Gpu::deviceToHost, d_cnt.begin(), d_cnt.end(), h_send.begin());
2028 }
2029
2030 Gpu::PinnedVector<Long> h_recv(ext.nrows+1);
2031 {
2032 auto const tag = ParallelDescriptor::SeqNum();
2034 reqs.reserve(nrecvs+nsends);
2035 Long os = 0;
2036 for (int i = 0; i < nrecvs; ++i) {
2037 reqs.push_back(MPI_REQUEST_NULL);
2038 BL_MPI_REQUIRE(MPI_Irecv(h_recv.data()+os, cm.recv_counts[i], mpi_long,
2039 cm.recv_from[i], tag, mpi_comm, &reqs.back()));
2040 os += cm.recv_counts[i];
2041 }
2042 os = 0;
2043 for (int i = 0; i < nsends; ++i) {
2044 reqs.push_back(MPI_REQUEST_NULL);
2045 BL_MPI_REQUIRE(MPI_Isend(h_send.data()+os, cm.send_counts[i], mpi_long,
2046 cm.send_to[i], tag, mpi_comm, &reqs.back()));
2047 os += cm.send_counts[i];
2048 }
2049 if (! reqs.empty()) {
2050 Vector<MPI_Status> stats(reqs.size());
2051 BL_MPI_REQUIRE(MPI_Waitall(int(reqs.size()), reqs.data(), stats.data()));
2052 }
2053 }
2054
2055 // Counts to offsets, in place; per-rank totals.
2056 auto to_offsets = [] (Gpu::PinnedVector<Long>& v, Long n) {
2057 Long s = 0;
2058 for (Long i = 0; i < n; ++i) {
2059 Long const c = v[i];
2060 v[i] = s;
2061 s += c;
2062 }
2063 v[n] = s;
2064 };
2065 to_offsets(h_recv, ext.nrows);
2066 to_offsets(h_send, nsend_rows);
2067 ext.nnz = h_recv[ext.nrows];
2068 Long const send_nnz = h_send[nsend_rows];
2069
2070 Vector<Long> recv_nnz(nrecvs), send_nnz_v(nsends);
2071 for (int i = 0, r = 0; i < nrecvs; ++i) {
2072 recv_nnz[i] = h_recv[r+cm.recv_counts[i]] - h_recv[r];
2073 r += cm.recv_counts[i];
2074 if (recv_nnz[i] >= Long(std::numeric_limits<int>::max())) {
2075 amrex::Abort("SpMatrix::fetch_remote_rows_mm: message exceeds MPI int count range.");
2076 }
2077 }
2078 for (int i = 0, r = 0; i < nsends; ++i) {
2079 send_nnz_v[i] = h_send[r+cm.send_counts[i]] - h_send[r];
2080 r += cm.send_counts[i];
2081 if (send_nnz_v[i] >= Long(std::numeric_limits<int>::max())) {
2082 amrex::Abort("SpMatrix::fetch_remote_rows_mm: message exceeds MPI int count range.");
2083 }
2084 }
2085
2086 Gpu::copyAsync(Gpu::hostToDevice, h_recv.begin(), h_recv.end(), ext.row_offset.begin());
2087 Gpu::DeviceVector<Long> d_send_off(nsend_rows+1);
2088 Gpu::copyAsync(Gpu::hostToDevice, h_send.begin(), h_send.end(), d_send_off.begin());
2089
2090 // Round 2: column indices and values.
2091 auto const tag_c = ParallelDescriptor::SeqNum();
2092 auto const tag_m = ParallelDescriptor::SeqNum();
2093
2094 Vector<MPI_Request> rreqs, sreqs;
2095 if (ext.nnz > 0) {
2096 ext.col_index = (Long*)The_Pinned_Arena()->alloc(sizeof(Long)*ext.nnz);
2097 ext.mat = (T*)The_Comms_Arena()->alloc(sizeof(T)*ext.nnz);
2098 rreqs.reserve(2*nrecvs);
2099 Long os = 0;
2100 for (int i = 0; i < nrecvs; ++i) {
2101 if (recv_nnz[i] > 0) {
2102 auto n = int(recv_nnz[i]);
2103 rreqs.push_back(MPI_REQUEST_NULL);
2104 BL_MPI_REQUIRE(MPI_Irecv(ext.col_index+os, n, mpi_long, cm.recv_from[i],
2105 tag_c, mpi_comm, &rreqs.back()));
2106 rreqs.push_back(MPI_REQUEST_NULL);
2107 BL_MPI_REQUIRE(MPI_Irecv(ext.mat+os, n, mpi_t, cm.recv_from[i],
2108 tag_m, mpi_comm, &rreqs.back()));
2109 os += recv_nnz[i];
2110 }
2111 }
2112 }
2113
2114 Long* send_col = nullptr;
2115 T* send_mat = nullptr;
2116 if (send_nnz > 0) {
2117 send_col = (Long*)The_Comms_Arena()->alloc(sizeof(Long)*send_nnz);
2118 send_mat = (T*)The_Comms_Arena()->alloc(sizeof(T)*send_nnz);
2119 auto const* poff = d_send_off.data();
2120 Long const b_col_begin = B.m_col_begin;
2121#ifdef AMREX_USE_GPU
2122 auto const* b_rcols = B.m_remote_cols_dv.data();
2123#else
2124 auto const* b_rcols = B.m_remote_cols_v.data();
2125#endif
2126 // Merge the diagonal and off-diagonal parts so that each packed
2127 // row is sorted by global column index.
2128 ParallelForOMP(nsend_rows, [=] AMREX_GPU_DEVICE (Long i)
2129 {
2130 constexpr Long gmax = std::numeric_limits<Long>::max();
2131 Long const lr = send_idx[i] - b_row_begin;
2132 Long p = poff[i];
2133 Long q0 = b0.row_offset[lr];
2134 Long q1 = b1.row_offset[lr];
2135 Long const e0 = b0.row_offset[lr+1];
2136 Long const e1 = b1.row_offset[lr+1];
2137 while (q0 < e0 || q1 < e1) {
2138 Long const g0 = (q0 < e0) ? b0.col_index[q0] + b_col_begin : gmax;
2139 Long const g1 = (q1 < e1) ? b_rcols[b1.col_index[q1]] : gmax;
2140 if (g0 < g1) {
2141 send_col[p] = g0;
2142 send_mat[p] = b0.mat[q0];
2143 ++q0;
2144 } else {
2145 send_col[p] = g1;
2146 send_mat[p] = b1.mat[q1];
2147 ++q1;
2148 }
2149 ++p;
2150 }
2151 });
2153
2154 sreqs.reserve(2*nsends);
2155 Long os = 0;
2156 for (int i = 0; i < nsends; ++i) {
2157 if (send_nnz_v[i] > 0) {
2158 auto n = int(send_nnz_v[i]);
2159 sreqs.push_back(MPI_REQUEST_NULL);
2160 BL_MPI_REQUIRE(MPI_Isend(send_col+os, n, mpi_long, cm.send_to[i],
2161 tag_c, mpi_comm, &sreqs.back()));
2162 sreqs.push_back(MPI_REQUEST_NULL);
2163 BL_MPI_REQUIRE(MPI_Isend(send_mat+os, n, mpi_t, cm.send_to[i],
2164 tag_m, mpi_comm, &sreqs.back()));
2165 os += send_nnz_v[i];
2166 }
2167 }
2168 }
2169
2170 if (! rreqs.empty()) {
2171 Vector<MPI_Status> stats(rreqs.size());
2172 BL_MPI_REQUIRE(MPI_Waitall(int(rreqs.size()), rreqs.data(), stats.data()));
2173 }
2174 if (! sreqs.empty()) {
2175 Vector<MPI_Status> stats(sreqs.size());
2176 BL_MPI_REQUIRE(MPI_Waitall(int(sreqs.size()), sreqs.data(), stats.data()));
2177 }
2179 if (send_col) { The_Comms_Arena()->free(send_col); }
2180 if (send_mat) { The_Comms_Arena()->free(send_mat); }
2181
2182 return ext;
2183}
2184
2185template <typename T, template<typename> class Allocator>
2187{
2188 if (! m_remote_row_offset.empty()) { return; }
2189
2190 AMREX_ASSERT(m_split);
2191
2192 auto nrows = m_csr.nrows();
2193 m_remote_row_offset.resize(nrows+1);
2194 auto* pro_full = m_remote_row_offset.data();
2195 auto const* pro_comp = m_csr_remote.row_offset.data();
2196 auto const* ri_ltor = m_ri_ltor.data();
2197 auto nnz_r = m_csr_remote.nnz;
2198 if (nnz_r == 0 || m_ri_ltor.empty()) {
2199 // split_csr only builds m_ri_ltor when remote entries exist.
2200 ParallelForOMP(nrows+1, [=] AMREX_GPU_DEVICE (Long i) { pro_full[i] = 0; });
2202 } else if (nnz_r < Long(std::numeric_limits<int>::max())) {
2203 Scan::PrefixSum<int>(nrows,
2204 [=] AMREX_GPU_DEVICE (Long i) -> int {
2205 Long rrow = ri_ltor[i];
2206 if (rrow == -1) {
2207 return int(0);
2208 } else {
2209 return int(pro_comp[rrow+1]-pro_comp[rrow]);
2210 }},
2211 [=] AMREX_GPU_DEVICE (Long i, int x) {
2212 if (i == 0) {
2213 pro_full[0] = 0;
2214 }
2215 pro_full[i+1] = x;
2216 },
2218 } else {
2219 Scan::PrefixSum<Long>(nrows,
2220 [=] AMREX_GPU_DEVICE (Long i) -> Long {
2221 Long rrow = ri_ltor[i];
2222 if (rrow == -1) {
2223 return Long(0);
2224 } else {
2225 return pro_comp[rrow+1]-pro_comp[rrow];
2226 }},
2227 [=] AMREX_GPU_DEVICE (Long i, Long x) {
2228 if (i == 0) {
2229 pro_full[0] = 0;
2230 }
2231 pro_full[i+1] = x;
2232 },
2234 }
2235}
2236
2237template <typename T, template<typename> class Allocator>
2239{
2240 if (m_remote_row_offset.empty()) {
2241 expand_remote_row_offset();
2242 }
2243 auto csr_view = m_csr_remote.const_view();
2244 csr_view.row_offset = m_remote_row_offset.data();
2245 csr_view.nrows = m_csr.nrows();
2246 return csr_view;
2247}
2248
2249#endif
2250
2251}
2252
2253#endif
#define AMREX_ASSERT(EX)
Definition AMReX_BLassert.H:38
#define AMREX_ALWAYS_ASSERT(EX)
Definition AMReX_BLassert.H:50
#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
Convenience header for the core AMReX GPU facilities.
Array4< int const > offset
Definition AMReX_HypreMLABecLap.cpp:1131
Real * pdst
Definition AMReX_HypreMLABecLap.cpp:1132
Definition AMReX_AlgPartition.H:21
Long numGlobalRows() const
Total number of rows covered by the partition.
Definition AMReX_AlgPartition.H:50
bool empty() const
True if the partition contains no rows.
Definition AMReX_AlgPartition.H:40
Vector< Long > const & dataVector() const
Underlying array describing row offsets (size nproc+1).
Definition AMReX_AlgPartition.H:67
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:63
void finishComm_tr(SpMatrix< T, Allocator > &AT)
Complete transpose communication, writing the assembled matrix into AT.
Definition AMReX_SpMatrix.H:1411
void split_csr(AlgPartition const &col_partition)
Definition AMReX_SpMatrix.H:1468
void setColumnPartition(AlgPartition const &col_partition)
Definition AMReX_SpMatrix.H:876
Long globalRowBegin() const
Inclusive global index begin on this process.
Definition AMReX_SpMatrix.H:198
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:890
Long * rowOffset()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:215
void sortCSR()
Definition AMReX_SpMatrix.H:869
void pack_buffer_mv(AlgVector< T, AllocT > const &v)
Definition AMReX_SpMatrix.H:1835
void unpack_buffer_mv(AlgVector< T, AllocT > &v)
Definition AMReX_SpMatrix.H:1848
void update_remote_col_index(C &csrr, bool in_device_memory)
Definition AMReX_SpMatrix.H:1651
void startComm_tr(AlgPartition const &col_partition)
Initiate communication required to build the transpose with column partition col_partition.
Definition AMReX_SpMatrix.H:1201
void comm_tr_clear()
Definition AMReX_SpMatrix.H:1440
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:801
T * data()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:203
friend class AMG
Definition AMReX_SpMatrix.H:304
Long numGlobalRows() const
Global row count.
Definition AMReX_SpMatrix.H:193
SpMatrix & operator=(SpMatrix const &)=delete
~SpMatrix()=default
T value_type
Definition AMReX_SpMatrix.H:65
CsrView< T const > remote_full_const_view() const
Off-diagonal part with the full (untrimmed) row offsets.
Definition AMReX_SpMatrix.H:2238
RemoteRowsMM fetch_remote_rows_mm(SpMatrix< T, Allocator > const &B)
Definition AMReX_SpMatrix.H:1991
Allocator< U > allocator_type
Definition AMReX_SpMatrix.H:66
Long globalRowEnd() const
Exclusive global index end on this process.
Definition AMReX_SpMatrix.H:200
Long numLocalNonZeros() const
Number of nonzeros stored locally.
Definition AMReX_SpMatrix.H:195
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:1040
AlgPartition const & columnPartition() const
Return the column partition used for matrix-vector and matrix-matrix multiplications.
Definition AMReX_SpMatrix.H:188
void printToFile(std::string const &file) const
Definition AMReX_SpMatrix.H:928
ParCsr< T const > const_parcsr() const
Const-qualified alias of parcsr() for convenience.
Definition AMReX_SpMatrix.H:1089
SpMatrix()=default
SpMatrix(SpMatrix const &)=delete
ParCsr< T > parcsr()
Build GPU-friendly CSR views split into diagonal/off-diagonal blocks.
Definition AMReX_SpMatrix.H:1065
SpMatrix(SpMatrix &&)=default
Long numLocalRows() const
Number of rows owned by this rank.
Definition AMReX_SpMatrix.H:191
AlgPartition const & partition() const
Row partition describing how matrix rows are distributed across ranks.
Definition AMReX_SpMatrix.H:179
AlgVector< T, AllocT > const & diagonalVector() const
Return diagonal elements in a square matrix.
Definition AMReX_SpMatrix.H:1014
Long * columnIndex()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:209
void comm_tr_recv_wait()
Definition AMReX_SpMatrix.H:1429
void finishComm_mv(AlgVector< T, AllocT > &y)
Finish halo exchanges and accumulate contributions into y.
Definition AMReX_SpMatrix.H:1169
Allocator< T > AllocT
Definition AMReX_SpMatrix.H:69
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:2186
void prepare_comm_mv(AlgPartition const &col_partition)
Definition AMReX_SpMatrix.H:1760
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:1120
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:1874
friend void SpMV(AlgVector< U, N > &y, SpMatrix< U, M > const &A, AlgVector< U, N > const &x)
void setVal(F const &f, CsrSorted is_sorted)
Initialize matrix entries using a row-wise functor.
Definition AMReX_SpMatrix.H:993
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:775
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:875
Arena * The_Pinned_Arena()
Definition AMReX_Arena.cpp:855
Arena * The_Arena()
Definition AMReX_Arena.cpp:815
int MyProc() noexcept
Definition AMReX_ParallelDescriptor.H:128
int NProcs() noexcept
Definition AMReX_ParallelDescriptor.H:255
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 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:259
amrex::ArenaAllocator< T > DefaultAllocator
Definition AMReX_GpuAllocators.H:205
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
void duplicateCSR(C c, CSR< T, AD > &dst, CSR< T, AS > const &src)
Definition AMReX_CSR.H:120
void Abort(const std::string &msg)
Print a fatal-error message to stderr and abort execution.
Definition AMReX.cpp:242
const int[]
Definition AMReX_BLProfiler.cpp:1665
void RemoveDuplicates(Vector< T > &vec)
Definition AMReX_Vector.H:210
V< Long > row_offset
Definition AMReX_CSR.H:53
Long nrows() const
Number of logical rows represented by the CSR offset array.
Definition AMReX_CSR.H:57
Long nnz
Definition AMReX_CSR.H:54
void sort()
Sort each row by column index. Uses GPU acceleration when possible.
Definition AMReX_CSR.H:141
CsrView< T > view()
Mutable view of the underlying buffers.
Definition AMReX_CSR.H:78
CsrView< T const > const_view() const
Convenience alias for view() const.
Definition AMReX_CSR.H:93
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:70
V< Long > col_index
Definition AMReX_CSR.H:52
V< T > mat
Definition AMReX_CSR.H:51
Sorted CSR means for each row the column indices are sorted.
Definition AMReX_SpMatrix.H:47
bool b
Definition AMReX_SpMatrix.H:48
Valid CSR means all entries are valid. It may be sorted ro unsorted.
Definition AMReX_SpMatrix.H:53
bool b
Definition AMReX_SpMatrix.H:54
Lightweight non-owning CSR view that can point to host or device buffers.
Definition AMReX_CSR.H:34
GPU-ready non-owning CSR data container.
Definition AMReX_SpMatrix.H:37
Long const *__restrict__ col_map
Definition AMReX_SpMatrix.H:43
Long const *__restrict__ row_map
Definition AMReX_SpMatrix.H:42
CsrView< T > csr1
Definition AMReX_SpMatrix.H:39
Long col_begin
Definition AMReX_SpMatrix.H:41
Long row_begin
Definition AMReX_SpMatrix.H:40
CsrView< T > csr0
Definition AMReX_SpMatrix.H:38
Definition AMReX_SpMatrix.H:402
T * send_buffer
Definition AMReX_SpMatrix.H:411
bool prepared
Definition AMReX_SpMatrix.H:418
Vector< int > recv_counts
Definition AMReX_SpMatrix.H:408
Long total_counts_recv
Definition AMReX_SpMatrix.H:416
Vector< int > recv_from
Definition AMReX_SpMatrix.H:407
T * recv_buffer
Definition AMReX_SpMatrix.H:415
Vector< int > send_counts
Definition AMReX_SpMatrix.H:404
Long total_counts_send
Definition AMReX_SpMatrix.H:412
Gpu::DeviceVector< Long > send_indices
Definition AMReX_SpMatrix.H:405
Vector< MPI_Request > recv_reqs
Definition AMReX_SpMatrix.H:414
Vector< int > send_to
Definition AMReX_SpMatrix.H:403
Vector< MPI_Request > send_reqs
Definition AMReX_SpMatrix.H:410
Definition AMReX_SpMatrix.H:421
Vector< std::array< int, 2 > > send_counts
Definition AMReX_SpMatrix.H:425
Long * recv_buffer_col_index
Definition AMReX_SpMatrix.H:438
Vector< MPI_Request > send_reqs
Definition AMReX_SpMatrix.H:426
Vector< MPI_Request > recv_reqs
Definition AMReX_SpMatrix.H:430
Vector< int > send_to
Definition AMReX_SpMatrix.H:424
std::array< Long, 2 > total_counts_recv
Definition AMReX_SpMatrix.H:432
Long * recv_buffer_row_offset
Definition AMReX_SpMatrix.H:439
Vector< std::array< int, 2 > > recv_counts
Definition AMReX_SpMatrix.H:429
CsrView< T > csrt
Definition AMReX_SpMatrix.H:422
T * recv_buffer_mat
Definition AMReX_SpMatrix.H:437
Vector< std::array< Long, 4 > > recv_buffer_offset
Definition AMReX_SpMatrix.H:433
Vector< int > recv_from
Definition AMReX_SpMatrix.H:428
Long * recv_buffer_idx_map
Definition AMReX_SpMatrix.H:440
Rows of another matrix fetched for SpGEMM. Column indices are global.
Definition AMReX_SpMatrix.H:458
container_type< Long > row_offset
Definition AMReX_SpMatrix.H:459
RemoteRowsMM(RemoteRowsMM &&rhs) noexcept
Definition AMReX_SpMatrix.H:469
RemoteRowsMM(RemoteRowsMM const &)=delete
Long nnz
Definition AMReX_SpMatrix.H:463
~RemoteRowsMM()
Definition AMReX_SpMatrix.H:466
Long * col_index
Definition AMReX_SpMatrix.H:460
RemoteRowsMM & operator=(RemoteRowsMM const &)=delete
Long nrows
Definition AMReX_SpMatrix.H:462
void clear()
Definition AMReX_SpMatrix.H:485
T * mat
Definition AMReX_SpMatrix.H:461
Definition AMReX_ccse-mpi.H:55