Eigen  5.0.1
 
Loading...
Searching...
No Matches
SparsityPatternRef.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// This Source Code Form is subject to the terms of the Mozilla
5// Public License v. 2.0. If a copy of the MPL was not distributed
6// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
7// SPDX-FileCopyrightText: The Eigen Authors
8// SPDX-License-Identifier: MPL-2.0
9
10#ifndef EIGEN_SPARSITY_PATTERN_REF_H
11#define EIGEN_SPARSITY_PATTERN_REF_H
12
13// IWYU pragma: private
14#include "./InternalHeaderCheck.h"
15
16namespace Eigen {
17namespace internal {
18
44template <typename StorageIndex>
45struct SparsityPatternRef {
46 const StorageIndex* outer = nullptr;
47 const StorageIndex* inner = nullptr;
48 // null ⇒ source is compressed: column j is [outer[j], outer[j+1]).
49 // otherwise: column j is [outer[j], outer[j] + innerNonZero[j]).
50 const StorageIndex* innerNonZero = nullptr;
51 Index outerSize = 0; // number of columns
52 Index innerSize = 0; // number of rows
53
54 EIGEN_DEVICE_FUNC inline bool isCompressed() const { return innerNonZero == nullptr; }
55
56 EIGEN_DEVICE_FUNC inline Index nonZeros(Index j) const {
57 return isCompressed() ? Index(outer[j + 1] - outer[j]) : Index(innerNonZero[j]);
58 }
59};
60
72template <typename Scalar, typename StorageIndex>
73SparsityPatternRef<StorageIndex> make_col_major_pattern_ref(const SparseMatrix<Scalar, ColMajor, StorageIndex>& amat,
74 Matrix<StorageIndex, Dynamic, 1>& /*outer_buf*/,
75 Matrix<StorageIndex, Dynamic, 1>& /*inner_buf*/) {
76 SparsityPatternRef<StorageIndex> p;
77 p.outer = amat.outerIndexPtr();
78 p.inner = amat.innerIndexPtr();
79 p.innerNonZero = amat.isCompressed() ? nullptr : amat.innerNonZeroPtr();
80 p.outerSize = amat.cols();
81 p.innerSize = amat.rows();
82 return p;
83}
84
91template <typename Derived>
92SparsityPatternRef<typename Derived::StorageIndex> make_col_major_pattern_ref(
93 const SparseMatrixBase<Derived>& amat_base, Matrix<typename Derived::StorageIndex, Dynamic, 1>& outer_buf,
94 Matrix<typename Derived::StorageIndex, Dynamic, 1>& inner_buf) {
95 using StorageIndex = typename Derived::StorageIndex;
96 const Derived& amat = amat_base.derived();
97 internal::evaluator<Derived> amat_eval(amat);
98 const Index n_cols = amat.cols();
99 const Index n_outer = amat.outerSize();
100
101 outer_buf.setZero(n_cols + 1);
102 for (Index i = 0; i < n_outer; ++i)
103 for (typename internal::evaluator<Derived>::InnerIterator it(amat_eval, i); it; ++it) ++outer_buf(it.col() + 1);
104 for (Index j = 0; j < n_cols; ++j) outer_buf(j + 1) += outer_buf(j);
105 inner_buf.resize(outer_buf(n_cols));
106 Matrix<StorageIndex, Dynamic, 1> head = outer_buf.head(n_cols);
107 for (Index i = 0; i < n_outer; ++i)
108 for (typename internal::evaluator<Derived>::InnerIterator it(amat_eval, i); it; ++it)
109 inner_buf(head(it.col())++) = convert_index<StorageIndex>(it.row());
110
111 SparsityPatternRef<StorageIndex> p;
112 p.outer = outer_buf.data();
113 p.inner = inner_buf.data();
114 p.innerNonZero = nullptr;
115 p.outerSize = n_cols;
116 p.innerSize = amat.rows();
117 return p;
118}
119
140template <typename StorageIndex>
141void materialize_col_major_pattern(const SparsityPatternRef<StorageIndex>& pat, const StorageIndex* row_perm,
142 SparseMatrix<signed char, ColMajor, StorageIndex>& out) {
143 const Index n_outer = pat.outerSize;
144 const Index n_inner = pat.innerSize;
145 out.resize(n_inner, n_outer);
146
147 Index total = 0;
148 if (pat.isCompressed()) {
149 total = pat.outer[n_outer];
150 } else {
151 for (Index j = 0; j < n_outer; ++j) total += pat.nonZeros(j);
152 }
153 out.resizeNonZeros(total);
154
155 StorageIndex* o_outer = out.outerIndexPtr();
156 StorageIndex* o_inner = out.innerIndexPtr();
157 if (pat.isCompressed()) {
158 // The source's outer array is already in CSC form, and SparseMatrix's
159 // outerIndexPtr() always starts at zero (as does the buffer-built fallback
160 // in make_col_major_pattern_ref), so the result's outer indices are a
161 // verbatim copy. For the no-permutation case the inner indices are also a
162 // straight bulk copy. Both reduce to single std::copy / memcpy calls.
163 std::copy(pat.outer, pat.outer + n_outer + 1, o_outer);
164 if (row_perm == nullptr) {
165 std::copy(pat.inner, pat.inner + total, o_inner);
166 } else {
167 // Remap-and-sort per column, reading from the source's inner array
168 // directly to avoid the write-amplification of bulk-copying then
169 // overwriting in place.
170 for (Index j = 0; j < n_outer; ++j) {
171 const StorageIndex* src = pat.inner + o_outer[j];
172 StorageIndex* dst = o_inner + o_outer[j];
173 const Index nz = o_outer[j + 1] - o_outer[j];
174 for (Index k = 0; k < nz; ++k) dst[k] = row_perm[src[k]];
175 std::sort(dst, dst + nz);
176 }
177 }
178 } else {
179 o_outer[0] = 0;
180 for (Index j = 0; j < n_outer; ++j) {
181 const Index nz = pat.nonZeros(j);
182 o_outer[j + 1] = convert_index<StorageIndex>(o_outer[j] + nz);
183 const StorageIndex* src = pat.inner + pat.outer[j];
184 StorageIndex* dst = o_inner + o_outer[j];
185 if (row_perm == nullptr) {
186 std::copy(src, src + nz, dst);
187 } else {
188 for (Index k = 0; k < nz; ++k) dst[k] = row_perm[src[k]];
189 std::sort(dst, dst + nz);
190 }
191 }
192 }
193 // Fill values with a fixed nonzero sentinel so downstream consumers that
194 // read coefficients (transpose, sparse +, selfadjointView expansion) do not
195 // observe uninitialized memory.
196 std::fill_n(out.valuePtr(), total, static_cast<signed char>(1));
197}
198
200template <typename StorageIndex>
201void materialize_col_major_pattern(const SparsityPatternRef<StorageIndex>& pat,
202 SparseMatrix<signed char, ColMajor, StorageIndex>& out) {
203 materialize_col_major_pattern(pat, static_cast<const StorageIndex*>(nullptr), out);
204}
205
206// The two materializers below — materialize_at_plus_a_pattern and
207// materialize_selfadjoint_pattern<UpLo> — share a common skeleton (counting
208// sort to build A^T, then two passes of linear two-way merge to count and
209// write column sizes). The two-way merge bodies are duplicated rather than
210// extracted into shared helpers because every attempt to factor them out
211// regressed performance by 2–5% on medium matrices: lambda-predicate
212// unification (because the lambda call wasn't elided across the inner loop)
213// and EIGEN_STRONG_INLINE function-template helpers (the compiler inlined
214// them but the function-template boundary still produced measurably worse
215// codegen than the explicit inline loops, presumably from differences in
216// register allocation or vectorization heuristics around the merge's
217// local-variable life-ranges). The merge logic is short enough that the
218// duplication is a small maintenance burden in exchange for stable codegen
219// on the AMD hot path. If a future compiler change makes the abstraction
220// free, this can be revisited.
221
236template <typename StorageIndex>
237void materialize_at_plus_a_pattern(const SparsityPatternRef<StorageIndex>& A,
238 SparseMatrix<signed char, ColMajor, StorageIndex>& out) {
239 const Index n = A.outerSize;
240 eigen_assert(n == A.innerSize && "materialize_at_plus_a_pattern: A must be square");
241
242 // 1. Build A^T's column starts (= A's row counts) via counting sort.
243 Matrix<StorageIndex, Dynamic, 1> AT_p(n + 1);
244 AT_p.setZero();
245 for (Index j = 0; j < n; ++j) {
246 const Index nz = A.nonZeros(j);
247 const StorageIndex* col = A.inner + A.outer[j];
248 for (Index k = 0; k < nz; ++k) ++AT_p(col[k] + 1);
249 }
250 for (Index i = 0; i < n; ++i) AT_p(i + 1) += AT_p(i);
251
252 // 2. Place A^T's inner indices. Visiting A column-by-column in increasing j
253 // means each A^T column accumulates its inner indices in increasing order,
254 // so AT_i is sorted within each column. \a head is a deliberate deep copy
255 // of \c AT_p.head(n) — it is incremented as a per-column write cursor while
256 // \c AT_p remains the immutable column-start array used in passes 3 and 4.
257 const StorageIndex a_nnz = AT_p(n);
258 Matrix<StorageIndex, Dynamic, 1> AT_i(a_nnz);
259 Matrix<StorageIndex, Dynamic, 1> head = AT_p.head(n);
260 for (Index j = 0; j < n; ++j) {
261 const Index nz = A.nonZeros(j);
262 const StorageIndex* col = A.inner + A.outer[j];
263 for (Index k = 0; k < nz; ++k) AT_i(head(col[k])++) = convert_index<StorageIndex>(j);
264 }
265
266 // 3. First merge pass: count column sizes of pattern(A + A^T) by two-way
267 // merge of A's and A^T's sorted columns.
268 out.resize(n, n);
269 StorageIndex* out_p = out.outerIndexPtr();
270 out_p[0] = 0;
271 for (Index j = 0; j < n; ++j) {
272 const StorageIndex* a_col = A.inner + A.outer[j];
273 const Index a_nz = A.nonZeros(j);
274 const StorageIndex* at_col = AT_i.data() + AT_p(j);
275 const Index at_nz = AT_p(j + 1) - AT_p(j);
276 Index ia = 0, it = 0, count = 0;
277 while (ia < a_nz && it < at_nz) {
278 const StorageIndex va = a_col[ia], vt = at_col[it];
279 if (va < vt) {
280 ++ia;
281 } else if (va > vt) {
282 ++it;
283 } else {
284 ++ia;
285 ++it;
286 }
287 ++count;
288 }
289 count += (a_nz - ia) + (at_nz - it);
290 out_p[j + 1] = out_p[j] + convert_index<StorageIndex>(count);
291 }
292
293 const StorageIndex total = out_p[n];
294 out.resizeNonZeros(total);
295
296 // 4. Second merge pass: write merged inner indices.
297 StorageIndex* out_i = out.innerIndexPtr();
298 for (Index j = 0; j < n; ++j) {
299 const StorageIndex* a_col = A.inner + A.outer[j];
300 const Index a_nz = A.nonZeros(j);
301 const StorageIndex* at_col = AT_i.data() + AT_p(j);
302 const Index at_nz = AT_p(j + 1) - AT_p(j);
303 StorageIndex* dst = out_i + out_p[j];
304 Index ia = 0, it = 0;
305 while (ia < a_nz && it < at_nz) {
306 const StorageIndex va = a_col[ia], vt = at_col[it];
307 if (va < vt) {
308 *dst++ = va;
309 ++ia;
310 } else if (va > vt) {
311 *dst++ = vt;
312 ++it;
313 } else {
314 *dst++ = va;
315 ++ia;
316 ++it;
317 }
318 }
319 while (ia < a_nz) *dst++ = a_col[ia++];
320 while (it < at_nz) *dst++ = at_col[it++];
321 }
322 std::fill_n(out.valuePtr(), total, static_cast<signed char>(1));
323}
324
335template <unsigned int UpLo, typename StorageIndex>
336void materialize_selfadjoint_pattern(const SparsityPatternRef<StorageIndex>& A,
337 SparseMatrix<signed char, ColMajor, StorageIndex>& out) {
338 static_assert(UpLo == static_cast<unsigned>(Lower) || UpLo == static_cast<unsigned>(Upper),
339 "UpLo must be Lower or Upper");
340 const Index n = A.outerSize;
341 eigen_assert(n == A.innerSize && "materialize_selfadjoint_pattern: A must be square");
342 constexpr bool IsLower = (UpLo == static_cast<unsigned>(Lower));
343
344 // 1. Count filtered row occurrences (A^T's column lengths). For UpLo == Lower
345 // we keep entries with row >= col; for Upper, row <= col. The diagonal is
346 // kept in both cases.
347 Matrix<StorageIndex, Dynamic, 1> AT_p(n + 1);
348 AT_p.setZero();
349 for (Index j = 0; j < n; ++j) {
350 const Index nz = A.nonZeros(j);
351 const StorageIndex* col = A.inner + A.outer[j];
352 for (Index k = 0; k < nz; ++k) {
353 const StorageIndex r = col[k];
354 const bool keep = IsLower ? (Index(r) >= j) : (Index(r) <= j);
355 if (keep) ++AT_p(r + 1);
356 }
357 }
358 for (Index i = 0; i < n; ++i) AT_p(i + 1) += AT_p(i);
359
360 // 2. Place A^T's inner indices. The kept entries of column j are visited in
361 // sorted row-order, so each A^T column ends up sorted. \a head is a deep
362 // copy used as a per-column write cursor while \c AT_p stays untouched.
363 const StorageIndex a_nnz = AT_p(n);
364 Matrix<StorageIndex, Dynamic, 1> AT_i(a_nnz);
365 Matrix<StorageIndex, Dynamic, 1> head = AT_p.head(n);
366 for (Index j = 0; j < n; ++j) {
367 const Index nz = A.nonZeros(j);
368 const StorageIndex* col = A.inner + A.outer[j];
369 for (Index k = 0; k < nz; ++k) {
370 const StorageIndex r = col[k];
371 const bool keep = IsLower ? (Index(r) >= j) : (Index(r) <= j);
372 if (keep) AT_i(head(r)++) = convert_index<StorageIndex>(j);
373 }
374 }
375
376 // Cache the kept-range bound for each column so passes 3 and 4 don't both
377 // pay the binary search. For UpLo == Lower the kept range starts at
378 // \c lower_bound(j) and runs to the end of the column; for Upper it starts
379 // at 0 and ends at \c upper_bound(j). Storing only the side that requires a
380 // search keeps scratch to one Index per column.
381 Matrix<Index, Dynamic, 1> a_split(n);
382 for (Index j = 0; j < n; ++j) {
383 const StorageIndex* a_col = A.inner + A.outer[j];
384 const Index a_nz = A.nonZeros(j);
385 EIGEN_IF_CONSTEXPR (IsLower) {
386 a_split(j) = std::lower_bound(a_col, a_col + a_nz, StorageIndex(j)) - a_col;
387 } else {
388 a_split(j) = std::upper_bound(a_col, a_col + a_nz, StorageIndex(j)) - a_col;
389 }
390 }
391
392 // 3. First merge pass: count column sizes of pattern(A + A^T) over the
393 // filtered subrange of each column.
394 out.resize(n, n);
395 StorageIndex* out_p = out.outerIndexPtr();
396 out_p[0] = 0;
397 for (Index j = 0; j < n; ++j) {
398 const StorageIndex* a_col = A.inner + A.outer[j];
399 const Index a_nz = A.nonZeros(j);
400 const StorageIndex* a_kept = IsLower ? a_col + a_split(j) : a_col;
401 const Index a_kept_nz = IsLower ? (a_nz - a_split(j)) : a_split(j);
402 const StorageIndex* at_col = AT_i.data() + AT_p(j);
403 const Index at_nz = AT_p(j + 1) - AT_p(j);
404 Index ia = 0, it = 0, count = 0;
405 while (ia < a_kept_nz && it < at_nz) {
406 const StorageIndex va = a_kept[ia], vt = at_col[it];
407 if (va < vt) {
408 ++ia;
409 } else if (va > vt) {
410 ++it;
411 } else {
412 ++ia;
413 ++it;
414 }
415 ++count;
416 }
417 count += (a_kept_nz - ia) + (at_nz - it);
418 out_p[j + 1] = out_p[j] + convert_index<StorageIndex>(count);
419 }
420
421 const StorageIndex total = out_p[n];
422 out.resizeNonZeros(total);
423
424 // 4. Second merge pass: write merged inner indices.
425 StorageIndex* out_i = out.innerIndexPtr();
426 for (Index j = 0; j < n; ++j) {
427 const StorageIndex* a_col = A.inner + A.outer[j];
428 const Index a_nz = A.nonZeros(j);
429 const StorageIndex* a_kept = IsLower ? a_col + a_split(j) : a_col;
430 const Index a_kept_nz = IsLower ? (a_nz - a_split(j)) : a_split(j);
431 const StorageIndex* at_col = AT_i.data() + AT_p(j);
432 const Index at_nz = AT_p(j + 1) - AT_p(j);
433 StorageIndex* dst = out_i + out_p[j];
434 Index ia = 0, it = 0;
435 while (ia < a_kept_nz && it < at_nz) {
436 const StorageIndex va = a_kept[ia], vt = at_col[it];
437 if (va < vt) {
438 *dst++ = va;
439 ++ia;
440 } else if (va > vt) {
441 *dst++ = vt;
442 ++it;
443 } else {
444 *dst++ = va;
445 ++ia;
446 ++it;
447 }
448 }
449 while (ia < a_kept_nz) *dst++ = a_kept[ia++];
450 while (it < at_nz) *dst++ = at_col[it++];
451 }
452 std::fill_n(out.valuePtr(), total, static_cast<signed char>(1));
453}
454
455} // namespace internal
456} // namespace Eigen
457
458#endif // EIGEN_SPARSITY_PATTERN_REF_H
@ Lower
Definition Constants.h:212
@ Upper
Definition Constants.h:214