Repository: Qiskit/qiskit (python)

Issue:
Remove code deprecated in 0.19 (released on December 06, 2021)
There is code released 20 month ago that needs to be removed.
 - `qiskit/transpiler/instruction_durations.py:173` (`get`)
 - `qiskit/providers/models/backendconfiguration.py:834` (`control`)
 - `qiskit/qobj/__init__.py:84` (`__init__`)
  
This includes:
- [ ] Remove deprecated code and tests (if they only check the deprecation raise)
- [ ] tag the PR as [`Changelog: Removal`](https://github.com/Qiskit/qiskit/labels/Changelog%3A%20Removal)
- [ ] create a release note in the category `upgrade:`. If you can include an example with an alternative for user to migrate to the new code, as this change might break users code.

Pull request description:
Remove deprecated code in 0.19
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [v] I have added the tests to cover my changes.
- [v] I have updated the documentation accordingly.
- [v] I have read the CONTRIBUTING document.
-->

### Summary
Remove deprecated code in 0.19


### Details and comments
Fixes #10746 
Please add `Changelog: Removal` label.

Patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 77660a558..6e01f3f32 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -2523,14 +2523,26 @@ class QuantumCircuit:
     @property
     def num_parameters(self) -> int:
         """The number of parameter objects in the circuit."""
+        # Avoid a (potential) object creation if we can.
+        if self._parameters is not None:
+            return len(self._parameters)
         return len(self._unsorted_parameters())
 
     def _unsorted_parameters(self) -> set[Parameter]:
-        """Efficiently get all parameters in the circuit, without any sorting overhead."""
-        parameters = set(self._parameter_table)
-        if isinstance(self.global_phase, ParameterExpression):
-            parameters.update(self.global_phase.parameters)
+        """Efficiently get all parameters in the circuit, without any sorting overhead.
+
+        .. warning::
 
+            The returned object may directly view onto the ``ParameterTable`` internals, and so
+            should not be mutated.  This is an internal performance detail.  Code outside of this
+            package should not use this method.
+        """
+        # This should be free, by accessing the actual backing data structure of the table, but that
+        # means that we need to copy it if adding keys from the global phase.
+        parameters = self._parameter_table.get_keys()
+        if isinstance(self.global_phase, ParameterExpression):
+            # Deliberate copy.
+            parameters = parameters | self.global_phase.parameters
         return parameters
 
     @overload
@@ -2648,6 +2660,7 @@ class QuantumCircuit:
         # 'target' so we can take advantage of any caching we might be doing.
         if isinstance(parameters, dict):
             raw_mapping = parameters if flat_input else self._unroll_param_dict(parameters)
+            # Remember that we _must not_ mutate the output of `_unsorted_parameters`.
             our_parameters = self._unsorted_parameters()
             if strict and (extras := raw_mapping.keys() - our_parameters):
                 raise CircuitError(
diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py
index 9ebcb0783..9ded481b2 100644
--- a/qiskit/compiler/assembler.py
+++ b/qiskit/compiler/assembler.py
@@ -27,7 +27,7 @@ from qiskit.exceptions import QiskitError
 from qiskit.providers.backend import Backend
 from qiskit.pulse import Instruction, LoConfig, Schedule, ScheduleBlock
 from qiskit.pulse.channels import PulseChannel
-from qiskit.qobj import Qobj, QobjHeader
+from qiskit.qobj import QasmQobj, PulseQobj, QobjHeader
 from qiskit.qobj.utils import MeasLevel, MeasReturnType
 
 logger = logging.getLogger(__name__)
@@ -74,7 +74,7 @@ def assemble(
     parametric_pulses: Optional[List[str]] = None,
     init_qubits: bool = True,
     **run_config: Dict,
-) -> Qobj:
+) -> Union[QasmQobj, PulseQobj]:
     """Assemble a list of circuits or pulse schedules into a ``Qobj``.
 
     This function serializes the payloads, which could be either circuits or schedules,
diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py
index 75ee19c1e..87150bcf7 100644
--- a/qiskit/providers/models/backendconfiguration.py
+++ b/qiskit/providers/models/backendconfiguration.py
@@ -26,7 +26,6 @@ from qiskit.pulse.channels import (
     DriveChannel,
     MeasureChannel,
 )
-from qiskit.utils.deprecation import deprecate_arg
 
 
 class GateConfig:
@@ -831,22 +830,13 @@ class PulseBackendConfiguration(QasmBackendConfiguration):
             raise BackendConfigurationError(f"Invalid index for {qubit}-qubit systems.")
         return AcquireChannel(qubit)
 
-    @deprecate_arg(
-        "channel",
-        since="0.19.0",
-        additional_msg=(
-            "Instead, use the ``qubits`` argument. This method will now return accurate "
-            "ControlChannels determined by qubit indices."
-        ),
-    )
-    def control(self, qubits: Iterable[int] = None, channel: int = None) -> List[ControlChannel]:
+    def control(self, qubits: Iterable[int] = None) -> List[ControlChannel]:
         """
         Return the secondary drive channel for the given qubit -- typically utilized for
         controlling multiqubit interactions. This channel is derived from other channels.
 
         Args:
             qubits: Tuple or list of qubits of the form `(control_qubit, target_qubit)`.
-            channel: Deprecated.
 
         Raises:
             BackendConfigurationError: If the ``qubits`` is not a part of the system or if
@@ -855,8 +845,6 @@ class PulseBackendConfiguration(QasmBackendConfiguration):
         Returns:
             List of control channels.
         """
-        if channel is not None:
-            qubits = [channel]
         try:
             if isinstance(qubits, list):
                 qubits = tuple(qubits)
diff --git a/qiskit/qobj/__init__.py b/qiskit/qobj/__init__.py
index edd6b0099..5922fdf5d 100644
--- a/qiskit/qobj/__init__.py
+++ b/qiskit/qobj/__init__.py
@@ -23,7 +23,6 @@ Base
 .. autosummary::
    :toctree: ../stubs/
 
-   Qobj
    QobjExperimentHeader
    QobjHeader
 
@@ -74,14 +73,3 @@ from qiskit.qobj.qasm_qobj import QasmQobjInstruction
 from qiskit.qobj.qasm_qobj import QasmQobjExperiment
 from qiskit.qobj.qasm_qobj import QasmQobjConfig
 from qiskit.qobj.qasm_qobj import QasmQobjExperimentConfig
-
-from qiskit.utils.deprecation import deprecate_func
-
-
-class Qobj(QasmQobj):
-    """A backwards compat alias for QasmQobj."""
-
-    @deprecate_func(additional_msg="Instead, use QasmQobj or PulseQobj", since="0.19.0")
-    def __init__(self, qobj_id=None, config=None, experiments=None, header=None):
-        """Initialize a Qobj object."""
-        super().__init__(qobj_id=qobj_id, config=config, experiments=experiments, header=header)
diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py
index d1611840a..fc9186a87 100644
--- a/qiskit/transpiler/instruction_durations.py
+++ b/qiskit/transpiler/instruction_durations.py
@@ -16,20 +16,13 @@ from typing import Optional, List, Tuple, Union, Iterable
 
 import qiskit.circuit
 from qiskit.circuit import Barrier, Delay
-from qiskit.circuit import Instruction, Qubit, ParameterExpression
+from qiskit.circuit import Instruction, ParameterExpression
 from qiskit.circuit.duration import duration_in_dt
 from qiskit.providers import Backend
 from qiskit.transpiler.exceptions import TranspilerError
-from qiskit.utils.deprecation import deprecate_arg
 from qiskit.utils.units import apply_prefix
 
 
-def _is_deprecated_qubits_argument(qubits: Union[int, list[int], Qubit, list[Qubit]]) -> bool:
-    if isinstance(qubits, (int, Qubit)):
-        qubits = [qubits]
-    return isinstance(qubits[0], Qubit)
-
-
 class InstructionDurations:
     """Helper class to provide durations of instructions for scheduling.
 
@@ -130,7 +123,6 @@ class InstructionDurations:
             )
         else:
             for i, items in enumerate(inst_durations):
-
                 if not isinstance(items[-1], str):
                     items = (*items, "dt")  # set default unit
 
@@ -170,19 +162,10 @@ class InstructionDurations:
 
         return self
 
-    @deprecate_arg(
-        "qubits",
-        deprecation_description=(
-            "Using a Qubit or List[Qubit] for the ``qubits`` argument to InstructionDurations.get()"
-        ),
-        additional_msg="Instead, use an integer for the qubit index.",
-        since="0.19.0",
-        predicate=_is_deprecated_qubits_argument,
-    )
     def get(
         self,
         inst: str | qiskit.circuit.Instruction,
-        qubits: int | list[int] | Qubit | list[Qubit] | list[int | Qubit],
+        qubits: int | list[int],
         unit: str = "dt",
         parameters: list[float] | None = None,
     ) -> float:
@@ -192,7 +175,7 @@ class InstructionDurations:
 
         Args:
             inst: An instruction or its name to be queried.
-            qubits: Qubits or its indices that the instruction acts on.
+            qubits: Qubit indices that the instruction acts on.
             unit: The unit of duration to be returned. It must be 's' or 'dt'.
             parameters: The value of the parameters of the desired instruction.
 
@@ -212,12 +195,9 @@ class InstructionDurations:
         else:
             inst_name = inst
 
-        if isinstance(qubits, (int, Qubit)):
+        if isinstance(qubits, int):
             qubits = [qubits]
 
-        if isinstance(qubits[0], Qubit):
-            qubits = [q.index for q in qubits]
-
         try:
             return self._get(inst_name, qubits, unit, parameters)
         except TranspilerError as ex:
diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
index bbb1b39bb..fb33361a4 100644
--- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
+++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
@@ -16,7 +16,7 @@ from __future__ import annotations
 import logging
 import numpy as np
 
-from qiskit.circuit import Qubit, Gate
+from qiskit.circuit import Gate, ParameterExpression, Qubit
 from qiskit.circuit.delay import Delay
 from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate
 from qiskit.circuit.reset import Reset
@@ -227,7 +227,8 @@ class PadDynamicalDecoupling(BasePadding):
             for gate in self._dd_sequence:
                 try:
                     # Check calibration.
-                    gate_length = dag.calibrations[gate.name][(physical_index, gate.params)]
+                    params = self._resolve_params(gate)
+                    gate_length = dag.calibrations[gate.name][((physical_index,), params)].duration
                     if gate_length % self._alignment != 0:
                         # This is necessary to implement lightweight scheduling logic for this pass.
                         # Usually the pulse alignment constraint and pulse data chunk size take
@@ -394,6 +395,17 @@ class PadDynamicalDecoupling(BasePadding):
 
         dag.global_phase = self._mod_2pi(dag.global_phase + sequence_gphase)
 
+    @staticmethod
+    def _resolve_params(gate: Gate) -> tuple:
+        """Return gate params with any bound parameters replaced with floats"""
+        params = []
+        for p in gate.params:
+            if isinstance(p, ParameterExpression) and not p.parameters:
+                params.append(float(p))
+            else:
+                params.append(p)
+        return tuple(params)
+
     @staticmethod
     def _mod_2pi(angle: float, atol: float = 0):
         """Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π"""
diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py
index a4bbb76e1..94c1bed53 100644
--- a/qiskit/utils/quantum_instance.py
+++ b/qiskit/utils/quantum_instance.py
@@ -21,7 +21,7 @@ import warnings
 
 import numpy as np
 
-from qiskit.qobj import Qobj
+from qiskit.qobj import QasmQobj, PulseQobj
 from qiskit.utils import circuit_utils
 from qiskit.exceptions import QiskitError
 from qiskit.utils.backend_utils import (
@@ -451,7 +451,7 @@ class QuantumInstance:
 
         return transpiled_circuits
 
-    def assemble(self, circuits) -> Qobj:
+    def assemble(self, circuits) -> Union[QasmQobj, PulseQobj]:
         """assemble circuits"""
         # pylint: disable=cyclic-import
         from qiskit import compiler
diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py
index 66e172b1e..ad4b8e070 100644
--- a/qiskit/visualization/circuit/latex.py
+++ b/qiskit/visualization/circuit/latex.py
@@ -536,7 +536,7 @@ class QCircuitImage:
             register = get_bit_register(self._circuit, node.cargs[0])
             if register is not None:
                 wire2 = self._wire_map[register]
-                idx_str = str(node.cargs[0].index)
+                idx_str = str(self._circuit.find_bit(node.cargs[0]).registers[0][1])
             else:
                 wire2 = self._wire_map[node.cargs[0]]
 
diff --git a/qiskit/visualization/timeline/layouts.py b/qiskit/visualization/timeline/layouts.py
index 5c5737c6e..d77284e3e 100644
--- a/qiskit/visualization/timeline/layouts.py
+++ b/qiskit/visualization/timeline/layouts.py
@@ -48,14 +48,10 @@ The function signature of the layout is restricted to:
 
 Arbitrary layout function satisfying the above format can be accepted.
 """
-
-import warnings
-
 from typing import List, Tuple
 import numpy as np
 
 from qiskit import circuit
-from qiskit.visualization.exceptions import VisualizationError
 from qiskit.visualization.timeline import types
 
 
@@ -70,23 +66,9 @@ def qreg_creg_ascending(bits: List[types.Bits]) -> List[types.Bits]:
     Returns:
         Sorted bits.
     """
-    qregs = []
-    cregs = []
-
-    for bit in bits:
-        if isinstance(bit, circuit.Qubit):
-            qregs.append(bit)
-        elif isinstance(bit, circuit.Clbit):
-            cregs.append(bit)
-        else:
-            raise VisualizationError(f"Unknown bit {bit} is provided.")
-
-    with warnings.catch_warnings():
-        warnings.simplefilter("ignore")
-        qregs = sorted(qregs, key=lambda x: x.index, reverse=False)
-        cregs = sorted(cregs, key=lambda x: x.index, reverse=False)
-
-    return qregs + cregs
+    return [x for x in bits if isinstance(x, circuit.Qubit)] + [
+        x for x in bits if isinstance(x, circuit.Clbit)
+    ]
 
 
 def qreg_creg_descending(bits: List[types.Bits]) -> List[types.Bits]:
@@ -100,21 +82,9 @@ def qreg_creg_descending(bits: List[types.Bits]) -> List[types.Bits]:
     Returns:
         Sorted bits.
     """
-    qregs = []
-    cregs = []
-
-    for bit in bits:
-        if isinstance(bit, circuit.Qubit):
-            qregs.append(bit)
-        elif isinstance(bit, circuit.Clbit):
-            cregs.append(bit)
-        else:
-            raise VisualizationError(f"Unknown bit {bit} is provided.")
-
-    qregs = sorted(qregs, key=lambda x: x.index, reverse=True)
-    cregs = sorted(cregs, key=lambda x: x.index, reverse=True)
-
-    return qregs + cregs
+    return [x for x in bits[::-1] if isinstance(x, circuit.Qubit)] + [
+        x for x in bits[::-1] if isinstance(x, circuit.Clbit)
+    ]
 
 
 def time_map_in_dt(time_window: Tuple[int, int]) -> types.HorizontalAxis:
diff --git a/releasenotes/notes/dd-pg-10833-ddddee68ffd913c4.yaml b/releasenotes/notes/dd-pg-10833-ddddee68ffd913c4.yaml
new file mode 100644
index 000000000..cdf23b9ef
--- /dev/null
+++ b/releasenotes/notes/dd-pg-10833-ddddee68ffd913c4.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed a bug in the :class:`.PadDynamicalDecoupling` transpiler pass which
+    would cause the pass to fail if a circuit contained a pulse gate
+    calibration for one of the gates in the decoupling sequence. Fixed `#10833
+    <https://github.com/Qiskit/qiskit/issues/10833>`_.
diff --git a/releasenotes/notes/optimise-unsorted-parameters-310e5f2420771f49.yaml b/releasenotes/notes/optimise-unsorted-parameters-310e5f2420771f49.yaml
new file mode 100644
index 000000000..2ab9a49af
--- /dev/null
+++ b/releasenotes/notes/optimise-unsorted-parameters-310e5f2420771f49.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    The performance of :meth:`.QuantumCircuit.assign_parameters` when assigning a
+    single parameter of a circuit that involves many parameters has been improved.
diff --git a/releasenotes/notes/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml b/releasenotes/notes/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml
new file mode 100644
index 000000000..eb62402ab
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml
@@ -0,0 +1,10 @@
+---
+upgrade:
+  - |
+    The argument ``qubits`` in the method :meth:`qiskit.transpiler.instruction_durations.InstructionDurations.get`, does not accept :class:`.Qubit` (or a list of them) any more. This functionality was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use an integer for the qubit indices.
+
+  - |
+    The argument ``channel`` in the method :meth:`qiskit.providers.models.backendconfiguration.PulseBackendConfiguration.control` is removed. It was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead use  the ``qubits`` argument.
+
+  - |
+    The class ``qiskit.qobj.Qobj`` is removed. It was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use :class:`qiskit.qobj.QasmQobj` or :class:`qiskit.qobj.PulseQobj`.
\ No newline at end of file


Test patch:
diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py
index bd8c44ab0..6ae6a7b3c 100644
--- a/test/python/transpiler/test_dynamical_decoupling.py
+++ b/test/python/transpiler/test_dynamical_decoupling.py
@@ -17,7 +17,8 @@ import numpy as np
 from numpy import pi
 from ddt import ddt, data
 
-from qiskit.circuit import QuantumCircuit, Delay, Measure, Reset, Parameter
+from qiskit import pulse
+from qiskit.circuit import Gate, QuantumCircuit, Delay, Measure, Reset, Parameter
 from qiskit.circuit.library import XGate, YGate, RXGate, UGate, CXGate, HGate
 from qiskit.quantum_info import Operator
 from qiskit.transpiler.instruction_durations import InstructionDurations
@@ -29,7 +30,6 @@ from qiskit.transpiler.passes import (
 from qiskit.transpiler.passmanager import PassManager
 from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.transpiler.target import Target, InstructionProperties
-from qiskit import pulse
 
 from qiskit.test import QiskitTestCase
 
@@ -339,6 +339,154 @@ class TestPadDynamicalDecoupling(QiskitTestCase):
 
         self.assertEqual(ghz4_dd, expected)
 
+    def test_insert_dd_with_pulse_gate_calibrations(self):
+        """Test DD gates are inserted without error when circuit calibrations are used
+
+                      ┌───┐            ┌───────────────┐      ┌───┐       »
+           q_0: ──────┤ H ├─────────■──┤ Delay(75[dt]) ├──────┤ X ├───────»
+                ┌─────┴───┴─────┐ ┌─┴─┐└───────────────┘┌─────┴───┴──────┐»
+           q_1: ┤ Delay(50[dt]) ├─┤ X ├────────■────────┤ Delay(300[dt]) ├»
+                ├───────────────┴┐└───┘      ┌─┴─┐      └────────────────┘»
+           q_2: ┤ Delay(750[dt]) ├───────────┤ X ├──────────────■─────────»
+                ├────────────────┤           └───┘            ┌─┴─┐       »
+           q_3: ┤ Delay(950[dt]) ├────────────────────────────┤ X ├───────»
+                └────────────────┘                            └───┘       »
+        meas: 4/══════════════════════════════════════════════════════════»
+                                                                           »
+        «        ┌────────────────┐┌───┐┌───────────────┐ ░ ┌─┐
+        «   q_0: ┤ Delay(150[dt]) ├┤ X ├┤ Delay(75[dt]) ├─░─┤M├─────────
+        «        └────────────────┘└───┘└───────────────┘ ░ └╥┘┌─┐
+        «   q_1: ─────────────────────────────────────────░──╫─┤M├──────
+        «                                                 ░  ║ └╥┘┌─┐
+        «   q_2: ─────────────────────────────────────────░──╫──╫─┤M├───
+        «                                                 ░  ║  ║ └╥┘┌─┐
+        «   q_3: ─────────────────────────────────────────░──╫──╫──╫─┤M├
+        «                                                 ░  ║  ║  ║ └╥┘
+        «meas: 4/════════════════════════════════════════════╩══╩══╩══╩═
+        «                                                     0  1  2  3
+        """
+        dd_sequence = [XGate(), XGate()]
+        pm = PassManager(
+            [
+                ALAPScheduleAnalysis(self.durations),
+                PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0]),
+            ]
+        )
+
+        # Change duration to 100 from the 50 in self.durations to make sure
+        # gate duration is used correctly.
+        with pulse.builder.build() as x_sched:
+            pulse.builder.delay(100, pulse.DriveChannel(0))
+
+        circ_in = self.ghz4.measure_all(inplace=False)
+        circ_in.add_calibration(XGate(), (0,), x_sched)
+
+        ghz4_dd = pm.run(circ_in)
+
+        expected = self.ghz4.copy()
+        expected = expected.compose(Delay(50), [1], front=True)
+        expected = expected.compose(Delay(750), [2], front=True)
+        expected = expected.compose(Delay(950), [3], front=True)
+
+        # Delays different from those of the default case using self.durations
+        expected = expected.compose(Delay(75), [0])
+        expected = expected.compose(XGate(), [0])
+        expected = expected.compose(Delay(150), [0])
+        expected = expected.compose(XGate(), [0])
+        expected = expected.compose(Delay(75), [0])
+
+        expected = expected.compose(Delay(300), [1])
+
+        expected.measure_all()
+        expected.add_calibration(XGate(), (0,), x_sched)
+
+        self.assertEqual(ghz4_dd, expected)
+
+    def test_insert_dd_with_pulse_gate_calibrations_with_parmas(self):
+        """Test DD gates are inserted without error when parameterized circuit calibrations are used
+
+                      ┌───┐            ┌───────────────┐      ┌───┐       »
+           q_0: ──────┤ H ├─────────■──┤ Delay(75[dt]) ├──────┤ X ├───────»
+                ┌─────┴───┴─────┐ ┌─┴─┐└───────────────┘┌─────┴───┴──────┐»
+           q_1: ┤ Delay(50[dt]) ├─┤ X ├────────■────────┤ Delay(300[dt]) ├»
+                ├───────────────┴┐└───┘      ┌─┴─┐      └────────────────┘»
+           q_2: ┤ Delay(750[dt]) ├───────────┤ X ├──────────────■─────────»
+                ├────────────────┤           └───┘            ┌─┴─┐       »
+           q_3: ┤ Delay(950[dt]) ├────────────────────────────┤ X ├───────»
+                └────────────────┘                            └───┘       »
+        meas: 4/══════════════════════════════════════════════════════════»
+                                                                           »
+        «        ┌────────────────┐┌───┐┌───────────────┐ ░ ┌─┐
+        «   q_0: ┤ Delay(150[dt]) ├┤ X ├┤ Delay(75[dt]) ├─░─┤M├─────────
+        «        └────────────────┘└───┘└───────────────┘ ░ └╥┘┌─┐
+        «   q_1: ─────────────────────────────────────────░──╫─┤M├──────
+        «                                                 ░  ║ └╥┘┌─┐
+        «   q_2: ─────────────────────────────────────────░──╫──╫─┤M├───
+        «                                                 ░  ║  ║ └╥┘┌─┐
+        «   q_3: ─────────────────────────────────────────░──╫──╫──╫─┤M├
+        «                                                 ░  ║  ║  ║ └╥┘
+        «meas: 4/════════════════════════════════════════════╩══╩══╩══╩═
+        «                                                     0  1  2  3
+        """
+        # Change duration to 100 from the 50 in self.durations to make sure
+        # gate duration is used correctly.
+        amp = Parameter("amp")
+        with pulse.builder.build() as sched:
+            pulse.builder.play(
+                pulse.Gaussian(100, amp=amp, sigma=10.0),
+                pulse.DriveChannel(0),
+            )
+
+        class Echo(Gate):
+            """Dummy Gate subclass for testing
+
+            In this test, we use a non-standard gate so we can add parameters
+            to it, in order to test the handling of parameters by
+            PadDynamicalDecoupling. PadDynamicalDecoupling checks that the DD
+            sequence is equivalent to the identity, so we can not use Gate
+            directly. Here we subclass Gate and add the identity as its matrix
+            representation to satisfy PadDynamicalDecoupling's check.
+            """
+
+            def __array__(self, dtype=None):
+                return np.eye(2, dtype=dtype)
+
+        # A gate with one unbound and one bound parameter to leave in the final
+        # circuit.
+        echo = Echo("echo", 1, [amp, 10.0])
+
+        circ_in = self.ghz4.measure_all(inplace=False)
+        circ_in.add_calibration(echo, (0,), sched)
+
+        dd_sequence = [echo, echo]
+        pm = PassManager(
+            [
+                ALAPScheduleAnalysis(self.durations),
+                PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0]),
+            ]
+        )
+
+        ghz4_dd = pm.run(circ_in)
+
+        expected = self.ghz4.copy()
+        expected = expected.compose(Delay(50), [1], front=True)
+        expected = expected.compose(Delay(750), [2], front=True)
+        expected = expected.compose(Delay(950), [3], front=True)
+
+        # Delays different from those of the default case using self.durations
+        expected = expected.compose(Delay(75), [0])
+        expected = expected.compose(echo, [0])
+        expected = expected.compose(Delay(150), [0])
+        expected = expected.compose(echo, [0])
+        expected = expected.compose(Delay(75), [0])
+
+        expected = expected.compose(Delay(300), [1])
+
+        expected.measure_all()
+        expected.add_calibration(echo, (0,), sched)
+
+        self.assertEqual(ghz4_dd, expected)
+
     def test_insert_dd_ghz_xy4(self):
         """Test XY4 sequence of DD gates.