summaryrefslogtreecommitdiff
path: root/nauty/schreier.c
diff options
context:
space:
mode:
authorAndrew Guschin <guschin.drew@gmail.com>2023-08-13 01:27:00 +0400
committerAndrew Guschin <guschin.drew@gmail.com>2023-08-13 06:00:02 -0500
commit58acff54b1cd64cb23b9d0b1a304eb9db768e3eb (patch)
tree87281f776e0015f218aadb5cbfdad43c66406342 /nauty/schreier.c
Initial commit
Diffstat (limited to 'nauty/schreier.c')
-rw-r--r--nauty/schreier.c1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/nauty/schreier.c b/nauty/schreier.c
new file mode 100644
index 0000000..d2a68f0
--- /dev/null
+++ b/nauty/schreier.c
@@ -0,0 +1,1128 @@
+/* schreier.c - procedures for manipulating a permutation group using
+ * the random schreier algorithm. There is a separate file schreier.txt
+ * which describes the usage.
+ *
+ * Written for nauty and traces, Brendan McKay 2010-2013.
+ */
+
+#include "schreier.h"
+
+TLS_ATTR long long multcount = 0;
+TLS_ATTR long long filtercount = 0;
+
+static permnode id_permnode;
+ /* represents identity, no actual content, doesn't need TLS_ATTR */
+#define ID_PERMNODE (&id_permnode)
+
+#if !MAXN
+DYNALLSTAT(int,workperm,workperm_sz);
+DYNALLSTAT(int,workperm2,workperm2_sz);
+DYNALLSTAT(int,workpermA,workpermA_sz);
+DYNALLSTAT(int,workpermB,workpermB_sz);
+DYNALLSTAT(set,workset,workset_sz);
+DYNALLSTAT(set,workset2,workset2_sz);
+#else
+static TLS_ATTR int workperm[MAXN];
+static TLS_ATTR int workperm2[MAXN];
+static TLS_ATTR int workpermA[MAXN];
+static TLS_ATTR int workpermB[MAXN];
+static TLS_ATTR set workset[MAXM];
+static TLS_ATTR set workset2[MAXM];
+#endif
+
+static TLS_ATTR schreier *schreier_freelist = NULL;
+ /* Freelist of scheier structures connected by next field.
+ * vec, pwr and orbits fields are assumed allocated. */
+static TLS_ATTR permnode *permnode_freelist = NULL;
+ /* Freelist of permnode structures connected by next field.
+ * p[] is assumed extended. */
+
+static TLS_ATTR int schreierfails = SCHREIERFAILS;
+
+#define TMP
+
+static boolean filterschreier(schreier*,int*,permnode**,boolean,int,int);
+#define PNCODE(x) ((int)(((size_t)(x)>>3)&0xFFFUL))
+
+/* #define TESTP(id,p,n) testispermutation(id,p,n) */
+#define TESTP(id,p,n)
+
+/************************************************************************/
+
+static void
+testispermutation(int id, int *p, int n)
+/* For debugging purposes, crash with a message if p[0..n-1] is
+ not a permutation. */
+{
+ int i,m;
+ DYNALLSTAT(set,seen,seen_sz);
+
+ for (i = 0; i < n; ++i)
+ if (p[i] < 0 || p[i] > n) break;
+
+ if (i < n)
+ {
+ fprintf(stderr,">E Bad permutation (id=%d): n=%d p[%d]=%d\n",
+ id,n,i,p[i]);
+ exit(1);
+ }
+
+ m = SETWORDSNEEDED(n);
+ DYNALLOC1(set,seen,seen_sz,m,"malloc seen");
+ EMPTYSET(seen,m);
+
+ for (i = 0; i < n; ++i)
+ {
+ if (ISELEMENT(seen,p[i]))
+ {
+ fprintf(stderr,
+ ">E Bad permutation (id=%d): n=%d p[%d]=%d is a repeat\n",
+ id,n,i,p[i]);
+ exit(1);
+ }
+ ADDELEMENT(seen,p[i]);
+ }
+}
+
+/************************************************************************/
+
+int
+schreier_fails(int nfails)
+/* Set the number of consecutive failures for filtering;
+ * A value of <= 0 defaults to SCHREIERFAILS.
+ * The function value is the previous setting. */
+{
+ int prev;
+
+ prev = schreierfails;
+
+ if (nfails <= 0) schreierfails = SCHREIERFAILS;
+ else schreierfails = nfails;
+
+ return prev;
+}
+
+/************************************************************************/
+
+static void
+clearfreelists(void)
+/* Clear the schreier and permnode freelists */
+{
+ schreier *sh,*nextsh;
+ permnode *p,*nextp;
+
+ nextsh = schreier_freelist;
+ while (nextsh)
+ {
+ sh = nextsh;
+ nextsh = sh->next;
+ free(sh->vec);
+ free(sh->pwr);
+ free(sh->orbits);
+ free(sh);
+ }
+ schreier_freelist = NULL;
+
+ nextp = permnode_freelist;
+ while (nextp)
+ {
+ p = nextp;
+ nextp = p->next;
+ free(p);
+ }
+ permnode_freelist = NULL;
+}
+
+/************************************************************************/
+
+static permnode
+*newpermnode(int n)
+/* Allocate a new permode structure, with initialized next/prev fields */
+{
+ permnode *p;
+
+ while (permnode_freelist)
+ {
+ p = permnode_freelist;
+ permnode_freelist = p->next;
+ if (p->nalloc >= n && p->nalloc <= n+100)
+ {
+ p->next = p->prev = NULL;
+ p->mark = 0;
+ return p;
+ }
+ else
+ free(p);
+ }
+
+#if FLEX_ARRAY_OK
+ p = (permnode*) malloc(sizeof(permnode)+n*sizeof(int));
+#else
+ p = (permnode*) malloc(sizeof(permnode)+(n-2)*sizeof(int));
+#endif
+
+ if (p == NULL)
+ {
+ fprintf(ERRFILE,">E malloc failed in newpermnode()\n");
+ exit(1);
+ }
+
+ p->next = p->prev = NULL;
+ p->nalloc = n;
+
+ return p;
+}
+
+/************************************************************************/
+
+static schreier
+*newschreier(int n)
+/* Allocate a new schreier structure, with initialised next field */
+{
+ schreier *sh;
+
+ while (schreier_freelist)
+ {
+ sh = schreier_freelist;
+ schreier_freelist = sh->next;
+ if (sh->nalloc >= n && sh->nalloc <= n+100)
+ {
+ sh->next = NULL;
+ return sh;
+ }
+ else
+ {
+ free(sh->vec);
+ free(sh->pwr);
+ free(sh->orbits);
+ free(sh);
+ }
+ }
+
+ sh = (schreier*) malloc(sizeof(schreier));
+
+ if (sh == NULL)
+ {
+ fprintf(ERRFILE,">E malloc failed in newschreier()\n");
+ exit(1);
+ }
+
+ sh->vec = (permnode**) malloc(sizeof(permnode*)*n);
+ sh->pwr = (int*) malloc(sizeof(int)*n);
+ sh->orbits = (int*) malloc(sizeof(int)*n);
+
+ if (sh->vec == NULL || sh->pwr == NULL || sh->orbits == NULL)
+ {
+ fprintf(ERRFILE,">E malloc failed in newschreier()\n");
+ exit(1);
+ }
+
+ sh->next = NULL;
+ sh->nalloc = n;
+
+ return sh;
+}
+
+/************************************************************************/
+
+void
+freeschreier(schreier **gp, permnode **gens)
+/* Free schreier structure and permutation ring. Assume this is everything. */
+/* Use NULL for arguments which don't need freeing. */
+{
+ schreier *sh,*nextsh;
+ permnode *p,*nextp;
+
+ if (gp && *gp)
+ {
+ nextsh = *gp;
+ while (nextsh)
+ {
+ sh = nextsh;
+ nextsh = sh->next;
+ sh->next = schreier_freelist;
+ schreier_freelist = sh;
+ }
+ *gp = NULL;
+ }
+
+ if (gens && *gens)
+ {
+ p = *gens;
+ do
+ {
+ nextp = p->next;
+ p->next = permnode_freelist;
+ permnode_freelist = p;
+ p = nextp;
+ } while (p != *gens);
+ *gens = NULL;
+ }
+}
+
+/************************************************************************/
+
+permnode*
+findpermutation(permnode *pn, int *p, int n)
+/* Return a pointer to permutation p in the circular list,
+ * or NULL if it isn't present. */
+{
+ permnode *rn;
+ int i;
+
+ if (!pn) return NULL;
+
+ rn = pn;
+ do
+ {
+ for (i = 0; i < n; ++i)
+ if (rn->p[i] != p[i]) break;
+ if (i == n) return rn;
+ rn = rn->next;
+ } while (rn != pn);
+
+ return NULL;
+}
+
+/************************************************************************/
+
+void
+addpermutation(permnode **ring, int *p, int n)
+/* Add new permutation to circular list, marked.
+ * and return pointer to it in *ring. */
+{
+ permnode *pn,*rn;
+
+ pn = newpermnode(n);
+ rn = *ring;
+
+ memcpy(pn->p,p,n*sizeof(int));
+
+ if (!rn)
+ pn->next = pn->prev = pn;
+ else
+ {
+ pn->next = rn->next;
+ pn->prev = rn;
+ rn->next = pn->next->prev = pn;
+ }
+
+ pn->refcount = 0;
+ pn->mark = 1;
+ *ring = pn;
+}
+
+/************************************************************************/
+
+static void
+addpermutationunmarked(permnode **ring, int *p, int n)
+/* Add new permutation to circular list, not marked.
+ * and return pointer to it in *ring. */
+{
+ TESTP(3,p,n);
+ addpermutation(ring,p,n);
+ (*ring)->mark = 0;
+}
+
+/************************************************************************/
+
+boolean
+addgenerator(schreier **gp, permnode **ring, int *p, int n)
+/* Add new permutation to group, unless it is discovered to be
+ * already in the group. It is is possible to be in the group
+ * and yet this fact is not discovered.
+ * Return TRUE if the generator (or an equivalent) is added or the
+ * group knowledge with the current partial base is improved. */
+{
+ TESTP(2,p,n);
+ return filterschreier(*gp,p,ring,FALSE,-1,n);
+}
+
+/************************************************************************/
+
+boolean
+condaddgenerator(schreier **gp, permnode **ring, int *p, int n)
+/* Add new permutation to group, unless it is discovered to be
+ * already in the group. It is is possible to be in the group
+ * and yet this fact is not discovered, but this version will
+ * always notice if this permutation precisely is present.
+ * Return TRUE if the generator (or an equivalent) is added or the
+ * group knowledge with the current partial base is improved. */
+{
+ TESTP(4,p,n);
+ if (findpermutation(*ring,p,n))
+ return FALSE;
+ else
+ return filterschreier(*gp,p,ring,FALSE,-1,n);
+}
+
+/************************************************************************/
+
+static void
+delpermnode(permnode **ring)
+/* Delete permnode at head of circular list, making the next node head. */
+{
+ permnode *newring;
+
+ if (!*ring) return;
+
+ if ((*ring)->next == *ring)
+ newring = NULL;
+ else
+ {
+ newring = (*ring)->next;
+ newring->prev = (*ring)->prev;
+ (*ring)->prev->next = newring;
+ }
+
+ (*ring)->next = permnode_freelist;
+ permnode_freelist = *ring;
+
+ *ring = newring;
+}
+
+/************************************************************************/
+
+void
+deleteunmarked(permnode **ring)
+/* Delete all permutations in the ring that are not marked */
+{
+ permnode *pn,*firstmarked;
+
+ pn = *ring;
+ firstmarked = NULL;
+
+ while (pn != NULL && pn != firstmarked)
+ {
+ if (pn->mark)
+ {
+ if (!firstmarked) firstmarked = pn;
+ pn = pn->next;
+ }
+ else
+ delpermnode(&pn);
+ }
+
+ *ring = pn;
+}
+
+/************************************************************************/
+
+static void
+clearvector(permnode **vec, permnode **ring, int n)
+/* clear vec[0..n-1], freeing permnodes that have no other references
+ * and are not marked */
+{
+ int i;
+
+ for (i = 0; i < n; ++i)
+ if (vec[i])
+ {
+ if (vec[i] != ID_PERMNODE)
+ {
+ --(vec[i]->refcount);
+ if (vec[i]->refcount == 0 && !vec[i]->mark)
+ {
+ *ring = vec[i];
+ delpermnode(ring);
+ }
+ }
+ vec[i] = NULL;
+ }
+}
+
+/************************************************************************/
+
+static void
+initschreier(schreier *sh, int n)
+/* Initialise schreier structure to trivial orbits and empty vector */
+{
+ int i;
+
+ sh->fixed = -1;
+ for (i = 0; i < n; ++i)
+ {
+ sh->vec[i] = NULL;
+ sh->orbits[i] = i;
+ }
+}
+
+/************************************************************************/
+
+void
+newgroup(schreier **sh, permnode **ring, int n)
+/* Make the trivial group, allow for ring to be set elsewhere */
+{
+ *sh = newschreier(n);
+ initschreier(*sh,n);
+ if (ring) *ring = NULL;
+}
+
+/************************************************************************/
+
+static void
+applyperm(int *wp, int *p, int k, int n)
+/* Apply the permutation p, k times to each element of wp */
+{
+ int i,j,cyclen,kk,m;
+
+ TESTP(1,p,n);
+
+ if (k <= 5)
+ {
+ if (k == 0)
+ return;
+ else if (k == 1)
+ for (i = 0; i < n; ++i) wp[i] = p[wp[i]];
+ else if (k == 2)
+ for (i = 0; i < n; ++i) wp[i] = p[p[wp[i]]];
+ else if (k == 3)
+ for (i = 0; i < n; ++i) wp[i] = p[p[p[wp[i]]]];
+ else if (k == 4)
+ for (i = 0; i < n; ++i) wp[i] = p[p[p[p[wp[i]]]]];
+ else if (k == 5)
+ for (i = 0; i < n; ++i) wp[i] = p[p[p[p[p[wp[i]]]]]];
+ }
+ else if (k <= 19)
+ {
+#if !MAXN
+ DYNALLOC1(int,workpermA,workpermA_sz,n,"applyperm");
+#endif
+ for (i = 0; i < n; ++i) workpermA[i] = p[p[p[i]]];
+ for (; k >= 6; k -= 6)
+ for (i = 0; i < n; ++i) wp[i] = workpermA[workpermA[wp[i]]];
+ if (k == 1)
+ for (i = 0; i < n; ++i) wp[i] = p[wp[i]];
+ else if (k == 2)
+ for (i = 0; i < n; ++i) wp[i] = p[p[wp[i]]];
+ else if (k == 3)
+ for (i = 0; i < n; ++i) wp[i] = workpermA[wp[i]];
+ else if (k == 4)
+ for (i = 0; i < n; ++i) wp[i] = p[workpermA[wp[i]]];
+ else if (k == 5)
+ for (i = 0; i < n; ++i) wp[i] = p[p[workpermA[wp[i]]]];
+ }
+ else
+ {
+ m = SETWORDSNEEDED(n);
+#if !MAXN
+ DYNALLOC1(int,workpermA,workpermA_sz,n,"applyperm");
+ DYNALLOC1(int,workpermB,workpermB_sz,n,"applyperm");
+ DYNALLOC1(set,workset2,workset2_sz,m,"applyperm");
+#endif
+
+ EMPTYSET(workset2,m);
+
+ /* We will construct p^k in workpermB one cycle at a time. */
+
+ for (i = 0; i < n; ++i)
+ {
+ if (ISELEMENT(workset2,i)) continue;
+ if (p[i] == i)
+ workpermB[i] = i;
+ else
+ {
+ cyclen = 1;
+ workpermA[0] = i;
+ for (j = p[i]; j != i; j = p[j])
+ {
+ workpermA[cyclen++] = j;
+ ADDELEMENT(workset2,j);
+ }
+ kk = k % cyclen;
+ for (j = 0; j < cyclen; ++j)
+ {
+ workpermB[workpermA[j]] = workpermA[kk];
+ if (++kk == cyclen) kk = 0;
+ }
+ }
+ }
+ for (i = 0; i < n; ++i) wp[i] = workpermB[wp[i]];
+ }
+}
+
+/************************************************************************/
+
+static boolean
+filterschreier(schreier *gp, int *p, permnode **ring,
+ boolean ingroup, int maxlevel, int n)
+/* Filter permutation p up to level maxlevel of gp.
+ * Use ingroup=TRUE if p is known to be in the group, otherwise
+ * at least one equivalent generator is added unless it is proved
+ * (nondeterministically) that it is in the group already.
+ * maxlevel < 0 means no limit, maxlevel=0 means top level only, etc.
+ * Return TRUE iff some change is made. */
+{
+ int i,j,j1,j2,lev;
+ int ipwr;
+ schreier *sh;
+ int *orbits,*pwr;
+ permnode **vec,*curr;
+ boolean changed,lchanged,ident;
+#if !MAXN
+ DYNALLOC1(int,workperm,workperm_sz,n,"filterschreier");
+#endif
+
+++filtercount;
+
+ memcpy(workperm,p,n*sizeof(int));
+
+ if (*ring && p == (*ring)->p)
+ {
+ ingroup = TRUE;
+ curr = *ring;
+ }
+ else
+ curr = NULL;
+
+ /* curr is the location of workperm in ring, if anywhere */
+
+ sh = gp;
+ changed = FALSE;
+ if (maxlevel < 0) maxlevel = n+1;
+
+ for (lev = 0; lev <= maxlevel; ++lev)
+ {
+ for (i = 0; i < n; ++i) if (workperm[i] != i) break;
+ ident = (i == n);
+ if (ident) break;
+
+ lchanged = FALSE;
+ orbits = sh->orbits;
+ vec = sh->vec;
+ pwr = sh->pwr;
+ for (i = 0; i < n; ++i)
+ {
+ j1 = orbits[i];
+ while (orbits[j1] != j1) j1 = orbits[j1];
+ j2 = orbits[workperm[i]];
+ while (orbits[j2] != j2) j2 = orbits[j2];
+
+ if (j1 != j2)
+ {
+ lchanged = TRUE;
+ if (j1 < j2) orbits[j2] = j1;
+ else orbits[j1] = j2;
+ }
+ }
+ if (lchanged)
+ for (i = 0; i < n; ++i) orbits[i] = orbits[orbits[i]];
+
+ if (lchanged) changed = TRUE;
+
+ if (sh->fixed >= 0)
+ {
+ for (i = 0; i < n; ++i)
+ if (vec[i] && !vec[workperm[i]])
+ {
+ changed = TRUE;
+ ipwr = 0;
+ for (j = workperm[i]; !vec[j] ; j = workperm[j]) ++ipwr;
+
+ for (j = workperm[i]; !vec[j] ; j = workperm[j])
+ {
+ if (!curr)
+ {
+ if (!ingroup) addpermutation(ring,workperm,n);
+ else addpermutationunmarked(ring,workperm,n);
+ ingroup = TRUE;
+ curr = *ring;
+ }
+ vec[j] = curr;
+ pwr[j] = ipwr--;
+ ++curr->refcount;
+ }
+ }
+
+ j = workperm[sh->fixed];
+
+ while (j != sh->fixed)
+ {
+ applyperm(workperm,vec[j]->p,pwr[j],n);
+ ++multcount;
+ curr = NULL;
+ j = workperm[sh->fixed];
+ }
+ sh = sh->next;
+ }
+ else
+ break;
+ }
+
+ if (!ident && !ingroup)
+ {
+ changed = TRUE;
+ addpermutation(ring,p,n);
+ }
+
+ return changed;
+}
+
+/************************************************************************/
+
+boolean
+expandschreier(schreier *gp, permnode **ring, int n)
+/* filter random elements until schreierfails failures.
+ * Return true if it ever expanded. */
+{
+ int i,j,nfails,wordlen,skips;
+ boolean changed;
+ permnode *pn;
+#if !MAXN
+ DYNALLOC1(int,workperm2,workperm2_sz,n,"expandschreier");
+#endif
+
+ pn = *ring;
+ if (pn == NULL) return FALSE;
+
+ nfails = 0;
+ changed = FALSE;
+
+ for (skips = KRAN(17); --skips >= 0; ) pn = pn->next;
+
+ memcpy(workperm2,pn->p,n*sizeof(int));
+
+ while (nfails < schreierfails)
+ {
+ wordlen = 1 + KRAN(3);
+ for (j = 0; j < wordlen; ++j)
+ {
+ for (skips = KRAN(17); --skips >= 0; ) pn = pn->next;
+ for (i = 0; i < n; ++i) workperm2[i] = pn->p[workperm2[i]];
+ }
+ if (filterschreier(gp,workperm2,ring,TRUE,-1,n))
+ {
+ changed = TRUE;
+ nfails = 0;
+ }
+ else
+ ++nfails;
+ }
+
+ return changed;
+}
+
+/************************************************************************/
+
+int*
+getorbits(int *fix, int nfix, schreier *gp, permnode **ring, int n)
+/* Get a pointer to the orbits for this partial base. The pointer
+ * remains valid until pruneset(), getorbits(), getorbitsmin()
+ * or grouporder() is called with an incompatible base (neither a
+ * prefix nor an extension). The contents of the array pointed to
+ * MUST NOT BE MODIFIED by the calling program.
+ */
+{
+ int k;
+ schreier *sh,*sha;
+
+ sh = gp;
+ for (k = 0; k < nfix; ++k)
+ {
+ if (sh->fixed != fix[k]) break;
+ sh = sh->next;
+ }
+
+ if (k == nfix) return sh->orbits;
+
+ sh->fixed = fix[k];
+ clearvector(sh->vec,ring,n);
+ sh->vec[fix[k]] = ID_PERMNODE;
+
+ for (sha = sh->next; sha ; sha = sha->next) clearvector(sha->vec,ring,n);
+
+ for (++k; k <= nfix; ++k)
+ {
+ if (!sh->next) sh->next = newschreier(n);
+ sh = sh->next;
+ initschreier(sh,n);
+ if (k < nfix)
+ {
+ sh->fixed = fix[k];
+ sh->vec[fix[k]] = ID_PERMNODE;
+ }
+ else
+ sh->fixed = -1;
+ }
+
+ if (*ring) expandschreier(gp,ring,n);
+ return sh->orbits;
+}
+
+/************************************************************************/
+
+int
+getorbitsmin(int *fix, int nfix, schreier *gp, permnode **ring,
+ int **orbits, int *cell, int ncell, int n, boolean changed)
+/* If the basis elements fix[0..nfix-1] are minimal in their orbits,
+ * as far as we know, return value nfix and set *orbits to point
+ * to orbits fixing fix[0..nfix-1]. If fix[i] is seen to be not
+ * minimal for some i <= nfix-1, return i and set *orbits to point
+ * to orbits fixing fix[0..i-1]. If the partial base is already
+ * known, or fix[0..nfix-1] can already be seen to be non-minimal,
+ * do this work without more filtering. This shortcut is turned
+ * off if changed==TRUE. Otherwise, filter until schreierfails
+ * failures.
+ * The pointer returned remains valid until pruneset(), getorbits(),
+ * getorbitsmin() or grouporder() is called with an incompatible base
+ * (neither a prefix nor an extension). The contents of the array
+ * pointed to MUST NOT BE MODIFIED by the calling program.
+ * If cell != NULL, return early if possible when cell[0..ncell-1]
+ * are all in the same orbit fixing fix[0..nfix-1]. Otherwise
+ * cell,ncell play no part in the computation.
+ */
+{
+ schreier *sh,*sha;
+ int *fixorbs;
+ int i,j,k,icell,nfails,wordlen,skips;
+ permnode *pn;
+#if !MAXN
+ DYNALLOC1(int,workperm2,workperm2_sz,n,"expandschreier");
+#endif
+
+ sh = gp;
+ k = 0;
+ if (!changed)
+ for (k = 0; k < nfix; ++k)
+ {
+ if (sh->orbits[fix[k]] != fix[k])
+ {
+ *orbits = sh->orbits;
+ return k;
+ }
+ if (sh->fixed != fix[k]) break;
+ sh = sh->next;
+ }
+
+ if (k == nfix)
+ {
+ *orbits = sh->orbits;
+ return nfix;
+ }
+
+ sh->fixed = fix[k];
+ clearvector(sh->vec,ring,n);
+ sh->vec[fix[k]] = ID_PERMNODE;
+
+ for (sha = sh->next; sha ; sha = sha->next) clearvector(sha->vec,ring,n);
+
+ for (++k; k <= nfix; ++k)
+ {
+ if (!sh->next) sh->next = newschreier(n);
+ sh = sh->next;
+ initschreier(sh,n);
+ if (k < nfix)
+ {
+ sh->fixed = fix[k];
+ sh->vec[fix[k]] = ID_PERMNODE;
+ }
+ else
+ sh->fixed = -1;
+ }
+ *orbits = fixorbs = sh->orbits;
+
+ if (cell)
+ {
+ for (icell = 1; icell < ncell; ++icell)
+ if (fixorbs[cell[icell]] != fixorbs[cell[0]]) break;
+
+ if (icell >= ncell) return nfix;
+ }
+
+ if (*ring)
+ {
+ pn = *ring;
+
+ nfails = 0;
+
+ for (skips = KRAN(17); --skips >= 0; ) pn = pn->next;
+
+ memcpy(workperm2,pn->p,n*sizeof(int));
+
+ while (nfails < schreierfails)
+ {
+ wordlen = 1 + KRAN(3);
+ for (j = 0; j < wordlen; ++j)
+ {
+ for (skips = KRAN(17); --skips >= 0; ) pn = pn->next;
+ for (i = 0; i < n; ++i) workperm2[i] = pn->p[workperm2[i]];
+ }
+ if (filterschreier(gp,workperm2,ring,TRUE,-1,n))
+ {
+ nfails = 0;
+ sh = gp;
+ for (k = 0; k < nfix; ++k)
+ {
+ if (sh->orbits[fix[k]] != fix[k])
+ {
+ *orbits = sh->orbits;
+ return k;
+ }
+ sh = sh->next;
+ }
+ if (cell)
+ {
+ for ( ; icell < ncell; ++icell)
+ if (fixorbs[cell[icell]] != fixorbs[cell[0]]) break;
+
+ if (icell >= ncell) return nfix;
+ }
+ }
+ else
+ ++nfails;
+ }
+ }
+
+ return nfix;
+}
+
+/************************************************************************/
+
+void
+pruneset(set *fixset, schreier *gp, permnode **ring, set *x, int m, int n)
+/* Remove from x any point not minimal for the orbits for this base.
+ * If the base is already known, just provide the orbits without
+ * more filtering. Otherwise, filter until schreierfails failures.
+ */
+{
+ int i,k;
+ schreier *sh,*sha;
+ int *orbits;
+
+#if !MAXN
+ DYNALLOC1(set,workset,workset_sz,m,"pruneset");
+#endif
+ for (i = 0; i < m; ++i) workset[i] = fixset[i];
+
+ sh = gp;
+ while (sh->fixed >= 0 && ISELEMENT(workset,sh->fixed))
+ {
+ DELELEMENT(workset,sh->fixed);
+ sh = sh->next;
+ }
+
+ k = nextelement(workset,m,-1);
+ if (k < 0)
+ orbits = sh->orbits;
+ else
+ {
+ sh->fixed = k;
+ clearvector(sh->vec,ring,n);
+ sh->vec[k] = ID_PERMNODE;
+
+ for (sha = sh->next; sha ; sha = sha->next)
+ clearvector(sha->vec,ring,n);
+
+ while ((k = nextelement(workset,m,k)) >= 0)
+ {
+ if (!sh->next) sh->next = newschreier(n);
+ sh = sh->next;
+ initschreier(sh,n);
+ sh->fixed = k;
+ sh->vec[k] = ID_PERMNODE;
+ }
+ if (!sh->next) sh->next = newschreier(n);
+ sh = sh->next;
+ initschreier(sh,n);
+ sh->fixed = -1;
+
+ if (*ring) expandschreier(gp,ring,n);
+ orbits = sh->orbits;
+ }
+
+ for (k = -1; (k = nextelement(x,m,k)) >= 0; )
+ if (orbits[k] != k) DELELEMENT(x,k);
+}
+
+/************************************************************************/
+
+int
+schreier_gens(permnode *ring)
+/* Returns the number of generators in the ring */
+{
+ int j;
+ permnode *pn;
+
+ if (!ring) j = 0;
+ else for (j = 1, pn = ring->next; pn != ring; pn = pn->next) ++j;
+
+ return j;
+}
+
+/************************************************************************/
+
+void
+dumpschreier(FILE *f, schreier *gp, permnode *ring, int n)
+/* Dump the whole schreier structure to file f. */
+{
+ schreier *sh;
+ permnode *pn;
+ int i,j,jj,k;
+
+
+ fprintf(f,"Schreier structure n=%d; ",n);
+
+ jj = -1;
+ for (j = 0, sh = gp; sh; sh = sh->next)
+ {
+ ++j;
+ if (sh->fixed < 0 && jj < 0) jj = j;
+ }
+ fprintf(f," levels=%d (%d used); ",j,jj);
+
+ if (!ring) j = 0;
+ else for (j = 1, pn = ring->next; pn != ring; pn = pn->next) ++j;
+ fprintf(f,"gens=%d; ",j);
+
+ for (j = 0, sh = schreier_freelist; sh; sh = sh->next) ++j;
+ for (k = 0, pn = permnode_freelist; pn; pn = pn->next) ++k;
+ fprintf(f,"freelists: %d,%d\n",j,k);
+
+ if (ring)
+ {
+ fprintf(f,"Generators:\n");
+ pn = ring;
+ do
+ {
+ fprintf(f," %03x ref=%lu mk=%d alloc=%d p=",PNCODE(pn),
+ pn->refcount,pn->mark,pn->nalloc);
+ for (i = 0; i < n; ++i) fprintf(f," %d",pn->p[i]);
+ fprintf(f,"\n");
+ pn = pn->next;
+ } while (pn != ring);
+ }
+
+ if (gp)
+ {
+ fprintf(f,"Levels:\n");
+ for (sh = gp; sh; sh = sh->next)
+ {
+ fprintf(f,"fixed=%2d alloc=%d vec=",sh->fixed,sh->nalloc);
+ for (i = 0; i < n; ++i)
+ {
+ if (sh->vec[i] == ID_PERMNODE) fprintf(f," %d=e",i);
+ else if (sh->vec[i])
+ {
+ k = sh->pwr[i];
+ j = (sh->vec[i])->p[i];
+ fprintf(f," %03x",PNCODE(sh->vec[i]));
+ if (k == 1)
+ fprintf(f,"(%d,%d)",i,j);
+ else
+ {
+ fprintf(f,"^%d",k);
+ while (--k >= 1) j = (sh->vec[i])->p[j];
+ fprintf(f,"(%d,%d)",i,j);
+ }
+ }
+ }
+ fprintf(f,"\n Orb=");
+ j = 0;
+ for (i = 0; i < n; ++i)
+ {
+ fprintf(f," %d",sh->orbits[i]);
+ if (sh->orbits[i] == i) ++j;
+ }
+ fprintf(f," [%d]\n",j);
+ if (sh->fixed < 0) break;
+ }
+ }
+}
+
+/************************************************************************/
+
+void
+grouporder(int *fix, int nfix, schreier *gp, permnode **ring,
+ double *grpsize1, int *grpsize2, int n)
+/* process the base like in getorbits(), then return the product of the
+ * orbits along the base, using the largest orbit at the end if the
+ * base is not complete.
+*/
+{
+ schreier *sh;
+ int i,j,k,fx;
+ int *orb;
+
+#if !MAXN
+ DYNALLOC1(int,workperm,workperm_sz,n,"grouporder");
+#endif
+
+ getorbits(fix,nfix,gp,ring,n);
+ expandschreier(gp,ring,n);
+ expandschreier(gp,ring,n);
+ *grpsize1 = 1.0; *grpsize2 = 0;
+
+ for (i = 0, sh = gp; i < nfix; ++i, sh = sh->next)
+ {
+ orb = sh->orbits;
+ fx = orb[sh->fixed];
+ k = 0;
+ for (j = fx; j < n; ++j) if (orb[j] == fx) ++k;
+ MULTIPLY(*grpsize1,*grpsize2,k);
+ }
+
+ orb = sh->orbits;
+ k = 1;
+ for (i = 0; i < n; ++i)
+ if (orb[i] == i)
+ workperm[i] = 1;
+ else
+ {
+ ++workperm[orb[i]];
+ if (workperm[orb[i]] > k) k = workperm[orb[i]];
+ }
+ MULTIPLY(*grpsize1,*grpsize2,k);
+}
+
+/*****************************************************************************
+* *
+* schreier_check() checks that this file is compiled compatibly with the *
+* given parameters. If not, call exit(1). *
+* *
+*****************************************************************************/
+
+void
+schreier_check(int wordsize, int m, int n, int version)
+{
+ if (wordsize != WORDSIZE)
+ {
+ fprintf(ERRFILE,"Error: WORDSIZE mismatch in schreier.c\n");
+ exit(1);
+ }
+
+#if MAXN
+ if (m > MAXM)
+ {
+ fprintf(ERRFILE,"Error: MAXM inadequate in schreier.c\n");
+ exit(1);
+ }
+
+ if (n > MAXN)
+ {
+ fprintf(ERRFILE,"Error: MAXN inadequate in schreier.c\n");
+ exit(1);
+ }
+#endif
+
+ if (version < NAUTYREQUIRED)
+ {
+ fprintf(ERRFILE,"Error: schreier.c version mismatch\n");
+ exit(1);
+ }
+}
+
+/************************************************************************/
+
+void
+schreier_freedyn(void)
+{
+#if !MAXN
+ DYNFREE(workperm,workperm_sz);
+ DYNFREE(workperm2,workperm2_sz);
+ DYNFREE(workpermA,workpermA_sz);
+ DYNFREE(workpermB,workpermB_sz);
+ DYNFREE(workset,workset_sz);
+ DYNFREE(workset2,workset2_sz);
+#endif
+ clearfreelists();
+}