diff --git a/clifford.py b/clifford.py index b3c45e9..522a543 100644 --- a/clifford.py +++ b/clifford.py @@ -27,16 +27,21 @@ def compose_u(decomposition): us = (elements[c] for c in decomposition) return np.matrix(reduce(np.dot, us), dtype=complex) +def name_of(vop): + """ Get the formatted name of a VOP """ + return get_name[vop].title() if vop in get_name else "VOP%d" % vop + @cache_to_disk("tables.pkl") def construct_tables(): """ Constructs / caches multiplication and conjugation tables """ + by_name = {name: find_up_to_phase(u)[0] for name, u in qi.by_name.items()} + get_name = {v:k for k, v in by_name.items()} conjugation_table = [find_up_to_phase(u.H)[0] for i, u in enumerate(unitaries)] times_table = [[find_up_to_phase(u * v)[0] for v in unitaries] for u in tqdm(unitaries)] - return conjugation_table, times_table - + return by_name, get_name, conjugation_table, times_table # Various useful tables decompositions = ("xxxx", "xx", "zzxx", "zz", "zxx", "z", "zzz", "xxz", @@ -44,15 +49,10 @@ decompositions = ("xxxx", "xx", "zzxx", "zz", "zxx", "z", "zzz", "xxz", "zzzx", "xxzx", "zx", "zxxx", "xxxz", "xzzz", "xz", "xzxx") elements = {"x": qi.sqx, "z": qi.msqz} unitaries = [compose_u(d) for d in decompositions] -conjugation_table, times_table = construct_tables() - -# TODO: generate these global names automatically via search -sqx = 15 -msqz = 5 - +by_name, get_name, conjugation_table, times_table = construct_tables() if __name__ == '__main__': - print find_up_to_phase(qi.sqx) - print find_up_to_phase(qi.msqz) + print by_name + print get_name diff --git a/cz.py b/cz.py index 89afd6a..1014223 100644 --- a/cz.py +++ b/cz.py @@ -3,6 +3,19 @@ import viz import itertools as it import clifford +#def cphase(a, b): + #""" Act a controlled-phase gate on two qubits """ + #if g + +def remove_vop(g, vops, a, b): + """ Reduces VOP[a] to the identity, avoiding (if possible) the use of vertex b as a swapping partner """ + free_neighbours = g[a] - {b} + c = b if len(free_neighbours) == 0 else free_neighbours.pop() + d = clifford.decompositions[a] + for v in reversed(d): + target = a if v == clifford.by_name["msqx"] else b + local_complementation(g, vops, target) + def local_complementation(g, vops, v): """ As defined in LISTING 1 of Anders & Briegel """ @@ -10,20 +23,11 @@ def local_complementation(g, vops, v): toggle_edge(g, i, j) # Update VOPs - vops[v] = clifford.times_table[vops[v]][clifford.sqx] + vops[v] = clifford.times_table[vops[v]][clifford.by_name["sqx"]] for i in g[v]: - vops[i] = clifford.times_table[vops[i]][clifford.msqz] + vops[i] = clifford.times_table[vops[i]][clifford.by_name["msqz"]] -def remove_vop(g, vops, a, b): - """ Reduces VOP[a] to the identity, avoiding (if possible) the use of vertex b as a swapping partner """ - free_neighbours = g[a] - {b} - c = b if len(free_neighbours) == 0 else free_neighbours.pop() - d = clifford.decompositions[a] - for v in reversed(d): - target = a if v == clifford.sqx else b - local_complementation(g, vops, target) - if __name__ == '__main__': g, vops = graph() @@ -31,7 +35,8 @@ if __name__ == '__main__': add_edge(g, 1, 2) add_edge(g, 0, 2) add_edge(g, 0, 3) + add_edge(g, 6, 7) - viz.draw(g, vops, "out.pdf") + pos = viz.draw(g, vops, "out.pdf") local_complementation(g, vops, 0) - viz.draw(g, vops, "out2.pdf") + viz.draw(g, vops, "out2.pdf", pos) diff --git a/graph.py b/graph.py index 243d9e4..0c1b28e 100644 --- a/graph.py +++ b/graph.py @@ -3,10 +3,13 @@ Provides an extremely basic graph structure, based on neighbour lists """ from collections import defaultdict +import clifford def graph(): """ Generate a graph with Hadamards on each qubit """ - return defaultdict(set), defaultdict(int) + #return defaultdict(set), defaultdict(lambda: clifford.by_name["hadamard"]) + return [set() for i in range(100)], [clifford.by_name["hadamard"] for i in range(100)] + def add_edge(graph, v1, v2): """ Add an edge between two vertices in the graph """ @@ -32,7 +35,7 @@ def toggle_edge(graph, v1, v2): def edgelist(g): """ Describe a graph as an edgelist """ edges = frozenset(frozenset((i, n)) - for i, v in enumerate(g.values()) + for i, v in enumerate(g) for n in v) return [tuple(e) for e in edges] diff --git a/qi.py b/qi.py index 8421b47..084d7f2 100644 --- a/qi.py +++ b/qi.py @@ -22,3 +22,7 @@ msqz = sqrtm(-1j * pz) sqx = sqrtm(1j * px) msqx = sqrtm(-1j * px) paulis = (px, py, pz) + +common_us = id, px, py, pz, ha, ph, sqz, msqz, sqy, msqy, sqx, msqx +names = "identity", "px", "py", "pz", "hadamard", "phase", "sqz", "msqz", "sqy", "msqy", "sqx", "msqx" +by_name = dict(zip(names, common_us)) diff --git a/tests/test_clifford.py b/tests/test_clifford.py index 2760761..73fbc90 100644 --- a/tests/test_clifford.py +++ b/tests/test_clifford.py @@ -1,7 +1,7 @@ import clifford as lc from numpy import * from scipy.linalg import sqrtm -from qi import * +import qi from tqdm import tqdm import itertools as it @@ -9,7 +9,7 @@ import itertools as it def identify_pauli(m): """ Given a signed Pauli matrix, name it. """ for sign in (+1, -1): - for pauli_label, pauli in zip("xyz", paulis): + for pauli_label, pauli in zip("xyz", qi.paulis): if allclose(sign * pauli, m): return sign, pauli_label @@ -22,7 +22,7 @@ def _test_find_up_to_phase(): def get_action(u): """ What does this unitary operator do to the Paulis? """ - return [identify_pauli(u * p * u.H) for p in paulis] + return [identify_pauli(u * p * u.H) for p in qi.paulis] def format_action(action): @@ -37,8 +37,7 @@ def test_we_have_24_matrices(): def test_we_have_all_useful_gates(): """ Check that all the interesting gates are included up to a global phase """ - common_us = id, px, py, pz, ha, ph, sqz, msqz, sqy, msqy, sqx, msqx - for u in common_us: + for name, u in qi.by_name.items(): lc.find_up_to_phase(u) diff --git a/tests/test_cz.py b/tests/test_cz.py index 777c3c4..06126a2 100644 --- a/tests/test_cz.py +++ b/tests/test_cz.py @@ -17,3 +17,9 @@ def test_local_complementation(): assert has_edge(g, 3, 1) # TODO: test VOP conditions + +def test_remove_vop(): + """ Test that removing VOPs really works """ + pass + + diff --git a/viz.py b/viz.py index edd9d24..96e04a0 100644 --- a/viz.py +++ b/viz.py @@ -5,21 +5,28 @@ Utility function for plotting graphs nicely import networkx as nx from matplotlib import pyplot as plt from graph import * +import clifford +import numpy as np VOP_COLORS = ["red", "green", "blue", "orange", "yellow", "purple", "black", "white"] -def draw(graph, vops, filename="out.pdf", ns=500): +def draw(graph, vops, filename="out.pdf", pos=None, ns=500): """ Draw a graph with networkx layout """ plt.clf() g = nx.from_edgelist(edgelist(graph)) - pos = nx.spring_layout(g) + pos = nx.spring_layout(g) if pos==None else pos colors = [VOP_COLORS[vop % len(VOP_COLORS)] for vop in vops] nx.draw_networkx_nodes(g, pos, node_color="white", node_size=ns) nx.draw_networkx_nodes(g, pos, node_color=colors, node_size=ns, alpha=.4) + nx.draw_networkx_edges(g, pos, edge_color="gray") nx.draw_networkx_labels(g, pos) - nx.draw_networkx_edges(g, pos) + + labels = {i: clifford.name_of(vops[i]) for i in g.nodes()} + pos = {k: v + np.array([0, -.1]) for k, v in pos.items()} + nx.draw_networkx_labels(g, pos, labels) plt.axis('off') plt.savefig(filename) + return pos if __name__ == '__main__': g, vops = graph()