pyGSTi icon indicating copy to clipboard operation
pyGSTi copied to clipboard

Instructions to generate compilation rules for a custom gate set

Open johnkgamble opened this issue 2 years ago • 13 comments

I am trying to run two-qubit direct RB with a gate set comprising X(pi/2), Y(pi/2), and X(pi/2) x X(pi/2). I can form the processor spec without any issues like so:

import numpy as np
from pygsti.processors import QubitProcessorSpec as QPS

uxx = 1./np.sqrt(2) * np.array([[1,0,0,-1j],
                               [0,1,-1j,0],
                               [0,-1j,1,0],
                               [-1j,0,0,1]])
pspec = QPS(2, ["Gxpi2", "Gypi2","Gxx"],
            geometry='line',
            nonstd_gate_unitaries={'Gxx':uxx},
            qubit_labels=qubit_labels)

However, when I set up direct RB, I'm not sure how to proceed with the compilation rules. The examples that I've seen all use:

  compilations = {
          "absolute": CCR.create_standard(
              pspec, "absolute", ("paulis", "1Qcliffords"), verbosity=0
          ),
          "paulieq": CCR.create_standard(
              pspec, "paulieq", ("1Qcliffords", "allcnots"), verbosity=0
          ),
      }

but this doesn't seem appropriate for my case since I don't have CNOTs. Just using the default compilation rules above does indeed error out for direct RB. I don't feel like I have a strong intuition around the compilation rule format - is there any guidance on how to accomplish this? Thanks!

Ping @kmrudin , @tjproct

johnkgamble avatar Jul 13 '22 04:07 johnkgamble

Hi John,

The compilation rules is something that is required for constructing the stabilizer state preparation and measurement sub-circuits in a DRB circuit. The compilation algorithm for constructing a uniformly random stabilizer state works in terms of CNOT and single-qubit gates. It then needs to replace those gates with whatever gates you have. This is what the compilation rules are for. For example, for each CNOT in that circuit it replaces it with your compilation of CNOT into your gate set (it can actually replace it with any circuit that implements a unitary that is equal to CNOT times Paulis, which is why we create a "paulieq" compilation for CNOTs). There's a default, brute-force, algorithm that creates these compilations that is run when you call CCR.create_standard(), and there might be occasions when you'd want to override that algorithm and do it yourself (in your case you get very efficient compilations of these gates though --- you can see you'll be using only a single Gxx gate to implement CNOT up to Paulis if you inspect compilations['paulieq']._clifford_templates).

So using the standard format for the compilation rules will get you exactly what you want. Unsurprisingly, it's not guaranteed that you're getting optimal depth / two-qubit gate count compilations of the stabilizer state prep and measurement circuits -- although I think our compilation algorithms are actually pretty decent -- but this has no impact on what your DRB experiments will be measuring (it just impacts the depth-0 success probability intercept, i.e., the A in Ap^d fit).

If you want to understand what's going on more, take a look at some depth-0 DRB circuits created using the code you posted here. You'll see that those circuits contain only the gates in your gate set (but they've been constructed using the compilation routine described above, that creates CNOT + all single-qubit Clifford circuits at an intermediate step).

EDIT: In case this wasn't clear, the compilation rules have no impact on the "core" of the DRB circuit, which consists of layers of gates from your gate set, sampled according to the sampling arguments provided in the DRB circuit creation routine. DRB is measuring the average error rate (weighting according to your sampling) of those layers.

tjproct avatar Jul 13 '22 13:07 tjproct

Tim, thanks for the detailed explanation - this is crystal clear now. And the code I posted above actually does work for me!

I was getting myself confused with a weird interaction from the processor spec (maybe it's a bug, or maybe I just don't understand something about processor specs?).

When I do the following, everything works as expected, and the auto-compilations look efficient as you indicated:

from pygsti.processors import QubitProcessorSpec as QPS
import numpy as np

uxx = 1./np.sqrt(2) * np.array([[1,0,0,-1j],
                               [0,1,-1j,0],
                               [0,-1j,1,0],
                               [-1j,0,0,1]])
pspec = QPS(2, ["Gxpi2", "Gypi2","Gxx"],
            geometry='line',
            nonstd_gate_unitaries={'Gxx':uxx},
            qubit_labels=qubit_labels)
compilations = {
        "absolute": CCR.create_standard(
            pspec, "absolute", ("paulis", "1Qcliffords"), verbosity=0
        ),
        "paulieq": CCR.create_standard(
            pspec, "paulieq", ("1Qcliffords", "allcnots"), verbosity=0
        ),
    }
depths = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
qubit_labels = ['Q0','Q1']
k = 10
rb_design = pygsti.protocols.DirectRBDesign(pspec, compilations, depths, k, qubit_labels=qubit_labels)

However, when I instead specify the processor using availability, I get the following:

from pygsti.processors import QubitProcessorSpec as QPS
import numpy as np

uxx = 1./np.sqrt(2) * np.array([[1,0,0,-1j],
                               [0,1,-1j,0],
                               [0,-1j,1,0],
                               [-1j,0,0,1]])
availability = {
    'Gxpi2': [('Q0',), ('Q1',)],
    'Gypi2': [('Q0',), ('Q1',)],
    'Gxx':[('Q0','Q1'),('Q1','Q0')]}

pspec = QPS(2, ["Gxpi2", "Gypi2","Gxx"],
            availability=availability,
            nonstd_gate_unitaries={'Gxx':uxx},
            qubit_labels=qubit_labels)
compilations = {
        "absolute": CCR.create_standard(
            pspec, "absolute", ("paulis", "1Qcliffords"), verbosity=0
        ),
        "paulieq": CCR.create_standard(
            pspec, "paulieq", ("1Qcliffords", "allcnots"), verbosity=0
        ),
    }
depths = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
qubit_labels = ['Q0','Q1']
k = 10
rb_design = pygsti.protocols.DirectRBDesign(pspec, compilations, depths, k, qubit_labels=qubit_labels)
File ~/repos/pyGSTi/pygsti/algorithms/compilers.py:2154, in compile_stabilizer_state(s, p, pspec, absolute_compilation, paulieq_compilation, qubit_labels, iterations, paulirandomize, algorithm, aargs, costfunction, rand_state)
   2151     except AssertionError:
   2152         failcount += 1
-> 2154 assert(failcount <= 5 * iterations), \
   2155     ("Randomized compiler is failing unexpectedly often. "
   2156      "Perhaps input QubitProcessorSpec is not valid or does not contain the neccessary information.")
   2158 if paulirandomize:
   2159     paulilist = ['I', 'X', 'Y', 'Z']

AssertionError: Randomized compiler is failing unexpectedly often. Perhaps input QubitProcessorSpec is not valid or does not contain the neccessary information.

I don't see a difference between the two models produced by the different ways of specifying the processor. One thing that I do notice is that neither of the pspecs have an identity compilation rule stated in compilations['paulieq']._clifford_templates. After I run the DRB design function, the one that terminates successfully has injected an identity compilation rule into that dictionary as a side-effect. Any idea what's going on?

johnkgamble avatar Jul 13 '22 16:07 johnkgamble

This sounds like more of a bug. I would have expected geometry='line' and your availability dict to be roughly the same. They follow slightly different code paths in the pspec, but it's not immediately obvious to me why they would trigger any difference in the compilation rules (because there is a unified interface that the comp rules use to resolve availability). I'll try to take a closer look at this soonish.

sserita avatar Jul 13 '22 17:07 sserita

Thanks Stefan! This looks horrifying enough (from my perspective of no longer understanding many of the inner workings of pyGSTi) that I probably won't get to it for a while.

tjproct avatar Jul 13 '22 17:07 tjproct

Indeed, thanks Stefan! The motivation for this is that I'd actually like to set my availability dict to be:

availability = {
    'Gxpi2': [('Q0',), ('Q1',)],
    'Gypi2': [('Q0',), ('Q1',)],
    'Gxx':[('Q0','Q1')]}

since Gxx is actually symmetric in qubits, so there's no need to consider a Gxx0->1 vs. a Gxx1->0 (unlike a gate like CNOT). Doing this results in the same error, but I wanted to make the example for the bug identical to the working case.

For now I'll just use the linear model and set the two versions of Gxx to be identical for simulation purposes.

johnkgamble avatar Jul 13 '22 17:07 johnkgamble

It might work find if you drop the single-qubit gates from the availability dict (I don't usually include them, as it at least used to default to maximal availability for gates that aren't included, and I have never encountered this bug).

tjproct avatar Jul 13 '22 17:07 tjproct

This, I believe, is a known issue/bug with the way the compilations rules interact with a process spec object. Currently, the compilation rules require that the availability be given as a geometry to work properly, and so this is not equivalent to specifying the availability separately (even though they do lead to the same model).

enielse avatar Jul 13 '22 17:07 enielse

Tim, just tried this out and unfortunately no dice (after I fixed my typo):

from pygsti.processors import QubitProcessorSpec as QPS
import numpy as np

uxx = 1./np.sqrt(2) * np.array([[1,0,0,-1j],
                               [0,1,-1j,0],
                               [0,-1j,1,0],
                               [-1j,0,0,1]])
availability = {
    'Gxx':[('Q0','Q1'),('Q1','Q0')]}
pspec = QPS(2, ["Gxpi2", "Gypi2","Gxx"],
            availability=availability,
            nonstd_gate_unitaries={'Gxx':uxx},
            qubit_labels=qubit_labels)
compilations = {
        "absolute": CCR.create_standard(
            pspec, "absolute", ("paulis", "1Qcliffords"), verbosity=0
        ),
        "paulieq": CCR.create_standard(
            pspec, "paulieq", ("1Qcliffords", "allcnots"), verbosity=0
        ),
    }
depths = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
qubit_labels = ['Q0','Q1']
k = 10
rb_design = pygsti.protocols.DirectRBDesign(pspec, compilations, depths, k, qubit_labels=qubit_labels)

Thanks for the information, Erik.

johnkgamble avatar Jul 13 '22 17:07 johnkgamble

Ah, oh well! Thanks Erik! This is now clearly beyond my pyGSTi powers to understand or contribute to, so I'll leave this to Stefan and/or Erik.

tjproct avatar Jul 13 '22 17:07 tjproct

I think it will work if you just use the geometry argument instead of availability to specify the available 2Q gates. This shouldn't be necessary, and so to be clear this is a hack, but if you create your own geometry:

my_geometry = pygsti.baseobjs.QubitGraph(qubit_labels=['Q0','Q1'], initial_edges=[('Q0', 'Q1')], directed=False)

(this line is based on your desired availability dictionary being just a single 'edge' from Q0 to Q1 but not the other, so directed=False) and then setting this as your processor spec's geometry:

pspec = QPS(2, ["Gxpi2", "Gypi2","Gxx"],
            geometry=my_geometry,
            nonstd_gate_unitaries={'Gxx':uxx},
            qubit_labels=qubit_labels)

Let me know if this works.

enielse avatar Jul 13 '22 17:07 enielse

Thanks, Erik. The compilation works fine in the above, but somehow the model still has a redundant gate (both Gxx:Q0:Q1 and Gxx:Q1:Q0).

johnkgamble avatar Jul 13 '22 17:07 johnkgamble

Hmmm - the compilation code probably doesn't check the geometry object properly then. Well, it was worth a shot. Let's leave this issue open until I or someone else has time to implement a fix.

enielse avatar Jul 13 '22 17:07 enielse

Erik, it looks like the comp rules is just looking at the availability dict, not the geometry. Probably things work with the default availability of "all-edges" but other edge cases (pun intended) don't work as well. I feel like there is some room for improvement regarding availability vs geometry in the pspec, and maybe the comp rules could use the resolve_availability functions instead of pulling the availability directly from the pspec. Maybe we should talk about that offline?

sserita avatar Jul 13 '22 18:07 sserita