/* ========================================================================== */
/* === MAXTRANS ============================================================= */
/* ========================================================================== */
/* Finds a column permutation that maximizes the number of entries on the
* diagonal of a sparse matrix. See btf.h for more information.
*/
#include "btf.h"
#include "btf_internal.h"
#ifndef RECURSIVE
/* ========================================================================== */
/* === dfs: non-recursive version (default) ================================= */
/* ========================================================================== */
/* Perform a depth-first-search starting at column k, to find an augmenting
* path. An augmenting path is a sequence of row/column pairs (i1,k), (i2,j1),
* (i3,j2), ..., (i(s+1), js), such that all of the following properties hold:
*
* * column k is not matched to any row
* * entries in the path are nonzero
* * the pairs (i1,j1), (i2,j2), (i3,j3) ..., (is,js) have been
* previously matched to each other
* * (i(s+1), js) is nonzero, and row i(s+1) is not matched to any column
*
* Once this path is found, the matching can be changed to the set of pairs
* path. An augmenting path is a sequence of row/column pairs
*
* (i1,k), (i2,j1), (i3,j2), ..., (i(s+1), js)
*
* Once a row is matched with a column it remains matched with some column, but
* not necessarily the column it was first matched with.
*
* This routine is very similar to the dfs routine in klu_kernel.c, in the
* KLU sparse LU factorization package.
*
* The algorithm is based on the paper "On Algorithms for obtaining a maximum
* transversal" by Iain Duff, ACM Trans. Mathematical Software, vol 7, no. 1,
* pp. 315-330, and "Algorithm 575: Permutations for a zero-free diagonal",
* same issue, pp. 387-390. The code here is a new implementation of that
* algorithm, with different data structures and control flow. After writing
* this code, I carefully compared my algorithm with MC21A/B (ACM Algorithm 575)
* Some of the comparisons are partial because I didn't dig deeply into all of
* the details of MC21A/B, such as how the stack is maintained. The following
* arguments are essentially identical between this code and MC21A:
*
* maxtrans MC21A,B
* -------- -------
* n N identical
* k JORD identical
* Ap IP column / row pointers
* Ai ICN row / column indices
* Ap[n] LICN length of index array (# of nonzeros in A)
* Match IPERM output column / row permutation
* nfound NUMNZ # of nonzeros on diagonal of permuted matrix
* Flag CV mark a node as visited by the depth-first-search
*
* The following are different, but analogous:
*
* Cheap ARP indicates what part of the a column / row has
* already been matched.
*
* The following arguments are very different:
*
* - LENR # of entries in each row/column (unused in maxtrans)
* Pstack OUT Pstack keeps track of where we are in the depth-
* first-search scan of column j. I think that OUT
* plays a similar role in MC21B, but I'm unsure.
* Istack PR keeps track of the rows in the path. PR is a link
* list, though, whereas Istack is a stack. Maxtrans
* does not use any link lists.
* Jstack OUT? PR? the stack for nodes in the path (unsure)
*
* The following control structures are roughly comparable:
*
* maxtrans MC21B
* -------- -----
* for (k = 0 ; k < n ; k++) DO 100 JORD=1,N
* while (head >= 0) DO 70 K=1,JORD
* for (p = Cheap [j] ; ...) DO 20 II=IN1,IN2
* for (p = head ; ...) DO 90 K=1,JORD
*/
static int dfs
(
int k, /* which stage of the main loop we're in */
int Ap [ ], /* column pointers, size n+1 */
int Ai [ ], /* row indices, size nz = Ap [n] */
int Match [ ], /* size n, Match [i] = j if col j matched to i */
int Cheap [ ], /* rows Ai [Ap [j] .. Cheap [j]-1] alread matched */
int Flag [ ], /* Flag [j] = k if j already visited this stage */
int Istack [ ], /* size n. Row index stack. */
int Jstack [ ], /* size n. Column index stack. */
int Pstack [ ] /* size n. Keeps track of position in adjacency list */
)
{
/* local variables, but "global" to all DFS levels: */
int found ; /* true if match found. */
int head ; /* top of stack */
/* variables that are purely local to any one DFS level: */
int j2 ; /* the next DFS goes to node j2 */
int pend ; /* one past the end of the adjacency list for node j */
/* variables that need to be pushed then popped from the stack: */
int i ; /* the row tentatively matched to i if DFS successful */
int j ; /* the DFS is at the current node j */
int p ; /* current index into the adj. list for node j */
/* the variables i, j, and p are stacked in Istack, Jstack, and Pstack */
/* start a DFS to find a match for column k */
found = FALSE ;
i = EMPTY ;
head = 0 ;
Jstack [0] = k ;
ASSERT (Flag [k] != k) ;
while (head >= 0)
{
j = Jstack [head] ;
pend = Ap [j+1] ;
if (Flag [j] != k) /* a node is not yet visited */
{
/* -------------------------------------------------------------- */
/* prework for node j */
/* -------------------------------------------------------------- */
/* first time that j has been visited */
Flag [j] = k ;
/* cheap assignment: find the next unmatched row in col j */
for (p = Cheap [j] ; p < pend && !found ; p++)
{
i = Ai [p] ;
found = (Match [i] == EMPTY) ;
}
Cheap [j] = p ;
/* -------------------------------------------------------------- */
/* prepare for DFS */
if (found)
{
/* end of augmenting path, column j matched with row i */
Istack [head] = i ;
break ;
}
/* set Pstack [head] to the first entry in column j to scan */
Pstack [head] = Ap [j] ;
}
/* ------------------------------------------------------------------ */
/* DFS for nodes adjacent to j */
/* ------------------------------------------------------------------ */
/* If cheap assignment not made, continue the dfs. All rows in column
* j are already matched. Add the adjacent nodes to the stack by
* iterating through until finding another non-visited node. */
for (p = Pstack [head] ; p < pend ; p++)
{
i = Ai [p] ;
j2 = Match [i] ;
ASSERT (j2 != EMPTY) ;
if (Flag [j2] != k)
{
/* Node j2 is not yet visited, start a dfs on node j2.
* Keep track of where we left off in the scan of adjacency list
* of node j so we can restart j where we left off. */
Pstack [head] = p + 1 ;
/* Push j2 onto the stack and immediately break
* so we can recurse on node j2. Also keep track of row i
* which (if this dfs works) will be matched with the
* current node j. */
Istack [head] = i ;
Jstack [++head] = j2 ;
break ;
}
}
/* node j is done, but the postwork is postponed - see below */
if (p == pend)
{
/* If all adjacent nodes of j are already visited, pop j from
* stack and continue. We failed to find a match. */
head-- ;
}
}
/* postwork for all nodes j in the stack */
/* unwind the path and make the corresponding matches */
if (found)
{
for (p = head ; p >= 0 ; p--)
{
j = Jstack [p] ;
i = Istack [p] ;
/* -------------------------------------------------------------- */
/* postwork for node j */
/* -------------------------------------------------------------- */
/* if found, match row i with column j */
Match [i] = j ;
}
}
return (found) ;
}
#else
/* ========================================================================== */
/* === dfs: recursive version (only for illustration) ======================= */
/* ========================================================================== */
/* The following is a recursive version of dfs, which computes identical results
* as the non-recursive dfs. It is included here because it is easier to read.
* Compare the comments in the code below with the identical comments in the
* non-recursive code above, and that will help you see the correlation between
* the two routines.
*
* This routine can cause stack overflow, and is thus not recommended for heavy
* usage, particularly for large matrices. To help in delaying stack overflow,
* global variables are used, reducing the amount of information each call to
* dfs places on the call/return stack (the integers i, j, p, and the return
* address). To try this version, compile the code with -DRECURSIVE or include
* the following line at the top of this file:
#define RECURSIVE
*/
static int found, *Ap, *Ai, *Match, k, *Cheap, *Flag ;
static void dfs
(
int j /* at column j in the DFS */
)
{
int p, i ;
/* ---------------------------------------------------------------------- */
/* prework for node j */
/* ---------------------------------------------------------------------- */
/* first time that j has been visited */
Flag [j] = k ;
/* cheap assignment: find the next unmatched row in col j */
for (p = Cheap [j] ; p < Ap [j+1] && !found ; p++)
{
i = Ai [p] ;
found = (Match [i] == EMPTY) ;
}
Cheap [j] = p ;
/* ---------------------------------------------------------------------- */
/* DFS for nodes adjacent to j */
/* ---------------------------------------------------------------------- */
/* If cheap assignment not made, continue the dfs. All rows in column
* j are already matched. Add the adjacent nodes to the stack by
* iterating through until finding another non-visited node. */
for (p = Ap [j] ; p < Ap [j+1] && !found ; p++)
{
i = Ai [p] ;
if (Flag [Match [i]] != k)
{
dfs (Match [i]) ;
}
}
/* ---------------------------------------------------------------------- */
/* postwork for node j */
/* ---------------------------------------------------------------------- */
/* if found, match row i with column j */
if (found)
{
Match [i] = j ;
}
}
#endif
/* ========================================================================== */
/* === maxtrans ============================================================= */
/* ========================================================================== */
#ifndef RECURSIVE
int maxtrans /* returns # of columns in the matching */
(
int nrow, /* A is nrow-by-ncol in compressed column form */
int ncol,
int Ap [ ], /* size ncol+1 */
int Ai [ ], /* size nz = Ap [ncol] */
/* output, not defined on input */
int Match [ ], /* size nrow. Match [i] = j if column j matched to row i */
/* workspace, not defined on input or output */
int Work [ ] /* size 5*ncol */
)
#else
int maxtrans /* recursive version - same as above except for Work size */
(
int nrow,
int ncol,
int Ap_in [ ],
int Ai_in [ ],
int Match_in [ ],
int Work [ ] /* size 2*ncol */
)
#endif
{
int i, j, nfound, nbadcol ;
#ifndef RECURSIVE
int k, *Cheap, *Flag, *Istack, *Jstack, *Pstack ;
#else
Ap = Ap_in ;
Ai = Ai_in ;
Match = Match_in ;
#endif
/* ---------------------------------------------------------------------- */
/* get workspace and initialize */
/* ---------------------------------------------------------------------- */
Cheap = Work ; Work += ncol ;
Flag = Work ; Work += ncol ;
#ifndef RECURSIVE
/* stack for non-recursive dfs */
Istack = Work ; Work += ncol ;
Jstack = Work ; Work += ncol ;
Pstack = Work ;
#endif
/* in column j, rows Ai [Ap [j] .. Cheap [j]-1] are known to be matched */
for (j = 0 ; j < ncol ; j++)
{
Cheap [j] = Ap [j] ;
Flag [j] = EMPTY ;
}
/* all rows and columns are currently unmatched */
for (i = 0 ; i < nrow ; i++)
{
Match [i] = EMPTY ;
}
/* ---------------------------------------------------------------------- */
/* find a matching row for each column k */
/* ---------------------------------------------------------------------- */
nfound = 0 ;
for (k = 0 ; k < ncol ; k++)
{
/* find an augmenting path to match some row i to column k */
#ifndef RECURSIVE
/* non-recursive dfs (default) */
if (dfs (k, Ap, Ai, Match, Cheap, Flag, Istack, Jstack, Pstack))
#else
/* recursive dfs (for illustration only) */
found = FALSE ;
dfs (k) ;
if (found)
#endif
{
/* we found it. Match [i] = k for some row i has been done. */
nfound++ ;
}
}
/* ---------------------------------------------------------------------- */
/* complete the permutation if the matrix is structurally singular */
/* ---------------------------------------------------------------------- */
/* note that at this point, all entries in Flag are less than ncol */
if (nfound < ncol)
{
/* flag all matched columns */
for (i = 0 ; i < nrow ; i++)
{
j = Match [i] ;
if (j != EMPTY)
{
/* printf ("i is OK %d matched to j %d\n", i, j) ; */
/* row i and column j are matched */
Flag [j] = ncol ;
}
}
/* make a list of all unmatched columns (use Cheap as workspace) */
nbadcol = 0 ;
for (j = 0 ; j < ncol ; j++)
{
if (Flag [j] != ncol)
{
Cheap [nbadcol++] = j ;
/* printf ("j is matched to nobody %d\n", j) ; */
}
}
ASSERT (nfound + nbadcol == ncol) ;
/* make an assignment for each unmatched row */
for (i = 0 ; i < nrow ; i++)
{
if (Match [i] == EMPTY && nbadcol > 0)
{
/* get an unmatched column j */
j = Cheap [--nbadcol] ;
/* assign j to row i and flag the entry by "flipping" it */
Match [i] = MAXTRANS_FLIP (j) ;
/* printf ("need to force i %d with j %d\n", i, j) ; */
}
}
}
/* ---------------------------------------------------------------------- */
/* return the Match, and the # of matches made */
/* ---------------------------------------------------------------------- */
/* At this point, row i is matched to j = Match [i] if j >= 0. i is an
* unmatched row if Match [i] < 0.
*
* the permutation can be recovered as follows: Row i is
* matched with column j, where j = MAXTRANS_UNFLIP (Match [i]) and where j
* will always be in the valid range 0 to ncol-1. The entry A(i,j) is zero
* if MAXTRANS_ISFLIPPED (Match [i]) is true, and nonzero otherwise. nfound
* is the number of entries in the Match array that are non-negative. */
return (nfound) ;
}
syntax highlighted by Code2HTML, v. 0.9.1