Using native gates with Cirq
Learn how to use our hardwarenative gateset to run a circuit with Cirq
Introduction
Building and submitting circuits using IonQ’s hardwarenative gateset enables you to bypass our compiler and optimizer, providing more control and transparency than the default abstract gateset (though often at the cost of performance and convenience).
Before working with native gates in Cirq, we recommend reviewing our guides on Getting Started with Native Gates and Getting Started with Cirq. Native gates are also supported in the IonQ API, Qiskit, and PennyLane.
Building a circuit with native gates
IonQ’s native gates are provided as part of the cirqionq
package, including:
GPIGate(phi)
GPI2Gate(phi)
MSGate(phi0, phi1, theta=0.25)
for Aria and Harmony systems
The ZZ twoqubit gate used on our Forte systems is currently not available in CirqIonQ. To use this type of native gate, build and submit circuits via the IonQ API or Qiskit.
The native gates can be imported from cirq_ionq.ionq_native_gates
:
import cirq
import cirq_ionq
from cirq_ionq.ionq_native_gates import GPIGate, GPI2Gate, MSGate
For more details about these gate definitions and parameters, refer to the native gates guide.
Native gates are used like other gates when building a circuit:
# Initialize the quantum circuit
q0, q1, q2 = cirq.LineQubit.range(3)
# Define gates (with parameter values in turns) and build the circuit
gpi = GPIGate(phi=0.5).on(q0)
gpi2 = GPI2Gate(phi=0.5).on(q1)
ms = MSGate(phi0=0, phi1=0.5).on(q1, q2)
meas = cirq.measure(q0, q1, q2, key='output')
circuit = cirq.Circuit([gpi, gpi2, ms, meas])
print(circuit)
0: GPI(0.5)M('output')
│
1: GPI2(0.5)MS(0)M
│ │
2: MS(0.5)M
Note that Cirq also defines an MS gate in cirq.MSGate
, but this gate is not equivalent to the IonQ native gates. To build a circuit in IonQ native gates, make sure you’re using the MS gate imported from cirq_ionq
.
Submitting a circuit with native gates
No changes or special arguments are needed to submit a native gate circuit to a cirq_ionq.Service
—the gateset will be detected automatically.
service = cirq_ionq.Service(api_key=YOUR_API_KEY)
result = service.run(circuit=circuit, repetitions=1024, target="simulator", name="Native gates in Cirq")
Transpiling a circuit to native gates
The cirqionq
integration currently does not support automatic transpilation to native gates via Cirq’s compiler, but we plan to add this capability in the future. If you’d like to help, feel free to open an issue or a pull request on the cirqionq module.
For now, we recommend following this general procedure (also described in our main native gates guide).

Decompose the gates used in the circuit so that each gate involves at most two qubits.

Convert all easytoconvert gates into RX, RY, RZ, and CNOT gates.

Convert CNOT gates into XX gates using the decomposition described here and at the bottom of this section.

For hardtoconvert gates, first calculate the matrix representation of the unitary, then use either KAK decomposition or the method introduced in this paper to implement the unitary using RX, RY, RZ and XX. Note that Cirq and Qiskit also have subroutines that can do this automatically, although potentially not optimally. See cirq.linag.kak_decomposition and qiskit.synthesis.TwoQubitBasisDecomposer.

Write RX, RY, RZ and XX into GPi, GPi2 and MS gates as documented above (making sure to convert all angles and phases from radians to turns).
CNOT to XX decomposition
A CNOT gate can be expressed in XX, RX, and RY gates which can be directly converted to IonQ’s native gates.
@  RY(π/2)   RX(π/2)  RY(π/2) 
 =  XX(π/4) 
X   RX(π/2) 
Final transpilation step in code
Once you’ve completed steps 14 and expressed your circuit in terms of RX, RY, RZ, and XX gates, this example code snippet can perform the final transpilation step. The key here is to use a qubit_phase
list to track and adjust the orientation of the Bloch sphere of each qubit as the circuit progresses.
from cirq_ionq.ionq_native_gates import GPIGate, GPI2Gate, MSGate
import numpy as np
def compile_to_native_json(circuit):
qubit_phase=[0]*32
op_list=[]
for op in circuit.all_operations():
if type(op.gate)==cirq.ops.common_gates.Rz:
qubit_phase[op.qubits[0].x]=(qubit_phase[op.qubits[0].x]op.gate._rads/(2*np.pi))%1
elif type(op.gate)==cirq.ops.common_gates.Ry:
if abs(op.gate._rads0.5*np.pi)<1e6:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.25)%1).on(op.qubits[0])
)
elif abs(op.gate._rads+0.5*np.pi)<1e6:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.75)%1).on(op.qubits[0])
)
elif abs(op.gate._radsnp.pi)<1e6:
op_list.append(
GPIGate(phi=(qubit_phase[op.qubits[0].x]+0.25)%1).on(op.qubits[0])
)
elif abs(op.gate._rads+np.pi)<1e6:
op_list.append(
GPIGate(phi=(qubit_phase[op.qubits[0].x]+0.75)%1).on(op.qubits[0])
)
else:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0)%1).on(op.qubits[0])
)
qubit_phase[op.qubits[0].x]=(qubit_phase[op.qubits[0].x]op.gate._rads/(2*np.pi))%1
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.5)%1).on(op.qubits[0])
)
elif type(op.gate)==cirq.ops.common_gates.Rx:
if abs(op.gate._rads0.5*np.pi)<1e6:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0)%1).on(op.qubits[0])
)
elif abs(op.gate._rads+0.5*np.pi)<1e6:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.5)%1).on(op.qubits[0])
)
elif abs(op.gate._radsnp.pi)<1e6:
op_list.append(
GPIGate(phi=(qubit_phase[op.qubits[0].x]+0)%1).on(op.qubits[0])
)
elif abs(op.gate._rads+np.pi)<1e6:
op_list.append(
GPIGate(phi=(qubit_phase[op.qubits[0].x]+0.5)%1).on(op.qubits[0])
)
else:
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.75)%1).on(op.qubits[0])
)
qubit_phase[op.qubits[0].x]=(qubit_phase[op.qubits[0].x]op.gate._rads/(2*np.pi))%1
op_list.append(
GPI2Gate(phi=(qubit_phase[op.qubits[0].x]+0.25)%1).on(op.qubits[0])
)
elif type(op.gate)==cirq.ops.parity_gates.XXPowGate:
if op.gate.exponent>0:
op_list.append(
MSGate(
phi0=qubit_phase[op.qubits[0].x],
phi1=qubit_phase[op.qubits[1].x]
).on(op.qubits[0],op.qubits[1])
)
else:
op_list.append(
MSGate(
phi0=qubit_phase[op.qubits[0].x],
phi1=(qubit_phase[op.qubits[1].x]+0.5)%1
).on(op.qubits[0],op.qubits[1])
)
else:
raise ValueError(f"Gate type unrecognized: must be Rx, Ry, Rz, XXPowGate")
return op_list
Once the circuit is fully expressed in IonQ native gates, it can be submitted to an IonQ backend as shown above.