3#include <AMReX_Config.H>
17namespace amrex::detail {
21 template <
typename I,
typename T>
22 Long operator() (
Long, I*, T*,
Long n)
const {
return n; }
26template <
typename T,
template<
typename>
class V,
typename I =
Long>
27CSR<T,V,I> spgemm_empty (
Long nrows)
30 C.row_offset.resize(nrows+1);
31 auto* p =
C.row_offset.data();
37#if !defined(AMREX_USE_GPU)
40template <
typename T,
typename I>
42 Vector<std::pair<I,T>>& tmp)
45 for (
Long k = 1; k < n; ++k) {
49 for (; m > 0 && col[m-1] > c; --m) {
58 for (
Long k = 0; k < n; ++k) { tmp[k] = {col[k], val[k]}; }
59 std::sort(tmp.begin(), tmp.end(),
60 [] (
auto const&
x,
auto const&
y) { return x.first < y.first; });
61 for (
Long k = 0; k < n; ++k) { col[k] = tmp[k].first; val[k] = tmp[k].second; }
75template <
typename T,
template<
typename>
class V,
typename I,
typename W,
typename F>
76CSR<T,V,I> csr_from_rows_cpu (
Long nrows, W
const& work,
F const& make_row)
79 C.row_offset.resize(nrows+1);
86 Vector<Long> rbegin{0};
87 rbegin.reserve(nblocks+1);
90 wsum.reserve(nrows+1);
91 for (
Long i = 0; i < nrows; ++i) { wsum.push_back(wsum.back() + work(i)); }
92 for (
int t = 1; t < nblocks; ++t) {
93 rbegin.push_back(std::lower_bound(wsum.begin(), wsum.end(), wsum.back()/nblocks*t)
97 rbegin.push_back(nrows);
103 for (
Long i = 0; i < nrows; ++i) { cap += work(i); }
104 C.col_index.resize(cap);
106 auto row = make_row(0);
108 for (
Long i = 0; i < nrows; ++i) {
109 I
const* rc =
nullptr;
110 T
const* rv =
nullptr;
111 Long const n = row(i, rc, rv);
112 if (p + n >
Long(
C.col_index.size())) {
113 Long const newcap = std::max(p + n,
Long(
C.col_index.size())*3/2);
114 C.col_index.resize(p);
116 C.col_index.resize(newcap);
117 C.mat.resize(newcap);
119 std::copy(rc, rc+n,
C.col_index.data()+p);
120 std::copy(rv, rv+n,
C.mat.data()+p);
123 "SpGEMM: too many nonzeros for the index type");
126 C.col_index.resize(p);
137 Vector<Vector<Buffer>> buffers(nblocks);
138 constexpr Long buffer_size =
Long(1) << 20;
141#pragma omp parallel for schedule(static,1)
143 for (
int t = 0; t < nblocks; ++t) {
144 auto row = make_row(t);
145 auto& bufs = buffers[t];
146 for (
Long i = rbegin[t]; i < rbegin[t+1]; ++i) {
147 I
const* rc =
nullptr;
148 T
const* rv =
nullptr;
149 Long const n = row(i, rc, rv);
150 if (bufs.empty() || bufs.back().n + n >
Long(bufs.back().col.size())) {
152 bufs.back().col.resize(std::max(buffer_size, n));
153 bufs.back().val.resize(std::max(buffer_size, n));
155 auto& buf = bufs.back();
156 std::copy(rc, rc+n, buf.col.data()+buf.n);
157 std::copy(rv, rv+n, buf.val.data()+buf.n);
165 for (
Long i = 0; i < nrows; ++i) {
166 Long const cnt = crow[i+1];
171 "SpGEMM: too many nonzeros for the index type");
172 crow[nrows] = I(total);
174 C.col_index.resize(
C.nnz);
180#pragma omp parallel for schedule(static,1)
182 for (
int t = 0; t < nblocks; ++t) {
183 Long p = crow[rbegin[t]];
184 for (
auto& buf : buffers[t]) {
185 std::copy(buf.col.data(), buf.col.data()+buf.n, ccol+p);
186 std::copy(buf.val.data(), buf.val.data()+buf.n, cmat+p);
199template <
typename T,
template<
typename>
class V,
typename I,
typename F = NoRowPost>
200CSR<T,V,I> spgemm_local_cpu (
Long nrows,
Long ncols,
201 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B,
202 F const& row_post = {})
204 auto nprod = [&] (
Long i) {
206 for (
Long ap = A.row_offset[i]; ap < A.row_offset[i+1]; ++ap) {
207 Long const k = A.col_index[ap];
208 n += B.row_offset[k+1] - B.row_offset[k];
212 return csr_from_rows_cpu<T,V,I>(nrows, nprod, [&] (
int) {
213 return [&, marker = Vector<I>(ncols, I(-1)), col = Vector<I>(), val = Vector<T>(),
214 tmp = Vector<std::pair<I,T>>()]
215 (
Long i, I
const*& rc, T
const*& rv)
mutable ->
Long
217 Long const maxlen = std::min(nprod(i), ncols);
218 if (
Long(col.size()) < maxlen) {
223 for (
Long ap = A.row_offset[i]; ap < A.row_offset[i+1]; ++ap) {
224 Long const k = A.col_index[ap];
225 T
const a = A.mat[ap];
226 for (
Long bp = B.row_offset[k]; bp < B.row_offset[k+1]; ++bp) {
227 I
const j = B.col_index[bp];
228 Long const m = marker[j];
229 if (m >= 0 && m < n && col[m] == j) {
230 val[m] += a * B.mat[bp];
234 val[n] = a * B.mat[bp];
239 sort_row_cpu(col.data(), val.data(), n, tmp);
240 n = row_post(i, col.data(), val.data(), n);
259template <
typename T,
template<
typename>
class V,
typename I,
typename F>
260CSR<T,V,I> spgemm_split_cpu (
Long nrows,
Long ncols,
Long nb,
261 CsrView<T const,I>
const& A0, CsrView<T const,I>
const& A1,
262 CsrView<T const,I>
const& B0, CsrView<T const,I>
const& B1,
263 I
const* b1map, CsrView<T const,I>
const& E,
F const& row_post)
267 return (B0.row_offset[k+1] - B0.row_offset[k])
268 + ((B1.nnz > 0) ? (B1.row_offset[k+1] - B1.row_offset[k]) : 0);
270 return E.row_offset[k-nb+1] - E.row_offset[k-nb];
273 auto nprod = [&] (
Long i) {
275 for (
Long ap = A0.row_offset[i]; ap < A0.row_offset[i+1]; ++ap) { n += blen(A0.col_index[ap]); }
277 for (
Long ap = A1.row_offset[i]; ap < A1.row_offset[i+1]; ++ap) { n += blen(nb + A1.col_index[ap]); }
281 return csr_from_rows_cpu<T,V,I>(nrows, nprod, [&] (
int) {
282 return [&, marker = Vector<I>(ncols, I(-1)), col = Vector<I>(), val = Vector<T>(),
283 tmp = Vector<std::pair<I,T>>()]
284 (
Long i, I
const*& rc, T
const*& rv)
mutable ->
Long
286 Long const maxlen = std::min(nprod(i), ncols);
287 if (
Long(col.size()) < maxlen) {
292 auto add = [&] (I j, T
x) {
293 Long const m = marker[j];
294 if (m >= 0 && m < n && col[m] == j) {
303 auto brow = [&] (
Long k, T a) {
305 for (
Long bp = B0.row_offset[k]; bp < B0.row_offset[k+1]; ++bp) {
306 add(B0.col_index[bp], a * B0.mat[bp]);
309 for (
Long bp = B1.row_offset[k]; bp < B1.row_offset[k+1]; ++bp) {
310 add(b1map[B1.col_index[bp]], a * B1.mat[bp]);
314 for (
Long bp = E.row_offset[k-nb]; bp < E.row_offset[k-nb+1]; ++bp) {
315 add(E.col_index[bp], a * E.mat[bp]);
319 for (
Long ap = A0.row_offset[i]; ap < A0.row_offset[i+1]; ++ap) {
320 brow(A0.col_index[ap], A0.mat[ap]);
323 for (
Long ap = A1.row_offset[i]; ap < A1.row_offset[i+1]; ++ap) {
324 brow(nb + A1.col_index[ap], A1.mat[ap]);
327 sort_row_cpu(col.data(), val.data(), n, tmp);
328 n = row_post(i, col.data(), val.data(), n);
346template <
typename T,
typename I>
349 CsrView<T const,I> A0, A1, P0, P1, PE;
350 I
const* p1map =
nullptr;
352 [[nodiscard]]
Long plen (Long k)
const {
353 return (P0.row_offset[k+1] - P0.row_offset[k])
354 + ((P1.nnz > 0) ? (P1.row_offset[k+1] - P1.row_offset[k]) : 0);
358 [[nodiscard]]
Long maxlen (Long i)
const {
360 for (Long ap = A0.row_offset[i]; ap < A0.row_offset[i+1]; ++ap) { n += plen(A0.col_index[ap]); }
362 for (Long ap = A1.row_offset[i]; ap < A1.row_offset[i+1]; ++ap) {
363 Long const k = A1.col_index[ap];
364 n += PE.row_offset[k+1] - PE.row_offset[k];
372 Long row (Long i, I* marker, I* col, T* val, Long p)
const {
374 auto add = [&] (I j, T
x) {
375 Long const m = marker[j];
376 if (m >= rs && m < p && col[m] == j) {
385 for (Long ap = A0.row_offset[i]; ap < A0.row_offset[i+1]; ++ap) {
386 Long const k = A0.col_index[ap];
387 T
const a = A0.mat[ap];
388 for (Long
pp = P0.row_offset[k];
pp < P0.row_offset[k+1]; ++
pp) {
389 add(P0.col_index[
pp], a * P0.mat[
pp]);
392 for (Long
pp = P1.row_offset[k];
pp < P1.row_offset[k+1]; ++
pp) {
393 add(p1map[P1.col_index[
pp]], a * P1.mat[
pp]);
398 for (Long ap = A1.row_offset[i]; ap < A1.row_offset[i+1]; ++ap) {
399 Long const k = A1.col_index[ap];
400 T
const a = A1.mat[ap];
401 for (Long
pp = PE.row_offset[k];
pp < PE.row_offset[k+1]; ++
pp) {
402 add(PE.col_index[
pp], a * PE.mat[
pp]);
420template <
typename T,
template<
typename>
class V,
typename I>
422 CsrView<T const,I>
const& R0, CsrView<T const,I>
const& R1,
423 CsrView<T const,I>
const& APE, APRowsCpu<T,I>
const& ap)
425 constexpr Long chunk_rows = 1024;
426 Long const nchunks = (nf + chunk_rows - 1) / chunk_rows;
429 auto work = [&] (
Long c) {
431 for (
Long rp = R0.row_offset[c]; rp < R0.row_offset[c+1]; ++rp) {
432 Long const i = R0.col_index[rp];
433 n += ap.A0.row_offset[i+1] - ap.A0.row_offset[i];
434 if (ap.A1.nnz > 0) { n += ap.A1.row_offset[i+1] - ap.A1.row_offset[i]; }
437 for (
Long rp = R1.row_offset[c]; rp < R1.row_offset[c+1]; ++rp) {
438 Long const j = R1.col_index[rp];
439 n += APE.row_offset[j+1] - APE.row_offset[j];
446 Vector<Long> chunk_last(nchunks, -1);
447 for (
Long q = 0; q < nchunks; ++q) {
448 for (
Long i = q*chunk_rows; i < std::min(nf, (q+1)*chunk_rows); ++i) {
449 if (ap.P0.row_offset[i+1] > ap.P0.row_offset[i]) {
450 chunk_last[q] = std::max(chunk_last[q],
451 Long(ap.P0.col_index[ap.P0.row_offset[i+1]-1]));
462 return csr_from_rows_cpu<T,V,I>(nc, work, [&] (
int) {
463 return [&, cmarker = Vector<I>(ncols, I(-1)), marker = Vector<I>(ncols, I(-1)),
464 slot = Vector<int>(nchunks, -1), chunks = Vector<Chunk>(),
465 live = Vector<Long>(), free_slots = Vector<int>(),
466 next_release = std::numeric_limits<Long>::max(),
467 col = Vector<I>(), val = Vector<T>(), tmp = Vector<std::pair<I,T>>()]
468 (
Long c, I
const*& rc, T
const*& rv)
mutable ->
Long
470 auto compute_chunk = [&] (
Long q) {
472 if (free_slots.empty()) {
473 s =
int(chunks.size());
474 chunks.emplace_back();
476 s = free_slots.back();
477 free_slots.pop_back();
481 next_release = std::min(next_release, chunk_last[q]);
482 auto& ch = chunks[
s];
483 Long const i0 = q*chunk_rows;
484 Long const i1 = std::min(nf, i0+chunk_rows);
485 ch.off.resize(i1-i0+1);
488 for (
Long i = i0; i < i1; ++i) {
489 Long const maxlen = ap.maxlen(i);
490 if (
Long(ch.col.size()) < p + maxlen) {
491 ch.col.resize(std::max(p + maxlen,
Long(ch.col.size())*2));
492 ch.val.resize(ch.col.size());
494 p = ap.row(i, cmarker.data(), ch.col.data(), ch.val.data(), p);
500 if (c > next_release) {
501 next_release = std::numeric_limits<Long>::max();
502 for (
Long pos = 0; pos <
Long(live.size()); ) {
503 Long const q = live[pos];
504 if (chunk_last[q] < c) {
505 free_slots.push_back(slot[q]);
507 live[pos] = live.back();
510 next_release = std::min(next_release, chunk_last[q]);
517 for (
Long rp = R0.row_offset[c]; rp < R0.row_offset[c+1]; ++rp) {
518 Long const i = R0.col_index[rp];
519 Long const q = i / chunk_rows;
520 if (slot[q] < 0) { compute_chunk(q); }
521 auto const& ch = chunks[slot[q]];
522 maxlen += ch.off[i-q*chunk_rows+1] - ch.off[i-q*chunk_rows];
525 for (
Long rp = R1.row_offset[c]; rp < R1.row_offset[c+1]; ++rp) {
526 Long const j = R1.col_index[rp];
527 maxlen += APE.row_offset[j+1] - APE.row_offset[j];
530 maxlen = std::min(maxlen, ncols);
531 if (
Long(col.size()) < maxlen) {
537 auto add = [&] (I j, T
x) {
538 Long const m = marker[j];
539 if (m >= 0 && m < n && col[m] == j) {
548 for (
Long rp = R0.row_offset[c]; rp < R0.row_offset[c+1]; ++rp) {
549 Long const i = R0.col_index[rp];
550 T
const r = R0.mat[rp];
551 Long const q = i / chunk_rows;
552 auto const& ch = chunks[slot[q]];
553 for (
Long x = ch.off[i-q*chunk_rows];
x < ch.off[i-q*chunk_rows+1]; ++
x) {
554 add(ch.col[
x], r * ch.val[
x]);
558 for (
Long rp = R1.row_offset[c]; rp < R1.row_offset[c+1]; ++rp) {
559 Long const j = R1.col_index[rp];
560 T
const r = R1.mat[rp];
561 for (
Long x = APE.row_offset[j];
x < APE.row_offset[j+1]; ++
x) {
562 add(APE.col_index[
x], r * APE.mat[
x]);
566 sort_row_cpu(col.data(), val.data(), n, tmp);
574#elif defined(AMREX_USE_CUDA)
576inline void spgemm_cusparse_check (cusparseStatus_t status)
578 if (status == CUSPARSE_STATUS_ALLOC_FAILED ||
579 status == CUSPARSE_STATUS_INSUFFICIENT_RESOURCES) {
580 (void)cudaGetLastError();
581 throw OutOfMemoryError(
"SpGEMM: cuSPARSE ran out of memory");
586template <
typename T,
template<
typename>
class V,
typename I>
587CSR<T,V,I> spgemm_local_cusparse (
Long nrows,
Long ncols,
588 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B)
590 static_assert(std::is_same_v<I,int>,
"spgemm_local_cusparse: 32-bit indices only");
592 cudaDataType data_type;
593 if constexpr (std::is_same_v<T,float>) {
594 data_type = CUDA_R_32F;
595 }
else if constexpr (std::is_same_v<T,double>) {
596 data_type = CUDA_R_64F;
597 }
else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
598 data_type = CUDA_C_32F;
599 }
else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
600 data_type = CUDA_C_64F;
606 ncols <
Long(std::numeric_limits<int>::max()));
609 C.row_offset.resize(nrows+1);
613 cusparseHandle_t handle =
nullptr;
614 cusparseSpMatDescr_t mat_A =
nullptr, mat_B =
nullptr, mat_C =
nullptr;
615 cusparseSpGEMMDescr_t descr =
nullptr;
616 void* buffer1 =
nullptr;
617 void* buffer2 =
nullptr;
618 Resources () =
default;
619 Resources (Resources
const&) =
delete;
620 Resources& operator= (Resources
const&) =
delete;
622 Gpu::streamSynchronize();
623 if (descr) { cusparseSpGEMM_destroyDescr(descr); }
624 if (mat_A) { cusparseDestroySpMat(mat_A); }
625 if (mat_B) { cusparseDestroySpMat(mat_B); }
626 if (mat_C) { cusparseDestroySpMat(mat_C); }
627 if (handle) { cusparseDestroy(handle); }
636 constexpr cusparseIndexType_t index_type = CUSPARSE_INDEX_32I;
637 void* rowA = (
void*)A.row_offset;
638 void* colA = (
void*)A.col_index;
639 void* rowB = (
void*)B.row_offset;
640 void* colB = (
void*)B.col_index;
641 void* rowC = (
void*)
C.row_offset.data();
644 (cusparseCreateCsr(&r.mat_A, nrows, B.nrows, A.nnz, rowA, colA, (
void*)A.mat,
645 index_type, index_type, CUSPARSE_INDEX_BASE_ZERO, data_type));
647 (cusparseCreateCsr(&r.mat_B, B.nrows, ncols, B.nnz, rowB, colB, (
void*)B.mat,
648 index_type, index_type, CUSPARSE_INDEX_BASE_ZERO, data_type));
650 (cusparseCreateCsr(&r.mat_C, nrows, ncols, 0, rowC,
nullptr,
nullptr,
651 index_type, index_type, CUSPARSE_INDEX_BASE_ZERO, data_type));
657 cusparseOperation_t op = CUSPARSE_OPERATION_NON_TRANSPOSE;
658 auto const alg = CUSPARSE_SPGEMM_DEFAULT;
660 std::size_t buffer_size1 = 0;
661 spgemm_cusparse_check
662 (cusparseSpGEMM_workEstimation(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta,
663 r.mat_C, data_type, alg, r.descr,
664 &buffer_size1,
nullptr));
666 spgemm_cusparse_check
667 (cusparseSpGEMM_workEstimation(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta,
668 r.mat_C, data_type, alg, r.descr,
669 &buffer_size1, r.buffer1));
671 std::size_t buffer_size2 = 0;
672 spgemm_cusparse_check
673 (cusparseSpGEMM_compute(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta,
674 r.mat_C, data_type, alg, r.descr,
675 &buffer_size2,
nullptr));
677 spgemm_cusparse_check
678 (cusparseSpGEMM_compute(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta,
679 r.mat_C, data_type, alg, r.descr,
680 &buffer_size2, r.buffer2));
682 std::int64_t c_nrows, c_ncols, c_nnz;
688 C.col_index.resize(c_nnz);
690 void* colC = (
void*)
C.col_index.data();
695 (cusparseSpGEMM_copy(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta, r.mat_C,
696 data_type, alg, r.descr));
701#elif defined(AMREX_USE_HIP)
703inline void spgemm_rocsparse_check (rocsparse_status status)
705 if (status == rocsparse_status_memory_error) {
706 (void)hipGetLastError();
707 throw OutOfMemoryError(
"SpGEMM: rocSPARSE ran out of memory");
709 AMREX_ROCSPARSE_SAFE_CALL(status);
712template <
typename T,
template<
typename>
class V,
typename I>
713CSR<T,V,I> spgemm_local_rocsparse (
Long nrows,
Long ncols,
714 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B)
716 static_assert(std::is_same_v<I,int>,
"spgemm_local_rocsparse: 32-bit indices only");
719 ncols <
Long(std::numeric_limits<int>::max()));
721 rocsparse_datatype data_type;
722 if constexpr (std::is_same_v<T,float>) {
723 data_type = rocsparse_datatype_f32_r;
724 }
else if constexpr (std::is_same_v<T,double>) {
725 data_type = rocsparse_datatype_f64_r;
726 }
else if constexpr (std::is_same_v<T,GpuComplex<float>>) {
727 data_type = rocsparse_datatype_f32_c;
728 }
else if constexpr (std::is_same_v<T,GpuComplex<double>>) {
729 data_type = rocsparse_datatype_f64_c;
734 constexpr rocsparse_indextype index_type = rocsparse_indextype_i32;
735 constexpr rocsparse_index_base index_base = rocsparse_index_base_zero;
738 C.row_offset.resize(nrows+1);
742 rocsparse_handle handle =
nullptr;
743 rocsparse_spmat_descr mat_A =
nullptr, mat_B =
nullptr, mat_C =
nullptr,
745 void* buffer =
nullptr;
746 Resources () =
default;
747 Resources (Resources
const&) =
delete;
748 Resources& operator= (Resources
const&) =
delete;
750 Gpu::streamSynchronize();
751 for (
auto m : {mat_A, mat_B, mat_C, mat_D}) {
752 if (m) { rocsparse_destroy_spmat_descr(m); }
754 if (handle) { rocsparse_destroy_handle(handle); }
759 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_create_handle(&r.handle));
760 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_set_stream(r.handle,
Gpu::gpuStream()));
762 AMREX_ROCSPARSE_SAFE_CALL
763 (rocsparse_create_csr_descr(&r.mat_A, nrows, B.nrows, A.nnz,
764 (
void*)A.row_offset, (
void*)A.col_index, (
void*)A.mat,
765 index_type, index_type, index_base, data_type));
766 AMREX_ROCSPARSE_SAFE_CALL
767 (rocsparse_create_csr_descr(&r.mat_B, B.nrows, ncols, B.nnz,
768 (
void*)B.row_offset, (
void*)B.col_index, (
void*)B.mat,
769 index_type, index_type, index_base, data_type));
770 AMREX_ROCSPARSE_SAFE_CALL
771 (rocsparse_create_csr_descr(&r.mat_C, nrows, ncols, 0,
772 (
void*)
C.row_offset.data(),
nullptr,
nullptr,
773 index_type, index_type, index_base, data_type));
775 AMREX_ROCSPARSE_SAFE_CALL
776 (rocsparse_create_csr_descr(&r.mat_D, 0, 0, 0,
nullptr,
nullptr,
nullptr,
777 index_type, index_type, index_base, data_type));
781 auto const op = rocsparse_operation_none;
782 auto const alg = rocsparse_spgemm_alg_default;
784 std::size_t buffer_size = 0;
785 spgemm_rocsparse_check
786 (rocsparse_spgemm(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta, r.mat_D, r.mat_C,
787 data_type, alg, rocsparse_spgemm_stage_buffer_size,
788 &buffer_size,
nullptr));
792 spgemm_rocsparse_check
793 (rocsparse_spgemm(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta, r.mat_D, r.mat_C,
794 data_type, alg, rocsparse_spgemm_stage_nnz,
795 &buffer_size, r.buffer));
797 std::int64_t c_nrows, c_ncols, c_nnz;
798 AMREX_ROCSPARSE_SAFE_CALL(rocsparse_spmat_get_size(r.mat_C, &c_nrows, &c_ncols, &c_nnz));
803 C.col_index.resize(c_nnz);
805 AMREX_ROCSPARSE_SAFE_CALL
806 (rocsparse_csr_set_pointers(r.mat_C, (
void*)
C.row_offset.data(),
807 (
void*)
C.col_index.data(), (
void*)
C.mat.data()));
809 spgemm_rocsparse_check
810 (rocsparse_spgemm(r.handle, op, op, &alpha, r.mat_A, r.mat_B, &
beta, r.mat_D, r.mat_C,
811 data_type, alg, rocsparse_spgemm_stage_compute,
812 &buffer_size, r.buffer));
823#elif defined(AMREX_USE_SYCL)
825template <
typename T,
template<
typename>
class V,
typename I>
826CSR<T,V,I> spgemm_local_onemkl (
Long nrows,
Long ncols,
827 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B)
829 auto& q = Gpu::Device::streamQueue();
832 C.row_offset.resize(nrows+1);
840 mkl::sparse::matrix_handle_t hA{}, hB{}, hC{};
841 mkl::sparse::matmat_descr_t descr =
nullptr;
842 std::int64_t* size_buf =
nullptr;
843 void* buffer1 =
nullptr;
844 void* buffer2 =
nullptr;
845 explicit Resources (sycl::queue& a_q) : q(a_q) {}
846 Resources (Resources
const&) =
delete;
847 Resources& operator= (Resources
const&) =
delete;
850 if (descr) { mkl::sparse::release_matmat_descr(&descr); }
851 mkl::sparse::release_matrix_handle(q, &hA);
852 mkl::sparse::release_matrix_handle(q, &hB);
853 mkl::sparse::release_matrix_handle(q, &hC).wait();
854 Gpu::streamSynchronize();
861 mkl::sparse::init_matrix_handle(&r.hA);
862 mkl::sparse::init_matrix_handle(&r.hB);
863 mkl::sparse::init_matrix_handle(&r.hC);
865#if defined(INTEL_MKL_VERSION) && (INTEL_MKL_VERSION < 20250300)
866 mkl::sparse::set_csr_data(q, r.hA, I(nrows), I(B.nrows), mkl::index_base::zero,
867 (I*)A.row_offset, (I*)A.col_index, (T*)A.mat);
868 mkl::sparse::set_csr_data(q, r.hB, I(B.nrows), I(ncols), mkl::index_base::zero,
869 (I*)B.row_offset, (I*)B.col_index, (T*)B.mat);
870 mkl::sparse::set_csr_data(q, r.hC, I(nrows), I(ncols), mkl::index_base::zero,
871 C.row_offset.data(), dummy_col.data(), dummy_mat.data());
873 mkl::sparse::set_csr_data(q, r.hA, I(nrows), I(B.nrows), I(A.nnz), mkl::index_base::zero,
874 (I*)A.row_offset, (I*)A.col_index, (T*)A.mat);
875 mkl::sparse::set_csr_data(q, r.hB, I(B.nrows), I(ncols), I(B.nnz), mkl::index_base::zero,
876 (I*)B.row_offset, (I*)B.col_index, (T*)B.mat);
877 mkl::sparse::set_csr_data(q, r.hC, I(nrows), I(ncols), I(0), mkl::index_base::zero,
878 C.row_offset.data(), dummy_col.data(), dummy_mat.data());
881 mkl::sparse::init_matmat_descr(&r.descr);
882 mkl::sparse::set_matmat_data(r.descr,
883 mkl::sparse::matrix_view_descr::general,
884 mkl::transpose::nontrans,
885 mkl::sparse::matrix_view_descr::general,
886 mkl::transpose::nontrans,
887 mkl::sparse::matrix_view_descr::general);
889 using req = mkl::sparse::matmat_request;
891 auto* size_buf = r.size_buf;
893 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::get_work_estimation_buf_size, r.descr,
894 size_buf,
nullptr, {}).wait();
896 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::work_estimation, r.descr,
897 size_buf, r.buffer1, {}).wait();
899 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::get_compute_buf_size, r.descr,
900 size_buf,
nullptr, {}).wait();
902 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::compute, r.descr,
903 size_buf, r.buffer2, {}).wait();
905 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::get_nnz, r.descr,
906 size_buf,
nullptr, {}).wait();
907 Long const c_nnz = *size_buf;
911 C.col_index.resize(c_nnz);
913#if defined(INTEL_MKL_VERSION) && (INTEL_MKL_VERSION < 20250300)
914 mkl::sparse::set_csr_data(q, r.hC, I(nrows), I(ncols), mkl::index_base::zero,
915 C.row_offset.data(),
C.col_index.data(),
C.mat.data());
917 mkl::sparse::set_csr_data(q, r.hC, I(nrows), I(ncols), I(c_nnz), mkl::index_base::zero,
918 C.row_offset.data(),
C.col_index.data(),
C.mat.data());
921 mkl::sparse::matmat(q, r.hA, r.hB, r.hC, req::finalize, r.descr,
922 size_buf,
nullptr, {}).wait();
939template <
typename T,
template<
typename>
class V,
typename I>
940using SpGEMMLocalFn = CSR<T,V,I> (*) (
Long,
Long, CsrView<T const,I>
const&,
941 CsrView<T const,I>
const&);
944template <
typename T,
template<
typename>
class V,
typename I>
945void spgemm_copy_block (CSR<T,V,I>&
C,
Long r0,
Long r1, CSR<T,V,I>
const& Cb,
Long p)
947 auto* crow =
C.row_offset.data() + r0;
948 auto const* src = Cb.row_offset.data();
951 crow[i] = src[i] +
offset;
954 C.col_index.begin() + p);
963template <
typename T,
template<
typename>
class V,
typename I>
964Vector<std::pair<Long,Long>>
965spgemm_blocks (Vector<std::pair<Long,Long>>
const& blocks,
Long ncols,
966 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B,
967 SpGEMMLocalFn<T,V,I> f, CSR<T,V,I>*
C,
Long& p)
969 Vector<std::pair<Long,Long>> done;
970 Vector<std::pair<Long,Long>> todo(blocks.rbegin(), blocks.rend());
971 while (!todo.empty()) {
972 auto const [r0, r1] = todo.back();
974 Long const n = r1 - r0;
975 if (n == 0) {
continue; }
987 Cb = spgemm_empty<T,V,I>(n);
990 auto* prow = row.data();
991 auto const* arow = A.row_offset + r0;
993 CsrView<T const,I> Ab{A.mat + a0, A.col_index + a0, row.data(), annz, n};
994 Cb = f(n, ncols, Ab, B);
996 if (
C) { spgemm_copy_block(*
C, r0, r1, Cb, p); }
998 }
catch (OutOfMemoryError
const&) {
999 if (n == 1) {
throw; }
1000 todo.emplace_back(r0 + n/2, r1);
1001 todo.emplace_back(r0, r0 + n/2);
1004 done.emplace_back(r0, r1);
1014template <
typename T,
template<
typename>
class V,
typename I>
1015CSR<T,V,I> spgemm_local_chunked (
Long nrows,
Long ncols,
1016 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B,
1017 SpGEMMLocalFn<T,V,I> f)
1020 auto const blocks = spgemm_blocks<T,V,I>({{0, nrows/2}, {nrows/2, nrows}}, ncols, A, B, f,
1025 C.resize(nrows, total);
1027 spgemm_blocks<T,V,I>(blocks, ncols, A, B, f, &
C, p);
1043template <
typename T,
template<
typename>
class V,
typename I,
typename F = NoRowPost>
1044CSR<T,V,I> spgemm_local (
Long nrows,
Long ncols,
1045 CsrView<T const,I>
const& A, CsrView<T const,I>
const& B,
1046 F const& row_post = {})
1050 if (nrows <= 0 || ncols <= 0 || A.nnz <= 0 || B.nnz <= 0 || B.nrows <= 0) {
1051 return spgemm_empty<T,V,I>(nrows);
1054#if !defined(AMREX_USE_GPU)
1055 return spgemm_local_cpu<T,V,I>(nrows, ncols, A, B, row_post);
1059#if defined(AMREX_USE_CUDA)
1061 return spgemm_local_cusparse<T,V,I>(nrows, ncols, A, B);
1062 }
catch (OutOfMemoryError
const&) {
1063 return spgemm_local_chunked<T,V,I>(nrows, ncols, A, B,
1064 spgemm_local_cusparse<T,V,I>);
1066#elif defined(AMREX_USE_HIP)
1068 return spgemm_local_rocsparse<T,V,I>(nrows, ncols, A, B);
1069 }
catch (OutOfMemoryError
const&) {
1070 return spgemm_local_chunked<T,V,I>(nrows, ncols, A, B,
1071 spgemm_local_rocsparse<T,V,I>);
1073#elif defined(AMREX_USE_SYCL)
1075 return spgemm_local_onemkl<T,V,I>(nrows, ncols, A, B);
1076 }
catch (OutOfMemoryError
const&) {
1077 return spgemm_local_chunked<T,V,I>(nrows, ncols, A, B,
1078 spgemm_local_onemkl<T,V,I>);
1088template <
typename T,
template<
typename>
class V,
typename I,
typename F0,
typename F1>
1089CSR<T,V,int> concat_csr_cols (
Long nrows, CsrView<T const,I>
const& X0, CsrView<T const,I>
const& X1,
1090 F0
const& map0, F1
const& map1)
1094 C.resize(nrows, X0.nnz + X1.nnz);
1100 crow[i] = X0.row_offset[i] + X1.row_offset[i];
1103 for (
Long q = X0.row_offset[i]; q < X0.row_offset[i+1]; ++q) {
1104 ccol[p] = map0(X0.col_index[q]);
1105 cmat[p] = X0.mat[q];
1108 for (
Long q = X1.row_offset[i]; q < X1.row_offset[i+1]; ++q) {
1109 ccol[p] = map1(X1.col_index[q]);
1110 cmat[p] = X1.mat[q];
1122template <
typename T,
template<
typename>
class V>
1123void append_ext_rows (CSR<T,V,int>& Bh,
Long const* ext_row_offset,
Long const* ext_col,
1124 T
const* ext_mat,
Long n_ext,
Long nnz_ext,
1127 Long const nb = Bh.nrows();
1128 Long const nnz_b = Bh.nnz;
1130 Bh.mat.resize(nnz_b + nnz_ext);
1131 Bh.col_index.resize(nnz_b + nnz_ext);
1132 Bh.row_offset.resize(nb + n_ext + 1);
1133 Bh.nnz = nnz_b + nnz_ext;
1137 Long const nlocal = c1 - c0;
1140 Long const b = ext_row_offset[r];
1141 Long const e = ext_row_offset[r+1];
1142 brow[nb+r+1] =
int(nnz_b + e);
1146 for (
Long q = p0; q < p1; ++q) {
1147 bcol[p] =
int(ext_col[q] - c0);
1148 bmat[p] = ext_mat[q];
1151 for (
Long q = b; q < e; ++q) {
1152 if (q < p0 || q >= p1) {
1154 bmat[p] = ext_mat[q];
1167template <
typename T,
template <
typename>
class Allocator,
typename F>
1168SpMatrix<T,Allocator>
1169SpGEMM (SpMatrix<T,Allocator>
const& A, SpMatrix<T,Allocator>
const& B,
1170 AlgPartition
const& col_partition,
F const& row_post);
1187template <
typename T,
template <
typename>
class Allocator>
1188SpMatrix<T,Allocator>
1192 return SpGEMM(A, B, col_partition, detail::NoRowPost{});
1210template <
typename T,
template <
typename>
class Allocator,
typename F>
1211SpMatrix<T,Allocator>
1217 static_assert(std::is_same_v<F,detail::NoRowPost>,
"SpGEMM: row_post is CPU only");
1223 auto& Am =
const_cast<SpMat&
>(A);
1224 auto& Bm =
const_cast<SpMat&
>(B);
1228 using LongVec =
typename SpMat::template container_type<Long>;
1230 Am.setColumnPartition(Bm.partition());
1231 Bm.setColumnPartition(col_partition);
1233 Long const nb = Bm.numLocalRows();
1236 Long const nlocal = c1 - c0;
1239 typename SpMat::RemoteRowsMM ext;
1240 if (! detail::spmat_comm_is_local(Am.partition(), Bm.partition())) {
1241 ext = Am.fetch_remote_rows_mm(Bm);
1244 if (ext.nrows == 0 && Am.m_remote_cols_v.empty() && Bm.m_remote_cols_v.empty()) {
1245 auto Ch = detail::spgemm_local<T,SpMat::template container_type,int>
1246 (nrows, nlocal, Am.m_csr_local.const_view(), Bm.m_csr_local.const_view(), row_post);
1248 C.define_split(Am.partition(), col_partition, std::move(Ch), nlocal,
nullptr, 0);
1255 for (
Long i = 0; i < ext.nnz; ++i) {
1256 auto g = ext.col_index[i];
1257 if (g < c0 || g >= c1) { ru_h.push_back(g); }
1263 Long const* ru = ru_d.data();
1264 Long const ncols_hat = nlocal + nru;
1266 nb +
Long(Am.m_remote_cols_v.size()) <
Long(std::numeric_limits<int>::max()));
1268 using local_csr_type =
typename SpMat::local_csr_type;
1269#ifndef AMREX_USE_GPU
1275 E.row_offset[0] = 0;
1276 if (ext.nrows > 0) {
1277 detail::append_ext_rows(E, ext.row_offset.data(), ext.col_index, ext.mat,
1278 ext.nrows, ext.nnz, c0, c1, ru, nru);
1283 b1map[j] =
int(nlocal + (std::lower_bound(ru, ru+nru, Bm.m_remote_cols_v[j]) - ru));
1286 : Am.remote_full_const_view();
1288 : Bm.remote_full_const_view();
1289 auto Ch = detail::spgemm_split_cpu<T,SpMat::template container_type,int>
1290 (nrows, ncols_hat, nb, Am.m_csr_local.const_view(), a1,
1291 Bm.m_csr_local.const_view(), b1, b1map.data(), E.const_view(), row_post);
1293 C.define_split(Am.partition(), col_partition, std::move(Ch), nlocal, ru, nru);
1297 local_csr_type Ah = detail::concat_csr_cols<T,SpMat::template container_type>
1298 (nrows, Am.m_csr_local.const_view(), Am.remote_full_const_view(),
1303 Long const* b_rcols = Bm.m_remote_cols_dv.data();
1305 Long const* b_rcols = Bm.m_remote_cols_v.data();
1307 local_csr_type Bh = detail::concat_csr_cols<T,SpMat::template container_type>
1308 (nb, Bm.m_csr_local.const_view(), Bm.remote_full_const_view(),
1313 if (ext.nrows > 0) {
1314 LongVec ext_col_d(ext.nnz);
1317 detail::append_ext_rows(Bh, ext.row_offset.data(), ext_col_d.data(), ext.mat,
1318 ext.nrows, ext.nnz, c0, c1, ru, nru);
1323 local_csr_type Ch = detail::spgemm_local<T,SpMat::template container_type,int>
1324 (nrows, ncols_hat, Ah.const_view(), Bh.const_view());
1325 Ah = local_csr_type{};
1326 Bh = local_csr_type{};
1331 C.define_split(Am.partition(), col_partition, std::move(Ch), nlocal, ru, nru);
1336 Am.setColumnPartition(Bm.partition());
1337 Bm.setColumnPartition(col_partition);
1342 auto Ch = detail::spgemm_local<T,SpMat::template container_type,int>
1343 (nrows, ncols, Am.m_csr_local.const_view(), Bm.m_csr_local.const_view(), row_post);
1345 C.define_split(Am.partition(), col_partition, std::move(Ch), ncols,
nullptr, 0);
1362template <
typename T,
template <
typename>
class Allocator>
1363SpMatrix<T,Allocator>
1369 auto AP =
SpGEMM(A, P, col_partition);
1370 return SpGEMM(R, AP, col_partition);
1373 using local_csr_type =
typename SpMat::local_csr_type;
1374 auto& Rm =
const_cast<SpMat&
>(R);
1375 auto& Am =
const_cast<SpMat&
>(A);
1376 auto& Pm =
const_cast<SpMat&
>(P);
1378 Pm.setColumnPartition(col_partition);
1379 Rm.setColumnPartition(Am.partition());
1381 Long const nf = Am.numLocalRows();
1385 detail::APRowsCpu<T,int> ap;
1386 ap.A0 = Am.m_csr_local.const_view();
1387 ap.P0 = Pm.m_csr_local.const_view();
1389 local_csr_type pe, ape;
1393 ape.row_offset[0] = 0;
1394 ap.PE = pe.const_view();
1401 if (! Am.m_remote_cols_v.empty()) { ap.A1 = Am.remote_full_const_view(); }
1402 if (! Pm.m_remote_cols_v.empty()) { ap.P1 = Pm.remote_full_const_view(); }
1407 auto add_remote = [&] (
Vector<Long>& cols,
typename SpMat::RemoteRowsMM
const& ext) {
1408 for (
Long i = 0; i < ext.nnz; ++i) {
1409 auto g = ext.col_index[i];
1410 if (g < c0 || g >= c1) { cols.push_back(g); }
1414 auto compact_ext = [&] (local_csr_type& E,
typename SpMat::RemoteRowsMM
const& ext,
1417 E.row_offset[0] = 0;
1418 if (ext.nrows > 0) {
1419 detail::append_ext_rows(E, ext.row_offset.data(), ext.col_index, ext.mat,
1420 ext.nrows, ext.nnz, c0, c1, cols.data(),
Long(cols.
size()));
1426 p1map.reserve(Pm.m_remote_cols_v.size());
1427 for (
auto const g : Pm.m_remote_cols_v) {
1428 p1map.push_back(
int(nc + (std::lower_bound(cols.begin(), cols.end(), g)
1431 ap.p1map = p1map.data();
1435 typename SpMat::RemoteRowsMM pext;
1436 if (! detail::spmat_comm_is_local(Am.partition(), Pm.partition())) {
1437 pext = Am.fetch_remote_rows_mm(Pm);
1440 add_remote(ru_ap, pext);
1446 compact_ext(pe, pext, ru_ap);
1447 ap.PE = pe.const_view();
1451 auto has_remote = [&] (
Long i) {
1452 return ap.P1.nnz > 0 && ap.P1.row_offset[i+1] > ap.P1.row_offset[i];
1454 auto csr = detail::csr_from_rows_cpu<T,SpMat::template container_type,int>
1455 (nf, [&] (
Long i) {
return has_remote(i) ? ap.maxlen(i) :
Long(0); },
1459 (
Long i,
int const*& rc, T
const*& rv)
mutable ->
Long
1462 if (has_remote(i)) {
1463 Long const maxlen = ap.maxlen(i);
1464 if (
Long(col.size()) < maxlen) {
1468 n = ap.row(i, marker.data(), col.data(), val.data(), 0);
1470 detail::sort_row_cpu(col.data(), val.data(), n, tmp);
1477 APb.define_split(Am.partition(), col_partition, std::move(csr), nc,
1482 typename SpMat::RemoteRowsMM apext;
1483 if (! detail::spmat_comm_is_local(Rm.partition(), APb.partition())) {
1484 apext = Rm.fetch_remote_rows_mm(APb);
1487 add_remote(ru, apext);
1489 compact_ext(pe, pext, ru);
1490 compact_ext(ape, apext, ru);
1491 ap.PE = pe.const_view();
1495 if (! Rm.m_remote_cols_v.empty()) { r1 = Rm.remote_full_const_view(); }
1499 auto csr = detail::rap_local_cpu<T,SpMat::template container_type,int>
1500 (nf, nc, nc+nru, Rm.m_csr_local.const_view(), r1, ape.const_view(), ap);
1502 C.define_split(Rm.partition(), col_partition, std::move(csr), nc, ru.data(), nru);
General-purpose algorithm utilities available on both host and device.
#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_ASSUME(ASSUMPTION)
Definition AMReX_Extension.H:287
#define AMREX_RESTRICT
Definition AMReX_Extension.H:37
#define AMREX_CUSPARSE_SAFE_CALL(call)
Definition AMReX_GpuError.H:101
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
amrex::ParmParse pp
Input file parser instance for the given namespace.
Definition AMReX_HypreIJIface.cpp:18
Array4< int const > offset
Definition AMReX_HypreMLABecLap.cpp:1139
GpuArray< Real, 3 > beta
Definition AMReX_MLEBNodeFDLaplacian.cpp:1834
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
Long globalRowEnd() const
Exclusive global index end on this process.
Definition AMReX_AlgPartition.H:67
Long globalRowBegin() const
Inclusive global index begin on this process.
Definition AMReX_AlgPartition.H:62
Long numLocalRows() const
Number of local rows.
Definition AMReX_AlgPartition.H:50
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.
Distributed CSR matrix that manages storage and GPU-friendly partitions.
Definition AMReX_SpMatrix.H:65
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 numLocalRows() const
Number of rows owned by this rank.
Definition AMReX_SpMatrix.H:194
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_Pinned_Arena()
Definition AMReX_Arena.cpp:869
Arena * The_Arena()
Definition AMReX_Arena.cpp:829
__host__ __device__ ItType lower_bound(ItType first, ItType last, const ValType &val)
Return an iterator to the first element not less than a given value.
Definition AMReX_Algorithm.H:298
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 HostToDevice hostToDevice
Definition AMReX_GpuContainers.H:105
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:310
void dtoh_memcpy_async(void *p_h, const void *p_d, const std::size_t sz) noexcept
Definition AMReX_GpuDevice.H:435
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:291
constexpr int get_max_threads()
Definition AMReX_OpenMP.H:36
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
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
SpMatrix< T, Allocator > RAP(SpMatrix< T, Allocator > const &R, SpMatrix< T, Allocator > const &A, SpMatrix< T, Allocator > const &P, AlgPartition const &col_partition)
Galerkin product R (A P), with the result of SpGEMM(R, SpGEMM(A, P, col_partition),...
Definition AMReX_SpGEMM.H:1364
SpMatrix< T, Allocator > SpGEMM(SpMatrix< T, Allocator > const &A, SpMatrix< T, Allocator > const &B, AlgPartition const &col_partition, F const &row_post)
SpGEMM with a callback on each row of the product.
Definition AMReX_SpGEMM.H:1212
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
Lightweight non-owning CSR view that can point to host or device buffers.
Definition AMReX_CSR.H:35
CI *__restrict__ row_offset
Definition AMReX_CSR.H:40