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
5.28k stars 2.37k forks source link

DynamicalDecoupling creates sequences of delays not multiple of 16 #7398

Closed anedumla closed 11 months ago

anedumla commented 2 years ago

Environment

What is happening?

When running PassManager with DynamicalDecoupling the sequence of delays returned are not always multiple of 16 and the resulting circuit cannot be run on real hardware.

How can we reproduce the issue?

from qiskit.quantum_info import Pauli
from qiskit.opflow import PauliOp
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import SuzukiTrotter
from qiskit import QuantumCircuit, transpile, IBMQ

# Dynamical decoupling
from qiskit.circuit.library import XGate
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling

# load the backend
provider = IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q-internal', group='deployed')
backend = provider.get_backend('ibmq_mumbai')

layout =  [0,1,4]
shots = 100000

# data for the pass manager
instruction_durations = InstructionDurations.from_backend(backend)

# functions to create the circuit
def heisenberg_EO(n_qubits):
    """Construct Hamiltonian of 1-D Heisenberg chain with n_qubits sites.
    Return a list of matrices corresponding to the even-odd-splitting (154) in
    Childs2021.
    """
    # Three qubits because for 2 we get H_O = 0
    assert n_qubits >= 3
    def OPauli(s):
        return PauliOp(Pauli(s))
    def two_qubit_interaction(i, coeff):
        assert i < n_qubits - 1
        return (OPauli("I" * i + "XX" + "I" * (n_qubits - 2 - i)) +
                OPauli("I" * i + "YY" + "I" * (n_qubits - 2 - i)) +
                OPauli("I" * i + "ZZ" + "I" * (n_qubits - 2 - i)) +
                OPauli("I" * i + "Z" + "I" * (n_qubits - 1 - i)) *
                coeff)

    H_E = sum((two_qubit_interaction(i, 1)
               for i in range(0, n_qubits - 1, 2)))
    H_O = sum((two_qubit_interaction(i, 1)
               for i in range(1, n_qubits - 1, 2)))
    return [H_E, H_O]

def get_circuit(qubits, evo_time, trotter_steps):
    num_qubits = qubits

    # Obtain the matrices for Trotter - careful the algorithm returns sometimes the all zero matrix
    # minimize ||ms||_1 such that ||a||_1 \leq 2
    paulis = heisenberg_EO(num_qubits)

    evo_op = PauliEvolutionGate(paulis, evo_time, synthesis=SuzukiTrotter(order=2, reps=trotter_steps))
    return evo_op.definition

num_qubits = len(layout) # size of the system
evo_time = 1 # evolution time
m = 1 # number of trotter steps (reduced example)

qc = QuantumCircuit(num_qubits, num_qubits)
qc.h(list(range(num_qubits))) # dummy initial state
qc.append(get_circuit(num_qubits, evo_time, m), list(range(num_qubits)))
qc.measure(range(num_qubits), range(num_qubits))

# transpiled circuit
qct = transpile(qc, backend, initial_layout=layout, optimization_level=2)
# add dynamical decoupling
dd_sequence = [XGate(), XGate()]
pm = PassManager([ALAPSchedule(instruction_durations),
                  DynamicalDecoupling(instruction_durations, dd_sequence)])
qctd = pm.run(qct)

Printing the values of the DynamicalDecoupling class gives:

slack 9952
spacing [0.25, 0.5, 0.25]
taus [2488, 4976, 2488]
slack 11488
spacing [0.25, 0.5, 0.25]
taus [2872, 5744, 2872]

What should happen?

The taus should all be multiples of 16.

Any suggestions?

From DynamicalDecoupling:

# insert the actual DD sequence
taus = [int(slack * a) for a in self._spacing]
unused_slack = slack - sum(taus)  # unused, due to rounding to int multiples of dt
middle_index = int((len(taus) - 1) / 2)  # arbitrary: redistribute to middle
taus[middle_index] += unused_slack  # now we add up to original delay duration

Even if slack is a multiple of 16, whenever a is not an integer there is no assurance that int(slack*a) will also be a multiple of 16 (e.g. the a's in the example above are not integers).

jakelishman commented 2 years ago

Is the number 16 particular to IBM hardware? If so, we probably need to solve this in a slightly more general way, with a user- or backend-defined alignment.

anedumla commented 2 years ago

I am not the right person to ask about this, maybe @eggerdj knows.

eggerdj commented 2 years ago

16 is indeed specific to IBM hardware. Other hardware may use different multiples. We should avoid hard-coding multiples of 16, instead this should come from a configuration.

eggerdj commented 2 years ago

@jakelishman We can solve this in a backend oriented way since the 16-sample granularity is available in the configuration. For example,

backend.configuration().timing_constraints

gives:

{'acquire_alignment': 16,
 'granularity': 16,
 'min_length': 64,
 'pulse_alignment': 1}

One question to consider, should this de done in the DD pass or is there some other pass that I am not aware of that tries to satisfy such timing constraints.

mtreinish commented 2 years ago

There is a separate pass to align with backend specified timing constraints: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/scheduling/instruction_alignment.py Can you try integrating that into your custom passmanager with dd as I expect it to solve the issue if it's run after dd.

ajavadia commented 11 months ago

This is satisfied both by the pass that Matthew linked, and also in the dynamical decoupling pass by setting (..pulse_alignment=backend.target.pulse_alignment).. So I think this can be marked as resolved.