/* glpqmd.c */

/*----------------------------------------------------------------------
-- This code is part of GNU Linear Programming Kit (GLPK).
--
-- THIS CODE IS THE RESULT OF TRANSLATION OF THE FORTRAN SUBROUTINES
-- GENQMD, QMDRCH, QMDQT, QMDUPD, AND QMDMRG FROM THE BOOK:
--
-- ALAN GEORGE, JOSEPH W-H LIU. COMPUTER SOLUTION OF LARGE SPARSE
-- POSITIVE DEFINITE SYSTEMS. PRENTICE-HALL, 1981.
--
-- THE TRANSLATION HAS BEEN DONE WITH THE PERMISSION OF THE AUTHORS
-- OF THE ORIGINAL FORTRAN SUBROUTINES: ALAN GEORGE AND JOSEPH LIU,
-- UNIVERSITY OF WATERLOO, WATERLOO, ONTARIO, CANADA.
--
-- The translation was made by Andrew Makhorin <mao@mai2.rcnet.ru>.
--
-- GLPK is free software; you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2, or (at your option)
-- any later version.
--
-- GLPK is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
-- License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with GLPK; see the file COPYING. If not, write to the Free
-- Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
-- 02110-1301, USA.
----------------------------------------------------------------------*/

#include "glpqmd.h"

/*----------------------------------------------------------------------
-- genqmd - GENeral Quotient Minimum Degree algorithm.
--
-- *Synopsis*
--
-- #include "glpqmd.h"
-- void genqmd(int *neqns, int xadj[], int adjncy[], int perm[],
--    int invp[], int deg[], int marker[], int rchset[], int nbrhd[],
--    int qsize[], int qlink[], int *nofsub);
--
-- *Purpose*
--
-- This routine implements the minimum degree algorithm. It makes use
-- of the implicit representation of the elimination graph by quotient
-- graphs, and the notion of indistinguishable nodes.
--
-- *Caution*
--
-- The adjancy vector adjncy will be destroyed.
--
-- *Input parameters*
--
-- neqns  - number of equations;
-- (xadj, adjncy) -
--          the adjancy structure.
--
-- *Output parameters*
--
-- perm   - the minimum degree ordering;
-- invp   - the inverse of perm.
--
-- *Working parameters*
--
-- deg    - the degree vector. deg[i] is negative means node i has been
--          numbered;
-- marker - a marker vector, where marker[i] is negative means node i
--          has been merged with another nodeand thus can be ignored;
-- rchset - vector used for the reachable set;
-- nbrhd  - vector used for neighborhood set;
-- qsize  - vector used to store the size of indistinguishable
--          supernodes;
-- qlink  - vector used to store indistinguishable nodes, i, qlink[i],
--          qlink[qlink[i]], ... are the members of the supernode
--          represented by i.
--
-- *Program subroutines*
--
-- qmdrch, qmdqt, qmdupd.
----------------------------------------------------------------------*/

void genqmd(int *_neqns, int xadj[], int adjncy[], int perm[],
      int invp[], int deg[], int marker[], int rchset[], int nbrhd[],
      int qsize[], int qlink[], int *_nofsub)
{     int inode, ip, irch, j, mindeg, ndeg, nhdsze, node, np, num,
         nump1, nxnode, rchsze, search, thresh;
#     define neqns  (*_neqns)
#     define nofsub (*_nofsub)
      /* Initialize degree vector and other working variables. */
      mindeg = neqns;
      nofsub = 0;
      for (node = 1; node <= neqns; node++)
      {  perm[node] = node;
         invp[node] = node;
         marker[node] = 0;
         qsize[node] = 1;
         qlink[node] = 0;
         ndeg = xadj[node+1] - xadj[node];
         deg[node] = ndeg;
         if (ndeg < mindeg) mindeg = ndeg;
      }
      num = 0;
      /* Perform threshold search to get a node of min degree.
         Variable search point to where search should start. */
s200: search = 1;
      thresh = mindeg;
      mindeg = neqns;
s300: nump1 = num + 1;
      if (nump1 > search) search = nump1;
      for (j = search; j <= neqns; j++)
      {  node = perm[j];
         if (marker[node] >= 0)
         {  ndeg = deg[node];
            if (ndeg <= thresh) goto s500;
            if (ndeg < mindeg) mindeg = ndeg;
         }
      }
      goto s200;
      /* Node has minimum degree. Find its reachable sets by calling
         qmdrch. */
s500: search = j;
      nofsub += deg[node];
      marker[node] = 1;
      qmdrch(&node, xadj, adjncy, deg, marker, &rchsze, rchset, &nhdsze,
         nbrhd);
      /* Eliminate all nodes indistinguishable from node. They are given
         by node, qlink[node], ... . */
      nxnode = node;
s600: num++;
      np = invp[nxnode];
      ip = perm[num];
      perm[np] = ip;
      invp[ip] = np;
      perm[num] = nxnode;
      invp[nxnode] = num;
      deg[nxnode] = -1;
      nxnode = qlink[nxnode];
      if (nxnode > 0) goto s600;
      if (rchsze > 0)
      {  /* Update the degrees of the nodes in the reachable set and
            identify indistinguishable nodes. */
         qmdupd(xadj, adjncy, &rchsze, rchset, deg, qsize, qlink,
            marker, &rchset[rchsze+1], &nbrhd[nhdsze+1]);
         /* Reset marker value of nodes in reach set. Update threshold
            value for cyclic search. Also call qmdqt to form new
            quotient graph. */
         marker[node] = 0;
         for (irch = 1; irch <= rchsze; irch++)
         {  inode = rchset[irch];
            if (marker[inode] >= 0)
            {  marker[inode] = 0;
               ndeg = deg[inode];
               if (ndeg < mindeg) mindeg = ndeg;
               if (ndeg <= thresh)
               {  mindeg = thresh;
                  thresh = ndeg;
                  search = invp[inode];
               }
            }
         }
         if (nhdsze > 0)
            qmdqt(&node, xadj, adjncy, marker, &rchsze, rchset, nbrhd);
      }
      if (num < neqns) goto s300;
      return;
#     undef neqns
#     undef nofsub
}

/*----------------------------------------------------------------------
-- qmdrch - Quotient MD ReaCHable set.
--
-- *Synopsis*
--
-- #include "glpqmd.h"
-- void qmdrch(int *root, int xadj[], int adjncy[], int deg[],
--    int marker[], int *rchsze, int rchset[], int *nhdsze,
--    int nbrhd[]);
--
-- *Purpose*
--
-- This subroutine determines the reachable set of a node through a
-- given subset. The adjancy structure is assumed to be stored in a
-- quotient graph format.
--
-- *Input parameters*
--
-- root   - the given node not in the subset;
-- (xadj, adjncy) -
--          the adjancy structure pair;
-- deg    - the degree vector. deg[i] < 0 means the node belongs to the
--          given subset.
--
-- *Output parameters*
--
-- (rchsze, rchset) -
--          the reachable set;
-- (nhdsze, nbrhd) -
--          the neighborhood set.
--
-- *Updated parameters*
--
-- marker - the marker vector for reach and nbrhd sets. > 0 means the
--          node is in reach set. < 0 means the node has been merged
--          with others in the quotient or it is in nbrhd set.
----------------------------------------------------------------------*/

void qmdrch(int *_root, int xadj[], int adjncy[], int deg[],
      int marker[], int *_rchsze, int rchset[], int *_nhdsze,
      int nbrhd[])
{     int i, istop, istrt, j, jstop, jstrt, nabor, node;
#     define root   (*_root)
#     define rchsze (*_rchsze)
#     define nhdsze (*_nhdsze)
      /* Loop through the neighbors of root in the quotient graph. */
      nhdsze = 0;
      rchsze = 0;
      istrt = xadj[root];
      istop = xadj[root+1] - 1;
      if (istop < istrt) return;
      for (i = istrt; i <= istop; i++)
      {  nabor = adjncy[i];
         if (nabor == 0) return;
         if (marker[nabor] == 0)
         {  if (deg[nabor] >= 0)
            {  /* Include nabor into the reachable set. */
               rchsze++;
               rchset[rchsze] = nabor;
               marker[nabor] = 1;
               goto s600;
            }
            /* nabor has been eliminated. Find nodes reachable from
               it. */
            marker[nabor] = -1;
            nhdsze++;
            nbrhd[nhdsze] = nabor;
s300:       jstrt = xadj[nabor];
            jstop = xadj[nabor+1] - 1;
            for (j = jstrt; j <= jstop; j++)
            {  node = adjncy[j];
               nabor = - node;
               if (node < 0) goto s300;
               if (node == 0) goto s600;
               if (marker[node] == 0)
               {  rchsze++;
                  rchset[rchsze] = node;
                  marker[node] = 1;
               }
            }
         }
s600:    ;
      }
      return;
#     undef root
#     undef rchsze
#     undef nhdsze
}

/*----------------------------------------------------------------------
-- qmdqt - Quotient MD Quotient graph Transformation.
--
-- *Synopsis*
--
-- #include "glpqmd.h"
-- void qmdqt(int *root, int xadj[], int adjncy[], int marker[],
--    int *rchsze, int rchset[], int nbrhd[]);
--
-- *Purpose*
--
-- This subroutine performs the quotient graph transformation after a
-- node has been eliminated.
--
-- *Input parameters*
--
-- root   - the node just eliminated. It becomes the representative of
--          the new supernode;
-- (xadj, adjncy) -
--          the adjancy structure;
-- (rchsze, rchset) -
--          the reachable set of root in the old quotient graph;
-- nbrhd  - the neighborhood set which will be merged with root to form
--          the new supernode;
-- marker - the marker vector.
--
-- *Updated parameter*
--
-- adjncy - becomes the adjncy of the quotient graph.
----------------------------------------------------------------------*/

void qmdqt(int *_root, int xadj[], int adjncy[], int marker[],
      int *_rchsze, int rchset[], int nbrhd[])
{     int inhd, irch, j, jstop, jstrt, link, nabor, node;
#     define root   (*_root)
#     define rchsze (*_rchsze)
      irch = 0;
      inhd = 0;
      node = root;
s100: jstrt = xadj[node];
      jstop = xadj[node+1] - 2;
      if (jstop >= jstrt)
      {  /* Place reach nodes into the adjacent list of node. */
         for (j = jstrt; j <= jstop; j++)
         {  irch++;
            adjncy[j] = rchset[irch];
            if (irch >= rchsze) goto s400;
         }
      }
      /* Link to other space provided by the nbrhd set. */
      link = adjncy[jstop+1];
      node = - link;
      if (link >= 0)
      {  inhd++;
         node = nbrhd[inhd];
         adjncy[jstop+1] = - node;
      }
      goto s100;
      /* All reachable nodes have been saved. End the adjacent list.
         Add root to the neighborhood list of each node in the reach
         set. */
s400: adjncy[j+1] = 0;
      for (irch = 1; irch <= rchsze; irch++)
      {  node = rchset[irch];
         if (marker[node] >= 0)
         {  jstrt = xadj[node];
            jstop = xadj[node+1] - 1;
            for (j = jstrt; j <= jstop; j++)
            {  nabor = adjncy[j];
               if (marker[nabor] < 0)
               {  adjncy[j] = root;
                  goto s600;
               }
            }
         }
s600:    ;
      }
      return;
#     undef root
#     undef rchsze
}

/*----------------------------------------------------------------------
-- qmdupd - Quotient MD UPDate.
--
-- *Synopsis*
--
-- #include "glpqmd.h"
-- void qmdupd(int xadj[], int adjncy[], int *nlist, int list[],
--    int deg[], int qsize[], int qlink[], int marker[], int rchset[],
--    int nbrhd[]);
--
-- *Purpose*
--
-- This routine performs degree update for a set of nodes in the minimum
-- degree algorithm.
--
-- *Input parameters*
--
-- (xadj, adjncy) -
--          the adjancy structure;
-- (nlist, list) -
--          the list of nodes whose degree has to be updated.
--
-- *Updated parameters*
--
-- deg    - the degree vector;
-- qsize  - size of indistinguishable supernodes;
-- qlink  - linked list for indistinguishable nodes;
-- marker - used to mark those nodes in reach/nbrhd sets.
--
-- *Working parameters*
--
-- rchset - the reachable set;
-- nbrhd  - the neighborhood set.
--
-- *Program subroutines*
--
-- qmdmrg.
----------------------------------------------------------------------*/

void qmdupd(int xadj[], int adjncy[], int *_nlist, int list[],
      int deg[], int qsize[], int qlink[], int marker[], int rchset[],
      int nbrhd[])
{     int deg0, deg1, il, inhd, inode, irch, j, jstop, jstrt, mark,
         nabor, nhdsze, node, rchsze;
#     define nlist  (*_nlist)
      /* Find all eliminated supernodes that are adjacent to some nodes
         in the given list. Put them into (nhdsze, nbrhd). deg0 contains
         the number of nodes in the list. */
      if (nlist <= 0) return;
      deg0 = 0;
      nhdsze = 0;
      for (il = 1; il <= nlist; il++)
      {  node = list[il];
         deg0 += qsize[node];
         jstrt = xadj[node];
         jstop = xadj[node+1] - 1;
         for (j = jstrt; j <= jstop; j++)
         {  nabor = adjncy[j];
            if (marker[nabor] == 0 && deg[nabor] < 0)
            {  marker[nabor] = -1;
               nhdsze++;
               nbrhd[nhdsze] = nabor;
            }
         }
      }
      /* Merge indistinguishable nodes in the list by calling the
         subroutine qmdmrg. */
      if (nhdsze > 0)
         qmdmrg(xadj, adjncy, deg, qsize, qlink, marker, &deg0, &nhdsze,
            nbrhd, rchset, &nbrhd[nhdsze+1]);
      /* Find the new degrees of the nodes that have not been merged. */
      for (il = 1; il <= nlist; il++)
      {  node = list[il];
         mark = marker[node];
         if (mark == 0 || mark == 1)
         {  marker[node] = 2;
            qmdrch(&node, xadj, adjncy, deg, marker, &rchsze, rchset,
               &nhdsze, nbrhd);
            deg1 = deg0;
            if (rchsze > 0)
            {  for (irch = 1; irch <= rchsze; irch++)
               {  inode = rchset[irch];
                  deg1 += qsize[inode];
                  marker[inode] = 0;
               }
            }
            deg[node] = deg1 - 1;
            if (nhdsze > 0)
            {  for (inhd = 1; inhd <= nhdsze; inhd++)
               {  inode = nbrhd[inhd];
                  marker[inode] = 0;
               }
            }
         }
      }
      return;
#     undef nlist
}

/*----------------------------------------------------------------------
-- qmdmrg - Quotient MD MeRGe.
--
-- *Synopsis*
--
-- #include "qmdmrg.h"
-- void qmdmrg(int xadj[], int adjncy[], int deg[], int qsize[],
--    int qlink[], int marker[], int *deg0, int *nhdsze, int nbrhd[],
--    int rchset[], int ovrlp[]);
--
-- *Purpose*
--
-- This routine merges indistinguishable nodes in the minimum degree
-- ordering algorithm. It also computes the new degrees of these new
-- supernodes.
--
-- *Input parameters*
--
-- (xadj, adjncy) -
--          the adjancy structure;
-- deg0   - the number of nodes in the given set;
-- (nhdsze, nbrhd) -
--          the set of eliminated supernodes adjacent to some nodes in
--          the set.
--
-- *Updated parameters*
--
-- deg    - the degree vector;
-- qsize  - size of indistinguishable nodes;
-- qlink  - linked list for indistinguishable nodes;
-- marker - the given set is given by those nodes with marker value set
--          to 1. Those nodes with degree updated will have marker value
--          set to 2.
--
-- *Working parameters*
--
-- rchset - the reachable set;
-- ovrlp  - temp vector to store the intersection of two reachable sets.
----------------------------------------------------------------------*/

void qmdmrg(int xadj[], int adjncy[], int deg[], int qsize[],
      int qlink[], int marker[], int *_deg0, int *_nhdsze, int nbrhd[],
      int rchset[], int ovrlp[])
{     int deg1, head, inhd, iov, irch, j, jstop, jstrt, link, lnode,
         mark, mrgsze, nabor, node, novrlp, rchsze, root;
#     define deg0   (*_deg0)
#     define nhdsze (*_nhdsze)
      /* Initialization. */
      if (nhdsze <= 0) return;
      for (inhd = 1; inhd <= nhdsze; inhd++)
      {  root = nbrhd[inhd];
         marker[root] = 0;
      }
      /* Loop through each eliminated supernode in the set
         (nhdsze, nbrhd). */
      for (inhd = 1; inhd <= nhdsze; inhd++)
      {  root = nbrhd[inhd];
         marker[root] = -1;
         rchsze = 0;
         novrlp = 0;
         deg1 = 0;
s200:    jstrt = xadj[root];
         jstop = xadj[root+1] - 1;
         /* Determine the reachable set and its intersection with the
            input reachable set. */
         for (j = jstrt; j <= jstop; j++)
         {  nabor = adjncy[j];
            root = - nabor;
            if (nabor < 0) goto s200;
            if (nabor == 0) break;
            mark = marker[nabor];
            if (mark == 0)
            {  rchsze++;
               rchset[rchsze] = nabor;
               deg1 += qsize[nabor];
               marker[nabor] = 1;
            }
            else if (mark == 1)
            {  novrlp++;
               ovrlp[novrlp] = nabor;
               marker[nabor] = 2;
            }
         }
         /* From the overlapped set, determine the nodes that can be
            merged together. */
         head = 0;
         mrgsze = 0;
         for (iov = 1; iov <= novrlp; iov++)
         {  node = ovrlp[iov];
            jstrt = xadj[node];
            jstop = xadj[node+1] - 1;
            for (j = jstrt; j <= jstop; j++)
            {  nabor = adjncy[j];
               if (marker[nabor] == 0)
               {  marker[node] = 1;
                  goto s1100;
               }
            }
            /* Node belongs to the new merged supernode. Update the
               vectors qlink and qsize. */
            mrgsze += qsize[node];
            marker[node] = -1;
            lnode = node;
s900:       link = qlink[lnode];
            if (link > 0)
            {  lnode = link;
               goto s900;
            }
            qlink[lnode] = head;
            head = node;
s1100:      ;
         }
         if (head > 0)
         {  qsize[head] = mrgsze;
            deg[head] = deg0 + deg1 - 1;
            marker[head] = 2;
         }
         /* Reset marker values. */
         root = nbrhd[inhd];
         marker[root] = 0;
         if (rchsze > 0)
         {  for (irch = 1; irch <= rchsze; irch++)
            {  node = rchset[irch];
               marker[node] = 0;
            }
         }
      }
      return;
#     undef deg0
#     undef nhdsze
}

/* eof */
