changeset 24849:96c74e33d17a

pcg.m: Overhaul function and BIST tests (bug #53258). * pcg.m: Add more calling forms (@deftypefnx) to docstring. Wrap docstring to 80 characters. Use @itemize to explain return values for FLAG output. Capitalize input variables M, M1, M2. Rephrase parts of docstring for clarity. Remove unnecessary comments in code. Use true/false rather than 1/0 for clarity. Make use of variable preconditioned_residual_out after setting it. Add newline to first line of 2-line error output. Use variable name from documentation in warning() message. Space out the code in %!demos to be less cramped. Use diag() rather than for loop to construct Laplacian matrix for %!demos and %!tests. Place legend in top center for demo #4, and change y-axis limits so that it doesn't overlap the actual data. Change %!tests to test how close the solution A*x - b is to 0, rather than testing the relative error between x and the true solution which pcg does not guarantee. Don't specify maxiter of N+1 (11), but instead use 2*N (20).
author Rik <rik@octave.org>
date Wed, 07 Mar 2018 22:24:23 -0800
parents 2d68dc548561
children 1fd64fa2cebc
files scripts/sparse/pcg.m
diffstat 1 files changed, 171 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/sparse/pcg.m	Wed Mar 07 17:34:52 2018 -0500
+++ b/scripts/sparse/pcg.m	Wed Mar 07 22:24:23 2018 -0800
@@ -17,49 +17,55 @@
 ## <https://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn  {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol}, @var{maxit}, @var{m1}, @var{m2}, @var{x0}, @dots{})
+## @deftypefn  {} {@var{x} =} pcg (@var{A}, @var{b})
+## @deftypefnx {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol})
+## @deftypefnx {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol}, @var{maxit})
+## @deftypefnx {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol}, @var{maxit}, @var{M})
+## @deftypefnx {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol}, @var{maxit}, @var{M1}, @var{M2})
+## @deftypefnx {} {@var{x} =} pcg (@var{A}, @var{b}, @var{tol}, @var{maxit}, @var{M1}, @var{M2}, @var{x0})
+## @deftypefnx {} {@var{x} =} pcg (@var{Afun}, @var{b}, @var{tol}, @var{maxit}, @var{M1}, @var{M2}, @var{x0}, @dots{})
 ## @deftypefnx {} {[@var{x}, @var{flag}, @var{relres}, @var{iter}, @var{resvec}, @var{eigest}] =} pcg (@dots{})
 ##
-## Solve the linear system of equations @w{@code{@var{A} * @var{x} = @var{b}}}
+## Solve the linear system of equations @w{@code{@var{A}*@var{x} = @var{b}}}
 ## by means of the Preconditioned Conjugate Gradient iterative method.
 ##
 ## The input arguments are
 ##
 ## @itemize
 ## @item
-## @var{A} can be either a square (preferably sparse) matrix or a function
-## handle, inline function or string containing the name of a function which
-## computes @w{@code{@var{A} * @var{x}}}.  In principle, @var{A} should be
-## symmetric and positive definite; if @code{pcg} finds @var{A} not to be
-## positive definite, a warning is printed and the @var{flag} output will be
-## set.
+## @var{A} can either be a square (preferably sparse) matrix or a function
+## handle, inline function, or string containing the name of a function that
+## computes @w{@code{@var{A}*@var{x}}}.  In principle, @var{A} should be
+## symmetric and positive definite; if @code{pcg} finds that @var{A} is not
+## positive definite then a warning is printed and the @var{flag} output will
+## be set to 3.
 ##
 ## @item
 ## @var{b} is the right-hand side vector.
 ##
 ## @item
 ## @var{tol} is the required relative tolerance for the residual error,
-## @w{@code{@var{b} - @var{A} * @var{x}}}.  The iteration stops if
-## @w{@code{norm (@var{b} - @var{A} * @var{x})} @leq{}
-## @w{@var{tol} * norm (@var{b})}}.
-## If @var{tol} is omitted or empty then a tolerance of 1e-6 is used.
+## @w{@code{@var{b} - @var{A}*@var{x}}}.  The iteration stops if
+## @w{@code{norm (@var{b} - @var{A}*@var{x})}} @leq{}
+## @w{@code{@var{tol} * norm (@var{b})}}.
+## If @var{tol} is omitted or empty @code{[]} then a tolerance of 1e-6 is used.
 ##
 ## @item
-## @var{maxit} is the maximum allowable number of iterations; if @var{maxit}
-## is omitted or empty then a value of 20 is used.
+## @var{maxit} is the maximum allowable number of iterations.  If @var{maxit}
+## is omitted or empty then a value of @code{min (rows (@var{A}), 20)} is used.
 ##
 ## @item
-## @var{m} = @var{m1} * @var{m2} is the (left) preconditioning matrix, so that
-## the iteration is (theoretically) equivalent to solving by @code{pcg}
-## @w{@code{@var{P} * @var{x} = @var{m} \ @var{b}}}, with
-## @w{@code{@var{P} = @var{m} \ @var{A}}}.
-## Note that a proper choice of the preconditioner may dramatically improve
-## the overall performance of the method.  Instead of matrices @var{m1} and
-## @var{m2}, the user may pass two functions which return the results of
-## applying the inverse of @var{m1} and @var{m2} to a vector (usually this is
-## the preferred way of using the preconditioner).  If @var{m1} is omitted or
-## empty @code{[]} then no preconditioning is applied.  If @var{m2} is
-## omitted, @var{m} = @var{m1} will be used as a preconditioner.
+## @var{M} = @var{M1} * @var{M2} is the (left) preconditioning matrix, such
+## that the iteration is (theoretically) equivalent to solving
+## @w{@code{@var{P} * @var{x} = @var{M} \ @var{b}}}, with
+## @w{@code{@var{P} = @var{M} \ @var{A}}}.
+## Note that a proper choice of the preconditioner can dramatically improve the
+## overall performance of the method.  Instead of matrices @var{M1} and
+## @var{M2}, the user may pass two functions which return the results of
+## applying the inverse of @var{M1} and @var{M2} to a vector (usually this is
+## the preferred way of using the preconditioner).  If @var{M1} is omitted or
+## empty then no preconditioning is applied.  If @var{M2} is omitted,
+## @var{M} = @var{M1} will be used as a preconditioner.
 ##
 ## @item
 ## @var{x0} is the initial guess.  If @var{x0} is omitted or empty then the
@@ -67,21 +73,29 @@
 ## @end itemize
 ##
 ## The arguments which follow @var{x0} are treated as parameters, and passed in
-## a proper way to any of the functions (@var{A} or @var{m}) which are passed
-## to @code{pcg}.  See the examples below for further details.  The output
-## arguments are
+## a proper way to any of the functions (@var{Afun} or @var{Mfun}) which are
+## given to @code{pcg}.  See the examples below for further details.
+##
+## The output arguments are
 ##
 ## @itemize
 ## @item
 ## @var{x} is the computed approximation to the solution of
-## @w{@code{@var{A} * @var{x} = @var{b}}}.
+## @w{@code{@var{A}*@var{x} = @var{b}}}.
 ##
 ## @item
-## @var{flag} reports on the convergence.  A value of 0 means the solution
-## converged and the tolerance criterion given by @var{tol} is satisfied.
-## A value of 1 means that the @var{maxit} limit for the iteration count was
-## reached.  A value of 3 indicates that the (preconditioned) matrix was found
-## not to be positive definite.
+## @var{flag} reports on convergence.
+##
+## @itemize
+## @item 0 : The algorithm converged and the tolerance criterion given by
+## @var{tol} is satisfied.
+##
+## @item 1 : The maximum number of iterations was reached (specified by
+## @var{maxit}).
+##
+## @item 3 : The (preconditioned) matrix was found @strong{not} to be positive
+## definite.
+## @end itemize
 ##
 ## @item
 ## @var{relres} is the ratio of the final residual to its initial value,
@@ -92,39 +106,39 @@
 ##
 ## @item
 ## @var{resvec} describes the convergence history of the method.
-## @code{@var{resvec}(i,1)} is the Euclidean norm of the residual, and
-## @code{@var{resvec}(i,2)} is the preconditioned residual norm, after the
-## (@var{i}-1)-th iteration, @code{@var{i} = 1, 2, @dots{}, @var{iter}+1}.
-## The preconditioned residual norm is defined as
-## @code{norm (@var{r}) ^ 2 = @var{r}' * (@var{m} \ @var{r})} where
-## @code{@var{r} = @var{b} - @var{A} * @var{x}}, see also the
-## description of @var{m}.  If @var{eigest} is not required, only
-## @code{@var{resvec}(:,1)} is returned.
+## The first column, @code{@var{resvec}(i,1)}, is the Euclidean norm of the
+## residual, and the second column, @code{@var{resvec}(i,2)} is the
+## preconditioned residual norm, after the (@var{i}-1)-th iteration,
+## @code{@var{i} = 1, 2, @dots{}, @var{iter}+1}.  The preconditioned residual
+## norm is defined as
+## @w{@code{norm (@var{r}) ^ 2 = @var{r}' * (@var{M} \ @var{r})}} where
+## @w{@code{@var{r} = @var{b} - @var{A}*@var{x}}}, see also the
+## description of @var{M}.  If @var{eigest} is not required, only the first
+## column of @var{resvec} (Euclidean norm of residual) is returned.
 ##
 ## @item
 ## @var{eigest} returns the estimate for the smallest @code{@var{eigest}(1)}
 ## and largest @code{@var{eigest}(2)} eigenvalues of the preconditioned matrix
-## @w{@code{@var{P} = @var{m} \ @var{A}}}.  In particular, if no
+## @w{@code{@var{P} = @var{M} \ @var{A}}}.  In particular, if no
 ## preconditioning is used, the estimates for the extreme eigenvalues of
 ## @var{A} are returned.  @code{@var{eigest}(1)} is an overestimate and
 ## @code{@var{eigest}(2)} is an underestimate, so that
 ## @code{@var{eigest}(2) / @var{eigest}(1)} is a lower bound for
-## @code{cond (@var{P}, 2)}, which nevertheless in the limit should
-## theoretically be equal to the actual value of the condition number.
-## The method which computes @var{eigest} works only for symmetric positive
-## definite @var{A} and @var{m}, and the user is responsible for verifying this
+## the condition number of @var{P} (@code{cond (@var{P}, 2)}).  The method
+## which computes @var{eigest} works only for symmetric positive definite
+## matrices @var{A} and @var{M}, and the user is responsible for verifying this
 ## assumption.
 ## @end itemize
 ##
-## Let us consider a trivial problem with a diagonal matrix (we exploit the
-## sparsity of A)
+## Consider a trivial problem with a diagonal matrix (which naturally has high
+## sparsity):
 ##
 ## @example
 ## @group
 ## n = 10;
 ## A = diag (sparse (1:n));
 ## b = rand (n, 1);
-## [l, u, p] = ilu (A, struct ("droptol", 1.e-3));
+## [L, U, P] = ilu (A, struct ("droptol", 1e-3));
 ## @end group
 ## @end example
 ##
@@ -135,30 +149,30 @@
 ## @end example
 ##
 ## @sc{Example 2:} @code{pcg} with a function which computes
-## @code{@var{A} * @var{x}}
+## @code{@var{A}*@var{x}}
 ##
 ## @example
 ## @group
-## function y = apply_a (x)
+## function y = apply_A (x)
 ##   y = [1:N]' .* x;
 ## endfunction
 ##
-## x = pcg ("apply_a", b)
+## x = pcg ("apply_A", b)
 ## @end group
 ## @end example
 ##
-## @sc{Example 3:} @code{pcg} with a preconditioner: @var{l} * @var{u}
+## @sc{Example 3:} @code{pcg} with a preconditioner: @var{L} * @var{U}
 ##
 ## @example
-## x = pcg (A, b, 1.e-6, 500, l*u)
+## x = pcg (A, b, 1e-6, 500, L*U)
 ## @end example
 ##
-## @sc{Example 4:} @code{pcg} with a preconditioner: @var{l} * @var{u}.
-## Faster than @sc{Example 3} since lower and upper triangular matrices are
+## @sc{Example 4:} @code{pcg} with a preconditioner: @var{L}, @var{U}.
+## Faster than @sc{Example 3} because lower and upper triangular matrices are
 ## easier to invert
 ##
 ## @example
-## x = pcg (A, b, 1.e-6, 500, l, u)
+## x = pcg (A, b, 1e-6, 500, L, U)
 ## @end example
 ##
 ## @sc{Example 5:} Preconditioned iteration, with full diagnostics.  The
@@ -167,20 +181,19 @@
 ##
 ## @example
 ## @group
-## function y = apply_m (x)
+## function y = apply_M (x)
 ##   k = floor (length (x) - 2);
 ##   y = x;
 ##   y(1:k) = x(1:k) ./ [1:k]';
 ## endfunction
 ##
 ## [x, flag, relres, iter, resvec, eigest] = ...
-##                    pcg (A, b, [], [], "apply_m");
+##                    pcg (A, b, [], [], "apply_M");
 ## semilogy (1:iter+1, resvec);
 ## @end group
 ## @end example
 ##
-## @sc{Example 6:} Finally, a preconditioner which depends on a parameter
-## @var{k}.
+## @sc{Example 6:} A preconditioner which depends on a parameter @var{k}.
 ##
 ## @example
 ## @group
@@ -216,9 +229,10 @@
 ## Modified by: Vittoria Rezzonico <vittoria.rezzonico@epfl.ch>
 ##  - Add the ability to provide the pre-conditioner as two separate matrices
 
-function [x, flag, relres, iter, resvec, eigest] = pcg (A, b, tol, maxit, m1, m2, x0, varargin)
+function [x, flag, relres, iter, resvec, eigest] = pcg (A, b, tol, maxit, M1, M2, x0, varargin)
 
-  ## M = M1*M2
+  ## FIXME: Would be good to have actual validation of some inputs, rather
+  ##        than just passing them through to internal variables.
 
   if (nargin < 7 || isempty (x0))
     x = zeros (size (b));
@@ -226,22 +240,23 @@
     x = x0;
   endif
 
-  if (nargin < 5 || isempty (m1))
-     exist_m1 = 0;
+  if (nargin < 5 || isempty (M1))
+     exist_m1 = false;
   else
-     exist_m1 = 1;
+     exist_m1 = true;
   endif
 
-  if (nargin < 6 || isempty (m2))
-     exist_m2 = 0;
+  if (nargin < 6 || isempty (M2))
+     exist_m2 = false;
   else
-     exist_m2 = 1;
+     exist_m2 = true;
   endif
 
   if (nargin < 4 || isempty (maxit))
     maxit = min (rows (b), 20);
   endif
 
+  ## Allow extra iterations for function eval done outside main loop.
   maxit += 2;
 
   if (nargin < 3 || isempty (tol))
@@ -250,11 +265,11 @@
 
   preconditioned_residual_out = false;
   if (nargout > 5)
+    preconditioned_residual_out = true;
     T = zeros (maxit, maxit);
-    preconditioned_residual_out = true;
   endif
 
-  ## Assume A is positive definite.
+  ## Assume A is positive definite, until proved otherwise.
   matrix_positive_definite = true;
 
   p = zeros (size (b));
@@ -272,21 +287,21 @@
   alpha = 1;
   iter = 2;
 
-  while (resvec(iter-1,1) > tol * b_norm && iter < maxit)
+  while (resvec(iter-1,1) > (tol * b_norm) && iter < maxit)
     if (exist_m1)
-      if (isnumeric (m1))
-        y = m1 \ r;
+      if (isnumeric (M1))
+        y = M1 \ r;
       else
-        y = feval (m1, r, varargin{:});
+        y = feval (M1, r, varargin{:});
       endif
     else
       y = r;
     endif
     if (exist_m2)
-      if (isnumeric (m2))
-        z = m2 \ y;
+      if (isnumeric (M2))
+        z = M2 \ y;
       else
-        z = feval (m2, y, varargin{:});
+        z = feval (M2, y, varargin{:});
       endif
     else
       z = y;
@@ -303,8 +318,7 @@
       ## A should be a function.
       w = feval (A, p, varargin{:});
     endif
-    ## Needed only for eigest.
-    oldalpha = alpha;
+    oldalpha = alpha;    # Needed only for eigest.
     alpha = tau / (p'*w);
     if (alpha <= 0.0)
       ## Negative matrix.
@@ -312,47 +326,42 @@
     endif
     x += alpha * p;
     r -= alpha * w;
-    if (nargout > 5 && iter > 2)
-      T(iter-1:iter, iter-1:iter) = T(iter-1:iter, iter-1:iter) + ...
-          [1 sqrt(beta); sqrt(beta) beta]./oldalpha;
-      ## EVS = eig (T(2:iter-1,2:iter-1));
-      ## fprintf (stderr,"PCG condest: %g (iteration: %d)\n", max (EVS)/min (EVS),iter);
+    if (preconditioned_residual_out && iter > 2)
+      T(iter-1:iter, iter-1:iter) += [1 sqrt(beta); sqrt(beta) beta]./oldalpha;
     endif
     resvec(iter,1) = norm (r);
     iter += 1;
   endwhile
 
-  if (nargout > 5)
+  if (preconditioned_residual_out)
     if (matrix_positive_definite)
       if (iter > 3)
         T = T(2:iter-2,2:iter-2);
         l = eig (T);
         eigest = [min(l), max(l)];
-        ## fprintf (stderr, "pcg condest: %g\n", eigest(2)/eigest(1));
       else
+        warning ("pcg: eigenvalue estimate failed: iteration converged too fast");
         eigest = [NaN, NaN];
-        warning ("pcg: eigenvalue estimate failed: iteration converged too fast");
       endif
     else
       eigest = [NaN, NaN];
     endif
 
-    ## Apply the preconditioner once more and finish with the precond
-    ## residual.
+    ## Apply the preconditioner once more and finish with the precond residual.
     if (exist_m1)
-      if (isnumeric (m1))
-        y = m1 \ r;
+      if (isnumeric (M1))
+        y = M1 \ r;
       else
-        y = feval (m1, r, varargin{:});
+        y = feval (M1, r, varargin{:});
       endif
     else
       y = r;
     endif
     if (exist_m2)
-      if (isnumeric (m2))
-        z = m2 \ y;
+      if (isnumeric (M2))
+        z = M2 \ y;
       else
-        z = feval (m2, y, varargin{:});
+        z = feval (M2, y, varargin{:});
       endif
     else
       z = y;
@@ -374,15 +383,15 @@
                1.0 / relres);
     endif
   elseif (nargout < 2)
-    fprintf (stderr, "pcg: converged in %d iterations. ", iter);
+    fprintf (stderr, "pcg: converged in %d iterations.\n", iter);
     fprintf (stderr, "pcg: the initial residual norm was reduced %g times.\n",
-             1.0/relres);
+             1.0 / relres);
   endif
 
   if (! matrix_positive_definite)
     flag = 3;
     if (nargout < 2)
-      warning ("pcg: matrix not positive definite?\n");
+      warning ("pcg: matrix A was not positive definite\n");
     endif
   endif
 
@@ -390,60 +399,63 @@
 
 
 %!demo
-%! ## Simplest usage of pcg (see also 'help pcg')
+%! ## Simplest usage of pcg (see also "help pcg")
 %!
 %! N = 10;
-%! A = diag ([1:N]); b = rand (N, 1);
+%! A = diag ([1:N]);
+%! b = rand (N, 1);
 %! y = A \ b;  # y is the true solution
 %! x = pcg (A, b);
 %! printf ("The solution relative error is %g\n", norm (x - y) / norm (y));
 %!
-%! ## You shouldn't be afraid if pcg issues some warning messages in this
-%! ## example: watch out in the second example, why it takes N iterations
-%! ## of pcg to converge to (a very accurate, by the way) solution
+%! ## Don't be alarmed if pcg issues some warning messages in this example.
 
 %!demo
-%! ## Full output from pcg, except for the eigenvalue estimates
-%! ## We use this output to plot the convergence history
+%! ## Full output from pcg, except for the eigenvalue estimates.
+%! ## Use the output to plot the convergence history.
 %!
 %! N = 10;
-%! A = diag ([1:N]); b = rand (N, 1);
+%! A = diag ([1:N]);
+%! b = rand (N, 1);
 %! X = A \ b;  # X is the true solution
 %! [x, flag, relres, iter, resvec] = pcg (A, b);
 %! printf ("The solution relative error is %g\n", norm (x - X) / norm (X));
+%! semilogy ([0:iter], resvec ./ resvec(1), "o-g");
 %! title ("Convergence history");
-%! semilogy ([0:iter], resvec / resvec(1), "o-g");
-%! xlabel ("Iteration"); ylabel ("log(||b-Ax||/||b||)");
+%! set (gca, "xtick", [0:iter]);
+%! xlabel ("Iteration");  ylabel ("log( ||b-Ax|| / ||b|| )");
 %! legend ("relative residual");
 
 %!demo
-%! ## Full output from pcg, including the eigenvalue estimates
-%! ## Hilbert matrix is extremely ill-conditioned, so pcg WILL have problems
+%! ## Full output from pcg, including the eigenvalue estimates.
+%! ## Hilbert matrix is extremely ill-conditioned, so pcg WILL have problems.
 %!
 %! N = 10;
-%! A = hilb (N); b = rand (N, 1);
+%! A = hilb (N);
+%! b = rand (N, 1);
 %! X = A \ b;  # X is the true solution
-%! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], 200);
+%! M = diag (diag (A));  # Jacobi preconditioner
+%! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], 200, M);
 %! printf ("The solution relative error is %g\n", norm (x - X) / norm (X));
 %! printf ("Condition number estimate is %g\n", eigest(2) / eigest(1));
 %! printf ("Actual condition number is   %g\n", cond (A));
+%! semilogy ([0:iter], resvec, ["o-g";"+-r"]);
 %! title ("Convergence history");
-%! semilogy ([0:iter], resvec, ["o-g";"+-r"]);
-%! xlabel ("Iteration"); ylabel ("log(||b-Ax||)");
+%! xlabel ("Iteration");  ylabel ("log( ||b-Ax|| )");
 %! legend ("absolute residual", "absolute preconditioned residual");
 
 %!demo
-%! ## Full output from pcg, including the eigenvalue estimates
-%! ## We use the 1-D Laplacian matrix for A, and cond(A) = O(N^2)
-%! ## and that's the reason we need some preconditioner; here we take
+%! ## Full output from pcg, including the eigenvalue estimates.
+%! ## We use the 1-D Laplacian matrix for A, and cond (A) = O(N^2)
+%! ## which is the reason we need some preconditioner; here we take
 %! ## a very simple and not powerful Jacobi preconditioner,
 %! ## which is the diagonal of A.
 %!
 %! N = 100;
-%! A = zeros (N, N);
-%! for i = 1 : N - 1 # form 1-D Laplacian matrix
-%!   A(i:i+1, i:i+1) = [2 -1; -1 2];
-%! endfor
+%! ## Form 1-D Laplacian matrix (2 on main diagonal, -1 on supra/sub-diagonals)
+%! A = diag (repmat (2, [100, 1])) ...
+%!     + diag (repmat (-1, [99, 1]), -1) ...
+%!     + diag (repmat (-1, [99, 1]), +1);
 %! b = rand (N, 1);
 %! X = A \ b;  # X is the true solution
 %! maxit = 80;
@@ -452,45 +464,50 @@
 %!
 %! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], maxit);
 %! printf ("System condition number estimate is %g\n", eigest(2) / eigest(1));
+%! semilogy ([0:iter], resvec(:,1), "o-g");
+%! ylim ([1e-6, 1e3])
 %! title ("Convergence history");
-%! semilogy ([0:iter], resvec(:,1), "o-g");
-%! xlabel ("Iteration"); ylabel ("log(||b-Ax||)");
-%! legend ("NO preconditioning: absolute residual");
+%! xlabel ("Iteration");  ylabel ("log( ||b-Ax|| )");
+%! legend ("NO preconditioning: absolute residual", ...
+%!         "location", "north");
+%! pause (1.5);
 %!
-%! pause (1);
-%! ## Test Jacobi preconditioner: it will not help much!!!
+%! ## Test Jacobi preconditioner: it does not help much.
 %!
-%! M = diag (diag (A)); # Jacobi preconditioner
+%! M = diag (diag (A));  # Jacobi preconditioner
 %! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], maxit, M);
 %! printf ("JACOBI preconditioned system condition number estimate is %g\n", eigest(2) / eigest(1));
 %! hold on;
-%! semilogy ([0:iter], resvec(:,1), "o-r");
+%! semilogy ([0:iter], resvec(:,1), "x-r");
 %! legend ("NO preconditioning: absolute residual", ...
-%!         "JACOBI preconditioner: absolute residual");
+%!         "JACOBI preconditioner: absolute residual",
+%!         "location", "north");
+%! pause (1.5);
 %!
-%! pause (1);
-%! ## Test nonoverlapping block Jacobi preconditioner: it will help much!
+%! ## Test non-overlapping block Jacobi preconditioner: it will help much!
 %!
-%! M = zeros (N, N); k = 4;
-%! for i = 1 : k : N # form 1-D Laplacian matrix
+%! M = zeros (N, N);
+%! k = 4;
+%! for i = 1 : k : N
 %!   M(i:i+k-1, i:i+k-1) = A(i:i+k-1, i:i+k-1);
 %! endfor
 %! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], maxit, M);
 %! printf ("BLOCK JACOBI preconditioned system condition number estimate is %g\n", eigest(2) / eigest(1));
-%! semilogy ([0:iter], resvec(:,1), "o-b");
+%! semilogy ([0:iter], resvec(:,1), "s-b");
 %! legend ("NO preconditioning: absolute residual", ...
 %!         "JACOBI preconditioner: absolute residual", ...
-%!         "BLOCK JACOBI preconditioner: absolute residual");
+%!         "BLOCK JACOBI preconditioner: absolute residual",
+%!         "location", "north");
 %! hold off;
 
 %!test
 %! ## solve small diagonal system
 %!
 %! N = 10;
-%! A = diag ([1:N]); b = rand (N, 1);
-%! X = A \ b;  # X is the true solution
-%! [x, flag] = pcg (A, b, [], N+1);
-%! assert (norm (x - X) / norm (X), 0, 1e-10);
+%! A = diag ([1:N]);
+%! b = rand (N, 1);
+%! [x, flag] = pcg (A, b, [], 2*N);
+%! assert (norm (A*x - b) / norm (b), 0, 1e-6);
 %! assert (flag, 0);
 
 %!test
@@ -499,10 +516,10 @@
 %! ## The indefiniteness of A is detected.
 %!
 %! N = 10;
-%! A = diag([1:N] .* (-ones(1, N) .^ 2)); b = rand (N, 1);
-%! X = A \ b;  # X is the true solution
-%! [x, flag] = pcg (A, b, [], N+1);
-%! assert (norm (x - X) / norm (X), 0, 1e-10);
+%! A = diag([1:N] .* (-ones(1, N) .^ 2));
+%! b = rand (N, 1);
+%! [x, flag] = pcg (A, b, [], 2*N);
+%! assert (norm (A*x - b) / norm (b), 0, 1e-6);
 %! assert (flag, 3);
 
 %!test
@@ -510,9 +527,10 @@
 %!
 %! N = 100;
 %! A = zeros (N, N);
-%! for i = 1 : N - 1 # form 1-D Laplacian matrix
-%!   A(i:i+1, i:i+1) = [2 -1; -1 2];
-%! endfor
+%! ## Form 1-D Laplacian matrix (2 on main diagonal, -1 on supra/sub-diagonals)
+%! A = diag (repmat (2, [100, 1])) ...
+%!     + diag (repmat (-1, [99, 1]), -1) ...
+%!     + diag (repmat (-1, [99, 1]), +1);
 %! b = ones (N, 1);
 %! X = A \ b;  # X is the true solution
 %! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, 1e-12);
@@ -526,13 +544,13 @@
 %!
 %! N = 100;
 %! A = zeros (N, N);
-%! for i = 1 : N - 1  # form 1-D Laplacian matrix
-%!   A(i:i+1, i:i+1) = [2 -1; -1 2];
-%! endfor
+%! ## Form 1-D Laplacian matrix (2 on main diagonal, -1 on supra/sub-diagonals)
+%! A = diag (repmat (2, [100, 1])) ...
+%!     + diag (repmat (-1, [99, 1]), -1) ...
+%!     + diag (repmat (-1, [99, 1]), +1);
 %! b = ones (N, 1);
-%! X = A \ b;  # X is the true solution
 %! [x, flag, relres, iter, resvec, eigest] = pcg (A, b, [], [], A, [], b);
-%! assert (norm (x - X) / norm (X), 0, 1e-6);
+%! assert (norm (A*x - b) / norm (b), 0, 1e-6);
 %! assert (flag, 0);
 %! assert (iter, 1);  # should converge in one iteration
 %! assert (isnan (eigest), isnan ([NaN, NaN]));