The inverse iteration example.
This example depends on simple-solver, .
Introduction
This example shows how components available in Ginkgo can be used to implement higher-level numerical methods. The method used here will be the shifted inverse iteration method for eigenvalue computation which find the eigenvalue and eigenvector of A closest to z, for some scalar z. The method requires repeatedly solving the shifted linear system (A - zI)x = b, as well as performing matrix-vector products with the matrix A
. Here is the complete pseudocode of the method:
x_0 = initial guess
for i = 0 .. max_iterations:
solve (A - zI) y_i = x_i for y_i+1
x_(i+1) = y_i / || y_i || # compute next eigenvector approximation
g_(i+1) = x_(i+1)^* A x_(i+1) # approximate eigenvalue (Rayleigh quotient)
if ||A x_(i+1) - g_(i+1)x_(i+1)|| < tol * g_(i+1): # check convergence
break
About the example
The commented program
#include <ginkgo/ginkgo.hpp>
#include <cmath>
#include <complex>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
int main(int argc, char *argv[])
{
Some shortcuts
using precision = std::complex<double>;
using real_precision = double;
using std::abs;
using std::sqrt;
Print version information
std::cout << std::scientific << std::setprecision(8) << std::showpos;
Figure out where to run the code
std::shared_ptr<gko::Executor> exec;
if (argc == 1 || std::string(argv[1]) == "reference") {
exec = gko::ReferenceExecutor::create();
} else if (argc == 2 && std::string(argv[1]) == "omp") {
} else if (argc == 2 && std::string(argv[1]) == "cuda" &&
} else {
std::cerr << "Usage: " << argv[0] << " [executor]" << std::endl;
std::exit(-1);
}
auto this_exec = exec->get_master();
linear system solver parameters
auto system_max_iterations = 100u;
auto system_residual_goal = real_precision{1e-16};
eigensolver parameters
auto max_iterations = 20u;
auto residual_goal = real_precision{1e-8};
auto z = precision{20.0, 2.0};
Read data
auto A =
share(gko::read<mtx>(std::ifstream(
"data/A.mtx"), exec));
Generate shifted matrix A - zI
- we avoid duplicating memory by not storing both A and A - zI, but compute A - zI on the fly by using Ginkgo's utilities for creating linear combinations of operators
auto one =
share(gko::initialize<vec>({precision{1.0}}, exec));
auto neg_one =
share(gko::initialize<vec>({-precision{1.0}}, exec));
auto neg_z = gko::initialize<vec>({-z}, exec);
one, A, gko::initialize<vec>({-z}, exec),
Generate solver operator (A - zI)^-1
auto solver =
solver_type::build()
.with_criteria(gko::stop::Iteration::build()
.with_max_iters(system_max_iterations)
.on(exec),
.with_reduction_factor(system_residual_goal)
.on(exec))
.on(exec)
->generate(system_matrix);
inverse iterations
start with guess [1, 1, ..., 1]
auto x = [&] {
auto work = vec::create(this_exec,
gko::dim<2>{A->get_size()[0], 1});
const auto n = work->get_size()[0];
for (int i = 0; i < n; ++i) {
work->get_values()[i] = precision{1.0} / sqrt(n);
}
return clone(exec, work);
}();
auto inv_norm =
clone(this_exec, one);
for (auto i = 0u; i < max_iterations; ++i) {
std::cout << "{ ";
(A - zI)y = x
x->compute_norm2(
lend(norm));
std::cout << "\"system_residual\": "
<<
clone(this_exec, norm)->get_values()[0] <<
", ";
x = y / || y ||
x->compute_norm2(
lend(norm));
inv_norm->get_values()[0] =
precision{1.0} /
clone(this_exec, norm)->get_values()[0];
g = x^* A x
auto g_val =
clone(this_exec, g)->get_values()[0];
std::cout << "\"eigenvalue\": " << g_val << ", ";
||Ax - gx|| < tol * g
auto v = gko::initialize<vec>({-g_val}, exec);
tmp->compute_norm2(
lend(norm));
auto res_val =
clone(exec->get_master(), norm)->get_values()[0];
std::cout << "\"residual\": " << res_val / g_val << " }," << std::endl;
if (
abs(res_val) < residual_goal *
abs(g_val)) {
break;
}
}
}
Results
This is the expected output:
{ "system_residual": (+1.59066966e-14,+0.00000000e+00), "eigenvalue": (+2.03741410e+01,-5.42101086e-18), "residual": (+2.92231055e-01,+7.77548230e-20) },
{ "system_residual": (+6.38877157e-15,+0.00000000e+00), "eigenvalue": (+1.94878474e+01,-4.34534678e-16), "residual": (+7.94370276e-02,+1.77126506e-18) },
{ "system_residual": (+6.79215294e-15,+0.00000000e+00), "eigenvalue": (+1.93282121e+01,-3.68988781e-16), "residual": (+4.11149623e-02,+7.84912734e-19) },
{ "system_residual": (+3.54015578e-15,+0.00000000e+00), "eigenvalue": (+1.92638912e+01,+2.03949917e-16), "residual": (+2.34717040e-02,-2.48498708e-19) },
{ "system_residual": (+2.12400044e-15,+0.00000000e+00), "eigenvalue": (+1.92409166e+01,-7.59991100e-16), "residual": (+1.34709547e-02,+5.32085134e-19) },
{ "system_residual": (+3.29202859e-15,+0.00000000e+00), "eigenvalue": (+1.92331106e+01,+2.90110055e-15), "residual": (+7.72060707e-03,-1.16456760e-18) },
{ "system_residual": (+3.99088304e-15,+0.00000000e+00), "eigenvalue": (+1.92305014e+01,-3.21058733e-16), "residual": (+4.42106625e-03,+7.38109682e-20) },
{ "system_residual": (+2.02648035e-15,+0.00000000e+00), "eigenvalue": (+1.92296339e+01,+5.11222288e-16), "residual": (+2.53081312e-03,-6.72819919e-20) },
{ "system_residual": (+1.83840397e-15,+0.00000000e+00), "eigenvalue": (+1.92293461e+01,+3.51208924e-16), "residual": (+1.44862114e-03,-2.64579289e-20) },
{ "system_residual": (+1.60253167e-15,+0.00000000e+00), "eigenvalue": (+1.92292506e+01,-2.02284978e-15), "residual": (+8.29183451e-04,+8.72271932e-20) },
{ "system_residual": (+1.96758490e-15,+0.00000000e+00), "eigenvalue": (+1.92292190e+01,+8.90545453e-16), "residual": (+4.74636702e-04,-2.19814209e-20) },
{ "system_residual": (+1.53327380e-14,+0.00000000e+00), "eigenvalue": (+1.92292085e+01,-8.25871947e-17), "residual": (+2.71701077e-04,+1.16692425e-21) },
{ "system_residual": (+3.42985865e-15,+0.00000000e+00), "eigenvalue": (+1.92292051e+01,+1.63122796e-16), "residual": (+1.55539937e-04,-1.31945701e-21) },
{ "system_residual": (+3.30861071e-11,+0.00000000e+00), "eigenvalue": (+1.92292039e+01,-5.49102025e-16), "residual": (+8.90457139e-05,+2.54275643e-21) },
{ "system_residual": (+7.11155374e-14,+0.00000000e+00), "eigenvalue": (+1.92292035e+01,+1.16689376e-15), "residual": (+5.09805252e-05,-3.09367244e-21) },
{ "system_residual": (+2.68204494e-15,+0.00000000e+00), "eigenvalue": (+1.92292034e+01,-4.07084034e-17), "residual": (+2.91887365e-05,+6.17928281e-23) },
{ "system_residual": (+5.78377594e-13,+0.00000000e+00), "eigenvalue": (+1.92292034e+01,-3.38561848e-17), "residual": (+1.67126561e-05,+2.94253882e-23) },
{ "system_residual": (+6.26422040e-12,+0.00000000e+00), "eigenvalue": (+1.92292034e+01,-3.14429218e-18), "residual": (+9.56961199e-06,+1.56478953e-24) },
{ "system_residual": (+1.41104829e-12,+0.00000000e+00), "eigenvalue": (+1.92292033e+01,-6.54656730e-16), "residual": (+5.47975753e-06,+1.86557918e-22) },
{ "system_residual": (+1.97926842e-10,+0.00000000e+00), "eigenvalue": (+1.92292033e+01,+1.58008702e-16), "residual": (+3.13794996e-06,-2.57849164e-23) },
Comments about programming and debugging
The plain program
#include <ginkgo/ginkgo.hpp>
#include <cmath>
#include <complex>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
int main(int argc, char *argv[])
{
using precision = std::complex<double>;
using real_precision = double;
using std::abs;
using std::sqrt;
std::cout << std::scientific << std::setprecision(8) << std::showpos;
std::shared_ptr<gko::Executor> exec;
if (argc == 1 || std::string(argv[1]) == "reference") {
exec = gko::ReferenceExecutor::create();
} else if (argc == 2 && std::string(argv[1]) == "omp") {
} else if (argc == 2 && std::string(argv[1]) == "cuda" &&
} else {
std::cerr << "Usage: " << argv[0] << " [executor]" << std::endl;
std::exit(-1);
}
auto this_exec = exec->get_master();
auto system_max_iterations = 100u;
auto system_residual_goal = real_precision{1e-16};
auto max_iterations = 20u;
auto residual_goal = real_precision{1e-8};
auto z = precision{20.0, 2.0};
auto A =
share(gko::read<mtx>(std::ifstream(
"data/A.mtx"), exec));
auto one =
share(gko::initialize<vec>({precision{1.0}}, exec));
auto neg_one =
share(gko::initialize<vec>({-precision{1.0}}, exec));
auto neg_z = gko::initialize<vec>({-z}, exec);
one, A, gko::initialize<vec>({-z}, exec),
auto solver =
solver_type::build()
.with_criteria(gko::stop::Iteration::build()
.with_max_iters(system_max_iterations)
.on(exec),
.with_reduction_factor(system_residual_goal)
.on(exec))
.on(exec)
->generate(system_matrix);
auto x = [&] {
auto work = vec::create(this_exec,
gko::dim<2>{A->get_size()[0], 1});
const auto n = work->get_size()[0];
for (int i = 0; i < n; ++i) {
work->get_values()[i] = precision{1.0} / sqrt(n);
}
return clone(exec, work);
}();
auto inv_norm =
clone(this_exec, one);
for (auto i = 0u; i < max_iterations; ++i) {
std::cout << "{ ";
x->compute_norm2(
lend(norm));
std::cout << "\"system_residual\": "
<<
clone(this_exec, norm)->get_values()[0] <<
", ";
x->compute_norm2(
lend(norm));
inv_norm->get_values()[0] =
precision{1.0} /
clone(this_exec, norm)->get_values()[0];
auto g_val =
clone(this_exec, g)->get_values()[0];
std::cout << "\"eigenvalue\": " << g_val << ", ";
auto v = gko::initialize<vec>({-g_val}, exec);
tmp->compute_norm2(
lend(norm));
auto res_val =
clone(exec->get_master(), norm)->get_values()[0];
std::cout << "\"residual\": " << res_val / g_val << " }," << std::endl;
if (
abs(res_val) < residual_goal *
abs(g_val)) {
break;
}
}
}