diff --git a/abp/fancy.py b/abp/fancy.py index 3178596..19f0a2d 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,7 +8,7 @@ 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) self.connect_to_server() @@ -50,7 +50,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/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/abp/lattices.py b/abp/lattices.py index e69de29..a1b4bb7 100644 --- a/abp/lattices.py +++ b/abp/lattices.py @@ -0,0 +1,61 @@ +""" +This is a sketch of a consistent language for defining resource states and lattices. +""" + +import networkx as nx +from abp.fancy import GraphState + +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()) + 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(psi, na, nb): + """ Deterministic fusion for testing purposes """ + 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 = 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) + +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 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__': + psi = union(unit_cell((0, 0)), unit_cell((2, 0))) + annotate(psi, position) + g = GraphState(psi) + diff --git a/tests/test_fancy.py b/tests/test_fancy.py index dcc6884..f1fba44 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,24 @@ 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) + 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),))) +