Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
4.89k stars 2.3k forks source link

Qubit identities get swapped #7472

Open maxdp opened 2 years ago

maxdp commented 2 years ago

Informations

What is the current behavior?

Qubit identities get swapped at the end of a circuit, in the absence of measurements, due to the transpiler. See here: https://quantumcomputing.stackexchange.com/questions/23458/qubit-identities-get-swapped-in-ibm-qiskit

Steps to reproduce the problem

The code at the above link reproduces it.

What is the expected behavior?

The circuit should not swap qubit identities. It should perform a circuit equivalent to the intended circuit. A good solution should scale well to repeating the same circuit many times before measurement, and to many qubits.

Suggested solutions

Find the place in the transpiler code where this occurs, i.e. where the circuit is simplified by moving some work into swapped measurements, and ensure this does not happen when no measurements are present. Preferably it should also not happen when a barrier is present before measurement.

jakelishman commented 2 years ago

This looks like expected behaviour - I think you're thinking of a different definition of "equivalence" to what we use in the transpiler. "Swapping qubit identities" is a feature of the transpiler; it solves the problem of mapping logical qubits to physical qubits for you, so you don't need to worry about connectivity of qubits when writing algorithms at a high level.

(By the way, your code example isn't quite a full reproducer - it doesn't have the imports or the name sant defined; I'm assuming that it refers to the ibm_santiago backend, but if that's not it, I might have missed something. In particular, the code you give does not seem to correspond to the images; the circuit seems to be permuting (0, 1, 2) to (2, 0, 1), but the image looks like a single swap of logical qubits 0 and 2.)

If you want to disable the layout passes, you can pass layout_method="trivial" to transpile, but beware that if you are providing a backend, then you may get even more unexpected results; Santiago does not have a direct connection between physical qubits 0 and 2, so transpile will need to insert an extra swap instruction to make the swap(0, 2) instruction work.

maxdp commented 2 years ago

I think you've misunderstood:

In particular, the code you give does not seem to correspond to the images; the circuit seems to be permuting (0, 1, 2) to (2, 0, 1), but the image looks like a single swap of logical qubits 0 and 2.)

This is exactly the bug I'm reporting! I am inputting a circuit equivalent to a permutation, but the transpiler is changing it to a single swap. The way it is doing this relies on measurements. In my first bug-free example it performs measurements out of order, which in combination with the gates is equivalent to a permutation. In my second buggy example no measurements are present, but it assumes they are present and does the same thing. This, as you point out, does not correspond to the intended circuit.

(I did try layout_method="trivial" out of curiosity, it does not fix this bug. But good point that my code wasn't a full reproducer, I've fixed this. I do realize santiago does not have a (0, 2) connection, that's a precondition for the transpiler to perform the mistaken optimization that causes this bug.)

jakelishman commented 2 years ago

Ok, I thought that was a mistake, because can't reproduce the behaviour you're describing using your code on either Santiago or Lima after 100,000 different seeds. I reliably always get both swaps if a barrier is there, and no operations at all if not (which is expected). You said that it's seed-dependent - could you provide a seed which shows the behaviour in your link?

maxdp commented 2 years ago

The problem is not the barrier. It's that absence of measurement creates an incorrect circuit. Have you tried not including a measurement before transpiling?

In my use case, I'd like to transpile a circuit without measurement, then repeat it, then measure at the end. This is often important to get the most gate-efficient circuit. But this sans-measurement bug makes this impossible.

Here's code to show the bug more clearly:

from qiskit.compiler import transpile
from qiskit import QuantumCircuit, IBMQ
provider = IBMQ.load_account()
lima = provider.get_backend('ibmq_lima')

n_q = 3
qc_foo = QuantumCircuit(n_q, n_q)
qubit_list = [0,1,2]
qc_foo.swap(0,1)
qc_foo.swap(0,2)
qc_foo.barrier()
qc_foo_trans = transpile(qc_foo, backend=lima, optimization_level=3
                         , initial_layout=qubit_list, seed_transpiler=101)

# repeat circuit, each iteration is incorrect due to transpiler
qubit_list_trans = [0,1,2,3,4]
qc_foo_trans.compose(qc_foo_trans, qubit_list_trans, inplace=True)
qc_foo_trans.draw()

The output shows an incorrect transpilation repeated twice. Do you know of a way to get a correct transpilation repeated twice? While only having to transpile a single repetition, to ensure both decent results and consistency between repetitions for larger circuits?

maxdp commented 2 years ago

Hi @jakelishman , do you have any updates on this? Did my last clarification make sense?

jakelishman commented 2 years ago

I'm sorry this fell through the cracks. If you're still seeing this: we don't consider this a bug, and it's a core feature of the transpiler's routing passes. The reason is that transpile is only designed to operate on final circuits, not components of them. We will need that in the future (and it's definitely on our radar), but the design of efficient routing strategies for these sorts of register-allocation problems is a major open research question.

Here's the full logic of the transpiler routing passes. In this, l[n] refers to logical qubit n, and h[n] is hardware qubit n.

So what we actually end up performing is a hardware swap of (1, 2), and a "logical layout swap" of (0, 1) which doesn't require any gates. None of this requires a quantum operation cross a barrier, so there's no violation there. It's just that the "logical layout" isn't constant throughout circuit execution, and that's a core part of the transpilation strategy for real hardware.

For a gate as an independent subcomponent, you need the logical layout to be the same at the start and end. We don't support a mode for specifying that, and even if we did, you wouldn't be able to compose that gate onto arbitrary qubits, because the other sets of qubits might not have the same connectivity.

That said, the routing passes do calculate the final layout and make that available, so it is technically possible to undo it if you really want to. You need to create your own pass manager, and then get the "final_layout" key from its property_set attribute after the run:

In [1]: from qiskit import QuantumCircuit
   ...: from qiskit.providers.fake_provider import FakeLima
   ...: from qiskit.transpiler import PassManagerConfig, CouplingMap, Layout
   ...: from qiskit.transpiler.preset_passmanagers import level_3_pass_manager
   ...:
   ...: lima = FakeLima()
   ...:
   ...: n_q = 3
   ...: qc_foo = QuantumCircuit(n_q, n_q)
   ...: qubit_list = [0,1,2]
   ...: qc_foo.swap(0,1)
   ...: qc_foo.swap(0,2)
   ...: qc_foo.barrier()
   ...:
   ...: cmap = CouplingMap(lima.configuration().coupling_map)
   ...: basis_gates = lima.configuration().basis_gates
   ...: initial_layout = Layout.from_intlist([0, 1, 2], *qc_foo.qregs)
   ...: pm = level_3_pass_manager(
   ...:     PassManagerConfig(
   ...:         coupling_map=cmap,
   ...:         basis_gates=basis_gates,
   ...:         seed_transpiler=101,
   ...:         initial_layout=initial_layout,
   ...:     )
   ...: )
   ...: qc_trans = pm.run(qc_foo)
   ...: final_layout = pm.property_set["final_layout"]
   ...: print(qc_trans.draw())
                               ░
      q_0 -> 0 ────────────────░─
                    ┌───┐      ░
      q_1 -> 1 ──■──┤ X ├──■───░─
               ┌─┴─┐└─┬─┘┌─┴─┐ ░
      q_2 -> 2 ┤ X ├──■──┤ X ├─░─
               └───┘     └───┘ ░
ancilla_0 -> 3 ────────────────░─
                               ░
ancilla_1 -> 4 ────────────────░─
                               ░
          c: 3/══════════════════

In [2]: print(final_layout)
Layout({
2: Qubit(QuantumRegister(5, 'q'), 2),
3: Qubit(QuantumRegister(5, 'q'), 3),
4: Qubit(QuantumRegister(5, 'q'), 4),
1: Qubit(QuantumRegister(5, 'q'), 0),
0: Qubit(QuantumRegister(5, 'q'), 1)
})

You can see the "logical" (0, 1) swap in the final layout there. This isn't very ergonomic right now, but we are working on it.

maxdp commented 1 year ago

Thanks for the thorough reply. I now understand that transpile is not meant to operate on circuit components but only the final circuit. However I would strongly suggest someone add a warning in Qiskit to this effect when the user tries to transpile a circuit with no measurement on sparse topology, which I think triggers the qubit reordering. This is surely attempted all the time for quantum simulation, Grover's search, etc. where a step is repeated many times. There are likely many users suffering from this, perhaps not realizing it.

Thanks again!