From dfa0e0c6b8954a0e9eaa4bcd47735e50c83b11f3 Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Thu, 18 Aug 2016 12:45:47 +0100 Subject: [PATCH 1/7] Add fancy.GraphState.from_nx, which is a hack To play around with lattices --- abp/fancy.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/abp/fancy.py b/abp/fancy.py index 3178596..6468f13 100644 --- a/abp/fancy.py +++ b/abp/fancy.py @@ -21,6 +21,15 @@ class GraphState(graphstate.GraphState, networkx.Graph): except: #TODO: bad practice self.ws = None + def from_nx(self, g): + """ Clone from a networkx graph. Hacky af """ + self.adj = g.adj.copy() + self.node = g.node.copy() + # TODO: hacky af + for key, value in self.node.items(): + self.node[key]["vop"] = clifford.by_name["identity"] + + def shutdown(self): """ Close the connection to the websocket """ if not self.ws: @@ -34,6 +43,7 @@ class GraphState(graphstate.GraphState, networkx.Graph): return # Automatically perform layout if position is not provided + print self.node.values() if not all(("position" in node) for node in self.node.values()): self.layout() From 5b007f9475efeaa1e18997dda9586fcc915672bf Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Thu, 18 Aug 2016 12:46:16 +0100 Subject: [PATCH 2/7] Add a sketch of lattice generation - half way Very functional, very explicit node names - working so far --- abp/lattices.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/abp/lattices.py b/abp/lattices.py index e69de29..8d2244a 100644 --- a/abp/lattices.py +++ b/abp/lattices.py @@ -0,0 +1,44 @@ +import networkx as nx +from abp.fancy import GraphState + +def fast_union(graphs): + """ Assumes that all graphs are completely independent and uniquely labelled """ + output = nx.Graph() + output.node = dict(i for g in graphs for i in g.node.items()) + output.adj = dict(i for g in graphs for i in g.adj.items()) + return output + +def relabel(g, label): + """ Shorthand relabel """ + return nx.relabel_nodes(g, lambda x: (label, x)) + +def fuse(a, b, na, nb): + """ Deterministic fusion for testing purposes """ + psi = fast_union((a, b)) + neighbors_a, neighbors_b = psi.neighbors(na), psi.neighbors(nb) + new_edges = ((i, j) for i in neighbors_a for j in neighbors_b if i != j) + psi.add_edges_from(new_edges) + psi.remove_nodes_from((na, nb)) + return psi + +def ghz(label): + """ A 3-GHZ state """ + psi = nx.Graph(((0, 1), (1, 2))) + return relabel(psi, label) + +def microcluster(label): + """ A microcluster """ + psi = fuse(ghz(0), ghz(1), (0, 1), (1, 0)) + psi = fuse(psi, ghz(2), (1, 2), (2, 1)) + return relabel(psi, label) + +if __name__ == '__main__': + print ghz(0).nodes() + print ghz(1).nodes() + print fuse(ghz(0), ghz(1), (0, 2), (1, 0)).adj + print microcluster("pete").nodes() + + g = GraphState() + g.from_nx(microcluster("pete")) + print g.to_stabilizer() + From 30bb8ceb8ce07e4bf4f7752c6160b62899ae35e0 Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Thu, 18 Aug 2016 13:00:49 +0100 Subject: [PATCH 3/7] Don't print --- abp/fancy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/abp/fancy.py b/abp/fancy.py index 6468f13..3d7a2c3 100644 --- a/abp/fancy.py +++ b/abp/fancy.py @@ -43,7 +43,6 @@ class GraphState(graphstate.GraphState, networkx.Graph): return # Automatically perform layout if position is not provided - print self.node.values() if not all(("position" in node) for node in self.node.values()): self.layout() From 80f065c3b825200d59effc75fccab87ef3c4762b Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Thu, 18 Aug 2016 13:01:02 +0100 Subject: [PATCH 4/7] Actually make a simple unit cell --- abp/lattices.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/abp/lattices.py b/abp/lattices.py index 8d2244a..70d3c45 100644 --- a/abp/lattices.py +++ b/abp/lattices.py @@ -1,7 +1,7 @@ import networkx as nx from abp.fancy import GraphState -def fast_union(graphs): +def union(*graphs): """ Assumes that all graphs are completely independent and uniquely labelled """ output = nx.Graph() output.node = dict(i for g in graphs for i in g.node.items()) @@ -12,9 +12,8 @@ def relabel(g, label): """ Shorthand relabel """ return nx.relabel_nodes(g, lambda x: (label, x)) -def fuse(a, b, na, nb): +def fuse(psi, na, nb): """ Deterministic fusion for testing purposes """ - psi = fast_union((a, b)) neighbors_a, neighbors_b = psi.neighbors(na), psi.neighbors(nb) new_edges = ((i, j) for i in neighbors_a for j in neighbors_b if i != j) psi.add_edges_from(new_edges) @@ -28,17 +27,25 @@ def ghz(label): def microcluster(label): """ A microcluster """ - psi = fuse(ghz(0), ghz(1), (0, 1), (1, 0)) - psi = fuse(psi, ghz(2), (1, 2), (2, 1)) + psi = union(ghz(0), ghz(1), ghz(2)) + psi = fuse(psi, (0, 1), (1, 0)) + psi = fuse(psi, (1, 2), (2, 1)) return relabel(psi, label) -if __name__ == '__main__': - print ghz(0).nodes() - print ghz(1).nodes() - print fuse(ghz(0), ghz(1), (0, 2), (1, 0)).adj - print microcluster("pete").nodes() +def unit_cell(label): + """ A simple ring-like unit cell """ + psi = union(microcluster(0), microcluster(1), microcluster(2), microcluster(3)) + psi = fuse(psi, (0, (0, 2)), (1, (2, 2))) + psi = fuse(psi, (1, (0, 2)), (2, (2, 2))) + psi = fuse(psi, (2, (0, 2)), (3, (2, 2))) + psi = fuse(psi, (3, (0, 2)), (0, (2, 2))) + return psi +if __name__ == '__main__': + #print ghz(0).nodes() + #print ghz(1).nodes() + #print fuse(ghz(0), ghz(1), (0, 2), (1, 0)).adj + #print microcluster("pete").nodes() g = GraphState() - g.from_nx(microcluster("pete")) - print g.to_stabilizer() + g.from_nx(unit_cell("pete")) From 15f3e548213fafa22966fbb1c482bb7937da2ce5 Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Fri, 19 Aug 2016 12:18:18 +0100 Subject: [PATCH 5/7] Still sketching --- abp/fancy.py | 11 +++++++---- abp/lattices.py | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/abp/fancy.py b/abp/fancy.py index 3d7a2c3..25b1f87 100644 --- a/abp/fancy.py +++ b/abp/fancy.py @@ -1,6 +1,6 @@ import time, atexit, json import sys -import networkx +import networkx as nx import numpy as np import websocket from socket import error as socket_error @@ -8,9 +8,12 @@ import graphstate import clifford import util -class GraphState(graphstate.GraphState, networkx.Graph): +class GraphState(graphstate.GraphState, nx.Graph): def __init__(self, *args, **kwargs): - graphstate.GraphState.__init__(self, *args, **kwargs) + if type(args[0])==nx.Graph: + self.from_nx(args[0]) + else: + graphstate.GraphState.__init__(self, *args, **kwargs) self.connect_to_server() def connect_to_server(self, uri = "ws://localhost:5000"): @@ -59,7 +62,7 @@ class GraphState(graphstate.GraphState, networkx.Graph): def layout(self): """ Automatically lay out the graph """ - pos = networkx.spring_layout(self, dim=3, scale=np.sqrt(self.order())) + pos = nx.spring_layout(self, dim=3, scale=np.sqrt(self.order())) middle = np.average(pos.values(), axis=0) pos = {key: value - middle for key, value in pos.items()} for key, (x, y, z) in pos.items(): diff --git a/abp/lattices.py b/abp/lattices.py index 70d3c45..a1b4bb7 100644 --- a/abp/lattices.py +++ b/abp/lattices.py @@ -1,3 +1,7 @@ +""" +This is a sketch of a consistent language for defining resource states and lattices. +""" + import networkx as nx from abp.fancy import GraphState @@ -39,13 +43,19 @@ def unit_cell(label): psi = fuse(psi, (1, (0, 2)), (2, (2, 2))) psi = fuse(psi, (2, (0, 2)), (3, (2, 2))) psi = fuse(psi, (3, (0, 2)), (0, (2, 2))) - return psi + return relabel(psi, label) + +def position(node): + print node + return {} + +def annotate(g, f): + """ Annotate a graph """ + for node in g.nodes(): + g.node[node].update(f(node)) if __name__ == '__main__': - #print ghz(0).nodes() - #print ghz(1).nodes() - #print fuse(ghz(0), ghz(1), (0, 2), (1, 0)).adj - #print microcluster("pete").nodes() - g = GraphState() - g.from_nx(unit_cell("pete")) + psi = union(unit_cell((0, 0)), unit_cell((2, 0))) + annotate(psi, position) + g = GraphState(psi) From 577e9177320c39029c639d59f28894c40125209b Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Mon, 22 Aug 2016 14:42:58 +0100 Subject: [PATCH 6/7] More convenient networkx -> abp conversion Now you can write `abp.fancy.GraphState(nx.Graph())`. TODO: make this work for a non-fancy `GraphState` --- abp/fancy.py | 3 ++- tests/test_fancy.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/abp/fancy.py b/abp/fancy.py index 25b1f87..32ea3b2 100644 --- a/abp/fancy.py +++ b/abp/fancy.py @@ -10,7 +10,8 @@ import util class GraphState(graphstate.GraphState, nx.Graph): def __init__(self, *args, **kwargs): - if type(args[0])==nx.Graph: + if args and type(args[0]) == nx.Graph: + graphstate.GraphState.__init__(self) self.from_nx(args[0]) else: graphstate.GraphState.__init__(self, *args, **kwargs) diff --git a/tests/test_fancy.py b/tests/test_fancy.py index dcc6884..636eecf 100644 --- a/tests/test_fancy.py +++ b/tests/test_fancy.py @@ -5,6 +5,7 @@ from abp import clifford from abp.util import xyz from mock import simple_graph + def test_json_basic(): """ Test that we can export to JSON """ g = simple_graph() @@ -12,6 +13,7 @@ def test_json_basic(): assert "adj" in js assert "node" in js + def test_tuple_keys(): """ Test that we can use tuple-ish keys """ g = fancy.GraphState() @@ -20,17 +22,22 @@ def test_tuple_keys(): g.add_edge((1, 2, 3), "string") json.dumps(g.to_json(True)) + def networkx_test(): """ Test that fancy graph states really behave like networkx graphs """ g = fancy.GraphState() - g.add_qubit(0, position = xyz(10, 0, 0)) - g.add_qubit(1, position = xyz(1, 0, 0)) + g.add_qubit(0, position=xyz(10, 0, 0)) + g.add_qubit(1, position=xyz(1, 0, 0)) g.act_hadamard(0) g.act_hadamard(1) g.act_cz(0, 1) g.copy() - # TODO: more tests here! - - +def test_from_nx(): + """ Test that making graphs from networkx objects goes smoothly """ + g = nx.random_geometric_graph(100, 2) + psi = fancy.GraphState(g) + assert psi.node[0]["vop"] == 0 + assert len(psi.edges()) > 0 + psi.measure(0, "px", detail=True) From 05b12a7bf163441dd3e18eceed6e5556bc314fed Mon Sep 17 00:00:00 2001 From: Pete Shadbolt Date: Mon, 22 Aug 2016 15:28:34 +0100 Subject: [PATCH 7/7] Removed `fancy.GraphState.from_nx` -- "just works" Moved that functionality into `abp.GraphState`, so that you can just run `state = abp.GraphState(nx.Graph(((0,1),)))`, `state = abp.fancy.GraphState(nx.Graph(((0,1),))` or whatever. --- abp/fancy.py | 15 +-------------- abp/graphstate.py | 23 ++++++++++++++++------- tests/test_fancy.py | 2 ++ tests/test_graphstate.py | 10 ++++++++++ 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/abp/fancy.py b/abp/fancy.py index 32ea3b2..19f0a2d 100644 --- a/abp/fancy.py +++ b/abp/fancy.py @@ -10,11 +10,7 @@ import util class GraphState(graphstate.GraphState, nx.Graph): def __init__(self, *args, **kwargs): - if args and type(args[0]) == nx.Graph: - graphstate.GraphState.__init__(self) - self.from_nx(args[0]) - else: - graphstate.GraphState.__init__(self, *args, **kwargs) + graphstate.GraphState.__init__(self, *args, **kwargs) self.connect_to_server() def connect_to_server(self, uri = "ws://localhost:5000"): @@ -25,15 +21,6 @@ class GraphState(graphstate.GraphState, nx.Graph): except: #TODO: bad practice self.ws = None - def from_nx(self, g): - """ Clone from a networkx graph. Hacky af """ - self.adj = g.adj.copy() - self.node = g.node.copy() - # TODO: hacky af - for key, value in self.node.items(): - self.node[key]["vop"] = clifford.by_name["identity"] - - def shutdown(self): """ Close the connection to the websocket """ if not self.ws: diff --git a/abp/graphstate.py b/abp/graphstate.py index 703f983..5b19447 100644 --- a/abp/graphstate.py +++ b/abp/graphstate.py @@ -16,10 +16,10 @@ class GraphState(object): Internally it uses the same dictionary-of-dictionaries data structure as ``networkx``. """ - def __init__(self, nodes=[], deterministic=False, vop="identity"): + def __init__(self, data=(), deterministic=False, vop="identity"): """ Construct a ``GraphState`` - :param nodes: An iterable of nodes used to construct the graph, or an integer -- the number of nodes. + :param data: An iterable of nodes used to construct the graph, or an integer -- the number of nodes, or a ``nx.Graph``. :param deterministic: If ``True``, the behaviour of the graph is deterministic up to but not including the choice of measurement outcome. This is slightly less efficient, but useful for testing. If ``False``, the specific graph representation will sometimes be random -- of course, all possible representations still map to the same state vector. :param vop: The default VOP for new qubits. Setting ``vop="identity"`` initializes qubits in :math:`|+\\rangle`. Setting ``vop="hadamard"`` initializes qubits in :math:`|0\\rangle`. """ @@ -27,11 +27,20 @@ class GraphState(object): self.deterministic = deterministic self.adj, self.node = {}, {} try: - for n in nodes: - self._add_node(n, vop=vop) - except TypeError: - for n in range(nodes): - self._add_node(n, vop=vop) + # Cloning from a networkx graph + self.adj = data.adj.copy() + self.node = data.node.copy() + for key, value in self.node.items(): + self.node[key]["vop"] = data.node[key].get("vop", clifford.by_name["identity"]) + except AttributeError: + try: + # Provided with a list of node names? + for n in data: + self._add_node(n, vop=vop) + except TypeError: + # Provided with an integer? + for n in range(data): + self._add_node(n, vop=vop) def _add_node(self, node, **kwargs): """ Add a node. By default, nodes are initialized with ``vop=``:math:`I`, i.e. they are in the :math:`|+\\rangle` state. diff --git a/tests/test_fancy.py b/tests/test_fancy.py index 636eecf..f1fba44 100644 --- a/tests/test_fancy.py +++ b/tests/test_fancy.py @@ -41,3 +41,5 @@ def test_from_nx(): assert psi.node[0]["vop"] == 0 assert len(psi.edges()) > 0 psi.measure(0, "px", detail=True) + + psi = fancy.GraphState(nx.Graph(((0, 1),))) diff --git a/tests/test_graphstate.py b/tests/test_graphstate.py index 0f6e3b5..889bfd2 100644 --- a/tests/test_graphstate.py +++ b/tests/test_graphstate.py @@ -3,6 +3,7 @@ import mock import random import numpy as np from tqdm import tqdm +import networkx as nx REPEATS = 100 DEPTH = 100 @@ -121,3 +122,12 @@ def test_stabilizer_state_multiqubit(n=6): b = mock.circuit_to_state(mock.CircuitModelWrapper, n, circuit) assert a.to_state_vector() == b + +def test_from_nx(): + """ Creating from a networkx graph """ + g = nx.random_geometric_graph(100, 2) + psi = GraphState(g) + assert len(psi.node) == 100 + + psi = GraphState(nx.Graph(((0, 1),))) +