int CGSolver(SparseMatrix A,    // CSR format matrix
             const std::vector<double> &b,
             std::vector<double> &u,
             double tol,
             std::map<int, std::vector<double>> &soln_iter) {
    unsigned int n_iter = 0;
    long unsigned int max_iter = A.GetRows();

    soln_iter[n_iter] = u;    // store initial state
    auto A_u = A.MulVec(u);   // A*u
    std::vector<double> r = subvec(b, A_u);   // b - A*u
    auto L2normr0 = L2norm(r);
    std::vector<double> p = r;    // p =r

    while (n_iter < max_iter) {
        n_iter++;
        auto A_p = A.MulVec(p);                         // A * p
        double alpha = vecvec(r, r) / vecvec(p, A_p);   // (r * r)/(p * A * p)

        auto alpha_p = scalvec(alpha, p);   // alpha * p
        u = addvec(u, alpha_p);             // u = u + alpha * p

        /**
         * alpha * A * p
         * r_n+1 = r_n - alpha * A * p
         */
        std::vector<double> alpha_A_p = scalvec(alpha, A_p);
        std::vector<double> r_next = subvec(r, alpha_A_p);

        auto L2normr = L2norm(r_next);  // L2normr = L2norm(r_n+1)

        if (L2normr / L2normr0 < tol) {
            soln_iter[n_iter] = u;
            std::cout << "SUCCESS: CG solver converged in "
                      << n_iter << " iterations" << std::endl;
            break;
        } else {
            double beta = vecvec(r_next, r_next) / vecvec(r, r);
            auto beta_p = scalvec(beta, p);

            p = addvec(r_next, beta_p);
            r = r_next;

            if ((n_iter % 10 == 0) || (n_iter == 0)) {
                soln_iter[n_iter] = u;
            }
            if (n_iter == max_iter) {
                std::cout << "FAILURE: CG solver failed to converge" << std::endl;
                return 1;
            }
        }
    }
    return 0;
}