DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
MatrixCSR.h
1// Copyright (C) 2021-2026 Garth N. Wells and Chris N. Richardson
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "SparsityPattern.h"
10#include "Vector.h"
11#include "matrix_csr_impl.h"
12#include <algorithm>
13#include <dolfinx/common/IndexMap.h>
14#include <dolfinx/common/MPI.h>
15#include <dolfinx/common/types.h>
16#include <dolfinx/graph/AdjacencyList.h>
17#include <mpi.h>
18#include <numeric>
19#include <ranges>
20#include <span>
21#include <stdexcept>
22#include <utility>
23#include <vector>
24
25// Define requirements on sparsity pattern required for MatrixCSR constructor
26// allowing alternative implementations that can provide these essentials.
27template <typename T>
28concept SparsityImplementation = requires(T sp, int i) {
29 { sp.graph() };
30 requires std::forward_iterator<typename decltype(sp.graph().first)::iterator>;
31 requires std::convertible_to<std::int32_t,
32 typename decltype(sp.graph().first)::value_type>;
33 requires std::forward_iterator<
34 typename decltype(sp.graph().second)::iterator>;
35 requires std::convertible_to<
36 std::int64_t, typename decltype(sp.graph().second)::value_type>;
37
38 { sp.block_size(i) } -> std::same_as<int>;
39 {
40 sp.index_map(i)
41 } -> std::same_as<std::shared_ptr<const dolfinx::common::IndexMap>>;
42};
43
44namespace dolfinx::la
45{
47enum class BlockMode : int
48{
49 compact = 0,
54};
55
70template <typename Scalar, typename Container = std::vector<Scalar>,
71 typename ColContainer = std::vector<std::int32_t>,
72 typename RowPtrContainer = std::vector<std::int64_t>>
73class MatrixCSR
74{
75 static_assert(std::is_same_v<typename Container::value_type, Scalar>);
76 static_assert(std::is_integral_v<typename ColContainer::value_type>);
77 static_assert(std::is_integral_v<typename RowPtrContainer::value_type>);
78
79 template <typename, typename, typename, typename>
80 friend class MatrixCSR;
81
82public:
84 using value_type = Scalar;
85
87 using container_type = Container;
88
90 using column_container_type = ColContainer;
91
93 using rowptr_container_type = RowPtrContainer;
94
118 template <int BS0 = 1, int BS1 = 1>
120 {
121 if ((BS0 != _bs[0] and BS0 > 1 and _bs[0] > 1)
122 or (BS1 != _bs[1] and BS1 > 1 and _bs[1] > 1))
123 {
124 throw std::invalid_argument(
125 "Cannot insert blocks of different size than matrix block size");
126 }
127
128 return [this](const common::LocalIndexRange auto& rows,
129 const common::LocalIndexRange auto& cols,
130 std::span<const value_type> data) -> int
131 {
132 this->set<BS0, BS1>(
133 data,
134 std::span<const std::int32_t>(std::ranges::data(rows),
135 std::ranges::size(rows)),
136 std::span<const std::int32_t>(std::ranges::data(cols),
137 std::ranges::size(cols)));
138 return 0;
139 };
140 }
141
165 template <int BS0 = 1, int BS1 = 1>
167 {
168 if ((BS0 != _bs[0] and BS0 > 1 and _bs[0] > 1)
169 or (BS1 != _bs[1] and BS1 > 1 and _bs[1] > 1))
170 {
171 throw std::invalid_argument(
172 "Cannot insert blocks of different size than matrix block size");
173 }
174
175 return [this](const common::LocalIndexRange auto& rows,
176 const common::LocalIndexRange auto& cols,
177 std::span<const value_type> data) -> int
178 {
179 this->add<BS0, BS1>(
180 data,
181 std::span<const std::int32_t>(std::ranges::data(rows),
182 std::ranges::size(rows)),
183 std::span<const std::int32_t>(std::ranges::data(cols),
184 std::ranges::size(cols)));
185 return 0;
186 };
187 }
188
212 template <SparsityImplementation T>
213 MatrixCSR(const T& p, BlockMode mode = BlockMode::compact);
214
215 // Copy constructor (deleted). Copying deep-copies the matrix data and
216 // duplicates the communicator, which is collective.
217 MatrixCSR(const MatrixCSR& A) = delete;
218
222 MatrixCSR(MatrixCSR&& A) = default;
223
225 ~MatrixCSR() = default;
226
227 // Copy assignment (deleted). Same reasons as the copy constructor.
228 MatrixCSR& operator=(const MatrixCSR& A) = delete;
229
235 MatrixCSR& operator=(MatrixCSR&& A) = default;
236
250 template <typename Scalar0, typename Container0, typename ColContainer0,
251 typename RowPtrContainer0>
252 explicit MatrixCSR(
253 const MatrixCSR<Scalar0, Container0, ColContainer0, RowPtrContainer0>& A)
254 : _index_maps(A._index_maps), _block_mode(A.block_mode()),
255 _bs(A.block_size()), _data(A._data.begin(), A._data.end()),
256 _cols(A.cols().begin(), A.cols().end()),
257 _row_ptr(A.row_ptr().begin(), A.row_ptr().end()),
258 _off_diagonal_offset(A.off_diag_offset().begin(),
259 A.off_diag_offset().end()),
260 _comm(A.comm()), _unpack_pos(A._unpack_pos),
261 _val_send_disp(A._val_send_disp), _val_recv_disp(A._val_recv_disp),
262 _ghost_row_to_rank(A._ghost_row_to_rank), _finalized(A._finalized)
263 {
264 }
265
270 [[deprecated("Use std::ranges::fill(A.values(), v) instead.")]]
272 {
273 check_not_finalized();
274 std::ranges::fill(_data, x);
275 }
276
293 template <int BS0, int BS1>
294 void set(std::span<const value_type> x, std::span<const std::int32_t> rows,
295 std::span<const std::int32_t> cols)
296 {
297 check_not_finalized();
298 auto set_fn = [](value_type& y, const value_type& x) { y = x; };
299
300 std::int32_t num_rows
301 = _index_maps[0]->size_local() + _index_maps[0]->num_ghosts();
302 assert(x.size() == rows.size() * cols.size() * BS0 * BS1);
303 if (_bs[0] == BS0 and _bs[1] == BS1)
304 {
305 impl::insert_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols, set_fn,
306 num_rows);
307 }
308 else if (_bs[0] == 1 and _bs[1] == 1)
309 {
310 // Set blocked data in a regular CSR matrix (_bs[0]=1, _bs[1]=1)
311 // with correct sparsity
312 impl::insert_blocked_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols,
313 set_fn, num_rows);
314 }
315 else
316 {
317 assert(BS0 == 1 and BS1 == 1);
318 // Set non-blocked data in a blocked CSR matrix (BS0=1, BS1=1)
319 impl::insert_nonblocked_csr(_data, _cols, _row_ptr, x, rows, cols, set_fn,
320 num_rows, _bs[0], _bs[1]);
321 }
322 }
323
339 template <int BS0 = 1, int BS1 = 1>
340 void add(std::span<const value_type> x, std::span<const std::int32_t> rows,
341 std::span<const std::int32_t> cols)
342 {
343 check_not_finalized();
344 auto add_fn = [](value_type& y, const value_type& x) { y += x; };
345
346 assert(x.size() == rows.size() * cols.size() * BS0 * BS1);
347 if (_bs[0] == BS0 and _bs[1] == BS1)
348 {
349 impl::insert_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols, add_fn,
350 _row_ptr.size());
351 }
352 else if (_bs[0] == 1 and _bs[1] == 1)
353 {
354 // Add blocked data to a regular CSR matrix (_bs[0]=1, _bs[1]=1)
355 impl::insert_blocked_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols,
356 add_fn, _row_ptr.size());
357 }
358 else
359 {
360 assert(BS0 == 1 and BS1 == 1);
361 // Add non-blocked data to a blocked CSR matrix (BS0=1, BS1=1)
362 impl::insert_nonblocked_csr(_data, _cols, _row_ptr, x, rows, cols, add_fn,
363 _row_ptr.size(), _bs[0], _bs[1]);
364 }
365 }
366
368 std::int32_t num_owned_rows() const { return _index_maps[0]->size_local(); }
369
371 std::int32_t num_all_rows() const { return _row_ptr.size() - 1; }
372
382 std::vector<value_type> to_dense() const
383 {
384 const std::size_t nrows = num_all_rows();
385 const std::size_t ncols = _index_maps[1]->size_global();
386 std::vector<value_type> A(nrows * ncols * _bs[0] * _bs[1], value_type(0));
387 for (std::size_t r = 0; r < nrows; ++r)
388 {
389 for (std::int32_t j = _row_ptr[r]; j < _row_ptr[r + 1]; ++j)
390 {
391 for (int i0 = 0; i0 < _bs[0]; ++i0)
392 {
393 for (int i1 = 0; i1 < _bs[1]; ++i1)
394 {
395 std::array<std::int32_t, 1> local_col{_cols[j]};
396 std::array<std::int64_t, 1> global_col{0};
397 _index_maps[1]->local_to_global(local_col, global_col);
398 A[(r * _bs[0] + i0) * ncols * _bs[1] + global_col[0] * _bs[1] + i1]
399 = _data[j * _bs[0] * _bs[1] + i0 * _bs[1] + i1];
400 }
401 }
402 }
403 }
404
405 return A;
406 }
407
415 {
418 }
419
430 {
431 check_not_finalized();
432 const std::int32_t local_size0 = _index_maps[0]->size_local();
433 const std::int32_t num_ghosts0 = _index_maps[0]->num_ghosts();
434 const int bs2 = _bs[0] * _bs[1];
435
436 // For each ghost row, pack and send values to send to neighborhood
437 std::vector<int> insert_pos = _val_send_disp;
438 _ghost_value_data.resize(_val_send_disp.back());
439 for (int i = 0; i < num_ghosts0; ++i)
440 {
441 int rank = _ghost_row_to_rank[i];
442
443 // Get position in send buffer to place data to send to this
444 // neighbour
445 std::int32_t val_pos = insert_pos[rank];
446 std::copy(std::next(_data.data(), _row_ptr[local_size0 + i] * bs2),
447 std::next(_data.data(), _row_ptr[local_size0 + i + 1] * bs2),
448 std::next(_ghost_value_data.begin(), val_pos));
449 insert_pos[rank]
450 += bs2 * (_row_ptr[local_size0 + i + 1] - _row_ptr[local_size0 + i]);
451 }
452
453 _ghost_value_data_in.resize(_val_recv_disp.back());
454
455 // Compute data sizes for send and receive from displacements
456 std::vector<int> val_send_count(_val_send_disp.size() - 1);
457 std::adjacent_difference(std::next(_val_send_disp.begin()),
458 _val_send_disp.end(), val_send_count.begin());
459
460 std::vector<int> val_recv_count(_val_recv_disp.size() - 1);
461 std::adjacent_difference(std::next(_val_recv_disp.begin()),
462 _val_recv_disp.end(), val_recv_count.begin());
463
464 int status = MPI_Ineighbor_alltoallv(
465 _ghost_value_data.data(), val_send_count.data(), _val_send_disp.data(),
466 dolfinx::MPI::mpi_t<value_type>, _ghost_value_data_in.data(),
467 val_recv_count.data(), _val_recv_disp.data(),
468 dolfinx::MPI::mpi_t<value_type>, _comm.comm(), &_request.request());
469 dolfinx::MPI::check_error(_comm.comm(), status);
470 }
471
478 {
479 check_not_finalized();
480 int status = MPI_Wait(&_request.request(), MPI_STATUS_IGNORE);
481 dolfinx::MPI::check_error(_comm.comm(), status);
482
483 _ghost_value_data.clear();
484 _ghost_value_data.shrink_to_fit();
485
486 // Add to local rows
487 int bs2 = _bs[0] * _bs[1];
488 assert(_ghost_value_data_in.size() == _unpack_pos.size() * bs2);
489 for (std::size_t i = 0; i < _unpack_pos.size(); ++i)
490 for (int j = 0; j < bs2; ++j)
491 _data[_unpack_pos[i] * bs2 + j] += _ghost_value_data_in[i * bs2 + j];
492
493 _ghost_value_data_in.clear();
494 _ghost_value_data_in.shrink_to_fit();
495
496 // Set ghost row data to zero
497 std::int32_t local_size0 = _index_maps[0]->size_local();
498 std::fill(std::next(_data.begin(), _row_ptr[local_size0] * bs2),
499 _data.end(), 0);
500 }
501
505 double squared_norm() const
506 {
507 const std::size_t num_owned_rows = _index_maps[0]->size_local();
508 const int bs2 = _bs[0] * _bs[1];
509 assert(num_owned_rows < _row_ptr.size());
510 double norm_sq_local = std::accumulate(
511 _data.cbegin(),
512 std::next(_data.cbegin(), _row_ptr[num_owned_rows] * bs2), double(0),
513 [](auto norm, value_type y) { return norm + std::norm(y); });
514 double norm_sq;
515 MPI_Allreduce(&norm_sq_local, &norm_sq, 1, MPI_DOUBLE, MPI_SUM,
516 _comm.comm());
517 return norm_sq;
518 }
519
530 void mult(Vector<value_type>& x, Vector<value_type>& y) const;
531
563
565 MPI_Comm comm() const { return _comm.comm(); }
566
574 std::shared_ptr<const common::IndexMap> index_map(int dim) const
575 {
576 return _index_maps.at(dim);
577 }
578
581 container_type& values() { return _data; }
582
585 const container_type& values() const { return _data; }
586
589 const rowptr_container_type& row_ptr() const { return _row_ptr; }
590
593 const column_container_type& cols() const { return _cols; }
594
605 {
606 return _off_diagonal_offset;
607 }
608
611 std::array<int, 2> block_size() const { return _bs; }
612
614 BlockMode block_mode() const { return _block_mode; }
615
635 {
636 // Remove any zero entries (blocks, where all entries in the block
637 // are within tolerance of zero) in data, and update the column
638 // indices and row pointers accordingly.
639 const std::size_t bs2 = _bs[0] * _bs[1];
640
641 // True if every entry of the block starting at block index j is
642 // within tolerance of zero, i.e. the whole block can be dropped.
643 auto is_zero_block = [this, bs2, tol](std::int64_t j)
644 {
645 return std::all_of(std::next(_data.begin(), j * bs2),
646 std::next(_data.begin(), (j + 1) * bs2),
647 [tol](value_type x)
648 { return std::abs(x) <= std::abs(tol); });
649 };
650
651 std::int64_t ptr_out = 0;
652 std::vector<std::int64_t> new_row_ptr = {0};
653 std::vector<std::int64_t> new_off_diagonal_offset;
654 new_row_ptr.reserve(_row_ptr.size());
655 new_off_diagonal_offset.reserve(_off_diagonal_offset.size());
656 for (std::size_t i = 0; i < _row_ptr.size() - 1; ++i)
657 {
658 for (std::int64_t j = _row_ptr[i]; j < _off_diagonal_offset[i]; ++j)
659 {
660 if (!is_zero_block(j))
661 {
662 _cols[ptr_out] = _cols[j];
663 std::copy_n(std::next(_data.begin(), j * bs2), bs2,
664 std::next(_data.begin(), ptr_out * bs2));
665 ++ptr_out;
666 }
667 }
668 new_off_diagonal_offset.push_back(ptr_out);
669 for (std::int64_t j = _off_diagonal_offset[i]; j < _row_ptr[i + 1]; ++j)
670 {
671 if (!is_zero_block(j))
672 {
673 _cols[ptr_out] = _cols[j];
674 std::copy_n(std::next(_data.begin(), j * bs2), bs2,
675 std::next(_data.begin(), ptr_out * bs2));
676 ++ptr_out;
677 }
678 }
679 new_row_ptr.push_back(ptr_out);
680 }
681 _data.resize(ptr_out * bs2);
682 _cols.resize(ptr_out);
683 _row_ptr = new_row_ptr;
684 _off_diagonal_offset = new_off_diagonal_offset;
685 _finalized = true;
686 }
687
688private:
689 // Parallel distribution of the rows and columns
690 std::array<std::shared_ptr<const common::IndexMap>, 2> _index_maps;
691
692 // Block mode (compact or expanded)
693 BlockMode _block_mode;
694
695 // Block sizes
696 std::array<int, 2> _bs;
697
698 // Matrix data
699 container_type _data;
701 rowptr_container_type _row_ptr;
702
703 // Start of off-diagonal (unowned columns) on each row
704 rowptr_container_type _off_diagonal_offset;
705
706 // Communicator with neighborhood (ghost->owner communicator for rows)
707 dolfinx::MPI::Comm _comm;
708
709 // -- Precomputed data for scatter_rev/update
710
711 // Request for a scatter in flight. Transfers on move, so that the
712 // moved-from matrix does not name the target's request.
713 dolfinx::MPI::Request _request;
714
715 // Position in _data to add received data
716 std::vector<std::size_t> _unpack_pos;
717
718 // Displacements for alltoall for each neighbor when sending and
719 // receiving
720 std::vector<int> _val_send_disp, _val_recv_disp;
721
722 // Ownership of each row, by neighbor (for the neighbourhood defined
723 // on _comm)
724 std::vector<int> _ghost_row_to_rank;
725
726 // Temporary stores for data during non-blocking communication
727 container_type _ghost_value_data;
728 container_type _ghost_value_data_in;
729
730 // Set by eliminate_zeros(). Once true, the sparsity may have been
731 // reduced and the precomputed scatter_rev communication pattern
732 // (_unpack_pos, _val_send_disp, _val_recv_disp) is no longer valid,
733 // so further modification of the matrix is disallowed.
734 bool _finalized = false;
735
736 // Throw if the matrix has been finalized by eliminate_zeros().
737 void check_not_finalized() const
738 {
739 if (_finalized)
740 {
741 throw std::runtime_error(
742 "MatrixCSR has been finalized by eliminate_zeros() and can no "
743 "longer be modified or scattered.");
744 }
745 }
746};
747//-----------------------------------------------------------------------------
748
750template <typename U, typename V, typename W, typename X>
751template <SparsityImplementation SparsityType>
752MatrixCSR<U, V, W, X>::MatrixCSR(const SparsityType& p, BlockMode mode)
753 : _index_maps({p.index_map(0), p.index_map(1)}), _block_mode(mode),
754 _bs({p.block_size(0), p.block_size(1)}),
755 _data(p.graph().first.size() * _bs[0] * _bs[1], 0),
756 _cols(p.graph().first.begin(), p.graph().first.end()),
757 _row_ptr(p.graph().second.begin(), p.graph().second.end()),
758 _comm(MPI_COMM_NULL)
759{
760 if (_block_mode == BlockMode::expanded)
761 {
762 // Rebuild IndexMaps
763 for (int i = 0; i < 2; ++i)
764 {
765 auto im = _index_maps[i];
766 std::int32_t size_local = im->size_local() * _bs[i];
767 std::span ghost_i = im->ghosts();
768 std::vector<std::int64_t> ghosts;
769 const std::vector<int> ghost_owner_i(im->owners().begin(),
770 im->owners().end());
771 std::vector<int> src_rank;
772 for (std::size_t j = 0; j < ghost_i.size(); ++j)
773 {
774 for (int k = 0; k < _bs[i]; ++k)
775 {
776 ghosts.push_back(ghost_i[j] * _bs[i] + k);
777 src_rank.push_back(ghost_owner_i[j]);
778 }
779 }
780
781 std::array<std::vector<int>, 2> src_dest0
782 = {std::vector(_index_maps[i]->src().begin(),
783 _index_maps[i]->src().end()),
784 std::vector(_index_maps[i]->dest().begin(),
785 _index_maps[i]->dest().end())};
786 _index_maps[i] = std::make_shared<common::IndexMap>(
787 _index_maps[i]->comm(), size_local, src_dest0, ghosts, src_rank);
788 }
789
790 // Convert sparsity pattern and set _bs to 1
791
792 column_container_type new_cols;
793 new_cols.reserve(_data.size());
794 rowptr_container_type new_row_ptr{0};
795 new_row_ptr.reserve(_row_ptr.size() * _bs[0]);
796 std::span<const std::int32_t> num_diag_nnz = p.off_diagonal_offsets();
797 for (std::size_t i = 0; i < _row_ptr.size() - 1; ++i)
798 {
799 // Repeat row _bs[0] times
800 for (int q0 = 0; q0 < _bs[0]; ++q0)
801 {
802 _off_diagonal_offset.push_back(new_row_ptr.back()
803 + num_diag_nnz[i] * _bs[1]);
804 for (auto j = _row_ptr[i]; j < _row_ptr[i + 1]; ++j)
805 {
806 for (int q1 = 0; q1 < _bs[1]; ++q1)
807 new_cols.push_back(_cols[j] * _bs[1] + q1);
808 }
809 new_row_ptr.push_back(new_cols.size());
810 }
811 }
812 _cols = new_cols;
813 _row_ptr = new_row_ptr;
814 _bs[0] = 1;
815 _bs[1] = 1;
816 }
817 else
818 {
819 // Compute off-diagonal offset for each row (compact)
820 std::span<const std::int32_t> num_diag_nnz = p.off_diagonal_offsets();
821 _off_diagonal_offset.reserve(num_diag_nnz.size());
822 std::ranges::transform(num_diag_nnz, _row_ptr,
823 std::back_inserter(_off_diagonal_offset),
824 std::plus{});
825 }
826
827 // Some short-hand
828 std::array local_size
829 = {_index_maps[0]->size_local(), _index_maps[1]->size_local()};
830 std::array local_range
831 = {_index_maps[0]->local_range(), _index_maps[1]->local_range()};
832 std::span ghosts1 = _index_maps[1]->ghosts();
833
834 std::span ghosts0 = _index_maps[0]->ghosts();
835 std::span src_ranks = _index_maps[0]->src();
836 std::span dest_ranks = _index_maps[0]->dest();
837
838 // Create neighbourhood communicator (owner <- ghost)
839 MPI_Comm comm;
840 MPI_Dist_graph_create_adjacent(_index_maps[0]->comm(), dest_ranks.size(),
841 dest_ranks.data(), MPI_UNWEIGHTED,
842 src_ranks.size(), src_ranks.data(),
843 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm);
844 _comm = dolfinx::MPI::Comm(comm, false);
845
846 // Build map from ghost row index position to owning (neighborhood)
847 // rank
848 _ghost_row_to_rank.reserve(_index_maps[0]->owners().size());
849 for (int r : _index_maps[0]->owners())
850 {
851 auto it = std::ranges::lower_bound(src_ranks, r);
852 assert(it != src_ranks.end() and *it == r);
853 std::size_t pos = std::ranges::distance(src_ranks.begin(), it);
854 _ghost_row_to_rank.push_back(pos);
855 }
856
857 // Compute size of data to send to each neighbor
858 std::vector<std::int32_t> data_per_proc(src_ranks.size(), 0);
859 for (std::size_t i = 0; i < _ghost_row_to_rank.size(); ++i)
860 {
861 assert(_ghost_row_to_rank[i] < (int)data_per_proc.size());
862 std::size_t pos = local_size[0] + i;
863 data_per_proc[_ghost_row_to_rank[i]] += _row_ptr[pos + 1] - _row_ptr[pos];
864 }
865
866 // Compute send displacements
867 _val_send_disp.resize(src_ranks.size() + 1, 0);
868 std::partial_sum(data_per_proc.begin(), data_per_proc.end(),
869 std::next(_val_send_disp.begin()));
870
871 // For each ghost row, pack and send indices to neighborhood
872 std::vector<std::int64_t> ghost_index_data(2 * _val_send_disp.back());
873 {
874 std::vector<int> insert_pos = _val_send_disp;
875 for (std::size_t i = 0; i < _ghost_row_to_rank.size(); ++i)
876 {
877 int rank = _ghost_row_to_rank[i];
878 std::int32_t row_id = local_size[0] + i;
879 for (int j = _row_ptr[row_id]; j < _row_ptr[row_id + 1]; ++j)
880 {
881 // Get position in send buffer
882 std::int32_t idx_pos = 2 * insert_pos[rank];
883
884 // Pack send data (row, col) as global indices
885 ghost_index_data[idx_pos] = ghosts0[i];
886 if (std::int32_t col_local = _cols[j]; col_local < local_size[1])
887 ghost_index_data[idx_pos + 1] = col_local + local_range[1][0];
888 else
889 ghost_index_data[idx_pos + 1] = ghosts1[col_local - local_size[1]];
890
891 insert_pos[rank] += 1;
892 }
893 }
894 }
895
896 // Communicate data with neighborhood
897 std::vector<std::int64_t> ghost_index_array;
898 std::vector<int> recv_disp;
899 {
900 std::vector<int> send_sizes;
901 std::ranges::transform(data_per_proc, std::back_inserter(send_sizes),
902 [](auto x) { return 2 * x; });
903
904 std::vector<int> recv_sizes(dest_ranks.size());
905 send_sizes.reserve(1);
906 recv_sizes.reserve(1);
907 MPI_Neighbor_alltoall(send_sizes.data(), 1, MPI_INT, recv_sizes.data(), 1,
908 MPI_INT, _comm.comm());
909
910 // Build send/recv displacement
911 std::vector<int> send_disp{0};
912 std::partial_sum(send_sizes.begin(), send_sizes.end(),
913 std::back_inserter(send_disp));
914 recv_disp = {0};
915 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
916 std::back_inserter(recv_disp));
917
918 ghost_index_array.resize(recv_disp.back());
919 MPI_Neighbor_alltoallv(ghost_index_data.data(), send_sizes.data(),
920 send_disp.data(), MPI_INT64_T,
921 ghost_index_array.data(), recv_sizes.data(),
922 recv_disp.data(), MPI_INT64_T, _comm.comm());
923 }
924
925 // Store receive displacements for future use, when transferring
926 // data values
927 _val_recv_disp.resize(recv_disp.size());
928 int bs2 = _bs[0] * _bs[1];
929 std::ranges::transform(recv_disp, _val_recv_disp.begin(),
930 [&bs2](auto d) { return bs2 * d / 2; });
931 std::ranges::transform(_val_send_disp, _val_send_disp.begin(),
932 [&bs2](auto d) { return d * bs2; });
933
934 // Global-to-local map for ghost columns
935 std::vector<std::pair<std::int64_t, std::int32_t>> global_to_local;
936 global_to_local.reserve(ghosts1.size());
937 for (std::int64_t idx : ghosts1)
938 global_to_local.push_back({idx, global_to_local.size() + local_size[1]});
939 std::ranges::sort(global_to_local);
940
941 // Compute location in which data for each index should be stored
942 // when received
943 for (std::size_t i = 0; i < ghost_index_array.size(); i += 2)
944 {
945 // Row must be on this process
946 std::int32_t local_row = ghost_index_array[i] - local_range[0][0];
947 assert(local_row >= 0 and local_row < local_size[0]);
948
949 // Column may be owned or unowned
950 std::int32_t local_col = ghost_index_array[i + 1] - local_range[1][0];
951 if (local_col < 0 or local_col >= local_size[1])
952 {
953 auto it = std::ranges::lower_bound(
954 global_to_local, std::pair(ghost_index_array[i + 1], -1),
955 [](auto a, auto b) { return a.first < b.first; });
956 assert(it != global_to_local.end()
957 and it->first == ghost_index_array[i + 1]);
958 local_col = it->second;
959 }
960 auto cit0 = std::next(_cols.begin(), _row_ptr[local_row]);
961 auto cit1 = std::next(_cols.begin(), _row_ptr[local_row + 1]);
962
963 // Find position of column index and insert data
964 auto cit = std::lower_bound(cit0, cit1, local_col);
965 assert(cit != cit1);
966 assert(*cit == local_col);
967 std::size_t d = std::ranges::distance(_cols.begin(), cit);
968 _unpack_pos.push_back(d);
969 }
970
971 _unpack_pos.shrink_to_fit();
972}
973//-----------------------------------------------------------------------------
974
975// The matrix A is distributed across P processes by blocks of rows:
976// A = | A_0 |
977// | A_1 |
978// | ... |
979// | A_P-1 |
980//
981// Each submatrix A_i is owned by a single process "i" and can be further
982// decomposed into diagonal (Ai[0]) and off diagonal (Ai[1]) blocks:
983// Ai = |Ai[0] Ai[1]|
984//
985// If A is square, the diagonal block Ai[0] is also square and contains
986// only owned columns and rows. The block Ai[1] contains ghost columns
987// (unowned dofs).
988
989// Likewise, a local vector x can be decomposed into owned and ghost blocks:
990// xi = | x[0] |
991// | x[1] |
992//
993// So the product y = Ax can be computed into two separate steps:
994// y[0] = |Ai[0] Ai[1]| | x[0] | = Ai[0] x[0] + Ai[1] x[1]
995// | x[1] |
996//
999template <typename Scalar, typename V, typename W, typename X>
1001 la::Vector<Scalar>& y) const
1002{
1003 // start communication (update ghosts)
1005
1006 std::int32_t nrowslocal = num_owned_rows();
1007 std::span<const std::int64_t> Arow_ptr(row_ptr().data(), nrowslocal + 1);
1008 std::span<const std::int32_t> Acols(cols().data(), Arow_ptr[nrowslocal]);
1009 std::span<const std::int64_t> Aoff_diag_offset(off_diag_offset().data(),
1010 nrowslocal);
1011 std::span<const Scalar> Avalues(values().data(),
1012 Arow_ptr[nrowslocal] * _bs[0] * _bs[1]);
1013
1014 std::span<const Scalar> _x = x.array();
1015 std::span<Scalar> _y = y.array();
1016
1017 std::span<const std::int64_t> Arow_begin(Arow_ptr.data(), nrowslocal);
1018 std::span<const std::int64_t> Arow_end(Arow_ptr.data() + 1, nrowslocal);
1019
1020 // First stage: spmv - diagonal
1021 // yi[0] += Ai[0] * xi[0]
1022 if (_bs[1] == 1)
1023 {
1024 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1025 _bs[0], std::integral_constant<int, 1>{});
1026 }
1027 else if (_bs[1] == 2)
1028 {
1029 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1030 _bs[0], std::integral_constant<int, 2>{});
1031 }
1032 else if (_bs[1] == 3)
1033 {
1034 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1035 _bs[0], std::integral_constant<int, 3>{});
1036 }
1037 else
1038 {
1039 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1040 _bs[0], _bs[1]);
1041 }
1042
1043 // finalize ghost update
1044 x.scatter_fwd_end();
1045
1046 // Second stage: spmv - off-diagonal
1047 // yi[0] += Ai[1] * xi[1]
1048 if (_bs[1] == 1)
1049 {
1050 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1051 _bs[0], std::integral_constant<int, 1>{});
1052 }
1053 else if (_bs[1] == 2)
1054 {
1055 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1056 _bs[0], std::integral_constant<int, 2>{});
1057 }
1058 else if (_bs[1] == 3)
1059 {
1060 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1061 _bs[0], std::integral_constant<int, 3>{});
1062 }
1063 else
1064 {
1065 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1066 _bs[0], _bs[1]);
1067 }
1068}
1069
1072template <typename Scalar, typename V, typename W, typename X>
1074 la::Vector<Scalar>& y) const
1075{
1076 std::int32_t nrowslocal = num_owned_rows();
1077 std::span<const std::int64_t> Arow_ptr(row_ptr().data(), nrowslocal + 1);
1078 std::span<const std::int32_t> Acols(cols().data(), Arow_ptr[nrowslocal]);
1079 std::span<const std::int64_t> Aoff_diag_offset(off_diag_offset().data(),
1080 nrowslocal);
1081 std::span<const Scalar> Avalues(values().data(),
1082 Arow_ptr[nrowslocal] * _bs[0] * _bs[1]);
1083
1084 std::span<const Scalar> _x = x.array();
1085 std::span<Scalar> _y = y.array();
1086
1087 std::span<const std::int64_t> Arow_begin(Arow_ptr.data(), nrowslocal);
1088 std::span<const std::int64_t> Arow_end(Arow_ptr.data() + 1, nrowslocal);
1089
1090 // Compute ghost region contribution and scatter back. Zero only the
1091 // ghost portion of y so the caller's owned values are preserved (multT
1092 // accumulates).
1093 std::int32_t ncolslocal = index_map(1)->size_local();
1094 std::fill(std::next(_y.begin(), ncolslocal * _bs[1]), _y.end(), Scalar(0));
1095 if (_bs[1] == 1)
1096 {
1097 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1098 _bs[0], std::integral_constant<int, 1>{});
1099 }
1100 else if (_bs[1] == 2)
1101 {
1102 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1103 _bs[0], std::integral_constant<int, 2>{});
1104 }
1105 else if (_bs[1] == 3)
1106 {
1107 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1108 _bs[0], std::integral_constant<int, 3>{});
1109 }
1110 else
1111 {
1112 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
1113 _bs[0], _bs[1]);
1114 }
1115
1116 y.scatter_rev(std::plus<Scalar>{});
1117
1118 if (_bs[1] == 1)
1119 {
1120 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1121 _bs[0], std::integral_constant<int, 1>{});
1122 }
1123 else if (_bs[1] == 2)
1124 {
1125 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1126 _bs[0], std::integral_constant<int, 2>{});
1127 }
1128 else if (_bs[1] == 3)
1129 {
1130 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1131 _bs[0], std::integral_constant<int, 3>{});
1132 }
1133 else
1134 {
1135 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1136 _bs[0], _bs[1]);
1137 }
1138}
1139} // namespace dolfinx::la
A duplicate MPI communicator and manage lifetime of the communicator.
Definition MPI.h:47
MPI_Comm comm() const noexcept
Return the underlying MPI_Comm object.
Definition MPI.cpp:71
Holder for the request of a non-blocking operation.
Definition MPI.h:421
const container_type & values() const
Get local values (const version).
Definition MatrixCSR.h:585
std::shared_ptr< const common::IndexMap > index_map(int dim) const
Index map for the row or column space.
Definition MatrixCSR.h:574
const rowptr_container_type & off_diag_offset() const
Get the start of off-diagonal (unowned columns) on each row, allowing the matrix to be split (virtual...
Definition MatrixCSR.h:604
void set(std::span< const value_type > x, std::span< const std::int32_t > rows, std::span< const std::int32_t > cols)
Set values in the matrix.
Definition MatrixCSR.h:294
MatrixCSR(const MatrixCSR< Scalar0, Container0, ColContainer0, RowPtrContainer0 > &A)
Copy-convert matrix, possibly using to different container types.
Definition MatrixCSR.h:252
RowPtrContainer rowptr_container_type
Row pointer container type.
Definition MatrixCSR.h:93
void scatter_rev_end()
End transfer of ghost row data to owning ranks.
Definition MatrixCSR.h:477
container_type & values()
Get local data values.
Definition MatrixCSR.h:581
auto mat_add_values()
Insertion functor for adding values to a matrix. It is typically used in finite element assembly func...
Definition MatrixCSR.h:166
BlockMode block_mode() const
Get 'block mode'.
Definition MatrixCSR.h:614
void add(std::span< const value_type > x, std::span< const std::int32_t > rows, std::span< const std::int32_t > cols)
Accumulate values in the matrix.
Definition MatrixCSR.h:340
std::int32_t num_owned_rows() const
Number of local rows excluding ghost rows.
Definition MatrixCSR.h:368
ColContainer column_container_type
Column index container type.
Definition MatrixCSR.h:90
void mult(Vector< value_type > &x, Vector< value_type > &y) const
Compute the product y += Ax.
Definition MatrixCSR.h:1000
MatrixCSR(MatrixCSR &&A)=default
MatrixCSR & operator=(MatrixCSR &&A)=default
MatrixCSR(const T &p, BlockMode mode=BlockMode::compact)
Create a distributed matrix.
double squared_norm() const
Compute the Frobenius norm squared across all processes.
Definition MatrixCSR.h:505
void scatter_rev()
Transfer ghost row data to the owning ranks accumulating received values on the owned rows,...
Definition MatrixCSR.h:414
void multT(Vector< value_type > &x, Vector< value_type > &y) const
Compute the product y += A^T x.
Definition MatrixCSR.h:1073
void eliminate_zeros(value_type tol=0)
Remove any zero entries in the matrix data.
Definition MatrixCSR.h:634
Container container_type
Matrix entries container type.
Definition MatrixCSR.h:87
Scalar value_type
Scalar type.
Definition MatrixCSR.h:84
void scatter_rev_begin()
Begin transfer of ghost row data to owning ranks, where it will be accumulated into existing owned ro...
Definition MatrixCSR.h:429
const column_container_type & cols() const
Definition MatrixCSR.h:593
void set(value_type x)
Set all non-zero local entries to a value, including entries in ghost rows.
Definition MatrixCSR.h:271
~MatrixCSR()=default
Destructor.
std::array< int, 2 > block_size() const
Get block sizes.
Definition MatrixCSR.h:611
std::int32_t num_all_rows() const
Number of local rows including ghost rows.
Definition MatrixCSR.h:371
const rowptr_container_type & row_ptr() const
Get local row pointers.
Definition MatrixCSR.h:589
std::vector< value_type > to_dense() const
Copy to a dense matrix.
Definition MatrixCSR.h:382
MPI_Comm comm() const
Get MPI communicator that matrix is defined on.
Definition MatrixCSR.h:565
auto mat_set_values()
Insertion functor for setting values in a matrix. It is typically used in finite element assembly fun...
Definition MatrixCSR.h:119
A vector that can be distributed across processes.
Definition Vector.h:53
void scatter_rev(BinaryOperation op)
Scatter (send) of ghost data values to the owning process and assign/accumulate into the owned data e...
Definition Vector.h:458
void scatter_fwd_end(U unpack)
End scatter (send) of local data values that are ghosted on other processes.
Definition Vector.h:331
container_type & array() noexcept
Get the process-local part of the vector.
Definition Vector.h:484
void scatter_fwd_begin(U pack, GetPtr get_ptr)
Begin scatter (send) of local data that is ghosted on other processes.
Definition Vector.h:288
Definition MatrixCSR.h:28
Concept for a contiguous list of process-local indices, e.g. the row or column indices passed to a ma...
Definition types.h:75
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
void check_error(MPI_Comm comm, int code) noexcept
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:89
int size(MPI_Comm comm)
Definition MPI.cpp:81
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
constexpr std::array< std::int64_t, 2 > local_range(int index, std::int64_t N, int size)
Partition a global range [0, N - 1] across callers into non-overlapping sub-partitions of almost equa...
Definition local_range.h:26
Linear algebra interface.
Definition dolfinx_la.h:7
BlockMode
Modes for representing block structured matrices.
Definition MatrixCSR.h:48
@ expanded
Definition MatrixCSR.h:51
auto norm(const V &x, Norm type=Norm::l2)
Compute the norm of the vector.
Definition Vector.h:576