DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_Linear.cpp
Go to the documentation of this file.
1/**
2 * @file test_Linear.cpp
3 * @brief Unit tests for GMRES and PCG iterative linear solvers in Solver/Linear.hpp.
4 *
5 * Tests solve Ax=b for small dense systems where the exact solution is known.
6 * Uses a thin Vec wrapper around Eigen::VectorXd.
7 */
8
9#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
10#include "doctest.h"
11
12#include "Solver/Linear.hpp"
13#include <Eigen/Dense>
14#include <iostream>
15#include <iomanip>
16
17using namespace DNDS;
18
19// ===================================================================
20// Wrapper for Eigen::VectorXd satisfying TDATA interface
21// ===================================================================
22struct DVec
23{
24 Eigen::VectorXd data;
25 DVec() = default;
26 DVec(int n) : data(Eigen::VectorXd::Zero(n)) {}
27 DVec &operator=(const DVec &) = default;
28 void operator+=(const DVec &o) { data += o.data; }
29 void operator*=(double s) { data *= s; }
30 void operator*=(const DVec &o) { data.array() *= o.data.array(); }
31 void setConstant(double s) { data.setConstant(s); }
32 void addTo(const DVec &o, double s) { data += o.data * s; }
33};
34
35static auto dvinit(int n)
36{
37 return [n](DVec &v) { v = DVec(n); };
38}
39
40// ===================================================================
41// GMRES: solve 3x3 SPD system
42// ===================================================================
43
44TEST_CASE("GMRES: solve 3x3 SPD system exactly")
45{
46 const int n = 3;
47 Eigen::Matrix3d A;
48 A << 4, 1, 0,
49 1, 3, 1,
50 0, 1, 2;
51 Eigen::Vector3d b_exact(1, 2, 3);
52 Eigen::Vector3d x_exact = A.ldlt().solve(b_exact);
53
55
56 DVec b(n), x(n);
57 b.data = b_exact;
58 x.data.setZero();
59
60 auto FA = [&](DVec &xin, DVec &Ax) { Ax.data = A * xin.data; };
61 auto FML = [&](DVec &xin, DVec &MLx) { MLx = xin; }; // no preconditioner
62 auto fDot = [](DVec &a, DVec &b) -> double { return a.data.dot(b.data); };
63 auto fStop = [](int, double res, double resB) -> bool { return res < 1e-14 * resB; };
64
65 bool converged = gmres.solve(FA, FML, fDot, b, x, 10, fStop);
66 CHECK(converged);
67
68 double err = (x.data - x_exact).norm();
69 std::cout << "[GMRES/3x3] err=" << std::scientific << err << std::endl;
70 CHECK(err < 1e-12);
71}
72
73TEST_CASE("GMRES: solve 10x10 random SPD system")
74{
75 const int n = 10;
76 Eigen::MatrixXd R = Eigen::MatrixXd::Random(n, n);
77 Eigen::MatrixXd A = R.transpose() * R + n * Eigen::MatrixXd::Identity(n, n); // SPD
78 Eigen::VectorXd b_exact = Eigen::VectorXd::Ones(n);
79 Eigen::VectorXd x_exact = A.ldlt().solve(b_exact);
80
82
83 DVec b(n), x(n);
84 b.data = b_exact;
85 x.data.setZero();
86
87 auto FA = [&](DVec &xin, DVec &Ax) { Ax.data = A * xin.data; };
88 auto FML = [&](DVec &xin, DVec &MLx) { MLx = xin; };
89 auto fDot = [](DVec &a, DVec &b) -> double { return a.data.dot(b.data); };
90 auto fStop = [](int, double res, double resB) -> bool { return res < 1e-12 * resB; };
91
92 bool converged = gmres.solve(FA, FML, fDot, b, x, 20, fStop);
93 CHECK(converged);
94
95 double err = (x.data - x_exact).norm();
96 std::cout << "[GMRES/10x10] err=" << std::scientific << err << std::endl;
97 CHECK(err < 1e-10);
98}
99
100TEST_CASE("GMRES: diagonal preconditioner improves convergence")
101{
102 const int n = 10;
103 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(n, n);
104 for (int i = 0; i < n; i++)
105 A(i, i) = (i + 1) * 10.0; // condition number = 100
106 A(0, 1) = 1.0;
107 A(1, 0) = 1.0;
108 Eigen::VectorXd b_exact = Eigen::VectorXd::Ones(n);
109 Eigen::VectorXd x_exact = A.fullPivLu().solve(b_exact);
110
111 // Without preconditioner
112 Linear::GMRES_LeftPreconditioned<DVec> gmres1(3, dvinit(n));
113 DVec b1(n), x1(n);
114 b1.data = b_exact;
115 int restarts_npc = 0;
116 auto fStop1 = [&](int iRestart, double res, double resB) -> bool {
117 restarts_npc = iRestart;
118 return res < 1e-12 * resB;
119 };
120 gmres1.solve(
121 [&](DVec &xin, DVec &Ax) { Ax.data = A * xin.data; },
122 [](DVec &xin, DVec &MLx) { MLx = xin; },
123 [](DVec &a, DVec &b) -> double { return a.data.dot(b.data); },
124 b1, x1, 50, fStop1);
125
126 // With diagonal preconditioner
127 Linear::GMRES_LeftPreconditioned<DVec> gmres2(3, dvinit(n));
128 DVec b2(n), x2(n);
129 b2.data = b_exact;
130 int restarts_pc = 0;
131 auto fStop2 = [&](int iRestart, double res, double resB) -> bool {
132 restarts_pc = iRestart;
133 return res < 1e-12 * resB;
134 };
135 Eigen::VectorXd diagInv = A.diagonal().cwiseInverse();
136 gmres2.solve(
137 [&](DVec &xin, DVec &Ax) { Ax.data = A * xin.data; },
138 [&](DVec &xin, DVec &MLx) { MLx.data = diagInv.asDiagonal() * xin.data; },
139 [](DVec &a, DVec &b) -> double { return a.data.dot(b.data); },
140 b2, x2, 50, fStop2);
141
142 double err2 = (x2.data - x_exact).norm();
143 CHECK(err2 < 1e-10);
144 // Preconditioned should converge in fewer restarts (or same)
145 CHECK(restarts_pc <= restarts_npc);
146}
147
148// ===================================================================
149// PCG: solve SPD system
150// ===================================================================
151
152TEST_CASE("PCG: solve 5x5 SPD system")
153{
154 const int n = 5;
155 Eigen::MatrixXd R = Eigen::MatrixXd::Random(n, n);
156 R << 1, 0.1, 0, 0, 0,
157 0.1, 2, 0.2, 0, 0,
158 0, 0.2, 3, 0.3, 0,
159 0, 0, 0.3, 4, 0.1,
160 0, 0, 0, 0.1, 5;
161 Eigen::MatrixXd A = (R + R.transpose()) / 2.0; // symmetric
162 Eigen::VectorXd b_exact = Eigen::VectorXd::Ones(n);
163 Eigen::VectorXd x_exact = A.ldlt().solve(b_exact);
164
165 using TScalar = double;
167
168 DVec x(n);
169 x.data.setZero();
170
171 auto FA = [&](DVec &p, DVec &Ap) { Ap.data = A * p.data; };
172 auto FM = [&](DVec &z, DVec &r) { r = z; }; // no preconditioning
173 auto FResPrec = [&](DVec &xin, DVec &z) { z.data = b_exact - A * xin.data; };
174 auto fDot = [](DVec &a, DVec &b) -> TScalar { return a.data.dot(b.data); };
175 auto fStop = [](int, TScalar zrDot, TScalar) -> bool { return std::abs(zrDot) < 1e-24; };
176
177 bool converged = pcg.solve(FA, FM, FResPrec, fDot, x, 100, fStop);
178 CHECK(converged);
179
180 double err = (x.data - x_exact).norm();
181 std::cout << "[PCG/5x5] err=" << std::scientific << err << std::endl;
182 CHECK(err < 1e-10);
183}
bool solve(TFA &&FA, TFML &&FML, TFDot &&fDot, TDATA &b, TDATA &x, uint32_t nRestart, TFstop &&FStop)
Definition Linear.hpp:45
bool solve(TFA &&FA, TFM &&FM, TFResPrec &&FResPrec, TFDot &&fDot, TDATA &x, uint32_t niter, TFstop &&FStop)
Definition Linear.hpp:160
the host side operators are provided as implemented
void addTo(const DVec &o, double s)
DVec()=default
Eigen::VectorXd data
DVec(int n)
void operator*=(double s)
DVec & operator=(const DVec &)=default
void setConstant(double s)
void operator+=(const DVec &o)
void operator*=(const DVec &o)
Eigen::Matrix< real, 5, 1 > v
tVec Ax(NCells)
real err
tVec r(NCells)
tVec z(NCells)
tVec x(NCells)
tVec b(NCells)
CHECK(result.size()==3)
auto res
Definition test_ODE.cpp:196
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")
Eigen::Vector3d n(1.0, 0.0, 0.0)