qBraid / qBraid

A platform-agnostic quantum runtime framework
https://docs.qbraid.com/sdk
GNU General Public License v3.0
62 stars 24 forks source link

Cirq --> pyQuil transpiler error for XX, YY, ZZ gates #386

Open ryanhill1 opened 10 months ago

ryanhill1 commented 10 months ago

There is a bug transpiling Cirq circuits containing the XXPowGate, YYPowGate or ZZPowGate to pyQuil. Below is a self-contained example showing the bug for the XXPowGate:

import cirq

from qbraid.transpiler import transpile
from qbraid.interface import circuits_allclose

# Create a quantum circuit with two qubits
q0, q1 = cirq.LineQubit.range(2)
cirq_in = cirq.Circuit()

# Add XX, YY, and ZZ gates to the circuit
exponents = [0.5, 1]
for theta in exponents:
    cirq_in.append(cirq.XXPowGate(exponent=theta).on(q0, q1))

pyquil_program = transpile(cirq_in, "pyquil")
cirq_out = transpile(pyquil_program, "cirq")

cirq_roundtrip_equal = circuits_allclose(cirq_in, cirq_out)

print(f"Cirq input:\n{cirq_in}\n")
print(f"Cirq -> pyQuil:\n{pyquil_program}\n")
print(f"pyQuil -> Cirq:\n{cirq_out}\n")
print(f"Cirq expected output:\n{cirq_in}\n")
print(f"Cirq roundtrip unitaries equal: {cirq_roundtrip_equal}")
Cirq input:
0: ───XX───────XX───
      │        │
1: ───XX^0.5───XX───

Cirq -> pyQuil:
RX(pi/2) 0
RX(pi/2) 1
X 0
X 1

pyQuil -> Cirq:
0: ───Rx(0.5π)───X───

1: ───Rx(0.5π)───X───

Cirq expected output:
0: ───XX───────XX───
      │        │
1: ───XX^0.5───XX───

Cirq roundtrip unitaries equal: False
krpcannon commented 10 months ago

Hi @ryanhill1 , would you kindly assign this issue to me? (or another 'good first issue' as you see fit)

ryanhill1 commented 10 months ago

Hi @krpcannon, absolutely! Upon further investigation, this is actually a bug in cirq_rigetti.quil_output.py that we hope to create a work-around for. For example, cirq_rigetti maps the cirq.ops.XXPowGate to a function _xxpow_gate():

def _xxpow_gate(op: cirq.Operation, formatter: QuilFormatter) -> str:
    gate = cast(cirq.XPowGate, op.gate)
    if gate._exponent == 1:
        return formatter.format('X {0}\nX {1}\n', op.qubits[0], op.qubits[1])
    return formatter.format(
        'RX({0}) {1}\nRX({2}) {3}\n',
        gate._exponent * np.pi,
        op.qubits[0],
        gate._exponent * np.pi,
        op.qubits[1],
    )

which uses two PyQuil X gates (for exponent = 1), or two Rx gates (otherwise). In most cases, this mapping is does not create an output circuit with an equivalent unitary. So we want to re-implement this function in our duplicate qbraid.transpiler.conversions.cirq.cirq_quil_output.py and investigate which built-in PyQuil gate(s) can be used to construct an equivalent mapping. Where/if necessary, we can also fall back to an OpenQASM decomposition for the conversion, e.g.

>>> import cirq
>>> q0, q1 = cirq.LineQubit.range(2)
>>> circuit = cirq.Circuit()
>>> circuit.append(cirq.XXPowGate(exponent=0.5).on(q0,q1)
>>> print(circuit.to_qasm())
// Generated from Cirq v1.3.0.dev20230825224332

OPENQASM 2.0;
include "qelib1.inc";

// Qubits: [q(0), q(1)]
qreg q[2];

// Gate: XX**0.5
ry(pi*-0.5) q[0];
ry(pi*-0.5) q[1];
s q[0];
s q[1];
u3(pi*0.5,0,pi*1.25) q[0];
u3(pi*0.5,pi*1.0,pi*1.75) q[1];
sx q[0];
cx q[0],q[1];
rx(0) q[0];
ry(pi*0.5) q[1];
cx q[1],q[0];
sxdg q[1];
s q[1];
cx q[0],q[1];
u3(pi*0.5,pi*1.25,pi*1.0) q[0];
u3(pi*0.5,pi*0.75,0) q[1];
ry(pi*0.5) q[0];
ry(pi*0.5) q[1];

and converted the decomposed gate set. However, if there is a more compact solution, that would be preferred.