Browse Source

Move lookup tables into a generated module

master
Pete Shadbolt 7 years ago
parent
commit
45bcdf1cca
8 changed files with 266 additions and 230 deletions
  1. +229
    -0
      abp/build_tables.py
  2. +1
    -214
      abp/clifford.py
  3. +17
    -0
      abp/tables.py
  4. +7
    -5
      static/scripts/tables.js
  5. +1
    -1
      tests/test_against_anders_and_briegel.py
  6. +7
    -6
      tests/test_clifford.py
  7. +2
    -2
      tests/test_cphase_against_anders.py
  8. +2
    -2
      tests/test_cphase_table.py

+ 229
- 0
abp/build_tables.py View File

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

+ 1
- 214
abp/clifford.py View File

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

+ 17
- 0
abp/tables.py
File diff suppressed because it is too large
View File


+ 7
- 5
static/scripts/tables.js
File diff suppressed because it is too large
View File


+ 1
- 1
tests/test_against_anders_and_briegel.py View File

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


+ 7
- 6
tests/test_clifford.py View File

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

+ 2
- 2
tests/test_cphase_against_anders.py View File

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


+ 2
- 2
tests/test_cphase_table.py View File

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



Loading…
Cancel
Save