| @@ -0,0 +1,229 @@ | |||
| """ | |||
| This program computes lookup tables and stores them as tables.py and tables.js | |||
| # TODO: clifford naming discrepancy | |||
| """ | |||
| import numpy as np | |||
| from tqdm import tqdm | |||
| import itertools as it | |||
| from functools import reduce | |||
| from os.path import dirname, join, split | |||
| import json | |||
| import qi, clifford | |||
| DECOMPOSITIONS = ( | |||
| "xxxx", "xx", "zzxx", "zz", "zxx", "z", "zzz", "xxz", "xzx", "xzxxx", "xzzzx", | |||
| "xxxzx", "xzz", "zzx", "xxx", "x", "zzzx", "xxzx", "zx", "zxxx", "xxxz", "xzzz", "xz", "xzxx") | |||
| JS_TEMPLATE = """\ | |||
| var tables = {{ | |||
| decompositions : {decompositions}, | |||
| conjugation_table : {conjugation_table}, | |||
| times_table : {times_table}, | |||
| cz_table : {cz_table}, | |||
| clifford : {by_name}, | |||
| measurement_table_real : {measurement_table_real}, | |||
| measurement_table_imag : {measurement_table_imag} | |||
| }}; | |||
| """ | |||
| PY_TEMPLATE = """\ | |||
| import numpy as np | |||
| # Define lookup tables | |||
| ir2 = 1/np.sqrt(2) | |||
| decompositions = {decompositions} | |||
| conjugation_table = np.array({conjugation_table}, dtype=int) | |||
| times_table = np.array({times_table}, dtype=int) | |||
| cz_table = np.array({cz_table}, dtype=int) | |||
| by_name = {by_name} | |||
| measurement_table_real = np.array({measurement_table_real}, dtype=complex) | |||
| measurement_table_imag = np.array({measurement_table_imag}, dtype=complex) | |||
| unitaries_real = np.array({unitaries_real}, dtype=complex) | |||
| unitaries_imag = np.array({unitaries_imag}, dtype=complex) | |||
| # Reconstruct | |||
| measurement_table = measurement_table_real + 1j*measurement_table_imag | |||
| unitaries = unitaries_real + 1j*unitaries_imag | |||
| """ | |||
| def find_clifford(needle, haystack): | |||
| """ Find the index of a given u within a list of unitaries, up to a global phase """ | |||
| needle = qi.normalize_global_phase(needle) | |||
| for i, t in enumerate(haystack): | |||
| if np.allclose(t, needle): | |||
| return i | |||
| raise IndexError | |||
| def find_cz(bond, c1, c2, commuters, state_table): | |||
| """ Find the output of a CZ operation """ | |||
| # Figure out the target state | |||
| target = qi.cz.dot(state_table[bond, c1, c2]) | |||
| target = qi.normalize_global_phase(target) | |||
| # Choose the sets to search over | |||
| s1 = commuters if c1 in commuters else xrange(24) | |||
| s2 = commuters if c2 in commuters else xrange(24) | |||
| # Find a match | |||
| for bondp, c1p, c2p in it.product([0, 1], s1, s2): | |||
| if np.allclose(target, state_table[bondp, c1p, c2p]): | |||
| return bondp, c1p, c2p | |||
| # Didn't find anything - this should never happen | |||
| raise IndexError | |||
| def compose_u(decomposition): | |||
| """ Get the unitary representation of a particular decomposition """ | |||
| matrices = ({"x": qi.msqx, "z": qi.sqz}[c] for c in decomposition) | |||
| output = reduce(np.dot, matrices, np.eye(2, dtype=complex)) | |||
| return qi.normalize_global_phase(output) | |||
| def get_unitaries(): | |||
| """ The Clifford group """ | |||
| return [compose_u(d) for d in DECOMPOSITIONS] | |||
| def get_by_name(unitaries): | |||
| """ Get a lookup table of cliffords by name """ | |||
| a = {name: find_clifford(u, unitaries) | |||
| for name, u in qi.by_name.items()} | |||
| a.update({clifford.get_name(i): i for i in range(24)}) | |||
| a.update({i: i for i in range(24)}) | |||
| return a | |||
| def get_conjugation_table(unitaries): | |||
| """ Construct the conjugation table """ | |||
| return np.array([find_clifford(qi.hermitian_conjugate(u), unitaries) for u in unitaries], dtype=int) | |||
| def get_times_table(unitaries): | |||
| """ Construct the times-table """ | |||
| return np.array([[find_clifford(u.dot(v), unitaries) for v in unitaries] | |||
| for u in tqdm(unitaries, desc="Building times-table")], dtype=int) | |||
| def get_state_table(unitaries): | |||
| """ Cache a table of state to speed up a little bit """ | |||
| state_table = np.zeros((2, 24, 24, 4), dtype=complex) | |||
| params = list(it.product([0, 1], range(24), range(24))) | |||
| for bond, i, j in tqdm(params, desc="Building state table"): | |||
| state = qi.bond if bond else qi.nobond | |||
| kp = np.kron(unitaries[i], unitaries[j]) | |||
| state_table[bond, i, j, :] = qi.normalize_global_phase( | |||
| np.dot(kp, state).T) | |||
| return state_table | |||
| def get_measurement_entry(operator, unitary): | |||
| """ | |||
| Any Clifford group unitary will map an operator A in {I, X, Y, Z} | |||
| to an operator B in +-{I, X, Y, Z}. This finds that mapping. | |||
| """ | |||
| matrices = ({"x": qi.msqx, "z": qi.sqz}[c] | |||
| for c in DECOMPOSITIONS[unitary]) | |||
| unitary = reduce(np.dot, matrices, np.eye(2, dtype=complex)) | |||
| operator = qi.operators[operator] | |||
| new_operator = reduce(np.dot, | |||
| (unitary, operator, qi.hermitian_conjugate(unitary))) | |||
| for i, o in enumerate(qi.operators): | |||
| if np.allclose(o, new_operator): | |||
| return i, 1 | |||
| elif np.allclose(o, -new_operator): | |||
| return i, -1 | |||
| raise IndexError | |||
| def get_measurement_table(): | |||
| """ | |||
| Compute a table of transform * operation * transform^dagger | |||
| This is pretty unintelligible right now, we should probably compute the phase from unitaries instead | |||
| """ | |||
| measurement_table = np.zeros((4, 24, 2), dtype=complex) | |||
| for operator, unitary in it.product(range(4), range(24)): | |||
| measurement_table[operator, unitary] = get_measurement_entry( | |||
| operator, unitary) | |||
| return measurement_table | |||
| def get_commuters(unitaries): | |||
| """ Get the indeces of gates which commute with CZ """ | |||
| commuters = (qi.id, qi.pz, qi.ph, qi.hermitian_conjugate(qi.ph)) | |||
| return [find_clifford(u, unitaries) for u in commuters] | |||
| def get_cz_table(unitaries): | |||
| """ Compute the lookup table for the CZ (A&B eq. 9) """ | |||
| # Get a cached state table and a list of gates which commute with CZ | |||
| commuters = get_commuters(unitaries) | |||
| state_table = get_state_table(unitaries) | |||
| # And now build the CZ table | |||
| cz_table = np.zeros((2, 24, 24, 3), dtype=int) | |||
| rows = list( | |||
| it.product([0, 1], it.combinations_with_replacement(range(24), 2))) | |||
| # CZ is symmetric so we only need combinations | |||
| for bond, (c1, c2) in tqdm(rows, desc="Building CZ table"): | |||
| newbond, c1p, c2p = find_cz( | |||
| bond, c1, c2, commuters, state_table) | |||
| cz_table[bond, c1, c2] = [newbond, c1p, c2p] | |||
| cz_table[bond, c2, c1] = [newbond, c2p, c1p] | |||
| return cz_table | |||
| def compute_everything(): | |||
| """ Compute all lookup tables """ | |||
| unitaries = get_unitaries() | |||
| return {"decompositions": DECOMPOSITIONS, | |||
| "unitaries": unitaries, | |||
| "by_name": get_by_name(unitaries), | |||
| "conjugation_table": get_conjugation_table(unitaries), | |||
| "times_table": get_times_table(unitaries), | |||
| "cz_table": get_cz_table(unitaries), | |||
| "measurement_table": get_measurement_table()} | |||
| def human_readable(data): | |||
| """ Format the data """ | |||
| unitaries = np.array(data["unitaries"]) | |||
| return {"decompositions": json.dumps(DECOMPOSITIONS), | |||
| "unitaries_real": json.dumps(unitaries.real.round(5).tolist()).replace("0.70711", "ir2"), | |||
| "unitaries_imag": json.dumps(unitaries.imag.round(5).tolist()).replace("0.70711", "ir2"), | |||
| "conjugation_table": json.dumps(data["conjugation_table"].tolist()), | |||
| "times_table": json.dumps(data["times_table"].tolist()), | |||
| "cz_table": json.dumps(data["cz_table"].tolist()), | |||
| "by_name": json.dumps(data["by_name"]), | |||
| "measurement_table_real": json.dumps(data["measurement_table"].real.tolist()), | |||
| "measurement_table_imag": json.dumps(data["measurement_table"].imag.tolist())} | |||
| def write_python(data): | |||
| """ Write the tables to a python module """ | |||
| path = join(dirname(__file__), "tables.py") | |||
| content = PY_TEMPLATE.format(**data) | |||
| with open(path, "w") as f: | |||
| f.write(content) | |||
| def write_javascript(data): | |||
| """ Write the tables to javascript files for consumption in the browser """ | |||
| path = join(split(dirname(__file__))[0], "static/scripts/tables.js") | |||
| content = JS_TEMPLATE.format(**data) | |||
| with open(path, "w") as f: | |||
| f.write(content) | |||
| if __name__ == '__main__': | |||
| data = compute_everything() | |||
| data = human_readable(data) | |||
| write_python(data) | |||
| write_javascript(data) | |||
| @@ -6,236 +6,23 @@ It provides tables for Clifford group multiplication and conjugation, | |||
| as well as CZ and decompositions of the 2x2 Cliffords. | |||
| """ | |||
| import os, json, tempfile, json | |||
| from functools import reduce | |||
| import itertools as it | |||
| import numpy as np | |||
| from tqdm import tqdm | |||
| import qi | |||
| decompositions = ("xxxx", "xx", "zzxx", "zz", "zxx", "z", "zzz", "xxz", | |||
| "xzx", "xzxxx", "xzzzx", "xxxzx", "xzz", "zzx", "xxx", "x", | |||
| "zzzx", "xxzx", "zx", "zxxx", "xxxz", "xzzz", "xz", "xzxx") | |||
| from tables import * | |||
| def conjugate(operator, unitary): | |||
| """ Returns transform * vop * transform^dagger and a phase in {+1, -1} """ | |||
| return measurement_table[operator, unitary] | |||
| def use_old_cz(): | |||
| """ Use the CZ table from A&B's code """ | |||
| global cz_table | |||
| from anders_cz import cz_table | |||
| def get_name(i): | |||
| """ Get the human-readable name of this clifford """ | |||
| return "IXYZ"[i & 0x03] + "ABCDEF"[i / 4] | |||
| def find_clifford(needle, haystack): | |||
| """ Find the index of a given u within a list of unitaries, up to a global phase """ | |||
| needle = qi.normalize_global_phase(needle) | |||
| for i, t in enumerate(haystack): | |||
| if np.allclose(t, needle): | |||
| return i | |||
| raise IndexError | |||
| def find_cz(bond, c1, c2, commuters, state_table): | |||
| """ Find the output of a CZ operation """ | |||
| # Figure out the target state | |||
| target = qi.cz.dot(state_table[bond, c1, c2]) | |||
| target = qi.normalize_global_phase(target) | |||
| # Choose the sets to search over | |||
| s1 = commuters if c1 in commuters else xrange(24) | |||
| s2 = commuters if c2 in commuters else xrange(24) | |||
| # Find a match | |||
| for bondp, c1p, c2p in it.product([0, 1], s1, s2): | |||
| if np.allclose(target, state_table[bondp, c1p, c2p]): | |||
| return bondp, c1p, c2p | |||
| # Didn't find anything - this should never happen | |||
| raise IndexError | |||
| def compose_u(decomposition): | |||
| """ Get the unitary representation of a particular decomposition """ | |||
| matrices = ({"x": qi.msqx, "z": qi.sqz}[c] for c in decomposition) | |||
| output = reduce(np.dot, matrices, np.eye(2, dtype=complex)) | |||
| return qi.normalize_global_phase(output) | |||
| def get_unitaries(): | |||
| """ The Clifford group """ | |||
| return [compose_u(d) for d in decompositions] | |||
| def get_by_name(unitaries): | |||
| """ Get a lookup table of cliffords by name """ | |||
| a = {name: find_clifford(u, unitaries) | |||
| for name, u in qi.by_name.items()} | |||
| a.update({get_name(i): i for i in range(24)}) | |||
| a.update({i: i for i in range(24)}) | |||
| return a | |||
| def get_conjugation_table(unitaries): | |||
| """ Construct the conjugation table """ | |||
| return np.array([find_clifford(qi.hermitian_conjugate(u), unitaries) for u in unitaries], dtype=int) | |||
| def get_times_table(unitaries): | |||
| """ Construct the times-table """ | |||
| return np.array([[find_clifford(u.dot(v), unitaries) for v in unitaries] | |||
| for u in tqdm(unitaries, desc="Building times-table")], dtype=int) | |||
| def get_state_table(unitaries): | |||
| """ Cache a table of state to speed up a little bit """ | |||
| state_table = np.zeros((2, 24, 24, 4), dtype=complex) | |||
| params = list(it.product([0, 1], range(24), range(24))) | |||
| for bond, i, j in tqdm(params, desc="Building state table"): | |||
| state = qi.bond if bond else qi.nobond | |||
| kp = np.kron(unitaries[i], unitaries[j]) | |||
| state_table[bond, i, j, :] = qi.normalize_global_phase( | |||
| np.dot(kp, state).T) | |||
| return state_table | |||
| def get_measurement_entry(operator, unitary): | |||
| """ | |||
| Any Clifford group unitary will map an operator A in {I, X, Y, Z} | |||
| to an operator B in +-{I, X, Y, Z}. This finds that mapping. | |||
| """ | |||
| matrices = ({"x": qi.msqx, "z": qi.sqz}[c] | |||
| for c in decompositions[unitary]) | |||
| unitary = reduce(np.dot, matrices, np.eye(2, dtype=complex)) | |||
| operator = qi.operators[operator] | |||
| new_operator = reduce( | |||
| np.dot, (unitary, operator, qi.hermitian_conjugate(unitary))) | |||
| for i, o in enumerate(qi.operators): | |||
| if np.allclose(o, new_operator): | |||
| return i, 1 | |||
| elif np.allclose(o, -new_operator): | |||
| return i, -1 | |||
| raise IndexError | |||
| def get_measurement_table(): | |||
| """ | |||
| Compute a table of transform * operation * transform^dagger | |||
| This is pretty unintelligible right now, we should probably compute the phase from unitaries instead | |||
| """ | |||
| measurement_table = np.zeros((4, 24, 2), dtype=complex) | |||
| for operator, unitary in it.product(range(4), range(24)): | |||
| measurement_table[operator, unitary] = get_measurement_entry( | |||
| operator, unitary) | |||
| return measurement_table | |||
| def get_commuters(unitaries): | |||
| """ Get the indeces of gates which commute with CZ """ | |||
| commuters = (qi.id, qi.pz, qi.ph, qi.hermitian_conjugate(qi.ph)) | |||
| return [find_clifford(u, unitaries) for u in commuters] | |||
| def get_cz_table(unitaries): | |||
| """ Compute the lookup table for the CZ (A&B eq. 9) """ | |||
| # Get a cached state table and a list of gates which commute with CZ | |||
| commuters = get_commuters(unitaries) | |||
| state_table = get_state_table(unitaries) | |||
| # And now build the CZ table | |||
| cz_table = np.zeros((2, 24, 24, 3), dtype=int) | |||
| rows = list( | |||
| it.product([0, 1], it.combinations_with_replacement(range(24), 2))) | |||
| # CZ is symmetric so we only need combinations | |||
| for bond, (c1, c2) in tqdm(rows, desc="Building CZ table"): | |||
| newbond, c1p, c2p = find_cz( | |||
| bond, c1, c2, commuters, state_table) | |||
| cz_table[bond, c1, c2] = [newbond, c1p, c2p] | |||
| cz_table[bond, c2, c1] = [newbond, c2p, c1p] | |||
| return cz_table | |||
| def write_javascript_tables(): | |||
| """ Write the tables to javascript files for consumption in the browser """ | |||
| path = os.path.dirname(__file__) | |||
| path = os.path.split(path)[0] | |||
| with open(os.path.join(path, "static/scripts/tables.js"), "w") as f: | |||
| f.write("var tables = {\n") | |||
| f.write("\tdecompositions : {},\n" | |||
| .format(json.dumps(decompositions))) | |||
| f.write("\tconjugation_table : {},\n" | |||
| .format(json.dumps(conjugation_table.tolist()))) | |||
| f.write("\ttimes_table : {},\n" | |||
| .format(json.dumps(times_table.tolist()))) | |||
| f.write("\tcz_table : {},\n" | |||
| .format(json.dumps(cz_table.tolist()))) | |||
| f.write("\tclifford : {}\n" | |||
| .format(json.dumps(by_name))) | |||
| f.write("};") | |||
| def temp(filename): | |||
| """ Get a temporary path """ | |||
| # TODO: this STILL fucking fails sometimes. WHY | |||
| tempdir = tempfile.gettempdir() | |||
| return os.path.join(tempdir, filename) | |||
| def compute_everything(): | |||
| """ Compute all lookup tables """ | |||
| global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table | |||
| unitaries = get_unitaries() | |||
| by_name = get_by_name(unitaries) | |||
| conjugation_table = get_conjugation_table(unitaries) | |||
| times_table = get_times_table(unitaries) | |||
| cz_table = get_cz_table(unitaries) | |||
| measurement_table = get_measurement_table() | |||
| def save_to_disk(): | |||
| """ Save all tables to disk """ | |||
| global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table | |||
| np.save(temp("unitaries.npy"), unitaries) | |||
| np.save(temp("conjugation_table.npy"), conjugation_table) | |||
| np.save(temp("times_table.npy"), times_table) | |||
| np.save(temp("cz_table.npy"), cz_table) | |||
| np.save(temp("measurement_table.npy"), measurement_table) | |||
| write_javascript_tables() | |||
| with open(temp("by_name.json"), "wb") as f: | |||
| json.dump(by_name, f) | |||
| def load_from_disk(): | |||
| """ Load all the tables from disk """ | |||
| global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table | |||
| unitaries = np.load(temp("unitaries.npy")) | |||
| conjugation_table = np.load(temp("conjugation_table.npy")) | |||
| times_table = np.load(temp("times_table.npy")) | |||
| measurement_table = np.load(temp("measurement_table.npy")) | |||
| cz_table = np.load(temp("cz_table.npy")) | |||
| with open(temp("by_name.json")) as f: | |||
| by_name = json.load(f) | |||
| def is_diagonal(v): | |||
| """ TODO: remove this. Checks if a VOP is diagonal or not """ | |||
| return v in {0, 3, 5, 6} | |||
| if __name__ == "__main__": | |||
| compute_everything() | |||
| save_to_disk() | |||
| else: | |||
| try: | |||
| load_from_disk() | |||
| except IOError: | |||
| compute_everything() | |||
| save_to_disk() | |||
| @@ -101,7 +101,7 @@ def test_with_cphase_gates_hadamard_only(N=10): | |||
| assert_equal(a, b) | |||
| def _test_cz_hadamard(N=3): | |||
| def test_cz_hadamard(N=3): | |||
| """ Test CZs and Hadamards at random """ | |||
| clifford.use_old_cz() | |||
| @@ -2,6 +2,7 @@ from numpy import * | |||
| from tqdm import tqdm | |||
| import itertools as it | |||
| from abp import clifford | |||
| from abp import build_tables | |||
| from abp import qi | |||
| from nose.tools import raises | |||
| @@ -16,14 +17,14 @@ def identify_pauli(m): | |||
| def test_find_clifford(): | |||
| """ Test that slightly suspicious function """ | |||
| assert clifford.find_clifford(qi.id, clifford.unitaries) == 0 | |||
| assert clifford.find_clifford(qi.px, clifford.unitaries) == 1 | |||
| assert build_tables.find_clifford(qi.id, clifford.unitaries) == 0 | |||
| assert build_tables.find_clifford(qi.px, clifford.unitaries) == 1 | |||
| @raises(IndexError) | |||
| def test_find_non_clifford(): | |||
| """ Test that looking for a non-Clifford gate fails """ | |||
| clifford.find_clifford(qi.t, clifford.unitaries) | |||
| build_tables.find_clifford(qi.t, clifford.unitaries) | |||
| def get_action(u): | |||
| @@ -44,14 +45,14 @@ 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 """ | |||
| for name, u in qi.by_name.items(): | |||
| clifford.find_clifford(u, clifford.unitaries) | |||
| build_tables.find_clifford(u, clifford.unitaries) | |||
| def test_group(): | |||
| """ Test we are really in a group """ | |||
| matches = set() | |||
| for a, b in tqdm(it.combinations(clifford.unitaries, 2), "Testing this is a group"): | |||
| i = clifford.find_clifford(a.dot(b), clifford.unitaries) | |||
| i = build_tables.find_clifford(a.dot(b), clifford.unitaries) | |||
| matches.add(i) | |||
| assert len(matches) == 24 | |||
| @@ -76,4 +77,4 @@ def test_cz_table_makes_sense(): | |||
| def test_commuters(): | |||
| """ Test that commutation is good """ | |||
| assert len(clifford.get_commuters(clifford.unitaries)) == 4 | |||
| assert len(build_tables.get_commuters(clifford.unitaries)) == 4 | |||
| @@ -4,11 +4,11 @@ import sys | |||
| import os | |||
| import itertools as it | |||
| from string import maketrans | |||
| from abp import clifford, qi, anders_cz | |||
| from abp import clifford, qi, anders_cz, build_tables | |||
| def test_cz_table(): | |||
| """ Does our clifford code work with anders & briegel's table? """ | |||
| state_table = clifford.get_state_table(clifford.unitaries) | |||
| state_table = build_tables.get_state_table(clifford.unitaries) | |||
| ab_cz_table = anders_cz.cz_table | |||
| rows = it.product([0, 1], it.combinations_with_replacement(range(24), 2)) | |||
| @@ -1,10 +1,10 @@ | |||
| import numpy as np | |||
| from abp import clifford, qi | |||
| from abp import clifford, qi, build_tables | |||
| import itertools as it | |||
| def test_cz_table(): | |||
| """ Does the CZ code work good? """ | |||
| state_table = clifford.get_state_table(clifford.unitaries) | |||
| state_table = build_tables.get_state_table(clifford.unitaries) | |||
| rows = it.product([0, 1], it.combinations_with_replacement(range(24), 2)) | |||