5

I have a certain quantum circuit (qc) in qiskit. My goal is to see it transpiled in IonQ Native Gates, to see the qc that is effectively run on the hardware.

However, the Ionq basis gate set which I see by using qiskit is the following:

['ccx', 'ch', 'cnot', 'cp', 'crx', 'cry', 'crz', 'csx', 'cx', 'cy', 'cz', 'h', 'i', 'id', 'mcp', 'mcphase', 'mct', 'mcx', 'mcx_gray', 'measure', 'p', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzz', 's', 'sdg', 'swap', 'sx', 'sxdg', 't', 'tdg', 'toffoli', 'x', 'y', 'z']

While reading the IonQ guides I see that their native gates are GPi, GPi2 and MS gates.

If I specify this as basis_gate in the transpile function of qiskit I get errors:

qiskit.transpiler.exceptions.TranspilerError: "Unable to map source basis {('x', 1), ('reset', 1), ('ccx', 3), ('measure', 1), ('cx', 2), ('ry', 1), ('barrier', 5)} to target basis {'reset', 'delay', 'gpi', 'snapshot', 'gpi2', 'ms', 'barrier', 'measure'} over library <qiskit.circuit.equivalence.EquivalenceLibrary object at 0x7f9f030ea580>."

Also, looking at the IonQ guide about their Native Gates (https://ionq.com/docs/getting-started-with-native-gates), they introduce the general algorithm they use to compile the qc in terms of IonQ Native Gates: it should work as follows:

  1. Decompose the gates used in the circuit so that each gate involves at most two qubits.
  2. Convert all easy-to-convert gates into RX, RY, RZ, and CNOT gates.
  3. Convert CNOT gates into XX gates using the decomposition described here and at the bottom of this section.
  4. For hard-to-convert gates, first calculate the matrix representation of the unitary, then use either KAK decomposition or the method introduced in this Phys, Rev. A. 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.quantum_info.TwoQubitBasisDecomposer.
  5. Write RX, RY, RZ and XX into GPi, GPi2 and MS gates as documented above.

However also if I try to specify the set ['rx', 'ry', 'rz', 'xx'] as basis gate in the transpile I get:

qiskit.transpiler.exceptions.TranspilerError: "Unable to map source basis {('reset', 1), ('measure', 1), ('ry', 1), ('cx', 2), ('ccx', 3), ('barrier', 5), ('x', 1)} to target basis {'delay', 'reset', 'xx', 'barrier', 'rx', 'ry', 'snapshot', 'measure', 'rz'} over library <qiskit.circuit.equivalence.EquivalenceLibrary object at 0x7fa8b8ab14f0>."

Any hint about this? How can I see the quantum circuit as close as possible to what is run on an IonQ machine ?

4 Answers4

3

I had a try implementing the general algorithm you posted as it is proposed on the IonQ Native Gates guide. Since 1-qubit rotation gates $R_x$, $R_y$, $R_z$ together with the 2-qubits $CNOT$ form a universal set of gates for quantum computation, step 1 and 2 can be easily accomplished for any given circuit.

Morever, in the paper arXiv:1603.07678, they show that the $CNOT$ operation can be implemented, up to a global phase, by the following sequence of gates (setting $v=s=1$):

enter image description here

In Qiskit, the XX interaction is implemented by the RXX gate. So, to complete step 3 of the algorithm, you can simply use the Qiskit transpile function as follows:

from qiskit.circuit.random import random_circuit
from qiskit import transpile

qc = random_circuit(3, 1, seed=5) tqc = transpile(qc, basis_gates=['rx', 'ry', 'rz', 'rxx', 'id']) tqc.draw(output='mpl')

enter image description here

Finally, to compile the circuit by using IonQ native gates only (namely MS, GPi, GPi2), you should take a look to the GMS class in Qiskit documentation and remember the simple relations: $$ \mathrm{GPi}(\phi) = -i R_z(2\phi) R_x(\pi) $$ $$ \mathrm{GPi}2(\phi) = R_z(\phi) R_x(\pi/2) R_z(-\phi) $$

SimoneGasperini
  • 1,634
  • 1
  • 3
  • 18
2

When benchmarking IonQ's devices I ran into the same problem. According to their website they are doing a pre-optimisation and a post-processing step by default. The pre-optimisation is a black-box method (it's proprietary), so you don't know what sequence of operations they actually execute on their device. For benchmarking that is of course not so helpful. I have implemented the compilation to the native gates, see the code below.

Moreover, you can get the results without the error-mitigation step in post-processing via

from Qiskit_ionq import ErrorMitigation
...
job = backend.run(qc, shots=1024, error_mitigation=ErrorMitigation.NO_DEBIASING)

Compilation to IonQ native gates

import qiskit.circuit.random
import qiskit_ionq
import numpy as np
from qiskit import QuantumCircuit, transpile
import matplotlib.pyplot as plt

def compile_to_ionq_native_gates(qc, check=False): qc_new = transpile(qc, basis_gates=["id", "rx", "ry", "rz", "cx"]) qc_new = cnot_to_msgate(qc_new, check=check) qc_new = rx_ry_to_rz(qc_new, check=check) qc_new = rx_ry_trivial_phases_to_gpigates(qc_new, check=check) qc_new = consume_rz_gates(qc_new, check=check) return qc_new

def check_qc_compilation(qc1, qc2): qc1_unitary = qiskit.quantum_info.Operator(qc1).data qc2_unitary = qiskit.quantum_info.Operator(qc2).data identity = np.eye(qc2_unitary.shape) prod = np.conj(qc2_unitary).T @ qc1_unitary global_phase = prod[0][0] prod = np.conj(global_phase) assert np.allclose(prod, identity)

def cnot_to_msgate(qc, check=False): qc_new = QuantumCircuit(qc.qregs[0]) for gate in qc.data: if gate.operation.name == "cx": control = gate.qubits[0] target = gate.qubits[1] qc_new.ry(np.pi / 2, control) qc_new.append(qiskit_ionq.MSGate(0, 0, 1 / 4), [control, target]) qc_new.rx(-np.pi / 2, control) qc_new.rx(-np.pi / 2, target) qc_new.ry(-np.pi / 2, control) else: qc_new.append(gate) if check: check_qc_compilation(qc_new, qc) return qc_new

def rx_ry_to_rz(qc, check=False): qc_new = QuantumCircuit(qc.qregs[0]) trivial_phases = np.array([0, 1 / 2, 1, 3 / 2]) * np.pi for gate in qc.data: if gate.operation.name == "rx": phase = gate.operation.params[0] if np.all(np.abs((trivial_phases - phase) % (2 * np.pi)) > 1e-7): qubit = gate.qubits[0] qc_new.ry(-np.pi / 2, qubit) qc_new.rz(phase, qubit) qc_new.ry(np.pi / 2, qubit) else: qc_new.append(gate) elif gate.operation.name == "ry": phase = gate.operation.params[0] if np.all(np.abs((trivial_phases - phase) % (2 * np.pi)) > 1e-7): qubit = gate.qubits[0] qc_new.rx(np.pi / 2, qubit) qc_new.rz(phase, qubit) qc_new.rx(-np.pi / 2, qubit) else: qc_new.append(gate) else: qc_new.append(gate) if check: check_qc_compilation(qc_new, qc) return qc_new

def rx_ry_trivial_phases_to_gpigates(qc, check=False): qc_new = QuantumCircuit(qc.qregs[0]) for gate in qc.data: if gate.operation.name == "rx": phase = gate.operation.params[0] % (2 * np.pi) qubit = gate.qubits[0] if abs(phase) < 1e-7 or abs(phase - (2 * np.pi)) < 1e-7: pass elif abs(phase - np.pi / 2) < 1e-7: qc_new.append(qiskit_ionq.GPI2Gate(0), [qubit]) elif abs(phase - np.pi) < 1e-7: qc_new.append(qiskit_ionq.GPIGate(0), [qubit]) elif abs(phase - 3 / 2 * np.pi) < 1e-7: qc_new.append(qiskit_ionq.GPI2Gate(1 / 2), [qubit]) elif gate.operation.name == "ry": phase = gate.operation.params[0] % (2 * np.pi) qubit = gate.qubits[0] if abs(phase) < 1e-7 or abs(phase - (2 * np.pi)) < 1e-7: pass elif abs(phase - np.pi / 2) < 1e-7: qc_new.append(qiskit_ionq.GPI2Gate(1 / 4), [qubit]) elif abs(phase - np.pi) < 1e-7: qc_new.append(qiskit_ionq.GPIGate(1 / 4), [qubit]) elif abs(phase - 3 / 2 * np.pi) < 1e-7: qc_new.append(qiskit_ionq.GPI2Gate(3 / 4), [qubit]) else: qc_new.append(gate) if check: check_qc_compilation(qc_new, qc) return qc_new

def consume_rz_gates(qc, check=False): qubit_phases = [0] * len(qc.qubits) qc_new = QuantumCircuit(qc.qregs[0]) for gate in qc.data: if gate.operation.name == "rz": index = gate.qubits[0]._index qubit_phases[index] += gate.operation.params[0] elif gate.operation.name == "gpi": index = gate.qubits[0]._index phase = (gate.operation.params[0] - qubit_phases[index] / (2 * np.pi)) % 1.0 qc_new.append(qiskit_ionq.GPIGate(phase), [index]) elif gate.operation.name == "gpi2": index = gate.qubits[0]._index phase = (gate.operation.params[0] - qubit_phases[index] / (2 * np.pi)) % 1.0 qc_new.append(qiskit_ionq.GPI2Gate(phase), [index]) elif gate.operation.name == "ms": indices = [gate.qubits[0]._index, gate.qubits[1]._index] assert gate.operation.params[0] == 0 and gate.operation.params[1] == 0 assert gate.operation.params[2] == 1 / 4 phase_0 = (-qubit_phases[indices[0]] / (2 * np.pi)) % 1.0 phase_1 = (-qubit_phases[indices[1]] / (2 * np.pi)) % 1.0 qc_new.append(qiskit_ionq.MSGate(phase_0, phase_1, 1 / 4), indices) else: raise ValueError if check: qc_check = qc_new.copy() for i, phase in enumerate(qubit_phases): qc_check.rz(phase, i) check_qc_compilation(qc_check, qc) return qc_new

def main(): qc = qiskit.circuit.random.random_circuit(4, 10, seed=1234) qc_compiled = compile_to_ionq_native_gates(qc, check=True) qc.draw(output="mpl") qc_compiled.draw(output="mpl") plt.show()

if name == "main": main()

jakg
  • 21
  • 3
1

Maybe this doesn't address your question directly but it should be possible to automatically compile your circuit to the IONQ gateset with the pytket-ionq extension.

github -> https://github.com/CQCL/pytket-ionq

API reference -> https://cqcl.github.io/pytket-ionq/api/api.html

I think you can do the following...

from pytket.extensions.ionq import IonQBackend
from pytket.circuit.display import render_circuit_jupyter

backend = IONQBackend(...) compiled_circ = backend.get_compiled_circuit(qc, optimisation_level=2) render_circuit_jupyter(compiled_circ) # Draw circuit

If you're working with qiskit you will need to use pytket-qiskit to convert your circuit to a pytket Circuit first.

Callum
  • 1,260
  • 1
  • 5
  • 24
0

I found a guide specific to Qiskit linked by the Native Gate Guide you provided. In the section “Transpilation to Native Gates in Qiskit”, it states that transpiler a circuit into a native gate set is not supported at the moment.

enter image description here

Junye Huang
  • 712
  • 8
  • 18