extened Makefile, added ./tools/removeiso.py, added option --graphviz, readme Makefile

This commit is contained in:
Ämin on ThinkPad 2023-09-07 10:03:10 +02:00
parent 6939bd5fa9
commit ab5a90dc38
6 changed files with 139 additions and 18 deletions

View File

@ -6,5 +6,8 @@ CFLAGS = -Wall -Ofast
all: SOCgen SOCadmissible SOCgraphviz all: SOCgen SOCadmissible SOCgraphviz
clean:
rm -v SOCgen SOCadmissible SOCgraphviz
%: src/%.c %: src/%.c
gcc $(CFLAGS) -o $@ $^ gcc $(CFLAGS) -o $@ $^

View File

@ -7,11 +7,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
# SOC Observation Code # SOC Observation Code
## Description ## Description
SOC stands for **S**OC **O**bservation **C**ode, and is composed of three C programs: SOC stands for **S**OC **O**bservation **C**ode, and is composed of three C programs and a python script:
- One, `SOCgen`, to generate SOC graphs (here, SOC stands for Siblings-on-Cycles), - One, `SOCgen`, to generate SOC graphs (here, SOC stands for Siblings-on-Cycles),
- and another, `SOCadmissible`, to verify the admissibility of these graphs as quantum causal structures, - and another, `SOCadmissible`, to verify the admissibility of these graphs as quantum causal structures,
- and the last, `SOCgraphviz`, to translate adjancency matrices into the [Graphviz language](https://graphviz.org/). - and the last, `SOCgraphviz`, to translate adjacency matrices into the [Graphviz language](https://graphviz.org/).
- The python script `/tools/removeiso.py` removes isomorphic graphs.
The first two programs are used in support of Conjecture 1 in the article [Admissible Causal Structures and Correlations, arXiv:2210.12796 \[quant-ph\]](https://arxiv.org/abs/2210.12796). The first two programs are used in support of Conjecture 1 in the article [Admissible Causal Structures and Correlations, arXiv:2210.12796 \[quant-ph\]](https://arxiv.org/abs/2210.12796).
@ -31,16 +32,17 @@ To display help and exit, run the respective program without command-line argume
### SOCgen ### SOCgen
``` ```
$ ./SOCgen $ ./SOCgen
Usage: ./SOCgen -n <order> [-r <num> ] [FILTER ...] Usage: ./SOCgen -n <order> [-r <num>] [--grpahviz] [FILTER ...]
-n <order> Generate SOCs with `order' connected nodes -n <order> Generate SOCs with `order' connected nodes
-r <num> Pick directed graphs at random, and exit after having found `num' SOCs -r <num> Pick directed graphs at random, and exit after having found `num' SOCs
--graphviz Output SOCs in Graphviz format, arcs of common parents are highlighted
[FILTER] Consider only simple directed graphs ... [FILTER] Consider only simple directed graphs ...
-c ... that are cyclic (i.e., not DAGs) -c ... that are cyclic (i.e., not DAGs)
--no-sink ... without sink nodes (this logically implies -c) --no-sink ... without sink nodes (this logically implies -c)
--no-source ... without source nodes (also this logically implies -c) --no-source ... without source nodes (also this logically implies -c)
This program prints the found SOCs as adjacency matrices to stdout. This program prints the found SOCs as adjacency matrices to stdout, unless --grpahviz has been specified.
To exclude (some) of the isomorphic SOCs, it uses a degree-order filter. To exclude (some) of the isomorphic SOCs, it uses a degree-order filter.
``` ```
@ -75,8 +77,17 @@ Usage: ./SOCgraphviz <filename>
This program translates to adjacency matrices into the Graphviz format, and prints them to stdout. This program translates to adjacency matrices into the Graphviz format, and prints them to stdout.
``` ```
### removeiso.py
```
$ ./tools/removeiso.py
Usage: ./tools/removeiso.py -f <filename>
Filters out the isomorphic graphs in `filename'.
```
This script requires [NumPy](https://numpy.org/) and [NetworkX](https://networkx.org/).
### Examples ### Examples
To generate all SOCs with three nodes, and save them in the file `3.soc`, run: To generate all SOCs with three nodes, and save them in the file `3.soc`, you may run:
``` ```
$ ./SOCgen -n 3 > 3.soc $ ./SOCgen -n 3 > 3.soc
Generating SOCs with 3 nodes Generating SOCs with 3 nodes
@ -93,7 +104,11 @@ These graphs are admissible
The SOCs generated can easily be displayed with the [Graphviz](https://graphviz.org/) tools: The SOCs generated can easily be displayed with the [Graphviz](https://graphviz.org/) tools:
``` ```
./SOCgraphviz 3.soc | dot | gvpack | circo -Nshape=point -Tx11 $ ./SOCgraphviz 3.soc | dot | gvpack | circo -Nshape=point -Tx11
```
also with the `--graphviz` option:
```
$ ./SOCgen -n 3 --graphviz | dot | gvpack | neato -Tx11
``` ```
or in [Wolfram Mathematica](https://www.wolfram.com/mathematica/) using: or in [Wolfram Mathematica](https://www.wolfram.com/mathematica/) using:
``` ```
@ -102,10 +117,11 @@ SOCs = DeleteDuplicatesBy[SOCs, CanonicalGraph];
SOCs SOCs
``` ```
To generate all SOCs with four nodes, and to display them using Graphviz, you may run: To generate all SOCs with four nodes, save them in the file `4.soc`, remove isomorphic ones (as stated below, `SOCgen` only rudimentary checks for isomorphic graphs), and to display them using Graphviz, you may run:
``` ```
./SOCgen -n 4 | ./SOCgraphviz /dev/stdin | dot | gvpack | circo -Nshape=point -Tx11 $ ./SOCgen -n 4 | tee 4.soc | ./tools/removeiso.py -f 4.soc | ./SOCgraphviz /dev/stdin | dot | gvpack | circo -Nshape=point -Tx11
``` ```
![All SOCs with four nodes](./example.png "SOCs with four nodes") ![All SOCs with four nodes](./example.png "SOCs with four nodes")
## Limitations ## Limitations

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 560 KiB

View File

@ -130,7 +130,7 @@ int nextintervention(int n, int *interventionslen, int *interventionidx) {
* With this, we might find some fixed-points entries without the need of entering any recursion. * With this, we might find some fixed-points entries without the need of entering any recursion.
* If `party' is not in the range of a parent's intervention, then `party' will definitely receive a 0. * If `party' is not in the range of a parent's intervention, then `party' will definitely receive a 0.
* If `party' has no parents, then `party' will definitely receive a 1. * If `party' has no parents, then `party' will definitely receive a 1.
* If the fixed-point value cannot be inferred, return -1, otherwise return the fixed-point entry. * If the fixed-point value cannot be inferred, return -1.
***/ ***/
int alphapre(int party, const int *parents, const int *parentslen, const int *interventions, const int *interventionidx, int maxn) { int alphapre(int party, const int *parents, const int *parentslen, const int *interventions, const int *interventionidx, int maxn) {
if(parentslen[party] == 0) if(parentslen[party] == 0)

View File

@ -31,6 +31,24 @@ void dumpgraph(int n, const int *children, const int *childrenlen) {
printf("}\n"); printf("}\n");
} }
/***
* Print the graph as graphviz command
***/
void dumpgv(int n, int graphnr, const int *children, const int *childrenlen) {
printf("strict digraph G%d {node[shape=point];", graphnr);
for(int v=0; v<n; v++) {
for(int i=0; i<childrenlen[v]; i++) {
const int w = children[v*n+i];
printf("G%dN%d -> G%dN%d", graphnr, v, graphnr, w);
if(childrenlen[v] >= 2)
printf(" [color=\"red\"]");
printf(";");
}
}
printf("}\n");
return;
}
/*** /***
* Block size of our datastructure. * Block size of our datastructure.
***/ ***/
@ -115,7 +133,7 @@ void canonical_cycle(int n, int *cycle, int *cyclelen, const int *path, int path
const int last = path[pathlen-1]; // This is the last node const int last = path[pathlen-1]; // This is the last node
for(int k=0; k<pathlen-1; k++) { for(int k=0; k<pathlen-1; k++) {
if(path[k]==last) { // We have found the first occurance of node `last' on the path if(path[k]==last) { // We have found the first occurance of node `last' on the path
// The cycle the following part of path: path[startidx], path[startidx+1], ..., path[pathlen-2], and therefore has length pathlen-startidx-1 // The cycle is the following part of path: path[startidx], path[startidx+1], ..., path[pathlen-2], and therefore has length pathlen-startidx-1
startidx = k; startidx = k;
(*cyclelen) = pathlen-startidx-1; (*cyclelen) = pathlen-startidx-1;
pathhascycle = 1; pathhascycle = 1;
@ -387,6 +405,7 @@ int main(int argc, char *argv[]) {
int NOSINK = 0; int NOSINK = 0;
int NOSOURCE = 0; int NOSOURCE = 0;
int RANDOM = 0; int RANDOM = 0;
int GRAPHVIZ = 0;
int UNKOPTION = 0; int UNKOPTION = 0;
for(int i=1; i<argc; i++) { for(int i=1; i<argc; i++) {
if(strcmp(argv[i], "-n") == 0 && i+1 < argc) { if(strcmp(argv[i], "-n") == 0 && i+1 < argc) {
@ -401,22 +420,25 @@ int main(int argc, char *argv[]) {
NOSINK = 1; NOSINK = 1;
else if(strcmp(argv[i], "--no-source") == 0) else if(strcmp(argv[i], "--no-source") == 0)
NOSOURCE = 1; NOSOURCE = 1;
else if(strcmp(argv[i], "--graphviz") == 0)
GRAPHVIZ = 1;
else { else {
UNKOPTION = 1; UNKOPTION = 1;
break; break;
} }
} }
if(UNKOPTION || n<=1) { if(UNKOPTION || n<=1) {
fprintf(stderr, "Usage: %s -n <order> [-r <num> ] [FILTER ...]\n", argv[0]); fprintf(stderr, "Usage: %s -n <order> [-r <num>] [--grpahviz] [FILTER ...]\n", argv[0]);
fprintf(stderr, " -n <order> Generate SOCs with `order' connected nodes\n"); fprintf(stderr, " -n <order> Generate SOCs with `order' connected nodes\n");
fprintf(stderr, " -r <num> Pick directed graphs at random, and exit after having found `num' SOCs\n"); fprintf(stderr, " -r <num> Pick directed graphs at random, and exit after having found `num' SOCs\n");
fprintf(stderr, " --graphviz Output SOCs in Graphviz format, arcs of common parents are highlighted\n");
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "[FILTER] Consider only simple directed graphs ...\n"); fprintf(stderr, "[FILTER] Consider only simple directed graphs ...\n");
fprintf(stderr, " -c ... that are cyclic (i.e., not DAGs)\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-sink ... without sink nodes (this logically implies -c)\n");
fprintf(stderr, " --no-source ... without source nodes (also this logically implies -c)\n"); fprintf(stderr, " --no-source ... without source nodes (also this logically implies -c)\n");
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "This program prints the found SOCs as adjacency matrices to stdout.\n"); fprintf(stderr, "This program prints the found SOCs as adjacency matrices to stdout, unless --grpahviz has been specified.\n");
fprintf(stderr, "To exclude (some) of the isomorphic SOCs, it uses a degree-order filter.\n"); fprintf(stderr, "To exclude (some) of the isomorphic SOCs, it uses a degree-order filter.\n");
return -1; return -1;
} }
@ -440,7 +462,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Too many graphs to enumarate with an unsiged long long integer (number of node pairs = %d)\n", m); fprintf(stderr, "Too many graphs to enumarate with an unsiged long long integer (number of node pairs = %d)\n", m);
return -1; return -1;
} }
const unsigned long long max = 1L << m; // Largest `graphnumber' unsigned long long max = 1L << m; // Largest `graphnumber'
const int padlen = (int)((float)m/3.322)+1; // Convert log 2 to log 10 const int padlen = (int)((float)m/3.322)+1; // Convert log 2 to log 10
int len = 0; int len = 0;
time_t t0 = time(NULL); time_t t0 = time(NULL);
@ -506,6 +528,9 @@ int main(int argc, char *argv[]) {
if(num_cycles > 0 && !gissoc(n, parents, parentslen, children, childrenlen, cycles, cyclescnt)) continue; if(num_cycles > 0 && !gissoc(n, parents, parentslen, children, childrenlen, cycles, cyclescnt)) continue;
// We have found a SOC // We have found a SOC
len++; len++;
if(GRAPHVIZ)
dumpgv(n, graphnumber, children, childrenlen);
else
dumpgraph(n, children, childrenlen); dumpgraph(n, children, childrenlen);
} }
const int deltat = time(NULL) - t0; const int deltat = time(NULL) - t0;

77
tools/removeiso.py Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/python3
"""
SPDX-FileCopyrightText: 2023 Ämin Baumeler <amin@indyfac.ch> and Eleftherios-Ermis Tselentis <eleftheriosermis.tselentis@oeaw.ac.at>
SPDX-License-Identifier: GPL-3.0-or-later
"""
import sys
import getopt
import numpy as np
import networkx as nx
"""Parse command line arguments first."""
helpstr = """Usage: """+sys.argv[0]+""" -f <filename>
Filters out the isomorphic graphs in `filename'."""
FILENAME = False
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:")
except getopt.GetoptError:
print(helpstr, file=sys.stderr)
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print(helpstr)
sys.exit()
elif opt == '-f':
FILENAME = arg
if not FILENAME:
print(helpstr, file=sys.stderr)
sys.exit(2)
with open(FILENAME, 'rb') as f:
num_lines = sum(1 for _ in f)
print(f"Total number of digraphs is {num_lines}", file=sys.stderr)
nonisodigraphs = set()
cnt = 0
fnd = 0
with open(FILENAME, 'r') as f:
while 1:
cnt = cnt + 1
print("\r{:.2f}%".format(100*cnt/num_lines), end='', file=sys.stderr)
line = f.readline().strip('{}\n')
if not line:
break
G = nx.from_numpy_matrix(np.vectorize(int)(np.matrix([x.split(',') for x in line.split('},{')])), create_using=nx.DiGraph)
couldbeold = False
for H in nonisodigraphs:
# Returns False if graphs are definitely not isomorphic.
# True does NOT guarantee isomorphism.
if nx.faster_could_be_isomorphic(G, H):
couldbeold = True
break
if not couldbeold:
nonisodigraphs.add(G)
fnd = fnd + 1
print(f"{{{{{line}}}}}")
continue
new = True
for H in nonisodigraphs:
if nx.is_isomorphic(G, H):
new = False
break
if new:
nonisodigraphs.add(G)
fnd = fnd + 1
print(f"{{{{{line}}}}}")
print(f"\r100% Found {fnd} non-isomoric graphs", file=sys.stderr)