def countY(circuit):
    Ts = 0
    Tcache = []  # recent T gates to check for any consecutive Ts
    Ms = []

    MTs = []
    # measurements depending on which a T is performed
    # depending on postselection we might not need those T
    # these are not counted in Ts variable

    lineNr = 0
    for line in circuit.splitlines():
        lineNr += 1
        gate = standardGate(line, lineMode=True)

        # identify measured qubits
        if 'M' in gate:
            Midx = gate.index('M')
            if Midx not in Ms:
                Ms.append(Midx)

        # Check for operations performed on already measured qubits
        for Midx in Ms:
            if gate[Midx] not in ['M', '_']:
                raise SyntaxError("Line %d: Cannot perform operations to measured qubit %d" % (lineNr, Midx))

        if 'T' in gate and 'M' not in gate:
            Ts += 1  # count T gate

            # Put qubit into Tcache
            Tidx = gate.index('T')
            if Tidx in Tcache:  # already there: consecutive Ts
                raise SyntaxError("Line %d: Consecutive Ts on qubit %d" % (lineNr, Midx))
            Tcache.append(Tidx)

        # remove qubits from Tcache if a gate is encountered
        for Tidx in Tcache:
            if gate[Tidx] != '_':
                Tcache.remove(Tidx)

        # T that is applied conditionally on a measurement
        if 'T' in gate and 'M' in gate:
            Midx = gate.index('M')
            MTs.append(Midx)

    return Ts, Ms, MTs
def gadgetize(circuit, Mdict, y):
    n = len(standardGate(circuit.split('\n', 1)[0], lineMode=True))
    size = n + len(y)

    phases = []  # list of numbers in Z_4
    xs = []  # lists of bitstrings
    zs = []

    def gen(idx, val, phases, xs, zs):
        xs.append(np.zeros(size).astype(int))
        z = np.zeros(size)
        z[idx] = 1
        zs.append(z)

        phase = 0
        if val == 1: phase = 2
        phases.append(phase)
        return phases, xs, zs

    # set measured ancillas
    for M in Mdict.keys(): phases, xs, zs = gen(M, Mdict[M], phases, xs, zs)

    # set T ancillas
    for i in range(n, size): phases, xs, zs = gen(i, y[i-n], phases, xs, zs)

    # conjugate stabilizer generators:
    tpos = n

    # from main import printProjector
    # printProjector((phases, xs, zs))
    for line in reversed(circuit.splitlines()):
        if line == "": continue

        gate = standardGate(line, lineMode=True)

        #  skip if contains a measurement
        if 'M' in gate:
            Midx = gate.index('M')
            if Mdict[Midx] == 0: continue

        std = standardGate(line)

        if False:
            pr = "  "
            for i in line:
                if i == "T":
                    pr = pr + "[" + i + str(y[tpos-n]) + "]"
                elif i == "M":
                    pr = pr + "[" + i + "1]"
                else:
                    pr = pr + "[" + i + " ]"
            print(pr)

        idxs = []
        for letter in std:
            idxs.append(gate.index(letter))

        lookup = {
            "H": {(0, 0): (0, 0, 0), (1, 0): (0, 0, 1), (0, 1): (0, 1, 0), (1, 1): (2, 1, 1)},  # X->Z, Z->X
            "X": {(0, 0): (0, 0, 0), (1, 0): (0, 1, 0), (0, 1): (2, 0, 1), (1, 1): (2, 1, 1)},  # X->X, Z->-Z
            "S": {(0, 0): (0, 0, 0), (1, 0): (1, 1, 1), (0, 1): (0, 0, 1), (1, 1): (1, 1, 0)},  # X->Y, Z->Z
        }

        def conjugateLookup(std, idxs, phase, xs, zs):
            if (len(std) == 1):
                mapping = lookup[std]
                state = (xs[idxs[0]], zs[idxs[0]])
                ph, xs[idxs[0]], zs[idxs[0]] = mapping[state]
                phase = (phase + ph) % 4
            else:  # evaluate CNOT
                top = ""
                bot = ""
                if xs[idxs[0]] == 1:
                    top += "X"
                    bot += "X"
                if zs[idxs[0]] == 1: top += "Z"
                if xs[idxs[1]] == 1: bot += "X"
                if zs[idxs[1]] == 1:
                    top += "Z"
                    bot += "Z"

                top = top.replace("ZZ", "")
                bot = bot.replace("XX", "")

                xs[idxs[0]] = 1 if ("X" in top) else 0
                zs[idxs[0]] = 1 if ("Z" in top) else 0
                xs[idxs[1]] = 1 if ("X" in bot) else 0
                zs[idxs[1]] = 1 if ("Z" in bot) else 0

            return phase, xs, zs

        for g in range(len(phases)):  # update all generators
            if std == "T":
                # execute T gadget
                phases[g], xs[g], zs[g] = conjugateLookup("CX", [idxs[0], tpos], phases[g], xs[g], zs[g])
                if y[tpos-n] == 1:
                    phases[g], xs[g], zs[g] = conjugateLookup("S", idxs, phases[g], xs[g], zs[g])

                # prepend HS^\dagger to t registers in compiled circuit
                for tUpdateGate in ["H", "S", "S", "S"]:
                    phases[g], xs[g], zs[g] = conjugateLookup(tUpdateGate, [tpos], phases[g], xs[g], zs[g])

                continue

            phases[g], xs[g], zs[g] = conjugateLookup(std, idxs, phases[g], xs[g], zs[g])

        if std == "T":
            tpos += 1

        # printProjector(([phases[0]], [xs[0]], [zs[0]]))
        # printProjector((phases, xs, zs))
        # test = input()
        # print("")

    # printProjector((phases, xs, zs))
    # raise ValueError("hi")
    return phases, xs, zs
def projectors(circ, measure, verbose=False, x=None, y=None):
    n = len(compilecirc.standardGate(circ.splitlines()[0], lineMode=True))

    t, Ms, MTs = gadgetize.countY(circ)
    # t  - number of T gates
    # Ms - qubits measured by circuit
    # MTs - qubits measured by circuit, depending on
    #               which a T gate is performed

    # postselect circuit measured qubits
    if x is None:
        Mselect = np.random.randint(2, size=len(Ms))
    else:
        tmpX = []
        if len(x) != len(Ms): raise ValueError("x needs to have length %d" % len(Ms))
        for l in x:
            if l == "0": tmpX.append(0)
            elif l == "1": tmpX.append(1)
            else: raise ValueError("Only 0s and 1s allowed in x string.")
        Mselect = np.array(tmpX).astype(int)

    # Increment t for measurement-dependent Ts
    for M in MTs:
        idx = Ms.index(M)
        if Mselect[idx] == 1:
            t += 1

    # pack circuit into measurement dictionary
    Mdict = {}
    for M in Ms:
        idx = Ms.index(M)
        Mdict[M] = Mselect[idx]
        if M in measure.keys(): continue
        measure[M] = Mselect[idx]

    # postselect measurement results on Ts
    if y is None:
        y = np.random.randint(2, size=t)
    else:
        tmpY = []
        if len(y) != t: raise ValueError("y needs to have length %d" % t)
        for l in y:
            if l == "0": tmpY.append(0)
            elif l == "1": tmpY.append(1)
            else: raise ValueError("Only 0s and 1s allowed in y string.")
        y = np.array(tmpY).astype(int)

    # Print measurement information:
    if verbose:
        print("n = %d, t = %d" % (n, t))
        print("Measurement info:")

        mstring = ""
        for i in range(n):
            if i in measure:
                mstring += str(measure[i])
            else: mstring += "_"
        print("x:", mstring)

        print("y:", "".join(y.astype(str).tolist()))

    G = gadgetize.gadgetize(circ, measure, y)
    H = gadgetize.gadgetize(circ, Mdict, y)

    return G, H, n, t  # also return n and t for convenience