diff --git a/Makefile b/Makefile index a38e14e..35009e0 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,9 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -SRC = *.c CFLAGS = -Wall -Ofast all: SOCgen SOCadmissible -%: %.c +%: src/%.c gcc $(CFLAGS) -o $@ $^ diff --git a/README.md b/README.md index af79e04..6c898d2 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ This integer is interpreted as a vector of bits, where each bit specifies the ab Since we consider simple directed graphs only (no self-loops), there are `n(n-1)` possible directed edges, where `n` is the number of nodes. This means that the largest number of nodes possible is limited by `n=8`. -While the SOCs generated by `SOCgen` satisfy some degree-order (see function `isdegreeordered(...)` in [SOCgen.c](./SOCgen.c)), `SOCgen` does not perform graph-isomorphism tests, and may output multiple isomorphic graphs. +While the SOCs generated by `SOCgen` satisfy some degree-order (see function `isdegreeordered(...)` in [SOCgen.c](./src/SOCgen.c)), `SOCgen` does not perform graph-isomorphism tests, and may output multiple isomorphic graphs. ## License -[GPL-3.0-or-later](./LICENSES/GPL-3.0-or-later.txt) \ No newline at end of file +[GPL-3.0-or-later](./LICENSES/GPL-3.0-or-later.txt) diff --git a/src/SOCadmissible.c b/src/SOCadmissible.c new file mode 100644 index 0000000..424f94a --- /dev/null +++ b/src/SOCadmissible.c @@ -0,0 +1,404 @@ +// SPDX-FileCopyrightText: 2023 Ämin Baumeler and Eleftherios-Ermis Tselentis +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +/*** + * Read graphs from file. + * Input `filename': string + * Input `cnt': pointer to int + * Input `maxdim': pointer to int + * Output: pointer to graph data + * + * This function sets `cnt' to the total number of graphs read from `filename', + * sets `maxdim' to the dimension (number of nodes) of the largest graph, + * and returns the graph data. Graph data is structured as follows: + * {dimension graph 1 | (0,0) | (0,1) | ... | (1,0) | (1,1) | ... | dimension graph 2 | ...} + * The ith graph is at position i*(maxdim*maxdim + 1). + ***/ +int* read_graphs(char* filename, int *cnt, int *maxdim) { + // Open file + FILE *fp; + fp = fopen(filename, "r"); + if(fp == NULL) + return NULL; + // Count number of lines, allocate space and initiate counter + char ch; + int n = 0; + *cnt = 0; // Total number of graphs + *maxdim = 0; // Maximum number of vertices + while(!feof(fp)) { + ch = fgetc(fp); + switch(ch) { + case '\n': + (*cnt)++; + n = 0; + break; + case '{': + case '}': + *maxdim = (n > *maxdim) ? n : *maxdim; + n = 0; + break; + case '0': + case '1': + n++; + break; + case ',': + case ' ': + case 0xffffffff: + break; + default: + return NULL; // Format error + } + } + rewind(fp); // Return to start of file + + // Allocate space for graph data + const int gdatasize = *maxdim * *maxdim + 1; // Size allocation per graph + const int arraysize = (1+ *cnt) * gdatasize; // Total space to allocate + int *graphs = (int*)malloc(sizeof(int)*arraysize); // Allocation + if(!graphs) { + fprintf(stderr, "ERROR: Could not allocate enough memory to store the graphs.\n"); + return NULL; + } + // Parse file + int col = 0; // Column number + int row = 0; // Row number + int i = 0; // Current graph index + n = -1; // Graph dimension + int j = 0; // Running index + while ((ch = fgetc(fp)) != EOF) { + // New line encountered + if(ch == '\n') { + if(row != n) { + fprintf(stderr, "ERROR: File not properly formatted.\n"); + return NULL; + } + graphs[i*gdatasize] = n; + i++; + j = 0; + col = 0; + row = 0; + n = -1; + // Matrix entry encountered + } else if(ch == '0' || ch == '1') { + int x = ch - '0'; + graphs[i*gdatasize + 1 + j] = x; + col++; + j++; + // End-of-row or end-of-matrix encountered + } else if(ch == '}') { + if(n == -1) { + n = col; + } else if(col > 0 && n != col) { + fprintf(stderr, "ERROR: File not properly formatted.\n"); + return NULL; + } + // End of row + if(col != 0) { + row++; + col = 0; + } + } + } + // Close file + fclose(fp); + return graphs; +} + +/*** + * Increase intervention counter by one. + * Returns 0 if we run out of interventions, and 1 otherwise. + ***/ +int nextintervention(int n, int *interventionslen, int *interventionidx) { + for(int i=0; i 0) { + x /= 10; + res++; + } + return res; +} + +/*** + * Verify the admissibility of the graphs + * Go through all graphs and test them + * `startidx': Index from which to start, assumed to be non-negative + * `len': Number of graphs to test, assumed to be non-negative and such that startidx+len does not exceed the length of the `graphs' array + * `maxn': Max. number of nodes + * `graphs': Pointer to graph structure + * + * Return 0 if test succeeds, else return graphidx+1 of failed graph (this is the line number). + * Return -1 on error. + ***/ +int test_graphs(int startidx, int len, int maxn, const int *graphs) { + const int gdatasize = maxn * maxn + 1; // Size allocation per graph + const int idatasize = (maxn+1)*(maxn+1)*2; // Max number of interventions per party + int range[maxn+1]; // Range of intervention + int interventions[maxn*idatasize]; // Hold all interventions per party {f_0(0), f_0(1), g_0(0), g_0(1), ... , f_1(0), f_1(1), ...} + int interventionslen[maxn]; // Number of interventions per party + int interventionidx[maxn]; // Current intervention executed + int parents[maxn*maxn]; // List all parents per party + int parentslen[maxn]; // Number of parents per party + int children[maxn*maxn]; // List all children per party + int childrenlen[maxn]; // Numer of children per party + const int padlen = ceillog10(len); + time_t t0 = time(NULL); + const time_t tstart = t0; + for(int graphidx=startidx; graphidx t0) { + const int count = graphidx - startidx + 1; + const float percentage = 100*(float)count/(float)len; + const int deltat = t1-tstart; + const float rate = (float)count/(float)deltat; + fprintf(stderr, "\r%6.2f%% %*d/%d (%*.2f graphs/s in %d seconds; current line %d)",percentage,padlen,count,len,padlen+3,rate,deltat,graphidx+1); + t0 = t1; + } + // Test graph with index `graphidx' + // `n' is the order of this graph + const int n = graphs[graphidx*gdatasize]; + // Get parents and children + // Adj matrix is such that (row,col) = 1 <=> edge from row to col + for(int i=0; i= 3) + START = atoi(argv[2]); + if(argc >= 4) { + if(argv[3][0] == '+') + NUM = atoi(argv[3]+1); + else + NUM = atoi(argv[3])-START+1; + } + if(!(argc >= 2 && (argc < 3 || START > 0) && (argc < 4 || NUM > 0) && argc <= 4)) { + fprintf(stderr, "Usage: %s [ [ | +]]\n", argv[0]); + fprintf(stderr, " File name with adjacency matrices of simple directed graphs\n"); + fprintf(stderr, " Verify graphs starting from line `startline'\n"); + fprintf(stderr, " Verify graphs up to and including line `endline'\n"); + fprintf(stderr, " + Verify `count' number of graphs\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "[FILE FORMAT]\n"); + fprintf(stderr, " Each line in `filename' must contain the adjacency matrix of a simple directed graph in the format\n"); + fprintf(stderr, " {{a00,a01,...},{a10,a11,...},...} where aij=1 if and only if the graph has the edge i -> j\n"); + fprintf(stderr, " The file `filename' may contain graphs with different order (number of vertices)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "This program verifies the admissibility of simple directed graphs.\n"); + return -1; + } + + // Read Graphs from File + int graphcount = 0; + int maxdimension = -1; + const int *graphs = read_graphs(argv[1], &graphcount, &maxdimension); + if(graphs == NULL) { + fprintf(stderr, "ERROR: Could not read graphs from file `%s'.\n", argv[1]); + return -1; + } + const int startidx = START ? START-1 : 0; // Index of graph to start verifying + const int tot = NUM ? NUM : graphcount-startidx; // Number of graphs to be verified + if(startidx >= graphcount || tot <= 0 || tot+startidx-1 >= graphcount) { + fprintf(stderr, "ERROR: Lines to be verified are out of range\n"); + return -1; + } + + // Test `tot' graphs, starting from index `startidx' + printf("Verifying the admissibility of %d graphs in the file `%s' (line %d to line %d)\n", tot, argv[1], startidx+1, startidx+tot); + const int result = test_graphs(startidx, tot, maxdimension, graphs); + if (result == 0) + printf("These graphs are admissible\n"); + else if(result == -1) + fprintf(stderr, "ERROR: Something went wrong\n"); + else + printf("The function alpha does not represent the fixed point, or the graph on line %d is inadmissible\n", result); + return result; +} diff --git a/src/SOCgen.c b/src/SOCgen.c new file mode 100644 index 0000000..6e53bcc --- /dev/null +++ b/src/SOCgen.c @@ -0,0 +1,519 @@ +// SPDX-FileCopyrightText: 2023 Ämin Baumeler and Eleftherios-Ermis Tselentis +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +/*** + * Print the graph's adjacency matrix + ***/ +void dumpgraph(int n, const int *children, const int *childrenlen) { + int adj[n*n]; + for(int i=0; i path[k]) { + smallest = path[k]; + smallestidx = k; + } + } + // Now, copy cycle to `cycle' starting from the smallest + int j=0; // This is the index on where to write on the array `cycle' + for(int i=smallestidx;; i++) { + if(j==*cyclelen) break; // Stop filling in, if we reached the end + if(i==pathlen-1) i = startidx; // If we reach the end of the path, jump to the start + cycle[j] = path[i]; // Write at position `j' the element from the cycle on the path at position `i`' + j++; // Increase index to write on `cycle' + } +} + +/*** + * Return 1 if the cycle is not in `cycles', otherwise 0. + ***/ +int cycleisnew(int n, const int *c, int clen, const int *cycles, const int *cyclescnt) { + const int cnt = cyclescnt[clen]; + const int bs = blocksize(n); + for(int i=0; i parentslen[i+1]) { + return 0; + } else if(parentslen[i] == parentslen[i+1]) { + if(childrenlen[i] > childrenlen[i+1]) { + return 0; + } else if(childrenlen[i] == childrenlen[i+1]) { + if(grandparentslen(i,parents,parentslen) > grandparentslen(i+1,parents,parentslen)) { + return 0; + } else if(grandparentslen(i,parents,parentslen) == grandparentslen(i+1,parents,parentslen)) { + if(grandchildrenlen(i,children,childrenlen) > grandchildrenlen(i+1,children,childrenlen)) { + return 0; + } + } + } + } + } + return 1; +} + +/* Random nubers */ +#if RAND_MAX/256 >= 0xFFFFFFFFFFFFFF +#define LOOP_COUNT 1 +#elif RAND_MAX/256 >= 0xFFFFFF +#define LOOP_COUNT 2 +#elif RAND_MAX/256 >= 0x3FFFF +#define LOOP_COUNT 3 +#elif RAND_MAX/256 >= 0x1FF +#define LOOP_COUNT 4 +#else +#define LOOP_COUNT 5 +#endif + +u_int64_t rand_uint64(unsigned long long max) { + u_int64_t r = 0; + for (int i=LOOP_COUNT; i > 0; i--) { + r = r*(RAND_MAX + (u_int64_t)1) + rand(); + } + return r%max; +} +/* End of random numbers */ + +int main(int argc, char *argv[]) { + // Parse command-line arguments + int n = -1; + int NONDAGONLY = 0; + int NOSINK = 0; + int NOSOURCE = 0; + int RANDOM = 0; + int UNKOPTION = 0; + for(int i=1; i [-r ] [FILTER ...]\n", argv[0]); + fprintf(stderr, " -n Generate SOCs with `order' connected nodes\n"); + fprintf(stderr, " -r Pick directed graphs at random, and exit after having found `num' SOCs\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "[FILTER] Consider only simple directed graphs ...\n"); + fprintf(stderr, " -c ... that are cyclic (i.e., not DAGs)\n"); + fprintf(stderr, " --no-sink ... without sink nodes (this logically implies -c)\n"); + fprintf(stderr, " --no-source ... without source nodes (also this logically implies -c)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "This program prints the found SOCs as adjacency matrices to stdout.\n"); + fprintf(stderr, "To exclude (some) of the isomorphic SOCs, it uses a degree-order filter.\n"); + return -1; + } + // EO Parse command-line arguments + // Setup datastructures + int parents[n*n]; // Each node i can have at most n-1 parents. These are listed at parents[n*i+k] for 0<=k64) { + fprintf(stderr, "Too many graphs to enumarate with an unsiged long long integer (number of node pairs = %d)\n", m); + return -1; + } + const unsigned long long max = 1L << m; // Largest `graphnumber' + const int padlen = (int)((float)m/3.322)+1; // Convert log 2 to log 10 + int len = 0; + time_t t0 = time(NULL); + time_t t = time(NULL); + srand((unsigned) time(&t)); + // EO Initiate graph enumeration + fprintf(stderr, "Generating SOCs with %d nodes\n", n); + if(RANDOM) fprintf(stderr, " Picking graphs at random\n"); + if(NONDAGONLY) fprintf(stderr, " Filter: Omitting DAGs\n"); + if(NOSINK) fprintf(stderr, " Filter: Omitting graphs with sink nodes\n"); + if(NOSOURCE) fprintf(stderr, " Filter: Omitting graphs with source nodes\n"); + unsigned long long graphnumber = 0; // We will always omit the graph with `graphnumber' 0; it is unintersting anyway + unsigned long long graphschecked = 0; + for(;graphnumber < max && (!RANDOM || len < RANDOM);) { + const time_t now = time(NULL); + // Break if we exhaustively check all graphs or if we found enough SOCs at random + //if(graphnumber >= max || (RANDOM && len >= RANDOM)) + // break; + // Print status every second + if(now > t) { + t = now; + const int deltat = now - t0; + const float rate = (float)graphschecked/(float)deltat; + const float SOCrate = (float)len/(float)deltat; + const float percentage = (RANDOM==0) ? 100*(float)graphnumber/(float)max : 100*(float)len/(float)RANDOM; + const float ETC = (RANDOM==0) ? ((float)(max - graphschecked)/(rate))/3600 : ((float)(RANDOM - len)/(SOCrate))/3600; + fprintf(stderr, "\r%6.2f%% %*llu/%llu (%i SOCs found, %d seconds, %*.2f graphs/s, %*.2f SOCs/s, %*.2fh estimated time left)",percentage,padlen,graphnumber,max,len,deltat,padlen+3,rate,padlen+3,SOCrate,padlen,ETC); + fflush(stderr); + } + // Convert graph index `grpahnumber' to the lists parents, children, parentslen, childrenlen + graphnrtolists(n, graphnumber, parents, parentslen, children, childrenlen); + // Increase checked counter and prepare for next iteration + graphschecked++; + if(RANDOM) + graphnumber = rand_uint64(max); + else + graphnumber++; + // EO Increase checked counter and prepare for next iteration + // If enabled, compare against degree options + if(NOSINK || NOSOURCE) { + // Get some degree properties + int minindegree = n; + int minoutdegree = n; + for(int node=0; node parentslen[node]) + minindegree = parentslen[node]; + if(minoutdegree > childrenlen[node]) + minoutdegree = childrenlen[node]; + } + if(NOSINK && minoutdegree == 0) continue; + if(NOSOURCE && minindegree == 0) continue; + } + // Check whether this graph is canonical or not; continue if graph has not proper ordering of degress (there is an isomorphic graphs to this one that has been or will be checked) + if(!isdegreeordered(n, parents, parentslen, children, childrenlen)) continue; + // Ignore graphs that are not connected + if(!isconnected(n, parents, parentslen, children, childrenlen)) continue; + // Find cycles + const int num_cycles = find_cycles(cycles, cyclescnt, n, children, childrenlen); + // If enabled, ignore DAGs + if(NONDAGONLY && num_cycles > 0) continue; + // Test the Siblings-On-Cycles property + // A DAG is trivially a SOC + if(num_cycles > 0 && !gissoc(n, parents, parentslen, children, childrenlen, cycles, cyclescnt)) continue; + // We have found a SOC + len++; + dumpgraph(n, children, childrenlen); + } + const int deltat = time(NULL) - t0; + const float rate = (deltat == 0) ? graphschecked : (float)graphschecked/(float)deltat; + const float SOCrate = (deltat == 0) ? len : (float)len/(float)deltat; + fprintf(stderr, "\r%6.2f%% %*llu/%llu (%i SOCs found, %d seconds, %*.2f graphs/s, %*.2f SOCs/s, %*.2fh estimated time left)",100.00,padlen,graphnumber,max,len,deltat,padlen+3,rate,padlen+3,SOCrate,padlen,0.00); + fprintf(stderr, "\nFound %d SOCs in %d seconds\n", len,deltat); + // Free manually allocated memory + free(cycles); + return 0; +}