quil-lang / quilc

The optimizing Quil compiler.
Apache License 2.0
454 stars 73 forks source link

dagger'd parametric 2Q gate raises compiler-does-not-apply #716

Closed notmgsk closed 2 years ago

notmgsk commented 3 years ago

echo 'DECLARE x REAL[1]; DAGGER CPHASE(x[0]) 0 1' | quilc

Unhandled CL-QUIL::COMPILER-DOES-NOT-APPLY in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                                        {10005D0083}>:
  Condition CL-QUIL::COMPILER-DOES-NOT-APPLY was signalled.
karlosz commented 2 years ago

It's unclear to me here what the compiler should attempt to produce. Can gates be parameterized on classical memory locations like this? Or do we just want a better error in this case?

ecpeterson commented 2 years ago

In general, I think “no applicable compilers” is a fine complaint. In this specific case (and in many similar others) DAGGER CPHASE(*) can be rewritten as CPHASE(-*), and someone should write a define-compiler which accomplishes that. That someone might also have to extend find-applicable-compilers to cope with gate name modifiers; I’m not sure if that functionality exists today.

genos commented 2 years ago

Apologies for necro-posting on an old issue, but I've been bitten by this behavior so I thought I'd chime in.

One quantum ML technique takes an encoding unitary E and runs two data points x_i and x_j through the circuit E(x_i)E†(x_j) to get a measure of their similarity. In pyquil with a simple angle encoding, it's easy enough to do something like

def angle_encoding(qubits: List[int], parameters: MemoryReference) -> Program:
    encoding = Program()
    for i, q in enumerate(qubits):
        encoding += RX(parameters[i], q)
    return encoding

and then use it like

program = Program()
x_i = program.declare("x_i", "REAL", num_qubits)
x_j = program.declare("x_j", "REAL", num_qubits)
program += angle_encoding(qubits, x_i)
program += angle_encoding(qubits, x_j).dagger()

However, a more complicated encoding similar to Pennylane's IQPEmbedding might look like

def iqp_style_encoding(qubits: List[int], parameters: MemoryReference) -> Program:
    n = len(qubits)
    program = Program()
    for _ in range(2):
        for i, q in enumerate(qubits):
            program += H(q)
            program += RZ(parameters[i], q)
        for i, j in zip(range(n), range(1, n)):
            program += CPHASE(parameters[i] * parameters[j], qubits[i], qubits[j])
    return program

Due to this compiler-does-not-apply behavior, we need to write the dagger-ed encoding ourselves or resort to something like

def iqp_style_dagger(qubits: List[int], parameters: MemoryReference) -> Program:
    program = Program()
    for i in reversed(iqp_style_encoding(qubits, parameters).instructions):
        if isinstance(i, Gate) and i.name == "CPHASE" and isinstance(i.params[0], Mul):
            i.params[0].op1 *= -1.0
            program += i
        elif isinstance(i, Gate):
            program += i.dagger()
        else:
            program += i
    return program

From the perspective of someone further up the stack, it'd be great if quilc could do the above for us.

stylewarning commented 2 years ago

@genos Thanks for bringing it up. I'll mention it at out Quil-Lang meeting this week and see if we can get it resolved.

stylewarning commented 2 years ago

@genos I pushed up a fix as a PR. Once reviewed, this problem should be solved. :)

genos commented 2 years ago

Amazing @stylewarning, thanks! Will this handle both negating parameters and reversing order? Or is the latter handled by pyquil? (Asked an annoying and needy user)

stylewarning commented 2 years ago

fix committed to master!

stylewarning commented 2 years ago

Amazing @stylewarning, thanks! Will this handle both negating parameters and reversing order? Or is the latter handled by pyquil? (Asked an annoying and needy user)

This will handle DAGGERing gates. DAGGERing circuits is already supported (which will reverse and negate accordingly).

DAGGERing won't work on user-defined parametric gates because the compiler won't be smart enough to figure out that DAGGER would actually negate the parameter. But for standard gates it'll be OK.