Eigen  5.0.1
 
Loading...
Searching...
No Matches
IncompleteLUT.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2012 Désiré Nuentsa-Wakam <desire.nuentsa_wakam@inria.fr>
5// Copyright (C) 2014 Gael Guennebaud <gael.guennebaud@inria.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla
8// Public License v. 2.0. If a copy of the MPL was not distributed
9// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
10// SPDX-License-Identifier: MPL-2.0
11
12#ifndef EIGEN_INCOMPLETE_LUT_H
13#define EIGEN_INCOMPLETE_LUT_H
14
15// IWYU pragma: private
16#include "./InternalHeaderCheck.h"
17
18namespace Eigen {
19
20namespace internal {
21
31template <typename VectorV, typename VectorI>
32Index QuickSplit(VectorV& row, VectorI& ind, Index ncut) {
33 using RealScalar = typename VectorV::RealScalar;
34 using std::abs;
35 using std::swap;
36 Index mid;
37 Index n = row.size(); /* length of the vector */
38 Index first, last;
39
40 ncut--; /* to fit the zero-based indices */
41 first = 0;
42 last = n - 1;
43 if (ncut < first || ncut > last) return 0;
44
45 do {
46 mid = first;
47 RealScalar abskey = abs(row(mid));
48 for (Index j = first + 1; j <= last; j++) {
49 if (abs(row(j)) > abskey) {
50 ++mid;
51 swap(row(mid), row(j));
52 swap(ind(mid), ind(j));
53 }
54 }
55 /* Interchange for the pivot element */
56 swap(row(mid), row(first));
57 swap(ind(mid), ind(first));
58
59 if (mid > ncut)
60 last = mid - 1;
61 else if (mid < ncut)
62 first = mid + 1;
63 } while (mid != ncut);
64
65 return 0; /* mid is equal to ncut */
66}
67
68} // end namespace internal
69
102template <typename Scalar_, typename StorageIndex_ = int>
103class IncompleteLUT : public SparseSolverBase<IncompleteLUT<Scalar_, StorageIndex_> > {
104 protected:
106 using Base::m_isInitialized;
107
108 public:
109 using Scalar = Scalar_;
110 using StorageIndex = StorageIndex_;
111 using RealScalar = typename NumTraits<Scalar>::Real;
112 using Vector = Matrix<Scalar, Dynamic, 1>;
113 using VectorI = Matrix<StorageIndex, Dynamic, 1>;
115
116 enum { ColsAtCompileTime = Dynamic, MaxColsAtCompileTime = Dynamic };
117
118 public:
119 IncompleteLUT()
120 : m_droptol(NumTraits<Scalar>::dummy_precision()),
121 m_fillfactor(10),
122 m_analysisIsOk(false),
123 m_factorizationIsOk(false) {}
124
125 template <typename MatrixType>
126 explicit IncompleteLUT(const MatrixType& mat, const RealScalar& droptol = NumTraits<Scalar>::dummy_precision(),
127 int fillfactor = 10)
128 : m_droptol(droptol), m_fillfactor(fillfactor), m_analysisIsOk(false), m_factorizationIsOk(false) {
129 eigen_assert(fillfactor != 0);
130 compute(mat);
131 }
132
134 const FactorType matrixL() const;
135
137 const FactorType matrixU() const;
138
139 constexpr Index rows() const noexcept { return m_lu.rows(); }
140
141 constexpr Index cols() const noexcept { return m_lu.cols(); }
142
153 eigen_assert(m_isInitialized && "IncompleteLUT is not initialized.");
154 return m_info;
155 }
156
157 template <typename MatrixType>
158 void analyzePattern(const MatrixType& amat);
159
160 template <typename MatrixType>
161 void factorize(const MatrixType& amat);
162
172 template <typename MatrixType>
173 IncompleteLUT& compute(const MatrixType& amat) {
174 analyzePattern(amat);
175 factorize(amat);
176 return *this;
177 }
178
179 void setDroptol(const RealScalar& droptol);
180 void setFillfactor(int fillfactor);
181
182 template <typename Rhs, typename Dest>
183 void _solve_impl(const Rhs& b, Dest& x) const {
184 x = m_PinvPr * b;
185 x = m_lu.template triangularView<UnitLower>().solve(x);
186 x = m_lu.template triangularView<Upper>().solve(x);
187 x = m_P * x;
188 }
189
190 protected:
192 struct keep_diag {
193 inline bool operator()(const Index& row, const Index& col, const Scalar&) const { return row != col; }
194 };
195
196 template <typename MatrixType>
197 Index computeRowMatching(const MatrixType& amat);
198
199 protected:
200 FactorType m_lu;
201 RealScalar m_droptol;
202 int m_fillfactor;
203 bool m_analysisIsOk;
204 bool m_factorizationIsOk;
205 ComputationInfo m_info;
206 PermutationMatrix<Dynamic, Dynamic, StorageIndex> m_P; // Fill-reducing permutation
207 PermutationMatrix<Dynamic, Dynamic, StorageIndex> m_Pinv; // Inverse permutation
208 PermutationMatrix<Dynamic, Dynamic, StorageIndex> m_Pr; // Static row permutation (matching-based)
209 PermutationMatrix<Dynamic, Dynamic, StorageIndex> m_PinvPr; // Cached composition m_Pinv * m_Pr for solve
210};
211
216template <typename Scalar, typename StorageIndex>
217void IncompleteLUT<Scalar, StorageIndex>::setDroptol(const RealScalar& droptol) {
218 this->m_droptol = droptol;
219}
220
225template <typename Scalar, typename StorageIndex>
227 this->m_fillfactor = fillfactor;
228}
229
235template <typename Scalar, typename StorageIndex>
236const typename IncompleteLUT<Scalar, StorageIndex>::FactorType IncompleteLUT<Scalar, StorageIndex>::matrixL() const {
237 eigen_assert(m_factorizationIsOk && "factorize() should be called first");
238 return m_lu.template triangularView<UnitLower>();
239}
240
246template <typename Scalar, typename StorageIndex>
247const typename IncompleteLUT<Scalar, StorageIndex>::FactorType IncompleteLUT<Scalar, StorageIndex>::matrixU() const {
248 eigen_assert(m_factorizationIsOk && "Factorization must be computed first.");
249 return m_lu.template triangularView<Upper>();
250}
251
252// Compute a row permutation m_Pr such that (m_Pr * amat) has a structurally
253// nonzero diagonal wherever one exists. Returns the number of matched columns.
254// Uses a maximum bipartite cardinality matching on the sparsity pattern, with
255// a greedy initialization that prefers the natural diagonal so that matrices
256// already having a nonzero diagonal yield the identity permutation.
257template <typename Scalar, typename StorageIndex>
258template <typename MatrixType_>
259Index IncompleteLUT<Scalar, StorageIndex>::computeRowMatching(const MatrixType_& amat) {
260 using internal::convert_index;
261 const Index n = amat.rows();
262 // We only need amat's column-major sparsity pattern; never read scalar
263 // values. The pattern view aliases amat's index storage when amat is
264 // already a column-major SparseMatrix, and otherwise materializes a CSC
265 // pattern into the scratch buffers.
268 internal::SparsityPatternRef<StorageIndex> pat = internal::make_col_major_pattern_ref(amat, outer_buf, inner_buf);
269 const StorageIndex* outer = pat.outer;
270 const StorageIndex* inner = pat.inner;
271
272 const StorageIndex kUnmatched = StorageIndex(-1);
273 // match_row[j] = original row matched to column j; match_col[i] = column matched to row i.
274 std::vector<StorageIndex> match_row(n, kUnmatched);
275 std::vector<StorageIndex> match_col(n, kUnmatched);
276
277 // The matching uses the stored sparsity pattern only and is independent of
278 // numerical values. This preserves the analyzePattern/factorize contract:
279 // the same analysis is reusable for any matrix sharing this stored pattern.
280 // Phase 1: greedy diagonal preference.
281 for (Index j = 0; j < n; ++j) {
282 const Index col_end = outer[j] + pat.nonZeros(j);
283 for (Index k = outer[j]; k < col_end; ++k) {
284 if (Index(inner[k]) == j) {
285 match_row[j] = convert_index<StorageIndex>(j);
286 match_col[j] = convert_index<StorageIndex>(j);
287 break;
288 }
289 }
290 }
291 // Phase 2: greedy off-diagonal pickup of any free row.
292 for (Index j = 0; j < n; ++j) {
293 if (match_row[j] != kUnmatched) continue;
294 const Index col_end = outer[j] + pat.nonZeros(j);
295 for (Index k = outer[j]; k < col_end; ++k) {
296 Index i = inner[k];
297 if (match_col[i] == kUnmatched) {
298 match_row[j] = convert_index<StorageIndex>(i);
299 match_col[i] = convert_index<StorageIndex>(j);
300 break;
301 }
302 }
303 }
304 // Phase 3: augmenting paths for any column still unmatched.
305 std::vector<StorageIndex> visited(n, kUnmatched);
306 // Iterative DFS: the stack frames are (column, edge index, chosen row).
307 // chosen_row[k] is the row that frame k will commit to if a path is found.
308 std::vector<Index> stack_col;
309 std::vector<Index> stack_pos;
310 std::vector<Index> stack_chosen_row;
311 stack_col.reserve(n);
312 stack_pos.reserve(n);
313 stack_chosen_row.reserve(n);
314
315 for (Index start = 0; start < n; ++start) {
316 if (match_row[start] != kUnmatched) continue;
317 StorageIndex epoch = convert_index<StorageIndex>(start);
318 stack_col.clear();
319 stack_pos.clear();
320 stack_chosen_row.clear();
321 stack_col.push_back(start);
322 stack_pos.push_back(outer[start]);
323 stack_chosen_row.push_back(-1);
324
325 while (!stack_col.empty()) {
326 Index j = stack_col.back();
327 Index pos = stack_pos.back();
328 Index col_end = outer[j] + pat.nonZeros(j);
329 bool advanced = false;
330
331 while (pos < col_end) {
332 Index i = inner[pos];
333 ++pos;
334 if (visited[i] == epoch) continue;
335 visited[i] = epoch;
336
337 if (match_col[i] == kUnmatched) {
338 // Found an augmenting path: commit it.
339 stack_chosen_row.back() = i;
340 stack_pos.back() = pos;
341 for (size_t k = 0; k < stack_col.size(); ++k) {
342 Index col = stack_col[k];
343 Index row = stack_chosen_row[k];
344 match_row[col] = convert_index<StorageIndex>(row);
345 match_col[row] = convert_index<StorageIndex>(col);
346 }
347 stack_col.clear();
348 break;
349 } else {
350 // Descend into the column currently matched to row i.
351 stack_chosen_row.back() = i;
352 stack_pos.back() = pos;
353 Index next_col = match_col[i];
354 stack_col.push_back(next_col);
355 stack_pos.push_back(outer[next_col]);
356 stack_chosen_row.push_back(-1);
357 advanced = true;
358 break;
359 }
360 }
361
362 if (!advanced && !stack_col.empty()) {
363 stack_col.pop_back();
364 stack_pos.pop_back();
365 stack_chosen_row.pop_back();
366 }
367 }
368 }
369
370 // Build the row permutation. Matched columns get their matching row;
371 // any leftover columns are filled in identity-fashion with the leftover rows.
372 m_Pr.resize(n);
373 std::vector<bool> col_used(n, false), row_used(n, false);
374 Index matched = 0;
375 for (Index j = 0; j < n; ++j) {
376 if (match_row[j] != kUnmatched) {
377 m_Pr.indices()(match_row[j]) = convert_index<StorageIndex>(j);
378 col_used[j] = true;
379 row_used[match_row[j]] = true;
380 ++matched;
381 }
382 }
383 Index next_col = 0;
384 for (Index i = 0; i < n; ++i) {
385 if (row_used[i]) continue;
386 while (next_col < n && col_used[next_col]) ++next_col;
387 m_Pr.indices()(i) = convert_index<StorageIndex>(next_col);
388 ++next_col;
389 }
390 return matched;
391}
392
393template <typename Scalar, typename StorageIndex>
394template <typename MatrixType_>
395void IncompleteLUT<Scalar, StorageIndex>::analyzePattern(const MatrixType_& amat) {
396 eigen_assert((amat.rows() == amat.cols()) && "The factorization should be done on a square matrix");
397 // 1. Compute a static row permutation that makes the diagonal structurally
398 // nonzero. This is a workaround for the lack of partial pivoting in ILUT.
399 // For matrices that already have a nonzero diagonal, this returns the
400 // identity permutation and is essentially free.
401 computeRowMatching(amat);
402
403 // 2. Compute the Fill-reducing permutation on the row-permuted matrix.
404 // Since ILUT does not perform any numerical pivoting, it is highly
405 // preferable to keep the diagonal through symmetric permutations. AMD
406 // computes a fill-reducing ordering for a symmetric matrix and only reads
407 // the sparsity pattern; build a value-free, row-permuted representation
408 // (1-byte placeholder Scalar, indices remapped through m_Pr) and feed that
409 // to AMDOrdering, avoiding the previous mat1/mat2/AtA value copies.
411 {
414 internal::SparsityPatternRef<StorageIndex> pat = internal::make_col_major_pattern_ref(amat, outer_buf, inner_buf);
415 internal::materialize_col_major_pattern(pat, m_Pr.indices().data(), permuted_pattern);
416 }
418 ordering(permuted_pattern, m_P);
419 m_Pinv = m_P.inverse(); // cache the inverse permutation
420 // Cache the composition m_Pinv * m_Pr so _solve_impl applies a single
421 // permutation to the RHS instead of two.
422 m_PinvPr = m_Pinv * m_Pr;
423 m_analysisIsOk = true;
424 m_factorizationIsOk = false;
425 m_isInitialized = true;
426}
427
428template <typename Scalar, typename StorageIndex>
429template <typename MatrixType_>
430void IncompleteLUT<Scalar, StorageIndex>::factorize(const MatrixType_& amat) {
431 using internal::convert_index;
432 using std::abs;
433 using std::sqrt;
434 using std::swap;
435
436 eigen_assert((amat.rows() == amat.cols()) && "The factorization should be done on a square matrix");
437 Index n = amat.cols(); // Size of the matrix
438 m_lu.resize(n, n);
439 // Declare Working vectors and variables
440 Vector u(n); // real values of the row -- maximum size is n --
441 VectorI ju(n); // column position of the values in u -- maximum size is n
442 VectorI jr(n); // Indicate the position of the nonzero elements in the vector u -- A zero location is indicated by -1
443
444 // Apply the static row permutation (from analyzePattern), then the
445 // fill-reducing symmetric permutation.
446 eigen_assert(m_analysisIsOk && "You must first call analyzePattern()");
447 SparseMatrix<Scalar, RowMajor, StorageIndex> row_permuted_mat = m_Pr * amat;
449 mat = row_permuted_mat.twistedBy(m_Pinv);
450 Index zero_pivots = 0;
451
452 // Initialization
453 jr.fill(-1);
454 ju.fill(0);
455 u.fill(0);
456
457 // number of largest elements to keep in each row:
458 Index fill_in = (amat.nonZeros() * m_fillfactor) / n + 1;
459 if (fill_in > n) fill_in = n;
460
461 // number of largest nonzero elements to keep in the L and the U part of the current row:
462 Index nnzL = fill_in / 2;
463 Index nnzU = nnzL;
464 m_lu.reserve(n * (nnzL + nnzU + 1));
465
466 // global loop over the rows of the sparse matrix
467 for (Index ii = 0; ii < n; ii++) {
468 // 1 - copy the lower and the upper part of the row i of mat in the working vector u
469
470 Index sizeu = 1; // number of nonzero elements in the upper part of the current row
471 Index sizel = 0; // number of nonzero elements in the lower part of the current row
472 ju(ii) = convert_index<StorageIndex>(ii);
473 u(ii) = 0;
474 jr(ii) = convert_index<StorageIndex>(ii);
475 RealScalar rownorm = 0;
476
477 typename FactorType::InnerIterator j_it(mat, ii); // Iterate through the current row ii
478 for (; j_it; ++j_it) {
479 Index k = j_it.index();
480 if (k < ii) {
481 // copy the lower part
482 ju(sizel) = convert_index<StorageIndex>(k);
483 u(sizel) = j_it.value();
484 jr(k) = convert_index<StorageIndex>(sizel);
485 ++sizel;
486 } else if (k == ii) {
487 u(ii) = j_it.value();
488 } else {
489 // copy the upper part
490 Index jpos = ii + sizeu;
491 ju(jpos) = convert_index<StorageIndex>(k);
492 u(jpos) = j_it.value();
493 jr(k) = convert_index<StorageIndex>(jpos);
494 ++sizeu;
495 }
496 rownorm += numext::abs2(j_it.value());
497 }
498
499 // 2 - detect possible zero row
500 if (rownorm == 0) {
501 m_info = NumericalIssue;
502 return;
503 }
504 // Take the 2-norm of the current row as a relative tolerance
505 rownorm = sqrt(rownorm);
506
507 // 3 - eliminate the previous nonzero rows
508 Index jj = 0;
509 Index len = 0;
510 while (jj < sizel) {
511 // In order to eliminate in the correct order,
512 // we must select first the smallest column index among ju(jj:sizel)
513 Index k;
514 Index minrow = ju.segment(jj, sizel - jj).minCoeff(&k); // k is relative to the segment
515 k += jj;
516 if (minrow != ju(jj)) {
517 // swap the two locations
518 Index j = ju(jj);
519 swap(ju(jj), ju(k));
520 jr(minrow) = convert_index<StorageIndex>(jj);
521 jr(j) = convert_index<StorageIndex>(k);
522 swap(u(jj), u(k));
523 }
524 // Reset this location
525 jr(minrow) = -1;
526
527 // Start elimination
528 typename FactorType::InnerIterator ki_it(m_lu, minrow);
529 while (ki_it && ki_it.index() < minrow) ++ki_it;
530 eigen_internal_assert(ki_it && ki_it.col() == minrow);
531 Scalar fact = u(jj) / ki_it.value();
532
533 // drop too small elements
534 if (abs(fact) <= m_droptol) {
535 jj++;
536 continue;
537 }
538
539 // linear combination of the current row ii and the row minrow
540 ++ki_it;
541 for (; ki_it; ++ki_it) {
542 Scalar prod = fact * ki_it.value();
543 Index j = ki_it.index();
544 Index jpos = jr(j);
545 if (jpos == -1) // fill-in element
546 {
547 Index newpos;
548 if (j >= ii) // dealing with the upper part
549 {
550 newpos = ii + sizeu;
551 sizeu++;
552 eigen_internal_assert(sizeu <= n);
553 } else // dealing with the lower part
554 {
555 newpos = sizel;
556 sizel++;
557 eigen_internal_assert(sizel <= ii);
558 }
559 ju(newpos) = convert_index<StorageIndex>(j);
560 u(newpos) = -prod;
561 jr(j) = convert_index<StorageIndex>(newpos);
562 } else
563 u(jpos) -= prod;
564 }
565 // store the pivot element
566 u(len) = fact;
567 ju(len) = convert_index<StorageIndex>(minrow);
568 ++len;
569
570 jj++;
571 } // end of the elimination on the row ii
572
573 // reset the upper part of the pointer jr to zero
574 for (Index k = 0; k < sizeu; k++) jr(ju(ii + k)) = -1;
575
576 // 4 - partially sort and insert the elements in the m_lu matrix
577
578 // sort the L-part of the row
579 sizel = len;
580 len = (std::min)(sizel, nnzL);
581 typename Vector::SegmentReturnType ul(u.segment(0, sizel));
582 typename VectorI::SegmentReturnType jul(ju.segment(0, sizel));
583 internal::QuickSplit(ul, jul, len);
584
585 // store the largest m_fill elements of the L part
586 m_lu.startVec(ii);
587 for (Index k = 0; k < len; k++) m_lu.insertBackByOuterInnerUnordered(ii, ju(k)) = u(k);
588
589 // store the diagonal element
590 // apply a shifting rule to avoid zero pivots (we are doing an incomplete factorization)
591 if (u(ii) == Scalar(0)) {
592 u(ii) = sqrt(m_droptol) * rownorm;
593 ++zero_pivots;
594 }
595 m_lu.insertBackByOuterInnerUnordered(ii, ii) = u(ii);
596
597 // sort the U-part of the row
598 // apply the dropping rule first
599 len = 0;
600 for (Index k = 1; k < sizeu; k++) {
601 if (abs(u(ii + k)) > m_droptol * rownorm) {
602 ++len;
603 u(ii + len) = u(ii + k);
604 ju(ii + len) = ju(ii + k);
605 }
606 }
607 sizeu = len + 1; // +1 to take into account the diagonal element
608 len = (std::min)(sizeu, nnzU);
609 typename Vector::SegmentReturnType uu(u.segment(ii + 1, sizeu - 1));
610 typename VectorI::SegmentReturnType juu(ju.segment(ii + 1, sizeu - 1));
611 internal::QuickSplit(uu, juu, len);
612
613 // store the largest elements of the U part
614 for (Index k = ii + 1; k < ii + len; k++) m_lu.insertBackByOuterInnerUnordered(ii, ju(k)) = u(k);
615 }
616 m_lu.finalize();
617 m_lu.makeCompressed();
618
619 m_factorizationIsOk = true;
620 // If we had to shift any zero pivot, the factorization is not faithful to
621 // the input matrix and the resulting preconditioner may be useless.
622 // Report this to the caller via NumericalIssue rather than silently
623 // returning Success.
624 m_info = (zero_pivots == 0) ? Success : NumericalIssue;
625}
626
627} // end namespace Eigen
628
629#endif // EIGEN_INCOMPLETE_LUT_H
Definition Ordering.h:31
void setFillfactor(int fillfactor)
Definition IncompleteLUT.h:226
IncompleteLUT & compute(const MatrixType &amat)
Definition IncompleteLUT.h:173
const FactorType matrixL() const
Extraction Method for L-Factor.
Definition IncompleteLUT.h:236
const FactorType matrixU() const
Extraction Method for U-Factor.
Definition IncompleteLUT.h:247
void setDroptol(const RealScalar &droptol)
Definition IncompleteLUT.h:217
ComputationInfo info() const
Reports whether previous computation was successful.
Definition IncompleteLUT.h:152
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:188
Permutation matrix.
Definition PermutationMatrix.h:346
SparseSymmetricPermutationProduct< Derived, Upper|Lower > twistedBy(const PermutationMatrix< Dynamic, Dynamic, StorageIndex > &perm) const
Definition SparseMatrixBase.h:375
A versatile sparse matrix representation.
Definition SparseMatrix.h:122
ComputationInfo
Definition Constants.h:455
@ NumericalIssue
Definition Constants.h:459
@ Success
Definition Constants.h:457
Matrix< Type, Size, 1 > Vector
Size×1 vector of type Type.
Definition Matrix.h:532
Definition IncompleteLUT.h:192