libMesh
dense_matrix_test.C
Go to the documentation of this file.
1 // libmesh includes
2 #include <libmesh/dense_matrix.h>
3 #include <libmesh/dense_vector.h>
4 
5 #ifdef LIBMESH_HAVE_PETSC
6 #include "libmesh/petsc_macro.h"
7 #endif
8 
9 #include "libmesh_cppunit.h"
10 
11 
12 using namespace libMesh;
13 
14 class DenseMatrixTest : public CppUnit::TestCase
15 {
16 public:
17  void setUp() {}
18 
19  void tearDown() {}
20 
21  LIBMESH_CPPUNIT_TEST_SUITE(DenseMatrixTest);
22 
23  CPPUNIT_TEST(testOuterProduct);
24  CPPUNIT_TEST(testSVD);
25  CPPUNIT_TEST(testEVDreal);
26  CPPUNIT_TEST(testEVDcomplex);
27  CPPUNIT_TEST(testComplexSVD);
28  CPPUNIT_TEST(testSubMatrix);
29 
30  CPPUNIT_TEST_SUITE_END();
31 
32 
33 private:
34 
36  {
37  LOG_UNIT_TEST;
38 
39  DenseVector<Real> a = {1.0, 2.0};
40  DenseVector<Real> b = {3.0, 4.0, 5.0};
41 
42  DenseMatrix<Real> a_times_b;
43  a_times_b.outer_product(a, b);
44 
45  DenseMatrix<Real> a_times_b_correct(2, 3,
46  {3., 4., 5.,
47  6., 8., 10.});
48 
49  for (unsigned int i = 0; i < a.size(); ++i)
50  for (unsigned int j = 0; j < b.size(); ++j)
51  LIBMESH_ASSERT_NUMBERS_EQUAL
52  (a_times_b(i,j), a_times_b_correct(i,j), TOLERANCE*TOLERANCE);
53  }
54 
55  void testSVD()
56  {
57  LOG_UNIT_TEST;
58 
59  DenseMatrix<Number> U, VT;
60  DenseVector<Real> sigma;
61  DenseMatrix<Number> A(3, 2, {
62  1.0, 2.0,
63  3.0, 4.0,
64  5.0, 6.0});
65 
66  A.svd(sigma, U, VT);
67 
68  // Solution for this case is (verified with numpy)
69  DenseMatrix<Number> true_U(3, 2, {
70  -2.298476964000715e-01, 8.834610176985250e-01,
71  -5.247448187602936e-01, 2.407824921325463e-01,
72  -8.196419411205157e-01, -4.018960334334318e-01});
73 
74  DenseMatrix<Number> true_VT(2, 2, {
75  -6.196294838293402e-01, -7.848944532670524e-01,
76  -7.848944532670524e-01, 6.196294838293400e-01});
77 
78  DenseVector<Real> true_sigma = {9.525518091565109e+00, 5.143005806586446e-01};
79 
80  // Tolerance is bounded by the double literals above and by using Real
81  const Real tol = std::max(Real(1e-12), TOLERANCE*TOLERANCE);
82 
83  for (unsigned i=0; i<U.m(); ++i)
84  for (unsigned j=0; j<U.n(); ++j)
85  LIBMESH_ASSERT_NUMBERS_EQUAL( U(i,j), true_U(i,j), tol);
86 
87  for (unsigned i=0; i<VT.m(); ++i)
88  for (unsigned j=0; j<VT.n(); ++j)
89  LIBMESH_ASSERT_NUMBERS_EQUAL( VT(i,j), true_VT(i,j), tol);
90 
91  for (unsigned i=0; i<sigma.size(); ++i)
92  LIBMESH_ASSERT_FP_EQUAL(sigma(i), true_sigma(i), tol);
93  }
94 
95  // This function is called by testEVD for different matrices. The
96  // Lapack results are compared to known eigenvalue real and
97  // imaginary parts for the matrix in question, which must also be
98  // passed in by non-const value, since this routine will sort them
99  // in-place.
101  std::vector<Real> true_lambda_real,
102  std::vector<Real> true_lambda_imag,
103  Real tol)
104  {
105  // Note: see bottom of this file, we only do this test if PETSc is
106  // available, but this function currently only exists if we're
107  // using real numbers.
108 #ifdef LIBMESH_USE_REAL_NUMBERS
109  // Let's compute the eigenvalues on a copy of A, so that we can
110  // use the original to check the computation.
111  DenseMatrix<Real> A_copy = A;
112 
113  DenseVector<Real> lambda_real, lambda_imag;
114  DenseMatrix<Real> VR; // right eigenvectors
115  DenseMatrix<Real> VL; // left eigenvectors
116  A_copy.evd_left_and_right(lambda_real, lambda_imag, VL, VR);
117 
118  // The matrix is square and of size N x N.
119  const unsigned N = A.m();
120 
121  // Verify left eigen-values.
122  // Test that the right eigenvalues are self-consistent by computing
123  // u_j**H * A = lambda_j * u_j**H
124  // Note that we have to handle real and complex eigenvalues
125  // differently, since complex eigenvectors share their storage.
126  for (unsigned eigenval=0; eigenval<N; ++eigenval)
127  {
128  // Only check real eigenvalues
129  if (std::abs(lambda_imag(eigenval)) < tol*tol)
130  {
131  // remove print libMesh::out << "Checking eigenvalue: " << eigenval << std::endl;
132  DenseVector<Real> lhs(N), rhs(N);
133  for (unsigned i=0; i<N; ++i)
134  {
135  rhs(i) = lambda_real(eigenval) * VL(i, eigenval);
136  for (unsigned j=0; j<N; ++j)
137  lhs(i) += A(j, i) * VL(j, eigenval); // Note: A(j,i)
138  }
139 
140  // Subtract and assert that the norm of the difference is
141  // below some tolerance.
142  lhs -= rhs;
143  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
144  }
145  else
146  {
147  // This is a complex eigenvalue, so:
148  // a.) It occurs in a complex-conjugate pair
149  // b.) the real part of the eigenvector is stored is VL(:,eigenval)
150  // c.) the imag part of the eigenvector is stored in VL(:,eigenval+1)
151  //
152  // Equating the real and imaginary parts of Ax=lambda*x leads to two sets
153  // of relations that must hold:
154  // 1.) A^T x_r = lambda_r*x_r + lambda_i*x_i
155  // 2.) A^T x_i = -lambda_i*x_r + lambda_r*x_i
156  // which we can verify.
157 
158  // 1.)
159  DenseVector<Real> lhs(N), rhs(N);
160  for (unsigned i=0; i<N; ++i)
161  {
162  rhs(i) = lambda_real(eigenval) * VL(i, eigenval) + lambda_imag(eigenval) * VL(i, eigenval+1);
163  for (unsigned j=0; j<N; ++j)
164  lhs(i) += A(j, i) * VL(j, eigenval); // Note: A(j,i)
165  }
166 
167  lhs -= rhs;
168  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
169 
170  // libMesh::out << "lhs=" << std::endl;
171  // lhs.print_scientific(libMesh::out, /*precision=*/15);
172  //
173  // libMesh::out << "rhs=" << std::endl;
174  // rhs.print_scientific(libMesh::out, /*precision=*/15);
175 
176  // 2.)
177  lhs.zero();
178  rhs.zero();
179  for (unsigned i=0; i<N; ++i)
180  {
181  rhs(i) = -lambda_imag(eigenval) * VL(i, eigenval) + lambda_real(eigenval) * VL(i, eigenval+1);
182  for (unsigned j=0; j<N; ++j)
183  lhs(i) += A(j, i) * VL(j, eigenval+1); // Note: A(j,i)
184  }
185 
186  lhs -= rhs;
187  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
188 
189  // libMesh::out << "lhs=" << std::endl;
190  // lhs.print_scientific(libMesh::out, /*precision=*/15);
191  //
192  // libMesh::out << "rhs=" << std::endl;
193  // rhs.print_scientific(libMesh::out, /*precision=*/15);
194 
195  // We'll skip the second member of the complex conjugate
196  // pair. If the first one worked, the second one should
197  // as well...
198  eigenval += 1;
199  }
200  }
201 
202  // Verify right eigen-values.
203  // Test that the right eigenvalues are self-consistent by computing
204  // A * v_j - lambda_j * v_j
205  // Note that we have to handle real and complex eigenvalues
206  // differently, since complex eigenvectors share their storage.
207  for (unsigned eigenval=0; eigenval<N; ++eigenval)
208  {
209  // Only check real eigenvalues
210  if (std::abs(lambda_imag(eigenval)) < tol*tol)
211  {
212  // remove print libMesh::out << "Checking eigenvalue: " << eigenval << std::endl;
213  DenseVector<Real> lhs(N), rhs(N);
214  for (unsigned i=0; i<N; ++i)
215  {
216  rhs(i) = lambda_real(eigenval) * VR(i, eigenval);
217  for (unsigned j=0; j<N; ++j)
218  lhs(i) += A(i, j) * VR(j, eigenval);
219  }
220 
221  lhs -= rhs;
222  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
223  }
224  else
225  {
226  // This is a complex eigenvalue, so:
227  // a.) It occurs in a complex-conjugate pair
228  // b.) the real part of the eigenvector is stored is VR(:,eigenval)
229  // c.) the imag part of the eigenvector is stored in VR(:,eigenval+1)
230  //
231  // Equating the real and imaginary parts of Ax=lambda*x leads to two sets
232  // of relations that must hold:
233  // 1.) Ax_r = lambda_r*x_r - lambda_i*x_i
234  // 2.) Ax_i = lambda_i*x_r + lambda_r*x_i
235  // which we can verify.
236 
237  // 1.)
238  DenseVector<Real> lhs(N), rhs(N);
239  for (unsigned i=0; i<N; ++i)
240  {
241  rhs(i) = lambda_real(eigenval) * VR(i, eigenval) - lambda_imag(eigenval) * VR(i, eigenval+1);
242  for (unsigned j=0; j<N; ++j)
243  lhs(i) += A(i, j) * VR(j, eigenval);
244  }
245 
246  lhs -= rhs;
247  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
248 
249  // 2.)
250  lhs.zero();
251  rhs.zero();
252  for (unsigned i=0; i<N; ++i)
253  {
254  rhs(i) = lambda_imag(eigenval) * VR(i, eigenval) + lambda_real(eigenval) * VR(i, eigenval+1);
255  for (unsigned j=0; j<N; ++j)
256  lhs(i) += A(i, j) * VR(j, eigenval+1);
257  }
258 
259  lhs -= rhs;
260  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0., /*actual=*/lhs.l2_norm(), std::sqrt(tol)*tol);
261 
262  // We'll skip the second member of the complex conjugate
263  // pair. If the first one worked, the second one should
264  // as well...
265  eigenval += 1;
266  }
267  }
268 
269  // Sort the results from Lapack *individually*.
270  std::sort(lambda_real.get_values().begin(), lambda_real.get_values().end());
271  std::sort(lambda_imag.get_values().begin(), lambda_imag.get_values().end());
272 
273  // Sort the true eigenvalues *individually*.
274  std::sort(true_lambda_real.begin(), true_lambda_real.end());
275  std::sort(true_lambda_imag.begin(), true_lambda_imag.end());
276 
277  // Compare the individually-sorted values.
278  for (unsigned i=0; i<lambda_real.size(); ++i)
279  {
280  // Note: I initially verified the results with TOLERANCE**2,
281  // but that turned out to be just a bit too tight for some of
282  // the test problems. I'm not sure what controls the accuracy
283  // of the eigenvalue computation in LAPACK, there is no way to
284  // set a tolerance in the LAPACKgeev_ interface.
285  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/true_lambda_real[i], /*actual=*/lambda_real(i), std::sqrt(tol)*tol);
286  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/true_lambda_imag[i], /*actual=*/lambda_imag(i), std::sqrt(tol)*tol);
287  }
288 #endif
289  }
290 
291  void testEVDreal()
292  {
293  LOG_UNIT_TEST;
294 
295  // This is an example from Matlab's gallery(3) which is a
296  // non-symmetric 3x3 matrix with eigen values lambda = 1, 2, 3.
297  DenseMatrix<Real> A(3, 3);
298  A(0,0) = -149; A(0,1) = -50; A(0,2) = -154;
299  A(1,0) = 537; A(1,1) = 180; A(1,2) = 546;
300  A(2,0) = -27; A(2,1) = -9; A(2,2) = -25;
301 
302  std::vector<Real> true_lambda_real(3);
303  true_lambda_real[0] = 1.;
304  true_lambda_real[1] = 2.;
305  true_lambda_real[2] = 3.;
306  std::vector<Real> true_lambda_imag(3); // all zero
307 
308  // call helper function to compute and verify results.
309  testEVD_helper(A, true_lambda_real, true_lambda_imag, TOLERANCE);
310  }
311 
313  {
314  LOG_UNIT_TEST;
315 
316  // This test is also from a Matlab example, and has complex eigenvalues.
317  // http://www.mathworks.com/help/matlab/math/eigenvalues.html?s_tid=gn_loc_drop
318  DenseMatrix<Real> A(3, 3);
319  A(0,0) = 0; A(0,1) = -6; A(0,2) = -1;
320  A(1,0) = 6; A(1,1) = 2; A(1,2) = -16;
321  A(2,0) = -5; A(2,1) = 20; A(2,2) = -10;
322 
323  std::vector<Real> true_lambda_real(3);
324  true_lambda_real[0] = -3.070950351248293;
325  true_lambda_real[1] = -2.464524824375853;
326  true_lambda_real[2] = -2.464524824375853;
327  std::vector<Real> true_lambda_imag(3);
328  true_lambda_imag[0] = 0.;
329  true_lambda_imag[1] = 17.60083096447099;
330  true_lambda_imag[2] = -17.60083096447099;
331 
332  // We're good up to double precision (due to the truncated
333  // literals above) or Real precision, whichever is worse
334  const Real tol = std::max(Real(1e-6), TOLERANCE);
335 
336  // call helper function to compute and verify results
337  testEVD_helper(A, true_lambda_real, true_lambda_imag, tol);
338  }
339 
341  {
342 #ifdef LIBMESH_USE_COMPLEX_NUMBERS
343  LOG_UNIT_TEST;
344 
345  DenseMatrix<Complex> A(3,3);
346 
347  A(0,0) = Complex(2.18904,4.44523e-18); A(0,1) = Complex(-3.20491,-0.136699); A(0,2) = Complex(0.716316,-0.964802);
348  A(1,0) = Complex(-3.20491,0.136699); A(1,1) = Complex(4.70076,-3.25261e-18); A(1,2) = Complex(-0.98849,1.45727);
349  A(2,0) = Complex(0.716316,0.964802); A(2,1) = Complex(-0.98849,-1.45727); A(2,2) = Complex(0.659629,-4.01155e-18);
350 
351  DenseVector<Real> sigma;
352  A.svd(sigma);
353 
354  DenseVector<Real> true_sigma(3);
355  true_sigma(0) = 7.54942516052;
356  true_sigma(1) = 3.17479511368e-06;
357  true_sigma(2) = 6.64680908281e-07;
358 
359  for (unsigned i=0; i<sigma.size(); ++i)
360  LIBMESH_ASSERT_FP_EQUAL(sigma(i), true_sigma(i), 1.e-10);
361 #endif
362  }
363 
365  {
366  LOG_UNIT_TEST;
367 
368  DenseMatrix<Number> A(4, 3);
369  A(0,0) = 1.0; A(0,1) = 2.0; A(0,2) = 3.0;
370  A(1,0) = 4.0; A(1,1) = 5.0; A(1,2) = 6.0;
371  A(2,0) = 7.0; A(2,1) = 8.0; A(2,2) = 9.0;
372  A(3,0) =10.0; A(3,1) =11.0; A(3,2) =12.0;
373 
374  DenseMatrix<Number> B(2, 2);
375  B(0,0) = 7.0; B(0,1) = 8.0;
376  B(1,0) =10.0; B(1,1) =11.0;
377 
378  DenseMatrix<Number> C = A.sub_matrix(2, 2, 0, 2);
379  CPPUNIT_ASSERT(B == C);
380  }
381 };
382 
383 // These tests require PETSc
384 #ifdef LIBMESH_HAVE_PETSC
386 #endif
virtual void zero() override final
Set every element in the vector to 0.
Definition: dense_vector.h:420
static constexpr Real TOLERANCE
std::vector< T > & get_values()
Definition: dense_vector.h:278
void evd_left_and_right(DenseVector< T > &lambda_real, DenseVector< T > &lambda_imag, DenseMatrix< T > &VL, DenseMatrix< T > &VR)
Compute the eigenvalues (both real and imaginary parts) as well as the left and right eigenvectors of...
unsigned int m() const
The libMesh namespace provides an interface to certain functionality in the library.
void outer_product(const DenseVector< T > &a, const DenseVector< T > &b)
Computes the outer (dyadic) product of two vectors and stores in (*this).
Definition: assembly.h:38
void svd(DenseVector< Real > &sigma)
Compute the singular value decomposition of the matrix.
CPPUNIT_TEST_SUITE_REGISTRATION(DenseMatrixTest)
void testEVD_helper(DenseMatrix< Real > &A, std::vector< Real > true_lambda_real, std::vector< Real > true_lambda_imag, Real tol)
std::complex< Real > Complex
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual unsigned int size() const override final
Definition: dense_vector.h:104
Defines a dense vector for use in Finite Element-type computations.
Definition: dof_map.h:74
unsigned int n() const
DenseMatrix sub_matrix(unsigned int row_id, unsigned int row_size, unsigned int col_id, unsigned int col_size) const
Get submatrix with the smallest row and column indices and the submatrix size.
Definition: dense_matrix.h:922