Eigen  5.0.1
 
Loading...
Searching...
No Matches
SimplicialCholesky_impl.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2008-2012 Gael Guennebaud <gael.guennebaud@inria.fr>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11/*
12NOTE: these functions have been adapted from the LDL library:
13
14LDL Copyright (c) 2005 by Timothy A. Davis. All Rights Reserved.
15
16The author of LDL, Timothy A. Davis., has executed a license with Google LLC
17to permit distribution of this code and derivative works as part of Eigen under
18the Mozilla Public License v. 2.0, as stated at the top of this file.
19 */
20
21#ifndef EIGEN_SIMPLICIAL_CHOLESKY_IMPL_H
22#define EIGEN_SIMPLICIAL_CHOLESKY_IMPL_H
23
24// IWYU pragma: private
25#include "./InternalHeaderCheck.h"
26
27namespace Eigen {
28
29namespace internal {
30
31template <typename Scalar, typename StorageIndex>
32struct simpl_chol_helper {
33 using CholMatrixType = SparseMatrix<Scalar, ColMajor, StorageIndex>;
34 using InnerIterator = typename CholMatrixType::InnerIterator;
35 using VectorI = Matrix<StorageIndex, Dynamic, 1>;
36 static constexpr StorageIndex kEmpty = -1;
37
38 // Implementation of a stack or last-in first-out structure with some debugging machinery.
39 struct Stack {
40 StorageIndex* m_data;
41 Index m_size;
42#ifndef EIGEN_NO_DEBUG
43 const Index m_maxSize;
44 Stack(StorageIndex* data, StorageIndex size, StorageIndex maxSize)
45 : m_data(data), m_size(size), m_maxSize(maxSize) {
46 eigen_assert(size >= 0);
47 eigen_assert(maxSize >= size);
48 }
49#else
50 Stack(StorageIndex* data, StorageIndex size, StorageIndex /*maxSize*/) : m_data(data), m_size(size) {}
51#endif
52 bool empty() const { return m_size == 0; }
53 Index size() const { return m_size; }
54 StorageIndex back() const {
55 eigen_assert(m_size > 0);
56 return m_data[m_size - 1];
57 }
58 void push(const StorageIndex& value) {
59#ifndef EIGEN_NO_DEBUG
60 eigen_assert(m_size < m_maxSize);
61#endif
62 m_data[m_size] = value;
63 m_size++;
64 }
65 void pop() {
66 eigen_assert(m_size > 0);
67 m_size--;
68 }
69 };
70
71 // Implementation of a disjoint-set or union-find structure with path compression.
72 struct DisjointSet {
73 StorageIndex* m_set;
74 DisjointSet(StorageIndex* set, StorageIndex size) : m_set(set) { std::iota(set, set + size, 0); }
75 // Find the set representative or root of `u`.
76 StorageIndex find(StorageIndex u) const {
77 eigen_assert(u != kEmpty);
78 while (m_set[u] != u) {
79 // manually unroll the loop by a factor of 2 to improve performance
80 u = m_set[m_set[u]];
81 }
82 return u;
83 }
84 // Perform full path compression such that each node from `u` to `v` points to `v`.
85 void compress(StorageIndex u, StorageIndex v) {
86 eigen_assert(u != kEmpty);
87 eigen_assert(v != kEmpty);
88 while (m_set[u] != v) {
89 StorageIndex next = m_set[u];
90 m_set[u] = v;
91 u = next;
92 }
93 }
94 };
95
96 // Computes the higher adjacency pattern by transposing the input lower adjacency matrix.
97 // Only the index arrays are calculated, as the values are not needed for the symbolic factorization.
98 // The outer index array provides the size requirements of the inner index array.
99
100 // Computes the outer index array of the higher adjacency matrix.
101 static void calc_hadj_outer(const StorageIndex size, const CholMatrixType& ap, StorageIndex* outerIndex) {
102 for (StorageIndex j = 1; j < size; j++) {
103 for (InnerIterator it(ap, j); it; ++it) {
104 StorageIndex i = it.index();
105 if (i < j) outerIndex[i + 1]++;
106 }
107 }
108 std::partial_sum(outerIndex, outerIndex + size + 1, outerIndex);
109 }
110
111 // inner index array
112 static void calc_hadj_inner(const StorageIndex size, const CholMatrixType& ap, const StorageIndex* outerIndex,
113 StorageIndex* innerIndex, StorageIndex* tmp) {
114 std::fill_n(tmp, size, 0);
115
116 for (StorageIndex j = 1; j < size; j++) {
117 for (InnerIterator it(ap, j); it; ++it) {
118 StorageIndex i = it.index();
119 if (i < j) {
120 StorageIndex b = outerIndex[i] + tmp[i];
121 innerIndex[b] = j;
122 tmp[i]++;
123 }
124 }
125 }
126 }
127
128 // Adapted from:
129 // Joseph W. Liu. (1986).
130 // A compact row storage scheme for Cholesky factors using elimination trees.
131 // ACM Trans. Math. Softw. 12, 2 (June 1986), 127-148. https://doi.org/10.1145/6497.6499
132
133 // Computes the elimination forest of the lower adjacency matrix, a compact representation of the sparse L factor.
134 // The L factor may contain multiple elimination trees if a column contains only its diagonal element.
135 // Each elimination tree is an n-ary tree in which each node points to its parent.
136 static void calc_etree(const StorageIndex size, const CholMatrixType& ap, StorageIndex* parent, StorageIndex* tmp) {
137 std::fill_n(parent, size, kEmpty);
138
139 DisjointSet ancestor(tmp, size);
140
141 for (StorageIndex j = 1; j < size; j++) {
142 for (InnerIterator it(ap, j); it; ++it) {
143 StorageIndex i = it.index();
144 if (i < j) {
145 StorageIndex r = ancestor.find(i);
146 if (r != j) parent[r] = j;
147 ancestor.compress(i, j);
148 }
149 }
150 }
151 }
152
153 // Computes the child pointers of the parent tree to facilitate a depth-first search traversal.
154 static void calc_lineage(const StorageIndex size, const StorageIndex* parent, StorageIndex* firstChild,
155 StorageIndex* firstSibling) {
156 std::fill_n(firstChild, size, kEmpty);
157 std::fill_n(firstSibling, size, kEmpty);
158
159 for (StorageIndex j = 0; j < size; j++) {
160 StorageIndex p = parent[j];
161 if (p == kEmpty) continue;
162 StorageIndex c = firstChild[p];
163 if (c == kEmpty)
164 firstChild[p] = j;
165 else {
166 while (firstSibling[c] != kEmpty) c = firstSibling[c];
167 firstSibling[c] = j;
168 }
169 }
170 }
171
172 // Computes a post-ordered traversal of the elimination tree.
173 static void calc_post(const StorageIndex size, const StorageIndex* parent, StorageIndex* firstChild,
174 const StorageIndex* firstSibling, StorageIndex* post, StorageIndex* dfs) {
175 Stack post_stack(post, 0, size);
176 for (StorageIndex j = 0; j < size; j++) {
177 if (parent[j] != kEmpty) continue;
178 // Begin at a root
179 Stack dfs_stack(dfs, 0, size);
180 dfs_stack.push(j);
181 while (!dfs_stack.empty()) {
182 StorageIndex i = dfs_stack.back();
183 StorageIndex c = firstChild[i];
184 if (c == kEmpty) {
185 post_stack.push(i);
186 dfs_stack.pop();
187 } else {
188 dfs_stack.push(c);
189 // Remove the path from `i` to `c` for future traversals.
190 firstChild[i] = firstSibling[c];
191 }
192 }
193 }
194 eigen_assert(post_stack.size() == size);
195 eigen_assert(std::all_of(firstChild, firstChild + size, [](StorageIndex a) { return a == kEmpty; }));
196 }
197
198 // Adapted from:
199 // Gilbert, J. R., Ng, E., & Peyton, B. W. (1994).
200 // An efficient algorithm to compute row and column counts for sparse Cholesky factorization.
201 // SIAM Journal on Matrix Analysis and Applications, 15(4), 1075-1091.
202
203 // Computes the non-zero pattern of the L factor.
204 static void calc_colcount(const StorageIndex size, const StorageIndex* hadjOuter, const StorageIndex* hadjInner,
205 const StorageIndex* parent, StorageIndex* prevLeaf, StorageIndex* tmp,
206 const StorageIndex* post, StorageIndex* nonZerosPerCol, bool doLDLT) {
207 // initialize nonZerosPerCol with 1 for leaves, 0 for non-leaves
208 std::fill_n(nonZerosPerCol, size, 1);
209 for (StorageIndex j = 0; j < size; j++) {
210 StorageIndex p = parent[j];
211 // p is not a leaf
212 if (p != kEmpty) nonZerosPerCol[p] = 0;
213 }
214
215 DisjointSet parentSet(tmp, size);
216 // prevLeaf is already initialized
217 eigen_assert(std::all_of(prevLeaf, prevLeaf + size, [](StorageIndex a) { return a == kEmpty; }));
218
219 for (StorageIndex j_ = 0; j_ < size; j_++) {
220 StorageIndex j = post[j_];
221 nonZerosPerCol[j] += hadjOuter[j + 1] - hadjOuter[j];
222 for (StorageIndex k = hadjOuter[j]; k < hadjOuter[j + 1]; k++) {
223 StorageIndex i = hadjInner[k];
224 eigen_assert(i > j);
225 StorageIndex prev = prevLeaf[i];
226 if (prev != kEmpty) {
227 StorageIndex q = parentSet.find(prev);
228 parentSet.compress(prev, q);
229 nonZerosPerCol[q]--;
230 }
231 prevLeaf[i] = j;
232 }
233 StorageIndex p = parent[j];
234 if (p != kEmpty) parentSet.compress(j, p);
235 }
236
237 for (StorageIndex j = 0; j < size; j++) {
238 StorageIndex p = parent[j];
239 if (p != kEmpty) nonZerosPerCol[p] += nonZerosPerCol[j] - 1;
240 if (doLDLT) nonZerosPerCol[j]--;
241 }
242 }
243
244 // Finalizes the non-zero pattern of the L factor and allocates the memory for the factorization.
245 static void init_matrix(const StorageIndex size, const StorageIndex* nonZerosPerCol, CholMatrixType& L) {
246 eigen_assert(L.outerIndexPtr()[0] == 0);
247 std::partial_sum(nonZerosPerCol, nonZerosPerCol + size, L.outerIndexPtr() + 1);
248 L.resizeNonZeros(L.outerIndexPtr()[size]);
249 }
250
251 // Driver routine for the symbolic sparse Cholesky factorization.
252 static void run(const StorageIndex size, const CholMatrixType& ap, CholMatrixType& L, VectorI& parent,
253 VectorI& workSpace, bool doLDLT) {
254 parent.resize(size);
255 workSpace.resize(4 * size);
256 L.resize(size, size);
257
258 StorageIndex* tmp1 = workSpace.data();
259 StorageIndex* tmp2 = workSpace.data() + size;
260 StorageIndex* tmp3 = workSpace.data() + 2 * size;
261 StorageIndex* tmp4 = workSpace.data() + 3 * size;
262
263 // Borrow L's outer index array for the higher adjacency pattern.
264 StorageIndex* hadj_outer = L.outerIndexPtr();
265 calc_hadj_outer(size, ap, hadj_outer);
266 // Request additional temporary storage for the inner indices of the higher adjacency pattern.
267 ei_declare_aligned_stack_constructed_variable(StorageIndex, hadj_inner, hadj_outer[size], nullptr);
268 calc_hadj_inner(size, ap, hadj_outer, hadj_inner, tmp1);
269
270 calc_etree(size, ap, parent.data(), tmp1);
271 calc_lineage(size, parent.data(), tmp1, tmp2);
272 calc_post(size, parent.data(), tmp1, tmp2, tmp3, tmp4);
273 calc_colcount(size, hadj_outer, hadj_inner, parent.data(), tmp1, tmp2, tmp3, tmp4, doLDLT);
274 init_matrix(size, tmp4, L);
275 }
276};
277
278// Required pre-C++17 for ODR; redundant and deprecated since (C++17 makes
279// constexpr static data members implicitly inline).
280#if EIGEN_COMP_CXXVER < 17
281template <typename Scalar, typename StorageIndex>
282constexpr StorageIndex simpl_chol_helper<Scalar, StorageIndex>::kEmpty;
283#endif
284
285} // namespace internal
286
287template <typename Derived>
288void SimplicialCholeskyBase<Derived>::analyzePattern_preordered(const CholMatrixType& ap, bool doLDLT) {
289 using Helper = internal::simpl_chol_helper<Scalar, StorageIndex>;
290
291 eigen_assert(ap.innerSize() == ap.outerSize());
292 const StorageIndex size = internal::convert_index<StorageIndex>(ap.outerSize());
293
294 Helper::run(size, ap, m_matrix, m_parent, m_workSpace, doLDLT);
295
296 m_isInitialized = true;
297 m_info = Success;
298 m_analysisIsOk = true;
299 m_factorizationIsOk = false;
300}
301
302template <typename Derived>
303template <bool DoLDLT, bool NonHermitian>
304void SimplicialCholeskyBase<Derived>::factorize_preordered(const CholMatrixType& ap) {
305 EIGEN_IF_CONSTEXPR (internal::packet_traits<Scalar>::Vectorizable && internal::packet_traits<Scalar>::HasMul &&
306 internal::packet_traits<Scalar>::HasSub && !NumTraits<Scalar>::IsComplex) {
307 const StorageIndex* outer = m_matrix.outerIndexPtr();
308 for (Index i = 0; i < m_matrix.cols(); ++i) {
309 // Exclude the diagonal (LLT) and the entry appended after the last update.
310 if (outer[i + 1] - outer[i] > internal::kSparseScatterPacketMinSize + (DoLDLT ? 0 : 1)) {
311 factorize_preordered_impl<DoLDLT, NonHermitian, true>(ap);
312 return;
313 }
314 }
315 }
316 factorize_preordered_impl<DoLDLT, NonHermitian, false>(ap);
317}
318
319template <typename Derived>
320template <bool DoLDLT, bool NonHermitian, bool UsePackets>
321void SimplicialCholeskyBase<Derived>::factorize_preordered_impl(const CholMatrixType& ap) {
322 using std::sqrt;
323 const StorageIndex size = StorageIndex(ap.rows());
324
325 eigen_assert(m_analysisIsOk && "You must first call analyzePattern()");
326 eigen_assert(ap.rows() == ap.cols());
327 eigen_assert(m_parent.size() == size);
328 eigen_assert(m_workSpace.size() >= 3 * size);
329
330 const StorageIndex* Lp = m_matrix.outerIndexPtr();
331 StorageIndex* Li = m_matrix.innerIndexPtr();
332 Scalar* Lx = m_matrix.valuePtr();
333
334 ei_declare_aligned_stack_constructed_variable(Scalar, y, size, 0);
335 StorageIndex* nonZerosPerCol = m_workSpace.data();
336 StorageIndex* pattern = m_workSpace.data() + size;
337 StorageIndex* tags = m_workSpace.data() + 2 * size;
338
339 bool ok = true;
340 m_diag.resize(DoLDLT ? size : 0);
341
342 for (StorageIndex k = 0; k < size; ++k) {
343 // compute nonzero pattern of kth row of L, in topological order
344 y[k] = Scalar(0); // Y(0:k) is now all zero
345 StorageIndex top = size; // stack for pattern is empty
346 tags[k] = k; // mark node k as visited
347 nonZerosPerCol[k] = 0; // count of nonzeros in column k of L
348 for (typename CholMatrixType::InnerIterator it(ap, k); it; ++it) {
349 StorageIndex i = it.index();
350 if (i <= k) {
351 y[i] += getSymm(it.value()); /* scatter A(i,k) into Y (sum duplicates) */
352 Index len;
353 for (len = 0; tags[i] != k; i = m_parent[i]) {
354 pattern[len++] = i; /* L(k,i) is nonzero */
355 tags[i] = k; /* mark i as visited */
356 }
357 while (len > 0) pattern[--top] = pattern[--len];
358 }
359 }
360
361 /* compute numerical values kth row of L (a sparse triangular solve) */
362
363 DiagonalScalar d =
364 getDiag(y[k]) * m_shiftScale + m_shiftOffset; // get D(k,k), apply the shift function, and clear Y(k)
365 y[k] = Scalar(0);
366 for (; top < size; ++top) {
367 Index i = pattern[top]; /* pattern[top:n-1] is pattern of L(:,k) */
368 Scalar yi = y[i]; /* get and clear Y(i) */
369 y[i] = Scalar(0);
370
371 /* the nonzero entry L(k,i) */
372 Scalar l_ki;
373 EIGEN_IF_CONSTEXPR (DoLDLT)
374 l_ki = yi / getDiag(m_diag[i]);
375 else
376 yi = l_ki = yi / Lx[Lp[i]];
377
378 Index p2 = Lp[i] + nonZerosPerCol[i];
379 Index p = Lp[i] + (DoLDLT ? 0 : 1);
380 EIGEN_IF_CONSTEXPR (UsePackets)
381 p += internal::sparse_scatter_sub_packets<!NonHermitian>(y, Li + p, Lx + p, p2 - p, yi);
382 for (; p < p2; ++p) y[Li[p]] -= getSymm(Lx[p]) * yi;
383 d -= getDiag(l_ki * getSymm(yi));
384 Li[p] = k; /* store L(k,i) in column form of L */
385 Lx[p] = l_ki;
386 ++nonZerosPerCol[i]; /* increment count of nonzeros in col i */
387 }
388 EIGEN_IF_CONSTEXPR (DoLDLT) {
389 m_diag[k] = d;
390 if (d == RealScalar(0)) {
391 ok = false; /* failure, D(k,k) is zero */
392 break;
393 }
394 } else {
395 Index p = Lp[k] + nonZerosPerCol[k]++;
396 Li[p] = k; /* store L(k,k) = sqrt (d) in column k */
397 bool failed;
398 EIGEN_IF_CONSTEXPR (NonHermitian) {
399 failed = d == RealScalar(0);
400 } else {
401 failed = numext::real(d) <= RealScalar(0);
402 }
403 if (failed) {
404 ok = false; /* failure, matrix is not positive definite */
405 break;
406 }
407 Lx[p] = sqrt(d);
408 }
409 }
410
411 m_info = ok ? Success : NumericalIssue;
412 m_factorizationIsOk = true;
413}
414
415} // end namespace Eigen
416
417#endif // EIGEN_SIMPLICIAL_CHOLESKY_IMPL_H
@ NumericalIssue
Definition Constants.h:459
@ Success
Definition Constants.h:457