function ccolamd_test
%CCOLAMD_TEST extensive test of ccolamd and csymamd
%
% Example:
%   ccolamd_test
%
% See also csymamd, ccolamd, ccolamd_make.

% CCOLAMD, Copyright (c) 2005-2022, Univ. of Florida, All Rights Reserved.
% Authors: Timothy A. Davis, Sivasankaran Rajamanickam, and Stefan Larimore.
% SPDX-License-Identifier: BSD-3-clause

help ccolamd_test

global ccolamd_default_knobs csymamd_default_knobs
ccolamd_default_knobs = [0 10 10 1 0] ;
csymamd_default_knobs = [10 1 0] ;

    fprintf ('Compiling ccolamd, csymamd, and test mexFunctions.\n') ;
    ccolamd_make ;

    d = '' ;
    if (~isempty (strfind (computer, '64')))
	d = '-largeArrayDims' ;
    end
    cmd = sprintf ( ...
        'mex -O %s -I../../SuiteSparse_config -I../Include ', d) ;
    src = '../Source/ccolamd_l.c ../../SuiteSparse_config/SuiteSparse_config.c' ;
    if (~(ispc || ismac))
        % for POSIX timing routine
        src = [src ' -lrt'] ;
    end
    eval ([cmd 'ccolamdtestmex.c ' src]) ;
    eval ([cmd 'csymamdtestmex.c ' src]) ;
    fprintf ('Done compiling.\n') ; 


fprintf ('\nThe following codes will be tested:\n') ;
which ccolamd 
which csymamd
which ccolamdtestmex
which csymamdtestmex

fprintf ('\nStarting the tests.  Please be patient.\n') ;

h = waitbar (0, 'COLAMD test') ;

rand ('state', 0) ;
randn ('state', 0) ;

A = sprandn (500,500,0.4) ;

p = ccolamd (A, [0 10 10 1 1]) ; check_perm (p, A) ;
p = ccolamd (A, [1  2  7 1 1]) ; check_perm (p, A) ;
p = ccolamd (A, [1  2 10 0 1]) ; check_perm (p, A) ;
p = ccolamd (A, [9  2  3 1 1]) ; check_perm (p, A) ;

p = csymamd (A, [10 1 1]) ; check_perm (p, A) ;
p = csymamd (A, [4  1 1]) ; check_perm (p, A) ;
p = csymamd (A, [9  0 1]) ; check_perm (p, A) ;

fprintf ('Null matrices') ;
A = zeros (0,0) ;
A = sparse (A) ;

p = ccolamd (A) ;
check_perm (p, A) ;

p = csymamd (A) ;
check_perm (p, A) ;

A = zeros (0, 100) ;
A = sparse (A) ;
p = ccolamd (A) ;
check_perm (p, A) ;

A = zeros (100, 0) ;
A = sparse (A) ;
p = ccolamd (A) ;
check_perm (p, A) ;
fprintf (' OK\n') ;


fprintf ('Matrices with a few dense row/cols\n') ;
for trial = 1:20

    waitbar (trial/20, h, 'CCOLAMD: dense rows/cols') ;

    % random square unsymmetric matrix
    A = rand_matrix (1000, 1000, 1, 10, 20) ;
    [m n] = size (A) ;

    cmember = irand (min (trial,n), n) ;

    for tol = [0:.1:2 3:20 1e6]
	B = A + A' ;
        fprintf ('.') ;

	p = ccolamd (A, [ ]) ;		  check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 1]) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1]) ;  check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 0]) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1]) ;  check_perm (p, A) ;
	p = csymamd (A, [tol 1]) ;	  check_perm (p, A) ;
	p = csymamd (A, tol) ;		  check_perm (p, A) ;
	p = csymamd (A, [ ]) ;		  check_perm (p, A) ;
	p = csymamd (B, [tol 0]) ;	  check_perm (p, A) ;
	p = ccolamd (A, [0 tol -1 1]) ;   check_perm (p, A) ;
	p = ccolamd (A, [0 -1 tol 1]) ;   check_perm (p, A) ;

	% check with non-null cmember

	p = ccolamd (A, [ ], cmember) ;		   check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 1], cmember) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1], cmember) ;  check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 0], cmember) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1], cmember) ;  check_perm (p, A) ;
	p = csymamd (A, [tol 1], cmember) ;	   check_perm (p, A) ;
	p = csymamd (A, tol, cmember) ;		   check_perm (p, A) ;
	p = csymamd (A, [ ], cmember) ;		   check_perm (p, A) ;
	p = csymamd (B, [tol 0], cmember) ;	   check_perm (p, A) ;
	p = ccolamd (A, [0 tol -1 1], cmember) ;   check_perm (p, A) ;
	p = ccolamd (A, [0 -1 tol 1], cmember) ;   check_perm (p, A) ;

	p = ccolamd (A, [ ], [ ]) ;	       check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 1], [ ]) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1], [ ]) ;  check_perm (p, A) ;
	p = ccolamd (A, [1 tol tol 0], [ ]) ;  check_perm (p, A) ;
	p = ccolamd (A, [0 tol tol 1], [ ]) ;  check_perm (p, A) ;
	p = csymamd (A, [tol 1], [ ]) ;	       check_perm (p, A) ;
	p = csymamd (A, tol, [ ]) ;	       check_perm (p, A) ;
	p = csymamd (A, [ ], [ ]) ;	       check_perm (p, A) ;
	p = csymamd (B, [tol 0], [ ]) ;	       check_perm (p, A) ;
	p = ccolamd (A, [0 tol -1 1], [ ]) ;   check_perm (p, A) ;
	p = ccolamd (A, [0 -1 tol 1], [ ]) ;   check_perm (p, A) ;

    end
end
fprintf (' OK\n') ;

fprintf ('General matrices\n') ;
for trial = 1:400

    waitbar (trial/400, h, 'CCOLAMD: with dense rows/cols') ;
    fprintf ('.') ;

    % matrix of random mtype
    mtype = irand (3) ;
    A = rand_matrix (2000, 2000, mtype, 0, 0) ;
    p = ccolamd (A) ;
    check_perm (p, A) ;

    if (mtype == 3)
	p = csymamd (A) ;
	check_perm (p, A) ;
    end

end
fprintf (' OK\n') ;



fprintf ('Test error handling with invalid inputs\n') ;

% Check different erroneous input.
for trial = 1:30

    waitbar (trial/30, h, 'CCOLAMD: error handling') ;

    A = rand_matrix (1000, 1000, 2, 0, 0) ;

    for err = 1:13
        fprintf ('.') ;

        p = Tcolamd (A, [ccolamd_default_knobs 1 err], [ ]) ;
        if (p(1) ~= -1)							    %#ok
	    check_perm (p, A) ;
	end

	if (err == 1)
	    % check different (valid) input args to ccolamd
	    p = Acolamd (A) ;
	    p2 = Acolamd (A, [ccolamd_default_knobs 0 0]) ;
	    if (any (p ~= p2))
		error ('ccolamd: mismatch 1!') ;
	    end
	end

	B = A'*A ;
        p = Tsymamd (B, [-1 1 0 err], [ ]) ;
        if (p(1) ~= -1)							    %#ok
	    check_perm (p, A) ;
	end

	if (err == 1)

	    % check different (valid) input args to csymamd
	    p = Asymamd (B) ;
	    check_perm (p, A) ;
	    p2 = Asymamd (B, [csymamd_default_knobs 0]) ;
	    if (any (p ~= p2))
		error ('symamd: mismatch 1!') ;
	    end
	end

    end

end
fprintf (' OK\n') ;

fprintf ('Matrices with a few empty columns\n') ;

for trial = 1:400

    waitbar (trial/400, h, 'CCOLAMD: with empty rows/cols') ;

    % some are square, some are rectangular
    n = 0 ;
    while (n < 5)
	A = rand_matrix (1000, 1000, irand (2), 0, 0) ;
	[m n] = size (A) ;
    end

    % Add 5 null columns at random locations.
    null_col = randperm (n) ;
    A (:, null_col) = 0 ;

    % Order the matrix and make sure that the null columns are ordered last.
    p = ccolamd (A, [1 1e6 1e6 0]) ;
    check_perm (p, A) ;

    % find all null columns in A
    null_col = find (sum (spones (A), 1) == 0) ;
    nnull = length (null_col) ;
    if (any (null_col ~= p ((n-nnull+1):n)))
	error ('ccolamd: Null cols are not ordered last in natural order') ;
    end

end
fprintf (' OK\n') ;

fprintf ('Matrices with a few empty rows and columns\n') ;

for trial = 1:400

    waitbar (trial/400, h, 'CCOLAMD: with empty rows/cols') ;
    fprintf ('.') ;

    % symmetric matrices
    n = 0 ;
    while (n < 5)
	A = rand_matrix (1000, 1000, 3, 0, 0) ;
	[m n] = size (A) ;
    end

    % Add 5 null columns and rows at random locations.
    null_col = randperm (n) ;
    A (:, null_col) = 0 ;
    A (null_col, :) = 0 ;

    % Order the matrix and make sure that the null rows/cols are ordered last.
    p = csymamd (A, -1) ;
    check_perm (p, A) ;

    % find all null rows/columns in A
    Alo = tril (A, -1) ;
    null_col = ...
	find ((sum (spones (Alo), 1) == 0) & (sum (spones (Alo), 2) == 0)') ;
    nnull = length (null_col) ;
    if (any (null_col ~= p ((n-nnull+1):n)))
	error ('csymamd: Null cols are not ordered last in natural order') ;
    end

end
fprintf (' OK\n') ;

fprintf ('Matrices with a few empty rows\n') ;

% Test matrices with null rows inserted.

for trial = 1:400

    waitbar (trial/400, h, 'CCOLAMD: with null rows') ;
    fprintf ('.') ;
    m = 0 ;
    while (m < 5)
	A = rand_matrix (1000, 1000, 2, 0, 0) ;
	m = size (A,1) ;
    end

    % Add 5 null rows at random locations.
    null_row = randperm (m) ;
    null_row = sort (null_row (1:5)) ;
    A (null_row, :) = 0 ;

    p = ccolamd (A) ;
    check_perm (p, A) ;

end
fprintf (' OK\n') ;

fprintf ('\nccolamd and csymamd:  all tests passed\n\n') ;
close (h) ;

%-------------------------------------------------------------------------------

function p = Acolamd (S, knobs)
% Acolamd:  compare ccolamd and Tcolamd results

global ccolamd_default_knobs

if (nargin < 2)
    p = ccolamd (S) ;
    p1 = Tcolamd (S, [ccolamd_default_knobs 0 0], [ ]) ;
else
    p = ccolamd (S, knobs) ;
    p1 = Tcolamd (S, knobs, [ ]) ;
end

check_perm (p, S) ;
check_perm (p1, S) ;

if (any (p1 ~= p))
    narg = nargin ;
    if (nargin == 2)
	save bad S narg knobs
    else
	save bad S narg
    end
    error ('Acolamd mismatch!') ;
end

%-------------------------------------------------------------------------------

function p = Asymamd (S, knobs)
% Asymamd:  compare csymamd and Tsymamd results

global csymamd_default_knobs

if (nargin < 2)
    p = csymamd (S) ;
    p1 = Tsymamd (S, [csymamd_default_knobs 0], [ ]) ;
else
    p = csymamd (S, knobs) ;
    p1 = Tsymamd (S, knobs, [ ]) ;
end

if (any (p1 ~= p))
    error ('Asymamd mismatch!') ;
end


%-------------------------------------------------------------------------------

function check_perm (p, A, cmember)
% check_perm:  check for a valid permutation vector

if (isempty (A) & isempty (p))						    %#ok
    % empty permutation vectors of empty matrices are OK
    return
end

if (isempty (p))
    error ('Bad permutation: cannot be empty') ;
end

[m n] = size (A) ;
[p_m p_n] = size (p) ;
if (p_n == 1)
    % force p to be a row vector
    p = p' ;
    [p_m p_n] = size (p) ;
end

if (n ~= p_n)
    error ('Bad permutation: wrong size') ;
end

if (p_m ~= 1) ;
    % p must be a vector
    error ('Bad permutation: not a vector') ;
else
    if (any (sort (p) - (1:p_n)))
	error ('Bad permutation') ;
    end
end

if (nargin > 2)
    % check cmember
    c = cmember (p) ;
    % c must be monotonically non-decreasing
    c = diff (c) ;
    if (any (c < 0))
	error ('permutation breaks the cmember constraints') ;
    end
end

%-------------------------------------------------------------------------------

function i = irand (n,s)
% irand: return a random vector of size s, with values between 1 and n
if (nargin == 1)
    s = 1 ;
end
i = min (n, 1 + floor (rand (1,s) * n)) ;

%-------------------------------------------------------------------------------

function A = rand_matrix (n_max, m_max, mtype, d_rows, d_cols)
% rand_matrix:  return a random sparse matrix
%
% A = rand_matrix (n_max, m_max, mtype, d_rows, d_cols)
%
% A binary matrix of random size, at most n_max-by-m_max, with d_rows dense rows
% and d_cols dense columns.
%
% mtype 1: square unsymmetric (m_max is ignored)
% mtype 2: rectangular
% mtype 3: symmetric (m_max is ignored)

n = irand (n_max) ;
if (mtype ~= 2)
    % square
    m = n ;
else
    m = irand (m_max) ;
end

A = sprand (m, n, 10 / max (m,n)) ;

if (d_rows > 0)
    % add dense rows
    for k = 1:d_rows
	i = irand (m) ;
	nz = irand (n) ;
	p = randperm (n) ;
	p = p (1:nz) ;
	A (i,p) = 1 ;
    end
end

if (d_cols > 0)
    % add dense cols
    for k = 1:d_cols
	j = irand (n) ;
	nz = irand (m) ;
	p = randperm (m) ;
	p = p (1:nz) ;
	A (p,j) = 1 ;
    end
end

A = spones (A) ;

% ensure that there are no empty columns
d = find (full (sum (A,1)) == 0) ;			    %#ok
A (m,d) = 1 ;						    %#ok

% ensure that there are no empty rows
d = find (full (sum (A,2)) == 0) ;			    %#ok
A (d,n) = 1 ;						    %#ok

if (mtype == 3)
    % symmetric
    A = A + A' + speye (n) ;
end

A = spones (A) ;

%-------------------------------------------------------------------------------
% Tcolamd:  run ccolamd in a testing mode
%-------------------------------------------------------------------------------

function p = Tcolamd (S, knobs, cmember)

% knobs (5) = 1 ;
p = ccolamdtestmex (S, knobs, cmember) ;

if (p (1) ~= -1)
    check_perm (p, S) ;
end


%-------------------------------------------------------------------------------
% Tsymamd: run csymamd in a testing mode
%-------------------------------------------------------------------------------

function p = Tsymamd (S, knobs, cmember)

% knobs (2) = 1 ;
p = csymamdtestmex (S, knobs, cmember) ;

if (p (1) ~= -1)
    check_perm (p, S) ;
end

