-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
21d5d6a
commit 2f043ac
Showing
18 changed files
with
1,065 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,32 @@ | ||
# Ignore all | ||
* | ||
|
||
# Unignore all with extensions | ||
!*.* | ||
|
||
# Unignore all dirs | ||
!*/ | ||
|
||
# Specific filetypes to ignore | ||
*.csv | ||
*.json | ||
*.pyc | ||
*.png | ||
*.sublime* | ||
*.o | ||
*.so | ||
.DS_Store | ||
|
||
# Nauty directories to ignore | ||
nauty25r9 | ||
nauty | ||
|
||
# Virtualenv directories to ignore | ||
bin | ||
include | ||
lib | ||
man | ||
share | ||
.Python | ||
pip-selfcheck.json | ||
thebigselfdualfile.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Tools for exploring graph state equivalence classes | ||
|
||
*By Sam Morley-Short* | ||
|
||
**Version 1.0** | ||
|
||
--- | ||
|
||
This repository contains a number of tools used for exploring the local complementation equivalence classes of quantum graph states. | ||
Specifically, it contains functions that: | ||
|
||
* Explores the entire LC-equivalence class, or *orbit* of a given graph state (up to isomorphism). | ||
* Detect if two graph states $\left|G\right\rangle$, $\left|G^\prime\right\rangle$ are LC-equivalent and if so returns all local unitaries $U$ such that $\left|G^\prime\right\rangle = U\left|G\right\rangle$. | ||
* Find a graphs' minimum and maximum edge representatives. | ||
* Visualize graphs and their equivalence classes | ||
|
||
Specific instructions on using these tools are given below and a glossary is also provided. | ||
|
||
N.B. this README contains latex-formatted equations. If viewing on GitHub these can be easily rendered using browser extensions such as [MathJax for GitHub](https://chrome.google.com/webstore/detail/github-with-mathjax/ioemnmodlmafdkllaclgeombjnmnbima). For mac users, [MacDown](https://macdown.uranusjr.com/) is recommended for offline viewing. | ||
|
||
## Exploring LC-equivalence classes | ||
|
||
### What is? | ||
|
||
Full exploration of a graph's local complementation class is not generally efficient. | ||
One reason for this is that within a class there may exist many isomorphs of a single graph, each produced by some unique series of LC's applied to some input graph. | ||
However, in many cases one only cares about the general structure of graphs attainable from the input graph, rather than specific labellings. | ||
|
||
By leveraging properties of graphs related to their [automorphisms](https://www.wikiwand.com/en/Graph_automorphism), our algorithm efficiently explores the class up to isomorphism. | ||
This is achieved using a recursive tree-search approach, whereby each edge that links two graphs within the class (indicating they are separated by at most one LC operation) is only traversed once, and hence the class graph | ||
|
||
### How do? | ||
|
||
The search itself is performed by the function `explore_LC_isomorphic_orbit` found in `explore_class.py`. | ||
This function takes some NetworkX graph as input and outputs a JSON format database of the graph's LC class. | ||
|
||
In practise, this is achieved by the recipe depicted in the following python script (found in `tests/test_README_LC_explore`: | ||
|
||
```python | ||
# Import Python packages | ||
import networkx as nx | ||
# Import local modules | ||
from explore_class import * | ||
|
||
# Create the input graph | ||
edges = [(0, 1), (1, 2), (2, 3), (3, 4)] | ||
graph = nx.Graph() | ||
graph.add_edges_from(edges) | ||
|
||
# Find the class graph | ||
class_graph = explore_LC_isomorphic_orbit(graph) | ||
|
||
# Export class graph to JSON file | ||
filename = 'L5_class_graph.json' | ||
export_class_graph(class_graph, filename) | ||
``` | ||
|
||
For example, the above code finds the orbit for the 5-qubit linear graph state. | ||
The output JSON file uses a standard NetworkX format so that in principle can be reloaded into NetworkX to reproduce the class graph in some other code. | ||
However, from our perspective it contains two important data structures representing the class graph's nodes and edges respectively. | ||
Firstly, the list keyed by `"nodes"` stores a list of dictionaries, each representing a node (i.e. graph) in the orbit containing the following information: | ||
|
||
* `"edges"`: The edges of the graph the node represents. | ||
* `"hash"`: A hash value of the canonically relabelled version of the graph (such that two isomorphic graphs will have the same hash value). | ||
* `"id"`: A short integer label for the graph used to define the edges. | ||
|
||
Secondly, the list keyed by `"adjacency"` gives an list in which the $i$th element is a list of dictionaries that represent each edge $(i, j)$ connected to node $i$. | ||
Each dictionary representing $(i, j)$ contains the `"id"` of node $j$ and `"equiv"`, a list of nodes in the graph $i$ that produce the same graph $j$ after LC (up to isomorphism). | ||
|
||
**Notes:** | ||
|
||
* All node labels on the input graph must be integers. If this is not the case, `int_relabel_graph` can be used to return a relabelled graph and the labelling applied. | ||
|
||
## Testing for LC-equivalence | ||
|
||
### What is? | ||
|
||
For the reduced task of testing whether two known graphs $\left|G\right\rangle$ and $\left|G^\prime\right\rangle$ are LC-equivalent, we include an implementation of an algorithm [originally presented](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.034302) by Van Den Nest, et. al. | ||
In the case that two graphs are equivalent, the algorithm also returns all local unitaries $U$ such that $\left|G^\prime\right\rangle = U\left|G\right\rangle$. | ||
|
||
### How do? | ||
|
||
LC equivalence is checked by function `are_lc_equiv` found in `is_lc_equiv.py`. The function takes as input two NetworkX graphs and outputs the tuple `(are_lc_equiv, local_ops)` which are a boolean and list of possible $U$. | ||
|
||
For example, consider the following python script (found in `tests/test_README_LC_equiv.py`): | ||
|
||
```python | ||
# Import Python packages | ||
import networkx as nx | ||
# Import local modules | ||
from is_lc_equiv import * | ||
|
||
# Create a linear 4 node graph | ||
edges = [(0, 1), (1, 2), (2, 3)] | ||
graph_a = nx.Graph() | ||
graph_a.add_edges_from(edges) | ||
|
||
# Create a 4 node ring graph | ||
edges = [(0, 1), (1, 2), (2, 3), (3, 0)] | ||
graph_b = nx.Graph() | ||
graph_b.add_edges_from(edges) | ||
|
||
# Create a 4 node ring graph | ||
edges = [(0, 2), (2, 1), (1, 3), (3, 0)] | ||
graph_c = nx.Graph() | ||
graph_c.add_edges_from(edges) | ||
|
||
# Checks equivalence between graph A and graph B | ||
is_equiv, local_us = are_lc_equiv(graph_a, graph_b) | ||
print is_equiv, local_us | ||
|
||
# Checks equivalence between graph A and graph C | ||
is_equiv, local_us = are_lc_equiv(graph_a, graph_c) | ||
print is_equiv, local_us | ||
``` | ||
|
||
which outputs the following: | ||
|
||
``` | ||
False None | ||
True [['I', 'H', 'H', 'I'], ['I', 'H', 'SH', 'S'], ['S', 'SH', 'H', 'I'], ['S', 'SH', 'SH', 'S']] | ||
``` | ||
|
||
The validity of the output can be seen by referring to Fig. 7 of the following [paper](https://arxiv.org/abs/quant-ph/0307130v7). | ||
|
||
## Dependancies | ||
|
||
This module relies on the following packages: | ||
|
||
* Nauty (found [here](http://pallini.di.uniroma1.it/)) | ||
* Pynauty-hack (found [here](https://github.com/sammorley-short/pynauty-hack)) | ||
* Various python modules: NetworkX, Numpy, pprint, abp (all installed via pip) | ||
|
||
## Glossary | ||
|
||
* **Graph state**: a quantum state $\left|G\right\rangle$ represented by some graph $G$. | ||
* **Local complementation (LC)**: the graph operation that represents taking graph states to other graph states using only local Clifford unitaries. | ||
* **Class graph**: a simple, connected and undirected graph representing the structure of an LC-equivalence class. Each node represents some graph state within the class with each edge between two nodes the application of an LC operation that takes one graph to another. | ||
* **Isomorphic**: Two graphs are isomorphic if they are the same up to relabelling. This is equivalent to saying they have the same [*canonical labelling*](https://www.wikiwand.com/en/Graph_canonization). | ||
* **Automorphism**: An automorphism of a graph is any relabelling that produces the same graph. E.g. for a ring graph with $V = \{0, \ldots, n\}$ nodes, the node relabelling $i \mapsto (i+1) \;\textrm{mod} \; |V|$ is a valid automorphism as it produces the same graph. | ||
* **NetworkX**: A useful python package for creating and manipulating graphs. On Unix systems it can be most easily installed via pip through the command `$ pip install networkx`. Documentation can be found [here](https://networkx.github.io/documentation/stable/index.html). | ||
* **ABP**: A python package for efficiently simulating and visualising quantum graph states based on Anders and Briegel's original algorithm. It can also be installed via pip, with installation instructions and documentation can be found [here](https://github.com/peteshadbolt/abp). | ||
* **Nauty**: A popular C library for finding graph auto- and isomorphisms, found [here](http://pallini.di.uniroma1.it/) which must be installed. As well as this, our code relies on a hacked version of the python wrapper `pynauty` found [here](https://github.com/sammorley-short/pynauty-hack). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Python packages | ||
import os | ||
import json | ||
import pynauty as pyn | ||
import networkx as nx | ||
import itertools as it | ||
from copy import deepcopy | ||
from networkx.readwrite import json_graph | ||
from pprint import pprint | ||
# Local modules | ||
from utils import canonical_edge_order | ||
from get_nauty import find_unique_lcs, hash_graph | ||
|
||
|
||
def local_complementation(graph, node, copy=True): | ||
""" Returns the graph for local complementation applied to node """ | ||
neighs = graph.neighbors(node) | ||
neigh_k_edges = it.combinations(neighs, 2) | ||
lc_graph = deepcopy(graph) if copy else graph | ||
for u, v in neigh_k_edges: | ||
if lc_graph.has_edge(u, v): | ||
lc_graph.remove_edge(u, v) | ||
else: | ||
lc_graph.add_edge(u, v) | ||
return lc_graph | ||
|
||
|
||
def edge_lc(graph, edge): | ||
u, v = edge | ||
edge_lc_graph = apply_lcs(graph, [u, v, u]) | ||
return edge_lc_graph | ||
|
||
|
||
def apply_lcs(graph, nodes): | ||
lc_graph = deepcopy(graph) | ||
for node in nodes: | ||
lc_graph = local_complementation(lc_graph, node, copy=False) | ||
return lc_graph | ||
|
||
|
||
def init_EC_database_dir(directory='EC_database'): | ||
""" Initialises the equivalence class database directory """ | ||
if not os.path.exists(directory): | ||
os.makedir(directory) | ||
|
||
|
||
def recursive_LC_search(class_graph, graph_label, node_equivs): | ||
""" Recursive search for exploring the isomorphic LC space of graphs """ | ||
graph = class_graph.node[graph_label]['nx_graph'] | ||
for rep_node, equiv_nodes in node_equivs.iteritems(): | ||
lc_graph = local_complementation(graph, rep_node) | ||
lc_edges = canonical_edge_order(lc_graph.edges()) | ||
try: | ||
lc_label = class_graph.member_hash_table[lc_edges] | ||
class_graph.add_edge(graph_label, lc_label, | ||
equivs=equiv_nodes) | ||
continue | ||
except KeyError: | ||
lc_label = max(class_graph.nodes()) + 1 | ||
lc_graph_hash = hash_graph(lc_graph) | ||
class_graph.add_node(lc_label, nx_graph=lc_graph, | ||
edges=lc_edges, hash=lc_graph_hash) | ||
class_graph.add_edge(graph_label, lc_label, | ||
equivs=equiv_nodes) | ||
class_graph.member_hash_table.update({lc_edges: lc_label}) | ||
lc_equivs = find_unique_lcs(lc_graph) | ||
recursive_LC_search(class_graph, lc_label, lc_equivs) | ||
return class_graph | ||
|
||
|
||
def int_relabel_graph(graph): | ||
""" | ||
Relabels graphs with tuple node names to int node names. | ||
Returns relabelled graph and node mapping applied. | ||
""" | ||
int_labels = {node: i for i, node in enumerate(graph.nodes())} | ||
int_graph = nx.relabel_nodes(graph, int_labels) | ||
return int_graph, int_labels | ||
|
||
|
||
def explore_LC_isomorphic_orbit(init_graph): | ||
""" Explores the LC equivalence class orbit up to isomorphism """ | ||
if not nx.is_connected(init_graph): | ||
raise TypeError("Initial graph must be connected.") | ||
init_graph_equivs = find_unique_lcs(init_graph) | ||
init_edges = canonical_edge_order(init_graph.edges()) | ||
init_hash = hash_graph(init_graph) | ||
class_graph = nx.Graph() | ||
class_graph.add_node(0, nx_graph=init_graph, edges=init_edges, | ||
hash=init_hash) | ||
class_graph.member_hash_table = {init_edges: 0} | ||
class_graph = recursive_LC_search(class_graph, 0, init_graph_equivs) | ||
return class_graph | ||
|
||
|
||
def export_class_graph(class_graph, filename): | ||
""" Exports class graph to JSON file """ | ||
for node, attrs in class_graph.node.iteritems(): | ||
class_graph.node[node].pop('nx_graph') | ||
class_graph_data = json_graph.adjacency_data(class_graph) | ||
with open(filename, 'w') as fp: | ||
json.dump(class_graph_data, fp) | ||
return class_graph_data | ||
|
||
|
||
if __name__ == '__main__': | ||
n = 4 | ||
init_edges = [(i, (i + 1) % n) for i in range(n)] | ||
init_graph = nx.Graph(init_edges) | ||
print init_graph.edges() | ||
class_graph = explore_LC_isomorphic_orbit(init_graph) | ||
pprint(dict(class_graph.node)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Python packages | ||
import networkx as nx | ||
import pynauty as pyn | ||
from pprint import pprint | ||
from collections import defaultdict | ||
# Local modules | ||
from utils import flatten | ||
|
||
|
||
def convert_nx_to_pyn(nx_g): | ||
""" Takes a NetworkX graph and outputs a PyNauty graph """ | ||
nodes, neighs = zip(*nx_g.adjacency()) | ||
graph_adj = {node: neighs.keys() for node, neighs in zip(nodes, neighs)} | ||
# print graph_adj | ||
n_v = len(graph_adj) | ||
pyn_g = pyn.Graph(n_v, directed=False, adjacency_dict=graph_adj) | ||
return pyn_g | ||
|
||
|
||
def hash_graph(graph): | ||
""" Returns a hash for the graph based on PyNauty's certificate fn """ | ||
pyn_g = convert_nx_to_pyn(graph) | ||
g_cert = pyn.certificate(pyn_g) | ||
return hash(g_cert) | ||
|
||
|
||
def canonical_relabel(nx_g): | ||
""" Returns isomorphic graph with canonical relabelling """ | ||
nodes, neighs = zip(*nx_g.adjacency()) | ||
pyn_g = convert_nx_to_pyn(nx_g) | ||
canon_lab = pyn.canon_label(pyn_g) | ||
canon_relab = {o_node: i_node for i_node, o_node in zip(nodes, canon_lab)} | ||
nx_g_canon = nx.relabel_nodes(nx_g, canon_relab) | ||
return nx_g_canon | ||
|
||
|
||
def find_automorphisms(nx_g): | ||
""" | ||
Takes a NetworkX graph and returns the automorphism generators | ||
""" | ||
nodes, neighs = zip(*nx_g.adjacency()) | ||
pyn_g = convert_nx_to_pyn(nx_g) | ||
gens, grp1_size, grp2_size, orbits, n_orbits = pyn.autgrp(pyn_g) | ||
am_node_maps = [{i_node: o_node for i_node, o_node in zip(nodes, gen)} | ||
for gen in gens] | ||
return nx_g, am_node_maps | ||
|
||
|
||
def find_unique_lcs(g): | ||
""" | ||
Takes a NetworkX graph and finds groups of nodes that are equivalent | ||
up to automorphism | ||
""" | ||
g, am_node_maps = find_automorphisms(g) | ||
aut_edges = flatten([am_node_map.items() for am_node_map in am_node_maps]) | ||
aut_g = nx.Graph(aut_edges) | ||
equiv_groups = [equiv for equiv in map(list, list( | ||
nx.connected_components(aut_g))) if len(equiv) > 1] | ||
all_nodes = g.nodes() | ||
node_equivs = {} | ||
for equiv_group in equiv_groups: | ||
rep_node = equiv_group[0] | ||
node_equivs.update({rep_node: equiv_group}) | ||
all_nodes = [node for node in all_nodes if node not in equiv_group] | ||
node_equivs.update({node: [node] for node in all_nodes}) | ||
# Removes any LC's that act trivially on the graph (i.e. act on d=1 nodes) | ||
node_equivs = {node: equivs for node, equivs in node_equivs.iteritems() | ||
if g.degree(node) > 1} | ||
return node_equivs | ||
|
||
|
||
# TODO: Comment functions | ||
# TODO: Package up hacked nauty for distribution | ||
|
||
|
||
if __name__ == '__main__': | ||
n = 12 | ||
edge_sets = [[(i, (i + 1) % n) for i in range(n)], | ||
[(i, (i + 1) % n) for i in range(n)] + [(1, 5)], | ||
[(i, (i + 1)) for i in range(n)] + [(2, 7)]] | ||
for edges in edge_sets: | ||
g = nx.Graph(edges) | ||
print "Edges:" | ||
pprint(list(g.edges())) | ||
# node_equivs = find_unique_lcs(g) | ||
# print "Node LC equivalences:", node_equivs | ||
# print hash_graph(g) | ||
relab_g = canonical_relabel(g) | ||
print "Relabelled edges:" | ||
pprint(list(relab_g.edges())) | ||
rr_g = canonical_relabel(relab_g) | ||
print "Relabelled edges:" | ||
pprint(list(rr_g.edges())) | ||
|
||
# g = nx.Graph([(0, 2), (0, 3), (0, 5), (1, 4), (1, 5), (3, 4), (3, 5)]) | ||
# g, g_ams = find_automorphisms(g) | ||
# print g_ams |
Oops, something went wrong.