Amesos Package Browser (Single Doxygen Collection) Development
amesos_btf_maxtrans.c
Go to the documentation of this file.
00001 /* ========================================================================== */
00002 /* === BTF_MAXTRANS ========================================================= */
00003 /* ========================================================================== */
00004 
00005 /* Finds a column permutation that maximizes the number of entries on the
00006  * diagonal of a sparse matrix.  See btf.h for more information.
00007  *
00008  * This function is identical to cs_maxtrans in CSparse, with the following
00009  * exceptions:
00010  *
00011  *  (1) cs_maxtrans finds both jmatch and imatch, where jmatch [i] = j and
00012  *  imatch [j] = i if row i is matched to column j.  This function returns
00013  *  just jmatch (the Match array).  The MATLAB interface to cs_maxtrans
00014  *  (the single-output cs_dmperm) returns imatch, not jmatch to the MATLAB
00015  *  caller.
00016  *
00017  *  (2) cs_maxtrans includes a pre-pass that counts the number of non-empty
00018  *  rows and columns (m2 and n2, respectively), and computes the matching
00019  *  using the transpose of A if m2 < n2.  cs_maxtrans also returns quickly
00020  *  if the diagonal of the matrix is already zero-free.  This pre-pass
00021  *  allows cs_maxtrans to be much faster than maxtrans, if the use of the
00022  *  transpose is warranted.
00023  *
00024  *  However, for square structurally non-singular matrices with one or more
00025  *  zeros on the diagonal, the pre-pass is a waste of time, and for these
00026  *  matrices, maxtrans can be twice as fast as cs_maxtrans.  Since the
00027  *  maxtrans function is intended primarily for square matrices that are
00028  *  typically structurally nonsingular, the pre-pass is not included here.
00029  *  If this maxtrans function is used on a matrix with many more columns
00030  *  than rows, consider passing the transpose to this function, or use
00031  *  cs_maxtrans instead.
00032  *
00033  *  (3) cs_maxtrans can operate as a randomized algorithm, to help avoid
00034  *  rare cases of excessive run-time.
00035  *
00036  *  (4) this maxtrans function includes an option that limits the total work
00037  *  performed.  If this limit is reached, the maximum transveral might not
00038  *  be found.
00039  *
00040  * Thus, for general usage, cs_maxtrans is preferred.  For square matrices that
00041  * are typically structurally non-singular, maxtrans is preferred.  A partial
00042  * maxtrans can still be very useful when solving a sparse linear system.
00043  *
00044  * Copyright (c) 2004-2007.  Tim Davis, University of Florida,
00045  * with support from Sandia National Laboratories.  All Rights Reserved.
00046  */ 
00047 
00048 #include "amesos_btf_decl.h"
00049 #include "amesos_btf_internal.h"
00050 
00051 
00052 /* ========================================================================== */
00053 /* === augment ============================================================== */
00054 /* ========================================================================== */
00055 
00056 /* Perform a depth-first-search starting at column k, to find an augmenting
00057  * path.  An augmenting path is a sequence of row/column pairs (i1,k), (i2,j1),
00058  * (i3,j2), ..., (i(s+1), js), such that all of the following properties hold:
00059  *
00060  *  * column k is not matched to any row
00061  *  * entries in the path are nonzero
00062  *  * the pairs (i1,j1), (i2,j2), (i3,j3) ..., (is,js) have been 
00063  *      previously matched to each other
00064  *  * (i(s+1), js) is nonzero, and row i(s+1) is not matched to any column
00065  *
00066  * Once this path is found, the matching can be changed to the set of pairs
00067  * path.  An augmenting path is a sequence of row/column pairs
00068  *
00069  *  (i1,k), (i2,j1), (i3,j2), ..., (i(s+1), js)
00070  *
00071  * Once a row is matched with a column it remains matched with some column, but
00072  * not necessarily the column it was first matched with.
00073  *
00074  * In the worst case, this function can examine every nonzero in A.  Since it
00075  * is called n times by maxtrans, the total time of maxtrans can be as high as
00076  * O(n*nnz(A)).  To limit this work, pass a value of maxwork > 0.  Then at
00077  * most O((maxwork+1)*nnz(A)) work will be performed; the maximum matching might
00078  * not be found, however.
00079  *
00080  * This routine is very similar to the dfs routine in klu_kernel.c, in the
00081  * KLU sparse LU factorization package.  It is essentially identical to the
00082  * cs_augment routine in CSparse, and its recursive version (augment function
00083  * in cs_maxtransr_mex.c), except that this routine allows for the search to be
00084  * terminated early if too much work is being performed.
00085  *
00086  * The algorithm is based on the paper "On Algorithms for obtaining a maximum
00087  * transversal" by Iain Duff, ACM Trans. Mathematical Software, vol 7, no. 1,
00088  * pp. 315-330, and "Algorithm 575: Permutations for a zero-free diagonal",
00089  * same issue, pp. 387-390.  The code here is a new implementation of that
00090  * algorithm, with different data structures and control flow.  After writing
00091  * this code, I carefully compared my algorithm with MC21A/B (ACM Algorithm 575)
00092  * Some of the comparisons are partial because I didn't dig deeply into all of
00093  * the details of MC21A/B, such as how the stack is maintained.  The following
00094  * arguments are essentially identical between this code and MC21A:
00095  *
00096  * maxtrans MC21A,B
00097  * -------- -------
00098  * n    N     identical
00099  * k    JORD      identical
00100  * Ap   IP      column / row pointers
00101  * Ai   ICN     row / column indices
00102  * Ap[n]  LICN      length of index array (# of nonzeros in A)
00103  * Match  IPERM     output column / row permutation
00104  * nmatch NUMNZ     # of nonzeros on diagonal of permuted matrix
00105  * Flag   CV      mark a node as visited by the depth-first-search
00106  *
00107  * The following are different, but analogous:
00108  *
00109  * Cheap  ARP     indicates what part of the a column / row has
00110  *          already been matched.
00111  *
00112  * The following arguments are very different:
00113  *
00114  * -    LENR      # of entries in each row/column (unused in maxtrans)
00115  * Pstack OUT     Pstack keeps track of where we are in the depth-
00116  *          first-search scan of column j.  I think that OUT
00117  *          plays a similar role in MC21B, but I'm unsure.
00118  * Istack PR      keeps track of the rows in the path.  PR is a link
00119  *          list, though, whereas Istack is a stack.  Maxtrans
00120  *          does not use any link lists.
00121  * Jstack OUT? PR?    the stack for nodes in the path (unsure)
00122  *
00123  * The following control structures are roughly comparable:
00124  *
00125  * maxtrans     MC21B
00126  * --------     -----
00127  * for (k = 0 ; k < n ; k++)  DO 100 JORD=1,N
00128  * while (head >= 0)    DO 70 K=1,JORD
00129  * for (p = Cheap [j] ; ...)  DO 20 II=IN1,IN2
00130  * for (p = head ; ...)   DO 90 K=1,JORD
00131  */
00132 
00133 static Int amesos_augment
00134 (
00135     Int k,    /* which stage of the main loop we're in */
00136     Int Ap [ ],   /* column pointers, size n+1 */
00137     Int Ai [ ],   /* row indices, size nz = Ap [n] */
00138     Int Match [ ],  /* size n,  Match [i] = j if col j matched to i */
00139     Int Cheap [ ],  /* rows Ai [Ap [j] .. Cheap [j]-1] alread matched */
00140     Int Flag [ ], /* Flag [j] = k if j already visited this stage */
00141     Int Istack [ ], /* size n.  Row index stack. */
00142     Int Jstack [ ], /* size n.  Column index stack. */
00143     Int Pstack [ ], /* size n.  Keeps track of position in adjacency list */
00144     double *work, /* work performed by the depth-first-search */
00145     double maxwork  /* maximum work allowed */
00146 )
00147 {
00148     /* local variables, but "global" to all DFS levels: */
00149     Int found ; /* true if match found.  */
00150     Int head ;  /* top of stack */
00151 
00152     /* variables that are purely local to any one DFS level: */
00153     Int j2 ;  /* the next DFS goes to node j2 */
00154     Int pend ;  /* one past the end of the adjacency list for node j */
00155     Int pstart ;
00156     Int quick ;
00157 
00158     /* variables that need to be pushed then popped from the stack: */
00159     Int i ; /* the row tentatively matched to i if DFS successful */
00160     Int j ; /* the DFS is at the current node j */
00161     Int p ; /* current index into the adj. list for node j */
00162     /* the variables i, j, and p are stacked in Istack, Jstack, and Pstack */
00163 
00164     quick = (maxwork > 0) ;
00165 
00166     /* start a DFS to find a match for column k */
00167     found = FALSE ;
00168     i = EMPTY ;
00169     head = 0 ;
00170     Jstack [0] = k ;
00171     ASSERT (Flag [k] != k) ;
00172 
00173     while (head >= 0)
00174     {
00175   j = Jstack [head] ;
00176   pend = Ap [j+1] ;
00177 
00178   if (Flag [j] != k)      /* a node is not yet visited */
00179   {
00180 
00181       /* -------------------------------------------------------------- */
00182       /* prework for node j */
00183       /* -------------------------------------------------------------- */
00184 
00185       /* first time that j has been visited */
00186       Flag [j] = k ;
00187       /* cheap assignment: find the next unmatched row in col j.  This
00188        * loop takes at most O(nnz(A)) time for the sum total of all
00189        * calls to augment. */
00190       for (p = Cheap [j] ; p < pend && !found ; p++)
00191       {
00192     i = Ai [p] ;
00193     found = (Match [i] == EMPTY) ;
00194       }
00195       Cheap [j] = p ;
00196 
00197       /* -------------------------------------------------------------- */
00198 
00199       /* prepare for DFS */
00200       if (found)
00201       {
00202     /* end of augmenting path, column j matched with row i */
00203     Istack [head] = i ;
00204     break ;
00205       }
00206       /* set Pstack [head] to the first entry in column j to scan */
00207       Pstack [head] = Ap [j] ;
00208   }
00209 
00210   /* ------------------------------------------------------------------ */
00211   /* quick return if too much work done */
00212   /* ------------------------------------------------------------------ */
00213 
00214   if (quick && *work > maxwork)
00215   {
00216       /* too much work has been performed; abort the search */
00217       return (EMPTY) ;
00218   }
00219 
00220   /* ------------------------------------------------------------------ */
00221   /* DFS for nodes adjacent to j */
00222   /* ------------------------------------------------------------------ */
00223 
00224   /* If cheap assignment not made, continue the depth-first search.  All
00225    * rows in column j are already matched.  Add the adjacent nodes to the
00226    * stack by iterating through until finding another non-visited node.
00227    *
00228    * It is the following loop that can force maxtrans to take
00229    * O(n*nnz(A)) time. */
00230 
00231   pstart = Pstack [head] ;
00232   for (p = pstart ; p < pend ; p++)
00233   {
00234       i = Ai [p] ;
00235       j2 = Match [i] ;
00236       ASSERT (j2 != EMPTY) ;
00237       if (Flag [j2] != k)
00238       {
00239     /* Node j2 is not yet visited, start a depth-first search on
00240      * node j2.  Keep track of where we left off in the scan of adj
00241      * list of node j so we can restart j where we left off. */
00242     Pstack [head] = p + 1 ;
00243     /* Push j2 onto the stack and immediately break so we can
00244      * recurse on node j2.  Also keep track of row i which (if this
00245      * search for an augmenting path works) will be matched with the
00246      * current node j. */
00247     Istack [head] = i ;
00248     Jstack [++head] = j2 ;
00249     break ;
00250       }
00251   }
00252 
00253   /* ------------------------------------------------------------------ */
00254   /* determine how much work was just performed */
00255   /* ------------------------------------------------------------------ */
00256 
00257   *work += (p - pstart + 1) ;
00258 
00259   /* ------------------------------------------------------------------ */
00260   /* node j is done, but the postwork is postponed - see below */
00261   /* ------------------------------------------------------------------ */
00262 
00263   if (p == pend)
00264   {
00265       /* If all adjacent nodes of j are already visited, pop j from
00266        * stack and continue.  We failed to find a match. */
00267       head-- ;
00268   }
00269     }
00270 
00271     /* postwork for all nodes j in the stack */
00272     /* unwind the path and make the corresponding matches */
00273     if (found)
00274     {
00275   for (p = head ; p >= 0 ; p--)
00276   {
00277       j = Jstack [p] ;
00278       i = Istack [p] ;
00279 
00280       /* -------------------------------------------------------------- */
00281       /* postwork for node j */
00282       /* -------------------------------------------------------------- */
00283       /* if found, match row i with column j */
00284       Match [i] = j ;
00285   }
00286     }
00287     return (found) ;
00288 }
00289 
00290 
00291 /* ========================================================================== */
00292 /* === maxtrans ============================================================= */
00293 /* ========================================================================== */
00294 
00295 Int BTF(maxtrans)   /* returns # of columns in the matching */
00296 (
00297     /* --- input --- */
00298     Int nrow,     /* A is nrow-by-ncol in compressed column form */
00299     Int ncol,
00300     Int Ap [ ],     /* size ncol+1 */
00301     Int Ai [ ],     /* size nz = Ap [ncol] */
00302     double maxwork, /* do at most maxwork*nnz(A) work; no limit if <= 0.  This
00303          * work limit excludes the O(nnz(A)) cheap-match phase. */
00304 
00305     /* --- output --- */
00306     double *work,   /* work = -1 if maxwork > 0 and the total work performed
00307          * reached the maximum of maxwork*nnz(A)).
00308          * Otherwise, work = the total work performed. */
00309 
00310     Int Match [ ],  /* size nrow.  Match [i] = j if column j matched to row i */
00311 
00312     /* --- workspace --- */
00313     Int Work [ ]    /* size 5*ncol */
00314 )
00315 {
00316     Int *Cheap, *Flag, *Istack, *Jstack, *Pstack ;
00317     Int i, j, k, nmatch, work_limit_reached, result ;
00318 
00319     /* ---------------------------------------------------------------------- */
00320     /* get workspace and initialize */
00321     /* ---------------------------------------------------------------------- */
00322 
00323     Cheap  = Work ; Work += ncol ;
00324     Flag   = Work ; Work += ncol ;
00325 
00326     /* stack for non-recursive depth-first search in augment function */
00327     Istack = Work ; Work += ncol ;
00328     Jstack = Work ; Work += ncol ;
00329     Pstack = Work ;
00330 
00331     /* in column j, rows Ai [Ap [j] .. Cheap [j]-1] are known to be matched */
00332     for (j = 0 ; j < ncol ; j++)
00333     {
00334   Cheap [j] = Ap [j] ;
00335   Flag [j] = EMPTY ; 
00336     }
00337 
00338     /* all rows and columns are currently unmatched */
00339     for (i = 0 ; i < nrow ; i++)
00340     {
00341   Match [i] = EMPTY ;
00342     }
00343 
00344     if (maxwork > 0)
00345     {
00346   maxwork *= Ap [ncol] ;
00347     }
00348     *work = 0 ;
00349 
00350     /* ---------------------------------------------------------------------- */
00351     /* find a matching row for each column k */
00352     /* ---------------------------------------------------------------------- */
00353 
00354     nmatch = 0 ;
00355     work_limit_reached = FALSE ;
00356     for (k = 0 ; k < ncol ; k++)
00357     {
00358   /* find an augmenting path to match some row i to column k */
00359   result = amesos_augment (k, Ap, Ai, Match, Cheap, Flag, Istack, Jstack, Pstack,
00360       work, maxwork) ;
00361   if (result == TRUE)
00362   {
00363       /* we found it.  Match [i] = k for some row i has been done. */
00364       nmatch++ ;
00365   }
00366   else if (result == EMPTY)
00367   {
00368       /* augment gave up because of too much work, and no match found */
00369       work_limit_reached = TRUE ;
00370   }
00371     }
00372 
00373     /* ---------------------------------------------------------------------- */
00374     /* return the Match, and the # of matches made */
00375     /* ---------------------------------------------------------------------- */
00376 
00377     /* At this point, row i is matched to j = Match [i] if j >= 0.  i is an
00378      * unmatched row if Match [i] == EMPTY. */
00379 
00380     if (work_limit_reached)
00381     {
00382   /* return -1 if the work limit of maxwork*nnz(A) was reached */
00383   *work = EMPTY ;
00384     }
00385 
00386     return (nmatch) ;
00387 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines