Hi Lillian,
Thanks for clarifying what is currently possible.
Attached is the end-to-end example code which prints transpiled GHZ circuit as executed on IBM Hanoi.
It extracts IBM job ID from PennyLane IBM-device after the circuit is executed and uses Qiskit to pull the transpiled circuit from the IBM database.
Additionally, I print the CX-depth and the total number of CX gates for the transpiled circuits - those are 2 crucial circuit properties deciding how good the results will be from the real HW.
The primary use case of analyzing a transpiled circuit is assessment, if it makes sense to run it on a given hardware. Since, Qiskit jobRes = job.result() requires a job to finish, which may take hours to days to learn if I made a bad choice, depending on how long the queue is.
So it is a good starting point, but eventually device.transpile(circuit) would be a more desirable solution, w/o need to execute the circuit on the HW. (It could call Qiskit transpiler under the hood).
I’m good for now,
thanks again for your assistance
Jan
import pennylane as qml
num_wires = 5
from qiskit_ibm_provider import IBMProvider
provider = IBMProvider()
backend = provider.get_backend('ibm_hanoi')
dev = qml.device('qiskit.ibmq', wires=num_wires, backend=backend, shots=200)
def ghz(dev):
@qml.qnode(dev)
def circuit():
n=num_wires
qml.Hadamard(wires=0)
for i in range(1, n):
qml.CNOT(wires=[0,i])
return qml.probs(wires=range(n))
return circuit
circ = ghz(dev)
print('backend:',backend)
print(qml.draw(circ)())
backend: <IBMBackend(‘ibm_hanoi’)>
0: ──H─╭●─╭●─╭●─╭●─┤ ╭Probs
1: ────╰X─│──│──│──┤ ├Probs
2: ───────╰X─│──│──┤ ├Probs
3: ──────────╰X─│──┤ ├Probs
4: ─────────────╰X─┤ ╰Probs
Run the job and circuit retrieval
probTens = circ() # run circuit on IBM device
print('probs:',probTens)
jid=dev._current_job.job_id()
print('IBM job ID',jid)
job = provider.retrieve_job(jid)
# Retrieve the results from the job
jobRes = job.result()
resL=jobRes.results
nqc=len(resL) # number of circuit in the job
counts=jobRes.get_counts()
if nqc==1: counts=[counts] # this is poor design
print('M: got %d circ+results'%nqc, 'JID=',jid)
for ic in range(nqc):
qc=job.circuits()[ic] # transpiled circuit
resHead=resL[ic].header # auxiliary info about used hardware
print('\nM: circ=%d %s'%(ic,qc.name))
print('counts:',counts[ic])
q2_depth=qc.depth(filter_function=lambda x: x.operation.num_qubits > 1)
print('circuit q2_depth=%d , gate count:'%q2_depth,dict(qc.count_ops()))
print(qc.draw(output="text", idle_wires=False,fold=120))
print('result header: ',end=''); print(resHead)
Transpiled circuit depth. In particular the ideal circuit have only 4 CX gates, but the final one has 10 , so it will perform worse on real HW.
circuit q2_depth=11 , gate count: {'cx': 10, 'measure': 5, 'rz': 2, 'sx': 1, 'barrier': 1}
Portion of transpiled circuit , including mapping to physical qubits and swaps.
global phase: π/4
┌───┐ »
Qubit(QuantumRegister(5, 'q'), 2) -> 0 ────────────────────────────────────────────────┤ X ├────────────────────»
┌───┐ ┌───┐└─┬─┘ ┌───┐ »
Qubit(QuantumRegister(5, 'q'), 3) -> 1 ─────────────────────────────────┤ X ├──■──┤ X ├──■────■────■──┤ X ├──■──»
└─┬─┘ │ └─┬─┘ │ ┌─┴─┐└─┬─┘┌─┴─┐»
Qubit(QuantumRegister(22, 'ancilla'), 3) -> 2 ───────────────────────────────────┼────┼────┼─────────┼──┤ X ├──■──┤ X ├»
│ │ │ │ └───┘ └───┘»
Qubit(QuantumRegister(5, 'q'), 4) -> 3 ───────────────────────────────────┼────┼────┼─────────┼─────────────────»
┌─────────┐┌────┐┌─────────┐ │ ┌─┴─┐ │ ┌─┴─┐ »
Qubit(QuantumRegister(5, 'q'), 0) -> 4 ┤ Rz(π/2) ├┤ √X ├┤ Rz(π/2) ├──■────■──┤ X ├──■───────┤ X ├───────────────»
└─────────┘└────┘└─────────┘┌─┴─┐ └───┘ └───┘ »
Qubit(QuantumRegister(5, 'q'), 1) -> 7 ────────────────────────────┤ X ├────────────────────────────────────────»
└───┘ »
c: 5/═════════════════════════════════════════════════════════════════════════»
»