qiskit-community / qiskit-dynamics

Tools for building and solving models of quantum systems in Qiskit
https://qiskit-community.github.io/qiskit-dynamics/
Apache License 2.0
105 stars 61 forks source link

Transpile problem using DynamicsBackend code #235

Closed BeomGyuChoii closed 1 year ago

BeomGyuChoii commented 1 year ago

Informations

What is the current behavior?

Sorry to my English. Please understand this post. When I use DynamicsBackend class at fakeprovider, there is variable 'subsystem_list'. Qiskit Document explanation said that 'subsystem_list' means the qubit which i wanted to use.

There are problems. When I use pulse class, If I write 1 or another number except 0 at subsystem_list, there is error. Also, If I write a list like [0,1], and If I use pulse class at two qubit, there is error. I described all code below.

Steps to reproduce the problem

I will write my code.

First problem: from qiskit.providers.fake_provider import FakeKolkata from qiskit_dynamics.backend import DynamicsBackend backend= DynamicsBackend.from_backend(FakeKolkata(), subsystem_list=[1]) X_cal=QuantumCircuit(1) X_cal.x(0) X_cal.measure_all() X_cal.calibrations with pulse.build(backend, name='X') as x_q0: pulse.play(Drag(duration=160, beta=0.7171287840008055, amp=0.16, sigma=40), pulse.DriveChannel(0)) X_cal.add_calibration('x',[0], x_q0) circ_transpile = transpile(X_cal, backend)

and there is error QiskitError: "Cannot unroll the circuit to the given basis, ['measure']. Instruction measure not found in equivalence library and no rule found to expand."

But if i write at subsystem_list = [0], there is not problem.

Second problem: from qiskit.providers.fake_provider import FakeKolkata from qiskit_dynamics.backend import DynamicsBackend backend= DynamicsBackend.from_backend(FakeKolkata(), subsystem_list=[0,1]) circ=QuantumCircuit(2) circ.h(0) circ.x(0) circ.z(1) circ.cx(0,1) circ.y(0) circ.y(1) circ.measure_all() with pulse.build(backend, name='X') as x_q0: pulse.play(Drag(duration=160, beta=-1.5, amp=0.15575757575757576, sigma=40), pulse.DriveChannel(0)) circ.add_calibration('x',[0], x_q0) with pulse.build(backend, name='Y') as y_q0: pulse.shift_phase(np.pi,pulse.DriveChannel(0)) pulse.play(Drag(duration=160, beta=-1.5, amp=0.15575757575757576, sigma=40), pulse.DriveChannel(0)) circ.add_calibration('y',[0], y_q0) with pulse.build(backend, name='Y') as y_q1: pulse.shift_phase(np.pi,pulse.DriveChannel(1)) pulse.play(Drag(duration=160, beta=-1.5, amp=0.15575757575757576, sigma=40), pulse.DriveChannel(1)) circ.add_calibration('y',[1], y_q1) circ_transpile = transpile(circ, backend)

and there is problem TranspilerError: "Unable to translate the operations in the circuit: ['cx', 'z', 'h', 'measure', 'barrier'] to the backend's (or manually specified) target basis: ['barrier', 'measure', 'snapshot']. This likely means the target basis is not universal or there are additional equivalence rules needed in the EquivalenceLibrary being used. For more details on this error see: https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.BasisTranslator.html#translation_errors"

What is the expected behavior?

First case is just drive qubit 1 and yield the result. Second case also just drive qubit to use our calibrated X gate and Y gate and other qiskit own calibrated gate.

Suggested solutions

Add all our calibration gate to basis gate.

DanPuzzuoli commented 1 year ago

Thank you for this bug report! We will try to address it soon and include it in the next patch release.

DanPuzzuoli commented 1 year ago

Just reproducing the first issue with code that is fully executable:

from qiskit_dynamics import DynamicsBackend
from qiskit import pulse
from qiskit import QuantumCircuit, transpile

from qiskit.providers.fake_provider import FakeKolkata
from qiskit_dynamics.backend import DynamicsBackend

backend= DynamicsBackend.from_backend(FakeKolkata(), subsystem_list=[1])
X_cal=QuantumCircuit(1)
X_cal.x(0)
X_cal.measure_all()
X_cal.calibrations

with pulse.build(backend, name='X') as x_q0:
    pulse.play(
        pulse.Drag(duration=160, beta=0.7171287840008055, amp=0.16, sigma=40), 
        pulse.DriveChannel(1)
    )

X_cal.add_calibration('x',[0], x_q0)
circ_transpile = transpile(X_cal, backend)

I think the issue here is purely to do with labelling. When specifying subsystem_list=[1], DynamicsBackend is only aware that it has a system labelled by index 1 (and it will internally automatically build some measurement definitions based on this labelling). Note that this is different from normal backends, which always assume they have a list of qubit labels of the form [0, 1, ..., num_qubits-1]. When you build your circuit/schedule, everything is done with label 0, so when it looks into the target for definitions of these things, it raises an error (all instructions are labelled with 1). Will need to look deeper to confirm the extent of this, and better understand how the labels behave in transpilation to determine the correct solution to this.

DanPuzzuoli commented 1 year ago

The second issue isn't actually an issue, it is a design choice. DynamicsBackend.from_backend does not import the gate definitions from the backend passed to it. This is explained in the from_backend method documentation. As such, the backend in your second example doesn't have internal definitions for any of the gates you're using in your circuit.

For some more context behind this choice: It is a reaction to the from_backend method of the PulseSimulator in Aer, which did import the pulse definitions. Due to mismatches in the reported model and the actual device, the pre-existing backend pulses will not perform well in simulation, which confused users. To use a simulation-based pulse backend, it is necessary to actually run through calibration procedures just like on a real backend to "bring up" the device, as is partially done in the DynamicsBackend tutorial.

mtreinish commented 1 year ago

In general a target defines the qubit indices as a contiguous set starting at 0. So if you're creating a target with gates that are only defined on qubit 1 you'll end up with a 2 qubit backend where nothing is supported on qubit 0. In newer versions of qiskit (qiskit-terra>=0.24.0) I'd expect this actually work ok with: https://github.com/Qiskit/qiskit-terra/pull/9927 the transpiler will ignore qubits (i.e. treat them as non-operational/not available) if there are no instructions listed in the target that operate on them (assuming you define the calibration for the gate on the correct qubit). If the intent behind:

backend= DynamicsBackend.from_backend(FakeKolkata(), subsystem_list=[1])

is to have backend be a 1 qubit backend, then you'll need to reindex the target based on the number of qubits you want.

DanPuzzuoli commented 1 year ago

I think I potential solution to this is to change the behaviour of the subsystem_list argument of from_backend so that all qubits of the original backend are "kept", but all of the qubits not in subsystem_list are treated as 1-dimensional systems. I don't think this will actually change anything about the internal functioning of DynamicsBackend, and should hopefully make the target behave better (as now we can always have qubits with labelling [0, ..., n-1]).