public static void mgmres(double[] a, int[] ia, int[] ja, ref double[] x, double[] rhs,
                              int n, int nz_num, int itr_max, int mr, double tol_abs, double tol_rel)

    //****************************************************************************80
    //
    //  Purpose:
    //
    //    MGMRES applies the restarted GMRES iteration to a linear system.
    //
    //  Discussion:
    //
    //    The linear system A*X=B is solved iteratively.
    //
    //    The matrix A is assumed to be sparse.  To save on storage, only
    //    the nonzero entries of A are stored.  For instance, the K-th nonzero
    //    entry in the matrix is stored by:
    //
    //      A(K) = value of entry,
    //      IA(K) = row of entry,
    //      JA(K) = column of entry.
    //
    //    The "matrices" H and V are treated as one-dimensional vectors
    //    which store the matrix data in row major form.
    //
    //    This requires that references to H[I][J] be replaced by references
    //    to H[I+J*(MR+1)] and references to V[I][J] by V[I+J*N].
    //
    //  Licensing:
    //
    //    This code is distributed under the GNU LGPL license.
    //
    //  Modified:
    //
    //    13 July 2007
    //
    //  Author:
    //
    //    Original C version by Lili Ju.
    //    C++ version by John Burkardt.
    //
    //  Reference:
    //
    //    Richard Barrett, Michael Berry, Tony Chan, James Demmel,
    //    June Donato, Jack Dongarra, Victor Eijkhout, Roidan Pozo,
    //    Charles Romine, Henk van der Vorst,
    //    Templates for the Solution of Linear Systems:
    //    Building Blocks for Iterative Methods,
    //    SIAM, 1994,
    //    ISBN: 0898714710,
    //    LC: QA297.8.T45.
    //
    //    Tim Kelley,
    //    Iterative Methods for Linear and Nonlinear Equations,
    //    SIAM, 2004,
    //    ISBN: 0898713528,
    //    LC: QA297.8.K45.
    //
    //    Yousef Saad,
    //    Iterative Methods for Sparse Linear Systems,
    //    Second Edition,
    //    SIAM, 2003,
    //    ISBN: 0898715342,
    //    LC: QA188.S17.
    //
    //  Parameters:
    //
    //    Input, double A[NZ_NUM], the matrix values.
    //
    //    Input, int IA[NZ_NUM], JA[NZ_NUM], the row and column indices
    //    of the matrix values.
    //
    //    Input/output, double X[N]; on input, an approximation to
    //    the solution.  On output, an improved approximation.
    //
    //    Input, double RHS[N], the right hand side of the linear system.
    //
    //    Input, int N, the order of the linear system.
    //
    //    Input, int NZ_NUM, the number of nonzero matrix values.
    //
    //    Input, int ITR_MAX, the maximum number of (outer) iterations to take.
    //
    //    Input, int MR, the maximum number of (inner) iterations to take.
    //    MR must be less than N.
    //
    //    Input, double TOL_ABS, an absolue tolerance applied to the
    //    current residual.
    //
    //    Input, double TOL_REL, a relative tolerance comparing the
    //    current residual to the initial residual.
    //
    {
        const double delta = 1.0e-03;
        int          itr;
        int          k_copy  = 0;
        double       rho     = 0;
        double       rho_tol = 0;
        const bool   verbose = true;

        double[] c = new double[mr];
        double[] g = new double[mr + 1];
        double[] h = new double[(mr + 1) * mr];
        double[] r = new double[n];
        double[] s = new double[mr];
        double[] v = new double[n * (mr + 1)];
        double[] y = new double[mr + 1];

        int itr_used = 0;

        if (n < mr)
        {
            Console.WriteLine("");
            Console.WriteLine("MGMRES - Fatal error!");
            Console.WriteLine("  N < MR.");
            Console.WriteLine("  N = " + n + "");
            Console.WriteLine("  MR = " + mr + "");
            return;
        }

        for (itr = 1; itr <= itr_max; itr++)
        {
            AX.ax(a, ia, ja, x, ref r, n, nz_num);

            int i;
            for (i = 0; i < n; i++)
            {
                r[i] = rhs[i] - r[i];
            }

            rho = Math.Sqrt(typeMethods.r8vec_dot_product(n, r, r));

            switch (verbose)
            {
            case true:
                Console.WriteLine("  ITR = " + itr + "  Residual = " + rho + "");
                break;
            }

            rho_tol = itr switch
            {
                1 => rho * tol_rel,
                _ => rho_tol
            };

            for (i = 0; i < n; i++)
            {
                v[i + 0 * n] = r[i] / rho;
            }

            g[0] = rho;
            for (i = 1; i <= mr; i++)
            {
                g[i] = 0.0;
            }

            int j;
            for (i = 0; i < mr + 1; i++)
            {
                for (j = 0; j < mr; j++)
                {
                    h[i + j * (mr + 1)] = 0.0;
                }
            }

            int k;
            for (k = 1; k <= mr; k++)
            {
                k_copy = k;

                AX.ax(a, ia, ja, v, ref v, n, nz_num, xIndex:  +(k - 1) * n, wIndex:  +k * n);

                double av = Math.Sqrt(typeMethods.r8vec_dot_product(n, v, v, a1Index: +k * n, a2Index: +k * n));

                for (j = 1; j <= k; j++)
                {
                    h[j - 1 + (k - 1) * (mr + 1)] =
                        typeMethods.r8vec_dot_product(n, v, v, a1Index: +k * n, a2Index: +(j - 1) * n);
                    for (i = 0; i < n; i++)
                    {
                        v[i + k * n] -= h[j - 1 + (k - 1) * (mr + 1)] * v[i + (j - 1) * n];
                    }
                }

                h[k + (k - 1) * (mr + 1)] =
                    Math.Sqrt(typeMethods.r8vec_dot_product(n, v, v, a1Index: +k * n, a2Index: +k * n));

                if (Math.Abs(av + delta * h[k + (k - 1) * (mr + 1)] - av) <= double.Epsilon)
                {
                    for (j = 1; j <= k; j++)
                    {
                        double htmp = typeMethods.r8vec_dot_product(n, v, v, a1Index: +k * n, a2Index: +(j - 1) * n);
                        h[j - 1 + (k - 1) * (mr + 1)] += htmp;
                        for (i = 0; i < n; i++)
                        {
                            v[i + k * n] -= htmp * v[i + (j - 1) * n];
                        }
                    }

                    h[k + (k - 1) * (mr + 1)] =
                        Math.Sqrt(typeMethods.r8vec_dot_product(n, v, v, a1Index: +k * n, a2Index: +k * n));
                }

                if (h[k + (k - 1) * (mr + 1)] != 0.0)
                {
                    for (i = 0; i < n; i++)
                    {
                        v[i + k * n] /= h[k + (k - 1) * (mr + 1)];
                    }
                }

                switch (k)
                {
                case > 1:
                {
                    for (i = 1; i <= k + 1; i++)
                    {
                        y[i - 1] = h[i - 1 + (k - 1) * (mr + 1)];
                    }

                    for (j = 1; j <= k - 1; j++)
                    {
                        Helpers.mult_givens(c[j - 1], s[j - 1], j - 1, ref y);
                    }

                    for (i = 1; i <= k + 1; i++)
                    {
                        h[i - 1 + (k - 1) * (mr + 1)] = y[i - 1];
                    }

                    break;
                }
                }

                double mu = Math.Sqrt(Math.Pow(h[k - 1 + (k - 1) * (mr + 1)], 2)
                                      + Math.Pow(h[k + (k - 1) * (mr + 1)], 2));
                c[k - 1] = h[k - 1 + (k - 1) * (mr + 1)] / mu;
                s[k - 1] = -h[k + (k - 1) * (mr + 1)] / mu;
                h[k - 1 + (k - 1) * (mr + 1)] = c[k - 1] * h[k - 1 + (k - 1) * (mr + 1)]
                                                - s[k - 1] * h[k + (k - 1) * (mr + 1)];
                h[k + (k - 1) * (mr + 1)] = 0;
                Helpers.mult_givens(c[k - 1], s[k - 1], k - 1, ref g);

                rho = Math.Abs(g[k]);

                itr_used += 1;

                switch (verbose)
                {
                case true:
                    Console.WriteLine("  K =   " + k + "  Residual = " + rho + "");
                    break;
                }

                if (rho <= rho_tol && rho <= tol_abs)
                {
                    break;
                }
            }

            k    = k_copy - 1;
            y[k] = g[k] / h[k + k * (mr + 1)];

            for (i = k; 1 <= i; i--)
            {
                y[i - 1] = g[i - 1];
                for (j = i + 1; j <= k + 1; j++)
                {
                    y[i - 1] -= h[i - 1 + (j - 1) * (mr + 1)] * y[j - 1];
                }

                y[i - 1] /= h[i - 1 + (i - 1) * (mr + 1)];
            }

            for (i = 1; i <= n; i++)
            {
                for (j = 1; j <= k + 1; j++)
                {
                    x[i - 1] += v[i - 1 + (j - 1) * n] * y[j - 1];
                }
            }

            if (rho <= rho_tol && rho <= tol_abs)
            {
                break;
            }
        }

        switch (verbose)
        {
        case true:
            Console.WriteLine("");
            Console.WriteLine("MGMRES");
            Console.WriteLine("  Number of iterations = " + itr_used + "");
            Console.WriteLine("  Final residual = " + rho + "");
            break;
        }
    }