From eda702cd6c0ddaae215ee943dcfae648e3b1ac40 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:27:40 +0100 Subject: [PATCH 01/11] feat: update source code to Surge 2.0 - WORDSIZE changed from 32 to 64 (nautyL1.a) - New -R switch for aromaticity detection filtering - New -h and -C switches for ring restrictions - Updated canonsdf utility Co-Authored-By: Claude Opus 4.6 --- doc/surge2_0.pdf | Bin 0 -> 441407 bytes src/canonsdf.c | 1243 ++- src/geng.c | 5014 +++++------ src/planarity.c | 20772 ++++++++++++++++++++++----------------------- src/plugin0.c | 28 +- src/plugin1.c | 34 +- src/plugin2.c | 30 +- src/plugin3.c | 36 +- src/surge.c | 5518 ++++++------ 9 files changed, 16679 insertions(+), 15996 deletions(-) create mode 100644 doc/surge2_0.pdf diff --git a/doc/surge2_0.pdf b/doc/surge2_0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4737dc11b15414dd897bf02387b06575bf1a0c6a GIT binary patch literal 441407 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 !}@HG)0atIiEvwdFk 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

$ZL@6oq~2 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
0jRk7?ZEttbLxYmX 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 U6(Are)UL>22Qf8u^L2;(6QO*;zyJUM literal 0 HcmV?d00001 diff --git a/src/canonsdf.c b/src/canonsdf.c index 8e2810d..93b796b 100644 --- a/src/canonsdf.c +++ b/src/canonsdf.c @@ -1,451 +1,792 @@ -#define WORDSIZE 64 -#define MAXN WORDSIZE -#include "gtools.h" - -#define USAGE \ - "canonsdf [-A|-w|-u] [-p#|-p#:#] [-e#|-e#:#] [-d#] [-c#] [-a] [-Hcode]" - -#define HELPTEXT \ -" Process molecules in SDF format.\n" \ -"\n" \ -" -a Allow more than 4 neighbours to include H\n" \ -" and N with more than 4 neighbours (default not allowed)\n" \ -" -d# Maximum degree for simple graph without H (default 4)\n" \ -" -c# Maximum coordination number without H (default 4)\n" \ -" -e# -e#:# Number of bonds between non-H atoms\n" \ -"\n" \ -" -p# -p#:# Only process one input or range of inputs (first is 1)\n" \ -" -Hcode Output is restricted to the input with this hash code.\n" \ -" The code continues to the end of the argument. If this option\n" \ -" is given, all other constraints are ignored.\n" \ -"\n" \ -" -w Write SDF to output identical to the input\n" \ -" -A Write an ASCII molecule decription\n" \ -" -u Just count\n" \ -" The default is to write a 16-character hash code\n" - - -static char *atom[] - = { "H","C","N","O","P","S","F","Cl","Br","I","B","Si" }; -static int val1[] - = { 1, 4, 3, 2, 3, 2, 1, 1, 1, 1, 1, 4 }; -static int val2[] - = { 0, 0, 5, 2, 5, 4, 0, 0, 0, 0, 0, 0 }; -static int val3[] - = { 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0 }; -#define NUMATOMS (sizeof(atom)/sizeof(atom[0])) -#define HYDROGEN 0 -#define NITROGEN 2 - -static int fuzz1[] = {0x37541,0x61532,0x05257,0x26416}; -static int fuzz2[] = {0x06532,0x70236,0x35523,0x62437}; - -#define FUZZ1(x) ((x) ^ fuzz1[(x)&3]) -#define FUZZ2(x) ((x) ^ fuzz2[(x)&3]) -#define MV(x) ((x) >> (WORDSIZE-n)) - - -typedef char card[84]; -static card line[200]; - -static int -atomcode(char *name) -{ - int i; - - for (i = 0; i < NUMATOMS; ++i) - if (strcmp(atom[i],name) == 0) break; - - if (i == NUMATOMS) - { - fprintf(stderr,">E unknown element %s\n",name); - exit(1); - } - - return i; -} - -static int -roundval(int index, int val) -/* Round val up to the next legal value */ -{ - if (val1[index] >= val) return val1[index]; - if (val2[index] >= val) return val2[index]; - if (val3[index] >= val) return val3[index]; - - fprintf(stderr,"index=%d val=%d\n",index,val); - gt_abort(">E can't determine valence\n"); -} - -static int -readmolecule() -/* Read one SDF molecule into line[*], return number of lines (0 if none) */ -{ - int numlines; - - if (fgets(line[0],82,stdin) == NULL) return 0; - numlines = 1; - while (fgets(line[numlines],82,stdin) != NULL) - { - if (strcmp(line[numlines],"$$$$\n") == 0 - || strcmp(line[numlines],"$$$$\r\n") == 0 - || strcmp(line[numlines],"$$$$\r\r\n") == 0) - { - if (numlines < 6) gt_abort(">E too few lines\n"); - return numlines+1; - } - ++numlines; - } - - gt_abort(">E EOF encountered\n"); -} - -static void -writemolecule(int numlines) -{ - int i; - - for (i = 0; i < numlines; ++i) fputs(line[i],stdout); -} - -static void -writeascii(int na, int *which, int *hyd, - int nb, int *u, int *v, int *mult) -/* Write molecule in ASCII */ -{ - int i; - - for (i = 0; i < na; ++i) - { - printf("%d:%s",i,atom[which[i]]); - if (hyd[i] == 1) printf("H "); - else if (hyd[i] > 1) printf("H%d ",hyd[i]); - else printf(" "); - } - - for (i = 0; i < nb; ++i) - { - printf(" %d%c%d",u[i],(mult[i]==1?'-':mult[i]==2?'=':'#'),v[i]); - } - printf("\n"); -} - -static void -makecanoncode(int na, int *which, int *hyd, - int nb, int *u, int *v, int *mult, char *code) -/* Make a canonical hashcode for the molecule */ -{ - int i,j,nhyd; - int n,nh; - graph g[MAXN],h[MAXN]; - int lab[MAXN],ptn[MAXN],orbits[MAXN],weight[MAXN]; - static DEFAULTOPTIONS_GRAPH(options); - statsblk stats; - set workspace[200]; - unsigned long long code1,code2; - char *p; - char *glyph = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%"; - - if (strlen(glyph) != 64) gt_abort(">E glyph error\n"); - - nhyd = 0; - for (i = 0; i < na; ++i) nhyd += hyd[i]; - - if (2*na + nhyd > MAXN) gt_abort(">E molecule too big\n"); - - n = 2*na + nhyd; - for (i = 0; i < na; ++i) - { - weight[i] = which[i]; - weight[na+i] = which[i] + 128; - } - for (i = 2*na; i < n; ++i) weight[i] = 0; - setlabptn(weight,lab,ptn,n); - - EMPTYSET(g,n); -#define ADDE(ii,jj) { g[ii] |= bit[jj]; g[jj] |= bit[ii]; } - - nh = 2*na; - for (i = 0; i < na; ++i) - { - ADDE(i,i+na); - for (j = 0; j < hyd[i]; ++j) - { - ADDE(i,nh); - ++nh; - } - } - - if (nh != n) gt_abort(">E huh?\n"); - - for (i = 0; i < nb; ++i) - { - if (mult[i] == 1 || mult[i] == 3) - ADDE(u[i],v[i]); - if (mult[i] == 2 || mult[i] == 3) - ADDE(na+u[i],na+v[i]); - } - - options.getcanon = TRUE; - options.defaultptn = FALSE; - nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,200,1,n,h); - - code1 = 2 + 13*(nhyd+1); - code2 = n + 17*nb; - for (i = 0; i < n; ++i) - { - code1 = 1237LLU * code1 + FUZZ1(MV(h[i])); - code2 = 1233457LLU * code2 + FUZZ2(MV(h[i])); - } - - p = code; - for (i = 0; i < 8; ++i) - { - *p++ = glyph[code1%64]; - code1 >>= 8; - } - for (i = 0; i < 8; ++i) - { - *p++ = glyph[code2%64]; - code2 >>= 8; - } - - *p = '\0'; -} - -static void -decode(int numlines, int *n, int *which, int *hyd, - int *nb, int *u, int *v, int *mult) -/* which[0..*n-1] = non-H atoms - hyd[0..*n-1] = number of hydrogens - *nb = number of non-H bonds - u[0..*nb-1]-v[0..*nb-1] = bonds - mult[0..*nb-1] = multiplicities (1,2,3) -*/ -{ - int i,j,nv,ne; - int val[200],needval[200]; - double x,y,z; - int j1,j2,j3,j4,j5,j6,q; - char s[5]; - - /* See SDFformats.txt for which formats are handled */ - - if (sscanf(line[3],"%d%d",&nv,&ne) != 2) - gt_abort(">E Can't read count line\n"); - - *n = 0; - - for (i = 0; i < nv; ++i) - { - if (sscanf(line[4+i],"%lf%lf%lf%s%d%d%d%d%d%d",&x,&y,&z, - s,&j1,&j2,&j3,&j4,&j5,&j6) != 10) - gt_abort(">E Can't read atom line\n"); - which[i] = atomcode(s); - if (which[i] != HYDROGEN) - { - if (j4 > 0) hyd[i] = j4-1; - else hyd[i] = 0; - ++*n; - - if (j6 > 0) val[i] = j6; - else val[i] = 0; - - needval[i] = (j4 == 0) && (j6 == 0); - } - } - - *nb = 0; - - for (i = 0; i < ne; ++i) - { - if (sscanf(line[4+nv+i],"%d%d%d",&j1,&j2,&j3) != 3) - gt_abort(">E Can't read bond line\n"); - if (j3 < 1 || j3 > 3) - gt_abort(">E irregular multiplicity, maybe aromatic\n"); - if (which[j1-1] != HYDROGEN && which[j2-1] != HYDROGEN) - { - u[*nb] = j1-1; - v[*nb] = j2-1; - mult[*nb] = j3; - ++*nb; - } - else if (which[j1-1] == HYDROGEN) - ++hyd[j2-1]; - else if (which[j2-1] == HYDROGEN) - ++hyd[j1-1]; - else - gt_abort(">E the impossible happened\n"); - } - - for (i = 0; i < *n; ++i) - { - if (needval[i]) - { - q = 0; - for (j = 0; j < *nb; ++j) - if (u[j] == i || v[j] == i) q += mult[j]; - val[i] = roundval(which[i],q+hyd[i]); - hyd[i] = val[i] - q; - } - else if (val[i] > 0) - { - q = 0; - for (j = 0; j < *nb; ++j) - if (u[j] == i || v[j] == i) q += mult[j]; - hyd[i] = val[i] - q; - } - } -} - -static boolean -isgood(int n, int *which, int *hyd, int nb, int *u, int *v, - int *mult, boolean aswitch, int dmax, int cmax, - long mine, long maxe, char *neededhash) -{ - int deg[MAXN]; - int i,coord; - char hashcode[20]; - - if (neededhash) - { - makecanoncode(n,which,hyd,nb,u,v,mult,hashcode); - return (strcmp(neededhash,hashcode) == 0); - } - - if (nb < mine || nb > maxe) return FALSE; - - for (i = 0; i < n; ++i) deg[i] = 0; - - for (i = 0; i < nb; ++i) - { - ++deg[u[i]]; - ++deg[v[i]]; - } - - for (i = 0; i < n; ++i) - { - coord = deg[i] + hyd[i]; - if (!aswitch) - { - if (coord > 4 && hyd[i] > 0) return FALSE; - if (which[i] == NITROGEN && coord > 4) return FALSE; - } - if (deg[i] > dmax) return FALSE; - if (coord > cmax) return FALSE; - } - - return TRUE; -} - -int -main(int argc, char *argv[]) -{ - long long nin,nout; - boolean badargs; - boolean eswitch,pswitch,aswitch,dswitch,cswitch; - boolean Hswitch,wswitch,uswitch,Aswitch; - int numlines,maxd,maxc; - int i,j,n,nb; - int u[2*MAXN],v[2*MAXN],mult[2*MAXN],which[MAXN],hyd[MAXN]; - char sw,*arg,*neededhash; - long startindex,endindex,mine,maxe; - char hashcode[20]; - - HELP; - - nauty_check(WORDSIZE,1,1,NAUTYVERSIONID); - - aswitch = dswitch = cswitch = wswitch = uswitch = Aswitch = FALSE; - eswitch = pswitch = Hswitch = badargs = FALSE; - - for (j = 1; !badargs && j < argc; ++j) - { - arg = argv[j]; - if (arg[0] == '-' && arg[1] != '\0') - { - ++arg; - while (*arg != '\0') - { - sw = *arg++; - SWBOOLEAN('u',uswitch) - else SWBOOLEAN('w',wswitch) - else SWBOOLEAN('A',Aswitch) - else SWBOOLEAN('a',aswitch) - else SWINT('c',cswitch,maxc,"canonsdf -c") - else SWINT('d',dswitch,maxd,"canonsdf -d") - else SWRANGE('p',":-",pswitch, - startindex,endindex,"canonsdf -p") - else SWRANGE('e',":-",eswitch,mine,maxe,"canonsdf -e") - else if (sw == 'H') - { - Hswitch = TRUE; - neededhash = arg; - break; - } - else badargs = TRUE; - } - } - else - badargs = TRUE; - } - - if (badargs) - { - fprintf(stderr,">E Usage: %s\n",USAGE); - GETHELP; - exit(1); - } - - if (!cswitch) maxc = 4; - if (!dswitch) maxd = 4; - if (pswitch && startindex < 0) startindex = 0; - if (!Hswitch) neededhash = NULL; - - if ((uswitch!=0)+(Aswitch!=0)+(wswitch!=0) > 1) - gt_abort(">E -v, -w and -u are incompatible\n"); - - if (!eswitch) - { - mine = 0; - maxe = NAUTY_INFINITY; - } - - nin = nout = 0; - while ((numlines = readmolecule()) != 0) - { - ++nin; - if (!pswitch || - (nin >= startindex && nin <= endindex)) - { - decode(numlines,&n,which,hyd,&nb,u,v,mult); - if (isgood(n,which,hyd,nb,u,v,mult,aswitch, - maxd,maxc,mine,maxe,neededhash)) - { - ++nout; - if (!uswitch) - { - if (wswitch) writemolecule(numlines); - else if (Aswitch) writeascii(n,which,hyd,nb,u,v,mult); - else - { - makecanoncode(n,which,hyd,nb,u,v,mult,hashcode); - printf("%s\n",hashcode); - } - } - } - } - } - - if (uswitch) - fprintf(stderr, - ">Z read %lld molecules; %lld removed, %lld remaining\n", - nin,nin-nout,nout); - else - fprintf(stderr, - ">Z read %lld molecules; %lld removed, %lld written\n", - nin,nin-nout,nout); - - exit(0); -} +#define WORDSIZE 64 +#define MAXN WORDSIZE +#include "gtools.h" + +#define USAGE \ + "canonsdf [-A|-w|-u] [-p#|-p#:#] [-e#|-e#:#] [-d#] [-c#] [-a] [-Hcode]" + +#define HELPTEXT \ +" Process molecules in SDF format.\n" \ +"\n" \ +" -a Allow more than 4 neighbours to include H\n" \ +" and N with more than 4 neighbours (default not allowed)\n" \ +" -d# Maximum degree for simple graph without H (default 4)\n" \ +" -c# Maximum coordination number without H (default 4)\n" \ +" -e# -e#:# Number of bonds between non-H atoms\n" \ +"\n" \ +" -p# -p#:# Only process one input or range of inputs (first is 1)\n" \ +" -Hcode Output is restricted to the input with this hash code.\n" \ +" The code continues to the end of the argument. If this option\n" \ +" is given, all other constraints are ignored.\n" \ +"\n" \ +" -w Write SDF to output identical to the input\n" \ +" -A Write an ASCII molecule decription\n" \ +" -R Use aromaticity to form canonical form\n" \ +" -s Form canonical form using only simple graph include hydrogens\n" \ +" -S Form canonical form using only simple graph exclude hydrogens\n" \ +" -u Just count\n" \ +" The default is to write a 16-character hash code\n" + + +static char *atom[] + = { "H","C","N","O","P","S","F","Cl","Br","I","B","Si" }; +static int val1[] + = { 1, 4, 3, 2, 3, 2, 1, 1, 1, 1, 1, 4 }; +static int val2[] + = { 0, 0, 5, 0, 5, 4, 0, 0, 0, 0, 0, 0 }; +static int val3[] + = { 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0 }; +#define NUMATOMS (sizeof(atom)/sizeof(atom[0])) +#define HYDROGEN 0 +#define CARBON 1 +#define NITROGEN 2 + +static int fuzz1[] = {0x37541,0x61532,0x05257,0x26416}; +static int fuzz2[] = {0x06532,0x70236,0x35523,0x62437}; + +#define FUZZ1(x) ((x) ^ fuzz1[(x)&3]) +#define FUZZ2(x) ((x) ^ fuzz2[(x)&3]) +#define MV(x) ((x) >> (WORDSIZE-n)) + +typedef char card[84]; +static card line[200]; +static unsigned long long nin; + +static int +atomcode(char *name) +{ + int i; + + for (i = 0; i < NUMATOMS; ++i) + if (strcmp(atom[i],name) == 0) break; + + if (i == NUMATOMS) + { + fprintf(stderr,">E unknown element %s\n",name); + exit(1); + } + + return i; +} + +static int +roundval(int index, int val) +/* Round val up to the next legal value */ +{ + if (val1[index] >= val) return val1[index]; + if (val2[index] >= val) return val2[index]; + if (val3[index] >= val) return val3[index]; + + fprintf(stderr,"index=%d val=%d\n",index,val); + gt_abort(">E can't determine valence\n"); +} + +static int +readmolecule() +/* Read one SDF molecule into line[*], return number of lines (0 if none) */ +{ + int numlines; + + if (fgets(line[0],82,stdin) == NULL) return 0; + numlines = 1; + while (fgets(line[numlines],82,stdin) != NULL) + { + if (strcmp(line[numlines],"$$$$\n") == 0 + || strcmp(line[numlines],"$$$$\r\n") == 0 + || strcmp(line[numlines],"$$$$\r\r\n") == 0) + { + if (numlines < 6) gt_abort(">E too few lines\n"); + return numlines+1; + } + ++numlines; + } + + gt_abort(">E EOF encountered\n"); +} + +static void +writemolecule(int numlines) +{ + int i; + + for (i = 0; i < numlines; ++i) fputs(line[i],stdout); +} + +static void +writeascii(int na, int *which, int *hyd, + int nb, int *u, int *v, int *mult) +/* Write molecule in ASCII */ +{ + int i; + + for (i = 0; i < na; ++i) + { + printf("%d:%s",i,atom[which[i]]); + if (hyd[i] == 1) printf("H "); + else if (hyd[i] > 1) printf("H%d ",hyd[i]); + else printf(" "); + } + + for (i = 0; i < nb; ++i) + { + printf(" %d%c%d",u[i],(mult[i]==1?'-':mult[i]==2?'=':'#'),v[i]); + } + printf("\n"); +} + +static void +makecanoncode(int na, int *which, int *hyd, + int nb, int *u, int *v, int *mult, char *code) +/* Make a canonical hashcode for the molecule */ +{ + int i,j,nhyd; + int n,nh; + graph g[MAXN],h[MAXN]; + int lab[MAXN],ptn[MAXN],orbits[MAXN],weight[MAXN]; + static DEFAULTOPTIONS_GRAPH(options); + statsblk stats; + set workspace[200]; + unsigned long long code1,code2; + char *p; + char *glyph = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%"; + + if (strlen(glyph) != 64) gt_abort(">E glyph error\n"); + + nhyd = 0; + for (i = 0; i < na; ++i) nhyd += hyd[i]; + + if (2*na + nhyd > MAXN) gt_abort(">E molecule too big\n"); + + n = 2*na + nhyd; + for (i = 0; i < na; ++i) + { + weight[i] = which[i]; + weight[na+i] = which[i] + 128; + } + for (i = 2*na; i < n; ++i) weight[i] = 0; + setlabptn(weight,lab,ptn,n); + + EMPTYSET(g,n); +#define ADDE(ii,jj) { g[ii] |= bit[jj]; g[jj] |= bit[ii]; } + + nh = 2*na; + for (i = 0; i < na; ++i) + { + ADDE(i,i+na); + for (j = 0; j < hyd[i]; ++j) + { + ADDE(i,nh); + ++nh; + } + } + + if (nh != n) gt_abort(">E huh?\n"); + + for (i = 0; i < nb; ++i) + { + if (mult[i] == 1 || mult[i] == 3) + ADDE(u[i],v[i]); + if (mult[i] == 2 || mult[i] == 3) + ADDE(na+u[i],na+v[i]); + } + + options.getcanon = TRUE; + options.defaultptn = FALSE; + nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,200,1,n,h); + + code1 = 2 + 13*(nhyd+1); + code2 = n + 17*nb; + for (i = 0; i < n; ++i) + { + code1 = 1237LLU * code1 + FUZZ1(MV(h[i])); + code2 = 1233457LLU * code2 + FUZZ2(MV(h[i])); + } + + p = code; + for (i = 0; i < 8; ++i) + { + *p++ = glyph[code1%64]; + code1 >>= 8; + } + for (i = 0; i < 8; ++i) + { + *p++ = glyph[code2%64]; + code2 >>= 8; + } + + *p = '\0'; +} + +static void +makesimplecode(int na, int *which, int *hyd, boolean Sswitch, + int nb, int *u, int *v, char *code) +/* Make a canonical hashcode for the molecule ignoring bond multiplicity. */ +{ + int i,j,nhyd; + int n,nh; + graph g[MAXN],h[MAXN]; + int lab[MAXN],ptn[MAXN],orbits[MAXN],weight[MAXN]; + static DEFAULTOPTIONS_GRAPH(options); + statsblk stats; + set workspace[200]; + unsigned long long code1,code2; + char *p; + char *glyph = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%"; + + if (strlen(glyph) != 64) gt_abort(">E glyph error\n"); + + nhyd = 0; + + if (!Sswitch) + { + for (i = 0; i < na; ++i) nhyd += hyd[i]; + if (na + nhyd > MAXN) gt_abort(">E molecule too big\n"); + } + + n = na + nhyd; + for (i = 0; i < na; ++i) weight[i] = which[i]; + for (i = na; i < n; ++i) weight[i] = 0; + setlabptn(weight,lab,ptn,n); + + EMPTYSET(g,n); +#define ADDE(ii,jj) { g[ii] |= bit[jj]; g[jj] |= bit[ii]; } + + nh = na; + if (!Sswitch) + { + for (i = 0; i < na; ++i) + { + for (j = 0; j < hyd[i]; ++j) + { + ADDE(i,nh); + ++nh; + } + } + } + + if (nh != n) gt_abort(">E huh?\n"); + + for (i = 0; i < nb; ++i) ADDE(u[i],v[i]); + +//writeg6(stdout,g,1,n); + + options.getcanon = TRUE; + options.defaultptn = FALSE; + nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,200,1,n,h); + + code1 = 2 + 13*(nhyd+1); + code2 = n + 17*nb; + for (i = 0; i < n; ++i) + { + code1 = 1237LLU * code1 + FUZZ1(MV(h[i])); + code2 = 1233457LLU * code2 + FUZZ2(MV(h[i])); + } + + p = code; + for (i = 0; i < 8; ++i) + { + *p++ = glyph[code1%64]; + code1 >>= 8; + } + for (i = 0; i < 8; ++i) + { + *p++ = glyph[code2%64]; + code2 >>= 8; + } + + *p = '\0'; +} + +#define MAXAROMCODES 200 +static struct { int mult[2*MAXN]; char code[20]; } aromqueue[MAXAROMCODES]; +static int Cpos,aromhead,aromtail; +static struct twostep { int v1,v3; int e1,e2; setword v23; } twosteps[MAXN][4]; + /* Paths of three carbon atoms of the form C-C=C. */ +static int numtwosteps[MAXN]; + +static void +maketwosteps(int na, int nb, int *which, int *u, int *v, int *mult) +/* Find all 2-paths C-C=C. Could remove tree-like bits. */ +{ + int i,j,xi,yi; + int x[4],ex[4],y[4],ey[4],nx,ny; + + for (i = 0; i < na; ++i) numtwosteps[i] = 0; + + for (i = 0; i < na; ++i) /* i is the central atom */ + { + nx = ny = 0; + if (which[i] == Cpos) + { + for (j = 0; j < nb; ++j) + { + if (u[j] == i && which[v[j]] == Cpos) + { + if (mult[j] == 1) { ex[nx] = j; x[nx++] = v[j]; } + if (mult[j] == 2) { ey[ny] = j; y[ny++] = v[j]; } + } + else if (v[j] == i && which[u[j]] == Cpos) + { + if (mult[j] == 1) { ex[nx] = j; x[nx++] = u[j]; } + if (mult[j] == 2) { ey[ny] = j; y[ny++] = u[j]; } + } + } + for (xi = 0; xi < nx; ++xi) + for (yi = 0; yi < ny; ++yi) + { + twosteps[x[xi]][numtwosteps[x[xi]]].e1 = ex[xi]; + twosteps[x[xi]][numtwosteps[x[xi]]].e2 = ey[yi]; + twosteps[x[xi]][numtwosteps[x[xi]]].v1 = x[xi]; + twosteps[x[xi]][numtwosteps[x[xi]]].v3 = y[yi]; + twosteps[x[xi]][numtwosteps[x[xi]]].v23 = bit[i] | bit[y[yi]]; + ++numtwosteps[x[xi]]; + } + } + } +} + +static void +gotaromcycle(int level, struct twostep *path[], + int na, int *which, int *hyd, int nb, int *u, int *v, int *mult) +{ + int newmult[2*MAXN]; + char code[20]; + int i,j; + + for (i = 0; i < nb; ++i) newmult[i] = mult[i]; + for (i = 0; i < level; ++i) + { + newmult[path[i]->e1] = 2; + newmult[path[i]->e2] = 1; + } + makecanoncode(na,which,hyd,nb,u,v,newmult,code); + + for (j = 0; j < aromtail; ++j) + if (strcmp(code,aromqueue[j].code) == 0) break; + if (j == aromtail) + { + if (aromtail == MAXAROMCODES) + gt_abort(">E increase MAXAROMCODES\n"); + strcpy(aromqueue[aromtail].code,code); + for (i = 0; i < nb; ++i) + aromqueue[aromtail].mult[i] = newmult[i]; + ++aromtail; + } +} + +static void +scancycles(int level, int first, struct twostep *path[], setword avail, + int na, int *which, int *hyd, int nb, int *u, int *v, int *mult) +{ + int last,i; + + last = (level == 0 ? first : path[level-1]->v3); + + for (i = 0; i < numtwosteps[last]; ++i) + { + if (!(twosteps[last][i].v23 & ~avail)) + { + path[level] = &twosteps[last][i]; + scancycles(level+1,first,path, + avail&~(twosteps[last][i].v23|bit[last]), + na,which,hyd,nb,u,v,mult); + } + else if (twosteps[last][i].v3 == first && level%2 == 0 + && (twosteps[last][i].v23 & ~avail) == bit[first]) + { + path[level] = &twosteps[last][i]; + gotaromcycle(level+1,path,na,which,hyd,nb,u,v,mult); + } + } +} + +static void +makearomcode(int na, int *which, int *hyd, + int nb, int *u, int *v, int *mult, char *code) +/* Make a canonical hashcode for the aromatic type of the molecule */ +{ + int i; + struct twostep *path[2*MAXN]; /* Sequence of twosteps */ + + Cpos = atomcode("C"); + for (i = 0; i < nb; ++i) aromqueue[0].mult[i] = mult[i]; + makecanoncode(na,which,hyd,nb,u,v,mult,aromqueue[0].code); + aromhead = 0; + aromtail = 1; + + while (aromhead < aromtail) + { + maketwosteps(na,nb,which,u,v,aromqueue[aromhead].mult); + + for (i = 0; i <= na-6; ++i) + if (which[i] == Cpos) + scancycles(0,i,path,ALLBITS>>(i+1),na,which,hyd,nb, + u,v,aromqueue[aromhead].mult); + ++aromhead; + } + + strcpy(code,aromqueue[0].code); + for (i = 1; i < aromtail; ++i) + if (strcmp(code,aromqueue[i].code) > 0) + strcpy(code,aromqueue[i].code); +} + +static void +decode(int numlines, int *n, int *which, int *hyd, + int *nb, int *u, int *v, int *mult) +/* which[0..*n-1] = non-H atoms + hyd[0..*n-1] = number of hydrogens attached to each + *nb = number of non-H bonds + u[0..*nb-1]-v[0..*nb-1] = bonds + mult[0..*nb-1] = multiplicities (1,2,3) +*/ +{ + int i,j,nv,ne; + int val[200],needval[200]; + double x,y,z; + int j1,j2,j3,j4,j5,j6; + char s[5]; + + /* See SDFformats.txt for which formats are handled */ + + if (sscanf(line[3],"%d%d",&nv,&ne) != 2) + gt_abort(">E Can't read count line\n"); + + *n = 0; + + for (i = 0; i < nv; ++i) + { + if (sscanf(line[4+i],"%lf%lf%lf%s%d%d%d%d%d%d",&x,&y,&z, + s,&j1,&j2,&j3,&j4,&j5,&j6) != 10) + gt_abort(">E Can't read atom line\n"); + which[i] = atomcode(s); + if (which[i] != HYDROGEN) + { + if (j4 > 0) hyd[i] = j4-1; + else hyd[i] = 0; + ++*n; + + if (j6 > 0) val[i] = j6; + else val[i] = 0; + + needval[i] = (j4 == 0) && (j6 == 0); + } + } + + *nb = 0; + + for (i = 0; i < ne; ++i) + { + if (sscanf(line[4+nv+i],"%d%d%d",&j1,&j2,&j3) != 3) + gt_abort(">E Can't read bond line\n"); + if (j3 < 1 || j3 > 4) + gt_abort_2(">E %llu: irregular multiplicity\n%s\n",nin,line[4+nv+i]); + if (which[j1-1] != HYDROGEN && which[j2-1] != HYDROGEN) + { + u[*nb] = j1-1; + v[*nb] = j2-1; + mult[*nb] = j3; + ++*nb; + } + else if (which[j1-1] == HYDROGEN) + ++hyd[j2-1]; + else if (which[j2-1] == HYDROGEN) + ++hyd[j1-1]; + else + gt_abort(">E the impossible happened\n"); + } + +#if 0 +{ int ii; +printf("----\nn=%d nb=%d nv=%d ne=%d\n",*n,*nb,nv,ne); +printf("atom="); for(ii=0;ii<*n;++ii)printf(" %d",which[ii]);printf("\n"); +printf(" hyd="); for(ii=0;ii<*n;++ii)printf(" %d",hyd[ii]);printf("\n"); +printf("need="); for(ii=0;ii<*n;++ii)printf(" %d",needval[ii]);printf("\n"); +printf(" val="); for(ii=0;ii<*n;++ii)printf(" %d",val[ii]);printf("\n"); +printf("edges="); for(ii=0;ii<*nb;++ii)printf(" %d%c%d", + u[ii],(mult[ii]==1?'-':mult[ii]==2?'=':mult[ii]==3?'#':'*'),v[ii]); + printf("\n"); +} +#endif + + for (i = 0; i < *n; ++i) + { + boolean saw4; + int q; + /* If aromatic edges are incident, we take one of them + to be double and the rest of them to be single. */ + + q = 0; + saw4 = FALSE; + for (j = 0; j < *nb; ++j) + if (u[j] == i || v[j] == i) + { + if (mult[j] == 4) + { + if (!saw4) + { + q += 2; + saw4 = TRUE; + } + else + ++q; + } + else + q += mult[j]; + } + + if (needval[i]) + { + val[i] = roundval(which[i],q+hyd[i]); + hyd[i] = val[i] - q; + } + else if (val[i] > 0) + hyd[i] = val[i] - q; + else + val[i] = roundval(which[i],q+hyd[i]); + } + +#if 0 +{ int ii; +printf("----\nn=%d nb=%d nv=%d ne=%d\n",*n,*nb,nv,ne); +printf("atom="); for(ii=0;ii<*n;++ii)printf(" %d",which[ii]);printf("\n"); +printf(" hyd="); for(ii=0;ii<*n;++ii)printf(" %d",hyd[ii]);printf("\n"); +printf("need="); for(ii=0;ii<*n;++ii)printf(" %d",needval[ii]);printf("\n"); +printf(" val="); for(ii=0;ii<*n;++ii)printf(" %d",val[ii]);printf("\n"); +printf("edges="); for(ii=0;ii<*nb;++ii)printf(" %d%c%d", + u[ii],(mult[ii]==1?'-':mult[ii]==2?'=':mult[ii]==3?'#':'*'),v[ii]); + printf("\n"); +} +#endif + + /* Try to find a plausible Kekule structure for aromatic edges. + * This is a real kludge and frequently doesn't work. */ + + { int k,laste,queue[2*MAXN],head,tail,ul,vl,ui,vi,du,dv; + + for (k = 0; k < *nb; ++k) + { + for (i = 0; i < *nb; ++i) if (mult[i] == 4) break; + if (i == *nb) break; + + queue[0] = i; + mult[i] = 1; + head = 0; + tail = 1; + while (head < tail) + { + laste = queue[head++]; + ul = u[laste]; vl = v[laste]; + for (i = 0; i < *nb; ++i) + { + ui = u[i]; vi = v[i]; + if (mult[i] == 4 && (ui == ul || ui == vl || + vi == ul || vi == vl)) + { + du = val[ui] - hyd[ui]; + dv = val[vi] - hyd[vi]; + for (j = 0; j < *nb; ++j) + { + if (j != i && (u[j] == ui || v[j] == ui)) + du -= (mult[j] == 4 ? 1 : mult[j]); + if (j != i && (u[j] == vi || v[j] == vi)) + dv -= (mult[j] == 4 ? 1 : mult[j]); + } + if (du <= 1 || dv <= 1) mult[i] = 1; + else mult[i] = 3 - mult[laste]; + queue[tail++] = i; + } + } + } + } + } + +#if 0 +{ int ii; +printf("----\nn=%d nb=%d nv=%d ne=%d\n",*n,*nb,nv,ne); +printf("atom="); for(ii=0;ii<*n;++ii)printf(" %d",which[ii]);printf("\n"); +printf(" hyd="); for(ii=0;ii<*n;++ii)printf(" %d",hyd[ii]);printf("\n"); +printf("need="); for(ii=0;ii<*n;++ii)printf(" %d",needval[ii]);printf("\n"); +printf(" val="); for(ii=0;ii<*n;++ii)printf(" %d",val[ii]);printf("\n"); +printf("edges="); for(ii=0;ii<*nb;++ii)printf(" %d%c%d", + u[ii],(mult[ii]==1?'-':mult[ii]==2?'=':mult[ii]==3?'#':'*'),v[ii]); + printf("\n"); +} +#endif + +} + +static boolean +isgood(int n, int *which, int *hyd, int nb, int *u, int *v, int *mult, + boolean aswitch, boolean Rswitch, boolean sswitch, boolean Sswitch, + int dmax, int cmax, long mine, long maxe, char *neededhash) +{ + int deg[MAXN]; + int i,coord; + char hashcode[20]; + + if (neededhash) + { + if (Rswitch) + makearomcode(n,which,hyd,nb,u,v,mult,hashcode); + else if (sswitch) + makesimplecode(n,which,hyd,Sswitch,nb,u,v,hashcode); + else + makecanoncode(n,which,hyd,nb,u,v,mult,hashcode); + return (strcmp(neededhash,hashcode) == 0); + } + + if (nb < mine || nb > maxe) return FALSE; + + for (i = 0; i < n; ++i) deg[i] = 0; + + for (i = 0; i < nb; ++i) + { + ++deg[u[i]]; + ++deg[v[i]]; + } + + for (i = 0; i < n; ++i) + { + coord = deg[i] + hyd[i]; + if (!aswitch) + { + if (coord > 4 && hyd[i] > 0) return FALSE; + if (which[i] == NITROGEN && coord > 4) return FALSE; + } + if (deg[i] > dmax) return FALSE; + if (coord > cmax) return FALSE; + } + + return TRUE; +} + +int +main(int argc, char *argv[]) +{ + unsigned long long nout; + boolean badargs; + boolean eswitch,pswitch,aswitch,dswitch,cswitch,Sswitch; + boolean Hswitch,wswitch,uswitch,Aswitch,Rswitch,sswitch; + int numlines,maxd,maxc; + int j,n,nb; + int u[2*MAXN],v[2*MAXN],mult[2*MAXN],which[MAXN],hyd[MAXN]; + char sw,*arg,*neededhash; + long startindex,endindex,mine,maxe; + char hashcode[20]; + + HELP; + + nauty_check(WORDSIZE,1,1,NAUTYVERSIONID); + + aswitch = dswitch = cswitch = wswitch = uswitch = Aswitch = FALSE; + Rswitch = eswitch = pswitch = Hswitch = sswitch = badargs = FALSE; + Sswitch = FALSE; + + for (j = 1; !badargs && j < argc; ++j) + { + arg = argv[j]; + if (arg[0] == '-' && arg[1] != '\0') + { + ++arg; + while (*arg != '\0') + { + sw = *arg++; + SWBOOLEAN('u',uswitch) + else SWBOOLEAN('w',wswitch) + else SWBOOLEAN('A',Aswitch) + else SWBOOLEAN('a',aswitch) + else SWBOOLEAN('R',Rswitch) + else SWBOOLEAN('s',sswitch) + else SWBOOLEAN('S',Sswitch) + else SWINT('c',cswitch,maxc,"canonsdf -c") + else SWINT('d',dswitch,maxd,"canonsdf -d") + else SWRANGE('p',":-",pswitch, + startindex,endindex,"canonsdf -p") + else SWRANGE('e',":-",eswitch,mine,maxe,"canonsdf -e") + else if (sw == 'H') + { + Hswitch = TRUE; + neededhash = arg; + break; + } + else badargs = TRUE; + } + } + else + badargs = TRUE; + } + + if (badargs) + { + fprintf(stderr,">E Usage: %s\n",USAGE); + GETHELP; + exit(1); + } + + if (!cswitch) maxc = 4; + if (!dswitch) maxd = 4; + if (pswitch && startindex < 0) startindex = 0; + if (!Hswitch) neededhash = NULL; + + if ((uswitch!=0)+(Aswitch!=0)+(wswitch!=0) > 1) + gt_abort(">E -v, -w and -u are incompatible\n"); + if ((Rswitch!=0)+(sswitch!=0)+(Sswitch!=0) > 1) + gt_abort(">E -s, -S and -R are incompatible\n"); + if (Sswitch) sswitch = TRUE; + + if (!eswitch) + { + mine = 0; + maxe = NAUTY_INFINITY; + } + + nin = nout = 0; + while ((numlines = readmolecule()) != 0) + { + ++nin; + if (!pswitch || + (nin >= startindex && nin <= endindex)) + { + decode(numlines,&n,which,hyd,&nb,u,v,mult); + if (isgood(n,which,hyd,nb,u,v,mult,aswitch,Rswitch,sswitch, + Sswitch,maxd,maxc,mine,maxe,neededhash)) + { + ++nout; + if (!uswitch) + { + if (wswitch) writemolecule(numlines); + else if (Aswitch) writeascii(n,which,hyd,nb,u,v,mult); + else if (Rswitch) + { + makearomcode(n,which,hyd,nb,u,v,mult,hashcode); + printf("%s\n",hashcode); + } + else if (sswitch) + { + makesimplecode(n,which,hyd,Sswitch,nb,u,v,hashcode); + printf("%s\n",hashcode); + } + else + { + makecanoncode(n,which,hyd,nb,u,v,mult,hashcode); + printf("%s\n",hashcode); + } + } + } + } + } + + if (uswitch) + fprintf(stderr, + ">Z read %lld molecules; %llu removed, %llu remaining\n", + nin,nin-nout,nout); + else + fprintf(stderr, + ">Z read %lld molecules; %llu removed, %llu written\n", + nin,nin-nout,nout); + + exit(0); +} diff --git a/src/geng.c b/src/geng.c index c66c825..e83f5fa 100644 --- a/src/geng.c +++ b/src/geng.c @@ -1,2507 +1,2507 @@ -/* TODO: - * add chordal graphs - * add perfect graphs - * add complements for ordinary graphs - * add 5-cycle rejection - * improve output by compiling g6 from level n-1 */ - -/* geng.c version 3.3; B D McKay, June 2021. */ - -#define USAGE \ -"geng [-cCmtfbd#D#] [-uygsnh] [-lvq] \n\ - [-x#X#] n [mine[:maxe]] [res/mod] [file]" - -#define HELPTEXT \ -" Generate all graphs of a specified class.\n\ -\n\ - n : the number of vertices\n\ - mine:maxe : a range for the number of edges\n\ - #:0 means '# or more' except in the case 0:0\n\ - res/mod : only generate subset res out of subsets 0..mod-1\n\ -\n\ - -c : only write connected graphs\n\ - -C : only write biconnected graphs\n\ - -t : only generate triangle-free graphs\n\ - -f : only generate 4-cycle-free graphs\n\ - -b : only generate bipartite graphs\n\ - (-t, -f and -b can be used in any combination)\n\ - -m : save memory at the expense of time (only makes a\n\ - difference in the absence of -b, -t, -f and n <= 28).\n\ - -d# : a lower bound for the minimum degree\n\ - -D# : an upper bound for the maximum degree\n\ - -v : display counts by number of edges\n\ - -l : canonically label output graphs\n\ -\n\ - -u : do not output any graphs, just generate and count them\n\ - -g : use graph6 output (default)\n\ - -s : use sparse6 output\n\ - -h : for graph6 or sparse6 format, write a header too\n\ -\n\ - -q : suppress auxiliary output (except from -v)\n\ -\n\ - See program text for much more information.\n" - - -/* Parameters: - - n = the number of vertices (1..MAXN) - Note that MAXN is limited to WORDSIZE - mine = the minimum number of edges (no bounds if missing) - maxe = the maximum number of edges (same as mine if missing) - 0 means "infinity" except in the case "0-0" - mod, res = a way to restrict the output to a subset. - All the graphs in G(n,mine..maxe) are divided into - disjoint classes C(0,mod),C(1,mod),...,C(mod-1,mod), - of very approximately equal size. - Only the class C(res,mod) is written. - - If the -x or -X switch is used, they must have the - same value for different values of res; otherwise - the partitioning may not be valid. In this case - (-x,-X with constant value), the usual relationships - between modulo classes are obeyed; for example - C(3,4) = C(3,8) union C(7,8). This is not true - if 3/8 and 7/8 are done with -x or -X values - different from those used for 3/4. - - file = a name for the output file (stdout if missing or "-") - - All switches can be concatenated or separate. However, the - value of -d must be attached to the "d", and similarly for "x". - - -c : only write connected graphs - -C : only write biconnected graphs - -t : only generate triangle-free graphs - -f : only generate 4-cycle-free graphs - -b : only generate bipartite graphs - (-t, -f and -b can be used in any combination) - -m : save memory at expense of time (only makes a - difference in the absence of -b, -t, -f and n <= 30). - -D : specify an upper bound for the maximum degree. - The value of the upper bound must be adjacent to - the "D". Example: -D6 - -d : specify a lower bound for the minimum degree. - The value of the upper bound must be adjacent to - the "d". Example: -d6 - -v : display counts by number of edges - -l : canonically label output graphs - - -u : do not output any graphs, just generate and count them - -g : use graph6 output (default) - -s : use sparse6 output - -n : use nauty format instead of graph6 format for output - -y : use the obsolete y-format for output - -h : for graph6 or sparse6 format, write a header too - - -q : suppress auxiliary output (except from -v) - - -x : specify a parameter that determines how evenly - the res/mod facility splits the graphs into subsets. - High values mean more even splitting at slight cost - to the total time. The default is 20*mod, and the - the legal minimum is 3*mod. More information is given - under "res/mod" above. - -X : move the initial splitting level higher by , - in order to force more even splitting at the cost - of speed. Default is -X0. More information is given - under "res/mod" above. - -Output formats. - - The output format is determined by the mutually exclusive switches - -u, -n, -y, -g and -s. The default is -g. - - -u suppresses output of graphs completely. - - -s and -g specify sparse6 and graph6 format, defined elsewhere. - In this case a header is also written if -h is present. - - If -y is present, graphs will be written in y-format. - y-format is obsolete and only provided for backwards compatibility. - - Each graph occupies one line with a terminating newline. - Except for the newline, each byte has the format 01xxxxxx, where - each "x" represents one bit of data. - First byte: xxxxxx is the number of vertices n - Other ceiling(n(n-1)/12) bytes: These contain the upper triangle of - the adjacency matrix in column major order. That is, the entries - appear in the order (0,1),(0,2),(1,2),(0,3),(1,3),(2,3),(0,4),... . - The bits are used in left to right order within each byte. - Any unused bits on the end are set to zero. - - If -n is present, any output graphs are written in nauty format. - - For a graph of n vertices, the output consists of one int giving - the number of vertices, and n setwords containing the adjacency - matrix. Note that this is system dependent (i.e. don't use it). - It will not work properly if the output is to stdout and your - system distinguishes binary and text files. - -OUTPROC feature. - - By defining the C preprocessor variable OUTPROC at compile time - (for Unix the syntax is -DOUTPROC=procname on the cc command), - geng can be made to call a procedure of your manufacture with - each output graph instead of writing anything. Your procedure - needs to have type void and the argument list (FILE *f, graph - *g, int n). f is a stream open for writing, g is the graph in - nauty format, and n is the number of vertices. Your procedure - can be in a separate file so long as it is linked with geng. The - global variables sparse6, graph6, quiet, nooutput, nautyformat, - yformat and canonise (all type boolean) can be used to test - for the presence of the flags -s, -g, -q, -u, -n, -y and -l, - respectively. If -l is present, the group size and similar - details can be found in the global variable nauty_stats. - -PRUNE feature. - - By defining the C preprocessor variable PRUNE at compile time, geng - can be made to call - int PRUNE(graph *g,int n,int maxn) - for each intermediate (and final) graph, and reject it if - the value returned is nonzero. The arguments are: - - g = the graph in nauty format (m=1) - n = the number of vertices in g - maxn = the number of vertices for output - (the value you gave on the command line to geng) - - geng constructs the graph starting with vertex 0, then adding - vertices 1,2,3,... in that order. Each graph in the sequence is - an induced subgraph of all later graphs in the sequence. - - A call is made for all orders from 1 to maxn. In testing for - a uniform property (such as a forbidden subgraph or forbidden - induced subgraph) it might save time to notice that a call to - PRUNE for n implies that the call for n-1 already passed. - - For very fast tests, it might be worthwhile using PREPRUNE as - well or instead. It has the same meaning but is applied earlier - and more often. - - If -c or -C is given, the connectivity test is done before - PRUNE but not necessarily before PREPRUNE. - - Some parameters are available in global variables: - geng_mindeg, geng_maxdeg, geng_mine, geng_maxe, geng_connec; - -SUMMARY - - If the C preprocessor variable SUMMARY is defined at compile time, the - procedure SUMMARY(nauty_counter nout, double cpu) is called just before - the program exits. The purpose is to allow reporting of statistics - collected by PRUNE or OUTPROC. The values nout and cpu are the output - count and cpu time reported on the >Z line. - Output should be written to stderr. - -INSTRUMENT feature. - - If the C preprocessor variable INSTRUMENT is defined at compile time, - extra code is inserted to collect statistics during execution, and - more information is written to stderr at termination. - -CALLING FROM A PROGRAM - - It is possible to call geng from another program instead of using it - as a stand-alone program. The main requirement is to change the name - of the main program to be other than "main". This is done by defining - the preprocessor variable GENG_MAIN. You might also like to define - OUTPROC to be the name of a procedure to receive the graphs. To call - the program you need to define an argument list argv[] consistent with - the usual one; don't forget that argv[0] is the command name and not - the first argument. The value of argc is the number of strings in - argv[]; that is, one more than the number of arguments. See the - sample program callgeng.c. - -************************************************************************** - -Counts and sample performance statistics. - - Here we give some graph counts and approximate execution times - on a Linux computer with Intel Core i7-4790 nominally 3.6GHz, - compiled with gcc 6.2.0. - Times are with the -u option (generate but don't write); add - 0.2-0.3 microseconds per graph for output to a file. - - - General Graphs C3-free Graphs (-t) - - 1 1 1 1 - 2 2 2 2 - 3 4 3 3 - 4 11 4 7 - 5 34 5 14 - 6 156 6 38 - 7 1044 7 107 - 8 12346 8 410 - 9 274668 0.08 sec 9 1897 - 10 12005168 2.7 sec 10 12172 - 11 1018997864 207 sec 11 105071 0.09 sec - 12 165091172592 9 hr 12 1262180 0.8 sec - 13 50502031367952 108 days 13 20797002 11 sec - These can be done in about half 14 467871369 220 sec - the time by setting the edge limit 15 14232552452 1.7 hr - half way then adding complements. 16 581460254001 65 hr - 17 31720840164950 145 days - - - C4-free Graphs (-f) (C3,C4)-free Graphs (-tf) - - 1 1 1 1 - 2 2 2 2 - 3 4 3 3 - 4 8 4 6 - 5 18 5 11 - 6 44 6 23 - 7 117 7 48 - 8 351 8 114 - 9 1230 9 293 - 10 5069 10 869 - 11 25181 0.04 sec 11 2963 - 12 152045 0.17 sec 12 12066 0.03 sec - 13 1116403 1.0 sec 13 58933 0.10 sec - 14 9899865 7.5 sec 14 347498 0.5 sec - 15 104980369 71 sec 15 2455693 2.7 sec - 16 1318017549 14 min 16 20592932 19 sec - 17 19427531763 3.4 hr 17 202724920 170 sec - 18 333964672216 56 hr 18 2322206466 32 min - 19 6660282066936 45 days 19 30743624324 7 hr - 20 468026657815 4 days - 21 8161170076257 106 days - - Old value was wrong: 18 2142368552 (The program was - ok, but somehow we tabulated the answer incorrectly.) - - - Bipartite Graphs (-b) C4-free Bipartite Graphs (-bf) - - 1 1 1 1 - 2 2 2 2 - 3 3 3 3 - 4 7 4 6 - 5 13 5 10 - 6 35 6 21 - 7 88 7 39 - 8 303 8 86 - 9 1119 9 182 - 10 5479 10 440 - 11 32303 0.03 sec 11 1074 - 12 251135 2.3 sec 12 2941 - 13 2527712 1.7 sec 13 8424 0.04 sec - 14 33985853 19 sec 14 26720 0.11 sec - 15 611846940 4.9 min 15 90883 0.33 sec - 16 14864650924 1.8 hr 16 340253 1.1 sec - 17 488222721992 2.4 days 17 1384567 3.7 sec - 18 21712049275198 105 days 18 6186907 14 sec - 19 30219769 59 sec - 20 161763233 280 sec - 21 946742190 24 min - 22 6054606722 2.5 hr - 23 42229136988 17 hr - 24 320741332093 125 hr - 25 2648348712904 58 days - -If you know any more of these counts, please tell me. - -************************************************************************** - -Hints: - -To make all the graphs of order n, without restriction on type, -it is fastest to make them up to binomial(n,2)/2 edges and append -the complement of those with strictly less than binomial(n,2)/2 edges. - -If it is necessary to split the computation into pieces, it is more -efficient to use the res/mod feature than to split by numbers of edges. - -************************************************************************** - - Author: B. D. McKay, Sep 1991 and many later dates. - Copyright B. McKay (1991-2018). All rights reserved. - This software is subject to the conditions and waivers - detailed in the file nauty.h. - - Changes: Nov 18, 1991 : added -d switch - fixed operation for n=16 - Nov 26, 1991 : added OUTPROC feature - Nov 29, 1991 : -c implies mine >= n-1 - Jan 8, 1992 : make writeny() not static - Jan 10, 1992 : added -n switch - Feb 9, 1992 : fixed case of n=1 - Feb 16, 1992 : changed mine,maxe,maxdeg testing - Feb 19, 1992 : added -b, -t and -u options - documented OUTPROC and added external - declaration for it. - Feb 20, 1992 : added -v option - Feb 22, 1992 : added INSTRUMENT compile-time option - Feb 23, 1992 : added xbnds() for more effective pruning - Feb 24, 1992 : added -l option - Feb 25, 1992 : changed writenauty() to use fwrite() - Mar 11, 1992 : completely revised many parts, incl - new refinement procedure for fast rejection, - distance invariant for regular graphs - May 19, 1992 : modified userautomproc slightly. xorb[] - is no longer idempotent but it doesn't matter. - Speed-up of 2-5% achieved. - June 5, 1993 : removed ";" after "CPUDEFS" to avoid illegal - empty declaration. - Nov 24, 1994 : tested for 0 <= res < mod - - Apr 13, 1996 : Major overhaul. Renamed "geng". - Changed argument syntax. - Removed 16-vertex limit. - Added -s, -m, -x. Allowed combinations. - Replaced code for non-general graphs. - Very many small changes. - Jul 12, 1996 : Changed semantics of -x and res/mod. - Changed >A line and added fflush()/ - All switches can be concatenated or not. - Aug 16, 1996 : Added -X switch and PRUNE() feature. - Fixed case of argument 0-0. - Sep 22, 1996 : Improved 1-2% by tweaking refinex(). - Jan 21, 1997 : Renamed to geng. - Changed -s to -f, and added -sghq. - Sep 7, 1997 : Fixed WORDSIZE=16 problems. - Sep 22, 1997 : Use "wb" open for nautyformat. - Jan 26, 1998 : Added SUMMARY feature. - Mar 4, 1998 : Added -C. - Mar 12, 1998 : Moved stats to nauty_stats. - Jan 1, 2000 : Changed -d to -D and added -d. - Feb 24, 2000 : Raised limit to 32 vertices. - Mar 3, 2000 : Made some counts into unsigned long. - (Includes first arg to SUMMARY.) - Mar 12, 2000 : Used bigint for counts that may exceed 2^32. - Now all counts from very long runs are ok. - Oct 12, 2000 : Changed maxef[32] to 92 after confirmation - from Yang Yuansheng. The old value of 93 was - valid but 92 is slightly more efficient. - Nov 16, 2000 : Used fuction prototypes. - Jul 31, 2001 : Added PREPRUNE - May 7, 2004 : Complete all function prototypes - Nov 24, 2004 : Force -m for very large sizes - Add -bf automatically if generating trees - Apr 1, 2007 : Write >A in one fputs() to try to reduce - mixing of outputs in multi-process pipes. - Sep 19, 2007 : Force -m for n > 28 regardless of word size. - Nov 29, 2008 : Slightly improved connectivity testing. - Mar 3, 2015 : Improve maxdeg tweaking. - Jan 18, 2016 : Replace bigint by nauty_counter. - Mar 8, 2018 : Can now compile for MAXN up to WORDSIZE. - Use setword instead of unsigned for xword. - Revised splitting level. - Updated sample execution times. - Mar 10, 2018 : Fix overflow at impossibly large n, maxdeg. - Jan 14, 2019 : Define geng_mindeg, geng_maxdeg, geng_mine, geng_maxe. - Jun 1, 2021 : Define geng_connec. - Jun 4, 2021 : Improve performance for -c and -C with small edge count. - Jun 21, 2021 : K1 is not 2-connected. - -**************************************************************************/ - -#define NAUTY_PGM 1 /* 1 = geng, 2 = genbg, 3 = gentourng */ - -#ifndef MAXN -#define MAXN WORDSIZE /* not more than WORDSIZE */ -#endif - -#if MAXN > WORDSIZE - #error "Can't have MAXN greater than WORDSIZE" -#endif - -#define ONE_WORD_SETS -#include "gtools.h" /* which includes nauty.h and stdio.h */ - -typedef setword xword; - -static void (*outproc)(FILE*,graph*,int); - -static FILE *outfile; /* file for output graphs */ -static int connec; /* 1 for -c, 2 for -C, 0 for neither */ -static boolean bipartite; /* presence of -b */ -static boolean trianglefree; /* presence of -t */ -static boolean squarefree; /* presence of -f */ -static boolean savemem; /* presence of -m */ -static boolean verbose; /* presence of -v */ -boolean nautyformat; /* presence of -n */ -boolean yformat; /* presence of -y */ -boolean graph6; /* presence of -g */ -boolean sparse6; /* presence of -s */ -boolean nooutput; /* presence of -u */ -boolean canonise; /* presence of -l */ -boolean quiet; /* presence of -q */ -boolean header; /* presence of -h */ -statsblk nauty_stats; -static int mindeg,maxdeg,maxn,mine,maxe,mod,res; -#define PRUNEMULT 50 /* bigger -> more even split at greater cost */ -static int min_splitlevel,odometer,splitlevel,multiplicity; -static graph gcan[MAXN]; - -#define XBIT(i) ((xword)1 << (i)) -#define XPOPCOUNT(x) POPCOUNT(x) -#define XNEXTBIT(x) (WORDSIZE-1-FIRSTBITNZ(x)) /* Assumes non-zero */ - -typedef struct -{ - int ne,dmax; /* values used for xlb,xub calculation */ - int xlb,xub; /* saved bounds on extension degree */ - xword lo,hi; /* work purposes for orbit calculation */ - xword xstart[MAXN+1]; /* index into xset[] for each cardinality */ - xword *xset; /* array of all x-sets in card order */ - xword *xcard; /* cardinalities of all x-sets */ - xword *xinv; /* map from x-set to index in xset */ - xword *xorb; /* min orbit representative */ - xword *xx; /* (-b, -t, -s, -m) candidate x-sets */ - /* note: can be the same as xcard */ - xword xlim; /* number of x-sets in xx[] */ -} leveldata; - -static leveldata data[MAXN]; /* data[n] is data for n -> n+1 */ -static nauty_counter ecount[1+MAXN*(MAXN-1)/2]; /* counts by number of edges */ -static nauty_counter nodes[MAXN]; /* nodes at each level */ - -#ifdef INSTRUMENT -static nauty_counter rigidnodes[MAXN],fertilenodes[MAXN]; -static nauty_counter a1calls,a1nauty,a1succs; -static nauty_counter a2calls,a2nauty,a2uniq,a2succs; -#endif - -/* The numbers below are actual maximum edge counts. - geng works correctly with any upper bounds. - To extend known upper bounds upwards: - (n-1, E) -> (n, E + floor(2*E/(n-2))), - which is done by the procedure findmaxe(). -*/ - -static int maxeb[65] = /* max edges for -b */ - {0,0,1,2,4, -1}; -static int maxet[65] = /* max edges for -t */ - {0,0,1,2,4, -1}; -static int maxef[65] = /* max edges for -f */ - {0,0,1,3,4, 6,7,9,11,13, - 16,18,21,24,27, 30,33,36,39,42, - 46,50,52,56,59, 63,67,71,76,80, - 85,90,92,96,102, 106,110,113,117,122, -1}; -static int maxeft[65] = /* max edges for -ft */ - {0,0,1,2,3, 5,6,8,10,12, - 15,16,18,21,23, 26,28,31,34,38, - 41,44,47,50,54, 57,61,65,68,72, - 76,80,85,87,90, 95,99,104,109,114, - 120,124,129,134,139, 145,150,156,162,168, - 175,176,178, -1}; -static int maxebf[65] = /* max edges for -bf */ - {0,0,1,2,3, 4,6,7,9,10, - 12,14,16,18,21, 22,24,26,29,31, - 34,36,39,42,45, 48,52,53,56,58, - 61,64,67,70,74, 77,81,84,88,92, - 96,100,105,106,108, 110,115,118,122,126, - 130,134,138,142,147, 151,156,160,165,170, - 175,180,186,187, -1}; - -#ifdef PLUGIN -#include PLUGIN -#endif - -#ifdef OUTPROC -extern void OUTPROC(FILE*,graph*,int); -#endif -#ifdef PRUNE -extern int PRUNE(graph*,int,int); -#endif -#ifdef PREPRUNE -extern int PREPRUNE(graph*,int,int); -#endif -#ifdef SUMMARY -extern void SUMMARY(nauty_counter,double); -#endif - -#if defined(PRUNE) || defined(PREPRUNE) -int geng_mindeg, geng_maxdeg, geng_mine, geng_maxe, geng_connec; -#endif - -/************************************************************************/ - -#define EXTEND(table,n) ((n) <= 1 ? 0 : (n) == 2 ? 1 : \ - table[(n)-1] + (2*table[(n)-1]/((n)-2))) - -static int -findmaxe(int *table, int n) -/* Return the n-th entry of a maxe table, extending existing values - if necessary. */ -{ - int i; - - for (i = 0; i <= n && table[i] >= 0; ++i) {} - for ( ; i <= n; ++i) table[i] = EXTEND(table,i); - - return table[n]; -} - -/************************************************************************/ - -void -writeny(FILE *f, graph *g, int n) -/* write graph g (n vertices) to file f in y format */ -{ - static char ybit[] = {32,16,8,4,2,1}; - char s[(MAXN*(MAXN-1)/2 + 5)/6 + 4]; - int i,j,k; - char y,*sp; - - sp = s; - *(sp++) = 0x40 | n; - y = 0x40; - - k = -1; - for (j = 1; j < n; ++j) - for (i = 0; i < j; ++i) - { - if (++k == 6) - { - *(sp++) = y; - y = 0x40; - k = 0; - } - if (g[i] & bit[j]) y |= ybit[k]; - } - if (n >= 2) *(sp++) = y; - *(sp++) = '\n'; - *sp = '\0'; - - if (fputs(s,f) == EOF || ferror(f)) - { - fprintf(stderr,">E writeny : error on writing file\n"); - exit(2); - } -} - -/************************************************************************/ - -void -writeg6x(FILE *f, graph *g, int n) -/* write graph g (n vertices) to file f in graph6 format */ -{ - writeg6(f,g,1,n); -} - -/************************************************************************/ - -void -writes6x(FILE *f, graph *g, int n) -/* write graph g (n vertices) to file f in sparse6 format */ -{ - writes6(f,g,1,n); -} - -/***********************************************************************/ - -static void -nullwrite(FILE *f, graph *g, int n) -/* don't write graph g (n vertices) to file f */ -{ -} - -/***********************************************************************/ - -void -writenauty(FILE *f, graph *g, int n) -/* write graph g (n vertices) to file f in nauty format */ -{ - int nn; - - nn = n; - - if (fwrite((char *)&nn,sizeof(int),(size_t)1,f) != 1 || - fwrite((char*)g,sizeof(setword),(size_t)n,f) != n) - { - fprintf(stderr,">E writenauty : error on writing file\n"); - exit(2); - } -} - -/*********************************************************************/ - -static boolean -isconnected(graph *g, int n) -/* test if g is connected */ -{ - setword seen,expanded,toexpand,allbits; - int i; - - allbits = ALLMASK(n); - - expanded = bit[n-1]; - seen = expanded | g[n-1]; - - while (seen != allbits && (toexpand = (seen & ~expanded))) /* not == */ - { - i = FIRSTBITNZ(toexpand); - expanded |= bit[i]; - seen |= g[i]; - } - - return seen == allbits; -} - -static boolean -connpreprune(graph *g, int n, int maxn) -/* This function speeds up the generation of connected graphs - with not many edges. */ -{ - setword notvisited,queue; - int ne,nc,i; - - if (n == maxn || maxe - maxn >= 5) return 0; - - ne = 0; - for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); - ne /= 2; - - nc = 0; - notvisited = ALLMASK(n); - - while (notvisited) - { - ++nc; - queue = SWHIBIT(notvisited); - notvisited &= ~queue; - while (queue) - { - TAKEBIT(i,queue); - notvisited &= ~bit[i]; - queue |= g[i] & notvisited; - } - } - - if (ne - n + nc > maxe - maxn + 1) return TRUE; - - return FALSE; -} - -/**********************************************************************/ - -static boolean -isbiconnected(graph *g, int n) -/* test if g is biconnected */ -{ - int sp,v,w; - setword sw; - setword visited; - int numvis,num[MAXN],lp[MAXN],stack[MAXN]; - - if (n <= 2) return FALSE; - - visited = bit[0]; - stack[0] = 0; - num[0] = 0; - lp[0] = 0; - numvis = 1; - sp = 0; - v = 0; - - for (;;) - { - if ((sw = g[v] & ~visited)) /* not "==" */ - { - w = v; - v = FIRSTBITNZ(sw); /* visit next child */ - stack[++sp] = v; - visited |= bit[v]; - lp[v] = num[v] = numvis++; - sw = g[v] & visited & ~bit[w]; - while (sw) - { - w = FIRSTBITNZ(sw); - sw &= ~bit[w]; - if (num[w] < lp[v]) lp[v] = num[w]; - } - } - else - { - w = v; /* back up to parent */ - if (sp <= 1) return numvis == n; - v = stack[--sp]; - if (lp[w] >= num[v]) return FALSE; - if (lp[w] < lp[v]) lp[v] = lp[w]; - } - } -} - -/**********************************************************************/ - -static void -gcomplement(graph *g, graph *gc, int n) -/* Take the complement of g and put it in gc */ -{ - int i; - setword all; - - all = ~(setword)BITMASK(n-1); - for (i = 0; i < n; ++i) - gc[i] = g[i] ^ all ^ bit[i]; -} - -/**********************************************************************/ - -static boolean -distinvar(graph *g, int *invar, int n) -/* make distance invariant - return FALSE if n-1 not maximal else return TRUE */ -{ - int w; - setword workset,frontier; - setword sofar; - int inv,d,v; - - for (v = n-1; v >= 0; --v) - { - inv = 0; - sofar = frontier = bit[v]; - for (d = 1; frontier != 0; ++d) - { - workset = 0; - inv += POPCOUNT(frontier) ^ (0x57 + d); - while (frontier) - { - w = FIRSTBITNZ(frontier); - frontier ^= bit[w]; - workset |= g[w]; - } - frontier = workset & ~sofar; - sofar |= frontier; - } - invar[v] = inv; - if (v < n-1 && inv > invar[n-1]) return FALSE; - } - return TRUE; -} - -/**************************************************************************/ - -static void -makexgraph(graph *g, xword *h, int n) -/* make x-format graph from nauty format graph */ -{ - setword gi; - int i,j; - xword hi; - - for (i = 0; i < n; ++i) - { - hi = 0; - gi = g[i]; - while (gi) - { - j = FIRSTBITNZ(gi); - gi ^= bit[j]; - hi |= XBIT(j); - } - h[i] = hi; - } -} - -/**************************************************************************/ - -static void -make0graph(graph *g, xword *h, int n) -/* make x-format graph without edges */ -{ - int i; - - for (i = 0; i < n; ++i) h[i] = 0; -} - -/**************************************************************************/ - -static void -makebgraph(graph *g, xword *h, int n) -/* make x-format graph of different colour graph */ -{ - setword seen1,seen2,expanded,w; - setword restv; - xword xseen1,xseen2; - int i; - - restv = 0; - for (i = 0; i < n; ++i) restv |= bit[i]; - - seen1 = seen2 = 0; - expanded = 0; - - while (TRUE) - { - if ((w = ((seen1 | seen2) & ~expanded)) == 0) - { - xseen1 = 0; - w = seen1; - while (w) - { - i = FIRSTBITNZ(w); - w ^= bit[i]; - xseen1 |= XBIT(i); - } - xseen2 = 0; - w = seen2; - while (w) - { - i = FIRSTBITNZ(w); - w ^= bit[i]; - xseen2 |= XBIT(i); - } - - w = seen1; - while (w) - { - i = FIRSTBITNZ(w); - w ^= bit[i]; - h[i] = xseen2; - } - w = seen2; - while (w) - { - i = FIRSTBITNZ(w); - w ^= bit[i]; - h[i] = xseen1; - } - - restv &= ~(seen1 | seen2); - if (restv == 0) return; - i = FIRSTBITNZ(restv); - seen1 = bit[i]; - seen2 = 0; - } - else - i = FIRSTBITNZ(w); - - expanded |= bit[i]; - if (bit[i] & seen1) seen2 |= g[i]; - else seen1 |= g[i]; - } -} - -/**************************************************************************/ - -static void -makeb6graph(graph *g, xword *h, int n) -/* make x-format bipartite girth 6 graph */ -{ - setword w,x; - xword hi; - int i,j; - - makebgraph(g,h,n); - - for (i = 0; i < n; ++i) - { - w = g[i]; - x = 0; - while (w) - { - j = FIRSTBITNZ(w); - w ^= bit[j]; - x |= g[j]; - } - x &= ~bit[i]; - hi = h[i]; - while (x) - { - j = FIRSTBITNZ(x); - x ^= bit[j]; - hi |= XBIT(j); - } - h[i] = hi; - } -} - -/**************************************************************************/ - -static void -makesgraph(graph *g, xword *h, int n) -/* make x-format square graph */ -{ - setword w,x; - xword hi; - int i,j; - - for (i = 0; i < n; ++i) - { - w = g[i]; - x = 0; - while (w) - { - j = FIRSTBITNZ(w); - w ^= bit[j]; - x |= g[j]; - } - x &= ~bit[i]; - hi = 0; - while (x) - { - j = FIRSTBITNZ(x); - x ^= bit[j]; - hi |= XBIT(j); - } - h[i] = hi; - } -} - -/**************************************************************************/ - -static void -makeg5graph(graph *g, xword *h, int n) -/* make x-format girth-5 graph */ -{ - setword w,x; - xword hi; - int i,j; - - for (i = 0; i < n; ++i) - { - w = g[i]; - x = g[i]; - while (w) - { - j = FIRSTBITNZ(w); - w ^= bit[j]; - x |= g[j]; - } - x &= ~bit[i]; - hi = 0; - while (x) - { - j = FIRSTBITNZ(x); - x ^= bit[j]; - hi |= XBIT(j); - } - h[i] = hi; - } -} - -/**************************************************************************/ - -static xword -arith(xword a, xword b, xword c) -/* Calculate a*b/c, assuming a*b/c and (c-1)*b are representable integers */ -{ - return (a/c)*b + ((a%c)*b)/c; -} - -/**************************************************************************/ - -static void -makeleveldata(boolean restricted) -/* make the level data for each level */ -{ - long h; - int n,nn; - xword ncj; - leveldata *d; - xword *xcard,*xinv; - xword *xset,xw,nxsets; - xword cw; - xword i,ilast,j; - size_t tttn; - - for (n = 1; n < maxn; ++n) - { - nn = maxdeg <= n ? maxdeg : n; - ncj = nxsets = 1; - for (j = 1; j <= nn; ++j) - { - ncj = arith(ncj,n-j+1,j); - nxsets += ncj; - } - - d = &data[n]; - d->ne = d->dmax = d->xlb = d->xub = -1; - - if (restricted) - { - d->xorb = (xword*) calloc(nxsets,sizeof(xword)); - d->xx = (xword*) calloc(nxsets,sizeof(xword)); - if (d->xorb == NULL || d->xx == NULL) - { - fprintf(stderr, - ">E geng: calloc failed in makeleveldata()\n"); - exit(2); - } - continue; /* <--- NOTE THIS! */ - } - - tttn = (size_t)1 << n; - d->xset = xset = (xword*) calloc(nxsets,sizeof(xword)); - d->xcard = xcard = (xword*) calloc(nxsets,sizeof(xword)); - d->xinv = xinv = (xword*) calloc(tttn,sizeof(xword)); - d->xorb = (xword*) calloc(nxsets,sizeof(xword)); - d->xx = d->xcard; - - if (xset==NULL || xcard==NULL || xinv==NULL || d->xorb==NULL) - { - fprintf(stderr,">E geng: calloc failed in makeleveldata()\n"); - exit(2); - } - - j = 0; - - ilast = (n == WORDSIZE ? ~(setword)0 : XBIT(n)-1); - for (i = 0;; ++i) - { - if ((h = XPOPCOUNT(i)) <= maxdeg) - { - xset[j] = i; - xcard[j] = h; - ++j; - } - if (i == ilast) break; - } - - if (j != nxsets) - { - fprintf(stderr,">E geng: j=" SETWORD_DEC_FORMAT - " nxsets=" SETWORD_DEC_FORMAT "\n", - j,nxsets); - exit(2); - } - - h = 1; - do - h = 3 * h + 1; - while (h < nxsets); - - do /* Shell sort, consider replacing */ - { - for (i = h; i < nxsets; ++i) - { - xw = xset[i]; - cw = xcard[i]; - for (j = i; xcard[j-h] > cw || - (xcard[j-h] == cw && xset[j-h] > xw); ) - { - xset[j] = xset[j-h]; - xcard[j] = xcard[j-h]; - if ((j -= h) < h) break; - } - xset[j] = xw; - xcard[j] = cw; - } - h /= 3; - } - while (h > 0); - - for (i = 0; i < nxsets; ++i) xinv[xset[i]] = i; - - d->xstart[0] = 0; - for (i = 1; i < nxsets; ++i) - if (xcard[i] > xcard[i-1]) d->xstart[xcard[i]] = i; - d->xstart[xcard[nxsets-1]+1] = nxsets; - } -} - -/**************************************************************************/ - -static void -userautomproc(int count, int *p, int *orbits, - int numorbits, int stabvertex, int n) -/* form orbits on powerset of VG - called by nauty; operates on data[n] */ -{ - xword i,j1,j2,moved,pi,pxi; - xword lo,hi; - xword *xorb,*xinv,*xset,w; - - xorb = data[n].xorb; - xset = data[n].xset; - xinv = data[n].xinv; - lo = data[n].lo; - hi = data[n].hi; - - if (count == 1) /* first automorphism */ - for (i = lo; i < hi; ++i) xorb[i] = i; - - moved = 0; - for (i = 0; i < n; ++i) - if (p[i] != i) moved |= XBIT(i); - - for (i = lo; i < hi; ++i) - { - if ((w = xset[i] & moved) == 0) continue; - pxi = xset[i] & ~moved; - while (w) - { - j1 = XNEXTBIT(w); - w ^= XBIT(j1); - pxi |= XBIT(p[j1]); - } - pi = xinv[pxi]; - - j1 = xorb[i]; - while (xorb[j1] != j1) j1 = xorb[j1]; - j2 = xorb[pi]; - while (xorb[j2] != j2) j2 = xorb[j2]; - - if (j1 < j2) xorb[j2] = xorb[i] = xorb[pi] = j1; - else if (j1 > j2) xorb[j1] = xorb[i] = xorb[pi] = j2; - } -} - -/**************************************************************************/ - -static void -userautomprocb(int count, int *p, int *orbits, - int numorbits, int stabvertex, int n) -/* form orbits on powerset of VG - called by nauty; operates on data[n] */ -{ - xword j1,j2,moved,pi,pxi,lo,hi,x; - xword i,*xorb,*xx,w,xlim,xlb; - - xorb = data[n].xorb; - xx = data[n].xx; - xlim = data[n].xlim; - - if (count == 1) /* first automorphism */ - { - j1 = 0; - xlb = data[n].xlb; - - for (i = 0; i < xlim; ++i) - { - x = xx[i]; - if (XPOPCOUNT(x) >= xlb) - { - xx[j1] = x; - xorb[j1] = j1; - ++j1; - } - } - data[n].xlim = xlim = j1; - } - - moved = 0; - for (i = 0; i < n; ++i) - if (p[i] != i) moved |= XBIT(i); - - for (i = 0; i < xlim; ++i) - { - if ((w = xx[i] & moved) == 0) continue; - pxi = xx[i] & ~moved; - while (w) - { - j1 = XNEXTBIT(w); - w ^= XBIT(j1); - pxi |= XBIT(p[j1]); - } - /* pi = position of pxi */ - - lo = 0; - hi = xlim - 1; - - for (;;) - { - pi = (lo + hi) >> 1; - if (xx[pi] == pxi) break; - else if (xx[pi] < pxi) lo = pi + 1; - else hi = pi - 1; - } - - j1 = xorb[i]; - while (xorb[j1] != j1) j1 = xorb[j1]; - j2 = xorb[pi]; - while (xorb[j2] != j2) j2 = xorb[j2]; - - if (j1 < j2) xorb[j2] = xorb[i] = xorb[pi] = j1; - else if (j1 > j2) xorb[j1] = xorb[i] = xorb[pi] = j2; - } -} - -/***************************************************************************** -* * -* refinex(g,lab,ptn,level,numcells,count,active,goodret,code,m,n) is a * -* custom version of refine() which can exit quickly if required. * -* * -* Only use at level==0. * -* goodret : whether to do an early return for code 1 * -* code := -1 for n-1 not max, 0 for maybe, 1 for definite * -* * -*****************************************************************************/ - -static void -refinex(graph *g, int *lab, int *ptn, int level, int *numcells, - int *count, set *active, boolean goodret, int *code, int m, int n) -{ - int i,c1,c2,labc1; - setword x,lact; - int split1,split2,cell1,cell2; - int cnt,bmin,bmax; - set *gptr; - setword workset; - int workperm[MAXN]; - int bucket[MAXN+2]; - - if (n == 1) - { - *code = 1; - return; - } - - *code = 0; - lact = *active; - - while (*numcells < n && lact) - { - TAKEBIT(split1,lact); - - for (split2 = split1; ptn[split2] > 0; ++split2) {} - if (split1 == split2) /* trivial splitting cell */ - { - gptr = GRAPHROW(g,lab[split1],1); - for (cell1 = 0; cell1 < n; cell1 = cell2 + 1) - { - for (cell2 = cell1; ptn[cell2] > 0; ++cell2) {} - if (cell1 == cell2) continue; - - c1 = cell1; - c2 = cell2; - while (c1 <= c2) - { - labc1 = lab[c1]; - if (ISELEMENT1(gptr,labc1)) - ++c1; - else - { - lab[c1] = lab[c2]; - lab[c2] = labc1; - --c2; - } - } - if (c2 >= cell1 && c1 <= cell2) - { - ptn[c2] = 0; - ++*numcells; - lact |= bit[c1]; - } - } - } - - else /* nontrivial splitting cell */ - { - workset = 0; - for (i = split1; i <= split2; ++i) workset |= bit[lab[i]]; - - for (cell1 = 0; cell1 < n; cell1 = cell2 + 1) - { - for (cell2 = cell1; ptn[cell2] > 0; ++cell2) {} - if (cell1 == cell2) continue; - i = cell1; - if ((x = workset & g[lab[i]]) != 0) cnt = POPCOUNT(x); - else cnt = 0; - count[i] = bmin = bmax = cnt; - bucket[cnt] = 1; - while (++i <= cell2) - { - if ((x = workset & g[lab[i]]) != 0) - cnt = POPCOUNT(x); - else - cnt = 0; - - while (bmin > cnt) bucket[--bmin] = 0; - while (bmax < cnt) bucket[++bmax] = 0; - ++bucket[cnt]; - count[i] = cnt; - } - if (bmin == bmax) continue; - c1 = cell1; - for (i = bmin; i <= bmax; ++i) - if (bucket[i]) - { - c2 = c1 + bucket[i]; - bucket[i] = c1; - if (c1 != cell1) - { - lact |= bit[c1]; - ++*numcells; - } - if (c2 <= cell2) ptn[c2-1] = 0; - 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 (ptn[n-2] == 0) - { - if (lab[n-1] == n-1) - { - *code = 1; - if (goodret) return; - } - else - { - *code = -1; - return; - } - } - else - { - i = n - 1; - while (TRUE) - { - if (lab[i] == n-1) break; - --i; - if (ptn[i] == 0) - { - *code = -1; - return; - } - } - } - } -} - -/**************************************************************************/ - -static void -makecanon(graph *g, graph *gcan, int n) -/* gcan := canonise(g) */ -{ - int lab[MAXN],ptn[MAXN],orbits[MAXN]; - static DEFAULTOPTIONS_GRAPH(options); - setword workspace[50]; - - options.getcanon = TRUE; - - nauty(g,lab,ptn,NULL,orbits,&options,&nauty_stats, - workspace,50,1,n,gcan); -} - -/**************************************************************************/ - -static boolean -accept1(graph *g, int n, xword x, graph *gx, int *deg, boolean *rigid) -/* decide if n in theta(g+x) - version for n+1 < maxn */ -{ - int i; - int lab[MAXN],ptn[MAXN],orbits[MAXN]; - int count[MAXN]; - graph h[MAXN]; - xword xw; - int nx,numcells,code; - int i0,i1,degn; - set active[MAXM]; - statsblk stats; - static DEFAULTOPTIONS_GRAPH(options); - setword workspace[50]; - -#ifdef INSTRUMENT - ++a1calls; -#endif - - nx = n + 1; - for (i = 0; i < n; ++i) gx[i] = g[i]; - gx[n] = 0; - deg[n] = degn = XPOPCOUNT(x); - - xw = x; - while (xw) - { - i = XNEXTBIT(xw); - xw ^= XBIT(i); - gx[i] |= bit[n]; - gx[n] |= bit[i]; - ++deg[i]; - } - -#ifdef PREPRUNE - if (PREPRUNE(gx,n+1,maxn)) return FALSE; -#endif - if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; - if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxn)) - && connpreprune(gx,n+1,maxn)) return FALSE; - - i0 = 0; - i1 = n; - for (i = 0; i < nx; ++i) - { - if (deg[i] == degn) lab[i1--] = i; - else lab[i0++] = i; - ptn[i] = 1; - } - ptn[n] = 0; - if (i0 == 0) - { - numcells = 1; - active[0] = bit[0]; - } - else - { - numcells = 2; - active[0] = bit[0] | bit[i1+1]; - ptn[i1] = 0; - } - refinex(gx,lab,ptn,0,&numcells,count,active,FALSE,&code,1,nx); - - if (code < 0) return FALSE; - - if (numcells == nx) - { - *rigid = TRUE; -#ifdef INSTRUMENT - ++a1succs; -#endif - return TRUE; - } - - options.getcanon = TRUE; - options.defaultptn = FALSE; - options.userautomproc = userautomproc; - - active[0] = 0; -#ifdef INSTRUMENT - ++a1nauty; -#endif - nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,h); - - if (orbits[lab[n]] == orbits[n]) - { - *rigid = stats.numorbits == nx; -#ifdef INSTRUMENT - ++a1succs; -#endif - return TRUE; - } - else - return FALSE; -} - -/**************************************************************************/ - -static boolean -accept1b(graph *g, int n, xword x, graph *gx, int *deg, boolean *rigid, - void (*makeh)(graph*,xword*,int)) -/* decide if n in theta(g+x) -- version for n+1 < maxn */ -{ - int i,v; - xword z,hv,bitv,ixx; - int lab[MAXN],ptn[MAXN],orbits[MAXN]; - int count[MAXN]; - graph gc[MAXN]; - xword h[MAXN],xw,jxx,kxx,*xx; - int nx,numcells,code; - int i0,i1,degn,xubx; - set active[MAXM]; - statsblk stats; - static DEFAULTOPTIONS_GRAPH(options); - setword workspace[50]; - -#ifdef INSTRUMENT - ++a1calls; -#endif - - nx = n + 1; - for (i = 0; i < n; ++i) gx[i] = g[i]; - gx[n] = 0; - deg[n] = degn = XPOPCOUNT(x); - - xw = x; - while (xw) - { - i = XNEXTBIT(xw); - xw ^= XBIT(i); - gx[i] |= bit[n]; - gx[n] |= bit[i]; - ++deg[i]; - } - -#ifdef PREPRUNE - if (PREPRUNE(gx,n+1,maxn)) return FALSE; -#endif - if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; - if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxe)) - && connpreprune(gx,n+1,maxn)) return FALSE; - - i0 = 0; - i1 = n; - for (i = 0; i < nx; ++i) - { - if (deg[i] == degn) lab[i1--] = i; - else lab[i0++] = i; - ptn[i] = 1; - } - ptn[n] = 0; - if (i0 == 0) - { - numcells = 1; - active[0] = bit[0]; - } - else - { - numcells = 2; - active[0] = bit[0] | bit[i1+1]; - ptn[i1] = 0; - } - refinex(gx,lab,ptn,0,&numcells,count,active,FALSE,&code,1,nx); - - if (code < 0) return FALSE; - - (*makeh)(gx,h,nx); - xx = data[nx].xx; - xubx = data[nx].xub; - - xx[0] = 0; - kxx = 1; - for (v = 0; v < nx; ++v) - { - bitv = XBIT(v); - hv = h[v]; - jxx = kxx; - for (ixx = 0; ixx < jxx; ++ixx) - if ((hv & xx[ixx]) == 0) - { - z = xx[ixx] | bitv; - if (XPOPCOUNT(z) <= xubx) xx[kxx++] = z; - } - } - data[nx].xlim = kxx; - - if (numcells == nx) - { - *rigid = TRUE; -#ifdef INSTRUMENT - ++a1succs; -#endif - return TRUE; - } - - options.getcanon = TRUE; - options.defaultptn = FALSE; - options.userautomproc = userautomprocb; - - active[0] = 0; -#ifdef INSTRUMENT - ++a1nauty; -#endif - nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,gc); - - if (orbits[lab[n]] == orbits[n]) - { - *rigid = stats.numorbits == nx; -#ifdef INSTRUMENT - ++a1succs; -#endif - return TRUE; - } - else - return FALSE; -} - -/**************************************************************************/ - -static boolean -accept2(graph *g, int n, xword x, graph *gx, int *deg, boolean nuniq) -/* decide if n in theta(g+x) -- version for n+1 == maxn */ -{ - int i; - int lab[MAXN],ptn[MAXN],orbits[MAXN]; - int degx[MAXN],invar[MAXN]; - setword vmax,gv; - int qn,qv; - int count[MAXN]; - xword xw; - int nx,numcells,code; - int degn,i0,i1,j,j0,j1; - set active[MAXM]; - statsblk stats; - static DEFAULTOPTIONS_GRAPH(options); - setword workspace[50]; - boolean cheapacc; - -#ifdef INSTRUMENT - ++a2calls; - if (nuniq) ++a2uniq; -#endif - nx = n + 1; - for (i = 0; i < n; ++i) - { - gx[i] = g[i]; - degx[i] = deg[i]; - } - gx[n] = 0; - degx[n] = degn = XPOPCOUNT(x); - - xw = x; - while (xw) - { - i = XNEXTBIT(xw); - xw ^= XBIT(i); - gx[i] |= bit[n]; - gx[n] |= bit[i]; - ++degx[i]; - } - -#ifdef PREPRUNE - if (PREPRUNE(gx,n+1,maxn)) return FALSE; -#endif - if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; - if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxe)) - && connpreprune(gx,n+1,maxn)) return FALSE; - - if (nuniq) - { -#ifdef INSTRUMENT - ++a2succs; -#endif - if (canonise) makecanon(gx,gcan,nx); - return TRUE; - } - - i0 = 0; - i1 = n; - for (i = 0; i < nx; ++i) - { - if (degx[i] == degn) lab[i1--] = i; - else lab[i0++] = i; - ptn[i] = 1; - } - ptn[n] = 0; - if (i0 == 0) - { - numcells = 1; - active[0] = bit[0]; - - if (!distinvar(gx,invar,nx)) return FALSE; - qn = invar[n]; - j0 = 0; - j1 = n; - while (j0 <= j1) - { - j = lab[j0]; - qv = invar[j]; - if (qv < qn) - ++j0; - else - { - lab[j0] = lab[j1]; - lab[j1] = j; - --j1; - } - } - if (j0 > 0) - { - if (j0 == n) - { -#ifdef INSTRUMENT - ++a2succs; -#endif - if (canonise) makecanon(gx,gcan,nx); - return TRUE; - } - ptn[j1] = 0; - ++numcells; - active[0] |= bit[j0]; - } - } - else - { - numcells = 2; - ptn[i1] = 0; - active[0] = bit[0] | bit[i1+1]; - - vmax = 0; - for (i = i1+1; i < nx; ++i) vmax |= bit[lab[i]]; - - gv = gx[n] & vmax; - qn = POPCOUNT(gv); - - j0 = i1+1; - j1 = n; - while (j0 <= j1) - { - j = lab[j0]; - gv = gx[j] & vmax; - qv = POPCOUNT(gv); - if (qv > qn) - return FALSE; - else if (qv < qn) - ++j0; - else - { - lab[j0] = lab[j1]; - lab[j1] = j; - --j1; - } - } - if (j0 > i1+1) - { - if (j0 == n) - { -#ifdef INSTRUMENT - ++a2succs; -#endif - if (canonise) makecanon(gx,gcan,nx); - return TRUE; - } - ptn[j1] = 0; - ++numcells; - active[0] |= bit[j0]; - } - } - - refinex(gx,lab,ptn,0,&numcells,count,active,TRUE,&code,1,nx); - - if (code < 0) return FALSE; - - cheapacc = FALSE; - if (code > 0 || numcells >= nx-4) - cheapacc = TRUE; - else if (numcells == nx-5) - { - for (j1 = nx-2; j1 >= 0 && ptn[j1] > 0; --j1) {} - if (nx - j1 != 5) cheapacc = TRUE; - } - else - { - j1 = nx; - j0 = 0; - for (i1 = 0; i1 < nx; ++i1) - { - --j1; - if (ptn[i1] > 0) - { - ++j0; - while (ptn[++i1] > 0) {} - } - } - if (j1 <= j0 + 1) cheapacc = TRUE; - } - - if (cheapacc) - { -#ifdef INSTRUMENT - ++a2succs; -#endif - if (canonise) makecanon(gx,gcan,nx); - return TRUE; - } - - options.getcanon = TRUE; - options.defaultptn = FALSE; - - active[0] = 0; -#ifdef INSTRUMENT - ++a2nauty; -#endif - nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,gcan); - - if (orbits[lab[n]] == orbits[n]) - { -#ifdef INSTRUMENT - ++a2succs; -#endif - if (canonise) makecanon(gx,gcan,nx); - return TRUE; - } - else - return FALSE; -} - -/**************************************************************************/ - -static void -xbnds(int n, int ne, int dmax) -/* find bounds on extension degree; store answer in data[*].* */ -{ - int xlb,xub,d,nn,m,xc; - - xlb = n == 1 ? 0 : (dmax > (2*ne + n - 2)/(n - 1) ? - dmax : (2*ne + n - 2)/(n - 1)); - xub = n < maxdeg ? n : maxdeg; - - for (xc = xub; xc >= xlb; --xc) - { - d = xc; - m = ne + d; - for (nn = n+1; nn < maxn; ++nn) - { - if (d < (2*m + nn - 2)/(nn - 1)) d = (2*m + nn - 2)/(nn - 1); - m += d; - } - if (d > maxdeg || m > maxe) xub = xc - 1; - else break; - } - - if (ne + xlb < mine) - for (xc = xlb; xc <= xub; ++xc) - { - m = ne + xc; - for (nn = n + 1; nn < maxn; ++nn) - m += maxdeg < nn ? maxdeg : nn; - if (m < mine) xlb = xc + 1; - else break; - } - - data[n].ne = ne; - data[n].dmax = dmax; - data[n].xlb = xlb; - data[n].xub = xub; -} - -/**************************************************************************/ - -static void -spaextend(graph *g, int n, int *deg, int ne, boolean rigid, - int xlb, int xub, void (*makeh)(graph*,xword*,int)) -/* extend from n to n+1 -- version for restricted graphs */ -{ - xword x,d,dlow; - xword xlim,*xorb; - int xc,nx,i,j,dmax,dcrit,xlbx,xubx; - graph gx[MAXN]; - xword *xx,ixx; - int degx[MAXN]; - boolean rigidx; - -#ifdef INSTRUMENT - boolean haschild; - - haschild = FALSE; - if (rigid) ++rigidnodes[n]; -#endif - ++nodes[n]; - - nx = n + 1; - dmax = deg[n-1]; - dcrit = mindeg - maxn + n; - d = dlow = 0; - for (i = 0; i < n; ++i) - { - if (deg[i] == dmax) d |= XBIT(i); - if (deg[i] == dcrit) dlow |= XBIT(i); - } - - if (xlb == dmax && XPOPCOUNT(d) + dmax > n) ++xlb; - if (nx == maxn && xlb < mindeg) xlb = mindeg; - if (xlb > xub) return; - -#ifdef PRUNE - if (PRUNE(g,n,maxn)) return; -#endif - - xorb = data[n].xorb; - xx = data[n].xx; - xlim = data[n].xlim; - - if (nx == maxn) - { - for (ixx = 0; ixx < xlim; ++ixx) - { - x = xx[ixx]; - xc = XPOPCOUNT(x); - if (xc < xlb || xc > xub) continue; - if ((rigid || xorb[ixx] == ixx) - && (xc > dmax || (xc == dmax && (x & d) == 0)) - && (dlow & ~x) == 0) - { - if (accept2(g,n,x,gx,deg, - xc > dmax+1 || (xc == dmax+1 && (x & d) == 0)) - && (!connec || - (connec==1 && isconnected(gx,nx)) || - (connec>1 && isbiconnected(gx,nx)))) - { -#ifdef PRUNE - if (!PRUNE(gx,nx,maxn)) -#endif - { -#ifdef INSTRUMENT - haschild = TRUE; -#endif - ++ecount[ne+xc]; - (*outproc)(outfile,canonise ? gcan : gx,nx); - } - } - } - } - } - else - { - for (ixx = 0; ixx < xlim; ++ixx) - { - if (nx == splitlevel) - { - if (odometer-- != 0) continue; - odometer = mod - 1; - } - x = xx[ixx]; - xc = XPOPCOUNT(x); - if (xc < xlb || xc > xub) continue; - if ((rigid || xorb[ixx] == ixx) - && (xc > dmax || (xc == dmax && (x & d) == 0)) - && (dlow & ~x) == 0) - { - for (j = 0; j < n; ++j) degx[j] = deg[j]; - if (data[nx].ne != ne+xc || data[nx].dmax != xc) - xbnds(nx,ne+xc,xc); - - xlbx = data[nx].xlb; - xubx = data[nx].xub; - if (xlbx <= xubx - && accept1b(g,n,x,gx,degx,&rigidx,makeh)) - { -#ifdef INSTRUMENT - haschild = TRUE; -#endif - spaextend(gx,nx,degx,ne+xc,rigidx,xlbx,xubx,makeh); - } - } - } - if (n == splitlevel - 1 && n >= min_splitlevel - && nodes[n] >= multiplicity) - --splitlevel; - } -#ifdef INSTRUMENT - if (haschild) ++fertilenodes[n]; -#endif -} - -/**************************************************************************/ - -static void -genextend(graph *g, int n, int *deg, int ne, boolean rigid, int xlb, int xub) -/* extend from n to n+1 -- version for general graphs */ -{ - xword x,d,dlow; - xword *xset,*xcard,*xorb; - xword i,imin,imax; - int nx,xc,j,dmax,dcrit; - int xlbx,xubx; - graph gx[MAXN]; - int degx[MAXN]; - boolean rigidx; - -#ifdef INSTRUMENT - boolean haschild; - - haschild = FALSE; - if (rigid) ++rigidnodes[n]; -#endif - ++nodes[n]; - - nx = n + 1; - dmax = deg[n-1]; - dcrit = mindeg - maxn + n; - d = dlow = 0; - for (i = 0; i < n; ++i) - { - if (deg[i] == dmax) d |= XBIT(i); - if (deg[i] == dcrit) dlow |= XBIT(i); - } - - if (xlb == dmax && XPOPCOUNT(d) + dmax > n) ++xlb; - if (nx == maxn && xlb < mindeg) xlb = mindeg; - if (xlb > xub) return; - -#ifdef PRUNE - if (PRUNE(g,n,maxn)) return; -#endif - - imin = data[n].xstart[xlb]; - imax = data[n].xstart[xub+1]; - xset = data[n].xset; - xcard = data[n].xcard; - xorb = data[n].xorb; - - if (nx == maxn) - for (i = imin; i < imax; ++i) - { - if (!rigid && xorb[i] != i) continue; - x = xset[i]; - xc = (int)xcard[i]; - if (xc == dmax && (x & d) != 0) continue; - if ((dlow & ~x) != 0) continue; - - if (accept2(g,n,x,gx,deg, - xc > dmax+1 || (xc == dmax+1 && (x & d) == 0))) - if (!connec || (connec==1 && isconnected(gx,nx)) - || (connec>1 && isbiconnected(gx,nx))) - { -#ifdef PRUNE - if (!PRUNE(gx,nx,maxn)) -#endif - { -#ifdef INSTRUMENT - haschild = TRUE; -#endif - ++ecount[ne+xc]; - (*outproc)(outfile,canonise ? gcan : gx,nx); - } - } - } - else - for (i = imin; i < imax; ++i) - { - if (!rigid && xorb[i] != i) continue; - x = xset[i]; - xc = (int)xcard[i]; - if (xc == dmax && (x & d) != 0) continue; - if ((dlow & ~x) != 0) continue; - if (nx == splitlevel) - { - if (odometer-- != 0) continue; - odometer = mod - 1; - } - - for (j = 0; j < n; ++j) degx[j] = deg[j]; - if (data[nx].ne != ne+xc || data[nx].dmax != xc) - xbnds(nx,ne+xc,xc); - xlbx = data[nx].xlb; - xubx = data[nx].xub; - if (xlbx > xubx) continue; - - data[nx].lo = data[nx].xstart[xlbx]; - data[nx].hi = data[nx].xstart[xubx+1]; - if (accept1(g,n,x,gx,degx,&rigidx)) - { -#ifdef INSTRUMENT - haschild = TRUE; -#endif - genextend(gx,nx,degx,ne+xc,rigidx,xlbx,xubx); - } - } - - if (n == splitlevel-1 && n >= min_splitlevel - && nodes[n] >= multiplicity) - --splitlevel; -#ifdef INSTRUMENT - if (haschild) ++fertilenodes[n]; -#endif -} - -/**************************************************************************/ -/**************************************************************************/ - -#ifdef GENG_MAIN -int -GENG_MAIN(int argc, char *argv[]) -#else -int -main(int argc, char *argv[]) -#endif -{ - char *arg; - boolean badargs,gote,gotmr,gotf,gotd,gotD,gotx,gotX; - boolean secret,connec1,connec2,safe,sparse; - char *outfilename,sw; - int i,j,argnum; - graph g[1]; - int tmaxe,deg[1]; - nauty_counter nout; - int splitlevinc; - double t1,t2; - char msg[201]; - - HELP; PUTVERSION; - nauty_check(WORDSIZE,1,MAXN,NAUTYVERSIONID); - - badargs = FALSE; - trianglefree = FALSE; - bipartite = FALSE; - squarefree = FALSE; - verbose = FALSE; - nautyformat = FALSE; - yformat = FALSE; - graph6 = FALSE; - sparse6 = FALSE; - savemem = FALSE; - nooutput = FALSE; - canonise = FALSE; - header = FALSE; - outfilename = NULL; - secret = FALSE; - safe = FALSE; - connec1 = connec2 = FALSE; - - maxdeg = MAXN; - mindeg = 0; - - gotX = gotx = gotd = gotD = gote = gotmr = gotf = FALSE; - - argnum = 0; - for (j = 1; !badargs && j < argc; ++j) - { - arg = argv[j]; - if (arg[0] == '-' && arg[1] != '\0') - { - ++arg; - while (*arg != '\0') - { - sw = *arg++; - SWBOOLEAN('n',nautyformat) - else SWBOOLEAN('u',nooutput) - else SWBOOLEAN('g',graph6) - else SWBOOLEAN('s',sparse6) - else SWBOOLEAN('t',trianglefree) - else SWBOOLEAN('f',squarefree) - else SWBOOLEAN('b',bipartite) - else SWBOOLEAN('v',verbose) - else SWBOOLEAN('l',canonise) - else SWBOOLEAN('y',yformat) - else SWBOOLEAN('h',header) - else SWBOOLEAN('m',savemem) - else SWBOOLEAN('c',connec1) - else SWBOOLEAN('C',connec2) - else SWBOOLEAN('q',quiet) - else SWBOOLEAN('$',secret) - else SWBOOLEAN('S',safe) - else SWINT('d',gotd,mindeg,"geng -d") - else SWINT('D',gotD,maxdeg,"geng -D") - else SWINT('x',gotx,multiplicity,"geng -x") - else SWINT('X',gotX,splitlevinc,"geng -X") -#ifdef PLUGIN_SWITCHES -PLUGIN_SWITCHES -#endif - else badargs = TRUE; - } - } - else if (arg[0] == '-' && arg[1] == '\0') - gotf = TRUE; - else - { - if (argnum == 0) - { - if (sscanf(arg,"%d",&maxn) != 1) badargs = TRUE; - ++argnum; - } - else if (gotf) - badargs = TRUE; - else - { - if (!gotmr) - { - if (sscanf(arg,"%d/%d",&res,&mod) == 2) - { - gotmr = TRUE; - continue; - } - } - if (!gote) - { - if (sscanf(arg,"%d:%d",&mine,&maxe) == 2 - || sscanf(arg,"%d-%d",&mine,&maxe) == 2) - { - gote = TRUE; - if (maxe == 0 && mine > 0) maxe = MAXN*(MAXN-1)/2; - continue; - } - else if (sscanf(arg,"%d",&mine) == 1) - { - gote = TRUE; - maxe = mine; - continue; - } - } - if (!gotf) - { - outfilename = arg; - gotf = TRUE; - continue; - } - } - } - } - - if (argnum == 0) - badargs = TRUE; - else if (maxn < 1 || maxn > MAXN) - { - fprintf(stderr,">E geng: n must be in the range 1..%d\n",MAXN); - badargs = TRUE; - } - - if (!gotmr) - { - mod = 1; - res = 0; - } - - if (!gote) - { - mine = 0; - maxe = (maxn*maxn - maxn) / 2; - } - - if (connec1 && mindeg < 1 && maxn > 1) mindeg = 1; - if (connec2 && mindeg < 2 && maxn > 2) mindeg = 2; - if (maxdeg >= maxn) maxdeg = maxn - 1; - if (maxe > maxn*maxdeg / 2) maxe = maxn*maxdeg / 2; - if (maxdeg > maxe) maxdeg = maxe; - if (mindeg < 0) mindeg = 0; - if (mine < (maxn*mindeg+1) / 2) mine = (maxn*mindeg+1) / 2; - if (maxdeg > 2*maxe - mindeg*(maxn-1)) maxdeg = 2*maxe - mindeg*(maxn-1); - - if (connec2) connec = 2; - else if (connec1) connec = 1; - else connec = 0; - if (connec && mine < maxn-1) mine = maxn - 2 + connec; - -#if defined(PRUNE) || defined(PREPRUNE) - geng_mindeg = mindeg; - geng_maxdeg = maxdeg; - geng_mine = mine; - geng_maxe = maxe; - geng_connec = connec; -#endif - - if (!badargs && (mine > maxe || maxe < 0 || maxdeg < 0)) - { - fprintf(stderr, - ">E geng: impossible mine,maxe,mindeg,maxdeg values\n"); - badargs = TRUE; - } - - if (!badargs && (res < 0 || res >= mod)) - { - fprintf(stderr,">E geng: must have 0 <= res < mod\n"); - badargs = TRUE; - } - - if (badargs) - { - fprintf(stderr,">E Usage: %s\n",USAGE); - GETHELP; - exit(1); - } - - if ((nautyformat!=0) + (yformat!=0) + (graph6!=0) - + (sparse6!=0) + (nooutput!=0) > 1) - gt_abort(">E geng: -uyngs are incompatible\n"); - -#ifdef OUTPROC - outproc = OUTPROC; -#else - if (nautyformat) outproc = writenauty; - else if (yformat) outproc = writeny; - else if (nooutput) outproc = nullwrite; - else if (sparse6) outproc = writes6x; - else outproc = writeg6x; -#endif - -#ifdef PLUGIN_INIT -PLUGIN_INIT -#endif - - for (i = 0; i <= maxe; ++i) ecount[i] = 0; - for (i = 0; i < maxn; ++i) nodes[i] = 0; - - if (nooutput) - outfile = stdout; - else if (!gotf || outfilename == NULL) - { - outfilename = "stdout"; - outfile = stdout; - } - else if ((outfile = fopen(outfilename, - nautyformat ? "wb" : "w")) == NULL) - { - fprintf(stderr, - ">E geng: can't open %s for writing\n",outfilename); - gt_abort(NULL); - } - - if (bipartite) - if (squarefree) tmaxe = findmaxe(maxebf,maxn); - else tmaxe = findmaxe(maxeb,maxn); - else if (trianglefree) - if (squarefree) tmaxe = findmaxe(maxeft,maxn); - else tmaxe = findmaxe(maxet,maxn); - else if (squarefree) tmaxe = findmaxe(maxef,maxn); - else tmaxe = (maxn*maxn - maxn) / 2; - - if (safe) ++tmaxe; - - if (maxe > tmaxe) maxe = tmaxe; - - if (gotx) - { - if (multiplicity < 3 * mod || multiplicity > 999999999) - gt_abort(">E geng: -x value must be in [3*mod,10^9-1]\n"); - } - else - { - multiplicity = PRUNEMULT * mod; - if (multiplicity / PRUNEMULT != mod) - gt_abort(">E geng: mod value is too large\n"); - } - - if (!gotX) splitlevinc = 0; - - if (!quiet) - { - msg[0] = '\0'; - if (strlen(argv[0]) > 75) - fprintf(stderr,">A %s",argv[0]); - else - CATMSG1(">A %s",argv[0]); - - CATMSG6(" -%s%s%s%s%s%s", - connec2 ? "C" : connec1 ? "c" : "", - trianglefree ? "t" : "", - squarefree ? "f" : "", - bipartite ? "b" : "", - canonise ? "l" : "", - savemem ? "m" : ""); - if (mod > 1) - CATMSG2("X%dx%d",splitlevinc,multiplicity); - CATMSG4("d%dD%d n=%d e=%d",mindeg,maxdeg,maxn,mine); - if (maxe > mine) CATMSG1("-%d",maxe); - if (mod > 1) CATMSG2(" class=%d/%d",res,mod); - CATMSG0("\n"); - fputs(msg,stderr); - fflush(stderr); - } - - g[0] = 0; - deg[0] = 0; - - sparse = bipartite || squarefree || trianglefree || savemem; - - t1 = CPUTIME; - - if (header) - { - if (sparse6) - { - writeline(outfile,SPARSE6_HEADER); - fflush(outfile); - } - else if (!yformat && !nautyformat && !nooutput) - { - writeline(outfile,GRAPH6_HEADER); - fflush(outfile); - } - } - - if (maxn == 1) - { - if (res == 0 && connec < 2) - { - ++ecount[0]; - (*outproc)(outfile,g,1); - } - } - else - { - if (maxn > 28 || maxn+4 > 8*sizeof(xword)) - savemem = sparse = TRUE; - if (maxn == maxe+1 && connec) - bipartite = squarefree = sparse = TRUE; /* trees */ - - makeleveldata(sparse); - - if (maxn >= 14 && mod > 1) splitlevel = maxn - 4; - else if (maxn >= 6 && mod > 1) splitlevel = maxn - 3; - else splitlevel = -1; - - if (splitlevel > 0) splitlevel += splitlevinc; - if (splitlevel > maxn - 1) splitlevel = maxn - 1; - if (splitlevel < 3) splitlevel = -1; - - min_splitlevel = 6; - odometer = secret ? -1 : res; - - if (maxe >= mine && - (mod <= 1 || (mod > 1 && (splitlevel > 2 || res == 0)))) - { - xbnds(1,0,0); - if (sparse) - { - data[1].xx[0] = 0; - if (maxdeg > 0) data[1].xx[1] = XBIT(0); - data[1].xlim = data[1].xub + 1; - } - - if (bipartite) - if (squarefree) - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,makeb6graph); - else - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,makebgraph); - else if (trianglefree) - if (squarefree) - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,makeg5graph); - else - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,makexgraph); - else if (squarefree) - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,makesgraph); - else if (savemem) - spaextend(g,1,deg,0,TRUE, - data[1].xlb,data[1].xub,make0graph); - else - genextend(g,1,deg,0,TRUE,data[1].xlb,data[1].xub); - } - } - t2 = CPUTIME; - - nout = 0; - for (i = 0; i <= maxe; ++i) nout += ecount[i]; - - if (verbose) - { - for (i = 0; i <= maxe; ++i) - if (ecount[i] != 0) - { - fprintf(stderr,">C " COUNTER_FMT " graphs with %d edges\n", - ecount[i],i); - } - } - -#ifdef INSTRUMENT - fprintf(stderr,"\n>N node counts\n"); - for (i = 1; i < maxn; ++i) - { - fprintf(stderr," level %2d: ",i); - fprintf(stderr,COUNTER_FMT " (" COUNTER_FMT - " rigid, " COUNTER_FMT " fertile)\n", - nodes[i],rigidnodes[i],fertilenodes[i]); - } - fprintf(stderr,">A1 " COUNTER_FMT " calls to accept1, " - COUNTER_FMT " nauty, " COUNTER_FMT " succeeded\n", - a1calls,a1nauty,a1succs); - fprintf(stderr,">A2 " COUNTER_FMT " calls to accept2, " COUNTER_FMT - " nuniq, "COUNTER_FMT " nauty, " COUNTER_FMT " succeeded\n", - a2calls,a2uniq,a2nauty,a2succs); - fprintf(stderr,"\n"); -#endif - -#ifdef SUMMARY - SUMMARY(nout,t2-t1); -#endif - - if (!quiet) - { - fprintf(stderr,">Z " COUNTER_FMT " graphs generated in %3.2f sec\n", - nout,t2-t1); - } - -#ifdef GENG_MAIN - for (i = 1; i < maxn; ++i) - if (sparse) - { - free(data[i].xorb); - free(data[i].xx); - } - else - { - free(data[i].xorb); - free(data[i].xset); - free(data[i].xinv); - free(data[i].xcard); - } - return 0; -#else - exit(0); -#endif -} +/* TODO: + * add chordal graphs + * add perfect graphs + * add complements for ordinary graphs + * add 5-cycle rejection + * improve output by compiling g6 from level n-1 */ + +/* geng.c version 3.3; B D McKay, June 2021. */ + +#define USAGE \ +"geng [-cCmtfbd#D#] [-uygsnh] [-lvq] \n\ + [-x#X#] n [mine[:maxe]] [res/mod] [file]" + +#define HELPTEXT \ +" Generate all graphs of a specified class.\n\ +\n\ + n : the number of vertices\n\ + mine:maxe : a range for the number of edges\n\ + #:0 means '# or more' except in the case 0:0\n\ + res/mod : only generate subset res out of subsets 0..mod-1\n\ +\n\ + -c : only write connected graphs\n\ + -C : only write biconnected graphs\n\ + -t : only generate triangle-free graphs\n\ + -f : only generate 4-cycle-free graphs\n\ + -b : only generate bipartite graphs\n\ + (-t, -f and -b can be used in any combination)\n\ + -m : save memory at the expense of time (only makes a\n\ + difference in the absence of -b, -t, -f and n <= 28).\n\ + -d# : a lower bound for the minimum degree\n\ + -D# : an upper bound for the maximum degree\n\ + -v : display counts by number of edges\n\ + -l : canonically label output graphs\n\ +\n\ + -u : do not output any graphs, just generate and count them\n\ + -g : use graph6 output (default)\n\ + -s : use sparse6 output\n\ + -h : for graph6 or sparse6 format, write a header too\n\ +\n\ + -q : suppress auxiliary output (except from -v)\n\ +\n\ + See program text for much more information.\n" + + +/* Parameters: + + n = the number of vertices (1..MAXN) + Note that MAXN is limited to WORDSIZE + mine = the minimum number of edges (no bounds if missing) + maxe = the maximum number of edges (same as mine if missing) + 0 means "infinity" except in the case "0-0" + mod, res = a way to restrict the output to a subset. + All the graphs in G(n,mine..maxe) are divided into + disjoint classes C(0,mod),C(1,mod),...,C(mod-1,mod), + of very approximately equal size. + Only the class C(res,mod) is written. + + If the -x or -X switch is used, they must have the + same value for different values of res; otherwise + the partitioning may not be valid. In this case + (-x,-X with constant value), the usual relationships + between modulo classes are obeyed; for example + C(3,4) = C(3,8) union C(7,8). This is not true + if 3/8 and 7/8 are done with -x or -X values + different from those used for 3/4. + + file = a name for the output file (stdout if missing or "-") + + All switches can be concatenated or separate. However, the + value of -d must be attached to the "d", and similarly for "x". + + -c : only write connected graphs + -C : only write biconnected graphs + -t : only generate triangle-free graphs + -f : only generate 4-cycle-free graphs + -b : only generate bipartite graphs + (-t, -f and -b can be used in any combination) + -m : save memory at expense of time (only makes a + difference in the absence of -b, -t, -f and n <= 30). + -D : specify an upper bound for the maximum degree. + The value of the upper bound must be adjacent to + the "D". Example: -D6 + -d : specify a lower bound for the minimum degree. + The value of the upper bound must be adjacent to + the "d". Example: -d6 + -v : display counts by number of edges + -l : canonically label output graphs + + -u : do not output any graphs, just generate and count them + -g : use graph6 output (default) + -s : use sparse6 output + -n : use nauty format instead of graph6 format for output + -y : use the obsolete y-format for output + -h : for graph6 or sparse6 format, write a header too + + -q : suppress auxiliary output (except from -v) + + -x : specify a parameter that determines how evenly + the res/mod facility splits the graphs into subsets. + High values mean more even splitting at slight cost + to the total time. The default is 20*mod, and the + the legal minimum is 3*mod. More information is given + under "res/mod" above. + -X : move the initial splitting level higher by , + in order to force more even splitting at the cost + of speed. Default is -X0. More information is given + under "res/mod" above. + +Output formats. + + The output format is determined by the mutually exclusive switches + -u, -n, -y, -g and -s. The default is -g. + + -u suppresses output of graphs completely. + + -s and -g specify sparse6 and graph6 format, defined elsewhere. + In this case a header is also written if -h is present. + + If -y is present, graphs will be written in y-format. + y-format is obsolete and only provided for backwards compatibility. + + Each graph occupies one line with a terminating newline. + Except for the newline, each byte has the format 01xxxxxx, where + each "x" represents one bit of data. + First byte: xxxxxx is the number of vertices n + Other ceiling(n(n-1)/12) bytes: These contain the upper triangle of + the adjacency matrix in column major order. That is, the entries + appear in the order (0,1),(0,2),(1,2),(0,3),(1,3),(2,3),(0,4),... . + The bits are used in left to right order within each byte. + Any unused bits on the end are set to zero. + + If -n is present, any output graphs are written in nauty format. + + For a graph of n vertices, the output consists of one int giving + the number of vertices, and n setwords containing the adjacency + matrix. Note that this is system dependent (i.e. don't use it). + It will not work properly if the output is to stdout and your + system distinguishes binary and text files. + +OUTPROC feature. + + By defining the C preprocessor variable OUTPROC at compile time + (for Unix the syntax is -DOUTPROC=procname on the cc command), + geng can be made to call a procedure of your manufacture with + each output graph instead of writing anything. Your procedure + needs to have type void and the argument list (FILE *f, graph + *g, int n). f is a stream open for writing, g is the graph in + nauty format, and n is the number of vertices. Your procedure + can be in a separate file so long as it is linked with geng. The + global variables sparse6, graph6, quiet, nooutput, nautyformat, + yformat and canonise (all type boolean) can be used to test + for the presence of the flags -s, -g, -q, -u, -n, -y and -l, + respectively. If -l is present, the group size and similar + details can be found in the global variable nauty_stats. + +PRUNE feature. + + By defining the C preprocessor variable PRUNE at compile time, geng + can be made to call + int PRUNE(graph *g,int n,int maxn) + for each intermediate (and final) graph, and reject it if + the value returned is nonzero. The arguments are: + + g = the graph in nauty format (m=1) + n = the number of vertices in g + maxn = the number of vertices for output + (the value you gave on the command line to geng) + + geng constructs the graph starting with vertex 0, then adding + vertices 1,2,3,... in that order. Each graph in the sequence is + an induced subgraph of all later graphs in the sequence. + + A call is made for all orders from 1 to maxn. In testing for + a uniform property (such as a forbidden subgraph or forbidden + induced subgraph) it might save time to notice that a call to + PRUNE for n implies that the call for n-1 already passed. + + For very fast tests, it might be worthwhile using PREPRUNE as + well or instead. It has the same meaning but is applied earlier + and more often. + + If -c or -C is given, the connectivity test is done before + PRUNE but not necessarily before PREPRUNE. + + Some parameters are available in global variables: + geng_mindeg, geng_maxdeg, geng_mine, geng_maxe, geng_connec; + +SUMMARY + + If the C preprocessor variable SUMMARY is defined at compile time, the + procedure SUMMARY(nauty_counter nout, double cpu) is called just before + the program exits. The purpose is to allow reporting of statistics + collected by PRUNE or OUTPROC. The values nout and cpu are the output + count and cpu time reported on the >Z line. + Output should be written to stderr. + +INSTRUMENT feature. + + If the C preprocessor variable INSTRUMENT is defined at compile time, + extra code is inserted to collect statistics during execution, and + more information is written to stderr at termination. + +CALLING FROM A PROGRAM + + It is possible to call geng from another program instead of using it + as a stand-alone program. The main requirement is to change the name + of the main program to be other than "main". This is done by defining + the preprocessor variable GENG_MAIN. You might also like to define + OUTPROC to be the name of a procedure to receive the graphs. To call + the program you need to define an argument list argv[] consistent with + the usual one; don't forget that argv[0] is the command name and not + the first argument. The value of argc is the number of strings in + argv[]; that is, one more than the number of arguments. See the + sample program callgeng.c. + +************************************************************************** + +Counts and sample performance statistics. + + Here we give some graph counts and approximate execution times + on a Linux computer with Intel Core i7-4790 nominally 3.6GHz, + compiled with gcc 6.2.0. + Times are with the -u option (generate but don't write); add + 0.2-0.3 microseconds per graph for output to a file. + + + General Graphs C3-free Graphs (-t) + + 1 1 1 1 + 2 2 2 2 + 3 4 3 3 + 4 11 4 7 + 5 34 5 14 + 6 156 6 38 + 7 1044 7 107 + 8 12346 8 410 + 9 274668 0.08 sec 9 1897 + 10 12005168 2.7 sec 10 12172 + 11 1018997864 207 sec 11 105071 0.09 sec + 12 165091172592 9 hr 12 1262180 0.8 sec + 13 50502031367952 108 days 13 20797002 11 sec + These can be done in about half 14 467871369 220 sec + the time by setting the edge limit 15 14232552452 1.7 hr + half way then adding complements. 16 581460254001 65 hr + 17 31720840164950 145 days + + + C4-free Graphs (-f) (C3,C4)-free Graphs (-tf) + + 1 1 1 1 + 2 2 2 2 + 3 4 3 3 + 4 8 4 6 + 5 18 5 11 + 6 44 6 23 + 7 117 7 48 + 8 351 8 114 + 9 1230 9 293 + 10 5069 10 869 + 11 25181 0.04 sec 11 2963 + 12 152045 0.17 sec 12 12066 0.03 sec + 13 1116403 1.0 sec 13 58933 0.10 sec + 14 9899865 7.5 sec 14 347498 0.5 sec + 15 104980369 71 sec 15 2455693 2.7 sec + 16 1318017549 14 min 16 20592932 19 sec + 17 19427531763 3.4 hr 17 202724920 170 sec + 18 333964672216 56 hr 18 2322206466 32 min + 19 6660282066936 45 days 19 30743624324 7 hr + 20 468026657815 4 days + 21 8161170076257 106 days + + Old value was wrong: 18 2142368552 (The program was + ok, but somehow we tabulated the answer incorrectly.) + + + Bipartite Graphs (-b) C4-free Bipartite Graphs (-bf) + + 1 1 1 1 + 2 2 2 2 + 3 3 3 3 + 4 7 4 6 + 5 13 5 10 + 6 35 6 21 + 7 88 7 39 + 8 303 8 86 + 9 1119 9 182 + 10 5479 10 440 + 11 32303 0.03 sec 11 1074 + 12 251135 2.3 sec 12 2941 + 13 2527712 1.7 sec 13 8424 0.04 sec + 14 33985853 19 sec 14 26720 0.11 sec + 15 611846940 4.9 min 15 90883 0.33 sec + 16 14864650924 1.8 hr 16 340253 1.1 sec + 17 488222721992 2.4 days 17 1384567 3.7 sec + 18 21712049275198 105 days 18 6186907 14 sec + 19 30219769 59 sec + 20 161763233 280 sec + 21 946742190 24 min + 22 6054606722 2.5 hr + 23 42229136988 17 hr + 24 320741332093 125 hr + 25 2648348712904 58 days + +If you know any more of these counts, please tell me. + +************************************************************************** + +Hints: + +To make all the graphs of order n, without restriction on type, +it is fastest to make them up to binomial(n,2)/2 edges and append +the complement of those with strictly less than binomial(n,2)/2 edges. + +If it is necessary to split the computation into pieces, it is more +efficient to use the res/mod feature than to split by numbers of edges. + +************************************************************************** + + Author: B. D. McKay, Sep 1991 and many later dates. + Copyright B. McKay (1991-2018). All rights reserved. + This software is subject to the conditions and waivers + detailed in the file nauty.h. + + Changes: Nov 18, 1991 : added -d switch + fixed operation for n=16 + Nov 26, 1991 : added OUTPROC feature + Nov 29, 1991 : -c implies mine >= n-1 + Jan 8, 1992 : make writeny() not static + Jan 10, 1992 : added -n switch + Feb 9, 1992 : fixed case of n=1 + Feb 16, 1992 : changed mine,maxe,maxdeg testing + Feb 19, 1992 : added -b, -t and -u options + documented OUTPROC and added external + declaration for it. + Feb 20, 1992 : added -v option + Feb 22, 1992 : added INSTRUMENT compile-time option + Feb 23, 1992 : added xbnds() for more effective pruning + Feb 24, 1992 : added -l option + Feb 25, 1992 : changed writenauty() to use fwrite() + Mar 11, 1992 : completely revised many parts, incl + new refinement procedure for fast rejection, + distance invariant for regular graphs + May 19, 1992 : modified userautomproc slightly. xorb[] + is no longer idempotent but it doesn't matter. + Speed-up of 2-5% achieved. + June 5, 1993 : removed ";" after "CPUDEFS" to avoid illegal + empty declaration. + Nov 24, 1994 : tested for 0 <= res < mod + + Apr 13, 1996 : Major overhaul. Renamed "geng". + Changed argument syntax. + Removed 16-vertex limit. + Added -s, -m, -x. Allowed combinations. + Replaced code for non-general graphs. + Very many small changes. + Jul 12, 1996 : Changed semantics of -x and res/mod. + Changed >A line and added fflush()/ + All switches can be concatenated or not. + Aug 16, 1996 : Added -X switch and PRUNE() feature. + Fixed case of argument 0-0. + Sep 22, 1996 : Improved 1-2% by tweaking refinex(). + Jan 21, 1997 : Renamed to geng. + Changed -s to -f, and added -sghq. + Sep 7, 1997 : Fixed WORDSIZE=16 problems. + Sep 22, 1997 : Use "wb" open for nautyformat. + Jan 26, 1998 : Added SUMMARY feature. + Mar 4, 1998 : Added -C. + Mar 12, 1998 : Moved stats to nauty_stats. + Jan 1, 2000 : Changed -d to -D and added -d. + Feb 24, 2000 : Raised limit to 32 vertices. + Mar 3, 2000 : Made some counts into unsigned long. + (Includes first arg to SUMMARY.) + Mar 12, 2000 : Used bigint for counts that may exceed 2^32. + Now all counts from very long runs are ok. + Oct 12, 2000 : Changed maxef[32] to 92 after confirmation + from Yang Yuansheng. The old value of 93 was + valid but 92 is slightly more efficient. + Nov 16, 2000 : Used fuction prototypes. + Jul 31, 2001 : Added PREPRUNE + May 7, 2004 : Complete all function prototypes + Nov 24, 2004 : Force -m for very large sizes + Add -bf automatically if generating trees + Apr 1, 2007 : Write >A in one fputs() to try to reduce + mixing of outputs in multi-process pipes. + Sep 19, 2007 : Force -m for n > 28 regardless of word size. + Nov 29, 2008 : Slightly improved connectivity testing. + Mar 3, 2015 : Improve maxdeg tweaking. + Jan 18, 2016 : Replace bigint by nauty_counter. + Mar 8, 2018 : Can now compile for MAXN up to WORDSIZE. + Use setword instead of unsigned for xword. + Revised splitting level. + Updated sample execution times. + Mar 10, 2018 : Fix overflow at impossibly large n, maxdeg. + Jan 14, 2019 : Define geng_mindeg, geng_maxdeg, geng_mine, geng_maxe. + Jun 1, 2021 : Define geng_connec. + Jun 4, 2021 : Improve performance for -c and -C with small edge count. + Jun 21, 2021 : K1 is not 2-connected. + +**************************************************************************/ + +#define NAUTY_PGM 1 /* 1 = geng, 2 = genbg, 3 = gentourng */ + +#ifndef MAXN +#define MAXN WORDSIZE /* not more than WORDSIZE */ +#endif + +#if MAXN > WORDSIZE + #error "Can't have MAXN greater than WORDSIZE" +#endif + +#define ONE_WORD_SETS +#include "gtools.h" /* which includes nauty.h and stdio.h */ + +typedef setword xword; + +static void (*outproc)(FILE*,graph*,int); + +static FILE *outfile; /* file for output graphs */ +static int connec; /* 1 for -c, 2 for -C, 0 for neither */ +static boolean bipartite; /* presence of -b */ +static boolean trianglefree; /* presence of -t */ +static boolean squarefree; /* presence of -f */ +static boolean savemem; /* presence of -m */ +static boolean verbose; /* presence of -v */ +boolean nautyformat; /* presence of -n */ +boolean yformat; /* presence of -y */ +boolean graph6; /* presence of -g */ +boolean sparse6; /* presence of -s */ +boolean nooutput; /* presence of -u */ +boolean canonise; /* presence of -l */ +boolean quiet; /* presence of -q */ +boolean header; /* presence of -h */ +statsblk nauty_stats; +static int mindeg,maxdeg,maxn,mine,maxe,mod,res; +#define PRUNEMULT 50 /* bigger -> more even split at greater cost */ +static int min_splitlevel,odometer,splitlevel,multiplicity; +static graph gcan[MAXN]; + +#define XBIT(i) ((xword)1 << (i)) +#define XPOPCOUNT(x) POPCOUNT(x) +#define XNEXTBIT(x) (WORDSIZE-1-FIRSTBITNZ(x)) /* Assumes non-zero */ + +typedef struct +{ + int ne,dmax; /* values used for xlb,xub calculation */ + int xlb,xub; /* saved bounds on extension degree */ + xword lo,hi; /* work purposes for orbit calculation */ + xword xstart[MAXN+1]; /* index into xset[] for each cardinality */ + xword *xset; /* array of all x-sets in card order */ + xword *xcard; /* cardinalities of all x-sets */ + xword *xinv; /* map from x-set to index in xset */ + xword *xorb; /* min orbit representative */ + xword *xx; /* (-b, -t, -s, -m) candidate x-sets */ + /* note: can be the same as xcard */ + xword xlim; /* number of x-sets in xx[] */ +} leveldata; + +static leveldata data[MAXN]; /* data[n] is data for n -> n+1 */ +static nauty_counter ecount[1+MAXN*(MAXN-1)/2]; /* counts by number of edges */ +static nauty_counter nodes[MAXN]; /* nodes at each level */ + +#ifdef INSTRUMENT +static nauty_counter rigidnodes[MAXN],fertilenodes[MAXN]; +static nauty_counter a1calls,a1nauty,a1succs; +static nauty_counter a2calls,a2nauty,a2uniq,a2succs; +#endif + +/* The numbers below are actual maximum edge counts. + geng works correctly with any upper bounds. + To extend known upper bounds upwards: + (n-1, E) -> (n, E + floor(2*E/(n-2))), + which is done by the procedure findmaxe(). +*/ + +static int maxeb[65] = /* max edges for -b */ + {0,0,1,2,4, -1}; +static int maxet[65] = /* max edges for -t */ + {0,0,1,2,4, -1}; +static int maxef[65] = /* max edges for -f */ + {0,0,1,3,4, 6,7,9,11,13, + 16,18,21,24,27, 30,33,36,39,42, + 46,50,52,56,59, 63,67,71,76,80, + 85,90,92,96,102, 106,110,113,117,122, -1}; +static int maxeft[65] = /* max edges for -ft */ + {0,0,1,2,3, 5,6,8,10,12, + 15,16,18,21,23, 26,28,31,34,38, + 41,44,47,50,54, 57,61,65,68,72, + 76,80,85,87,90, 95,99,104,109,114, + 120,124,129,134,139, 145,150,156,162,168, + 175,176,178, -1}; +static int maxebf[65] = /* max edges for -bf */ + {0,0,1,2,3, 4,6,7,9,10, + 12,14,16,18,21, 22,24,26,29,31, + 34,36,39,42,45, 48,52,53,56,58, + 61,64,67,70,74, 77,81,84,88,92, + 96,100,105,106,108, 110,115,118,122,126, + 130,134,138,142,147, 151,156,160,165,170, + 175,180,186,187, -1}; + +#ifdef PLUGIN +#include PLUGIN +#endif + +#ifdef OUTPROC +extern void OUTPROC(FILE*,graph*,int); +#endif +#ifdef PRUNE +extern int PRUNE(graph*,int,int); +#endif +#ifdef PREPRUNE +extern int PREPRUNE(graph*,int,int); +#endif +#ifdef SUMMARY +extern void SUMMARY(nauty_counter,double); +#endif + +#if defined(PRUNE) || defined(PREPRUNE) +int geng_mindeg, geng_maxdeg, geng_mine, geng_maxe, geng_connec; +#endif + +/************************************************************************/ + +#define EXTEND(table,n) ((n) <= 1 ? 0 : (n) == 2 ? 1 : \ + table[(n)-1] + (2*table[(n)-1]/((n)-2))) + +static int +findmaxe(int *table, int n) +/* Return the n-th entry of a maxe table, extending existing values + if necessary. */ +{ + int i; + + for (i = 0; i <= n && table[i] >= 0; ++i) {} + for ( ; i <= n; ++i) table[i] = EXTEND(table,i); + + return table[n]; +} + +/************************************************************************/ + +void +writeny(FILE *f, graph *g, int n) +/* write graph g (n vertices) to file f in y format */ +{ + static char ybit[] = {32,16,8,4,2,1}; + char s[(MAXN*(MAXN-1)/2 + 5)/6 + 4]; + int i,j,k; + char y,*sp; + + sp = s; + *(sp++) = 0x40 | n; + y = 0x40; + + k = -1; + for (j = 1; j < n; ++j) + for (i = 0; i < j; ++i) + { + if (++k == 6) + { + *(sp++) = y; + y = 0x40; + k = 0; + } + if (g[i] & bit[j]) y |= ybit[k]; + } + if (n >= 2) *(sp++) = y; + *(sp++) = '\n'; + *sp = '\0'; + + if (fputs(s,f) == EOF || ferror(f)) + { + fprintf(stderr,">E writeny : error on writing file\n"); + exit(2); + } +} + +/************************************************************************/ + +void +writeg6x(FILE *f, graph *g, int n) +/* write graph g (n vertices) to file f in graph6 format */ +{ + writeg6(f,g,1,n); +} + +/************************************************************************/ + +void +writes6x(FILE *f, graph *g, int n) +/* write graph g (n vertices) to file f in sparse6 format */ +{ + writes6(f,g,1,n); +} + +/***********************************************************************/ + +static void +nullwrite(FILE *f, graph *g, int n) +/* don't write graph g (n vertices) to file f */ +{ +} + +/***********************************************************************/ + +void +writenauty(FILE *f, graph *g, int n) +/* write graph g (n vertices) to file f in nauty format */ +{ + int nn; + + nn = n; + + if (fwrite((char *)&nn,sizeof(int),(size_t)1,f) != 1 || + fwrite((char*)g,sizeof(setword),(size_t)n,f) != n) + { + fprintf(stderr,">E writenauty : error on writing file\n"); + exit(2); + } +} + +/*********************************************************************/ + +static boolean +isconnected(graph *g, int n) +/* test if g is connected */ +{ + setword seen,expanded,toexpand,allbits; + int i; + + allbits = ALLMASK(n); + + expanded = bit[n-1]; + seen = expanded | g[n-1]; + + while (seen != allbits && (toexpand = (seen & ~expanded))) /* not == */ + { + i = FIRSTBITNZ(toexpand); + expanded |= bit[i]; + seen |= g[i]; + } + + return seen == allbits; +} + +static boolean +connpreprune(graph *g, int n, int maxn) +/* This function speeds up the generation of connected graphs + with not many edges. */ +{ + setword notvisited,queue; + int ne,nc,i; + + if (n == maxn || maxe - maxn >= 5) return 0; + + ne = 0; + for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); + ne /= 2; + + nc = 0; + notvisited = ALLMASK(n); + + while (notvisited) + { + ++nc; + queue = SWHIBIT(notvisited); + notvisited &= ~queue; + while (queue) + { + TAKEBIT(i,queue); + notvisited &= ~bit[i]; + queue |= g[i] & notvisited; + } + } + + if (ne - n + nc > maxe - maxn + 1) return TRUE; + + return FALSE; +} + +/**********************************************************************/ + +static boolean +isbiconnected(graph *g, int n) +/* test if g is biconnected */ +{ + int sp,v,w; + setword sw; + setword visited; + int numvis,num[MAXN],lp[MAXN],stack[MAXN]; + + if (n <= 2) return FALSE; + + visited = bit[0]; + stack[0] = 0; + num[0] = 0; + lp[0] = 0; + numvis = 1; + sp = 0; + v = 0; + + for (;;) + { + if ((sw = g[v] & ~visited)) /* not "==" */ + { + w = v; + v = FIRSTBITNZ(sw); /* visit next child */ + stack[++sp] = v; + visited |= bit[v]; + lp[v] = num[v] = numvis++; + sw = g[v] & visited & ~bit[w]; + while (sw) + { + w = FIRSTBITNZ(sw); + sw &= ~bit[w]; + if (num[w] < lp[v]) lp[v] = num[w]; + } + } + else + { + w = v; /* back up to parent */ + if (sp <= 1) return numvis == n; + v = stack[--sp]; + if (lp[w] >= num[v]) return FALSE; + if (lp[w] < lp[v]) lp[v] = lp[w]; + } + } +} + +/**********************************************************************/ + +static void +gcomplement(graph *g, graph *gc, int n) +/* Take the complement of g and put it in gc */ +{ + int i; + setword all; + + all = ~(setword)BITMASK(n-1); + for (i = 0; i < n; ++i) + gc[i] = g[i] ^ all ^ bit[i]; +} + +/**********************************************************************/ + +static boolean +distinvar(graph *g, int *invar, int n) +/* make distance invariant + return FALSE if n-1 not maximal else return TRUE */ +{ + int w; + setword workset,frontier; + setword sofar; + int inv,d,v; + + for (v = n-1; v >= 0; --v) + { + inv = 0; + sofar = frontier = bit[v]; + for (d = 1; frontier != 0; ++d) + { + workset = 0; + inv += POPCOUNT(frontier) ^ (0x57 + d); + while (frontier) + { + w = FIRSTBITNZ(frontier); + frontier ^= bit[w]; + workset |= g[w]; + } + frontier = workset & ~sofar; + sofar |= frontier; + } + invar[v] = inv; + if (v < n-1 && inv > invar[n-1]) return FALSE; + } + return TRUE; +} + +/**************************************************************************/ + +static void +makexgraph(graph *g, xword *h, int n) +/* make x-format graph from nauty format graph */ +{ + setword gi; + int i,j; + xword hi; + + for (i = 0; i < n; ++i) + { + hi = 0; + gi = g[i]; + while (gi) + { + j = FIRSTBITNZ(gi); + gi ^= bit[j]; + hi |= XBIT(j); + } + h[i] = hi; + } +} + +/**************************************************************************/ + +static void +make0graph(graph *g, xword *h, int n) +/* make x-format graph without edges */ +{ + int i; + + for (i = 0; i < n; ++i) h[i] = 0; +} + +/**************************************************************************/ + +static void +makebgraph(graph *g, xword *h, int n) +/* make x-format graph of different colour graph */ +{ + setword seen1,seen2,expanded,w; + setword restv; + xword xseen1,xseen2; + int i; + + restv = 0; + for (i = 0; i < n; ++i) restv |= bit[i]; + + seen1 = seen2 = 0; + expanded = 0; + + while (TRUE) + { + if ((w = ((seen1 | seen2) & ~expanded)) == 0) + { + xseen1 = 0; + w = seen1; + while (w) + { + i = FIRSTBITNZ(w); + w ^= bit[i]; + xseen1 |= XBIT(i); + } + xseen2 = 0; + w = seen2; + while (w) + { + i = FIRSTBITNZ(w); + w ^= bit[i]; + xseen2 |= XBIT(i); + } + + w = seen1; + while (w) + { + i = FIRSTBITNZ(w); + w ^= bit[i]; + h[i] = xseen2; + } + w = seen2; + while (w) + { + i = FIRSTBITNZ(w); + w ^= bit[i]; + h[i] = xseen1; + } + + restv &= ~(seen1 | seen2); + if (restv == 0) return; + i = FIRSTBITNZ(restv); + seen1 = bit[i]; + seen2 = 0; + } + else + i = FIRSTBITNZ(w); + + expanded |= bit[i]; + if (bit[i] & seen1) seen2 |= g[i]; + else seen1 |= g[i]; + } +} + +/**************************************************************************/ + +static void +makeb6graph(graph *g, xword *h, int n) +/* make x-format bipartite girth 6 graph */ +{ + setword w,x; + xword hi; + int i,j; + + makebgraph(g,h,n); + + for (i = 0; i < n; ++i) + { + w = g[i]; + x = 0; + while (w) + { + j = FIRSTBITNZ(w); + w ^= bit[j]; + x |= g[j]; + } + x &= ~bit[i]; + hi = h[i]; + while (x) + { + j = FIRSTBITNZ(x); + x ^= bit[j]; + hi |= XBIT(j); + } + h[i] = hi; + } +} + +/**************************************************************************/ + +static void +makesgraph(graph *g, xword *h, int n) +/* make x-format square graph */ +{ + setword w,x; + xword hi; + int i,j; + + for (i = 0; i < n; ++i) + { + w = g[i]; + x = 0; + while (w) + { + j = FIRSTBITNZ(w); + w ^= bit[j]; + x |= g[j]; + } + x &= ~bit[i]; + hi = 0; + while (x) + { + j = FIRSTBITNZ(x); + x ^= bit[j]; + hi |= XBIT(j); + } + h[i] = hi; + } +} + +/**************************************************************************/ + +static void +makeg5graph(graph *g, xword *h, int n) +/* make x-format girth-5 graph */ +{ + setword w,x; + xword hi; + int i,j; + + for (i = 0; i < n; ++i) + { + w = g[i]; + x = g[i]; + while (w) + { + j = FIRSTBITNZ(w); + w ^= bit[j]; + x |= g[j]; + } + x &= ~bit[i]; + hi = 0; + while (x) + { + j = FIRSTBITNZ(x); + x ^= bit[j]; + hi |= XBIT(j); + } + h[i] = hi; + } +} + +/**************************************************************************/ + +static xword +arith(xword a, xword b, xword c) +/* Calculate a*b/c, assuming a*b/c and (c-1)*b are representable integers */ +{ + return (a/c)*b + ((a%c)*b)/c; +} + +/**************************************************************************/ + +static void +makeleveldata(boolean restricted) +/* make the level data for each level */ +{ + long h; + int n,nn; + xword ncj; + leveldata *d; + xword *xcard,*xinv; + xword *xset,xw,nxsets; + xword cw; + xword i,ilast,j; + size_t tttn; + + for (n = 1; n < maxn; ++n) + { + nn = maxdeg <= n ? maxdeg : n; + ncj = nxsets = 1; + for (j = 1; j <= nn; ++j) + { + ncj = arith(ncj,n-j+1,j); + nxsets += ncj; + } + + d = &data[n]; + d->ne = d->dmax = d->xlb = d->xub = -1; + + if (restricted) + { + d->xorb = (xword*) calloc(nxsets,sizeof(xword)); + d->xx = (xword*) calloc(nxsets,sizeof(xword)); + if (d->xorb == NULL || d->xx == NULL) + { + fprintf(stderr, + ">E geng: calloc failed in makeleveldata()\n"); + exit(2); + } + continue; /* <--- NOTE THIS! */ + } + + tttn = (size_t)1 << n; + d->xset = xset = (xword*) calloc(nxsets,sizeof(xword)); + d->xcard = xcard = (xword*) calloc(nxsets,sizeof(xword)); + d->xinv = xinv = (xword*) calloc(tttn,sizeof(xword)); + d->xorb = (xword*) calloc(nxsets,sizeof(xword)); + d->xx = d->xcard; + + if (xset==NULL || xcard==NULL || xinv==NULL || d->xorb==NULL) + { + fprintf(stderr,">E geng: calloc failed in makeleveldata()\n"); + exit(2); + } + + j = 0; + + ilast = (n == WORDSIZE ? ~(setword)0 : XBIT(n)-1); + for (i = 0;; ++i) + { + if ((h = XPOPCOUNT(i)) <= maxdeg) + { + xset[j] = i; + xcard[j] = h; + ++j; + } + if (i == ilast) break; + } + + if (j != nxsets) + { + fprintf(stderr,">E geng: j=" SETWORD_DEC_FORMAT + " nxsets=" SETWORD_DEC_FORMAT "\n", + j,nxsets); + exit(2); + } + + h = 1; + do + h = 3 * h + 1; + while (h < nxsets); + + do /* Shell sort, consider replacing */ + { + for (i = h; i < nxsets; ++i) + { + xw = xset[i]; + cw = xcard[i]; + for (j = i; xcard[j-h] > cw || + (xcard[j-h] == cw && xset[j-h] > xw); ) + { + xset[j] = xset[j-h]; + xcard[j] = xcard[j-h]; + if ((j -= h) < h) break; + } + xset[j] = xw; + xcard[j] = cw; + } + h /= 3; + } + while (h > 0); + + for (i = 0; i < nxsets; ++i) xinv[xset[i]] = i; + + d->xstart[0] = 0; + for (i = 1; i < nxsets; ++i) + if (xcard[i] > xcard[i-1]) d->xstart[xcard[i]] = i; + d->xstart[xcard[nxsets-1]+1] = nxsets; + } +} + +/**************************************************************************/ + +static void +userautomproc(int count, int *p, int *orbits, + int numorbits, int stabvertex, int n) +/* form orbits on powerset of VG + called by nauty; operates on data[n] */ +{ + xword i,j1,j2,moved,pi,pxi; + xword lo,hi; + xword *xorb,*xinv,*xset,w; + + xorb = data[n].xorb; + xset = data[n].xset; + xinv = data[n].xinv; + lo = data[n].lo; + hi = data[n].hi; + + if (count == 1) /* first automorphism */ + for (i = lo; i < hi; ++i) xorb[i] = i; + + moved = 0; + for (i = 0; i < n; ++i) + if (p[i] != i) moved |= XBIT(i); + + for (i = lo; i < hi; ++i) + { + if ((w = xset[i] & moved) == 0) continue; + pxi = xset[i] & ~moved; + while (w) + { + j1 = XNEXTBIT(w); + w ^= XBIT(j1); + pxi |= XBIT(p[j1]); + } + pi = xinv[pxi]; + + j1 = xorb[i]; + while (xorb[j1] != j1) j1 = xorb[j1]; + j2 = xorb[pi]; + while (xorb[j2] != j2) j2 = xorb[j2]; + + if (j1 < j2) xorb[j2] = xorb[i] = xorb[pi] = j1; + else if (j1 > j2) xorb[j1] = xorb[i] = xorb[pi] = j2; + } +} + +/**************************************************************************/ + +static void +userautomprocb(int count, int *p, int *orbits, + int numorbits, int stabvertex, int n) +/* form orbits on powerset of VG + called by nauty; operates on data[n] */ +{ + xword j1,j2,moved,pi,pxi,lo,hi,x; + xword i,*xorb,*xx,w,xlim,xlb; + + xorb = data[n].xorb; + xx = data[n].xx; + xlim = data[n].xlim; + + if (count == 1) /* first automorphism */ + { + j1 = 0; + xlb = data[n].xlb; + + for (i = 0; i < xlim; ++i) + { + x = xx[i]; + if (XPOPCOUNT(x) >= xlb) + { + xx[j1] = x; + xorb[j1] = j1; + ++j1; + } + } + data[n].xlim = xlim = j1; + } + + moved = 0; + for (i = 0; i < n; ++i) + if (p[i] != i) moved |= XBIT(i); + + for (i = 0; i < xlim; ++i) + { + if ((w = xx[i] & moved) == 0) continue; + pxi = xx[i] & ~moved; + while (w) + { + j1 = XNEXTBIT(w); + w ^= XBIT(j1); + pxi |= XBIT(p[j1]); + } + /* pi = position of pxi */ + + lo = 0; + hi = xlim - 1; + + for (;;) + { + pi = (lo + hi) >> 1; + if (xx[pi] == pxi) break; + else if (xx[pi] < pxi) lo = pi + 1; + else hi = pi - 1; + } + + j1 = xorb[i]; + while (xorb[j1] != j1) j1 = xorb[j1]; + j2 = xorb[pi]; + while (xorb[j2] != j2) j2 = xorb[j2]; + + if (j1 < j2) xorb[j2] = xorb[i] = xorb[pi] = j1; + else if (j1 > j2) xorb[j1] = xorb[i] = xorb[pi] = j2; + } +} + +/***************************************************************************** +* * +* refinex(g,lab,ptn,level,numcells,count,active,goodret,code,m,n) is a * +* custom version of refine() which can exit quickly if required. * +* * +* Only use at level==0. * +* goodret : whether to do an early return for code 1 * +* code := -1 for n-1 not max, 0 for maybe, 1 for definite * +* * +*****************************************************************************/ + +static void +refinex(graph *g, int *lab, int *ptn, int level, int *numcells, + int *count, set *active, boolean goodret, int *code, int m, int n) +{ + int i,c1,c2,labc1; + setword x,lact; + int split1,split2,cell1,cell2; + int cnt,bmin,bmax; + set *gptr; + setword workset; + int workperm[MAXN]; + int bucket[MAXN+2]; + + if (n == 1) + { + *code = 1; + return; + } + + *code = 0; + lact = *active; + + while (*numcells < n && lact) + { + TAKEBIT(split1,lact); + + for (split2 = split1; ptn[split2] > 0; ++split2) {} + if (split1 == split2) /* trivial splitting cell */ + { + gptr = GRAPHROW(g,lab[split1],1); + for (cell1 = 0; cell1 < n; cell1 = cell2 + 1) + { + for (cell2 = cell1; ptn[cell2] > 0; ++cell2) {} + if (cell1 == cell2) continue; + + c1 = cell1; + c2 = cell2; + while (c1 <= c2) + { + labc1 = lab[c1]; + if (ISELEMENT1(gptr,labc1)) + ++c1; + else + { + lab[c1] = lab[c2]; + lab[c2] = labc1; + --c2; + } + } + if (c2 >= cell1 && c1 <= cell2) + { + ptn[c2] = 0; + ++*numcells; + lact |= bit[c1]; + } + } + } + + else /* nontrivial splitting cell */ + { + workset = 0; + for (i = split1; i <= split2; ++i) workset |= bit[lab[i]]; + + for (cell1 = 0; cell1 < n; cell1 = cell2 + 1) + { + for (cell2 = cell1; ptn[cell2] > 0; ++cell2) {} + if (cell1 == cell2) continue; + i = cell1; + if ((x = workset & g[lab[i]]) != 0) cnt = POPCOUNT(x); + else cnt = 0; + count[i] = bmin = bmax = cnt; + bucket[cnt] = 1; + while (++i <= cell2) + { + if ((x = workset & g[lab[i]]) != 0) + cnt = POPCOUNT(x); + else + cnt = 0; + + while (bmin > cnt) bucket[--bmin] = 0; + while (bmax < cnt) bucket[++bmax] = 0; + ++bucket[cnt]; + count[i] = cnt; + } + if (bmin == bmax) continue; + c1 = cell1; + for (i = bmin; i <= bmax; ++i) + if (bucket[i]) + { + c2 = c1 + bucket[i]; + bucket[i] = c1; + if (c1 != cell1) + { + lact |= bit[c1]; + ++*numcells; + } + if (c2 <= cell2) ptn[c2-1] = 0; + 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 (ptn[n-2] == 0) + { + if (lab[n-1] == n-1) + { + *code = 1; + if (goodret) return; + } + else + { + *code = -1; + return; + } + } + else + { + i = n - 1; + while (TRUE) + { + if (lab[i] == n-1) break; + --i; + if (ptn[i] == 0) + { + *code = -1; + return; + } + } + } + } +} + +/**************************************************************************/ + +static void +makecanon(graph *g, graph *gcan, int n) +/* gcan := canonise(g) */ +{ + int lab[MAXN],ptn[MAXN],orbits[MAXN]; + static DEFAULTOPTIONS_GRAPH(options); + setword workspace[50]; + + options.getcanon = TRUE; + + nauty(g,lab,ptn,NULL,orbits,&options,&nauty_stats, + workspace,50,1,n,gcan); +} + +/**************************************************************************/ + +static boolean +accept1(graph *g, int n, xword x, graph *gx, int *deg, boolean *rigid) +/* decide if n in theta(g+x) - version for n+1 < maxn */ +{ + int i; + int lab[MAXN],ptn[MAXN],orbits[MAXN]; + int count[MAXN]; + graph h[MAXN]; + xword xw; + int nx,numcells,code; + int i0,i1,degn; + set active[MAXM]; + statsblk stats; + static DEFAULTOPTIONS_GRAPH(options); + setword workspace[50]; + +#ifdef INSTRUMENT + ++a1calls; +#endif + + nx = n + 1; + for (i = 0; i < n; ++i) gx[i] = g[i]; + gx[n] = 0; + deg[n] = degn = XPOPCOUNT(x); + + xw = x; + while (xw) + { + i = XNEXTBIT(xw); + xw ^= XBIT(i); + gx[i] |= bit[n]; + gx[n] |= bit[i]; + ++deg[i]; + } + +#ifdef PREPRUNE + if (PREPRUNE(gx,n+1,maxn)) return FALSE; +#endif + if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; + if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxn)) + && connpreprune(gx,n+1,maxn)) return FALSE; + + i0 = 0; + i1 = n; + for (i = 0; i < nx; ++i) + { + if (deg[i] == degn) lab[i1--] = i; + else lab[i0++] = i; + ptn[i] = 1; + } + ptn[n] = 0; + if (i0 == 0) + { + numcells = 1; + active[0] = bit[0]; + } + else + { + numcells = 2; + active[0] = bit[0] | bit[i1+1]; + ptn[i1] = 0; + } + refinex(gx,lab,ptn,0,&numcells,count,active,FALSE,&code,1,nx); + + if (code < 0) return FALSE; + + if (numcells == nx) + { + *rigid = TRUE; +#ifdef INSTRUMENT + ++a1succs; +#endif + return TRUE; + } + + options.getcanon = TRUE; + options.defaultptn = FALSE; + options.userautomproc = userautomproc; + + active[0] = 0; +#ifdef INSTRUMENT + ++a1nauty; +#endif + nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,h); + + if (orbits[lab[n]] == orbits[n]) + { + *rigid = stats.numorbits == nx; +#ifdef INSTRUMENT + ++a1succs; +#endif + return TRUE; + } + else + return FALSE; +} + +/**************************************************************************/ + +static boolean +accept1b(graph *g, int n, xword x, graph *gx, int *deg, boolean *rigid, + void (*makeh)(graph*,xword*,int)) +/* decide if n in theta(g+x) -- version for n+1 < maxn */ +{ + int i,v; + xword z,hv,bitv,ixx; + int lab[MAXN],ptn[MAXN],orbits[MAXN]; + int count[MAXN]; + graph gc[MAXN]; + xword h[MAXN],xw,jxx,kxx,*xx; + int nx,numcells,code; + int i0,i1,degn,xubx; + set active[MAXM]; + statsblk stats; + static DEFAULTOPTIONS_GRAPH(options); + setword workspace[50]; + +#ifdef INSTRUMENT + ++a1calls; +#endif + + nx = n + 1; + for (i = 0; i < n; ++i) gx[i] = g[i]; + gx[n] = 0; + deg[n] = degn = XPOPCOUNT(x); + + xw = x; + while (xw) + { + i = XNEXTBIT(xw); + xw ^= XBIT(i); + gx[i] |= bit[n]; + gx[n] |= bit[i]; + ++deg[i]; + } + +#ifdef PREPRUNE + if (PREPRUNE(gx,n+1,maxn)) return FALSE; +#endif + if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; + if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxe)) + && connpreprune(gx,n+1,maxn)) return FALSE; + + i0 = 0; + i1 = n; + for (i = 0; i < nx; ++i) + { + if (deg[i] == degn) lab[i1--] = i; + else lab[i0++] = i; + ptn[i] = 1; + } + ptn[n] = 0; + if (i0 == 0) + { + numcells = 1; + active[0] = bit[0]; + } + else + { + numcells = 2; + active[0] = bit[0] | bit[i1+1]; + ptn[i1] = 0; + } + refinex(gx,lab,ptn,0,&numcells,count,active,FALSE,&code,1,nx); + + if (code < 0) return FALSE; + + (*makeh)(gx,h,nx); + xx = data[nx].xx; + xubx = data[nx].xub; + + xx[0] = 0; + kxx = 1; + for (v = 0; v < nx; ++v) + { + bitv = XBIT(v); + hv = h[v]; + jxx = kxx; + for (ixx = 0; ixx < jxx; ++ixx) + if ((hv & xx[ixx]) == 0) + { + z = xx[ixx] | bitv; + if (XPOPCOUNT(z) <= xubx) xx[kxx++] = z; + } + } + data[nx].xlim = kxx; + + if (numcells == nx) + { + *rigid = TRUE; +#ifdef INSTRUMENT + ++a1succs; +#endif + return TRUE; + } + + options.getcanon = TRUE; + options.defaultptn = FALSE; + options.userautomproc = userautomprocb; + + active[0] = 0; +#ifdef INSTRUMENT + ++a1nauty; +#endif + nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,gc); + + if (orbits[lab[n]] == orbits[n]) + { + *rigid = stats.numorbits == nx; +#ifdef INSTRUMENT + ++a1succs; +#endif + return TRUE; + } + else + return FALSE; +} + +/**************************************************************************/ + +static boolean +accept2(graph *g, int n, xword x, graph *gx, int *deg, boolean nuniq) +/* decide if n in theta(g+x) -- version for n+1 == maxn */ +{ + int i; + int lab[MAXN],ptn[MAXN],orbits[MAXN]; + int degx[MAXN],invar[MAXN]; + setword vmax,gv; + int qn,qv; + int count[MAXN]; + xword xw; + int nx,numcells,code; + int degn,i0,i1,j,j0,j1; + set active[MAXM]; + statsblk stats; + static DEFAULTOPTIONS_GRAPH(options); + setword workspace[50]; + boolean cheapacc; + +#ifdef INSTRUMENT + ++a2calls; + if (nuniq) ++a2uniq; +#endif + nx = n + 1; + for (i = 0; i < n; ++i) + { + gx[i] = g[i]; + degx[i] = deg[i]; + } + gx[n] = 0; + degx[n] = degn = XPOPCOUNT(x); + + xw = x; + while (xw) + { + i = XNEXTBIT(xw); + xw ^= XBIT(i); + gx[i] |= bit[n]; + gx[n] |= bit[i]; + ++degx[i]; + } + +#ifdef PREPRUNE + if (PREPRUNE(gx,n+1,maxn)) return FALSE; +#endif + if (connec == 2 && n+2 == maxn && !isconnected(gx,n+1)) return FALSE; + if (((connec ==2 && n+2 < maxn) || (connec == 1 && n+2 <= maxe)) + && connpreprune(gx,n+1,maxn)) return FALSE; + + if (nuniq) + { +#ifdef INSTRUMENT + ++a2succs; +#endif + if (canonise) makecanon(gx,gcan,nx); + return TRUE; + } + + i0 = 0; + i1 = n; + for (i = 0; i < nx; ++i) + { + if (degx[i] == degn) lab[i1--] = i; + else lab[i0++] = i; + ptn[i] = 1; + } + ptn[n] = 0; + if (i0 == 0) + { + numcells = 1; + active[0] = bit[0]; + + if (!distinvar(gx,invar,nx)) return FALSE; + qn = invar[n]; + j0 = 0; + j1 = n; + while (j0 <= j1) + { + j = lab[j0]; + qv = invar[j]; + if (qv < qn) + ++j0; + else + { + lab[j0] = lab[j1]; + lab[j1] = j; + --j1; + } + } + if (j0 > 0) + { + if (j0 == n) + { +#ifdef INSTRUMENT + ++a2succs; +#endif + if (canonise) makecanon(gx,gcan,nx); + return TRUE; + } + ptn[j1] = 0; + ++numcells; + active[0] |= bit[j0]; + } + } + else + { + numcells = 2; + ptn[i1] = 0; + active[0] = bit[0] | bit[i1+1]; + + vmax = 0; + for (i = i1+1; i < nx; ++i) vmax |= bit[lab[i]]; + + gv = gx[n] & vmax; + qn = POPCOUNT(gv); + + j0 = i1+1; + j1 = n; + while (j0 <= j1) + { + j = lab[j0]; + gv = gx[j] & vmax; + qv = POPCOUNT(gv); + if (qv > qn) + return FALSE; + else if (qv < qn) + ++j0; + else + { + lab[j0] = lab[j1]; + lab[j1] = j; + --j1; + } + } + if (j0 > i1+1) + { + if (j0 == n) + { +#ifdef INSTRUMENT + ++a2succs; +#endif + if (canonise) makecanon(gx,gcan,nx); + return TRUE; + } + ptn[j1] = 0; + ++numcells; + active[0] |= bit[j0]; + } + } + + refinex(gx,lab,ptn,0,&numcells,count,active,TRUE,&code,1,nx); + + if (code < 0) return FALSE; + + cheapacc = FALSE; + if (code > 0 || numcells >= nx-4) + cheapacc = TRUE; + else if (numcells == nx-5) + { + for (j1 = nx-2; j1 >= 0 && ptn[j1] > 0; --j1) {} + if (nx - j1 != 5) cheapacc = TRUE; + } + else + { + j1 = nx; + j0 = 0; + for (i1 = 0; i1 < nx; ++i1) + { + --j1; + if (ptn[i1] > 0) + { + ++j0; + while (ptn[++i1] > 0) {} + } + } + if (j1 <= j0 + 1) cheapacc = TRUE; + } + + if (cheapacc) + { +#ifdef INSTRUMENT + ++a2succs; +#endif + if (canonise) makecanon(gx,gcan,nx); + return TRUE; + } + + options.getcanon = TRUE; + options.defaultptn = FALSE; + + active[0] = 0; +#ifdef INSTRUMENT + ++a2nauty; +#endif + nauty(gx,lab,ptn,active,orbits,&options,&stats,workspace,50,1,nx,gcan); + + if (orbits[lab[n]] == orbits[n]) + { +#ifdef INSTRUMENT + ++a2succs; +#endif + if (canonise) makecanon(gx,gcan,nx); + return TRUE; + } + else + return FALSE; +} + +/**************************************************************************/ + +static void +xbnds(int n, int ne, int dmax) +/* find bounds on extension degree; store answer in data[*].* */ +{ + int xlb,xub,d,nn,m,xc; + + xlb = n == 1 ? 0 : (dmax > (2*ne + n - 2)/(n - 1) ? + dmax : (2*ne + n - 2)/(n - 1)); + xub = n < maxdeg ? n : maxdeg; + + for (xc = xub; xc >= xlb; --xc) + { + d = xc; + m = ne + d; + for (nn = n+1; nn < maxn; ++nn) + { + if (d < (2*m + nn - 2)/(nn - 1)) d = (2*m + nn - 2)/(nn - 1); + m += d; + } + if (d > maxdeg || m > maxe) xub = xc - 1; + else break; + } + + if (ne + xlb < mine) + for (xc = xlb; xc <= xub; ++xc) + { + m = ne + xc; + for (nn = n + 1; nn < maxn; ++nn) + m += maxdeg < nn ? maxdeg : nn; + if (m < mine) xlb = xc + 1; + else break; + } + + data[n].ne = ne; + data[n].dmax = dmax; + data[n].xlb = xlb; + data[n].xub = xub; +} + +/**************************************************************************/ + +static void +spaextend(graph *g, int n, int *deg, int ne, boolean rigid, + int xlb, int xub, void (*makeh)(graph*,xword*,int)) +/* extend from n to n+1 -- version for restricted graphs */ +{ + xword x,d,dlow; + xword xlim,*xorb; + int xc,nx,i,j,dmax,dcrit,xlbx,xubx; + graph gx[MAXN]; + xword *xx,ixx; + int degx[MAXN]; + boolean rigidx; + +#ifdef INSTRUMENT + boolean haschild; + + haschild = FALSE; + if (rigid) ++rigidnodes[n]; +#endif + ++nodes[n]; + + nx = n + 1; + dmax = deg[n-1]; + dcrit = mindeg - maxn + n; + d = dlow = 0; + for (i = 0; i < n; ++i) + { + if (deg[i] == dmax) d |= XBIT(i); + if (deg[i] == dcrit) dlow |= XBIT(i); + } + + if (xlb == dmax && XPOPCOUNT(d) + dmax > n) ++xlb; + if (nx == maxn && xlb < mindeg) xlb = mindeg; + if (xlb > xub) return; + +#ifdef PRUNE + if (PRUNE(g,n,maxn)) return; +#endif + + xorb = data[n].xorb; + xx = data[n].xx; + xlim = data[n].xlim; + + if (nx == maxn) + { + for (ixx = 0; ixx < xlim; ++ixx) + { + x = xx[ixx]; + xc = XPOPCOUNT(x); + if (xc < xlb || xc > xub) continue; + if ((rigid || xorb[ixx] == ixx) + && (xc > dmax || (xc == dmax && (x & d) == 0)) + && (dlow & ~x) == 0) + { + if (accept2(g,n,x,gx,deg, + xc > dmax+1 || (xc == dmax+1 && (x & d) == 0)) + && (!connec || + (connec==1 && isconnected(gx,nx)) || + (connec>1 && isbiconnected(gx,nx)))) + { +#ifdef PRUNE + if (!PRUNE(gx,nx,maxn)) +#endif + { +#ifdef INSTRUMENT + haschild = TRUE; +#endif + ++ecount[ne+xc]; + (*outproc)(outfile,canonise ? gcan : gx,nx); + } + } + } + } + } + else + { + for (ixx = 0; ixx < xlim; ++ixx) + { + if (nx == splitlevel) + { + if (odometer-- != 0) continue; + odometer = mod - 1; + } + x = xx[ixx]; + xc = XPOPCOUNT(x); + if (xc < xlb || xc > xub) continue; + if ((rigid || xorb[ixx] == ixx) + && (xc > dmax || (xc == dmax && (x & d) == 0)) + && (dlow & ~x) == 0) + { + for (j = 0; j < n; ++j) degx[j] = deg[j]; + if (data[nx].ne != ne+xc || data[nx].dmax != xc) + xbnds(nx,ne+xc,xc); + + xlbx = data[nx].xlb; + xubx = data[nx].xub; + if (xlbx <= xubx + && accept1b(g,n,x,gx,degx,&rigidx,makeh)) + { +#ifdef INSTRUMENT + haschild = TRUE; +#endif + spaextend(gx,nx,degx,ne+xc,rigidx,xlbx,xubx,makeh); + } + } + } + if (n == splitlevel - 1 && n >= min_splitlevel + && nodes[n] >= multiplicity) + --splitlevel; + } +#ifdef INSTRUMENT + if (haschild) ++fertilenodes[n]; +#endif +} + +/**************************************************************************/ + +static void +genextend(graph *g, int n, int *deg, int ne, boolean rigid, int xlb, int xub) +/* extend from n to n+1 -- version for general graphs */ +{ + xword x,d,dlow; + xword *xset,*xcard,*xorb; + xword i,imin,imax; + int nx,xc,j,dmax,dcrit; + int xlbx,xubx; + graph gx[MAXN]; + int degx[MAXN]; + boolean rigidx; + +#ifdef INSTRUMENT + boolean haschild; + + haschild = FALSE; + if (rigid) ++rigidnodes[n]; +#endif + ++nodes[n]; + + nx = n + 1; + dmax = deg[n-1]; + dcrit = mindeg - maxn + n; + d = dlow = 0; + for (i = 0; i < n; ++i) + { + if (deg[i] == dmax) d |= XBIT(i); + if (deg[i] == dcrit) dlow |= XBIT(i); + } + + if (xlb == dmax && XPOPCOUNT(d) + dmax > n) ++xlb; + if (nx == maxn && xlb < mindeg) xlb = mindeg; + if (xlb > xub) return; + +#ifdef PRUNE + if (PRUNE(g,n,maxn)) return; +#endif + + imin = data[n].xstart[xlb]; + imax = data[n].xstart[xub+1]; + xset = data[n].xset; + xcard = data[n].xcard; + xorb = data[n].xorb; + + if (nx == maxn) + for (i = imin; i < imax; ++i) + { + if (!rigid && xorb[i] != i) continue; + x = xset[i]; + xc = (int)xcard[i]; + if (xc == dmax && (x & d) != 0) continue; + if ((dlow & ~x) != 0) continue; + + if (accept2(g,n,x,gx,deg, + xc > dmax+1 || (xc == dmax+1 && (x & d) == 0))) + if (!connec || (connec==1 && isconnected(gx,nx)) + || (connec>1 && isbiconnected(gx,nx))) + { +#ifdef PRUNE + if (!PRUNE(gx,nx,maxn)) +#endif + { +#ifdef INSTRUMENT + haschild = TRUE; +#endif + ++ecount[ne+xc]; + (*outproc)(outfile,canonise ? gcan : gx,nx); + } + } + } + else + for (i = imin; i < imax; ++i) + { + if (!rigid && xorb[i] != i) continue; + x = xset[i]; + xc = (int)xcard[i]; + if (xc == dmax && (x & d) != 0) continue; + if ((dlow & ~x) != 0) continue; + if (nx == splitlevel) + { + if (odometer-- != 0) continue; + odometer = mod - 1; + } + + for (j = 0; j < n; ++j) degx[j] = deg[j]; + if (data[nx].ne != ne+xc || data[nx].dmax != xc) + xbnds(nx,ne+xc,xc); + xlbx = data[nx].xlb; + xubx = data[nx].xub; + if (xlbx > xubx) continue; + + data[nx].lo = data[nx].xstart[xlbx]; + data[nx].hi = data[nx].xstart[xubx+1]; + if (accept1(g,n,x,gx,degx,&rigidx)) + { +#ifdef INSTRUMENT + haschild = TRUE; +#endif + genextend(gx,nx,degx,ne+xc,rigidx,xlbx,xubx); + } + } + + if (n == splitlevel-1 && n >= min_splitlevel + && nodes[n] >= multiplicity) + --splitlevel; +#ifdef INSTRUMENT + if (haschild) ++fertilenodes[n]; +#endif +} + +/**************************************************************************/ +/**************************************************************************/ + +#ifdef GENG_MAIN +int +GENG_MAIN(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + char *arg; + boolean badargs,gote,gotmr,gotf,gotd,gotD,gotx,gotX; + boolean secret,connec1,connec2,safe,sparse; + char *outfilename,sw; + int i,j,argnum; + graph g[1]; + int tmaxe,deg[1]; + nauty_counter nout; + int splitlevinc; + double t1,t2; + char msg[201]; + + HELP; PUTVERSION; + nauty_check(WORDSIZE,1,MAXN,NAUTYVERSIONID); + + badargs = FALSE; + trianglefree = FALSE; + bipartite = FALSE; + squarefree = FALSE; + verbose = FALSE; + nautyformat = FALSE; + yformat = FALSE; + graph6 = FALSE; + sparse6 = FALSE; + savemem = FALSE; + nooutput = FALSE; + canonise = FALSE; + header = FALSE; + outfilename = NULL; + secret = FALSE; + safe = FALSE; + connec1 = connec2 = FALSE; + + maxdeg = MAXN; + mindeg = 0; + + gotX = gotx = gotd = gotD = gote = gotmr = gotf = FALSE; + + argnum = 0; + for (j = 1; !badargs && j < argc; ++j) + { + arg = argv[j]; + if (arg[0] == '-' && arg[1] != '\0') + { + ++arg; + while (*arg != '\0') + { + sw = *arg++; + SWBOOLEAN('n',nautyformat) + else SWBOOLEAN('u',nooutput) + else SWBOOLEAN('g',graph6) + else SWBOOLEAN('s',sparse6) + else SWBOOLEAN('t',trianglefree) + else SWBOOLEAN('f',squarefree) + else SWBOOLEAN('b',bipartite) + else SWBOOLEAN('v',verbose) + else SWBOOLEAN('l',canonise) + else SWBOOLEAN('y',yformat) + else SWBOOLEAN('h',header) + else SWBOOLEAN('m',savemem) + else SWBOOLEAN('c',connec1) + else SWBOOLEAN('C',connec2) + else SWBOOLEAN('q',quiet) + else SWBOOLEAN('$',secret) + else SWBOOLEAN('S',safe) + else SWINT('d',gotd,mindeg,"geng -d") + else SWINT('D',gotD,maxdeg,"geng -D") + else SWINT('x',gotx,multiplicity,"geng -x") + else SWINT('X',gotX,splitlevinc,"geng -X") +#ifdef PLUGIN_SWITCHES +PLUGIN_SWITCHES +#endif + else badargs = TRUE; + } + } + else if (arg[0] == '-' && arg[1] == '\0') + gotf = TRUE; + else + { + if (argnum == 0) + { + if (sscanf(arg,"%d",&maxn) != 1) badargs = TRUE; + ++argnum; + } + else if (gotf) + badargs = TRUE; + else + { + if (!gotmr) + { + if (sscanf(arg,"%d/%d",&res,&mod) == 2) + { + gotmr = TRUE; + continue; + } + } + if (!gote) + { + if (sscanf(arg,"%d:%d",&mine,&maxe) == 2 + || sscanf(arg,"%d-%d",&mine,&maxe) == 2) + { + gote = TRUE; + if (maxe == 0 && mine > 0) maxe = MAXN*(MAXN-1)/2; + continue; + } + else if (sscanf(arg,"%d",&mine) == 1) + { + gote = TRUE; + maxe = mine; + continue; + } + } + if (!gotf) + { + outfilename = arg; + gotf = TRUE; + continue; + } + } + } + } + + if (argnum == 0) + badargs = TRUE; + else if (maxn < 1 || maxn > MAXN) + { + fprintf(stderr,">E geng: n must be in the range 1..%d\n",MAXN); + badargs = TRUE; + } + + if (!gotmr) + { + mod = 1; + res = 0; + } + + if (!gote) + { + mine = 0; + maxe = (maxn*maxn - maxn) / 2; + } + + if (connec1 && mindeg < 1 && maxn > 1) mindeg = 1; + if (connec2 && mindeg < 2 && maxn > 2) mindeg = 2; + if (maxdeg >= maxn) maxdeg = maxn - 1; + if (maxe > maxn*maxdeg / 2) maxe = maxn*maxdeg / 2; + if (maxdeg > maxe) maxdeg = maxe; + if (mindeg < 0) mindeg = 0; + if (mine < (maxn*mindeg+1) / 2) mine = (maxn*mindeg+1) / 2; + if (maxdeg > 2*maxe - mindeg*(maxn-1)) maxdeg = 2*maxe - mindeg*(maxn-1); + + if (connec2) connec = 2; + else if (connec1) connec = 1; + else connec = 0; + if (connec && mine < maxn-1) mine = maxn - 2 + connec; + +#if defined(PRUNE) || defined(PREPRUNE) + geng_mindeg = mindeg; + geng_maxdeg = maxdeg; + geng_mine = mine; + geng_maxe = maxe; + geng_connec = connec; +#endif + + if (!badargs && (mine > maxe || maxe < 0 || maxdeg < 0)) + { + fprintf(stderr, + ">E geng: impossible mine,maxe,mindeg,maxdeg values\n"); + badargs = TRUE; + } + + if (!badargs && (res < 0 || res >= mod)) + { + fprintf(stderr,">E geng: must have 0 <= res < mod\n"); + badargs = TRUE; + } + + if (badargs) + { + fprintf(stderr,">E Usage: %s\n",USAGE); + GETHELP; + exit(1); + } + + if ((nautyformat!=0) + (yformat!=0) + (graph6!=0) + + (sparse6!=0) + (nooutput!=0) > 1) + gt_abort(">E geng: -uyngs are incompatible\n"); + +#ifdef OUTPROC + outproc = OUTPROC; +#else + if (nautyformat) outproc = writenauty; + else if (yformat) outproc = writeny; + else if (nooutput) outproc = nullwrite; + else if (sparse6) outproc = writes6x; + else outproc = writeg6x; +#endif + +#ifdef PLUGIN_INIT +PLUGIN_INIT +#endif + + for (i = 0; i <= maxe; ++i) ecount[i] = 0; + for (i = 0; i < maxn; ++i) nodes[i] = 0; + + if (nooutput) + outfile = stdout; + else if (!gotf || outfilename == NULL) + { + outfilename = "stdout"; + outfile = stdout; + } + else if ((outfile = fopen(outfilename, + nautyformat ? "wb" : "w")) == NULL) + { + fprintf(stderr, + ">E geng: can't open %s for writing\n",outfilename); + gt_abort(NULL); + } + + if (bipartite) + if (squarefree) tmaxe = findmaxe(maxebf,maxn); + else tmaxe = findmaxe(maxeb,maxn); + else if (trianglefree) + if (squarefree) tmaxe = findmaxe(maxeft,maxn); + else tmaxe = findmaxe(maxet,maxn); + else if (squarefree) tmaxe = findmaxe(maxef,maxn); + else tmaxe = (maxn*maxn - maxn) / 2; + + if (safe) ++tmaxe; + + if (maxe > tmaxe) maxe = tmaxe; + + if (gotx) + { + if (multiplicity < 3 * mod || multiplicity > 999999999) + gt_abort(">E geng: -x value must be in [3*mod,10^9-1]\n"); + } + else + { + multiplicity = PRUNEMULT * mod; + if (multiplicity / PRUNEMULT != mod) + gt_abort(">E geng: mod value is too large\n"); + } + + if (!gotX) splitlevinc = 0; + + if (!quiet) + { + msg[0] = '\0'; + if (strlen(argv[0]) > 75) + fprintf(stderr,">A %s",argv[0]); + else + CATMSG1(">A %s",argv[0]); + + CATMSG6(" -%s%s%s%s%s%s", + connec2 ? "C" : connec1 ? "c" : "", + trianglefree ? "t" : "", + squarefree ? "f" : "", + bipartite ? "b" : "", + canonise ? "l" : "", + savemem ? "m" : ""); + if (mod > 1) + CATMSG2("X%dx%d",splitlevinc,multiplicity); + CATMSG4("d%dD%d n=%d e=%d",mindeg,maxdeg,maxn,mine); + if (maxe > mine) CATMSG1("-%d",maxe); + if (mod > 1) CATMSG2(" class=%d/%d",res,mod); + CATMSG0("\n"); + fputs(msg,stderr); + fflush(stderr); + } + + g[0] = 0; + deg[0] = 0; + + sparse = bipartite || squarefree || trianglefree || savemem; + + t1 = CPUTIME; + + if (header) + { + if (sparse6) + { + writeline(outfile,SPARSE6_HEADER); + fflush(outfile); + } + else if (!yformat && !nautyformat && !nooutput) + { + writeline(outfile,GRAPH6_HEADER); + fflush(outfile); + } + } + + if (maxn == 1) + { + if (res == 0 && connec < 2) + { + ++ecount[0]; + (*outproc)(outfile,g,1); + } + } + else + { + if (maxn > 28 || maxn+4 > 8*sizeof(xword)) + savemem = sparse = TRUE; + if (maxn == maxe+1 && connec) + bipartite = squarefree = sparse = TRUE; /* trees */ + + makeleveldata(sparse); + + if (maxn >= 14 && mod > 1) splitlevel = maxn - 4; + else if (maxn >= 6 && mod > 1) splitlevel = maxn - 3; + else splitlevel = -1; + + if (splitlevel > 0) splitlevel += splitlevinc; + if (splitlevel > maxn - 1) splitlevel = maxn - 1; + if (splitlevel < 3) splitlevel = -1; + + min_splitlevel = 6; + odometer = secret ? -1 : res; + + if (maxe >= mine && + (mod <= 1 || (mod > 1 && (splitlevel > 2 || res == 0)))) + { + xbnds(1,0,0); + if (sparse) + { + data[1].xx[0] = 0; + if (maxdeg > 0) data[1].xx[1] = XBIT(0); + data[1].xlim = data[1].xub + 1; + } + + if (bipartite) + if (squarefree) + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,makeb6graph); + else + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,makebgraph); + else if (trianglefree) + if (squarefree) + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,makeg5graph); + else + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,makexgraph); + else if (squarefree) + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,makesgraph); + else if (savemem) + spaextend(g,1,deg,0,TRUE, + data[1].xlb,data[1].xub,make0graph); + else + genextend(g,1,deg,0,TRUE,data[1].xlb,data[1].xub); + } + } + t2 = CPUTIME; + + nout = 0; + for (i = 0; i <= maxe; ++i) nout += ecount[i]; + + if (verbose) + { + for (i = 0; i <= maxe; ++i) + if (ecount[i] != 0) + { + fprintf(stderr,">C " COUNTER_FMT " graphs with %d edges\n", + ecount[i],i); + } + } + +#ifdef INSTRUMENT + fprintf(stderr,"\n>N node counts\n"); + for (i = 1; i < maxn; ++i) + { + fprintf(stderr," level %2d: ",i); + fprintf(stderr,COUNTER_FMT " (" COUNTER_FMT + " rigid, " COUNTER_FMT " fertile)\n", + nodes[i],rigidnodes[i],fertilenodes[i]); + } + fprintf(stderr,">A1 " COUNTER_FMT " calls to accept1, " + COUNTER_FMT " nauty, " COUNTER_FMT " succeeded\n", + a1calls,a1nauty,a1succs); + fprintf(stderr,">A2 " COUNTER_FMT " calls to accept2, " COUNTER_FMT + " nuniq, "COUNTER_FMT " nauty, " COUNTER_FMT " succeeded\n", + a2calls,a2uniq,a2nauty,a2succs); + fprintf(stderr,"\n"); +#endif + +#ifdef SUMMARY + SUMMARY(nout,t2-t1); +#endif + + if (!quiet) + { + fprintf(stderr,">Z " COUNTER_FMT " graphs generated in %3.2f sec\n", + nout,t2-t1); + } + +#ifdef GENG_MAIN + for (i = 1; i < maxn; ++i) + if (sparse) + { + free(data[i].xorb); + free(data[i].xx); + } + else + { + free(data[i].xorb); + free(data[i].xset); + free(data[i].xinv); + free(data[i].xcard); + } + return 0; +#else + exit(0); +#endif +} diff --git a/src/planarity.c b/src/planarity.c index 9047f67..3cd5cbd 100644 --- a/src/planarity.c +++ b/src/planarity.c @@ -1,10386 +1,10386 @@ -/* planarity.c - code for planarity testing of undirected graphs. - * Method of Boyer and Myrvold, programmed by Paulette Lieby. - * The copyright of this program is owned by the Magma project. - * Distributed with nauty by permission. - ***************************************************************/ - -/* - * sparseg_adjl.c - */ - -/* - What: - ***** - - Implementing: - - Some high-level functions on the sparse graph as - an adjacency list. - In particular, testing if it is planar. - - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - -boolean -sparseg_adjl_plan_and_iso (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, - int e, int *c, t_ver_sparse_rep **VR, t_adjl_sparse_rep **AR, - t_embed_sparse_rep **ER, int *nbr_e_obs) - /* - the input graph is given as an adjacency list: - V: array of vertices - n: size of graph - A: adjacency list - e: number of edges - - if the graph is planar the embedding is stored in VR and ER; - the embedding contains e edges - (nbr_e_obs not used) - - if the graph is non planar the obstruction is returned in - VR and AR together with the number of edges in nbr_e_obs - - in all cases is also returned the number of components (in c) - */ -{ - t_dlcl **dfs_tree, **back_edges, **mult_edges; - int edge_pos, v, w; - boolean ans; - t_ver_edge *embed_graph; - - ans = sparseg_adjl_is_planar(V, n, A, c, - &dfs_tree, &back_edges, &mult_edges, - &embed_graph, &edge_pos, &v, &w); - - if (!ans) - { - embedg_obstruction(V, A, dfs_tree, back_edges, - embed_graph, n, &edge_pos, - v, w, VR, AR, nbr_e_obs); - } - else - { - embedg_embedding(V, A, embed_graph, n, e, *c, edge_pos, mult_edges, - VR, ER); - } - - sparseg_dlcl_delete(dfs_tree, n); - sparseg_dlcl_delete(back_edges, n); - sparseg_dlcl_delete(mult_edges, n); - embedg_VES_delete(embed_graph, n); - - return ans; -} - - - -int * -sparseg_adjl_footprint (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int v) - /* - return v's footprint: - an array fp of size n where fp[i] = index of (directed) - edge [v, i] in A - */ -{ - /* - note that we won't initialise the array: - its subsequent usage doesn't require it - */ - int *fp, e; - - fp = (int *) mem_malloc(sizeof(int) * n); - - if (V[v].first_edge == NIL) - /* - do nothing - */ - return fp; - - e = V[v].first_edge; - while (e != NIL) - { - fp[A[e].end_vertex] = e; - e = A[e].next; - } - - return fp; -} - - -void -sparseg_adjl_print (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, boolean user_level) -{ - int v; - - for (v = 0; v < n; v++) - { - int next; - - if (user_level) - fprintf(stdout, "%d:\t", v + 1); - else - fprintf(stdout, "%d:\t", v); - - next = V[v].first_edge; - while (next != NIL) - { - if (user_level) - fprintf(stdout, "%d ", A[next].end_vertex + 1); - else - fprintf(stdout, "%d ", A[next].end_vertex); - - next = A[next].next; - } - fprintf(stdout, "\n"); - } -} - - - - -void -sparseg_adjl_embed_print (t_ver_sparse_rep *V_e, int n, - t_adjl_sparse_rep *A, t_embed_sparse_rep *E, boolean user_level) - /* - print the embedding given by E, - edges are referred to by their index in A - - and V_e[v].first_edge is the index in E of the first edge - (in the embedding's order) incident from v - - note that E is NOT indexed by the same vertices' array - that indexes A (at the creation of the sparse graph) - */ -{ - int v; - - for (v = 0; v < n; v++) - { - int start, next; - - if (user_level) - fprintf(stdout, "%d:\t", v + 1); - else - fprintf(stdout, "%d:\t", v); - - if (V_e[v].first_edge == NIL) - { - fprintf(stdout, "\n"); - continue; - } - start = next = V_e[v].first_edge; - - if (user_level) - fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex + 1); - else - fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex); - - next = E[next].next; - - while (next != start) - /* - recall that in E edges are linked into a circular list - */ - { - if (user_level) - fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex + 1); - else - fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex); - - next = E[next].next; - } - fprintf(stdout, "\n"); - } -} - -graph * -sparseg_adjl_to_nauty_graph (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A) - /* - write the sparse graph as a nauty graph - */ -{ - int m, v, e, i; - graph *g; - - m = (n + WORDSIZE - 1) / WORDSIZE; - g = (graph *) mem_malloc(n * m * sizeof(graph)); - for (i = (long) m * n; --i >= 0;) - g[i] = 0; - - /* - we first copy V and A's information into g - */ - for (v = 0; v < n; v++) - { - e = V[v].first_edge; - while (e != NIL) - /* - A[e].end_vertex is the next neighbour in the list, - A[e].next points to the next edge in the list - */ - { - if (A[e].end_vertex != v) /* no loops */ - { - ADDELEMENT(GRAPHROW(g, v, m), A[e].end_vertex); - } - e = A[e].next; - } - } - - return g; -} - - - -#if 0 -t_edge_sparse_rep * -sparseg_adjl_edges (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int e, boolean digraph) - /* - e is the number of edges - */ -{ - t_edge_sparse_rep *edges; - int m, u, v, pos_e; - graph *g; - - edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); - - m = (n + WORDSIZE - 1) / WORDSIZE; - g = sparseg_adjl_to_nauty_graph(V, n, A); - - pos_e = 0; - for (u = 0; u < n; u++) - { - v = digraph == TRUE ? 0 : u + 1; - for (; v < n; v++) - { - if (ISELEMENT(GRAPHROW(g, u, m), v)) - { - t_edge_sparse_rep edge; - - edge.ends[0] = u; - edge.ends[1] = v; - edges[pos_e++] = edge; - } - } - } - ASSERT(pos_e == e); - mem_free(g); - - return edges; -} -#endif - - - -t_edge_sparse_rep * -sparseg_adjl_edges (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, - int e, boolean digraph) - /* - e is the number of edges - */ -{ -#if 0 - t_edge_sparse_rep *edges; - int u, v, pos_e, *loops, *foot_print; - graph *g; - - loops = (int *) mem_malloc(sizeof(int) * n); - for (v = 0; v < n; v++) - { - loops[v] = 0; - } - - edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); - pos_e = 0; - - foot_print = (int *) mem_malloc(sizeof(int) * n); - for (u = 0; u < n; u++) - foot_print[u] = NIL; - - for (v = 0; v < n; v++) - { - int ne; - t_edge_sparse_rep edge; - - ne = V[v].first_edge; - while (ne != NIL) - { - u = A[ne].end_vertex; - if (digraph - || (!digraph && u > v)) - { - foot_print[u] = v; - } - else if (!digraph && u == v) - { - if (loops[v] == 0) - { - foot_print[u] = v; - } - - loops[v] ^= 1; - } - - ne = A[ne].next; - } - - for (u = 0; u < n; u++) - if (foot_print[u] == v) - { - edge.ends[0] = v; - edge.ends[1] = u; - edges[pos_e++] = edge; - } - } - ASSERT(pos_e == e); - mem_free(loops); - mem_free(foot_print); - - return edges; - -#endif - /* - there must be a simpler way - */ -#if 0 - typedef struct edge_list { - int size; - t_edge_sparse_rep *edges; - } t_edge_list; - - t_edge_list *edge_table; - t_edge_sparse_rep *edges; - int u, v, nbr_e, pos_e, *loops; - graph *g; - - loops = (int *) mem_malloc(sizeof(int) * n); - for (v = 0; v < n; v++) - { - loops[v] = 0; - } - - /* - now create an edge table as follows: - - there are n lists in total - - their respective size is given by size - - their contents by *edges: - - edge_table[i] will contain all the edges whose end-point is i: - these edges, by construction, will be sorted according to their - starting point - - what for? to finish off each start-vertex processing - with a bucket sort so that - the edges are sorted wrt start- & end-point - - bucket sort is linear, hence why... - */ - edge_table = (t_edge_list *) mem_malloc(sizeof(t_edge_list) * n); - for (v = 0; v < n; v++) - { - edge_table[v].size = 0; - edge_table[v].edges = NP; - } - - edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); - - nbr_e = 0; - pos_e = 0; - for (v = 0; v < n; v++) - { - int ne, w, u; - - ne = V[v].first_edge; - while (ne != NIL) - { - u = A[ne].end_vertex; - if (digraph - || (!digraph && u > v)) - { - t_edge_sparse_rep edge; - - edge.ends[0] = v; - edge.ends[1] = u; - - /* - now stick this edge into the table: one may ponder - as to the cost of constantly reallocating memory... - some cursory tests in another context tell me that - this is pretty much ok - (and certainly better than allocating n^2 storage space) - */ - if (edge_table[u].size == 0) - { - edge_table[u].edges = (t_edge_sparse_rep *) - mem_malloc(sizeof(t_edge_sparse_rep)); - } - else - { - edge_table[u].edges = (t_edge_sparse_rep *) - mem_realloc(edge_table[u].edges, - sizeof(t_edge_sparse_rep) - * (edge_table[u].size + 1)); - } - - (edge_table[u].edges)[edge_table[u].size] = edge; - edge_table[u].size += 1; - nbr_e++; - } - else if (!digraph && u == v) - { - if (loops[v] == 0) - { - t_edge_sparse_rep edge; - - edge.ends[0] = v; - edge.ends[1] = u; - - if (edge_table[u].size == 0) - { - edge_table[u].edges = (t_edge_sparse_rep *) - mem_malloc(sizeof(t_edge_sparse_rep)); - } - else - { - edge_table[u].edges = (t_edge_sparse_rep *) - mem_realloc(edge_table[u].edges, - sizeof(t_edge_sparse_rep) - * (edge_table[u].size + 1)); - } - - (edge_table[u].edges)[edge_table[u].size] = edge; - edge_table[u].size += 1; - nbr_e++; - } - - loops[v] ^= 1; - } - - ne = A[ne].next; - } - - /* - bucket sort must take place here: - of course the whole lot is not exactly linear! - since we perform the sort n times; but we can hope for - a "good" ?? average behaviour: - - in any case this must be better that checking adjacencies - n^2 times in a sparse rep. (see edge_set_iset_assure) - */ - for (w = 0; w < n; w++) - { - if (edge_table[w].size > 0) - { - for (u = 0; u < edge_table[w].size; u++) - { - ASSERT((edge_table[w].edges)[u].ends[0] == v); - edges[pos_e++] = (edge_table[w].edges)[u]; - } - mem_free(edge_table[w].edges); - edge_table[w].size = 0; - edge_table[w].edges = NP; - } - } - } - ASSERT(nbr_e == e); - ASSERT(pos_e == e); - mem_free(loops); - mem_free(edge_table); - - return edges; -#endif - - t_edge_sparse_rep *edges; - int v, pos_e, *loops; - - edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); - loops = (int *) mem_malloc(sizeof(int) * n); - for (v = 0; v < n; v++) - { - loops[v] = 0; - } - - pos_e = 0; - for (v = 0; v < n; v++) - { - int ne; - - ne = V[v].first_edge; - while (ne != NIL) - { - int u; - - u = A[ne].end_vertex; - if (digraph - || (!digraph && u > v)) - { - t_edge_sparse_rep edge; - - edge.ends[0] = v; - edge.ends[1] = u; - edges[pos_e++] = edge; - } - else if (!digraph && u == v) - { - if (loops[v] == 0) - { - t_edge_sparse_rep edge; - - edge.ends[0] = v; - edge.ends[1] = u; - edges[pos_e++] = edge; - } - - loops[v] ^= 1; - } - ne = A[ne].next; - } - } - ASSERT(pos_e == e); - mem_free(loops); - - return edges; - -} - -/* - * sparseg_adjl_modify.c - */ - -/* - What: - ***** - - Implementing: - - Some high-level functions on the sparse graph as - an adjacency list. - In particular, adding/removing vertices/edges. - - - NOTE: Most of the functions implicitely assume that the - graph is undirected; - this must be slightly rewritten for the general case - -- just haven't got the time right now... - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - - - -#ifndef PLANAR_IN_MAGMA -#endif - - - -boolean -sparseg_adjl_add_edge (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep **A, - int *size_A, int *pos, int u, int v, boolean CHECK) - /* - add the UNDIRECTED edge to the sparse graph (V, n, A) - - pos records where to add the next edge in A - - if pos + 1 == size_A, we must extend A - - we check if the edge is already in the graph iff CHECK true - - also we assume that the graph (V, n, A) is undirected - */ -{ - boolean edge_exists; - - edge_exists = FALSE; - if (CHECK) - { - edge_exists = sparseg_adjl_dir_edge_exists(V, n, *A, u, v); - - if (edge_exists) - return FALSE; - } - - if (*pos == *size_A) - { - IF_DEB( - fprintf(stdout, "realloc \n"); - ) - - *size_A += 2; /* add two directed edges */ - *A = (t_adjl_sparse_rep *) - mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); - } - else if (*pos + 1 == *size_A) - { - IF_DEB( - fprintf(stdout, "realloc \n"); - ) - - *size_A += 1; /* add two directed edges */ - *A = (t_adjl_sparse_rep *) - mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); - } - ASSERT(*pos + 1 < *size_A); - - sparseg_adjl_add_dir_edge(V, n, A, size_A, pos, u, v, FALSE); - sparseg_adjl_add_dir_edge(V, n, A, size_A, pos, v, u, FALSE); - - return TRUE; -} - -boolean -sparseg_adjl_add_edge_no_extend (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int size_A, int *pos, int u, int v, boolean CHECK) - /* - like sparseg_adjl_add_edge but here we are guaranteed - that pos + 1 < size_A - (unless that for some reason we attempt to add - an edge which is already there) - - this feature is required when A is part of a Magma block: - we do not want to reallocate A here - (would be done at a higher level) - - we check if the edge is already in the graph iff CHECK true - - also, we assume that we use this procedur only when dealing - with an undirected graph - */ -{ - boolean edge_added; - - edge_added = - sparseg_adjl_add_dir_edge_no_extend(V, n, A, size_A, pos, u, v, - CHECK); - - if (edge_added) - sparseg_adjl_add_dir_edge_no_extend(V, n, A, size_A, pos, v, u, - FALSE); - - return edge_added; -} - - -boolean -sparseg_adjl_add_dir_edge (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep **A, int *size_A, int *pos, int u, int v, - boolean CHECK) - /* - add the DIRECTED edge to the sparse graph (V, n, A) - - pos records where to add the next edge in A - - if pos >= size_A, we must extend A - - we check if the edge is already in the graph iff CHECK true - */ -{ - boolean edge_exists; - - edge_exists = FALSE; - if (CHECK) - { - edge_exists = sparseg_adjl_dir_edge_exists(V, n, *A, u, v); - - if (edge_exists) - return FALSE; - } - - if (*pos == *size_A) - { - *size_A += 1; /* add one directed edge */ - *A = (t_adjl_sparse_rep *) - mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); - } - ASSERT(*pos < *size_A); - - sparseg_adjl_add_dir_edge_no_extend(V, n, *A, *size_A, pos, u, v, - FALSE); - - return TRUE; -} - -boolean -sparseg_adjl_add_dir_edge_no_extend (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int size_A, int *pos, int u, int v, boolean CHECK) - /* - add an edge where A is guaranteed to be be big enough - (unless that for some reason we attempt to add - an edge which is already there) - - this feature is required when A is part of a Magma block: - we do not want to reallocate A here - (would be done at a higher level) - - we check if the edge is already in the graph iff CHECK true - */ -{ - /* - given the way V and A represent the graph, it is simplest - to add the new edge at the beginning of i's adj. list - */ - int i_v; - t_adjl_sparse_rep a; - - if (CHECK && sparseg_adjl_dir_edge_exists(V, n, A, u, v)) - return FALSE; - - if (*pos >= size_A) - DIE(); - - /* - otherwise always add the edge - */ - i_v = *pos; - a.end_vertex = v; - a.next = V[u].first_edge; - A[(*pos)++] = a; - V[u].first_edge = i_v; - - return TRUE; -} - - - -boolean -sparseg_adjl_remove_edge_no_red (t_ver_sparse_rep *V, t_adjl_sparse_rep *A, - int u, int v) - /* - remove the UNDIRECTED edge from sparse graph (V, A) - if (u, v) is not an edge then nothing changes (and return FALSE) - - A will be left with "holes" - */ -{ - sparseg_adjl_remove_dir_edge_no_red(V, A, u, v); - return sparseg_adjl_remove_dir_edge_no_red(V, A, v, u); -} - - -boolean -sparseg_adjl_remove_dir_edge_no_red (t_ver_sparse_rep *V, - t_adjl_sparse_rep *A, int u, int v) - /* - remove the DIRECTED edge from the sparse graph (V, n, A) - if (u, v) is not an edge then nothing changes (and return FALSE) - - A will be left with "holes" - */ -{ - int cur_e, prev_e; - - cur_e = V[u].first_edge; - if (cur_e == NIL) - /* - (u, v) is not an edge - */ - return FALSE; - - if (A[cur_e].end_vertex == v) - { - V[u].first_edge = A[cur_e].next; - return TRUE; /* done */ - } - - while (A[cur_e].end_vertex != v) - /* - if (u, v) is an edge then this loop will terminate - */ - { - prev_e = cur_e; - cur_e = A[cur_e].next; - if (cur_e == NIL) - /* - (u, v) is not an edge - */ - return FALSE; - } - ASSERT(A[cur_e].end_vertex == v); - - A[prev_e].next = A[cur_e].next; - return TRUE; -} - -int -sparseg_adjl_remove_all_dir_edge_no_red (t_ver_sparse_rep *V, - t_adjl_sparse_rep *A, int u, int v) - /* - remove all DIRECTED edges [u, v] from the non-simple - sparse graph (V, n, A) - if (u, v) is not an edge then nothing changes; - we return the number of edges removed - - A will be left with "holes" - */ -{ - int cur_e, prev_e, e_removed; - - if (V[u].first_edge == NIL) - /* - (u, v) is not an edge - */ - return 0; - - e_removed = 0; - while (A[V[u].first_edge].end_vertex == v) - { - V[u].first_edge = A[V[u].first_edge].next; - e_removed++; - - if (V[u].first_edge == NIL) - return e_removed; - } - ASSERT(A[V[u].first_edge].end_vertex != v); - - prev_e = V[u].first_edge; - cur_e = A[prev_e].next; - while (cur_e != NIL) - { - if (A[cur_e].end_vertex == v) - { - A[prev_e].next = A[cur_e].next; - e_removed++; - cur_e = A[cur_e].next; - } - else - { - prev_e = cur_e; - cur_e = A[cur_e].next; - } - } - - return e_removed; -} - - - -void -sparseg_adjl_add_vertices (t_ver_sparse_rep **V, int n, int nmore) - /* - add nmore vertices - V is assumed to have length n - */ -{ - *V = (t_ver_sparse_rep *) - mem_realloc(*V, sizeof(t_ver_sparse_rep) * (n + nmore)); - - sparseg_adjl_add_vertices_no_extend(*V, n, nmore); -} - -void -sparseg_adjl_add_vertices_no_extend (t_ver_sparse_rep *V, int n, int nmore) - /* - add nmore vertices, - here V is assumed to have length n + nmore (ie V has already - been made bigger) - */ -{ - int v; - - for (v = n; v < n + nmore; v++) - { - V[v].first_edge = NIL; - } -} - -void -sparseg_adjl_remove_vertex (t_ver_sparse_rep **V, int n, - t_adjl_sparse_rep *A, int pos_A, int w, int *e) - /* - V is assumed to have length n: we will reallocate - V so that V will have length n-1 - - A is occupied from [0..pos-1], A will be left with holes - - we also assume that the graph can have loops and multiple edges; - further, we the edge counting implicitely assumes that graph - is undirected!!! - - this must be eventually fixed - */ -{ - int v, nv, edge, loops; - t_ver_sparse_rep *new_V; - - /* - we first count the loops if any - */ - loops = 0; - edge = (*V)[w].first_edge; - while (edge != NIL) - { - loops = A[edge].end_vertex == w ? loops + 1 : loops; - edge = A[edge].next; - } - ASSERT(loops % 2 == 0); - loops /= 2; - - /* - we recreate the vertices array - */ - new_V = (t_ver_sparse_rep *) - mem_malloc(sizeof(t_ver_sparse_rep) * (n - 1)); - - for (v = 0, nv = 0; v < n; v++, nv++) - { - if (v == w) - { - nv--; - } - else - { - new_V[nv].first_edge = (*V)[v].first_edge; - } - } - mem_free(*V); - *V = new_V; - - *e -= loops; - sparseg_adjl_remove_vertex_no_red(*V, n, A, w, e); - - /* - oops! not relabelling vertices can wreck havock! - */ - sparseg_adjl_relabel_vertex(A, pos_A, w); -} - -void -sparseg_adjl_remove_vertex_no_red (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int w, int *e) - /* - here V has already size n - 1 and has been initialised, - all what remains to do is to remove the edges incident - from w in A - - A will be left with holes - */ -{ - int v, nbr_e_removed; - - nbr_e_removed = 0; - for (v = 0; v < n - 1; v++) - { - nbr_e_removed += sparseg_adjl_remove_all_dir_edge_no_red(V, A, v, w); - } - - *e= *e - nbr_e_removed; -} - -void -sparseg_adjl_relabel_vertex (t_adjl_sparse_rep *A, int pos, int u) - /* - relabel all vertices v > u as v-1 - (required when removing a vertex) - */ -{ - int i; - - for (i = 0; i < pos; i++) - { - A[i].end_vertex = A[i].end_vertex > u ? - A[i].end_vertex - 1 : A[i].end_vertex; - } -} - -/* - * sparseg_adjl_pred.c - */ - -/* - What: - ***** - - Implementing: - - Some high-level functions on the sparse graph as - an adjacency list: predicates. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - -boolean -sparseg_adjl_dir_edge_exists (t_ver_sparse_rep *V, int n, - t_adjl_sparse_rep *A, int u, int v) - /* - does the directed edge [u, v] already exist in the graph - */ -{ - int cur_e, prev_e; - - cur_e = V[u].first_edge; - if (cur_e == NIL) - return FALSE; - - if (A[cur_e].end_vertex == v) - { - return TRUE; - } - - while (A[cur_e].end_vertex != v) - { - prev_e = cur_e; - cur_e = A[cur_e].next; - if (cur_e == NIL) - /* - (u, v) is not an edge - */ - return FALSE; - } - ASSERT(A[cur_e].end_vertex == v); - return TRUE; -} - - - -boolean -sparseg_adjl_u_adj_v (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, - int u, int v) - /* - is u adj. to v - */ -{ - return sparseg_adjl_dir_edge_exists(V, n, A, u, v); -} - - -boolean -sparseg_adjl_sub (t_ver_sparse_rep *V1, int n1, t_adjl_sparse_rep *A1, - t_ver_sparse_rep *V2, int n2, t_adjl_sparse_rep *A2) - /* - test if the (V1, n1, A1) sparse graph is a subgraph of - the (V2, n2, A2) graph - */ -{ - int v, *fp, n, bign, i; - - n = n1 > n2 ? n2 : n1; - bign = n1 > n2 ? n1 : 0; - fp = (int *) mem_malloc(sizeof(int) * n); - for (i = 0; i < n; i++) - fp[i] = NIL; - - for (v = 0; v < n; v++) - { - int ne1, ne2; - - ne1 = V1[v].first_edge; - ne2 = V2[v].first_edge; - if (ne1 == NIL) - { - continue; - } - else if (ne2 == NIL) - { - mem_free(fp); - return FALSE; - } - - while (ne2 != NIL) - { - int u2; - - u2 = A2[ne2].end_vertex; - fp[u2] = v; - ne2 = A2[ne2].next; - } - - while (ne1 != NIL) - { - int u1; - - u1 = A1[ne1].end_vertex; - if (fp[u1] != v) - { - mem_free(fp); - return FALSE; - } - ne1 = A1[ne1].next; - } - } - mem_free(fp); - - for (v = n; v < bign; v++) - /* - those vertices must not be end points of edges: - this chcek is only necessary in the digraph case - */ - { - if (V1[v].first_edge != NIL) - return FALSE; - } - - return TRUE; -} - - - -boolean -sparseg_adjl_eq (t_ver_sparse_rep *V1, int n1, t_adjl_sparse_rep *A1, - t_ver_sparse_rep *V2, int n2, t_adjl_sparse_rep *A2) - /* - compare the two sparse graphs (V1, n1, A1) & (V2, n2, A2) - we don't know their number of edges - */ -{ - if (n1 != n2) - return FALSE; - - return sparseg_adjl_sub(V1, n1, A1, V2, n2, A2) - && sparseg_adjl_sub(V2, n2, A2, V1, n1, A1); -} - - - -/* - * sparseg_dlcl_misc.c - */ - -/* - What: - ***** - - Implementing: - - Housekeeping for an internal sparse graph representation - internal to the planarity tester and obstruction isolator. - - This sparse graph consists of an array of doubly linked circular lists - (the neighbour lists for each vertex). - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - -/* aproto: beginstatic -- don't touch this!! */ -static boolean sparseg_dlcl_is_present (t_dlcl *, int, t_dlcl **); -/* aproto: endstatic -- don't touch this!! */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - -void -sparseg_dlcl_delete (t_dlcl **g, int n) -{ - int i; - - for (i = 0; i < n; i++) - { - embedg_dlcl_delete(g[i]); - } - mem_free(g); -} - -void -sparseg_dlcl_print (t_dlcl **g, int n) -{ - int i; - - for (i = 0; i < n; i++) - { - fprintf(stdout,"%d:\t", i); - embedg_dlcl_print(g[i]); - } -} - - -static boolean -sparseg_dlcl_is_present (t_dlcl *l, int label, t_dlcl **p) -{ - *p = embedg_dlcl_find(l, label); - return *p == NP ? FALSE : TRUE; -} - - -boolean -sparseg_dlcl_is_adjacent (t_dlcl **g, int n, int v, int u, t_dlcl **p) - /* - is u adjacent to v - */ -{ - ASSERT(v >= 0 && v < n && u >= 0 && u < n); - return sparseg_dlcl_is_present(g[v], u, p); -} - -void -sparseg_dlcl_append_to_neigh_list (t_dlcl **g, int n, int v, int u, int in_adjl) - /* - append u to the neighbour list of v - */ -{ - t_dlcl *u_rec; - - u_rec = embedg_dlcl_rec_new(u); - u_rec->in_adjl = in_adjl; - g[v] = embedg_dlcl_rec_append(g[v], u_rec); -} - - - - -void -sparseg_dlcl_to_sparseg (t_dlcl **g, int n, int e, - t_ver_sparse_rep **V, t_adjl_sparse_rep **A) - /* - e is the number of undirected edges of g - - convert a dlcl into the standard sparseg rep. as an - adjacency list - */ -{ - int i_e, v; - - *V = (t_ver_sparse_rep *) mem_malloc(sizeof(t_ver_sparse_rep) * n); - *A = (t_adjl_sparse_rep *) mem_malloc(sizeof(t_adjl_sparse_rep) * 2 * e); - - for (v = 0; v < n; v++) - (*V)[v].first_edge = NIL; - - i_e = 0; - for (v = 0; v < n; v++) - { - t_dlcl *l, *p; - - l = p = g[v]; - if (!embedg_dlcl_is_empty(p)) - { - t_adjl_sparse_rep a; - - ASSERT((*V)[v].first_edge == NIL); - (*V)[v].first_edge = i_e; - a.end_vertex = p->info; - a.next = i_e + 1; - (*A)[i_e++] = a; - - p = embedg_dlcl_list_next(p); - while (p != l) - { - a.end_vertex = p->info; - a.next = i_e + 1; - (*A)[i_e++] = a; - - p = embedg_dlcl_list_next(p); - } - - /* - end of list for v - */ - (*A)[i_e - 1].next = NIL; - } - } - ASSERT(i_e == 2 * e); -} - -boolean -sparseg_dlcl_sub (t_dlcl **g1, int n1, t_dlcl **g2, int n2) - /* - is g2 a subgraph of g1 - - I request that both graphs have same order - - This is not used anywhere... do we need it??? - */ -{ - int n, v, *fp; - - if (n1 != n2) - return FALSE; - - n = n1; - fp = (int *) mem_malloc(sizeof(int) * n); - for (v = 0; v < n; v++) - fp[v] = NIL; - - for (v = 0; v < n; v++) - { - t_dlcl *l1, *p1, *l2, *p2; - - l1 = p1 = g1[v]; - l2 = p2 = g2[v]; - if (embedg_dlcl_is_empty(p1) && !embedg_dlcl_is_empty(p2)) - { - mem_free(fp); - return FALSE; - } - if (embedg_dlcl_is_empty(p2)) - { - continue; - } - - fp[p1->info] = v; - p1 = embedg_dlcl_list_next(p1); - while (p1 != l1) - { - fp[p1->info] = v; - p1 = embedg_dlcl_list_next(p1); - } - - if (fp[p2->info] != v) - { - mem_free(fp); - return FALSE; - } - p2 = embedg_dlcl_list_next(p2); - while (p2 != l2) - { - if (fp[p2->info] != v) - { - mem_free(fp); - return FALSE; - } - } - } - mem_free(fp); - - return TRUE; -} -/* - * VES_misc.c - */ - -/* - What: - ***** - - Implementing: - - All low-level routines for the VES structure: - - - the VES structure is solely used within the planarity tester - and obstruction isolator - - - it stores vertices, virtual vertices and edges - --more on this later-- - - - it allows for circular doubly linked lists, hence - enabling us -among other things- to store the - graph embedding if the tester is successful - - - basic features: - + the VES has exactly size 2n + 2(3n-5) : - we add at most one more edge than the max for a planar graph - (need to x by 2: we store directed edges) - + a vertex and the edges incident FROM it are linked in a doubly - linked circular list - + where a vertex is inserted between two of its outcoming edges - determines an external face walk for a bicomponent - + the twin edge is more commonly known as the inverse edge - + we have tree and back edges (from the DFS), and short-cut edges - which are added by the tester - -but short-cut edges are added in such a way as to maintain - planarity (in a local sense) - + vertices and edges can be marked (visited for example) - + they have an orientation which must be eventuall recovered - and which is set in the merge_bicomp routine - + vertices are essentially known via their DFI or DFS index - (though their label is stored too) - - blah, blah.... later then. - Have a look at embedg_planar_alg_init which initialises the VES - structure - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_SCE(x) {} -#define IF_DEB_PROPER_FACE(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - -boolean -embedg_VES_is_vertex (int n, int i) - /* - is this a vertex - (relative to the "big" array of size 2n + 2(3n-5)) - */ -{ - return i < n ? TRUE : FALSE; -} - -boolean -embedg_VES_is_virtual_vertex (int n, int i) - /* - is this a virtual vertex - (relative to the "big" array of size 2n + 2(3n-5)) - - a virtual vertex is a vertex v^c which denotes the - DFS parent of the child c - - see embedg_planar_alg_init for more - */ -{ - return i >= n && i < 2*n ? TRUE : FALSE; -} - -boolean -embedg_VES_is_edge (int n, int i) - /* - is this an edge - (relative to the "big" array of size 2n + 2(3n-5)) - */ -{ - return i >= 2*n ? TRUE : FALSE; -} - -boolean -embedg_VES_is_tree_edge (t_ver_edge *embed_graph, int n, int i) - /* - is this s tree edge - */ -{ - return embedg_VES_is_edge(n, i) - && embed_graph[i].type == TE; -} - -boolean -embedg_VES_is_back_edge (t_ver_edge *embed_graph, int n, int i) - /* - is this a back edge - */ -{ - return embedg_VES_is_edge(n, i) - && embed_graph[i].type == BE; -} - -boolean -embedg_VES_is_short_cut_edge (t_ver_edge *embed_graph, int n, int i) - /* - as the name indicates... - */ -{ - return embedg_VES_is_edge(n, i) - && embed_graph[i].type == SCE; -} - -void -embedg_VES_print_vertex (int n, int v) -{ - ASSERT(embedg_VES_is_vertex(n, v)); - fprintf(stdout, "%d ", v); -} - -void -embedg_VES_print_virtual_vertex (t_ver_edge *embed_graph, int n, int v) -{ - int c; - - ASSERT(embedg_VES_is_virtual_vertex(n, v)); - c = v - n; - fprintf(stdout, "%d^%d ", embed_graph[c].DFS_parent, c); -} - -void -embedg_VES_print_any_vertex (t_ver_edge *embed_graph, int n, int v) -{ - if (embedg_VES_is_vertex(n, v)) - { - embedg_VES_print_vertex(n, v); - } - else - { - embedg_VES_print_virtual_vertex(embed_graph, n, v); - } -} - -void -embedg_VES_print_any_rec (t_ver_edge *embed_graph, int n, int r) -{ - if (embedg_VES_is_edge(n, r)) - { - embedg_VES_print_edge(embed_graph, n, r); - } - else - { - embedg_VES_print_any_vertex(embed_graph, n, r); - } -} - -void -embedg_VES_print_edge (t_ver_edge *embed_graph, int n, int e) -{ - int v, prev, cur; - - ASSERT(embedg_VES_is_edge(n, e)); - - /* - must find the vertex in the doubly linked circular list - of vertices/edges - */ - - prev = e; - cur = v = embed_graph[e].link[0]; - if (embedg_VES_is_vertex(n, v) - || embedg_VES_is_virtual_vertex(n, v)) - { - embedg_VES_print_any_vertex(embed_graph, n, v); - fprintf(stdout, ", "); - embedg_VES_print_any_vertex(embed_graph, n, - embed_graph[e].neighbour); - fprintf(stdout, "):0\n"); - } - else while (!embedg_VES_is_vertex(n, v) - && !embedg_VES_is_virtual_vertex(n, v)) - { - v = embedg_VES_get_next_in_dlcl(embed_graph, n, - cur, prev); - - if (embedg_VES_is_vertex(n, v) - || embedg_VES_is_virtual_vertex(n, v)) - { - embedg_VES_print_any_vertex(embed_graph, n, v); - fprintf(stdout, ", "); - embedg_VES_print_any_vertex(embed_graph, n, - embed_graph[e].neighbour); - fprintf(stdout, "):0\n"); - } - else - { - prev = cur; - cur = v; - } - } -} - -void -embedg_VES_print_flipped_edges (t_ver_edge *embed_graph, int n, int edge_pos) - /* - print those edges in the structure whose sign is CLOCKW, - ie which have been flipped at some stage - */ -{ - int e; - - for (e = 2*n; e <= edge_pos; e++) - { - if (!embedg_VES_is_short_cut_edge(embed_graph, n, e)) - /* - we don't care about the short-cut edges - */ - { - if (embed_graph[e].sign != CCLOCKW) - { - embedg_VES_print_edge(embed_graph, n, e); - } - } - } -} - -#if 0 -int -embedg_VES_get_edge_from_ver (t_ver_edge *embed_graph, int n, int v) - /* - not used anywhere; why is this here??? - */ -{ - int in, e; - - ASSERT(embedg_VES_is_vertex(n, v) - || embedg_VES_is_virtual_vertex(n, v)); - - in = embedg_VES_is_edge(n, embed_graph[v].link[0]) ? 0 : 1; - e = embed_graph[v].link[in]; - ASSERT(embedg_VES_is_edge(n, e)); - - return e; -} - -int -embedg_VES_get_ver_from_edge (t_ver_edge *embed_graph, int n, int e) -{ - int in, v; - - ASSERT(embedg_VES_is_edge(n, e)); - - in = embedg_VES_is_vertex(n, embed_graph[e].link[0]) - || embedg_VES_is_virtual_vertex(n, embed_graph[e].link[0]) - ? - 0 : 1; - - v = embed_graph[e].link[in]; - ASSERT(embedg_VES_is_vertex(n, v) - || embedg_VES_is_virtual_vertex(n, v)); - - return v; -} -#endif - -int -embedg_VES_get_twin_edge (t_ver_edge *embed_graph, int n, int e) - /* - the twin edge is understood as being the inverse edge - */ -{ - int twin; - - ASSERT(embedg_VES_is_edge(n, e)); - - twin = e % 2 == 0 ? e + 1 : e - 1; - ASSERT(embedg_VES_is_edge(n, twin)); - - return twin; -} - -int -embedg_VES_get_ver_from_virtual (t_ver_edge *embed_graph, int n, int vv) - /* - get v from the virtual vertex v^c - */ -{ - int v; - - ASSERT(embedg_VES_is_virtual_vertex(n, vv)); - v = embed_graph[vv - n].DFS_parent; - - return v; -} - -int -embedg_VES_get_ver (t_ver_edge *embed_graph, int n, int v) -{ - if (embedg_VES_is_virtual_vertex(n, v)) - return embedg_VES_get_ver_from_virtual(embed_graph, n, v); - - return v; -} - - -int -embedg_VES_get_next_in_dlcl (t_ver_edge *embed_graph, int n, int r, int prev) - /* - r is a (virtual) vertex or edge record in embed_graph: - get the next in the list (formed by the .link[] fields) - in the doubly linked circular list - - so that prev != next - -- NOTE: a priori these lists always contain 2 elts at least - so that there shouldn't be any problem... - --> huh? is that true? - */ -{ - return embed_graph[r].link[0] == prev ? - embed_graph[r].link[1] : embed_graph[r].link[0]; -} - - -void -embedg_VES_walk_bicomp (t_ver_edge *embed_graph, int n, int v, int vin) - /* - walk the external face of the bicomp starting - at VIRTUAL vertex v entered via vin - - this of course assumes that the "thing" rooted at - v is a bicomponent -- depending where we are at in the - tester this is not necessarily the case - -- I comment upon this in merge_bicomps.c: - embedg_VES_merge_pertinent_bicomps - */ -{ - int start, startin, s, sin; - - ASSERT(embedg_VES_is_virtual_vertex(n, v)); - - embedg_VES_print_virtual_vertex(embed_graph, n, v); - - s = NIL; - start = v; - startin = vin; - while (s != v) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, start, startin, - FALSE, 0, &s, &sin); - if (embedg_VES_is_virtual_vertex(n, s)) - { - embedg_VES_print_virtual_vertex(embed_graph, n, s); - } - else - { - embedg_VES_print_vertex(n, s); - } - start = s; - startin = sin; - } - fprintf(stdout, "\n"); -} - -void -embedg_VES_print_adj_list (t_ver_edge *embed_graph, int n, int r, - boolean consistent) - /* - print r's adjacency list - r can be a vertex or edge - - the boolean if true assumes that - the list is consistent (will determine the way we traverse the list) - - a priori we should get the same result either way - */ -{ - if (consistent) - { - int next; - - embedg_VES_print_any_rec(embed_graph, n, r); - - next = embed_graph[r].link[0]; - while (next != r) - { - embedg_VES_print_any_rec(embed_graph, n, next); - next = embed_graph[next].link[0]; - } - } - else - { - int prev, cur, next; - - embedg_VES_print_any_rec(embed_graph, n, r); - - prev = r; - cur = embed_graph[r].link[0]; - - while (cur != r) - { - embedg_VES_print_any_rec(embed_graph, n, cur); - next = embedg_VES_get_next_in_dlcl(embed_graph, n, - cur, prev); - prev = cur; - cur = next; - } - } -} - -boolean -embedg_VES_is_adj_list_consistent (t_ver_edge *embed_graph, int n, int r) - /* - checks that r's adjacency list is consistent: - ie, that either traversing it using link[0] always - or traversing it using embedg_VES_get_next_in_dlcl - gives the SAME result - */ -{ - int *list_link, *list_n_dldl, il, id, i; - - list_link = (int *) mem_malloc(sizeof(int) * 2 * n); - list_n_dldl = (int *) mem_malloc(sizeof(int) * 2 * n); - /* - must allocate 2*n space: I could have TE and SCE with same neighbour - (or BE and SCE as well) - */ - il = id = -1; - - /* - traversing the list via link[0] - */ - { - int next; - - list_link[++il] = r; - - next = embed_graph[r].link[0]; - while (next != r) - { - list_link[++il] = next; - next = embed_graph[next].link[0]; - } - } - - /* - traversing the list using embedg_VES_get_next_in_dlcl - */ - { - int prev, cur, next; - - list_n_dldl[++id] = r; - prev = r; - cur = embed_graph[r].link[0]; - - while (cur != r) - { - list_n_dldl[++id] = cur; - next = embedg_VES_get_next_in_dlcl(embed_graph, n, - cur, prev); - prev = cur; - cur = next; - } - } - - if (il != id) - { - mem_free(list_link); - mem_free(list_n_dldl); - return FALSE; - } - - for (i = 0; i <= il; i++) - { - if (list_link[i] != list_n_dldl[i]) - { - mem_free(list_link); - mem_free(list_n_dldl); - return FALSE; - } - } - - mem_free(list_link); - mem_free(list_n_dldl); - return TRUE; -} - - -boolean -embedg_VES_are_adj_lists_consistent (t_ver_edge *embed_graph, int n) - /* - checks that the adjacency list of each vertex is consistent - in the manner of embedg_VES_is_adj_list_consistent - */ -{ - int i; - - /* - it is enough to visit the vertices and virtual vertices only - (I don't think it is enough to do the vertices only --??) - */ - for (i = 0; i < 2*n; i++) - if (!embedg_VES_is_adj_list_consistent(embed_graph, n, i)) - return FALSE; - - return TRUE; -} - - - -void -embedg_VES_remove_edge (t_ver_edge *embed_graph, int n, int e) - /* - remove edge e from the embedding - */ -{ - int r1, r2, r1out, r2in, twin; - - ASSERT(embedg_VES_is_edge(n, e)); - - IF_DEB_SCE( - fprintf(stdout, "removing an SCE, enter\n"); - embedg_VES_print_edge(embed_graph, n, e); - ) - - r1 = embed_graph[e].link[0]; - r2 = embed_graph[e].link[1]; - - /* - disable e and link r1 and r2 together: - we had r1 -> e -> r2 - */ - embed_graph[e].link[0] = embed_graph[e].link[1] = e; - - r1out = embed_graph[r1].link[0] == e ? 0 : 1; - r2in = embed_graph[r2].link[0] == e ? 0 : 1; - - if (r1 == r2) - /* - this I think should never happen, but one never knows... - */ - { - embed_graph[r1].link[0] = embed_graph[r1].link[1] = r1; - } - else - { - embed_graph[r1].link[r1out] = r2; - embed_graph[r2].link[r2in] = r1; - } - - ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, r1)); - - /* - now we must do a similar thing for the twin - (which must get reomved as well) - */ - twin = embedg_VES_get_twin_edge(embed_graph, n, e); - - IF_DEB_SCE( - fprintf(stdout, "removing an SCE, the twin\n"); - embedg_VES_print_edge(embed_graph, n, twin); - ) - - r1 = embed_graph[twin].link[0]; - r2 = embed_graph[twin].link[1]; - - embed_graph[twin].link[0] = embed_graph[twin].link[1] = twin; - - r1out = embed_graph[r1].link[0] == twin ? 0 : 1; - r2in = embed_graph[r2].link[0] == twin ? 0 : 1; - - if (r1 == r2) - { - embed_graph[r1].link[0] = embed_graph[r1].link[1] = r1; - } - else - { - embed_graph[r1].link[r1out] = r2; - embed_graph[r2].link[r2in] = r1; - } - - ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, r1)); -} - - -void -embedg_VES_set_orientation (t_ver_edge *embed_graph, int n, int *ver_orient) - /* - using the vertices' orientation as given in ver_orient - we set the orientation for each edge in the adjacency list - for each vertex - - to do this we use the field sign which is NOT needed - anymore by the tester since by the time we call this - function we would have finished with that bit (the tester) - - sign is only set when merging bicomps - - even though we'll perform another walkdown when - recovering an obstruction (if any) no bicomp merging will occur, - so we are safe - */ -{ - int v; - - for (v = 0; v < n; v++) - { - int o, e; - - o = ver_orient[v]; - embed_graph[v].sign = o; - - e = embed_graph[v].link[0]; - - while (e != v) - /* - just as a note: note the way I get the next in the list - here (as opposed to using - embedg_VES_get_next_in_dlcl): - this is because I implicitely assume that - the adjacency lists are consistent - - Also note that edges can be SCE, it doesn't really matter - anyway (they may not have been removed yet - -- see the way we recover the obstruction: - embedg_mark_obstruction) - */ - { - embed_graph[e].sign = o; - e = embed_graph[e].link[0]; - } - } -} - - -/* - * dlcl_misc.c - */ - -/* - What: - ***** - - Implementing: - - Housekeeping for a simple doubly linked circular list: - this is a data structure ONLY used WITHIN - the planarity tester and obstruction isolator and is not to be - confused with the VES structure mentionned elsewhere. - - The VES structure is an array, while the dlcl one is a list of - pointers. - - The dlcl is especially useful as it allows for the storage - of an ordered list. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - -/* aproto: beginstatic -- don't touch this!! */ -static void embedg_dlcl_rec_free (t_dlcl *); -static void embedg_dlcl_rec_insert_right (t_dlcl *, t_dlcl *); -static void embedg_dlcl_rec_insert_left (t_dlcl *, t_dlcl *); -static void embedg_dlcl_rec_retrieve (t_dlcl *); -static void embedg_dlcl_rec_delete (t_dlcl *); -static boolean embedg_dlcl_is_singleton (t_dlcl *); -/* aproto: endstatic -- don't touch this!! */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - -t_dlcl * -embedg_dlcl_rec_new (int info) - /* - create a new record with info in the global array - to insert in the list - */ -{ - t_dlcl *r; - - r = (t_dlcl *) mem_malloc(sizeof(t_dlcl)); - r->info = info; - r->in_adjl = r->twin_in_adjl = NIL; - r->mult = 1; - r->right = r; - r->left = r; - return r; -} - -static void -embedg_dlcl_rec_free (t_dlcl *r) - /* - free - */ -{ - mem_free(r); -} - -void -embedg_dlcl_rec_print (t_dlcl *r) -{ - fprintf(stdout,"%d ", r->info); -} - -void -embedg_dlcl_print (t_dlcl *l) -{ - t_dlcl *p = l; - - if (!embedg_dlcl_is_empty(p)) - { - embedg_dlcl_rec_print(p); - p = embedg_dlcl_list_next(p); - while (p != l) - { - embedg_dlcl_rec_print(p); - p = embedg_dlcl_list_next(p); - } - } - fprintf(stdout,"\n"); -} - - -static void -embedg_dlcl_rec_insert_right (t_dlcl *l, t_dlcl *r) -{ - t_dlcl *tmp_r, *tmp_l; - - tmp_r = l->right; - tmp_l = tmp_r->left; - - l->right = r; - r->right = tmp_r; - - r->left = tmp_l; - tmp_r->left = r; -} - - -static void -embedg_dlcl_rec_insert_left (t_dlcl *l, t_dlcl *r) -{ - t_dlcl *tmp_r, *tmp_l; - - tmp_l = l->left; - tmp_r = tmp_l->right; - - l->left = r; - r->left = tmp_l; - - r->right = tmp_r; - tmp_l->right = r; -} - -t_dlcl * -embedg_dlcl_rec_append (t_dlcl *l, t_dlcl *r) -{ - if (embedg_dlcl_is_empty(l)) - return r; - - embedg_dlcl_rec_insert_left(l, r); - return l; -} - -t_dlcl * -embedg_dlcl_rec_prepend (t_dlcl *l, t_dlcl *r) -{ - if (embedg_dlcl_is_empty(l)) - return r; - - embedg_dlcl_rec_insert_left(l, r); - return r; -} - -t_dlcl * -embedg_dlcl_cat (t_dlcl *l, t_dlcl *m) - /* - concatenate m to the RIGHT of the end of l - WITHOUT copying m - */ -{ - t_dlcl *h1, *h2, *e1, *e2; - - if (embedg_dlcl_is_empty(l)) - return m; - if (embedg_dlcl_is_empty(m)) - return l; - - h1 = l; - e1 = l->left; - h2 = m; - e2 = m->left; - - e1->right = h2; - h2->left = e1; - e2->right = h1; - h1->left = e2; - - return l; -} - -t_dlcl * -embedg_dlcl_find (t_dlcl *l, int info) -{ - t_dlcl *p = l; - - if (!embedg_dlcl_is_empty(p)) - { - if (p->info == info) - { - return p; - } - p = embedg_dlcl_list_next(p); - while (p != l) - { - if (p->info == info) - { - return p; - } - p = embedg_dlcl_list_next(p); - } - } - return NP; -} - -t_dlcl * -embedg_dlcl_find_with_NIL_twin_in_adjl (t_dlcl *l, int info) -{ - t_dlcl *p = l; - - if (!embedg_dlcl_is_empty(p)) - { - if (p->info == info && p->twin_in_adjl == NIL) - { - return p; - } - p = embedg_dlcl_list_next(p); - while (p != l) - { - if (p->info == info && p->twin_in_adjl == NIL) - { - return p; - } - p = embedg_dlcl_list_next(p); - } - } - return NP; -} - - - -static void -embedg_dlcl_rec_retrieve (t_dlcl *r) -{ - t_dlcl *right, *left; - - right = r->right; - left = r->left; - - left->right = right; - right->left = left; - - r->right = r; - r->left = r; -} - -static void -embedg_dlcl_rec_delete (t_dlcl *r) -{ - embedg_dlcl_rec_retrieve(r); - embedg_dlcl_rec_free(r); -} - - -t_dlcl * -embedg_dlcl_delete_first (t_dlcl *l) - /* - prune the list from the head: - - set new head to right of old head - - delete old head - */ -{ - t_dlcl *new_head; - - ASSERT(!embedg_dlcl_is_empty(l)); - if (embedg_dlcl_is_singleton(l)) - { - new_head = NP; - } - else - { - new_head = l->right; - } - embedg_dlcl_rec_delete(l); - return new_head; -} - - -t_dlcl * -embedg_dlcl_delete_rec (t_dlcl *l, t_dlcl *r) - /* - delete r from l; - if r == l, set new head to right of old head - */ -{ - if (r == l) - { - return embedg_dlcl_delete_first(l); - } - embedg_dlcl_rec_delete(r); - return l; -} - - -boolean -embedg_dlcl_is_empty (t_dlcl *l) -{ - return (l == NP) ? TRUE : FALSE; -} - - -static boolean -embedg_dlcl_is_singleton (t_dlcl *l) -{ - return (l->right == l) ? TRUE : FALSE; - /* - same as l->left == l - */ -} - -t_dlcl * -embedg_dlcl_list_next (t_dlcl *l) - /* - this assumes no choice in the direction of the walking - (always to the right) - -- good enough when deleting for example or when - the direction of the walking does not matter - */ -{ - return l->right; -} - - -t_dlcl * -embedg_dlcl_list_prev (t_dlcl *l) - /* - this assumes no choice in the direction of the walking - (always to the right) - */ -{ - return l->left; -} - -t_dlcl * -embedg_dlcl_list_last (t_dlcl *l) -{ - return embedg_dlcl_list_prev(l); -} - - - -void -embedg_dlcl_delete (t_dlcl *l) -{ - if (!embedg_dlcl_is_empty(l)) - { - while (!embedg_dlcl_is_singleton(l)) - { - t_dlcl *next; - - next = embedg_dlcl_list_next(l); - embedg_dlcl_rec_delete(next); - } - embedg_dlcl_rec_delete(l); - } -} - -t_dlcl * -embedg_dlcl_copy (t_dlcl *l) -{ - t_dlcl *p, *c; - - if (embedg_dlcl_is_empty(l)) - return NP; - - c = embedg_dlcl_rec_new(l->info); - - p = embedg_dlcl_list_next(l); - while (p != l) - { - t_dlcl *temp; - - temp = embedg_dlcl_rec_new(p->info); - temp->in_adjl = p->in_adjl; - temp->twin_in_adjl = p->twin_in_adjl; - temp->mult = p->mult; - c = embedg_dlcl_rec_append(c, temp); - p = embedg_dlcl_list_next(p); - } - return c; -} - - -int -embedg_dlcl_length (t_dlcl *l) -{ - t_dlcl *p; - int n; - - if (embedg_dlcl_is_empty(l)) - return 0; - - p = embedg_dlcl_list_next(l); - n = 1; - while (p != l) - { - n++; - p = embedg_dlcl_list_next(p); - } - return n; -} -/* - * planar_by_edge_addition.c - */ - -/* - What: - ***** - - Implementing: - - The top level for the planarity tester. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} -#define IF_DEB_EDGES(x) {} -#define IF_CPU(x) {} - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - -boolean -sparseg_adjl_is_planar ( - t_ver_sparse_rep *V, - int n, - t_adjl_sparse_rep *A, /* input sparse graph */ - int *nbr_c, /* size of the graph, #components - */ - t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree - -- vertices are as DFIs - -- and children are ordered wrt - lowpoint value - */ - t_dlcl ***back_edges, /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - (vertices are given as DFIs) - */ - t_dlcl ***mult_edges, /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - (vertices are given as DFIs) - */ - t_ver_edge **embed_graph, /* output graph embedding -- more on that - later - */ - int *edge_pos, /* pos. in embed_graph for addition - of the next edge */ - int *vr, - int *wr /* if graph is non planar, return - the unembedded edge - (where wr descendant of vr) - */ -) - /* - as the name indicates: is the graph planar? - */ -{ - int v; - - IF_CPU( - float sttime; float time_to_now; - ) - - - *embed_graph = - embedg_planar_alg_init(V, n, A, nbr_c, - edge_pos, dfs_tree, back_edges, mult_edges); - IF_CPU( - sttime = time_current_user(); - ) - - for (v = n - 1; v >= 0; v--) - /* - visit all vertices in descending DFI order - */ - { - t_dlcl *be_l, *te_l, *p; - - IF_DEB( - fprintf(stdout, "top level, vertex %d\n", v); - ) - - /* - find all the back edges [w, v] where w is a descendant of v - and perform a walkup from w to v - (ie determine which bicomps are pertinent) - */ - be_l = (*back_edges)[v]; - p = be_l; - - if (!embedg_dlcl_is_empty(p)) - { - int w; - - w = p->info; - IF_DEB( - fprintf(stdout, "top level, before walkup for w %d\n", w); - ) - embedg_walkup(*embed_graph, n, v, p); - - p = embedg_dlcl_list_next(p); - while (p != be_l) - { - w = p->info; - IF_DEB( - fprintf(stdout, "top level, before walkup for w %d\n", w); - ) - embedg_walkup(*embed_graph, n, v, p); - - p = embedg_dlcl_list_next(p); - } - } - - /* - perform a walkdown for each tree edge [v, c], c a descendant of v - (ie attempt to embed all back edges on the pertinent bicomps) - */ - te_l = (*dfs_tree)[v]; - p = te_l; - - if (!embedg_dlcl_is_empty(p)) - { - int c, vv; - t_merge_queue q; - - c = p->info; - vv = c + n; - IF_DEB( - fprintf(stdout, "top level, before walkdown for c %d\n", c); - ) - q = embedg_walkdown(*embed_graph, n, edge_pos, vv); - - IF_DEB( - fprintf(stdout, "top level, after walkdown for c %d, state of edges'sign\n", c); - embedg_VES_print_flipped_edges(*embed_graph, - n, *edge_pos); - ) - - /* - temp only - */ - embedg_merge_queue_delete(q); - p = embedg_dlcl_list_next(p); - while (p != te_l) - { - c = p->info; - vv = c + n; - IF_DEB( - fprintf(stdout, "top level, before walkdown for c %d\n", c); - ) - q = embedg_walkdown(*embed_graph, n, edge_pos, vv); - - IF_DEB( - fprintf(stdout, "top level, after walkdown for c %d, state of edges'sign\n", c); - embedg_VES_print_flipped_edges(*embed_graph, - n, *edge_pos); - ) - - /* - temp only - */ - embedg_merge_queue_delete(q); - - p = embedg_dlcl_list_next(p); - } - } - - - /* - check that each back edge [w, v], w a descendant of v, - has been embedded - */ - be_l = (*back_edges)[v]; - p = be_l; - - if (!embedg_dlcl_is_empty(p)) - { - int w; - - w = p->info; - IF_DEB( - fprintf(stdout, "top level, before checking embedding for w %d\n", - w); - ) - if ((*embed_graph)[w].adjacent_to == v) - /* - this edge hasn't been embedded: - the graph is non-planar - */ - { - /* - before returning we really want to ensure that - the vertices' adjacency lists are consistent - */ - ASSERT(embedg_VES_are_adj_lists_consistent( - *embed_graph, n)); - - IF_CPU( - fprintf(stdout, "CPU for tester only %f\n", - (time_current_user() - sttime)); - ) - - *vr = v; - *wr = w; - return FALSE; - } - - p = embedg_dlcl_list_next(p); - while (p != be_l) - { - w = p->info; - IF_DEB( - fprintf(stdout, "top level, before checking embedding for w %d\n", - w); - ) - if ((*embed_graph)[w].adjacent_to == v) - { - /* - before returning we really want to ensure that - the vertices' adjacency lists are consistent - */ - ASSERT(embedg_VES_are_adj_lists_consistent( - *embed_graph, n)); - - IF_CPU( - fprintf(stdout, "CPU for tester only %f\n", - (time_current_user() - sttime)); - ) - - *vr = v; - *wr = w; - return FALSE; - } - - p = embedg_dlcl_list_next(p); - } - } - } - IF_DEB_EDGES( - fprintf(stdout, "top level, total number of edges in embedding %d\n", - *edge_pos - 2 * n + 1); - ) - - - /* - before returning we really want to ensure that - the vertices' adjacency lists are consistent - */ - ASSERT(embedg_VES_are_adj_lists_consistent(*embed_graph, n)); - - IF_CPU( - fprintf(stdout, "CPU for tester only %f\n", - (time_current_user() - sttime)); - ) - - return TRUE; -} - - - -/* - * walkup.c - */ - -/* - What: - ***** - - Implementing: - - The walkup routine within the VES structure: - - Walking up from w where [w, v^c] is a (directed) - back edge to be embeeding later. - Along the way collect all the pertinent bicomps that - will need to be merged before embedding the back edges - to v^c. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - -void -embedg_walkup (t_ver_edge *embed_graph, int n, int v, t_dlcl *p) - /* - walkup from w = p->info to v: [w, v] is a back edge where w is a DFS - descendant of v - */ -{ - int w, x, xin, y, yin; - - w = p->info; - - IF_DEB( - fprintf(stdout, "walkup from %d to %d, enter\n", w, v); - ) - - embed_graph[w].adjacent_to = v; - /* - dirty trick to record some information about the BE [w, v] - which will be useful at the time of creation and insertion of - this BE: this happens in the walkdown procedure - - note that what I am doing here is safe: [w].in_adjl, - [w].twin_in_adjl, [w].mult had no use so far since w is a vertex - (and not an edge...) - */ - embed_graph[w].in_adjl = p->in_adjl; - embed_graph[w].twin_in_adjl = p->twin_in_adjl; - embed_graph[w].mult = p->mult; - - /* - set up the traversal contexts for w: one in each direction - */ - x = w; - xin = 1; - y = w; - yin = 0; - - while (x != v) - { - int vz, z, c; - - IF_DEB( - fprintf(stdout, "walkup, x %d and y %d\n", x, y); - ) - - if (embed_graph[x].visited == v - || embed_graph[y].visited == v) - { - IF_DEB( - if (embed_graph[x].visited == v) - fprintf(stdout, "walkup, x visited\n"); - else - fprintf(stdout, "walkup, y visited\n"); - ) - break; - } - - /* - set x and y as visited! - */ - embed_graph[x].visited = embed_graph[y].visited = v; - - vz = embedg_VES_is_virtual_vertex(n, x) ? x : NIL; - vz = embedg_VES_is_virtual_vertex(n, y) ? y : vz; - - if (vz != NIL) - /* - that is, x (or y) is a virtual vertex - -- in other words, we are set to find the root of the bicomp - containing w, or of the bicomp r^c such that w is in the tree - rooted by c - - consequently, by definition, vz is PERTINENT - */ - { - c = vz - n; - z = embed_graph[c].DFS_parent; - - IF_DEB( - fprintf(stdout, "walkup, vz is virtual, %d^%d\n", - z, c); - ) - - if (z != v) - /* - determine if vz externally or internally active - */ - { - if (embed_graph[c].lowpoint < v) - /* - vz is externally active: APPEND to the list - of pertinent bicomps - */ - { - IF_DEB( - fprintf(stdout, "walkup, vz is ext. active\n"); - ) - - embed_graph[z].pertinent_bicomp_list = - embedg_dlcl_rec_append( - embed_graph[z].pertinent_bicomp_list, - embedg_dlcl_rec_new(vz)); - } - else - /* - vz is internally active: PREPEND to the list - of pertinent bicomps - */ - { - IF_DEB( - fprintf(stdout, "walkup, vz is pertinent\n"); - ) - - embed_graph[z].pertinent_bicomp_list = - embedg_dlcl_rec_prepend( - embed_graph[z].pertinent_bicomp_list, - embedg_dlcl_rec_new(vz)); - } - } - - /* - continue the walkup, look if there are any other - pertinent bicomps - -- here "jump" to the next bicomp "up" - */ - x = z; - xin = 1; - y = z; - yin = 0; - } - else - /* - continue the traversal of the bicomp until one finds - its (virtual) root - */ - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - x, xin, FALSE, 0, &x, &xin); - embedg_VES_get_succ_on_ext_face(embed_graph, n, - y, yin, FALSE, 0, &y, &yin); - } - } -} - -/* - * walkdown.c - */ - -/* - What: - ***** - - Implementing: - - The walkdown routine within the VES structure: - - walking down a bicomp rooted by a virtual vertex v^c - and attempting to embed the back edges. - This cannot be done if the walk has to stop due to the - presence of externally active vertices on both - the clockwise and the anticlockwise side of the bicomp. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_EMBED(x) {} -#define IF_DEB_BE(x) {} -#define IF_DEB_SCE(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - - -t_merge_queue -embedg_walkdown (t_ver_edge *embed_graph, int n, int *edge_pos, int vv) - /* - walkdown from the virtual vertex: - embed any back edges incident to vv if any - and merge the encountered bicomps while walking down - (very informative isn't it? :)) - - ... and return the merge queue: will be useful when - isolating the Kuratowski subgraphs - */ -{ - t_merge_queue q; - int v, c, vvout; - - ASSERT(embedg_VES_is_virtual_vertex(n, vv)); - - /* - find v and c such that v^c = vv - */ - c = vv - n; - v = embed_graph[c].DFS_parent; - - IF_DEB( - fprintf(stdout, "walkdown from %d^%d, enter\n", v, c); - ) - - IF_DEB_EMBED( - fprintf(stdout, "walkdown, embedding at start\n"); - embedg_VES_print_bigcomps(embed_graph, n); - ) - - /* - create an empty merge queue - */ - q = embedg_merge_queue_new(n); - - for (vvout = 0; vvout <= 1; vvout++) - /* - chose a direction for the walk, but walk in both - directions unless a stopping vertex is encountered - and other conditions are satisfied (see below) - */ - { - int w, win; - - embedg_VES_get_succ_on_ext_face(embed_graph, n, vv, vvout ^ 1, - FALSE, 0, &w, &win); - - IF_DEB( - fprintf(stdout, "walkdown, successor (outside while loop) from %d^%d:%d is %d:%d\n", - embed_graph[vv-n].DFS_parent, vv-n, vvout ^ 1, - w, win); - ) - - while (w != vv) - /* - is there no danger we walk the whole way back to vv - and that all the vertices along the walk are inactive? - - answer: no, because of the short-cut edges. - - Short-cut edges are precisely inserted to remove the inactive - vertices from the external face (ie they are "pushed" - to the internal face of the bicomp) - */ - { - if (embed_graph[w].adjacent_to == v) - /* - ie there is a (directed) back edge [w, v] - (would have been set in the previous walkup routine): - embed this edge, but before that, merge all the bicomps - previouslsy collected - */ - { - IF_DEB( - fprintf(stdout, "walkdown, embed BE (%d^%d:%d, %d:%d)\n", - embed_graph[vv-n].DFS_parent, vv - n, vvout, - w, win); - fprintf(stdout, "walkdown, queue before pulling elts\n"); - embedg_merge_queue_print(q); - ) - - while (!embedg_merge_queue_empty(q)) - { - int u, uin, vu, vuout; - - embedg_merge_queue_get(&q, &u, &uin, &vu, &vuout); - - IF_DEB( - fprintf(stdout, "walkdown, pull from queue (%d:%d, %d^%d:%d)\n", - u, uin, - embed_graph[vu-n].DFS_parent, vu-n, - vuout); - ) - - embedg_VES_merge_pertinent_bicomps( - embed_graph, n, - vu, vuout, u, uin); - } - IF_DEB_BE( - fprintf(stdout, "walkdown, before embed BE [%d^%d:%d, %d:%d]\n", - embed_graph[vv-n].DFS_parent, vv - n, - vvout, w, win); - embedg_VES_print_adj_list( - embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list( - embed_graph, n, vv, - FALSE); - ) - - embedg_VES_embed_edge(embed_graph, n, edge_pos, - BE, vv, vvout, w, win); - - IF_DEB_BE( - fprintf(stdout, "walkdown, after embed BE [%d^%d:%d, %d:%d]\n", - embed_graph[vv-n].DFS_parent, vv - n, - vvout, w, win); - embedg_VES_print_adj_list( - embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list( - embed_graph, n, vv, - FALSE); - ) - IF_DEB_EMBED( - fprintf(stdout, "walkdown, embedding after bicomp merge & back edge embedding\n"); - embedg_VES_print_bigcomps(embed_graph, n); - ) - - /* - clear the adjacent_to flag - */ - embed_graph[w].adjacent_to = n; /* "invalid" value */ - } - - if (!embedg_dlcl_is_empty(embed_graph[w].pertinent_bicomp_list)) - /* - each pertinent child bicomp of w - (pertinent: contains active (ie more back edges to embed) - elts) - must be traversed - and pushed onto the queue for later bicomp merging - */ - { - int vw, vwout, x, xin, y, yin, s, sin; - - IF_DEB( - fprintf(stdout, "walkdown, pertinent list for %d\n", - w); - embedg_dlcl_print(embed_graph[w].pertinent_bicomp_list); - ) - - /* - get the first child in the pertinent list - (see how the list is built in embedg_walkup) - - the child will eventually be removed from that list - when merging the bicomps, and surely - this bicomp (rooted at vw) will be merged (later) - because it is active and hence pushed on - the merge queue - */ - - /* - we can start by pushing the vertex (w, win) on - the merge queue - */ - embedg_merge_queue_append_vertex(&q, embed_graph, n, w, win); - - IF_DEB( - fprintf(stdout, "walkdown, push 1rst 2-tuple on queue\n"); - embedg_merge_queue_print(q); - ) - - /* - get the first child in the pertinent list - */ - vw = (embed_graph[w].pertinent_bicomp_list)->info; - - IF_DEB( - fprintf(stdout, "walkdown, get pertinent %d^%d\n", - embed_graph[vw - n].DFS_parent, vw - n); - ) - - /* - start two walks starting at vw - */ - embedg_VES_get_succ_active_on_ext_face(embed_graph, n, - v , vw, 1, - FALSE, 0, &x, &xin); - embedg_VES_get_succ_active_on_ext_face(embed_graph, n, - v, vw, 0, - FALSE, 0, &y, &yin); - - /* - because of the trick of inserting short-cut edges - at previous stages, neighbours of vw are guaranteed - to be active - - (however I'll use the more general - embedg_VES_get_succ_active_on_ext_face - instead of the restrictive - embedg_VES_get_succ_on_ext_face - because the walkdown may be used later to isolate - Kuratowski minors, in a situation where SCEs could have - been removed and thus where the successor on the - external face will no longer be guaranteed to be active) - (* actually I have decided to remove the SCE at the - very last moment hence the above pb - does not occur in the present implementation) - - - it only remains to chose the next vertex where from - to continue the walk; the choice is made in that order: - - an internally active vertex - (incident to v via a backedge but whose lowpoint - is NO less than v) - - a (externally active) pertinent vertex - (incident to v via a backedge but whose lowpoint - is less than v: ie which is also externally active) - - as a last resort, a non-pertinent externally vertex, - which is then a stopping vertex - */ - IF_DEB( - fprintf(stdout, "walkdown, x and y: %d, %d\n", x, y); - ) - - if (embedg_VES_is_ver_int_active(embed_graph, n, - v, x)) - /* - x is internally active - */ - { - IF_DEB( - fprintf(stdout, "walkdown, x is int. active\n"); - ) - - s = x; - sin = xin; - } - else if (embedg_VES_is_ver_int_active( - embed_graph, n, - v, y)) - /* - y is internally active - */ - { - IF_DEB( - fprintf(stdout, "walkdown, y is int. active\n"); - ) - - s = y; - sin = yin; - } - else if (embedg_VES_is_ver_pertinent( - embed_graph, n, - v, x)) - /* - x is pertinent - */ - { - IF_DEB( - fprintf(stdout, "walkdown, x is pertinent\n"); - ) - - s = x; - sin = xin; - } - else - /* - tough luck: y may be externally active - */ - { - IF_DEB( - fprintf(stdout, "walkdown, tough luck\n"); - ) - - s = y; - sin = yin; - } - - IF_DEB( - fprintf(stdout, "walkdown, succ. on pertinent bicomp is %d:%d\n", s, sin); - ) - - /* - set vwout to respect consistency of traversal - */ - vwout = s == x ? 0 : 1; - - /* - now that we know vwout we can push (vw, vwout) - on the merge queue, thus completing the 4-tuple - (w, win, vw, vwout) describing a bicomp merge - to occur at a later stage - */ - embedg_merge_queue_append_virtual_vertex(&q, embed_graph, n, - vw, vwout); - - IF_DEB( - fprintf(stdout, "walkdown, push on queue (%d:%d, %d^%d:%d)\n", - w, win, embed_graph[vw-n].DFS_parent, vw - n, - vwout); - embedg_merge_queue_print(q); - ) - - /* - we continue the walk - */ - w = s; - win = sin; - } - /* - at this point, w is either inactive or externally active - (w can't be pertinent: its pertinent bicomp list is empty, - and the back edge [w, v], if any, has already been embedded) - */ - else if (embedg_VES_is_ver_inactive(embed_graph, n, - v, w)) - /* - w is inactive: continue with the walk on the external face - and, insert a short cut edge so that w is removed - from the external face - */ - { - int s, sin; - - IF_DEB( - fprintf(stdout, "walkdown, %d has no pertinent bicomps and is inactive\n", w); - ) - - embedg_VES_get_succ_on_ext_face(embed_graph, n, - w, win, - FALSE, 0, &s, &sin); - - IF_DEB( - fprintf(stdout, "walkdown, successor from %d:%d is %d:%d\n", - w, win, s, sin); - ) - - /* - s is the successor of w: we embed a short circuit edge - [vv, s] if - - the bicomp is externally active (to ensure that - at a later stage this new face gets bisected: - so that we don't end up with a face of degree 2 - (parallel edges)) - - if [s, vv] is not a back edge - - CONSEQUENTLY, adding SCE edges - + does not destroy the planarity of the graph - + ensures that each face has degree > 2 so that - |E| <= 3 * |V| - 6 remains valid at all times - + that the space allocated to the edges in embed_graph - (via MAXDE(n)) is sufficient - - NOTE: - the above still allows to embed a short-cut edge - as an edge parallel to a tree edge OR a back edge - (which then has been embedded previously - so that [w].adjacent has been cleared) - - but again, since the degree of the face will be - > 2, that's ok - - recall that c = vv - n - */ - if (embed_graph[c].lowpoint < v - /* - bicomp rooted at vv is externally active - */ - && embed_graph[s].adjacent_to != v) - /* - [s, vv] is not a back edge - */ - { - IF_DEB_SCE( - fprintf(stdout, "walkdown, before embed SCE [%d^%d:%d, %d:%d]\n", - embed_graph[vv-n].DFS_parent, vv - n, - vvout, s, sin); - embedg_VES_print_adj_list( - embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list( - embed_graph, n, vv, - FALSE); - - ) - - embedg_VES_embed_edge(embed_graph, - n, edge_pos, - SCE, vv, vvout, s, sin); - /* - note also that the addition of short cut edges - does not change the fact that the graph is planar - (when it is, so we never run into the problem - of creating/adding too many edges to embed-graph) - */ - IF_DEB_SCE( - fprintf(stdout, "walkdown, after embed SCE [%d^%d:%d, %d:%d]\n", - embed_graph[vv-n].DFS_parent, vv - n, - vvout, s, sin); - embedg_VES_print_adj_list( - embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list( - embed_graph, n, vv, - FALSE); - - ) - IF_DEB( - fprintf(stdout, "walkdown, embed SCE [%d^%d:%d, %d:%d]\n", - embed_graph[vv-n].DFS_parent, vv - n, - vvout, s, sin); - ) - - } - /* - continue the walk - */ - w = s; - win = sin; - } - else - /* - w is non-pertinent and externally active: - it is a stopping vertex: - we stop here and see if we can walk in the other direction - */ - { - IF_DEB( - fprintf(stdout, "walkdown, %d is externally active\n", w); - ) - break; - } - } - if (!embedg_merge_queue_empty(q)) - /* - mumm.... don't understand this one... let's see: - the queue constains pertinent bicomps collected during one of - the traversal of the external face, so that once - a stopping vertex has been encountered and the queue - is not empty, this means that we will be unable - to embed any remaining back edges: - - it is important to remember that when w is a stopping vertex - there is no choice left, since we walk the pertinent - bicomp in both directions at once, and always choose - the "best" possible vertex - (see the choice strategy: (a) internally active, (b) pertinent, - (c) the rest) - */ - { - IF_DEB( - fprintf(stdout, "walkdown, merge queue is not empty\n"); - ) - break; - } - } - - /* - and return the merge queue - */ - return q; -} - - - - -/* - * merge_queue_misc.c - */ - -/* - What: - ***** - - Implementing: - - The merge queue stores the pertinent bicomps waiting to - be merged before a subsequent back edge embedding. - See walkdown.c - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - -t_merge_queue -embedg_merge_queue_new (int n) - /* - create a merge queue of 4 * (n-1) elts: - we can only have at most n-1 virtual vertices, - and for each of those we need to store 4 bits of info - */ -{ - t_merge_queue q; - - q.start = q.end = 0; - q.b = (int *) mem_malloc(sizeof(int) * 4 * (n - 1)); - - return q; -} - -void -embedg_merge_queue_delete (t_merge_queue q) -{ - mem_free(q.b); -} - - -boolean -embedg_merge_queue_empty (t_merge_queue q) -{ - return q.start == q.end ? TRUE : FALSE; -} - -void -embedg_merge_queue_print (t_merge_queue q) -{ - int i; - - for (i = q.start; i < q.end; i++) - { - fprintf(stdout, "%d:%d ", q.b[i], q.b[i+1]); - ++i; - } - fprintf(stdout, "\n"); -} - -void -embedg_merge_queue_append (t_merge_queue *q, t_ver_edge *embed_graph, - int n, int v, int vin, int vv, int vvout) - /* - append the 4-tuple (v, vin, vv, vvout) - where v is a vertex and vv is its virtual counterpart - - we don't do much here, most of the work is done - when pulling a bicomp/4-tuple from the queue - */ -{ - /* - is this really necessary? - YES!!! - */ - ASSERT((*q).end < 4 * (n - 2)); - ASSERT(embedg_VES_is_vertex(n, v)); - ASSERT(embedg_VES_is_virtual_vertex(n, vv)); - ASSERT(embed_graph[vv - n].DFS_parent == v); - - (*q).b[(*q).end++] = v; - (*q).b[(*q).end++] = vin; - (*q).b[(*q).end++] = vv; - (*q).b[(*q).end++] = vvout; -} - -void -embedg_merge_queue_append_vertex (t_merge_queue *q, t_ver_edge *embed_graph, - int n, int v, int vin) - /* - same as above but were we only append the 2-tuple (v, vin), - appending the 2-tuple (vv, vvout) at a later stage - (see embedg_merge_queue_append_virtual_vertex) - */ -{ - ASSERT((*q).end < 4 * (n - 2)); - ASSERT(embedg_VES_is_vertex(n, v)); - - (*q).b[(*q).end++] = v; - (*q).b[(*q).end++] = vin; - - IF_DEB( - fprintf(stdout, "merge_queue_append_vertex, after, end is %d\n", - (*q).end); - ) -} - -void -embedg_merge_queue_append_virtual_vertex (t_merge_queue *q, - t_ver_edge *embed_graph, int n, int vv, int vvout) - /* - counterpart to embedg_merge_queue_append_vertex: - here we append the 2-tuple (vv, vvout), vv = v^c, - where the 2-tuple (v, vin) is already in the queue - (see embedg_merge_queue_append_vertex) - */ -{ - ASSERT(!embedg_merge_queue_empty(*q)); - ASSERT(embedg_VES_is_virtual_vertex(n, vv)); - ASSERT(embed_graph[vv - n].DFS_parent == (*q).b[(*q).end - 2]); - - (*q).b[(*q).end++] = vv; - (*q).b[(*q).end++] = vvout; - - IF_DEB( - fprintf(stdout, "merge_queue_append_virtual_vertex, after, end is %d\n", - (*q).end); - ) -} - -void -embedg_merge_queue_get (t_merge_queue *q, int *v, int *vin, int *vv, int *vvout) - /* - pulling out a 4-tuple from the beginning of the FIFO queue - */ -{ - ASSERT(!embedg_merge_queue_empty((*q))); - - *v = (*q).b[(*q).start++]; - *vin = (*q).b[(*q).start++]; - *vv = (*q).b[(*q).start++]; - *vvout = (*q).b[(*q).start++]; -} - -void -embedg_merge_queue_prune (t_merge_queue *q, int *v, - int *vin, int *vv, int *vvout) - /* - pulling out a 4-tuple from the end of the FIFO queue - */ -{ - ASSERT(!embedg_merge_queue_empty((*q))); - - *vvout = (*q).b[--((*q).end)]; - *vv = (*q).b[--((*q).end)]; - *vin = (*q).b[--((*q).end)]; - *v = (*q).b[--((*q).end)]; -} - -/* - * vertex_activity.c - */ - -/* - What: - ***** - - Implementing: - - Determining a vertex's activity. This takes place within - the VES structure. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - - - - -boolean -embedg_VES_is_ver_pertinent (t_ver_edge *embed_graph, int n, int v, int w) - /* - is w pertinent (wrt v) - - the field adjacent_to = v: means there is a back edge [w, v] - - or w has a non empty pertinent_bicomp_list - */ -{ - boolean ans; - - ans = embed_graph[w].adjacent_to == v ? TRUE : FALSE; - - if (ans) - return TRUE; - else - return embedg_dlcl_is_empty(embed_graph[w].pertinent_bicomp_list) ? - FALSE : TRUE; -} - -boolean -embedg_VES_is_ver_ext_active (t_ver_edge *embed_graph, int n, int v, int w) - /* - is w externally active (wrt v) - this is the case when either w's least_ancestor < v - or the first member of w's separated_DFS_child_list has lowpoint < v - (the vertices in separated_DFS_child_list are ordered by lowpoint) - - why? because w's separated_DFS_child_list may be empty - (due to prior bicomp merging say) and so its children are in effect - inactive - */ -{ - boolean ans; - - ans = embed_graph[w].least_ancestor < v ? TRUE : FALSE; - - if (ans) - return TRUE; - else - { - if (embedg_dlcl_is_empty(embed_graph[w].separated_DFS_child_list)) - { - return FALSE; - } - else - { - int c; - - c = (embed_graph[w].separated_DFS_child_list)->info; - return embed_graph[c].lowpoint < v ? TRUE : FALSE; - } - } -} - - -boolean -embedg_VES_is_ver_int_active (t_ver_edge *embed_graph, int n, int v, int w) - /* - is w internally active (wrt v): - this happens when w is pertinent but NOT externally active - */ -{ - return embedg_VES_is_ver_pertinent(embed_graph, n, v, w) - && !embedg_VES_is_ver_ext_active(embed_graph, n, v, w); -} - -boolean -embedg_VES_is_ver_inactive (t_ver_edge *embed_graph, int n, int v, int w) - /* - is w inactive (wrt v), that is w nor pertinent nor externally activ - */ -{ - return !embedg_VES_is_ver_pertinent(embed_graph, n, v, w) - && !embedg_VES_is_ver_ext_active(embed_graph, n, v, w); -} - -/* - * merge_bicomps.c - */ - -/* - What: - ***** - - Implementing: - - In the VES structure, merging two bicomponents. - That is, merging the virtual vertex v^c with the - actual vertex v while merging their respective - adjacency lists. - This must be done in a very specific manner so as to able to - determine the subsequent internal/external faces. - Also, great care must be taken so that the resulting - adj. list for v is consistent (wrt to the direction - of traversal). - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_ADJL(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - -void -embedg_VES_merge_simple_bicomps (t_ver_edge *embed_graph, int n, int vv, - int vvout, int v, int vin) - /* - merge the bicomp rooted at vv (vv a virtual vertex) with - its counterpart v so that the resulting adjacency list for v - is consistent and is the union of the adjacency lists for vv and v - - we treat the case that the bicomp may be flipped (vvout == vin) - here - */ -{ - int c, edge, twin, root_edge, cur, prev; - int vout, vvin, e1, e2, e3, e4, e1out, e3out, e4in; - - /* - find c such that [v^c, c] is the root edge of the bicomp - rooted at vv = v^c - */ - c = vv - n; - ASSERT(embed_graph[c].DFS_parent == v); - - IF_DEB( - fprintf(stdout, "merge_simple_bicomp, start: merge\n"); - embedg_VES_print_virtual_vertex(embed_graph, n, vv); - fprintf(stdout, ":%d & ", vvout); - embedg_VES_print_vertex(n, v); - fprintf(stdout, ":%d\n", vin); - ) - - IF_DEB_ADJL( - fprintf(stdout, "merge_simple_bicomp, adj. list for %d (before)\n", vv); - embedg_VES_print_adj_list(embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list(embed_graph, n, vv, - FALSE); - fprintf(stdout, "\n"); - - fprintf(stdout, "merge_simple_bicomp, adj. list for %d (before)\n", v); - embedg_VES_print_adj_list(embed_graph, n, v, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list(embed_graph, n, v, - FALSE); - ) - /* - find all edges incident to vv and (re)set all references - to incidence to vv to incidence to v - - by the same token, find the root_edge [v^c, c] - - MOREVOVER, when vin == vvout, the bicomp (rooted by v^v = vv) - will be flipped: - we must invert the links of all the edges incident - to vv so that their further union with v's adjacency list - results in a consistent adjacency list for v! - - we do everything in one go - */ - - /* - very careful here: a root edge must ALSO be a TE - (because the same edge could have been added as a SCE) - */ - - root_edge = NIL; - edge = embed_graph[vv].link[vvout]; - ASSERT(embedg_VES_is_edge(n, edge)); - if (embed_graph[edge].neighbour == c - && embedg_VES_is_tree_edge(embed_graph, n, edge)) - { - root_edge = edge; - } - - if (vin == vvout) - /* - invert the links - */ - { - int in, out; - - in = embed_graph[edge].link[0]; - out = embed_graph[edge].link[1]; - embed_graph[edge].link[0] = out; - embed_graph[edge].link[1] = in; - } - /* - get the twin and set the neighbour there to v (was vv originally) - */ - twin = embedg_VES_get_twin_edge(embed_graph, n, edge); - ASSERT(embed_graph[twin].neighbour == vv); - embed_graph[twin].neighbour = v; - - prev = vv; - cur = edge; - while (edge != vv) - { - edge = - embedg_VES_get_next_in_dlcl(embed_graph, n, - cur, prev); - - if (embedg_VES_is_edge(n, edge)) - /* - get the twin again (and invert the links if need be) - */ - { - if (embed_graph[edge].neighbour == c - && embedg_VES_is_tree_edge(embed_graph, n, edge)) - { - root_edge = edge; - } - - if (vin == vvout) - { - int in, out; - - in = embed_graph[edge].link[0]; - out = embed_graph[edge].link[1]; - embed_graph[edge].link[0] = out; - embed_graph[edge].link[1] = in; - } - - twin = - embedg_VES_get_twin_edge(embed_graph, n, edge); - ASSERT(embed_graph[twin].neighbour == vv); - embed_graph[twin].neighbour = v; - - prev = cur; - cur = edge; - } - else - { - ASSERT(edge == vv); - /* - only one vertex in the whole circular list - */ - } - } - ASSERT(root_edge != NIL); - - /* - and now union the adjacency lists of v and vv: - - let e1 be the edge record used to enter v - e2 exit v - e3 enter vv - e4 exit vv : - - e1 -> v -> e2 - e3 -> vv -> e4 - - the union of the list is done in such a way that - - e1 and e4 are consecutive in v's adjacency list: - they are now in the internal face - - e3 is now the edge record used to enter v: - it is on the external face (along with e2) : - - e1 -> e4 - e3 -> v -> e2 - - (note that this does not assume that e1 & e2 are distinct - or that e3 & e4 are distinct) - */ - /* - I must not forget the case where v is a lone vertex: - this is the case where v has no DFS ancestor, ie when - v is the root of a tree in the DFS forest - */ - - e1 = embed_graph[v].link[vin]; - vout = 1 ^ vin; - e2 = embed_graph[v].link[vout]; - - if (e1 != v) - { - ASSERT(e2 != v); - ASSERT(embedg_VES_is_edge(n, e1)); - ASSERT(embedg_VES_is_edge(n, e2)); - } - - e4 = embed_graph[vv].link[vvout]; - ASSERT(embedg_VES_is_edge(n, e4)); - - vvin = 1 ^ vvout; - e3 = embed_graph[vv].link[vvin]; - ASSERT(embedg_VES_is_edge(n, e3)); - - /* - must take care of the adjacency list's consistency of traversal - (will be important only when recovering the embedding) - */ - if (e1 == e2) - { - ASSERT(embed_graph[e1].link[0] == embed_graph[e1].link[1]); - if (vin == vvout) - /* - the bicomp will be flipped: - must take 1 ^ vvout - difficult to explain -- later... - */ - { - e1out = 1 ^ vvout; - } - else - { - e1out = vvout; - } - } - else - { - e1out = embed_graph[e1].link[0] == v ? 0 : 1; - } - if (e3 == e4) - { - ASSERT(embed_graph[e3].link[0] == embed_graph[e3].link[1]); - e3out = 1 ^ vin; - e4in = vin; - } - else - { - e4in = embed_graph[e4].link[0] == vv ? 0 : 1; - e3out = embed_graph[e3].link[0] == vv ? 0 : 1; - } - - IF_DEB( - fprintf(stdout, "merge_simple_bicomp, before union of lists, e1\n"); - embedg_VES_print_edge(embed_graph, n, e1); - fprintf(stdout, "merge_simple_bicomp, e3\n"); - embedg_VES_print_edge(embed_graph, n, e3); - fprintf(stdout, "merge_simple_bicomp, e4\n"); - embedg_VES_print_edge(embed_graph, n, e4); - ) - - /* - make e1 and e4 consecutive in the adjacency list - */ - embed_graph[e1].link[e1out] = e4; - embed_graph[e4].link[e4in] = e1; - embed_graph[e3].link[e3out] = v; - embed_graph[v].link[vin] = e3; - - IF_DEB( - fprintf(stdout, "merge_simple_bicomp, after union of lists, e1\n"); - embedg_VES_print_edge(embed_graph, n, e1); - fprintf(stdout, "merge_simple_bicomp, e3\n"); - embedg_VES_print_edge(embed_graph, n, e3); - fprintf(stdout, "merge_simple_bicomp, e4\n"); - embedg_VES_print_edge(embed_graph, n, e4); - ) - - /* - also, want to "disable" vv links, meaning then that - vv is no longer a root of a bicomp - */ - embed_graph[vv].link[0] = embed_graph[vv].link[1] = vv; - - IF_DEB_ADJL( - fprintf(stdout, "merge_simple_bicomp, adj. list for %d (after)\n", vv); - embedg_VES_print_adj_list(embed_graph, n, vv, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list(embed_graph, n, vv, - FALSE); - fprintf(stdout, "\n"); - - fprintf(stdout, "merge_simple_bicomp, adj. list for %d (after)\n", v); - embedg_VES_print_adj_list(embed_graph, n, v, - TRUE); - fprintf(stdout, "\n"); - embedg_VES_print_adj_list(embed_graph, n, v, - FALSE); - ) - - ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, v)); - - /* - finally, give an orientation to the (formerly) root edge [vv, c] - to keep traversal consistent (when recovering embedding) - */ - if (vin == vvout) - /* - flip: set the sign of the root edge to clockwise - - note: a bicomp is merged only once, so there is no need to - "flip" the root_edge's sign: it is set once at initialisation - and then changed here if need be. - */ - { - embed_graph[root_edge].sign = CLOCKW; - - IF_VERB( - fprintf(stdout, "merge_simple_bicomp, flip for %d, sign is now %d for %d of type %d\n", - c, embed_graph[root_edge].sign, root_edge, embed_graph[root_edge].type); - embedg_VES_print_edge(embed_graph, n, root_edge); - ) - } -} - - - -void -embedg_VES_merge_pertinent_bicomps (t_ver_edge *embed_graph, int n, - int vv, int vvout, int v, int vin) - /* - the bicomps to be merged are pertinent: on top (and before) - performing a simple merge, there are several things to do - related to the merging to pertinent bicomps - */ -{ - /* - a note of caution: - it is (very) likely that after a bicomp merge the resulting - bicomp is not biconnected (and hence traversal of the external face - of the bicomp via embedg_VES_get_succ_on_ext_face is non-sensical) - - remembering that a PERTINENT bicomp merge is ALWAYS followed - by a back edge embedding we see that the end result is then a bicomp - where again traversal of the external face - via embedg_VES_get_succ_on_ext_face will make sense - */ - t_dlcl *pertinent_list, *head, *rep_in_parent_list, *parent_list; - int c; - - /* - find c such that [v^c, c] is the root edge of the bicomp - rooted at vv = v^c - */ - c = vv - n; - ASSERT(embed_graph[c].DFS_parent == v); - - /* - two things to do first: - - remove vv from head of pertinent_bicomp_list of v - - remove c from separated_DFS_child_list of v - - one may ask the point of this since the separated_DFS_child_list - seems to mirror pertinent_bicomp_list: but this is not exactly so: - + pertinent_bicomp_list is ordered according to the activity - of the (virtual) vertices - + separated_DFS_child_list is ordered according to the vertices' - lowpoint values - in effect, it could (almost?*) be said that these two lists - are in reverse order (the *almost bit would warrant some thinking here) - */ - - /* - remove vv from head of pertinent_bicomp_list of v - */ - pertinent_list = head = embed_graph[v].pertinent_bicomp_list; - ASSERT(!embedg_dlcl_is_empty(pertinent_list)); - ASSERT(head->info == vv); - - IF_DEB( - fprintf(stdout, "merge_pertinent_bicomp, start: merge\n"); - embedg_VES_print_virtual_vertex(embed_graph, n, vv); - fprintf(stdout, ":%d & ", vvout); - embedg_VES_print_vertex(n, v); - fprintf(stdout, ":%d\n", vin); - ) - - IF_DEB( - fprintf(stdout, "merge_pertinent_bicomp, pertinent bicomp_list of %d (before)\n", v); - embedg_dlcl_print(embed_graph[v].pertinent_bicomp_list); - ) - - - embed_graph[v].pertinent_bicomp_list = - embedg_dlcl_delete_first(pertinent_list); - - IF_DEB( - fprintf(stdout, "merge_pertinent_bicomp, pertinent bicomp_list of %d (after)\n", v); - embedg_dlcl_print(embed_graph[v].pertinent_bicomp_list); - ) - - /* - vv = v^c: remove c from separated_DFS_child_list of v - */ - rep_in_parent_list = embed_graph[c].rep_in_parent_list; - ASSERT(!embedg_dlcl_is_empty(rep_in_parent_list)); - - parent_list = embed_graph[v].separated_DFS_child_list; - ASSERT(!embedg_dlcl_is_empty(parent_list)); - embed_graph[v].separated_DFS_child_list = - embedg_dlcl_delete_rec(parent_list, rep_in_parent_list); - - /* - that's it, it remains to merge, ie. union the adjacency list, - and flipping the bicomp if necessary - */ - embedg_VES_merge_simple_bicomps(embed_graph, n, - vv, vvout, v, vin); -} - - - - - - -/* - * embed_edge.c - */ - -/* - What: - ***** - - Implementing: - - Embedding an edge so that it lies on the external face of a bicomp. - We work here with the VES structure. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_EMBED(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - - -void -embedg_VES_embed_edge (t_ver_edge *embed_graph, int n, int *edge_pos, - int edge_type, int vv, int vvout, int w, int win) - /* - embed the edge (vv, w) (vv a virtual vertex, w a vertex) between - vv and the edge vvout - and the edge win and w - - so that after the embedding, one exits vv via (vv, w) and - enters w via the twin (w, vv) - */ -{ - int temp, tempin, tempout; - - ASSERT(edge_type == BE || edge_type == SCE); - ASSERT(embedg_VES_is_virtual_vertex(n, vv)); - ASSERT(embedg_VES_is_vertex(n, w)); - - IF_DEB( - fprintf(stdout, "embed_edge, (%d:%d)\n", vv, w); - ) - - /* - first, set the edge [vv, w] with the appropriate info - - when [vv, w] is a back edge there is some more work to do - (see the walkup procedure for the extra information we need - to copy here - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = w; - embed_graph[*edge_pos].type = edge_type; - embed_graph[*edge_pos].sign = CCLOCKW; - if (edge_type == BE) - { - ASSERT(embed_graph[w].adjacent_to == - embed_graph[vv - n].DFS_parent); - - /* - PLUS: originally when the back edge [w, vv] was - created (in the dfs preprocessing stage), it carried in - .in_adjl the index of this directed edge in the - adjacency list - - but now, note that we are actually inserting the - directed edge [vv, w] in vv's adjacency list, - meaning that in_adjl and twin_in_adjl - must be exchanged! - */ - embed_graph[*edge_pos].in_adjl = embed_graph[w].twin_in_adjl; - embed_graph[*edge_pos].twin_in_adjl = embed_graph[w].in_adjl; - - ASSERT(embed_graph[w].mult % 2 == 0); - /* - the original graph is always undirected: - we store its number of undirected edges - */ - embed_graph[*edge_pos].mult = embed_graph[w].mult / 2; - } - - /* - insert this edge between vertex record for vv - and edge record vv.link[vvout] - */ - temp = embed_graph[vv].link[vvout]; - - if (embed_graph[temp].link[0] == embed_graph[temp].link[1]) - /* - this needs special treatment to ensure consistency of - orientation - */ - { - ASSERT(embed_graph[temp].link[0] == vv); - tempin = 1 ^ vvout; - } - else - { - tempin = embed_graph[temp].link[0] == vv ? 0 : 1; - } - - IF_DEB( - fprintf(stdout, "embed_edge, edge out of vv\n"); - embedg_VES_print_edge(embed_graph, n, temp); - ) - - embed_graph[vv].link[vvout] = *edge_pos; - embed_graph[temp].link[tempin] = *edge_pos; - /* - the links for *edge_pos must also be "consistent" - */ - embed_graph[*edge_pos].link[vvout] = temp; - embed_graph[*edge_pos].link[vvout ^ 1] = vv; - - /* - now create/set the twin edge, the directed edge [w, vv] - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = vv; - embed_graph[*edge_pos].type = edge_type; - embed_graph[*edge_pos].sign = CCLOCKW; - if (edge_type == BE) - { - embed_graph[*edge_pos].in_adjl = embed_graph[w].in_adjl; - embed_graph[*edge_pos].twin_in_adjl = embed_graph[w].twin_in_adjl; - embed_graph[*edge_pos].mult = embed_graph[w].mult / 2; - } - - /* - and insert the twin edge between edge record w.link[win] - and vertex record for w - */ - temp = embed_graph[w].link[win]; - - if (embed_graph[temp].link[0] == embed_graph[temp].link[1]) - /* - again, special treatment to ensure consistency of orientation - */ - { - ASSERT(embed_graph[temp].link[0] == w); - tempout = 1 ^ win; - } - else - { - tempout = embed_graph[temp].link[0] == w ? 0 : 1; - } - - IF_DEB( - fprintf(stdout, "embed_edge, edge in of w\n"); - embedg_VES_print_edge(embed_graph, n, temp); - ) - - embed_graph[w].link[win] = *edge_pos; - embed_graph[temp].link[tempout] = *edge_pos; - /* - and consistent orientation - */ - embed_graph[*edge_pos].link[win] = temp; - embed_graph[*edge_pos].link[win ^ 1] = w; -} - - - -void -embedg_VES_add_edge (t_ver_edge *embed_graph, int n, int *edge_pos, - int v, int w, boolean MARK, int mark) - /* - add the edge (v, w): this is DIFFERENT from - embedg_VES_embed_edge in the sense - that the present function will only be used - when building the Kuratowski homeomorphs: - - that is, we are in a situation where the graph is NON planar - - consequently it doesn't matter much where in the adjacency - lists of v & w the edge is added: - let's say that we always add it at the beginning - - for our sanity's sake, we'll ensure that the resulting - adjacency lists remain consistent! - - and we add the edge as a BE! - PLUS we mark it with mark in MARK true - */ -{ - int temp; - - ASSERT(embedg_VES_is_vertex(n, v) || - embedg_VES_is_virtual_vertex(n, v)); - ASSERT(embedg_VES_is_vertex(n, w) || - embedg_VES_is_virtual_vertex(n, w)); - - IF_DEB( - fprintf(stdout, "add_edge, (%d:%d)\n", v, w); - ) - - /* - not sure this is the best place to do this: mark the endpoints - */ - if (MARK) - { - embed_graph[v].visited = mark; - embed_graph[w].visited = mark; - } - - /* - first, set the edge [v, w] with the appropriate info - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = w; - embed_graph[*edge_pos].type = BE; - /* - the edge's orientation will be the same as the vertex - */ - embed_graph[*edge_pos].sign = embed_graph[v].sign; - /* - and mark the edge - */ - if (MARK) - { - embed_graph[*edge_pos].visited = mark; - } - - /* - insert this edge between vertex record for v - and edge record v.link[1] - */ - temp = embed_graph[v].link[1]; - - IF_DEB( - fprintf(stdout, "add_edge, edge out of v\n"); - embedg_VES_print_edge(embed_graph, n, temp); - ) - - embed_graph[v].link[1] = *edge_pos; - embed_graph[temp].link[0] = *edge_pos; - /* - the links for *edge_pos must also be "consistent" - */ - embed_graph[*edge_pos].link[1] = temp; - embed_graph[*edge_pos].link[0] = v; - - /* - now create/set the twin edge, the directed edge [w, v] - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = v; - embed_graph[*edge_pos].type = BE; - embed_graph[*edge_pos].sign = embed_graph[w].sign; - if (MARK) - { - embed_graph[*edge_pos].visited = mark; - } - - /* - insert this edge between vertex record for w - and edge record w.link[1] - */ - temp = embed_graph[w].link[1]; - - IF_DEB( - fprintf(stdout, "add_edge, edge out of w\n"); - embedg_VES_print_edge(embed_graph, n, temp); - ) - - embed_graph[w].link[1] = *edge_pos; - embed_graph[temp].link[0] = *edge_pos; - /* - and consistent orientation - */ - embed_graph[*edge_pos].link[1] = temp; - embed_graph[*edge_pos].link[0] = w; -} - - -/* - * recover.c - */ - -/* - What: - ***** - - Implementing: - - From the VES data structure recover either the embedding ot - the obstruction into the - - t_sparseg_ver_struct, - t_sparseg_adjl_struct, - t_sparseg_embed_struct - - data types. - - - (This is no even quite true: for some obscure reason - I recover the obstruction as a dlcl[] structure to be - converted later. - The obvious reason being that it is easier to check as such. - Maybe I leave it as it is...) - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_EMBED_MULT(x) {} -#define IF_DEB_EMBED_LOOPS(x) {} -#define IF_DEB_EMBED(x) {} -#define IF_DEB_CHECK_EMBED(x) {} -#define IF_DEB_FACES(x) {} -#define IF_VERB(x) {} -#define IF_DEB_SCE(x) {} -#define IF_DEB_OBS(x) {} -#define IF_DEB_CHECK_OBS(x) {} -#define IF_CPU(x) {} - - - -/* aproto: header embed_graph_protos.h */ - -/* aproto: beginstatic -- don't touch this!! */ -static void embedg_recover_embedding_embed_mult - (t_dlcl **, t_embed_sparse_rep *, int, int, int, int, int *, boolean *, int *); -static void embedg_recover_embedding_embed_loops - (t_dlcl **, t_embed_sparse_rep *, int, int, int *, boolean *); -static t_dlcl **embedg_get_reduced_obs (t_dlcl **, int); -static boolean embedg_is_red_obs_K33 (t_dlcl **, int); -static boolean embedg_is_red_obs_K5 (t_dlcl **, int); -/* aproto: endstatic -- don't touch this!! */ - - -#ifndef PLANAR_IN_MAGMA -#endif - -void -embedg_recover_embedding ( - t_ver_sparse_rep *V, - t_adjl_sparse_rep *A, /* input (original sparse graph) */ - t_ver_edge *embed_graph, - int n, - int nbr_e, - t_dlcl **mult_edges, - t_ver_sparse_rep **vertices, - t_embed_sparse_rep **embedding -) - /* - recover the embedding - to prepare for the final Magma type for sparse & embedded graph - - we assume that all vertices/edges have been given their - orientation - - at this stage we also embed the multiple edges and loops - which were set aside in mult_edges by - sparseg_adjl_dfs_preprocessing: - - as it turns out the last bit is pretty hairy! - */ -{ - /* - the idea is to return an array of vertices and an array - representing the embedding - (careful: need to weedout the SCE) - - vertices: (*vertices)[i].first_edge contains index - to first edge in embedding - - embedding: a doubly linked circular list of edges, - for each record/edge e = (*embedding)[i]: - e.in_adjl: index in A of e - e.next: next edge in CLOCKW - (as an index in the embedding) - e.prev: previous edge in CLOCKW - (as an index in embedding) - e.inv: inverse edge (as an index in embedding) - e.mark: a mark for this edge - - let's say that this new array is a slimmed down version of embed_graph - - one issue to address: - - for edge e, find its index in A: this should be found - in either the embed_graph[v] record of the mult_edges[v] record - */ - int index_embed, v, mult, w, v_w_in_embed, new_first_edge; - boolean set_next; - - IF_DEB( - fprintf(stdout, "in recover emb.\n"); - sparseg_dlcl_print(mult_edges, n); - ); - - *vertices = (t_ver_sparse_rep *) - mem_malloc(sizeof(t_ver_sparse_rep) * n); - *embedding = (t_embed_sparse_rep *) - mem_malloc(sizeof(t_embed_sparse_rep) * 2 * nbr_e); - - index_embed = 0; - set_next = TRUE; - for (v = 0; v < n; v++) - { - int v_l, orient, in, out, e, cur_e, next_e; - - /* - we take v's label - */ - v_l = embed_graph[v].label; - - /* - first let's deal with the isolated vertex case: those - that refer to self - */ - if (embed_graph[v].link[0] == v) - { - int temp_index_embed; - - ASSERT(embed_graph[v].link[1] == v); - - /* - there may be [v, v] loops for this vertex, must check this - */ - temp_index_embed = index_embed - 1; - /* - temp_index_embed is pre-increased below - */ - embedg_recover_embedding_embed_loops(mult_edges, *embedding, - nbr_e, v, - &temp_index_embed, - &set_next); - - if (temp_index_embed > index_embed - 1) - /* - must fix beginning and end of adjacency list: - */ - { - (*vertices)[v_l].first_edge = index_embed; - (*embedding)[temp_index_embed].next = - (*vertices)[v_l].first_edge; - (*embedding)[(*vertices)[v_l].first_edge].prev = - temp_index_embed; - - index_embed = temp_index_embed; - index_embed += 1; - } - else - { - (*vertices)[v_l].first_edge = NIL; - } - continue; - } - - /* - get v's orientation, and from this decide the way in which - v's adjacency list will be traversed - (recall that the list is supposed to be consistent, so no bad - surprises) - */ - orient = embed_graph[v].sign; - in = orient == CCLOCKW ? 0 : 1; - out = 1 ^ in; - - e = embed_graph[v].link[out]; - while (embedg_VES_is_short_cut_edge(embed_graph, n, e)) - { - e = embed_graph[e].link[out]; - } - ASSERT(embedg_VES_is_edge(n, e) - && !embedg_VES_is_short_cut_edge(embed_graph, n, e)); - /* - strictly speaking there should be no SCEs left at this stage... - - if there are SCEs in v's list, it must be the case that - the list also contains tree or back edges... - */ - - (*vertices)[v_l].first_edge = index_embed; - - IF_DEB_EMBED( - fprintf(stdout, "recov. embed. DFI %d vertex %d at %d (edges) and %d (embedding)\n", - v, v_l, index_e, (*vertices)[v_l].first_edge); - ) - - cur_e = e; - while (TRUE) - { - next_e = embed_graph[cur_e].link[out]; - while (embedg_VES_is_short_cut_edge(embed_graph, n, next_e)) - { - next_e = embed_graph[next_e].link[out]; - } - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, next_e)); - - if (next_e == v) - /* - end of adjacency list - */ - { - break; - } - - ASSERT(embedg_VES_is_edge(n, next_e)); - - (*embedding)[index_embed].in_adjl = embed_graph[cur_e].in_adjl; - (*embedding)[index_embed].next = index_embed + 1; /* next in adj. - list */ - (*embedding)[index_embed].mark = NIL; /* mark */ - - /* - cur_e's twin is trickier: - we'll use twin's label field to store cur_e's index in - the embedding - - if cur_e's label != NIL this means that cur_e's twin - is already stored in edges/embedding and consequently - that cur_e.label = index of its twin (in the embedding) - - note that it is safe to do so since an edge's label - has no meaning - */ - if (embed_graph[cur_e].label != NIL) - { - (*embedding)[index_embed].inv = embed_graph[cur_e].label; - - /* - but fix the twin by the same token - */ - (*embedding)[embed_graph[cur_e].label].inv = index_embed; - ASSERT((*embedding)[embed_graph[cur_e].label].in_adjl == - embed_graph[cur_e].twin_in_adjl); - } - else - /* - we store cur_e's index in the embedding in twin's label - */ - { - int twin; - - twin = embedg_VES_get_twin_edge(embed_graph, n, cur_e); - embed_graph[twin].label = index_embed; - } - - /* - so the only thing we couldn't update yet is - (*embedding)[index_embed].prev, cur_e previous edge in the list - - but we can do this for next_e - */ - (*embedding)[index_embed + 1].prev = index_embed; - - /* - we check if there are any multiple edges or loops - to embed - */ - w = embed_graph[cur_e].neighbour; - mult = embed_graph[cur_e].mult - 1; - /* - one was for the TE or BE edge - */ - - if (index_embed == (*vertices)[v_l].first_edge) - /* - when looking for multiple edges/loops - we must temporarily "close" this ordered - list of vertices when in presence of the first - edge in the list: - - not doing this would mean that - (*embedding)[(*vertices)[v_l].first_edge].prev - contains some irrelevant value which may cause - (major) trouble when embedding inverses of - multiple edges... - */ - { - (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; - } - - embedg_recover_embedding_embed_mult(mult_edges, *embedding, - nbr_e, v, w, mult, - &index_embed, &set_next, - &new_first_edge); - embedg_recover_embedding_embed_loops(mult_edges, *embedding, - nbr_e, v, &index_embed, - &set_next); - set_next = TRUE; - - /* - yes, it may be the case that (*vertices)[v_l].first_edge - change while in embedg_recover_embedding_embed_mult - -- see that function for more - */ - (*vertices)[v_l].first_edge = new_first_edge == NIL ? - (*vertices)[v_l].first_edge : new_first_edge; - - /* - that's all, we proceed to read a new edge in the list - */ - index_embed += 1; - cur_e = next_e; - } - - /* - now next_e = v so that cur_e is the last edge in v's adjacency list - we must deal with this case separately - */ - - /* - fix cur_e in embedding (and its twin) - */ - (*embedding)[index_embed].in_adjl = embed_graph[cur_e].in_adjl; - - /* - we temporarily set next of cur_e in to index_embed + 1 - */ - (*embedding)[index_embed].next = index_embed + 1; - (*embedding)[index_embed].mark = NIL; /* mark */ - - /* - fix cur_e's twin - */ - if (embed_graph[cur_e].label != NIL) - { - (*embedding)[index_embed].inv = embed_graph[cur_e].label; - (*embedding)[embed_graph[cur_e].label].inv = index_embed; - ASSERT((*embedding)[embed_graph[cur_e].label].in_adjl == - embed_graph[cur_e].twin_in_adjl); - } - else - { - int twin; - - twin = embedg_VES_get_twin_edge(embed_graph, n, cur_e); - embed_graph[twin].label = index_embed; - } - - /* - we temporarily set the next record's prev field: - but we can do that only if we haven't processed - all the edges yet - */ - if (index_embed < 2 * nbr_e - 1) - { - (*embedding)[index_embed + 1].prev = index_embed; - - /* - again, check if there are any multiple edges/loops - to embed - */ - w = embed_graph[cur_e].neighbour; - mult = embed_graph[cur_e].mult - 1; - /* - one was for the TE or BE edge - */ - v_w_in_embed = index_embed; - - if (index_embed == (*vertices)[v_l].first_edge) - /* - same comment as above - */ - { - (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; - } - - embedg_recover_embedding_embed_mult(mult_edges, *embedding, - nbr_e, v, w, mult, - &index_embed, &set_next, - &new_first_edge); - embedg_recover_embedding_embed_loops(mult_edges, *embedding, - nbr_e, v, &index_embed, - &set_next); - - /* - same comment as above - */ - (*vertices)[v_l].first_edge = new_first_edge == NIL ? - (*vertices)[v_l].first_edge : new_first_edge; - } - - /* - to finish off, we must set: - - cur_e's next field: - next of cur_e in the list is ... vertices[v_l].first_edge - - cur_e's next's previous field... - */ - if (set_next) - /* - set_next (poorly named) is used to indicate which - edges must be updated to "close off" the list: - - if set_next is TRUE, we are in the standard case - where the last edge in the ordered adj. list - is at index_embed - - if set_next is FALSE, the last edge in the ordered adj. list - is at v_w_in_embed: because it could have happened - (in embedg_recover_embedding_embed_mult only) - that the edges have been "wedged" between - v_w_in_embed.prev and v_w_in_embed, - leaving v_w_in_embed the last in the list - */ - { - (*embedding)[index_embed].next = (*vertices)[v_l].first_edge; - (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; - } - else - { - (*embedding)[v_w_in_embed].next = (*vertices)[v_l].first_edge; - (*embedding)[(*vertices)[v_l].first_edge].prev = v_w_in_embed; - } - set_next = TRUE; - - /* - a simple check - */ - ASSERT(embedg_dlcl_is_empty(mult_edges[v])); - - /* - we can process another vertex - */ - index_embed += 1; - } - /* - when this is done there are a few things that must hold - */ - ASSERT(index_embed == 2 * nbr_e); -} - - -static void -embedg_recover_embedding_embed_mult (t_dlcl **mult_edges, - t_embed_sparse_rep *embedding, int nbr_e, int v, int w, - int mult, int *index_embed, boolean *set_next, int *first_edge) - /* - see if the directed edge [v, w] is multiple: if so embed it - in embedding - - moreover if there are any [v, v] loops do that too - */ -{ - /* - we take care of multiple edges: for tree edges and back - edges their multiplicity is indicated by the - embed_graph[cur_e].mult field (which records the number - of undirected edges) - - for loops hovewer this information is stored in the mult - field of the FIRST encountered neighbour v in v's neighbour - list - */ - t_dlcl *p; - int v_w_in_embed, v_w_prev; - boolean do_twins, start, do_first_edge; - - IF_DEB_EMBED_MULT( - fprintf(stdout, "in recover emb. mult, v %d w %d mult %d\n", - v, w, mult); - ) - - /* - the current index_embed value is the edge [v, w]: - I must record this value as it will be needed - later - */ - v_w_in_embed = *index_embed; - start = TRUE; - *set_next = TRUE; - do_twins = FALSE; - *first_edge = NIL; - do_first_edge = FALSE; - v_w_prev = NIL; - while (mult > 0) - { - ASSERT(!embedg_dlcl_is_empty(mult_edges[v])); - p = embedg_dlcl_find(mult_edges[v], w); - /* - note that using embedg_dlcl_find to always find - the first in the list with p->info == w - is ok here since any previous such records would - have been deleted/removed from the list - */ - ASSERT(p != NP); - /* - otherwise we couldn't have mult > 0 ! - */ - - *index_embed += 1; - - /* - once again I must use a similar sort of trick as in the - main function to deal with the inverse edge: - - the inverse edge is to be found in mult_edges[w]: - if p->twin_in_adjl (which was initialised to NIL - and has NOT been set in the DFS preprocessing), - if p->twin_in_adjl != NIL, then - a. its inverse in mult_edges[w] has already been embedded - in *embedding - b. its index there is stored in p->twin_in_adjl - precisely - */ - if (p->twin_in_adjl != NIL) - { - if (! start) - /* - if the first the multiple edges' inverse is already - stored, then this is true for ALL of them - */ - { - ASSERT(do_twins == TRUE); - } - do_twins = TRUE; - } - else - /* - similarly, if the first the multiple edges' inverse is - not already stored, then this is true for ALL of them - */ - { - ASSERT(do_twins == FALSE); - } - - embedding[*index_embed].in_adjl = p->in_adjl; - embedding[*index_embed].mark = NIL; - - /* - as we will see do_twins has to be treated differently - */ - if (!do_twins) - /* - this is pretty standard as works as the - main recover function - */ - { - t_dlcl *i_m_l, *i_p; - - embedding[*index_embed].next = *index_embed + 1; - - /* - we store the current index in the embedding in - the twin/inverse's twin_in_adjl field - */ - i_p = i_m_l = mult_edges[w]; - ASSERT(!embedg_dlcl_is_empty(i_m_l)); - i_p = embedg_dlcl_find_with_NIL_twin_in_adjl(i_m_l, v); - ASSERT(i_p != NP); - ASSERT(i_p->twin_in_adjl == NIL); - - i_p->twin_in_adjl = *index_embed; - - /* - to finish off this bit we set embedding[*index_embed + 1].prev - - but I can only set this prev field if I haven't reached - the end of the embedding[] array: this is why we needed - nbr_e (total number of edges to embed) as input - */ - - if (*index_embed < 2 * nbr_e - 1) - { - embedding[*index_embed + 1].prev = *index_embed; - } - } - else - /* - how to insert the inverses of multiple edges already - in the embedding: - - if one studies how the twin_in_adjl field has been - set while dealing with the inverses of the - present multiple edges one sees that - the latter must be inserted in counter clockwise - order (assuming that the inverses were inserted - in clockwise order) - - this is necessariy to ensure a correct matching between - the edge and its inverse - */ - { - - embedding[*index_embed].inv = p->twin_in_adjl; - - /* - fix the twin by the same token - */ - embedding[p->twin_in_adjl].inv = *index_embed; - - /* - general (reverse) insertion for these edges - */ - embedding[*index_embed].prev = *index_embed + 1; - embedding[*index_embed].next = *index_embed - 1; - - /* - ok, that was the easy bit, things are a bit more complicated - below... - */ - if (start) - /* - the edges are "wedged" between - embedding[v_w_in_embed].prev and v_w_in_embed, - - hence the following - */ - { - v_w_prev = embedding[v_w_in_embed].prev; - if (v_w_prev == v_w_in_embed) - /* - in this case the first edge in the adj. list - of the vertex whose first_edges is v_w_in_embed - will be changed - (because we insert in reverse order) - */ - { - do_first_edge = TRUE; - } - - embedding[*index_embed].next = v_w_in_embed; - embedding[v_w_in_embed].prev = *index_embed; - - ASSERT(embedding[embedding[*index_embed].inv].prev == - embedding[v_w_in_embed].inv); - ASSERT(embedding[embedding[v_w_in_embed].inv].next == - embedding[*index_embed].inv); - } - - if (mult == 1) - /* - last inv. edge in this list to add - */ - { - ASSERT(v_w_prev != NIL); - - /* - must fix embedding[v_w_prev].next appropriately - (and embedding[*index_embed].prev) - - this may be overwritten later on, but not necessarily so - - the next_set flag will enable us to decide - which edge ends this adjacency list: see above - */ - - embedding[*index_embed].prev = v_w_prev; - embedding[v_w_prev].next = *index_embed; - *set_next = FALSE; - - ASSERT(embedding[embedding[*index_embed].inv].prev == - embedding[*index_embed - 1].inv); - ASSERT(embedding[embedding[*index_embed - 1].inv].next == - embedding[*index_embed].inv); - - if (do_first_edge) - /* - the first edge is the last one added - */ - { - *first_edge = *index_embed; - } - - embedding[v_w_in_embed].next = *index_embed + 1; - if (*index_embed < 2 * nbr_e - 1) - { - embedding[*index_embed + 1].prev = v_w_in_embed; - } - } - - ASSERT(embedding[embedding[*index_embed].inv].prev == - embedding[embedding[*index_embed].next].inv); - } - - /* - to finish off this bit we delete the p record from m_l - and set embedding[*index_embed + 1].prev - */ - mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); - - mult--; - start = FALSE; - } - /* - conclusion: sevral days to get this working! *sigh* - */ -} - - - - -static void -embedg_recover_embedding_embed_loops (t_dlcl **mult_edges, - t_embed_sparse_rep *embedding, int nbr_e, int v, - int *index_embed, boolean *set_next) - /* - embed the [v, v] loops - */ -{ - /* - the loops' multiplicity is stored in the mult - field of the FIRST encountered neighbour v in v's neighbour - list - */ - t_dlcl *p; - int nbr_loops; - - /* - have a look if there are any [v. v] loops - */ - p = embedg_dlcl_find(mult_edges[v], v); - if (p == NP) - { - return; - } - - /* - when there are loops to add to the adjaceny list, - edge insertion resume in the "normal" clockwaise saya, way: - so we reset set_next to true - */ - *set_next = TRUE; - - nbr_loops = p->mult; - ASSERT(nbr_loops % 2 == 0); - /* - we counted directed edges - */ - nbr_loops /= 2; - - IF_DEB_EMBED_LOOPS( - fprintf(stdout, "in recover emb. loops, nbr_loops [v, v] %d\n", - nbr_loops); - ) - - while (nbr_loops > 0) - /* - a loop requires to embed two directed edges - */ - { - p = embedg_dlcl_find(mult_edges[v], v); - ASSERT(p != NP); - - *index_embed += 1; - - embedding[*index_embed].in_adjl = p->in_adjl; - embedding[*index_embed].next = *index_embed + 1; - embedding[*index_embed].mark = NIL; - embedding[*index_embed].inv = *index_embed + 1; - embedding[*index_embed + 1].prev = *index_embed; - - mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); - - IF_DEB_EMBED_LOOPS( - fprintf(stdout, "in recover emb. loops, mid\n"); - embedg_dlcl_print(mult_edges[v]); - ); - - /* - now do the "inverse" loop - */ - p = embedg_dlcl_find(mult_edges[v], v); - ASSERT(p != NP); - - *index_embed += 1; - - embedding[*index_embed].in_adjl = p->in_adjl; - embedding[*index_embed].next = *index_embed + 1; - embedding[*index_embed].mark = NIL; - embedding[*index_embed].inv = *index_embed - 1; - - if (*index_embed < 2 * nbr_e - 1) - { - embedding[*index_embed + 1].prev = *index_embed; - } - mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); - - nbr_loops--; - - IF_DEB_EMBED_LOOPS( - fprintf(stdout, "in recover emb. loops, end\n"); - embedg_dlcl_print(mult_edges[v]); - ); - } -} - - - - -void -embedg_recov_embed_walk_proper_face (int n, int e, t_adjl_sparse_rep *A, - t_embed_sparse_rep *embedding, boolean MARK, int mark) - /* - do a proper face walk in the recovered embedding starting - at index e in the embedding - */ -{ - int cur, next; - - IF_DEB_FACES( - fprintf(stdout, "recov. emb. proper face walk\n"); - fprintf(stdout, "[-, %d] ", - A[embedding[e].in_adjl].end_vertex); - ) - - cur = e; - next = NIL; - while (next != e) - /* - to get the next in a proper face traversal: - get the previous of the cur's inverse - */ - { - int inv; - - inv = embedding[cur].inv; - next = embedding[inv].prev; - - ASSERT(embedding[next].mark != mark); - - if (MARK) - { - embedding[next].mark = mark; - } - - cur = next; - IF_DEB_FACES( - fprintf(stdout, "[-, %d] ", - A[embedding[cur].in_adjl].end_vertex); - ) - } - IF_DEB_FACES( - fprintf(stdout, "\n"); - ) -} - - - -boolean -embedg_check_recov_embedding (int n, int nbr_e, int nbr_comp, - t_ver_sparse_rep *vertices, t_adjl_sparse_rep *A, - t_embed_sparse_rep *embedding) - /* - check if the recovered embedding is a valid embedding - SHOULD ONLY be use after creation, that is, after having - recovered the embedding from the VES structure - (because of the mark MIN_EMBED_MARK we use) - */ -{ - int v, e, f; - - f = 0; - /* - do all the edges in embedding: - careful: we have 2 * nbr_e to visit (the edge and its inverse!) - */ - for (e = 0; e < 2 * nbr_e; e++) - { - /* - we check if the current edge is marked: if not, we - traverse a proper face bordered by this edge - */ - if (embedding[e].mark != MIN_EMBED_MARK) - /* - we --hopefully-- perform this check only after creation - where mark == NIL - */ - { - embedg_recov_embed_walk_proper_face(n, e, A, embedding, - TRUE, MIN_EMBED_MARK); - f++; - } - } - - /* - must also count a face for each isolated vertex - */ - for (v = 0; v < n; v++) - { - if (vertices[v].first_edge == NIL) - f++; - } - - IF_DEB_CHECK_EMBED( - fprintf(stdout, "recovered embedding, n: %d\t e: %d\t C: %d\t f: %d\n", - n, nbr_e, nbr_comp, f); - ) - - return f == 2 * nbr_comp + nbr_e - n ? TRUE : FALSE; -} - - -t_dlcl ** -embedg_recover_obstruction (t_ver_edge *embed_graph, int n, minor m, int *nbr_e) - /* - recover the obstruction as a t_dlcl * structure: - and return the number of edges: lets say we agree on returning - the number of undirected edges - -- I don't know yet which way to do, directed or undirected??? - - so far in the algorithm we only dealt with DFIs, - but now, we retrieve the obstruction not wrt DFIs but - wrt the vertices' labels - */ -{ - /* - so I am looking, in embed_graph, for the vertices and edges - marked MARK_MINORS(n) - */ - - int v; - t_dlcl **obs; - - obs = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - for (v = 0; v < n; v++) - obs[v] = NP; - - *nbr_e = 0; - for (v = 0; v < 2*n; v++) - /* - must check real vertices as well as virtual vertices - */ - { - int e; - - if (embed_graph[v].link[0] == v) - /* - isolated vertex case - */ - { - ASSERT(embed_graph[v].link[1] == v); - continue; - } - - e = embed_graph[v].link[0]; - while (e != v) - { - ASSERT(embedg_VES_is_edge(n, e)); - if (embed_graph[e].visited == MARK_MINORS(n)) - { - int cur_v, neigh; - - /* - virtual vertices may still hang around - */ - /* - let's get the "actual" v: - note that the statement below is safe since if v were - not a valid virtual vertex (ie [v - n].DFS_parent = n) - it would have an empty - adjacency list and we wouldn't be there anyway - */ - cur_v = embedg_VES_get_ver(embed_graph, n, v); - - neigh = embedg_VES_get_ver(embed_graph, n, - embed_graph[e].neighbour); - - /* - again, cur_v and neigh are DFIs, - we want vertex labels at this stage - */ - cur_v = embed_graph[cur_v].label; - neigh = embed_graph[neigh].label; - sparseg_dlcl_append_to_neigh_list(obs, n, cur_v, neigh, - embed_graph[e].in_adjl); - (*nbr_e)++; - } - e = embed_graph[e].link[0]; - } - } - - IF_DEB_OBS( - fprintf(stdout, "recovering the obstruction\n"); - sparseg_dlcl_print(obs, n); - ); - - ASSERT(*nbr_e % 2 == 0); - *nbr_e /= 2; - - return obs; -} - - -static t_dlcl ** -embedg_get_reduced_obs (t_dlcl **obs, int n) - /* - reduce the obstruction by removing all degree 2 vertices - (so that they become isolated vertices) - */ -{ - t_dlcl **reduced; - int v; - - reduced = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - for (v = 0; v < n; v++) - { - reduced[v] = embedg_dlcl_copy(obs[v]); - } - - for (v = 0; v < n; v++) - { - t_dlcl *n_l, *n_l_b, *p, *new_n_v, *n_l_x, *b_in_n_x; - int a, b, n_x; - - n_l = reduced[v]; - while (!embedg_dlcl_is_empty(n_l) - && embedg_dlcl_list_last(n_l) == embedg_dlcl_list_next(n_l)) - /* - pick out which vertices have deg 2 - */ - { - a = n_l->info; - b = embedg_dlcl_list_next(n_l)->info; - /* - we remove the edge (v, b), or rather, we identify v and b: - b will then be an isolated vertex - - fix v's neighbour list: all of b's neighbours - are now v's neighbours - */ - reduced[v] = n_l = - embedg_dlcl_delete_rec(n_l, embedg_dlcl_list_last(n_l)); - - p = n_l_b = reduced[b]; - ASSERT(!embedg_dlcl_is_empty(n_l_b)); - n_x = p->info; - if (n_x != v) - { - new_n_v = embedg_dlcl_rec_new(n_x); - reduced[v] = n_l = embedg_dlcl_cat(n_l, new_n_v); - - /* - and in n_x neighbour list, we must replace b by v - */ - n_l_x = reduced[n_x]; - b_in_n_x = embedg_dlcl_find(n_l_x, b); - b_in_n_x->info = v; - } - /* - and do this for all of b's neighbours - */ - p = embedg_dlcl_list_next(p); - while (p != n_l_b) - { - n_x = p->info; - if (n_x != v) - { - new_n_v = embedg_dlcl_rec_new(n_x); - reduced[v] = n_l = embedg_dlcl_cat(n_l, new_n_v); - n_l_x = reduced[n_x]; - b_in_n_x = embedg_dlcl_find(n_l_x, b); - b_in_n_x->info = v; - } - p = embedg_dlcl_list_next(p); - } - embedg_dlcl_delete(reduced[b]); - reduced[b] = NP; - } - } - - IF_DEB_CHECK_OBS( - fprintf(stdout, "reducing the obstruction\n"); - sparseg_dlcl_print(reduced, n); - ) - - /* - now check no degree 2 vertices are left - */ - for (v = 0; v < n; v++) - { - t_dlcl *n_l; - - n_l = reduced[v]; - if (!embedg_dlcl_is_empty(n_l)) - { - ASSERT(embedg_dlcl_list_last(n_l) != embedg_dlcl_list_next(n_l)); - } - } - - return reduced; -} - -static boolean -embedg_is_red_obs_K33 (t_dlcl **reduced, int n) - /* - check if the (reduced) obstruction is indeed K33 - */ -{ - int v, order, vs[6], i, b1[3]; - - /* - check that order == 6 and that the obstruction is cubic - */ - order = 0; - for (v = 0; v < n; v++) - { - if (!embedg_dlcl_is_empty(reduced[v])) - { - if (order == 6) - { - return FALSE; - } - order++; - vs[order - 1] = v; - - if (embedg_dlcl_length(reduced[v]) != 3) - { - return FALSE; - } - } - } - if (order != 6) - { - return FALSE; - } - - /* - check if bipartite - */ - v = vs[0]; - ASSERT(!embedg_dlcl_is_empty(reduced[v])); - b1[0] = reduced[v]->info; - b1[1] = embedg_dlcl_list_next(reduced[v])->info; - b1[2] = embedg_dlcl_list_prev(reduced[v])->info; - - for (i = 1; i < 6; i++) - { - t_dlcl *n_v; - - v = vs[i]; - n_v = reduced[v]; - ASSERT(!embedg_dlcl_is_empty(n_v)); - if (n_v->info == b1[0] - || embedg_dlcl_list_next(n_v)->info == b1[0] - || embedg_dlcl_list_prev(n_v)->info == b1[0]) - { - if ((n_v->info != b1[1] - && embedg_dlcl_list_next(n_v)->info != b1[1] - && embedg_dlcl_list_prev(n_v)->info != b1[1]) - && - (n_v->info != b1[2] - && embedg_dlcl_list_next(n_v)->info != b1[2] - && embedg_dlcl_list_prev(n_v)->info != b1[2])) - { - return FALSE; - } - } - else - { - if ((n_v->info == b1[1] - || embedg_dlcl_list_next(n_v)->info == b1[1] - || embedg_dlcl_list_prev(n_v)->info == b1[1]) - || - (n_v->info == b1[2] - || embedg_dlcl_list_next(n_v)->info == b1[2] - || embedg_dlcl_list_prev(n_v)->info == b1[2])) - { - return FALSE; - } - } - } - - return TRUE; -} - - -static boolean -embedg_is_red_obs_K5 (t_dlcl **reduced, int n) - /* - check if the (reduced) obstruction is indeed K5 - */ -{ - int v, order; - - /* - check that order == 5 and that the obstruction is quadric - */ - order = 0; - for (v = 0; v < n; v++) - { - if (!embedg_dlcl_is_empty(reduced[v])) - { - if (order == 5) - { - return FALSE; - } - order++; - - if (embedg_dlcl_length(reduced[v]) != 4) - { - return FALSE; - } - } - } - - return TRUE; -} - - -boolean -embedg_check_recov_obs (t_dlcl **obs, int n, minor m) - /* - check if the recovered obstruction is one of K33 or K5 - */ -{ - t_dlcl **reduced; - boolean ans; - - reduced = embedg_get_reduced_obs(obs, n); - if (m != MINOR_E5) - { - ans = embedg_is_red_obs_K33(reduced, n); - } - else - { - ans = embedg_is_red_obs_K5(reduced, n); - } - - sparseg_dlcl_delete(reduced, n); - return ans; -} -/* - * obstruction.c - */ - -/* - What: - ***** - - Implementing: - - The graph is not planar: we recover the obstruction from the VES structure - and check it as well. - (Some of these checks will disappear later) - - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_OBS(x) {} -#define IF_DEB_CHECK_OBS(x) {} -#define IF_CPU(x) {} -#define IF_DEB_MINOR(x) {} - - -/* aproto: header embed_graph_protos.h */ - -void -embedg_obstruction ( - t_ver_sparse_rep *V, - t_adjl_sparse_rep *A, /* the input graph as a sparse graph */ - t_dlcl **dfs_tree, /* a sparse graph rep. for the dfs tree - -- vertices are as DFIs - -- and children are ordered wrt - lowpoint value - */ - t_dlcl **back_edges, /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - (vertices are given as DFIs) - */ - t_ver_edge *embed_graph, /* output of tester */ - int n, /* size of the graph */ - int *edge_pos, /* pos. in embed_graph for addition - of the next edge */ - int v, - int w_in, /* the unembedded directed back edge - [w_in, v] - */ - t_ver_sparse_rep **OV, /* the obstruction as an adjacency list */ - t_adjl_sparse_rep **OA, - int *nbr_e_obs /* obstruction's #edges */ -) - - /* - the graph is non planar: we must mark & recover the K33 or K5 - homeomorph - */ -{ - int *ver_orient; - minor m; - t_dlcl **obs; - - /* - this is magma code - must be removed - */ - float sttime, time_to_now; - - IF_CPU( - sttime = time_current_user(); - ) - - /* - we will NOT remove the short-cut edges at this stage: - we'll have to perform another walkdown in embedg_iso_is_minor_A - so - 1. saves time when looking for ext. active vertices - 2. more importantly this enables us to ascertain that the number of - edges in embed_graph (even after completing whichever obstruction - applying in this case) will NEVER be > 3*n - 5!!! - 3. SCEs are then removed in embedg_iso_is_minor_A - (obligatory path for every possible case) - */ - - /* - we must compute each vertex's orientation (wrt flipped bicomps) - and set the edges' orientation: - - the other day I was wondering why this was necessary in this - instance (because after all we won't get an embedding): - orientation is required bacause later in the piece we - do a proper face traversal (I guess for Minor C testing) - */ - ver_orient = embedg_vertices_orientation(embed_graph, n); - embedg_VES_set_orientation(embed_graph, n, ver_orient); - mem_free(ver_orient); - - m = embedg_mark_obstruction(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w_in); - - /* - get the obstruction - */ - obs = embedg_recover_obstruction(embed_graph, n, m, nbr_e_obs); - - /* - and check it - */ - if (!embedg_check_recov_obs(obs, n, m)) - { - sparseg_dlcl_delete(obs, n); - DIE(); - } - - sparseg_dlcl_to_sparseg(obs, n, *nbr_e_obs, OV, OA); - sparseg_dlcl_delete(obs, n); - - /* - just for the sake of it, chcek if the obstruction is - a subgraph of the input graph - */ - if (!sparseg_adjl_sub(*OV, n, *OA, V, n, A)) - { - DIE(); - } - - IF_DEB_OBS( - sparseg_adjl_print(*V, n, *A, FALSE); - ) - - IF_CPU( - fprintf(stdout, "CPU for obstruction recovering %f\n", - (time_current_user() - sttime)); - ) -} - - - - - - - -minor -embedg_mark_obstruction ( - t_dlcl **dfs_tree, /* a sparse graph rep. for the dfs tree - -- vertices are as DFIs - -- and children are ordered wrt - lowpoint value - */ - t_dlcl **back_edges, /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - (vertices are given as DFIs) - */ - t_ver_edge *embed_graph, /* output of tester */ - int n, /* size of the graph */ - int *edge_pos, /* pos. in embed_graph for addition - of the next edge */ - int v, - int w_in /* the unembedded directed back edge - [w_in, v] - */ -) - /* - the graph is non planar: we must mark & recover the K33 or K5 - homeomorph - */ -{ - int c, vr, x, y, w; - int *path_v, *path_e, nbr_v, entry_in_path_e; - boolean px_attached_high, py_attached_high, is_minor_D; - minor m; - - - IF_CPU( - float sttime; float time_to_now; - - sttime = time_current_user(); - ) - - - /* - find c such that v^c is the root of the biconnected - component on which the walkdown failed - */ - c = embedg_iso_get_c_of_v(embed_graph, n, v, w_in); - - /* - now: decide which minor we are dealing with and mark the - appropriate one (vertices/edges marked as MARK_MINOR(n) - in embed_graph) - */ - if (embedg_iso_is_minor_A(embed_graph, n, edge_pos, v, c, &vr)) - { - embedg_mark_minor_A(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, c, vr); - - IF_DEB_MINOR( - fprintf(stdout, "Minor A\n"); - ) - - return MINOR_A; - } - - /* - get the externally active vertices x & y and the pertinent w - on the external face of the bicomp rooted by v^c - - and determine if minor B - */ - if (embedg_iso_is_minor_B(embed_graph, n, edge_pos, v, c, - &x, &y, &w)) - { - embedg_mark_minor_B(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, c, - x, y, w); - IF_DEB_MINOR( - fprintf(stdout, "Minor B\n"); - ) - - IF_CPU( - fprintf(stdout, "CPU for obstruction isolation %f\n", - time_current_user() - sttime); - ) - - return MINOR_B; - } - - /* - the remaining cases: must get the highest x-y path - - it will be containing in path_v (vertices), path_e (edges) - */ - embedg_iso_get_highest_x_y_path(embed_graph, n, MARK_EXT_FACE(n), - MARK_EXT_FACE_L(n), - MARK_EXT_FACE_R(n), - v, c, x, y, w, - &path_v, &path_e, - &nbr_v, &entry_in_path_e, - &px_attached_high, - &py_attached_high, - &is_minor_D); - - /* - we are in the minor C case if either one of p_x or p_y - is attached high - */ - if (px_attached_high || py_attached_high) - { - embedg_mark_minor_C(dfs_tree, back_edges, embed_graph, n, edge_pos, - v, c, x, y, w, - path_v, path_e, nbr_v, - px_attached_high, py_attached_high); - IF_DEB_MINOR( - fprintf(stdout, "Minor C\n"); - ) - - mem_free(path_v); - mem_free(path_e); - - IF_CPU( - fprintf(stdout, "CPU for obstruction isolation %f\n", - time_current_user() - sttime); - ) - - return MINOR_C; - } - - if (is_minor_D) - { - embedg_mark_minor_D(dfs_tree, back_edges, embed_graph, n, edge_pos, - v, c, x, y, w, - path_v, path_e, nbr_v, entry_in_path_e); - IF_DEB_MINOR( - fprintf(stdout, "Minor D\n"); - ) - - mem_free(path_v); - mem_free(path_e); - - IF_CPU( - fprintf(stdout, "CPU for obstruction isolation %f\n", - time_current_user() - sttime); - ) - - return MINOR_D; - } - - /* - finally, the minor E case - */ - m = embedg_mark_minor_E(dfs_tree, back_edges, embed_graph, n, edge_pos, - v, c, x, y, w, - path_v, path_e, nbr_v); - switch (m) - { - case MINOR_E1: - IF_DEB_MINOR( - fprintf(stdout, "Minor E1\n"); - ) - break; - case MINOR_E2: - IF_DEB_MINOR( - fprintf(stdout, "Minor E2\n"); - ) - break; - case MINOR_E3: - IF_DEB_MINOR( - fprintf(stdout, "Minor E3\n"); - ) - break; - case MINOR_E4: - IF_DEB_MINOR( - fprintf(stdout, "Minor E4\n"); - ) - break; - case MINOR_E5: - IF_DEB_MINOR( - fprintf(stdout, "Minor E5\n"); - ) - break; - case MINOR_A: - case MINOR_B: - case MINOR_C: - case MINOR_D: - case MINOR_E: - case NBR_MINORS: - break; - } - - mem_free(path_v); - mem_free(path_e); - - IF_CPU( - fprintf(stdout, "CPU (scaled) for obstruction isolation %f\n", - (time_current_user() - sttime) / e); - ) - - return m; -} -/* - * isolator.c - */ - -/* - What: - ***** - - Implementing: - - The graph is non planar: we isolate the obstruction. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} -#define IF_DEB_EDGES(x) {} -#define IF_CPU(x) {} -/* #define IF_DEB_MINOR(x) {x} -- Not Used */ - - -/* aproto: header embed_graph_protos.h */ - -#ifndef PLANAR_IN_MAGMA -#endif - - - - -int -embedg_iso_get_c_of_v (t_ver_edge *embed_graph, int n, int v, int w) - /* - the edge [v, w] (w a descendant of v) remains unembedded - after the walkdown returns - - find c such that v^c is the root of the biconnected - component on which the walkdown failed - */ -{ - /* - how to do this??? easy! follow the DFS tree path as given - by the field DFS_parent - */ - - int u; - - u = embed_graph[w].DFS_parent; - while (embed_graph[u].DFS_parent != v) - { - u = embed_graph[u].DFS_parent; - } - /* - this is guaranteed to succeed given the structure of the DFS tree - and the fact that there exists a back edge [w, v] - */ - - return u; -} - - -boolean -embedg_iso_is_minor_A (t_ver_edge *embed_graph, int n, - int *edge_pos, int v, int c, int *vr) - /* - determines if the obstruction is a minor A - */ -{ - /* - to do this we again call the walkdown routine with v^c as input, - the walkdown routine will fail (since there will be an - un-embedded back edge incident to v and to a vertex - in the subtree rooted by v^c) - - the obstruction is a minor A if the merge queue returned by the - walkdown is non-empty, if this is the case we return - the bicomp last appended to the queue - */ - int vv; - t_merge_queue q; - - vv = c + n; - - q = embedg_walkdown(embed_graph, n, edge_pos, vv); - /* - we MUST remove the SCEs here: this is the only place where it - will be done when looking for and recovering an obstruction - - this is safe since this very function applies to ALL cases! - */ - embedg_remove_SCE(embed_graph, n, *edge_pos); - - if (!embedg_merge_queue_empty(q)) - /* - the bicomp of interest is the last in the queue - */ - { - int r, rin, vrout; - - embedg_merge_queue_prune(&q, &r, &rin, vr, &vrout); - embedg_merge_queue_delete(q); - return TRUE; - } - else - { - embedg_merge_queue_delete(q); - return FALSE; - } -} - - -void -embedg_iso_get_x_y_w (t_ver_edge *embed_graph, int n, int v, int r, - int c, int mark, int mark_l, int mark_r, int *x, int *y, int *w) - /* - the obstruction is one of minor B, C, D, E. - - get the externally active vertices x & y along the - external face paths starting at r^c - - get a pertinent vertex w along the lower external - face path between x and y - - external activity and pertinence are wrt v - - all the vertices on the external face r^c...x...w - and r^c...y...w will be marked (the visited field) - */ -{ - int vr, vrin, x_y[4]; - int s, sin, cur, curin; - - vr = c + n; - - /* - find x and y first: - - note that we mark the vertices on the external face r^c...x - and r^c...y - - more on that below - */ - embed_graph[vr].visited = mark; - for (vrin = 0; vrin <= 1; vrin++) - { - int m; - - m = vrin == 0 ? mark_l : mark_r; - embedg_VES_get_succ_ext_active_on_ext_face(embed_graph, n, v, - vr, vrin, - TRUE, m, - &s, &sin); - x_y[vrin] = s; - x_y[vrin + 2] = sin; - /* - note the bizarre way I store the active vertex - and the direction out of which to continue a walk - on the lower external face as described above - */ - } - *x = x_y[0]; - *y = x_y[1]; - - /* - next get the pertinent w on the lower external face from x to y - */ - cur = x_y[0]; - curin = x_y[2]; - embedg_VES_get_succ_pertinent_on_ext_face(embed_graph, n, v, - cur, curin, - TRUE, mark_l, w, &sin); - - /* - now all the vertices r^c...x...w and r^c...y have been marked, - it remains to mark the vertices on the y...w external face path - - (will need to be able to distinguish the external face later on) - - Note the way the external face is marked (needed when recovering - the highest x-y path): - mark_l for the path v^c...x...w - mark_r for the path v^c...y - mark for the lower external face y...w - */ - cur = x_y[1]; - curin = x_y[3]; - s = n; - while (s != *w) - { - embedg_VES_get_succ_pertinent_on_ext_face(embed_graph, n, v, - cur, curin, - TRUE, mark, &s, &sin); - cur = s; - curin = sin; - } - - IF_DEB( - fprintf(stdout, "get x, y & w: the external face\n"); - fprintf(stdout, "%d\t", vr); - cur = vr; - curin = 0; - while (s != vr) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - cur, curin, - FALSE, 0, &s, &sin); - cur = s; - curin = sin; - fprintf(stdout, "%d\t", s); - } - fprintf(stdout, "\n"); - ) -} - - - - -boolean -embedg_iso_is_minor_B (t_ver_edge *embed_graph, int n, int *edge_pos, - int v, int c, int *x, int *y, int *w) - /* - determines if the obstruction is a minor B and return x, y - (ext. active) and w (pertinent) - */ -{ - /* - get x & y the ext. active vertices on the (external face) - path out of v^c, - and w the pertinent vertex on the lower external face x-y - - PLUS mark the whole external face with MARK_EXT_FACE(n) - */ - embedg_iso_get_x_y_w(embed_graph, n, v, v, c, - MARK_EXT_FACE(n), - MARK_EXT_FACE_L(n), MARK_EXT_FACE_R(n), - x, y, w); - - if (embedg_dlcl_is_empty(embed_graph[*w].pertinent_bicomp_list)) - /* - w has no pertinent child bicomp: not a minor B - */ - return FALSE; - else - { - t_dlcl *pert_l; - int l; - - pert_l = embed_graph[*w].pertinent_bicomp_list; - l = embedg_dlcl_list_last(pert_l)->info; - /* - if w has an ext. active pertinent child bicomp then minor B - - note that we need to know if w has an ext. active AND pertinent - bicomp child: so it is NOT good enough to test - w's separated_DFS_child_list as is done in - embedg_VES_is_ver_ext_active!!!!!!!!! - - PLUS: l is actually a VIRTUAL vertex: to check its lowpoint - I must take its DFS child l - n !!!!!!!! - */ - ASSERT(embedg_VES_is_virtual_vertex(n, l)); - l = l - n; - return embed_graph[l].lowpoint < v ? TRUE : FALSE; - } -} - -void -embedg_iso_get_highest_x_y_path ( - t_ver_edge *embed_graph, - int n, - int mark, - int mark_l, - int mark_r, - int v, - int c, - int x, - int y, - int w, - int **path_v, /* stack of vertices in x-y path */ - int **path_e, /* stack of egdes in x-y path */ - int *nbr_v, /* number of vertices in path_v */ - int *entry_in_path_e, /* the in direction for the FIRST edge in - path_e: needed later on *sigh* - */ - boolean *px_attached_high, - boolean *py_attached_high, - boolean *is_minor_D -) - /* - the obstruction is one of minor C, D, E. - - we want to recover the highest x-y path: - the obstructing path attached to the external faces v^c - x - w - and v^c - y - w - - while doing all this we also determine if the case is a minor C - or a minor D - */ -{ - /* - the path is obtained by walking the proper face starting at v - where ALL the edges incident to v^c BUT the ones bordering - the external face have been removed - - I won't I don't think remove these edges, but instead I'll be - implementing an "avoidance" walk - */ - - int vv, s, sin, p_x, p_y, cur_v, cur_vin; - int e, ein, s_e, s_ein; - boolean avoid_vv; - - /* - must start the walk at edge embed_graph[v^c].link[1 ^ 0], - (vvin = 0 is in direction of x, see embedg_iso_get_x_y_w) - */ - vv = c + n; - e = embed_graph[vv].link[1]; - ein = 0; /* because of adjacency list consistency */ - - *path_v = (int *) mem_malloc(sizeof(int) * n); - *path_e = (int *) mem_malloc(sizeof(int) * n); - (*nbr_v) = -1; - - /* - recall that in embedg_iso_get_x_y_w we did mark - (with mark, mark_l, mark_r) - ALL the vertices lying on the external face walk starting - & ending at v^c: we will use this fact to enable us - to decide if a vertex is on the external face - (as opposed to being on the internal face) - */ - - s = embed_graph[e].neighbour; - ASSERT(embed_graph[s].visited == mark_l); - /* - this must be the case since s lies on the external face - starting at v^c in x's direction - -- we push s onto the stack - */ - (*path_v)[++(*nbr_v)] = s; - - /* - start the proper face walk which "avoids" v^c since the - internal edges incident to v^c are supposed to have - been removed - - please read on - */ - avoid_vv = FALSE; - while (TRUE) - { - boolean av; - - av = - embedg_VES_get_succ_on_proper_face_with_avoidance( - embed_graph, n, - e, ein, vv, - FALSE, 0, - &s, &s_e, &s_ein); - avoid_vv = av == TRUE ? av : avoid_vv; - if (embed_graph[s].visited == mark_l) - /* - means that s is still on the external face: - empty the path's stack and push s - */ - { - (*nbr_v) = -1; - (*path_v)[++(*nbr_v)] = s; - e = s_e; - ein = s_ein; - } - else if (*nbr_v == 0) - /* - s is the first encountered vertex after - path_v[0] which does not - lie on the external face v^c...c...w - - given the way we pushed things on the vertex stack, path_v[0] - will be the point of attachement of the x-y path - on the v^c...x...w external face - - path_e[0] will contain nothing: a dummy - - path_e[1] will be the first edge in the x-y path - (and entry_in_path will give the in-direction to this edge) - - oh yes!, we break the loop at this point if - the vertex s lies on the v^c...y...w external face - */ - { - ASSERT(embed_graph[(*path_v)[0]].visited == mark_l); - /* - the first vertex on the path must be on the - v^c...x...w external face - */ - (*path_v)[++(*nbr_v)] = s; - /* - and now we also push the edge on the edge stack - - I'll need this later to initiate a proper face walk - starting at the first vertex/edge in the x-y path, - which is the same as starting from s_e - */ - (*path_e)[*nbr_v] = s_e; - *entry_in_path_e = s_ein; - e = s_e; - ein = s_ein; - - /* - since we are at the start of the path, we must not - forget to reset avoid_vv - */ - avoid_vv = FALSE; - - if (embed_graph[s].visited == mark_r - || embed_graph[s].visited == mark) - /* - we have reached the v^c...y...w external face: - we can stop here - */ - { - break; - } - - /* - if not finished yet, - we also mark s (and path_v[0]) as visited: - later on we'll need to recognise which of the vertices - in path have already been encountered - (in case of encountering a cut-vertex due to the - "removal" of the "internal" edges incidnet ot v^c) - - note that we mark s as visited iff s if not already - on the v^c..y..w external face - */ - - ASSERT(embedg_VES_is_vertex(n, (*path_v)[0])); - ASSERT(embedg_VES_is_vertex(n, s)); - - embed_graph[s].visited = MARK_X_Y_PATH(n); - } - else if (embed_graph[s].visited == MARK_X_Y_PATH(n)) - /* - this means that s is a cut vertex on the internal - face walk: pop all the vertices from path - until s's last occurrence in path - */ - { - ASSERT((*nbr_v) >= 0); - while ((*path_v)[(*nbr_v)] != s) - { - (*nbr_v)--; - ASSERT((*nbr_v) >= 0); - /* - note that s should be somewhere in path! - */ - } - /* - note also that popping from path_v also implies - popping from path_e - */ - e = s_e; - ein = s_ein; - } - else - /* - we push s and s_e on their respective stacks - */ - { - (*path_v)[++(*nbr_v)] = s; - (*path_e)[*nbr_v] = s_e; - e = s_e; - ein = s_ein; - - if (embed_graph[s].visited == mark_r - || embed_graph[s].visited == mark) - /* - again, s lies on the v^c...y...w external face: - we end the walk: path_v now contains the highest x-y path - - note that there can be no conflict between - mark_r or mark and MARK_X_Y_PATH(n) since - we mark with MARK_X_Y_PATH iff the vertex - is NOT marked with mark_r/mark! - */ - { - break; - } - else - /* - we must mark this vertex as MARK_X_Y_PATH since we aren't - finished yet - */ - { - embed_graph[s].visited = MARK_X_Y_PATH(n); - } - } - } - - /* - there is only one thing remaining to do: see if p_x or - p_y are attached high - (ie closer to v^c than x or y resp.) - - we walk the external face starting at v^c in y's direction - (again see embedg_iso_get_x_y_w) - */ - *px_attached_high = TRUE; - p_x = (*path_v)[0]; - /* - p_y denotes the attachement point of the x-y path - on the v^c...y...w external face - */ - - s = n; - cur_v = vv; - cur_vin = 0; - while (s != p_x) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - cur_v, cur_vin, - FALSE, 0, &s, &sin); - if (s == x) - { - *px_attached_high = FALSE; - break; - } - cur_v = s; - cur_vin = sin; - } - - *py_attached_high = TRUE; - p_y = (*path_v)[*nbr_v]; - /* - p_y denotes the attachement point of the x-y path - on the v^c...y...w external face - */ - - s = n; - cur_v = vv; - cur_vin = 1; - while (s != p_y) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - cur_v, cur_vin, - FALSE, 0, &s, &sin); - if (s == y) - { - *py_attached_high = FALSE; - break; - } - cur_v = s; - cur_vin = sin; - } - - /* - now we are in the minor C case if either p_x or p_y are - attached high - - the minor D case: - this happens when there is a path v^c - z where z lies - on the x-y path - - that is, when - - either v^c has been effectively "avoided" within the - embedg_VES_get_succ_on_proper_face_with_avoidance function - BUT ONLY if this "avoidance" happened AFTER having - encountered the very first vertex on the x-y path! - - or when a cut vertex has been encountered on the x-y path: - separable components on this walk can only occur - if one walks the face while skipping the edges incident to v^c - - in any case this means that checking the return from - the embedg_VES_get_succ_on_proper_face_with_avoidance function - is enough: this is the purpose of avoid_vv. - */ - - *is_minor_D = !(*px_attached_high || *py_attached_high) && avoid_vv; - - - IF_DEB( - int i; - - fprintf(stdout, "x-y path\t"); - for (i = 0; i <= *nbr_v; i++) - fprintf(stdout, "%d\t", (*path_v)[i]); - fprintf(stdout, "\n"); - ) -} - - -/* - * embedg_misc.c - */ - -/* - What: - ***** - - Implementing: - - Some high level routinse for the VES structure. - See VES_misc.c. - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} - - - -/* aproto: header embed_graph_protos.h */ - -#ifndef PLANAR_IN_MAGMA -#endif - - - - -void -embedg_VES_delete (t_ver_edge *embed_graph, int n) -{ - int i; - - for (i = 0; i < n; i++) - { - embedg_dlcl_delete(embed_graph[i].separated_DFS_child_list); - /* - embedg_dlcl_delete(embed_graph[i].rep_in_parent_list); - - NO!!! this points to something in separated_DFS_child_list - */ - embedg_dlcl_delete(embed_graph[i].pertinent_bicomp_list); - } - mem_free(embed_graph); -} - - - -void -embedg_VES_print (t_ver_edge *embed_graph, int n) -{ - int i; - - fprintf(stdout, "vertices\n"); - for (i = 0; i < n; i++) - { - t_ver_edge rec; - - rec = embed_graph[i]; - - fprintf(stdout, "\nDFI\t%d\tlabel\t%d\n", i, rec.label); - fprintf(stdout, "DFS parent\t%d\tleast_a\t%d\tlowpoint\t%d\n", - rec.DFS_parent, rec.least_ancestor, rec.lowpoint); - fprintf(stdout, "separated_DFS_child_list\n"); - embedg_dlcl_print(rec.separated_DFS_child_list); - } - - fprintf(stdout, "\nvirtual vertices\n"); - for (i = n; i < 2*n; i++) - { - int c; - - c = i - n; - fprintf(stdout, "%d^%d\t", embed_graph[c].DFS_parent, c); - } - fprintf(stdout, "\n"); - - embedg_VES_print_bigcomps(embed_graph, n); -} - - -void -embedg_VES_print_bigcomps (t_ver_edge *embed_graph, int n) - /* - walking the external faces of all the bicomp; for testing only - */ -{ - int i; - - fprintf(stdout, "bicomponents\n"); - /* - to get to the bicomps, it makes sense to start at the - virtual vertices???? - */ - for (i = n + 1; i < 2*n; i++) - /* - a note of caution: there is no virtual vertex at - embed_graph[n] since that would mean a virtual vertex x^0 - which makes no sense (0 is the root of the dfs_tree) - */ - { - embedg_VES_walk_bicomp(embed_graph, n, i, 0); - } - fprintf(stdout, "\n"); -} -/* - * planar_alg_init.c - */ - -/* - What: - ***** - - Implementing: - - Initialising the embed_graph aka VES data structure from the information - collected from the DFS. - - The embed_graph/VES data structure is an array consisting of vertices, - virtual vertices and edges; - vertices, virtual vertices and edges share a common record structure; - one of the particular features is that any vertex is linked - together with its incident edges into a doubly circular linked list. - - See also VES_misc.c. - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_DFS(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} -#define IF_CPU(x) {} - - -/* aproto: header embed_graph_protos.h */ - -/* aproto: beginstatic -- don't touch this!! */ -static void embedg_init_insert_TE (t_ver_edge *, int, int *, t_dlcl *); -/* aproto: endstatic -- don't touch this!! */ - -#ifndef PLANAR_IN_MAGMA -#endif - - -t_ver_edge * -embedg_planar_alg_init ( - t_ver_sparse_rep *V, - int n, - t_adjl_sparse_rep *A, /* input sparse graph */ - int *nbr_c, /* size of the graph, #components*/ - int *edge_pos, /* pos in the struct where the last edge - has been inserted - */ - t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree - -- vertices are as DFIs - */ - t_dlcl ***back_edges, /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - -- vertices are as DFIs - */ - t_dlcl ***mult_edges /* for each vertex v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v - -- vertices are as DFIs - */ -) - /* - initialising embed_graph, the fundamental data structure - underpinning the tester and obstruction isolator - - from there on, a vertex is exclusively referred to by its DFI!! - -- so forget about labels - */ -{ - int *dfs_nbr; /* dfs numbering for each vertex */ - int *dfs_order; /* vertices in dfs order */ - int *lowpoint; /* lowpoint value for each DFI */ - int *dfs_parent; /* for each DFI, its DFS ancestor - as a DFI (DFS index) - */ - int *least_a; /* for each DFI, its least ancestor's DFI - (via a back edge exclusively) - */ - - t_ver_edge *embed_graph; - int i; - - - IF_CPU( - float sttime; float time_to_now; - - sttime = time_current_user(); - ) - - ASSERT(n >= 1); - - /* - DFS and lowpoint calculations + ordering - */ - sparseg_adjl_dfs_preprocessing(V, n, A, nbr_c, - &dfs_nbr, &dfs_order, &lowpoint, - dfs_tree, back_edges, - &dfs_parent, &least_a, mult_edges); - - IF_CPU( - fprintf(stdout, "CPU for DFS only %f\n", - (time_current_user() - sttime)); - sttime = time_current_user(); - ) - - IF_DEB_DFS( - fprintf(stdout, "DFS indices\n"); - for (i = 0; i < n; i++) - fprintf(stdout, "%d ", dfs_nbr[i]); - fprintf(stdout, "\n"); - - fprintf(stdout, "DFS order\n"); - for (i = 0; i < n; i++) - fprintf(stdout, "%d ", dfs_order[i]); - fprintf(stdout, "\n"); - - fprintf(stdout, "lowpoint values\n"); - for (i = 0; i < n; i++) - fprintf(stdout, "%d ", lowpoint[i]); - fprintf(stdout, "\n"); - ); - - IF_VERB( - fprintf(stdout, "DFS parent\n"); - for (i = 0; i < n; i++) - fprintf(stdout, "%d ", dfs_parent[i]); - fprintf(stdout, "\n"); - ); - - IF_VERB( - fprintf(stdout, "least ancestors\n"); - for (i = 0; i < n; i++) - fprintf(stdout, "%d ", least_a[i]); - fprintf(stdout, "\n"); - ); - - IF_VERB( - for (i = 0; i < n; i++) - { - fprintf(stdout, "the list of children ordered by lowpoint for %d\n", - i); - embedg_dlcl_print((*dfs_tree)[i]); - } - ); - - IF_DEB_DFS( - fprintf(stdout, "the tree edges\n"); - sparseg_dlcl_print(*dfs_tree, n); - - fprintf(stdout, "the back edges\n"); - sparseg_dlcl_print(*back_edges, n); - - fprintf(stdout, "multiple edges\n"); - sparseg_dlcl_print(*mult_edges, n); - ); - - /* - create the data structure for the embedded graph: - it will have (max) size 2*n + 2 * MAXE(n) - - we will see that that number of edges is sufficient - even when later adding short-cut edges (see embedg_walkdown) - */ - embed_graph = (t_ver_edge *) mem_malloc(sizeof(t_ver_edge) - * (2*n + 2 * MAXE(n))); - /* - initialisation - */ - for (i = 0; i < 2*n + 2 * MAXE(n); i++) - /* - some fields are initialised to n as n is actually - an "invalid" value - */ - { - t_ver_edge rec; - - rec.label = NIL; - rec.DFS_parent = n; - rec.least_ancestor = n; - rec.lowpoint = n; - rec.separated_DFS_child_list = NP; - rec.rep_in_parent_list = NP; - rec.pertinent_bicomp_list = NP; - rec.adjacent_to = n; - rec.visited = n; - rec.neighbour = n; - rec.in_adjl = NIL; - rec.twin_in_adjl = NIL; - rec.mult = 0; - rec.type = NIL; - rec.sign = NILSIGN; - /* - make the links refer back to self - */ - rec.link[0] = rec.link[1] = i; - - embed_graph[i] = rec; - } - - /* - embed_graph[0..n-1]: the n vertices - ATTENTION: the vertices are stored according to their DFS numbering - */ - for (i = 0; i < n; i++) - { - t_ver_edge rec; - - rec = embed_graph[i]; - - rec.label = dfs_order[i]; - rec.DFS_parent = dfs_parent[i]; - rec.least_ancestor = least_a[i]; - rec.lowpoint = lowpoint[i]; - rec.separated_DFS_child_list = embedg_dlcl_copy((*dfs_tree)[i]); - - IF_VERB( - fprintf(stdout, "the list of children ordered by lowpoint for DFI %d\n", - i); - embedg_dlcl_print(rec.separated_DFS_child_list); - ); - - embed_graph[i] = rec; - } - - /* - one more thing to do for these vertices: - fix the rep_in_parent_list field - */ - for (i = 1; i < n; i++) - { - t_dlcl *parent_list, *rep; - int parent; - - parent = embed_graph[i].DFS_parent; /* careful: this is a DFI */ - /* - recall that the vertices in embed_graph are accessed via their DFI - */ - - if (parent != n) - /* - when parent == n this means that i the root of a DFS tree - in the disconnected graph - */ - { - parent_list = embed_graph[parent].separated_DFS_child_list; - rep = embedg_dlcl_find(parent_list, i); - ASSERT(rep != NP); - embed_graph[i].rep_in_parent_list = rep; - } - } - - /* - embed_graph[n..2*n-1]: the n virtual vertices - do I need to do anything here????? - - no - I don't think so - - let's try to explain what virtual vertices are: - let v^c be a virtual vertex: - - it is at position c + n in the array, - - c is the DFS child of v, - - v can be retrieved by taking embed_graph[c].DFS_parent, - - v^c is said virtual as long as the bicomp rooted by v^c is not - merged with the vertex v - - once v is merged (identified?) with v^c, then v^c - is of no relevance anymore - - below we will see that we embed all the tree edges as singleton - bicomps (bicomponent): (0^1, 1), (1^2, 2) etc...: - this is what virtual vertices are there for: - to distinguish them from their "real" counterpart with - which they will be ultimately merged - - the primary reason for this is: - while testing for planarity virtual vertices are the roots of bicomps - */ - - /* - now the edges: - we actually embed the tree edges so that each tree edge - forms a (singleton) biconnected component - - embedding an edge in effect means creating the - doubly linked circular list of [virtual] vertices & the edges incident - to it - - this list is built using the links 0 & 1 in embed_graph[i] - */ - - /* - for each tree edge (v,u) we embed (v^u, u) (v^u is the virtual vertex) - - CAREFUL: when talking about vertex v say, - we mean the vertex with DFI v, and NOT the vertex with label v - ************************************************************** - */ - *edge_pos = 2*n - 1; - /* - edge_pos will tell us where to insert the next edge in embed_graph[] - */ - for (i = 0; i < n; i++) - { - t_dlcl *te_l, *p; - - te_l = (*dfs_tree)[i]; - p = te_l; - - if (!embedg_dlcl_is_empty(p)) - { - /* - the test below is a bit stupid... well... - */ - ASSERT(embed_graph[p->info].DFS_parent == i); - - embedg_init_insert_TE(embed_graph, n, edge_pos, p); - p = embedg_dlcl_list_next(p); - while (p != te_l) - { - ASSERT(embed_graph[p->info].DFS_parent == i); - embedg_init_insert_TE(embed_graph, n, edge_pos, p); - - p = embedg_dlcl_list_next(p); - } - } - } - - mem_free(dfs_nbr); - mem_free(dfs_order); - mem_free(lowpoint); - - mem_free(dfs_parent); - mem_free(least_a); - - IF_CPU( - fprintf(stdout, "CPU for remainder of initialisation %f\n", - (time_current_user() - sttime)); - ) - - return embed_graph; -} - - -static void -embedg_init_insert_TE (t_ver_edge *embed_graph, int n, int *edge_pos, t_dlcl *p) - /* - init and insert a tree edge in embed graph: - - the tree edge will form a singleton bicomponent (v^c, c) - where c is p->info and v is c.DFS_parent - */ -{ - int c, v; - - c = p->info; - v = embed_graph[c].DFS_parent; - ASSERT(v >= 0 && v < n); - - /* - now (v, c) is a tree edge; embed the directed edge [v^c, c] - - -- and recall that v^c is a virtual vertex, at position c + n - in embed_graph, and that vertex c is at position c - */ - - /* - first, set this edge with the appropriate info - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = c; - embed_graph[*edge_pos].in_adjl = p->in_adjl; - embed_graph[*edge_pos].twin_in_adjl = p->twin_in_adjl; - - ASSERT(p->mult % 2 == 0); - /* - we want the number of undirected edges - */ - embed_graph[*edge_pos].mult = p->mult / 2; - embed_graph[*edge_pos].type = TE; - embed_graph[*edge_pos].sign = CCLOCKW; - - /* - link this with vertex v^c in a doubly linked circular list - */ - embed_graph[c + n].link[0] = - embed_graph[c + n].link[1] = *edge_pos; - embed_graph[*edge_pos].link[0] = - embed_graph[*edge_pos].link[1] = c + n; - - /* - now create/set the twin edge, the directed edge [c, v^c] - */ - (*edge_pos)++; - ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); - embed_graph[*edge_pos].neighbour = c + n; - embed_graph[*edge_pos].in_adjl = p->twin_in_adjl; - embed_graph[*edge_pos].twin_in_adjl = p->in_adjl; - embed_graph[*edge_pos].mult = p->mult / 2; - embed_graph[*edge_pos].type = TE; - embed_graph[*edge_pos].sign = CCLOCKW; - - /* - and link it with vertex c in a doubly linked circular list - */ - embed_graph[c].link[0] = embed_graph[c].link[1] = *edge_pos; - embed_graph[*edge_pos].link[0] = - embed_graph[*edge_pos].link[1] = c; -} -/* - * dfs_preprocessing.c - */ - -/* - What: - ***** - - Implementing: - - A DFS as an initialisation step for the planarity tester. - This is an especially beefed up DFS that collects lots of - marginal information: - - - a DFS tree as a list of DFS children for each vertex - - the DFS children are sorted according to their lowpoint value - - a back_edge structure as a list of descendants v for each - vertex u such that [v, u] is a back edge - - a multiple edges structure which stores multiple (directed) edges - NOT in the DFS tree nor in the back_edge struc, and loops - - - the vertices in DFS order - - the DFS index (DFI) for each vertex - - the lowpoint value for each vertex - - the number of components of the (possibly disconnected) graph - - for each vertex, its DFS parent - - for each vertex v, its least ancestor u such that [v, u] - is a back edge - - ALL info above (except the vertices in DFS order) is given - in terms of the vertices' DFIs and NOT their labels. - - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -/* - There are some dodgy things which need some thought; it would be nice - to fix them so that the code get's cleaner: - - - we do store in_adj (and twin_in_adjl) for each directed edge: - - + this is ONLY needed at the time of recovering the embedding - (see embedg_recover_embedding) and its sole use is to establish - a link between an edge in the embedding structure - t_embed_sparse_rep *E and the corresponding edge - in the t_adjl_sparse_rep *A struc. - - + well, I cannot recall why I thought this correspondence - was needed in the first place and it might well be the case - that there is no use for it; in which case recovering the - embedding is simplified - (we would store the end-vertex in the embedding's edges instead - of their index in the adjacency list) - - - there are some non-linear bits in the DFS below: when searching - for an already existing tree/back/multiple edge. - I couldn't fix this in less then one hour so I leave it as it is... - for now. - - This shouldn't be a major issue, overall timings of the planarity - tester do not show this non-linear "bump"... - - - also, this algorithm has been growing incrementally and I now - realise that I am using some redundant data structures: - for example visited[] and the vertex and could be dispensed with... - ...more things to clean up... - - Paulette 07/02/02 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} - - -/* aproto: header embed_graph_protos.h */ - -#ifndef PLANAR_IN_MAGMA -#endif - - -void -sparseg_adjl_dfs_preprocessing ( - t_ver_sparse_rep *V, - int n, /* size of the graph */ - t_adjl_sparse_rep *A, /* input sparse graph */ - int *c, /* nbr of components */ - int **dfs_nbr, /* dfs numbering for each vertex */ - int **dfs_order, /* vertices in dfs order */ - int **lowpoint, /* lowpoint value for each DFI */ - t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree: - for each DFI, a list of its children's - DFI ordered wrt their lowpoint values - */ - t_dlcl ***back_edges, /* for each DFI v, a dlcl - of the back edges [v, x] incident to v - where x is a DESCENDANT of v */ - int **dfs_parent, /* for each DFI its DFS ancestor */ - int **least_a, /* for each DFI, its least ancestor's DFI - via a back edge exclusively */ - t_dlcl ***mult_edges /* for each DFI v, a dlcl - of the multiple directed - edges NOT included - in either dfs_tree or back_edges - */ -) - - /* - in ALL the returned info above BUT dfs_order[] we store - the vertices' DFIs (DFS indices) and NOT their labels! - - -- shuffling between labels and vertices can then be done - via dfs_nbr[] and dfs_order[] - */ -{ - int pos_v_stack, pos_e_stack, dfs_n; - int *visited, *vertex_stack, *edge_stack, *lowpoint_order; - int *TE_in_adjl, *TE_twin_in_adjl, *TE_mult; - int v, lp, cur, cur_e, next; - t_dlcl **temp, *lowpoint_list, **new_dfs_tree; - - /* - create the dfs tree as a sparse graph - */ - *dfs_tree = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - /* - the DFS numbering for the vertices - */ - *dfs_nbr = (int *) mem_malloc(sizeof(int) * n); - /* - the vertices as ordered by their DFS index - */ - *dfs_order = (int *) mem_malloc(sizeof(int) * n); - /* - the lowpoint value for each DFI - */ - *lowpoint = (int *) mem_malloc(sizeof(int) * n); - - /* - the (directed) back edges - */ - *back_edges = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - - - /* - the DFS parent for each DFI - */ - *dfs_parent = (int *) mem_malloc(sizeof(int) * n); - /* - the least ancestor (via a back edge exlusively) for each DFI - */ - *least_a = (int *) mem_malloc(sizeof(int) * n); - - /* - the (directed) multiple edges - */ - *mult_edges = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - - /* - the vertices visited while DFS - */ - visited = (int *) mem_malloc(sizeof(int) * n); - /* - stack of vertices: last current vertex - */ - vertex_stack = (int *) mem_malloc(sizeof(int) * n); - /* - stack of (tree) edges: last added tree edge - */ - edge_stack = (int *) mem_malloc(sizeof(int) * n); - - /* - the following will be used in order to recreate the dfs_tree - so that the DFS children of each DFI are ordered - according to their lowpoint value - */ - lowpoint_order = (int *) mem_malloc(sizeof(int) * n); - temp = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - new_dfs_tree = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); - - /* - finally, three more holding arrays: a trick to remember which - tree edges we are talking about: - - when constructing dfs_tree, back_edges, mult_edges - - we NEED to record the index in A (the adjacency list) - of some of the edges and their twins/inverses - we are currently storing in either of these structures - - we also need to record the number of multiple (directed) - edges we encounter when the graph is not simple - - this is easy to do when storing back edges and multiple edges, - and tree edges also: but this lattest set of neighbour lists (dfs_tree) - is subsequently reordered so that DFS children are ordered - wrt lowpoint values; - - consequently the info about position in adjacency list - and edge multiplicity are lost in the ordering process - - the two following arrays will remember the info we'll need later - - more about this below - */ - TE_in_adjl = (int *) mem_malloc(sizeof(int) * n); - TE_twin_in_adjl = (int *) mem_malloc(sizeof(int) * n); - TE_mult = (int *) mem_malloc(sizeof(int) * n); - - - /* - initialization of the data structures - */ - for (v = 0; v < n; v++) - { - (*dfs_tree)[v] = (*back_edges)[v] = (*mult_edges)[v] = NP; - visited[v] = TE_mult[v] = 0; - (*dfs_parent)[v] = (*least_a)[v] = n; - temp[v] = new_dfs_tree[v] = NP; - TE_in_adjl[v] = TE_twin_in_adjl[v] = NIL; - /* - note that in the 3rd last statement n is considered - as an "invalid" value; - will be if importance in the overall algorithm - */ - } - - /* - the DFS tree is rooted at vertex 0 - */ - dfs_n = -1; - pos_v_stack = -1; - pos_e_stack = -1; - *c = 0; - for (v = 0; v < n; v++) - { - if (visited[v]) - /* - we come only at this level when looking for - a new subtree (when graph is disconnected) - */ - { - continue; - } - else - { - (*c)++; - } - - cur = v; - visited[cur] = 1; - (*dfs_nbr)[cur] = ++dfs_n; - (*lowpoint)[(*dfs_nbr)[cur]] = dfs_n; - (*dfs_order)[dfs_n] = cur; - - cur_e = V[cur].first_edge == NIL ? NIL : V[cur].first_edge; - while (TRUE) - { - if (cur_e != NIL) - { - t_dlcl *existing_e; - - next = A[cur_e].end_vertex; - if (!visited[next]) - /* - adding tree edges (careful: directed edges) - - AND tree edges are stored as - [dfs_nbr[u], dfs_nbr[cv]] - instead of [u, cv]: that is we store the edges - according to the vertices' DFIs - */ - { - IF_DEB_TREE( - io_printf("add tree edge %d\t%d\n", - cur+1, next+1); - ); - - (*dfs_nbr)[next] = ++dfs_n; - (*lowpoint)[(*dfs_nbr)[next]] = dfs_n; - (*dfs_order)[dfs_n] = next; - - sparseg_dlcl_append_to_neigh_list(*dfs_tree, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - NIL); - TE_in_adjl[(*dfs_nbr)[next]] = cur_e; - TE_mult[(*dfs_nbr)[next]]++; - - /* - we push cur and the edge (cur, cur_e) on their - respective stacks - */ - vertex_stack[++pos_v_stack] = cur; - edge_stack[++pos_e_stack] = cur_e; - - /* - and mark next as visited - */ - visited[next] = 1; - - /* - update dfs_parent (always deal with DFIs rembember!) - */ - (*dfs_parent)[(*dfs_nbr)[next]] = (*dfs_nbr)[cur]; - - /* - the DFS goes one level deeper - */ - cur = next; - cur_e = V[cur].first_edge == NIL ? - NIL : V[cur].first_edge; - } - /* - the next three tests deal with multiple edges - and loops: apart from storing these (DIRECTED) edges - in mult_edges, we also need to update - the multipliciaty information about these edges - */ - else if (sparseg_dlcl_is_adjacent(*dfs_tree, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - &existing_e)) - /* - [cur, next] is a tree edge - */ - { - sparseg_dlcl_append_to_neigh_list(*mult_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - cur_e); - TE_mult[(*dfs_nbr)[next]]++; - - cur_e = A[cur_e].next; /* next in cur's adjacency list */ - } - else if (sparseg_dlcl_is_adjacent(*back_edges, n, - (*dfs_nbr)[next], - (*dfs_nbr)[cur], - &existing_e)) - /* - [cur, next] is a back edge - */ - { - sparseg_dlcl_append_to_neigh_list(*mult_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - cur_e); - (existing_e->mult)++; - - cur_e = A[cur_e].next; /* next in cur's adjacency list */ - } - else if (next == cur) - /* - the case of a loop - */ - { - if (sparseg_dlcl_is_adjacent(*mult_edges, n, - (*dfs_nbr)[next], - (*dfs_nbr)[cur], - &existing_e)) - /* - in this case we must update the multiplicity - of this edge: note that the elt. in cur's - neighbours list that gets updated is the first - in the list - - dodgy??? certainly, but can't think - of a better way to do this - - eventually it will happen that even myself - won't understand what I am doing.......... - */ - { - (existing_e->mult)++; - } - sparseg_dlcl_append_to_neigh_list(*mult_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - cur_e); - - cur_e = A[cur_e].next; /* next in cur's adjacency list */ - } - else if (sparseg_dlcl_is_adjacent(*dfs_tree, n, - (*dfs_nbr)[next], - (*dfs_nbr)[cur], - &existing_e)) - /* - [next, cur] is a tree edge: - that is, [cur, next] is [next, cur]'s twin/inverse: - - 1. if it is the first time one encounters - [cur, next] (as it would always be the case - for a simple graph) then all I need to do - is to update the tree edge's multiplicity, - and the twin info in TE_[] - - 2. if [cur, next] is actually a multiple edge, - then I'll need to store it in mult_edges; - and I update the tree edge's multiplicity too. - No twin info will be required here. - Why? see how recover.c embeds the multiple - edges in the planar embedding. - - 3. how do I know it is the first time I encounter - [cur, next]?: - when TE_twin_in_adjl = NIL - - 4. finally, note that the present counting scheme - implies that the mult field always holds - the number of directed edges: - ie, if [a, b] is a tree edge, [a, b].mult = 2 - because we would have counted [a, b] and [b, a] - - this applies to tree edges, back edges, and loops - */ - { - ASSERT(TE_in_adjl[(*dfs_nbr)[cur]] != NIL); - if (TE_twin_in_adjl[(*dfs_nbr)[cur]] == NIL) - { - TE_twin_in_adjl[(*dfs_nbr)[cur]] = cur_e; - } - else - { - sparseg_dlcl_append_to_neigh_list(*mult_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - cur_e); - } - - TE_mult[(*dfs_nbr)[cur]]++; - - cur_e = A[cur_e].next; /* next in cur's adjacency list */ - } - else if (sparseg_dlcl_is_adjacent(*back_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - &existing_e)) - /* - [next, cur] is a back edge: [cur, next] is its inverse: - we proceed as for the tree edge case above - */ - { - ASSERT(existing_e->in_adjl != NIL); - if (existing_e->twin_in_adjl == NIL) - { - existing_e->twin_in_adjl = cur_e; - } - else - { - sparseg_dlcl_append_to_neigh_list(*mult_edges, n, - (*dfs_nbr)[cur], - (*dfs_nbr)[next], - cur_e); - } - - (existing_e->mult)++; - - cur_e = A[cur_e].next; /* next in cur's adjacency list */ - } - /* - the next bit concludes the DFS: it deals with the case - where a back edge needs to be added - */ - else - /* - that is, next is visited and neither - the tree edge [next, cur] nor - the back edge [next, cur] exist: - - this implies that [cur, next] is a back edge - that must be added to the back_edges structure - (with dfs_nbr(next) < dfs_nbr(cur)) - */ - { - IF_DEB_TREE( - io_printf("add back edge %d\t%d\n", - cur+1, next+1); - ); - - ASSERT(visited[next]); - ASSERT((*dfs_nbr)[cur] > (*dfs_nbr)[next]); - - sparseg_dlcl_append_to_neigh_list(*back_edges, n, - (*dfs_nbr)[next], - (*dfs_nbr)[cur], - cur_e); - - /* - update cur's lowpoint - */ - (*lowpoint)[(*dfs_nbr)[cur]] = - (*dfs_nbr)[next] < (*lowpoint)[(*dfs_nbr)[cur]] ? - (*dfs_nbr)[next] : (*lowpoint)[(*dfs_nbr)[cur]]; - - /* - update least_a (of cur) - (always deal with DFIs remember!) - */ - (*least_a)[(*dfs_nbr)[cur]] = - (*dfs_nbr)[next] < (*least_a)[(*dfs_nbr)[cur]] ? - (*dfs_nbr)[next] : (*least_a)[(*dfs_nbr)[cur]]; - - /* - get the next edge in cur's adjacency list - */ - cur_e = A[cur_e].next; - } - } - - if (cur_e == NIL) - /* - we are either at a leaf or have finished scanning - cur's adjacency list: backtrack - */ - { - if (pos_v_stack == -1) /* no previous vertex */ - { - /* - no edge left on the stack: DFS ends for - this subtree: - we visit the next vertex - */ - ASSERT(pos_e_stack == -1); - break; - } - else - { - int prev_e; - /* - Otherwise backtrack and pop cur from the stack - as well as the last tree edge added to the tree. - We use next to get a new lowpoint value for cur: - This value will be min(lowpoint(cur), lowpoint(next)). - */ - cur = vertex_stack[pos_v_stack--]; - prev_e = edge_stack[pos_e_stack--]; - next = A[prev_e].end_vertex; - (*lowpoint)[(*dfs_nbr)[cur]] = - (*lowpoint)[(*dfs_nbr)[cur]] - < (*lowpoint)[(*dfs_nbr)[next]] ? - (*lowpoint)[(*dfs_nbr)[cur]] - : (*lowpoint)[(*dfs_nbr)[next]]; - - cur_e = A[prev_e].next; - } - /* - we proceed with DFS - */ - } - } - } - mem_free(vertex_stack); - mem_free(edge_stack); - - /* - just for the sake of it, check that all vertices have - been visited - */ -#ifdef ASSERTIONS - for (v = 0; v < n; v++) - { - ASSERT(visited[v]); - } -#endif - mem_free(visited); - - /* - we now order the DFIs wrt lowpoint values: - use bucket sort (linear time) - */ - /* - for each lowpoint value, collect the DFIs (in a t_dlcl) - with that lowpoint value - (IMPORTANT: we want the DFIs since the aim is to rewrite dfs_tree - which stores DFIs and not labels!) - */ - for (v = 0; v < n; v++) - /* - v is taken as a DFI here - */ - { - t_dlcl *r; - - r = embedg_dlcl_rec_new(v); - temp[(*lowpoint)[v]] = - embedg_dlcl_rec_append(temp[(*lowpoint)[v]], r); - } - - /* - concatenate these lists now - */ - lowpoint_list = temp[0]; - for (lp = 1; lp < n; lp++) - { - lowpoint_list = embedg_dlcl_cat(lowpoint_list, temp[lp]); - } - ASSERT(embedg_dlcl_length(lowpoint_list) == n); - - lowpoint_order[0] = lowpoint_list->info; - for (lp = 1; lp < n; lp++) - { - lowpoint_list = embedg_dlcl_list_next(lowpoint_list); - lowpoint_order[lp] = lowpoint_list->info; - } - embedg_dlcl_delete(lowpoint_list); - mem_free(temp); - - IF_DEB( - fprintf(stdout, "dfs_preprocessing, lowpoint_order\n"); - for (lp = 0; lp < n; lp++) - fprintf(stdout, "%d ", lowpoint_order[lp]); - fprintf(stdout, "\n"); - fprintf(stdout, "dfs_preprocessing, lowpoint\n"); - for (lp = 0; lp < n; lp++) - fprintf(stdout, "%d ", (*lowpoint)[lp]); - fprintf(stdout, "\n"); - ) - - /* - we now use this order to rewrite dfs_tree such that - the DFS children of each vertex are ordered wrt lowpoint values - */ - for (lp = 0; lp < n; lp ++) - /* - for each DFI in lowpoint_order[] I know its DFS_parent - from dfs_parent[] -- the rest is then trivial - */ - { - int parent; - - v = lowpoint_order[lp]; - /* - lowpoint_order stores DFIs as does dfs_parent, so the lot - makes sense - */ - parent = (*dfs_parent)[v]; - if (parent != n) - /* - v may be the root of a DFS tree - */ - { - t_dlcl *temp; - - temp = embedg_dlcl_rec_new(v); - - /* - this is where the TE_ holding arrays are useful *sigh* - */ - ASSERT(TE_in_adjl[v] != NIL); - temp->in_adjl = TE_in_adjl[v]; - - ASSERT(TE_twin_in_adjl[v] != NIL); - temp->twin_in_adjl = TE_twin_in_adjl[v]; - - ASSERT(TE_mult[v] != 0 && TE_mult[v] % 2 == 0); - temp->mult = TE_mult[v]; - - new_dfs_tree[parent] = - embedg_dlcl_rec_append(new_dfs_tree[parent], temp); - } - } - mem_free(lowpoint_order); - mem_free(TE_in_adjl); - mem_free(TE_twin_in_adjl); - mem_free(TE_mult); - - /* - some checks are in order here - */ -#ifdef ASSERTIONS - for (v = 0; v < n; v++) - { - ASSERT(embedg_dlcl_length((*dfs_tree)[v]) - == embedg_dlcl_length(new_dfs_tree[v])); - - IF_DEB( - fprintf(stdout, "dfs_preprocessing dfs_tree for %d\n", v); - embedg_dlcl_print((*dfs_tree)[v]); - fprintf(stdout, "dfs_preprocessing new_dfs_tree for %d\n", v); - embedg_dlcl_print(new_dfs_tree[v]); - ); - } -#endif - - sparseg_dlcl_delete(*dfs_tree, n); - *dfs_tree = new_dfs_tree; -} - -/* - * embedding.c - */ - -/* - What: - ***** - - Implementing: - - The graph is planar: we recover the embedding from the VES structure - and check it as well. - (Some of these checks will disappear later) - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_EMBED(x) {} -#define IF_DEB_CHECK_EMBED(x) {} -#define IF_DEB_FACES(x) {} -#define IF_VERB(x) {} -#define IF_DEB_SCE(x) {} -#define IF_CPU(x) {} - - -/* aproto: header embed_graph_protos.h */ - - -#ifndef PLANAR_IN_MAGMA -#endif - -void -embedg_embedding (t_ver_sparse_rep *V, t_adjl_sparse_rep *A, - t_ver_edge *embed_graph, int n, int e, int nbr_c, - int edge_pos, t_dlcl **mult_edges, t_ver_sparse_rep **vertices, - t_embed_sparse_rep **embedding) - /* - recovering the embedding for the (planar) graph - - - the embedding is returned in vertices and embedding, vertices - indexes embedding, the ordered list of edges - - edges in the embedding are given as their index in A, the graph's - adajacency list - - the nbr of edges in the embedding is given as nbr_e_embed: - this may be different form the original number of edges when the graph - iss not simple - */ -{ - int *ver_orient, nbr_comp, nbr_e_embed; - - IF_CPU( - float sttime; float time_to_now; - - sttime = time_current_user(); - ) - - IF_DEB( - fprintf(stdout, "embedding, begin, which edges have been flipped\n"); - embedg_VES_print_flipped_edges(embed_graph, n, edge_pos); - ) - - IF_DEB( - fprintf(stdout, "embedding, before removing SCE\n"); - embedg_VES_print_bigcomps(embed_graph, n); - ) - - /* - several things to do: - 1. removing the short-cut edges - */ - embedg_remove_SCE(embed_graph, n, edge_pos); - - IF_DEB( - fprintf(stdout, "embedding, after removing SCE\n"); - embedg_VES_print_bigcomps(embed_graph, n); - ) - - /* - 2. computing each vertex's orientation (wrt flipped bicomps) - */ - ver_orient = embedg_vertices_orientation(embed_graph, n); - - - /* - 3. merging the remaining virtual vertices with their - non-virtual counterpart - */ - nbr_comp = embedg_merge_remaining_virtual(embed_graph, n); - /* - actually there is no need to return the nbr of components - from the above function - but let's do it for the sake of it and for possible checking - */ - ASSERT(nbr_c == nbr_comp); - - IF_DEB( - fprintf(stdout, "embedding, after merging of remaining vertices\n"); - ) - - /* - 4. to be on the safe side: check that the embedding is a valid one - - for now, we DIE if not - */ - - if (!embedg_is_embed_valid(embed_graph, n, nbr_comp, edge_pos, - ver_orient, &nbr_e_embed)) - { - mem_free(ver_orient); - DIE(); - } - mem_free(ver_orient); - - ASSERT(nbr_e_embed <= e); - /* - when the graph is not simple, multiple edges and loops are - not in embed_graph[]: they will be added to the final - embedding in embedg_recover_embedding below - */ - - /* - 5. recover the embedding in preparation for the Magma type, - and check it as well - */ - embedg_recover_embedding(V, A, embed_graph, n, e, - mult_edges, vertices, embedding); - if (!embedg_check_recov_embedding(n, e, nbr_comp, - *vertices, A, *embedding)) - { - mem_free(*vertices); - mem_free(*embedding); - - IF_CPU( - fprintf(stdout, "CPU for embedding recovering %f\n", - time_current_user() - sttime); - ) - - DIE(); - } - - IF_DEB_EMBED( - fprintf(stdout, "embedding, original graph and embedding\n"); - sparseg_adjl_print(V, n, A, FALSE); - fprintf(stdout, "\n"); - sparseg_adjl_embed_print(*vertices, n, A, *embedding, - FALSE); - ) - - IF_CPU( - fprintf(stdout, "CPU for embedding recovering %f\n", - time_current_user() - sttime); - ) -} - - -void -embedg_remove_SCE (t_ver_edge *embed_graph, int n, int edge_pos) - /* - remove all the short-cut edges from the embedding - */ -{ - int i, c; - - c = 0; - for (i = 2*n; i <= edge_pos; i += 2) - /* - and edge and its twin occupy consecutive positions in embed_graph: - need only to examine one out of two - (removing an edge also entails removing its twin of course - */ - { - if (embedg_VES_is_short_cut_edge(embed_graph, n, i)) - { - IF_DEB_SCE( - fprintf(stdout, "remove SCE\n"); - embedg_VES_print_edge(embed_graph, n, i); - ) - - embedg_VES_remove_edge(embed_graph, n, i); - c++; - } - } - - IF_DEB_SCE( - fprintf(stdout, "nbr of SCE edges removed %d\n", c); - ) -} - - -int * -embedg_vertices_orientation (t_ver_edge *embed_graph, int n) - /* - for each vertex return its orientation from the - bicomps in embed_graph: - perform a DFS of each bicomp - */ -{ - int i, vv, prod_sign; - int *stack, *ver_orient, to_prev; - - /* - the whole lot makes sense iff the adjacency lists are consistent: - this is a very important issue and it might be the case - that the ASSERT warrants replacement by a DIE - (the check is linear - I think) - */ - ASSERT(embedg_VES_are_adj_lists_consistent(embed_graph, n)); - - ver_orient = (int *) mem_malloc(sizeof(int) * n); - for (i = 0; i < n; i++) - { - ver_orient[i] = CCLOCKW; - } - - /* - create the stack for the DFS - */ - stack = (int *) mem_malloc(sizeof(int) * 3*n); - to_prev = -1; - - IF_DEB( - fprintf(stdout, "vertex orientation, one line (of vert.) for each bicomp\n"); - ) - - /* - now visit all the bicomps, ie, all the virtual vertices - in embed_graph - */ - for (vv = n; vv < 2*n; vv++) - { - int c, cur, cur_e; - boolean NEW_BICOMP; - - if (embed_graph[vv].link[0] == vv) - /* - means that vv is disabled and is not the root of a bicomp - */ - { - continue; - } - - c = vv - n; - IF_DEB( - fprintf(stdout, "%d ", c); - ) - /* - orientation for c (vv is as yet unembedded) is CCLOCKW - - now find the orientation of all its DFS descendants - */ - - if (embed_graph[c].DFS_parent == n) - /* - this means that actually c is an isolated vertex: - we initialise the sign to CCLOCKW - */ - { - prod_sign = CCLOCKW; - } - else - /* - we initialise the sign to CCLOCKW to the sign of c's parent - */ - { - prod_sign = ver_orient[embed_graph[c].DFS_parent]; - } - - /* - we must not forget to set c's sign!! - (won't be done below) - */ - ver_orient[c] = prod_sign; - - NEW_BICOMP = FALSE; - cur = c; - cur_e = embed_graph[cur].link[0]; - ASSERT(embedg_VES_is_edge(n, cur_e)); - - ASSERT(to_prev == -1); - while (TRUE) - { - while (!embedg_VES_is_tree_edge(embed_graph, n, cur_e) - || !embedg_VES_is_vertex(n, - embed_graph[cur_e].neighbour) - || embed_graph[cur_e].neighbour <= cur) - /* - want to find a tree edge [cur, u] - where u is a descendant of cur - */ - { - cur_e = embed_graph[cur_e].link[0]; - - while (cur_e == cur) - /* - back to the vertex where we started from: - no edge has been found: - cur is a leaf, backtrack - */ - { - if (to_prev == -1) - { - NEW_BICOMP = TRUE; - break; - } - prod_sign = stack[to_prev--]; - cur_e = stack[to_prev--]; - /* - must advance one more edge - */ - cur_e = embed_graph[cur_e].link[0]; - cur = stack[to_prev--]; - } - if (NEW_BICOMP) - { - break; - } - } - - if (NEW_BICOMP) - { - break; - } - else - /* - now cur_e is the edge we were looking for, get its sign - */ - { - /* - push on stack the current vertex, the edge where we - stopped the DFS, AND the sign carried by that vertex - - and go down one level in the DFS - */ - stack[++to_prev] = cur; - stack[++to_prev] = cur_e; - stack[++to_prev] = prod_sign; - - cur = embed_graph[cur_e].neighbour; - prod_sign *= embed_graph[cur_e].sign; - ver_orient[cur] = prod_sign; - - cur_e = embed_graph[cur].link[0]; - ASSERT(embedg_VES_is_edge(n, cur_e)); - - IF_DEB( - fprintf(stdout, "%d with sign %d\n", cur, prod_sign); - ) - } - } - - IF_DEB( - fprintf(stdout, "\n"); - ) - } - - IF_DEB( - fprintf(stdout, "vertex orientation\n"); - for (i = 0; i < n; i++) - { - fprintf(stdout, "%d ", ver_orient[i]); - } - fprintf(stdout, "\n"); - ) - - mem_free(stack); - return ver_orient; -} - - -int -embedg_merge_remaining_virtual (t_ver_edge *embed_graph, int n) - /* - after the short-cut edges have been removed and the vertices' - orientation computed, one finishes by merging all - remaining virtual vertices with their virtual counterpart - (without flip of course) - - and use this routine to return the number of disconnected - components of the graph - */ -{ - /* - at this stage it is easy to see that all remaining - virtual vertices are DFS roots (if the graph is not connected) - or cut vertices - */ - - int vv, nbr_comp; - - nbr_comp = 0; - for (vv = n; vv < 2*n; vv++) - { - int v, c; - - - c = vv - n; - v = embed_graph[c].DFS_parent; - - /* - must fish out which virtual vertices are actual roots - of DFS trees (esp. for the disconnected graph case): - roots of DFS trees are those virtual vertices for which - v = embed_graph[c].DFS_parent = n - */ - if (v == n) - { - nbr_comp++; - continue; - } - - if (embed_graph[vv].link[0] == vv) - /* - means that vv is disabled and is not the root of a bicomp - */ - { - continue; - } - - embedg_VES_merge_simple_bicomps(embed_graph, n, - vv, 1, v, 0); - /* - note: - since v is a cut vertex in this intance the bicomp - rooted by vv will be merged without flip; - therefore we could have done - embedg_VES_merge_simple_bicomps(embed_graph, n, - vv, 0, v, 1) - as well, the important thing being that vin != vvout - (see embedg_VES_merge_simple_bicomps) - */ - } - - return nbr_comp; -} - - -int -embedg_nbr_faces (t_ver_edge *embed_graph, int n, int edge_pos, - int *ver_orient, int *nbr_e_embed) - /* - count the number of faces and the number of edges of the embedding - */ -{ - int v, e, f, total_e; - - IF_DEB_FACES( - int v; - - fprintf(stdout, "nbr of faces, the vertices' adj. lists\n"); - for (v = 0; v < n; v++) - embedg_VES_print_adj_list(embed_graph, n, - v, TRUE); - ) - - /* - the following is no more than a quick check -- certainly - not very useful -- or could be done elsewhere - */ - total_e = 0; - for (e = 2*n; e <= edge_pos; e++) - { - if (!embedg_VES_is_short_cut_edge(embed_graph, n, e)) - { - total_e++; - } - } - ASSERT(total_e % 2 == 0); - *nbr_e_embed = total_e / 2; - - /* - I now set each edge's orientation - - QUESTION: do I really need to do this??? - so far, when doing a proper face traversal, the way in which - the adjacency list of an edge must be traversed is given - by the vertex's (in that list) orientation... - So this seems sensible to me huh? - */ - embedg_VES_set_orientation(embed_graph, n, ver_orient); - - /* - I will be using the visited field to enable me to check - if all edges have been traversed - - let's be smart (?!): so far the visited field has been used - and set in the following circumstances: - + initialisation: set to n - + walkup: set to whatever DFI of interest - - so here we set it to MARK_EMBED(n) - */ - f = 0; - for (e = 2*n; e <= edge_pos; e++) - { - if (!embedg_VES_is_short_cut_edge(embed_graph, n, e) - /* - arrghh!!! I must also skip the SCE!!! - */ - && embed_graph[e].visited != MARK_EMBED(n)) - { - int ein; - - IF_DEB_FACES( - fprintf(stdout, "nbr of faces, edges not visited\n"); - embedg_VES_print_edge(embed_graph, n, e); - ) - - ein = embed_graph[e].sign == CCLOCKW ? 0 : 1; - /* - the way I enter e in dependent on its sign: - all the proper face traversal must obviously be done - with the same orientation! - */ - embedg_VES_walk_proper_face(embed_graph, n, e, - ein, - TRUE, - MARK_EMBED(n)); - f++; - } - } - - /* - counting the faces by traversing all the edges does not - account of the face defined by isolated vertices - -- we do that now - - we only need to check which vertices refer to self, ie with - no incident edges - */ - for (v = 0; v < n; v++) - { - if (embed_graph[v].link[0] == v) - { - ASSERT(embed_graph[v].link[1] == v); - f++; - } - } - - return f; -} - - -boolean -embedg_is_embed_valid (t_ver_edge *embed_graph, int n, int nbr_comp, - int edge_pos, int *ver_orient, int *nbr_e_embed) - /* - use Euler's formula to assertain that the embedding is a valid - embedding: - - f = 2 * nbr_comp + nbr_e_embed - n - - */ -{ - int v, f; - - f = embedg_nbr_faces(embed_graph, n, edge_pos, ver_orient, nbr_e_embed); - - IF_DEB_CHECK_EMBED( - fprintf(stdout, "embedding, n: %d\t e: %d\t C: %d\t f: %d\n", - n, nbr_e, nbr_comp, f); - ) - - return f == 2 * nbr_comp + *nbr_e_embed - n ? TRUE : FALSE; -} -/* - * ext_face_walk.c - */ - -/* - What: - ***** - - Implementing the external face walk of a bicomponent. - The concept of an external face --in the context of the VES - data structure-- makes only sense when talking - about a bicomp. - - Recall that a vertex is linked together with the edges - incident from it in a circular (doubly) linked list - (this is the VES structure). - - One particular feature is that if a vertex v is on - the external face of a component and if in the list - we have edges e1, e2 such as e1 -> v -> e2 - then e1 and e2 border the external face. - - In other words, in the circular list of vertex v and edges, - v is ALWAYS between the two edges bordering the external face - - Of course, when v is (maybe) pushed into the internal face - (by embedding of some edge) then we don't care about this any more - (for v that is). - - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} - - -/* aproto: header embed_graph_protos.h */ - - -void -embedg_VES_get_succ_on_ext_face (t_ver_edge *embed_graph, int n, int v, - int vin, boolean MARK, int mark, int *s, int *sin) - /* - find the successor s of v (entered via vin) on the external face - -- also return the direction in which s has been entered - - if MARK true mark the succ. vertex and the edges traversed - with mark (the visited field) - */ -{ - int e, twin; - int vout, ein, eout, tout; - - ASSERT(embedg_VES_is_vertex(n, v) - || embedg_VES_is_virtual_vertex(n, v)); - - IF_DEB( - fprintf(stdout, "get_succ_on_ext_face, of %d:%d\n", v, vin); - ) - - /* - find the direction out of the vertex, and get the edge - */ - vout = vin == 0 ? 1 : 0; - e = embed_graph[v].link[vout]; - if (embedg_VES_is_virtual_vertex(n, v) && e == v) - /* - this can happen if a virtual vertex has been "disabled" - - -- this should not never happen since we can only walk - on the external face of a bicomp! - */ - { - *s = v; - *sin = vin; - return; - } - - /* - otherwise we must have an edge: - note that it is entirely irrelevant if I walk SCEs: - those are precisely there to "jump" over inactive vertices - */ - ASSERT(embedg_VES_is_edge(n, e)); - - /* - get the twin edge - */ - twin = embedg_VES_get_twin_edge(embed_graph, n, e); - - IF_DEB( - fprintf(stdout, "get_succ_on_ext_face, edge [%d, %d]\n", - v, embed_graph[e].neighbour); - fprintf(stdout, "get_succ_on_ext_face, twin edge [%d, %d]\n", - embed_graph[e].neighbour, embed_graph[twin].neighbour); - ) - /* - find which of twin's link links a vertex - */ - tout = embedg_VES_is_vertex(n, embed_graph[twin].link[0]) - || embedg_VES_is_virtual_vertex(n, - embed_graph[twin].link[0]) - ? - 0 : 1; - - /* - get this vertex: this is v's successor on the external face - */ - *s = embed_graph[twin].link[tout]; - - /* - one more thing to do: find the direction in which s was entered - */ - *sin = embed_graph[*s].link[0] == twin ? 0 : 1; - - IF_DEB( - fprintf(stdout, "get_succ_on_ext_face, succ is %d:%d\n", - *s, *sin); - ) - /* - a special case: when the bicomp is a singleton bicomp - (ie a single edge) - */ - if (embed_graph[*s].link[0] == (embed_graph[*s].link[1])) - { - ASSERT(embed_graph[*s].link[0] == twin); - *sin = vin; - } - - /* - finally, mark the vertex and edges if so requested - */ - if (MARK) - { - embed_graph[*s].visited = mark; - embed_graph[e].visited = mark; - embed_graph[twin].visited = mark; - } -} - -void -embedg_VES_get_succ_active_on_ext_face (t_ver_edge *embed_graph, int n, - int v, int w, int win, boolean MARK, int mark, int *s, int *sin) - /* - find the ACTIVE (wrt v) successor s of w (entered via win) - on the external face - -- also return the direction in which s has been entered - - if MARK true mark the succ. vertex (and the edge) - with mark (the visited field) - */ -{ - /* - simply repeatedly calls embedg_VES_get_succ_on_ext_face - until an active vertex is found - */ - ASSERT(embedg_VES_is_vertex(n, w) - || embedg_VES_is_virtual_vertex(n, w)); - - embedg_VES_get_succ_on_ext_face(embed_graph, n, - w, win, MARK, mark, s, sin); - while (embedg_VES_is_ver_inactive(embed_graph, n, v, *s)) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - *s, *sin, MARK, mark, s, sin); - } - ASSERT(!embedg_VES_is_ver_inactive(embed_graph, n, v, *s)); -} - -void -embedg_VES_get_succ_ext_active_on_ext_face (t_ver_edge *embed_graph, int n, - int v, int w, int win, boolean MARK, int mark, int *s, int *sin) - /* - find the externally active (wrt v) successor s of w (entered via win) - on the external face - -- also return the direction in which s has been entered - - if MARK true mark the succ. vertex (and the edge) - with mark (the visited field) - */ -{ - ASSERT(embedg_VES_is_vertex(n, w) - || embedg_VES_is_virtual_vertex(n, w)); - - embedg_VES_get_succ_on_ext_face(embed_graph, n, - w, win, MARK, mark, s, sin); - while (!embedg_VES_is_ver_ext_active(embed_graph, n, v, *s)) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - *s, *sin, MARK, mark, s, sin); - } - ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, *s)); -} - -void -embedg_VES_get_succ_pertinent_on_ext_face (t_ver_edge *embed_graph, int n, - int v, int w, int win, boolean MARK, int mark, int *s, int *sin) - /* - find the pertinent (wrt v) successor s of w (entered via win) - on the external face - -- also return the direction in which s has been entered - - if MARK true mark the succ. vertex (and the edge) - with mark (the visited field) - */ -{ - ASSERT(embedg_VES_is_vertex(n, w) - || embedg_VES_is_virtual_vertex(n, w)); - - embedg_VES_get_succ_on_ext_face(embed_graph, n, - w, win, MARK, mark, s, sin); - while (!embedg_VES_is_ver_pertinent(embed_graph, n, v, *s)) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - *s, *sin, MARK, mark, s, sin); - } - ASSERT(embedg_VES_is_ver_pertinent(embed_graph, n, v, *s)); -} - -/* - * mark_kur.c - */ - -/* - What: - ***** - - Implementing: - - Marking the Kuratowski obstruction (in the VES structure): - this we do once we know which minor we are talking about - (see isolator.c). - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_VERB(x) {} -#define IF_DEB_TREE(x) {} -#define IF_DEB_EDGES(x) {} -#define IF_CPU(x) {} - - - -/* aproto: header embed_graph_protos.h */ - -/* aproto: beginstatic -- don't touch this!! */ -static void embedg_VES_walk_mark_part_ext_face (t_ver_edge *, int, int, int, int, int, int); -static void embedg_VES_walk_mark_ext_face (t_ver_edge *, int, int, int); -static void embedg_VES_walk_mark_part_proper_face (t_ver_edge *, int, int, int, int, int); -static boolean embedg_VES_is_part_ext_face_marked (t_ver_edge *, int, int, int, int, int, int); -static void embedg_get_u_x (t_ver_edge *, int, int, int, int *); -static int embedg_get_least_neigh (t_dlcl **, t_dlcl **, int, int, int); -static void embedg_add_mark_u_x (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int *, int); -static void embedg_mark_tree_path (t_ver_edge *, int, int, int, int); -static void embedg_add_mark_v_w (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int); -static void embedg_add_mark_v_w_for_B (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int *, int); -static void embedg_mark_x_y_path (t_ver_edge *, int, int *, int *, int, int); -/* aproto: endstatic -- don't touch this!! */ - -#ifndef PLANAR_IN_MAGMA -#endif - - - -static void -embedg_VES_walk_mark_part_ext_face (t_ver_edge *embed_graph, int n, - int v, int vin, int from, int to, int mark) - /* - walk & mark the external face: - walk in the direction vin -> v -> vout and mark - */ -{ - int cur, curin, next, nextin; - - embed_graph[from].visited = mark; - embed_graph[to].visited = mark; - - IF_DEB( - fprintf(stdout, "part. ext face marked\t"); - fprintf(stdout, "%d\t", from); - ) - - next = cur = v; - curin = vin; - while (next != from) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, - FALSE, 0, &next, &nextin); - cur = next; - curin = nextin; - } - next = n; - while (next != to) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, - TRUE, mark, &next, &nextin); - cur = next; - curin = nextin; - - IF_DEB( - fprintf(stdout, "%d\t", next); - ) - } - IF_DEB( - fprintf(stdout, "\n"); - ) -} - -static void -embedg_VES_walk_mark_ext_face (t_ver_edge *embed_graph, int n, int v, int mark) - /* - walk & mark the external face, starting & ending at vertex v - */ -{ - embedg_VES_walk_mark_part_ext_face(embed_graph, n, v, 0, v, v, - mark); -} - - - -static void -embedg_VES_walk_mark_part_proper_face (t_ver_edge *embed_graph, int n, - int from_e, int from_ein, int to, int mark) - /* - walk & mark a proper face starting at EDGE from_e and ending - at VERTEX to - - walk in the direction from_ein -> from_e -> to and mark - everything in between - */ -{ - int s, cur_e, cur_ein, next_e, next_ein; - - next_e = s = n; /* this is an invalid value for an edge/vertex */ - - cur_e = from_e; - cur_ein = from_ein; - while (s != to) - { - ASSERT(embedg_VES_is_edge(n, cur_e)); - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, - n, cur_e)); - - embedg_VES_get_succ_on_proper_face(embed_graph, n, - cur_e, cur_ein, - TRUE, mark, - &s, &next_e, &next_ein); - cur_e = next_e; - cur_ein = next_ein; - } -} - - - -static boolean -embedg_VES_is_part_ext_face_marked (t_ver_edge *embed_graph, int n, int v, - int vin, int from, int to, int mark) - /* - simple check to see if all the vertices on the external - face walk starting at vin -> v -> vout are marked - (with mark) - */ -{ - int cur, curin, next, nextin; - - if (embed_graph[from].visited != mark || embed_graph[to].visited != mark) - return FALSE; - - cur = v; - curin = vin; - next = n; - while (next != from) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, - FALSE, 0, &next, &nextin); - cur = next; - curin = nextin; - } - while (next != to) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, - FALSE, 0, &next, &nextin); - if (embed_graph[next].visited != mark) - return FALSE; - - cur = next; - curin = nextin; - } - - return TRUE; -} - - -boolean -embedg_VES_is_ext_face_marked (t_ver_edge *embed_graph, int n, int v, int mark) - /* - simple check to see if all the vertices on the external - face walk starting/ending at v are marked (with mark) - */ -{ - return embedg_VES_is_part_ext_face_marked(embed_graph, n, v, 0, - v, v, mark); -} - - -static void -embedg_get_u_x (t_ver_edge *embed_graph, int n, int v, int x, int *u_x) - /* - x is an externally active vertex (wrt v): - we want u_x, the lowest point of "attachement" for - the unembedded directed edge [x, u_x] - */ -{ - int c; - t_dlcl *child_list; - - ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, x)); - if (embed_graph[x].least_ancestor < v) - /* - then there is a single unembedded back edge (u_x, x), - u_x an ancestor of v - */ - { - *u_x = embed_graph[x].least_ancestor; - return; - } - - /* - else there is a tree path x to d_x and an - unembedded back edge (u_x, d_x) - - get the lowpoint of the first elt. in separated_DFS_child_list of x - */ - child_list = embed_graph[x].separated_DFS_child_list; - ASSERT(!embedg_dlcl_is_empty(child_list)); - c = child_list->info; - *u_x = embed_graph[c].lowpoint; -} - -static int -embedg_get_least_neigh (t_dlcl **dfs_tree, t_dlcl **back_edges, - int n, int v, int c) - /* - get the least neighbour of v >= c, ie a vertex in the sub tree - rooted by c - - somehow this must always succeed - */ -{ - int least_n; - t_dlcl *tree_l, *back_l, *p; - - /* - neighbours are found in either dfs_tree[v] or back_edges[v] - */ - - tree_l = dfs_tree[v]; - back_l = back_edges[v]; - ASSERT(!embedg_dlcl_is_empty(tree_l) || !embedg_dlcl_is_empty(back_l)); - - least_n = n; /* ok, invalid value for any neighbour */ - p = tree_l; - if (!embedg_dlcl_is_empty(p)) - { - if (p->info >= c) - { - least_n = p->info < least_n ? p->info : least_n; - } - p = embedg_dlcl_list_next(p); - while (p != tree_l) - { - if (p->info >= c) - { - least_n = p->info < least_n ? p->info : least_n; - } - p = embedg_dlcl_list_next(p); - } - } - p = back_l; - if (!embedg_dlcl_is_empty(p)) - { - if (p->info >= c) - { - least_n = p->info < least_n ? p->info : least_n; - } - p = embedg_dlcl_list_next(p); - while (p != back_l) - { - if (p->info >= c) - { - least_n = p->info < least_n ? p->info : least_n; - } - p = embedg_dlcl_list_next(p); - } - } - - ASSERT(least_n >= c); - /* - this is so because of the context where this function is called from - */ - return least_n; -} - -static void -embedg_add_mark_u_x (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, - int x, int *u_x, int mark) - /* - marking a Kuratowski homeomorph: - - marking and adding the unembedded dotted edge (u, x), - x an ext. active vertex wrt v - */ -{ - int c, d_x; - t_dlcl *child_list; - - ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, x)); - if (embed_graph[x].least_ancestor < v) - /* - then there is a single unembedded back edge (u_x, x), - u_x an ancestor of v - */ - { - *u_x = embed_graph[x].least_ancestor; - embed_graph[x].visited = mark; - embed_graph[*u_x].visited = mark; - embedg_VES_add_edge(embed_graph, n, edge_pos, *u_x, x, - TRUE, mark); - return; - } - - /* - else there is a tree path x to d_x and an - unembedded back edge (u_x, d_x) - - get the lowpoint of the first elt. in separated_DFS_child_list of x - */ - child_list = embed_graph[x].separated_DFS_child_list; - ASSERT(!embedg_dlcl_is_empty(child_list)); - c = child_list->info; - *u_x = embed_graph[c].lowpoint; - - /* - search for the least neighbour of *u_x >= c, - that is in the subtree rooted by c - */ - d_x = embedg_get_least_neigh(dfs_tree, back_edges, n, *u_x, c); - ASSERT(d_x >= c); - /* - this must be true since u_x is incident to a descendant of x - (remember: x is externally active) - */ - - /* - mark the DFS tree path from d_x to x - */ - embedg_mark_tree_path(embed_graph, n, d_x, x, mark); - /* - add the unembedded (u_x, d_x) edge - */ - embedg_VES_add_edge(embed_graph, n, edge_pos, *u_x, d_x, - TRUE, mark); -} - -static void -embedg_mark_tree_path (t_ver_edge *embed_graph, int n, int d_x, int x, int mark) - /* - marking the DFS tree path d_x...x where x is an ancestor of d_x - */ -{ - int cur_v, te, twe; - - ASSERT(d_x >= x); - - cur_v = d_x; - - while (cur_v != x) - { - embed_graph[cur_v].visited = mark; - te = embed_graph[cur_v].link[0]; - ASSERT(embedg_VES_is_edge(n, te)); - while (!embedg_VES_is_tree_edge(embed_graph, n, te) - || (embed_graph[te].neighbour > cur_v - && embed_graph[te].neighbour != cur_v + n)) - /* - want to find a tree edge incident to an ancestor of d_x: - given that d_x..x is a tree path, we MUST find such an edge! - - note also that I must take account of the fact that - [te].neighbour could be a virtual vertex, in which case - it can only be cur_v + n! - */ - { - te = embed_graph[te].link[0]; - } - ASSERT(embedg_VES_is_tree_edge(embed_graph, n, te)); - ASSERT(embed_graph[te].neighbour == embed_graph[cur_v].DFS_parent - || embed_graph[te].neighbour == cur_v + n); - - embed_graph[te].visited = mark; - twe = embedg_VES_get_twin_edge(embed_graph, n, te); - embed_graph[twe].visited = mark; - - /* - want only to deal with real vertices instead of virtual vertices - */ - cur_v = embed_graph[te].neighbour < cur_v ? - embed_graph[te].neighbour : embed_graph[cur_v].DFS_parent; - } - embed_graph[x].visited = MARK_MINORS(n); -} - - -static void -embedg_add_mark_v_w (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, int w, int mark) - /* - marking a Kuratowski homeomorph: - - marking and adding the unembedded dotted edge (v, w), - w is pertinent wrt v - */ -{ - int vw, c, d_w; - t_dlcl *bicomp_list; - - if (embed_graph[w].adjacent_to == v) - /* - then there is a single unembedded back edge (v, w) - w an ancestor of w - */ - { - embed_graph[v].visited = mark; - embed_graph[w].visited = mark; - embedg_VES_add_edge(embed_graph, n, edge_pos, v, w, - TRUE, mark); - return; - } - - /* - else there is a tree path w to d_w and an - unembedded back edge (v, d_w) - - get the last elt in w's bicomp list - */ - bicomp_list = embed_graph[w].pertinent_bicomp_list; - ASSERT(!embedg_dlcl_is_empty(bicomp_list)); - vw = (embedg_dlcl_list_last(bicomp_list))->info; - c = vw - n; - - /* - search for the least neighbour of v >= c, - that is in the subtree rooted by c - */ - d_w = embedg_get_least_neigh(dfs_tree, back_edges, n, v, c); - ASSERT(d_w >= c); - /* - this must be true since v is incident to a descendant of w - (remember: w is pertinent) - */ - - /* - mark the DFS tree path from d_w to w - */ - embedg_mark_tree_path(embed_graph, n, d_w, w, mark); - /* - add the unembedded (d_w, v) edge - */ - embedg_VES_add_edge(embed_graph, n, edge_pos, d_w, v, - TRUE, mark); -} - - -static void -embedg_add_mark_v_w_for_B (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, int w, - int *u_z, int mark) - /* - marking a Kuratowski homeomorph: - - marking and adding the unembedded dotted edge (v, w) for minor B: - w is pertinent wrt v - */ -{ - int vz, z, d_z, d_w; - t_dlcl *bicomp_list; - - /* - get the last elt in w's bicomp list - */ - bicomp_list = embed_graph[w].pertinent_bicomp_list; - ASSERT(!embedg_dlcl_is_empty(bicomp_list)); - vz = (embedg_dlcl_list_last(bicomp_list))->info; - z = vz - n; - - /* - get the lowpoint of z - */ - *u_z = embed_graph[z].lowpoint; - - /* - search for the least neighbour of *u_z >= z, - that is in the subtree rooted by c - */ - d_z = embedg_get_least_neigh(dfs_tree, back_edges, n, *u_z, z); - ASSERT(d_z >= z); - /* - this must be true since u_z is incident to z or a descendant of z - */ - - /* - now do the same for neighbours of v - */ - d_w = embedg_get_least_neigh(dfs_tree, back_edges, n, v, z); - ASSERT(d_w >= z); - /* - this must be true since v is incident to a descendant of w - (remember: w is pertinent) - */ - - /* - mark the DFS tree path from d_w to w - */ - embedg_mark_tree_path(embed_graph, n, d_w, w, mark); - /* - mark the DFS tree path from d_z to z - */ - embedg_mark_tree_path(embed_graph, n, d_z, z, mark); - /* - add & mark the edges (u_z, d_z), (v, d_w) - */ - embedg_VES_add_edge(embed_graph, n, edge_pos, *u_z, d_z, - TRUE, mark); - embedg_VES_add_edge(embed_graph, n, edge_pos, v, d_w, - TRUE, mark); -} - -static void -embedg_mark_x_y_path (t_ver_edge *embed_graph, int n, int *path_v, - int *path_e, int nbr_v, int mark) -{ - int i; - - /* - have a look at embedg_iso_get_highest_x_y_path - to see that path_e[0] is a dummy - - (note: path_v and path_e contain nbr_v + 1 elts!) - */ - embed_graph[path_v[0]].visited = mark; - for (i = 1; i <= nbr_v; i++) - { - int e, twin; - - embed_graph[path_v[i]].visited = mark; - e = path_e[i]; - twin = embedg_VES_get_twin_edge(embed_graph, n, e); - embed_graph[e].visited = - embed_graph[twin].visited = mark; - } -} - -void -embedg_mark_minor_A (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, int c, int vr) -{ - int r, r_c, x, y, w, u_x, u_y, u; - - ASSERT(embedg_VES_is_virtual_vertex(n, vr)); - r_c = vr - n; - r = embed_graph[r_c].DFS_parent; - - /* - find the ext. active x & y, and the pertinent w, - and mark the external face of the bicomp rooted at vr - */ - embedg_iso_get_x_y_w(embed_graph, n, v, r, r_c, - MARK_MINORS(n), - MARK_MINORS(n), MARK_MINORS(n), &x, &y, &w); - - /* - mark the edges (u, x), (u, y), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, x, &u_x, - MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, y, &u_y, - MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - /* - mark the tree path from r to min(u_x, u_y) - */ - u = u_x <= u_y ? u_x : u_y; - embedg_mark_tree_path(embed_graph, n, r, u, MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor A\n"); - fprintf(stdout, "v %d\t c %d\t r %d\t r_c %d\t x %d\t y %d\t w %d\t u_x %d\t u_y %d\n", - v, c, r, r_c, x, y, w, u_x, u_y); - ) -} - -void -embedg_mark_minor_B (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, - int c, int x, int y, int w) -{ - int vv, u_x, u_y, vz, u_z, u_max, u_min; - - vv = c + n; - - /* - mark the external face of the bicomp rooted by v^c - */ - embedg_VES_walk_mark_ext_face(embed_graph, n, vv, MARK_MINORS(n)); - ASSERT(embedg_VES_is_ext_face_marked(embed_graph, n, vv, - MARK_MINORS(n))); - - /* - mark the edges (u, x), (u, y) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, x, &u_x, - MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, y, &u_y, - MARK_MINORS(n)); - - /* - mark the dotted edges (v, w), (v, u) - */ - embedg_add_mark_v_w_for_B(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - &u_z, MARK_MINORS(n)); - - /* - mark the tree path from max(u_x, u_y, u_z) to min(u_x, u_y, u_z) - */ - u_max = u_x > u_y ? u_x : u_y; - u_max = u_max > u_z ? u_max : u_z; - u_min = u_x < u_y ? u_x : u_y; - u_min = u_min < u_z ? u_min : u_z; - embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor B\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t u_x %d\t u_y %d\t u_z %d\n", - v, c, x, y, w, u_x, u_y, u_z); - ) -} - -void -embedg_mark_minor_C (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, - int c, int x, int y, int w, int *path_v, int *path_e, - int nbr_v, boolean px_attached_high, boolean py_attached_high) -{ - int vv, p_x, p_y, u_x, u_y, u; - - vv = c + n; - p_x = path_v[0]; - p_y = path_v[nbr_v]; - /* - see embedg_iso_get_highest_x_y_path for the above - */ - - if (px_attached_high) - /* - mark the external face: - - from v^c to p_y if py_attached_high - - from v^c to y if !py_attached_high - - not too sure about that one.... - - from v^c to p_y: so vvin = 0, - in x's direction - */ - { - if (py_attached_high) - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, p_y, MARK_MINORS(n)); - else - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, y, MARK_MINORS(n)); - } - else - /* - symmetric case: - mark the external face from v^c to p_x: so vvin = 1, - in y's direction - */ - { - if (px_attached_high) - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, p_x, MARK_MINORS(n)); - else - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, x, MARK_MINORS(n)); - } - - /* - mark the edges (u, x), (u, y), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, x, &u_x, - MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, y, &u_y, - MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - /* - mark the tree path from v to min(u_x, u_y) - */ - u = u_x <= u_y ? u_x : u_y; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - finally, mark the x-y path, ie the vertices in path_v - and the edges in path_e - */ - embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor C p_x high %d\t p_y high %d\n", - px_attached_high, py_attached_high); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y); - ) -} - -void -embedg_mark_minor_D (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, - int c, int x, int y, int w, int *path_v, int *path_e, - int nbr_v, int entry_in_path_e) -{ - int i, vv, p_x, p_y, u_x, u_y, u; - - vv = c + n; - p_x = path_v[0]; - p_y = path_v[nbr_v]; - /* - see embedg_iso_get_highest_x_y_path for the above - */ - - /* - mark the lower external face from x to y: we can walk in - either direction - */ - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - x, y, MARK_MINORS(n)); - - /* - mark the internal path which goes from the x-y path to v - - since I haven't stored those vertices/edges I assume - that a proper face walk should suffice - - BUT a walk that say starts at p_x and ends at vv, - that is, a walk starting at path_e[1] entered from entry_in_path_e - (recall that path_e[0] is a dummy) - */ - embedg_VES_walk_mark_part_proper_face(embed_graph, n, - path_e[1], entry_in_path_e, - vv, MARK_MINORS(n)); - - /* - a note of caution here: - ALWAYS mark external/internal faces before adding any other edges: - since adding edges destroys the faces' consistency - (adding edges makes no sense of face since we are in a non-planar - situation) - */ - /* - mark the edges (u, x), (u, y), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, x, &u_x, - MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, y, &u_y, - MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - /* - mark the tree path from v to min(u_x, u_y) - */ - u = u_x <= u_y ? u_x : u_y; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - mark the x-y path, ie the vertices in path_v - and the edges in path_e - */ - embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor D\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y); - ) -} - - - - -minor -embedg_mark_minor_E (t_dlcl **dfs_tree, t_dlcl **back_edges, - t_ver_edge *embed_graph, int n, int *edge_pos, int v, - int c, int x, int y, int w, int *path_v, int *path_e, int nbr_v) - /* - while marking minor E return which of the minors we are dealing with - */ -{ - int vv, p_x, p_y, u_x, u_y, u_w, u, u_max, u_min; - - vv = c + n; - p_x = path_v[0]; - p_y = path_v[nbr_v]; - /* - see embedg_iso_get_highest_x_y_path for the above - */ - - if (!embedg_VES_is_ver_ext_active(embed_graph, n, v, w)) - /* - minor E1 case: we must find an ext. active z, distinct from w, - on the external face p_x..w..p_y - */ - { - int s, sin, cur, curin, z, u_z, u_xy; - - s = n; - /* - start searching at vv entered from 0 (in x's direction) - -- we MUST reach p_x - hopefully! :) - */ - cur = vv; - curin = 0; - while (s != p_x) - /* - first advance to p_x: we are sure of reaching it - */ - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, - cur, curin, - FALSE, 0, &s, &sin); - cur = s; - curin = sin; - } - - /* - continue the walk on the external face: - stop if either s is ext. active OR s == w - - we'll mark the lot later on - */ - while ( - !(embedg_VES_is_ver_ext_active(embed_graph, n, v, - s) - && s != p_x) - && s != w) - { - embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, - FALSE, 0, &s, &sin); - cur = s; - curin = sin; - } - /* - now we must decide which symmetry we are in - */ - if (embedg_VES_is_ver_ext_active(embed_graph, n, v, s)) - /* - z is between x and w (recall that w is NOT ext. active) - */ - { - z = s; - ASSERT(z != w); - - /* - mark the external face from v^c to y in x's direction - */ - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, y, MARK_MINORS(n)); - /* - add/mark dotted edge (u, y) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_xy, MARK_MINORS(n)); - } - else - /* - this is the symmetric case: must find z between w and p_y - */ - { - ASSERT(s == w); - embedg_VES_get_succ_ext_active_on_ext_face(embed_graph, n, - v, cur, curin, - FALSE, 0, - &s, &sin); - /* - and z is distinct from p_y! - */ - z = s; - ASSERT(z != p_y); - - /* - mark the external face from v^c to x in y's direction - */ - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, x, MARK_MINORS(n)); - /* - add/mark dotted edge (u, x) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_xy, MARK_MINORS(n)); - } - /* - now the marked bits which are common to both cases: - dotted edges (u, z), (v, w), the x-y path, - the tree path (v, min(u_xy, u_z)) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, z, &u_z, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, - MARK_MINORS(n)); - - u = u_z <= u_xy ? u_z : u_xy; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E1\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t z %d\t w %d\t p_x %d\t p_y %d\t u_xy %d\t u_z %d\n", - v, c, x, y, z, w, p_x, p_y, u_xy, u_z); - ) - - return MINOR_E1; - } - - /* - in all other cases we get u_x, u_y, u_w back - from the ext. active vertices x, y, w resp. - - again, I CANNOT embed these edges now since that would destroy - my external/internal faces - */ - - embedg_get_u_x(embed_graph, n, v, x, &u_x); - embedg_get_u_x(embed_graph, n, v, y, &u_y); - embedg_get_u_x(embed_graph, n, v, w, &u_w); - - if (u_w > u_x && u_w > u_y) - /* - minor E2 case: - we mark the whole external face rooted by v^c - and the tree path (v, min(u_x, u_y)) - */ - { - embedg_VES_walk_mark_ext_face(embed_graph, n, vv, - MARK_MINORS(n)); - u = u_x <= u_y ? u_x : u_y; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y) & (u, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E2\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E2; - } - - /* - two more things common to all remaining cases: - - mark the dotted edge (v, w) (but we MUST do that later) - - mark the x-y path - */ - embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, - MARK_MINORS(n)); - - if (u_x < u_y && u_w < u_y) - /* - minor E3 case: one of the symmetric cases: - the external face rooted at v_c from vv to x (in x's direction) - the external face rooted at v_c from y to w (in y's direction) - the (v, min(u_w, u_x)) tree path - */ - { - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, p_x, MARK_MINORS(n)); - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - y, w, MARK_MINORS(n)); - - u = u_x <= u_w ? u_x : u_w; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y), (u, w), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E3/a\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E3; - } - if (u_y < u_x && u_w < u_x) - /* - minor E3 case: the other symmetric case: - the external face rooted at v_c from vv to y (in y's direction) - the external face rooted at v_c from x to w (in x's direction) - the (v, min(u_w, u_y)) tree path - */ - { - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, p_y, MARK_MINORS(n)); - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - x, w, MARK_MINORS(n)); - - u = u_y <= u_w ? u_y : u_w; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y), (u, w), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E3/b\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E3; - } - - if (p_x != x) - /* - minor E4 case: one of the symmetric cases: - the external face rooted at v_c from vv to w (in x's direction) - the external face rooted at v_c from vv to p_y (in y's direction) - the tree path from max(u_x, u_y, u_w) to min(u_x, u_y, u_w) - */ - { - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, w, MARK_MINORS(n)); - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, p_y, MARK_MINORS(n)); - - u_max = u_x > u_y ? u_x : u_y; - u_max = u_max > u_w ? u_max : u_w; - u_min = u_x < u_y ? u_x : u_y; - u_min = u_min < u_w ? u_min : u_w; - embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y), (u, w), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E4/a\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E4; - } - if (p_y != y) - /* - minor E4 case: the other symmetric case: - the external face rooted at v_c from vv to w (in y's direction) - the external face rooted at v_c from vv to x (in x's direction) - (here p_x = x!) - the tree path from max(u_x, u_y, u_w) to min(u_x, u_y, u_w) - */ - { - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, - vv, w, MARK_MINORS(n)); - embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, - vv, x, MARK_MINORS(n)); - - u_max = u_x > u_y ? u_x : u_y; - u_max = u_max > u_w ? u_max : u_w; - u_min = u_x < u_y ? u_x : u_y; - u_min = u_min < u_w ? u_min : u_w; - embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y), (u, w), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E$/b\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E4; - } - - /* - this is the last case for minor E: when the homeomorph is K5 - - mark the whole external face rooted at v^c - mark the tree path from v to min(u_x, u_y, u_w) - */ - - embedg_VES_walk_mark_ext_face(embed_graph, n, vv, MARK_MINORS(n)); - - u = u_x < u_y ? u_x : u_y; - u = u < u_w ? u : u_w; - embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); - - /* - embed dotted edges (u, x), (u, y), (u, w), (v, w) - */ - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, x, &u_x, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, y, &u_y, MARK_MINORS(n)); - embedg_add_mark_u_x(dfs_tree, back_edges, - embed_graph, n, edge_pos, - v, w, &u_w, MARK_MINORS(n)); - embedg_add_mark_v_w(dfs_tree, back_edges, - embed_graph, n, edge_pos, v, w, - MARK_MINORS(n)); - - IF_DEB( - fprintf(stdout, "mark minor E5\n"); - fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", - v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); - ) - - return MINOR_E5; -} -/* - * proper_face_walk.c - */ - -/* - What: - ***** - - Implementing a proper face walk within the VES structure. - This is obviously not the same as an external face walk, - but is simply the standard face walk in a planar embedding. - - Not much to say, if only to emphasize that for our - purposes here we assume: - - 1. the short-cut edges have been removed from the VES structure - 2. each vertex/edge has been given its orientation - 2. the adjacency lists (vertex + plus its incident edges) - are consistent: (and this is IMPORTANT) - that is, the way to traverse an adj. list (ie what - constitute previous and next in the list which actually - is a planar embedding at this stage) is indicated - by the vertex/edge's orientation - - - try to explain this better another time.... sorry... - - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - from - - Simplified O(n) Planarity Algorithms (draft) - ************************************ - - John Boyer JBoyer@PureEdge.com, jboyer@acm.org - Wendy Myrvold wendym@csr.uvic.ca - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++ - authors: - ******** - - Paulette Lieby (Magma), Brendan McKay (ANU) - - Started October 2001 -*/ - - -#include "planarity.h" - -#define IF_DEB(x) {} -#define IF_DEB_PROPER_FACE(x) {} -#define IF_VERB(x) {} - - - -/* aproto: header embed_graph_protos.h */ - - - - -boolean -embedg_VES_get_succ_on_proper_face_with_avoidance (t_ver_edge *embed_graph, - int n, int e, int ein, int a, boolean MARK, int mark, int *s, - int *next_e, int *next_ein) - /* - find the successor s of embed_graph[e].neighbour - (entered via ein) on a proper face traversal - which avoids (the vertex) a if a != n - - also returns the edge next_e such that - embed_graph[next_e].neighbour = s (to allow for continuation - of the walk) - - assumes that short-cut edges have been removed and that each - edge/vertex has been given its orientation - - and (more importantly) assumes that adjacency lists are consistent - - this function has been written especially to retrieve the highest - x-y path for the isolator; - (see embedg_iso_get_highest_x_y_path) - but as I discovered later (when marking an internal face - as for minor D) this function is general purpose - - PLUS: return true if the proper face walk has to skip an edge - incident to a (ie had to "avoid" a) - - PLUS: mark s & next_e if so requested - */ -{ - int eout; - int twin, twinout; - boolean avoid_a; - - ASSERT(embedg_VES_is_edge(n, e)); - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); - - IF_DEB( - fprintf(stdout, "get_succ_on_proper_face, \n"); - ) - - avoid_a = FALSE; - /* - find the direction out of the edge - */ - eout = 1 ^ ein; - - /* - get the twin edge - */ - twin = embedg_VES_get_twin_edge(embed_graph, n, e); - - /* - for each edge we must set the way to get to the next - in the adjacency list: - adjacency lists are traversed according to the vertex/edges - orientation (one unique orientation per list of course) - */ - if (embed_graph[e].sign != embed_graph[twin].sign) - /* - invert traversal - */ - { - twinout = 1 ^ eout; - } - else - /* - traversal is identical - */ - { - twinout = eout; - } - - /* - now, we want the edge previous to twin in twin's adjacency list, - ie link[1 ^ twinout] - */ - *next_e = embed_graph[twin].link[1 ^ twinout]; - /* - next_e could be a vertex, I need an edge - */ - if (embedg_VES_is_vertex(n, *next_e) - || embedg_VES_is_virtual_vertex(n, *next_e)) - /* - at this stage all virtual vertices should have - been disabled BUT the vertices rooting the bicomps!!! - */ - { - *next_e = embed_graph[*next_e].link[1 ^ twinout]; - } - ASSERT(embedg_VES_is_edge(n, *next_e)); - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); - *s = embed_graph[*next_e].neighbour; - - if (*s == a) - /* - want to avoid this vertex, so must get yet previous - edge in adjacency list - */ - { - avoid_a = TRUE; - - *next_e = embed_graph[*next_e].link[1 ^ twinout]; - if (embedg_VES_is_vertex(n, *next_e) - || embedg_VES_is_virtual_vertex(n, *next_e)) - { - *next_e = embed_graph[*next_e].link[1 ^ twinout]; - } - ASSERT(embedg_VES_is_edge(n, *next_e)); - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); - } - *s = embed_graph[*next_e].neighbour; - ASSERT(*s != a); - - /* - finally (again, because lists are consistent) - */ - *next_ein = 1 ^ twinout; - - /* - now mark s and next_e if required - */ - if (MARK) - { - embed_graph[*s].visited = - embed_graph[*next_e].visited = mark; - /* - ouuh... must mark the twin as well.... - but ONLY when we mark the minors.... - that is poor design, can we do better???? - -- don't think so... - - (when we mark when counting the faces, we MUST only - mark the edge and NOT its twin) - */ - if (mark == MARK_MINORS(n)) - { - twin = - embedg_VES_get_twin_edge(embed_graph, n, *next_e); - embed_graph[twin].visited = mark; - } - } - - return avoid_a; -} - - - -void -embedg_VES_get_succ_on_proper_face (t_ver_edge *embed_graph, int n, int e, - int ein, int MARK, int mark, int *s, int *next_e, int *next_ein) - /* - same as above but without avoidance - */ -{ - boolean avoid; - - avoid = - embedg_VES_get_succ_on_proper_face_with_avoidance( - embed_graph, n, - e, ein, n, - MARK, mark, - s, next_e, next_ein); - ASSERT(avoid == FALSE); -} - - -void -embedg_VES_walk_proper_face (t_ver_edge *embed_graph, int n, int e, - int ein, boolean MARK, int mark) - /* - traversing a proper face starting at edge e which has been entered - via ein - - -- we mark the visited edges with mark if so requested - - assumes that short-cut edges have been removed and that each - edge/vertex has been given its orientation - */ -{ - int s, cur_e, cur_ein, next_e, next_ein; - - next_e = n; /* this is an invalid value for an edge */ - - IF_DEB_PROPER_FACE( - fprintf(stdout, "proper face traversal\n"); - ) - - cur_e = e; - cur_ein = ein; - while (next_e != e) - { - ASSERT(embedg_VES_is_edge(n, cur_e)); - ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, - n, cur_e)); - IF_DEB_PROPER_FACE( - embedg_VES_print_edge(embed_graph, n, cur_e); - ) - - embedg_VES_get_succ_on_proper_face(embed_graph, n, - cur_e, cur_ein, - MARK, mark, - &s, &next_e, &next_ein); - cur_e = next_e; - cur_ein = next_ein; - } - - /* - note that by doing so we would have marked e and the first of e's - endpoints since by exiting the loop e = next_e and s is the - actual starting vertex of the walk - */ -} - - - - - +/* planarity.c - code for planarity testing of undirected graphs. + * Method of Boyer and Myrvold, programmed by Paulette Lieby. + * The copyright of this program is owned by the Magma project. + * Distributed with nauty by permission. + ***************************************************************/ + +/* + * sparseg_adjl.c + */ + +/* + What: + ***** + + Implementing: + + Some high-level functions on the sparse graph as + an adjacency list. + In particular, testing if it is planar. + + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + +boolean +sparseg_adjl_plan_and_iso (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, + int e, int *c, t_ver_sparse_rep **VR, t_adjl_sparse_rep **AR, + t_embed_sparse_rep **ER, int *nbr_e_obs) + /* + the input graph is given as an adjacency list: + V: array of vertices + n: size of graph + A: adjacency list + e: number of edges + + if the graph is planar the embedding is stored in VR and ER; + the embedding contains e edges + (nbr_e_obs not used) + + if the graph is non planar the obstruction is returned in + VR and AR together with the number of edges in nbr_e_obs + + in all cases is also returned the number of components (in c) + */ +{ + t_dlcl **dfs_tree, **back_edges, **mult_edges; + int edge_pos, v, w; + boolean ans; + t_ver_edge *embed_graph; + + ans = sparseg_adjl_is_planar(V, n, A, c, + &dfs_tree, &back_edges, &mult_edges, + &embed_graph, &edge_pos, &v, &w); + + if (!ans) + { + embedg_obstruction(V, A, dfs_tree, back_edges, + embed_graph, n, &edge_pos, + v, w, VR, AR, nbr_e_obs); + } + else + { + embedg_embedding(V, A, embed_graph, n, e, *c, edge_pos, mult_edges, + VR, ER); + } + + sparseg_dlcl_delete(dfs_tree, n); + sparseg_dlcl_delete(back_edges, n); + sparseg_dlcl_delete(mult_edges, n); + embedg_VES_delete(embed_graph, n); + + return ans; +} + + + +int * +sparseg_adjl_footprint (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int v) + /* + return v's footprint: + an array fp of size n where fp[i] = index of (directed) + edge [v, i] in A + */ +{ + /* + note that we won't initialise the array: + its subsequent usage doesn't require it + */ + int *fp, e; + + fp = (int *) mem_malloc(sizeof(int) * n); + + if (V[v].first_edge == NIL) + /* + do nothing + */ + return fp; + + e = V[v].first_edge; + while (e != NIL) + { + fp[A[e].end_vertex] = e; + e = A[e].next; + } + + return fp; +} + + +void +sparseg_adjl_print (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, boolean user_level) +{ + int v; + + for (v = 0; v < n; v++) + { + int next; + + if (user_level) + fprintf(stdout, "%d:\t", v + 1); + else + fprintf(stdout, "%d:\t", v); + + next = V[v].first_edge; + while (next != NIL) + { + if (user_level) + fprintf(stdout, "%d ", A[next].end_vertex + 1); + else + fprintf(stdout, "%d ", A[next].end_vertex); + + next = A[next].next; + } + fprintf(stdout, "\n"); + } +} + + + + +void +sparseg_adjl_embed_print (t_ver_sparse_rep *V_e, int n, + t_adjl_sparse_rep *A, t_embed_sparse_rep *E, boolean user_level) + /* + print the embedding given by E, + edges are referred to by their index in A + + and V_e[v].first_edge is the index in E of the first edge + (in the embedding's order) incident from v + + note that E is NOT indexed by the same vertices' array + that indexes A (at the creation of the sparse graph) + */ +{ + int v; + + for (v = 0; v < n; v++) + { + int start, next; + + if (user_level) + fprintf(stdout, "%d:\t", v + 1); + else + fprintf(stdout, "%d:\t", v); + + if (V_e[v].first_edge == NIL) + { + fprintf(stdout, "\n"); + continue; + } + start = next = V_e[v].first_edge; + + if (user_level) + fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex + 1); + else + fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex); + + next = E[next].next; + + while (next != start) + /* + recall that in E edges are linked into a circular list + */ + { + if (user_level) + fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex + 1); + else + fprintf(stdout, "%d ", A[ E[next].in_adjl ].end_vertex); + + next = E[next].next; + } + fprintf(stdout, "\n"); + } +} + +graph * +sparseg_adjl_to_nauty_graph (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A) + /* + write the sparse graph as a nauty graph + */ +{ + int m, v, e, i; + graph *g; + + m = (n + WORDSIZE - 1) / WORDSIZE; + g = (graph *) mem_malloc(n * m * sizeof(graph)); + for (i = (long) m * n; --i >= 0;) + g[i] = 0; + + /* + we first copy V and A's information into g + */ + for (v = 0; v < n; v++) + { + e = V[v].first_edge; + while (e != NIL) + /* + A[e].end_vertex is the next neighbour in the list, + A[e].next points to the next edge in the list + */ + { + if (A[e].end_vertex != v) /* no loops */ + { + ADDELEMENT(GRAPHROW(g, v, m), A[e].end_vertex); + } + e = A[e].next; + } + } + + return g; +} + + + +#if 0 +t_edge_sparse_rep * +sparseg_adjl_edges (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int e, boolean digraph) + /* + e is the number of edges + */ +{ + t_edge_sparse_rep *edges; + int m, u, v, pos_e; + graph *g; + + edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); + + m = (n + WORDSIZE - 1) / WORDSIZE; + g = sparseg_adjl_to_nauty_graph(V, n, A); + + pos_e = 0; + for (u = 0; u < n; u++) + { + v = digraph == TRUE ? 0 : u + 1; + for (; v < n; v++) + { + if (ISELEMENT(GRAPHROW(g, u, m), v)) + { + t_edge_sparse_rep edge; + + edge.ends[0] = u; + edge.ends[1] = v; + edges[pos_e++] = edge; + } + } + } + ASSERT(pos_e == e); + mem_free(g); + + return edges; +} +#endif + + + +t_edge_sparse_rep * +sparseg_adjl_edges (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, + int e, boolean digraph) + /* + e is the number of edges + */ +{ +#if 0 + t_edge_sparse_rep *edges; + int u, v, pos_e, *loops, *foot_print; + graph *g; + + loops = (int *) mem_malloc(sizeof(int) * n); + for (v = 0; v < n; v++) + { + loops[v] = 0; + } + + edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); + pos_e = 0; + + foot_print = (int *) mem_malloc(sizeof(int) * n); + for (u = 0; u < n; u++) + foot_print[u] = NIL; + + for (v = 0; v < n; v++) + { + int ne; + t_edge_sparse_rep edge; + + ne = V[v].first_edge; + while (ne != NIL) + { + u = A[ne].end_vertex; + if (digraph + || (!digraph && u > v)) + { + foot_print[u] = v; + } + else if (!digraph && u == v) + { + if (loops[v] == 0) + { + foot_print[u] = v; + } + + loops[v] ^= 1; + } + + ne = A[ne].next; + } + + for (u = 0; u < n; u++) + if (foot_print[u] == v) + { + edge.ends[0] = v; + edge.ends[1] = u; + edges[pos_e++] = edge; + } + } + ASSERT(pos_e == e); + mem_free(loops); + mem_free(foot_print); + + return edges; + +#endif + /* + there must be a simpler way + */ +#if 0 + typedef struct edge_list { + int size; + t_edge_sparse_rep *edges; + } t_edge_list; + + t_edge_list *edge_table; + t_edge_sparse_rep *edges; + int u, v, nbr_e, pos_e, *loops; + graph *g; + + loops = (int *) mem_malloc(sizeof(int) * n); + for (v = 0; v < n; v++) + { + loops[v] = 0; + } + + /* + now create an edge table as follows: + - there are n lists in total + - their respective size is given by size + - their contents by *edges: + + edge_table[i] will contain all the edges whose end-point is i: + these edges, by construction, will be sorted according to their + starting point + + what for? to finish off each start-vertex processing + with a bucket sort so that + the edges are sorted wrt start- & end-point + + bucket sort is linear, hence why... + */ + edge_table = (t_edge_list *) mem_malloc(sizeof(t_edge_list) * n); + for (v = 0; v < n; v++) + { + edge_table[v].size = 0; + edge_table[v].edges = NP; + } + + edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); + + nbr_e = 0; + pos_e = 0; + for (v = 0; v < n; v++) + { + int ne, w, u; + + ne = V[v].first_edge; + while (ne != NIL) + { + u = A[ne].end_vertex; + if (digraph + || (!digraph && u > v)) + { + t_edge_sparse_rep edge; + + edge.ends[0] = v; + edge.ends[1] = u; + + /* + now stick this edge into the table: one may ponder + as to the cost of constantly reallocating memory... + some cursory tests in another context tell me that + this is pretty much ok + (and certainly better than allocating n^2 storage space) + */ + if (edge_table[u].size == 0) + { + edge_table[u].edges = (t_edge_sparse_rep *) + mem_malloc(sizeof(t_edge_sparse_rep)); + } + else + { + edge_table[u].edges = (t_edge_sparse_rep *) + mem_realloc(edge_table[u].edges, + sizeof(t_edge_sparse_rep) + * (edge_table[u].size + 1)); + } + + (edge_table[u].edges)[edge_table[u].size] = edge; + edge_table[u].size += 1; + nbr_e++; + } + else if (!digraph && u == v) + { + if (loops[v] == 0) + { + t_edge_sparse_rep edge; + + edge.ends[0] = v; + edge.ends[1] = u; + + if (edge_table[u].size == 0) + { + edge_table[u].edges = (t_edge_sparse_rep *) + mem_malloc(sizeof(t_edge_sparse_rep)); + } + else + { + edge_table[u].edges = (t_edge_sparse_rep *) + mem_realloc(edge_table[u].edges, + sizeof(t_edge_sparse_rep) + * (edge_table[u].size + 1)); + } + + (edge_table[u].edges)[edge_table[u].size] = edge; + edge_table[u].size += 1; + nbr_e++; + } + + loops[v] ^= 1; + } + + ne = A[ne].next; + } + + /* + bucket sort must take place here: + of course the whole lot is not exactly linear! + since we perform the sort n times; but we can hope for + a "good" ?? average behaviour: + + in any case this must be better that checking adjacencies + n^2 times in a sparse rep. (see edge_set_iset_assure) + */ + for (w = 0; w < n; w++) + { + if (edge_table[w].size > 0) + { + for (u = 0; u < edge_table[w].size; u++) + { + ASSERT((edge_table[w].edges)[u].ends[0] == v); + edges[pos_e++] = (edge_table[w].edges)[u]; + } + mem_free(edge_table[w].edges); + edge_table[w].size = 0; + edge_table[w].edges = NP; + } + } + } + ASSERT(nbr_e == e); + ASSERT(pos_e == e); + mem_free(loops); + mem_free(edge_table); + + return edges; +#endif + + t_edge_sparse_rep *edges; + int v, pos_e, *loops; + + edges = (t_edge_sparse_rep *) mem_malloc(sizeof(t_edge_sparse_rep) * e); + loops = (int *) mem_malloc(sizeof(int) * n); + for (v = 0; v < n; v++) + { + loops[v] = 0; + } + + pos_e = 0; + for (v = 0; v < n; v++) + { + int ne; + + ne = V[v].first_edge; + while (ne != NIL) + { + int u; + + u = A[ne].end_vertex; + if (digraph + || (!digraph && u > v)) + { + t_edge_sparse_rep edge; + + edge.ends[0] = v; + edge.ends[1] = u; + edges[pos_e++] = edge; + } + else if (!digraph && u == v) + { + if (loops[v] == 0) + { + t_edge_sparse_rep edge; + + edge.ends[0] = v; + edge.ends[1] = u; + edges[pos_e++] = edge; + } + + loops[v] ^= 1; + } + ne = A[ne].next; + } + } + ASSERT(pos_e == e); + mem_free(loops); + + return edges; + +} + +/* + * sparseg_adjl_modify.c + */ + +/* + What: + ***** + + Implementing: + + Some high-level functions on the sparse graph as + an adjacency list. + In particular, adding/removing vertices/edges. + + + NOTE: Most of the functions implicitely assume that the + graph is undirected; + this must be slightly rewritten for the general case + -- just haven't got the time right now... + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + + + +#ifndef PLANAR_IN_MAGMA +#endif + + + +boolean +sparseg_adjl_add_edge (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep **A, + int *size_A, int *pos, int u, int v, boolean CHECK) + /* + add the UNDIRECTED edge to the sparse graph (V, n, A) + - pos records where to add the next edge in A + - if pos + 1 == size_A, we must extend A + + we check if the edge is already in the graph iff CHECK true + + also we assume that the graph (V, n, A) is undirected + */ +{ + boolean edge_exists; + + edge_exists = FALSE; + if (CHECK) + { + edge_exists = sparseg_adjl_dir_edge_exists(V, n, *A, u, v); + + if (edge_exists) + return FALSE; + } + + if (*pos == *size_A) + { + IF_DEB( + fprintf(stdout, "realloc \n"); + ) + + *size_A += 2; /* add two directed edges */ + *A = (t_adjl_sparse_rep *) + mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); + } + else if (*pos + 1 == *size_A) + { + IF_DEB( + fprintf(stdout, "realloc \n"); + ) + + *size_A += 1; /* add two directed edges */ + *A = (t_adjl_sparse_rep *) + mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); + } + ASSERT(*pos + 1 < *size_A); + + sparseg_adjl_add_dir_edge(V, n, A, size_A, pos, u, v, FALSE); + sparseg_adjl_add_dir_edge(V, n, A, size_A, pos, v, u, FALSE); + + return TRUE; +} + +boolean +sparseg_adjl_add_edge_no_extend (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int size_A, int *pos, int u, int v, boolean CHECK) + /* + like sparseg_adjl_add_edge but here we are guaranteed + that pos + 1 < size_A + (unless that for some reason we attempt to add + an edge which is already there) + + this feature is required when A is part of a Magma block: + we do not want to reallocate A here + (would be done at a higher level) + + we check if the edge is already in the graph iff CHECK true + + also, we assume that we use this procedur only when dealing + with an undirected graph + */ +{ + boolean edge_added; + + edge_added = + sparseg_adjl_add_dir_edge_no_extend(V, n, A, size_A, pos, u, v, + CHECK); + + if (edge_added) + sparseg_adjl_add_dir_edge_no_extend(V, n, A, size_A, pos, v, u, + FALSE); + + return edge_added; +} + + +boolean +sparseg_adjl_add_dir_edge (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep **A, int *size_A, int *pos, int u, int v, + boolean CHECK) + /* + add the DIRECTED edge to the sparse graph (V, n, A) + - pos records where to add the next edge in A + - if pos >= size_A, we must extend A + + we check if the edge is already in the graph iff CHECK true + */ +{ + boolean edge_exists; + + edge_exists = FALSE; + if (CHECK) + { + edge_exists = sparseg_adjl_dir_edge_exists(V, n, *A, u, v); + + if (edge_exists) + return FALSE; + } + + if (*pos == *size_A) + { + *size_A += 1; /* add one directed edge */ + *A = (t_adjl_sparse_rep *) + mem_realloc(*A, sizeof(t_adjl_sparse_rep) * *size_A); + } + ASSERT(*pos < *size_A); + + sparseg_adjl_add_dir_edge_no_extend(V, n, *A, *size_A, pos, u, v, + FALSE); + + return TRUE; +} + +boolean +sparseg_adjl_add_dir_edge_no_extend (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int size_A, int *pos, int u, int v, boolean CHECK) + /* + add an edge where A is guaranteed to be be big enough + (unless that for some reason we attempt to add + an edge which is already there) + + this feature is required when A is part of a Magma block: + we do not want to reallocate A here + (would be done at a higher level) + + we check if the edge is already in the graph iff CHECK true + */ +{ + /* + given the way V and A represent the graph, it is simplest + to add the new edge at the beginning of i's adj. list + */ + int i_v; + t_adjl_sparse_rep a; + + if (CHECK && sparseg_adjl_dir_edge_exists(V, n, A, u, v)) + return FALSE; + + if (*pos >= size_A) + DIE(); + + /* + otherwise always add the edge + */ + i_v = *pos; + a.end_vertex = v; + a.next = V[u].first_edge; + A[(*pos)++] = a; + V[u].first_edge = i_v; + + return TRUE; +} + + + +boolean +sparseg_adjl_remove_edge_no_red (t_ver_sparse_rep *V, t_adjl_sparse_rep *A, + int u, int v) + /* + remove the UNDIRECTED edge from sparse graph (V, A) + if (u, v) is not an edge then nothing changes (and return FALSE) + + A will be left with "holes" + */ +{ + sparseg_adjl_remove_dir_edge_no_red(V, A, u, v); + return sparseg_adjl_remove_dir_edge_no_red(V, A, v, u); +} + + +boolean +sparseg_adjl_remove_dir_edge_no_red (t_ver_sparse_rep *V, + t_adjl_sparse_rep *A, int u, int v) + /* + remove the DIRECTED edge from the sparse graph (V, n, A) + if (u, v) is not an edge then nothing changes (and return FALSE) + + A will be left with "holes" + */ +{ + int cur_e, prev_e; + + cur_e = V[u].first_edge; + if (cur_e == NIL) + /* + (u, v) is not an edge + */ + return FALSE; + + if (A[cur_e].end_vertex == v) + { + V[u].first_edge = A[cur_e].next; + return TRUE; /* done */ + } + + while (A[cur_e].end_vertex != v) + /* + if (u, v) is an edge then this loop will terminate + */ + { + prev_e = cur_e; + cur_e = A[cur_e].next; + if (cur_e == NIL) + /* + (u, v) is not an edge + */ + return FALSE; + } + ASSERT(A[cur_e].end_vertex == v); + + A[prev_e].next = A[cur_e].next; + return TRUE; +} + +int +sparseg_adjl_remove_all_dir_edge_no_red (t_ver_sparse_rep *V, + t_adjl_sparse_rep *A, int u, int v) + /* + remove all DIRECTED edges [u, v] from the non-simple + sparse graph (V, n, A) + if (u, v) is not an edge then nothing changes; + we return the number of edges removed + + A will be left with "holes" + */ +{ + int cur_e, prev_e, e_removed; + + if (V[u].first_edge == NIL) + /* + (u, v) is not an edge + */ + return 0; + + e_removed = 0; + while (A[V[u].first_edge].end_vertex == v) + { + V[u].first_edge = A[V[u].first_edge].next; + e_removed++; + + if (V[u].first_edge == NIL) + return e_removed; + } + ASSERT(A[V[u].first_edge].end_vertex != v); + + prev_e = V[u].first_edge; + cur_e = A[prev_e].next; + while (cur_e != NIL) + { + if (A[cur_e].end_vertex == v) + { + A[prev_e].next = A[cur_e].next; + e_removed++; + cur_e = A[cur_e].next; + } + else + { + prev_e = cur_e; + cur_e = A[cur_e].next; + } + } + + return e_removed; +} + + + +void +sparseg_adjl_add_vertices (t_ver_sparse_rep **V, int n, int nmore) + /* + add nmore vertices + V is assumed to have length n + */ +{ + *V = (t_ver_sparse_rep *) + mem_realloc(*V, sizeof(t_ver_sparse_rep) * (n + nmore)); + + sparseg_adjl_add_vertices_no_extend(*V, n, nmore); +} + +void +sparseg_adjl_add_vertices_no_extend (t_ver_sparse_rep *V, int n, int nmore) + /* + add nmore vertices, + here V is assumed to have length n + nmore (ie V has already + been made bigger) + */ +{ + int v; + + for (v = n; v < n + nmore; v++) + { + V[v].first_edge = NIL; + } +} + +void +sparseg_adjl_remove_vertex (t_ver_sparse_rep **V, int n, + t_adjl_sparse_rep *A, int pos_A, int w, int *e) + /* + V is assumed to have length n: we will reallocate + V so that V will have length n-1 + + A is occupied from [0..pos-1], A will be left with holes + + we also assume that the graph can have loops and multiple edges; + further, we the edge counting implicitely assumes that graph + is undirected!!! + + this must be eventually fixed + */ +{ + int v, nv, edge, loops; + t_ver_sparse_rep *new_V; + + /* + we first count the loops if any + */ + loops = 0; + edge = (*V)[w].first_edge; + while (edge != NIL) + { + loops = A[edge].end_vertex == w ? loops + 1 : loops; + edge = A[edge].next; + } + ASSERT(loops % 2 == 0); + loops /= 2; + + /* + we recreate the vertices array + */ + new_V = (t_ver_sparse_rep *) + mem_malloc(sizeof(t_ver_sparse_rep) * (n - 1)); + + for (v = 0, nv = 0; v < n; v++, nv++) + { + if (v == w) + { + nv--; + } + else + { + new_V[nv].first_edge = (*V)[v].first_edge; + } + } + mem_free(*V); + *V = new_V; + + *e -= loops; + sparseg_adjl_remove_vertex_no_red(*V, n, A, w, e); + + /* + oops! not relabelling vertices can wreck havock! + */ + sparseg_adjl_relabel_vertex(A, pos_A, w); +} + +void +sparseg_adjl_remove_vertex_no_red (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int w, int *e) + /* + here V has already size n - 1 and has been initialised, + all what remains to do is to remove the edges incident + from w in A + + A will be left with holes + */ +{ + int v, nbr_e_removed; + + nbr_e_removed = 0; + for (v = 0; v < n - 1; v++) + { + nbr_e_removed += sparseg_adjl_remove_all_dir_edge_no_red(V, A, v, w); + } + + *e= *e - nbr_e_removed; +} + +void +sparseg_adjl_relabel_vertex (t_adjl_sparse_rep *A, int pos, int u) + /* + relabel all vertices v > u as v-1 + (required when removing a vertex) + */ +{ + int i; + + for (i = 0; i < pos; i++) + { + A[i].end_vertex = A[i].end_vertex > u ? + A[i].end_vertex - 1 : A[i].end_vertex; + } +} + +/* + * sparseg_adjl_pred.c + */ + +/* + What: + ***** + + Implementing: + + Some high-level functions on the sparse graph as + an adjacency list: predicates. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + +boolean +sparseg_adjl_dir_edge_exists (t_ver_sparse_rep *V, int n, + t_adjl_sparse_rep *A, int u, int v) + /* + does the directed edge [u, v] already exist in the graph + */ +{ + int cur_e, prev_e; + + cur_e = V[u].first_edge; + if (cur_e == NIL) + return FALSE; + + if (A[cur_e].end_vertex == v) + { + return TRUE; + } + + while (A[cur_e].end_vertex != v) + { + prev_e = cur_e; + cur_e = A[cur_e].next; + if (cur_e == NIL) + /* + (u, v) is not an edge + */ + return FALSE; + } + ASSERT(A[cur_e].end_vertex == v); + return TRUE; +} + + + +boolean +sparseg_adjl_u_adj_v (t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, + int u, int v) + /* + is u adj. to v + */ +{ + return sparseg_adjl_dir_edge_exists(V, n, A, u, v); +} + + +boolean +sparseg_adjl_sub (t_ver_sparse_rep *V1, int n1, t_adjl_sparse_rep *A1, + t_ver_sparse_rep *V2, int n2, t_adjl_sparse_rep *A2) + /* + test if the (V1, n1, A1) sparse graph is a subgraph of + the (V2, n2, A2) graph + */ +{ + int v, *fp, n, bign, i; + + n = n1 > n2 ? n2 : n1; + bign = n1 > n2 ? n1 : 0; + fp = (int *) mem_malloc(sizeof(int) * n); + for (i = 0; i < n; i++) + fp[i] = NIL; + + for (v = 0; v < n; v++) + { + int ne1, ne2; + + ne1 = V1[v].first_edge; + ne2 = V2[v].first_edge; + if (ne1 == NIL) + { + continue; + } + else if (ne2 == NIL) + { + mem_free(fp); + return FALSE; + } + + while (ne2 != NIL) + { + int u2; + + u2 = A2[ne2].end_vertex; + fp[u2] = v; + ne2 = A2[ne2].next; + } + + while (ne1 != NIL) + { + int u1; + + u1 = A1[ne1].end_vertex; + if (fp[u1] != v) + { + mem_free(fp); + return FALSE; + } + ne1 = A1[ne1].next; + } + } + mem_free(fp); + + for (v = n; v < bign; v++) + /* + those vertices must not be end points of edges: + this chcek is only necessary in the digraph case + */ + { + if (V1[v].first_edge != NIL) + return FALSE; + } + + return TRUE; +} + + + +boolean +sparseg_adjl_eq (t_ver_sparse_rep *V1, int n1, t_adjl_sparse_rep *A1, + t_ver_sparse_rep *V2, int n2, t_adjl_sparse_rep *A2) + /* + compare the two sparse graphs (V1, n1, A1) & (V2, n2, A2) + we don't know their number of edges + */ +{ + if (n1 != n2) + return FALSE; + + return sparseg_adjl_sub(V1, n1, A1, V2, n2, A2) + && sparseg_adjl_sub(V2, n2, A2, V1, n1, A1); +} + + + +/* + * sparseg_dlcl_misc.c + */ + +/* + What: + ***** + + Implementing: + + Housekeeping for an internal sparse graph representation + internal to the planarity tester and obstruction isolator. + + This sparse graph consists of an array of doubly linked circular lists + (the neighbour lists for each vertex). + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + +/* aproto: beginstatic -- don't touch this!! */ +static boolean sparseg_dlcl_is_present (t_dlcl *, int, t_dlcl **); +/* aproto: endstatic -- don't touch this!! */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + +void +sparseg_dlcl_delete (t_dlcl **g, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + embedg_dlcl_delete(g[i]); + } + mem_free(g); +} + +void +sparseg_dlcl_print (t_dlcl **g, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fprintf(stdout,"%d:\t", i); + embedg_dlcl_print(g[i]); + } +} + + +static boolean +sparseg_dlcl_is_present (t_dlcl *l, int label, t_dlcl **p) +{ + *p = embedg_dlcl_find(l, label); + return *p == NP ? FALSE : TRUE; +} + + +boolean +sparseg_dlcl_is_adjacent (t_dlcl **g, int n, int v, int u, t_dlcl **p) + /* + is u adjacent to v + */ +{ + ASSERT(v >= 0 && v < n && u >= 0 && u < n); + return sparseg_dlcl_is_present(g[v], u, p); +} + +void +sparseg_dlcl_append_to_neigh_list (t_dlcl **g, int n, int v, int u, int in_adjl) + /* + append u to the neighbour list of v + */ +{ + t_dlcl *u_rec; + + u_rec = embedg_dlcl_rec_new(u); + u_rec->in_adjl = in_adjl; + g[v] = embedg_dlcl_rec_append(g[v], u_rec); +} + + + + +void +sparseg_dlcl_to_sparseg (t_dlcl **g, int n, int e, + t_ver_sparse_rep **V, t_adjl_sparse_rep **A) + /* + e is the number of undirected edges of g + + convert a dlcl into the standard sparseg rep. as an + adjacency list + */ +{ + int i_e, v; + + *V = (t_ver_sparse_rep *) mem_malloc(sizeof(t_ver_sparse_rep) * n); + *A = (t_adjl_sparse_rep *) mem_malloc(sizeof(t_adjl_sparse_rep) * 2 * e); + + for (v = 0; v < n; v++) + (*V)[v].first_edge = NIL; + + i_e = 0; + for (v = 0; v < n; v++) + { + t_dlcl *l, *p; + + l = p = g[v]; + if (!embedg_dlcl_is_empty(p)) + { + t_adjl_sparse_rep a; + + ASSERT((*V)[v].first_edge == NIL); + (*V)[v].first_edge = i_e; + a.end_vertex = p->info; + a.next = i_e + 1; + (*A)[i_e++] = a; + + p = embedg_dlcl_list_next(p); + while (p != l) + { + a.end_vertex = p->info; + a.next = i_e + 1; + (*A)[i_e++] = a; + + p = embedg_dlcl_list_next(p); + } + + /* + end of list for v + */ + (*A)[i_e - 1].next = NIL; + } + } + ASSERT(i_e == 2 * e); +} + +boolean +sparseg_dlcl_sub (t_dlcl **g1, int n1, t_dlcl **g2, int n2) + /* + is g2 a subgraph of g1 + + I request that both graphs have same order + + This is not used anywhere... do we need it??? + */ +{ + int n, v, *fp; + + if (n1 != n2) + return FALSE; + + n = n1; + fp = (int *) mem_malloc(sizeof(int) * n); + for (v = 0; v < n; v++) + fp[v] = NIL; + + for (v = 0; v < n; v++) + { + t_dlcl *l1, *p1, *l2, *p2; + + l1 = p1 = g1[v]; + l2 = p2 = g2[v]; + if (embedg_dlcl_is_empty(p1) && !embedg_dlcl_is_empty(p2)) + { + mem_free(fp); + return FALSE; + } + if (embedg_dlcl_is_empty(p2)) + { + continue; + } + + fp[p1->info] = v; + p1 = embedg_dlcl_list_next(p1); + while (p1 != l1) + { + fp[p1->info] = v; + p1 = embedg_dlcl_list_next(p1); + } + + if (fp[p2->info] != v) + { + mem_free(fp); + return FALSE; + } + p2 = embedg_dlcl_list_next(p2); + while (p2 != l2) + { + if (fp[p2->info] != v) + { + mem_free(fp); + return FALSE; + } + } + } + mem_free(fp); + + return TRUE; +} +/* + * VES_misc.c + */ + +/* + What: + ***** + + Implementing: + + All low-level routines for the VES structure: + + - the VES structure is solely used within the planarity tester + and obstruction isolator + + - it stores vertices, virtual vertices and edges + --more on this later-- + + - it allows for circular doubly linked lists, hence + enabling us -among other things- to store the + graph embedding if the tester is successful + + - basic features: + + the VES has exactly size 2n + 2(3n-5) : + we add at most one more edge than the max for a planar graph + (need to x by 2: we store directed edges) + + a vertex and the edges incident FROM it are linked in a doubly + linked circular list + + where a vertex is inserted between two of its outcoming edges + determines an external face walk for a bicomponent + + the twin edge is more commonly known as the inverse edge + + we have tree and back edges (from the DFS), and short-cut edges + which are added by the tester + -but short-cut edges are added in such a way as to maintain + planarity (in a local sense) + + vertices and edges can be marked (visited for example) + + they have an orientation which must be eventuall recovered + and which is set in the merge_bicomp routine + + vertices are essentially known via their DFI or DFS index + (though their label is stored too) + + blah, blah.... later then. + Have a look at embedg_planar_alg_init which initialises the VES + structure + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_SCE(x) {} +#define IF_DEB_PROPER_FACE(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + +boolean +embedg_VES_is_vertex (int n, int i) + /* + is this a vertex + (relative to the "big" array of size 2n + 2(3n-5)) + */ +{ + return i < n ? TRUE : FALSE; +} + +boolean +embedg_VES_is_virtual_vertex (int n, int i) + /* + is this a virtual vertex + (relative to the "big" array of size 2n + 2(3n-5)) + + a virtual vertex is a vertex v^c which denotes the + DFS parent of the child c + + see embedg_planar_alg_init for more + */ +{ + return i >= n && i < 2*n ? TRUE : FALSE; +} + +boolean +embedg_VES_is_edge (int n, int i) + /* + is this an edge + (relative to the "big" array of size 2n + 2(3n-5)) + */ +{ + return i >= 2*n ? TRUE : FALSE; +} + +boolean +embedg_VES_is_tree_edge (t_ver_edge *embed_graph, int n, int i) + /* + is this s tree edge + */ +{ + return embedg_VES_is_edge(n, i) + && embed_graph[i].type == TE; +} + +boolean +embedg_VES_is_back_edge (t_ver_edge *embed_graph, int n, int i) + /* + is this a back edge + */ +{ + return embedg_VES_is_edge(n, i) + && embed_graph[i].type == BE; +} + +boolean +embedg_VES_is_short_cut_edge (t_ver_edge *embed_graph, int n, int i) + /* + as the name indicates... + */ +{ + return embedg_VES_is_edge(n, i) + && embed_graph[i].type == SCE; +} + +void +embedg_VES_print_vertex (int n, int v) +{ + ASSERT(embedg_VES_is_vertex(n, v)); + fprintf(stdout, "%d ", v); +} + +void +embedg_VES_print_virtual_vertex (t_ver_edge *embed_graph, int n, int v) +{ + int c; + + ASSERT(embedg_VES_is_virtual_vertex(n, v)); + c = v - n; + fprintf(stdout, "%d^%d ", embed_graph[c].DFS_parent, c); +} + +void +embedg_VES_print_any_vertex (t_ver_edge *embed_graph, int n, int v) +{ + if (embedg_VES_is_vertex(n, v)) + { + embedg_VES_print_vertex(n, v); + } + else + { + embedg_VES_print_virtual_vertex(embed_graph, n, v); + } +} + +void +embedg_VES_print_any_rec (t_ver_edge *embed_graph, int n, int r) +{ + if (embedg_VES_is_edge(n, r)) + { + embedg_VES_print_edge(embed_graph, n, r); + } + else + { + embedg_VES_print_any_vertex(embed_graph, n, r); + } +} + +void +embedg_VES_print_edge (t_ver_edge *embed_graph, int n, int e) +{ + int v, prev, cur; + + ASSERT(embedg_VES_is_edge(n, e)); + + /* + must find the vertex in the doubly linked circular list + of vertices/edges + */ + + prev = e; + cur = v = embed_graph[e].link[0]; + if (embedg_VES_is_vertex(n, v) + || embedg_VES_is_virtual_vertex(n, v)) + { + embedg_VES_print_any_vertex(embed_graph, n, v); + fprintf(stdout, ", "); + embedg_VES_print_any_vertex(embed_graph, n, + embed_graph[e].neighbour); + fprintf(stdout, "):0\n"); + } + else while (!embedg_VES_is_vertex(n, v) + && !embedg_VES_is_virtual_vertex(n, v)) + { + v = embedg_VES_get_next_in_dlcl(embed_graph, n, + cur, prev); + + if (embedg_VES_is_vertex(n, v) + || embedg_VES_is_virtual_vertex(n, v)) + { + embedg_VES_print_any_vertex(embed_graph, n, v); + fprintf(stdout, ", "); + embedg_VES_print_any_vertex(embed_graph, n, + embed_graph[e].neighbour); + fprintf(stdout, "):0\n"); + } + else + { + prev = cur; + cur = v; + } + } +} + +void +embedg_VES_print_flipped_edges (t_ver_edge *embed_graph, int n, int edge_pos) + /* + print those edges in the structure whose sign is CLOCKW, + ie which have been flipped at some stage + */ +{ + int e; + + for (e = 2*n; e <= edge_pos; e++) + { + if (!embedg_VES_is_short_cut_edge(embed_graph, n, e)) + /* + we don't care about the short-cut edges + */ + { + if (embed_graph[e].sign != CCLOCKW) + { + embedg_VES_print_edge(embed_graph, n, e); + } + } + } +} + +#if 0 +int +embedg_VES_get_edge_from_ver (t_ver_edge *embed_graph, int n, int v) + /* + not used anywhere; why is this here??? + */ +{ + int in, e; + + ASSERT(embedg_VES_is_vertex(n, v) + || embedg_VES_is_virtual_vertex(n, v)); + + in = embedg_VES_is_edge(n, embed_graph[v].link[0]) ? 0 : 1; + e = embed_graph[v].link[in]; + ASSERT(embedg_VES_is_edge(n, e)); + + return e; +} + +int +embedg_VES_get_ver_from_edge (t_ver_edge *embed_graph, int n, int e) +{ + int in, v; + + ASSERT(embedg_VES_is_edge(n, e)); + + in = embedg_VES_is_vertex(n, embed_graph[e].link[0]) + || embedg_VES_is_virtual_vertex(n, embed_graph[e].link[0]) + ? + 0 : 1; + + v = embed_graph[e].link[in]; + ASSERT(embedg_VES_is_vertex(n, v) + || embedg_VES_is_virtual_vertex(n, v)); + + return v; +} +#endif + +int +embedg_VES_get_twin_edge (t_ver_edge *embed_graph, int n, int e) + /* + the twin edge is understood as being the inverse edge + */ +{ + int twin; + + ASSERT(embedg_VES_is_edge(n, e)); + + twin = e % 2 == 0 ? e + 1 : e - 1; + ASSERT(embedg_VES_is_edge(n, twin)); + + return twin; +} + +int +embedg_VES_get_ver_from_virtual (t_ver_edge *embed_graph, int n, int vv) + /* + get v from the virtual vertex v^c + */ +{ + int v; + + ASSERT(embedg_VES_is_virtual_vertex(n, vv)); + v = embed_graph[vv - n].DFS_parent; + + return v; +} + +int +embedg_VES_get_ver (t_ver_edge *embed_graph, int n, int v) +{ + if (embedg_VES_is_virtual_vertex(n, v)) + return embedg_VES_get_ver_from_virtual(embed_graph, n, v); + + return v; +} + + +int +embedg_VES_get_next_in_dlcl (t_ver_edge *embed_graph, int n, int r, int prev) + /* + r is a (virtual) vertex or edge record in embed_graph: + get the next in the list (formed by the .link[] fields) + in the doubly linked circular list + + so that prev != next + -- NOTE: a priori these lists always contain 2 elts at least + so that there shouldn't be any problem... + --> huh? is that true? + */ +{ + return embed_graph[r].link[0] == prev ? + embed_graph[r].link[1] : embed_graph[r].link[0]; +} + + +void +embedg_VES_walk_bicomp (t_ver_edge *embed_graph, int n, int v, int vin) + /* + walk the external face of the bicomp starting + at VIRTUAL vertex v entered via vin + + this of course assumes that the "thing" rooted at + v is a bicomponent -- depending where we are at in the + tester this is not necessarily the case + -- I comment upon this in merge_bicomps.c: + embedg_VES_merge_pertinent_bicomps + */ +{ + int start, startin, s, sin; + + ASSERT(embedg_VES_is_virtual_vertex(n, v)); + + embedg_VES_print_virtual_vertex(embed_graph, n, v); + + s = NIL; + start = v; + startin = vin; + while (s != v) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, start, startin, + FALSE, 0, &s, &sin); + if (embedg_VES_is_virtual_vertex(n, s)) + { + embedg_VES_print_virtual_vertex(embed_graph, n, s); + } + else + { + embedg_VES_print_vertex(n, s); + } + start = s; + startin = sin; + } + fprintf(stdout, "\n"); +} + +void +embedg_VES_print_adj_list (t_ver_edge *embed_graph, int n, int r, + boolean consistent) + /* + print r's adjacency list - r can be a vertex or edge + + the boolean if true assumes that + the list is consistent (will determine the way we traverse the list) + + a priori we should get the same result either way + */ +{ + if (consistent) + { + int next; + + embedg_VES_print_any_rec(embed_graph, n, r); + + next = embed_graph[r].link[0]; + while (next != r) + { + embedg_VES_print_any_rec(embed_graph, n, next); + next = embed_graph[next].link[0]; + } + } + else + { + int prev, cur, next; + + embedg_VES_print_any_rec(embed_graph, n, r); + + prev = r; + cur = embed_graph[r].link[0]; + + while (cur != r) + { + embedg_VES_print_any_rec(embed_graph, n, cur); + next = embedg_VES_get_next_in_dlcl(embed_graph, n, + cur, prev); + prev = cur; + cur = next; + } + } +} + +boolean +embedg_VES_is_adj_list_consistent (t_ver_edge *embed_graph, int n, int r) + /* + checks that r's adjacency list is consistent: + ie, that either traversing it using link[0] always + or traversing it using embedg_VES_get_next_in_dlcl + gives the SAME result + */ +{ + int *list_link, *list_n_dldl, il, id, i; + + list_link = (int *) mem_malloc(sizeof(int) * 2 * n); + list_n_dldl = (int *) mem_malloc(sizeof(int) * 2 * n); + /* + must allocate 2*n space: I could have TE and SCE with same neighbour + (or BE and SCE as well) + */ + il = id = -1; + + /* + traversing the list via link[0] + */ + { + int next; + + list_link[++il] = r; + + next = embed_graph[r].link[0]; + while (next != r) + { + list_link[++il] = next; + next = embed_graph[next].link[0]; + } + } + + /* + traversing the list using embedg_VES_get_next_in_dlcl + */ + { + int prev, cur, next; + + list_n_dldl[++id] = r; + prev = r; + cur = embed_graph[r].link[0]; + + while (cur != r) + { + list_n_dldl[++id] = cur; + next = embedg_VES_get_next_in_dlcl(embed_graph, n, + cur, prev); + prev = cur; + cur = next; + } + } + + if (il != id) + { + mem_free(list_link); + mem_free(list_n_dldl); + return FALSE; + } + + for (i = 0; i <= il; i++) + { + if (list_link[i] != list_n_dldl[i]) + { + mem_free(list_link); + mem_free(list_n_dldl); + return FALSE; + } + } + + mem_free(list_link); + mem_free(list_n_dldl); + return TRUE; +} + + +boolean +embedg_VES_are_adj_lists_consistent (t_ver_edge *embed_graph, int n) + /* + checks that the adjacency list of each vertex is consistent + in the manner of embedg_VES_is_adj_list_consistent + */ +{ + int i; + + /* + it is enough to visit the vertices and virtual vertices only + (I don't think it is enough to do the vertices only --??) + */ + for (i = 0; i < 2*n; i++) + if (!embedg_VES_is_adj_list_consistent(embed_graph, n, i)) + return FALSE; + + return TRUE; +} + + + +void +embedg_VES_remove_edge (t_ver_edge *embed_graph, int n, int e) + /* + remove edge e from the embedding + */ +{ + int r1, r2, r1out, r2in, twin; + + ASSERT(embedg_VES_is_edge(n, e)); + + IF_DEB_SCE( + fprintf(stdout, "removing an SCE, enter\n"); + embedg_VES_print_edge(embed_graph, n, e); + ) + + r1 = embed_graph[e].link[0]; + r2 = embed_graph[e].link[1]; + + /* + disable e and link r1 and r2 together: + we had r1 -> e -> r2 + */ + embed_graph[e].link[0] = embed_graph[e].link[1] = e; + + r1out = embed_graph[r1].link[0] == e ? 0 : 1; + r2in = embed_graph[r2].link[0] == e ? 0 : 1; + + if (r1 == r2) + /* + this I think should never happen, but one never knows... + */ + { + embed_graph[r1].link[0] = embed_graph[r1].link[1] = r1; + } + else + { + embed_graph[r1].link[r1out] = r2; + embed_graph[r2].link[r2in] = r1; + } + + ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, r1)); + + /* + now we must do a similar thing for the twin + (which must get reomved as well) + */ + twin = embedg_VES_get_twin_edge(embed_graph, n, e); + + IF_DEB_SCE( + fprintf(stdout, "removing an SCE, the twin\n"); + embedg_VES_print_edge(embed_graph, n, twin); + ) + + r1 = embed_graph[twin].link[0]; + r2 = embed_graph[twin].link[1]; + + embed_graph[twin].link[0] = embed_graph[twin].link[1] = twin; + + r1out = embed_graph[r1].link[0] == twin ? 0 : 1; + r2in = embed_graph[r2].link[0] == twin ? 0 : 1; + + if (r1 == r2) + { + embed_graph[r1].link[0] = embed_graph[r1].link[1] = r1; + } + else + { + embed_graph[r1].link[r1out] = r2; + embed_graph[r2].link[r2in] = r1; + } + + ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, r1)); +} + + +void +embedg_VES_set_orientation (t_ver_edge *embed_graph, int n, int *ver_orient) + /* + using the vertices' orientation as given in ver_orient + we set the orientation for each edge in the adjacency list + for each vertex + + to do this we use the field sign which is NOT needed + anymore by the tester since by the time we call this + function we would have finished with that bit (the tester) + + sign is only set when merging bicomps + - even though we'll perform another walkdown when + recovering an obstruction (if any) no bicomp merging will occur, + so we are safe + */ +{ + int v; + + for (v = 0; v < n; v++) + { + int o, e; + + o = ver_orient[v]; + embed_graph[v].sign = o; + + e = embed_graph[v].link[0]; + + while (e != v) + /* + just as a note: note the way I get the next in the list + here (as opposed to using + embedg_VES_get_next_in_dlcl): + this is because I implicitely assume that + the adjacency lists are consistent + + Also note that edges can be SCE, it doesn't really matter + anyway (they may not have been removed yet + -- see the way we recover the obstruction: + embedg_mark_obstruction) + */ + { + embed_graph[e].sign = o; + e = embed_graph[e].link[0]; + } + } +} + + +/* + * dlcl_misc.c + */ + +/* + What: + ***** + + Implementing: + + Housekeeping for a simple doubly linked circular list: + this is a data structure ONLY used WITHIN + the planarity tester and obstruction isolator and is not to be + confused with the VES structure mentionned elsewhere. + + The VES structure is an array, while the dlcl one is a list of + pointers. + + The dlcl is especially useful as it allows for the storage + of an ordered list. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + +/* aproto: beginstatic -- don't touch this!! */ +static void embedg_dlcl_rec_free (t_dlcl *); +static void embedg_dlcl_rec_insert_right (t_dlcl *, t_dlcl *); +static void embedg_dlcl_rec_insert_left (t_dlcl *, t_dlcl *); +static void embedg_dlcl_rec_retrieve (t_dlcl *); +static void embedg_dlcl_rec_delete (t_dlcl *); +static boolean embedg_dlcl_is_singleton (t_dlcl *); +/* aproto: endstatic -- don't touch this!! */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + +t_dlcl * +embedg_dlcl_rec_new (int info) + /* + create a new record with info in the global array + to insert in the list + */ +{ + t_dlcl *r; + + r = (t_dlcl *) mem_malloc(sizeof(t_dlcl)); + r->info = info; + r->in_adjl = r->twin_in_adjl = NIL; + r->mult = 1; + r->right = r; + r->left = r; + return r; +} + +static void +embedg_dlcl_rec_free (t_dlcl *r) + /* + free + */ +{ + mem_free(r); +} + +void +embedg_dlcl_rec_print (t_dlcl *r) +{ + fprintf(stdout,"%d ", r->info); +} + +void +embedg_dlcl_print (t_dlcl *l) +{ + t_dlcl *p = l; + + if (!embedg_dlcl_is_empty(p)) + { + embedg_dlcl_rec_print(p); + p = embedg_dlcl_list_next(p); + while (p != l) + { + embedg_dlcl_rec_print(p); + p = embedg_dlcl_list_next(p); + } + } + fprintf(stdout,"\n"); +} + + +static void +embedg_dlcl_rec_insert_right (t_dlcl *l, t_dlcl *r) +{ + t_dlcl *tmp_r, *tmp_l; + + tmp_r = l->right; + tmp_l = tmp_r->left; + + l->right = r; + r->right = tmp_r; + + r->left = tmp_l; + tmp_r->left = r; +} + + +static void +embedg_dlcl_rec_insert_left (t_dlcl *l, t_dlcl *r) +{ + t_dlcl *tmp_r, *tmp_l; + + tmp_l = l->left; + tmp_r = tmp_l->right; + + l->left = r; + r->left = tmp_l; + + r->right = tmp_r; + tmp_l->right = r; +} + +t_dlcl * +embedg_dlcl_rec_append (t_dlcl *l, t_dlcl *r) +{ + if (embedg_dlcl_is_empty(l)) + return r; + + embedg_dlcl_rec_insert_left(l, r); + return l; +} + +t_dlcl * +embedg_dlcl_rec_prepend (t_dlcl *l, t_dlcl *r) +{ + if (embedg_dlcl_is_empty(l)) + return r; + + embedg_dlcl_rec_insert_left(l, r); + return r; +} + +t_dlcl * +embedg_dlcl_cat (t_dlcl *l, t_dlcl *m) + /* + concatenate m to the RIGHT of the end of l + WITHOUT copying m + */ +{ + t_dlcl *h1, *h2, *e1, *e2; + + if (embedg_dlcl_is_empty(l)) + return m; + if (embedg_dlcl_is_empty(m)) + return l; + + h1 = l; + e1 = l->left; + h2 = m; + e2 = m->left; + + e1->right = h2; + h2->left = e1; + e2->right = h1; + h1->left = e2; + + return l; +} + +t_dlcl * +embedg_dlcl_find (t_dlcl *l, int info) +{ + t_dlcl *p = l; + + if (!embedg_dlcl_is_empty(p)) + { + if (p->info == info) + { + return p; + } + p = embedg_dlcl_list_next(p); + while (p != l) + { + if (p->info == info) + { + return p; + } + p = embedg_dlcl_list_next(p); + } + } + return NP; +} + +t_dlcl * +embedg_dlcl_find_with_NIL_twin_in_adjl (t_dlcl *l, int info) +{ + t_dlcl *p = l; + + if (!embedg_dlcl_is_empty(p)) + { + if (p->info == info && p->twin_in_adjl == NIL) + { + return p; + } + p = embedg_dlcl_list_next(p); + while (p != l) + { + if (p->info == info && p->twin_in_adjl == NIL) + { + return p; + } + p = embedg_dlcl_list_next(p); + } + } + return NP; +} + + + +static void +embedg_dlcl_rec_retrieve (t_dlcl *r) +{ + t_dlcl *right, *left; + + right = r->right; + left = r->left; + + left->right = right; + right->left = left; + + r->right = r; + r->left = r; +} + +static void +embedg_dlcl_rec_delete (t_dlcl *r) +{ + embedg_dlcl_rec_retrieve(r); + embedg_dlcl_rec_free(r); +} + + +t_dlcl * +embedg_dlcl_delete_first (t_dlcl *l) + /* + prune the list from the head: + - set new head to right of old head + - delete old head + */ +{ + t_dlcl *new_head; + + ASSERT(!embedg_dlcl_is_empty(l)); + if (embedg_dlcl_is_singleton(l)) + { + new_head = NP; + } + else + { + new_head = l->right; + } + embedg_dlcl_rec_delete(l); + return new_head; +} + + +t_dlcl * +embedg_dlcl_delete_rec (t_dlcl *l, t_dlcl *r) + /* + delete r from l; + if r == l, set new head to right of old head + */ +{ + if (r == l) + { + return embedg_dlcl_delete_first(l); + } + embedg_dlcl_rec_delete(r); + return l; +} + + +boolean +embedg_dlcl_is_empty (t_dlcl *l) +{ + return (l == NP) ? TRUE : FALSE; +} + + +static boolean +embedg_dlcl_is_singleton (t_dlcl *l) +{ + return (l->right == l) ? TRUE : FALSE; + /* + same as l->left == l + */ +} + +t_dlcl * +embedg_dlcl_list_next (t_dlcl *l) + /* + this assumes no choice in the direction of the walking + (always to the right) + -- good enough when deleting for example or when + the direction of the walking does not matter + */ +{ + return l->right; +} + + +t_dlcl * +embedg_dlcl_list_prev (t_dlcl *l) + /* + this assumes no choice in the direction of the walking + (always to the right) + */ +{ + return l->left; +} + +t_dlcl * +embedg_dlcl_list_last (t_dlcl *l) +{ + return embedg_dlcl_list_prev(l); +} + + + +void +embedg_dlcl_delete (t_dlcl *l) +{ + if (!embedg_dlcl_is_empty(l)) + { + while (!embedg_dlcl_is_singleton(l)) + { + t_dlcl *next; + + next = embedg_dlcl_list_next(l); + embedg_dlcl_rec_delete(next); + } + embedg_dlcl_rec_delete(l); + } +} + +t_dlcl * +embedg_dlcl_copy (t_dlcl *l) +{ + t_dlcl *p, *c; + + if (embedg_dlcl_is_empty(l)) + return NP; + + c = embedg_dlcl_rec_new(l->info); + + p = embedg_dlcl_list_next(l); + while (p != l) + { + t_dlcl *temp; + + temp = embedg_dlcl_rec_new(p->info); + temp->in_adjl = p->in_adjl; + temp->twin_in_adjl = p->twin_in_adjl; + temp->mult = p->mult; + c = embedg_dlcl_rec_append(c, temp); + p = embedg_dlcl_list_next(p); + } + return c; +} + + +int +embedg_dlcl_length (t_dlcl *l) +{ + t_dlcl *p; + int n; + + if (embedg_dlcl_is_empty(l)) + return 0; + + p = embedg_dlcl_list_next(l); + n = 1; + while (p != l) + { + n++; + p = embedg_dlcl_list_next(p); + } + return n; +} +/* + * planar_by_edge_addition.c + */ + +/* + What: + ***** + + Implementing: + + The top level for the planarity tester. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} +#define IF_DEB_EDGES(x) {} +#define IF_CPU(x) {} + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + +boolean +sparseg_adjl_is_planar ( + t_ver_sparse_rep *V, + int n, + t_adjl_sparse_rep *A, /* input sparse graph */ + int *nbr_c, /* size of the graph, #components + */ + t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree + -- vertices are as DFIs + -- and children are ordered wrt + lowpoint value + */ + t_dlcl ***back_edges, /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + (vertices are given as DFIs) + */ + t_dlcl ***mult_edges, /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + (vertices are given as DFIs) + */ + t_ver_edge **embed_graph, /* output graph embedding -- more on that + later + */ + int *edge_pos, /* pos. in embed_graph for addition + of the next edge */ + int *vr, + int *wr /* if graph is non planar, return + the unembedded edge + (where wr descendant of vr) + */ +) + /* + as the name indicates: is the graph planar? + */ +{ + int v; + + IF_CPU( + float sttime; float time_to_now; + ) + + + *embed_graph = + embedg_planar_alg_init(V, n, A, nbr_c, + edge_pos, dfs_tree, back_edges, mult_edges); + IF_CPU( + sttime = time_current_user(); + ) + + for (v = n - 1; v >= 0; v--) + /* + visit all vertices in descending DFI order + */ + { + t_dlcl *be_l, *te_l, *p; + + IF_DEB( + fprintf(stdout, "top level, vertex %d\n", v); + ) + + /* + find all the back edges [w, v] where w is a descendant of v + and perform a walkup from w to v + (ie determine which bicomps are pertinent) + */ + be_l = (*back_edges)[v]; + p = be_l; + + if (!embedg_dlcl_is_empty(p)) + { + int w; + + w = p->info; + IF_DEB( + fprintf(stdout, "top level, before walkup for w %d\n", w); + ) + embedg_walkup(*embed_graph, n, v, p); + + p = embedg_dlcl_list_next(p); + while (p != be_l) + { + w = p->info; + IF_DEB( + fprintf(stdout, "top level, before walkup for w %d\n", w); + ) + embedg_walkup(*embed_graph, n, v, p); + + p = embedg_dlcl_list_next(p); + } + } + + /* + perform a walkdown for each tree edge [v, c], c a descendant of v + (ie attempt to embed all back edges on the pertinent bicomps) + */ + te_l = (*dfs_tree)[v]; + p = te_l; + + if (!embedg_dlcl_is_empty(p)) + { + int c, vv; + t_merge_queue q; + + c = p->info; + vv = c + n; + IF_DEB( + fprintf(stdout, "top level, before walkdown for c %d\n", c); + ) + q = embedg_walkdown(*embed_graph, n, edge_pos, vv); + + IF_DEB( + fprintf(stdout, "top level, after walkdown for c %d, state of edges'sign\n", c); + embedg_VES_print_flipped_edges(*embed_graph, + n, *edge_pos); + ) + + /* + temp only + */ + embedg_merge_queue_delete(q); + p = embedg_dlcl_list_next(p); + while (p != te_l) + { + c = p->info; + vv = c + n; + IF_DEB( + fprintf(stdout, "top level, before walkdown for c %d\n", c); + ) + q = embedg_walkdown(*embed_graph, n, edge_pos, vv); + + IF_DEB( + fprintf(stdout, "top level, after walkdown for c %d, state of edges'sign\n", c); + embedg_VES_print_flipped_edges(*embed_graph, + n, *edge_pos); + ) + + /* + temp only + */ + embedg_merge_queue_delete(q); + + p = embedg_dlcl_list_next(p); + } + } + + + /* + check that each back edge [w, v], w a descendant of v, + has been embedded + */ + be_l = (*back_edges)[v]; + p = be_l; + + if (!embedg_dlcl_is_empty(p)) + { + int w; + + w = p->info; + IF_DEB( + fprintf(stdout, "top level, before checking embedding for w %d\n", + w); + ) + if ((*embed_graph)[w].adjacent_to == v) + /* + this edge hasn't been embedded: + the graph is non-planar + */ + { + /* + before returning we really want to ensure that + the vertices' adjacency lists are consistent + */ + ASSERT(embedg_VES_are_adj_lists_consistent( + *embed_graph, n)); + + IF_CPU( + fprintf(stdout, "CPU for tester only %f\n", + (time_current_user() - sttime)); + ) + + *vr = v; + *wr = w; + return FALSE; + } + + p = embedg_dlcl_list_next(p); + while (p != be_l) + { + w = p->info; + IF_DEB( + fprintf(stdout, "top level, before checking embedding for w %d\n", + w); + ) + if ((*embed_graph)[w].adjacent_to == v) + { + /* + before returning we really want to ensure that + the vertices' adjacency lists are consistent + */ + ASSERT(embedg_VES_are_adj_lists_consistent( + *embed_graph, n)); + + IF_CPU( + fprintf(stdout, "CPU for tester only %f\n", + (time_current_user() - sttime)); + ) + + *vr = v; + *wr = w; + return FALSE; + } + + p = embedg_dlcl_list_next(p); + } + } + } + IF_DEB_EDGES( + fprintf(stdout, "top level, total number of edges in embedding %d\n", + *edge_pos - 2 * n + 1); + ) + + + /* + before returning we really want to ensure that + the vertices' adjacency lists are consistent + */ + ASSERT(embedg_VES_are_adj_lists_consistent(*embed_graph, n)); + + IF_CPU( + fprintf(stdout, "CPU for tester only %f\n", + (time_current_user() - sttime)); + ) + + return TRUE; +} + + + +/* + * walkup.c + */ + +/* + What: + ***** + + Implementing: + + The walkup routine within the VES structure: + + Walking up from w where [w, v^c] is a (directed) + back edge to be embeeding later. + Along the way collect all the pertinent bicomps that + will need to be merged before embedding the back edges + to v^c. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + +void +embedg_walkup (t_ver_edge *embed_graph, int n, int v, t_dlcl *p) + /* + walkup from w = p->info to v: [w, v] is a back edge where w is a DFS + descendant of v + */ +{ + int w, x, xin, y, yin; + + w = p->info; + + IF_DEB( + fprintf(stdout, "walkup from %d to %d, enter\n", w, v); + ) + + embed_graph[w].adjacent_to = v; + /* + dirty trick to record some information about the BE [w, v] + which will be useful at the time of creation and insertion of + this BE: this happens in the walkdown procedure + + note that what I am doing here is safe: [w].in_adjl, + [w].twin_in_adjl, [w].mult had no use so far since w is a vertex + (and not an edge...) + */ + embed_graph[w].in_adjl = p->in_adjl; + embed_graph[w].twin_in_adjl = p->twin_in_adjl; + embed_graph[w].mult = p->mult; + + /* + set up the traversal contexts for w: one in each direction + */ + x = w; + xin = 1; + y = w; + yin = 0; + + while (x != v) + { + int vz, z, c; + + IF_DEB( + fprintf(stdout, "walkup, x %d and y %d\n", x, y); + ) + + if (embed_graph[x].visited == v + || embed_graph[y].visited == v) + { + IF_DEB( + if (embed_graph[x].visited == v) + fprintf(stdout, "walkup, x visited\n"); + else + fprintf(stdout, "walkup, y visited\n"); + ) + break; + } + + /* + set x and y as visited! + */ + embed_graph[x].visited = embed_graph[y].visited = v; + + vz = embedg_VES_is_virtual_vertex(n, x) ? x : NIL; + vz = embedg_VES_is_virtual_vertex(n, y) ? y : vz; + + if (vz != NIL) + /* + that is, x (or y) is a virtual vertex + -- in other words, we are set to find the root of the bicomp + containing w, or of the bicomp r^c such that w is in the tree + rooted by c + + consequently, by definition, vz is PERTINENT + */ + { + c = vz - n; + z = embed_graph[c].DFS_parent; + + IF_DEB( + fprintf(stdout, "walkup, vz is virtual, %d^%d\n", + z, c); + ) + + if (z != v) + /* + determine if vz externally or internally active + */ + { + if (embed_graph[c].lowpoint < v) + /* + vz is externally active: APPEND to the list + of pertinent bicomps + */ + { + IF_DEB( + fprintf(stdout, "walkup, vz is ext. active\n"); + ) + + embed_graph[z].pertinent_bicomp_list = + embedg_dlcl_rec_append( + embed_graph[z].pertinent_bicomp_list, + embedg_dlcl_rec_new(vz)); + } + else + /* + vz is internally active: PREPEND to the list + of pertinent bicomps + */ + { + IF_DEB( + fprintf(stdout, "walkup, vz is pertinent\n"); + ) + + embed_graph[z].pertinent_bicomp_list = + embedg_dlcl_rec_prepend( + embed_graph[z].pertinent_bicomp_list, + embedg_dlcl_rec_new(vz)); + } + } + + /* + continue the walkup, look if there are any other + pertinent bicomps + -- here "jump" to the next bicomp "up" + */ + x = z; + xin = 1; + y = z; + yin = 0; + } + else + /* + continue the traversal of the bicomp until one finds + its (virtual) root + */ + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + x, xin, FALSE, 0, &x, &xin); + embedg_VES_get_succ_on_ext_face(embed_graph, n, + y, yin, FALSE, 0, &y, &yin); + } + } +} + +/* + * walkdown.c + */ + +/* + What: + ***** + + Implementing: + + The walkdown routine within the VES structure: + + walking down a bicomp rooted by a virtual vertex v^c + and attempting to embed the back edges. + This cannot be done if the walk has to stop due to the + presence of externally active vertices on both + the clockwise and the anticlockwise side of the bicomp. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_EMBED(x) {} +#define IF_DEB_BE(x) {} +#define IF_DEB_SCE(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + + +t_merge_queue +embedg_walkdown (t_ver_edge *embed_graph, int n, int *edge_pos, int vv) + /* + walkdown from the virtual vertex: + embed any back edges incident to vv if any + and merge the encountered bicomps while walking down + (very informative isn't it? :)) + + ... and return the merge queue: will be useful when + isolating the Kuratowski subgraphs + */ +{ + t_merge_queue q; + int v, c, vvout; + + ASSERT(embedg_VES_is_virtual_vertex(n, vv)); + + /* + find v and c such that v^c = vv + */ + c = vv - n; + v = embed_graph[c].DFS_parent; + + IF_DEB( + fprintf(stdout, "walkdown from %d^%d, enter\n", v, c); + ) + + IF_DEB_EMBED( + fprintf(stdout, "walkdown, embedding at start\n"); + embedg_VES_print_bigcomps(embed_graph, n); + ) + + /* + create an empty merge queue + */ + q = embedg_merge_queue_new(n); + + for (vvout = 0; vvout <= 1; vvout++) + /* + chose a direction for the walk, but walk in both + directions unless a stopping vertex is encountered + and other conditions are satisfied (see below) + */ + { + int w, win; + + embedg_VES_get_succ_on_ext_face(embed_graph, n, vv, vvout ^ 1, + FALSE, 0, &w, &win); + + IF_DEB( + fprintf(stdout, "walkdown, successor (outside while loop) from %d^%d:%d is %d:%d\n", + embed_graph[vv-n].DFS_parent, vv-n, vvout ^ 1, + w, win); + ) + + while (w != vv) + /* + is there no danger we walk the whole way back to vv + and that all the vertices along the walk are inactive? + + answer: no, because of the short-cut edges. + + Short-cut edges are precisely inserted to remove the inactive + vertices from the external face (ie they are "pushed" + to the internal face of the bicomp) + */ + { + if (embed_graph[w].adjacent_to == v) + /* + ie there is a (directed) back edge [w, v] + (would have been set in the previous walkup routine): + embed this edge, but before that, merge all the bicomps + previouslsy collected + */ + { + IF_DEB( + fprintf(stdout, "walkdown, embed BE (%d^%d:%d, %d:%d)\n", + embed_graph[vv-n].DFS_parent, vv - n, vvout, + w, win); + fprintf(stdout, "walkdown, queue before pulling elts\n"); + embedg_merge_queue_print(q); + ) + + while (!embedg_merge_queue_empty(q)) + { + int u, uin, vu, vuout; + + embedg_merge_queue_get(&q, &u, &uin, &vu, &vuout); + + IF_DEB( + fprintf(stdout, "walkdown, pull from queue (%d:%d, %d^%d:%d)\n", + u, uin, + embed_graph[vu-n].DFS_parent, vu-n, + vuout); + ) + + embedg_VES_merge_pertinent_bicomps( + embed_graph, n, + vu, vuout, u, uin); + } + IF_DEB_BE( + fprintf(stdout, "walkdown, before embed BE [%d^%d:%d, %d:%d]\n", + embed_graph[vv-n].DFS_parent, vv - n, + vvout, w, win); + embedg_VES_print_adj_list( + embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list( + embed_graph, n, vv, + FALSE); + ) + + embedg_VES_embed_edge(embed_graph, n, edge_pos, + BE, vv, vvout, w, win); + + IF_DEB_BE( + fprintf(stdout, "walkdown, after embed BE [%d^%d:%d, %d:%d]\n", + embed_graph[vv-n].DFS_parent, vv - n, + vvout, w, win); + embedg_VES_print_adj_list( + embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list( + embed_graph, n, vv, + FALSE); + ) + IF_DEB_EMBED( + fprintf(stdout, "walkdown, embedding after bicomp merge & back edge embedding\n"); + embedg_VES_print_bigcomps(embed_graph, n); + ) + + /* + clear the adjacent_to flag + */ + embed_graph[w].adjacent_to = n; /* "invalid" value */ + } + + if (!embedg_dlcl_is_empty(embed_graph[w].pertinent_bicomp_list)) + /* + each pertinent child bicomp of w + (pertinent: contains active (ie more back edges to embed) + elts) + must be traversed + and pushed onto the queue for later bicomp merging + */ + { + int vw, vwout, x, xin, y, yin, s, sin; + + IF_DEB( + fprintf(stdout, "walkdown, pertinent list for %d\n", + w); + embedg_dlcl_print(embed_graph[w].pertinent_bicomp_list); + ) + + /* + get the first child in the pertinent list + (see how the list is built in embedg_walkup) + + the child will eventually be removed from that list + when merging the bicomps, and surely + this bicomp (rooted at vw) will be merged (later) + because it is active and hence pushed on + the merge queue + */ + + /* + we can start by pushing the vertex (w, win) on + the merge queue + */ + embedg_merge_queue_append_vertex(&q, embed_graph, n, w, win); + + IF_DEB( + fprintf(stdout, "walkdown, push 1rst 2-tuple on queue\n"); + embedg_merge_queue_print(q); + ) + + /* + get the first child in the pertinent list + */ + vw = (embed_graph[w].pertinent_bicomp_list)->info; + + IF_DEB( + fprintf(stdout, "walkdown, get pertinent %d^%d\n", + embed_graph[vw - n].DFS_parent, vw - n); + ) + + /* + start two walks starting at vw + */ + embedg_VES_get_succ_active_on_ext_face(embed_graph, n, + v , vw, 1, + FALSE, 0, &x, &xin); + embedg_VES_get_succ_active_on_ext_face(embed_graph, n, + v, vw, 0, + FALSE, 0, &y, &yin); + + /* + because of the trick of inserting short-cut edges + at previous stages, neighbours of vw are guaranteed + to be active + + (however I'll use the more general + embedg_VES_get_succ_active_on_ext_face + instead of the restrictive + embedg_VES_get_succ_on_ext_face + because the walkdown may be used later to isolate + Kuratowski minors, in a situation where SCEs could have + been removed and thus where the successor on the + external face will no longer be guaranteed to be active) + (* actually I have decided to remove the SCE at the + very last moment hence the above pb + does not occur in the present implementation) + + + it only remains to chose the next vertex where from + to continue the walk; the choice is made in that order: + - an internally active vertex + (incident to v via a backedge but whose lowpoint + is NO less than v) + - a (externally active) pertinent vertex + (incident to v via a backedge but whose lowpoint + is less than v: ie which is also externally active) + - as a last resort, a non-pertinent externally vertex, + which is then a stopping vertex + */ + IF_DEB( + fprintf(stdout, "walkdown, x and y: %d, %d\n", x, y); + ) + + if (embedg_VES_is_ver_int_active(embed_graph, n, + v, x)) + /* + x is internally active + */ + { + IF_DEB( + fprintf(stdout, "walkdown, x is int. active\n"); + ) + + s = x; + sin = xin; + } + else if (embedg_VES_is_ver_int_active( + embed_graph, n, + v, y)) + /* + y is internally active + */ + { + IF_DEB( + fprintf(stdout, "walkdown, y is int. active\n"); + ) + + s = y; + sin = yin; + } + else if (embedg_VES_is_ver_pertinent( + embed_graph, n, + v, x)) + /* + x is pertinent + */ + { + IF_DEB( + fprintf(stdout, "walkdown, x is pertinent\n"); + ) + + s = x; + sin = xin; + } + else + /* + tough luck: y may be externally active + */ + { + IF_DEB( + fprintf(stdout, "walkdown, tough luck\n"); + ) + + s = y; + sin = yin; + } + + IF_DEB( + fprintf(stdout, "walkdown, succ. on pertinent bicomp is %d:%d\n", s, sin); + ) + + /* + set vwout to respect consistency of traversal + */ + vwout = s == x ? 0 : 1; + + /* + now that we know vwout we can push (vw, vwout) + on the merge queue, thus completing the 4-tuple + (w, win, vw, vwout) describing a bicomp merge + to occur at a later stage + */ + embedg_merge_queue_append_virtual_vertex(&q, embed_graph, n, + vw, vwout); + + IF_DEB( + fprintf(stdout, "walkdown, push on queue (%d:%d, %d^%d:%d)\n", + w, win, embed_graph[vw-n].DFS_parent, vw - n, + vwout); + embedg_merge_queue_print(q); + ) + + /* + we continue the walk + */ + w = s; + win = sin; + } + /* + at this point, w is either inactive or externally active + (w can't be pertinent: its pertinent bicomp list is empty, + and the back edge [w, v], if any, has already been embedded) + */ + else if (embedg_VES_is_ver_inactive(embed_graph, n, + v, w)) + /* + w is inactive: continue with the walk on the external face + and, insert a short cut edge so that w is removed + from the external face + */ + { + int s, sin; + + IF_DEB( + fprintf(stdout, "walkdown, %d has no pertinent bicomps and is inactive\n", w); + ) + + embedg_VES_get_succ_on_ext_face(embed_graph, n, + w, win, + FALSE, 0, &s, &sin); + + IF_DEB( + fprintf(stdout, "walkdown, successor from %d:%d is %d:%d\n", + w, win, s, sin); + ) + + /* + s is the successor of w: we embed a short circuit edge + [vv, s] if + - the bicomp is externally active (to ensure that + at a later stage this new face gets bisected: + so that we don't end up with a face of degree 2 + (parallel edges)) + - if [s, vv] is not a back edge + + CONSEQUENTLY, adding SCE edges + + does not destroy the planarity of the graph + + ensures that each face has degree > 2 so that + |E| <= 3 * |V| - 6 remains valid at all times + + that the space allocated to the edges in embed_graph + (via MAXDE(n)) is sufficient + + NOTE: + the above still allows to embed a short-cut edge + as an edge parallel to a tree edge OR a back edge + (which then has been embedded previously + so that [w].adjacent has been cleared) + + but again, since the degree of the face will be + > 2, that's ok + + recall that c = vv - n + */ + if (embed_graph[c].lowpoint < v + /* + bicomp rooted at vv is externally active + */ + && embed_graph[s].adjacent_to != v) + /* + [s, vv] is not a back edge + */ + { + IF_DEB_SCE( + fprintf(stdout, "walkdown, before embed SCE [%d^%d:%d, %d:%d]\n", + embed_graph[vv-n].DFS_parent, vv - n, + vvout, s, sin); + embedg_VES_print_adj_list( + embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list( + embed_graph, n, vv, + FALSE); + + ) + + embedg_VES_embed_edge(embed_graph, + n, edge_pos, + SCE, vv, vvout, s, sin); + /* + note also that the addition of short cut edges + does not change the fact that the graph is planar + (when it is, so we never run into the problem + of creating/adding too many edges to embed-graph) + */ + IF_DEB_SCE( + fprintf(stdout, "walkdown, after embed SCE [%d^%d:%d, %d:%d]\n", + embed_graph[vv-n].DFS_parent, vv - n, + vvout, s, sin); + embedg_VES_print_adj_list( + embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list( + embed_graph, n, vv, + FALSE); + + ) + IF_DEB( + fprintf(stdout, "walkdown, embed SCE [%d^%d:%d, %d:%d]\n", + embed_graph[vv-n].DFS_parent, vv - n, + vvout, s, sin); + ) + + } + /* + continue the walk + */ + w = s; + win = sin; + } + else + /* + w is non-pertinent and externally active: + it is a stopping vertex: + we stop here and see if we can walk in the other direction + */ + { + IF_DEB( + fprintf(stdout, "walkdown, %d is externally active\n", w); + ) + break; + } + } + if (!embedg_merge_queue_empty(q)) + /* + mumm.... don't understand this one... let's see: + the queue constains pertinent bicomps collected during one of + the traversal of the external face, so that once + a stopping vertex has been encountered and the queue + is not empty, this means that we will be unable + to embed any remaining back edges: + + it is important to remember that when w is a stopping vertex + there is no choice left, since we walk the pertinent + bicomp in both directions at once, and always choose + the "best" possible vertex + (see the choice strategy: (a) internally active, (b) pertinent, + (c) the rest) + */ + { + IF_DEB( + fprintf(stdout, "walkdown, merge queue is not empty\n"); + ) + break; + } + } + + /* + and return the merge queue + */ + return q; +} + + + + +/* + * merge_queue_misc.c + */ + +/* + What: + ***** + + Implementing: + + The merge queue stores the pertinent bicomps waiting to + be merged before a subsequent back edge embedding. + See walkdown.c + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + +t_merge_queue +embedg_merge_queue_new (int n) + /* + create a merge queue of 4 * (n-1) elts: + we can only have at most n-1 virtual vertices, + and for each of those we need to store 4 bits of info + */ +{ + t_merge_queue q; + + q.start = q.end = 0; + q.b = (int *) mem_malloc(sizeof(int) * 4 * (n - 1)); + + return q; +} + +void +embedg_merge_queue_delete (t_merge_queue q) +{ + mem_free(q.b); +} + + +boolean +embedg_merge_queue_empty (t_merge_queue q) +{ + return q.start == q.end ? TRUE : FALSE; +} + +void +embedg_merge_queue_print (t_merge_queue q) +{ + int i; + + for (i = q.start; i < q.end; i++) + { + fprintf(stdout, "%d:%d ", q.b[i], q.b[i+1]); + ++i; + } + fprintf(stdout, "\n"); +} + +void +embedg_merge_queue_append (t_merge_queue *q, t_ver_edge *embed_graph, + int n, int v, int vin, int vv, int vvout) + /* + append the 4-tuple (v, vin, vv, vvout) + where v is a vertex and vv is its virtual counterpart + + we don't do much here, most of the work is done + when pulling a bicomp/4-tuple from the queue + */ +{ + /* + is this really necessary? - YES!!! + */ + ASSERT((*q).end < 4 * (n - 2)); + ASSERT(embedg_VES_is_vertex(n, v)); + ASSERT(embedg_VES_is_virtual_vertex(n, vv)); + ASSERT(embed_graph[vv - n].DFS_parent == v); + + (*q).b[(*q).end++] = v; + (*q).b[(*q).end++] = vin; + (*q).b[(*q).end++] = vv; + (*q).b[(*q).end++] = vvout; +} + +void +embedg_merge_queue_append_vertex (t_merge_queue *q, t_ver_edge *embed_graph, + int n, int v, int vin) + /* + same as above but were we only append the 2-tuple (v, vin), + appending the 2-tuple (vv, vvout) at a later stage + (see embedg_merge_queue_append_virtual_vertex) + */ +{ + ASSERT((*q).end < 4 * (n - 2)); + ASSERT(embedg_VES_is_vertex(n, v)); + + (*q).b[(*q).end++] = v; + (*q).b[(*q).end++] = vin; + + IF_DEB( + fprintf(stdout, "merge_queue_append_vertex, after, end is %d\n", + (*q).end); + ) +} + +void +embedg_merge_queue_append_virtual_vertex (t_merge_queue *q, + t_ver_edge *embed_graph, int n, int vv, int vvout) + /* + counterpart to embedg_merge_queue_append_vertex: + here we append the 2-tuple (vv, vvout), vv = v^c, + where the 2-tuple (v, vin) is already in the queue + (see embedg_merge_queue_append_vertex) + */ +{ + ASSERT(!embedg_merge_queue_empty(*q)); + ASSERT(embedg_VES_is_virtual_vertex(n, vv)); + ASSERT(embed_graph[vv - n].DFS_parent == (*q).b[(*q).end - 2]); + + (*q).b[(*q).end++] = vv; + (*q).b[(*q).end++] = vvout; + + IF_DEB( + fprintf(stdout, "merge_queue_append_virtual_vertex, after, end is %d\n", + (*q).end); + ) +} + +void +embedg_merge_queue_get (t_merge_queue *q, int *v, int *vin, int *vv, int *vvout) + /* + pulling out a 4-tuple from the beginning of the FIFO queue + */ +{ + ASSERT(!embedg_merge_queue_empty((*q))); + + *v = (*q).b[(*q).start++]; + *vin = (*q).b[(*q).start++]; + *vv = (*q).b[(*q).start++]; + *vvout = (*q).b[(*q).start++]; +} + +void +embedg_merge_queue_prune (t_merge_queue *q, int *v, + int *vin, int *vv, int *vvout) + /* + pulling out a 4-tuple from the end of the FIFO queue + */ +{ + ASSERT(!embedg_merge_queue_empty((*q))); + + *vvout = (*q).b[--((*q).end)]; + *vv = (*q).b[--((*q).end)]; + *vin = (*q).b[--((*q).end)]; + *v = (*q).b[--((*q).end)]; +} + +/* + * vertex_activity.c + */ + +/* + What: + ***** + + Implementing: + + Determining a vertex's activity. This takes place within + the VES structure. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + + + + +boolean +embedg_VES_is_ver_pertinent (t_ver_edge *embed_graph, int n, int v, int w) + /* + is w pertinent (wrt v) + - the field adjacent_to = v: means there is a back edge [w, v] + - or w has a non empty pertinent_bicomp_list + */ +{ + boolean ans; + + ans = embed_graph[w].adjacent_to == v ? TRUE : FALSE; + + if (ans) + return TRUE; + else + return embedg_dlcl_is_empty(embed_graph[w].pertinent_bicomp_list) ? + FALSE : TRUE; +} + +boolean +embedg_VES_is_ver_ext_active (t_ver_edge *embed_graph, int n, int v, int w) + /* + is w externally active (wrt v) + this is the case when either w's least_ancestor < v + or the first member of w's separated_DFS_child_list has lowpoint < v + (the vertices in separated_DFS_child_list are ordered by lowpoint) + + why? because w's separated_DFS_child_list may be empty + (due to prior bicomp merging say) and so its children are in effect + inactive + */ +{ + boolean ans; + + ans = embed_graph[w].least_ancestor < v ? TRUE : FALSE; + + if (ans) + return TRUE; + else + { + if (embedg_dlcl_is_empty(embed_graph[w].separated_DFS_child_list)) + { + return FALSE; + } + else + { + int c; + + c = (embed_graph[w].separated_DFS_child_list)->info; + return embed_graph[c].lowpoint < v ? TRUE : FALSE; + } + } +} + + +boolean +embedg_VES_is_ver_int_active (t_ver_edge *embed_graph, int n, int v, int w) + /* + is w internally active (wrt v): + this happens when w is pertinent but NOT externally active + */ +{ + return embedg_VES_is_ver_pertinent(embed_graph, n, v, w) + && !embedg_VES_is_ver_ext_active(embed_graph, n, v, w); +} + +boolean +embedg_VES_is_ver_inactive (t_ver_edge *embed_graph, int n, int v, int w) + /* + is w inactive (wrt v), that is w nor pertinent nor externally activ + */ +{ + return !embedg_VES_is_ver_pertinent(embed_graph, n, v, w) + && !embedg_VES_is_ver_ext_active(embed_graph, n, v, w); +} + +/* + * merge_bicomps.c + */ + +/* + What: + ***** + + Implementing: + + In the VES structure, merging two bicomponents. + That is, merging the virtual vertex v^c with the + actual vertex v while merging their respective + adjacency lists. + This must be done in a very specific manner so as to able to + determine the subsequent internal/external faces. + Also, great care must be taken so that the resulting + adj. list for v is consistent (wrt to the direction + of traversal). + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_ADJL(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + +void +embedg_VES_merge_simple_bicomps (t_ver_edge *embed_graph, int n, int vv, + int vvout, int v, int vin) + /* + merge the bicomp rooted at vv (vv a virtual vertex) with + its counterpart v so that the resulting adjacency list for v + is consistent and is the union of the adjacency lists for vv and v + + we treat the case that the bicomp may be flipped (vvout == vin) + here + */ +{ + int c, edge, twin, root_edge, cur, prev; + int vout, vvin, e1, e2, e3, e4, e1out, e3out, e4in; + + /* + find c such that [v^c, c] is the root edge of the bicomp + rooted at vv = v^c + */ + c = vv - n; + ASSERT(embed_graph[c].DFS_parent == v); + + IF_DEB( + fprintf(stdout, "merge_simple_bicomp, start: merge\n"); + embedg_VES_print_virtual_vertex(embed_graph, n, vv); + fprintf(stdout, ":%d & ", vvout); + embedg_VES_print_vertex(n, v); + fprintf(stdout, ":%d\n", vin); + ) + + IF_DEB_ADJL( + fprintf(stdout, "merge_simple_bicomp, adj. list for %d (before)\n", vv); + embedg_VES_print_adj_list(embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list(embed_graph, n, vv, + FALSE); + fprintf(stdout, "\n"); + + fprintf(stdout, "merge_simple_bicomp, adj. list for %d (before)\n", v); + embedg_VES_print_adj_list(embed_graph, n, v, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list(embed_graph, n, v, + FALSE); + ) + /* + find all edges incident to vv and (re)set all references + to incidence to vv to incidence to v + + by the same token, find the root_edge [v^c, c] + + MOREVOVER, when vin == vvout, the bicomp (rooted by v^v = vv) + will be flipped: + we must invert the links of all the edges incident + to vv so that their further union with v's adjacency list + results in a consistent adjacency list for v! + + we do everything in one go + */ + + /* + very careful here: a root edge must ALSO be a TE + (because the same edge could have been added as a SCE) + */ + + root_edge = NIL; + edge = embed_graph[vv].link[vvout]; + ASSERT(embedg_VES_is_edge(n, edge)); + if (embed_graph[edge].neighbour == c + && embedg_VES_is_tree_edge(embed_graph, n, edge)) + { + root_edge = edge; + } + + if (vin == vvout) + /* + invert the links + */ + { + int in, out; + + in = embed_graph[edge].link[0]; + out = embed_graph[edge].link[1]; + embed_graph[edge].link[0] = out; + embed_graph[edge].link[1] = in; + } + /* + get the twin and set the neighbour there to v (was vv originally) + */ + twin = embedg_VES_get_twin_edge(embed_graph, n, edge); + ASSERT(embed_graph[twin].neighbour == vv); + embed_graph[twin].neighbour = v; + + prev = vv; + cur = edge; + while (edge != vv) + { + edge = + embedg_VES_get_next_in_dlcl(embed_graph, n, + cur, prev); + + if (embedg_VES_is_edge(n, edge)) + /* + get the twin again (and invert the links if need be) + */ + { + if (embed_graph[edge].neighbour == c + && embedg_VES_is_tree_edge(embed_graph, n, edge)) + { + root_edge = edge; + } + + if (vin == vvout) + { + int in, out; + + in = embed_graph[edge].link[0]; + out = embed_graph[edge].link[1]; + embed_graph[edge].link[0] = out; + embed_graph[edge].link[1] = in; + } + + twin = + embedg_VES_get_twin_edge(embed_graph, n, edge); + ASSERT(embed_graph[twin].neighbour == vv); + embed_graph[twin].neighbour = v; + + prev = cur; + cur = edge; + } + else + { + ASSERT(edge == vv); + /* + only one vertex in the whole circular list + */ + } + } + ASSERT(root_edge != NIL); + + /* + and now union the adjacency lists of v and vv: + + let e1 be the edge record used to enter v + e2 exit v + e3 enter vv + e4 exit vv : + + e1 -> v -> e2 + e3 -> vv -> e4 + + the union of the list is done in such a way that + - e1 and e4 are consecutive in v's adjacency list: + they are now in the internal face + - e3 is now the edge record used to enter v: + it is on the external face (along with e2) : + + e1 -> e4 + e3 -> v -> e2 + + (note that this does not assume that e1 & e2 are distinct + or that e3 & e4 are distinct) + */ + /* + I must not forget the case where v is a lone vertex: + this is the case where v has no DFS ancestor, ie when + v is the root of a tree in the DFS forest + */ + + e1 = embed_graph[v].link[vin]; + vout = 1 ^ vin; + e2 = embed_graph[v].link[vout]; + + if (e1 != v) + { + ASSERT(e2 != v); + ASSERT(embedg_VES_is_edge(n, e1)); + ASSERT(embedg_VES_is_edge(n, e2)); + } + + e4 = embed_graph[vv].link[vvout]; + ASSERT(embedg_VES_is_edge(n, e4)); + + vvin = 1 ^ vvout; + e3 = embed_graph[vv].link[vvin]; + ASSERT(embedg_VES_is_edge(n, e3)); + + /* + must take care of the adjacency list's consistency of traversal + (will be important only when recovering the embedding) + */ + if (e1 == e2) + { + ASSERT(embed_graph[e1].link[0] == embed_graph[e1].link[1]); + if (vin == vvout) + /* + the bicomp will be flipped: + must take 1 ^ vvout - difficult to explain -- later... + */ + { + e1out = 1 ^ vvout; + } + else + { + e1out = vvout; + } + } + else + { + e1out = embed_graph[e1].link[0] == v ? 0 : 1; + } + if (e3 == e4) + { + ASSERT(embed_graph[e3].link[0] == embed_graph[e3].link[1]); + e3out = 1 ^ vin; + e4in = vin; + } + else + { + e4in = embed_graph[e4].link[0] == vv ? 0 : 1; + e3out = embed_graph[e3].link[0] == vv ? 0 : 1; + } + + IF_DEB( + fprintf(stdout, "merge_simple_bicomp, before union of lists, e1\n"); + embedg_VES_print_edge(embed_graph, n, e1); + fprintf(stdout, "merge_simple_bicomp, e3\n"); + embedg_VES_print_edge(embed_graph, n, e3); + fprintf(stdout, "merge_simple_bicomp, e4\n"); + embedg_VES_print_edge(embed_graph, n, e4); + ) + + /* + make e1 and e4 consecutive in the adjacency list + */ + embed_graph[e1].link[e1out] = e4; + embed_graph[e4].link[e4in] = e1; + embed_graph[e3].link[e3out] = v; + embed_graph[v].link[vin] = e3; + + IF_DEB( + fprintf(stdout, "merge_simple_bicomp, after union of lists, e1\n"); + embedg_VES_print_edge(embed_graph, n, e1); + fprintf(stdout, "merge_simple_bicomp, e3\n"); + embedg_VES_print_edge(embed_graph, n, e3); + fprintf(stdout, "merge_simple_bicomp, e4\n"); + embedg_VES_print_edge(embed_graph, n, e4); + ) + + /* + also, want to "disable" vv links, meaning then that + vv is no longer a root of a bicomp + */ + embed_graph[vv].link[0] = embed_graph[vv].link[1] = vv; + + IF_DEB_ADJL( + fprintf(stdout, "merge_simple_bicomp, adj. list for %d (after)\n", vv); + embedg_VES_print_adj_list(embed_graph, n, vv, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list(embed_graph, n, vv, + FALSE); + fprintf(stdout, "\n"); + + fprintf(stdout, "merge_simple_bicomp, adj. list for %d (after)\n", v); + embedg_VES_print_adj_list(embed_graph, n, v, + TRUE); + fprintf(stdout, "\n"); + embedg_VES_print_adj_list(embed_graph, n, v, + FALSE); + ) + + ASSERT(embedg_VES_is_adj_list_consistent(embed_graph, n, v)); + + /* + finally, give an orientation to the (formerly) root edge [vv, c] + to keep traversal consistent (when recovering embedding) + */ + if (vin == vvout) + /* + flip: set the sign of the root edge to clockwise + + note: a bicomp is merged only once, so there is no need to + "flip" the root_edge's sign: it is set once at initialisation + and then changed here if need be. + */ + { + embed_graph[root_edge].sign = CLOCKW; + + IF_VERB( + fprintf(stdout, "merge_simple_bicomp, flip for %d, sign is now %d for %d of type %d\n", + c, embed_graph[root_edge].sign, root_edge, embed_graph[root_edge].type); + embedg_VES_print_edge(embed_graph, n, root_edge); + ) + } +} + + + +void +embedg_VES_merge_pertinent_bicomps (t_ver_edge *embed_graph, int n, + int vv, int vvout, int v, int vin) + /* + the bicomps to be merged are pertinent: on top (and before) + performing a simple merge, there are several things to do + related to the merging to pertinent bicomps + */ +{ + /* + a note of caution: + it is (very) likely that after a bicomp merge the resulting + bicomp is not biconnected (and hence traversal of the external face + of the bicomp via embedg_VES_get_succ_on_ext_face is non-sensical) + + remembering that a PERTINENT bicomp merge is ALWAYS followed + by a back edge embedding we see that the end result is then a bicomp + where again traversal of the external face + via embedg_VES_get_succ_on_ext_face will make sense + */ + t_dlcl *pertinent_list, *head, *rep_in_parent_list, *parent_list; + int c; + + /* + find c such that [v^c, c] is the root edge of the bicomp + rooted at vv = v^c + */ + c = vv - n; + ASSERT(embed_graph[c].DFS_parent == v); + + /* + two things to do first: + - remove vv from head of pertinent_bicomp_list of v + - remove c from separated_DFS_child_list of v + + one may ask the point of this since the separated_DFS_child_list + seems to mirror pertinent_bicomp_list: but this is not exactly so: + + pertinent_bicomp_list is ordered according to the activity + of the (virtual) vertices + + separated_DFS_child_list is ordered according to the vertices' + lowpoint values + in effect, it could (almost?*) be said that these two lists + are in reverse order (the *almost bit would warrant some thinking here) + */ + + /* + remove vv from head of pertinent_bicomp_list of v + */ + pertinent_list = head = embed_graph[v].pertinent_bicomp_list; + ASSERT(!embedg_dlcl_is_empty(pertinent_list)); + ASSERT(head->info == vv); + + IF_DEB( + fprintf(stdout, "merge_pertinent_bicomp, start: merge\n"); + embedg_VES_print_virtual_vertex(embed_graph, n, vv); + fprintf(stdout, ":%d & ", vvout); + embedg_VES_print_vertex(n, v); + fprintf(stdout, ":%d\n", vin); + ) + + IF_DEB( + fprintf(stdout, "merge_pertinent_bicomp, pertinent bicomp_list of %d (before)\n", v); + embedg_dlcl_print(embed_graph[v].pertinent_bicomp_list); + ) + + + embed_graph[v].pertinent_bicomp_list = + embedg_dlcl_delete_first(pertinent_list); + + IF_DEB( + fprintf(stdout, "merge_pertinent_bicomp, pertinent bicomp_list of %d (after)\n", v); + embedg_dlcl_print(embed_graph[v].pertinent_bicomp_list); + ) + + /* + vv = v^c: remove c from separated_DFS_child_list of v + */ + rep_in_parent_list = embed_graph[c].rep_in_parent_list; + ASSERT(!embedg_dlcl_is_empty(rep_in_parent_list)); + + parent_list = embed_graph[v].separated_DFS_child_list; + ASSERT(!embedg_dlcl_is_empty(parent_list)); + embed_graph[v].separated_DFS_child_list = + embedg_dlcl_delete_rec(parent_list, rep_in_parent_list); + + /* + that's it, it remains to merge, ie. union the adjacency list, + and flipping the bicomp if necessary + */ + embedg_VES_merge_simple_bicomps(embed_graph, n, + vv, vvout, v, vin); +} + + + + + + +/* + * embed_edge.c + */ + +/* + What: + ***** + + Implementing: + + Embedding an edge so that it lies on the external face of a bicomp. + We work here with the VES structure. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_EMBED(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + + +void +embedg_VES_embed_edge (t_ver_edge *embed_graph, int n, int *edge_pos, + int edge_type, int vv, int vvout, int w, int win) + /* + embed the edge (vv, w) (vv a virtual vertex, w a vertex) between + vv and the edge vvout + and the edge win and w + + so that after the embedding, one exits vv via (vv, w) and + enters w via the twin (w, vv) + */ +{ + int temp, tempin, tempout; + + ASSERT(edge_type == BE || edge_type == SCE); + ASSERT(embedg_VES_is_virtual_vertex(n, vv)); + ASSERT(embedg_VES_is_vertex(n, w)); + + IF_DEB( + fprintf(stdout, "embed_edge, (%d:%d)\n", vv, w); + ) + + /* + first, set the edge [vv, w] with the appropriate info + + when [vv, w] is a back edge there is some more work to do + (see the walkup procedure for the extra information we need + to copy here + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = w; + embed_graph[*edge_pos].type = edge_type; + embed_graph[*edge_pos].sign = CCLOCKW; + if (edge_type == BE) + { + ASSERT(embed_graph[w].adjacent_to == + embed_graph[vv - n].DFS_parent); + + /* + PLUS: originally when the back edge [w, vv] was + created (in the dfs preprocessing stage), it carried in + .in_adjl the index of this directed edge in the + adjacency list + + but now, note that we are actually inserting the + directed edge [vv, w] in vv's adjacency list, + meaning that in_adjl and twin_in_adjl + must be exchanged! + */ + embed_graph[*edge_pos].in_adjl = embed_graph[w].twin_in_adjl; + embed_graph[*edge_pos].twin_in_adjl = embed_graph[w].in_adjl; + + ASSERT(embed_graph[w].mult % 2 == 0); + /* + the original graph is always undirected: + we store its number of undirected edges + */ + embed_graph[*edge_pos].mult = embed_graph[w].mult / 2; + } + + /* + insert this edge between vertex record for vv + and edge record vv.link[vvout] + */ + temp = embed_graph[vv].link[vvout]; + + if (embed_graph[temp].link[0] == embed_graph[temp].link[1]) + /* + this needs special treatment to ensure consistency of + orientation + */ + { + ASSERT(embed_graph[temp].link[0] == vv); + tempin = 1 ^ vvout; + } + else + { + tempin = embed_graph[temp].link[0] == vv ? 0 : 1; + } + + IF_DEB( + fprintf(stdout, "embed_edge, edge out of vv\n"); + embedg_VES_print_edge(embed_graph, n, temp); + ) + + embed_graph[vv].link[vvout] = *edge_pos; + embed_graph[temp].link[tempin] = *edge_pos; + /* + the links for *edge_pos must also be "consistent" + */ + embed_graph[*edge_pos].link[vvout] = temp; + embed_graph[*edge_pos].link[vvout ^ 1] = vv; + + /* + now create/set the twin edge, the directed edge [w, vv] + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = vv; + embed_graph[*edge_pos].type = edge_type; + embed_graph[*edge_pos].sign = CCLOCKW; + if (edge_type == BE) + { + embed_graph[*edge_pos].in_adjl = embed_graph[w].in_adjl; + embed_graph[*edge_pos].twin_in_adjl = embed_graph[w].twin_in_adjl; + embed_graph[*edge_pos].mult = embed_graph[w].mult / 2; + } + + /* + and insert the twin edge between edge record w.link[win] + and vertex record for w + */ + temp = embed_graph[w].link[win]; + + if (embed_graph[temp].link[0] == embed_graph[temp].link[1]) + /* + again, special treatment to ensure consistency of orientation + */ + { + ASSERT(embed_graph[temp].link[0] == w); + tempout = 1 ^ win; + } + else + { + tempout = embed_graph[temp].link[0] == w ? 0 : 1; + } + + IF_DEB( + fprintf(stdout, "embed_edge, edge in of w\n"); + embedg_VES_print_edge(embed_graph, n, temp); + ) + + embed_graph[w].link[win] = *edge_pos; + embed_graph[temp].link[tempout] = *edge_pos; + /* + and consistent orientation + */ + embed_graph[*edge_pos].link[win] = temp; + embed_graph[*edge_pos].link[win ^ 1] = w; +} + + + +void +embedg_VES_add_edge (t_ver_edge *embed_graph, int n, int *edge_pos, + int v, int w, boolean MARK, int mark) + /* + add the edge (v, w): this is DIFFERENT from + embedg_VES_embed_edge in the sense + that the present function will only be used + when building the Kuratowski homeomorphs: + + that is, we are in a situation where the graph is NON planar + + consequently it doesn't matter much where in the adjacency + lists of v & w the edge is added: + let's say that we always add it at the beginning + + for our sanity's sake, we'll ensure that the resulting + adjacency lists remain consistent! + + and we add the edge as a BE! + PLUS we mark it with mark in MARK true + */ +{ + int temp; + + ASSERT(embedg_VES_is_vertex(n, v) || + embedg_VES_is_virtual_vertex(n, v)); + ASSERT(embedg_VES_is_vertex(n, w) || + embedg_VES_is_virtual_vertex(n, w)); + + IF_DEB( + fprintf(stdout, "add_edge, (%d:%d)\n", v, w); + ) + + /* + not sure this is the best place to do this: mark the endpoints + */ + if (MARK) + { + embed_graph[v].visited = mark; + embed_graph[w].visited = mark; + } + + /* + first, set the edge [v, w] with the appropriate info + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = w; + embed_graph[*edge_pos].type = BE; + /* + the edge's orientation will be the same as the vertex + */ + embed_graph[*edge_pos].sign = embed_graph[v].sign; + /* + and mark the edge + */ + if (MARK) + { + embed_graph[*edge_pos].visited = mark; + } + + /* + insert this edge between vertex record for v + and edge record v.link[1] + */ + temp = embed_graph[v].link[1]; + + IF_DEB( + fprintf(stdout, "add_edge, edge out of v\n"); + embedg_VES_print_edge(embed_graph, n, temp); + ) + + embed_graph[v].link[1] = *edge_pos; + embed_graph[temp].link[0] = *edge_pos; + /* + the links for *edge_pos must also be "consistent" + */ + embed_graph[*edge_pos].link[1] = temp; + embed_graph[*edge_pos].link[0] = v; + + /* + now create/set the twin edge, the directed edge [w, v] + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = v; + embed_graph[*edge_pos].type = BE; + embed_graph[*edge_pos].sign = embed_graph[w].sign; + if (MARK) + { + embed_graph[*edge_pos].visited = mark; + } + + /* + insert this edge between vertex record for w + and edge record w.link[1] + */ + temp = embed_graph[w].link[1]; + + IF_DEB( + fprintf(stdout, "add_edge, edge out of w\n"); + embedg_VES_print_edge(embed_graph, n, temp); + ) + + embed_graph[w].link[1] = *edge_pos; + embed_graph[temp].link[0] = *edge_pos; + /* + and consistent orientation + */ + embed_graph[*edge_pos].link[1] = temp; + embed_graph[*edge_pos].link[0] = w; +} + + +/* + * recover.c + */ + +/* + What: + ***** + + Implementing: + + From the VES data structure recover either the embedding ot + the obstruction into the + + t_sparseg_ver_struct, + t_sparseg_adjl_struct, + t_sparseg_embed_struct + + data types. + + + (This is no even quite true: for some obscure reason + I recover the obstruction as a dlcl[] structure to be + converted later. + The obvious reason being that it is easier to check as such. + Maybe I leave it as it is...) + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_EMBED_MULT(x) {} +#define IF_DEB_EMBED_LOOPS(x) {} +#define IF_DEB_EMBED(x) {} +#define IF_DEB_CHECK_EMBED(x) {} +#define IF_DEB_FACES(x) {} +#define IF_VERB(x) {} +#define IF_DEB_SCE(x) {} +#define IF_DEB_OBS(x) {} +#define IF_DEB_CHECK_OBS(x) {} +#define IF_CPU(x) {} + + + +/* aproto: header embed_graph_protos.h */ + +/* aproto: beginstatic -- don't touch this!! */ +static void embedg_recover_embedding_embed_mult + (t_dlcl **, t_embed_sparse_rep *, int, int, int, int, int *, boolean *, int *); +static void embedg_recover_embedding_embed_loops + (t_dlcl **, t_embed_sparse_rep *, int, int, int *, boolean *); +static t_dlcl **embedg_get_reduced_obs (t_dlcl **, int); +static boolean embedg_is_red_obs_K33 (t_dlcl **, int); +static boolean embedg_is_red_obs_K5 (t_dlcl **, int); +/* aproto: endstatic -- don't touch this!! */ + + +#ifndef PLANAR_IN_MAGMA +#endif + +void +embedg_recover_embedding ( + t_ver_sparse_rep *V, + t_adjl_sparse_rep *A, /* input (original sparse graph) */ + t_ver_edge *embed_graph, + int n, + int nbr_e, + t_dlcl **mult_edges, + t_ver_sparse_rep **vertices, + t_embed_sparse_rep **embedding +) + /* + recover the embedding + to prepare for the final Magma type for sparse & embedded graph + + we assume that all vertices/edges have been given their + orientation + + at this stage we also embed the multiple edges and loops + which were set aside in mult_edges by + sparseg_adjl_dfs_preprocessing: + + as it turns out the last bit is pretty hairy! + */ +{ + /* + the idea is to return an array of vertices and an array + representing the embedding + (careful: need to weedout the SCE) + + vertices: (*vertices)[i].first_edge contains index + to first edge in embedding + + embedding: a doubly linked circular list of edges, + for each record/edge e = (*embedding)[i]: + e.in_adjl: index in A of e + e.next: next edge in CLOCKW + (as an index in the embedding) + e.prev: previous edge in CLOCKW + (as an index in embedding) + e.inv: inverse edge (as an index in embedding) + e.mark: a mark for this edge + + let's say that this new array is a slimmed down version of embed_graph + + one issue to address: + - for edge e, find its index in A: this should be found + in either the embed_graph[v] record of the mult_edges[v] record + */ + int index_embed, v, mult, w, v_w_in_embed, new_first_edge; + boolean set_next; + + IF_DEB( + fprintf(stdout, "in recover emb.\n"); + sparseg_dlcl_print(mult_edges, n); + ); + + *vertices = (t_ver_sparse_rep *) + mem_malloc(sizeof(t_ver_sparse_rep) * n); + *embedding = (t_embed_sparse_rep *) + mem_malloc(sizeof(t_embed_sparse_rep) * 2 * nbr_e); + + index_embed = 0; + set_next = TRUE; + for (v = 0; v < n; v++) + { + int v_l, orient, in, out, e, cur_e, next_e; + + /* + we take v's label + */ + v_l = embed_graph[v].label; + + /* + first let's deal with the isolated vertex case: those + that refer to self + */ + if (embed_graph[v].link[0] == v) + { + int temp_index_embed; + + ASSERT(embed_graph[v].link[1] == v); + + /* + there may be [v, v] loops for this vertex, must check this + */ + temp_index_embed = index_embed - 1; + /* + temp_index_embed is pre-increased below + */ + embedg_recover_embedding_embed_loops(mult_edges, *embedding, + nbr_e, v, + &temp_index_embed, + &set_next); + + if (temp_index_embed > index_embed - 1) + /* + must fix beginning and end of adjacency list: + */ + { + (*vertices)[v_l].first_edge = index_embed; + (*embedding)[temp_index_embed].next = + (*vertices)[v_l].first_edge; + (*embedding)[(*vertices)[v_l].first_edge].prev = + temp_index_embed; + + index_embed = temp_index_embed; + index_embed += 1; + } + else + { + (*vertices)[v_l].first_edge = NIL; + } + continue; + } + + /* + get v's orientation, and from this decide the way in which + v's adjacency list will be traversed + (recall that the list is supposed to be consistent, so no bad + surprises) + */ + orient = embed_graph[v].sign; + in = orient == CCLOCKW ? 0 : 1; + out = 1 ^ in; + + e = embed_graph[v].link[out]; + while (embedg_VES_is_short_cut_edge(embed_graph, n, e)) + { + e = embed_graph[e].link[out]; + } + ASSERT(embedg_VES_is_edge(n, e) + && !embedg_VES_is_short_cut_edge(embed_graph, n, e)); + /* + strictly speaking there should be no SCEs left at this stage... + + if there are SCEs in v's list, it must be the case that + the list also contains tree or back edges... + */ + + (*vertices)[v_l].first_edge = index_embed; + + IF_DEB_EMBED( + fprintf(stdout, "recov. embed. DFI %d vertex %d at %d (edges) and %d (embedding)\n", + v, v_l, index_e, (*vertices)[v_l].first_edge); + ) + + cur_e = e; + while (TRUE) + { + next_e = embed_graph[cur_e].link[out]; + while (embedg_VES_is_short_cut_edge(embed_graph, n, next_e)) + { + next_e = embed_graph[next_e].link[out]; + } + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, next_e)); + + if (next_e == v) + /* + end of adjacency list + */ + { + break; + } + + ASSERT(embedg_VES_is_edge(n, next_e)); + + (*embedding)[index_embed].in_adjl = embed_graph[cur_e].in_adjl; + (*embedding)[index_embed].next = index_embed + 1; /* next in adj. + list */ + (*embedding)[index_embed].mark = NIL; /* mark */ + + /* + cur_e's twin is trickier: + we'll use twin's label field to store cur_e's index in + the embedding + + if cur_e's label != NIL this means that cur_e's twin + is already stored in edges/embedding and consequently + that cur_e.label = index of its twin (in the embedding) + + note that it is safe to do so since an edge's label + has no meaning + */ + if (embed_graph[cur_e].label != NIL) + { + (*embedding)[index_embed].inv = embed_graph[cur_e].label; + + /* + but fix the twin by the same token + */ + (*embedding)[embed_graph[cur_e].label].inv = index_embed; + ASSERT((*embedding)[embed_graph[cur_e].label].in_adjl == + embed_graph[cur_e].twin_in_adjl); + } + else + /* + we store cur_e's index in the embedding in twin's label + */ + { + int twin; + + twin = embedg_VES_get_twin_edge(embed_graph, n, cur_e); + embed_graph[twin].label = index_embed; + } + + /* + so the only thing we couldn't update yet is + (*embedding)[index_embed].prev, cur_e previous edge in the list + + but we can do this for next_e + */ + (*embedding)[index_embed + 1].prev = index_embed; + + /* + we check if there are any multiple edges or loops + to embed + */ + w = embed_graph[cur_e].neighbour; + mult = embed_graph[cur_e].mult - 1; + /* + one was for the TE or BE edge + */ + + if (index_embed == (*vertices)[v_l].first_edge) + /* + when looking for multiple edges/loops + we must temporarily "close" this ordered + list of vertices when in presence of the first + edge in the list: + + not doing this would mean that + (*embedding)[(*vertices)[v_l].first_edge].prev + contains some irrelevant value which may cause + (major) trouble when embedding inverses of + multiple edges... + */ + { + (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; + } + + embedg_recover_embedding_embed_mult(mult_edges, *embedding, + nbr_e, v, w, mult, + &index_embed, &set_next, + &new_first_edge); + embedg_recover_embedding_embed_loops(mult_edges, *embedding, + nbr_e, v, &index_embed, + &set_next); + set_next = TRUE; + + /* + yes, it may be the case that (*vertices)[v_l].first_edge + change while in embedg_recover_embedding_embed_mult + -- see that function for more + */ + (*vertices)[v_l].first_edge = new_first_edge == NIL ? + (*vertices)[v_l].first_edge : new_first_edge; + + /* + that's all, we proceed to read a new edge in the list + */ + index_embed += 1; + cur_e = next_e; + } + + /* + now next_e = v so that cur_e is the last edge in v's adjacency list + we must deal with this case separately + */ + + /* + fix cur_e in embedding (and its twin) + */ + (*embedding)[index_embed].in_adjl = embed_graph[cur_e].in_adjl; + + /* + we temporarily set next of cur_e in to index_embed + 1 + */ + (*embedding)[index_embed].next = index_embed + 1; + (*embedding)[index_embed].mark = NIL; /* mark */ + + /* + fix cur_e's twin + */ + if (embed_graph[cur_e].label != NIL) + { + (*embedding)[index_embed].inv = embed_graph[cur_e].label; + (*embedding)[embed_graph[cur_e].label].inv = index_embed; + ASSERT((*embedding)[embed_graph[cur_e].label].in_adjl == + embed_graph[cur_e].twin_in_adjl); + } + else + { + int twin; + + twin = embedg_VES_get_twin_edge(embed_graph, n, cur_e); + embed_graph[twin].label = index_embed; + } + + /* + we temporarily set the next record's prev field: + but we can do that only if we haven't processed + all the edges yet + */ + if (index_embed < 2 * nbr_e - 1) + { + (*embedding)[index_embed + 1].prev = index_embed; + + /* + again, check if there are any multiple edges/loops + to embed + */ + w = embed_graph[cur_e].neighbour; + mult = embed_graph[cur_e].mult - 1; + /* + one was for the TE or BE edge + */ + v_w_in_embed = index_embed; + + if (index_embed == (*vertices)[v_l].first_edge) + /* + same comment as above + */ + { + (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; + } + + embedg_recover_embedding_embed_mult(mult_edges, *embedding, + nbr_e, v, w, mult, + &index_embed, &set_next, + &new_first_edge); + embedg_recover_embedding_embed_loops(mult_edges, *embedding, + nbr_e, v, &index_embed, + &set_next); + + /* + same comment as above + */ + (*vertices)[v_l].first_edge = new_first_edge == NIL ? + (*vertices)[v_l].first_edge : new_first_edge; + } + + /* + to finish off, we must set: + + cur_e's next field: + next of cur_e in the list is ... vertices[v_l].first_edge + + cur_e's next's previous field... + */ + if (set_next) + /* + set_next (poorly named) is used to indicate which + edges must be updated to "close off" the list: + + if set_next is TRUE, we are in the standard case + where the last edge in the ordered adj. list + is at index_embed + + if set_next is FALSE, the last edge in the ordered adj. list + is at v_w_in_embed: because it could have happened + (in embedg_recover_embedding_embed_mult only) + that the edges have been "wedged" between + v_w_in_embed.prev and v_w_in_embed, + leaving v_w_in_embed the last in the list + */ + { + (*embedding)[index_embed].next = (*vertices)[v_l].first_edge; + (*embedding)[(*vertices)[v_l].first_edge].prev = index_embed; + } + else + { + (*embedding)[v_w_in_embed].next = (*vertices)[v_l].first_edge; + (*embedding)[(*vertices)[v_l].first_edge].prev = v_w_in_embed; + } + set_next = TRUE; + + /* + a simple check + */ + ASSERT(embedg_dlcl_is_empty(mult_edges[v])); + + /* + we can process another vertex + */ + index_embed += 1; + } + /* + when this is done there are a few things that must hold + */ + ASSERT(index_embed == 2 * nbr_e); +} + + +static void +embedg_recover_embedding_embed_mult (t_dlcl **mult_edges, + t_embed_sparse_rep *embedding, int nbr_e, int v, int w, + int mult, int *index_embed, boolean *set_next, int *first_edge) + /* + see if the directed edge [v, w] is multiple: if so embed it + in embedding + + moreover if there are any [v, v] loops do that too + */ +{ + /* + we take care of multiple edges: for tree edges and back + edges their multiplicity is indicated by the + embed_graph[cur_e].mult field (which records the number + of undirected edges) + + for loops hovewer this information is stored in the mult + field of the FIRST encountered neighbour v in v's neighbour + list + */ + t_dlcl *p; + int v_w_in_embed, v_w_prev; + boolean do_twins, start, do_first_edge; + + IF_DEB_EMBED_MULT( + fprintf(stdout, "in recover emb. mult, v %d w %d mult %d\n", + v, w, mult); + ) + + /* + the current index_embed value is the edge [v, w]: + I must record this value as it will be needed + later + */ + v_w_in_embed = *index_embed; + start = TRUE; + *set_next = TRUE; + do_twins = FALSE; + *first_edge = NIL; + do_first_edge = FALSE; + v_w_prev = NIL; + while (mult > 0) + { + ASSERT(!embedg_dlcl_is_empty(mult_edges[v])); + p = embedg_dlcl_find(mult_edges[v], w); + /* + note that using embedg_dlcl_find to always find + the first in the list with p->info == w + is ok here since any previous such records would + have been deleted/removed from the list + */ + ASSERT(p != NP); + /* + otherwise we couldn't have mult > 0 ! + */ + + *index_embed += 1; + + /* + once again I must use a similar sort of trick as in the + main function to deal with the inverse edge: + + the inverse edge is to be found in mult_edges[w]: + if p->twin_in_adjl (which was initialised to NIL + and has NOT been set in the DFS preprocessing), + if p->twin_in_adjl != NIL, then + a. its inverse in mult_edges[w] has already been embedded + in *embedding + b. its index there is stored in p->twin_in_adjl + precisely + */ + if (p->twin_in_adjl != NIL) + { + if (! start) + /* + if the first the multiple edges' inverse is already + stored, then this is true for ALL of them + */ + { + ASSERT(do_twins == TRUE); + } + do_twins = TRUE; + } + else + /* + similarly, if the first the multiple edges' inverse is + not already stored, then this is true for ALL of them + */ + { + ASSERT(do_twins == FALSE); + } + + embedding[*index_embed].in_adjl = p->in_adjl; + embedding[*index_embed].mark = NIL; + + /* + as we will see do_twins has to be treated differently + */ + if (!do_twins) + /* + this is pretty standard as works as the + main recover function + */ + { + t_dlcl *i_m_l, *i_p; + + embedding[*index_embed].next = *index_embed + 1; + + /* + we store the current index in the embedding in + the twin/inverse's twin_in_adjl field + */ + i_p = i_m_l = mult_edges[w]; + ASSERT(!embedg_dlcl_is_empty(i_m_l)); + i_p = embedg_dlcl_find_with_NIL_twin_in_adjl(i_m_l, v); + ASSERT(i_p != NP); + ASSERT(i_p->twin_in_adjl == NIL); + + i_p->twin_in_adjl = *index_embed; + + /* + to finish off this bit we set embedding[*index_embed + 1].prev + + but I can only set this prev field if I haven't reached + the end of the embedding[] array: this is why we needed + nbr_e (total number of edges to embed) as input + */ + + if (*index_embed < 2 * nbr_e - 1) + { + embedding[*index_embed + 1].prev = *index_embed; + } + } + else + /* + how to insert the inverses of multiple edges already + in the embedding: + + if one studies how the twin_in_adjl field has been + set while dealing with the inverses of the + present multiple edges one sees that + the latter must be inserted in counter clockwise + order (assuming that the inverses were inserted + in clockwise order) + + this is necessariy to ensure a correct matching between + the edge and its inverse + */ + { + + embedding[*index_embed].inv = p->twin_in_adjl; + + /* + fix the twin by the same token + */ + embedding[p->twin_in_adjl].inv = *index_embed; + + /* + general (reverse) insertion for these edges + */ + embedding[*index_embed].prev = *index_embed + 1; + embedding[*index_embed].next = *index_embed - 1; + + /* + ok, that was the easy bit, things are a bit more complicated + below... + */ + if (start) + /* + the edges are "wedged" between + embedding[v_w_in_embed].prev and v_w_in_embed, + + hence the following + */ + { + v_w_prev = embedding[v_w_in_embed].prev; + if (v_w_prev == v_w_in_embed) + /* + in this case the first edge in the adj. list + of the vertex whose first_edges is v_w_in_embed + will be changed + (because we insert in reverse order) + */ + { + do_first_edge = TRUE; + } + + embedding[*index_embed].next = v_w_in_embed; + embedding[v_w_in_embed].prev = *index_embed; + + ASSERT(embedding[embedding[*index_embed].inv].prev == + embedding[v_w_in_embed].inv); + ASSERT(embedding[embedding[v_w_in_embed].inv].next == + embedding[*index_embed].inv); + } + + if (mult == 1) + /* + last inv. edge in this list to add + */ + { + ASSERT(v_w_prev != NIL); + + /* + must fix embedding[v_w_prev].next appropriately + (and embedding[*index_embed].prev) + + this may be overwritten later on, but not necessarily so + + the next_set flag will enable us to decide + which edge ends this adjacency list: see above + */ + + embedding[*index_embed].prev = v_w_prev; + embedding[v_w_prev].next = *index_embed; + *set_next = FALSE; + + ASSERT(embedding[embedding[*index_embed].inv].prev == + embedding[*index_embed - 1].inv); + ASSERT(embedding[embedding[*index_embed - 1].inv].next == + embedding[*index_embed].inv); + + if (do_first_edge) + /* + the first edge is the last one added + */ + { + *first_edge = *index_embed; + } + + embedding[v_w_in_embed].next = *index_embed + 1; + if (*index_embed < 2 * nbr_e - 1) + { + embedding[*index_embed + 1].prev = v_w_in_embed; + } + } + + ASSERT(embedding[embedding[*index_embed].inv].prev == + embedding[embedding[*index_embed].next].inv); + } + + /* + to finish off this bit we delete the p record from m_l + and set embedding[*index_embed + 1].prev + */ + mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); + + mult--; + start = FALSE; + } + /* + conclusion: sevral days to get this working! *sigh* + */ +} + + + + +static void +embedg_recover_embedding_embed_loops (t_dlcl **mult_edges, + t_embed_sparse_rep *embedding, int nbr_e, int v, + int *index_embed, boolean *set_next) + /* + embed the [v, v] loops + */ +{ + /* + the loops' multiplicity is stored in the mult + field of the FIRST encountered neighbour v in v's neighbour + list + */ + t_dlcl *p; + int nbr_loops; + + /* + have a look if there are any [v. v] loops + */ + p = embedg_dlcl_find(mult_edges[v], v); + if (p == NP) + { + return; + } + + /* + when there are loops to add to the adjaceny list, + edge insertion resume in the "normal" clockwaise saya, way: + so we reset set_next to true + */ + *set_next = TRUE; + + nbr_loops = p->mult; + ASSERT(nbr_loops % 2 == 0); + /* + we counted directed edges + */ + nbr_loops /= 2; + + IF_DEB_EMBED_LOOPS( + fprintf(stdout, "in recover emb. loops, nbr_loops [v, v] %d\n", + nbr_loops); + ) + + while (nbr_loops > 0) + /* + a loop requires to embed two directed edges + */ + { + p = embedg_dlcl_find(mult_edges[v], v); + ASSERT(p != NP); + + *index_embed += 1; + + embedding[*index_embed].in_adjl = p->in_adjl; + embedding[*index_embed].next = *index_embed + 1; + embedding[*index_embed].mark = NIL; + embedding[*index_embed].inv = *index_embed + 1; + embedding[*index_embed + 1].prev = *index_embed; + + mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); + + IF_DEB_EMBED_LOOPS( + fprintf(stdout, "in recover emb. loops, mid\n"); + embedg_dlcl_print(mult_edges[v]); + ); + + /* + now do the "inverse" loop + */ + p = embedg_dlcl_find(mult_edges[v], v); + ASSERT(p != NP); + + *index_embed += 1; + + embedding[*index_embed].in_adjl = p->in_adjl; + embedding[*index_embed].next = *index_embed + 1; + embedding[*index_embed].mark = NIL; + embedding[*index_embed].inv = *index_embed - 1; + + if (*index_embed < 2 * nbr_e - 1) + { + embedding[*index_embed + 1].prev = *index_embed; + } + mult_edges[v] = embedg_dlcl_delete_rec(mult_edges[v], p); + + nbr_loops--; + + IF_DEB_EMBED_LOOPS( + fprintf(stdout, "in recover emb. loops, end\n"); + embedg_dlcl_print(mult_edges[v]); + ); + } +} + + + + +void +embedg_recov_embed_walk_proper_face (int n, int e, t_adjl_sparse_rep *A, + t_embed_sparse_rep *embedding, boolean MARK, int mark) + /* + do a proper face walk in the recovered embedding starting + at index e in the embedding + */ +{ + int cur, next; + + IF_DEB_FACES( + fprintf(stdout, "recov. emb. proper face walk\n"); + fprintf(stdout, "[-, %d] ", + A[embedding[e].in_adjl].end_vertex); + ) + + cur = e; + next = NIL; + while (next != e) + /* + to get the next in a proper face traversal: + get the previous of the cur's inverse + */ + { + int inv; + + inv = embedding[cur].inv; + next = embedding[inv].prev; + + ASSERT(embedding[next].mark != mark); + + if (MARK) + { + embedding[next].mark = mark; + } + + cur = next; + IF_DEB_FACES( + fprintf(stdout, "[-, %d] ", + A[embedding[cur].in_adjl].end_vertex); + ) + } + IF_DEB_FACES( + fprintf(stdout, "\n"); + ) +} + + + +boolean +embedg_check_recov_embedding (int n, int nbr_e, int nbr_comp, + t_ver_sparse_rep *vertices, t_adjl_sparse_rep *A, + t_embed_sparse_rep *embedding) + /* + check if the recovered embedding is a valid embedding + SHOULD ONLY be use after creation, that is, after having + recovered the embedding from the VES structure + (because of the mark MIN_EMBED_MARK we use) + */ +{ + int v, e, f; + + f = 0; + /* + do all the edges in embedding: + careful: we have 2 * nbr_e to visit (the edge and its inverse!) + */ + for (e = 0; e < 2 * nbr_e; e++) + { + /* + we check if the current edge is marked: if not, we + traverse a proper face bordered by this edge + */ + if (embedding[e].mark != MIN_EMBED_MARK) + /* + we --hopefully-- perform this check only after creation + where mark == NIL + */ + { + embedg_recov_embed_walk_proper_face(n, e, A, embedding, + TRUE, MIN_EMBED_MARK); + f++; + } + } + + /* + must also count a face for each isolated vertex + */ + for (v = 0; v < n; v++) + { + if (vertices[v].first_edge == NIL) + f++; + } + + IF_DEB_CHECK_EMBED( + fprintf(stdout, "recovered embedding, n: %d\t e: %d\t C: %d\t f: %d\n", + n, nbr_e, nbr_comp, f); + ) + + return f == 2 * nbr_comp + nbr_e - n ? TRUE : FALSE; +} + + +t_dlcl ** +embedg_recover_obstruction (t_ver_edge *embed_graph, int n, minor m, int *nbr_e) + /* + recover the obstruction as a t_dlcl * structure: + and return the number of edges: lets say we agree on returning + the number of undirected edges + -- I don't know yet which way to do, directed or undirected??? + + so far in the algorithm we only dealt with DFIs, + but now, we retrieve the obstruction not wrt DFIs but + wrt the vertices' labels + */ +{ + /* + so I am looking, in embed_graph, for the vertices and edges + marked MARK_MINORS(n) + */ + + int v; + t_dlcl **obs; + + obs = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + for (v = 0; v < n; v++) + obs[v] = NP; + + *nbr_e = 0; + for (v = 0; v < 2*n; v++) + /* + must check real vertices as well as virtual vertices + */ + { + int e; + + if (embed_graph[v].link[0] == v) + /* + isolated vertex case + */ + { + ASSERT(embed_graph[v].link[1] == v); + continue; + } + + e = embed_graph[v].link[0]; + while (e != v) + { + ASSERT(embedg_VES_is_edge(n, e)); + if (embed_graph[e].visited == MARK_MINORS(n)) + { + int cur_v, neigh; + + /* + virtual vertices may still hang around + */ + /* + let's get the "actual" v: + note that the statement below is safe since if v were + not a valid virtual vertex (ie [v - n].DFS_parent = n) + it would have an empty + adjacency list and we wouldn't be there anyway + */ + cur_v = embedg_VES_get_ver(embed_graph, n, v); + + neigh = embedg_VES_get_ver(embed_graph, n, + embed_graph[e].neighbour); + + /* + again, cur_v and neigh are DFIs, + we want vertex labels at this stage + */ + cur_v = embed_graph[cur_v].label; + neigh = embed_graph[neigh].label; + sparseg_dlcl_append_to_neigh_list(obs, n, cur_v, neigh, + embed_graph[e].in_adjl); + (*nbr_e)++; + } + e = embed_graph[e].link[0]; + } + } + + IF_DEB_OBS( + fprintf(stdout, "recovering the obstruction\n"); + sparseg_dlcl_print(obs, n); + ); + + ASSERT(*nbr_e % 2 == 0); + *nbr_e /= 2; + + return obs; +} + + +static t_dlcl ** +embedg_get_reduced_obs (t_dlcl **obs, int n) + /* + reduce the obstruction by removing all degree 2 vertices + (so that they become isolated vertices) + */ +{ + t_dlcl **reduced; + int v; + + reduced = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + for (v = 0; v < n; v++) + { + reduced[v] = embedg_dlcl_copy(obs[v]); + } + + for (v = 0; v < n; v++) + { + t_dlcl *n_l, *n_l_b, *p, *new_n_v, *n_l_x, *b_in_n_x; + int a, b, n_x; + + n_l = reduced[v]; + while (!embedg_dlcl_is_empty(n_l) + && embedg_dlcl_list_last(n_l) == embedg_dlcl_list_next(n_l)) + /* + pick out which vertices have deg 2 + */ + { + a = n_l->info; + b = embedg_dlcl_list_next(n_l)->info; + /* + we remove the edge (v, b), or rather, we identify v and b: + b will then be an isolated vertex + + fix v's neighbour list: all of b's neighbours + are now v's neighbours + */ + reduced[v] = n_l = + embedg_dlcl_delete_rec(n_l, embedg_dlcl_list_last(n_l)); + + p = n_l_b = reduced[b]; + ASSERT(!embedg_dlcl_is_empty(n_l_b)); + n_x = p->info; + if (n_x != v) + { + new_n_v = embedg_dlcl_rec_new(n_x); + reduced[v] = n_l = embedg_dlcl_cat(n_l, new_n_v); + + /* + and in n_x neighbour list, we must replace b by v + */ + n_l_x = reduced[n_x]; + b_in_n_x = embedg_dlcl_find(n_l_x, b); + b_in_n_x->info = v; + } + /* + and do this for all of b's neighbours + */ + p = embedg_dlcl_list_next(p); + while (p != n_l_b) + { + n_x = p->info; + if (n_x != v) + { + new_n_v = embedg_dlcl_rec_new(n_x); + reduced[v] = n_l = embedg_dlcl_cat(n_l, new_n_v); + n_l_x = reduced[n_x]; + b_in_n_x = embedg_dlcl_find(n_l_x, b); + b_in_n_x->info = v; + } + p = embedg_dlcl_list_next(p); + } + embedg_dlcl_delete(reduced[b]); + reduced[b] = NP; + } + } + + IF_DEB_CHECK_OBS( + fprintf(stdout, "reducing the obstruction\n"); + sparseg_dlcl_print(reduced, n); + ) + + /* + now check no degree 2 vertices are left + */ + for (v = 0; v < n; v++) + { + t_dlcl *n_l; + + n_l = reduced[v]; + if (!embedg_dlcl_is_empty(n_l)) + { + ASSERT(embedg_dlcl_list_last(n_l) != embedg_dlcl_list_next(n_l)); + } + } + + return reduced; +} + +static boolean +embedg_is_red_obs_K33 (t_dlcl **reduced, int n) + /* + check if the (reduced) obstruction is indeed K33 + */ +{ + int v, order, vs[6], i, b1[3]; + + /* + check that order == 6 and that the obstruction is cubic + */ + order = 0; + for (v = 0; v < n; v++) + { + if (!embedg_dlcl_is_empty(reduced[v])) + { + if (order == 6) + { + return FALSE; + } + order++; + vs[order - 1] = v; + + if (embedg_dlcl_length(reduced[v]) != 3) + { + return FALSE; + } + } + } + if (order != 6) + { + return FALSE; + } + + /* + check if bipartite + */ + v = vs[0]; + ASSERT(!embedg_dlcl_is_empty(reduced[v])); + b1[0] = reduced[v]->info; + b1[1] = embedg_dlcl_list_next(reduced[v])->info; + b1[2] = embedg_dlcl_list_prev(reduced[v])->info; + + for (i = 1; i < 6; i++) + { + t_dlcl *n_v; + + v = vs[i]; + n_v = reduced[v]; + ASSERT(!embedg_dlcl_is_empty(n_v)); + if (n_v->info == b1[0] + || embedg_dlcl_list_next(n_v)->info == b1[0] + || embedg_dlcl_list_prev(n_v)->info == b1[0]) + { + if ((n_v->info != b1[1] + && embedg_dlcl_list_next(n_v)->info != b1[1] + && embedg_dlcl_list_prev(n_v)->info != b1[1]) + && + (n_v->info != b1[2] + && embedg_dlcl_list_next(n_v)->info != b1[2] + && embedg_dlcl_list_prev(n_v)->info != b1[2])) + { + return FALSE; + } + } + else + { + if ((n_v->info == b1[1] + || embedg_dlcl_list_next(n_v)->info == b1[1] + || embedg_dlcl_list_prev(n_v)->info == b1[1]) + || + (n_v->info == b1[2] + || embedg_dlcl_list_next(n_v)->info == b1[2] + || embedg_dlcl_list_prev(n_v)->info == b1[2])) + { + return FALSE; + } + } + } + + return TRUE; +} + + +static boolean +embedg_is_red_obs_K5 (t_dlcl **reduced, int n) + /* + check if the (reduced) obstruction is indeed K5 + */ +{ + int v, order; + + /* + check that order == 5 and that the obstruction is quadric + */ + order = 0; + for (v = 0; v < n; v++) + { + if (!embedg_dlcl_is_empty(reduced[v])) + { + if (order == 5) + { + return FALSE; + } + order++; + + if (embedg_dlcl_length(reduced[v]) != 4) + { + return FALSE; + } + } + } + + return TRUE; +} + + +boolean +embedg_check_recov_obs (t_dlcl **obs, int n, minor m) + /* + check if the recovered obstruction is one of K33 or K5 + */ +{ + t_dlcl **reduced; + boolean ans; + + reduced = embedg_get_reduced_obs(obs, n); + if (m != MINOR_E5) + { + ans = embedg_is_red_obs_K33(reduced, n); + } + else + { + ans = embedg_is_red_obs_K5(reduced, n); + } + + sparseg_dlcl_delete(reduced, n); + return ans; +} +/* + * obstruction.c + */ + +/* + What: + ***** + + Implementing: + + The graph is not planar: we recover the obstruction from the VES structure + and check it as well. + (Some of these checks will disappear later) + + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_OBS(x) {} +#define IF_DEB_CHECK_OBS(x) {} +#define IF_CPU(x) {} +#define IF_DEB_MINOR(x) {} + + +/* aproto: header embed_graph_protos.h */ + +void +embedg_obstruction ( + t_ver_sparse_rep *V, + t_adjl_sparse_rep *A, /* the input graph as a sparse graph */ + t_dlcl **dfs_tree, /* a sparse graph rep. for the dfs tree + -- vertices are as DFIs + -- and children are ordered wrt + lowpoint value + */ + t_dlcl **back_edges, /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + (vertices are given as DFIs) + */ + t_ver_edge *embed_graph, /* output of tester */ + int n, /* size of the graph */ + int *edge_pos, /* pos. in embed_graph for addition + of the next edge */ + int v, + int w_in, /* the unembedded directed back edge + [w_in, v] + */ + t_ver_sparse_rep **OV, /* the obstruction as an adjacency list */ + t_adjl_sparse_rep **OA, + int *nbr_e_obs /* obstruction's #edges */ +) + + /* + the graph is non planar: we must mark & recover the K33 or K5 + homeomorph + */ +{ + int *ver_orient; + minor m; + t_dlcl **obs; + + /* + this is magma code - must be removed + */ + float sttime, time_to_now; + + IF_CPU( + sttime = time_current_user(); + ) + + /* + we will NOT remove the short-cut edges at this stage: + we'll have to perform another walkdown in embedg_iso_is_minor_A + so + 1. saves time when looking for ext. active vertices + 2. more importantly this enables us to ascertain that the number of + edges in embed_graph (even after completing whichever obstruction + applying in this case) will NEVER be > 3*n - 5!!! + 3. SCEs are then removed in embedg_iso_is_minor_A + (obligatory path for every possible case) + */ + + /* + we must compute each vertex's orientation (wrt flipped bicomps) + and set the edges' orientation: + + the other day I was wondering why this was necessary in this + instance (because after all we won't get an embedding): + orientation is required bacause later in the piece we + do a proper face traversal (I guess for Minor C testing) + */ + ver_orient = embedg_vertices_orientation(embed_graph, n); + embedg_VES_set_orientation(embed_graph, n, ver_orient); + mem_free(ver_orient); + + m = embedg_mark_obstruction(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w_in); + + /* + get the obstruction + */ + obs = embedg_recover_obstruction(embed_graph, n, m, nbr_e_obs); + + /* + and check it + */ + if (!embedg_check_recov_obs(obs, n, m)) + { + sparseg_dlcl_delete(obs, n); + DIE(); + } + + sparseg_dlcl_to_sparseg(obs, n, *nbr_e_obs, OV, OA); + sparseg_dlcl_delete(obs, n); + + /* + just for the sake of it, chcek if the obstruction is + a subgraph of the input graph + */ + if (!sparseg_adjl_sub(*OV, n, *OA, V, n, A)) + { + DIE(); + } + + IF_DEB_OBS( + sparseg_adjl_print(*V, n, *A, FALSE); + ) + + IF_CPU( + fprintf(stdout, "CPU for obstruction recovering %f\n", + (time_current_user() - sttime)); + ) +} + + + + + + + +minor +embedg_mark_obstruction ( + t_dlcl **dfs_tree, /* a sparse graph rep. for the dfs tree + -- vertices are as DFIs + -- and children are ordered wrt + lowpoint value + */ + t_dlcl **back_edges, /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + (vertices are given as DFIs) + */ + t_ver_edge *embed_graph, /* output of tester */ + int n, /* size of the graph */ + int *edge_pos, /* pos. in embed_graph for addition + of the next edge */ + int v, + int w_in /* the unembedded directed back edge + [w_in, v] + */ +) + /* + the graph is non planar: we must mark & recover the K33 or K5 + homeomorph + */ +{ + int c, vr, x, y, w; + int *path_v, *path_e, nbr_v, entry_in_path_e; + boolean px_attached_high, py_attached_high, is_minor_D; + minor m; + + + IF_CPU( + float sttime; float time_to_now; + + sttime = time_current_user(); + ) + + + /* + find c such that v^c is the root of the biconnected + component on which the walkdown failed + */ + c = embedg_iso_get_c_of_v(embed_graph, n, v, w_in); + + /* + now: decide which minor we are dealing with and mark the + appropriate one (vertices/edges marked as MARK_MINOR(n) + in embed_graph) + */ + if (embedg_iso_is_minor_A(embed_graph, n, edge_pos, v, c, &vr)) + { + embedg_mark_minor_A(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, c, vr); + + IF_DEB_MINOR( + fprintf(stdout, "Minor A\n"); + ) + + return MINOR_A; + } + + /* + get the externally active vertices x & y and the pertinent w + on the external face of the bicomp rooted by v^c + + and determine if minor B + */ + if (embedg_iso_is_minor_B(embed_graph, n, edge_pos, v, c, + &x, &y, &w)) + { + embedg_mark_minor_B(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, c, + x, y, w); + IF_DEB_MINOR( + fprintf(stdout, "Minor B\n"); + ) + + IF_CPU( + fprintf(stdout, "CPU for obstruction isolation %f\n", + time_current_user() - sttime); + ) + + return MINOR_B; + } + + /* + the remaining cases: must get the highest x-y path + + it will be containing in path_v (vertices), path_e (edges) + */ + embedg_iso_get_highest_x_y_path(embed_graph, n, MARK_EXT_FACE(n), + MARK_EXT_FACE_L(n), + MARK_EXT_FACE_R(n), + v, c, x, y, w, + &path_v, &path_e, + &nbr_v, &entry_in_path_e, + &px_attached_high, + &py_attached_high, + &is_minor_D); + + /* + we are in the minor C case if either one of p_x or p_y + is attached high + */ + if (px_attached_high || py_attached_high) + { + embedg_mark_minor_C(dfs_tree, back_edges, embed_graph, n, edge_pos, + v, c, x, y, w, + path_v, path_e, nbr_v, + px_attached_high, py_attached_high); + IF_DEB_MINOR( + fprintf(stdout, "Minor C\n"); + ) + + mem_free(path_v); + mem_free(path_e); + + IF_CPU( + fprintf(stdout, "CPU for obstruction isolation %f\n", + time_current_user() - sttime); + ) + + return MINOR_C; + } + + if (is_minor_D) + { + embedg_mark_minor_D(dfs_tree, back_edges, embed_graph, n, edge_pos, + v, c, x, y, w, + path_v, path_e, nbr_v, entry_in_path_e); + IF_DEB_MINOR( + fprintf(stdout, "Minor D\n"); + ) + + mem_free(path_v); + mem_free(path_e); + + IF_CPU( + fprintf(stdout, "CPU for obstruction isolation %f\n", + time_current_user() - sttime); + ) + + return MINOR_D; + } + + /* + finally, the minor E case + */ + m = embedg_mark_minor_E(dfs_tree, back_edges, embed_graph, n, edge_pos, + v, c, x, y, w, + path_v, path_e, nbr_v); + switch (m) + { + case MINOR_E1: + IF_DEB_MINOR( + fprintf(stdout, "Minor E1\n"); + ) + break; + case MINOR_E2: + IF_DEB_MINOR( + fprintf(stdout, "Minor E2\n"); + ) + break; + case MINOR_E3: + IF_DEB_MINOR( + fprintf(stdout, "Minor E3\n"); + ) + break; + case MINOR_E4: + IF_DEB_MINOR( + fprintf(stdout, "Minor E4\n"); + ) + break; + case MINOR_E5: + IF_DEB_MINOR( + fprintf(stdout, "Minor E5\n"); + ) + break; + case MINOR_A: + case MINOR_B: + case MINOR_C: + case MINOR_D: + case MINOR_E: + case NBR_MINORS: + break; + } + + mem_free(path_v); + mem_free(path_e); + + IF_CPU( + fprintf(stdout, "CPU (scaled) for obstruction isolation %f\n", + (time_current_user() - sttime) / e); + ) + + return m; +} +/* + * isolator.c + */ + +/* + What: + ***** + + Implementing: + + The graph is non planar: we isolate the obstruction. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} +#define IF_DEB_EDGES(x) {} +#define IF_CPU(x) {} +/* #define IF_DEB_MINOR(x) {x} -- Not Used */ + + +/* aproto: header embed_graph_protos.h */ + +#ifndef PLANAR_IN_MAGMA +#endif + + + + +int +embedg_iso_get_c_of_v (t_ver_edge *embed_graph, int n, int v, int w) + /* + the edge [v, w] (w a descendant of v) remains unembedded + after the walkdown returns + + find c such that v^c is the root of the biconnected + component on which the walkdown failed + */ +{ + /* + how to do this??? easy! follow the DFS tree path as given + by the field DFS_parent + */ + + int u; + + u = embed_graph[w].DFS_parent; + while (embed_graph[u].DFS_parent != v) + { + u = embed_graph[u].DFS_parent; + } + /* + this is guaranteed to succeed given the structure of the DFS tree + and the fact that there exists a back edge [w, v] + */ + + return u; +} + + +boolean +embedg_iso_is_minor_A (t_ver_edge *embed_graph, int n, + int *edge_pos, int v, int c, int *vr) + /* + determines if the obstruction is a minor A + */ +{ + /* + to do this we again call the walkdown routine with v^c as input, + the walkdown routine will fail (since there will be an + un-embedded back edge incident to v and to a vertex + in the subtree rooted by v^c) + + the obstruction is a minor A if the merge queue returned by the + walkdown is non-empty, if this is the case we return + the bicomp last appended to the queue + */ + int vv; + t_merge_queue q; + + vv = c + n; + + q = embedg_walkdown(embed_graph, n, edge_pos, vv); + /* + we MUST remove the SCEs here: this is the only place where it + will be done when looking for and recovering an obstruction + + this is safe since this very function applies to ALL cases! + */ + embedg_remove_SCE(embed_graph, n, *edge_pos); + + if (!embedg_merge_queue_empty(q)) + /* + the bicomp of interest is the last in the queue + */ + { + int r, rin, vrout; + + embedg_merge_queue_prune(&q, &r, &rin, vr, &vrout); + embedg_merge_queue_delete(q); + return TRUE; + } + else + { + embedg_merge_queue_delete(q); + return FALSE; + } +} + + +void +embedg_iso_get_x_y_w (t_ver_edge *embed_graph, int n, int v, int r, + int c, int mark, int mark_l, int mark_r, int *x, int *y, int *w) + /* + the obstruction is one of minor B, C, D, E. + + get the externally active vertices x & y along the + external face paths starting at r^c + + get a pertinent vertex w along the lower external + face path between x and y + + external activity and pertinence are wrt v + + all the vertices on the external face r^c...x...w + and r^c...y...w will be marked (the visited field) + */ +{ + int vr, vrin, x_y[4]; + int s, sin, cur, curin; + + vr = c + n; + + /* + find x and y first: + + note that we mark the vertices on the external face r^c...x + and r^c...y + + more on that below + */ + embed_graph[vr].visited = mark; + for (vrin = 0; vrin <= 1; vrin++) + { + int m; + + m = vrin == 0 ? mark_l : mark_r; + embedg_VES_get_succ_ext_active_on_ext_face(embed_graph, n, v, + vr, vrin, + TRUE, m, + &s, &sin); + x_y[vrin] = s; + x_y[vrin + 2] = sin; + /* + note the bizarre way I store the active vertex + and the direction out of which to continue a walk + on the lower external face as described above + */ + } + *x = x_y[0]; + *y = x_y[1]; + + /* + next get the pertinent w on the lower external face from x to y + */ + cur = x_y[0]; + curin = x_y[2]; + embedg_VES_get_succ_pertinent_on_ext_face(embed_graph, n, v, + cur, curin, + TRUE, mark_l, w, &sin); + + /* + now all the vertices r^c...x...w and r^c...y have been marked, + it remains to mark the vertices on the y...w external face path + + (will need to be able to distinguish the external face later on) + + Note the way the external face is marked (needed when recovering + the highest x-y path): + mark_l for the path v^c...x...w + mark_r for the path v^c...y + mark for the lower external face y...w + */ + cur = x_y[1]; + curin = x_y[3]; + s = n; + while (s != *w) + { + embedg_VES_get_succ_pertinent_on_ext_face(embed_graph, n, v, + cur, curin, + TRUE, mark, &s, &sin); + cur = s; + curin = sin; + } + + IF_DEB( + fprintf(stdout, "get x, y & w: the external face\n"); + fprintf(stdout, "%d\t", vr); + cur = vr; + curin = 0; + while (s != vr) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + cur, curin, + FALSE, 0, &s, &sin); + cur = s; + curin = sin; + fprintf(stdout, "%d\t", s); + } + fprintf(stdout, "\n"); + ) +} + + + + +boolean +embedg_iso_is_minor_B (t_ver_edge *embed_graph, int n, int *edge_pos, + int v, int c, int *x, int *y, int *w) + /* + determines if the obstruction is a minor B and return x, y + (ext. active) and w (pertinent) + */ +{ + /* + get x & y the ext. active vertices on the (external face) + path out of v^c, + and w the pertinent vertex on the lower external face x-y + + PLUS mark the whole external face with MARK_EXT_FACE(n) + */ + embedg_iso_get_x_y_w(embed_graph, n, v, v, c, + MARK_EXT_FACE(n), + MARK_EXT_FACE_L(n), MARK_EXT_FACE_R(n), + x, y, w); + + if (embedg_dlcl_is_empty(embed_graph[*w].pertinent_bicomp_list)) + /* + w has no pertinent child bicomp: not a minor B + */ + return FALSE; + else + { + t_dlcl *pert_l; + int l; + + pert_l = embed_graph[*w].pertinent_bicomp_list; + l = embedg_dlcl_list_last(pert_l)->info; + /* + if w has an ext. active pertinent child bicomp then minor B + + note that we need to know if w has an ext. active AND pertinent + bicomp child: so it is NOT good enough to test + w's separated_DFS_child_list as is done in + embedg_VES_is_ver_ext_active!!!!!!!!! + + PLUS: l is actually a VIRTUAL vertex: to check its lowpoint + I must take its DFS child l - n !!!!!!!! + */ + ASSERT(embedg_VES_is_virtual_vertex(n, l)); + l = l - n; + return embed_graph[l].lowpoint < v ? TRUE : FALSE; + } +} + +void +embedg_iso_get_highest_x_y_path ( + t_ver_edge *embed_graph, + int n, + int mark, + int mark_l, + int mark_r, + int v, + int c, + int x, + int y, + int w, + int **path_v, /* stack of vertices in x-y path */ + int **path_e, /* stack of egdes in x-y path */ + int *nbr_v, /* number of vertices in path_v */ + int *entry_in_path_e, /* the in direction for the FIRST edge in + path_e: needed later on *sigh* + */ + boolean *px_attached_high, + boolean *py_attached_high, + boolean *is_minor_D +) + /* + the obstruction is one of minor C, D, E. + + we want to recover the highest x-y path: + the obstructing path attached to the external faces v^c - x - w + and v^c - y - w + + while doing all this we also determine if the case is a minor C + or a minor D + */ +{ + /* + the path is obtained by walking the proper face starting at v + where ALL the edges incident to v^c BUT the ones bordering + the external face have been removed + + I won't I don't think remove these edges, but instead I'll be + implementing an "avoidance" walk + */ + + int vv, s, sin, p_x, p_y, cur_v, cur_vin; + int e, ein, s_e, s_ein; + boolean avoid_vv; + + /* + must start the walk at edge embed_graph[v^c].link[1 ^ 0], + (vvin = 0 is in direction of x, see embedg_iso_get_x_y_w) + */ + vv = c + n; + e = embed_graph[vv].link[1]; + ein = 0; /* because of adjacency list consistency */ + + *path_v = (int *) mem_malloc(sizeof(int) * n); + *path_e = (int *) mem_malloc(sizeof(int) * n); + (*nbr_v) = -1; + + /* + recall that in embedg_iso_get_x_y_w we did mark + (with mark, mark_l, mark_r) + ALL the vertices lying on the external face walk starting + & ending at v^c: we will use this fact to enable us + to decide if a vertex is on the external face + (as opposed to being on the internal face) + */ + + s = embed_graph[e].neighbour; + ASSERT(embed_graph[s].visited == mark_l); + /* + this must be the case since s lies on the external face + starting at v^c in x's direction + -- we push s onto the stack + */ + (*path_v)[++(*nbr_v)] = s; + + /* + start the proper face walk which "avoids" v^c since the + internal edges incident to v^c are supposed to have + been removed + + please read on + */ + avoid_vv = FALSE; + while (TRUE) + { + boolean av; + + av = + embedg_VES_get_succ_on_proper_face_with_avoidance( + embed_graph, n, + e, ein, vv, + FALSE, 0, + &s, &s_e, &s_ein); + avoid_vv = av == TRUE ? av : avoid_vv; + if (embed_graph[s].visited == mark_l) + /* + means that s is still on the external face: + empty the path's stack and push s + */ + { + (*nbr_v) = -1; + (*path_v)[++(*nbr_v)] = s; + e = s_e; + ein = s_ein; + } + else if (*nbr_v == 0) + /* + s is the first encountered vertex after + path_v[0] which does not + lie on the external face v^c...c...w + + given the way we pushed things on the vertex stack, path_v[0] + will be the point of attachement of the x-y path + on the v^c...x...w external face + + path_e[0] will contain nothing: a dummy + + path_e[1] will be the first edge in the x-y path + (and entry_in_path will give the in-direction to this edge) + + oh yes!, we break the loop at this point if + the vertex s lies on the v^c...y...w external face + */ + { + ASSERT(embed_graph[(*path_v)[0]].visited == mark_l); + /* + the first vertex on the path must be on the + v^c...x...w external face + */ + (*path_v)[++(*nbr_v)] = s; + /* + and now we also push the edge on the edge stack + + I'll need this later to initiate a proper face walk + starting at the first vertex/edge in the x-y path, + which is the same as starting from s_e + */ + (*path_e)[*nbr_v] = s_e; + *entry_in_path_e = s_ein; + e = s_e; + ein = s_ein; + + /* + since we are at the start of the path, we must not + forget to reset avoid_vv + */ + avoid_vv = FALSE; + + if (embed_graph[s].visited == mark_r + || embed_graph[s].visited == mark) + /* + we have reached the v^c...y...w external face: + we can stop here + */ + { + break; + } + + /* + if not finished yet, + we also mark s (and path_v[0]) as visited: + later on we'll need to recognise which of the vertices + in path have already been encountered + (in case of encountering a cut-vertex due to the + "removal" of the "internal" edges incidnet ot v^c) + + note that we mark s as visited iff s if not already + on the v^c..y..w external face + */ + + ASSERT(embedg_VES_is_vertex(n, (*path_v)[0])); + ASSERT(embedg_VES_is_vertex(n, s)); + + embed_graph[s].visited = MARK_X_Y_PATH(n); + } + else if (embed_graph[s].visited == MARK_X_Y_PATH(n)) + /* + this means that s is a cut vertex on the internal + face walk: pop all the vertices from path + until s's last occurrence in path + */ + { + ASSERT((*nbr_v) >= 0); + while ((*path_v)[(*nbr_v)] != s) + { + (*nbr_v)--; + ASSERT((*nbr_v) >= 0); + /* + note that s should be somewhere in path! + */ + } + /* + note also that popping from path_v also implies + popping from path_e + */ + e = s_e; + ein = s_ein; + } + else + /* + we push s and s_e on their respective stacks + */ + { + (*path_v)[++(*nbr_v)] = s; + (*path_e)[*nbr_v] = s_e; + e = s_e; + ein = s_ein; + + if (embed_graph[s].visited == mark_r + || embed_graph[s].visited == mark) + /* + again, s lies on the v^c...y...w external face: + we end the walk: path_v now contains the highest x-y path + + note that there can be no conflict between + mark_r or mark and MARK_X_Y_PATH(n) since + we mark with MARK_X_Y_PATH iff the vertex + is NOT marked with mark_r/mark! + */ + { + break; + } + else + /* + we must mark this vertex as MARK_X_Y_PATH since we aren't + finished yet + */ + { + embed_graph[s].visited = MARK_X_Y_PATH(n); + } + } + } + + /* + there is only one thing remaining to do: see if p_x or + p_y are attached high + (ie closer to v^c than x or y resp.) + + we walk the external face starting at v^c in y's direction + (again see embedg_iso_get_x_y_w) + */ + *px_attached_high = TRUE; + p_x = (*path_v)[0]; + /* + p_y denotes the attachement point of the x-y path + on the v^c...y...w external face + */ + + s = n; + cur_v = vv; + cur_vin = 0; + while (s != p_x) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + cur_v, cur_vin, + FALSE, 0, &s, &sin); + if (s == x) + { + *px_attached_high = FALSE; + break; + } + cur_v = s; + cur_vin = sin; + } + + *py_attached_high = TRUE; + p_y = (*path_v)[*nbr_v]; + /* + p_y denotes the attachement point of the x-y path + on the v^c...y...w external face + */ + + s = n; + cur_v = vv; + cur_vin = 1; + while (s != p_y) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + cur_v, cur_vin, + FALSE, 0, &s, &sin); + if (s == y) + { + *py_attached_high = FALSE; + break; + } + cur_v = s; + cur_vin = sin; + } + + /* + now we are in the minor C case if either p_x or p_y are + attached high + + the minor D case: + this happens when there is a path v^c - z where z lies + on the x-y path + + that is, when + + either v^c has been effectively "avoided" within the + embedg_VES_get_succ_on_proper_face_with_avoidance function + BUT ONLY if this "avoidance" happened AFTER having + encountered the very first vertex on the x-y path! + + or when a cut vertex has been encountered on the x-y path: + separable components on this walk can only occur + if one walks the face while skipping the edges incident to v^c + + in any case this means that checking the return from + the embedg_VES_get_succ_on_proper_face_with_avoidance function + is enough: this is the purpose of avoid_vv. + */ + + *is_minor_D = !(*px_attached_high || *py_attached_high) && avoid_vv; + + + IF_DEB( + int i; + + fprintf(stdout, "x-y path\t"); + for (i = 0; i <= *nbr_v; i++) + fprintf(stdout, "%d\t", (*path_v)[i]); + fprintf(stdout, "\n"); + ) +} + + +/* + * embedg_misc.c + */ + +/* + What: + ***** + + Implementing: + + Some high level routinse for the VES structure. + See VES_misc.c. + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} + + + +/* aproto: header embed_graph_protos.h */ + +#ifndef PLANAR_IN_MAGMA +#endif + + + + +void +embedg_VES_delete (t_ver_edge *embed_graph, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + embedg_dlcl_delete(embed_graph[i].separated_DFS_child_list); + /* + embedg_dlcl_delete(embed_graph[i].rep_in_parent_list); + + NO!!! this points to something in separated_DFS_child_list + */ + embedg_dlcl_delete(embed_graph[i].pertinent_bicomp_list); + } + mem_free(embed_graph); +} + + + +void +embedg_VES_print (t_ver_edge *embed_graph, int n) +{ + int i; + + fprintf(stdout, "vertices\n"); + for (i = 0; i < n; i++) + { + t_ver_edge rec; + + rec = embed_graph[i]; + + fprintf(stdout, "\nDFI\t%d\tlabel\t%d\n", i, rec.label); + fprintf(stdout, "DFS parent\t%d\tleast_a\t%d\tlowpoint\t%d\n", + rec.DFS_parent, rec.least_ancestor, rec.lowpoint); + fprintf(stdout, "separated_DFS_child_list\n"); + embedg_dlcl_print(rec.separated_DFS_child_list); + } + + fprintf(stdout, "\nvirtual vertices\n"); + for (i = n; i < 2*n; i++) + { + int c; + + c = i - n; + fprintf(stdout, "%d^%d\t", embed_graph[c].DFS_parent, c); + } + fprintf(stdout, "\n"); + + embedg_VES_print_bigcomps(embed_graph, n); +} + + +void +embedg_VES_print_bigcomps (t_ver_edge *embed_graph, int n) + /* + walking the external faces of all the bicomp; for testing only + */ +{ + int i; + + fprintf(stdout, "bicomponents\n"); + /* + to get to the bicomps, it makes sense to start at the + virtual vertices???? + */ + for (i = n + 1; i < 2*n; i++) + /* + a note of caution: there is no virtual vertex at + embed_graph[n] since that would mean a virtual vertex x^0 + which makes no sense (0 is the root of the dfs_tree) + */ + { + embedg_VES_walk_bicomp(embed_graph, n, i, 0); + } + fprintf(stdout, "\n"); +} +/* + * planar_alg_init.c + */ + +/* + What: + ***** + + Implementing: + + Initialising the embed_graph aka VES data structure from the information + collected from the DFS. + + The embed_graph/VES data structure is an array consisting of vertices, + virtual vertices and edges; + vertices, virtual vertices and edges share a common record structure; + one of the particular features is that any vertex is linked + together with its incident edges into a doubly circular linked list. + + See also VES_misc.c. + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_DFS(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} +#define IF_CPU(x) {} + + +/* aproto: header embed_graph_protos.h */ + +/* aproto: beginstatic -- don't touch this!! */ +static void embedg_init_insert_TE (t_ver_edge *, int, int *, t_dlcl *); +/* aproto: endstatic -- don't touch this!! */ + +#ifndef PLANAR_IN_MAGMA +#endif + + +t_ver_edge * +embedg_planar_alg_init ( + t_ver_sparse_rep *V, + int n, + t_adjl_sparse_rep *A, /* input sparse graph */ + int *nbr_c, /* size of the graph, #components*/ + int *edge_pos, /* pos in the struct where the last edge + has been inserted + */ + t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree + -- vertices are as DFIs + */ + t_dlcl ***back_edges, /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + -- vertices are as DFIs + */ + t_dlcl ***mult_edges /* for each vertex v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v + -- vertices are as DFIs + */ +) + /* + initialising embed_graph, the fundamental data structure + underpinning the tester and obstruction isolator + + from there on, a vertex is exclusively referred to by its DFI!! + -- so forget about labels + */ +{ + int *dfs_nbr; /* dfs numbering for each vertex */ + int *dfs_order; /* vertices in dfs order */ + int *lowpoint; /* lowpoint value for each DFI */ + int *dfs_parent; /* for each DFI, its DFS ancestor + as a DFI (DFS index) + */ + int *least_a; /* for each DFI, its least ancestor's DFI + (via a back edge exclusively) + */ + + t_ver_edge *embed_graph; + int i; + + + IF_CPU( + float sttime; float time_to_now; + + sttime = time_current_user(); + ) + + ASSERT(n >= 1); + + /* + DFS and lowpoint calculations + ordering + */ + sparseg_adjl_dfs_preprocessing(V, n, A, nbr_c, + &dfs_nbr, &dfs_order, &lowpoint, + dfs_tree, back_edges, + &dfs_parent, &least_a, mult_edges); + + IF_CPU( + fprintf(stdout, "CPU for DFS only %f\n", + (time_current_user() - sttime)); + sttime = time_current_user(); + ) + + IF_DEB_DFS( + fprintf(stdout, "DFS indices\n"); + for (i = 0; i < n; i++) + fprintf(stdout, "%d ", dfs_nbr[i]); + fprintf(stdout, "\n"); + + fprintf(stdout, "DFS order\n"); + for (i = 0; i < n; i++) + fprintf(stdout, "%d ", dfs_order[i]); + fprintf(stdout, "\n"); + + fprintf(stdout, "lowpoint values\n"); + for (i = 0; i < n; i++) + fprintf(stdout, "%d ", lowpoint[i]); + fprintf(stdout, "\n"); + ); + + IF_VERB( + fprintf(stdout, "DFS parent\n"); + for (i = 0; i < n; i++) + fprintf(stdout, "%d ", dfs_parent[i]); + fprintf(stdout, "\n"); + ); + + IF_VERB( + fprintf(stdout, "least ancestors\n"); + for (i = 0; i < n; i++) + fprintf(stdout, "%d ", least_a[i]); + fprintf(stdout, "\n"); + ); + + IF_VERB( + for (i = 0; i < n; i++) + { + fprintf(stdout, "the list of children ordered by lowpoint for %d\n", + i); + embedg_dlcl_print((*dfs_tree)[i]); + } + ); + + IF_DEB_DFS( + fprintf(stdout, "the tree edges\n"); + sparseg_dlcl_print(*dfs_tree, n); + + fprintf(stdout, "the back edges\n"); + sparseg_dlcl_print(*back_edges, n); + + fprintf(stdout, "multiple edges\n"); + sparseg_dlcl_print(*mult_edges, n); + ); + + /* + create the data structure for the embedded graph: + it will have (max) size 2*n + 2 * MAXE(n) + + we will see that that number of edges is sufficient + even when later adding short-cut edges (see embedg_walkdown) + */ + embed_graph = (t_ver_edge *) mem_malloc(sizeof(t_ver_edge) + * (2*n + 2 * MAXE(n))); + /* + initialisation + */ + for (i = 0; i < 2*n + 2 * MAXE(n); i++) + /* + some fields are initialised to n as n is actually + an "invalid" value + */ + { + t_ver_edge rec; + + rec.label = NIL; + rec.DFS_parent = n; + rec.least_ancestor = n; + rec.lowpoint = n; + rec.separated_DFS_child_list = NP; + rec.rep_in_parent_list = NP; + rec.pertinent_bicomp_list = NP; + rec.adjacent_to = n; + rec.visited = n; + rec.neighbour = n; + rec.in_adjl = NIL; + rec.twin_in_adjl = NIL; + rec.mult = 0; + rec.type = NIL; + rec.sign = NILSIGN; + /* + make the links refer back to self + */ + rec.link[0] = rec.link[1] = i; + + embed_graph[i] = rec; + } + + /* + embed_graph[0..n-1]: the n vertices + ATTENTION: the vertices are stored according to their DFS numbering + */ + for (i = 0; i < n; i++) + { + t_ver_edge rec; + + rec = embed_graph[i]; + + rec.label = dfs_order[i]; + rec.DFS_parent = dfs_parent[i]; + rec.least_ancestor = least_a[i]; + rec.lowpoint = lowpoint[i]; + rec.separated_DFS_child_list = embedg_dlcl_copy((*dfs_tree)[i]); + + IF_VERB( + fprintf(stdout, "the list of children ordered by lowpoint for DFI %d\n", + i); + embedg_dlcl_print(rec.separated_DFS_child_list); + ); + + embed_graph[i] = rec; + } + + /* + one more thing to do for these vertices: + fix the rep_in_parent_list field + */ + for (i = 1; i < n; i++) + { + t_dlcl *parent_list, *rep; + int parent; + + parent = embed_graph[i].DFS_parent; /* careful: this is a DFI */ + /* + recall that the vertices in embed_graph are accessed via their DFI + */ + + if (parent != n) + /* + when parent == n this means that i the root of a DFS tree + in the disconnected graph + */ + { + parent_list = embed_graph[parent].separated_DFS_child_list; + rep = embedg_dlcl_find(parent_list, i); + ASSERT(rep != NP); + embed_graph[i].rep_in_parent_list = rep; + } + } + + /* + embed_graph[n..2*n-1]: the n virtual vertices + do I need to do anything here????? + + no - I don't think so + + let's try to explain what virtual vertices are: + let v^c be a virtual vertex: + - it is at position c + n in the array, + - c is the DFS child of v, + - v can be retrieved by taking embed_graph[c].DFS_parent, + - v^c is said virtual as long as the bicomp rooted by v^c is not + merged with the vertex v + - once v is merged (identified?) with v^c, then v^c + is of no relevance anymore + + below we will see that we embed all the tree edges as singleton + bicomps (bicomponent): (0^1, 1), (1^2, 2) etc...: + this is what virtual vertices are there for: + to distinguish them from their "real" counterpart with + which they will be ultimately merged + + the primary reason for this is: + while testing for planarity virtual vertices are the roots of bicomps + */ + + /* + now the edges: + we actually embed the tree edges so that each tree edge + forms a (singleton) biconnected component + + embedding an edge in effect means creating the + doubly linked circular list of [virtual] vertices & the edges incident + to it + + this list is built using the links 0 & 1 in embed_graph[i] + */ + + /* + for each tree edge (v,u) we embed (v^u, u) (v^u is the virtual vertex) + + CAREFUL: when talking about vertex v say, + we mean the vertex with DFI v, and NOT the vertex with label v + ************************************************************** + */ + *edge_pos = 2*n - 1; + /* + edge_pos will tell us where to insert the next edge in embed_graph[] + */ + for (i = 0; i < n; i++) + { + t_dlcl *te_l, *p; + + te_l = (*dfs_tree)[i]; + p = te_l; + + if (!embedg_dlcl_is_empty(p)) + { + /* + the test below is a bit stupid... well... + */ + ASSERT(embed_graph[p->info].DFS_parent == i); + + embedg_init_insert_TE(embed_graph, n, edge_pos, p); + p = embedg_dlcl_list_next(p); + while (p != te_l) + { + ASSERT(embed_graph[p->info].DFS_parent == i); + embedg_init_insert_TE(embed_graph, n, edge_pos, p); + + p = embedg_dlcl_list_next(p); + } + } + } + + mem_free(dfs_nbr); + mem_free(dfs_order); + mem_free(lowpoint); + + mem_free(dfs_parent); + mem_free(least_a); + + IF_CPU( + fprintf(stdout, "CPU for remainder of initialisation %f\n", + (time_current_user() - sttime)); + ) + + return embed_graph; +} + + +static void +embedg_init_insert_TE (t_ver_edge *embed_graph, int n, int *edge_pos, t_dlcl *p) + /* + init and insert a tree edge in embed graph: + + the tree edge will form a singleton bicomponent (v^c, c) + where c is p->info and v is c.DFS_parent + */ +{ + int c, v; + + c = p->info; + v = embed_graph[c].DFS_parent; + ASSERT(v >= 0 && v < n); + + /* + now (v, c) is a tree edge; embed the directed edge [v^c, c] + + -- and recall that v^c is a virtual vertex, at position c + n + in embed_graph, and that vertex c is at position c + */ + + /* + first, set this edge with the appropriate info + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = c; + embed_graph[*edge_pos].in_adjl = p->in_adjl; + embed_graph[*edge_pos].twin_in_adjl = p->twin_in_adjl; + + ASSERT(p->mult % 2 == 0); + /* + we want the number of undirected edges + */ + embed_graph[*edge_pos].mult = p->mult / 2; + embed_graph[*edge_pos].type = TE; + embed_graph[*edge_pos].sign = CCLOCKW; + + /* + link this with vertex v^c in a doubly linked circular list + */ + embed_graph[c + n].link[0] = + embed_graph[c + n].link[1] = *edge_pos; + embed_graph[*edge_pos].link[0] = + embed_graph[*edge_pos].link[1] = c + n; + + /* + now create/set the twin edge, the directed edge [c, v^c] + */ + (*edge_pos)++; + ASSERT(*edge_pos < 2*n + 2 * MAXE(n)); + embed_graph[*edge_pos].neighbour = c + n; + embed_graph[*edge_pos].in_adjl = p->twin_in_adjl; + embed_graph[*edge_pos].twin_in_adjl = p->in_adjl; + embed_graph[*edge_pos].mult = p->mult / 2; + embed_graph[*edge_pos].type = TE; + embed_graph[*edge_pos].sign = CCLOCKW; + + /* + and link it with vertex c in a doubly linked circular list + */ + embed_graph[c].link[0] = embed_graph[c].link[1] = *edge_pos; + embed_graph[*edge_pos].link[0] = + embed_graph[*edge_pos].link[1] = c; +} +/* + * dfs_preprocessing.c + */ + +/* + What: + ***** + + Implementing: + + A DFS as an initialisation step for the planarity tester. + This is an especially beefed up DFS that collects lots of + marginal information: + + - a DFS tree as a list of DFS children for each vertex + - the DFS children are sorted according to their lowpoint value + - a back_edge structure as a list of descendants v for each + vertex u such that [v, u] is a back edge + - a multiple edges structure which stores multiple (directed) edges + NOT in the DFS tree nor in the back_edge struc, and loops + + - the vertices in DFS order + - the DFS index (DFI) for each vertex + - the lowpoint value for each vertex + - the number of components of the (possibly disconnected) graph + - for each vertex, its DFS parent + - for each vertex v, its least ancestor u such that [v, u] + is a back edge + + ALL info above (except the vertices in DFS order) is given + in terms of the vertices' DFIs and NOT their labels. + + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +/* + There are some dodgy things which need some thought; it would be nice + to fix them so that the code get's cleaner: + + - we do store in_adj (and twin_in_adjl) for each directed edge: + + + this is ONLY needed at the time of recovering the embedding + (see embedg_recover_embedding) and its sole use is to establish + a link between an edge in the embedding structure + t_embed_sparse_rep *E and the corresponding edge + in the t_adjl_sparse_rep *A struc. + + + well, I cannot recall why I thought this correspondence + was needed in the first place and it might well be the case + that there is no use for it; in which case recovering the + embedding is simplified + (we would store the end-vertex in the embedding's edges instead + of their index in the adjacency list) + + - there are some non-linear bits in the DFS below: when searching + for an already existing tree/back/multiple edge. + I couldn't fix this in less then one hour so I leave it as it is... + for now. + + This shouldn't be a major issue, overall timings of the planarity + tester do not show this non-linear "bump"... + + - also, this algorithm has been growing incrementally and I now + realise that I am using some redundant data structures: + for example visited[] and the vertex and could be dispensed with... + ...more things to clean up... + + Paulette 07/02/02 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} + + +/* aproto: header embed_graph_protos.h */ + +#ifndef PLANAR_IN_MAGMA +#endif + + +void +sparseg_adjl_dfs_preprocessing ( + t_ver_sparse_rep *V, + int n, /* size of the graph */ + t_adjl_sparse_rep *A, /* input sparse graph */ + int *c, /* nbr of components */ + int **dfs_nbr, /* dfs numbering for each vertex */ + int **dfs_order, /* vertices in dfs order */ + int **lowpoint, /* lowpoint value for each DFI */ + t_dlcl ***dfs_tree, /* a sparse graph rep. for the dfs tree: + for each DFI, a list of its children's + DFI ordered wrt their lowpoint values + */ + t_dlcl ***back_edges, /* for each DFI v, a dlcl + of the back edges [v, x] incident to v + where x is a DESCENDANT of v */ + int **dfs_parent, /* for each DFI its DFS ancestor */ + int **least_a, /* for each DFI, its least ancestor's DFI + via a back edge exclusively */ + t_dlcl ***mult_edges /* for each DFI v, a dlcl + of the multiple directed + edges NOT included + in either dfs_tree or back_edges + */ +) + + /* + in ALL the returned info above BUT dfs_order[] we store + the vertices' DFIs (DFS indices) and NOT their labels! + + -- shuffling between labels and vertices can then be done + via dfs_nbr[] and dfs_order[] + */ +{ + int pos_v_stack, pos_e_stack, dfs_n; + int *visited, *vertex_stack, *edge_stack, *lowpoint_order; + int *TE_in_adjl, *TE_twin_in_adjl, *TE_mult; + int v, lp, cur, cur_e, next; + t_dlcl **temp, *lowpoint_list, **new_dfs_tree; + + /* + create the dfs tree as a sparse graph + */ + *dfs_tree = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + /* + the DFS numbering for the vertices + */ + *dfs_nbr = (int *) mem_malloc(sizeof(int) * n); + /* + the vertices as ordered by their DFS index + */ + *dfs_order = (int *) mem_malloc(sizeof(int) * n); + /* + the lowpoint value for each DFI + */ + *lowpoint = (int *) mem_malloc(sizeof(int) * n); + + /* + the (directed) back edges + */ + *back_edges = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + + + /* + the DFS parent for each DFI + */ + *dfs_parent = (int *) mem_malloc(sizeof(int) * n); + /* + the least ancestor (via a back edge exlusively) for each DFI + */ + *least_a = (int *) mem_malloc(sizeof(int) * n); + + /* + the (directed) multiple edges + */ + *mult_edges = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + + /* + the vertices visited while DFS + */ + visited = (int *) mem_malloc(sizeof(int) * n); + /* + stack of vertices: last current vertex + */ + vertex_stack = (int *) mem_malloc(sizeof(int) * n); + /* + stack of (tree) edges: last added tree edge + */ + edge_stack = (int *) mem_malloc(sizeof(int) * n); + + /* + the following will be used in order to recreate the dfs_tree + so that the DFS children of each DFI are ordered + according to their lowpoint value + */ + lowpoint_order = (int *) mem_malloc(sizeof(int) * n); + temp = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + new_dfs_tree = (t_dlcl **) mem_malloc(sizeof(t_dlcl *) * n); + + /* + finally, three more holding arrays: a trick to remember which + tree edges we are talking about: + + when constructing dfs_tree, back_edges, mult_edges + - we NEED to record the index in A (the adjacency list) + of some of the edges and their twins/inverses + we are currently storing in either of these structures + - we also need to record the number of multiple (directed) + edges we encounter when the graph is not simple + + this is easy to do when storing back edges and multiple edges, + and tree edges also: but this lattest set of neighbour lists (dfs_tree) + is subsequently reordered so that DFS children are ordered + wrt lowpoint values; + - consequently the info about position in adjacency list + and edge multiplicity are lost in the ordering process + + the two following arrays will remember the info we'll need later + - more about this below + */ + TE_in_adjl = (int *) mem_malloc(sizeof(int) * n); + TE_twin_in_adjl = (int *) mem_malloc(sizeof(int) * n); + TE_mult = (int *) mem_malloc(sizeof(int) * n); + + + /* + initialization of the data structures + */ + for (v = 0; v < n; v++) + { + (*dfs_tree)[v] = (*back_edges)[v] = (*mult_edges)[v] = NP; + visited[v] = TE_mult[v] = 0; + (*dfs_parent)[v] = (*least_a)[v] = n; + temp[v] = new_dfs_tree[v] = NP; + TE_in_adjl[v] = TE_twin_in_adjl[v] = NIL; + /* + note that in the 3rd last statement n is considered + as an "invalid" value; + will be if importance in the overall algorithm + */ + } + + /* + the DFS tree is rooted at vertex 0 + */ + dfs_n = -1; + pos_v_stack = -1; + pos_e_stack = -1; + *c = 0; + for (v = 0; v < n; v++) + { + if (visited[v]) + /* + we come only at this level when looking for + a new subtree (when graph is disconnected) + */ + { + continue; + } + else + { + (*c)++; + } + + cur = v; + visited[cur] = 1; + (*dfs_nbr)[cur] = ++dfs_n; + (*lowpoint)[(*dfs_nbr)[cur]] = dfs_n; + (*dfs_order)[dfs_n] = cur; + + cur_e = V[cur].first_edge == NIL ? NIL : V[cur].first_edge; + while (TRUE) + { + if (cur_e != NIL) + { + t_dlcl *existing_e; + + next = A[cur_e].end_vertex; + if (!visited[next]) + /* + adding tree edges (careful: directed edges) + + AND tree edges are stored as + [dfs_nbr[u], dfs_nbr[cv]] + instead of [u, cv]: that is we store the edges + according to the vertices' DFIs + */ + { + IF_DEB_TREE( + io_printf("add tree edge %d\t%d\n", + cur+1, next+1); + ); + + (*dfs_nbr)[next] = ++dfs_n; + (*lowpoint)[(*dfs_nbr)[next]] = dfs_n; + (*dfs_order)[dfs_n] = next; + + sparseg_dlcl_append_to_neigh_list(*dfs_tree, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + NIL); + TE_in_adjl[(*dfs_nbr)[next]] = cur_e; + TE_mult[(*dfs_nbr)[next]]++; + + /* + we push cur and the edge (cur, cur_e) on their + respective stacks + */ + vertex_stack[++pos_v_stack] = cur; + edge_stack[++pos_e_stack] = cur_e; + + /* + and mark next as visited + */ + visited[next] = 1; + + /* + update dfs_parent (always deal with DFIs rembember!) + */ + (*dfs_parent)[(*dfs_nbr)[next]] = (*dfs_nbr)[cur]; + + /* + the DFS goes one level deeper + */ + cur = next; + cur_e = V[cur].first_edge == NIL ? + NIL : V[cur].first_edge; + } + /* + the next three tests deal with multiple edges + and loops: apart from storing these (DIRECTED) edges + in mult_edges, we also need to update + the multipliciaty information about these edges + */ + else if (sparseg_dlcl_is_adjacent(*dfs_tree, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + &existing_e)) + /* + [cur, next] is a tree edge + */ + { + sparseg_dlcl_append_to_neigh_list(*mult_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + cur_e); + TE_mult[(*dfs_nbr)[next]]++; + + cur_e = A[cur_e].next; /* next in cur's adjacency list */ + } + else if (sparseg_dlcl_is_adjacent(*back_edges, n, + (*dfs_nbr)[next], + (*dfs_nbr)[cur], + &existing_e)) + /* + [cur, next] is a back edge + */ + { + sparseg_dlcl_append_to_neigh_list(*mult_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + cur_e); + (existing_e->mult)++; + + cur_e = A[cur_e].next; /* next in cur's adjacency list */ + } + else if (next == cur) + /* + the case of a loop + */ + { + if (sparseg_dlcl_is_adjacent(*mult_edges, n, + (*dfs_nbr)[next], + (*dfs_nbr)[cur], + &existing_e)) + /* + in this case we must update the multiplicity + of this edge: note that the elt. in cur's + neighbours list that gets updated is the first + in the list + + dodgy??? certainly, but can't think + of a better way to do this + + eventually it will happen that even myself + won't understand what I am doing.......... + */ + { + (existing_e->mult)++; + } + sparseg_dlcl_append_to_neigh_list(*mult_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + cur_e); + + cur_e = A[cur_e].next; /* next in cur's adjacency list */ + } + else if (sparseg_dlcl_is_adjacent(*dfs_tree, n, + (*dfs_nbr)[next], + (*dfs_nbr)[cur], + &existing_e)) + /* + [next, cur] is a tree edge: + that is, [cur, next] is [next, cur]'s twin/inverse: + + 1. if it is the first time one encounters + [cur, next] (as it would always be the case + for a simple graph) then all I need to do + is to update the tree edge's multiplicity, + and the twin info in TE_[] + + 2. if [cur, next] is actually a multiple edge, + then I'll need to store it in mult_edges; + and I update the tree edge's multiplicity too. + No twin info will be required here. + Why? see how recover.c embeds the multiple + edges in the planar embedding. + + 3. how do I know it is the first time I encounter + [cur, next]?: + when TE_twin_in_adjl = NIL + + 4. finally, note that the present counting scheme + implies that the mult field always holds + the number of directed edges: + ie, if [a, b] is a tree edge, [a, b].mult = 2 + because we would have counted [a, b] and [b, a] + + this applies to tree edges, back edges, and loops + */ + { + ASSERT(TE_in_adjl[(*dfs_nbr)[cur]] != NIL); + if (TE_twin_in_adjl[(*dfs_nbr)[cur]] == NIL) + { + TE_twin_in_adjl[(*dfs_nbr)[cur]] = cur_e; + } + else + { + sparseg_dlcl_append_to_neigh_list(*mult_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + cur_e); + } + + TE_mult[(*dfs_nbr)[cur]]++; + + cur_e = A[cur_e].next; /* next in cur's adjacency list */ + } + else if (sparseg_dlcl_is_adjacent(*back_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + &existing_e)) + /* + [next, cur] is a back edge: [cur, next] is its inverse: + we proceed as for the tree edge case above + */ + { + ASSERT(existing_e->in_adjl != NIL); + if (existing_e->twin_in_adjl == NIL) + { + existing_e->twin_in_adjl = cur_e; + } + else + { + sparseg_dlcl_append_to_neigh_list(*mult_edges, n, + (*dfs_nbr)[cur], + (*dfs_nbr)[next], + cur_e); + } + + (existing_e->mult)++; + + cur_e = A[cur_e].next; /* next in cur's adjacency list */ + } + /* + the next bit concludes the DFS: it deals with the case + where a back edge needs to be added + */ + else + /* + that is, next is visited and neither + the tree edge [next, cur] nor + the back edge [next, cur] exist: + + this implies that [cur, next] is a back edge + that must be added to the back_edges structure + (with dfs_nbr(next) < dfs_nbr(cur)) + */ + { + IF_DEB_TREE( + io_printf("add back edge %d\t%d\n", + cur+1, next+1); + ); + + ASSERT(visited[next]); + ASSERT((*dfs_nbr)[cur] > (*dfs_nbr)[next]); + + sparseg_dlcl_append_to_neigh_list(*back_edges, n, + (*dfs_nbr)[next], + (*dfs_nbr)[cur], + cur_e); + + /* + update cur's lowpoint + */ + (*lowpoint)[(*dfs_nbr)[cur]] = + (*dfs_nbr)[next] < (*lowpoint)[(*dfs_nbr)[cur]] ? + (*dfs_nbr)[next] : (*lowpoint)[(*dfs_nbr)[cur]]; + + /* + update least_a (of cur) + (always deal with DFIs remember!) + */ + (*least_a)[(*dfs_nbr)[cur]] = + (*dfs_nbr)[next] < (*least_a)[(*dfs_nbr)[cur]] ? + (*dfs_nbr)[next] : (*least_a)[(*dfs_nbr)[cur]]; + + /* + get the next edge in cur's adjacency list + */ + cur_e = A[cur_e].next; + } + } + + if (cur_e == NIL) + /* + we are either at a leaf or have finished scanning + cur's adjacency list: backtrack + */ + { + if (pos_v_stack == -1) /* no previous vertex */ + { + /* + no edge left on the stack: DFS ends for + this subtree: + we visit the next vertex + */ + ASSERT(pos_e_stack == -1); + break; + } + else + { + int prev_e; + /* + Otherwise backtrack and pop cur from the stack + as well as the last tree edge added to the tree. + We use next to get a new lowpoint value for cur: + This value will be min(lowpoint(cur), lowpoint(next)). + */ + cur = vertex_stack[pos_v_stack--]; + prev_e = edge_stack[pos_e_stack--]; + next = A[prev_e].end_vertex; + (*lowpoint)[(*dfs_nbr)[cur]] = + (*lowpoint)[(*dfs_nbr)[cur]] + < (*lowpoint)[(*dfs_nbr)[next]] ? + (*lowpoint)[(*dfs_nbr)[cur]] + : (*lowpoint)[(*dfs_nbr)[next]]; + + cur_e = A[prev_e].next; + } + /* + we proceed with DFS + */ + } + } + } + mem_free(vertex_stack); + mem_free(edge_stack); + + /* + just for the sake of it, check that all vertices have + been visited + */ +#ifdef ASSERTIONS + for (v = 0; v < n; v++) + { + ASSERT(visited[v]); + } +#endif + mem_free(visited); + + /* + we now order the DFIs wrt lowpoint values: + use bucket sort (linear time) + */ + /* + for each lowpoint value, collect the DFIs (in a t_dlcl) + with that lowpoint value + (IMPORTANT: we want the DFIs since the aim is to rewrite dfs_tree + which stores DFIs and not labels!) + */ + for (v = 0; v < n; v++) + /* + v is taken as a DFI here + */ + { + t_dlcl *r; + + r = embedg_dlcl_rec_new(v); + temp[(*lowpoint)[v]] = + embedg_dlcl_rec_append(temp[(*lowpoint)[v]], r); + } + + /* + concatenate these lists now + */ + lowpoint_list = temp[0]; + for (lp = 1; lp < n; lp++) + { + lowpoint_list = embedg_dlcl_cat(lowpoint_list, temp[lp]); + } + ASSERT(embedg_dlcl_length(lowpoint_list) == n); + + lowpoint_order[0] = lowpoint_list->info; + for (lp = 1; lp < n; lp++) + { + lowpoint_list = embedg_dlcl_list_next(lowpoint_list); + lowpoint_order[lp] = lowpoint_list->info; + } + embedg_dlcl_delete(lowpoint_list); + mem_free(temp); + + IF_DEB( + fprintf(stdout, "dfs_preprocessing, lowpoint_order\n"); + for (lp = 0; lp < n; lp++) + fprintf(stdout, "%d ", lowpoint_order[lp]); + fprintf(stdout, "\n"); + fprintf(stdout, "dfs_preprocessing, lowpoint\n"); + for (lp = 0; lp < n; lp++) + fprintf(stdout, "%d ", (*lowpoint)[lp]); + fprintf(stdout, "\n"); + ) + + /* + we now use this order to rewrite dfs_tree such that + the DFS children of each vertex are ordered wrt lowpoint values + */ + for (lp = 0; lp < n; lp ++) + /* + for each DFI in lowpoint_order[] I know its DFS_parent + from dfs_parent[] -- the rest is then trivial + */ + { + int parent; + + v = lowpoint_order[lp]; + /* + lowpoint_order stores DFIs as does dfs_parent, so the lot + makes sense + */ + parent = (*dfs_parent)[v]; + if (parent != n) + /* + v may be the root of a DFS tree + */ + { + t_dlcl *temp; + + temp = embedg_dlcl_rec_new(v); + + /* + this is where the TE_ holding arrays are useful *sigh* + */ + ASSERT(TE_in_adjl[v] != NIL); + temp->in_adjl = TE_in_adjl[v]; + + ASSERT(TE_twin_in_adjl[v] != NIL); + temp->twin_in_adjl = TE_twin_in_adjl[v]; + + ASSERT(TE_mult[v] != 0 && TE_mult[v] % 2 == 0); + temp->mult = TE_mult[v]; + + new_dfs_tree[parent] = + embedg_dlcl_rec_append(new_dfs_tree[parent], temp); + } + } + mem_free(lowpoint_order); + mem_free(TE_in_adjl); + mem_free(TE_twin_in_adjl); + mem_free(TE_mult); + + /* + some checks are in order here + */ +#ifdef ASSERTIONS + for (v = 0; v < n; v++) + { + ASSERT(embedg_dlcl_length((*dfs_tree)[v]) + == embedg_dlcl_length(new_dfs_tree[v])); + + IF_DEB( + fprintf(stdout, "dfs_preprocessing dfs_tree for %d\n", v); + embedg_dlcl_print((*dfs_tree)[v]); + fprintf(stdout, "dfs_preprocessing new_dfs_tree for %d\n", v); + embedg_dlcl_print(new_dfs_tree[v]); + ); + } +#endif + + sparseg_dlcl_delete(*dfs_tree, n); + *dfs_tree = new_dfs_tree; +} + +/* + * embedding.c + */ + +/* + What: + ***** + + Implementing: + + The graph is planar: we recover the embedding from the VES structure + and check it as well. + (Some of these checks will disappear later) + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_EMBED(x) {} +#define IF_DEB_CHECK_EMBED(x) {} +#define IF_DEB_FACES(x) {} +#define IF_VERB(x) {} +#define IF_DEB_SCE(x) {} +#define IF_CPU(x) {} + + +/* aproto: header embed_graph_protos.h */ + + +#ifndef PLANAR_IN_MAGMA +#endif + +void +embedg_embedding (t_ver_sparse_rep *V, t_adjl_sparse_rep *A, + t_ver_edge *embed_graph, int n, int e, int nbr_c, + int edge_pos, t_dlcl **mult_edges, t_ver_sparse_rep **vertices, + t_embed_sparse_rep **embedding) + /* + recovering the embedding for the (planar) graph + + - the embedding is returned in vertices and embedding, vertices + indexes embedding, the ordered list of edges + - edges in the embedding are given as their index in A, the graph's + adajacency list + - the nbr of edges in the embedding is given as nbr_e_embed: + this may be different form the original number of edges when the graph + iss not simple + */ +{ + int *ver_orient, nbr_comp, nbr_e_embed; + + IF_CPU( + float sttime; float time_to_now; + + sttime = time_current_user(); + ) + + IF_DEB( + fprintf(stdout, "embedding, begin, which edges have been flipped\n"); + embedg_VES_print_flipped_edges(embed_graph, n, edge_pos); + ) + + IF_DEB( + fprintf(stdout, "embedding, before removing SCE\n"); + embedg_VES_print_bigcomps(embed_graph, n); + ) + + /* + several things to do: + 1. removing the short-cut edges + */ + embedg_remove_SCE(embed_graph, n, edge_pos); + + IF_DEB( + fprintf(stdout, "embedding, after removing SCE\n"); + embedg_VES_print_bigcomps(embed_graph, n); + ) + + /* + 2. computing each vertex's orientation (wrt flipped bicomps) + */ + ver_orient = embedg_vertices_orientation(embed_graph, n); + + + /* + 3. merging the remaining virtual vertices with their + non-virtual counterpart + */ + nbr_comp = embedg_merge_remaining_virtual(embed_graph, n); + /* + actually there is no need to return the nbr of components + from the above function + but let's do it for the sake of it and for possible checking + */ + ASSERT(nbr_c == nbr_comp); + + IF_DEB( + fprintf(stdout, "embedding, after merging of remaining vertices\n"); + ) + + /* + 4. to be on the safe side: check that the embedding is a valid one + + for now, we DIE if not + */ + + if (!embedg_is_embed_valid(embed_graph, n, nbr_comp, edge_pos, + ver_orient, &nbr_e_embed)) + { + mem_free(ver_orient); + DIE(); + } + mem_free(ver_orient); + + ASSERT(nbr_e_embed <= e); + /* + when the graph is not simple, multiple edges and loops are + not in embed_graph[]: they will be added to the final + embedding in embedg_recover_embedding below + */ + + /* + 5. recover the embedding in preparation for the Magma type, + and check it as well + */ + embedg_recover_embedding(V, A, embed_graph, n, e, + mult_edges, vertices, embedding); + if (!embedg_check_recov_embedding(n, e, nbr_comp, + *vertices, A, *embedding)) + { + mem_free(*vertices); + mem_free(*embedding); + + IF_CPU( + fprintf(stdout, "CPU for embedding recovering %f\n", + time_current_user() - sttime); + ) + + DIE(); + } + + IF_DEB_EMBED( + fprintf(stdout, "embedding, original graph and embedding\n"); + sparseg_adjl_print(V, n, A, FALSE); + fprintf(stdout, "\n"); + sparseg_adjl_embed_print(*vertices, n, A, *embedding, + FALSE); + ) + + IF_CPU( + fprintf(stdout, "CPU for embedding recovering %f\n", + time_current_user() - sttime); + ) +} + + +void +embedg_remove_SCE (t_ver_edge *embed_graph, int n, int edge_pos) + /* + remove all the short-cut edges from the embedding + */ +{ + int i, c; + + c = 0; + for (i = 2*n; i <= edge_pos; i += 2) + /* + and edge and its twin occupy consecutive positions in embed_graph: + need only to examine one out of two + (removing an edge also entails removing its twin of course + */ + { + if (embedg_VES_is_short_cut_edge(embed_graph, n, i)) + { + IF_DEB_SCE( + fprintf(stdout, "remove SCE\n"); + embedg_VES_print_edge(embed_graph, n, i); + ) + + embedg_VES_remove_edge(embed_graph, n, i); + c++; + } + } + + IF_DEB_SCE( + fprintf(stdout, "nbr of SCE edges removed %d\n", c); + ) +} + + +int * +embedg_vertices_orientation (t_ver_edge *embed_graph, int n) + /* + for each vertex return its orientation from the + bicomps in embed_graph: + perform a DFS of each bicomp + */ +{ + int i, vv, prod_sign; + int *stack, *ver_orient, to_prev; + + /* + the whole lot makes sense iff the adjacency lists are consistent: + this is a very important issue and it might be the case + that the ASSERT warrants replacement by a DIE + (the check is linear - I think) + */ + ASSERT(embedg_VES_are_adj_lists_consistent(embed_graph, n)); + + ver_orient = (int *) mem_malloc(sizeof(int) * n); + for (i = 0; i < n; i++) + { + ver_orient[i] = CCLOCKW; + } + + /* + create the stack for the DFS + */ + stack = (int *) mem_malloc(sizeof(int) * 3*n); + to_prev = -1; + + IF_DEB( + fprintf(stdout, "vertex orientation, one line (of vert.) for each bicomp\n"); + ) + + /* + now visit all the bicomps, ie, all the virtual vertices + in embed_graph + */ + for (vv = n; vv < 2*n; vv++) + { + int c, cur, cur_e; + boolean NEW_BICOMP; + + if (embed_graph[vv].link[0] == vv) + /* + means that vv is disabled and is not the root of a bicomp + */ + { + continue; + } + + c = vv - n; + IF_DEB( + fprintf(stdout, "%d ", c); + ) + /* + orientation for c (vv is as yet unembedded) is CCLOCKW + + now find the orientation of all its DFS descendants + */ + + if (embed_graph[c].DFS_parent == n) + /* + this means that actually c is an isolated vertex: + we initialise the sign to CCLOCKW + */ + { + prod_sign = CCLOCKW; + } + else + /* + we initialise the sign to CCLOCKW to the sign of c's parent + */ + { + prod_sign = ver_orient[embed_graph[c].DFS_parent]; + } + + /* + we must not forget to set c's sign!! + (won't be done below) + */ + ver_orient[c] = prod_sign; + + NEW_BICOMP = FALSE; + cur = c; + cur_e = embed_graph[cur].link[0]; + ASSERT(embedg_VES_is_edge(n, cur_e)); + + ASSERT(to_prev == -1); + while (TRUE) + { + while (!embedg_VES_is_tree_edge(embed_graph, n, cur_e) + || !embedg_VES_is_vertex(n, + embed_graph[cur_e].neighbour) + || embed_graph[cur_e].neighbour <= cur) + /* + want to find a tree edge [cur, u] + where u is a descendant of cur + */ + { + cur_e = embed_graph[cur_e].link[0]; + + while (cur_e == cur) + /* + back to the vertex where we started from: + no edge has been found: + cur is a leaf, backtrack + */ + { + if (to_prev == -1) + { + NEW_BICOMP = TRUE; + break; + } + prod_sign = stack[to_prev--]; + cur_e = stack[to_prev--]; + /* + must advance one more edge + */ + cur_e = embed_graph[cur_e].link[0]; + cur = stack[to_prev--]; + } + if (NEW_BICOMP) + { + break; + } + } + + if (NEW_BICOMP) + { + break; + } + else + /* + now cur_e is the edge we were looking for, get its sign + */ + { + /* + push on stack the current vertex, the edge where we + stopped the DFS, AND the sign carried by that vertex + + and go down one level in the DFS + */ + stack[++to_prev] = cur; + stack[++to_prev] = cur_e; + stack[++to_prev] = prod_sign; + + cur = embed_graph[cur_e].neighbour; + prod_sign *= embed_graph[cur_e].sign; + ver_orient[cur] = prod_sign; + + cur_e = embed_graph[cur].link[0]; + ASSERT(embedg_VES_is_edge(n, cur_e)); + + IF_DEB( + fprintf(stdout, "%d with sign %d\n", cur, prod_sign); + ) + } + } + + IF_DEB( + fprintf(stdout, "\n"); + ) + } + + IF_DEB( + fprintf(stdout, "vertex orientation\n"); + for (i = 0; i < n; i++) + { + fprintf(stdout, "%d ", ver_orient[i]); + } + fprintf(stdout, "\n"); + ) + + mem_free(stack); + return ver_orient; +} + + +int +embedg_merge_remaining_virtual (t_ver_edge *embed_graph, int n) + /* + after the short-cut edges have been removed and the vertices' + orientation computed, one finishes by merging all + remaining virtual vertices with their virtual counterpart + (without flip of course) + + and use this routine to return the number of disconnected + components of the graph + */ +{ + /* + at this stage it is easy to see that all remaining + virtual vertices are DFS roots (if the graph is not connected) + or cut vertices + */ + + int vv, nbr_comp; + + nbr_comp = 0; + for (vv = n; vv < 2*n; vv++) + { + int v, c; + + + c = vv - n; + v = embed_graph[c].DFS_parent; + + /* + must fish out which virtual vertices are actual roots + of DFS trees (esp. for the disconnected graph case): + roots of DFS trees are those virtual vertices for which + v = embed_graph[c].DFS_parent = n + */ + if (v == n) + { + nbr_comp++; + continue; + } + + if (embed_graph[vv].link[0] == vv) + /* + means that vv is disabled and is not the root of a bicomp + */ + { + continue; + } + + embedg_VES_merge_simple_bicomps(embed_graph, n, + vv, 1, v, 0); + /* + note: + since v is a cut vertex in this intance the bicomp + rooted by vv will be merged without flip; + therefore we could have done + embedg_VES_merge_simple_bicomps(embed_graph, n, + vv, 0, v, 1) + as well, the important thing being that vin != vvout + (see embedg_VES_merge_simple_bicomps) + */ + } + + return nbr_comp; +} + + +int +embedg_nbr_faces (t_ver_edge *embed_graph, int n, int edge_pos, + int *ver_orient, int *nbr_e_embed) + /* + count the number of faces and the number of edges of the embedding + */ +{ + int v, e, f, total_e; + + IF_DEB_FACES( + int v; + + fprintf(stdout, "nbr of faces, the vertices' adj. lists\n"); + for (v = 0; v < n; v++) + embedg_VES_print_adj_list(embed_graph, n, + v, TRUE); + ) + + /* + the following is no more than a quick check -- certainly + not very useful -- or could be done elsewhere + */ + total_e = 0; + for (e = 2*n; e <= edge_pos; e++) + { + if (!embedg_VES_is_short_cut_edge(embed_graph, n, e)) + { + total_e++; + } + } + ASSERT(total_e % 2 == 0); + *nbr_e_embed = total_e / 2; + + /* + I now set each edge's orientation + + QUESTION: do I really need to do this??? + so far, when doing a proper face traversal, the way in which + the adjacency list of an edge must be traversed is given + by the vertex's (in that list) orientation... + So this seems sensible to me huh? + */ + embedg_VES_set_orientation(embed_graph, n, ver_orient); + + /* + I will be using the visited field to enable me to check + if all edges have been traversed + + let's be smart (?!): so far the visited field has been used + and set in the following circumstances: + + initialisation: set to n + + walkup: set to whatever DFI of interest + + so here we set it to MARK_EMBED(n) + */ + f = 0; + for (e = 2*n; e <= edge_pos; e++) + { + if (!embedg_VES_is_short_cut_edge(embed_graph, n, e) + /* + arrghh!!! I must also skip the SCE!!! + */ + && embed_graph[e].visited != MARK_EMBED(n)) + { + int ein; + + IF_DEB_FACES( + fprintf(stdout, "nbr of faces, edges not visited\n"); + embedg_VES_print_edge(embed_graph, n, e); + ) + + ein = embed_graph[e].sign == CCLOCKW ? 0 : 1; + /* + the way I enter e in dependent on its sign: + all the proper face traversal must obviously be done + with the same orientation! + */ + embedg_VES_walk_proper_face(embed_graph, n, e, + ein, + TRUE, + MARK_EMBED(n)); + f++; + } + } + + /* + counting the faces by traversing all the edges does not + account of the face defined by isolated vertices + -- we do that now + + we only need to check which vertices refer to self, ie with + no incident edges + */ + for (v = 0; v < n; v++) + { + if (embed_graph[v].link[0] == v) + { + ASSERT(embed_graph[v].link[1] == v); + f++; + } + } + + return f; +} + + +boolean +embedg_is_embed_valid (t_ver_edge *embed_graph, int n, int nbr_comp, + int edge_pos, int *ver_orient, int *nbr_e_embed) + /* + use Euler's formula to assertain that the embedding is a valid + embedding: + + f = 2 * nbr_comp + nbr_e_embed - n + + */ +{ + int v, f; + + f = embedg_nbr_faces(embed_graph, n, edge_pos, ver_orient, nbr_e_embed); + + IF_DEB_CHECK_EMBED( + fprintf(stdout, "embedding, n: %d\t e: %d\t C: %d\t f: %d\n", + n, nbr_e, nbr_comp, f); + ) + + return f == 2 * nbr_comp + *nbr_e_embed - n ? TRUE : FALSE; +} +/* + * ext_face_walk.c + */ + +/* + What: + ***** + + Implementing the external face walk of a bicomponent. + The concept of an external face --in the context of the VES + data structure-- makes only sense when talking + about a bicomp. + + Recall that a vertex is linked together with the edges + incident from it in a circular (doubly) linked list + (this is the VES structure). + + One particular feature is that if a vertex v is on + the external face of a component and if in the list + we have edges e1, e2 such as e1 -> v -> e2 + then e1 and e2 border the external face. + + In other words, in the circular list of vertex v and edges, + v is ALWAYS between the two edges bordering the external face + + Of course, when v is (maybe) pushed into the internal face + (by embedding of some edge) then we don't care about this any more + (for v that is). + + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} + + +/* aproto: header embed_graph_protos.h */ + + +void +embedg_VES_get_succ_on_ext_face (t_ver_edge *embed_graph, int n, int v, + int vin, boolean MARK, int mark, int *s, int *sin) + /* + find the successor s of v (entered via vin) on the external face + -- also return the direction in which s has been entered + + if MARK true mark the succ. vertex and the edges traversed + with mark (the visited field) + */ +{ + int e, twin; + int vout, ein, eout, tout; + + ASSERT(embedg_VES_is_vertex(n, v) + || embedg_VES_is_virtual_vertex(n, v)); + + IF_DEB( + fprintf(stdout, "get_succ_on_ext_face, of %d:%d\n", v, vin); + ) + + /* + find the direction out of the vertex, and get the edge + */ + vout = vin == 0 ? 1 : 0; + e = embed_graph[v].link[vout]; + if (embedg_VES_is_virtual_vertex(n, v) && e == v) + /* + this can happen if a virtual vertex has been "disabled" + + -- this should not never happen since we can only walk + on the external face of a bicomp! + */ + { + *s = v; + *sin = vin; + return; + } + + /* + otherwise we must have an edge: + note that it is entirely irrelevant if I walk SCEs: + those are precisely there to "jump" over inactive vertices + */ + ASSERT(embedg_VES_is_edge(n, e)); + + /* + get the twin edge + */ + twin = embedg_VES_get_twin_edge(embed_graph, n, e); + + IF_DEB( + fprintf(stdout, "get_succ_on_ext_face, edge [%d, %d]\n", + v, embed_graph[e].neighbour); + fprintf(stdout, "get_succ_on_ext_face, twin edge [%d, %d]\n", + embed_graph[e].neighbour, embed_graph[twin].neighbour); + ) + /* + find which of twin's link links a vertex + */ + tout = embedg_VES_is_vertex(n, embed_graph[twin].link[0]) + || embedg_VES_is_virtual_vertex(n, + embed_graph[twin].link[0]) + ? + 0 : 1; + + /* + get this vertex: this is v's successor on the external face + */ + *s = embed_graph[twin].link[tout]; + + /* + one more thing to do: find the direction in which s was entered + */ + *sin = embed_graph[*s].link[0] == twin ? 0 : 1; + + IF_DEB( + fprintf(stdout, "get_succ_on_ext_face, succ is %d:%d\n", + *s, *sin); + ) + /* + a special case: when the bicomp is a singleton bicomp + (ie a single edge) + */ + if (embed_graph[*s].link[0] == (embed_graph[*s].link[1])) + { + ASSERT(embed_graph[*s].link[0] == twin); + *sin = vin; + } + + /* + finally, mark the vertex and edges if so requested + */ + if (MARK) + { + embed_graph[*s].visited = mark; + embed_graph[e].visited = mark; + embed_graph[twin].visited = mark; + } +} + +void +embedg_VES_get_succ_active_on_ext_face (t_ver_edge *embed_graph, int n, + int v, int w, int win, boolean MARK, int mark, int *s, int *sin) + /* + find the ACTIVE (wrt v) successor s of w (entered via win) + on the external face + -- also return the direction in which s has been entered + + if MARK true mark the succ. vertex (and the edge) + with mark (the visited field) + */ +{ + /* + simply repeatedly calls embedg_VES_get_succ_on_ext_face + until an active vertex is found + */ + ASSERT(embedg_VES_is_vertex(n, w) + || embedg_VES_is_virtual_vertex(n, w)); + + embedg_VES_get_succ_on_ext_face(embed_graph, n, + w, win, MARK, mark, s, sin); + while (embedg_VES_is_ver_inactive(embed_graph, n, v, *s)) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + *s, *sin, MARK, mark, s, sin); + } + ASSERT(!embedg_VES_is_ver_inactive(embed_graph, n, v, *s)); +} + +void +embedg_VES_get_succ_ext_active_on_ext_face (t_ver_edge *embed_graph, int n, + int v, int w, int win, boolean MARK, int mark, int *s, int *sin) + /* + find the externally active (wrt v) successor s of w (entered via win) + on the external face + -- also return the direction in which s has been entered + + if MARK true mark the succ. vertex (and the edge) + with mark (the visited field) + */ +{ + ASSERT(embedg_VES_is_vertex(n, w) + || embedg_VES_is_virtual_vertex(n, w)); + + embedg_VES_get_succ_on_ext_face(embed_graph, n, + w, win, MARK, mark, s, sin); + while (!embedg_VES_is_ver_ext_active(embed_graph, n, v, *s)) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + *s, *sin, MARK, mark, s, sin); + } + ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, *s)); +} + +void +embedg_VES_get_succ_pertinent_on_ext_face (t_ver_edge *embed_graph, int n, + int v, int w, int win, boolean MARK, int mark, int *s, int *sin) + /* + find the pertinent (wrt v) successor s of w (entered via win) + on the external face + -- also return the direction in which s has been entered + + if MARK true mark the succ. vertex (and the edge) + with mark (the visited field) + */ +{ + ASSERT(embedg_VES_is_vertex(n, w) + || embedg_VES_is_virtual_vertex(n, w)); + + embedg_VES_get_succ_on_ext_face(embed_graph, n, + w, win, MARK, mark, s, sin); + while (!embedg_VES_is_ver_pertinent(embed_graph, n, v, *s)) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + *s, *sin, MARK, mark, s, sin); + } + ASSERT(embedg_VES_is_ver_pertinent(embed_graph, n, v, *s)); +} + +/* + * mark_kur.c + */ + +/* + What: + ***** + + Implementing: + + Marking the Kuratowski obstruction (in the VES structure): + this we do once we know which minor we are talking about + (see isolator.c). + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_VERB(x) {} +#define IF_DEB_TREE(x) {} +#define IF_DEB_EDGES(x) {} +#define IF_CPU(x) {} + + + +/* aproto: header embed_graph_protos.h */ + +/* aproto: beginstatic -- don't touch this!! */ +static void embedg_VES_walk_mark_part_ext_face (t_ver_edge *, int, int, int, int, int, int); +static void embedg_VES_walk_mark_ext_face (t_ver_edge *, int, int, int); +static void embedg_VES_walk_mark_part_proper_face (t_ver_edge *, int, int, int, int, int); +static boolean embedg_VES_is_part_ext_face_marked (t_ver_edge *, int, int, int, int, int, int); +static void embedg_get_u_x (t_ver_edge *, int, int, int, int *); +static int embedg_get_least_neigh (t_dlcl **, t_dlcl **, int, int, int); +static void embedg_add_mark_u_x (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int *, int); +static void embedg_mark_tree_path (t_ver_edge *, int, int, int, int); +static void embedg_add_mark_v_w (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int); +static void embedg_add_mark_v_w_for_B (t_dlcl **, t_dlcl **, t_ver_edge *, int, int *, int, int, int *, int); +static void embedg_mark_x_y_path (t_ver_edge *, int, int *, int *, int, int); +/* aproto: endstatic -- don't touch this!! */ + +#ifndef PLANAR_IN_MAGMA +#endif + + + +static void +embedg_VES_walk_mark_part_ext_face (t_ver_edge *embed_graph, int n, + int v, int vin, int from, int to, int mark) + /* + walk & mark the external face: + walk in the direction vin -> v -> vout and mark + */ +{ + int cur, curin, next, nextin; + + embed_graph[from].visited = mark; + embed_graph[to].visited = mark; + + IF_DEB( + fprintf(stdout, "part. ext face marked\t"); + fprintf(stdout, "%d\t", from); + ) + + next = cur = v; + curin = vin; + while (next != from) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, + FALSE, 0, &next, &nextin); + cur = next; + curin = nextin; + } + next = n; + while (next != to) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, + TRUE, mark, &next, &nextin); + cur = next; + curin = nextin; + + IF_DEB( + fprintf(stdout, "%d\t", next); + ) + } + IF_DEB( + fprintf(stdout, "\n"); + ) +} + +static void +embedg_VES_walk_mark_ext_face (t_ver_edge *embed_graph, int n, int v, int mark) + /* + walk & mark the external face, starting & ending at vertex v + */ +{ + embedg_VES_walk_mark_part_ext_face(embed_graph, n, v, 0, v, v, + mark); +} + + + +static void +embedg_VES_walk_mark_part_proper_face (t_ver_edge *embed_graph, int n, + int from_e, int from_ein, int to, int mark) + /* + walk & mark a proper face starting at EDGE from_e and ending + at VERTEX to + + walk in the direction from_ein -> from_e -> to and mark + everything in between + */ +{ + int s, cur_e, cur_ein, next_e, next_ein; + + next_e = s = n; /* this is an invalid value for an edge/vertex */ + + cur_e = from_e; + cur_ein = from_ein; + while (s != to) + { + ASSERT(embedg_VES_is_edge(n, cur_e)); + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, + n, cur_e)); + + embedg_VES_get_succ_on_proper_face(embed_graph, n, + cur_e, cur_ein, + TRUE, mark, + &s, &next_e, &next_ein); + cur_e = next_e; + cur_ein = next_ein; + } +} + + + +static boolean +embedg_VES_is_part_ext_face_marked (t_ver_edge *embed_graph, int n, int v, + int vin, int from, int to, int mark) + /* + simple check to see if all the vertices on the external + face walk starting at vin -> v -> vout are marked + (with mark) + */ +{ + int cur, curin, next, nextin; + + if (embed_graph[from].visited != mark || embed_graph[to].visited != mark) + return FALSE; + + cur = v; + curin = vin; + next = n; + while (next != from) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, + FALSE, 0, &next, &nextin); + cur = next; + curin = nextin; + } + while (next != to) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, + FALSE, 0, &next, &nextin); + if (embed_graph[next].visited != mark) + return FALSE; + + cur = next; + curin = nextin; + } + + return TRUE; +} + + +boolean +embedg_VES_is_ext_face_marked (t_ver_edge *embed_graph, int n, int v, int mark) + /* + simple check to see if all the vertices on the external + face walk starting/ending at v are marked (with mark) + */ +{ + return embedg_VES_is_part_ext_face_marked(embed_graph, n, v, 0, + v, v, mark); +} + + +static void +embedg_get_u_x (t_ver_edge *embed_graph, int n, int v, int x, int *u_x) + /* + x is an externally active vertex (wrt v): + we want u_x, the lowest point of "attachement" for + the unembedded directed edge [x, u_x] + */ +{ + int c; + t_dlcl *child_list; + + ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, x)); + if (embed_graph[x].least_ancestor < v) + /* + then there is a single unembedded back edge (u_x, x), + u_x an ancestor of v + */ + { + *u_x = embed_graph[x].least_ancestor; + return; + } + + /* + else there is a tree path x to d_x and an + unembedded back edge (u_x, d_x) + + get the lowpoint of the first elt. in separated_DFS_child_list of x + */ + child_list = embed_graph[x].separated_DFS_child_list; + ASSERT(!embedg_dlcl_is_empty(child_list)); + c = child_list->info; + *u_x = embed_graph[c].lowpoint; +} + +static int +embedg_get_least_neigh (t_dlcl **dfs_tree, t_dlcl **back_edges, + int n, int v, int c) + /* + get the least neighbour of v >= c, ie a vertex in the sub tree + rooted by c + + somehow this must always succeed + */ +{ + int least_n; + t_dlcl *tree_l, *back_l, *p; + + /* + neighbours are found in either dfs_tree[v] or back_edges[v] + */ + + tree_l = dfs_tree[v]; + back_l = back_edges[v]; + ASSERT(!embedg_dlcl_is_empty(tree_l) || !embedg_dlcl_is_empty(back_l)); + + least_n = n; /* ok, invalid value for any neighbour */ + p = tree_l; + if (!embedg_dlcl_is_empty(p)) + { + if (p->info >= c) + { + least_n = p->info < least_n ? p->info : least_n; + } + p = embedg_dlcl_list_next(p); + while (p != tree_l) + { + if (p->info >= c) + { + least_n = p->info < least_n ? p->info : least_n; + } + p = embedg_dlcl_list_next(p); + } + } + p = back_l; + if (!embedg_dlcl_is_empty(p)) + { + if (p->info >= c) + { + least_n = p->info < least_n ? p->info : least_n; + } + p = embedg_dlcl_list_next(p); + while (p != back_l) + { + if (p->info >= c) + { + least_n = p->info < least_n ? p->info : least_n; + } + p = embedg_dlcl_list_next(p); + } + } + + ASSERT(least_n >= c); + /* + this is so because of the context where this function is called from + */ + return least_n; +} + +static void +embedg_add_mark_u_x (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, + int x, int *u_x, int mark) + /* + marking a Kuratowski homeomorph: + + marking and adding the unembedded dotted edge (u, x), + x an ext. active vertex wrt v + */ +{ + int c, d_x; + t_dlcl *child_list; + + ASSERT(embedg_VES_is_ver_ext_active(embed_graph, n, v, x)); + if (embed_graph[x].least_ancestor < v) + /* + then there is a single unembedded back edge (u_x, x), + u_x an ancestor of v + */ + { + *u_x = embed_graph[x].least_ancestor; + embed_graph[x].visited = mark; + embed_graph[*u_x].visited = mark; + embedg_VES_add_edge(embed_graph, n, edge_pos, *u_x, x, + TRUE, mark); + return; + } + + /* + else there is a tree path x to d_x and an + unembedded back edge (u_x, d_x) + + get the lowpoint of the first elt. in separated_DFS_child_list of x + */ + child_list = embed_graph[x].separated_DFS_child_list; + ASSERT(!embedg_dlcl_is_empty(child_list)); + c = child_list->info; + *u_x = embed_graph[c].lowpoint; + + /* + search for the least neighbour of *u_x >= c, + that is in the subtree rooted by c + */ + d_x = embedg_get_least_neigh(dfs_tree, back_edges, n, *u_x, c); + ASSERT(d_x >= c); + /* + this must be true since u_x is incident to a descendant of x + (remember: x is externally active) + */ + + /* + mark the DFS tree path from d_x to x + */ + embedg_mark_tree_path(embed_graph, n, d_x, x, mark); + /* + add the unembedded (u_x, d_x) edge + */ + embedg_VES_add_edge(embed_graph, n, edge_pos, *u_x, d_x, + TRUE, mark); +} + +static void +embedg_mark_tree_path (t_ver_edge *embed_graph, int n, int d_x, int x, int mark) + /* + marking the DFS tree path d_x...x where x is an ancestor of d_x + */ +{ + int cur_v, te, twe; + + ASSERT(d_x >= x); + + cur_v = d_x; + + while (cur_v != x) + { + embed_graph[cur_v].visited = mark; + te = embed_graph[cur_v].link[0]; + ASSERT(embedg_VES_is_edge(n, te)); + while (!embedg_VES_is_tree_edge(embed_graph, n, te) + || (embed_graph[te].neighbour > cur_v + && embed_graph[te].neighbour != cur_v + n)) + /* + want to find a tree edge incident to an ancestor of d_x: + given that d_x..x is a tree path, we MUST find such an edge! + + note also that I must take account of the fact that + [te].neighbour could be a virtual vertex, in which case + it can only be cur_v + n! + */ + { + te = embed_graph[te].link[0]; + } + ASSERT(embedg_VES_is_tree_edge(embed_graph, n, te)); + ASSERT(embed_graph[te].neighbour == embed_graph[cur_v].DFS_parent + || embed_graph[te].neighbour == cur_v + n); + + embed_graph[te].visited = mark; + twe = embedg_VES_get_twin_edge(embed_graph, n, te); + embed_graph[twe].visited = mark; + + /* + want only to deal with real vertices instead of virtual vertices + */ + cur_v = embed_graph[te].neighbour < cur_v ? + embed_graph[te].neighbour : embed_graph[cur_v].DFS_parent; + } + embed_graph[x].visited = MARK_MINORS(n); +} + + +static void +embedg_add_mark_v_w (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, int w, int mark) + /* + marking a Kuratowski homeomorph: + + marking and adding the unembedded dotted edge (v, w), + w is pertinent wrt v + */ +{ + int vw, c, d_w; + t_dlcl *bicomp_list; + + if (embed_graph[w].adjacent_to == v) + /* + then there is a single unembedded back edge (v, w) + w an ancestor of w + */ + { + embed_graph[v].visited = mark; + embed_graph[w].visited = mark; + embedg_VES_add_edge(embed_graph, n, edge_pos, v, w, + TRUE, mark); + return; + } + + /* + else there is a tree path w to d_w and an + unembedded back edge (v, d_w) + + get the last elt in w's bicomp list + */ + bicomp_list = embed_graph[w].pertinent_bicomp_list; + ASSERT(!embedg_dlcl_is_empty(bicomp_list)); + vw = (embedg_dlcl_list_last(bicomp_list))->info; + c = vw - n; + + /* + search for the least neighbour of v >= c, + that is in the subtree rooted by c + */ + d_w = embedg_get_least_neigh(dfs_tree, back_edges, n, v, c); + ASSERT(d_w >= c); + /* + this must be true since v is incident to a descendant of w + (remember: w is pertinent) + */ + + /* + mark the DFS tree path from d_w to w + */ + embedg_mark_tree_path(embed_graph, n, d_w, w, mark); + /* + add the unembedded (d_w, v) edge + */ + embedg_VES_add_edge(embed_graph, n, edge_pos, d_w, v, + TRUE, mark); +} + + +static void +embedg_add_mark_v_w_for_B (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, int w, + int *u_z, int mark) + /* + marking a Kuratowski homeomorph: + + marking and adding the unembedded dotted edge (v, w) for minor B: + w is pertinent wrt v + */ +{ + int vz, z, d_z, d_w; + t_dlcl *bicomp_list; + + /* + get the last elt in w's bicomp list + */ + bicomp_list = embed_graph[w].pertinent_bicomp_list; + ASSERT(!embedg_dlcl_is_empty(bicomp_list)); + vz = (embedg_dlcl_list_last(bicomp_list))->info; + z = vz - n; + + /* + get the lowpoint of z + */ + *u_z = embed_graph[z].lowpoint; + + /* + search for the least neighbour of *u_z >= z, + that is in the subtree rooted by c + */ + d_z = embedg_get_least_neigh(dfs_tree, back_edges, n, *u_z, z); + ASSERT(d_z >= z); + /* + this must be true since u_z is incident to z or a descendant of z + */ + + /* + now do the same for neighbours of v + */ + d_w = embedg_get_least_neigh(dfs_tree, back_edges, n, v, z); + ASSERT(d_w >= z); + /* + this must be true since v is incident to a descendant of w + (remember: w is pertinent) + */ + + /* + mark the DFS tree path from d_w to w + */ + embedg_mark_tree_path(embed_graph, n, d_w, w, mark); + /* + mark the DFS tree path from d_z to z + */ + embedg_mark_tree_path(embed_graph, n, d_z, z, mark); + /* + add & mark the edges (u_z, d_z), (v, d_w) + */ + embedg_VES_add_edge(embed_graph, n, edge_pos, *u_z, d_z, + TRUE, mark); + embedg_VES_add_edge(embed_graph, n, edge_pos, v, d_w, + TRUE, mark); +} + +static void +embedg_mark_x_y_path (t_ver_edge *embed_graph, int n, int *path_v, + int *path_e, int nbr_v, int mark) +{ + int i; + + /* + have a look at embedg_iso_get_highest_x_y_path + to see that path_e[0] is a dummy + + (note: path_v and path_e contain nbr_v + 1 elts!) + */ + embed_graph[path_v[0]].visited = mark; + for (i = 1; i <= nbr_v; i++) + { + int e, twin; + + embed_graph[path_v[i]].visited = mark; + e = path_e[i]; + twin = embedg_VES_get_twin_edge(embed_graph, n, e); + embed_graph[e].visited = + embed_graph[twin].visited = mark; + } +} + +void +embedg_mark_minor_A (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, int c, int vr) +{ + int r, r_c, x, y, w, u_x, u_y, u; + + ASSERT(embedg_VES_is_virtual_vertex(n, vr)); + r_c = vr - n; + r = embed_graph[r_c].DFS_parent; + + /* + find the ext. active x & y, and the pertinent w, + and mark the external face of the bicomp rooted at vr + */ + embedg_iso_get_x_y_w(embed_graph, n, v, r, r_c, + MARK_MINORS(n), + MARK_MINORS(n), MARK_MINORS(n), &x, &y, &w); + + /* + mark the edges (u, x), (u, y), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, x, &u_x, + MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, y, &u_y, + MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + /* + mark the tree path from r to min(u_x, u_y) + */ + u = u_x <= u_y ? u_x : u_y; + embedg_mark_tree_path(embed_graph, n, r, u, MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor A\n"); + fprintf(stdout, "v %d\t c %d\t r %d\t r_c %d\t x %d\t y %d\t w %d\t u_x %d\t u_y %d\n", + v, c, r, r_c, x, y, w, u_x, u_y); + ) +} + +void +embedg_mark_minor_B (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, + int c, int x, int y, int w) +{ + int vv, u_x, u_y, vz, u_z, u_max, u_min; + + vv = c + n; + + /* + mark the external face of the bicomp rooted by v^c + */ + embedg_VES_walk_mark_ext_face(embed_graph, n, vv, MARK_MINORS(n)); + ASSERT(embedg_VES_is_ext_face_marked(embed_graph, n, vv, + MARK_MINORS(n))); + + /* + mark the edges (u, x), (u, y) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, x, &u_x, + MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, y, &u_y, + MARK_MINORS(n)); + + /* + mark the dotted edges (v, w), (v, u) + */ + embedg_add_mark_v_w_for_B(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + &u_z, MARK_MINORS(n)); + + /* + mark the tree path from max(u_x, u_y, u_z) to min(u_x, u_y, u_z) + */ + u_max = u_x > u_y ? u_x : u_y; + u_max = u_max > u_z ? u_max : u_z; + u_min = u_x < u_y ? u_x : u_y; + u_min = u_min < u_z ? u_min : u_z; + embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor B\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t u_x %d\t u_y %d\t u_z %d\n", + v, c, x, y, w, u_x, u_y, u_z); + ) +} + +void +embedg_mark_minor_C (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, + int c, int x, int y, int w, int *path_v, int *path_e, + int nbr_v, boolean px_attached_high, boolean py_attached_high) +{ + int vv, p_x, p_y, u_x, u_y, u; + + vv = c + n; + p_x = path_v[0]; + p_y = path_v[nbr_v]; + /* + see embedg_iso_get_highest_x_y_path for the above + */ + + if (px_attached_high) + /* + mark the external face: + - from v^c to p_y if py_attached_high + - from v^c to y if !py_attached_high + + not too sure about that one.... + + from v^c to p_y: so vvin = 0, + in x's direction + */ + { + if (py_attached_high) + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, p_y, MARK_MINORS(n)); + else + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, y, MARK_MINORS(n)); + } + else + /* + symmetric case: + mark the external face from v^c to p_x: so vvin = 1, + in y's direction + */ + { + if (px_attached_high) + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, p_x, MARK_MINORS(n)); + else + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, x, MARK_MINORS(n)); + } + + /* + mark the edges (u, x), (u, y), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, x, &u_x, + MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, y, &u_y, + MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + /* + mark the tree path from v to min(u_x, u_y) + */ + u = u_x <= u_y ? u_x : u_y; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + finally, mark the x-y path, ie the vertices in path_v + and the edges in path_e + */ + embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor C p_x high %d\t p_y high %d\n", + px_attached_high, py_attached_high); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y); + ) +} + +void +embedg_mark_minor_D (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, + int c, int x, int y, int w, int *path_v, int *path_e, + int nbr_v, int entry_in_path_e) +{ + int i, vv, p_x, p_y, u_x, u_y, u; + + vv = c + n; + p_x = path_v[0]; + p_y = path_v[nbr_v]; + /* + see embedg_iso_get_highest_x_y_path for the above + */ + + /* + mark the lower external face from x to y: we can walk in + either direction + */ + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + x, y, MARK_MINORS(n)); + + /* + mark the internal path which goes from the x-y path to v + - since I haven't stored those vertices/edges I assume + that a proper face walk should suffice + + BUT a walk that say starts at p_x and ends at vv, + that is, a walk starting at path_e[1] entered from entry_in_path_e + (recall that path_e[0] is a dummy) + */ + embedg_VES_walk_mark_part_proper_face(embed_graph, n, + path_e[1], entry_in_path_e, + vv, MARK_MINORS(n)); + + /* + a note of caution here: + ALWAYS mark external/internal faces before adding any other edges: + since adding edges destroys the faces' consistency + (adding edges makes no sense of face since we are in a non-planar + situation) + */ + /* + mark the edges (u, x), (u, y), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, x, &u_x, + MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, y, &u_y, + MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + /* + mark the tree path from v to min(u_x, u_y) + */ + u = u_x <= u_y ? u_x : u_y; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + mark the x-y path, ie the vertices in path_v + and the edges in path_e + */ + embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor D\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y); + ) +} + + + + +minor +embedg_mark_minor_E (t_dlcl **dfs_tree, t_dlcl **back_edges, + t_ver_edge *embed_graph, int n, int *edge_pos, int v, + int c, int x, int y, int w, int *path_v, int *path_e, int nbr_v) + /* + while marking minor E return which of the minors we are dealing with + */ +{ + int vv, p_x, p_y, u_x, u_y, u_w, u, u_max, u_min; + + vv = c + n; + p_x = path_v[0]; + p_y = path_v[nbr_v]; + /* + see embedg_iso_get_highest_x_y_path for the above + */ + + if (!embedg_VES_is_ver_ext_active(embed_graph, n, v, w)) + /* + minor E1 case: we must find an ext. active z, distinct from w, + on the external face p_x..w..p_y + */ + { + int s, sin, cur, curin, z, u_z, u_xy; + + s = n; + /* + start searching at vv entered from 0 (in x's direction) + -- we MUST reach p_x - hopefully! :) + */ + cur = vv; + curin = 0; + while (s != p_x) + /* + first advance to p_x: we are sure of reaching it + */ + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, + cur, curin, + FALSE, 0, &s, &sin); + cur = s; + curin = sin; + } + + /* + continue the walk on the external face: + stop if either s is ext. active OR s == w + + we'll mark the lot later on + */ + while ( + !(embedg_VES_is_ver_ext_active(embed_graph, n, v, + s) + && s != p_x) + && s != w) + { + embedg_VES_get_succ_on_ext_face(embed_graph, n, cur, curin, + FALSE, 0, &s, &sin); + cur = s; + curin = sin; + } + /* + now we must decide which symmetry we are in + */ + if (embedg_VES_is_ver_ext_active(embed_graph, n, v, s)) + /* + z is between x and w (recall that w is NOT ext. active) + */ + { + z = s; + ASSERT(z != w); + + /* + mark the external face from v^c to y in x's direction + */ + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, y, MARK_MINORS(n)); + /* + add/mark dotted edge (u, y) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_xy, MARK_MINORS(n)); + } + else + /* + this is the symmetric case: must find z between w and p_y + */ + { + ASSERT(s == w); + embedg_VES_get_succ_ext_active_on_ext_face(embed_graph, n, + v, cur, curin, + FALSE, 0, + &s, &sin); + /* + and z is distinct from p_y! + */ + z = s; + ASSERT(z != p_y); + + /* + mark the external face from v^c to x in y's direction + */ + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, x, MARK_MINORS(n)); + /* + add/mark dotted edge (u, x) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_xy, MARK_MINORS(n)); + } + /* + now the marked bits which are common to both cases: + dotted edges (u, z), (v, w), the x-y path, + the tree path (v, min(u_xy, u_z)) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, z, &u_z, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, + MARK_MINORS(n)); + + u = u_z <= u_xy ? u_z : u_xy; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E1\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t z %d\t w %d\t p_x %d\t p_y %d\t u_xy %d\t u_z %d\n", + v, c, x, y, z, w, p_x, p_y, u_xy, u_z); + ) + + return MINOR_E1; + } + + /* + in all other cases we get u_x, u_y, u_w back + from the ext. active vertices x, y, w resp. + + again, I CANNOT embed these edges now since that would destroy + my external/internal faces + */ + + embedg_get_u_x(embed_graph, n, v, x, &u_x); + embedg_get_u_x(embed_graph, n, v, y, &u_y); + embedg_get_u_x(embed_graph, n, v, w, &u_w); + + if (u_w > u_x && u_w > u_y) + /* + minor E2 case: + we mark the whole external face rooted by v^c + and the tree path (v, min(u_x, u_y)) + */ + { + embedg_VES_walk_mark_ext_face(embed_graph, n, vv, + MARK_MINORS(n)); + u = u_x <= u_y ? u_x : u_y; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y) & (u, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E2\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E2; + } + + /* + two more things common to all remaining cases: + + mark the dotted edge (v, w) (but we MUST do that later) + + mark the x-y path + */ + embedg_mark_x_y_path(embed_graph, n, path_v, path_e, nbr_v, + MARK_MINORS(n)); + + if (u_x < u_y && u_w < u_y) + /* + minor E3 case: one of the symmetric cases: + the external face rooted at v_c from vv to x (in x's direction) + the external face rooted at v_c from y to w (in y's direction) + the (v, min(u_w, u_x)) tree path + */ + { + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, p_x, MARK_MINORS(n)); + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + y, w, MARK_MINORS(n)); + + u = u_x <= u_w ? u_x : u_w; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y), (u, w), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E3/a\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E3; + } + if (u_y < u_x && u_w < u_x) + /* + minor E3 case: the other symmetric case: + the external face rooted at v_c from vv to y (in y's direction) + the external face rooted at v_c from x to w (in x's direction) + the (v, min(u_w, u_y)) tree path + */ + { + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, p_y, MARK_MINORS(n)); + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + x, w, MARK_MINORS(n)); + + u = u_y <= u_w ? u_y : u_w; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y), (u, w), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E3/b\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E3; + } + + if (p_x != x) + /* + minor E4 case: one of the symmetric cases: + the external face rooted at v_c from vv to w (in x's direction) + the external face rooted at v_c from vv to p_y (in y's direction) + the tree path from max(u_x, u_y, u_w) to min(u_x, u_y, u_w) + */ + { + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, w, MARK_MINORS(n)); + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, p_y, MARK_MINORS(n)); + + u_max = u_x > u_y ? u_x : u_y; + u_max = u_max > u_w ? u_max : u_w; + u_min = u_x < u_y ? u_x : u_y; + u_min = u_min < u_w ? u_min : u_w; + embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y), (u, w), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E4/a\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E4; + } + if (p_y != y) + /* + minor E4 case: the other symmetric case: + the external face rooted at v_c from vv to w (in y's direction) + the external face rooted at v_c from vv to x (in x's direction) + (here p_x = x!) + the tree path from max(u_x, u_y, u_w) to min(u_x, u_y, u_w) + */ + { + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 1, + vv, w, MARK_MINORS(n)); + embedg_VES_walk_mark_part_ext_face(embed_graph, n, vv, 0, + vv, x, MARK_MINORS(n)); + + u_max = u_x > u_y ? u_x : u_y; + u_max = u_max > u_w ? u_max : u_w; + u_min = u_x < u_y ? u_x : u_y; + u_min = u_min < u_w ? u_min : u_w; + embedg_mark_tree_path(embed_graph, n, u_max, u_min, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y), (u, w), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E$/b\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E4; + } + + /* + this is the last case for minor E: when the homeomorph is K5 + + mark the whole external face rooted at v^c + mark the tree path from v to min(u_x, u_y, u_w) + */ + + embedg_VES_walk_mark_ext_face(embed_graph, n, vv, MARK_MINORS(n)); + + u = u_x < u_y ? u_x : u_y; + u = u < u_w ? u : u_w; + embedg_mark_tree_path(embed_graph, n, v, u, MARK_MINORS(n)); + + /* + embed dotted edges (u, x), (u, y), (u, w), (v, w) + */ + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, x, &u_x, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, y, &u_y, MARK_MINORS(n)); + embedg_add_mark_u_x(dfs_tree, back_edges, + embed_graph, n, edge_pos, + v, w, &u_w, MARK_MINORS(n)); + embedg_add_mark_v_w(dfs_tree, back_edges, + embed_graph, n, edge_pos, v, w, + MARK_MINORS(n)); + + IF_DEB( + fprintf(stdout, "mark minor E5\n"); + fprintf(stdout, "v %d\t c %d\t x %d\t y %d\t w %d\t p_x %d\t p_y %d\t u_x %d\t u_y %d\t u_w %d\n", + v, c, x, y, w, p_x, p_y, u_x, u_y, u_w); + ) + + return MINOR_E5; +} +/* + * proper_face_walk.c + */ + +/* + What: + ***** + + Implementing a proper face walk within the VES structure. + This is obviously not the same as an external face walk, + but is simply the standard face walk in a planar embedding. + + Not much to say, if only to emphasize that for our + purposes here we assume: + + 1. the short-cut edges have been removed from the VES structure + 2. each vertex/edge has been given its orientation + 2. the adjacency lists (vertex + plus its incident edges) + are consistent: (and this is IMPORTANT) + that is, the way to traverse an adj. list (ie what + constitute previous and next in the list which actually + is a planar embedding at this stage) is indicated + by the vertex/edge's orientation + + + try to explain this better another time.... sorry... + + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + from + + Simplified O(n) Planarity Algorithms (draft) + ************************************ + + John Boyer JBoyer@PureEdge.com, jboyer@acm.org + Wendy Myrvold wendym@csr.uvic.ca + + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + authors: + ******** + + Paulette Lieby (Magma), Brendan McKay (ANU) + + Started October 2001 +*/ + + +#include "planarity.h" + +#define IF_DEB(x) {} +#define IF_DEB_PROPER_FACE(x) {} +#define IF_VERB(x) {} + + + +/* aproto: header embed_graph_protos.h */ + + + + +boolean +embedg_VES_get_succ_on_proper_face_with_avoidance (t_ver_edge *embed_graph, + int n, int e, int ein, int a, boolean MARK, int mark, int *s, + int *next_e, int *next_ein) + /* + find the successor s of embed_graph[e].neighbour + (entered via ein) on a proper face traversal + which avoids (the vertex) a if a != n + + also returns the edge next_e such that + embed_graph[next_e].neighbour = s (to allow for continuation + of the walk) + + assumes that short-cut edges have been removed and that each + edge/vertex has been given its orientation + + and (more importantly) assumes that adjacency lists are consistent + + this function has been written especially to retrieve the highest + x-y path for the isolator; + (see embedg_iso_get_highest_x_y_path) + but as I discovered later (when marking an internal face + as for minor D) this function is general purpose + + PLUS: return true if the proper face walk has to skip an edge + incident to a (ie had to "avoid" a) + + PLUS: mark s & next_e if so requested + */ +{ + int eout; + int twin, twinout; + boolean avoid_a; + + ASSERT(embedg_VES_is_edge(n, e)); + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); + + IF_DEB( + fprintf(stdout, "get_succ_on_proper_face, \n"); + ) + + avoid_a = FALSE; + /* + find the direction out of the edge + */ + eout = 1 ^ ein; + + /* + get the twin edge + */ + twin = embedg_VES_get_twin_edge(embed_graph, n, e); + + /* + for each edge we must set the way to get to the next + in the adjacency list: + adjacency lists are traversed according to the vertex/edges + orientation (one unique orientation per list of course) + */ + if (embed_graph[e].sign != embed_graph[twin].sign) + /* + invert traversal + */ + { + twinout = 1 ^ eout; + } + else + /* + traversal is identical + */ + { + twinout = eout; + } + + /* + now, we want the edge previous to twin in twin's adjacency list, + ie link[1 ^ twinout] + */ + *next_e = embed_graph[twin].link[1 ^ twinout]; + /* + next_e could be a vertex, I need an edge + */ + if (embedg_VES_is_vertex(n, *next_e) + || embedg_VES_is_virtual_vertex(n, *next_e)) + /* + at this stage all virtual vertices should have + been disabled BUT the vertices rooting the bicomps!!! + */ + { + *next_e = embed_graph[*next_e].link[1 ^ twinout]; + } + ASSERT(embedg_VES_is_edge(n, *next_e)); + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); + *s = embed_graph[*next_e].neighbour; + + if (*s == a) + /* + want to avoid this vertex, so must get yet previous + edge in adjacency list + */ + { + avoid_a = TRUE; + + *next_e = embed_graph[*next_e].link[1 ^ twinout]; + if (embedg_VES_is_vertex(n, *next_e) + || embedg_VES_is_virtual_vertex(n, *next_e)) + { + *next_e = embed_graph[*next_e].link[1 ^ twinout]; + } + ASSERT(embedg_VES_is_edge(n, *next_e)); + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, n, e)); + } + *s = embed_graph[*next_e].neighbour; + ASSERT(*s != a); + + /* + finally (again, because lists are consistent) + */ + *next_ein = 1 ^ twinout; + + /* + now mark s and next_e if required + */ + if (MARK) + { + embed_graph[*s].visited = + embed_graph[*next_e].visited = mark; + /* + ouuh... must mark the twin as well.... + but ONLY when we mark the minors.... + that is poor design, can we do better???? + -- don't think so... + + (when we mark when counting the faces, we MUST only + mark the edge and NOT its twin) + */ + if (mark == MARK_MINORS(n)) + { + twin = + embedg_VES_get_twin_edge(embed_graph, n, *next_e); + embed_graph[twin].visited = mark; + } + } + + return avoid_a; +} + + + +void +embedg_VES_get_succ_on_proper_face (t_ver_edge *embed_graph, int n, int e, + int ein, int MARK, int mark, int *s, int *next_e, int *next_ein) + /* + same as above but without avoidance + */ +{ + boolean avoid; + + avoid = + embedg_VES_get_succ_on_proper_face_with_avoidance( + embed_graph, n, + e, ein, n, + MARK, mark, + s, next_e, next_ein); + ASSERT(avoid == FALSE); +} + + +void +embedg_VES_walk_proper_face (t_ver_edge *embed_graph, int n, int e, + int ein, boolean MARK, int mark) + /* + traversing a proper face starting at edge e which has been entered + via ein + + -- we mark the visited edges with mark if so requested + + assumes that short-cut edges have been removed and that each + edge/vertex has been given its orientation + */ +{ + int s, cur_e, cur_ein, next_e, next_ein; + + next_e = n; /* this is an invalid value for an edge */ + + IF_DEB_PROPER_FACE( + fprintf(stdout, "proper face traversal\n"); + ) + + cur_e = e; + cur_ein = ein; + while (next_e != e) + { + ASSERT(embedg_VES_is_edge(n, cur_e)); + ASSERT(!embedg_VES_is_short_cut_edge(embed_graph, + n, cur_e)); + IF_DEB_PROPER_FACE( + embedg_VES_print_edge(embed_graph, n, cur_e); + ) + + embedg_VES_get_succ_on_proper_face(embed_graph, n, + cur_e, cur_ein, + MARK, mark, + &s, &next_e, &next_ein); + cur_e = next_e; + cur_ein = next_ein; + } + + /* + note that by doing so we would have marked e and the first of e's + endpoints since by exiting the loop e = next_e and s is the + actual starting vertex of the walk + */ +} + + + + + diff --git a/src/plugin0.c b/src/plugin0.c index 5793fae..c093ac2 100644 --- a/src/plugin0.c +++ b/src/plugin0.c @@ -1,14 +1,14 @@ -/* This is a plugin for surge that removes molecules with - two atoms having three or more common neighbours. - Also, arsenic at valence 3 and 5 is added. */ - -#define HELPTEXT2 " This version removes molecules with K(2,3).\n" - -#define SURGEPLUGIN_INIT \ - addelement("As","As",3,3); addelement("Az","As",5,5); - -#define SURGEPLUGIN_STEP0 \ - { int ii,jj; \ - for (jj = n; --jj >= 1; ) \ - for (ii = jj; --ii >= 0; ) \ - if (POPCOUNT(g[ii] & g[jj]) >= 3) return 1; } +/* This is a plugin for surge that removes molecules with + two atoms having three or more common neighbours. + Also, arsenic at valence 3 and 5 is added. */ + +#define HELPTEXT2 " This version removes molecules with K(2,3).\n" + +#define SURGEPLUGIN_INIT \ + addelement("As","As",3,3); addelement("Az","As",5,5); + +#define SURGEPLUGIN_STEP0 \ + { int ii,jj; \ + for (jj = n; --jj >= 1; ) \ + for (ii = jj; --ii >= 0; ) \ + if (POPCOUNT(g[ii] & g[jj]) >= 3) return 1; } diff --git a/src/plugin1.c b/src/plugin1.c index f642376..23a7cef 100644 --- a/src/plugin1.c +++ b/src/plugin1.c @@ -1,17 +1,17 @@ -/* This is a plugin for surge that implements an extra option - -F# or -F#:# for the number of atoms with exactly 4 distinct - non-H neighbours. */ - -#define HELPTEXT2 \ -" -F# -F#:# Specify number of atoms with exactly 4 non-H neighbours\n" - -static boolean Fswitch = FALSE; -static long Fmin,Fmax; - -#define SURGEPLUGIN_STEP1 \ - { int ii,Fval; Fval=0; \ - for (ii = 0; ii < n; ++ii) if (deg[ii] == 4) ++Fval; \ - if (Fswitch && (Fval < Fmin || Fval > Fmax)) return; } - -#define SURGEPLUGIN_SWITCHES \ - SWRANGE('F',":-",Fswitch,Fmin,Fmax,"surge -F") +/* This is a plugin for surge that implements an extra option + -V# or -V#:# for the number of atoms with exactly 4 distinct + non-H neighbours. */ + +#define HELPTEXT2 \ +" -V# -V#:# Specify number of atoms with exactly 4 non-H neighbours\n" + +static boolean Vswitch = FALSE; +static long Vmin,Vmax; + +#define SURGEPLUGIN_STEP1 \ + { int ii,Vval; Vval=0; \ + for (ii = 0; ii < n; ++ii) if (deg[ii] == 4) ++Vval; \ + if (Vswitch && (Vval < Vmin || Vval > Vmax)) return; } + +#define SURGEPLUGIN_SWITCHES \ + SWRANGE('V',":-",Vswitch,Vmin,Vmax,"surge -V") diff --git a/src/plugin2.c b/src/plugin2.c index 919cf03..f207abf 100644 --- a/src/plugin2.c +++ b/src/plugin2.c @@ -1,15 +1,15 @@ -/* This is a plugin for surge that optionally forbids - adjacent oxygen atoms. */ - -#define HELPTEXT2 \ -" This version forbids adjacent oxygen atoms if -Y is given.\n" - -static boolean Yswitch = FALSE; -#define SURGEPLUGIN_SWITCHES SWBOOLEAN('Y',Yswitch) - -static int oxygenindex = -1; -#define SURGEPLUGIN_STEP2 \ - if (oxygenindex < 0) oxygenindex = elementindex("O"); \ - if (Yswitch) { int ii; for (ii = 0; ii < ne; ++ii) \ - if (vcol[edge[ii].x] == oxygenindex \ - && vcol[edge[ii].y] == oxygenindex) return; } +/* This is a plugin for surge that optionally forbids + adjacent oxygen atoms. */ + +#define HELPTEXT2 \ +" This version forbids adjacent oxygen atoms if -Y is given.\n" + +static boolean Yswitch = FALSE; +#define SURGEPLUGIN_SWITCHES SWBOOLEAN('Y',Yswitch) + +static int oxygenindex = -1; +#define SURGEPLUGIN_STEP2 \ + if (oxygenindex < 0) oxygenindex = elementindex("O"); \ + if (Yswitch) { int ii; for (ii = 0; ii < ne; ++ii) \ + if (vcol[edge[ii].x] == oxygenindex \ + && vcol[edge[ii].y] == oxygenindex) return; } diff --git a/src/plugin3.c b/src/plugin3.c index a279347..89c8bd2 100644 --- a/src/plugin3.c +++ b/src/plugin3.c @@ -1,19 +1,17 @@ -/* This is a plugin for surge that counts how many hydrogen atoms - are attached to carbon atoms. */ - -#define HELPTEXT2 \ -" This version counts the hydrogen atoms attached to carbon atoms.\n" - -static int carbonindex = -1; /* index into element table */ -static long long CHcount[5*MAXN+1]; - -#define SURGEPLUGIN_STEP3 \ - if (carbonindex < 0) carbonindex = elementindex("C"); \ - { int ii,CHval; CHval=0; for (ii = 0; ii < n; ++ii) \ - if (vcol[ii] == carbonindex) CHval += hyd[ii]; \ - ++CHcount[CHval]; } - -#define SURGEPLUGIN_SUMMARY \ - fprintf(stderr,"Counts by the number of hydrogens attached to carbons:\n"); \ - { int ii; for (ii = 0; ii <= 5*MAXN; ++ii) \ - if (CHcount[ii] > 0) fprintf(stderr," %2d : %lld\n",ii,CHcount[ii]); } +/* This is a plugin for surge that counts how many hydrogen atoms + are attached to carbon atoms. */ + +#define HELPTEXT2 \ +" This version counts the hydrogen atoms attached to carbon atoms.\n" + +static long long CHcount[5*MAXN+1]={0}; + +#define SURGEPLUGIN_STEP3 \ + { int ii,CHval; CHval=0; for (ii = 0; ii < n; ++ii) \ + if (vcol[ii] == carbonindex) CHval += hyd[ii]; \ + ++CHcount[CHval]; } + +#define SURGEPLUGIN_SUMMARY \ + fprintf(stderr,"Counts by the number of hydrogens attached to carbons:\n"); \ + { int ii; for (ii = 0; ii <= 5*MAXN; ++ii) \ + if (CHcount[ii] > 0) fprintf(stderr," %2d : %lld\n",ii,CHcount[ii]); } diff --git a/src/surge.c b/src/surge.c index 8d4a31f..bad0383 100644 --- a/src/surge.c +++ b/src/surge.c @@ -1,2587 +1,2931 @@ -/* This is a molecule generator based on geng. - Version 1.0, November 11, 2021. - - Unix-style compilation command would be: - - gcc -o surge -O3 -DWORDSIZE=32 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ - -march=native -mtune=native -DPREPRUNE=surgepreprune \ - -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ - surge.c geng.c planarity.c nautyW1.a - - You can build-in gzip output using the zlib library (https://zlib.net). - Add -DZLIB to the compilation, and link with the zlib library either - by adding -lz or libz.a . This will activate the -z command to gzip - the output. - - gcc -o surge -O3 -DWORDSIZE=32 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ - -march=native -mtune=native -DPREPRUNE=surgepreprune -DZLIB \ - -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ - surge.c geng.c planarity.c nautyW1.a -lz - - There is a makefile in the package; edit the first few lines. - - This version works best with geng version 3.2 or later. To use - with an earlier version, add -DOLDGENG to the compilation command. - -***********************************************************************/ - -#ifdef ZLIB -#define USAGE \ - "[-oFILE] [-z] [-u|-A|-S] [-T] [-e#|-e#:#] [-d#] [-c#] [-m#/#] formula" -#else -#define USAGE \ - "[-oFILE] [-u|-A|-S] [-T] [-e#|-e#:#] [-d#] [-c#] [-m#/#] formula" -#endif - -#define HELPUSECMD - -#define HELPTEXT1 \ -"Make chemical graphs from a formula. Version 1.0.\n" \ -" Known elements are C,B,N,P,O,S,H,Cl,F,Br,I at their lowest valences.\n" \ -" Higher valences can be selected using Nx (Nitrogen/5), Sx,Sy (Sulfur 4/6)\n" \ -" Px (Phosphorus/5).\n" \ -"\n" \ -" formula = a formula like C8H6N2\n" \ -"\n" \ -" -u Just count, don't write\n" \ -" -S Output in SMILES format\n" \ -" -A Output in alphabetical format\n" \ -" -O# Output stage: 1 after geng, 2 after vcolg, 3 after multig\n" \ -" Default is to write SDfile format\n" \ -" -e# -e#:# Restrict to given range of distinct non-H bonds\n" \ -" -t# -t#:# Limit number of rings of length 3\n" \ -" -f# -f#:# Limit number of cycles of length 4\n" \ -" -p# -p#:# Limit number of cycles of length 5\n" \ -" -b Only rings of even length (same as only cycles of even length)\n" \ -" -T Disallow triple bonds\n" \ -" -P Require planarity\n" \ -" -d# Maximum degree not counting bond multiplicity or hydrogens (default 4)\n" \ -" -c# Maximum coordination number (default 4). This is the maximum number\n" \ -" of distinct atoms (including H) that an atom can be bonded to\n" \ -" Coordination number > 4 is only allowed if no neighbours are H\n" \ -" -B#,...,# Specify sets of substructures to avoid (details in manual)\n" \ -" 1 = no triple bonds in rings up to length 7\n" \ -" 2 = Bredt's rule for two rings ij with one bond in\n" \ -" common (33, 34, 35, 36, 44, 45)\n" \ -" 3 = Bredt's rule for two rings ij with two bonds in\n" \ -" common (i,j up to 56)\n" \ -" 4 = Bredt's rule for two rings of length 6 sharing three bonds\n" \ -" 5 = no substructures A=A=A (in ring or not)\n" \ -" 6 = no substructures A=A=A in rings up to length 8\n" \ -" For -B5 and -B6, the central atom only has 2 non-H neighbours\n" \ -" 7 = no K_33 or K_24 structure\n" \ -" 8 = none of cone of P4 or K4 with 3-ear\n" \ -" 9 = no atom in more than one ring of length 3 or 4\n" \ -" -v Write more information to stderr\n" \ -" -m#/# Do only a part. The two numbers are res/mod where 0<=res -#ifdef ZLIB -#include "zlib.h" -#endif - -#define DEBUG 0 - -static struct smilesstruct -{ - int item; - int x,y,r; -} smilesskeleton[4*MAXN+6*MAXNE]; -/* Values for the item field */ -#define SM_ATOM 1 /* Atom number x */ -#define SM_BOND 2 /* Bond x,y */ -#define SM_OPEN 3 /* Open ( */ -#define SM_CLOSE 4 /* Close ) */ -#define SM_RING0 5 /* Broken bond x,y for new ring r */ -#define SM_RING1 6 /* End of ring r */ -static int smileslen; - -typedef unsigned long long counter; /* For counters that might overflow */ - -static int hydrogens; -static int nv; /* number of atoms except H */ -static int numtypes; /* different elements except H */ -#define FORMULALEN 50 -static int elementtype[FORMULALEN],elementcount[FORMULALEN]; - /* Not H, decreasing valence */ -static char canonform[2*FORMULALEN]; /* The formula in canonical order */ -static int valencesum; /* Sum of valences for non-H */ -static int maxtype[8]; /* maxtype[d] is maximum index into - elementtype/elementcount for vertices of degree d */ - -/* Used with -A */ -static boolean alphabetic; -static int newlabel[MAXN]; /* New number of atom i */ - -#define BADLISTS 9 /* Number of defined bad lists */ -static boolean bad1; /* Avoid triple edges in rings up to length 7 */ - /* bad1 is turned off if -T is given */ -static boolean bad2; /* Bredt's rule for one common bond */ -static boolean bad3; /* Bredt's rule for two common bonds */ -static boolean bad4; /* Bredt's rule for three common bonds */ -static boolean bad5; /* Avoid =A= even if not in a ring */ -static boolean bad6; /* Avoid =A= in rings up to length 8 */ - /* Note that bad6 is turned off if bad5 is set */ -static boolean bad7; /* Avoid K_{2,4} and K_{3,3} */ -static boolean bad8; /* Avoid cone(P4) and K4 with 3-ear */ - /* bad8 is turned off if -t and -f options make it impossible */ -static boolean bad9; /* No atom on two rings of length 3 or 4 */ - /* bad9 is turned off if -t and -f options make it impossible */ - -static boolean needcoordtest; - -static boolean needcycles; -static int maxcycles=0; -#define MAXCYCLES 200 -static setword inducedcycle[MAXCYCLES]; -static int cyclecount; - -int GENG_MAIN(int argc, char *argv[]); /* geng main() */ -static int min1,min12,max34,max4; /* bounds on degree counts on geng output */ - -static counter gengout=0, genggood=0; -static counter vcolgnontriv=0,vcolgout=0; -static counter multignontriv=0,multigout=0; -static long maxvgroup,maxegroup; - -static boolean uswitch; /* suppress output */ -static boolean verbose; /* print more information to stderr */ -static int outlevel; /* 1 = geng only, 2 = geng+vcolg, - 3 = geng+vcolg+multig, 4 = everything */ -static boolean smiles; /* output in SMILES format */ -static int maxbond; /* maximum mult -1 of bonds (1 if -t, else 2) */ - -static boolean planar; /* Molecules must be planar */ -static int maxcoord; /* Maximum coordination number allowed */ //UNUSED -static boolean xswitch; /* Undocumented, used for development */ - -/* In the following, the counts are only meaningful if the - corresponding boolean is true. */ -static boolean tswitch; -static long min3rings,max3rings; /* number of rings of length 3 */ -static int count3ring[MAXN+1]; -static boolean fswitch; -static long min4rings,max4rings; /* number of rings of length 4 */ -static int count4ring[MAXN+1]; -static boolean pswitch; -static long min5rings,max5rings; /* number of rings of length 5 */ -static int count5ring[MAXN+1]; -static boolean bipartite; - -/* The following is only used if bad9 is selected */ -static setword cycle34verts[MAXN+1]; /* set of vertices on rings - of length 3 or 4 */ - -static long vgroupsize; /* vcolg group size */ -static size_t vgroupalloc=0; /* Space allocated for vertex groups */ -static int *vgroup=NULL; /* Store of vertex group */ -static long vgroupcount; - -static long egroupsize; /* multig group size */ -static size_t egroupalloc=0; /* Space allocated for edge groups */ -static int *egroup=NULL; /* Store of edge group */ - -typedef struct edge -{ - int x,y; /* The atoms with this bond */ - int maxmult; /* This edge can have multiplicity 1..maxmult+1 */ - int allenemate1,allenemate2; - /* Previous bond that cannot be multiple at the same time as this */ - setword xy; -} edgetype; -static int numedges; -static edgetype edge[MAXNE]; -static int edgenumber[MAXN][MAXN]; -static int deg[MAXN]; /* Simple graph degree */ - -static FILE *outfile; -#ifdef ZLIB -static gzFile gzoutfile; -#endif -static boolean gzip; - -/* Macros for appending to a string using pointer p */ -#define PUTINT(xx) { unsigned long ul = (xx); char *sp,s[15]; \ - if (ul == 0) *(p++) = '0'; \ - else { sp = s; while (ul) { *(sp++) = (ul % 10) + '0'; ul /= 10; } \ - while (sp > s) { *(p++) = *(--sp); } }} -#define SPC *(p++) = ' ' -#define PUTSTR(xx) { char *sp = (xx); \ - while (*sp != '\0') *(p++) = *(sp++); } -#define PUTBND(xx) { int bnd = (xx); if (bnd == 0) *(p++) = '-'; \ - else if (bnd == 1) *(p++) = '='; else *(p++) = '#'; } - -/******************************************************************/ - -#define MAXELEMENTS 30 -static struct elementstruct -{ - char *inputname,*name; - boolean organic; /* Belongs to organic subset */ - int valence; - int lowervalence; /* Next lower valence, or 0 if none */ - int maxcoord; /* Maximum number of distinct neighbours including H */ - int index; /* Used in -T style outputs */ -} element[MAXELEMENTS] = -{ /* The order of listing does not matter. - Other elements can be added to the table by following the same - pattern, up to any limit. All extra elements must be marked - as non-organic. The inputname field must have the form X or Xx - and must be unique. */ - { "C", "C", TRUE, 4,0,4, 0 }, - { "N", "N", TRUE, 3,0,3, 1 }, - { "Nx","N", TRUE, 5,3,4, 10 }, - { "P", "P", TRUE, 3,0,3, 3 }, - { "Px","P", TRUE, 5,3,5, 13 }, - { "B", "B", TRUE, 3,0,3, 9 }, - { "O", "O", TRUE, 2,0,2, 2 }, - { "S", "S", TRUE, 2,0,2, 4 }, - { "Sx","S", TRUE, 4,2,4, 11 }, - { "Sy","S", TRUE, 6,4,6, 12 }, - { "H", "H", FALSE, 1,0,1, 99 }, - { "F", "F", TRUE, 1,0,1, 5 }, - { "Cl","Cl",TRUE, 1,0,1, 6 }, - { "Br","Br",TRUE, 1,0,1, 7 }, - { "I", "I", TRUE, 1,0,1, 8 }, - { "Si","Si",FALSE, 4,0,4, 14 }, - { NULL,NULL,FALSE, 0,0,0, 0 } -}; -static int numelements; /* Actual number in the table */ -static int maxindex; /* Max value of the index field except 99 */ -#define ISHYDROGEN(i) (strcmp(element[i].name,"H") == 0) - -/******************************************************************/ - -static int -elementindex(char *inputname) -/* Index into element[] of element with this element input name, - or -1 if it isn't present. */ -{ - int i; - - for (i = 0; i < numelements; ++i) - if (strcmp(element[i].inputname,inputname) == 0) - break; - - if (i < numelements) return i; - else - { - fprintf(stderr,">E Unknown element %s\n",inputname); - exit(1); - } -} - -static void -addelement(char *inputname, char *name, int valence, int maxcoord) -/* Add an element to element[]. inputname and name must be of the -form A or Aa. If name==NULL then name is the same as inputname. If -maxcoord==0 or maxcoord > valence then maxcoord is the same as valence. */ -{ - int i; - - if (numelements == MAXELEMENTS) - gt_abort(">E increase MAXELEMENTS\n"); - - if (name == NULL) name = inputname; - if (maxcoord == 0 || maxcoord > valence) maxcoord = valence; - - if (inputname == NULL || valence <= 0) - gt_abort(">E invalid parameters in addelement()\n"); - - for (i = 0; i < numelements; ++i) - { - if (strcmp(element[i].inputname,inputname) == 0) - { - fprintf(stderr,">E element %s is already present\n",name); - exit(1); - } - } - - if (!isupper(inputname[0]) || (inputname[1] != '\0' - && (!islower(inputname[1]) || inputname[2] != '\0'))) - gt_abort(">E element names must have form A or Aa\n"); - if (!isupper(name[0]) || (name[1] != '\0' - && (!islower(name[1]) || name[2] != '\0'))) - gt_abort(">E element names must have form A or Aa\n"); - - if (3*maxcoord < valence || valence < 0) - gt_abort(">E impossible maxcoord/valence\n"); - - element[numelements].inputname = strdup(inputname); - element[numelements].name = strdup(name); - element[numelements].valence = valence; - /* lowervalence is only used for SMILES output of - atoms in the organic subset and since those are - in the table already we don't need a value to - be set here. */ - element[numelements].lowervalence = 0; - element[numelements].maxcoord = maxcoord; - element[numelements].organic = FALSE; - element[numelements].index = ++maxindex; - - if (verbose) - fprintf(stderr,"Added element %s input %s, valence=%d maxcoord=%d\n", - element[numelements].name,element[numelements].inputname, - element[numelements].valence,element[numelements].maxcoord); - ++numelements; -} - -#undef HELPTEXT2 -#ifdef SURGEPLUGIN -/* Note that the value of SURGEPLUGIN must be a filename in quotes, so on - the compilation command you will probably need something like - -DSURGEPLUGIN='"myplugin.h"' . */ -#include SURGEPLUGIN -#endif - -#ifndef HELPTEXT2 -#define HELPTEXT2 "" -#endif - -/******************************************************************/ - -#if 0 -static unsigned long long -molcode(int *vcol, int *mult, int n, int ne) -/* This makes an isomorph-invariant code for a molecule. It is intended -for debugging and can't be relied on to distinguish between different -molecules. It only works up to MAXN/2 atoms (but you can compile the -program with WORDSIZE=64 if you really need to). */ -{ - graph g[MAXN],h[MAXN]; - int lab[MAXN],ptn[MAXN],orbits[MAXN],weight[MAXN]; - static DEFAULTOPTIONS_GRAPH(options); - statsblk stats; - setword workspace[2*MAXN]; - int i,x,y,nv; - unsigned long long ans1,ans2; - - nv = 2*n; - if (nv >= MAXN) gt_abort(">E surge : too big for longcode\n"); - - for (i = 0; i < n; ++i) { weight[i] = vcol[i]; weight[n+i] = n+vcol[i]; } - setlabptn(weight,lab,ptn,nv); - - for (i = 0; i < nv; ++i) g[i] = 0; - - for (i = 0; i < n; ++i) { g[i] |= bit[n+i]; g[n+i] |= bit[i]; } - - for (i = 0; i < ne; ++i) - { - x = edge[i].x; - y = edge[i].y; - g[x] |= bit[y]; g[y] |= bit[x]; - if (mult[i] > 0) { g[n+x] |= bit[n+y]; g[n+y] |= bit[n+x]; } - if (mult[i] > 1) - { - g[x] |= bit[y+n]; g[y+n] |= bit[x]; - g[x+n] |= bit[y]; g[y] |= bit[x+n]; - } - } - - options.defaultptn = FALSE; - options.getcanon = TRUE; - - nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,2*MAXN,1,nv,h); - - ans1 = n; - ans2 = ne; - for (i = 0; i < nv; ++i) - { - ans1 = 177*ans1 + (h[i] >> (WORDSIZE-nv)); - ans2 = 1237*ans2 + (h[i] >> (WORDSIZE-nv)); - } - - return ans1^ans2; -} -#endif - -/******************************************************************/ - -void dummy() -{ -} - -/******************************************************************/ - -static boolean -isplanar(graph *g, int n) -/* Check if g is planar, assuming g is connected */ -{ - t_ver_sparse_rep V[MAXN]; - t_adjl_sparse_rep A[2*MAXNE+1]; - t_dlcl **dfs_tree,**back_edges,**mult_edges; - t_ver_edge *embed_graph; - int i,j,k,pop,nv,ne,c; - int edge_pos,v,w; - setword ww; - boolean ans; - graph h[MAXN]; - int newlab[MAXN]; - setword queue; - - queue = 0; - ne = 0; - for (i = 0; i < n; ++i) - { - h[i] = g[i]; - pop = POPCOUNT(h[i]); - ne += pop; - if (pop <= 2) queue |= bit[i]; - } - ne /= 2; - nv = n; - - while (queue && ne >= nv+3) - { - TAKEBIT(i,queue); - pop = POPCOUNT(h[i]); - if (pop == 1) /* i--j with deg(i)=1 */ - { - j = FIRSTBITNZ(h[i]); - h[i] = 0; - h[j] &= ~bit[i]; - --nv; - --ne; - queue |= bit[j]; - } - else if (pop == 2) /* j--i--k with deg(i)=2 */ - { - j = FIRSTBITNZ(h[i]); - k = FIRSTBITNZ(h[i] & ~bit[j]); - h[i] = 0; - h[j] &= ~bit[i]; - h[k] &= ~bit[i]; - --nv; - if ((h[j] & bit[k])) - { - ne -= 2; - queue |= (bit[j] | bit[k]); - } - else - { - --ne; - h[j] |= bit[k]; - h[k] |= bit[j]; - } - } - } - - if (ne <= nv + 2) return TRUE; - if (nv == 5 && ne <= 9) return TRUE; - - nv = 0; - for (i = 0; i < n; ++i) if (h[i] != 0) newlab[i] = nv++; - - k = 0; - for (i = 0; i < n; ++i) - if (h[i] != 0) - { - V[newlab[i]].first_edge = k; - ww = h[i]; - while (ww) - { - TAKEBIT(j,ww); - A[k].end_vertex = newlab[j]; - A[k].next = k+1; - ++k; - } - A[k-1].next = NIL; - } - - ne = k/2; - - ans = sparseg_adjl_is_planar(V,nv,A,&c,&dfs_tree,&back_edges, - &mult_edges,&embed_graph,&edge_pos,&v,&w); - - sparseg_dlcl_delete(dfs_tree,nv); - sparseg_dlcl_delete(back_edges,nv); - sparseg_dlcl_delete(mult_edges,nv); - embedg_VES_delete(embed_graph,nv); - - return ans; -} - -/******************************************************************/ - -static void -SMILESoutput(int *vcol, int n, int *hyd, int *mult, int ne) -/* Write molecules in SMILES format */ -{ - char *p,line[20*MAXNE]; - int i,x,y,r,m; - const struct elementstruct *thiselement; - - p = line; - - for (i = 0; i < smileslen; ++i) - { - x = smilesskeleton[i].x; - y = smilesskeleton[i].y; - switch(smilesskeleton[i].item) - { - case SM_ATOM : - thiselement = &element[vcol[x]]; -#if 0 - /* This version seems to meet the OpenSmiles standard, but some - problems with obabel have not been tracked down. */ - if (!thiselement->organic || - thiselement->valence - hyd[x] <= thiselement->lowervalence) -#else - /* This version gives explicit H for atoms in higher valences even - if they are in the organic subset. */ - if (!thiselement->organic || - (thiselement->lowervalence > 0 && hyd[x] > 0)) -#endif - { - *(p++) = '['; - PUTSTR(thiselement->name); - if (hyd[x] > 0) *(p++) = 'H'; - if (hyd[x] > 1) PUTINT(hyd[x]) - *(p++) = ']'; - } - else - PUTSTR(thiselement->name); - break; - case SM_BOND : - m = mult[edgenumber[x][y]]; - if (m == 1) *(p++) = '='; - else if (m == 2) *(p++) = '#'; - break; - case SM_OPEN : - *(p++) = '('; - break; - case SM_CLOSE : - *(p++) = ')'; - break; - case SM_RING0 : - m = mult[edgenumber[x][y]]; - if (m == 1) *(p++) = '='; - else if (m == 2) *(p++) = '#'; - r = smilesskeleton[i].r; - if (r < 10) - *(p++) = '0' + r; - else - { - *(p++) = '%'; - *(p++) = '0' + r/10; - *(p++) = '0' + r%10; - } - break; - case SM_RING1 : - r = smilesskeleton[i].r; - if (r < 10) - *(p++) = '0' + r; - else - { - *(p++) = '%'; - *(p++) = '0' + r/10; - *(p++) = '0' + r%10; - } - break; - } - } - - *(p++) = '\n'; - *p = '\0'; - -#ifdef ZLIB - if (gzip) - { - if (gzputs(gzoutfile,line) < 0) - gt_abort(">E surge : zlib output error\n"); - return; - } -#endif - - if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); -} - -/******************************************************************/ - -static void -SDFformat(int *vcol, int n, int *hyd, int *mult, int ne) -/* Write molecules in SDF format */ -{ - int i,j; - -#ifdef ZLIB - if (gzip) - { - gzprintf(gzoutfile,"\nSurge 1.0\n\n"); - gzprintf(gzoutfile,"%3d%3d 0 0 0 0 999 V2000\n",n,ne); - - for (i = 0; i < n; ++i) - gzprintf(gzoutfile," 0.0000 0.0000 0.0000 %-2s" - " 0 0 0 0 0%3d 0 0 0 0 0 0\n", - element[vcol[i]].name,element[vcol[i]].valence); - - for (i = 0; i < ne; ++i) - gzprintf(gzoutfile,"%3d%3d%3d 0 0 0 0\n", - edge[i].x+1,edge[i].y+1,mult[i]+1); - - gzprintf(gzoutfile,"M END\n$$$$\n"); - - return; - } -#endif - - fprintf(outfile,"\nSurge 1.0\n\n"); - fprintf(outfile,"%3d%3d 0 0 0 0 999 V2000\n",n,ne); - - for (i = 0; i < n; ++i) - fprintf(outfile," 0.0000 0.0000 0.0000 %-2s" - " 0 0 0 0 0%3d 0 0 0 0 0 0\n", - element[vcol[i]].name,element[vcol[i]].valence); - - for (i = 0; i < ne; ++i) - fprintf(outfile,"%3d%3d%3d 0 0 0 0\n", - edge[i].x+1,edge[i].y+1,mult[i]+1); - - fprintf(outfile,"M END\n$$$$\n"); -} - -/****************************************************************/ - -static void -multigoutput(int *vcol, int n, int *mult, int ne) -/* Write output equal to the multig -T format. */ -{ - char line[10+60*MAXN],*p; - int i; - - p = line; - PUTINT(n); SPC; PUTINT(ne); - for (i = 0; i < n; ++i) - { - SPC; - PUTINT(element[vcol[i]].index); - } - SPC; - for (i = 0; i < ne; ++i) - { - SPC; PUTINT(edge[i].x); - SPC; PUTINT(edge[i].y); - SPC; PUTINT(mult[i]); - } - *(p++) = '\n'; - *p = '\0'; - -#ifdef ZLIB - if (gzip) - { - if (gzputs(gzoutfile,line) < 0) - gt_abort(">E surge : zlib output error\n"); - return; - } -#endif - - if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); -} - -/****************************************************************/ - -static void -alphabeticoutput(int *vcol, int n, int *mult, int ne) -/* Write alphabetic output */ -{ - char line[10+60*MAXN],*p; - int i,xx,yy; - - p = line; - PUTINT(n); - SPC; - PUTINT(ne); - SPC; - PUTSTR(canonform); - - for (i = 0; i < ne; ++i) - { - SPC; - xx = newlabel[edge[i].x]; - yy = newlabel[edge[i].y]; - if (xx < yy) - { - PUTINT(xx); PUTBND(mult[i]); PUTINT(yy); - } - else - { - PUTINT(yy); PUTBND(mult[i]); PUTINT(xx); - } - } - *(p++) = '\n'; - *p = '\0'; - -#ifdef ZLIB - if (gzip) - { - if (gzputs(gzoutfile,line) < 0) - gt_abort(">E surge : zlib output error\n"); - return; - } -#endif - - if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); -} - -/******************************************************************/ - -static void -gotone(int *vcol, int n, int *hyd, int *mult, int ne, int level) -/* Now we have a completed molecule. - deg[0..n-1] is the simple graph degrees - hyd[0..n-1] is the number of implicit hydrogens -*/ -{ - int i,val; - - for (i = level; i < ne; ++i) mult[i] = 0; - - if (needcoordtest) - for (i = 0; i < n; ++i) - { - if (deg[i] + hyd[i] > element[vcol[i]].maxcoord) return; - if (deg[i] + hyd[i] > 4 && hyd[i] > 0) return; - } - -#ifdef SURGEPLUGIN_STEP3 - SURGEPLUGIN_STEP3 -#endif - - ++multigout; - if (uswitch) return; - - if (outlevel == 3) - multigoutput(vcol,n,mult,ne); - else if (alphabetic) - alphabeticoutput(vcol,n,mult,ne); - else if (smiles) - SMILESoutput(vcol,n,hyd,mult,ne); - else - SDFformat(vcol,n,hyd,mult,ne); -} - -/******************************************************************/ - -static boolean -testemax(int *mult, int ne, int level) -/* Test if edge colouring is maximum wrt group. */ -{ - int *gp,i,j; - long kgp; - - for (i = level; i < ne; ++i) mult[i] = 0; - - /* kgp really starts at 1 on the next line */ - for (kgp = 1, gp = egroup; kgp < egroupsize; ++kgp, gp += ne) - { - for (i = 0; i < ne; ++i) - { - j = gp[i]; - if (mult[j] > mult[i]) return FALSE; - else if (mult[j] < mult[i]) break; - } - } - - return TRUE; -} - -/***************************************************************************/ - -static void -/* Recursive scan for multiplying edges - Version which checks allenemate for -B3 */ -escan2(int level, int needed, int *vcol, int *hyd, int *prev, int n, int *mult, int ne) -{ - int lev,maxlev,k,max,x,y; - - if (needed == 0) - { - if (egroupsize > 1 && !testemax(mult,ne,level)) - return; - gotone(vcol,n,hyd,mult,ne,level); - return; - } - else - { - maxlev = ne + 1 - (needed+maxbond-1)/maxbond; - for (lev = level; lev < maxlev; ++lev) - { - x = edge[lev].x; - y = edge[lev].y; - max = edge[lev].maxmult; - - if (needed < max) max = needed; - if (hyd[x] < max) max = hyd[x]; - if (hyd[y] < max) max = hyd[y]; - if (prev[lev] >= 0 && mult[prev[lev]] < max) max = mult[prev[lev]]; - if (edge[lev].allenemate1 >= 0 && mult[edge[lev].allenemate1] >= 1) - max = 0; - if (edge[lev].allenemate2 >= 0 && mult[edge[lev].allenemate2] >= 1) - max = 0; - - for (k = 1; k <= max; ++k) - { - mult[lev] = k; - hyd[x] -= k; - hyd[y] -= k; - escan2(lev+1,needed-k,vcol,hyd,prev,n,mult,ne); - hyd[x] += k; - hyd[y] += k; - } - - mult[lev] = 0; - } - } - - return; -} - -/***************************************************************************/ - -static void -/* Recursive scan for multiplying edges */ -escan(int level, int needed, int *vcol, int *hyd, - int *prev, int n, int *mult, int ne) -{ - int lev,maxlev,k,max,x,y; - - if (needed == 0) - { - if (egroupsize > 1 && !testemax(mult,ne,level)) - return; - gotone(vcol,n,hyd,mult,ne,level); - return; - } - else - { - maxlev = ne + 1 - (needed+maxbond-1)/maxbond; - for (lev = level; lev < maxlev; ++lev) - { - x = edge[lev].x; - y = edge[lev].y; - max = edge[lev].maxmult; - - if (needed < max) max = needed; - if (hyd[x] < max) max = hyd[x]; - if (hyd[y] < max) max = hyd[y]; - if (prev[lev] >= 0 && mult[prev[lev]] < max) - max = mult[prev[lev]]; - - for (k = 1; k <= max; ++k) - { - mult[lev] = k; - hyd[x] -= k; - hyd[y] -= k; - escan(lev+1,needed-k,vcol,hyd,prev,n,mult,ne); - hyd[x] += k; - hyd[y] += k; - } - - mult[lev] = 0; - } - } - - return; -} - -/******************************************************************/ - -static void -findegroup(int *vcol, int n, int ne) -/* Set egroup to the set of vertex-colour-preserving vgroup elements */ -{ - int *vgp,*egp,i,j,kgp; - - egp = egroup; - - /* kgp really starts at 1 on the next line */ - for (kgp = 1, vgp = vgroup; kgp < vgroupsize; ++kgp, vgp += n) - { - for (i = 0; i < n; ++i) - if (vcol[vgp[i]] != vcol[i]) break; - if (i == n) - for (j = 0; j < ne; ++j) - *(egp++) = edgenumber[vgp[edge[j].x]][vgp[edge[j].y]]; - } - - egroupsize = 1 + (egp - egroup) / ne; -} - -/****************************************************************/ - -static void -colouredges(graph *g, int *vcolindex, int n) -/* This procedure receives graphs from the vcolg phase and - colours the edges. */ -{ - int hyd[MAXN]; /* Remaining degree of vertex */ - int i,j,k,ne; - int mult[MAXNE]; - int prev[MAXNE]; /* If >= 0, earlier point that must have greater colour */ - int needed; /* Extra edges needed */ - int iter[FORMULALEN]; - int vcol[MAXN]; /* index into element[] */ - - ne = numedges; - - /* hyd[i] starts with the number of hydrogens needed if all bonds are - single and is reduced as bonds are multiplied */ - - needcoordtest = FALSE; - for (i = 0; i < n; ++i) - { - vcol[i] = elementtype[vcolindex[i]]; - hyd[i] = element[vcol[i]].valence - deg[i]; - if (element[vcol[i]].valence > element[vcol[i]].maxcoord - || (element[vcol[i]].valence > 4 && hyd[i] > 0)) - needcoordtest = TRUE; - } - -#ifdef SURGEPLUGIN_STEP2 - SURGEPLUGIN_STEP2 -#endif - - needed = (valencesum - hydrogens)/2 - ne; /* Extra edges needed */ - - if (ne == 0 && needed > 0) return; - - if (alphabetic) - { - iter[0] = 0; - for (i = 1; i < numtypes; ++i) iter[i] = iter[i-1] + elementcount[i-1]; - - for (i = 0; i < n; ++i) newlabel[i] = iter[vcolindex[i]]++; - } - - if (needed == 0) - { - gotone(vcol,n,hyd,mult,ne,0); - return; - } - - for (i = 0; i < ne; ++i) prev[i] = -1; - - for (i = 0; i < n; ++i) - { - if (deg[i] != 1) continue; - /* Find most recent equivalent j */ - for (j = i; --j >= 0; ) - if (g[i] == g[j] && vcol[j] == vcol[i]) - break; - - if (j >= 0) - { - k = FIRSTBITNZ(g[i]); - prev[edgenumber[i][k]] = edgenumber[j][k]; - } - } - - if (vgroupsize == 1) - egroupsize = 1; - else - { - if (egroupalloc < vgroupsize*ne) - { - if (egroup) free(egroup); - if ((egroup = malloc((vgroupsize+48)*ne*sizeof(int))) == NULL) - gt_abort(">E surge : Can't allocate space for egroup\n"); - egroupalloc = (vgroupsize+48) * ne; - } - findegroup(vcol,n,ne); - if (vgroupsize % egroupsize != 0) gt_abort(">E egroup error\n"); - } - - if (egroupsize == 1 && needed == 1) - { - for (i = 0; i < ne; ++i) mult[i] = 0; - for (i = 0; i < ne; ++i) - if (prev[i] < 0 && edge[i].maxmult >= 1 - && hyd[edge[i].x] > 0 && hyd[edge[i].y] > 0) - { - mult[i] = 1; - --hyd[edge[i].x]; - --hyd[edge[i].y]; - gotone(vcol,n,hyd,mult,ne,ne); - ++hyd[edge[i].x]; - ++hyd[edge[i].y]; - mult[i] = 0; - } - return; - } - - if (egroupsize != 1) ++multignontriv; - if (egroupsize > maxegroup) maxegroup = egroupsize; - - if (bad5 || bad6) escan2(0,needed,vcol,hyd,prev,n,mult,ne); - else escan(0,needed,vcol,hyd,prev,n,mult,ne); -} - -/******************************************************************/ - -static void -vcolgoutput(graph *g, int *vcolindex, int n) -/* Write output equal to the vcolg -T format. */ -{ - char line[10+30*MAXN],*p; - int i,j,ne; - setword w; - - ne = 0; - for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); - ne /= 2; - - p = line; - PUTINT(n); SPC; PUTINT(ne); - for (i = 0; i < n; ++i) - { - SPC; - PUTINT(element[elementtype[vcolindex[i]]].index); - } - SPC; - for (i = 0; i < n; ++i) - { - w = g[i] & BITMASK(i); - while (w) - { - TAKEBIT(j,w); - SPC; PUTINT(i); SPC; PUTINT(j); - } - } - *(p++) = '\n'; - *p = '\0'; - -#ifdef ZLIB - if (gzip) - { - if (gzputs(gzoutfile,line) < 0) - gt_abort(">E surge : zlib output error\n"); - return; - } -#endif - - if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); -} - -/******************************************************************/ - -static boolean -testvmax(int *colindex, int n) -/* Test if vertex colouring is maximum wrt group. If so, return group. - If not, return a safe level to return to. */ -{ - int *gp,i,j; - long kgp; - - /* kgp really starts at 1 on the next line */ - for (kgp = 1, gp = vgroup; kgp < vgroupsize; ++kgp, gp += n) - { - for (i = 0; i < n; ++i) - { - j = gp[i]; - if (colindex[j] > colindex[i]) return FALSE; - else if (colindex[j] < colindex[i]) break; - } - } - - return TRUE; -} - -/**********************************************************************/ - -static void -vscan(int level, int *colindex, graph *g, int *prev, - int *maxcolindex, int *remain, int n) -/* Recursive vertex colour scan */ -{ - int k,max; - - if (level == n) - { - if (vgroupsize == 1 || testvmax(colindex,n)) - { - ++vcolgout; - if (outlevel == 2) - { - if (!uswitch) vcolgoutput(g,colindex,n); - } - else - colouredges(g,colindex,n); - } - return; - } - - max = maxcolindex[level]; - if (prev[level] >= 0 && colindex[prev[level]] < max) - max = colindex[prev[level]]; - - for (k = 0; k <= max; ++k) - { - if (remain[k] == 0) continue; - colindex[level] = k; - --remain[k]; - vscan(level+1,colindex,g,prev,maxcolindex,remain,n); - ++remain[k]; - } -} - -/**********************************************************************/ - -static void -storevgroup(int *p, int n) -/* Called by allgroup; store full group at vcolg phase */ -{ - int *gp,i; - - if (vgroupcount == 0) - { - vgroupcount = 1; /* Don't store identity */ - return; - } - - gp = vgroup + n * (vgroupcount-1); - for (i = 0; i < n; ++i) gp[i] = p[i]; - - ++vgroupcount; -} - -/**********************************************************************/ - -static void -colourvertices(graph *g, int n) -/* Main procedure for vcolg phase */ -{ - static DEFAULTOPTIONS_GRAPH(options); - statsblk stats; - setword workspace[2*MAXN]; - grouprec *group; - int lab[MAXN],ptn[MAXN],orbits[MAXN]; - int prev[MAXN]; /* If >= 0, earlier point that must have greater colour */ - int weight[MAXN]; - int maxcolindex[MAXN]; /* Max colour index for each vertex */ - int remain[FORMULALEN]; /* Remaining number of colours for each vertex */ - int vcolindex[MAXN]; /* Index into elementtype[] */ - int i,j; - -#ifdef SURGEPLUGIN_STEP1 - SURGEPLUGIN_STEP1 -#endif - - for (i = 0; i < n; ++i) - { - prev[i] = -1; - weight[i] = n*POPCOUNT(g[i]); - } - - for (i = 0; i < n; ++i) - { - if (POPCOUNT(g[i]) != 1) continue; - /* Find most recent equivalent j */ - for (j = i; --j >= 0; ) - if (g[j] == g[i]) break; - - if (j >= 0) - { - prev[i] = j; - weight[i] = weight[j] + 1; - } - } - - options.userautomproc = groupautomproc; - options.userlevelproc = grouplevelproc; - options.defaultptn = FALSE; - - setlabptn(weight,lab,ptn,n); - - nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,2*MAXN,1,n,NULL); - - if (stats.grpsize2 > 0 || stats.grpsize1 > 1e7) - gt_abort(">E surge : vgroup size greater than 10^7 encountered\n"); - vgroupsize = stats.grpsize1 + 0.01; - - for (i = 0; i < numtypes; ++i) remain[i] = elementcount[i]; - for (i = 0; i < n; ++i) maxcolindex[i] = maxtype[deg[i]]; - - if (vgroupsize == 1) /* Trivial group */ - { - vscan(0,vcolindex,g,prev,maxcolindex,remain,n); - return; - } - - ++vcolgnontriv; - if (vgroupsize > maxvgroup) maxvgroup = vgroupsize; - - group = groupptr(FALSE); - makecosetreps(group); - - if (vgroupalloc < (vgroupsize-1) * n) - { - if (vgroup) free(vgroup); - if ((vgroup = malloc((vgroupsize+48)*n*sizeof(int))) == NULL) - gt_abort(">E surge : Can't allocate space for vgroup\n"); - vgroupalloc = (vgroupsize+48) * n; - } - - vgroupcount = 0; - allgroup(group,storevgroup); - - if (vgroupcount != vgroupsize) gt_abort(">E surge : vgroup error\n"); - - /* Check the logic of the next section. What about maxdeg? */ - if (numtypes == 1) - { - for (i = 0; i < n; ++i) vcolindex[i] = 0; - ++vcolgout; - colouredges(g,vcolindex,n); - return; - } - - j = n; /* Can choose a better orbit? */ - for (i = 0; i < n; ++i) - if (orbits[i] < i && orbits[i] < j) j = orbits[i]; - for (i = j + 1; i < n; ++i) - if (orbits[i] == j) prev[i] = j; - - vscan(0,vcolindex,g,prev,maxcolindex,remain,n); -} - -/******************************************************************/ - -extern int geng_maxe; - -int -surgepreprune(graph *g, int n, int maxn) -/* This function is called by the PREPRUNE service of geng. - It does -B9 which is more efficient here. If OLDGENG is - defined, it also speeds up the generation of connected - graphs. (For new geng, that function is built in.) */ -{ - setword notvisited,queue; - setword c34,w,ww,cyc; - int ne,nc,i; - - if (bad9) /* cycle34verts */ - { - if (n <= 2) - cycle34verts[n] = 0; - else - { - c34 = cycle34verts[n-1]; - if (!tswitch) - { - w = g[n-1]; - while (w) - { - TAKEBIT(i,w); - ww = g[i] & w; - if (POPCOUNT(ww) > 1) return 1; - if ((ww)) - { - cyc = bit[n-1] | bit[i] | ww; - if ((c34 & cyc)) return 1; - c34 |= cyc; - } - } - } - if (!fswitch) - { - for (i = n-1; --i >= 0;) - { - w = g[i] & g[n-1]; - if (POPCOUNT(w) > 2) return 1; - if (POPCOUNT(w) == 2) - { - cyc = bit[n-1] | bit[i] | w; - if ((c34 & cyc)) return 1; - c34 |= cyc; - } - } - } - cycle34verts[n] = c34; - } - } - -#ifdef OLDGENG - if (n == maxn || geng_maxe - maxn >= 5) return 0; - - ne = 0; - for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); - ne /= 2; - - nc = 0; - notvisited = ALLMASK(n); - - while (notvisited) - { - ++nc; - queue = SWHIBIT(notvisited); - notvisited &= ~queue; - while (queue) - { - TAKEBIT(i,queue); - notvisited &= ~bit[i]; - queue |= g[i] & notvisited; - } - } - - if (ne - n + nc > geng_maxe - maxn + 1) return 1; -#endif - - return 0; -} - -int -surgeprune(graph *g, int n, int nmax) -/* This is a procedure that geng will call at each level -using the PRUNE service. -The options -t, -f, -p, -B7,8 are implemented here by -incrementally updating the required counts. */ -{ - setword w,ax,ay,gx,gy,gxy,gxya,gi,gj; - int i,j,x,y,k,a,b,extra; - int i1,i2,i3,i4,d1,d2,d3,d4; - int v[MAXN]; - - if (tswitch) - { - if (n <= 2) - count3ring[n] = 0; - else - { - extra = 0; - w = g[n-1]; - while (w) - { - TAKEBIT(i,w); - extra += POPCOUNT(g[i]&w); - } - count3ring[n] = count3ring[n-1] + extra; - if (count3ring[n] > max3rings) return 1; - } - if (n == nmax && count3ring[n] < min3rings) - return 1; - } - - if (fswitch) - { - if (n <= 3) - count4ring[n] = 0; - else - { - extra = 0; - for (i = 0; i < n-1; ++i) - { - k = POPCOUNT(g[i]&g[n-1]); - extra += k*k - k; - } - count4ring[n] = count4ring[n-1] + extra/2; - if (count4ring[n] > max4rings) return 1; - } - if (n == nmax && count4ring[n] < min4rings) - return 1; - } - - if (pswitch) - { - if (n <= 4) - count5ring[n] = 0; - else - { - extra = 0; - for (y = 1; y < n-1; ++y) - { - w = g[y] & ~BITMASK(y); - while (w) - { - TAKEBIT(x,w); - ax = (g[x] & g[n-1]) & ~bit[y]; - ay = (g[y] & g[n-1]) & ~bit[x]; - extra += POPCOUNT(ax)*POPCOUNT(ay) - POPCOUNT(ax&ay); - } - } - count5ring[n] = count5ring[n-1] + extra; - if (count5ring[n] > max5rings) return 1; - } - if (n == nmax && count5ring[n] < min5rings) - return 1; - } - - if (bad7 && n >= 6) - { - /* For K_33 we can assume that vertex n-1 is included */ - for (x = n-1; --x >= 1;) - if (POPCOUNT(g[x]&g[n-1]) >= 3) - { - for (y = x; --y >= 0;) - if (POPCOUNT(g[y]&g[x]&g[n-1]) >= 3) return 1; - } - - /* But for K_24 we can't */ - for (x = n; --x >= 1;) - if (POPCOUNT(g[x]) >= 4) - { - for (y = x; --y >= 0;) - if (POPCOUNT(g[x]&g[y]) >= 4) return 1; - } - } - - if (bad8) - { - /* cone of P4 */ - if (n >= 5) - { - for (x = n; --x >= 0;) - if (POPCOUNT(g[x]) == 4) - { - /* Think of a better way to do this */ - w = gx = g[x]; - TAKEBIT(i1,w); - TAKEBIT(i2,w); - TAKEBIT(i3,w); - i4 = FIRSTBITNZ(w); - d1 = POPCOUNT(g[i1]&gx); - d2 = POPCOUNT(g[i2]&gx); - d3 = POPCOUNT(g[i3]&gx); - d4 = POPCOUNT(g[i4]&gx); - if (d1 > 0 && d2 > 0 && d3 > 0 && d4 > 0 - && ((d1 >= 2)+(d2 >= 2)+(d3 >= 2)+(d4 >= 2)) >= 2) - return 1; - } - else if (POPCOUNT(g[x]) > 4) - { - w = gx = g[x]; - i = 0; - while (w) - { - TAKEBIT(y,w); - if (POPCOUNT(g[y]&gx) >= 2) v[i++] = y; - } - for (--i; i >= 1; --i) - for (j = 0; j < i; ++j) - if ((g[v[i]] & bit[v[j]]) && - POPCOUNT((g[v[i]]|g[v[j]])&gx) >= 4) - return 1; - } - } - - /* K4 with a path of 3 edges between two of its vertices */ - - if (n >= 6) - { - for (x = n; --x >= 1;) - if (POPCOUNT(g[x]) >= 4) - { - for (y = x; --y >= 0;) - if (POPCOUNT(g[y]) >= 4 && (g[y]&bit[x])) - { - gxy = g[x] & g[y]; - while (gxy) - { - TAKEBIT(a,gxy); - gxya = gxy & g[a]; - while (gxya) - { - TAKEBIT(b,gxya); - w = bit[x] | bit[y] | bit[a] | bit[b]; - gi = g[x] & ~w; - gj = g[y] & ~w; - while (gi) - { - TAKEBIT(i,gi); - if ((g[i] & gj)) return 1; - } - } - } - } - } - } - } - -#ifdef SURGEPLUGIN_STEP0 - SURGEPLUGIN_STEP0 -#endif - - return 0; -} - -/******************************************************************/ - -static void -smilesdfs(graph *g, setword *seen, int v, int par, graph *back, - struct smilesstruct *smilestemp, int *len) -/* Recursive DFS to collect SMILES information */ -{ - setword gv,w; - int k; - boolean first; - - gv = g[v]; - first = TRUE; - *seen |= bit[v]; - - while (gv) - { - TAKEBIT(k,gv); - if ((*seen & bit[k])) - { - if (k != par) - { - back[v] |= bit[k]; - back[k] |= bit[v]; - } - } - else - { - if (first) - { - if (POPCOUNT(g[k]) == 1) - { - if ((w = (gv & ~*seen))) /* really = */ - { - gv |= bit[k]; - k = FIRSTBITNZ(w); - gv &= ~bit[k]; - } - } - smilesdfs(g,seen,k,v,back,smilestemp,len); - first = FALSE; - } - else - { - smilestemp[(*len)++].item = SM_CLOSE; - smilesdfs(g,seen,k,v,back,smilestemp,len); - smilestemp[(*len)++].item = SM_OPEN; - } - } - } - - smilestemp[*len].item = SM_ATOM; - smilestemp[*len].x = v; - ++*len; - if (par >= 0) - { - smilestemp[*len].item = SM_BOND; - smilestemp[*len].x = par; - smilestemp[*len].y = v; - ++*len; - } -} - -static void -makesmilesskeleton(graph *g, int n) -/* Make a skeleton SMILES structure for use in SMILESoutput */ -{ - struct smilesstruct smilestemp[4*MAXN+6*MAXNE]; - graph back[MAXN],ring[MAXN]; - setword w,seen; - int len,ringnumber; - int i,j,v; - - for (v = n; --v >= 0; ) if (POPCOUNT(g[v]) == 1) break; - if (v < 0) v = n-1; - - len = 0; - seen = 0; - for (i = 0; i < n; ++i) back[i] = 0; - for (i = 0; i < n; ++i) ring[i] = 0; - - smilesdfs(g,&seen,v,-1,back,smilestemp,&len); - - smileslen = 0; - ringnumber = 0; - for (i = len; --i >= 0; ) - { - smilesskeleton[smileslen++] = smilestemp[i]; - if (smilestemp[i].item == SM_ATOM) - { - v = smilestemp[i].x; - w = ring[v]; - while (w) - { - TAKEBIT(j,w); - smilesskeleton[smileslen].item = SM_RING1; - smilesskeleton[smileslen].r = j+1; - ++smileslen; - } - w = back[v]; - while (w) - { - TAKEBIT(j,w); - ++ringnumber; - smilesskeleton[smileslen].item = SM_RING0; - smilesskeleton[smileslen].x = v; - smilesskeleton[smileslen].y = j; - smilesskeleton[smileslen].r = ringnumber; - ++smileslen; - ring[j] |= bit[ringnumber-1]; - back[j] &= ~bit[v]; - } - } - } - -#ifdef SMILESSKELETON - /* This will print the SMILES skeleton to stdout */ - - fprintf(stdout,"len=%d",smileslen); - for (i = 0; i < smileslen; ++i) - { - switch (smilesskeleton[i].item) - { - case SM_ATOM : - fprintf(stdout," %d",smilesskeleton[i].x); - break; - case SM_BOND : - fprintf(stdout," %d-%d",smilesskeleton[i].x,smilesskeleton[i].y); - break; - case SM_OPEN : - fprintf(stdout," ("); - break; - case SM_CLOSE : - fprintf(stdout," )"); - break; - case SM_RING0 : - fprintf(stdout," R%d:%d-%d",smilesskeleton[i].r, - smilesskeleton[i].x,smilesskeleton[i].y); - break; - case SM_RING1 : - fprintf(stdout," R%d",smilesskeleton[i].r); - } - } - fprintf(stdout,"\n"); -#endif -} - -/******************************************************************/ - -static void -inducedpaths(graph *g, int origin, int start, setword body, - setword last, setword path) -/* Number of induced paths in g starting at start, extravertices within - * body and ending in last. - * {start}, body and last should be disjoint. */ -{ - setword gs,w; - int i; - - gs = g[start]; - - w = gs & last; - while (w) - { - TAKEBIT(i,w); - inducedcycle[cyclecount++] - = path | bit[edgenumber[start][i]] | bit[edgenumber[origin][i]]; - } - - w = gs & body; - while (w) - { - TAKEBIT(i,w); - inducedpaths(g,origin,i,body&~gs,last&~bit[i]&~gs, - path|bit[edgenumber[start][i]]); - } -} - -static void -findinducedcycles(graph *g, int n) -/* Find all the induced cycles */ -{ - setword body,last,cni; - int i,j; - -#if 0 - body = 0; - for (i = 0; i < n; ++i) - if (POPCOUNT(g[i]) > 1) body |= bit[i]; -#else - body = ALLMASK(n); -#endif - - cyclecount = 0; - - for (i = 0; i < n-2; ++i) - { - body &= ~bit[i]; - last = g[i] & body; - cni = g[i] | bit[i]; - while (last) - { - TAKEBIT(j,last); - inducedpaths(g,i,j,body&~cni,last,bit[edgenumber[i][j]]); - } - if (cyclecount > 3*MAXCYCLES/4) gt_abort(">E increase MAXCYCLES\n"); - } - - if (cyclecount > maxcycles) maxcycles = cyclecount; - -#if 0 - printf("cyclecount=%d\n",cyclecount); - - { - setword cyc; int i,j; - - for (i = 0; i < cyclecount; ++i) - { - cyc = inducedcycle[i]; - while (cyc) - { - TAKEBIT(j,cyc); - printf(" %d-%d",edge[j].x,edge[j].y); - } - printf("\n"); - } - } -#endif -} - -/******************************************************************/ - -void -surgeproc(FILE *outfile, graph *gin, int n) -/* This is called by geng for each graph. */ -{ - int i,j,k,d,n1,n12,n34,n4,ne; - int isize,jsize; - graph g[MAXN]; - setword w,wxy,ww,pw,cyc,cycle8; - int x,y,e1,e2,e3; - - n1 = n12 = n34 = n4 = 0; - - ++gengout; - - for (i = 0; i < n; ++i) - { - d = POPCOUNT(gin[i]); /* deg[i] is not defined yet */ - if (d == 1) { ++n1; ++n12; } - else if (d == 2) ++n12; - else if (d == 3) ++n34; - else { ++n34; ++n4; } - } - - if (n > 1 && (n1 < min1 || n12 < min12 || n34 > max34 || n4 > max4)) - return; - - if (planar && !isplanar(gin,n)) return; /* Try later */ - - ++genggood; - - /* Reverse to put higher degrees first */ - - for (i = 0; i < n; ++i) - { - w = gin[n-i-1]; - pw = 0; - while (w) - { - TAKEBIT(j,w); - pw |= bit[n-j-1]; - } - g[i] = pw; - } - - /* Make the edge list with default parameters */ - - ne = 0; - for (i = 0; i < n; ++i) - { - deg[i] = POPCOUNT(g[i]); - w = g[i] & BITMASK(i); - while (w) - { - TAKEBIT(j,w); - edge[ne].x = i; - edge[ne].y = j; - edge[ne].xy = bit[i] | bit[j]; - edge[ne].maxmult = maxbond; - edge[ne].allenemate1 = edge[ne].allenemate2 = -1; - edgenumber[i][j] = edgenumber[j][i] = ne; - ++ne; - } - } - numedges = ne; - - if (needcycles) - { - if (ne > WORDSIZE) - gt_abort(">E surge : too many edges for badlists\n"); - findinducedcycles(g,n); - } - - if (bad1) /* no triple bonds in rings smaller than 7 */ - { - for (i = 0; i < cyclecount; ++i) - { - cyc = inducedcycle[i]; - if (POPCOUNT(cyc) <= 7) - while (cyc) - { - TAKEBIT(j,cyc); - if (edge[j].maxmult == 2) edge[j].maxmult = 1; - } - } - } - - if (bad2) /* Bredt's rule for one common bond */ - { - for (i = 0; i < cyclecount-1; ++i) - { - isize = POPCOUNT(inducedcycle[i]); - if (isize > 6) continue; - for (j = i+1; j < cyclecount; ++j) - { - jsize = POPCOUNT(inducedcycle[j]); - if (jsize > 6) continue; - - w = inducedcycle[i] & inducedcycle[j]; - if (POPCOUNT(w) != 1) continue; - - if (isize*jsize <= 15) - edge[FIRSTBITNZ(w)].maxmult = 0; - - if (isize+jsize <= 9) - { - wxy = edge[FIRSTBITNZ(w)].xy; - ww = (inducedcycle[i] | inducedcycle[j]) & ~w; - while (ww) - { - TAKEBIT(k,ww); - if ((edge[k].xy & wxy)) edge[k].maxmult = 0; - } - } - } - } - } - - if (bad3) /* Bredt's rule for two common bonds */ - { - for (i = 0; i < cyclecount-1; ++i) - { - isize = POPCOUNT(inducedcycle[i]); - if (isize == 3 || isize > 6) continue; - - for (j = i+1; j < cyclecount; ++j) - { - jsize = POPCOUNT(inducedcycle[j]); - if (jsize == 3 || jsize > 6 || isize+jsize == 12) continue; - - w = inducedcycle[i] & inducedcycle[j]; - if (POPCOUNT(w) != 2) continue; - - ww = w; - TAKEBIT(k,ww); - edge[k].maxmult = 0; - edge[FIRSTBITNZ(ww)].maxmult = 0; - wxy = edge[k].xy ^ edge[FIRSTBITNZ(ww)].xy; - - ww = (inducedcycle[i] | inducedcycle[j]) & ~w; - while (ww) - { - TAKEBIT(k,ww); - if ((edge[k].xy & wxy)) edge[k].maxmult = 0; - } - } - } - } - - if (bad4) /* Bredt's rule for two hexagons with 3 bonds in common */ - { - for (i = 0; i < cyclecount-1; ++i) - { - isize = POPCOUNT(inducedcycle[i]); - if (isize != 6) continue; - - for (j = i+1; j < cyclecount; ++j) - { - jsize = POPCOUNT(inducedcycle[j]); - if (jsize != 6) continue; - - w = inducedcycle[i] & inducedcycle[j]; - if (POPCOUNT(w) != 3) continue; - - ww = inducedcycle[i] | inducedcycle[j]; - - TAKEBIT(e1,w); - TAKEBIT(e2,w); - e3 = FIRSTBITNZ(w); - - wxy = edge[e1].xy ^ edge[e2].xy ^ edge[e3].xy; - while (ww) - { - TAKEBIT(k,ww); - if ((edge[k].xy & wxy)) edge[k].maxmult = 0; - } - } - } - } - - if (bad5) /* No A=A=A, whether in ring or not */ - { - for (i = 0; i < n; ++i) - if (deg[i] == 2) - { - x = FIRSTBITNZ(g[i]); - y = FIRSTBITNZ(g[i]&~bit[x]); - e1 = edgenumber[i][x]; - e2 = edgenumber[i][y]; - if (edge[e2].allenemate1 < 0) - { - if (e1 < e2) edge[e2].allenemate1 = e1; - else edge[e1].allenemate1 = e2; - } - else - { - if (e1 < e2) edge[e2].allenemate2 = e1; - else edge[e1].allenemate2 = e2; - } - } - } - - if (bad6) /* No A=A=A in rings up to length 8 */ - { - cycle8 = 0; - for (i = 0; i < cyclecount; ++i) - if (POPCOUNT(inducedcycle[i]) <= 8) cycle8 |= inducedcycle[i]; - - for (i = 0; i < n; ++i) - if (deg[i] == 2) - { - x = FIRSTBITNZ(g[i]); - y = FIRSTBITNZ(g[i]&~bit[x]); - e1 = edgenumber[i][x]; - if (!(bit[e1] & cycle8)) continue; - e2 = edgenumber[i][y]; - if (edge[e2].allenemate1 < 0) - { - if (e1 < e2) edge[e2].allenemate1 = e1; - else edge[e1].allenemate1 = e2; - } - else - { - if (e1 < e2) edge[e2].allenemate2 = e1; - else edge[e1].allenemate2 = e2; - } - } - } - - if (outlevel == 1) - { - if (!uswitch) writeg6(outfile,g,1,n); - return; - } - - /* Make a SMILES skeleton structure for later use */ - if (smiles) makesmilesskeleton(g,n); - - colourvertices(g,n); -} - -/****************************************************************/ - -static void -decode_formula(char *formula, int *nv, - int *mine, int *maxe, int *maxd, int *maxc) -/* Parse the input formula. The number of hydrogens goes to hydrogens. - The other distinct elements go to elementlist[0..numtypes-1] and - elementcount[0..numtypes-1]. - *mine and *maxe have an edge range from -e and are updated. - *mind and *maxc come from -d and -c and are updated. - *nv gets the number of non-H atoms. -*/ -{ - int i,j,d,mult,val,cnt,totval,dbe,forced; - int maxvcoord,localmine,localmaxe,localmaxd,xi,yi; - char *s1,*s2,*p; - int count[FORMULALEN]; - - if (numelements > FORMULALEN) - gt_abort(">E surge : increase FORMULALEN\n"); - - /* First we fill in count[*], which is parallel to element[*] */ - - for (i = 0; i < numelements; ++i) count[i] = 0; - - for (s1 = formula; *s1 != '\0'; s1 = s2) - { - if (!isupper(*s1)) gt_abort(">E surge : unknown element name\n"); - for (s2 = s1+1; islower(*s2); ++s2) {} - for (i = 0; i < numelements; ++i) - { - for (j = 0; element[i].inputname[j] != '\0' - && s1+j != s2 && element[i].inputname[j] == s1[j]; ++j) {} - if (element[i].inputname[j] == '\0' && s1+j == s2) break; - } - if (i == numelements) gt_abort(">E surge : unknown element name\n"); - s1 = s2; - if (!isdigit(*s2)) - ++count[i]; - else - { - mult = *s2 - '0'; - for (s2 = s1+1; isdigit(*s2); ++s2) mult = 10*mult+(*s2-'0'); - count[i] += mult; - } - } - - /* Next we collect elements actually used into elementtype[0..numtypes-1] - and elementcount[0..numtypes-1], except for H which we just count. */ - - numtypes = hydrogens = 0; - for (i = 0; i < numelements; ++i) - { - cnt = count[i]; - if (cnt > 0) - { - if (ISHYDROGEN(i)) - hydrogens = cnt; - else - { - elementtype[numtypes] = i; - elementcount[numtypes] = cnt; - ++numtypes; - } - } - } - - /* Next we adjust *maxd and *maxc, as well as the maxcoord - fields of elements */ - - maxvcoord = 0; - for (i = 0; i < numtypes; ++i) - if (element[elementtype[i]].maxcoord > maxvcoord) - maxvcoord = element[elementtype[i]].maxcoord; - if (maxvcoord < *maxc) - *maxc = maxvcoord; - else if (maxvcoord > *maxc) - for (i = 0; i < numtypes; ++i) - if (element[elementtype[i]].maxcoord > *maxc) - element[elementtype[i]].maxcoord = *maxc; - if (*maxd > *maxc) *maxd = *maxc; - - /* Next we find some bounds on the number of vertices - with various simple degrees. */ - - min1 = min12 = max34 = max4 = 0; - for (i = 0; i < numelements; ++i) - { - if (!ISHYDROGEN(i)) - { - cnt = count[i]; - val = element[i].maxcoord; - if (val <= 1) min1 += cnt; // Check logic - if (val <= 2) min12 += cnt; - if (val >= 3) max34 += cnt; - if (val >= 4) max4 += cnt; - // Could add max5, could use both bounds everywhere - } - } - - /* Now sort by decreasing maximum coordination number */ - - for (i = 1; i < numtypes; ++i) /* really 1 */ - { - xi = elementtype[i]; - yi = elementcount[i]; - for (j = i; element[elementtype[j-1]].maxcoord < element[xi].maxcoord; ) - { - elementtype[j] = elementtype[j-1]; - elementcount[j] = elementcount[j-1]; - if (--j < 1) break; - } - elementtype[j] = xi; - elementcount[j] = yi; - } - - /* Make "canonical" molecule name (used for -A output) */ - - p = canonform; - for (i = 0; i < numtypes; ++i) - { - PUTSTR(element[elementtype[i]].inputname); - if (elementcount[i] > 1) PUTINT(elementcount[i]); - } - if (hydrogens > 0) PUTSTR("H"); - if (hydrogens > 1) PUTINT(hydrogens); - *p = '\0'; - - /* Calculate *nv, totalval, forced which all exclude H */ - - *nv = forced = 0; - totval = hydrogens; - for (i = 0; i < numtypes; ++i) - { - j = elementtype[i]; - cnt = elementcount[i]; - *nv += cnt; - totval += cnt * element[j].valence; - if (element[j].valence > element[j].maxcoord) - forced += element[j].valence - element[j].maxcoord; - } - forced = (forced+1) / 2; - - if ((totval & 1)) gt_abort(">E surge : impossible parity\n"); - dbe = totval / 2 - (*nv + hydrogens - 1) - forced; - if (dbe < 0) gt_abort(">E surge : negative DBE\n"); - if (*nv > MAXN) gt_abort(">E surge : too many non-hydrogen atoms\n"); - if (*nv == 0) gt_abort(">E surge : only hydrogen\n"); - valencesum = totval - hydrogens; - - localmine = *nv - 1; - localmaxe = localmine + dbe; - if (localmaxe > *nv * (*nv-1) / 2) localmaxe = *nv * (*nv-1) / 2; - - if (localmine > *mine) *mine = localmine; - if (localmaxe < *maxe) *maxe = localmaxe; - if (*mine > *maxe) gt_abort(">E surge : edge range is empty\n"); - - fprintf(stderr,"%s ",canonform); - fprintf(stderr,"H=%d",hydrogens); - for (i = 0; i < numtypes; ++i) - fprintf(stderr," %s=%d",element[elementtype[i]].inputname,elementcount[i]); - - fprintf(stderr," nv=%d edges=%d-%d DBE=%d maxd=%d maxc=%d\n", - *nv,*mine,*maxe,dbe,*maxd,*maxc); - if (*maxe > MAXNE) gt_abort(">E surge : too many edges\n"); - - for (d = 1; d <= *maxd; ++d) - { - for (i = 0; i < numtypes; ++i) - { - val = element[elementtype[i]].maxcoord; - if (d <= val) maxtype[d] = i; - } - } -} - -/****************************************************************/ - -static void -start_geng(int n, int maxd, int maxc, - int mine, int maxe, char *extra1, long res, long mod) -/* start geng with arguments, extra1 before n, extra2 after n */ -{ - int i,geng_argc,mind; - char *geng_argv[20]; - char arga[30],argb[30]; - char resmod[40]; - char gengargs[80]; - char edgecount[40]; - - mind = 1; - if (hydrogens == 0) - { - for (i = 0; i < numtypes; ++i) - if (element[elementtype[i]].valence < 4) break; - if (i == numtypes) mind = 2; - } - - if (n == 1) mind = 0; - - sprintf(arga,"-qcd%dD%d",mind,maxd); - sprintf(argb,"%d",n); - sprintf(edgecount,"%d:%d",mine,maxe); - - geng_argv[0] = "geng_surge"; - geng_argv[1] = arga; - geng_argc = 2; - - if (tswitch && max3rings == 0) - { - geng_argv[geng_argc++] = "-t"; - tswitch = FALSE; - } - - if (fswitch && max4rings == 0) - { - geng_argv[geng_argc++] = "-f"; - fswitch = FALSE; - } - - if (bipartite) geng_argv[geng_argc++] = "-b"; - - if (extra1) - { - snprintf(gengargs,78,"-%s",extra1); - geng_argv[geng_argc++] = gengargs; - } - geng_argv[geng_argc++] = argb; - geng_argv[geng_argc++] = edgecount; - if (mod > 1) - { - sprintf(resmod,"%ld/%ld",res,mod); - geng_argv[geng_argc++] = resmod; - } - geng_argv[geng_argc] = NULL; - - if (verbose) - { - fprintf(stderr,">geng"); - for (i = 1; geng_argv[i] != NULL; ++i) - fprintf(stderr," %s",geng_argv[i]); - fprintf(stderr,"\n"); - } - - geng_main(geng_argc,geng_argv); -} - -/*******************************************************************/ - -static void -processEswitch(char **ps, char *id) -/* Process -E starting at *ps = the character after E, and update *ps. - The value has the form [,][], - where and are either or and - and are single digits. Inputname comes first. */ -{ - char inputname[3],name[3]; - int valence,maxcoord; - char *s; - int state,err; - - s = *ps; - state = 0; - - while (state < 5) - { - switch (state) - { - case 0: - if (!isupper(*s)) - { - state = 6; - break; - } - inputname[0] = *s++; - if (islower(*s)) - { - inputname[1] = *s++; - inputname[2] = '\0'; - } - else - inputname[1] = '\0'; - state = 1; - break; - case 1: - if (isupper(*s)) - state = 2; - else - { - name[0] = inputname[0]; - name[1] = inputname[1]; - name[2] = inputname[2]; - state = 3; - } - break; - case 2: - name[0] = *s++; - if (islower(*s)) - { - name[1] = *s++; - name[2] = '\0'; - } - else - name[1] = '\0'; - state = 3; - break; - case 3: - if (!isdigit(*s)) - { - state = 7; - break; - } - valence = *s++ - '0'; - if (!isdigit(*s)) - maxcoord = valence; - else - maxcoord = *s++ - '0'; - state = 5; - break; - } - } - - if (state == 6) - { - fprintf(stderr,">E %s : bad element name\n",id); - exit(1); - } - if (state == 7) - { - fprintf(stderr,">E %s : bad valence or maxcoord\n",id); - exit(1); - } - - *ps = s; - - addelement(inputname,name,valence,maxcoord); -} - -#define SWELEMENT(c,id) if (sw==c) {processEswitch(&arg,id);} - -/*******************************************************************/ - -int -main(int argc, char *argv[]) -{ - int argnum,i,j; - boolean badargs,Gswitch,mswitch,Oswitch,eswitch,notriples; - boolean oswitch,Bswitch,cswitch,Dswitch; - char *extra1,*extra2,*formula,*arg,sw,*outfilename; - long res,mod; - int mine,maxe,maxd,maxc; - long eminval,emaxval; - double t1,t2; - long badlist[BADLISTS]; - int badlen; - - HELP; - - nauty_check(WORDSIZE,1,1,NAUTYVERSIONID); - - maxindex = 0; - for (i = 0; i < MAXELEMENTS; ++i) - if (element[i].inputname == NULL) break; - else if (element[i].index < maxindex && !ISHYDROGEN(i)) - maxindex = element[i].index; - numelements = i; - -#ifdef SURGEPLUGIN_INIT - SURGEPLUGIN_INIT; -#endif - - argnum = 0; - badargs = verbose = Gswitch = mswitch = FALSE; - uswitch = eswitch = notriples = smiles = FALSE; - oswitch = gzip = alphabetic = Bswitch = FALSE; - tswitch = fswitch = pswitch = bipartite = FALSE; - cswitch = planar = xswitch = Dswitch = FALSE; - Oswitch = FALSE; outlevel = 4; - extra1 = extra2 = formula = NULL; - bad1 = bad2 = bad3 = bad4 = bad5 = bad6 = bad7 = bad8 = bad9 = FALSE; - - for (j = 1; !badargs && j < argc; ++j) - { - arg = argv[j]; - if (arg[0] == '-' && arg[1] != '\0') - { - ++arg; - while (*arg != '\0') - { - sw = *arg++; - if (sw == 'G') - { - if (Gswitch) - gt_abort(">E surge: -G is only allowed once\n"); - Gswitch = TRUE; - extra1 = arg; - break; - } - else if (sw == 'o') - { - if (oswitch) - gt_abort(">E surge : -o is only allowed once\n"); - oswitch = TRUE; - outfilename = arg; - break; - } - else SWRANGE('m',"/",mswitch,res,mod,"surge -m") - else SWINT('O',Oswitch,outlevel,"surge -O") - else SWINT('c',cswitch,maxc,"surge -c") - else SWINT('d',Dswitch,maxd,"surge -d") - else SWBOOLEAN('u',uswitch) - else SWBOOLEAN('v',verbose) - else SWBOOLEAN('T',notriples) - else SWBOOLEAN('S',smiles) - else SWBOOLEAN('z',gzip) - else SWBOOLEAN('A',alphabetic) - else SWBOOLEAN('b',bipartite) - else SWBOOLEAN('P',planar) - else SWBOOLEAN('x',xswitch) - else SWSEQUENCEMIN('B',",",Bswitch,badlist,1,BADLISTS,badlen,"surge -B") - else SWRANGE('e',":-",eswitch,eminval,emaxval,"surge -e") - else SWRANGE('t',":-",tswitch,min3rings,max3rings,"surge -t") - else SWRANGE('f',":-",fswitch,min4rings,max4rings,"surge -f") - else SWRANGE('p',":-",pswitch,min5rings,max5rings,"surge -p") - else SWELEMENT('E',"surge -E") -#ifdef SURGEPLUGIN_SWITCHES - else SURGEPLUGIN_SWITCHES -#endif - else badargs = TRUE; - - if (Bswitch) - { - for (i = 0; i < badlen; ++i) - { - if (badlist[i] < 1 || badlist[i] > BADLISTS) - gt_abort(">E surge : invalid bad list number\n"); - if (badlist[i] == 1) bad1 = TRUE; - else if (badlist[i] == 2) bad2 = TRUE; - else if (badlist[i] == 3) bad3 = TRUE; - else if (badlist[i] == 4) bad4 = TRUE; - else if (badlist[i] == 5) bad5 = TRUE; - else if (badlist[i] == 6) bad6 = TRUE; - else if (badlist[i] == 7) bad7 = TRUE; - else if (badlist[i] == 8) bad8 = TRUE; - else if (badlist[i] == 9) bad9 = TRUE; - /* Don't forget initialization if you add more */ - } - Bswitch = FALSE; - } - } - } - else - { - ++argnum; - if (argnum == 1) formula = arg; - else badargs = TRUE; - } - } - - if (badargs) - { - fprintf(stderr,">E Usage: %s\n",USAGE EXTRAUSAGE); - GETHELP; - exit(1); - } - - if (Oswitch && (outlevel <= 0 || outlevel >= 5)) - gt_abort(">E surge : unknown value for -O\n"); - - if (!cswitch) maxc = 4; - if (!Dswitch) maxd = 4; - -#ifndef ZLIB - if (gzip) - gt_abort(">E surge : -z is only allowed if zlib is compiled in\n"); -#endif - - if (uswitch) gzip = oswitch = alphabetic = FALSE; - - if (alphabetic && smiles) - gt_abort(">E surge : -A and -S are incompatible\n"); - - if (!oswitch || (oswitch && strcmp(outfilename,"-") == 0)) - outfilename = "stdout"; - - if (bad5) bad6 = FALSE; /* bad6 is a subset of bad5 */ - if (notriples) bad1 = FALSE; - if (tswitch && fswitch && max3rings+max4rings <= 1) - bad9 = FALSE; - - needcycles = (bad1 || bad2 || bad3 || bad4 || bad6); - - if (fswitch && max4rings < 6) bad7 = FALSE; - - if (tswitch && max3rings < 3) bad8 = FALSE; - if (fswitch && max4rings < 2) bad8 = FALSE; - if (pswitch && max5rings == 0) bad8 = FALSE; - - if (gzip) - { -#ifdef ZLIB - if (strcmp(outfilename,"stdout") == 0) - gzoutfile = gzdopen(fileno(stdout),"wb"); - else - gzoutfile = gzopen(outfilename,"wb"); - if (!gzoutfile) - gt_abort(">E surge : unable to open compressed stream\n"); - gzbuffer(gzoutfile,1<<16); /* Remove this line if gzbuffer() - is not found; it means you have a old version of zlib. */ -#endif - } - else - { - if (strcmp(outfilename,"stdout") == 0) - outfile = stdout; - else - outfile = fopen(outfilename,"w"); - if (!outfile) - gt_abort(">E surge : can't open output file\n"); - } - - maxbond = (notriples ? 1 : 2); - - if (!Oswitch) outlevel = 4; - - if (mswitch) - { - if (res < 0 || res >= mod) - gt_abort(">E surge : -mres/mod needs 0 <= res < mod\n"); - } - else - { - res = 0; - mod = 1; - } - - if (badargs || argnum != 1) - { - fprintf(stderr,">E Usage: %s\n",USAGE); - GETHELP; - exit(1); - } - - if (eswitch) - { - mine = (int)eminval; - maxe = (int)emaxval; - } - else - { - mine = 0; - maxe = NOLIMIT; - } - - decode_formula(formula,&nv,&mine,&maxe,&maxd,&maxc); - - t1 = CPUTIME; - start_geng(nv,maxd,maxc,mine,maxe,extra1,res,mod); -#ifdef ZLIB - if (gzip) - if (gzclose(gzoutfile) != Z_OK) - gt_abort(">E surge : error on closing compressed stream\n"); -#endif - t2 = CPUTIME; - - if (vgroup) free(vgroup); /* Make valgrind happy */ - if (egroup) free(egroup); - - if (verbose) - { - fprintf(stderr,">G geng made %lld graphs, %lld accepted\n", - gengout,genggood); - if (outlevel > 1) - fprintf(stderr,">V vcolg %lld nontrivial groups, max size" - " %ld, made %lld graphs\n",vcolgnontriv,maxvgroup,vcolgout); - if (outlevel > 2) - fprintf(stderr,">M multig %lld nontrivial groups, max size" - " %ld, made %lld graphs\n",multignontriv,maxegroup,multigout); - } - - if (needcycles) fprintf(stderr,"Max cycles = %d\n",maxcycles); - -#ifdef SURGEPLUGIN_SUMMARY - SURGEPLUGIN_SUMMARY -#endif - - fprintf(stderr,">Z %s %llu -> %llu -> %llu in %.2f sec\n", - (uswitch ? "generated" : "wrote"), - gengout,vcolgout,multigout,t2-t1); - - return 0; -} +/* This is a molecule generator based on geng. + Version 2.0, January 2026. + + Brendan McKay + Christoph Steinbeck + + A typical Unix-style compilation command is: + + gcc -o surge -O3 -DWORDSIZE=32 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ + -march=native -DPREPRUNE=surgepreprune \ + -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ + surge.c geng.c planarity.c nautyW1.a + + But use the makefile if you can. + + You can build-in gzip output using the zlib library (https://zlib.net). + Add -DZLIB to the compilation, and link with the zlib library either + by adding -lz or libz.a . This will activate the -z command to gzip + the output. + + gcc -o surge -O3 -DWORDSIZE=32 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ + -march=native -mtune=native -DPREPRUNE=surgepreprune -DZLIB \ + -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ + surge.c geng.c planarity.c nautyW1.a -lz + + There is a makefile in the package; edit the first few lines. + + This version works best with geng version 3.3 or later. To use + with an earlier version, add -DOLDGENG to the compilation command. + + Changes since version 1.0. + + 1.1: For SDfile output, a single output call is made for each molecule + rather than each line. This gives a small improvement in throughput. + + Counting only (-u) is now the default. + To obtain SDfile output, use -F. + + 2.0: The new -R switch removes all but one molecule from each set of + Kekule structures equivalent under carbon-ring aromaticity. + See the manual for a precise definition. The summary line now + shows the counts before and after the filtering. + + -h# and -h#:# restrict the number of hexagons + -C# and -C#:# restrict the number of carbon 6-rings + +***********************************************************************/ + +#ifdef ZLIB +#define USAGE \ + "[-oFILE] [-z] [-A|-S|-F] [-T] [-e#|-e#:#] [-R] [-d#] [-c#] [-m#/#] formula" +#else +#define USAGE \ + "[-oFILE] [-A|-S|-F] [-T] [-e#|-e#:#] [-R] [-d#] [-c#] [-m#/#] formula" +#endif + +#define HELPUSECMD + +#define HELPTEXT1 \ +"Make chemical graphs from a formula. Version 2.0.\n" \ +" Known elements are C,B,N,P,O,S,H,Cl,F,Br,I at their lowest valences.\n" \ +" Higher valences can be selected using Nx (Nitrogen/5), Sx,Sy (Sulfur 4/6)\n" \ +" Px (Phosphorus/5).\n" \ +"\n" \ +" formula = a formula like C8H6N2\n" \ +"\n" \ +" -u Just count, don't write molecules (default)\n" \ +" -S Output in SMILES format\n" \ +" -F Output in SDfile format\n" \ +" -A Output in alphabetical format\n" \ +" -e# -e#:# Limit the number of distinct non-H bonds\n" \ +" -t# -t#:# Limit the number of cycles of length 3\n" \ +" -f# -f#:# Limit the number of cycles of length 4\n" \ +" -p# -p#:# Limit the number of cycles of length 5\n" \ +" -h# -h#:# Limit the number of cycles of length 6\n" \ +" -C# -C#:# Limit the number of chord-free cycles 6 carbon atoms\n" \ +" -b Only rings of even length (same as only cycles of even length)\n" \ +" -T Disallow triple bonds\n" \ +" -P Require planarity\n" \ +" -d# Maximum degree not counting bond multiplicity or hydrogens (default 4)\n" \ +" -c# Maximum coordination number (default 4). This is the maximum number\n" \ +" of distinct atoms (including H) that an atom can be bonded to\n" \ +" Coordination number > 4 is only allowed if no neighbours are H\n" \ +" -B#,...,# Specify sets of substructures to avoid (details in manual)\n" \ +" 1 = no triple bonds in rings up to length 7\n" \ +" 2 = Bredt's rule for two rings ij with one bond in\n" \ +" common (33, 34, 35, 36, 44, 45)\n" \ +" 3 = Bredt's rule for two rings ij with two bonds in\n" \ +" common (i,j up to 56)\n" \ +" 4 = Bredt's rule for two rings of length 6 sharing three bonds\n" \ +" 5 = no substructures A=A=A (in ring or not)\n" \ +" 6 = no substructures A=A=A in rings up to length 8\n" \ +" For -B5 and -B6, the central atom only has 2 non-H neighbours\n" \ +" 7 = no K_33 or K_24 structure\n" \ +" 8 = none of cone of P4 or K4 with 3-ear\n" \ +" 9 = no atom in more than one ring of length 3 or 4\n" \ +" -R Enable aromaticity detection (filters duplicate Kekule structures)\n" \ +" -v Write more information to stderr\n" \ +" -m#/# Do only a part. The two numbers are res/mod where 0<=res +#ifdef ZLIB +#include "zlib.h" +#endif + +#define DEBUG 0 + +static struct smilesstruct +{ + int item; + int x,y,r; +} smilesskeleton[4*MAXN+6*MAXNE]; +/* Values for the item field */ +#define SM_ATOM 1 /* Atom number x */ +#define SM_BOND 2 /* Bond x,y */ +#define SM_OPEN 3 /* Open ( */ +#define SM_CLOSE 4 /* Close ) */ +#define SM_RING0 5 /* Broken bond x,y for new ring r */ +#define SM_RING1 6 /* End of ring r */ +static int smileslen; + +typedef unsigned long long counter; /* For counters that might overflow */ + +static int hydrogens; +static int nv; /* number of atoms except H */ +static int numtypes; /* different elements except H */ +#define FORMULALEN 50 +static int elementtype[FORMULALEN],elementcount[FORMULALEN]; + /* Not H, decreasing valence */ +static char canonform[2*FORMULALEN]; /* The formula in canonical order */ +static int valencesum; /* Sum of valences for non-H */ +static int maxtype[8]; /* maxtype[d] is maximum index into + elementtype/elementcount for vertices of degree d */ + +/* Used with -A */ +static boolean alphabetic; +static int newlabel[MAXN]; /* New number of atom i */ + +#define BADLISTS 9 /* Number of defined bad lists */ +static boolean bad1; /* Avoid triple edges in rings up to length 7 */ + /* bad1 is turned off if -T is given */ +static boolean bad2; /* Bredt's rule for one common bond */ +static boolean bad3; /* Bredt's rule for two common bonds */ +static boolean bad4; /* Bredt's rule for three common bonds */ +static boolean bad5; /* Avoid =A= even if not in a ring */ +static boolean bad6; /* Avoid =A= in rings up to length 8 */ + /* Note that bad6 is turned off if bad5 is set */ +static boolean bad7; /* Avoid K_{2,4} and K_{3,3} */ +static boolean bad8; /* Avoid cone(P4) and K4 with 3-ear */ + /* bad8 is turned off if -t and -f options make it impossible */ +static boolean bad9; /* No atom on two rings of length 3 or 4 */ + /* bad9 is turned off if -t and -f options make it impossible */ + +static boolean needcoordtest; + +static boolean needrings; /* List of induced cycles needed */ +static int maxrings=0,maxcycles=0; +#define MAXCYCLES 300 +static setword inducedcycle[MAXCYCLES]; /* Only if needrings */ +static int ringcount; +static setword sixring[MAXCYCLES]; /* Only if Cswitch */ +static int sixringcount; + +int GENG_MAIN(int argc, char *argv[]); /* geng main() */ +static int min1,min12,max34,max4; /* bounds on degree counts on geng output */ + +static counter gengout=0, genggood=0; +static counter vcolgnontriv=0,vcolgout=0; +static counter multignontriv=0,multigout=0; +static counter molnum=0; +static long maxvgroup,maxegroup; + +static boolean uswitch; /* suppress output */ +static boolean verbose; /* print more information to stderr */ +static int outlevel; /* 1 = geng only, 2 = geng+vcolg, + 3 = geng+vcolg+multig, 4 = everything */ +static boolean smiles; /* output in SMILES format */ +static boolean SDFoutput; /* output in SDfile format */ +static int maxbond; /* maximum mult -1 of bonds (1 if -t, else 2) */ + +static boolean planar; /* Molecules must be planar */ +static int maxcoord; /* Maximum coordination number allowed */ //UNUSED +static boolean xswitch; /* Undocumented, used for development */ +static boolean Rswitch; /* Enable aromaticity detection */ + +static int carbonindex = -1; /* Index of C */ + +/* Pre-computed aromatic-size cycles + * (computed once per graph after geng if -R is given) */ +#define AROM_MAXCYCLES 1024 +static struct cycle { setword odd,even; } arom_cycles[AROM_MAXCYCLES]; +static int arom_cyclecount; + +/* In the following, the counts are only meaningful if the + corresponding boolean is true. */ +static boolean tswitch; +static long min3cycles,max3cycles; /* number of rings of length 3 */ +static int count3cyc[MAXN+1]; +static boolean fswitch; +static long min4cycles,max4cycles; /* number of cycles of length 4 */ +static int count4cyc[MAXN+1]; +static boolean pswitch; +static long min5cycles,max5cycles; /* number of cycles of length 5 */ +static int count5cyc[MAXN+1]; +static boolean hswitch; +static long min6cycles,max6cycles; /* number of cycles of length 6 */ +static int count6cyc[MAXN+1]; +static boolean Cswitch; +static long minCrings,maxCrings; /* Number of carbon 6-rings */ + +static boolean bipartite; + +/* The following is only used if bad9 is selected */ +static setword cycle34verts[MAXN+1]; /* set of vertices on rings + of length 3 or 4 */ + +static long vgroupsize; /* vcolg group size */ +static size_t vgroupalloc=0; /* Space allocated for vertex groups */ +static int *vgroup=NULL; /* Store of vertex group */ +static long vgroupcount; + +static long egroupsize; /* multig group size */ +static size_t egroupalloc=0; /* Space allocated for edge groups */ +static int *egroup=NULL; /* Store of edge group */ + +typedef struct edge +{ + int x,y; /* The atoms with this bond */ + int maxmult; /* This edge can have multiplicity 1..maxmult+1 */ + int allenemate1,allenemate2; + /* Previous bond that cannot be multiple at the same time as this */ + setword xy; +} edgetype; +static int numedges; +static edgetype edge[MAXNE]; +static int edgenumber[MAXN][MAXN]; +static int deg[MAXN]; /* Simple graph degree */ + +static FILE *outfile; +#ifdef ZLIB +static gzFile gzoutfile; +#endif +static boolean gzip; + +/* Macros for appending to a string using pointer p */ +#define PUTINT(xx) { unsigned long ul = (xx); char *sp,s[15]; \ + if (ul == 0) *(p++) = '0'; \ + else { sp = s; while (ul) { *(sp++) = (ul % 10) + '0'; ul /= 10; } \ + while (sp > s) { *(p++) = *(--sp); } }} +#define SPC *(p++) = ' ' +#define PUTSTR(xx) { char *sp = (xx); \ + while (*sp != '\0') *(p++) = *(sp++); } +#define PUTBND(xx) { int bnd = (xx); if (bnd == 0) *(p++) = '-'; \ + else if (bnd == 1) *(p++) = '='; else *(p++) = '#'; } + +/******************************************************************/ + +#define MAXELEMENTS 30 +static struct elementstruct +{ + char *inputname,*name; + boolean organic; /* Belongs to organic subset */ + int valence; + int lowervalence; /* Next lower valence, or 0 if none */ + int maxcoord; /* Maximum number of distinct neighbours including H */ + int index; /* Used in -T style outputs */ +} element[MAXELEMENTS] = +{ /* The order of listing does not matter. + Other elements can be added to the table by following the same + pattern, up to any limit. All extra elements must be marked + as non-organic. The inputname field must have the form X or Xx + and must be unique. */ + { "C", "C", TRUE, 4,0,4, 0 }, + { "N", "N", TRUE, 3,0,3, 1 }, + { "Nx","N", TRUE, 5,3,4, 10 }, + { "P", "P", TRUE, 3,0,3, 3 }, + { "Px","P", TRUE, 5,3,5, 13 }, + { "B", "B", TRUE, 3,0,3, 9 }, + { "O", "O", TRUE, 2,0,2, 2 }, + { "S", "S", TRUE, 2,0,2, 4 }, + { "Sx","S", TRUE, 4,2,4, 11 }, + { "Sy","S", TRUE, 6,4,6, 12 }, + { "H", "H", FALSE, 1,0,1, 99 }, + { "F", "F", TRUE, 1,0,1, 5 }, + { "Cl","Cl",TRUE, 1,0,1, 6 }, + { "Br","Br",TRUE, 1,0,1, 7 }, + { "I", "I", TRUE, 1,0,1, 8 }, + { "Si","Si",FALSE, 4,0,4, 14 }, + { NULL,NULL,FALSE, 0,0,0, 0 } +}; +static int numelements; /* Actual number in the table */ +static int maxindex; /* Max value of the index field except 99 */ +#define ISHYDROGEN(i) (strcmp(element[i].name,"H") == 0) + +/******************************************************************/ + +static int +elementindex(char *inputname) +/* Index into element[] of element with this element input name, + or -1 if it isn't present. */ +{ + int i; + + for (i = 0; i < numelements; ++i) + if (strcmp(element[i].inputname,inputname) == 0) + break; + + if (i < numelements) return i; + else + { + fprintf(stderr,">E Unknown element %s\n",inputname); + exit(1); + } +} + +static void +addelement(char *inputname, char *name, int valence, int maxcoord) +/* Add an element to element[]. inputname and name must be of the +form A or Aa. If name==NULL then name is the same as inputname. If +maxcoord==0 or maxcoord > valence then maxcoord is the same as valence. */ +{ + int i; + + if (numelements == MAXELEMENTS) + gt_abort(">E increase MAXELEMENTS\n"); + + if (name == NULL) name = inputname; + if (maxcoord == 0 || maxcoord > valence) maxcoord = valence; + + if (inputname == NULL || valence <= 0) + gt_abort(">E invalid parameters in addelement()\n"); + + for (i = 0; i < numelements; ++i) + { + if (strcmp(element[i].inputname,inputname) == 0) + { + fprintf(stderr,">E element %s is already present\n",name); + exit(1); + } + } + + if (!isupper(inputname[0]) || (inputname[1] != '\0' + && (!islower(inputname[1]) || inputname[2] != '\0'))) + gt_abort(">E element names must have form A or Aa\n"); + if (!isupper(name[0]) || (name[1] != '\0' + && (!islower(name[1]) || name[2] != '\0'))) + gt_abort(">E element names must have form A or Aa\n"); + + if (3*maxcoord < valence || valence < 0) + gt_abort(">E impossible maxcoord/valence\n"); + + element[numelements].inputname = strdup(inputname); + element[numelements].name = strdup(name); + element[numelements].valence = valence; + /* lowervalence is only used for SMILES output of + atoms in the organic subset and since those are + in the table already we don't need a value to + be set here. */ + element[numelements].lowervalence = 0; + element[numelements].maxcoord = maxcoord; + element[numelements].organic = FALSE; + element[numelements].index = ++maxindex; + + if (verbose) + fprintf(stderr,"Added element %s input %s, valence=%d maxcoord=%d\n", + element[numelements].name,element[numelements].inputname, + element[numelements].valence,element[numelements].maxcoord); + ++numelements; +} + +#undef HELPTEXT2 +#ifdef SURGEPLUGIN +/* Note that the value of SURGEPLUGIN must be a filename in quotes, so on + the compilation command you will probably need something like + -DSURGEPLUGIN='"myplugin.h"' . */ +#include SURGEPLUGIN +#endif + +#ifndef HELPTEXT2 +#define HELPTEXT2 "" +#endif + +/******************************************************************/ + +#if 0 +static unsigned long long +molcode(int *vcol, int *mult, int n, int ne) +/* This makes an isomorph-invariant code for a molecule. It is intended +for debugging and can't be relied on to distinguish between different +molecules. It only works up to MAXN/2 atoms (but you can compile the +program with WORDSIZE=64 if you really need to). */ +{ + graph g[MAXN],h[MAXN]; + int lab[MAXN],ptn[MAXN],orbits[MAXN],weight[MAXN]; + static DEFAULTOPTIONS_GRAPH(options); + statsblk stats; + setword workspace[2*MAXN]; + int i,x,y,nv; + unsigned long long ans1,ans2; + + nv = 2*n; + if (nv >= MAXN) gt_abort(">E surge : too big for longcode\n"); + + for (i = 0; i < n; ++i) { weight[i] = vcol[i]; weight[n+i] = n+vcol[i]; } + setlabptn(weight,lab,ptn,nv); + + for (i = 0; i < nv; ++i) g[i] = 0; + + for (i = 0; i < n; ++i) { g[i] |= bit[n+i]; g[n+i] |= bit[i]; } + + for (i = 0; i < ne; ++i) + { + x = edge[i].x; + y = edge[i].y; + g[x] |= bit[y]; g[y] |= bit[x]; + if (mult[i] > 0) { g[n+x] |= bit[n+y]; g[n+y] |= bit[n+x]; } + if (mult[i] > 1) + { + g[x] |= bit[y+n]; g[y+n] |= bit[x]; + g[x+n] |= bit[y]; g[y] |= bit[x+n]; + } + } + + options.defaultptn = FALSE; + options.getcanon = TRUE; + + nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,2*MAXN,1,nv,h); + + ans1 = n; + ans2 = ne; + for (i = 0; i < nv; ++i) + { + ans1 = 177*ans1 + (h[i] >> (WORDSIZE-nv)); + ans2 = 1237*ans2 + (h[i] >> (WORDSIZE-nv)); + } + + return ans1^ans2; +} +#endif + +/******************************************************************/ + +void dummy(counter cnt) +{ + fprintf(stderr,">C %llu\n",cnt); +} + +/******************************************************************/ + +static void +printset(FILE *f, setword w, int newline) +/* Write the set to f with optional newline. */ +{ + int i,first; + + if (w == 0) + fprintf(f,"{}"); + else + { + first = 1; + while (w) + { + TAKEBIT(i,w); + if (first) fprintf(f,"{%d",i); + else fprintf(f,",%d",i); + first = 0; + } + fprintf(f,"}"); + } + if (newline) fprintf(f,"\n"); +} + +/******************************************************************/ + +static boolean +isplanar(graph *g, int n) +/* Check if g is planar, assuming g is connected */ +{ + t_ver_sparse_rep V[MAXN]; + t_adjl_sparse_rep A[2*MAXNE+1]; + t_dlcl **dfs_tree,**back_edges,**mult_edges; + t_ver_edge *embed_graph; + int i,j,k,pop,nv,ne,c; + int edge_pos,v,w; + setword ww; + boolean ans; + graph h[MAXN]; + int newlab[MAXN]; + setword queue; + + queue = 0; + ne = 0; + for (i = 0; i < n; ++i) + { + h[i] = g[i]; + pop = POPCOUNT(h[i]); + ne += pop; + if (pop <= 2) queue |= bit[i]; + } + ne /= 2; + nv = n; + + while (queue && ne >= nv+3) + { + TAKEBIT(i,queue); + pop = POPCOUNT(h[i]); + if (pop == 1) /* i--j with deg(i)=1 */ + { + j = FIRSTBITNZ(h[i]); + h[i] = 0; + h[j] &= ~bit[i]; + --nv; + --ne; + queue |= bit[j]; + } + else if (pop == 2) /* j--i--k with deg(i)=2 */ + { + j = FIRSTBITNZ(h[i]); + k = FIRSTBITNZ(h[i] & ~bit[j]); + h[i] = 0; + h[j] &= ~bit[i]; + h[k] &= ~bit[i]; + --nv; + if ((h[j] & bit[k])) + { + ne -= 2; + queue |= (bit[j] | bit[k]); + } + else + { + --ne; + h[j] |= bit[k]; + h[k] |= bit[j]; + } + } + } + + if (ne <= nv + 2) return TRUE; + if (nv == 5 && ne <= 9) return TRUE; + + nv = 0; + for (i = 0; i < n; ++i) if (h[i] != 0) newlab[i] = nv++; + + k = 0; + for (i = 0; i < n; ++i) + if (h[i] != 0) + { + V[newlab[i]].first_edge = k; + ww = h[i]; + while (ww) + { + TAKEBIT(j,ww); + A[k].end_vertex = newlab[j]; + A[k].next = k+1; + ++k; + } + A[k-1].next = NIL; + } + + ne = k/2; + + ans = sparseg_adjl_is_planar(V,nv,A,&c,&dfs_tree,&back_edges, + &mult_edges,&embed_graph,&edge_pos,&v,&w); + + sparseg_dlcl_delete(dfs_tree,nv); + sparseg_dlcl_delete(back_edges,nv); + sparseg_dlcl_delete(mult_edges,nv); + embedg_VES_delete(embed_graph,nv); + + return ans; +} + +/******************************************************************/ + +static void +SMILESoutput(int *vcol, int n, int *hyd, int *mult, int ne) +/* Write molecules in SMILES format */ +{ + char *p,line[20*MAXNE]; + int i,x,y,r,m; + const struct elementstruct *thiselement; + + p = line; + + for (i = 0; i < smileslen; ++i) + { + x = smilesskeleton[i].x; + y = smilesskeleton[i].y; + switch(smilesskeleton[i].item) + { + case SM_ATOM : + thiselement = &element[vcol[x]]; +#if 0 + /* This version seems to meet the OpenSmiles standard, but some + problems with obabel have not been tracked down. */ + if (!thiselement->organic || + thiselement->valence - hyd[x] <= thiselement->lowervalence) +#else + /* This version gives explicit H for atoms in higher valences even + if they are in the organic subset. */ + if (!thiselement->organic || + (thiselement->lowervalence > 0 && hyd[x] > 0)) +#endif + { + *(p++) = '['; + PUTSTR(thiselement->name); + if (hyd[x] > 0) *(p++) = 'H'; + if (hyd[x] > 1) PUTINT(hyd[x]) + *(p++) = ']'; + } + else + PUTSTR(thiselement->name); + break; + case SM_BOND : + m = mult[edgenumber[x][y]]; + if (m == 1) *(p++) = '='; + else if (m == 2) *(p++) = '#'; + break; + case SM_OPEN : + *(p++) = '('; + break; + case SM_CLOSE : + *(p++) = ')'; + break; + case SM_RING0 : + m = mult[edgenumber[x][y]]; + if (m == 1) *(p++) = '='; + else if (m == 2) *(p++) = '#'; + r = smilesskeleton[i].r; + if (r < 10) + *(p++) = '0' + r; + else + { + *(p++) = '%'; + *(p++) = '0' + r/10; + *(p++) = '0' + r%10; + } + break; + case SM_RING1 : + r = smilesskeleton[i].r; + if (r < 10) + *(p++) = '0' + r; + else + { + *(p++) = '%'; + *(p++) = '0' + r/10; + *(p++) = '0' + r%10; + } + break; + } + } + + *(p++) = '\n'; + *p = '\0'; + +#ifdef ZLIB + if (gzip) + { + if (gzputs(gzoutfile,line) < 0) + gt_abort(">E surge : zlib output error\n"); + return; + } +#endif + + if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); +} + +static char SDbuffer[70+70*MAXN+22*MAXNE]; /* Used for SDfile output */ + +/******************************************************************/ + +static void +SDFformat(int *vcol, int n, int *hyd, int *mult, int ne) +/* Write molecules in SDF format */ +{ + int i; + char *p; + + p = SDbuffer; + sprintf(p,"\nSurge 2.0\n\n"); + p += 12; + sprintf(p,"%3d%3d 0 0 0 0 999 V2000\n",n,ne); + p += 40; + + for (i = 0; i < n; ++i) + { + sprintf(p," 0.0000 0.0000 0.0000 %-2s" + " 0 0 0 0 0%3d 0 0 0 0 0 0\n", + element[vcol[i]].name,element[vcol[i]].valence); + p += 70; + } + + for (i = 0; i < ne; ++i) + { + sprintf(p,"%3d%3d%3d 0 0 0 0\n", + edge[i].x+1,edge[i].y+1,mult[i]+1); + p += 22; + } + + sprintf(p,"M END\n$$$$\n"); + +#ifdef ZLIB + if (gzip) + { + gzwrite(gzoutfile,SDbuffer,64+70*n+22*ne); + + return; + } +#endif + + fwrite(SDbuffer,1,64+70*n+22*ne,outfile); +} + +/****************************************************************/ + +static void +multigoutput(int *vcol, int n, int *mult, int ne) +/* Write output equal to the multig -T format. */ +{ + char line[10+60*MAXN],*p; + int i; + + p = line; + PUTINT(n); SPC; PUTINT(ne); + for (i = 0; i < n; ++i) + { + SPC; + PUTINT(element[vcol[i]].index); + } + SPC; + for (i = 0; i < ne; ++i) + { + SPC; PUTINT(edge[i].x); + SPC; PUTINT(edge[i].y); + SPC; PUTINT(mult[i]); + } + *(p++) = '\n'; + *p = '\0'; + +#ifdef ZLIB + if (gzip) + { + if (gzputs(gzoutfile,line) < 0) + gt_abort(">E surge : zlib output error\n"); + return; + } +#endif + + if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); +} + +/****************************************************************/ + +static void +alphabeticoutput(int *vcol, int n, int *mult, int ne) +/* Write alphabetic output */ +{ + char line[10+60*MAXN],*p; + int i,xx,yy; + + p = line; + PUTINT(n); + SPC; + PUTINT(ne); + SPC; + PUTSTR(canonform); + + for (i = 0; i < ne; ++i) + { + SPC; + xx = newlabel[edge[i].x]; + yy = newlabel[edge[i].y]; + if (xx < yy) + { + PUTINT(xx); PUTBND(mult[i]); PUTINT(yy); + } + else + { + PUTINT(yy); PUTBND(mult[i]); PUTINT(xx); + } + } + *(p++) = '\n'; + *p = '\0'; + +#ifdef ZLIB + if (gzip) + { + if (gzputs(gzoutfile,line) < 0) + gt_abort(">E surge : zlib output error\n"); + return; + } +#endif + + if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); +} + +/******************************************************************/ +/* Aromaticity detection functions */ + +static void +pathscan(graph *g, int first, int start, setword body, + setword last, setword thiseven, setword thisodd) +/* Paths in g starting at start, lying within body and + ending in last. {start} and last should be disjoint subsets of body. */ +{ + setword gs,w; + int i,nc,len; + + gs = g[start]; + w = gs & last; + nc = POPCOUNT(w); + len = POPCOUNT(thisodd|thiseven); + if (len % 4 == 0 && nc) + { + if (arom_cyclecount + nc > AROM_MAXCYCLES) + gt_abort(">E surge: increase AROM_MAXCYCLES\n"); + while (w) + { + TAKEBIT(i,w); + arom_cycles[arom_cyclecount].even + = thiseven | bit[edgenumber[first][i]]; + arom_cycles[arom_cyclecount].odd + = thisodd | bit[edgenumber[start][i]]; + ++arom_cyclecount; + } + } + + body &= ~bit[start]; + w = gs & body; + while (w) + { + TAKEBIT(i,w); + if (len % 2 == 0) + pathscan(g,first,i,body,last&~bit[i], + thiseven,thisodd|bit[edgenumber[start][i]]); + else + pathscan(g,first,i,body,last&~bit[i], + thiseven|bit[edgenumber[start][i]],thisodd); + } +} + +static void +arom_find_cycles(graph *g, int n) +{ + setword body,nbhd; + int first,j,d; + + arom_cyclecount = 0; + + body = 0; + for (j = 0; j < n; ++j) + { + d = POPCOUNT(g[j]); + if (d > 1 && d < 4) body |= bit[j]; + } + + while (body) + { + TAKEBIT(first,body); + nbhd = g[first] & body; + while (nbhd) + { + TAKEBIT(j,nbhd); + pathscan(g,first,j,body,nbhd,0,bit[edgenumber[first][j]]); + } + } + + if (arom_cyclecount > maxcycles) maxcycles = arom_cyclecount; +} + +static boolean +largermult(setword singles, setword doubles, int *mult, int ne) +/* Return TRUE if an non-trivial edge group element takes this to + * something greater than mult[]. Otherwise return FALSE. */ +{ + int newmult[MAXNE]; + int i,j,*gp,res; + long kgp; + + res = 0; + for (i = 0; i < ne; ++i) + { + if ((singles&bit[i])) newmult[i] = 0; + else if ((doubles&bit[i])) newmult[i] = 1; + else newmult[i] = mult[i]; + } + + /* The identity is not stored, kgp starts at 1 below */ + for (kgp = 1, gp = egroup; kgp < egroupsize; ++kgp, gp += ne) + { + for (i = 0; i < ne; ++i) + { + j = gp[i]; + if (newmult[j] > mult[i]) return TRUE; + else if (newmult[j] < mult[i]) break; + } + } + return FALSE; +} + +/* Used by -R to list single C-C bonds for aromaticity test. + Bound on number of labelled molecules that can be obtained by + repeated rotation of aromatic cycles. */ +#define MAXAROMATES 512 +static setword singlelist[MAXAROMATES]; +static int arom_parent[MAXAROMATES]; + +static boolean +is_not_maximal(setword singles, setword doubles, int *mult, int ne) +/* Return TRUE iff is not a maximal form. */ +{ + setword allCC,sing,doub,newsing; + int ci,head,tail,i,par; + + allCC = singles | doubles; /* All CC bonds */ + + /* We use a queue to find everything reached by rotating aromatic + * cycles recursively. For each one, we test if it is minimal + * under the edge group. */ + + tail = 0; + head = 1; + singlelist[0] = singles; + arom_parent[0] = -1; + while (tail < head) + { + par = arom_parent[tail]; + sing = singlelist[tail++]; + doub = allCC ^ sing; + for (ci = 0; ci < arom_cyclecount; ++ci) + if (ci != par) + { + if ((!(arom_cycles[ci].odd & ~sing) + && !(arom_cycles[ci].even & ~doub)) + || (!(arom_cycles[ci].odd & ~doub) + && !(arom_cycles[ci].even & ~sing))) + { + /* Now this is an aromatic cycle */ + if ((arom_cycles[ci].odd & ~sing)) + newsing = (sing | arom_cycles[ci].odd) & ~arom_cycles[ci].even; + else + newsing = (sing | arom_cycles[ci].even) & ~arom_cycles[ci].odd; + for (i = 0; i < head; ++i) /* Check if new */ + if (newsing == singlelist[i]) break; + if (i == head) + { + if (newsing < singles) return TRUE; + if (egroupsize > 1 + && largermult(newsing,allCC^newsing,mult,ne)) return TRUE; + if (head == MAXAROMATES) + { + fprintf(stderr,">E surge: increase MAXAROMATES\n"); + exit(1); + } + arom_parent[head] = ci; + singlelist[head++] = newsing; + } + } + } + } + + return FALSE; +} + +static boolean +arom_check_duplicate(int *vcol, int n, int *hyd, int *mult, int ne) +/* Returns TRUE iff this molecule is a duplicate aromatic structure. */ +{ + int ei; + setword singles,doubles; /* C-C and C=C bonds */ + + singles = doubles = 0; + + for (ei = 0; ei < ne; ++ei) + { + if (vcol[edge[ei].x] == carbonindex + && vcol[edge[ei].y] == carbonindex) + { + if (mult[ei] == 0) singles |= bit[ei]; + if (mult[ei] == 1) doubles |= bit[ei]; + } + } + + return is_not_maximal(singles,doubles,mult,ne); +} + +/******************************************************************/ + +static void +gotone(int *vcol, int n, int *hyd, int *mult, int ne, int level) +/* Now we have a completed molecule. + deg[0..n-1] is the simple graph degrees + hyd[0..n-1] is the number of implicit hydrogens +*/ +{ + int i; + + for (i = level; i < ne; ++i) mult[i] = 0; + + if (needcoordtest) + for (i = 0; i < n; ++i) + { + if (deg[i] + hyd[i] > element[vcol[i]].maxcoord) return; + if (deg[i] + hyd[i] > 4 && hyd[i] > 0) return; + } + + ++molnum; + + /* Check for duplicate aromatic structure if -R enabled */ + if (Rswitch && arom_check_duplicate(vcol, n, hyd, mult, ne)) return; + +#ifdef SURGEPLUGIN_STEP3 + SURGEPLUGIN_STEP3 +#endif + + ++multigout; + if (uswitch) return; + + if (outlevel == 3) + multigoutput(vcol,n,mult,ne); + else if (alphabetic) + alphabeticoutput(vcol,n,mult,ne); + else if (smiles) + SMILESoutput(vcol,n,hyd,mult,ne); + else + SDFformat(vcol,n,hyd,mult,ne); +} + +/******************************************************************/ + +static boolean +testemax(int *mult, int ne, int level) +/* Test if edge colouring is maximum wrt group. */ +{ + int *gp,i,j; + long kgp; + + for (i = level; i < ne; ++i) mult[i] = 0; + + /* kgp really starts at 1 on the next line */ + for (kgp = 1, gp = egroup; kgp < egroupsize; ++kgp, gp += ne) + { + for (i = 0; i < ne; ++i) + { + j = gp[i]; + if (mult[j] > mult[i]) return FALSE; + else if (mult[j] < mult[i]) break; + } + } + + return TRUE; +} + +/***************************************************************************/ + +static void +/* Recursive scan for multiplying edges + Version which checks allenemate for -B3 */ +escan2(int level, int needed, int *vcol, int *hyd, int *prev, int n, int *mult, int ne) +{ + int lev,maxlev,k,max,x,y; + + if (needed == 0) + { + if (egroupsize > 1 && !testemax(mult,ne,level)) + return; + gotone(vcol,n,hyd,mult,ne,level); + return; + } + else + { + maxlev = ne + 1 - (needed+maxbond-1)/maxbond; + for (lev = level; lev < maxlev; ++lev) + { + x = edge[lev].x; + y = edge[lev].y; + max = edge[lev].maxmult; + + if (needed < max) max = needed; + if (hyd[x] < max) max = hyd[x]; + if (hyd[y] < max) max = hyd[y]; + if (prev[lev] >= 0 && mult[prev[lev]] < max) max = mult[prev[lev]]; + if (edge[lev].allenemate1 >= 0 && mult[edge[lev].allenemate1] >= 1) + max = 0; + if (edge[lev].allenemate2 >= 0 && mult[edge[lev].allenemate2] >= 1) + max = 0; + + for (k = 1; k <= max; ++k) + { + mult[lev] = k; + hyd[x] -= k; + hyd[y] -= k; + escan2(lev+1,needed-k,vcol,hyd,prev,n,mult,ne); + hyd[x] += k; + hyd[y] += k; + } + + mult[lev] = 0; + } + } + + return; +} + +/***************************************************************************/ + +static void +/* Recursive scan for multiplying edges */ +escan(int level, int needed, int *vcol, int *hyd, + int *prev, int n, int *mult, int ne) +{ + int lev,maxlev,k,max,x,y; + + if (needed == 0) + { + if (egroupsize > 1 && !testemax(mult,ne,level)) + return; + gotone(vcol,n,hyd,mult,ne,level); + return; + } + else + { + maxlev = ne + 1 - (needed+maxbond-1)/maxbond; + for (lev = level; lev < maxlev; ++lev) + { + x = edge[lev].x; + y = edge[lev].y; + max = edge[lev].maxmult; + + if (needed < max) max = needed; + if (hyd[x] < max) max = hyd[x]; + if (hyd[y] < max) max = hyd[y]; + if (prev[lev] >= 0 && mult[prev[lev]] < max) + max = mult[prev[lev]]; + + for (k = 1; k <= max; ++k) + { + mult[lev] = k; + hyd[x] -= k; + hyd[y] -= k; + escan(lev+1,needed-k,vcol,hyd,prev,n,mult,ne); + hyd[x] += k; + hyd[y] += k; + } + + mult[lev] = 0; + } + } + + return; +} + +/******************************************************************/ + +static void +findegroup(int *vcol, int n, int ne) +/* Set egroup to the set of vertex-colour-preserving vgroup elements */ +{ + int *vgp,*egp,i,j,kgp; + + egp = egroup; + + /* kgp really starts at 1 on the next line */ + for (kgp = 1, vgp = vgroup; kgp < vgroupsize; ++kgp, vgp += n) + { + for (i = 0; i < n; ++i) + if (vcol[vgp[i]] != vcol[i]) break; + if (i == n) + for (j = 0; j < ne; ++j) + *(egp++) = edgenumber[vgp[edge[j].x]][vgp[edge[j].y]]; + } + + egroupsize = 1 + (egp - egroup) / ne; +} + +/****************************************************************/ + +static void +colouredges(graph *g, int *vcolindex, int n) +/* This procedure receives graphs from the vcolg phase and + colours the edges. */ +{ + int hyd[MAXN]; /* Remaining degree of vertex */ + int i,j,k,ne; + int mult[MAXNE]; + int prev[MAXNE]; /* If >= 0, earlier point that must have greater colour */ + int needed; /* Extra edges needed */ + int iter[FORMULALEN]; + int vcol[MAXN]; /* index into element[] */ + setword CCbonds; + int Cringcount; + + ne = numedges; + + /* hyd[i] starts with the number of hydrogens needed if all bonds are + single and is reduced as bonds are multiplied */ + + needcoordtest = FALSE; + for (i = 0; i < n; ++i) + { + vcol[i] = elementtype[vcolindex[i]]; + hyd[i] = element[vcol[i]].valence - deg[i]; + if (element[vcol[i]].valence > element[vcol[i]].maxcoord + || (element[vcol[i]].valence > 4 && hyd[i] > 0)) + needcoordtest = TRUE; + } + +#ifdef SURGEPLUGIN_STEP2 + SURGEPLUGIN_STEP2 +#endif + + needed = (valencesum - hydrogens)/2 - ne; /* Extra edges needed */ + + if (ne == 0 && needed > 0) return; + + if (Cswitch) + { + CCbonds = 0; + for (i = 0; i < ne; ++i) + if (vcol[edge[i].x] == carbonindex + && vcol[edge[i].y] == carbonindex) + CCbonds |= bit[i]; + Cringcount = 0; + for (i = 0; i < sixringcount; ++i) + if (!(sixring[i] & ~CCbonds)) ++Cringcount; + if (Cringcount < minCrings || Cringcount > maxCrings) + return; + } + + if (alphabetic) + { + iter[0] = 0; + for (i = 1; i < numtypes; ++i) iter[i] = iter[i-1] + elementcount[i-1]; + + for (i = 0; i < n; ++i) newlabel[i] = iter[vcolindex[i]]++; + } + + if (needed == 0) + { + gotone(vcol,n,hyd,mult,ne,0); + return; + } + + for (i = 0; i < ne; ++i) prev[i] = -1; + + for (i = 0; i < n; ++i) + { + if (deg[i] != 1) continue; + /* Find most recent equivalent j */ + for (j = i; --j >= 0; ) + if (g[i] == g[j] && vcol[j] == vcol[i]) + break; + + if (j >= 0) + { + k = FIRSTBITNZ(g[i]); + prev[edgenumber[i][k]] = edgenumber[j][k]; + } + } + + if (vgroupsize == 1) + egroupsize = 1; + else + { + if (egroupalloc < vgroupsize*ne) + { + if (egroup) free(egroup); + if ((egroup = malloc((vgroupsize+48)*ne*sizeof(int))) == NULL) + gt_abort(">E surge : Can't allocate space for egroup\n"); + egroupalloc = (vgroupsize+48) * ne; + } + findegroup(vcol,n,ne); + if (vgroupsize % egroupsize != 0) gt_abort(">E egroup error\n"); + } + + if (egroupsize == 1 && needed == 1) + { + for (i = 0; i < ne; ++i) mult[i] = 0; + for (i = 0; i < ne; ++i) + if (prev[i] < 0 && edge[i].maxmult >= 1 + && hyd[edge[i].x] > 0 && hyd[edge[i].y] > 0) + { + mult[i] = 1; + --hyd[edge[i].x]; + --hyd[edge[i].y]; + gotone(vcol,n,hyd,mult,ne,ne); + ++hyd[edge[i].x]; + ++hyd[edge[i].y]; + mult[i] = 0; + } + return; + } + + if (egroupsize != 1) ++multignontriv; + if (egroupsize > maxegroup) maxegroup = egroupsize; + + if (bad5 || bad6) escan2(0,needed,vcol,hyd,prev,n,mult,ne); + else escan(0,needed,vcol,hyd,prev,n,mult,ne); +} + +/******************************************************************/ + +static void +vcolgoutput(graph *g, int *vcolindex, int n) +/* Write output equal to the vcolg -T format. */ +{ + char line[10+30*MAXN],*p; + int i,j,ne; + setword w; + + ne = 0; + for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); + ne /= 2; + + p = line; + PUTINT(n); SPC; PUTINT(ne); + for (i = 0; i < n; ++i) + { + SPC; + PUTINT(element[elementtype[vcolindex[i]]].index); + } + SPC; + for (i = 0; i < n; ++i) + { + w = g[i] & BITMASK(i); + while (w) + { + TAKEBIT(j,w); + SPC; PUTINT(i); SPC; PUTINT(j); + } + } + *(p++) = '\n'; + *p = '\0'; + +#ifdef ZLIB + if (gzip) + { + if (gzputs(gzoutfile,line) < 0) + gt_abort(">E surge : zlib output error\n"); + return; + } +#endif + + if (fputs(line,outfile) == EOF) gt_abort(">E surge : output error\n"); +} + +/******************************************************************/ + +static boolean +testvmax(int *colindex, int n) +/* Test if vertex colouring is maximum wrt group. If so, return group. + If not, return a safe level to return to. */ +{ + int *gp,i,j; + long kgp; + + /* kgp really starts at 1 on the next line */ + for (kgp = 1, gp = vgroup; kgp < vgroupsize; ++kgp, gp += n) + { + for (i = 0; i < n; ++i) + { + j = gp[i]; + if (colindex[j] > colindex[i]) return FALSE; + else if (colindex[j] < colindex[i]) break; + } + } + + return TRUE; +} + +/**********************************************************************/ + +static void +vscan(int level, int *colindex, graph *g, int *prev, + int *maxcolindex, int *remain, int n) +/* Recursive vertex colour scan */ +{ + int k,max; + + if (level == n) + { + if (vgroupsize == 1 || testvmax(colindex,n)) + { + ++vcolgout; + if (outlevel == 2) + { + if (!uswitch) vcolgoutput(g,colindex,n); + } + else + colouredges(g,colindex,n); + } + return; + } + + max = maxcolindex[level]; + if (prev[level] >= 0 && colindex[prev[level]] < max) + max = colindex[prev[level]]; + + for (k = 0; k <= max; ++k) + { + if (remain[k] == 0) continue; + colindex[level] = k; + --remain[k]; + vscan(level+1,colindex,g,prev,maxcolindex,remain,n); + ++remain[k]; + } +} + +/**********************************************************************/ + +static void +storevgroup(int *p, int n) +/* Called by allgroup; store full group at vcolg phase */ +{ + int *gp,i; + + if (vgroupcount == 0) + { + vgroupcount = 1; /* Don't store identity */ + return; + } + + gp = vgroup + n * (vgroupcount-1); + for (i = 0; i < n; ++i) gp[i] = p[i]; + + ++vgroupcount; +} + +/**********************************************************************/ + +static void +colourvertices(graph *g, int n) +/* Main procedure for vcolg phase */ +{ + static DEFAULTOPTIONS_GRAPH(options); + statsblk stats; + setword workspace[2*MAXN]; + grouprec *group; + int lab[MAXN],ptn[MAXN],orbits[MAXN]; + int prev[MAXN]; /* If >= 0, earlier point that must have greater colour */ + int weight[MAXN]; + int maxcolindex[MAXN]; /* Max colour index for each vertex */ + int remain[FORMULALEN]; /* Remaining number of colours for each vertex */ + int vcolindex[MAXN]; /* Index into elementtype[] */ + int i,j; + +#ifdef SURGEPLUGIN_STEP1 + SURGEPLUGIN_STEP1 +#endif + + for (i = 0; i < n; ++i) + { + prev[i] = -1; + weight[i] = n*POPCOUNT(g[i]); + } + + for (i = 0; i < n; ++i) + { + if (POPCOUNT(g[i]) != 1) continue; + /* Find most recent equivalent j */ + for (j = i; --j >= 0; ) + if (g[j] == g[i]) break; + + if (j >= 0) + { + prev[i] = j; + weight[i] = weight[j] + 1; + } + } + + options.userautomproc = groupautomproc; + options.userlevelproc = grouplevelproc; + options.defaultptn = FALSE; + + setlabptn(weight,lab,ptn,n); + + nauty(g,lab,ptn,NULL,orbits,&options,&stats,workspace,2*MAXN,1,n,NULL); + + if (stats.grpsize2 > 0 || stats.grpsize1 > 1e7) + gt_abort(">E surge : vgroup size greater than 10^7 encountered\n"); + vgroupsize = stats.grpsize1 + 0.01; + + for (i = 0; i < numtypes; ++i) remain[i] = elementcount[i]; + for (i = 0; i < n; ++i) maxcolindex[i] = maxtype[deg[i]]; + + if (vgroupsize == 1) /* Trivial group */ + { + vscan(0,vcolindex,g,prev,maxcolindex,remain,n); + return; + } + + ++vcolgnontriv; + if (vgroupsize > maxvgroup) maxvgroup = vgroupsize; + + group = groupptr(FALSE); + makecosetreps(group); + + if (vgroupalloc < (vgroupsize-1) * n) + { + if (vgroup) free(vgroup); + if ((vgroup = malloc((vgroupsize+48)*n*sizeof(int))) == NULL) + gt_abort(">E surge : Can't allocate space for vgroup\n"); + vgroupalloc = (vgroupsize+48) * n; + } + + vgroupcount = 0; + allgroup(group,storevgroup); + + if (vgroupcount != vgroupsize) gt_abort(">E surge : vgroup error\n"); + + /* Check the logic of the next section. What about maxdeg? */ + if (numtypes == 1) + { + for (i = 0; i < n; ++i) vcolindex[i] = 0; + ++vcolgout; + colouredges(g,vcolindex,n); + return; + } + + j = n; /* Can choose a better orbit? */ + for (i = 0; i < n; ++i) + if (orbits[i] < i && orbits[i] < j) j = orbits[i]; + for (i = j + 1; i < n; ++i) + if (orbits[i] == j) prev[i] = j; + + vscan(0,vcolindex,g,prev,maxcolindex,remain,n); +} + +/******************************************************************/ + +extern int geng_maxe; + +int +surgepreprune(graph *g, int n, int maxn) +/* This function is called by the PREPRUNE service of geng. + It does -B9 which is more efficient here. If OLDGENG is + defined, it also speeds up the generation of connected + graphs. (For new geng, that function is built in.) */ +{ + setword notvisited,queue; + setword c34,w,ww,cyc; + int ne,nc,i; + + if (bad9) /* cycle34verts */ + { + if (n <= 2) + cycle34verts[n] = 0; + else + { + c34 = cycle34verts[n-1]; + if (!tswitch) + { + w = g[n-1]; + while (w) + { + TAKEBIT(i,w); + ww = g[i] & w; + if (POPCOUNT(ww) > 1) return 1; + if ((ww)) + { + cyc = bit[n-1] | bit[i] | ww; + if ((c34 & cyc)) return 1; + c34 |= cyc; + } + } + } + if (!fswitch) + { + for (i = n-1; --i >= 0;) + { + w = g[i] & g[n-1]; + if (POPCOUNT(w) > 2) return 1; + if (POPCOUNT(w) == 2) + { + cyc = bit[n-1] | bit[i] | w; + if ((c34 & cyc)) return 1; + c34 |= cyc; + } + } + } + cycle34verts[n] = c34; + } + } + +#ifdef OLDGENG + if (n == maxn || geng_maxe - maxn >= 5) return 0; + + ne = 0; + for (i = 0; i < n; ++i) ne += POPCOUNT(g[i]); + ne /= 2; + + nc = 0; + notvisited = ALLMASK(n); + + while (notvisited) + { + ++nc; + queue = SWHIBIT(notvisited); + notvisited &= ~queue; + while (queue) + { + TAKEBIT(i,queue); + notvisited &= ~bit[i]; + queue |= g[i] & notvisited; + } + } + + if (ne - n + nc > geng_maxe - maxn + 1) return 1; +#endif + + return 0; +} + +int +surgeprune(graph *g, int n, int nmax) +/* This is a procedure that geng will call at each level +using the PRUNE service. +The options -t, -f, -p, -h, -B7,8 are implemented here by +incrementally updating the required counts. */ +{ + setword w,ax,ay,gx,gxy,gxya,gi,gj,gn,gxn,gyn,bitxyn; + int i,j,x,y,k,a,b,extra; + int i1,i2,i3,i4,d1,d2,d3,d4,xy,xn,yn,xyn; + int v[MAXN]; + + if (tswitch) + { + if (n <= 2) + count3cyc[n] = 0; + else + { + extra = 0; + w = g[n-1]; + while (w) + { + TAKEBIT(i,w); + extra += POPCOUNT(g[i]&w); + } + count3cyc[n] = count3cyc[n-1] + extra; + if (count3cyc[n] > max3cycles) return 1; + } + if (n == nmax && count3cyc[n] < min3cycles) + return 1; + } + + if (fswitch) + { + if (n <= 3) + count4cyc[n] = 0; + else + { + extra = 0; + for (i = 0; i < n-1; ++i) + { + k = POPCOUNT(g[i]&g[n-1]); + extra += k*k - k; + } + count4cyc[n] = count4cyc[n-1] + extra/2; + if (count4cyc[n] > max4cycles) return 1; + } + if (n == nmax && count4cyc[n] < min4cycles) + return 1; + } + + if (pswitch) + { + if (n <= 4) + count5cyc[n] = 0; + else + { + extra = 0; + for (y = 1; y < n-1; ++y) + { + w = g[y] & ~BITMASK(y); + while (w) + { + TAKEBIT(x,w); + ax = (g[x] & g[n-1]) & ~bit[y]; + ay = (g[y] & g[n-1]) & ~bit[x]; + extra += POPCOUNT(ax)*POPCOUNT(ay) - POPCOUNT(ax&ay); + } + } + count5cyc[n] = count5cyc[n-1] + extra; + if (count5cyc[n] > max5cycles) return 1; + } + if (n == nmax && count5cyc[n] < min5cycles) + return 1; + } + + if (hswitch) + { + if (n <= 4) + count6cyc[n] = 0; + else + { + extra = 0; + gn = g[n-1]; + for (y = 1; y < n-1; ++y) + if ((g[y] & gn)) + { + for (x = 0; x < y; ++x) + if ((g[x] & g[y]) && (g[x] & gn)) + { + bitxyn = bit[x] | bit[y] | bit[n-1]; + gxy = g[x] & g[y] & ~bitxyn; + gxn = g[x] & gn & ~bitxyn; + gyn = g[y] & gn & ~bitxyn; + xy = POPCOUNT(gxy); + xn = POPCOUNT(gxn); + yn = POPCOUNT(gyn); + xyn = POPCOUNT(gxy&gxn&gyn); + extra += xy*xn*yn - xyn*(xy+xn+yn-2); + } + } + count6cyc[n] = count6cyc[n-1] + extra; + if (count6cyc[n] > max6cycles) return 1; + } + if (n == nmax && count6cyc[n] < min6cycles) + return 1; + } + + if (bad7 && n >= 6) + { + /* For K_33 we can assume that vertex n-1 is included */ + for (x = n-1; --x >= 1;) + if (POPCOUNT(g[x]&g[n-1]) >= 3) + { + for (y = x; --y >= 0;) + if (POPCOUNT(g[y]&g[x]&g[n-1]) >= 3) return 1; + } + + /* But for K_24 we can't */ + for (x = n; --x >= 1;) + if (POPCOUNT(g[x]) >= 4) + { + for (y = x; --y >= 0;) + if (POPCOUNT(g[x]&g[y]) >= 4) return 1; + } + } + + if (bad8) + { + /* cone of P4 */ + if (n >= 5) + { + for (x = n; --x >= 0;) + if (POPCOUNT(g[x]) == 4) + { + /* Think of a better way to do this */ + w = gx = g[x]; + TAKEBIT(i1,w); + TAKEBIT(i2,w); + TAKEBIT(i3,w); + i4 = FIRSTBITNZ(w); + d1 = POPCOUNT(g[i1]&gx); + d2 = POPCOUNT(g[i2]&gx); + d3 = POPCOUNT(g[i3]&gx); + d4 = POPCOUNT(g[i4]&gx); + if (d1 > 0 && d2 > 0 && d3 > 0 && d4 > 0 + && ((d1 >= 2)+(d2 >= 2)+(d3 >= 2)+(d4 >= 2)) >= 2) + return 1; + } + else if (POPCOUNT(g[x]) > 4) + { + w = gx = g[x]; + i = 0; + while (w) + { + TAKEBIT(y,w); + if (POPCOUNT(g[y]&gx) >= 2) v[i++] = y; + } + for (--i; i >= 1; --i) + for (j = 0; j < i; ++j) + if ((g[v[i]] & bit[v[j]]) && + POPCOUNT((g[v[i]]|g[v[j]])&gx) >= 4) + return 1; + } + } + + /* K4 with a path of 3 edges between two of its vertices */ + + if (n >= 6) + { + for (x = n; --x >= 1;) + if (POPCOUNT(g[x]) >= 4) + { + for (y = x; --y >= 0;) + if (POPCOUNT(g[y]) >= 4 && (g[y]&bit[x])) + { + gxy = g[x] & g[y]; + while (gxy) + { + TAKEBIT(a,gxy); + gxya = gxy & g[a]; + while (gxya) + { + TAKEBIT(b,gxya); + w = bit[x] | bit[y] | bit[a] | bit[b]; + gi = g[x] & ~w; + gj = g[y] & ~w; + while (gi) + { + TAKEBIT(i,gi); + if ((g[i] & gj)) return 1; + } + } + } + } + } + } + } + +#ifdef SURGEPLUGIN_STEP0 + SURGEPLUGIN_STEP0 +#endif + + return 0; +} + +/******************************************************************/ + +static void +smilesdfs(graph *g, setword *seen, int v, int par, graph *back, + struct smilesstruct *smilestemp, int *len) +/* Recursive DFS to collect SMILES information */ +{ + setword gv,w; + int k; + boolean first; + + gv = g[v]; + first = TRUE; + *seen |= bit[v]; + + while (gv) + { + TAKEBIT(k,gv); + if ((*seen & bit[k])) + { + if (k != par) + { + back[v] |= bit[k]; + back[k] |= bit[v]; + } + } + else + { + if (first) + { + if (POPCOUNT(g[k]) == 1) + { + if ((w = (gv & ~*seen))) /* really = */ + { + gv |= bit[k]; + k = FIRSTBITNZ(w); + gv &= ~bit[k]; + } + } + smilesdfs(g,seen,k,v,back,smilestemp,len); + first = FALSE; + } + else + { + smilestemp[(*len)++].item = SM_CLOSE; + smilesdfs(g,seen,k,v,back,smilestemp,len); + smilestemp[(*len)++].item = SM_OPEN; + } + } + } + + smilestemp[*len].item = SM_ATOM; + smilestemp[*len].x = v; + ++*len; + if (par >= 0) + { + smilestemp[*len].item = SM_BOND; + smilestemp[*len].x = par; + smilestemp[*len].y = v; + ++*len; + } +} + +static void +makesmilesskeleton(graph *g, int n) +/* Make a skeleton SMILES structure for use in SMILESoutput */ +{ + struct smilesstruct smilestemp[4*MAXN+6*MAXNE]; + graph back[MAXN],ring[MAXN]; + setword w,seen; + int len,ringnumber; + int i,j,v; + + for (v = n; --v >= 0; ) if (POPCOUNT(g[v]) == 1) break; + if (v < 0) v = n-1; + + len = 0; + seen = 0; + for (i = 0; i < n; ++i) back[i] = 0; + for (i = 0; i < n; ++i) ring[i] = 0; + + smilesdfs(g,&seen,v,-1,back,smilestemp,&len); + + smileslen = 0; + ringnumber = 0; + for (i = len; --i >= 0; ) + { + smilesskeleton[smileslen++] = smilestemp[i]; + if (smilestemp[i].item == SM_ATOM) + { + v = smilestemp[i].x; + w = ring[v]; + while (w) + { + TAKEBIT(j,w); + smilesskeleton[smileslen].item = SM_RING1; + smilesskeleton[smileslen].r = j+1; + ++smileslen; + } + w = back[v]; + while (w) + { + TAKEBIT(j,w); + ++ringnumber; + smilesskeleton[smileslen].item = SM_RING0; + smilesskeleton[smileslen].x = v; + smilesskeleton[smileslen].y = j; + smilesskeleton[smileslen].r = ringnumber; + ++smileslen; + ring[j] |= bit[ringnumber-1]; + back[j] &= ~bit[v]; + } + } + } + +#ifdef SMILESSKELETON + /* This will print the SMILES skeleton to stdout */ + + fprintf(stdout,"len=%d",smileslen); + for (i = 0; i < smileslen; ++i) + { + switch (smilesskeleton[i].item) + { + case SM_ATOM : + fprintf(stdout," %d",smilesskeleton[i].x); + break; + case SM_BOND : + fprintf(stdout," %d-%d",smilesskeleton[i].x,smilesskeleton[i].y); + break; + case SM_OPEN : + fprintf(stdout," ("); + break; + case SM_CLOSE : + fprintf(stdout," )"); + break; + case SM_RING0 : + fprintf(stdout," R%d:%d-%d",smilesskeleton[i].r, + smilesskeleton[i].x,smilesskeleton[i].y); + break; + case SM_RING1 : + fprintf(stdout," R%d",smilesskeleton[i].r); + } + } + fprintf(stdout,"\n"); +#endif +} + +/******************************************************************/ + +static void +inducedpaths(graph *g, int origin, int start, setword body, + setword last, setword path) +/* Trace induced paths in g starting at start, extra vertices within + * body and ending in last. This is used to find induced cycles. + * {start}, body and last should be disjoint. */ +{ + setword gs,w; + int i; + + gs = g[start]; + + w = gs & last; + while (w) + { + TAKEBIT(i,w); + if (ringcount == MAXCYCLES) gt_abort(">E Increase MAXCYCLES\n"); + inducedcycle[ringcount++] + = path | bit[edgenumber[start][i]] | bit[edgenumber[origin][i]]; + } + + w = gs & body; + while (w) + { + TAKEBIT(i,w); + inducedpaths(g,origin,i,body&~gs,last&~bit[i]&~gs, + path|bit[edgenumber[start][i]]); + } +} + +static void +findinducedcycles(graph *g, int n) +/* Find all the induced cycles (called rings in the manual) */ +{ + setword body,last,cni; + int i,j; + + body = 0; + for (i = 0; i < n; ++i) + if (POPCOUNT(g[i]) > 1) body |= bit[i]; + + ringcount = 0; + + while (body) + { + TAKEBIT(i,body); + last = g[i] & body; + cni = g[i] | bit[i]; + while (last) + { + TAKEBIT(j,last); + inducedpaths(g,i,j,body&~cni,last,bit[edgenumber[i][j]]); + } + } + + if (ringcount > maxrings) maxrings = ringcount; + + if (verbose) + { + setword cyc; int i,j; + fprintf(stderr, "SURGE rings (%d):", ringcount); + for (i = 0; i < ringcount; ++i) + { + fprintf(stderr, " ["); + cyc = inducedcycle[i]; + int first = 1; + while (cyc) + { + TAKEBIT(j,cyc); + if (!first) fprintf(stderr, ","); + fprintf(stderr, "%d-%d", edge[j].x, edge[j].y); + first = 0; + } + fprintf(stderr, "](%d)", POPCOUNT(inducedcycle[i])); + } + fprintf(stderr, "\n"); + } +} + +static void +find6rings(void) +{ + int i; + + sixringcount = 0; + for (i = 0; i < ringcount; ++i) + if (POPCOUNT(inducedcycle[i]) == 6) + sixring[sixringcount++] = inducedcycle[i]; +} + +/******************************************************************/ + +void +surgeproc(FILE *outfile, graph *gin, int n) +/* This is called by geng for each graph. */ +{ + int i,j,k,d,n1,n12,n34,n4,ne; + int isize,jsize; + graph g[MAXN]; + setword w,wxy,ww,pw,cyc,cycle8; + int x,y,e1,e2,e3; + + n1 = n12 = n34 = n4 = 0; + + ++gengout; + + for (i = 0; i < n; ++i) + { + d = POPCOUNT(gin[i]); /* deg[i] is not defined yet */ + if (d == 1) { ++n1; ++n12; } + else if (d == 2) ++n12; + else if (d == 3) ++n34; + else { ++n34; ++n4; } + } + + if (n > 1 && (n1 < min1 || n12 < min12 || n34 > max34 || n4 > max4)) + return; + + if (planar && !isplanar(gin,n)) return; + + ++genggood; + + /* Reverse to put higher degrees first */ + + for (i = 0; i < n; ++i) + { + w = gin[n-i-1]; + pw = 0; + while (w) + { + TAKEBIT(j,w); + pw |= bit[n-j-1]; + } + g[i] = pw; + } + + /* Make the edge list with default parameters */ + + ne = 0; + for (i = 0; i < n; ++i) + { + deg[i] = POPCOUNT(g[i]); + w = g[i] & BITMASK(i); + while (w) + { + TAKEBIT(j,w); + edge[ne].x = i; + edge[ne].y = j; + edge[ne].xy = bit[i] | bit[j]; + edge[ne].maxmult = maxbond; + edge[ne].allenemate1 = edge[ne].allenemate2 = -1; + edgenumber[i][j] = edgenumber[j][i] = ne; + ++ne; + } + } + numedges = ne; + + if (ne > WORDSIZE && (needrings || Rswitch)) + if (ne > WORDSIZE) + gt_abort(">E surge : too many edges for badlists or -R\n"); + + if (needrings) findinducedcycles(g,n); + if (Cswitch) + { + find6rings(); + if (sixringcount < minCrings) return; + } + if (Rswitch) arom_find_cycles(g,n); + + if (bad1) /* no triple bonds in rings smaller than 7 */ + { + for (i = 0; i < ringcount; ++i) + { + cyc = inducedcycle[i]; + if (POPCOUNT(cyc) <= 7) + while (cyc) + { + TAKEBIT(j,cyc); + if (edge[j].maxmult == 2) edge[j].maxmult = 1; + } + } + } + + if (bad2) /* Bredt's rule for one common bond */ + { + for (i = 0; i < ringcount-1; ++i) + { + isize = POPCOUNT(inducedcycle[i]); + if (isize > 6) continue; + for (j = i+1; j < ringcount; ++j) + { + jsize = POPCOUNT(inducedcycle[j]); + if (jsize > 6) continue; + + w = inducedcycle[i] & inducedcycle[j]; + if (POPCOUNT(w) != 1) continue; + + if (isize*jsize <= 15) + edge[FIRSTBITNZ(w)].maxmult = 0; + + if (isize+jsize <= 9) + { + wxy = edge[FIRSTBITNZ(w)].xy; + ww = (inducedcycle[i] | inducedcycle[j]) & ~w; + while (ww) + { + TAKEBIT(k,ww); + if ((edge[k].xy & wxy)) edge[k].maxmult = 0; + } + } + } + } + } + + if (bad3) /* Bredt's rule for two common bonds */ + { + for (i = 0; i < ringcount-1; ++i) + { + isize = POPCOUNT(inducedcycle[i]); + if (isize == 3 || isize > 6) continue; + + for (j = i+1; j < ringcount; ++j) + { + jsize = POPCOUNT(inducedcycle[j]); + if (jsize == 3 || jsize > 6 || isize+jsize == 12) continue; + + w = inducedcycle[i] & inducedcycle[j]; + if (POPCOUNT(w) != 2) continue; + + ww = w; + TAKEBIT(k,ww); + edge[k].maxmult = 0; + edge[FIRSTBITNZ(ww)].maxmult = 0; + wxy = edge[k].xy ^ edge[FIRSTBITNZ(ww)].xy; + + ww = (inducedcycle[i] | inducedcycle[j]) & ~w; + while (ww) + { + TAKEBIT(k,ww); + if ((edge[k].xy & wxy)) edge[k].maxmult = 0; + } + } + } + } + + if (bad4) /* Bredt's rule for two hexagons with 3 bonds in common */ + { + for (i = 0; i < ringcount-1; ++i) + { + isize = POPCOUNT(inducedcycle[i]); + if (isize != 6) continue; + + for (j = i+1; j < ringcount; ++j) + { + jsize = POPCOUNT(inducedcycle[j]); + if (jsize != 6) continue; + + w = inducedcycle[i] & inducedcycle[j]; + if (POPCOUNT(w) != 3) continue; + + ww = inducedcycle[i] | inducedcycle[j]; + + TAKEBIT(e1,w); + TAKEBIT(e2,w); + e3 = FIRSTBITNZ(w); + + wxy = edge[e1].xy ^ edge[e2].xy ^ edge[e3].xy; + while (ww) + { + TAKEBIT(k,ww); + if ((edge[k].xy & wxy)) edge[k].maxmult = 0; + } + } + } + } + + if (bad5) /* No A=A=A, whether in ring or not */ + { + for (i = 0; i < n; ++i) + if (deg[i] == 2) + { + x = FIRSTBITNZ(g[i]); + y = FIRSTBITNZ(g[i]&~bit[x]); + e1 = edgenumber[i][x]; + e2 = edgenumber[i][y]; + if (edge[e2].allenemate1 < 0) + { + if (e1 < e2) edge[e2].allenemate1 = e1; + else edge[e1].allenemate1 = e2; + } + else + { + if (e1 < e2) edge[e2].allenemate2 = e1; + else edge[e1].allenemate2 = e2; + } + } + } + + if (bad6) /* No A=A=A in rings up to length 8 */ + { + cycle8 = 0; + for (i = 0; i < ringcount; ++i) + if (POPCOUNT(inducedcycle[i]) <= 8) cycle8 |= inducedcycle[i]; + + for (i = 0; i < n; ++i) + if (deg[i] == 2) + { + x = FIRSTBITNZ(g[i]); + y = FIRSTBITNZ(g[i]&~bit[x]); + e1 = edgenumber[i][x]; + if (!(bit[e1] & cycle8)) continue; + e2 = edgenumber[i][y]; + if (edge[e2].allenemate1 < 0) + { + if (e1 < e2) edge[e2].allenemate1 = e1; + else edge[e1].allenemate1 = e2; + } + else + { + if (e1 < e2) edge[e2].allenemate2 = e1; + else edge[e1].allenemate2 = e2; + } + } + } + + if (outlevel == 1) + { + if (!uswitch) writeg6(outfile,g,1,n); + return; + } + + /* Make a SMILES skeleton structure for later use */ + if (smiles) makesmilesskeleton(g,n); + + colourvertices(g,n); +} + +/****************************************************************/ + +static void +decode_formula(char *formula, int *nv, + int *mine, int *maxe, int *maxd, int *maxc) +/* Parse the input formula. The number of hydrogens goes to hydrogens. + The other distinct elements go to elementlist[0..numtypes-1] and + elementcount[0..numtypes-1]. + *mine and *maxe have an edge range from -e and are updated. + *mind and *maxc come from -d and -c and are updated. + *nv gets the number of non-H atoms. +*/ +{ + int i,j,d,mult,val,cnt,totval,dbe,forced; + int maxvcoord,localmine,localmaxe,xi,yi; + char *s1,*s2,*p; + int count[FORMULALEN]; + + if (numelements > FORMULALEN) + gt_abort(">E surge : increase FORMULALEN\n"); + + /* First we fill in count[*], which is parallel to element[*] */ + + for (i = 0; i < numelements; ++i) count[i] = 0; + + for (s1 = formula; *s1 != '\0'; s1 = s2) + { + if (!isupper(*s1)) gt_abort(">E surge : unknown element name\n"); + for (s2 = s1+1; islower(*s2); ++s2) {} + for (i = 0; i < numelements; ++i) + { + for (j = 0; element[i].inputname[j] != '\0' + && s1+j != s2 && element[i].inputname[j] == s1[j]; ++j) {} + if (element[i].inputname[j] == '\0' && s1+j == s2) break; + } + if (i == numelements) gt_abort(">E surge : unknown element name\n"); + s1 = s2; + if (!isdigit(*s2)) + ++count[i]; + else + { + mult = *s2 - '0'; + for (s2 = s1+1; isdigit(*s2); ++s2) mult = 10*mult+(*s2-'0'); + count[i] += mult; + } + } + + /* Next we collect elements actually used into elementtype[0..numtypes-1] + and elementcount[0..numtypes-1], except for H which we just count. */ + + numtypes = hydrogens = 0; + for (i = 0; i < numelements; ++i) + { + cnt = count[i]; + if (cnt > 0) + { + if (ISHYDROGEN(i)) + hydrogens = cnt; + else + { + elementtype[numtypes] = i; + elementcount[numtypes] = cnt; + ++numtypes; + } + } + } + + /* Next we adjust *maxd and *maxc, as well as the maxcoord + fields of elements */ + + maxvcoord = 0; + for (i = 0; i < numtypes; ++i) + if (element[elementtype[i]].maxcoord > maxvcoord) + maxvcoord = element[elementtype[i]].maxcoord; + if (maxvcoord < *maxc) + *maxc = maxvcoord; + else if (maxvcoord > *maxc) + for (i = 0; i < numtypes; ++i) + if (element[elementtype[i]].maxcoord > *maxc) + element[elementtype[i]].maxcoord = *maxc; + if (*maxd > *maxc) *maxd = *maxc; + + /* Next we find some bounds on the number of vertices + with various simple degrees. */ + + min1 = min12 = max34 = max4 = 0; + for (i = 0; i < numelements; ++i) + { + if (!ISHYDROGEN(i)) + { + cnt = count[i]; + val = element[i].maxcoord; + if (val <= 1) min1 += cnt; // Check logic + if (val <= 2) min12 += cnt; + if (val >= 3) max34 += cnt; + if (val >= 4) max4 += cnt; + // Could add max5, could use both bounds everywhere + } + } + + /* Now sort by decreasing maximum coordination number */ + + for (i = 1; i < numtypes; ++i) /* really 1 */ + { + xi = elementtype[i]; + yi = elementcount[i]; + for (j = i; element[elementtype[j-1]].maxcoord < element[xi].maxcoord; ) + { + elementtype[j] = elementtype[j-1]; + elementcount[j] = elementcount[j-1]; + if (--j < 1) break; + } + elementtype[j] = xi; + elementcount[j] = yi; + } + + /* Make "canonical" molecule name (used for -A output) */ + + p = canonform; + for (i = 0; i < numtypes; ++i) + { + PUTSTR(element[elementtype[i]].inputname); + if (elementcount[i] > 1) PUTINT(elementcount[i]); + } + if (hydrogens > 0) PUTSTR("H"); + if (hydrogens > 1) PUTINT(hydrogens); + *p = '\0'; + + /* Calculate *nv, totalval, forced which all exclude H */ + + *nv = forced = 0; + totval = hydrogens; + for (i = 0; i < numtypes; ++i) + { + j = elementtype[i]; + cnt = elementcount[i]; + *nv += cnt; + totval += cnt * element[j].valence; + if (element[j].valence > element[j].maxcoord) + forced += element[j].valence - element[j].maxcoord; + } + forced = (forced+1) / 2; + + if ((totval & 1)) gt_abort(">E surge : impossible parity\n"); + dbe = totval / 2 - (*nv + hydrogens - 1) - forced; + if (dbe < 0) gt_abort(">E surge : negative DBE\n"); + if (*nv > MAXN) gt_abort(">E surge : too many non-hydrogen atoms\n"); + if (*nv == 0) gt_abort(">E surge : only hydrogen\n"); + valencesum = totval - hydrogens; + + localmine = *nv - 1; + localmaxe = localmine + dbe; + if (localmaxe > *nv * (*nv-1) / 2) localmaxe = *nv * (*nv-1) / 2; + + if (localmine > *mine) *mine = localmine; + if (localmaxe < *maxe) *maxe = localmaxe; + if (*mine > *maxe) gt_abort(">E surge : edge range is empty\n"); + + fprintf(stderr,"%s ",canonform); + fprintf(stderr,"H=%d",hydrogens); + for (i = 0; i < numtypes; ++i) + fprintf(stderr," %s=%d",element[elementtype[i]].inputname,elementcount[i]); + + fprintf(stderr," nv=%d edges=%d-%d DBE=%d maxd=%d maxc=%d\n", + *nv,*mine,*maxe,dbe,*maxd,*maxc); + if (*maxe > MAXNE) gt_abort(">E surge : too many edges\n"); + + for (d = 1; d <= *maxd; ++d) + { + for (i = 0; i < numtypes; ++i) + { + val = element[elementtype[i]].maxcoord; + if (d <= val) maxtype[d] = i; + } + } +} + +/****************************************************************/ + +static void +start_geng(int n, int maxd, int maxc, + int mine, int maxe, char *extra1, long res, long mod) +/* start geng with arguments, extra1 before n, extra2 after n */ +{ + int i,geng_argc,mind; + char *geng_argv[20]; + char arga[30],argb[30]; + char resmod[40]; + char gengargs[80]; + char edgecount[40]; + + mind = 1; + if (hydrogens == 0) + { + for (i = 0; i < numtypes; ++i) + if (element[elementtype[i]].valence < 4) break; + if (i == numtypes) mind = 2; + } + + if (n == 1) mind = 0; + + sprintf(arga,"-qcd%dD%d",mind,maxd); + sprintf(argb,"%d",n); + sprintf(edgecount,"%d:%d",mine,maxe); + + geng_argv[0] = "geng_surge"; + geng_argv[1] = arga; + geng_argc = 2; + + if (tswitch && max3cycles == 0) + { + geng_argv[geng_argc++] = "-t"; + tswitch = FALSE; + } + + if (fswitch && max4cycles == 0) + { + geng_argv[geng_argc++] = "-f"; + fswitch = FALSE; + } + + if (bipartite) geng_argv[geng_argc++] = "-b"; + + if (extra1) + { + snprintf(gengargs,78,"-%s",extra1); + geng_argv[geng_argc++] = gengargs; + } + geng_argv[geng_argc++] = argb; + geng_argv[geng_argc++] = edgecount; + if (mod > 1) + { + sprintf(resmod,"%ld/%ld",res,mod); + geng_argv[geng_argc++] = resmod; + } + geng_argv[geng_argc] = NULL; + + if (verbose) + { + fprintf(stderr,">geng"); + for (i = 1; geng_argv[i] != NULL; ++i) + fprintf(stderr," %s",geng_argv[i]); + fprintf(stderr,"\n"); + } + + geng_main(geng_argc,geng_argv); +} + +/*******************************************************************/ + +static void +processEswitch(char **ps, char *id) +/* Process -E starting at *ps = the character after E, and update *ps. + The value has the form [][], + where and are either or and + and are single digits. Inputname comes first. */ +{ + char inputname[3],name[3]; + int valence,maxcoord; + char *s; + int state; + + s = *ps; + state = 0; + + while (state < 5) + { + switch (state) + { + case 0: + if (!isupper(*s)) + { + state = 6; + break; + } + inputname[0] = *s++; + if (islower(*s)) + { + inputname[1] = *s++; + inputname[2] = '\0'; + } + else + inputname[1] = '\0'; + state = 1; + break; + case 1: + if (isupper(*s)) + state = 2; + else + { + name[0] = inputname[0]; + name[1] = inputname[1]; + name[2] = inputname[2]; + state = 3; + } + break; + case 2: + name[0] = *s++; + if (islower(*s)) + { + name[1] = *s++; + name[2] = '\0'; + } + else + name[1] = '\0'; + state = 3; + break; + case 3: + if (!isdigit(*s)) + { + state = 7; + break; + } + valence = *s++ - '0'; + if (!isdigit(*s)) + maxcoord = valence; + else + maxcoord = *s++ - '0'; + state = 5; + break; + } + } + + if (state == 6) + { + fprintf(stderr,">E %s : bad element name\n",id); + exit(1); + } + if (state == 7) + { + fprintf(stderr,">E %s : bad valence or maxcoord\n",id); + exit(1); + } + + *ps = s; + + addelement(inputname,name,valence,maxcoord); +} + +#define SWELEMENT(c,id) if (sw==c) {processEswitch(&arg,id);} + +/*******************************************************************/ + +int +main(int argc, char *argv[]) +{ + int argnum,i,j; + boolean badargs,Gswitch,mswitch,Oswitch,eswitch,notriples; + boolean oswitch,Bswitch,cswitch,Dswitch; + char *extra1,*extra2,*formula,*arg,sw,*outfilename; + long res,mod; + int mine,maxe,maxd,maxc; + long eminval,emaxval; + double t1,t2; + long badlist[BADLISTS]; + int badlen,outf; + + HELP; + + nauty_check(WORDSIZE,1,1,NAUTYVERSIONID); + + maxindex = 0; + for (i = 0; i < MAXELEMENTS; ++i) + if (element[i].inputname == NULL) break; + else if (element[i].index < maxindex && !ISHYDROGEN(i)) + maxindex = element[i].index; + numelements = i; + +#ifdef SURGEPLUGIN_INIT + SURGEPLUGIN_INIT; +#endif + + argnum = 0; + badargs = verbose = Gswitch = mswitch = FALSE; + uswitch = eswitch = notriples = smiles = FALSE; + oswitch = gzip = alphabetic = Bswitch = FALSE; + tswitch = fswitch = pswitch = bipartite = FALSE; + cswitch = planar = xswitch = Dswitch = FALSE; + Oswitch = Cswitch = FALSE; outlevel = 4; + extra1 = extra2 = formula = NULL; + bad1 = bad2 = bad3 = bad4 = bad5 = bad6 = bad7 = bad8 = bad9 = FALSE; + + for (j = 1; !badargs && j < argc; ++j) + { + arg = argv[j]; + if (arg[0] == '-' && arg[1] != '\0') + { + ++arg; + while (*arg != '\0') + { + sw = *arg++; + if (sw == 'G') + { + if (Gswitch) + gt_abort(">E surge: -G is only allowed once\n"); + Gswitch = TRUE; + extra1 = arg; + break; + } + else if (sw == 'o') + { + if (oswitch) + gt_abort(">E surge : -o is only allowed once\n"); + oswitch = TRUE; + outfilename = arg; + break; + } + else SWRANGE('m',"/",mswitch,res,mod,"surge -m") + else SWINT('O',Oswitch,outlevel,"surge -O") + else SWINT('c',cswitch,maxc,"surge -c") + else SWINT('d',Dswitch,maxd,"surge -d") + else SWBOOLEAN('u',uswitch) + else SWBOOLEAN('v',verbose) + else SWBOOLEAN('T',notriples) + else SWBOOLEAN('S',smiles) + else SWBOOLEAN('F',SDFoutput) + else SWBOOLEAN('z',gzip) + else SWBOOLEAN('A',alphabetic) + else SWBOOLEAN('b',bipartite) + else SWBOOLEAN('P',planar) + else SWBOOLEAN('R',Rswitch) + else SWBOOLEAN('x',xswitch) + else SWSEQUENCEMIN('B',",",Bswitch,badlist,1,BADLISTS,badlen,"surge -B") + else SWRANGE('e',":-",eswitch,eminval,emaxval,"surge -e") + else SWRANGE('t',":-",tswitch,min3cycles,max3cycles,"surge -t") + else SWRANGE('f',":-",fswitch,min4cycles,max4cycles,"surge -f") + else SWRANGE('p',":-",pswitch,min5cycles,max5cycles,"surge -p") + else SWRANGE('h',":-",hswitch,min6cycles,max6cycles,"surge -h") + else SWRANGE('C',":-",Cswitch,minCrings,maxCrings,"surge -C") + else SWELEMENT('E',"surge -E") +#ifdef SURGEPLUGIN_SWITCHES + else SURGEPLUGIN_SWITCHES +#endif + else badargs = TRUE; + + if (Bswitch) + { + for (i = 0; i < badlen; ++i) + { + if (badlist[i] < 1 || badlist[i] > BADLISTS) + gt_abort(">E surge : invalid bad list number\n"); + if (badlist[i] == 1) bad1 = TRUE; + else if (badlist[i] == 2) bad2 = TRUE; + else if (badlist[i] == 3) bad3 = TRUE; + else if (badlist[i] == 4) bad4 = TRUE; + else if (badlist[i] == 5) bad5 = TRUE; + else if (badlist[i] == 6) bad6 = TRUE; + else if (badlist[i] == 7) bad7 = TRUE; + else if (badlist[i] == 8) bad8 = TRUE; + else if (badlist[i] == 9) bad9 = TRUE; + /* Don't forget initialization if you add more */ + } + Bswitch = FALSE; + } + } + } + else + { + ++argnum; + if (argnum == 1) formula = arg; + else badargs = TRUE; + } + } + + if (badargs) + { + fprintf(stderr,">E Usage: %s\n",USAGE EXTRAUSAGE); + GETHELP; + exit(1); + } + + if (Oswitch && (outlevel <= 0 || outlevel >= 5)) + gt_abort(">E surge : unknown value for -O\n"); + + if (!cswitch) maxc = 4; + if (!Dswitch) maxd = 4; + +#ifndef ZLIB + if (gzip) + gt_abort(">E surge : -z is only allowed if zlib is compiled in\n"); +#endif + + outf = (alphabetic==TRUE) + (smiles==TRUE) + + (SDFoutput==TRUE) + (uswitch==TRUE); + + if (outf > 1) + gt_abort(">E surge : -A,-S,-F,-u are incompatible\n"); + if (outf == 0 && !Oswitch) uswitch = TRUE; + + if (uswitch) gzip = FALSE; + + if (!oswitch || (oswitch && strcmp(outfilename,"-") == 0)) + outfilename = "stdout"; + + if (bad5) bad6 = FALSE; /* bad6 is a subset of bad5 */ + if (notriples) bad1 = FALSE; + if (tswitch && fswitch && max3cycles+max4cycles <= 1) + bad9 = FALSE; + + needrings = (bad1 || bad2 || bad3 || bad4 || bad6 || Cswitch); + + if (fswitch && max4cycles < 6) bad7 = FALSE; + + if (tswitch && max3cycles < 3) bad8 = FALSE; + if (fswitch && max4cycles < 2) bad8 = FALSE; + if (pswitch && max5cycles == 0) bad8 = FALSE; + if (hswitch && max6cycles < 3) bad4 = FALSE; + + if (gzip) + { +#ifdef ZLIB + if (strcmp(outfilename,"stdout") == 0) + gzoutfile = gzdopen(fileno(stdout),"wb"); + else + gzoutfile = gzopen(outfilename,"wb"); + if (!gzoutfile) + gt_abort(">E surge : unable to open compressed stream\n"); + gzbuffer(gzoutfile,1<<16); /* Remove this line if gzbuffer() + is not found; it means you have a old version of zlib. */ +#endif + } + else + { + if (strcmp(outfilename,"stdout") == 0) + outfile = stdout; + else + outfile = fopen(outfilename,"w"); + if (!outfile) + gt_abort(">E surge : can't open output file\n"); + } + + maxbond = (notriples ? 1 : 2); + + if (!Oswitch) outlevel = 4; + + if (mswitch) + { + if (res < 0 || res >= mod) + gt_abort(">E surge : -mres/mod needs 0 <= res < mod\n"); + } + else + { + res = 0; + mod = 1; + } + + if (badargs || argnum != 1) + { + fprintf(stderr,">E Usage: %s\n",USAGE); + GETHELP; + exit(1); + } + + if (eswitch) + { + mine = (int)eminval; + maxe = (int)emaxval; + } + else + { + mine = 0; + maxe = NOLIMIT; + } + + carbonindex = elementindex("C"); + decode_formula(formula,&nv,&mine,&maxe,&maxd,&maxc); + + t1 = CPUTIME; + start_geng(nv,maxd,maxc,mine,maxe,extra1,res,mod); +#ifdef ZLIB + if (gzip) + if (gzclose(gzoutfile) != Z_OK) + gt_abort(">E surge : error on closing compressed stream\n"); +#endif + t2 = CPUTIME; + + if (vgroup) free(vgroup); /* Make valgrind happy */ + if (egroup) free(egroup); + + if (verbose) + { + fprintf(stderr,">G geng made %lld graphs, %lld accepted\n", + gengout,genggood); + if (outlevel > 1) + fprintf(stderr,">V vcolg %lld nontrivial groups, max size" + " %ld, made %lld graphs\n",vcolgnontriv,maxvgroup,vcolgout); + if (outlevel > 2) + fprintf(stderr,">M multig %lld nontrivial groups, max size" + " %ld, made %lld graphs\n",multignontriv,maxegroup,multigout); + } + + if (needrings) fprintf(stderr,"Max rings = %d\n",maxrings); + if (Rswitch) fprintf(stderr,"Max 2 mod 4 cycles = %d\n",maxcycles); + +#ifdef SURGEPLUGIN_SUMMARY + SURGEPLUGIN_SUMMARY +#endif + + if (Rswitch) + fprintf(stderr,">Z %s %llu -> %llu -> %llu -> %llu in %.2f sec\n", + (uswitch ? "generated" : "wrote"), + gengout,vcolgout,molnum,multigout,t2-t1); + else + fprintf(stderr,">Z %s %llu -> %llu -> %llu in %.2f sec\n", + (uswitch ? "generated" : "wrote"), + gengout,vcolgout,multigout,t2-t1); + + return 0; +} From a5a4317e7a8dc95b5184a946d523f782142d371e Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:28:23 +0100 Subject: [PATCH 02/11] build: update Makefile for v2.0 (WORDSIZE=64, nautyL1.a) Co-Authored-By: Claude Opus 4.6 --- src/Makefile | 128 +++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/src/Makefile b/src/Makefile index 6a95de3..1004a72 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,65 +1,63 @@ -# Makefile for surge -VERSION=1_0 - -# Define CC to be the name of the C compiler, and CCOPT to be -# switches for good optimization in your environment. -CC=gcc -CCOPT=-march=native -mtune=native -O3 - -# Define NAUTY to be the directory where files like nauty.h are -# located. Define NAUTYLIB to be the directory where files like -# nautyW1.a are located. -NAUTY=${HOME}/nauty -NAUTYLIB=${NAUTY} - -# If you have zlib installed, define ZLIB to be "-DZLIB" and ZLIBLIB -# to be the linker command for zlib. -ZLIB=-DZLIB -ZLIBLIB=-lz -# If you don't have zlib installed, make both of them null. -#ZLIB= -#ZLIBLIB= - -SURGE=-g -I ${NAUTY} -DWORDSIZE=32 -DMAXN=WORDSIZE -DOUTPROC=surgeproc ${CCOPT} \ - -DPREPRUNE=surgepreprune ${ZLIB} -DPRUNE=surgeprune -DGENG_MAIN=geng_main - -all : surge surge_0 surge_1 surge_2 surge_3 - -surge : surge.c - ${CC} -o surge ${SURGE} \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -surge_0 : surge.c plugin0.c - ${CC} -o surge_0 ${SURGE} -DSURGEPLUGIN='"plugin0.c"' \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -surge_1 : surge.c plugin1.c - ${CC} -o surge_1 ${SURGE} -DSURGEPLUGIN='"plugin1.c"' \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -surge_2 : surge.c plugin2.c - ${CC} -o surge_2 ${SURGE} -DSURGEPLUGIN='"plugin2.c"' \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -surge_3 : surge.c plugin3.c - ${CC} -o surge_3 ${SURGE} -DSURGEPLUGIN='"plugin3.c"' \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -clean : - rm -rf surge surge_0 surge_1 surge_2 surge_3 canonsdf - -# This one is unoptimized for easier debugging -surge_g : - ${CC} -o surge_g ${SURGE} -g -O0 \ - surge.c geng.c planarity.c ${NAUTYLIB}/nautyW1.a ${ZLIBLIB} - -canonsdf : canonsdf.c - ${CC} -o canonsdf -I {NAUTY} canonsdf.c ${NAUTYLIB}/nautyL1.a - -tarfile : - rm -rf surge${VERSION} - mkdir surge${VERSION} - cp SDFformats.txt surge.c planarity.c geng.c makefile COPYRIGHT \ - canonsdf.c plugin[0-3].c surge1_0.pdf surge${VERSION}/ - touch surge${VERSION}/This_is_surge_${VERSION}.txt - tar cvf surge${VERSION}.tar surge${VERSION}/ +# Makefile for surge +VERSION=2_0 + +# Define CC to be the name of the C compiler, and CCOPT to be +# switches for good optimization in your environment. +CC=gcc +CCOPT=-O3 -march=native + +# Define NAUTY to be the directory where files like nauty.h are +# located. Define NAUTYLIB to be the directory where files like +# nautyL1.a are located. +NAUTY=${HOME}/nauty +NAUTYLIB=${NAUTY} + +# If you have zlib installed, define ZLIB to be "-DZLIB" and LZ +# to be the linker command for zlib. +ZLIB=-DZLIB +LZ=-lz +# If you don't have zlib installed, make both of them null. +#ZLIB= +#LZ= + +SURGE=-g -I ${NAUTY} ${CCOPT} -DWORDSIZE=64 -DMAXN=WORDSIZE \ + -DOUTPROC=surgeproc -DPREPRUNE=surgepreprune \ + -DPRUNE=surgeprune -DGENG_MAIN=geng_main + +all : surge + +surge : surge.c + ${CC} -o surge ${SURGE} ${ZLIB} \ + surge.c geng.c planarity.c ${NAUTYLIB}/nautyL1.a ${LZ} + +samples : surge_0 surge_1 surge_2 surge_3 + +surge_0 : surge.c plugin0.c + ${CC} -o surge_0 ${SURGE} ${ZLIB} -DSURGEPLUGIN='"plugin0.c"' \ + surge.c geng.c planarity.c ${NAUTYLIB}/nautyL1.a ${LZ} + +surge_1 : surge.c plugin1.c + ${CC} -o surge_1 ${SURGE} ${ZLIB} -DSURGEPLUGIN='"plugin1.c"' \ + surge.c geng.c planarity.c ${NAUTYLIB}/nautyL1.a ${LZ} + +surge_2 : surge.c plugin2.c + ${CC} -o surge_2 ${SURGE} ${ZLIB} -DSURGEPLUGIN='"plugin2.c"' \ + surge.c geng.c planarity.c ${NAUTYLIB}/nautyL1.a ${LZ} + +surge_3 : surge.c plugin3.c + ${CC} -o surge_3 ${SURGE} ${ZLIB} -DSURGEPLUGIN='"plugin3.c"' \ + surge.c geng.c planarity.c ${NAUTYLIB}/nautyL1.a ${LZ} + +canonsdf : canonsdf.c + ${CC} -o canonsdf -I${NAUTY} ${CCOPT} canonsdf.c ${NAUTYLIB}/nautyL1.a + +clean : + rm -rf surge surge_0 surge_1 surge_2 surge_3 canonsdf + +tarfile : + rm -rf surge${VERSION} + mkdir surge${VERSION} + cp surge.c planarity.c geng.c makefile LICENSE-2.0 README.txt \ + canonsdf.c plugin[0-3].c surge${VERSION}.pdf surge${VERSION}/ + touch surge${VERSION}/This_is_surge_${VERSION}.txt + tar cvf surge${VERSION}.tar surge${VERSION}/ From 8014913d1ce5bedfa2095fa2432f8cc311f9085b Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:31:53 +0100 Subject: [PATCH 03/11] build: add .dSYM cleanup to make clean target macOS clang creates .dSYM directories when compiling with -g. Co-Authored-By: Claude Opus 4.6 --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 1004a72..d771416 100644 --- a/src/Makefile +++ b/src/Makefile @@ -52,7 +52,7 @@ canonsdf : canonsdf.c ${CC} -o canonsdf -I${NAUTY} ${CCOPT} canonsdf.c ${NAUTYLIB}/nautyL1.a clean : - rm -rf surge surge_0 surge_1 surge_2 surge_3 canonsdf + rm -rf surge surge_0 surge_1 surge_2 surge_3 canonsdf *.dSYM tarfile : rm -rf surge${VERSION} From 89d7b55fc15ed2221e9c02e4ce9bcfe2c1bff4c7 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:33:53 +0100 Subject: [PATCH 04/11] ci: add GitHub Actions for Linux, macOS, Windows builds Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build.yml | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..cf829fc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,140 @@ +name: Build and Test + +on: + push: + branches: [ main, 'release/**' ] + pull_request: + branches: [ main ] + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc make zlib1g-dev + + - name: Build nauty + run: | + curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz + tar xzf nauty29r3.tar.gz + cd nauty2_9_3 + ./configure && make + + - name: Build surge + working-directory: src + run: | + make surge \ + NAUTY=${{ github.workspace }}/nauty2_9_3 \ + NAUTYLIB=${{ github.workspace }}/nauty2_9_3 \ + CCOPT="-O3" + + - name: Test surge + working-directory: src + run: | + ./surge -help 2>&1 | head -3 + ./surge -u C6H6 + ./surge -u C4H10 + + - name: Build canonsdf + working-directory: src + run: | + make canonsdf \ + NAUTY=${{ github.workspace }}/nauty2_9_3 \ + NAUTYLIB=${{ github.workspace }}/nauty2_9_3 \ + CCOPT="-O3" + + - name: Upload Linux binary + uses: actions/upload-artifact@v4 + with: + name: surge-linux-x86_64 + path: src/surge + + build-macos: + strategy: + matrix: + include: + - os: macos-13 + arch: x86_64 + - os: macos-14 + arch: arm64 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Install nauty + run: brew install nauty + + - name: Create nauty library symlink + run: | + NAUTY_LIB=$(brew --prefix nauty)/lib + ln -sf "$NAUTY_LIB/libnautyL1.a" "$NAUTY_LIB/nautyL1.a" + + - name: Build surge + working-directory: src + run: | + make surge \ + NAUTY=$(brew --prefix nauty)/include/nauty \ + NAUTYLIB=$(brew --prefix nauty)/lib \ + CCOPT="-O3" + + - name: Test surge + working-directory: src + run: | + ./surge -help 2>&1 | head -3 + ./surge -u C6H6 + ./surge -u C4H10 + + - name: Upload macOS binary + uses: actions/upload-artifact@v4 + with: + name: surge-macos-${{ matrix.arch }} + path: src/surge + + build-windows: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v4 + + - uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + install: >- + mingw-w64-ucrt-x86_64-gcc + make + mingw-w64-ucrt-x86_64-zlib + curl + tar + + - name: Build nauty + run: | + curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz + tar xzf nauty29r3.tar.gz + cd nauty2_9_3 + ./configure && make + + - name: Build surge + working-directory: src + run: | + make surge \ + NAUTY=$(cygpath -u "$GITHUB_WORKSPACE")/nauty2_9_3 \ + NAUTYLIB=$(cygpath -u "$GITHUB_WORKSPACE")/nauty2_9_3 \ + CCOPT="-O3" + + - name: Test surge + working-directory: src + run: | + ./surge.exe -help 2>&1 | head -3 + ./surge.exe -u C6H6 + + - name: Upload Windows binary + uses: actions/upload-artifact@v4 + with: + name: surge-windows-x86_64 + path: src/surge.exe From 22fada6629918613fb88dd292efd8abd2d54e6c7 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:33:57 +0100 Subject: [PATCH 05/11] ci: add release workflow for multi-platform binary distribution Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 141 ++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2647b82 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,141 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build-release: + strategy: + matrix: + include: + - os: ubuntu-latest + artifact: surge-linux-x86_64 + binary: surge + - os: macos-13 + artifact: surge-macos-x86_64 + binary: surge + - os: macos-14 + artifact: surge-macos-arm64 + binary: surge + - os: windows-latest + artifact: surge-windows-x86_64 + binary: surge.exe + runs-on: ${{ matrix.os }} + defaults: + run: + shell: ${{ matrix.os == 'windows-latest' && 'msys2 {0}' || 'bash' }} + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 (Windows only) + if: matrix.os == 'windows-latest' + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + install: >- + mingw-w64-ucrt-x86_64-gcc + make + mingw-w64-ucrt-x86_64-zlib + curl + tar + + - name: Install dependencies (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y gcc make zlib1g-dev + + - name: Install nauty (macOS) + if: startsWith(matrix.os, 'macos') + run: | + brew install nauty + NAUTY_LIB=$(brew --prefix nauty)/lib + ln -sf "$NAUTY_LIB/libnautyL1.a" "$NAUTY_LIB/nautyL1.a" + + - name: Build nauty from source (Linux/Windows) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' + run: | + curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz + tar xzf nauty29r3.tar.gz + cd nauty2_9_3 + ./configure && make + + - name: Build surge (Linux/Windows) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' + working-directory: src + run: | + make surge \ + NAUTY=$(cygpath -u "$GITHUB_WORKSPACE" 2>/dev/null || echo "$GITHUB_WORKSPACE")/nauty2_9_3 \ + NAUTYLIB=$(cygpath -u "$GITHUB_WORKSPACE" 2>/dev/null || echo "$GITHUB_WORKSPACE")/nauty2_9_3 \ + CCOPT="-O3" + + - name: Build surge (macOS) + if: startsWith(matrix.os, 'macos') + working-directory: src + run: | + make surge \ + NAUTY=$(brew --prefix nauty)/include/nauty \ + NAUTYLIB=$(brew --prefix nauty)/lib \ + CCOPT="-O3" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: src/${{ matrix.binary }} + + create-release: + needs: build-release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Package binaries + run: | + cd artifacts + for dir in */; do + name="${dir%/}" + cd "$name" + chmod +x * 2>/dev/null || true + tar czf "../${name}.tar.gz" * + cd .. + done + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + name: "Surge ${{ github.ref_name }}" + draft: true + generate_release_notes: false + body: | + ## Surge 2.0 + + A fast open-source chemical graph generator. + + ### What's New in 2.0 + - **64-bit word size**: Supports larger molecular structures + - **Aromaticity detection** (`-R`): Filter duplicate Kekule structures under carbon-ring aromaticity + - **Hexagon restrictions** (`-h#`, `-h#:#`): Control 6-membered cycle counts + - **Carbon 6-ring restrictions** (`-C#`, `-C#:#`): Limit carbon-only 6-membered rings + - **Improved summary reporting** + + ### Installation + Download the binary for your platform below, or build from source (see README). + + ### Binaries + - `surge-linux-x86_64.tar.gz` - Linux (x86_64) + - `surge-macos-x86_64.tar.gz` - macOS (Intel) + - `surge-macos-arm64.tar.gz` - macOS (Apple Silicon) + - `surge-windows-x86_64.tar.gz` - Windows (x86_64, built with MSYS2) + files: | + artifacts/*.tar.gz + doc/surge2_0.pdf From eac3e1ef4dbcbfb80958fc6324bbe78400c6f6c0 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:35:28 +0100 Subject: [PATCH 06/11] build: update Dockerfile for Ubuntu 24.04 and nauty 2.9.3 Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 59 +++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33db01b..01e24b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,34 @@ -FROM ubuntu:21.04 +FROM ubuntu:24.04 AS builder RUN apt-get update && \ apt-get -y install curl gcc make zlib1g-dev && \ apt-get -y clean && \ - rm -rf \ - /var/lib/apt/lists/* \ - /usr/share/doc \ - /usr/share/doc-base \ - /usr/share/man \ - /usr/share/locale \ - /usr/share/zoneinfo -WORKDIR /root -RUN curl -o nauty27r3.tar.gz http://users.cecs.anu.edu.au/~bdm/nauty/nauty27r3.tar.gz \ - && tar xzvf nauty27r3.tar.gz \ - && cd nauty27r3 \ - && ./configure && make -ENV NAUTY_HOME=/root/nauty27r3 -COPY src/surge.c $NAUTY_HOME -COPY src/Makefile /root -WORKDIR $NAUTY_HOME -RUN ln -s /root/nauty27r3 /root/nauty -RUN make -f ../Makefile clean ; make -f ../Makefile surge - -FROM ubuntu:21.04 + rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Download and build nauty +RUN curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz \ + && tar xzf nauty29r3.tar.gz \ + && cd nauty2_9_3 \ + && ./configure && make + +# Copy source and build surge +COPY src/ /build/src/ +WORKDIR /build/src +RUN make surge \ + NAUTY=/build/nauty2_9_3 \ + NAUTYLIB=/build/nauty2_9_3 \ + CCOPT="-O3" + +# Runtime image +FROM ubuntu:24.04 RUN apt-get update && \ - apt-get -y install curl time gnupg zlib1g && \ + apt-get -y install zlib1g && \ apt-get -y clean && \ - rm -rf \ - /var/lib/apt/lists/* \ - /usr/share/doc \ - /usr/share/doc-base \ - /usr/share/man \ - /usr/share/locale \ - /usr/share/zoneinfo -RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && apt-get update -y && apt-get install google-cloud-sdk -y - -COPY --from=0 /root/nauty27r3/surge /usr/bin + rm -rf /var/lib/apt/lists/* + +COPY --from=builder /build/src/surge /usr/local/bin/surge + +ENTRYPOINT ["surge"] From 635cb9673793701fcff0c4de0c8e1cdac9a2d14a Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:36:39 +0100 Subject: [PATCH 07/11] docs: update README for Surge 2.0 Co-Authored-By: Claude Opus 4.6 --- README.md | 97 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index e199e4c..980a6f9 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,18 @@ # Surge: A Fast Open-Source Chemical Graph Generator ## About Surge is a chemical structure generator based on the Nauty package and thereby on the principles of canonical augmentation. -More precisely, Surge generates all non-isomorphic constitutional isomers of a given molecular formula. [**Surge's article**](https://jcheminf.biomedcentral.com/articles/10.1186/s13321-022-00604-9) is published in Journal of Cheminformatics and more details are given in its [**user manual**](https://github.com/StructureGenerator/SURGE/blob/main/doc/surge1_0.pdf) for more information. +More precisely, Surge generates all non-isomorphic constitutional isomers of a given molecular formula. [**Surge's article**](https://jcheminf.biomedcentral.com/articles/10.1186/s13321-022-00604-9) is published in Journal of Cheminformatics and more details are given in its [**user manual**](https://github.com/StructureGenerator/SURGE/blob/main/doc/surge2_0.pdf). + +## What's New in 2.0 +- **Aromaticity filtering** (`-R`): Removes duplicate Kekule structures under carbon-ring aromaticity. +- **6-member cycle limits** (`-h#`, `-h#:#`): Restrict the number of cycles of length 6. +- **Chord-free 6-carbon cycle limits** (`-C#`, `-C#:#`): Restrict the number of chord-free cycles of 6 carbon atoms. +- **SDfile output flag** (`-F`): Explicit flag for SDfile output format. +- **64-bit word size**: Surge 2.0 is built with `WORDSIZE=64`, supporting larger molecules. +- Updated to nauty 2.9.3. ## Usage -Surge is a command line tool. Running `surge -u C10H16O` will generate the 452458 isomers of C10H16O in 0.1s on some vanilla flavor year-2021 PC. Running `surge -S C10H16O` outputs those structurs in SMILES format. You can either use `surge -S C10H16O > myresults.smi` to redirect the output into a result file, or use the `-o`switch to provide a filename. Further formats supported are SD Files (SDF) and a concise Surge-specific format. +Surge is a command line tool. Running `surge -u C10H16O` will generate the 452458 isomers of C10H16O in 0.1s on some vanilla flavor year-2021 PC. Running `surge -S C10H16O` outputs those structures in SMILES format. You can either use `surge -S C10H16O > myresults.smi` to redirect the output into a result file, or use the `-o` switch to provide a filename. Further formats supported are SD Files (SDF) and a concise Surge-specific format. For large sets of structures, the -z option for compressing the output in gzip format will come in handy. `surge -help` will show all options: @@ -16,47 +24,50 @@ For large sets of structures, the -z option for compressing the output in gzip f

``` -Usage: surge [-oFILE] [-z] [-u|-A|-S] [-T] [-e#|-e#:#] [-d#] [-c#] [-m#/#] formula +Usage: surge [-oFILE] [-z] [-A|-S|-F] [-T] [-e#|-e#:#] [-R] [-d#] [-c#] [-m#/#] formula -Make chemical graphs from a formula. Version 0.9. -Known elements are C,B,N,P,O,S,H,Cl,F,Br,I at their lowest valences. -Higher valences can be selected using Nx (Nitrogen/5), Sx,Sy (Sulfur 4/6), Px (Phosphorus/5). +Make chemical graphs from a formula. Version 2.0. + Known elements are C,B,N,P,O,S,H,Cl,F,Br,I at their lowest valences. + Higher valences can be selected using Nx (Nitrogen/5), Sx,Sy (Sulfur 4/6) + Px (Phosphorus/5). -formula = a formula like C8H6N2 + formula = a formula like C8H6N2 - -E.. Define a new element (see the manual) - -O# Output stage: 1 after geng, 2 after vcolg, 3 after multig - Default is to write SDF format + -u Just count, don't write molecules (default) -S Output in SMILES format + -F Output in SDfile format -A Output in alphabetical format - -u Just count, don't write - -e# -e#:# Restrict to given range of distinct non-H bonds - -t# -t#:# Limit number of rings of length 3 - -f# -f#:# Limit number of cycles of length 4 - -p# -p#:# Limit number of cycles of length 5 + -e# -e#:# Limit the number of distinct non-H bonds + -t# -t#:# Limit the number of cycles of length 3 + -f# -f#:# Limit the number of cycles of length 4 + -p# -p#:# Limit the number of cycles of length 5 + -h# -h#:# Limit the number of cycles of length 6 + -C# -C#:# Limit the number of chord-free cycles 6 carbon atoms -b Only rings of even length (same as only cycles of even length) -T Disallow triple bonds -P Require planarity -d# Maximum degree not counting bond multiplicity or hydrogens (default 4) -c# Maximum coordination number (default 4). This is the maximum number - of distinct atoms (including H) that an atom can be bonded to - Coordination number > 4 is only allowed if no neighbours are H + of distinct atoms (including H) that an atom can be bonded to + Coordination number > 4 is only allowed if no neighbours are H -B#,...,# Specify sets of substructures to avoid (details in manual) 1 = no triple bonds in rings up to length 7 2 = Bredt's rule for two rings ij with one bond in - common (33, 34, 35, 36, 44, 45) + common (33, 34, 35, 36, 44, 45) 3 = Bredt's rule for two rings ij with two bonds in - common (i,j up to 56) + common (i,j up to 56) 4 = Bredt's rule for two rings of length 6 sharing three bonds 5 = no substructures A=A=A (in ring or not) 6 = no substructures A=A=A in rings up to length 8 - For -B5 and -B6, the central atom only has 2 non-H neighbours + For -B5 and -B6, the central atom only has 2 non-H neighbours 7 = no K_33 or K_24 structure 8 = none of cone of P4 or K4 with 3-ear 9 = no atom in more than one ring of length 3 or 4 + -R Enable aromaticity detection (filters duplicate Kekule structures) -v Write more information to stderr -m#/# Do only a part. The two numbers are res/mod where 0<=res results.smi ``` ## Misc Surge was developed by [Brendan McKay](http://users.cecs.anu.edu.au/~bdm) with the help of [Christoph Steinbeck](https://github.com/steinbeck) and [Mehmet Aziz Yirik](https://github.com/mehmetazizyirik). - From 24ab6ecbd7588ca4cc4def51dca6fac7d4379e20 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:36:48 +0100 Subject: [PATCH 08/11] chore: add surge binary names to .gitignore Co-Authored-By: Claude Opus 4.6 --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index be4f0d8..04726a4 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,8 @@ dkms.conf # Editor backup files *~ + +# Surge binaries +surge +surge_[0-9] +canonsdf From d1261dd7d653b462f147695c3c00cfaea74fe7a5 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 12:53:26 +0100 Subject: [PATCH 09/11] fix: correct nauty download URL and README improvements - Fix nauty download URL: nauty29r3.tar.gz -> nauty2_9_3.tar.gz (404 fix) - README: use dynamic brew --prefix path for Homebrew symlink example - README: add -I /path/to/nauty to gcc compilation examples - CI: add C4H10 test to Windows build for parity with other platforms Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build.yml | 9 +++++---- .github/workflows/release.yml | 4 ++-- Dockerfile | 4 ++-- README.md | 14 ++++++++------ 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf829fc..15f2edd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,8 +19,8 @@ jobs: - name: Build nauty run: | - curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz - tar xzf nauty29r3.tar.gz + curl -L -o nauty2_9_3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty2_9_3.tar.gz + tar xzf nauty2_9_3.tar.gz cd nauty2_9_3 ./configure && make @@ -114,8 +114,8 @@ jobs: - name: Build nauty run: | - curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz - tar xzf nauty29r3.tar.gz + curl -L -o nauty2_9_3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty2_9_3.tar.gz + tar xzf nauty2_9_3.tar.gz cd nauty2_9_3 ./configure && make @@ -132,6 +132,7 @@ jobs: run: | ./surge.exe -help 2>&1 | head -3 ./surge.exe -u C6H6 + ./surge.exe -u C4H10 - name: Upload Windows binary uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2647b82..faa97e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,8 +57,8 @@ jobs: - name: Build nauty from source (Linux/Windows) if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' run: | - curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz - tar xzf nauty29r3.tar.gz + curl -L -o nauty2_9_3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty2_9_3.tar.gz + tar xzf nauty2_9_3.tar.gz cd nauty2_9_3 ./configure && make diff --git a/Dockerfile b/Dockerfile index 01e24b6..dee3187 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,8 @@ RUN apt-get update && \ WORKDIR /build # Download and build nauty -RUN curl -L -o nauty29r3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty29r3.tar.gz \ - && tar xzf nauty29r3.tar.gz \ +RUN curl -L -o nauty2_9_3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty2_9_3.tar.gz \ + && tar xzf nauty2_9_3.tar.gz \ && cd nauty2_9_3 \ && ./configure && make diff --git a/README.md b/README.md index 980a6f9..64dd132 100644 --- a/README.md +++ b/README.md @@ -84,22 +84,24 @@ make surge NAUTY=/path/to/nauty NAUTYLIB=/path/to/nauty ``` Or compile directly with gcc: ``` -gcc -o surge -O3 -DWORDSIZE=64 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ - -march=native -DPREPRUNE=surgepreprune \ +gcc -o surge -O3 -I /path/to/nauty -DWORDSIZE=64 -DMAXN=WORDSIZE \ + -DOUTPROC=surgeproc -march=native -DPREPRUNE=surgepreprune \ -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ surge.c geng.c planarity.c /path/to/nautyL1.a ``` You can build-in gzip output using the zlib library (https://zlib.net). Add `-DZLIB` to the compilation, and link with the zlib library either by adding `-lz` or `libz.a`. This will activate the `-z` command to gzip the output: ``` -gcc -o surge -O3 -DWORDSIZE=64 -DMAXN=WORDSIZE -DOUTPROC=surgeproc \ - -march=native -DPREPRUNE=surgepreprune -DZLIB \ - -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ +gcc -o surge -O3 -I /path/to/nauty -DWORDSIZE=64 -DMAXN=WORDSIZE \ + -DOUTPROC=surgeproc -march=native -DPREPRUNE=surgepreprune \ + -DZLIB -DPRUNE=surgeprune -DGENG_MAIN=geng_main \ surge.c geng.c planarity.c /path/to/nautyL1.a -lz ``` **Note for macOS Homebrew users:** If you installed nauty via Homebrew (`brew install nauty`), the library file may be named `libnautyL1.a` rather than `nautyL1.a`. You can create a symlink to make it work with the Makefile: ``` -ln -s /opt/homebrew/lib/libnautyL1.a /opt/homebrew/lib/nautyL1.a +NAUTY_LIB=$(brew --prefix nauty)/lib +ln -s "$NAUTY_LIB/libnautyL1.a" "$NAUTY_LIB/nautyL1.a" +make surge NAUTY=$(brew --prefix nauty)/include/nauty NAUTYLIB=$(brew --prefix nauty)/lib ``` ### Option 3: Use Docker From 3f56ceb2da7e2df84dbbdbd95c6917969dad4f29 Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 13:06:11 +0100 Subject: [PATCH 10/11] ci: replace deprecated macos-13 runner with macos-14/macos-15 GitHub has deprecated macos-13 (x86_64) runners. Use macos-14 and macos-15 (both arm64 Apple Silicon) for CI builds. Release workflow now produces a single macOS ARM64 binary. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15f2edd..4ae7f69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,10 +57,10 @@ jobs: strategy: matrix: include: - - os: macos-13 - arch: x86_64 - os: macos-14 arch: arm64 + - os: macos-15 + arch: arm64-m15 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index faa97e6..3a30013 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,6 @@ jobs: - os: ubuntu-latest artifact: surge-linux-x86_64 binary: surge - - os: macos-13 - artifact: surge-macos-x86_64 - binary: surge - os: macos-14 artifact: surge-macos-arm64 binary: surge @@ -133,7 +130,6 @@ jobs: ### Binaries - `surge-linux-x86_64.tar.gz` - Linux (x86_64) - - `surge-macos-x86_64.tar.gz` - macOS (Intel) - `surge-macos-arm64.tar.gz` - macOS (Apple Silicon) - `surge-windows-x86_64.tar.gz` - Windows (x86_64, built with MSYS2) files: | From af382321ca6aa064215eec82332cbc61cdd63bde Mon Sep 17 00:00:00 2001 From: Christoph Steinbeck Date: Thu, 26 Feb 2026 13:42:58 +0100 Subject: [PATCH 11/11] build: use Alpine Linux for minimal Docker image (~9MB) Inspired by PR #3 (ferchault). Switch from Ubuntu 24.04 to Alpine 3.21 for both build and runtime stages. Static linking eliminates runtime dependencies, producing a ~9MB image instead of ~80MB. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index dee3187..7b5c2fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,6 @@ -FROM ubuntu:24.04 AS builder +FROM alpine:3.21 AS builder -RUN apt-get update && \ - apt-get -y install curl gcc make zlib1g-dev && \ - apt-get -y clean && \ - rm -rf /var/lib/apt/lists/* +RUN apk add --no-cache build-base curl zlib-dev WORKDIR /build @@ -11,23 +8,18 @@ WORKDIR /build RUN curl -L -o nauty2_9_3.tar.gz https://users.cecs.anu.edu.au/~bdm/nauty/nauty2_9_3.tar.gz \ && tar xzf nauty2_9_3.tar.gz \ && cd nauty2_9_3 \ - && ./configure && make + && ./configure && make -j4 -# Copy source and build surge +# Copy source and build surge (static linking for minimal runtime image) COPY src/ /build/src/ WORKDIR /build/src RUN make surge \ NAUTY=/build/nauty2_9_3 \ NAUTYLIB=/build/nauty2_9_3 \ - CCOPT="-O3" + CCOPT="-O3 -static" -# Runtime image -FROM ubuntu:24.04 - -RUN apt-get update && \ - apt-get -y install zlib1g && \ - apt-get -y clean && \ - rm -rf /var/lib/apt/lists/* +# Minimal runtime image (~9MB) +FROM alpine:3.21 COPY --from=builder /build/src/surge /usr/local/bin/surge

CGJ1yamr#(Kr08e)W5UXp5+y8nIvaWOmQD>l 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�C^Ms z=I^~Q-~QA|?#>QxhvpyAcYu3P{C)&If9BkjO9<~Q5Z19p8jf#zRl=v$Gbv%b>Yvpz zFi^hfpp@j;!QMFJD=NenFipz47isekM0BN0qC0pZ)_3z*kgPYFy56M z(wZ{VUIizAz`v2{`%kg=cOX2aURnQy1fT%WH+xd3y}CDc6c7iEA6Q4ndk~I%0C3(z z+YjUsq(k3NAE0t&0UpLTYn|WA%j%f$>ogE3kVh~L3$_tSIuWUJBjM!|=Qj*uOAceb zWf=aqiy2!U_!&j3Hr|rT$;B1B^_+CYQUO8NF)e2)?6~L!WL{C4*V5gJ$#e_%u6LtI z&3F!5%sceK&wb&qiVi+17b!OUaot@=O#N;m?;(sgInyv6Om;!MSdbv=cs=mhj-G~+ z#JY9->1``N^r_E=-z^&3ScmxswQ%nkoXo{dlniWCENSNExo!INbY*$RaezWKIiJXV zcf85Rol%Dz?i;fa?-HT#&F1Ks+^K{VnEQkEG18*=ryp(8B+k4fL`G{P=UXt6Lr6?v zuVgcor!k9fR*3@Q5*s7Uz{(u3C*5{}G))Ca-bqJSphMUTE|xrnFck31BdU%9ltw%$ zhrVf0{8YCj;I8FK`R&=xIC&RtO`qfg$$1u zpO5Q(U-O{r1Vv%^eQ?D)uw2b>g`pL&n9oFtcEvq!s45ipNS2w_2fTj4<0sY`Y$0)V zDP6E{=xJ^0U}Kz#aX#qm^YH9Ads7tYEQ;AU&)&eMLUdzB+0vpl=4{X@aPP>97nTNO;R2BWjtiGq_U=UUF z6Q-Hro9r+*;ivni>Uj?6xL+$JPv(?SSL5llnh;T!K?_`7q4SgJDodCCK$-}r0>KYA zij@1Louy_w30~`#LJfbAc6I<1E%SF=Vqvo?AEX;4Svt=@WZ~8ht=&^YK`!KpjMvGJR|C4ZbUfl4Bd<-CXJ;p z?RT9*t@z74uY@TN%q%%d1>U)=+l)GhbC6j^ru%!wAYAsdi&gzK0isM+fLc>bAr|Ur9LXNcG!H? z@QPcx{KvF-1d|3gX?2H(K8l|~uK2+pit|uTTP*UzM8I!6#Vgm6&_Yj|s`F(`kvk&6 zYWB&7@;BWMAO37R4y2i%r5DlnJ6pDk^GQ*?Q37D!8l-V@fnM@CUc|y3EDcV#?MEhr zB^qnu#`!Nto8LL85M3idC+a1iw&$1OY~_f~Xwv<%5Yt>OHtRYcckjA4@;Fv%!H5R6 zLi}zJ0K=@!E!+gI1m^BoT9w-Fq^QPwa6vJ4Q_{7|gHj8XIgJVzQ+afw^kZ@3##HLy z-eI(ChW;*Hyj_^HDL0!}y~QQs*xF7kDcTSB6Sk?bA4^zGD$URCh-pY-WhcQUIdSQVEsz5PZX$+rLcUCcO}d zeKd{9hS;9fnCo}sJo>BQ*M81xUf59&hY+w_Rz`6QT0348G{OOLesXva4Q9D#Egk2s zPNiG-Vw!r;jXLyPd1LgaXf3Xkm%KQ3zSosBMVkhpuW}sif%ue1H?WmeQQMe08z#d9 z_A`S=bjP=3-=uNt6HU^y0q};s=tZ^rW>?_htu8j_w;hh9mjKM}*r_*#n3d^~inb1& z(1LZVkXP`Cupdn(Z%eHCjw^{Zr+Fq@T%PtZzs44#vexe5KTcqg=q&7lOs`_Ozlw5I z<#?IsT;N@}_SD?EobQ@{@J8M55TxruqTUbYI=}R~LLsB{{UF&$jB_HJ+tA08MDvV> z%+aersXO9t?Bt}=@p3}M+L-Lt2Ws6OZX%>&?~;)AsC%9b{YN%}*P#sx@1H7aJZ!!E zq1H{e6Bu}`leW5-NtO~Iq5)gE9A5rjT}K236#T|Crq%%Dq#cdWuDEpC2$9&Jgg_Us ziJ*mbdGw0EB7e{IXojNqwqO4J!fz%Rp)mJI$No``&TVFfOm%k%&*^99<#fr@Gs9og zRLY6()Hh9MY2hRbWcIKGCNs2C^q4lqTwbjI60*-$&7#Vn=S%m+@QFYm&jY7rubMb2 zy?3ylZwmsh|87DZU^w2(F)G+G#+yBWX`jH#)s8pYmlU@^M?kdn3WHui{VW#SSJzXzolK?_Yd<;!Af*l5#;3mJs;;+ML$|=N zWb5EdC*?i}umXuMkN4H-zq%cU+&0ukr&xS7Z@@9fbdh!bb zoi?-Lw)fHSqPUuE5w}~Qx*#$CrP+r{aCX#`fab(JV$I3H+ZnpfPMNkX2HSy!Hn2r! zb(eipK|nX>rDiX#U}rWDrsJX+YRqnF@9P~W@oklDpW`A-MI01TN)2qy96)kwP!Nl6 zQ?LAG3i<=Yd2 zCOELUi`PwdAykE!%!VtEjM_8*Mn++u7#lkh*E5%O^fF50Ps@v$5 z`?400uHxyOU--uTm8J42F;0Yk9Z`@sRd!9(Iy28h4k{17>NMr3?MmUDFVRXqYcr^A5XT27ox<%R zBvWoG7#zFULfz7hOC=47;)qf2XTe1VziABEGcHU@7Z zS>P3vhi>yrQRg|nO#{`EZ@I5qe=SY>i=*zgZ1pVWs3W>|P$K4@?PW&!U24Sby(Xmu z5D)}$ym5bJuD+H&M%wHyVP1^bez?93TDsS`Ulp zLJvqz4pVmRQgoD0RLv%iF#<%`NXPLEf8Rf(&mM|l72_k~tb>u)MeJIdJLU0()cf2f zOJfJL@nD|EWLV;!t_{aqAM>+N^(zhk;q4D*aaU$JH~7{Nqn=KWm#ILyHBqxV?B z63x4#TeYIdN{mbPLUGCgab~W}TfxI`PhP~&FCe}CG?MAg+LPKNe!V<(6@4CH^tW#R z(V;QJ3M?h!dTHfSq=Ar!93hG_idlb@>O%Fm+?1_%eg6nz)v`OpKwY^GIFCUG_b zuFV^Z;Tx9H>9=sT@W3Aee#B-O5+4K(!l&0m^uW;UD~u9LZ8w%MZO5d-dC|@_dpg4I zZZrHICV*Fd4LD7XZut>vj6Jw}%^8q(OIPx0=(xx|zg~_W zP3YLJLeZ(9*UU%Iz0|JlEp_>U7yKm+%okFsHrKY#l~d%>YkCEJo1>5Hi?R)3)3DvJ zLv-`x@1M)yF&sg2A7#|<7xzWR5FW>s)%7ct2lvU}qIa3?aCg69@+7sOuemg^_vIp7 zu}7PzCpa7tnJJAX7MrQXZMkAvv%GH#nc~#Q6ZJM=8p2T*;Lum4Nq-KWh5gpqnOt)7 z{H(~Bnf}l_c|PeBv=)^z)Rp(nCdgItMY8lZ-MGLY9+9k)#6{0llxT(hoG>bw>Lu;C z3##=p{qp+A78dSC7H6Hja;|Ksh4^em=1G`%Dq*)>(mUFZI7XD zl?eu_l9Hplnc{|Ss&A=xJn7nAg4$87*3LGi<9Z{Iuzuf6BS#@2vjUyOi>>kmjFHm` z>;vQ6jsCG+eD&=@6cPSXS^Pcarc%pC0}=_;EnuS@u_|0`srQangw(I4bp*b#Bfu#v zSXN#>Y8IK%Ei!?1uuzm^5*kKd@=fLlWMA-g=$KJO7~Ma3>Y0k@HjK?rn=P&@z&T^^q^@BK-U@!}#*9Kl`br>44#((w?*Q+Y`a%PkfR z3Ur#GI8xzyn`ydECT6~G7P{eFS>xGE*FGtCNDi;1B~~}jbC!FUW$CG$j~&;>*lAP! z31kON=SVl_Vbj_azuNG^60ve`8vdEYqnH6cUmEW{pM-6;=;emOA}i7>SElz|V?I^_r$Wwtz+FI#oWV1ShNMrjO5b9f5(BfIyniaC)UIPf5^Z@JNXw4%Zi& zMm4JcL}Ue}Ls7Ee>-lePv|(Mzy(n8s+#EFb9G}_susb`dVi`r)7xOw^{My1TwWmQx zOdmMiAs#yvvf;gY)BWkvD>3=v9se;NAx`;=Zfiu!5ctxPv1ZbtC}&i(2{}oC;6yKm zN5;c7(APAO%c{5*R|lkoyOHX7hsKGd*qVK?sQB#!31amWOjrvg25yK{Z<`d^6yn3u z$_tj&kEx`HeW^&_$%gAyx&~VlGWOJ5-v4hjj`I@%B;7JD8j`Bi9fjbCJfA&f2Jc^p zlkHiqTry={KO1f}_k6=Fy)jSkO4nCax6l4h;o7XPI!{cqI2`^w{ zF`=BeTTvvExB_Sn1+XlQx%;zviSDxVCCR%6FNY6_5kb+yoPeKj;g0H#3eptIrIhyl zj)FinOJ4BO0M|RV$oUqf`ks_>lgA&mG!1w_i3n?dAUvKQ9B<1^ozVGw;}?;ftGa4W zGap7BG4$SDwEM83_Zr%Dz&~OUHZK_8-sKT zEqiFVo)!NsoSm>CNbxl{X!L4=MLeXgEY@OWI-6|6wZGrx{9kq6XD&Tf@21Y5q`$1I zlwKX+Z;T5}!4St5RJY{g4T9HtFNGUgs=rOhq|O479xSPi7AuYplCT(!rd1Pea<%`- z(i4dRNXXL-Rs|!%A3B{a>-5cc2YX~C`Niq!r7ww_3_q3=ZGr!iK}UnZ31-_LCF)>g zZAz-NpG1;WUABX;QdCZg0*B0$NoS2au^_>Pfx0)U$R=^`nW8nTZM`MlWyAvOf;(I=c0kSk|zEMBUC@hT0G)j>@1DwJL`#6h9g zsUae8w(pwxRD9x-Zu@IaO?IxeL3>CwSTrF1(iO@No!&gZ^FbSN7ty23g=dGvD+6kA znMD#KIx3t!dw=1tx%eO{C0c}e<=5&!%oD<1&~-JJXFBhV4$!GVY2lcx^ffaMGc#LA z7{$g>bsZ~Zuwih%)@=nS^|M3M?rzXPTalC{v^7DvXimg6N>aLsF#@>5+F+}>UNv4iH}B88*tL?t zZRT%GGQf`GIbg0Wwaz4~eAxjtIXoU^iSGV;lloA6kxayGNvBLklbpcvz09bs6~p1( z#~@4Uu;-<~fezgMhQVpAkzZS^;hK?LbJ|m8iIOkPS*P#_n1v|FpxoJm0Kn9wGFjgD zvr%f_#qokktawK%34_Ghl-x1}KKfKNe5ENnx-me+HM!yow0_9=bm6hzX`fo>fSOCt z;1%>#Wv6SOU0^C2&73dPTumbP%ZxrnO!S#RZml zI<(aJ?~R4P;P9cEySE#2DBaai_U6{0ORt~xKyi?3*Fp3=blM3iCf$|&(jf9Ghjg3u zU?KvkBx7vC&*e}@P=IzQ>-RzzJIW)151wCb;%p&F(PffJ9%MSoAQ~SS**AyBmO#eF zUdGH{GNfMS?BIXGTjCpqn&6F z)JV2)!*>Ns0Z>V{O40Jg5=5*N+^Z*|mu4_Az|JhPr7L(6L24R8*3*^=*b7;`E`MkN z17$Ozv$oiNsXuX!EdA??V{&8N^hPvaGk<6G3KR!xvQg5C#c=e}eoFJWh0cxKP}F8Z z7`~EI>2ebOO3kjr+S4;3tQ)oP6f(09@t)Uxr{inv@IhgFXOnPk<|L#u?PlKnaR{3M` zSu?&fTBxPop%nuI@tq>h%FnbC={a=S4S zqFvvc?*8pcor5RQH~Casl@gQy{8EVS?1(`K9+>o&QRu4=`!jJ%Hfw$h+NDd{=)TG$ zN{E|#S%Br1dvKqZGD`ei%pO_2cX^5PgZ{Np8D!hO@QN;(d4Ac7np4U~9I{@3`2K2Q zdaIgBH;Z#@Yfo(f<9|^y{cgNfxoBuJ=+DW->VTXFQ}ja0&pw}%bqdW7rB2@x*AH1f zd;pi$xD++ttM$>6i!gM|P5Sp;63O6hJd?t`rg$bf_4Dc9>}q>vE;f7NPZH$t7UWvC zrmx0tKPVoT+i7(Ry6ql>clI`rL=+*rdZ8?lEmy-El7~B)3gWHsAtAIds$KWl_dV^& z76!R25xcIeYGhW&tS>75^aFCeyX_!+SoC@!-!INw$Kl7Y`iQ@uU2>uj91&T)UZD&U zCU!xL&krc2_J>-M}O_tg}=iMz1P&3Ne!87QfY0$qlrQn3A#}IvLdXp*?5N)1D=D?Cdlg%VjyQ84QmF5h)9C@@yGOMMLr|ZUgQ8X}kC-S-223@X zW2}0mVf(UWx5SLT$*oEnS11m$j3nZ>4E1yjb<4W-Qp;nRZR7P1vXtBm|5UP1tY)pf z2kRhi4VBbLxaFn^DjibBcSE{d7l5pH%IS`1y2(|`p+o0 z_gxq%TI1>ub-Qfddhuuo%Z~23Xdd)bDkG%e?8;bF)lzw-_aTcoD`5*hSTsjGIRb(2 zMXPEVL3j^2oQa35hkH}I|W#;|X;TxbISa zIbU^&f~3CbyaC>Oso07?#IY8ILHimSS2C(#35hWL6jW+;WU&vlPNIE+fA3={{VY_5 zv~YC4AuhryGT{81+B$E{q>zJ>0wUR0tdqo+^tb8}#})aYgj3mUHLnF@xBTqB*=tJ1 zrKvGTbP)r9A~_qucB?93T)^n*tlEdvkSzrmp*fduI+n?JdJ3o7~I- z4|BB9#Xl7fgIE%UWPEq=f}gPQYrU5d$JmKzw;7f=gNLf$-F%NV5|Y}9$CS`>i&|A$ zG7PJbDJSkL0oxL{)btQr^f9>c2J8~WtUB*6v{XMZgdP6Jks9hX=Y-=$O1;5x^tgP>zg4gA-&mk@RPH;)mftd(mt^=*P@ z7xv=FpN$6$pY2NTnr{Wi>lC+LMY~#}9oLgn#e1p=BUS3A5$#96Kg~5cRQm%R@Y<|p z$H7>TF3l*y=}Sk(b?5|@UHVeBSCD$M z#c|A!zut8PJ-Yd~z&!UA5Bvj@F}m)WfvnTfskCiWK11lwewp^{T|~j65TT2rXP;e+ zS~#q;V@mlQ{$2TnE|Lg*tAfYi32pxn?1Y-#MDLT}%MCqADcsL2c-zZub`?-Ufhd68 zu~~Cv(n(kEQ6Zo6nNq6$Ze?+XLi$&abev!8fy67Un@_&dW3gS4LB^XZcle2Q`Fa%C zJfD>OT%|V>F@I?J1?79Pd$t)n*NTJtr{u}7TlrQ#sYr<4ciYbvFwd76UAVdDXt<9H z?K0jY@%AYX>vNO&=iU?77;Ziv9l@%^B~9qK$WFPepRL15#R%j?6s*WqufZ={el1Jh z{~V*YiAiXL0eQaNcqNaJPRsm-pyqjn#tVM^5e8ap^oWaxYRli7g8Y_fbr>UCMn;rz z3y0QU!BWk>kTR3ctf#wGY@a_RDWy>r#kI%}u3+a|(7cmw(QER<6zgSJlmd+$=XVZ> z$K<!-DZ0BK+L*gNlHzt~B)j-AAcl2fD1P(bZk5(+v#Zxp_yyGsosdUq|zO%U%> zG*pSlh%26MZL@-APisyHRGv`Dc{HRixk{D<9fnQ)nd)?_s{X;=`9Zbnw;C*Z7Pp!Q zid{(NG|fKJSu{-wCs&5D-~Y?^W?J{*aJef98`liZ2e~cCHtTgKT##_wb%?c zxN4Y%drmJ0ViKF@ulhqfdc;T#;*$9W?lsCJfJ~K}7^t>&$dCztnV|ZU3hEL>yM^cV%d7@b&q72y{fyAfjf zrd}fLw}lnfTPKMsfi1jX)|OR$wL!-c9WWjDU+|sxg#9hS)*@D3zv3nQ8VQWJ0vLI! zu5a`$x|?%zS6_KQd-%>$bphqUc&B3jbQeWd@T=CT9j%2F$FGAsG#w&8rw5@lWPXT` z{8$yD_AHBhCV!SCVL~!>{g{4#wAu9JMM(^6xmEXq-f+M}CHZ;Bm}K~l|6LGGab?FX z$>9ph1VhB9%31i%sxb@u>8`YeM7CsRBcR*u$K*Q8rCv&yIzLQHr9nkmss~-v{&mUQ z^e`8gk@(89MaZXjBT+^afW_UIi*00OQ9ujBj*7cFt)(zjc`rvZ)W^)pcD}b*sI2}A z&5swVCnA370tHfIkw+jMw-|S#B_upX=@;!C5ARuuL0MdMs1xLq3+^t=Yqh@xMX85R z3ooA?jd3voA35p(kwu9(CKf^R+nw~sgR;Fnyn6(css5Y<=HMjJ-GO7~$bcvS`gh#{ zE-r{WEeK=fXv7-;I8D(A`Tv7t{U4_G|H)c05-~Bb{d1fD2a?6e%)$P@L9GAGWHGWb zGO_$unXG4Ug>35;aF|28epZ&7n;W|RZ5#_c9QReA{vheOe~cCiQJc2|6b@dH?o4*( zc;+pj>Za!J#Z9wo&FT85Z^f9(Qs&T9e<_h=T`)B&E;BX1fRLn4cr1S3#6tfL2&m*RQwHTzq;o+g-mpYfg z6v2^=ErbgYC9rOdz>7WpbbuR>Yh5Pm*z%>n)Etafdk5D;W1WYG2LtG}Zbtu1+JH+8 z5Tdm@bpXy7_|X*t6XX{ec0ODavLfD}YlX5ck`AcEcs zLK+aPH8;{;a1%*mRT=d!pfiIXb)vZ$@UJvP7f7v5VA)-eA5na$1PoFS5a{=|s`R{*7^XQi}K640hB7!arnV=zYTz6BC2ekU*Tk0=Z^tH2$n}cTQm6 zljASx@3O&p@xRuP4erT7kIZd>y*>pW9DzH6f&R|e5%K5wbNyfv*VhA2)lBOEml8=^ zLl*Nd?@kd#_ug-Qb8CBpJZbKZ#KZS}te*A);C9KU@U9F_pDDid)u%}4YX26JkKEah z16-q`gL#4ZWOVR=$ld7JfN*gk2?N5T(f3|^T~z{B04Mz)6zVhmAaHx1e{ME8zr!0o zd(8u0c437e-*&Yo_FBimfh_=V9l+F>RqLCWn}AbCfcgZW_cx$w0&tlR&=*^?3)Hya(1O{)zU`0H#^+N5TydJwZGG zsvG+jw8uvS{(TRh}?jpHwd59l)n+Z7W3crhh9f;-XObO8NP#e zxH7&m9lPJB`rPcr0fdwK?0rT*h;OA}PZU3~pS$S*ioMvh;k*ELEsT{Lz}VX)yqoin z_<(lAF+UMS!0-j(oowVY{~oSjr+**&%ZoqJChjW#Qu~excUSBb|CjwyKOi*QetZw$ z*z4&0b?CQv^41c!=U;B_{p%I+!p8nc=jZ7YHgHQ2U~q?V^YF6tk$k82IIG!x%@u3$?Bcfes^;vYA>=?B1q{qFmfOa!?v}#=cA&4SkL=Hdi zyK7FNUqhsQrZ#5hS*b(8uE&z_VN7s|W*ZDj|3RMvo9iZv|{Avo-; z;#sPHazDPWlPQ68#^~=^IJ>PW#`mx&$6E$V$lRJ+=5dla-3Bx++I+c(y~^F;L^sJPVluY!tP;vg(hX?b z{+z?rLT>vz7%`Y4dP5fURUpU*z|IPrtpzTGmcAg@x@H_u_PAxs%bf!HNmUI4siNf+OG?nR#x>%*oqf})-|>}k zBT^#zul{dHMYQs1QHogJ);Y3ACf_+1n=trZpIXo<_s~&F7`NvyobTepe)@yb3=(5> zsf7N$Ij-Cb6rww8DHA?;L+OxX*ZUk8Xn@F|%lVY}QQpb&!AYKP+3`f_%z#NO4(vz*R^g=)njT;c z-fkHbZ+&JS2MZBfpE-%T>_Fc(CXu#YVXuYxo!=9-bih@>{f(g{Ql-pO3m`I1;&b7F z>i!|A&-aUKQxgO({|wn<3LA=9slE&OOM1~8NJ|uHLd@4oNaIcRvtVRh3B*Oc%V8Gq zbJ9Ya3Gp6qb?~jH+c%Y9+IlwUosiY-&Z>$E_^K@78VG=s#L-4mz`8 z$qIrmNFrB=Q^hCyUpP)cpXa9=GOt8DMGClsuS~R-JdM6_f^k{5V7nf+kcl;NF^2N{ zR~wE9LwHCB{y@wxIKhAHQ@wi%W?UZu6Vxm6Wq7lUr+E!*gPIbpo@pK`M!T%ohfaT$ zVmc4#`*v#pTwmUe4c1%!kHa*W&Wht;^(M?H(NjUQ8XCvt8;F7**5|e*3|9+&IBHJH zI9~)H=0=dvwd_z*sDXk60JN9Dz88GpsC%V(U&C*0+CcqF|I(oFMs3B_vgfY-6PF)m4R{bAQ8l~=+`_`L(#3z1~% z<#|j}{Fbc0U6W79Fr>$c4s6gnE?2f`-L;N}iDR#V z3n|mSRq@W`?ns(Dt1%YMH1@@j;bCh@mwow!k5)^OeJ1X&gHEE&gy+41prw3(4(abtMxi(iF{nGMxfuxzluIwdpto;~0EhvI1yyBu>*Pu)aV5GDB zqxB&_VmLDMAUv5)|zM7$c{hu={M8kZAtP z?pKXzQyXu4RA?+T2FT z;Vj0j60>M}gvgdRyeEgR-x|y-^gvnNw>-&4;>7E`6vvZoIqO8-%Bgu2hC0TAGtDr` z7&wx#q(Mw1qwyYx<_*q8%UfdA{(ocaoPslfx@{ZVwr$(CZQFJ_cG9tJckHBN+qTUw z&gs8S-CMVAorm+f_xrBeYt1=EoaY~5N&^bJVTmLh*-qBe)x$wkz0zHB>h?#Z%$NlN z;K5TOhAgeY$zKpX1gZCc_`%-|F){MuGs`4{H1~h!ke{H3#c(LBiiAs~S#vzi=WRXl zSOUUueuF`>Vbgj$Bk-dQN(?ADvv$C;b;xDXGX>!W*)9O|31kS_4ywuZj~08YQCiqP zZ{Y8PB>mtnme$T}dM)Y~NTe!MJl>d5KfIy{=ETVf8cm+hT~d?05}hw)xGV*O?_fSv ziQU8WUM9`hH}HHI3QBYQMevHnTyr8Mded6ZyK|3pPoB(6v9qJMd_Gt3oAm20@Jgm5 z*;3^dqGUi(HnqFjs6V9CYd!YDQ9^n|T&*tWIAJ9o*f9`(f`e>=MmN#&{4nx%WD0MM z%0Stx{#Ja+N1&N_-&2?j?cl*tniS3Sc~3DSx~Spe*y0HH zA+DWF(nY+ceE91t0TgzPxmqe8-ZvrCu9!hyt}IqpM4)bwk8d4dT#yD*kWRYkp;^Ym zhQr0J3}m*-b8g4fGfMm6sb~nqRO0-GIg?W+t_x{{-)70{|3UBFj5gi%3i4BPpwr_^ zs&Ey0r)UKd36pZ?boW@zl7wxd#VO?CMGBdsos3?tydFyQoK%tA`#{F)ib4f$l;BMH z1xORf657!Z#IsM@{*ZnDKCd$@1{$l{!cM`-$Z)uxQr+Q!b)S!m8EQg9-nk!!u?TJ$ zVrPzzPxr3MaP5%syc^E;SULhae-85#a`B^Qslm5Fnf0_w`xqWYSFmm!7opy#%+CPw zvBTeIUdF#{d_Psk?phE!<8b$!&jh*5mnVQ^H9(s^!LC>G-<=&~uF#VRe6_?83Lzl5 zwS>1eoz?vP3SbDjvNsejYgk$F8*l-5n7LpyB8y$VMm${61R!<-{aa6_cbRQiG#Alr z)F1DEj;`jK5Z@_CmfFZP^DYV`STHTg~vZFy~dP z_5>4^BrJ$<;(g$*?V7exAiR#xJF@LX$;!d_Mw$6B5d?)^@5v4O9qcO9v8VEk{bN~~ z+h)zbKO0S8i4YV5rCG3Z#zCUA<|1{+AKPg;O<={TuLYDnbDt5O8j6ZYIr^!*#zVn-Hoh4=Rml#9+DpXnT!!)yC8^PhY! z$SgZiPYd6fMJZ>fwj;~u)*rPs>#$5El3MfHnWLEgKD@GE_&gNZo9R@K9c#a&LxF^* zv**iou<$L;;l}h=yf8}o+4M)Y%!}tI^=AKiWFQ}=rU48#&rWg=sJ``ks3!+Cf5e?c zaX`Cx7Hngqq*B)Onh)ZQSZBbu1rnEmJ>bpYyq(TUw0fYppBkLm+o}yQmbj^^A;@n$ zE@%^K5&cPJTIitxmH2at@LB(q!=osxDOG#?Fc?e~<3iSJYqsDUo&t{}kcM9}r=P5I zDkReBa$#O(B|L?_aVrjCJuO03r1 zm;#P9=5HK=$Xw{#n3v_S>r8dZEVs8r3=1n5X~nyWz87q zjn&M{Y0mj;_KPA04fx*4;$MeSZ3t4F5w>Pl2h%ild&26{`@~1&w6XKpV&?1+ANRh@ zbk#Q~$0O_XyNb86?TT7(T)v^CIG?~BaWY+9L4s{luHckf|$MiboPtDq9c zd}$ow!-&Y;#c3!Qop zxqTiYl*V9@z*_yl8%xs4O_?7797@5C$D~N5hdG7c2wm{!2{oSgHV$!JzsBGxHFy36h`( zF6C25wiuJCwtq9xV46YyN)q1`y0SN3H47Vf;eA~Md^4seV%~IYZ@;y*Zsii&i8F^0 z9K(2)wJas6%GonxVZ+q8ihq<6KSu%)Yy(U!S={l&w?yK@>HDSqK=m|$v!=DrmLE~N zjvpLR=DWK$9U1;ABbQiKj6kg-!O3_u{Jp~|Husmr>?kEy*^f_13O5?zaSGsrGmCwY|Z8Klm#hh8#t8So{0tD^HO1ibc#fX%PP9>!UgoO zU3$s(Wb)<>kj<5Q8D1C@5;>bSUm^lzbT|_GNL{7}ApDe+ z^EjwmQhs((XIZQgY!1QbyR!1ZgFOB(j->(sdQ;yIfz^@cA!WxwdqAmZU&ITAy?m7k zC7nP_r*Keg=JGS8zPK@8_;7vpTL*2PS-V!3bp#xF0zZB~o!Kt((#=PAnjtR1>lNB{ z@n`RDIZr$10|vJ_p$*KbT`uG2u?TUA3G8+KDFe$EEorqp_Qw+|-(+w|YdEvppjh@I zM{X4QeU`6PK(dS9DJM1BVPfRtsxi}!;-yOQ?{)M=94GeJq%9`NYa1d_JtO$7GDMdT z;I0dQ`_33m)Eo^_H@%x0UVk06ay<3qMKO1Fd7~&GXKuNQN>O|E??m2!bJ5lQ<=biQ zYv>o9V)V}sFDW+1%!@_JUo9>-Q(|wAP3hw04ezvXjd!F?rQ`I4Ty0ap3y*gVUJoDT z#;&`o1dZlXQSpjfEcLUAyeA8)PSBjwX@~?hVATS!4HQ z>xm6|iL_DU(YLV*b~zVC3^B^59O!dQ*{#(64cP%m8$_@`b+3G4fR1+lu=_bbo`Jop zY+Sr#4;}utCD%0>s^w8wV%GWu=8!Opd8I3IoU!8K{Fq>5c<7%+Jk{**E`5WVBjpo` zykoq~1;b#nv>M}0Mof1~Bnjz40__^@gddLdf@(~5V&JO?ehu>4thRo_+HYvi2JK-P zd;(9Cz~P=}ZqcT{odij3c!9^=L7Q`D4Cm=vT7i<{CXu*4SpMNzMz;x**Ax$~0TySb zKT%J6gkcgGU&Lu63mB<8(7*nz|ldt#JK3C92vFJ61l1zHu(r?abSR>Ms7$)h`g zagQL;Q#ka-;J&>L&)H^sPR0V3;jk)fg4>iqQ1j?sXJsct^UWAZuadHW`lPr1^9CEu z_sMW^QL<09V#uF&o?%Zmn?cc1jv%>ri<{8wa(mL!UhH~F?8lXV-d7WcMxH7F3~qcj zbSyS6LVQpAE&5jrIkum^#B_^!d-X;4+MfJm!@fJ0F}fMdB^AVsR~aJLNoJ1oKdmR% zIKC3)kjhbg8gtZMQk#@BSjHFeAsPA!abJB<$a8cV7!02re?3pUv*=i!$VapGRq}^K zS45;rDF^SG-mG&2`P~+LB3DuBNgy+p^De#ojTnu#-dT= zZ!lwawMAXTo0XN$@eSQv$1u9Le%JO1KBnT*%j2MfhG{jw#OM;UWIQVUAS8;%QPQPi z;d#ue+h}I^T0$a21@*k1HPi1ix9BntVQd+RcMmT5pu_yZ_n@wJeWns8aL@p{k7^#T zJ5mBf4_>;XgGBX!KV!|(@)tLCALHpE5;`U?<`IAStEyZFFEl79M34U%UxhuEd8ye8 zNKOWb`VQ+|oJNPMMh=|+B~1n?#v_o=Vnj)dM`jWJ_C(YG#