MyraMath
cLDLTSolver


Source: tests/dense/ZLDLTSolver.cpp

1 // ========================================================================= //
2 // This file is part of MyraMath, copyright (c) 2014-2019 by Ryan A Chilton //
3 // and distributed by MyraCore, LLC. See LICENSE.txt for license terms. //
4 // ========================================================================= //
5 
11 // Containers.
13 #include <myramath/dense/Matrix.h>
16 
17 // Algorithms.
19 #include <myramath/dense/gemm.h>
20 #include <myramath/dense/syrk.h>
25 #include <myramath/dense/inverse.h>
26 
27 // Solver class under test.
29 
30 // Serialization testing.
32 
33 // Reporting.
34 #include <tests/myratest.h>
35 
36 using namespace myra;
37 
38 namespace {
39 
40 template<class Precision> void test(int I, int J, Precision tolerance)
41  {
42  typedef std::complex<Precision> Number;
43  myra::out() << typestring<Number>() << std::endl;
44  // Construct random hermitian matrix, A = G+G'
45  auto G = Matrix<Number>::random(I,I);
46  auto A = G + transpose(G);
47  // Restrict A to lower triangle, generate LDL' solver for it.
49  typedef ZLDLTSolver<Precision> Solver;
50  Solver solver(L);
51  // Total of 8 solve() variations to validiate, side['L',R'] x op['N','T','H','C']
52  // Solve A*X=B
53  {
54  auto X = Matrix<Number>::random(I,J);
55  auto B = A*X;
56  solver.solve(B,'L','N');
57  Precision error = frobenius(B-X)/frobenius(X);
58  myra::out() << " |inv(A)*B-X| = " << error << std::endl;
59  REQUIRE(error < tolerance);
60  }
61  // Solve transpose(A)*X=B
62  {
63  auto X = Matrix<Number>::random(I,J);
64  auto B = transpose(A)*X;
65  solver.solve(B,'L','T');
66  Precision error = frobenius(B-X)/frobenius(X);
67  myra::out() << " |inv(A^T)*B-X| = " << error << std::endl;
68  REQUIRE(error < tolerance);
69  }
70  // Solve hermitian(A)*X=B
71  {
72  auto X = Matrix<Number>::random(I,J);
73  auto B = hermitian(A)*X;
74  solver.solve(B,'L','H');
75  Precision error = frobenius(B-X)/frobenius(X);
76  myra::out() << " |inv(A^H)*B-X| = " << error << std::endl;
77  REQUIRE(error < tolerance);
78  }
79  // Solve conjugate(A)*X=B
80  {
81  auto X = Matrix<Number>::random(I,J);
82  auto B = conjugate(A)*X;
83  solver.solve(B,'L','C');
84  Precision error = frobenius(B-X)/frobenius(X);
85  myra::out() << " |inv(A^C)*B-X| = " << error << std::endl;
86  REQUIRE(error < tolerance);
87  }
88  // Solve X*A=B
89  {
90  auto X = Matrix<Number>::random(J,I);
91  auto B = X*A;
92  solver.solve(B,'R','N');
93  Precision error = frobenius(B-X)/frobenius(X);
94  myra::out() << " |B*inv(A)-X| = " << error << std::endl;
95  REQUIRE(error < tolerance);
96  }
97  // Solve X*transpose(A)=B
98  {
99  auto X = Matrix<Number>::random(J,I);
100  auto B = X*transpose(A);
101  solver.solve(B,'R','T');
102  Precision error = frobenius(B-X)/frobenius(X);
103  myra::out() << " |B*inv(A^T)-X| = " << error << std::endl;
104  REQUIRE(error < tolerance);
105  }
106  // Solve X*hermitian(A)=B
107  {
108  auto X = Matrix<Number>::random(J,I);
109  auto B = X*hermitian(A);
110  solver.solve(B,'R','H');
111  Precision error = frobenius(B-X)/frobenius(X);
112  myra::out() << " |B*inv(A^H)-X| = " << error << std::endl;
113  REQUIRE(error < tolerance);
114  }
115  // Solve X*conjugate(A)=B
116  {
117  auto X = Matrix<Number>::random(J,I);
118  auto B = X*conjugate(A);
119  solver.solve(B,'R','C');
120  Precision error = frobenius(B-X)/frobenius(X);
121  myra::out() << " |B*inv(A^C)-X| = " << error << std::endl;
122  REQUIRE(error < tolerance);
123  }
124  // Generate symmetric schur complement.
125  {
126  // ZLDLTSolver is well suited for computing symmetric schur complements,
127  // S = Bt*inv(A)*B. Try it out and compare to a reference calculation.
128  // (In the case of sparse A/B, this approach is dramatically more efficient)
129  // Reference answer.
130  auto B = Matrix<Number>::random(I,J);
131  LowerMatrix<Number> S1 (transpose(B)*inverse(A)*B);
132  // Structure exploiting answer, approximately half the work and inplace on B.
133  Number one(1);
134  int n_plus = solver.inertia().first;
135  int n_minus = solver.inertia().second;
136  LowerMatrix<Number> S2(J);
137  solver.solveL(B,'L','N');
138  syrk_inplace(S2,B.top(n_plus),'T',one,one);
139  syrk_inplace(S2,B.bottom(n_minus),'T',-one,one);
140  // Compare/report.
141  Precision error = frobenius(S1-S2) / frobenius(S1);
142  myra::out() << " |B'*inv(A)*B - (L\\B)'*(L\\B)| = " << error << std::endl;
143  REQUIRE(error < tolerance);
144  }
145  // Generate unsymmetric schur complement.
146  {
147  // ZLTDTSolver can also do unsymmetric schur complements, S = Ct*inv(A)*B.
148  // Not especially efficient, but still tricky so it's demonstrated here.
149  // (In the case of sparse A/B/C, this approach is dramatically more efficient)
150  // Reference answer.
151  auto B = Matrix<Number>::random(I,J);
152  auto C = Matrix<Number>::random(I,J+3);
153  Matrix<Number> S1 = (transpose(C)*inverse(A)*B);
154  // Structure exploiting answer.
155  Number one(1);
156  int n_plus = solver.inertia().first;
157  int n_minus = solver.inertia().second;
158  Matrix<Number> S2(J+3,J);
159  solver.solveL(B,'L','N');
160  solver.solveL(C,'L','N');
161  gemm_inplace(S2,C.top(n_plus),'T',B.top(n_plus),'N',one,one);
162  gemm_inplace(S2,C.bottom(n_minus),'T',B.bottom(n_minus),'N',-one,one);
163  // Compare/report.
164  Precision error = frobenius(S1-S2) / frobenius(S1);
165  myra::out() << " |C'*inv(A)*B - (L\\C)'*(L\\B)| = " << error << std::endl;
166  REQUIRE(error < tolerance);
167  }
168  // Roundtrip solver to disk, then solve A*X=B again.
169  {
170  VectorStream inout;
171  solver.write(inout);
172  Solver saved(inout);
173  auto X = Matrix<Number>::random(I,J);
174  auto B = A*X;
175  saved.solve(B,'L','N');
176  Precision error = frobenius(B-X)/frobenius(X);
177  myra::out() << " |inv(A)*B-X| (saved)= " << error << std::endl;
178  REQUIRE(error < tolerance);
179  }
180  }
181 
182 template<class Precision> void test3(Precision tolerance)
183  {
184  typedef std::complex<Precision> Number;
185  myra::out() << typestring<Number>() << std::endl;
186  // Construct A = [0 1 0; 1 0 0; 0 0 1], a problematic example.
187  auto A = Matrix<Number>::zeros(3,3);
188  Precision one(1);
189  Precision two(2);
190  Precision pi = std::acos(-one);
191  Precision phase = two*pi*random<Precision>();
192  Number phasor(std::cos(phase),std::sin(phase));
193  A(1,0) = phasor;
194  A(0,1) = phasor;
195  A(2,2) = one;
196  // Restrict A to lower triangle, generate LDL' solver for it.
197  LowerMatrix<Number> L(A);
198  typedef ZLDLTSolver<Precision> Solver;
199  Solver solver(L);
200  // Solve A*X=B
201  auto X = Matrix<Number>::random(3,2);
202  auto B = A*X;
203  solver.solve(B,'L','N');
204  Precision error = frobenius(B-X)/frobenius(X);
205  myra::out() << " |inv(A)*B-X| = " << error << std::endl;
206  REQUIRE(error < tolerance);
207  }
208 
209 } // namespace
210 
211 ADD_TEST("cLDLTSolver","[dense][solver]")
212  { test<float>(43,14,1.0e-3f); }
213 
214 ADD_TEST("zLDLTSolver","[dense][solver]")
215  { test<double>(82,45,1.0e-10); }
216 
217 ADD_TEST("cLDLTSolver3","[dense][solver]")
218  { test3<float>(1.0e-3f); }
219 
220 ADD_TEST("zLDLTSolver3","[dense][solver]")
221  { test3<double>(1.0e-10); }
Returns a conjugated copy of a Matrix or Vector. Or, conjugate one inplace.
Factors A = L*D*Lt, where D is a "sign matrix" of the form [I 0; 0 -I]. Presents solve methods...
Definition: ZLDLTSolver.h:30
Tabulates an IxJ matrix. Allows random access, has column major layout to be compatible with BLAS/LAP...
Definition: bdsqr.h:20
static Matrix< Number > zeros(int I, int J)
Generates a zeros Matrix of specified size.
Definition: Matrix.cpp:357
Routines for computing Frobenius norms of various algebraic containers.
static Matrix< Number > random(int I, int J)
Generates a random Matrix of specified size.
Definition: Matrix.cpp:353
CMatrixRange< Number > bottom(int i) const
Returns a MatrixRange over the i bottommost rows, this(I-i:I,:)
Definition: Matrix.cpp:225
Wraps a std::vector<char>, presents it as both an InputStream and OutputStream. Useful for hygienic u...
Definition: VectorStream.h:22
Range construct for a lower triangular matrix stored in rectangular packed format.
Definition: syntax.dox:1
Specialized container for a lower triangular matrix, O(N^2/2) storage. Used by symmetry exploiting ma...
Returns a transposed copy of a Matrix. The inplace version only works on a square operand...
Various utility functions/classes related to scalar Number types.
A solver suitable for a complex symmetric Matrix (indefinite is fine). Encapsulates sytrf() ...
A stream that serialize/deserializes to std::vector<char> buffer.
General purpose dense matrix container, O(i*j) storage.
Overwrites a LowerMatrix, DiagonalMatrix, or square Matrix with its own inverse. Or, returns it as a copy.
Returns a hermitian copy of a Matrix. The inplace version only works on a square operand.
Simplistic random number functions.
Stores a lower triangular matrix in rectangular packed format.
Definition: conjugate.h:22
Routines for symmetric rank-k updates, a specialized form of Matrix*Matrix multiplication.
Variety of routines all for dense Matrix*Matrix multiplies. Delegates to the BLAS.
CMatrixRange< Number > top(int i) const
Returns a MatrixRange over the i topmost rows, this(0:i,:)
Definition: Matrix.cpp:207


Results: [PASS]

std::complex<float>
|inv(A)*B-X| = 1.07733e-05
|inv(A^T)*B-X| = 1.40538e-05
|inv(A^H)*B-X| = 1.10574e-05
|inv(A^C)*B-X| = 1.15433e-05
|B*inv(A)-X| = 1.14482e-05
|B*inv(A^T)-X| = 1.20242e-05
|B*inv(A^H)-X| = 1.19082e-05
|B*inv(A^C)-X| = 1.24889e-05
|B'*inv(A)*B - (L\B)'*(L\B)| = 1.22627e-05
|C'*inv(A)*B - (L\C)'*(L\B)| = 1.20489e-05
|inv(A)*B-X| (saved)= 1.15767e-05


Go back to Summary of /test programs.