@@ -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)) | |||