/*****************************************************************************
*                                                                            *
*  Vertex-invariants source file for nauty 2.8.                              *
*                                                                            *
*   Copyright (1989-2022) Brendan McKay.  All rights reserved.               *
*   Subject to waivers and disclaimers in nauty.h.                           *
*                                                                            *
*   CHANGE HISTORY                                                           *
*       13-Mar-90 : initial release for version 1.5                          *
*       10-Nov-90 : changes for version 1.6 :                                *
*                 - added dummy routine nautinv_null()                       *
*       27-Aug-92 : renamed to version 1.7, no changes to this file          *
*        5-Jun-93 : renamed to version 1.7+, no changes to this file         *
*       18-Aug-93 : renamed to version 1.8, no changes to this file          *
*       17-Sep-93 : changes for version 1.9 :                                *
*                 - added invariant routine adjacencies()                    *
*       20-Jun-96 : changes for version 2.0 :                                *
*                 - added invariants cellfano() and cellfano2()              *
*       11-Jul-96 - added dynamic allocation                                 *
*       21-Oct-98 - use shortish in place of short for BIGNAUTY              *
*        9-Jan-00 - added nautinv_check()                                    *
*       12-Feb-00 - minor code formatting                                    *
*       16-Nov-00 - made changes listed in nauty.h                           *
*       22-Apr-01 : changes for version 2.1 :                                *
*                 - made all large dynamic memory external to routines       *
*                 - added nautinv_freedyn() to free all such memory          *
*                 - include nautinv.h rather than naututil.h                 *
*                 - removed nautinv_null()                                   *
*                 - added code to facilitate compilation into Magma          *
*                 - removed EXTDEFS                                          *
*       12-Jul-01 - use invararg in distances()                              *
*                 - fixed comments in ind and cliq routines                  *
*       21-Nov-01 : use NAUTYREQUIRED in nautinv_check()                     *
*       10-Dec-06 : remove BIGNAUTY                                          *
*       10-Nov-09 : remove types shortish and permutation                    *
*       23-Nov-09 : add refinvar()                                           *
*       12-Jun-10 : fixed identical errors in cellcliq() and cellind()       *
*       15-Jan-12 : add TLS_ATTR attributes                                  *
*       23-Aug-12 : fix getbigcells(), thanks to Fatih Demirkale             *
*       23-Jan-13 : add some parens to satisfy icc                           *
*                                                                            *
*****************************************************************************/

#define ONE_WORD_SETS
#include "nautinv.h"

#if  MAXM==1
#define M 1
#else
#define M m
#endif

#define MASH(l,i) ((((l) ^ 056345) + (i)) & 077777)
    /* : expression whose long value depends only on long l and int/long i.
         Anything goes, preferably non-commutative. */

#define CLEANUP(l) ((int)((l) % 077777))
    /* : expression whose value depends on long l and is less than 077777
         when converted to int then short.  Anything goes. */

#define ACCUM(x,y)   x = (((x) + (y)) & 077777)
    /* : must be commutative. */

static const int fuzz1[] = {037541,061532,005257,026416};
static const int fuzz2[] = {006532,070236,035523,062437};

#define FUZZ1(x) ((x) ^ fuzz1[(x)&3])
#define FUZZ2(x) ((x) ^ fuzz2[(x)&3])

#define MAXCLIQUE 10    /* max clique size for cliques() and maxindset() */

#if MAXN
static TLS_ATTR int workshort[MAXN+2];
static TLS_ATTR int vv[MAXN],ww[MAXN];
static TLS_ATTR int workperm[MAXN];
static TLS_ATTR int bucket[MAXN+2];
static TLS_ATTR int count[MAXN];
static TLS_ATTR set workset[MAXM];
static TLS_ATTR set w01[MAXM],w02[MAXM],w03[MAXM],w12[MAXM],w13[MAXM],w23[MAXM];
static TLS_ATTR set pt0[MAXM],pt1[MAXM],pt2[MAXM];
static TLS_ATTR set wss[MAXCLIQUE-1][MAXM];
static TLS_ATTR set ws1[MAXM],ws2[MAXM];
#else
DYNALLSTAT(int,workshort,workshort_sz);
DYNALLSTAT(int,vv,vv_sz);
DYNALLSTAT(int,ww,ww_sz);
DYNALLSTAT(int,workperm,workperm_sz);
DYNALLSTAT(int,bucket,bucket_sz);
DYNALLSTAT(int,count,count_sz);
DYNALLSTAT(set,ws1,ws1_sz);
DYNALLSTAT(set,ws2,ws2_sz);
DYNALLSTAT(set,workset,workset_sz);
DYNALLSTAT(set,w01,w01_sz);
DYNALLSTAT(set,w02,w02_sz);
DYNALLSTAT(set,w03,w03_sz);
DYNALLSTAT(set,w12,w12_sz);
DYNALLSTAT(set,w13,w13_sz);
DYNALLSTAT(set,w23,w23_sz);
DYNALLSTAT(set,pt0,pt0_sz);
DYNALLSTAT(set,pt1,pt1_sz);
DYNALLSTAT(set,pt2,pt2_sz);
DYNALLSTAT(set,wss,wss_sz);
#endif

/* aproto: header new_nauty_protos.h */

/*****************************************************************************
*                                                                            *
*  This file contains a number of procedures which compute vertex-invariants *
*  for stronger partition refinement.   Since entirely different             *
*  vertex-invariants seem to work better for different types of graph, we    *
*  cannot do more than give a small collection of representative examples.   *
*  Any serious computations with difficult graphs may well need to use       *
*  specially-written vertex-invariants.  The use of vertex-invariants        *
*  procedures is supported by nauty from version 1.5 onwards, via the        *
*  options userinvarproc, mininvarlevel, maxinvarlevel and invararg.         *
*  The meaning of these fields in detail are as follows:                     *
*     userinvarproc  is the address of the vertex-invariant procedure.  If   *
*                    no vertex-invariants is required, this field should     *
*                    have the value NULL.                                    *
*     maxinvarlevel  The absolute value of this is the maximum level in the  *
*                    search tree at which the vertex-invariant will be       *
*                    computed.  The root of the tree is at level 1, so the   *
*                    vertex-invariant will not be invoked at all if          *
*                    maxinvarlevel==0.  Negative values of maxinvarlevel     *
*                    request nauty to not compute the vertex-invariant at    *
*                    a level greater than that of the earliest node (if any) *
*                    on the path to the first leaf of the search tree at     *
*                    which the vertex-invariant refines the partition.       *
*     mininvarlevel  The absolute value of this is the minimum level in the  *
*                    search tree at which the vertex-invariant will be       *
*                    computed.  The root of the tree is at level 1, so there *
*                    is no effective limit if mininvarlevel is -1, 0 or 1.   *
*                    Negative values of mininvarlevel request nauty to not   *
*                    compute the vertex-invariant at a level less than       *
*                    that of the earliest node (if any) on the path to the   *
*                    first leaf of the search tree at which the              *
*                    vertex-invariant refines the partition.                 *
*     invararg       is passed to the vertex-invariant procedure via the     *
*                    argument of the same name.  It can be used by the       *
*                    procedure for any purpose.                              *
*  Note that negative values of maxinvarlevel and mininvarlevel make the     *
*  canonical labelling invalid, but can speed automorphism group finding.    *
*  Nauty already knows this and takes their absolute values.                 *
*                                                                            *
*  A vertex-invariant must be declared thus:                                 *
*  void invarproc(g,lab,ptn,level,numcells,tvpos,invar,invararg,digraph,m,n) *
*  All of these arguments must be treated as read-only except for invar.     *
*  g        : the graph, exactly as passed to nauty()                        *
*  lab,ptn  : the current partition nest (see nauty.h for the format)        *
*  level    : the level of this node in the search tree.                     *
*  numcells : the number of cells in the partition at this node.             *
*  tvpos    : the index in (lab,ptn) of one cell in the partition.           *
*             If level <= 1, the cell will be the first fragment of the      *
*             first active cell (as provided by the initial call to nauty),  *
*             or the first cell, if there were no active cells.              *
*             If level > 1, the cell will be the singleton cell which was    *
*             created to make this node of the search tree from its parent.  *
*  invararg : a copy of options.invararg                                     *
*  digraph  : a copy of options.digraph                                      *
*  m,n      : size parameters as passed to nauty()                           *
*  invar    : an array to return the answer in.   The procedure must put in  *
*             each invar[i]  (0 <= i < n)  an invariant of the 6-tuple       *
*             (<vertex i>,g,<the partition nest to this level>,level,        *
*               invararg,digraph)                                            *
*             Note that invar[] is declared as an int array.  Since the      *
*             absolute value of the invariant is irrelevant, only the        *
*             comparative values, any short, int or long value can be        *
*             assigned to the entries of invar[] without fear.  However,     *  
*             you should assign a value less than 077777 to ensure machine-  *
*             independence of the canonical labelling.                       *
*                                                                            *
*  The refinement procedure has already been called before the invariant     *
*  procedure is called.  That means that the partition is equitable if       *
*  digraph==FALSE.                                                           *
*                                                                            *
*****************************************************************************/

/*****************************************************************************
*                                                                            *
*  twopaths() assigns to each vertex v the sum of the weights of each vertex *
*  which can be reached from v along a walk of length two (including itself  *
*  usually).  The weight of each vertex w is defined as the ordinal number   *
*  of the cell containing w, starting at 1 for the first cell.               *
*                                                                            *
*****************************************************************************/

void
twopaths(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
         int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,v,w;
    int wt;
    set *gv,*gw;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"twopaths");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"twopaths");
#endif

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = wt;
        if (ptn[i] <= level) ++wt;
    }

    for (v = 0, gv = (set*)g; v < n; ++v, gv += M)
    {
        EMPTYSET(workset,m);
        w = -1;
        while ((w = nextelement(gv,M,w)) >= 0)
        {
            gw = GRAPHROW(g,w,m);
            for (i = M; --i >= 0;) UNION(workset[i],gw[i]);
        }
        wt = 0;
        w = -1;
        while ((w = nextelement(workset,M,w)) >= 0) ACCUM(wt,workshort[w]);
        invar[v] = wt;
    }
}

/*****************************************************************************
*                                                                            *
*  quadruples() assigns to each vertex v a value depending on the set of     *
*  weights w(v,v1,v2,v3), where w(v,v1,v2,v3) depends on the number of       *
*  vertices adjacent to an odd number of {v,v1,v2,v3}, and to the cells      *
*  that v,v1,v2,v3 belong to.  {v,v1,v2,v3} are permitted to range over all  *
*  distinct 4-tuples which contain at least one member in the cell tvpos.    *
*                                                                            *
*****************************************************************************/

void
quadruples(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
           int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    set *gw;
    int wt;
    int v,iv,v1,v2,v3;
    set *gv;
    long wv,wv1,wv2,wv3;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"quadruples");
    DYNALLOC1(set,ws1,ws1_sz,m,"quadruples");
    DYNALLOC1(set,workset,workset_sz,m,"quadruples");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ2(wt);
        if (ptn[i] <= level) ++wt;
    }

    iv = tvpos - 1;
    do
    {
         v = lab[++iv];
         gv = GRAPHROW(g,v,m);
         wv = workshort[v];
         for (v1 = 0; v1 < n-2; ++v1)
         {
            wv1 = workshort[v1];
            if (wv1 == wv && v1 <= v) continue;
            wv1 += wv;
            gw = GRAPHROW(g,v1,m);
            for (i = M; --i >= 0;) workset[i] = gv[i] ^ gw[i];
            for (v2 = v1+1; v2 < n-1; ++v2)
            {
                wv2 = workshort[v2];
                if (wv2 == wv && v2 <= v) continue;
                wv2 += wv1;
                gw = GRAPHROW(g,v2,m);
                for (i = M; --i >= 0;) ws1[i] = workset[i] ^ gw[i];
                for (v3 = v2+1; v3 < n; ++v3)
                {
                    wv3 = workshort[v3];
                    if (wv3 == wv && v3 <= v) continue;
                    wv3 += wv2;
                    gw = GRAPHROW(g,v3,m);
                    pc = 0;
                    for (i = M; --i >= 0;)
                        if ((sw = ws1[i] ^ gw[i]) != 0) pc += POPCOUNT(sw);
                    wt = (FUZZ1(pc)+wv3) & 077777;
                    wt = FUZZ2(wt);
                    ACCUM(invar[v],wt);
                    ACCUM(invar[v1],wt);
                    ACCUM(invar[v2],wt);
                    ACCUM(invar[v3],wt);
                }
            }
        }
    }
    while (ptn[iv] > level);
}

/*****************************************************************************
*                                                                            *
*  triples() assigns to each vertex v a value depending on the set of        *
*  weights w(v,v1,v2), where w(v,v1,v2) depends on the number of vertices    *
*  adjacent to an odd number of {v,v1,v2}, and to the cells that             *
*  v,v1,v2 belong to.  {v,v1,v2} are permitted to range over all distinct    *
*  triples which contain at least one member in the cell tvpos.              *
*                                                                            *
*****************************************************************************/

void
triples(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
        int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    set *gw;
    int wt;
    int v,iv,v1,v2;
    set *gv;
    long wv,wv1,wv2;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"triples");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"triples");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ1(wt);
        if (ptn[i] <= level) ++wt;
    }

    iv = tvpos - 1;
    do
    {
         v = lab[++iv];
         gv = GRAPHROW(g,v,m);
         wv = workshort[v];
         for (v1 = 0; v1 < n-1; ++v1)
         {
            wv1 = workshort[v1];
            if (wv1 == wv && v1 <= v) continue;
            wv1 += wv;
            gw = GRAPHROW(g,v1,m);
            for (i = M; --i >= 0;) workset[i] = gv[i] ^ gw[i];
            for (v2 = v1+1; v2 < n; ++v2)
            {
                wv2 = workshort[v2];
                if (wv2 == wv && v2 <= v) continue;
                wv2 += wv1;
                gw = GRAPHROW(g,v2,m);
                pc = 0;
                for (i = M; --i >= 0;)
                    if ((sw = workset[i] ^ gw[i]) != 0) pc += POPCOUNT(sw);
                wt = (FUZZ1(pc)+wv2) & 077777;
                wt = FUZZ2(wt);
                ACCUM(invar[v],wt);
                ACCUM(invar[v1],wt);
                ACCUM(invar[v2],wt);
            }
        }
    }
    while (ptn[iv] > level);
}

/*****************************************************************************
*                                                                            *
*  adjtriang() assigns to each vertex v a value depending on the numbers     *
*  of common neighbours between each pair {v1,v2} of neighbours of v, and    *
*  which cells v1 and v2 lie in.  The vertices v1 and v2 must be adjacent    *
*  if invararg == 0 and not adjacent if invararg == 1.                       *
*                                                                            *
*****************************************************************************/

void
adjtriang(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int j,pc;
    setword sw;
    set *gi;
    int wt;
    int i,v1,v2;
    boolean v1v2;
    set *gv1,*gv2;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"adjtriang");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"adjtriang");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ1(wt);
        if (ptn[i] <= level) ++wt;
    }

    for (v1 = 0, gv1 = g; v1 < n; ++v1, gv1 += M)
    {
        for (v2 = (digraph ? 0 : v1+1); v2 < n; ++v2)
        {
            if (v2 == v1) continue;
            v1v2 = (ISELEMENT(gv1,v2) != 0);
            if ((invararg == 0 && !v1v2)
                     || (invararg == 1 && v1v2)) continue;
            wt = workshort[v1];
            ACCUM(wt,workshort[v2]);
            ACCUM(wt,v1v2);

            gv2 = GRAPHROW(g,v2,m);
            for (i = M; --i >= 0;) workset[i] = gv1[i] & gv2[i];
            i = -1;
            while ((i = nextelement(workset,M,i)) >= 0)
            {
                pc = 0;
                gi = GRAPHROW(g,i,m);
                for (j = M; --j >= 0;)
                    if ((sw = workset[j] & gi[j]) != 0) pc += POPCOUNT(sw);
                pc = (pc + wt) & 077777;
                ACCUM(invar[i],pc);
            }
        }
    }
}

/*****************************************************************************
*                                                                            *
*  getbigcells(ptn,level,minsize,bigcells,cellstart,cellsize,n) is an        *
*  auxiliary procedure to make a list of all the large cells in the current  *
*  partition.  On entry, ptn, level and n have their usual meanings,         *
*  while minsize is the smallest size of an interesting cell.  On return,    *
*  bigcells is the number of cells of size at least minsize, cellstart[0...] *
*  contains their starting positions in ptn, and cellsize[0...] contain      *
*  their sizes.  These two arrays are in increasing order of cell size,      *
*  then position.                                                            *
*                                                                            *
*****************************************************************************/

void
getbigcells(int *ptn, int level, int minsize, int *bigcells,
            int *cellstart, int *cellsize, int n)
{
    int cell1,cell2,j;
    int si,st;
    int bc,i,h;

    bc = 0;
    for (cell1 = 0; cell1 < n; cell1 = cell2 + 1)
    {
        for (cell2 = cell1; ptn[cell2] > level; ++cell2) {}

        if (cell2 >= cell1 + minsize - 1)
        {
            cellstart[bc] = cell1;
            cellsize[bc] = cell2 - cell1 + 1;
            ++bc;
        }
    }
    *bigcells = bc;

    j = bc / 3;
    h = 1;
    do
        h = 3 * h + 1;
    while (h < j);

    do                      /* shell sort */
    {
        for (i = h; i < bc; ++i)
        {
            st = cellstart[i];
            si = cellsize[i];
            for (j = i; cellsize[j-h] > si ||
                        (cellsize[j-h] == si && cellstart[j-h] > st); )
            {
                cellsize[j] = cellsize[j-h];
                cellstart[j] = cellstart[j-h];
                if ((j -= h) < h) break;
            }
            cellsize[j] = si;
            cellstart[j] = st;
        }
        h /= 3;
    }
    while (h > 0);
}

/*****************************************************************************
*                                                                            *
*  celltrips() assigns to each vertex v a value depending on the set of      *
*  weights w(v,v1,v2), where w(v,v1,v2) depends on the number of vertices    *
*  adjacent to an odd number of {v,v1,v2}.  {v,v1,v2} are  constrained to    *
*  belong to the same cell.  We try the cells in increasing order of size,   *
*  and stop as soon as any cell splits.                                      *
*                                                                            *
*****************************************************************************/

void
celltrips(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    set *gw;
    int wt;
    int v,iv,v1,iv1,v2,iv2;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    set *gv;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"celltrips");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"celltrips");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,3,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (iv = cell1; iv <= cell2 - 2; ++iv)
        {
            v = lab[iv];
            gv = GRAPHROW(g,v,m);
            for (iv1 = iv + 1; iv1 <= cell2 - 1; ++iv1)
            {
                v1 = lab[iv1];
                gw = GRAPHROW(g,v1,m);
                for (i = M; --i >= 0;) workset[i] = gv[i] ^ gw[i];
                for (iv2 = iv1 + 1; iv2 <= cell2; ++iv2)
                {
                    v2 = lab[iv2];
                    gw = GRAPHROW(g,v2,m);
                    pc = 0;
                    for (i = M; --i >= 0;)
                        if ((sw = workset[i] ^ gw[i]) != 0)
                            pc += POPCOUNT(sw);
                    wt = FUZZ1(pc);
                    ACCUM(invar[v],wt);
                    ACCUM(invar[v1],wt);
                    ACCUM(invar[v2],wt);
                }
            }
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  cellquads() assigns to each vertex v a value depending on the set of      *
*  weights w(v,v1,v2,v3), where w(v,v1,v2,v3) depends on the number of       *
*  vertices adjacent to an odd number of {v,v1,v2,v3}.  {v,v1,v2,v3} are     *
*  constrained to belong to the same cell.  We try the cells in increasing   *
*  order of size, and stop as soon as any cell splits.                       *
*                                                                            *
*****************************************************************************/

void
cellquads(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    set *gw;
    int wt;
    int v,iv,v1,iv1,v2,iv2,v3,iv3;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    set *gv;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"cellquads");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellquads");
    DYNALLOC1(set,ws1,ws1_sz,m,"cellquads");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,4,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (iv = cell1; iv <= cell2 - 3; ++iv)
        {
            v = lab[iv];
            gv = GRAPHROW(g,v,m);
            for (iv1 = iv + 1; iv1 <= cell2 - 2; ++iv1)
            {
                v1 = lab[iv1];
                gw = GRAPHROW(g,v1,m);
                for (i = M; --i >= 0;) workset[i] = gv[i] ^ gw[i];
                for (iv2 = iv1 + 1; iv2 <= cell2 - 1; ++iv2)
                {
                    v2 = lab[iv2];
                    gw = GRAPHROW(g,v2,m);
                    for (i = M; --i >= 0;) ws1[i] = workset[i] ^ gw[i];
                    for (iv3 = iv2 + 1; iv3 <= cell2; ++iv3)
                    {
                        v3 = lab[iv3];
                        gw = GRAPHROW(g,v3,m);
                        pc = 0;
                        for (i = M; --i >= 0;)
                            if ((sw = ws1[i] ^ gw[i]) != 0)
                                pc += POPCOUNT(sw);
                        wt = FUZZ1(pc);
                        ACCUM(invar[v],wt);
                        ACCUM(invar[v1],wt);
                        ACCUM(invar[v2],wt);
                        ACCUM(invar[v3],wt);
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  cellquins() assigns to each vertex v a value depending on the set of      *
*  weights w(v,v1,v2,v3,v4), where w(v,v1,v2,v3,v4) depends on the number    *
*  of vertices adjacent to an odd number of {v,v1,v2,v3,v4}.                 *
*  {v,v1,v2,v3,v4} are constrained to belong to the same cell.  We try the   *
*  cells in increasing order of size, and stop as soon as any cell splits.   *
*                                                                            *
*****************************************************************************/

void
cellquins(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    set *gw;
    int wt;
    int v,iv,v1,iv1,v2,iv2,v3,iv3,v4,iv4;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    set *gv;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"cellquins");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellquins");
    DYNALLOC1(set,ws1,ws1_sz,m,"cellquins");
    DYNALLOC1(set,ws2,ws2_sz,m,"cellquins");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,5,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (iv = cell1; iv <= cell2 - 4; ++iv)
        {
            v = lab[iv];
            gv = GRAPHROW(g,v,m);
            for (iv1 = iv + 1; iv1 <= cell2 - 3; ++iv1)
            {
                v1 = lab[iv1];
                gw = GRAPHROW(g,v1,m);
                for (i = M; --i >= 0;) workset[i] = gv[i] ^ gw[i];
                for (iv2 = iv1 + 1; iv2 <= cell2 - 2; ++iv2)
                {
                    v2 = lab[iv2];
                    gw = GRAPHROW(g,v2,m);
                    for (i = M; --i >= 0;) ws1[i] = workset[i] ^ gw[i];
                    for (iv3 = iv2 + 1; iv3 <= cell2 - 1; ++iv3)
                    {
                        v3 = lab[iv3];
                        gw = GRAPHROW(g,v3,m);
                        for (i = M; --i >= 0;) ws2[i] = ws1[i] ^ gw[i];
                        for (iv4 = iv3 + 1; iv4 <= cell2; ++iv4)
                        {
                            v4 = lab[iv4];
                            gw = GRAPHROW(g,v4,m);
                            pc = 0;
                            for (i = M; --i >= 0;)
                                if ((sw = ws2[i] ^ gw[i]) != 0)
                                    pc += POPCOUNT(sw);
                            wt = FUZZ1(pc);
                            ACCUM(invar[v],wt);
                            ACCUM(invar[v1],wt);
                            ACCUM(invar[v2],wt);
                            ACCUM(invar[v3],wt);
                            ACCUM(invar[v4],wt);
                        }
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  uniqinter(s1,s2,m) returns the number in both sets if it is unique,       *
*  or -1 if there is none or it is not unique.                               *
*****************************************************************************/

static int
uniqinter(set *s1, set *s2, int m)
{
    int i,j;
    setword w;

    for (i = 0; i < M; ++i)
    {
        if ((w = s1[i] & s2[i]) != 0)
        {
            j = FIRSTBITNZ(w);
            if (w != BITT[j]) return -1;
            j += TIMESWORDSIZE(i);
            for (++i; i < M; ++i)
                if (s1[i] & s2[i]) return -1;
            return j;
        }
    }
    return -1;
}

/*****************************************************************************
*                                                                            *
*  cellfano2() assigns to each vertex v a value depending on the set of      *
*  weights w(v,v1,v2,v3), where w(v,v1,v2,v3) depends on the number of       *
*  fano-plane analogues containing {v,v1,v2,v3}.  {v,v1,v2,v3} are           *
*  constrained to belong to the same cell and being independent and          *
*  non-collinear.  We try the cells in increasing order of size, and stop    *
*  as soon as any cell splits.                                               *
*                                                                            *
*****************************************************************************/

void 
cellfano2(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    int wt;
    int v0,v1,v2,v3,iv0,iv1,iv2,iv3;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    int nw,x01,x02,x03,x12,x13,x23;
    int pnt0,pnt1,pnt2;
    set *gv0,*gv1,*gv2,*gv3;
    set *gp0,*gp1,*gp2;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellfano2");
    DYNALLOC1(int,vv,vv_sz,n,"cellfano2");
    DYNALLOC1(int,ww,ww_sz,n,"cellfano2");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,4,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (iv0 = cell1; iv0 <= cell2 - 3; ++iv0)
        {
            v0 = lab[iv0];
            gv0 = GRAPHROW(g,v0,m);
            nw = 0;
            for (iv1 = iv0 + 1; iv1 <= cell2; ++iv1)
            {
                v1 = lab[iv1];
                if (ISELEMENT(gv0,v1)) continue;
                if ((x01 = uniqinter(gv0,GRAPHROW(g,v1,m),m)) < 0) continue;
                vv[nw] = v1;
                ww[nw] = x01;
                ++nw;
            }

            for (iv1 = 0; iv1 < nw-2; ++iv1)
            {
                v1 = vv[iv1];
                gv1 = GRAPHROW(g,v1,m);
                x01 = ww[iv1];

                for (iv2 = iv1 + 1; iv2 < nw-1; ++iv2)
                {
                    x02 = ww[iv2];
                    if (x02 == x01) continue;
                    v2 = vv[iv2];
                    if (ISELEMENT(gv1,v2)) continue;
                    gv2 = GRAPHROW(g,v2,m);
                    if ((x12 = uniqinter(gv1,gv2,m)) < 0) continue;

                    for (iv3 = iv2 + 1; iv3 < nw; ++iv3)
                    {
                        x03 = ww[iv3];
                        if (x03 == x01 || x03 == x02) continue;
                        v3 = vv[iv3];
                        if (ISELEMENT(gv1,v3) || ISELEMENT(gv2,v3))
                            continue;
                        gv3 = GRAPHROW(g,v3,m);
                        if ((x13 = uniqinter(gv1,gv3,m)) < 0) continue;
                        if ((x23 = uniqinter(gv2,gv3,m)) < 0
                                               || x23 == x13) continue;

                        if ((pnt0 = uniqinter(GRAPHROW(g,x01,m),
                                             GRAPHROW(g,x23,m),m)) < 0)
                            continue;
                        if ((pnt1 = uniqinter(GRAPHROW(g,x02,m),
                                             GRAPHROW(g,x13,m),m)) < 0)
                            continue;
                        if ((pnt2 = uniqinter(GRAPHROW(g,x03,m),
                                             GRAPHROW(g,x12,m),m)) < 0)
                            continue;

                        gp0 = GRAPHROW(g,pnt0,m);
                        gp1 = GRAPHROW(g,pnt1,m);
                        gp2 = GRAPHROW(g,pnt2,m);

                        pc = 0;
                        for (i = M; --i >= 0;)
                        {
                            sw = gp0[i] & gp1[i] & gp2[i];
                            if (sw) pc += POPCOUNT(sw);
                        }
                        wt = FUZZ1(pc);
                        ACCUM(invar[v0],wt);
                        ACCUM(invar[v1],wt);
                        ACCUM(invar[v2],wt);
                        ACCUM(invar[v3],wt);
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  setnbhd(g,m,n,w,wn) is an auxiliary routine that sets wn to the union     *
*  of the neighbours of the vertices in w.                                   *
*                                                                            *
*****************************************************************************/

void
setnbhd(graph *g, int m, int n, set *w, set *wn)
{
    int i,j;
    set *gi;

    i = nextelement(w,M,-1);
    if (i < 0)
    {
        EMPTYSET(wn,M);
        return;
    }

    gi = GRAPHROW(g,i,M);
    for (j = M; --j >= 0;) wn[j] = gi[j];

    while ((i = nextelement(w,M,i)) >= 0)
    {
        gi = GRAPHROW(g,i,M);
        for (j = M; --j >= 0;) wn[j] |= gi[j];
    }
}    

/*****************************************************************************
*                                                                            *
*  cellfano() assigns to each vertex v a value depending on the set of       *
*  weights w(v,v1,v2,v3), where w(v,v1,v2,v3) depends on the number of       *
*  fano-plane analogues containing {v,v1,v2,v3}.  {v,v1,v2,v3} are           *
*  constrained to belong to the same cell and being independent.  We try     *
*  the cells in increasing order of size, and stop as soon as any cell       *
*  splits.                                                                   *
*                                                                            *
*****************************************************************************/

void 
cellfano(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
         int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,pc;
    setword sw;
    int wt;
    int v0,v1,v2,v3,iv0,iv1,iv2,iv3;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    set *gv0,*gv1,*gv2,*gv3;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellfano");
    DYNALLOC1(set,w01,w01_sz,m,"cellfano");
    DYNALLOC1(set,w02,w02_sz,m,"cellfano");
    DYNALLOC1(set,w03,w03_sz,m,"cellfano");
    DYNALLOC1(set,w12,w12_sz,m,"cellfano");
    DYNALLOC1(set,w13,w13_sz,m,"cellfano");
    DYNALLOC1(set,w23,w23_sz,m,"cellfano");
    DYNALLOC1(set,pt0,pt0_sz,m,"cellfano");
    DYNALLOC1(set,pt1,pt1_sz,m,"cellfano");
    DYNALLOC1(set,pt2,pt2_sz,m,"cellfano");
    DYNALLOC1(set,workset,workset_sz,m,"cellfano");
#else
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,4,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (iv0 = cell1; iv0 <= cell2 - 3; ++iv0)
        {
            v0 = lab[iv0];
            gv0 = GRAPHROW(g,v0,m);
            for (iv1 = iv0 + 1; iv1 <= cell2 - 2; ++iv1)
            {
                v1 = lab[iv1];
                if (ISELEMENT(gv0,v1)) continue;
                gv1 = GRAPHROW(g,v1,m);
                for (i = M; --i >= 0;) workset[i] = gv0[i] & gv1[i];
                setnbhd(g,m,n,workset,w01);

                for (iv2 = iv1 + 1; iv2 <= cell2 - 1; ++iv2)
                {
                    v2 = lab[iv2];
                    if (ISELEMENT(gv0,v2) || ISELEMENT(gv1,v2))
                        continue;
                    gv2 = GRAPHROW(g,v2,m);
                    for (i = M; --i >= 0;) workset[i] = gv0[i] & gv2[i];
                    setnbhd(g,m,n,workset,w02);
                    for (i = M; --i >= 0;) workset[i] = gv1[i] & gv2[i];
                    setnbhd(g,m,n,workset,w12);

                    for (iv3 = iv2 + 1; iv3 <= cell2; ++iv3)
                    {
                        v3 = lab[iv3];
                        if (ISELEMENT(gv0,v3) || ISELEMENT(gv1,v3) ||
                                    ISELEMENT(gv2,v3))
                            continue;
                        gv3 = GRAPHROW(g,v3,m);
                        for (i = M; --i >= 0;) workset[i] = gv0[i] & gv3[i];
                        setnbhd(g,m,n,workset,w03);
                        for (i = M; --i >= 0;) workset[i] = gv1[i] & gv3[i];
                        setnbhd(g,m,n,workset,w13);
                        for (i = M; --i >= 0;) workset[i] = gv2[i] & gv3[i];
                        setnbhd(g,m,n,workset,w23);
                    
                        for (i = M; --i >= 0;) workset[i] = w01[i] & w23[i];
                        setnbhd(g,m,n,workset,pt0);
                        for (i = M; --i >= 0;) workset[i] = w03[i] & w12[i];
                        setnbhd(g,m,n,workset,pt1);
                        for (i = M; --i >= 0;) workset[i] = w02[i] & w13[i];
                        setnbhd(g,m,n,workset,pt2);
                        pc = 0;
                        for (i = M; --i >= 0;)
                        {
                            sw = pt0[i] & pt1[i] & pt2[i];
                            if (sw) pc += POPCOUNT(sw);
                        }
                        wt = FUZZ1(pc);
                        ACCUM(invar[v0],wt);
                        ACCUM(invar[v1],wt);
                        ACCUM(invar[v2],wt);
                        ACCUM(invar[v3],wt);
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  distances() assigns to each vertex v a value depending on the number of   *
*  vertices at each distance from v, and what cells they lie in.             *
*  If we find any cell which is split in this manner, we don't try any       *
*  further cells.                                                            *
*                                                                            *
*****************************************************************************/

void
distances(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i;
    set *gw;
    int wt;
    int d,dlim,cell1,cell2,iv,v,w;
    boolean success;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"distances");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"distances");
    DYNALLOC1(set,ws1,ws1_sz,m,"distances");
    DYNALLOC1(set,ws2,ws2_sz,m,"distances"); 
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ1(wt);
        if (ptn[i] <= level) ++wt;
    }

    if (invararg > n || invararg == 0) dlim = n;
    else                               dlim = invararg+1;

    success = FALSE;
    for (cell1 = 0; cell1 < n; cell1 = cell2 + 1)
    {
        for (cell2 = cell1; ptn[cell2] > level; ++cell2) {}
        if (cell2 == cell1) continue;

        for (iv = cell1; iv <= cell2; ++iv)
        {
            v = lab[iv];
            EMPTYSET(ws1,m);
            ADDELEMENT(ws1,v);
            EMPTYSET(ws2,m);
            ADDELEMENT(ws2,v);
            for (d = 1; d < dlim; ++d)
            {
                EMPTYSET(workset,m);
                wt = 0;
                w = -1;
                while ((w = nextelement(ws2,M,w)) >= 0)
                {
                    gw = GRAPHROW(g,w,m);
                    ACCUM(wt,workshort[w]);
                    for (i = M; --i >= 0;) workset[i] |= gw[i];
                }
                if (wt == 0) break;
                ACCUM(wt,d);
                wt = FUZZ2(wt);
                ACCUM(invar[v],wt);
                for (i = M; --i >= 0;)
                {
                    ws2[i] = workset[i] & ~ws1[i];
                    ws1[i] |= ws2[i];
                }
            }
            if (invar[v] != invar[lab[cell1]]) success = TRUE;
        }
        if (success) break;
    }
}

/*****************************************************************************
*                                                                            *
*  indsets() assigns to each vertex v a value depending on which cells the   *
*  vertices which join v in an independent set lie in.  The size of the      *
*  independent sets which are used is the smallest of invararg and MAXCLIQUE.*
*                                                                            *
*****************************************************************************/

void
indsets(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
        int *invar, int invararg, boolean digraph, int m, int n)
{
    int i;
    int wt;
    set *gv;
    int ss,setsize;
    int v[MAXCLIQUE];
    long wv[MAXCLIQUE];
    set *s0,*s1;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"indsets");
    DYNALLOC2(set,wss,wss_sz,m,MAXCLIQUE-1,"indsets");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    if (invararg <= 1 || digraph) return;

    if (invararg > MAXCLIQUE) setsize = MAXCLIQUE;
    else                      setsize = invararg;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ2(wt);
        if (ptn[i] <= level) ++wt;
    }

    for (v[0] = 0; v[0] < n; ++v[0])
    {
        wv[0] = workshort[v[0]];
        s0 = (set*)wss;
        EMPTYSET(s0,m);
        for (i = v[0]+1; i < n; ++i) ADDELEMENT(s0,i);
        gv = GRAPHROW(g,v[0],m);
        for (i = M; --i >= 0;) s0[i] &= ~gv[i];
        ss = 1;
        v[1] = v[0];
        while (ss > 0)
        {
            if (ss == setsize)
            {
                wt = FUZZ1(wv[ss-1]);
                for (i = ss; --i >= 0;) ACCUM(invar[v[i]],wt);
                --ss;
            }
            else if ((v[ss] = nextelement((set*)wss+M*(ss-1),M,v[ss])) < 0)
                --ss;
            else
            {
                wv[ss] = wv[ss-1] + workshort[v[ss]];
                ++ss;
                if (ss < setsize)
                {
                    gv = GRAPHROW(g,v[ss-1],m);
                    s1 = (set*)wss + M*(ss-2);
                    for (i = M; --i >= 0;) s1[i+M] = s1[i] & ~gv[i];
                    v[ss] = v[ss-1];
                }
            }
        }
    }
}

/*****************************************************************************
*                                                                            *
*  cliques() assigns to each vertex v a value depending on which cells the   *
*  vertices which join v in a clique lie in.  The size of the cliques used   *
*  is the smallest of invararg and MAXCLIQUE.                                *
*                                                                            *
*****************************************************************************/

void
cliques(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
        int *invar, int invararg, boolean digraph, int m, int n)
{
    int i;
    int wt;
    set *gv;
    int ss,setsize;
    int v[MAXCLIQUE];
    long wv[MAXCLIQUE];
    set *ns;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cliques");
    DYNALLOC2(set,wss,wss_sz,m,MAXCLIQUE-1,"cliques");
#else
    set wss[MAXCLIQUE-1][MAXM];
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    if (invararg <= 1 || digraph) return;

    if (invararg > MAXCLIQUE) setsize = MAXCLIQUE;
    else                      setsize = invararg;

    wt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = FUZZ2(wt);
        if (ptn[i] <= level) ++wt;
    }

    for (v[0] = 0; v[0] < n; ++v[0])
    {
        wv[0] = workshort[v[0]];
        gv = GRAPHROW(g,v[0],m);
        ns = (set*)wss;
        for (i = M; --i >= 0;) ns[i] = gv[i];
        ss = 1;
        v[1] = v[0];
        while (ss > 0)
        {
            if (ss == setsize)
            {
                wt = FUZZ1(wv[ss-1]);
                for (i = ss; --i >= 0;) ACCUM(invar[v[i]],wt);
                --ss;
            }
            else if ((v[ss] = nextelement((set*)wss+M*(ss-1),M,v[ss])) < 0)
                --ss;
            else
            {
                wv[ss] = wv[ss-1] + workshort[v[ss]];
                ++ss;
                if (ss < setsize)
                {
                    gv = GRAPHROW(g,v[ss-1],m);
                    ns = (set*)wss + M*(ss-2);
                    for (i = M; --i >= 0;) ns[i+M] = ns[i] & gv[i];
                    v[ss] = v[ss-1];
                }
            }
        }
    }
}

/*****************************************************************************
*                                                                            *
*  cellcliq() assigns to each vertex v a value depending on the number of    *
*  cliques which v lies in and which lie in the same cell as v.  The size    *
*  of clique counted is the smallest of invararg and MAXCLIQUE.  We try the  *
*  cells in increasing order of size and stop as soon as any cell splits.    *
*                                                                            *
*****************************************************************************/

void
cellcliq(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
         int *invar, int invararg, boolean digraph, int m, int n)
{
    int i;
    int wt;
    set *gv;
    int ss,setsize;
    int v[MAXCLIQUE];
    set *ns;
    int *cellstart,*cellsize;
    int iv,icell,bigcells,cell1,cell2;
    int pc;
    setword sw;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"cellcliq");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellcliq");
    DYNALLOC2(set,wss,wss_sz,m,MAXCLIQUE-1,"cellcliq");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    if (invararg <= 1 || digraph) return;

    if (invararg > MAXCLIQUE) setsize = MAXCLIQUE;
    else                      setsize = invararg;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,setsize > 6 ? setsize : 6,&bigcells,
                cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;

        EMPTYSET(workset,m);
        for (iv = cell1; iv <= cell2; ++iv) ADDELEMENT(workset,lab[iv]);

        for (iv = cell1; iv <= cell2; ++iv)
        {
            v[0] = lab[iv];
            gv = GRAPHROW(g,v[0],m);
            ns = (set*)wss;
            pc = 0;

            for (i = M; --i >= 0;)
            {
                ns[i] = gv[i] & workset[i];
                if ((sw = ns[i]) != 0) pc += POPCOUNT(sw);
            }
            if (pc <= 1 || pc >= cellsize[icell] - 2) continue;

            ss = 1;
            v[1] = v[0];
            while (ss > 0)
            {
                if (ss == setsize)
                {
                    for (i = ss; --i >= 0;) ++invar[v[i]];
                    --ss;
                }
                else if ((v[ss] 
                            = nextelement((set*)wss+M*(ss-1),M,v[ss])) < 0)
                    --ss;
                else
                {
                    ++ss;
                    if (ss < setsize)
                    {
                        gv = GRAPHROW(g,v[ss-1],m);
                        ns = (set*)wss + M*(ss-2);
                        for (i = M; --i >= 0;) ns[i+M] = ns[i] & gv[i];
                        v[ss] = v[ss-1];
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (iv = cell1 + 1; iv <= cell2; ++iv)
            if (invar[lab[iv]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  cellind() assigns to each vertex v a value depending on the number of     *
*  independent sets which v lies in and which lie in the same cell as v.     *
*  The size of clique counted is the smallest of invararg and MAXCLIQUE.     *
*  We try the cells in increasing order of size and stop as soon as any      *
*  cell splits.                                                              *
*                                                                            *
*****************************************************************************/

void
cellind(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
        int *invar, int invararg, boolean digraph, int m, int n)
{
    int i;
    int wt;
    set *gv;
    int ss,setsize;
    int v[MAXCLIQUE];
    set *ns;
    int *cellstart,*cellsize;
    int iv,icell,bigcells,cell1,cell2;
    int pc;
    setword sw;

#if !MAXN
    DYNALLOC1(set,workset,workset_sz,m,"cellind");
    DYNALLOC1(int,workshort,workshort_sz,n+2,"cellind");
    DYNALLOC2(set,wss,wss_sz,m,MAXCLIQUE-1,"cellind");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    if (invararg <= 1 || digraph) return;

    if (invararg > MAXCLIQUE) setsize = MAXCLIQUE;
    else                      setsize = invararg;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,setsize > 6 ? setsize : 6,&bigcells,
                cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;

        EMPTYSET(workset,m);
        for (iv = cell1; iv <= cell2; ++iv) ADDELEMENT(workset,lab[iv]);

        for (iv = cell1; iv <= cell2; ++iv)
        {
            v[0] = lab[iv];
            gv = GRAPHROW(g,v[0],m);
            ns = (set*)wss;
            pc = 0;

            for (i = M; --i >= 0;)
            {
                ns[i] = ~gv[i] & workset[i];
                if ((sw = ns[i]) != 0) pc += POPCOUNT(sw);
            }
            if (pc <= 1 || pc >= cellsize[icell] - 2) continue;

            ss = 1;
            v[1] = v[0];
            while (ss > 0)
            {
                if (ss == setsize)
                {
                    for (i = ss; --i >= 0;) ++invar[v[i]];
                    --ss;
                }
                else if ((v[ss] 
                       = nextelement((set*)wss+M*(ss-1),M,v[ss])) < 0)
                    --ss;
                else
                {
                    ++ss;
                    if (ss < setsize)
                    {
                        gv = GRAPHROW(g,v[ss-1],m);
                        ns = (set*)wss + M*(ss-2);
                        for (i = M; --i >= 0;) ns[i+M] = ns[i] & ~gv[i];
                        v[ss] = v[ss-1];
                    }
                }
            }
        }
        wt = invar[lab[cell1]];
        for (iv = cell1 + 1; iv <= cell2; ++iv)
            if (invar[lab[iv]] != wt) return;
    }
}

/*****************************************************************************
*                                                                            *
*  adjacencies() assigns to each vertex v a code depending on which cells    *
*  it is joined to and from, and how many times.  It is intended to provide  *
*  better partitioning that the normal refinement routine for digraphs.      *
*  It will not help with undirected graphs in nauty at all.                  *
*                                                                            *
*****************************************************************************/

void
adjacencies(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
            int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,v,w;
    int vwt,wwt;
    set *gv;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"adjacencies");
#endif

    vwt = 1;
    for (i = 0; i < n; ++i)
    {
        workshort[lab[i]] = vwt;
        if (ptn[i] <= level) ++vwt;
        invar[i] = 0;
    }

    for (v = 0, gv = (set*)g; v < n; ++v, gv += M)
    {
        vwt = FUZZ1(workshort[v]);
        wwt = 0;
        w = -1;
        while ((w = nextelement(gv,M,w)) >= 0)
        {
            ACCUM(wwt,FUZZ2(workshort[w]));
            ACCUM(invar[w],vwt);
        }
        ACCUM(invar[v],wwt);
    }
}

/*****************************************************************************
*                                                                            *
*  nautinv_check() checks that this file is compiled compatibly with the     *
*  given parameters.   If not, call exit(1).                                 *
*                                                                            *
*****************************************************************************/

void
nautinv_check(int wordsize, int m, int n, int version)
{
    if (wordsize != WORDSIZE)
    {
        fprintf(ERRFILE,"Error: WORDSIZE mismatch in nautinv.c\n");
        exit(1);
    }

#if MAXN
    if (m > MAXM)
    {
        fprintf(ERRFILE,"Error: MAXM inadequate in nautinv.c\n");
        exit(1);
    }

    if (n > MAXN)
    {
        fprintf(ERRFILE,"Error: MAXN inadequate in nautinv.c\n");
        exit(1);
    }
#endif

    if (version < NAUTYREQUIRED)
    {
        fprintf(ERRFILE,"Error: nautinv.c version mismatch\n");
        exit(1);
    }
}

/*****************************************************************************
*                                                                            *
*  nautinv_freedyn() - free the dynamic memory in this module                *
*                                                                            *
*****************************************************************************/
 
void
nautinv_freedyn(void)
{
#if !MAXN
    DYNFREE(workset,workset_sz);
    DYNFREE(workshort,workshort_sz);
    DYNFREE(ws1,ws1_sz);
    DYNFREE(ws2,ws2_sz);
    DYNFREE(vv,vv_sz);
    DYNFREE(ww,ww_sz);
    DYNFREE(w01,w01_sz);
    DYNFREE(w02,w02_sz);
    DYNFREE(w03,w03_sz);
    DYNFREE(w12,w12_sz);
    DYNFREE(w13,w13_sz);
    DYNFREE(w23,w23_sz);
    DYNFREE(pt0,pt0_sz);
    DYNFREE(pt1,pt1_sz);
    DYNFREE(pt2,pt2_sz);
    DYNFREE(wss,wss_sz);
#endif
}

/*===================================================================*/


/*****************************************************************************
*                                                                            *
*  semirefine(g,lab,ptn,level,numcells,strength,active,m,n) performs a       *
*  refinement operation on the partition at the specified level of the       *
*  partition nest (lab,ptn).  *numcells is assumed to contain the number of  *
*  cells on input, and is updated.  The initial set of active cells (alpha   *
*  in the paper) is specified in the set active.  Precisely, x is in active  *
*  iff the cell starting at index x in lab is active.                        *
*  *code is set to a value which depends on the fine detail of the           *
*  algorithm, but which is independent of the labelling of the graph.        *
*                                                                            *
*****************************************************************************/

static int
semirefine(graph *g, int *lab, int *ptn, int level, int *numcells,
           int strength, set *active, int m, int n)
{
    int i,c1,c2,labc1;
    setword x;
    set *set1,*set2;
    int split1,split2,cell1,cell2;
    int cnt,bmin,bmax;
    long longcode;
    set *gptr;
    int maxcell,maxpos,hint;

#if !MAXN
    DYNALLOC1(int,workperm,workperm_sz,n,"refine");
    DYNALLOC1(set,workset,workset_sz,m,"refine");
    DYNALLOC1(int,bucket,bucket_sz,n+2,"refine");
    DYNALLOC1(int,count,count_sz,n,"refine");
#endif

    longcode = *numcells;
    split1 = -1;
    hint = 0;
    while (*numcells < n && ((split1 = hint, ISELEMENT(active,split1))
                         || (split1 = nextelement(active,M,split1)) >= 0
                         || (split1 = nextelement(active,M,-1)) >= 0))
    {
        DELELEMENT(active,split1);
        for (split2 = split1; ptn[split2] > level; ++split2) {}
        longcode = MASH(longcode,split1+split2);
        if (split1 == split2)       /* trivial splitting cell */
        {
            gptr = GRAPHROW(g,lab[split1],M);
            for (cell1 = 0; cell1 < n; cell1 = cell2 + 1)
            {
                for (cell2 = cell1; ptn[cell2] > level; ++cell2) {}
                if (cell1 == cell2) continue;
                c1 = cell1;
                c2 = cell2;
                while (c1 <= c2)
                {
                    labc1 = lab[c1];
                    if (ISELEMENT(gptr,labc1))
                        ++c1;
                    else
                    {
                        lab[c1] = lab[c2];
                        lab[c2] = labc1;
                        --c2;
                    }
                }
                if (c2 >= cell1 && c1 <= cell2)
                {
                    ptn[c2] = level;
                    longcode = MASH(longcode,FUZZ1(c2));
                    ++*numcells;
                    if (ISELEMENT(active,cell1) || c2-cell1 >= cell2-c1)
                    {
                        ADDELEMENT(active,c1);
                        if (c1 == cell2) hint = c1;
                    }
                    else
                    {
                        ADDELEMENT(active,cell1);
                        if (c2 == cell1) hint = cell1;
                    }
                }
            }
        }
        else        /* nontrivial splitting cell */
        {
            EMPTYSET(workset,m);
            for (i = split1; i <= split2; ++i)
                ADDELEMENT(workset,lab[i]);
            longcode = MASH(longcode,FUZZ2(split2-split1+1));

            for (cell1 = 0; cell1 < n; cell1 = cell2 + 1)
            {
                for (cell2 = cell1; ptn[cell2] > level; ++cell2) {}
                if (cell1 == cell2) continue;
                i = cell1;
                set1 = workset;
                set2 = GRAPHROW(g,lab[i],m);
                cnt = 0;
                for (c1 = m; --c1 >= 0;)
                    if ((x = ((*set1++) & (*set2++))) != 0)
                        cnt += POPCOUNT(x);

                count[i] = bmin = bmax = cnt;
                bucket[cnt] = 1;
                while (++i <= cell2)
                {
                    set1 = workset;
                    set2 = GRAPHROW(g,lab[i],m);
                    cnt = 0;
                    for (c1 = m; --c1 >= 0;)
                        if ((x = ((*set1++) & (*set2++))) != 0)
                            cnt += POPCOUNT(x);

                    while (bmin > cnt) bucket[--bmin] = 0;
                    while (bmax < cnt) bucket[++bmax] = 0;
                    ++bucket[cnt];
                    count[i] = cnt;
                }
                if (bmin == bmax)
                {
                    longcode = MASH(longcode,FUZZ1(bmin+cell1));
                    continue;
                }
                c1 = cell1;
                maxcell = -1;
                for (i = bmin; i <= bmax; ++i)
                    if (bucket[i])
                    {
                        c2 = c1 + bucket[i];
                        bucket[i] = c1;
                        longcode = MASH(longcode,i+c1);
                        if (c2-c1 > maxcell)
                        {
                            maxcell = c2-c1;
                            maxpos = c1;
                        }
                        if (c1 != cell1)
                        {
                            ADDELEMENT(active,c1);
                            if (c2-c1 == 1) hint = c1;
                            ++*numcells;
                        }
                        if (c2 <= cell2) ptn[c2-1] = level;
                        c1 = c2;
                    }
                for (i = cell1; i <= cell2; ++i)
                    workperm[bucket[count[i]]++] = lab[i];
                for (i = cell1; i <= cell2; ++i) lab[i] = workperm[i];
                if (!ISELEMENT(active,cell1))
                {
                    ADDELEMENT(active,cell1);
                    DELELEMENT(active,maxpos);     /* check maxpos is alwas defined */
                }
            }
        }
        if (--strength == 0) break;   /* negative is fine! */
    }

    longcode = MASH(longcode,FUZZ2(*numcells));
    return CLEANUP(longcode);
}

void 
refinvar(graph *g, int *lab, int *ptn, int level, int numcells, int tvpos,
          int *invar, int invararg, boolean digraph, int m, int n)
{
    int i,j;
    int wt;
    int icell,bigcells,cell1,cell2;
    int *cellstart,*cellsize;
    int newnumcells;

#if !MAXN
    DYNALLOC1(int,workshort,workshort_sz,n+2,"refinvar");
    DYNALLOC1(int,vv,vv_sz,n,"refinvar");
    DYNALLOC1(int,ww,ww_sz,n,"refinvar");
    DYNALLOC1(set,ws1,ws1_sz,n,"refinvar");
#endif

    for (i = n; --i >= 0;) invar[i] = 0;

    cellstart = workshort;
    cellsize = workshort + (n/2);
    getbigcells(ptn,level,2,&bigcells,cellstart,cellsize,n);

    for (icell = 0; icell < bigcells; ++icell)
    {
        cell1 = cellstart[icell];
        cell2 = cell1 + cellsize[icell] - 1;
        for (i = cell1; i <= cell2; ++i)
        {
            for (j = 0; j < n; ++j)
            {
                vv[j] = lab[j];
                ww[j] = ptn[j];
            }
            newnumcells = numcells + 1;
            ww[cell1] = level;
            EMPTYSET(ws1,m);
            ADDELEMENT(ws1,cell1);
            vv[i] = lab[cell1];
            vv[cell1] = lab[i];
            invar[lab[i]] = semirefine(g,vv,ww,level,&newnumcells,
                                            invararg,ws1,m,n);
        }
        wt = invar[lab[cell1]];
        for (i = cell1 + 1; i <= cell2; ++i)
            if (invar[lab[i]] != wt) return;
    }
}
