2

I do a VQE using the pennylane UCCSD ansatz for execution on the 'default.qubit' device and am interested in the actual circuit that is executed on the device (ideally without making changes in any pennylane code directly). Is there a way to retrieve it?

So far, I found out that the QNode constructs a QuantumTape which has operations and measurements. These operations are executed using execute() or new_execute(). Is that what is executed directly on the device or is there a step where something like QuantumTape.expand() is done. If yes, what controls the depth? Is there a further compilation step? How do I access the tape? What if I use a different device (eg. qiskitAer as plug-in)?

(Addition: If anyone could explain the queueing happening in the QNode in this context a bit as well I would be grateful.)

Follow up: Using the expasion_strategy argument of draw() as explained in the answer is very helpful. But is there a way to also show the transpiled circuit including the rotations for the measurement? Using draw() shows the measurement only as a whole (eg. '' in the example by Isaac), but when using eg. the qiskit plugin the full circuit including the pre-measurement rotations is transpiled, isn't it?

qcabepsilon
  • 195
  • 9

1 Answers1

2

The PennyLane circuit drawer (both the text version qml.draw and the matplotlib version qml.draw_mpl) have keyword arguments that control how you want the circuit displayed. By default, it will print the circuit as defined in the QNode, but you can pass expansion_strategy="device" to instead display the circuit as it will be expanded for the device.

For example:

dev = qml.device("default.qubit", wires=4)

hf_state = np.array([1, 1, 0, 0], requires_grad=True) coordinates = np.array([0.0, 0.0, -0.66140414, 0.0, 0.0, 0.66140414]) H, _ = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates)

@qml.qnode(dev) def circuit(weights): qml.UCCSD( weights, wires=[0, 1, 2, 3], init_state=hf_state, s_wires=[[0, 1, 2], [1, 2, 3]], d_wires=[[[0, 1], [2, 3]]], ) return qml.expval(H)

weights = np.random.normal(0, np.pi, 3)

Drawing the circuit with no arguments:

>>> print(qml.draw(circuit)(weights))
0: ─╭UCCSD(M0)─┤ ╭<>
1: ─├UCCSD(M0)─┤ ├<>
2: ─├UCCSD(M0)─┤ ├<>
3: ─╰UCCSD(M0)─┤ ╰<>

Drawing the circuit with the expansion strategy set:

>>> print(qml.draw(circuit, expansion_strategy="device")(weights))
0: ─╭BasisState(M0)──H─────────╭●───────────────────────╭●──────────H──────────RX(-1.57)─╭●──────────────────────────────╭●──────────RX(1.57)───H─────────╭●──────────────────────────────╭●──────────H─────────H─╭●──────────────────────────────╭●──H──RX(-1.57)─╭●────────────────────────╭●──RX(1.57)──H─────────╭●────────────────────────╭●──H──────────RX(-1.57)─╭●────────────────────────╭●──────────RX(1.57)──RX(-1.57)─╭●───────────────────────────────╭●──RX(1.57)──RX(-1.57)─╭●─────────────────╭●──RX(1.57)───H─╭●──────────────────╭●─────────H──────────────────────────────────────────────────────────────────────────────┤ ╭<>
1: ─├BasisState(M0)──H─────────╰X─╭●─────────────────╭●─╰X──────────H──────────H─────────╰X─╭●─────────────────╭●────────╰X──────────H──────────RX(-1.57)─╰X─╭●─────────────────╭●────────╰X──────────RX(1.57)──H─╰X─╭●─────────────────╭●────────╰X──H──H─────────╰X─╭●──────────────────╭●─╰X──H─────────RX(-1.57)─╰X─╭●──────────────────╭●─╰X──RX(1.57)───RX(-1.57)─╰X─╭●──────────────────╭●─╰X──────────RX(1.57)──RX(-1.57)─╰X─╭●──────────────────╭●────────╰X──RX(1.57)────────────╰X─╭●───────────╭●─╰X───────────────╰X─╭●────────────╭●─╰X─────────RX(-1.57)─╭●──────────────────╭●──RX(1.57)───H─╭●─────────────────╭●─────────H─┤ ├<>
2: ─├BasisState(M0)──RX(-1.57)────╰X─╭●───────────╭●─╰X──RX(1.57)───RX(-1.57)───────────────╰X─╭●───────────╭●─╰X─────────RX(1.57)───RX(-1.57)───────────────╰X─╭●───────────╭●─╰X─────────RX(1.57)───H──────────────╰X─╭●───────────╭●─╰X─────────H──H───────────────╰X─╭●────────────╭●─╰X──H──H──────────────────────╰X─╭●────────────╭●─╰X──H──RX(-1.57)───────────────╰X─╭●────────────╭●─╰X──RX(1.57)───H──────────────────────╰X─╭●────────────╭●─╰X─────────H──H──────────────────────╰X──RZ(2.69)─╰X──H──RX(-1.57)───────╰X──RZ(-2.69)─╰X──RX(1.57)────────────╰X─╭●────────────╭●─╰X───────────────╰X─╭●───────────╭●─╰X───────────┤ ├<>
3: ─╰BasisState(M0)──H───────────────╰X──RZ(0.42)─╰X──H──RX(-1.57)─────────────────────────────╰X──RZ(0.42)─╰X──RX(1.57)──RX(-1.57)─────────────────────────────╰X──RZ(0.42)─╰X──RX(1.57)──RX(-1.57)────────────────────╰X──RZ(0.42)─╰X──RX(1.57)──H─────────────────────╰X──RZ(-0.42)─╰X──H──H────────────────────────────╰X──RZ(-0.42)─╰X──H──H─────────────────────────────╰X──RZ(-0.42)─╰X──H──RX(-1.57)────────────────────────────╰X──RZ(-0.42)─╰X──RX(1.57)──H──────────────────────────────────────────────────────────────────────────────────────────────────────╰X──RZ(-2.58)─╰X──H──RX(-1.57)───────╰X──RZ(2.58)─╰X──RX(1.57)────┤ ╰<>
Josh Izaac
  • 859
  • 7
  • 10
  • This works, but one thing I observe is: If I use draw() as you described, I get an expanded circuit of CNOTs, Rx, Ry. But when using draw_mpl() the output looks as if I did not specify the expansion strategy – qcabepsilon Sep 28 '22 at 14:26
  • Oh interesting, this must be a bug! Thanks for letting me know, I can report this :) – Josh Izaac Sep 28 '22 at 14:39
  • Your text is the answer to my question, thank you. Despite that, could you maybe say a few words on how the shown circuit comes about? – qcabepsilon Sep 28 '22 at 14:46
  • @qcabepsilon I tried to reproduce your bug report above, but qml.draw_mpl(circuit, expansion_strategy="device")(weights) reproduces the expanded circuit as above. Could I ask you to make a bug report at https://github.com/PennyLaneAI/pennylane/issues` with the code you used? – Josh Izaac Sep 28 '22 at 15:22
  • Despite that, could you maybe say a few words on how the shown circuit comes about?

    Unfortunately, quantum chemistry/UCCSD is not my domain of expertise, sorry! But you might find these demos helpful: https://pennylane.ai/qml/demos/tutorial_vqe.html https://pennylane.ai/qml/demos/tutorial_givens_rotations.html

    – Josh Izaac Sep 28 '22 at 15:25
  • I'll do the bug report at the link you posted – qcabepsilon Sep 28 '22 at 15:47
  • I think by question is not directly about this specific ansatz, but rather general about what happens in pennylane under the hood. If I give a quantum function with operators which are not defined on the device to the QNode, what happens? Where exactly is the circuit transplied to what I see with the drawer? – qcabepsilon Sep 28 '22 at 15:50
  • 1
    Got it :) The expansion of circuits is 'owned' by the device; the device is free to specify any logic it requires. However, there is also 'default' logic that is specified here: https://github.com/PennyLaneAI/pennylane/blob/master/pennylane/_device.py#L631 – Josh Izaac Sep 28 '22 at 15:59
  • Ok, that makes sense. So, just that I get it right: The QNode constructs the QuantumTape, which is then expanded as specified by the device. The expanded tape is then executed without further transpilation (only compilation?). – qcabepsilon Sep 28 '22 at 16:16
  • 1
    One more comment for completeness: bug report: https://github.com/PennyLaneAI/pennylane/issues/3117 – qcabepsilon Sep 28 '22 at 17:00