DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
MatrixCSR.h
1// Copyright (C) 2021-2022 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/graph/AdjacencyList.h>
16#include <mpi.h>
17#include <numeric>
18#include <span>
19#include <utility>
20#include <vector>
21
22// Define requirements on sparsity pattern required for MatrixCSR constructor
23// allowing alternative implementations that can provide these essentials.
24template <typename T>
25concept SparsityImplementation = requires(T sp, int i) {
26 { sp.graph() };
27 requires std::forward_iterator<typename decltype(sp.graph().first)::iterator>;
28 requires std::convertible_to<std::int32_t,
29 typename decltype(sp.graph().first)::value_type>;
30 requires std::forward_iterator<
31 typename decltype(sp.graph().second)::iterator>;
32 requires std::convertible_to<
33 std::int64_t, typename decltype(sp.graph().second)::value_type>;
34
35 { sp.block_size(i) } -> std::same_as<int>;
36 {
37 sp.index_map(i)
38 } -> std::same_as<std::shared_ptr<const dolfinx::common::IndexMap>>;
39};
40
41namespace dolfinx::la
42{
44enum class BlockMode : int
45{
46 compact = 0,
51};
52
67template <typename Scalar, typename Container = std::vector<Scalar>,
68 typename ColContainer = std::vector<std::int32_t>,
69 typename RowPtrContainer = std::vector<std::int64_t>>
70class MatrixCSR
71{
72 static_assert(std::is_same_v<typename Container::value_type, Scalar>);
73 static_assert(std::is_integral_v<typename ColContainer::value_type>);
74 static_assert(std::is_integral_v<typename RowPtrContainer::value_type>);
75
76 template <typename, typename, typename, typename>
77 friend class MatrixCSR;
78
79public:
81 using value_type = Scalar;
82
84 using container_type = Container;
85
87 using column_container_type = ColContainer;
88
90 using rowptr_container_type = RowPtrContainer;
91
115 template <int BS0 = 1, int BS1 = 1>
117 {
118 if ((BS0 != _bs[0] and BS0 > 1 and _bs[0] > 1)
119 or (BS1 != _bs[1] and BS1 > 1 and _bs[1] > 1))
120 {
121 throw std::runtime_error(
122 "Cannot insert blocks of different size than matrix block size");
123 }
124
125 return [this](std::span<const std::int32_t> rows,
126 std::span<const std::int32_t> cols,
127 std::span<const value_type> data) -> int
128 {
129 this->set<BS0, BS1>(data, rows, cols);
130 return 0;
131 };
132 }
133
157 template <int BS0 = 1, int BS1 = 1>
159 {
160 if ((BS0 != _bs[0] and BS0 > 1 and _bs[0] > 1)
161 or (BS1 != _bs[1] and BS1 > 1 and _bs[1] > 1))
162 {
163 throw std::runtime_error(
164 "Cannot insert blocks of different size than matrix block size");
165 }
166
167 return [this](std::span<const std::int32_t> rows,
168 std::span<const std::int32_t> cols,
169 std::span<const value_type> data) -> int
170 {
171 this->add<BS0, BS1>(data, rows, cols);
172 return 0;
173 };
174 }
175
199 template <SparsityImplementation T>
200 MatrixCSR(const T& p, BlockMode mode = BlockMode::compact);
201
204 MatrixCSR(MatrixCSR&& A) = default;
205
208 MatrixCSR(const MatrixCSR& A) = default;
209
223 template <typename Scalar0, typename Container0, typename ColContainer0,
224 typename RowPtrContainer0>
225 explicit MatrixCSR(
226 const MatrixCSR<Scalar0, Container0, ColContainer0, RowPtrContainer0>& A)
227 : _index_maps(A._index_maps), _block_mode(A.block_mode()),
228 _bs(A.block_size()), _data(A._data.begin(), A._data.end()),
229 _cols(A.cols().begin(), A.cols().end()),
230 _row_ptr(A.row_ptr().begin(), A.row_ptr().end()),
231 _off_diagonal_offset(A.off_diag_offset().begin(),
232 A.off_diag_offset().end()),
233 _comm(A.comm()), _request(MPI_REQUEST_NULL), _unpack_pos(A._unpack_pos),
234 _val_send_disp(A._val_send_disp), _val_recv_disp(A._val_recv_disp),
235 _ghost_row_to_rank(A._ghost_row_to_rank)
236 {
237 }
238
243 [[deprecated("Use std::ranges::fill(A.values(), v) instead.")]]
245 {
246 std::ranges::fill(_data, x);
247 }
248
265 template <int BS0, int BS1>
266 void set(std::span<const value_type> x, std::span<const std::int32_t> rows,
267 std::span<const std::int32_t> cols)
268 {
269 auto set_fn = [](value_type& y, const value_type& x) { y = x; };
270
271 std::int32_t num_rows
272 = _index_maps[0]->size_local() + _index_maps[0]->num_ghosts();
273 assert(x.size() == rows.size() * cols.size() * BS0 * BS1);
274 if (_bs[0] == BS0 and _bs[1] == BS1)
275 {
276 impl::insert_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols, set_fn,
277 num_rows);
278 }
279 else if (_bs[0] == 1 and _bs[1] == 1)
280 {
281 // Set blocked data in a regular CSR matrix (_bs[0]=1, _bs[1]=1)
282 // with correct sparsity
283 impl::insert_blocked_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols,
284 set_fn, num_rows);
285 }
286 else
287 {
288 assert(BS0 == 1 and BS1 == 1);
289 // Set non-blocked data in a blocked CSR matrix (BS0=1, BS1=1)
290 impl::insert_nonblocked_csr(_data, _cols, _row_ptr, x, rows, cols, set_fn,
291 num_rows, _bs[0], _bs[1]);
292 }
293 }
294
310 template <int BS0 = 1, int BS1 = 1>
311 void add(std::span<const value_type> x, std::span<const std::int32_t> rows,
312 std::span<const std::int32_t> cols)
313 {
314 auto add_fn = [](value_type& y, const value_type& x) { y += x; };
315
316 assert(x.size() == rows.size() * cols.size() * BS0 * BS1);
317 if (_bs[0] == BS0 and _bs[1] == BS1)
318 {
319 impl::insert_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols, add_fn,
320 _row_ptr.size());
321 }
322 else if (_bs[0] == 1 and _bs[1] == 1)
323 {
324 // Add blocked data to a regular CSR matrix (_bs[0]=1, _bs[1]=1)
325 impl::insert_blocked_csr<BS0, BS1>(_data, _cols, _row_ptr, x, rows, cols,
326 add_fn, _row_ptr.size());
327 }
328 else
329 {
330 assert(BS0 == 1 and BS1 == 1);
331 // Add non-blocked data to a blocked CSR matrix (BS0=1, BS1=1)
332 impl::insert_nonblocked_csr(_data, _cols, _row_ptr, x, rows, cols, add_fn,
333 _row_ptr.size(), _bs[0], _bs[1]);
334 }
335 }
336
338 std::int32_t num_owned_rows() const { return _index_maps[0]->size_local(); }
339
341 std::int32_t num_all_rows() const { return _row_ptr.size() - 1; }
342
352 std::vector<value_type> to_dense() const
353 {
354 const std::size_t nrows = num_all_rows();
355 const std::size_t ncols = _index_maps[1]->size_global();
356 std::vector<value_type> A(nrows * ncols * _bs[0] * _bs[1], value_type(0));
357 for (std::size_t r = 0; r < nrows; ++r)
358 {
359 for (std::int32_t j = _row_ptr[r]; j < _row_ptr[r + 1]; ++j)
360 {
361 for (int i0 = 0; i0 < _bs[0]; ++i0)
362 {
363 for (int i1 = 0; i1 < _bs[1]; ++i1)
364 {
365 std::array<std::int32_t, 1> local_col{_cols[j]};
366 std::array<std::int64_t, 1> global_col{0};
367 _index_maps[1]->local_to_global(local_col, global_col);
368 A[(r * _bs[0] + i0) * ncols * _bs[1] + global_col[0] * _bs[1] + i1]
369 = _data[j * _bs[0] * _bs[1] + i0 * _bs[1] + i1];
370 }
371 }
372 }
373 }
374
375 return A;
376 }
377
385 {
388 }
389
400 {
401 const std::int32_t local_size0 = _index_maps[0]->size_local();
402 const std::int32_t num_ghosts0 = _index_maps[0]->num_ghosts();
403 const int bs2 = _bs[0] * _bs[1];
404
405 // For each ghost row, pack and send values to send to neighborhood
406 std::vector<int> insert_pos = _val_send_disp;
407 _ghost_value_data.resize(_val_send_disp.back());
408 for (int i = 0; i < num_ghosts0; ++i)
409 {
410 int rank = _ghost_row_to_rank[i];
411
412 // Get position in send buffer to place data to send to this
413 // neighbour
414 std::int32_t val_pos = insert_pos[rank];
415 std::copy(std::next(_data.data(), _row_ptr[local_size0 + i] * bs2),
416 std::next(_data.data(), _row_ptr[local_size0 + i + 1] * bs2),
417 std::next(_ghost_value_data.begin(), val_pos));
418 insert_pos[rank]
419 += bs2 * (_row_ptr[local_size0 + i + 1] - _row_ptr[local_size0 + i]);
420 }
421
422 _ghost_value_data_in.resize(_val_recv_disp.back());
423
424 // Compute data sizes for send and receive from displacements
425 std::vector<int> val_send_count(_val_send_disp.size() - 1);
426 std::adjacent_difference(std::next(_val_send_disp.begin()),
427 _val_send_disp.end(), val_send_count.begin());
428
429 std::vector<int> val_recv_count(_val_recv_disp.size() - 1);
430 std::adjacent_difference(std::next(_val_recv_disp.begin()),
431 _val_recv_disp.end(), val_recv_count.begin());
432
433 int status = MPI_Ineighbor_alltoallv(
434 _ghost_value_data.data(), val_send_count.data(), _val_send_disp.data(),
435 dolfinx::MPI::mpi_t<value_type>, _ghost_value_data_in.data(),
436 val_recv_count.data(), _val_recv_disp.data(),
437 dolfinx::MPI::mpi_t<value_type>, _comm.comm(), &_request);
438 dolfinx::MPI::check_error(_comm.comm(), status);
439 }
440
447 {
448 int status = MPI_Wait(&_request, MPI_STATUS_IGNORE);
449 dolfinx::MPI::check_error(_comm.comm(), status);
450
451 _ghost_value_data.clear();
452 _ghost_value_data.shrink_to_fit();
453
454 // Add to local rows
455 int bs2 = _bs[0] * _bs[1];
456 assert(_ghost_value_data_in.size() == _unpack_pos.size() * bs2);
457 for (std::size_t i = 0; i < _unpack_pos.size(); ++i)
458 for (int j = 0; j < bs2; ++j)
459 _data[_unpack_pos[i] * bs2 + j] += _ghost_value_data_in[i * bs2 + j];
460
461 _ghost_value_data_in.clear();
462 _ghost_value_data_in.shrink_to_fit();
463
464 // Set ghost row data to zero
465 std::int32_t local_size0 = _index_maps[0]->size_local();
466 std::fill(std::next(_data.begin(), _row_ptr[local_size0] * bs2),
467 _data.end(), 0);
468 }
469
473 double squared_norm() const
474 {
475 const std::size_t num_owned_rows = _index_maps[0]->size_local();
476 const int bs2 = _bs[0] * _bs[1];
477 assert(num_owned_rows < _row_ptr.size());
478 double norm_sq_local = std::accumulate(
479 _data.cbegin(),
480 std::next(_data.cbegin(), _row_ptr[num_owned_rows] * bs2), double(0),
481 [](auto norm, value_type y) { return norm + std::norm(y); });
482 double norm_sq;
483 MPI_Allreduce(&norm_sq_local, &norm_sq, 1, MPI_DOUBLE, MPI_SUM,
484 _comm.comm());
485 return norm_sq;
486 }
487
498 void mult(Vector<value_type>& x, Vector<value_type>& y) const;
499
531
533 MPI_Comm comm() const { return _comm.comm(); }
534
542 std::shared_ptr<const common::IndexMap> index_map(int dim) const
543 {
544 return _index_maps.at(dim);
545 }
546
549 container_type& values() { return _data; }
550
553 const container_type& values() const { return _data; }
554
557 const rowptr_container_type& row_ptr() const { return _row_ptr; }
558
561 const column_container_type& cols() const { return _cols; }
562
573 {
574 return _off_diagonal_offset;
575 }
576
579 std::array<int, 2> block_size() const { return _bs; }
580
582 BlockMode block_mode() const { return _block_mode; }
583
584private:
585 // Parallel distribution of the rows and columns
586 std::array<std::shared_ptr<const common::IndexMap>, 2> _index_maps;
587
588 // Block mode (compact or expanded)
589 BlockMode _block_mode;
590
591 // Block sizes
592 std::array<int, 2> _bs;
593
594 // Matrix data
595 container_type _data;
597 rowptr_container_type _row_ptr;
598
599 // Start of off-diagonal (unowned columns) on each row
600 rowptr_container_type _off_diagonal_offset;
601
602 // Communicator with neighborhood (ghost->owner communicator for rows)
603 dolfinx::MPI::Comm _comm;
604
605 // -- Precomputed data for scatter_rev/update
606
607 // Request in non-blocking communication
608 MPI_Request _request;
609
610 // Position in _data to add received data
611 std::vector<std::size_t> _unpack_pos;
612
613 // Displacements for alltoall for each neighbor when sending and
614 // receiving
615 std::vector<int> _val_send_disp, _val_recv_disp;
616
617 // Ownership of each row, by neighbor (for the neighbourhood defined
618 // on _comm)
619 std::vector<int> _ghost_row_to_rank;
620
621 // Temporary stores for data during non-blocking communication
622 container_type _ghost_value_data;
623 container_type _ghost_value_data_in;
624};
625//-----------------------------------------------------------------------------
626
628template <typename U, typename V, typename W, typename X>
629template <SparsityImplementation SparsityType>
630MatrixCSR<U, V, W, X>::MatrixCSR(const SparsityType& p, BlockMode mode)
631 : _index_maps({p.index_map(0), p.index_map(1)}), _block_mode(mode),
632 _bs({p.block_size(0), p.block_size(1)}),
633 _data(p.graph().first.size() * _bs[0] * _bs[1], 0),
634 _cols(p.graph().first.begin(), p.graph().first.end()),
635 _row_ptr(p.graph().second.begin(), p.graph().second.end()),
636 _comm(MPI_COMM_NULL)
637{
638 if (_block_mode == BlockMode::expanded)
639 {
640 // Rebuild IndexMaps
641 for (int i = 0; i < 2; ++i)
642 {
643 auto im = _index_maps[i];
644 std::int32_t size_local = im->size_local() * _bs[i];
645 std::span ghost_i = im->ghosts();
646 std::vector<std::int64_t> ghosts;
647 const std::vector<int> ghost_owner_i(im->owners().begin(),
648 im->owners().end());
649 std::vector<int> src_rank;
650 for (std::size_t j = 0; j < ghost_i.size(); ++j)
651 {
652 for (int k = 0; k < _bs[i]; ++k)
653 {
654 ghosts.push_back(ghost_i[j] * _bs[i] + k);
655 src_rank.push_back(ghost_owner_i[j]);
656 }
657 }
658
659 std::array<std::vector<int>, 2> src_dest0
660 = {std::vector(_index_maps[i]->src().begin(),
661 _index_maps[i]->src().end()),
662 std::vector(_index_maps[i]->dest().begin(),
663 _index_maps[i]->dest().end())};
664 _index_maps[i] = std::make_shared<common::IndexMap>(
665 _index_maps[i]->comm(), size_local, src_dest0, ghosts, src_rank);
666 }
667
668 // Convert sparsity pattern and set _bs to 1
669
670 column_container_type new_cols;
671 new_cols.reserve(_data.size());
672 rowptr_container_type new_row_ptr{0};
673 new_row_ptr.reserve(_row_ptr.size() * _bs[0]);
674 std::span<const std::int32_t> num_diag_nnz = p.off_diagonal_offsets();
675 for (std::size_t i = 0; i < _row_ptr.size() - 1; ++i)
676 {
677 // Repeat row _bs[0] times
678 for (int q0 = 0; q0 < _bs[0]; ++q0)
679 {
680 _off_diagonal_offset.push_back(new_row_ptr.back()
681 + num_diag_nnz[i] * _bs[1]);
682 for (auto j = _row_ptr[i]; j < _row_ptr[i + 1]; ++j)
683 {
684 for (int q1 = 0; q1 < _bs[1]; ++q1)
685 new_cols.push_back(_cols[j] * _bs[1] + q1);
686 }
687 new_row_ptr.push_back(new_cols.size());
688 }
689 }
690 _cols = new_cols;
691 _row_ptr = new_row_ptr;
692 _bs[0] = 1;
693 _bs[1] = 1;
694 }
695 else
696 {
697 // Compute off-diagonal offset for each row (compact)
698 std::span<const std::int32_t> num_diag_nnz = p.off_diagonal_offsets();
699 _off_diagonal_offset.reserve(num_diag_nnz.size());
700 std::ranges::transform(num_diag_nnz, _row_ptr,
701 std::back_inserter(_off_diagonal_offset),
702 std::plus{});
703 }
704
705 // Some short-hand
706 std::array local_size
707 = {_index_maps[0]->size_local(), _index_maps[1]->size_local()};
708 std::array local_range
709 = {_index_maps[0]->local_range(), _index_maps[1]->local_range()};
710 std::span ghosts1 = _index_maps[1]->ghosts();
711
712 std::span ghosts0 = _index_maps[0]->ghosts();
713 std::span src_ranks = _index_maps[0]->src();
714 std::span dest_ranks = _index_maps[0]->dest();
715
716 // Create neighbourhood communicator (owner <- ghost)
717 MPI_Comm comm;
718 MPI_Dist_graph_create_adjacent(_index_maps[0]->comm(), dest_ranks.size(),
719 dest_ranks.data(), MPI_UNWEIGHTED,
720 src_ranks.size(), src_ranks.data(),
721 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm);
722 _comm = dolfinx::MPI::Comm(comm, false);
723
724 // Build map from ghost row index position to owning (neighborhood)
725 // rank
726 _ghost_row_to_rank.reserve(_index_maps[0]->owners().size());
727 for (int r : _index_maps[0]->owners())
728 {
729 auto it = std::ranges::lower_bound(src_ranks, r);
730 assert(it != src_ranks.end() and *it == r);
731 std::size_t pos = std::ranges::distance(src_ranks.begin(), it);
732 _ghost_row_to_rank.push_back(pos);
733 }
734
735 // Compute size of data to send to each neighbor
736 std::vector<std::int32_t> data_per_proc(src_ranks.size(), 0);
737 for (std::size_t i = 0; i < _ghost_row_to_rank.size(); ++i)
738 {
739 assert(_ghost_row_to_rank[i] < (int)data_per_proc.size());
740 std::size_t pos = local_size[0] + i;
741 data_per_proc[_ghost_row_to_rank[i]] += _row_ptr[pos + 1] - _row_ptr[pos];
742 }
743
744 // Compute send displacements
745 _val_send_disp.resize(src_ranks.size() + 1, 0);
746 std::partial_sum(data_per_proc.begin(), data_per_proc.end(),
747 std::next(_val_send_disp.begin()));
748
749 // For each ghost row, pack and send indices to neighborhood
750 std::vector<std::int64_t> ghost_index_data(2 * _val_send_disp.back());
751 {
752 std::vector<int> insert_pos = _val_send_disp;
753 for (std::size_t i = 0; i < _ghost_row_to_rank.size(); ++i)
754 {
755 int rank = _ghost_row_to_rank[i];
756 std::int32_t row_id = local_size[0] + i;
757 for (int j = _row_ptr[row_id]; j < _row_ptr[row_id + 1]; ++j)
758 {
759 // Get position in send buffer
760 std::int32_t idx_pos = 2 * insert_pos[rank];
761
762 // Pack send data (row, col) as global indices
763 ghost_index_data[idx_pos] = ghosts0[i];
764 if (std::int32_t col_local = _cols[j]; col_local < local_size[1])
765 ghost_index_data[idx_pos + 1] = col_local + local_range[1][0];
766 else
767 ghost_index_data[idx_pos + 1] = ghosts1[col_local - local_size[1]];
768
769 insert_pos[rank] += 1;
770 }
771 }
772 }
773
774 // Communicate data with neighborhood
775 std::vector<std::int64_t> ghost_index_array;
776 std::vector<int> recv_disp;
777 {
778 std::vector<int> send_sizes;
779 std::ranges::transform(data_per_proc, std::back_inserter(send_sizes),
780 [](auto x) { return 2 * x; });
781
782 std::vector<int> recv_sizes(dest_ranks.size());
783 send_sizes.reserve(1);
784 recv_sizes.reserve(1);
785 MPI_Neighbor_alltoall(send_sizes.data(), 1, MPI_INT, recv_sizes.data(), 1,
786 MPI_INT, _comm.comm());
787
788 // Build send/recv displacement
789 std::vector<int> send_disp{0};
790 std::partial_sum(send_sizes.begin(), send_sizes.end(),
791 std::back_inserter(send_disp));
792 recv_disp = {0};
793 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
794 std::back_inserter(recv_disp));
795
796 ghost_index_array.resize(recv_disp.back());
797 MPI_Neighbor_alltoallv(ghost_index_data.data(), send_sizes.data(),
798 send_disp.data(), MPI_INT64_T,
799 ghost_index_array.data(), recv_sizes.data(),
800 recv_disp.data(), MPI_INT64_T, _comm.comm());
801 }
802
803 // Store receive displacements for future use, when transferring
804 // data values
805 _val_recv_disp.resize(recv_disp.size());
806 int bs2 = _bs[0] * _bs[1];
807 std::ranges::transform(recv_disp, _val_recv_disp.begin(),
808 [&bs2](auto d) { return bs2 * d / 2; });
809 std::ranges::transform(_val_send_disp, _val_send_disp.begin(),
810 [&bs2](auto d) { return d * bs2; });
811
812 // Global-to-local map for ghost columns
813 std::vector<std::pair<std::int64_t, std::int32_t>> global_to_local;
814 global_to_local.reserve(ghosts1.size());
815 for (std::int64_t idx : ghosts1)
816 global_to_local.push_back({idx, global_to_local.size() + local_size[1]});
817 std::ranges::sort(global_to_local);
818
819 // Compute location in which data for each index should be stored
820 // when received
821 for (std::size_t i = 0; i < ghost_index_array.size(); i += 2)
822 {
823 // Row must be on this process
824 std::int32_t local_row = ghost_index_array[i] - local_range[0][0];
825 assert(local_row >= 0 and local_row < local_size[0]);
826
827 // Column may be owned or unowned
828 std::int32_t local_col = ghost_index_array[i + 1] - local_range[1][0];
829 if (local_col < 0 or local_col >= local_size[1])
830 {
831 auto it = std::ranges::lower_bound(
832 global_to_local, std::pair(ghost_index_array[i + 1], -1),
833 [](auto a, auto b) { return a.first < b.first; });
834 assert(it != global_to_local.end()
835 and it->first == ghost_index_array[i + 1]);
836 local_col = it->second;
837 }
838 auto cit0 = std::next(_cols.begin(), _row_ptr[local_row]);
839 auto cit1 = std::next(_cols.begin(), _row_ptr[local_row + 1]);
840
841 // Find position of column index and insert data
842 auto cit = std::lower_bound(cit0, cit1, local_col);
843 assert(cit != cit1);
844 assert(*cit == local_col);
845 std::size_t d = std::ranges::distance(_cols.begin(), cit);
846 _unpack_pos.push_back(d);
847 }
848
849 _unpack_pos.shrink_to_fit();
850}
851//-----------------------------------------------------------------------------
852
853// The matrix A is distributed across P processes by blocks of rows:
854// A = | A_0 |
855// | A_1 |
856// | ... |
857// | A_P-1 |
858//
859// Each submatrix A_i is owned by a single process "i" and can be further
860// decomposed into diagonal (Ai[0]) and off diagonal (Ai[1]) blocks:
861// Ai = |Ai[0] Ai[1]|
862//
863// If A is square, the diagonal block Ai[0] is also square and contains
864// only owned columns and rows. The block Ai[1] contains ghost columns
865// (unowned dofs).
866
867// Likewise, a local vector x can be decomposed into owned and ghost blocks:
868// xi = | x[0] |
869// | x[1] |
870//
871// So the product y = Ax can be computed into two separate steps:
872// y[0] = |Ai[0] Ai[1]| | x[0] | = Ai[0] x[0] + Ai[1] x[1]
873// | x[1] |
874//
877template <typename Scalar, typename V, typename W, typename X>
879 la::Vector<Scalar>& y) const
880{
881 // start communication (update ghosts)
883
884 std::int32_t nrowslocal = num_owned_rows();
885 std::span<const std::int64_t> Arow_ptr(row_ptr().data(), nrowslocal + 1);
886 std::span<const std::int32_t> Acols(cols().data(), Arow_ptr[nrowslocal]);
887 std::span<const std::int64_t> Aoff_diag_offset(off_diag_offset().data(),
888 nrowslocal);
889 std::span<const Scalar> Avalues(values().data(),
890 Arow_ptr[nrowslocal] * _bs[0] * _bs[1]);
891
892 std::span<const Scalar> _x = x.array();
893 std::span<Scalar> _y = y.array();
894
895 std::span<const std::int64_t> Arow_begin(Arow_ptr.data(), nrowslocal);
896 std::span<const std::int64_t> Arow_end(Arow_ptr.data() + 1, nrowslocal);
897
898 // First stage: spmv - diagonal
899 // yi[0] += Ai[0] * xi[0]
900 if (_bs[1] == 1)
901 {
902 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
903 _bs[0], std::integral_constant<int, 1>{});
904 }
905 else if (_bs[1] == 2)
906 {
907 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
908 _bs[0], std::integral_constant<int, 2>{});
909 }
910 else if (_bs[1] == 3)
911 {
912 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
913 _bs[0], std::integral_constant<int, 3>{});
914 }
915 else
916 {
917 impl::spmv<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
918 _bs[0], _bs[1]);
919 }
920
921 // finalize ghost update
922 x.scatter_fwd_end();
923
924 // Second stage: spmv - off-diagonal
925 // yi[0] += Ai[1] * xi[1]
926 if (_bs[1] == 1)
927 {
928 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
929 _bs[0], std::integral_constant<int, 1>{});
930 }
931 else if (_bs[1] == 2)
932 {
933 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
934 _bs[0], std::integral_constant<int, 2>{});
935 }
936 else if (_bs[1] == 3)
937 {
938 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
939 _bs[0], std::integral_constant<int, 3>{});
940 }
941 else
942 {
943 impl::spmv<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
944 _bs[0], _bs[1]);
945 }
946}
947
950template <typename Scalar, typename V, typename W, typename X>
952 la::Vector<Scalar>& y) const
953{
954 std::int32_t nrowslocal = num_owned_rows();
955 std::span<const std::int64_t> Arow_ptr(row_ptr().data(), nrowslocal + 1);
956 std::span<const std::int32_t> Acols(cols().data(), Arow_ptr[nrowslocal]);
957 std::span<const std::int64_t> Aoff_diag_offset(off_diag_offset().data(),
958 nrowslocal);
959 std::span<const Scalar> Avalues(values().data(),
960 Arow_ptr[nrowslocal] * _bs[0] * _bs[1]);
961
962 std::span<const Scalar> _x = x.array();
963 std::span<Scalar> _y = y.array();
964
965 std::span<const std::int64_t> Arow_begin(Arow_ptr.data(), nrowslocal);
966 std::span<const std::int64_t> Arow_end(Arow_ptr.data() + 1, nrowslocal);
967
968 // Compute ghost region contribution and scatter back. Zero only the
969 // ghost portion of y so the caller's owned values are preserved (multT
970 // accumulates).
971 std::int32_t ncolslocal = index_map(1)->size_local();
972 std::fill(std::next(_y.begin(), ncolslocal * _bs[1]), _y.end(), Scalar(0));
973 if (_bs[1] == 1)
974 {
975 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
976 _bs[0], std::integral_constant<int, 1>{});
977 }
978 else if (_bs[1] == 2)
979 {
980 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
981 _bs[0], std::integral_constant<int, 2>{});
982 }
983 else if (_bs[1] == 3)
984 {
985 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
986 _bs[0], std::integral_constant<int, 3>{});
987 }
988 else
989 {
990 impl::spmvT<Scalar>(Avalues, Aoff_diag_offset, Arow_end, Acols, _x, _y,
991 _bs[0], _bs[1]);
992 }
993
994 y.scatter_rev(std::plus<Scalar>{});
995
996 if (_bs[1] == 1)
997 {
998 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
999 _bs[0], std::integral_constant<int, 1>{});
1000 }
1001 else if (_bs[1] == 2)
1002 {
1003 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1004 _bs[0], std::integral_constant<int, 2>{});
1005 }
1006 else if (_bs[1] == 3)
1007 {
1008 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1009 _bs[0], std::integral_constant<int, 3>{});
1010 }
1011 else
1012 {
1013 impl::spmvT<Scalar>(Avalues, Arow_begin, Aoff_diag_offset, Acols, _x, _y,
1014 _bs[0], _bs[1]);
1015 }
1016}
1017} // namespace dolfinx::la
A duplicate MPI communicator and manage lifetime of the communicator.
Definition MPI.h:45
const container_type & values() const
Get local values (const version).
Definition MatrixCSR.h:553
std::shared_ptr< const common::IndexMap > index_map(int dim) const
Index map for the row or column space.
Definition MatrixCSR.h:542
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:572
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:266
MatrixCSR(const MatrixCSR< Scalar0, Container0, ColContainer0, RowPtrContainer0 > &A)
Copy-convert matrix, possibly using to different container types.
Definition MatrixCSR.h:225
RowPtrContainer rowptr_container_type
Row pointer container type.
Definition MatrixCSR.h:90
void scatter_rev_end()
End transfer of ghost row data to owning ranks.
Definition MatrixCSR.h:446
container_type & values()
Get local data values.
Definition MatrixCSR.h:549
auto mat_add_values()
Insertion functor for adding values to a matrix. It is typically used in finite element assembly func...
Definition MatrixCSR.h:158
BlockMode block_mode() const
Get 'block mode'.
Definition MatrixCSR.h:582
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:311
std::int32_t num_owned_rows() const
Number of local rows excluding ghost rows.
Definition MatrixCSR.h:338
ColContainer column_container_type
Column index container type.
Definition MatrixCSR.h:87
void mult(Vector< value_type > &x, Vector< value_type > &y) const
Compute the product y += Ax.
Definition MatrixCSR.h:878
MatrixCSR(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:473
void scatter_rev()
Transfer ghost row data to the owning ranks accumulating received values on the owned rows,...
Definition MatrixCSR.h:384
void multT(Vector< value_type > &x, Vector< value_type > &y) const
Compute the product y += A^T x.
Definition MatrixCSR.h:951
Container container_type
Matrix entries container type.
Definition MatrixCSR.h:84
Scalar value_type
Scalar type.
Definition MatrixCSR.h:81
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:399
const column_container_type & cols() const
Definition MatrixCSR.h:561
void set(value_type x)
Set all non-zero local entries to a value, including entries in ghost rows.
Definition MatrixCSR.h:244
std::array< int, 2 > block_size() const
Get block sizes.
Definition MatrixCSR.h:579
std::int32_t num_all_rows() const
Number of local rows including ghost rows.
Definition MatrixCSR.h:341
const rowptr_container_type & row_ptr() const
Get local row pointers.
Definition MatrixCSR.h:557
std::vector< value_type > to_dense() const
Copy to a dense matrix.
Definition MatrixCSR.h:352
MPI_Comm comm() const
Get MPI communicator that matrix is defined on.
Definition MatrixCSR.h:533
MatrixCSR(const MatrixCSR &A)=default
auto mat_set_values()
Insertion functor for setting values in a matrix. It is typically used in finite element assembly fun...
Definition MatrixCSR.h:116
A vector that can be distributed across processes.
Definition Vector.h:50
container_type & array()
Get the process-local part of the vector.
Definition Vector.h:390
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:375
void scatter_fwd_end(U unpack)
End scatter (send) of local data values that are ghosted on other processes.
Definition Vector.h:259
void scatter_fwd_begin(U pack, GetPtr get_ptr)
Begin scatter (send) of local data that is ghosted on other processes.
Definition Vector.h:222
Definition MatrixCSR.h:25
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:320
void check_error(MPI_Comm comm, int code)
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:45
@ expanded
Definition MatrixCSR.h:48
auto norm(const V &x, Norm type=Norm::l2)
Compute the norm of the vector.
Definition Vector.h:480