Closed notmgsk closed 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?
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.
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.
@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.
@genos I pushed up a fix as a PR. Once reviewed, this problem should be solved. :)
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)
fix committed to master!
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 DAGGER
ing gates. DAGGER
ing circuits is already supported (which will reverse and negate accordingly).
DAGGER
ing 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.
echo 'DECLARE x REAL[1]; DAGGER CPHASE(x[0]) 0 1' | quilc