Beispiel #1
0
def test_adjoint():

    for A in Gate.I, Gate.X, Gate.Y, Gate.Z, Gate.H:
        B = A * ~A
        assert B.is_close(Gate.I)
        B = ~A * A
        assert B.is_close(Gate.I)

    A = Qu.random((2, 3), 'ud')

    assert (~A).shape == (2, 3)
    assert (~A).valence == 'du'

    H = ~A * A
    assert Gate.promote(H).is_hermitian()

    B = Qu.random((4, 2), 'ud')
    E = Qu.random((3, 2, 2), 'udu')
    u = Qu.random(2, 'u')
    v = Qu.random(3, 'd')

    for i in range(100):
        word = []
        for j in range(randint(1, 4)):
            word.append(choice((A, B, E, u, v)))

        lhs = ~reduce(matmul, word)
        rhs = reduce(matmul, [~x for x in word])

        assert lhs.space == rhs.space, (lhs.space, rhs.space)

        assert lhs.is_close(rhs)
Beispiel #2
0
def test_flatten():

    shape = (2, 3, 4)
    valence = 'uuu'
    A = Qu.random(shape, valence)
    op = A.get_flatop()
    B = op.do(A)
    assert B.shape == (2 * 3 * 4, ), B.shape
    assert B.valence == 'u'

    shape = (2, 3)
    valence = 'ud'
    a = Qu.random(shape, valence)
    op = a.get_flatop()
    assert op.do(a).is_close(a)

    shape = (2, 3, 4, 5)
    valence = "udud"
    a = Qu.random(shape, valence)

    op = a.get_flatop()

    b = op.do(a)

    c = op.undo(b)

    assert c.is_close(a)
Beispiel #3
0
def main_1():

    I = Qu((2, 2), 'ud', [[1, 0], [0, 1]])
    X = Qu((2, 2), 'ud', [[0, 1], [1, 0]])
    Z = Qu((2, 2), 'ud', [[1, 0], [0, -1]])
    Y = X*Z
    T = Qu((2, 2), 'ud', [[1, 0], [0, cyclotomic(8)]])


    op = get_projector()
    n = op.grade

    if argv.show:
        print(op)

    P = op.evaluate({"I":I, "X":X, "Z":Z, "Y":Y})

    Tn = reduce(matmul, [T]*n)

    TnP = Tn*P
    PTn = P*Tn
    print(numpy.abs(TnP.v - PTn.v).sum())

    if Tn*P == P*Tn:
        print("transversal T")
    else:
        print("no transversal T")
Beispiel #4
0
 def get_encoded(self):
     self.build()
     n = self.n
     v = Qu((self.d, ) * n, 'u' * n)
     v[(0, ) * n] = 1.
     v = self.P * v
     #for op in self.LG:
     #    yield op*v
     v /= v.norm()
     return v
Beispiel #5
0
def build_pauli(*args):

    X = Qu((2, 2), 'ud', [[0., 1], [1., 0.]])
    Z = Qu((2, 2), 'ud', [[1., 0], [0., -1.]])
    gen = [X, Z]

    G = mulclose(gen)

    assert len(G) == 8

    return gen, G
Beispiel #6
0
def test_transpose():

    a = Qu.random(2, 'u')
    r = ~a * a
    assert is_close(r, a.norm()**2)

    v = a.v[:]
    v.shape = v.shape + (1, )
    A = Qu(v.shape, 'ud', v)

    r = ~A * A
    assert is_close(r[0, 0], a.norm()**2)
Beispiel #7
0
def test_shor_code():

    x0 = (1./(2*r2)) * (bits('000') + bits('111'))\
        @ (bits('000') + bits('111')) \
        @ (bits('000') + bits('111'))
    x1 = (1./(2*r2)) * (bits('000') - bits('111')) \
        @ (bits('000') - bits('111')) \
        @ (bits('000') - bits('111'))

    A = Qu((2, ) * 10, 'u' * 9 + 'd')

    A[(slice(None), ) * 9 + (0, )] = x0
    A[(slice(None), ) * 9 + (1, )] = x1

    x = Qu.random(2, 'u')
    x.normalize()

    y = A * x  # encode

    # now entangle with environment...

    env = Qu.random(2, 'u')

    z = y @ env

    return

    # this takes a while...

    rank = z.rank
    space = Space(2**rank, 'u')
    H = Qu.random_hermitian(space @ ~space)
    print("H:", H.space)
    U = H.evolution_operator(1.)
    print("U:", U.space, "rank:", U.rank)

    op = z.get_flatop()
    z = op.do(z)

    z1 = U * z

    if 0:
        space = z.space @ ~z.space
        #    U = Qu.random_unitary(space)
        H = Qu.random_hermitian(space)
        print("H:", H.space)
        U = H.evolution_operator(1.)
        print("U:", U.space, "rank:", U.rank)

        z1 = U * z
Beispiel #8
0
def measure(H, v):
    assert H.is_hermitian()
    d = FuzzyDict()
    for val, vec in H.eigs():
        assert abs(val.imag) < EPSILON
        val = val.real
        space = d.get(val, [])
        space.append(vec)
        d[val] = space
    povm = []
    assert abs(1. - v.norm()) < EPSILON
    x = random()
    total = 0.
    result = None
    value = None
    ps = []
    for val, space in d.items():
        op = Qu(H.shape, H.valence)
        for vec in space:
            op += vec @ ~vec
        assert op*op == op
        u = op*v
        r = ~u * v
        #print(fstr(r))
        total += r
        if result is None and x < total:
            result = u / u.norm()
            value = val
        ps.append(r)
    #print(fstr(sum(ps)))
    return value, result
Beispiel #9
0
def test_1():

    assert S*S == Z

    i = 1.j
    phase = numpy.exp(i*pi/8)
    C1 = mulclose([X, Z, phase*I]) # Pauli group
    C2 = mulclose([H, S, phase*I]) # Clifford group

    assert len(C1) == 64, len(C1)
    assert len(C2) == 384, len(C2)

    r = numpy.exp(-pi*i/8)/sqrt(2)
    v = r*numpy.array([[1., i], [i, 1.]])
    M = Qu((2, 2), 'ud', v)

    for g in C2:
        ginv = inv(g)
        assert g*ginv == I

    for g2 in C2:
        g2inv = inv(g2)
        assert g2*g2inv == I
        for g in C1:
            k = g2 * g * g2inv
            assert find(k, C1)

    print(M)
    print(find(M, C2))
    for g in C1:
        k = M * g * inv(M)
        assert find(k, C1)
Beispiel #10
0
    def __init__(self, gen, els, ops):
        n = ops.shape[0] # number of generators
        assert n == len(gen)
        degree = ops.shape[1]
        assert ops.shape[2] == degree
        ops = [Qu((degree, degree), 'ud', v) for v in ops]
        assert len(ops) == n
        self.ops = ops
        lookup = dict(zip(gen, ops))
        #self.group = mulclose(ops)
        lookup["I"] = Qu.identity(degree)

        for el in els:
            op = eval(el, lookup)
            print("%s =" % el)
            print(op)
Beispiel #11
0
def test_gate_identities():

    X, Y, Z, H, I = Gate.X, Gate.Y, Gate.Z, Gate.H, Gate.I

    zero = Qu((2, 2), 'ud')

    assert (X * X) == I
    assert (Y * Y) == I
    assert (Z * Z) == I

    assert anticommutator(X, Z) == zero
    assert anticommutator(Y, Z) == zero
    assert anticommutator(X, Y) == zero

    assert (Z * X) == 1.j * Y
    assert (X * Y) == 1.j * Z
    assert (Y * Z) == 1.j * X

    CZ = Z.control()
    A = (I @ H) * CZ * (I @ H)
    CX = X.control()
    assert A == CX

    assert CZ * CX == (Z * X).control()
    assert CX * CZ == (X * Z).control()

    assert (X.control() * (H @ I)) * ((H @ I) * X.control()) == I @ I

    assert H == ~H
    assert H * ~H == I
    assert H * X * ~H == Z
Beispiel #12
0
def build_octa(idx=0):
    " binary _octahedral group "

    x = cyclotomic(8)

    r2 = x - x**3  # sqrt(2)
    i = x**2

    gen = [
        [[[(-1 + i) / 2, (-1 + i) / 2], [(1 + i) / 2, (-1 - i) / 2]],
         [[(1 + i) / r2, 0], [0, (1 - i) / r2]]],
        [  # 2A
            [[(1. + i) / r2, 0.], [0., (1 - i) / r2]],
            [[1. / r2, -i / r2], [-i / r2, 1. / r2]],
        ],
        [  # 2B
            [[(-1. - i) / r2, 0.], [0., (-1. + i) / r2]],
            [[-1. / r2, i / r2], [i / r2, -1. / r2]],
        ],
        #        [ # 2C
        #        [[1., 0.], [-1., -1.]],
        #        [[0., 1.], [1., 0.]],
        #        ],
    ][idx]
    gen = [Qu((2, 2), 'ud', v) for v in gen]
    octa_gen = gen
    #a, b = gen
    #print((a**3*b).trace())

    Octa = mulclose(gen)
    assert len(Octa) == 48, len(Octa)
    #print("Octa:", Octa.words.values())

    return gen, Octa
Beispiel #13
0
def inv(A):
    A = A.v
    a, b = A[0]
    c, d = A[1]
    r = 1. / (a*d - b*c)
    v = numpy.array([[d, -b], [-c, a]])*r
    B = Qu((2, 2), 'ud', v)
    return B
Beispiel #14
0
def test_bitflip_code():

    x0 = bits('000')
    x1 = bits('111')

    A = Qu((2, ) * 4, "uduu")

    A[:, 0, :, :] = x0
    A[:, 1, :, :] = x1

    assert (A * off).is_close(x0)
    assert (A * on).is_close(x1)

    B = Gate.CN
    B = B * (Gate.I @ off)
    B = B @ off

    C = Gate.CN @ Gate.I
    assert (C * bits('000')).decode() == '000'
    assert (C * bits('100')).decode() == '110'
    assert (C * bits('111')).decode() == '101'
    C = C.swap((2, 4), (3, 5))  # yuck...
    assert (C * bits('000')).decode() == '000'
    assert (C * bits('100')).decode() == '101'
    assert (C * bits('111')).decode() == '110'

    B = C * B

    assert B.is_close(A)

    x = Qu.random(2, 'u')
    x.normalize()

    y = A * x  # encode

    # now entangle with environment...

    env = Qu.random(2, 'u')

    z = y @ env

    space = z.space @ ~z.space
    U = Qu.random_unitary(space)

    z1 = U * z
Beispiel #15
0
def test_random():

    A = Qu.random((2, 2), 'ud')

    A = Gate.random_hermitian(4)
    assert A.is_hermitian()

    A = Gate.random_unitary(4)
    assert A.is_unitary()
Beispiel #16
0
def build_icosa(idx=0):
    " binary _icosahedral group "

    i = cyclotomic(4)
    v = cyclotomic(10)
    z = v**2
    r5 = 2 * z**3 + 2 * z**2 + 1  #sqrt(5)
    gen = [
        [  # Same as Nat ?
            [[z**3, 0], [0, z**2]],
            [[(z**4 - z) / r5, (z**2 - z**3) / r5],
             [(z**2 - z**3) / r5, -(z**4 - z) / r5]]
        ],
        [  # Nat
            [[v, 0], [0, v**9]],
            [[(5 * (v - v**4) - r5 * (v + v**4)) / 10.,
              (-2 * r5 * (v + v**4)) / 10.],
             [(-2 * r5 * (v + v**4)) / 10.,
              (5 * (v - v**4) + r5 * (v + v**4)) / 10.]]
        ],
        [
            [[0, i], [i, (1 - r5) / 2]],
            [[-i, i], [-(1 - r5) / 2., (1 - r5) / 2 + i]],
        ]
    ][idx]

    gen = [Qu((2, 2), 'ud', v) for v in gen]
    #a, b = gen
    #H = mulclose([b], maxsize=100)
    #print(len(H))

    X = Qu((2, 2), 'ud', [[0., 1], [1., 0.]])
    Z = Qu((2, 2), 'ud', [[1., 0], [0., -1.]])

    G = mulclose(gen, maxsize=256)

    assert len(G) == 120

    if idx in (0, 1):
        assert X * Z in G
        assert X not in G
        assert Z not in G

    return gen, G
Beispiel #17
0
 def make_state(self, idxs=None):
     "computational basis state"
     n = self.n
     v = Qu((self.d, ) * n, 'u' * n)
     if idxs is not None:
         bits = [0] * n
         for i in idxs:
             bits[i] = 1
         v[tuple(bits)] = 1.
     return v
Beispiel #18
0
def get_group(n):

    I = Gate.I
    X = Gate.X
    Z = Gate.Z
    Y = Gate.Y

    S = Qu((2, 2), 'ud', [[1, 0], [0, cyclotomic(4)]])

    for op in [X, Z]:
        op = reduce(matmul, [op] * n)
        yield op
Beispiel #19
0
def test_pure():
    v = Qu.random((2, ), 'u')
    v /= v.norm()
    A = v @ ~v
    A = Gate.promote(A)

    assert is_close(A.trace(), 1.)
    assert A.is_pure()

    A = 0.5 * Gate.dyads[0] + 0.5 * Gate.dyads[1]
    A = Gate.promote(A)
    assert is_close(A.trace(), 1.)
    assert not A.is_pure()
Beispiel #20
0
def test_bell_basis():

    raise test.Skip

    A = (Gate.H @ Gate.I) * Gate.CN

    print()
    xs = Qu.bell_basis(2)
    for x in xs:
        print(x.shortstr())

    print()
    for i, word in enumerate(['00', '01', '10', '11']):
        x = bits(word)
        print((A * x).shortstr())
Beispiel #21
0
    def build_compound(self, rows, col, namespace):
        tracks = self.tracks
        rank = self.rank
        ops = [self[row, col] for row in rows]
        if '.' in ops:
            ctrl_bits = [tracks.index(rows[idx])
                for idx in range(len(ops)) if ops[idx]=='.']

            A = Qu((2,)*(2*rank), 'ud'*rank)

            for ctrl in genidx((2,)*len(ctrl_bits)):
                factors = [Gate.I] * rank
                for idx, bit_idx in enumerate(ctrl_bits):
                    factors[bit_idx] = Gate.dyads[ctrl[idx]]
                if 0 not in ctrl: # all 1's
                    for row in rows:
                        op = self[row, col]
                        if op != '.':
                            op = 'X' if op == '+' else op
                            G = namespace.get(op)
                            if G is None:
                                raise ParseError(self, row, col, "cannot find gate %r"%op)
                            factors[tracks.index(row)] = G
                A += reduce(matmul, factors)

        elif 'x' in ops:
            if ops != ['x', 'x']:
                raise ParseError(self, rows, col, 'expected exactly two swaps (x)')
            row0, row1 = rows
            factors = [Gate.I]*(self.rank-1)
            factors[row0] = Gate.SWAP
            A = reduce(matmul, factors)
            A.swap2(tracks.index(row0)+1, tracks.index(row1))
        else:
            raise ParseError(self, rows, col, 'no control or swap found')
        return A
Beispiel #22
0
def build_tetra(idx=0):
    " binary _tetrahedral group "

    i = cyclotomic(4)
    r2 = sqrt(2)
    r3 = sqrt(3)
    r6 = sqrt(6)

    gen = [
        [  # same as Natural repr ?
            [[(-1 + i) / 2, (-1 + i) / 2], [(1 + i) / 2,
                                            (-1 - i) / 2]],  # order 3
            [[-1 / 2 - 1 / 2 * i, -1 / 2 - 1 / 2 * i],
             [1 / 2 - 1 / 2 * i, -1 / 2 + 1 / 2 * i]]  # order 3
        ],
        [  # Natural repr
            [[(1 + i * r3) / 2, 0], [0, (1 - i * r3) / 2]],
            [[(r3 - i) / (2 * r3), (-i * 2 * r2) / (2 * r3)],
             [(-i * 2 * r2) / (2 * r3), (r3 + i) / (2 * r3)]],
        ],
        [  # 2A
            [[(-2) / 2., 0], [0, (1 + i * r3) / 2.]],
            [[(2 * i) / (2 * r3), (r6 + i * r2) / (2 * r3)],
             [(r6 + i * r2) / (2 * r3), (i - r3) / (2 * r3)]],
        ],
        [  # 2B
            [[(1 - i * r3) / 2., 0], [0, (-2) / 2.]],
            [[(-r3 - i) / (2 * r3), (-r6 + i * r2) / (2 * r3)],
             [(-r6 + i * r2) / (2 * r3), (-2 * i) / (2 * r3)]],
        ],
    ][idx]

    gen = [Qu((2, 2), 'ud', v) for v in gen]
    a, b = gen
    #print( len(mulclose([b], maxsize=32)))
    assert len(mulclose([a], maxsize=32)) in (3, 6)
    assert len(mulclose([b], maxsize=32)) in (3, 6)

    Tetra = mulclose(gen, maxsize=32)
    assert len(Tetra) == 24

    return gen, Tetra
Beispiel #23
0
def test_evolve():

    t = 1.

    H = Gate.random_hermitian(2)
    U = H.evolution_operator(t)
    W = H.evolution_operator(t / 2)

    #print (W*W).shortstr()

    assert (W * W).is_close(U)

    v = Qu.random(2, 'u')
    v.normalize()

    #print
    w = U * v
    #print w.shortstr()

    x = W * v
    x = W * x
    #print x.shortstr()
    assert w.is_close(x)
Beispiel #24
0
def test_pipe_op():

    a = Qu(2, 'u', [0, 1])
    b = Qu(2, 'd', [1, 2])
    c = b * a  # dot product!
    assert type(c) is scalar
    assert is_close(c, 2)

    A = Qu((2, 2), 'ud')
    v = Qu(2, 'u')
    u = A * v
    assert u.space == v.space

    # The pipe operator: valence

    A = Gate.I @ Gate.X
    B0 = A * A
    B1 = A @ A

    assert B1.valence == "udududud"
    valence = list(B1.valence)
    B1 = B1.contract(1, 4)
    assert B1.valence == "uuddud"
    B1 = B1.contract(2, 4)
    assert B1.valence == "uudd"

    B1.permute([0, 2, 1, 3])

    assert B0.is_close(B1)

    #assert (1.j*A).is_close(1.j*A) # use left multiply for this...

    B = A.clone()
    A = A * A

    assert A.is_close(B * B)
Beispiel #25
0
from qupy.argv import argv
from qupy.util import mulclose, show_spec


I, X, Z, H = Gate.I, Gate.X, Gate.Z, Gate.H
S, T = Gate.S, Gate.T # S aka P
SWAP = Gate.SWAP
CX = Gate.CN

v = numpy.array([
    [1., 0., 0., 0.],
    [0., 1., 0., 0.],
    [0., 0., 1., 0.],
    [0., 0., 0., -1.]])
v.shape = (2,2,2,2)
CZ = Qu((2,2,2,2), 'udud', v)


def find(op, ops):
    for op1 in ops:
        #if numpy.abs(op-op1).sum() < EPSILON:
        if op == op1:
            return True
    return False


def inv(A):
    A = A.v
    a, b = A[0]
    c, d = A[1]
    r = 1. / (a*d - b*c)
Beispiel #26
0
def test_2():
    "two qubits"

    i = 1.j
    phase = numpy.exp(i*pi/8)

    II = I@I
    XI = X@I
    IX = I@X
    ZI = Z@I
    IZ = I@Z
    gen = [XI, IX, ZI, IZ, phase*II]
    gen = [op.flat() for op in gen]
    C1 = mulclose(gen)

    assert len(C1) == 256, len(C1)

#    CZ = Z.control()
#    op = CZ.flat()
#    print(op)
#    print(op.valence)

    r = numpy.exp(-pi*i/8)
    v = r*numpy.array([
        [1., 0., 0., 0.],
        [0., 1.j, 0., 0.],
        [0., 0., 1.j, 0.],
        [0., 0., 0., 1.]])
    CZ = Qu((4, 4), 'ud', v)

    II = II.flat()
    assert (CZ*~CZ) == II

    assert not find(CZ, C1)
    for g in C1:
        k = CZ * g * ~CZ
        assert find(k, C1)

    r = numpy.exp(pi*i/8)/sqrt(2)
    v = r*numpy.array([[1., i], [i, 1.]])
    H = Qu((2, 2), 'ud', v)

    plus = 1./sqrt(2) * (bitvec(0) + bitvec(1)) 
    #print(plus)
    #print(H*plus) # proportional to plus state

    IH = (I@H).flat()
    HI = (H@I).flat()
    HH = (H@H).flat()

    assert IH * CZ * IH == CZ * IH * CZ # Reidemeister III move

    g = CZ * HH * CZ
    assert g*g == II
    assert g != SWAP.flat()

    A = (CZ @ I).flat()
    B = (I @ CZ).flat()
    III = (II@I).flat()

    assert A*B == B*A

    ABA = A*B*A
    BAB = B*A*B
    r = numpy.exp(pi*i/8)
    print((ABA/r).shortstr())
    print((BAB/r).shortstr())
    #assert A*B*A == B*A*B
    op = III
    for i in range(1, 100):
        op = ABA * op
        if op == III:
            break
    else:
        assert 0
    print(i)

    assert ABA ** 16 == III
    assert BAB ** 16 == III
Beispiel #27
0
def test_setitem():

    A = Qu((2, ) * 3, 'uuu')
    A[:] = bits('010')
Beispiel #28
0
def main():

    _I = Qu((2, 2), 'ud', [[1, 0], [0, 1]])
    _X = Qu((2, 2), 'ud', [[0, 1], [1, 0]])
    _Z = Qu((2, 2), 'ud', [[1, 0], [0, -1]])
    _Y = _X*_Z
    _S = Qu((2, 2), 'ud', [[1, 0], [0, cyclotomic(4)]])
    _T = Qu((2, 2), 'ud', [[1, 0], [0, cyclotomic(8)]])
    _Ti = _T.dag()
    assert _T * _Ti == _I
    _H = (1./sqrt(2))*Qu((2, 2), 'ud', [[1, 1], [1, -1]])

    pauli = build_algebra("IXZY",
        "X*X=I Z*Z=I Y*Y=-I X*Z=Y Z*X=-Y X*Y=Z Y*X=-Z Z*Y=-X Y*Z=X")

    I = pauli.I
    X = pauli.X
    Y = pauli.Y
    Z = pauli.Z

    S, Si, T, Ti, H = list(promote_pauli(pauli, [_S, _S.dag(), _T, _Ti, _H]))
    assert S*Si == I
    assert Si*S == I
    assert T*Ti == I
    assert Ti*T == I
    K = S*H # the "Bravyi-Kitaev T" gate
    Ki = H*Si
    assert K*Ki == I

    print("S conjugation:")
    for src in [X, Z, Y]:
        print(src, "-->", S*src*Si)
    print("T conjugation:")
    for src in [X, Z, Y]:
        print(src, "-->", T*src*Ti)
    return

    if argv.hamiltonian:
        P = get_hamiltonian(pauli)
    else:
        code = build_code(pauli)
        P = code.get_projector()
        m = len(code.ops) # number of independent stab gen
        print("m =", m)
        r = 2**(-m)
        P = r*P
        assert P*P == P

    n = P.grade

    #Xn = reduce(matmul, [X]*n)
    #print("K transverse:", Kn*P == P*Kn)
    #print(P*Kn == Kn*P)

    gate = argv.get("gate", "S")

    if gate == "X":
        A = B = reduce(matmul, [X]*n)
        Q = Xn*P*Xn
    elif gate == "S": # clifford
        A = reduce(matmul, [S]*n)
        B = reduce(matmul, [Si]*n)
    elif gate == "SSdag": # clifford
        A = reduce(matmul, ([S, Si]*n)[:n])
        B = reduce(matmul, ([Si, S]*n)[:n])
    elif gate == "S2": # clifford
        A = reduce(matmul, [S, Si]*2 + [I]*4)
        B = reduce(matmul, [Si, S]*2 + [I]*4)
    elif gate == "K": # clifford
        A = reduce(matmul, [K]*n)
        B = reduce(matmul, [Ki]*n)
    elif gate == "T": # non-clifford
        A = reduce(matmul, [T]*n)
        B = reduce(matmul, [Ti]*n)
    else:
        assert 0, gate

    print("go:")
    Q = A*P*B

    print("32*Q:")
    print(32*Q)
    print("Q==P:", Q == P)

    QQ = (Q*Q)
    print("Q*Q==Q:", QQ == Q)
Beispiel #29
0
def main():

    t = argv.get("t", 0.02)

    T = build("""
    XZZXI 
    IXZZX
    XIXZZ 
    ZXIXZ
    XXXXX
    """)

    n = 5
    stabs = [ 
        X@Z@Z@X@I, 
        I@X@Z@Z@X, 
        X@I@X@Z@Z, 
        Z@X@I@X@Z]
    Lx = X@X@X@X@X
    #Lz = Z@Z@Z@Z@Z
    Lz = T[n-1]

    ops = stabs + [Lx]
    for i in range(n):
      for j in range(n):
        c = (T[i]*ops[j] == ops[j]*T[i])
        assert c == (i!=j)
        a = (T[i]*ops[j] == -ops[j]*T[i])
        assert a == (i==j)

    G = mulclose(stabs)
    assert len(G) == 16
    N = len(G)
    P = (1./N) * sumops(G)

    assert P*P == P

    for op in stabs:
        assert Lx*op == op*Lx
        assert Lz*op == op*Lz

    trials = argv.get("trials", 100)

    for trial in range(trials):

        if 1:
            # noise on one qubit
            U = errop(t)
            U = U@I@I@I@I
        else:
            # noise on all qubits
            Us = []
            for i in range(n):
                Us.append(errop(t))
            U = reduce(matmul, Us)

        v = Qu.random((2,)*n, "u"*n)
        v = P*v
        v.normalize()
        #print(v.flatstr())

        u = U*v
        u.normalize()
    
        for i, op in enumerate(stabs):
            val, u = measure(op, u)
            print(fstr(val), end=" ")
            if val < 0:
                #print("*")
                u = T[i]*u
        #print()
    
        #for i, op in enumerate(stabs):
            #val, v = measure(op, v)
            #print(fstr(val), end=" ")
        #print()
    
        assert P*u == u
        assert P*v == v

        phase = None
        for uu in [u, Lx*u, Lz*u, Lx*Lz*u]:
            r = ~uu * v
            if abs(1. - abs(r)) < EPSILON:
                #write("+")
                phase = r
            #else:
                #write(".")
        #write("\n")
        if phase is not None:
            print(phase)
        else:
            print()