How to transpile (or compile) a circuit with a specific hardware setting?

Hello! I am wondering how to transpile (or compile) a circuit with a specific hardware setting (or specific coupling maps and basic gate sets). Suppose I have a circuit like this:

def circuit():
    qml.CNOT(wires=[0, 1])
    qml.PauliY(wires=0)
    qml.CNOT(wires=[2, 3])
    qml.Hadamard(wires=3)
    qml.CNOT(wires=[1, 2])
    qml.PauliZ(wires=0)
    qml.PauliX(wires=1)
    qml.Hadamard(wires=2)
    qml.CNOT(wires=[3,1])
    qml.PauliZ(wires=2)
    qml.CNOT(wires=[0, 3])
    return qml.probs(wires=[0, 1, 2, 3])

dev = qml.device('default.qubit', wires=4)
qnode = qml.QNode(circuit, dev)
print(qml.draw(qnode)())

which is:

0: ─╭●──Y──Z───────╭●── β•­Probs
1: ─╰X─╭●──X─╭X────│─── β”œProbs
2: ─╭●─╰X──H─│───Z─│─── β”œProbs
3: ─╰X──H────╰●────╰X── β•°Probs

Now, suppose I want to have it executed on the ibmq_lima (or fake ibmq_lima) device, what can I do to acquire get the corresponding transpile circuit for my designed circuit circuit()? I do not want to execute on the real hardware but only would like to get the transpiled to-be-executed circuit and output some data (like physical depth, gate counts, etc.) from it. Does Pennylane have another different compiling strategy for this compared with qiskit, and how can I implement it?

There is another approach in that I can specify the coupling map and the basis set of the specific hardware and implement the qml.compile or qml.transforms.transpile. However, I use the code below to transpile my circuit with specific basis_set={["CNOT", "X", "SX", "RZ"]} (which is exactly
the gate set of ibmq_lima):

compiled_circuit = qml.compile(basis_set=["CNOT", "X", "SX", "RZ"])(circuit)
compiled_qnode = qml.QNode(compiled_circuit, dev)
print(qml.draw(compiled_qnode)())

and I get:

0: ─╭●──RZ(1.57)──RY(3.14)──────────────────────────────────╭●──RZ(4.71)── β•­Probs
1: ─╰X─╭●─────────RZ(1.57)──RX(3.14)──RZ(1.57)─╭X───────────│───────────── β”œProbs
2: ─╭●─╰X─────────RZ(1.57)──RX(1.57)──RZ(4.71)─│────────────│───────────── β”œProbs
3: ─╰X──RZ(1.57)──RX(1.57)─────────────────────╰●──RZ(1.57)─╰X──────────── β•°Probs

We can see that there is still RX gate inside. This will become even worse when I implement custom unitary gates in the circuit. I cannot get the circuit with my indicated basis_set.

Thanks a lot and looking forward to your answers!

This is my environment:
Name: PennyLane
Version: 0.30.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: GitHub - PennyLaneAI/pennylane: PennyLane is a cross-platform Python library for differentiable programming of quantum computers. Train a quantum computer the same way as a neural network.
*Author: *
*Author-email: *
License: Apache License 2.0
Location: e:\anaconda3\lib\site-packages
Requires: autograd, requests, autoray, semantic-version, scipy, rustworkx, appdirs, numpy, networkx, toml, pennylane-lightning, cachetools
Required-by: PennyLane-qiskit, PennyLane-Lightning

Platform info: Windows-10-10.0.19042-SP0
Python version: 3.9.12
Numpy version: 1.23.5
Scipy version: 1.9.3
Installed devices:
- qiskit.aer (PennyLane-qiskit-0.30.1)
- qiskit.basicaer (PennyLane-qiskit-0.30.1)
- qiskit.ibmq (PennyLane-qiskit-0.30.1)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.30.1)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.30.1)
- default.gaussian (PennyLane-0.30.0)
- default.mixed (PennyLane-0.30.0)
- default.qubit (PennyLane-0.30.0)
- default.qubit.autograd (PennyLane-0.30.0)
- default.qubit.jax (PennyLane-0.30.0)
- default.qubit.tf (PennyLane-0.30.0)
- default.qubit.torch (PennyLane-0.30.0)
- default.qutrit (PennyLane-0.30.0)
- null.qubit (PennyLane-0.30.0)
- lightning.qubit (PennyLane-Lightning-0.30.0)

Hey @Snowman_Liu! Welcome to the forum :partying_face:!

qml.compile has some rough edges related to the fact that QNodes are bound to a device, not the compilation β€œspace” in which you define via a basis_set. So, what can happen is you can move out of the basis_set when compile goes through its steps.

On a more detailed level, how PennyLane decomposes operations is very unidirectional β€” we have a particular gateset that we consider β€œuniversal”. E.g., for PauiiY:

>>> qml.PauliY(wires=0).decomposition()
[PhaseShift(1.5707963267948966, wires=[0]),
 RY(3.141592653589793, wires=[0]),
 PhaseShift(1.5707963267948966, wires=[0])]

In other words, PennyLane has no way to turn PauliY into something in ["CNOT", "X", "SX", "RZ"]. It’s a shortcoming that we’re aware of β€” we need more versatile decomposition strategies!

For the gates in your circuit that are causing problems, you can see if creating a custom decomposition will work by using qml.transforms.create_decomp_expand_fn: qml.transforms.create_decomp_expand_fn β€” PennyLane 0.30.0 documentation

This should tell the device how to decompose things in your desired way. Let me know if this works!

1 Like

Hey @isaacdevlugt ! Thank you so much, and yes it works when I pre-define the transpire strategy by myself. Thanks again for your help!

1 Like

Awesome! Glad that works for you :grin: