The file config solver example..
This example depends on simple-solver.
This example shows how to use file to configure solver.
In this example, we first read in a matrix from a file. Then we read the config file to configure the solver. The example application reports the generation and run time of the solver.
The commented program
const std::string matrix_path = argc >= 4 ? argv[3] : "data/A.mtx";
Figure out where to run the code
std::map<std::string, std::function<std::shared_ptr<gko::Executor>()>>
exec_map{
{"cuda",
[] {
}},
{"hip",
[] {
}},
{"dpcpp",
[] {
0, gko::ReferenceExecutor::create());
}},
{"reference", [] { return gko::ReferenceExecutor::create(); }}};
executor where Ginkgo will perform the computation
const auto exec = exec_map.at(executor_string)();
Read data
auto A =
share(gko::read<mtx>(std::ifstream(matrix_path), exec));
Create RHS as 1 and initial guess as 0
auto host_x = vec::create(exec->get_master(),
gko::dim<2>(size, 1));
auto host_b = vec::create(exec->get_master(),
gko::dim<2>(size, 1));
for (auto i = 0; i < size; i++) {
host_x->at(i, 0) = 0.;
host_b->at(i, 0) = 1.;
}
auto x = vec::create(exec);
auto b = vec::create(exec);
x->copy_from(host_x);
b->copy_from(host_b);
Calculate initial residual by overwriting b
auto one = gko::initialize<vec>({1.0}, exec);
auto neg_one = gko::initialize<vec>({-1.0}, exec);
auto initres = gko::initialize<vec>({0.0}, exec);
A->apply(one, x, neg_one, b);
b->compute_norm2(initres);
Copy b again
Read the json config file to configure the ginkgo solver. The following files, which are mapped to corresponding examples, are available cg.json: simple-solver blockjacobi-cg.json: preconditioned-solver ir.json: iterative-refinement parilu.json: ilu-preconditioned-solver (by using factoization parameter directly) pgm-multigrid-cg.json: multigrid-preconditioned-solver (set min_coarse_rows additionally due to this small example matrix) mixed-pgm-multigrid-cg.json: mixed-multigrid-preconditioned-solver (assuming there are always more than one level)
auto config = gko::ext::config::parse_json_file(configfile);
Create the registry, which allows passing the existing data into config This example does not use existing data.
Create the default type descriptor, which gives the default common type (value/index) for solver generation. If the solver does not specify value type, the solver will use these types.
auto td = gko::config::make_type_descriptor<ValueType, IndexType>();
generate the linopfactory on the given executors
auto solver_gen = gko::config::parse(config, reg, td).on(exec);
Create solver
const auto gen_tic = std::chrono::steady_clock::now();
auto solver = solver_gen->generate(A);
exec->synchronize();
const auto gen_toc = std::chrono::steady_clock::now();
const auto gen_time = std::chrono::duration_cast<std::chrono::milliseconds>(
gen_toc - gen_tic);
Add logger
std::shared_ptr<const gko::log::Convergence<ValueType>> logger =
Solve system
exec->synchronize();
const auto tic = std::chrono::steady_clock::now();
exec->synchronize();
const auto toc = std::chrono::steady_clock::now();
const auto time =
std::chrono::duration_cast<std::chrono::milliseconds>(toc - tic);
Print out the solver config
std::cout << "Config file: " << configfile << std::endl;
std::ifstream f(configfile);
std::cout << f.rdbuf() << std::endl;
Calculate residual
auto res = gko::as<vec>(logger->get_residual_norm());
std::cout << "Initial residual norm sqrt(r^T r): \n";
write(std::cout, initres);
std::cout << "Final residual norm sqrt(r^T r): \n";
Print solver statistics
std::cout << "Solver iteration count: " << logger->get_num_iterations()
<< std::endl;
std::cout << "Solver generation time [ms]: "
<< static_cast<double>(gen_time.count()) << std::endl;
std::cout << "Solver execution time [ms]: "
<< static_cast<double>(time.count()) << std::endl;
std::cout << "Solver execution time per iteration[ms]: "
<< static_cast<double>(time.count()) /
logger->get_num_iterations()
<< std::endl;
}
Results
This is the expected output:
Config file: config/cg.json
{
"type": "solver::Cg",
"criteria": [
{
"type": "Iteration",
"max_iters": 20
},
{
"type": "ResidualNorm",
"reduction_factor": 1e-7
}
]
}
Initial residual norm sqrt(r^T r):
1 1
25.9808
Final residual norm sqrt(r^T r):
1 1
58.79
Solver iteration count: 20
Solver generation time [ms]: 0.065244
Solver execution time [ms]: 0.793764
Solver execution time per iteration[ms]: 0.0396882
Comments about programming and debugging
The plain program
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <ginkgo/ginkgo.hpp>
#include <ginkgo/extensions/config/json_config.hpp>
int main(int argc, char* argv[])
{
using ValueType = double;
using IndexType = int;
std::cout << argv[0] << " executor configfile matrix" << std::endl;
const auto executor_string = argc >= 2 ? argv[1] : "reference";
const auto configfile = argc >= 3 ? argv[2] : "config/cg.json";
const std::string matrix_path = argc >= 4 ? argv[3] : "data/A.mtx";
std::map<std::string, std::function<std::shared_ptr<gko::Executor>()>>
exec_map{
{"cuda",
[] {
}},
{"hip",
[] {
}},
{"dpcpp",
[] {
0, gko::ReferenceExecutor::create());
}},
{"reference", [] { return gko::ReferenceExecutor::create(); }}};
const auto exec = exec_map.at(executor_string)();
auto A =
share(gko::read<mtx>(std::ifstream(matrix_path), exec));
auto host_x = vec::create(exec->get_master(),
gko::dim<2>(size, 1));
auto host_b = vec::create(exec->get_master(),
gko::dim<2>(size, 1));
for (auto i = 0; i < size; i++) {
host_x->at(i, 0) = 0.;
host_b->at(i, 0) = 1.;
}
auto x = vec::create(exec);
auto b = vec::create(exec);
x->copy_from(host_x);
b->copy_from(host_b);
auto one = gko::initialize<vec>({1.0}, exec);
auto neg_one = gko::initialize<vec>({-1.0}, exec);
auto initres = gko::initialize<vec>({0.0}, exec);
A->apply(one, x, neg_one, b);
b->compute_norm2(initres);
b->copy_from(host_b);
auto config = gko::ext::config::parse_json_file(configfile);
auto td = gko::config::make_type_descriptor<ValueType, IndexType>();
auto solver_gen = gko::config::parse(config, reg, td).on(exec);
const auto gen_tic = std::chrono::steady_clock::now();
auto solver = solver_gen->generate(A);
exec->synchronize();
const auto gen_toc = std::chrono::steady_clock::now();
const auto gen_time = std::chrono::duration_cast<std::chrono::milliseconds>(
gen_toc - gen_tic);
std::shared_ptr<const gko::log::Convergence<ValueType>> logger =
exec->synchronize();
const auto tic = std::chrono::steady_clock::now();
exec->synchronize();
const auto toc = std::chrono::steady_clock::now();
const auto time =
std::chrono::duration_cast<std::chrono::milliseconds>(toc - tic);
std::cout << "Config file: " << configfile << std::endl;
std::ifstream f(configfile);
std::cout << f.rdbuf() << std::endl;
auto res = gko::as<vec>(logger->get_residual_norm());
std::cout << "Initial residual norm sqrt(r^T r): \n";
write(std::cout, initres);
std::cout << "Final residual norm sqrt(r^T r): \n";
std::cout << "Solver iteration count: " << logger->get_num_iterations()
<< std::endl;
std::cout << "Solver generation time [ms]: "
<< static_cast<double>(gen_time.count()) << std::endl;
std::cout << "Solver execution time [ms]: "
<< static_cast<double>(time.count()) << std::endl;
std::cout << "Solver execution time per iteration[ms]: "
<< static_cast<double>(time.count()) /
logger->get_num_iterations()
<< std::endl;
}