QRMatrix.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2016 OpenFOAM Foundation
9  Copyright (C) 2019-2022 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "QRMatrix.H"
30 
31 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
32 
33 template<class MatrixType>
35 (
36  const MatrixType& A
37 ) const
38 {
39  if (mode_ == modes::FULL)
40  {
41  return A.m();
42  }
43  else if (mode_ == modes::ECONOMY)
44  {
45  return min(A.m(), A.n());
46  }
47 
48  return 0;
49 }
50 
51 
52 template<class MatrixType>
54 (
55  MatrixType& AT
56 )
57 {
58  const label n = AT.m();
59  const label m = AT.n();
60 
61  List<cmptType> Rdiag(n, Zero);
62 
63  for (label k = 0; k < n; ++k)
64  {
65  // Compute 2-norm of k-th column without under/overflow
66  scalar nrm = 0;
67 
68  for (label i = k; i < m; ++i)
69  {
70  nrm = Foam::hypot(nrm, mag(AT(k,i)));
71  }
72 
73  // Avoid divide-by-zero. Use compare with == 0 not mag or VSMALL etc,
74  // otherwise wrong results (may need more investigation)
75  if (nrm != scalar(0))
76  {
77  // Form k-th Householder vector
78  if (mag(AT(k,k)) < 0)
79  {
80  nrm *= -1;
81  }
82 
83  for (label i = k; i < m; ++i)
84  {
85  AT(k,i) /= nrm;
86  }
87 
88  AT(k,k) += scalar(1);
89 
90  // Apply transformation to remaining columns
91  for (label j = k + 1; j < n; ++j)
92  {
93  cmptType s(Zero);
94 
95  for (label i = k; i < m; ++i)
96  {
97  s += Detail::conj(AT(k,i))*AT(j,i);
98  }
99 
100  if (mag(AT(k,k)) > SMALL)
101  {
102  s /= -AT(k,k);
103  }
104 
105  for (label i = k; i < m; ++i)
106  {
107  AT(j,i) += s*AT(k,i);
108  }
109  }
110  }
111 
112  Rdiag[k] = -nrm;
113  }
114 
115  calcQ(AT);
116 
117  calcR(AT, Rdiag);
118 }
119 
120 
121 template<class MatrixType>
123 (
124  MatrixType& AT,
125  const bool pivoting
126 )
127 {
128  const label n = AT.m();
129  const label m = AT.n();
130  const label sz = min(m,n);
131 
132  // Initialise permutation vector, and column norm vector
133  p_ = identity(n);
134 
135  // Initialise vector norms of each column of A
136  List<scalar> norms(n, Zero);
137  for (label k = 0; k < n; ++k)
138  {
139  for (label i = 0; i < m; ++i)
140  {
141  norms[k] += magSqr(AT(k, i));
142  }
143  }
144 
145  List<cmptType> Rdiag(n, Zero);
146 
147  for (label k = 0; k < sz; ++k)
148  {
149  const auto it = std::next(norms.cbegin(), k);
150  const label maxNormi =
152  (
153  it,
154  std::max_element(it, norms.cend())
155  );
156 
157  // Swap A, P and norms according to pivot column if the current
158  // column is not the max norm column. Also avoid any column swaps
159  // where the leading element is very small
160  if (mag(AT(k + maxNormi,k)) > SMALL && maxNormi != 0)
161  {
162  const RMatrix R1(AT.subRow(k));
163  const RMatrix R2(AT.subRow(maxNormi + k));
164 
165  AT.subRow(k) = R2;
166  AT.subRow(maxNormi + k) = R1;
167 
168  std::swap(p_[k], p_[maxNormi + k]);
169  std::swap(norms[k], norms[maxNormi + k]);
170  }
171 
172  {
173  // Compute 2-norm of k-th column without under/overflow
174  scalar nrm = 0;
175 
176  for (label i = k; i < m; ++i)
177  {
178  nrm = Foam::hypot(nrm, mag(AT(k,i)));
179  }
180 
181  if (nrm != scalar(0))
182  {
183  // Form k-th Householder vector
184  if (mag(AT(k,k)) < 0)
185  {
186  nrm *= -1;
187  }
188 
189  for (label i = k; i < m; ++i)
190  {
191  AT(k,i) /= nrm;
192  }
193 
194  AT(k,k) += scalar(1);
195 
196  // Apply transformation to remaining columns
197  for (label j = k + 1; j < n; ++j)
198  {
199  cmptType s(Zero);
200 
201  for (label i = k; i < m; ++i)
202  {
203  s += Detail::conj(AT(k,i))*AT(j,i);
204  }
205 
206  if (mag(AT(k,k)) > SMALL)
207  {
208  s /= -AT(k,k);
209  }
210 
211  for (label i = k; i < m; ++i)
212  {
213  AT(j,i) += s*AT(k,i);
214  }
215  }
216  }
217 
218  Rdiag[k] = -nrm;
219  }
220 
221  // Update norms
222  if (k < sz - 1)
223  {
224  label q = k + 1;
225  for (const auto& val : RMatrix(AT.subColumn(k, q)))
226  {
227  norms[q] -= magSqr(val);
228  ++q;
229  }
230  }
231  }
232 
233  calcQ(AT);
234 
235  calcR(AT, Rdiag);
236 }
237 
238 
239 template<class MatrixType>
241 (
242  const MatrixType& AT
243 )
244 {
245  if (output_ == outputs::ONLY_R)
246  {
247  return;
248  }
249 
250  const label n = AT.m();
251  const label m = AT.n();
252 
253  Q_.resize(m, sz_);
254 
255  MatrixType QT(Q_.transpose());
256 
257  for (label k = sz_ - 1; k >= 0; --k)
258  {
259  for (label i = 0; i < m; ++i)
260  {
261  QT(k,i) = Zero;
262  }
263 
264  QT(k,k) = scalar(1);
265 
266  for (label j = k; j < sz_; ++j)
267  {
268  if (n > k && mag(AT(k,k)) > SMALL)
269  {
270  cmptType s(Zero);
271 
272  for (label i = k; i < m; ++i)
273  {
274  s += AT(k,i)*QT(j,i);
275  }
276 
277  s /= -AT(k,k);
278 
279  for (label i = k; i < m; ++i)
280  {
281  QT(j,i) += s*Detail::conj(AT(k,i));
282  }
283  }
284  }
285  }
286 
287  Q_ = QT.T();
288 }
289 
290 
291 template<class MatrixType>
293 (
294  const MatrixType& AT,
295  const List<cmptType>& diag
296 )
297 {
298  if (output_ == outputs::ONLY_Q)
299  {
300  return;
301  }
302 
303  const label n = AT.m();
304 
305  R_.resize(sz_, n);
306 
307  for (label i = 0; i < sz_; ++i)
308  {
309  for (label j = 0; j < n; ++j)
310  {
311  if (i < j)
312  {
313  R_(i,j) = AT(j,i);
314  }
315  else if (i == j)
316  {
317  R_(i,j) = diag[i];
318  }
319  }
320  }
321 }
322 
323 
324 template<class MatrixType>
325 template<template<typename> class ListContainer>
327 (
328  ListContainer<cmptType>& x
329 ) const
330 {
331  const label n = R_.n();
332 
333  for (label i = n - 1; 0 <= i; --i)
334  {
335  cmptType sum = x[i];
336 
337  for (label j = i + 1; j < n; ++j)
338  {
339  sum -= x[j]*R_(i, j);
340  }
341 
342  if (mag(R_(i, i)) < SMALL)
343  {
345  << "Back-substitution failed due to small diagonal"
346  << abort(FatalError);
347  }
348 
349  x[i] = sum/R_(i, i);
350  }
351 }
352 
353 
354 template<class MatrixType>
355 template<template<typename> class ListContainer>
357 (
358  List<cmptType>& x,
359  const ListContainer<cmptType>& source
360 ) const
361 {
362  // Assert (&x != &source) ?
363  const label m = Q_.m();
364 
365  // x = Q_.T()*source; i.e., Q_.Tmul(source)
366  for (label i = 0; i < m; ++i)
367  {
368  x[i] = 0;
369  for (label j = 0; j < m; ++j)
370  {
371  x[i] += Q_(j, i)*source[j];
372  }
373  }
374 
375  solvex(x);
376 }
377 
378 
379 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
380 
381 template<class MatrixType>
383 (
384  const modes mode,
385  const outputs output,
386  const bool pivoting,
387  MatrixType& A
388 )
389 :
390  mode_(mode),
391  output_(output),
392  sz_(calcShapeFactor(A)),
393  Q_(),
394  R_(),
395  p_()
396 {
397  // Non-conjugate transpose of input matrix
398  MatrixType AT(A.transpose());
399  A.clear();
400 
401  if (pivoting)
402  {
403  decompose(AT, pivoting);
404  }
405  else
406  {
407  decompose(AT);
408  }
410  AT.clear();
411 }
412 
413 
414 template<class MatrixType>
416 (
417  const MatrixType& A,
418  const modes mode,
419  const outputs output,
420  const bool pivoting
421 )
422 :
423  mode_(mode),
424  output_(output),
425  sz_(calcShapeFactor(A)),
426  Q_(),
427  R_(),
428  p_()
429 {
430  MatrixType AT(A.transpose());
431 
432  if (pivoting)
433  {
434  decompose(AT, pivoting);
435  }
436  else
437  {
438  decompose(AT);
439  }
440 
441  AT.clear();
442 }
443 
444 
445 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
446 
447 template<class MatrixType>
449 (
450  List<cmptType>& x,
451  const UList<cmptType>& source
452 ) const
453 {
454  solveImpl(x, source);
455 }
456 
457 
458 template<class MatrixType>
459 template<class Addr>
461 (
462  List<cmptType>& x,
464 ) const
465 {
466  solveImpl(x, source);
467 }
468 
469 
470 template<class MatrixType>
473 (
474  const UList<cmptType>& source
475 ) const
476 {
477  auto tresult(Q_.Tmul(source));
478 
479  solvex(tresult.ref());
480 
481  return tresult;
482 }
483 
484 
485 template<class MatrixType>
486 template<class Addr>
489 (
491 ) const
492 {
493  auto tresult(Q_.Tmul(source));
494 
495  solvex(tresult.ref());
496 
497  return tresult;
498 }
499 
500 
501 template<class MatrixType>
504 (
505  const RMatrix& rhs
506 )
507 {
508  const label mRows = R_.m();
509  const label nCols = R_.n();
510  const label pCols = rhs.n();
511 
512  #ifdef FULLDEBUG
513  {
514  const label qRows = rhs.m();
515  if (mRows != qRows)
516  {
518  << "Linear system is not solvable since the number of rows of"
519  << "A and rhs are not equal:" << tab << mRows << "vs." << qRows
520  << abort(FatalError);
521  }
522 
523  const List<cmptType> diag(R_.diag());
524  for (const auto& val : diag)
525  {
526  if (mag(val) < SMALL)
527  {
529  << "SMALL-valued diagonal elem in back-substitution."
530  << endl;
531  }
532  }
533  }
534  #endif
535 
536  RMatrix b({nCols, pCols}, Zero);
537 
538  for (label i = 0; i < pCols; ++i)
539  {
540  for (label j = mRows - 1; -1 < j; --j)
541  {
542  cmptType alpha(rhs(j, i));
543 
544  for (label k = j + 1; k < mRows; ++k)
545  {
546  alpha -= b(k, i)*R_(j, k);
547  }
548 
549  b(j, i) = alpha/R_(j, j);
550  }
551  }
553  return b;
554 }
555 
556 
557 template<class MatrixType>
560 {
561  const label m = Q_.m();
562 
563  Field<cmptType> x(m);
564  SMatrix inv(m);
565 
566  for (label i = 0; i < m; ++i)
567  {
568  for (label j = 0; j < m; ++j)
569  {
570  x[j] = Q_(i, j);
571  }
572  solvex(x);
573  inv.subColumn(i) = x;
574  }
575 
576  return inv;
577 }
578 
579 
580 // * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * //
581 
582 template<class MatrixType>
583 MatrixType Foam::MatrixTools::pinv
584 (
585  const MatrixType& A,
586  scalar tol
587 )
588 {
589  typedef typename MatrixType::cmptType cmptType;
590 
591  if (A.empty())
592  {
594  << "Empty matrix found."
595  << abort(FatalError);
596  }
597 
598  if (A.size() == 1)
599  {
600  if (A(0,0) == cmptType(0))
601  {
602  return MatrixType({1, 1}, cmptType(0));
603  }
604 
605  return MatrixType({1, 1}, cmptType(1)/(A(0,0) + cmptType(VSMALL)));
606  }
607 
608  QRMatrix<MatrixType> QRM
609  (
610  A,
611  QRMatrix<MatrixType>::modes::FULL,
612  QRMatrix<MatrixType>::outputs::BOTH_QR,
613  QRMatrix<MatrixType>::pivoting::TRUE
614  );
615  const MatrixType& R = QRM.R();
616  const MatrixType& Q = QRM.Q();
617 
618  // R1 (KP:p. 648)
619  // Find the first diagonal element index with (almost) zero value in R
620  label elemi = 0;
621  label i = 0;
622  while (i < 2)
623  {
624  const List<cmptType> diag(R.diag());
625 
626  auto lessThan = [=](const cmptType& x) { return tol > mag(x); };
627 
628  elemi =
630  (
631  diag.cbegin(),
632  std::find_if(diag.cbegin(), diag.cend(), lessThan)
633  );
634 
635  if (elemi == 0)
636  {
637  if (i == 0)
638  {
640  << "The largest diagonal element magnitude is nearly zero. "
641  << "Tightening the tolerance."
642  << endl;
643 
644  tol = 1e-13;
645  ++i;
646  }
647  else
648  {
650  << "The largest diagonal element magnitude is nearly zero. "
651  << "Returning a zero matrix."
652  << endl;
653  ++i;
654 
655  return MatrixType({A.n(), A.m()}, Zero);
656  }
657  }
658  else
659  {
660  i += 2;
661  }
662  }
663 
664  // Exclude the first (almost) zero diagonal row and the rows below
665  // since R diagonal is already descending due to the QR column pivoting
666  const RectangularMatrix<cmptType> R1(R.subMatrix(0, 0, elemi));
667 
668  // R2 (KP:p. 648)
669  if (R1.n() < R1.m())
670  {
671  const SquareMatrix<cmptType> C(R1&R1);
672 
673  QRMatrix<SquareMatrix<cmptType>> QRSolve
674  (
675  C,
676  QRMatrix<SquareMatrix<cmptType>>::modes::FULL,
677  QRMatrix<SquareMatrix<cmptType>>::outputs::BOTH_QR
678  );
679 
680  RectangularMatrix<cmptType> R2
681  (
682  QRSolve.solve
683  (
684  QRSolve.Q() & R1.T()
685  )
686  );
687 
688  // R3 (KP:p. 648)
689  R2.resize(R.m(), R.n());
690 
691  return MatrixType((QRM.P()^R2)^Q);
692  }
693  else
694  {
695  const SquareMatrix<cmptType> C(R1^R1);
696 
697  QRMatrix<SquareMatrix<cmptType>> QRSolve
698  (
699  C,
700  QRMatrix<SquareMatrix<cmptType>>::modes::FULL,
701  QRMatrix<SquareMatrix<cmptType>>::outputs::BOTH_QR
702  );
703 
704  RectangularMatrix<cmptType> R2
705  (
706  QRSolve.solve
707  (
708  QRSolve.Q() & R1
709  )
710  );
711 
712  // R3
713  R2.resize(R.m(), R.n());
714 
715  return MatrixType((QRM.P()^R2)^Q);
716  }
717 }
718 
719 
720 // ************************************************************************* //
pivoting
Options for the column pivoting.
Definition: QRMatrix.H:237
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1)
std::enable_if< !std::is_same< complex, T >::value, const T &>::type conj(const T &val)
The &#39;conjugate&#39; of non-complex returns itself (pass-through) it does not return a complex! ...
Definition: complex.H:405
QRMatrix computes QR decomposition of a given scalar/complex matrix A into the following: ...
Definition: QRMatrix.H:207
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
void solve(List< cmptType > &x, const UList< cmptType > &source) const
Solve the linear system with the given source and return the solution in the argument x...
Definition: QRMatrix.C:442
dimensionedSphericalTensor inv(const dimensionedSphericalTensor &dt)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static bool lessThan(const scalar &val, const scalar &upper)
scalar distance(const vector &p1, const vector &p2)
Definition: curveTools.C:12
constexpr char tab
The tab &#39;\t&#39; character(0x09)
Definition: Ostream.H:49
label k
Boltzmann constant.
Base for lists with indirect addressing, templated on the list contents type and the addressing type...
SMatrix inv() const
Return the inverse of (Q*R), solving x = (Q*R).inv()*source.
Definition: QRMatrix.C:552
void diag(pointPatchField< vector > &, const pointPatchField< tensor > &)
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
labelList identity(const label len, label start=0)
Return an identity map of the given length with (map[i] == i), works like std::iota() but returning a...
Definition: labelLists.C:44
Generic templated field type.
Definition: Field.H:62
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
MatrixType pinv(const MatrixType &A, scalar tol=1e-5)
Moore-Penrose inverse of singular/non-singular square/rectangular scalar/complex matrices (KPP:p...
Definition: QRMatrix.C:577
A templated (M x N) rectangular matrix of objects of <Type>, containing M*N elements, derived from Matrix.
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
errorManip< error > abort(error &err)
Definition: errorManip.H:139
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: HashTable.H:105
volScalarField & C
label m() const noexcept
The number of rows.
Definition: Matrix.H:248
#define R(A, B, C, D, E, F, K, M)
#define WarningInFunction
Report a warning using Foam::Warning.
label find_if(const ListType &input, const UnaryPredicate &pred, const label start=0)
Find index of the first occurrence that satisfies the predicate.
static Ostream & output(Ostream &os, const IntRange< T > &range)
Definition: IntRanges.C:44
label n
A class for managing temporary objects.
Definition: HashPtrTable.H:50
gmvFile<< "tracers "<< particles.size()<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().x()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().y()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.timeName(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
const dimensionedScalar alpha
Fine-structure constant: default SI units: [].
static const Foam::dimensionedScalar A("", Foam::dimPressure, 611.21)
dimensionedScalar hypot(const dimensionedScalar &x, const dimensionedScalar &y)
mode_t mode(const fileName &name, const bool followLink=true)
Return the file mode, normally following symbolic links.
Definition: POSIX.C:773
QRMatrix()=delete
Construct null.
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127