ezp
lightweight C++ wrapper for selected distributed solvers for linear systems
Loading...
Searching...
No Matches
sparse_solver.hpp
1/*******************************************************************************
2 * Copyright (C) 2025-2026 Theodore Chang
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17
18#ifndef SPARSE_SOLVER_HPP
19#define SPARSE_SOLVER_HPP
20
21#include "traits.hpp"
22
23#include <numeric>
24#ifdef EZP_TBB
25#include <tbb/parallel_sort.h>
26#define ezp_sort tbb::parallel_sort
27#else
28#include <algorithm>
29#define ezp_sort std::sort
30#endif
31
32namespace ezp {
33 template<data_t DT, index_t IT> struct sparse_coo_mat {
34 IT n, nnz;
35 IT *row, *col;
36 DT* data;
37
38 sparse_coo_mat(const IT n, const IT nnz, IT* const row, IT* const col, DT* const data)
39 : n(n)
40 , nnz(nnz)
41 , row(row)
42 , col(col)
43 , data(data) {}
44
45 auto is_valid() const { return row && col && data; }
46 };
47
48 namespace detail {
49 template<index_t IT> class csr_comparator {
50 const IT* const row_idx;
51 const IT* const col_idx;
52
53 public:
54 csr_comparator(const IT* const in_row_idx, const IT* const in_col_idx)
55 : row_idx(in_row_idx)
56 , col_idx(in_col_idx) {}
57
58 bool operator()(const IT idx_a, const IT idx_b) const {
59 if(row_idx[idx_a] == row_idx[idx_b]) return col_idx[idx_a] < col_idx[idx_b];
60 return row_idx[idx_a] < row_idx[idx_b];
61 }
62 };
63
64 template<data_t T> bool approx_equal(const T x, const T y, const int ulp = 2) {
65 using R = decltype(std::abs(x));
66 return std::abs(x - y) <= std::numeric_limits<R>::epsilon() * std::abs(x + y) * ulp || std::abs(x - y) < std::numeric_limits<R>::min();
67 }
68 } // namespace detail
69
70 template<data_t DT, index_t IT> struct sparse_csr_mat final {
71 IT n{}, nnz{};
72 IT *row_ptr{nullptr}, *col_idx{nullptr};
73 DT* data{nullptr};
74
75 std::vector<IT> row_storage, col_storage;
76 std::vector<DT> data_storage;
77
78 sparse_csr_mat() = default;
80 : n(other.n)
81 , nnz(other.nnz)
82 , row_ptr(other.row_ptr)
83 , col_idx(other.col_idx)
84 , data(other.data)
85 , row_storage(other.row_storage)
86 , col_storage(other.col_storage)
87 , data_storage(other.data_storage) {
88 if(!row_storage.empty()) row_ptr = row_storage.data();
89 if(!col_storage.empty()) col_idx = col_storage.data();
90 if(!data_storage.empty()) data = data_storage.data();
91 }
93 sparse_csr_mat& operator=(const sparse_csr_mat&) = delete;
94 sparse_csr_mat& operator=(sparse_csr_mat&&) = default;
95 ~sparse_csr_mat() = default;
96
97 sparse_csr_mat(const IT n, const IT nnz, IT* const row_ptr, IT* const col_idx, DT* const data)
98 : n(n)
99 , nnz(nnz)
100 , row_ptr(row_ptr)
101 , col_idx(col_idx)
102 , data(data) {}
103
111 template<data_t DT2, index_t IT2> explicit sparse_csr_mat(const sparse_coo_mat<DT2, IT2>& coo, const bool one_based = false, const bool full = false)
112 : n(IT{coo.n})
113 , nnz(IT{coo.nnz}) {
114 if(!coo.is_valid()) return;
115
116 std::vector<IT2> index(nnz);
117 std::iota(index.begin(), index.end(), IT2(0));
118 ezp_sort(index.begin(), index.end(), detail::csr_comparator(coo.row, coo.col));
119
120 row_storage.resize(n + 1);
121 col_storage.resize(nnz);
122 data_storage.resize(nnz);
123
124 condense(coo, index, one_based, full);
125
126 row_ptr = row_storage.data();
127 col_idx = col_storage.data();
128 data = data_storage.data();
129 }
130
138 template<data_t DT2, index_t IT2> auto condense(const sparse_coo_mat<DT2, IT2>& coo, const std::vector<IT2>& index, const bool one_based, const bool full) {
139 const auto shift = one_based ? IT{1} : IT{0};
140
141 auto current_pos = IT{0}, current_row = IT{0};
142 auto last_row = IT{0}, last_col = IT{0};
143 auto last_sum = DT{0};
144
145 auto populate = [&] {
146 if(!detail::approx_equal(last_sum, DT{0}) || (full && last_row == last_col)) {
147 while(current_row + shift <= last_row) row_storage[current_row++] = current_pos + shift;
148 col_storage[current_pos] = last_col;
149 data_storage[current_pos++] = last_sum;
150 }
151 last_sum = DT{0};
152 };
153
154 if(nnz > 0) {
155 last_row = static_cast<IT>(coo.row[index[0]]);
156 last_col = static_cast<IT>(coo.col[index[0]]);
157 }
158
159 for(auto I = IT{0}; I < nnz; ++I) {
160 const auto idx = index[I];
161 const auto row = static_cast<IT>(coo.row[idx]), col = static_cast<IT>(coo.col[idx]);
162 if(last_row != row || last_col != col) {
163 populate();
164 last_row = row;
165 last_col = col;
166 }
167 last_sum += coo.data[idx];
168 }
169
170 if(nnz > 0) populate();
171
172 nnz = current_pos;
173
174 // remaining (trailing empty) rows and the end pointer
175 while(current_row <= n) row_storage[current_row++] = nnz + shift;
176
177 col_storage.resize(nnz);
178 data_storage.resize(nnz);
179
181 }
182
192 IT missing{0};
193 for(auto row = IT{0}; row < n; ++row) {
194 auto found = false;
195 for(auto k = row_storage[row] - shift; k < row_storage[row + 1] - shift; ++k)
196 if(col_storage[k] == row + shift) {
197 found = true;
198 break;
199 }
200 if(!found) ++missing;
201 }
202
203 if(0 == missing) return;
204
205 std::vector<IT> new_row(n + 1);
206 std::vector<IT> new_col(nnz + missing);
207 std::vector<DT> new_data(nnz + missing);
208
209 auto pos = IT{0};
210 for(auto row = IT{0}; row < n; ++row) {
211 new_row[row] = pos + shift;
212 auto inserted = false;
213 for(auto k = row_storage[row] - shift; k < row_storage[row + 1] - shift; ++k) {
214 const auto col = col_storage[k];
215 if(!inserted && col > row + shift) {
216 new_col[pos] = row + shift;
217 new_data[pos++] = DT{0};
218 inserted = true;
219 }
220 new_col[pos] = col;
221 new_data[pos++] = data_storage[k];
222 if(col == row + shift) inserted = true;
223 }
224 if(!inserted) {
225 new_col[pos] = row + shift;
226 new_data[pos++] = DT{0};
227 }
228 }
229 new_row[n] = pos + shift;
230
231 nnz = pos;
232 row_storage = std::move(new_row);
233 col_storage = std::move(new_col);
234 data_storage = std::move(new_data);
235 }
236
237 auto is_valid() { return row_ptr && col_idx && data; }
238 };
239} // namespace ezp
240
241#endif // SPARSE_SOLVER_HPP
Definition sparse_solver.hpp:49
Solver for general sparse matrices.
Definition sparse_solver.hpp:33
Definition sparse_solver.hpp:70
sparse_csr_mat(const sparse_coo_mat< DT2, IT2 > &coo, const bool one_based=false, const bool full=false)
Definition sparse_solver.hpp:111
auto insert_missing_diagonals(const IT shift)
Definition sparse_solver.hpp:191
auto condense(const sparse_coo_mat< DT2, IT2 > &coo, const std::vector< IT2 > &index, const bool one_based, const bool full)
Definition sparse_solver.hpp:138