From 558fb0ad21658ce696f5229896416cb29400b16f Mon Sep 17 00:00:00 2001 From: Kazuki Tsuoka Date: Thu, 25 May 2023 02:08:28 +0900 Subject: [PATCH 01/13] Fix typo in documentation (#10140) * fix typos * fix lint * fix typo on excitation_preserving.py --- qiskit/circuit/library/generalized_gates/rv.py | 6 ++++-- .../library/n_local/excitation_preserving.py | 4 ++-- .../circuit/library/standard_gates/global_phase.py | 3 +-- qiskit/circuit/library/standard_gates/p.py | 4 ++-- qiskit/circuit/library/standard_gates/r.py | 4 ++-- qiskit/circuit/library/standard_gates/rx.py | 12 ++++++------ qiskit/circuit/library/standard_gates/ry.py | 14 +++++++------- qiskit/circuit/library/standard_gates/rz.py | 2 +- qiskit/circuit/library/standard_gates/u1.py | 6 +++--- qiskit/circuit/library/standard_gates/y.py | 2 +- 10 files changed, 29 insertions(+), 28 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/rv.py b/qiskit/circuit/library/generalized_gates/rv.py index 04eb70b23e36..a65c9c25ef8a 100644 --- a/qiskit/circuit/library/generalized_gates/rv.py +++ b/qiskit/circuit/library/generalized_gates/rv.py @@ -40,8 +40,10 @@ class RVGate(Gate): \newcommand{\sinc}{\text{sinc}} R(\vec{v}) = e^{-i \vec{v}\cdot\vec{\sigma}} = \begin{pmatrix} - \cos{\th} -i v_z \sinc(\th) & -(i v_x + v_y) \sinc(\th) \\ - -(i v_x - v_y) \sinc(\th) & \cos(\th) + i v_z \sinc(\th) + \cos\left(\th\right) -i v_z \sinc\left(\th\right) + & -(i v_x + v_y) \sinc\left(\th\right) \\ + -(i v_x - v_y) \sinc\left(\th\right) + & \cos\left(\th\right) + i v_z \sinc\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/n_local/excitation_preserving.py b/qiskit/circuit/library/n_local/excitation_preserving.py index d6c3be3b356a..a474e8ceb2c4 100644 --- a/qiskit/circuit/library/n_local/excitation_preserving.py +++ b/qiskit/circuit/library/n_local/excitation_preserving.py @@ -33,8 +33,8 @@ class ExcitationPreserving(TwoLocal): \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos(\th) & -i\sin(\th) & 0 \\ - 0 & -i\sin(\th) & \cos(\th) & 0 \\ + 0 & \cos\left(\th\right) & -i\sin\left(\th\right) & 0 \\ + 0 & -i\sin\left(\th\right) & \cos\left(\th\right) & 0 \\ 0 & 0 & 0 & e^{-i\phi} \end{pmatrix} diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py index a92c8d4c7c9d..e0a0d0e805e1 100644 --- a/qiskit/circuit/library/standard_gates/global_phase.py +++ b/qiskit/circuit/library/standard_gates/global_phase.py @@ -43,7 +43,6 @@ def __init__(self, phase: ParameterValueType, label: Optional[str] = None): super().__init__("global_phase", 0, [phase], label=label) def _define(self): - q = QuantumRegister(0, "q") qc = QuantumCircuit(q, name=self.name, global_phase=self.params[0]) @@ -52,7 +51,7 @@ def _define(self): def inverse(self): r"""Return inverted GLobalPhaseGate gate. - :math:`\text{GlobalPhaseGate}(\lambda){\dagger} = \text{GlobalPhaseGate}(-\lambda)` + :math:`\text{GlobalPhaseGate}(\lambda)^{\dagger} = \text{GlobalPhaseGate}(-\lambda)` """ return GlobalPhaseGate(-self.params[0]) diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 67a3b273b3ef..de19ee5d8732 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -238,7 +238,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted CPhase gate (:math:`CPhase(\lambda){\dagger} = CPhase(-\lambda)`)""" + r"""Return inverted CPhase gate (:math:`CPhase(\lambda)^{\dagger} = CPhase(-\lambda)`)""" return CPhaseGate(-self.params[0], ctrl_state=self.ctrl_state) def __array__(self, dtype=None): @@ -341,5 +341,5 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted MCU1 gate (:math:`MCU1(\lambda){\dagger} = MCU1(-\lambda)`)""" + r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)""" return MCPhaseGate(-self.params[0], self.num_ctrl_qubits) diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 28ca03385efc..9f05413879c1 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -44,8 +44,8 @@ class RGate(Gate): R(\theta, \phi) = e^{-i \th \left(\cos{\phi} x + \sin{\phi} y\right)} = \begin{pmatrix} - \cos{\th} & -i e^{-i \phi} \sin{\th} \\ - -i e^{i \phi} \sin{\th} & \cos{\th} + \cos\left(\th\right) & -i e^{-i \phi} \sin\left(\th\right) \\ + -i e^{i \phi} \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 7eddd05e9510..76721fa84040 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -45,8 +45,8 @@ class RXGate(Gate): RX(\theta) = \exp\left(-i \th X\right) = \begin{pmatrix} - \cos{\th} & -i\sin{\th} \\ - -i\sin{\th} & \cos{\th} + \cos\left(\th\right) & -i\sin\left(\th\right) \\ + -i\sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ @@ -137,9 +137,9 @@ class CRXGate(ControlledGate): I \otimes |0\rangle\langle 0| + RX(\theta) \otimes |1\rangle\langle 1| = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos{\th} & 0 & -i\sin{\th} \\ + 0 & \cos\left(\th\right) & 0 & -i\sin\left(\th\right) \\ 0 & 0 & 1 & 0 \\ - 0 & -i\sin{\th} & 0 & \cos{\th} + 0 & -i\sin\left(\th\right) & 0 & \cos\left(\th\right) \end{pmatrix} .. note:: @@ -165,8 +165,8 @@ class CRXGate(ControlledGate): \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ - 0 & 0 & \cos{\th} & -i\sin{\th} \\ - 0 & 0 & -i\sin{\th} & \cos{\th} + 0 & 0 & \cos\left(\th\right) & -i\sin\left(\th\right) \\ + 0 & 0 & -i\sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 8cba2ed43252..fd561784f056 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -44,8 +44,8 @@ class RYGate(Gate): RY(\theta) = \exp\left(-i \th Y\right) = \begin{pmatrix} - \cos{\th} & -\sin{\th} \\ - \sin{\th} & \cos{\th} + \cos\left(\th\right) & -\sin\left(\th\right) \\ + \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ @@ -95,7 +95,7 @@ def control( def inverse(self): r"""Return inverted RY gate. - :math:`RY(\lambda){\dagger} = RY(-\lambda)` + :math:`RY(\lambda)^{\dagger} = RY(-\lambda)` """ return RYGate(-self.params[0]) @@ -136,9 +136,9 @@ class CRYGate(ControlledGate): I \otimes |0\rangle\langle 0| + RY(\theta) \otimes |1\rangle\langle 1| = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos{\th} & 0 & -\sin{\th} \\ + 0 & \cos\left(\th\right) & 0 & -\sin\left(\th\right) \\ 0 & 0 & 1 & 0 \\ - 0 & \sin{\th} & 0 & \cos{\th} + 0 & \sin\left(\th\right) & 0 & \cos\left(\th\right) \end{pmatrix} .. note:: @@ -164,8 +164,8 @@ class CRYGate(ControlledGate): \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ - 0 & 0 & \cos{\th} & -\sin{\th} \\ - 0 & 0 & \sin{\th} & \cos{\th} + 0 & 0 & \cos\left(\th\right) & -\sin\left(\th\right) \\ + 0 & 0 & \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index f3da059f82ff..3ae7c62a6913 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -106,7 +106,7 @@ def control( def inverse(self): r"""Return inverted RZ gate - :math:`RZ(\lambda){\dagger} = RZ(-\lambda)` + :math:`RZ(\lambda)^{\dagger} = RZ(-\lambda)` """ return RZGate(-self.params[0]) diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 4dde8058adc4..168eb2e9dac7 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -138,7 +138,7 @@ def control( return gate def inverse(self): - r"""Return inverted U1 gate (:math:`U1(\lambda){\dagger} = U1(-\lambda)`)""" + r"""Return inverted U1 gate (:math:`U1(\lambda)^{\dagger} = U1(-\lambda)`)""" return U1Gate(-self.params[0]) def __array__(self, dtype=None): @@ -256,7 +256,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted CU1 gate (:math:`CU1(\lambda){\dagger} = CU1(-\lambda)`)""" + r"""Return inverted CU1 gate (:math:`CU1(\lambda)^{\dagger} = CU1(-\lambda)`)""" return CU1Gate(-self.params[0], ctrl_state=self.ctrl_state) def __array__(self, dtype=None): @@ -365,5 +365,5 @@ def control( return gate def inverse(self): - r"""Return inverted MCU1 gate (:math:`MCU1(\lambda){\dagger} = MCU1(-\lambda)`)""" + r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)""" return MCU1Gate(-self.params[0], self.num_ctrl_qubits) diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index 6240a70338ad..871aa04c2e77 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -111,7 +111,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted Y gate (:math:`Y{\dagger} = Y`)""" + r"""Return inverted Y gate (:math:`Y^{\dagger} = Y`)""" return YGate() # self-inverse def __array__(self, dtype=complex): From 716b648cb993de6e98cab4750374373d6cba2711 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 24 May 2023 20:31:30 +0100 Subject: [PATCH 02/13] Fix `initial_layout` in `transpile` with loose qubits (#10153) The previous logic around building a `Layout` object from an `initial_layout` was still couched in the "registers own bits" model, so could not cope with loose bits. It's most convenient to just build the mapping ourselves during the parsing, since the logic is quite specific to the form of the `initial_layout` argument, rather than being something that's inherently tied to the `Layout` class. --- qiskit/compiler/transpiler.py | 30 ++++++++++++------ ..._layout-loose-qubits-0c59b2d6fb99d7e6.yaml | 6 ++++ test/python/compiler/test_transpiler.py | 31 +++++++++++++++---- 3 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index dc93c85a5fcf..706a223f8c92 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -28,7 +28,6 @@ from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import Qubit -from qiskit.converters import isinstanceint, isinstancelist from qiskit.dagcircuit import DAGCircuit from qiskit.providers.backend import Backend from qiskit.providers.models import BackendProperties @@ -757,16 +756,27 @@ def _parse_initial_layout(initial_layout, circuits): def _layout_from_raw(initial_layout, circuit): if initial_layout is None or isinstance(initial_layout, Layout): return initial_layout - elif isinstancelist(initial_layout): - if all(isinstanceint(elem) for elem in initial_layout): - initial_layout = Layout.from_intlist(initial_layout, *circuit.qregs) - elif all(elem is None or isinstance(elem, Qubit) for elem in initial_layout): - initial_layout = Layout.from_qubit_list(initial_layout, *circuit.qregs) - elif isinstance(initial_layout, dict): - initial_layout = Layout(initial_layout) + if isinstance(initial_layout, dict): + return Layout(initial_layout) + # Should be an iterable either of ints or bits/None. + specifier = tuple(initial_layout) + if all(phys is None or isinstance(phys, Qubit) for phys in specifier): + mapping = {phys: virt for phys, virt in enumerate(specifier) if virt is not None} + if len(mapping) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(mapping)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) else: - raise TranspilerError("The initial_layout parameter could not be parsed") - return initial_layout + if len(specifier) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(specifier)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) + if len(specifier) != len(set(specifier)): + raise TranspilerError(f"'initial_layout' contained duplicate entries: {specifier}") + mapping = {int(phys): virt for phys, virt in zip(specifier, circuit.qubits)} + return Layout(mapping) # multiple layouts? if isinstance(initial_layout, list) and any( diff --git a/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml new file mode 100644 index 000000000000..4bac5214f5c6 --- /dev/null +++ b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Using ``initial_layout`` in calls to :func:`.transpile` will no longer error if the + circuit contains qubits not in any registers, or qubits that exist in more than one + register. See `#10125 `__. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index fe5670aba861..c25425d051f1 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -627,14 +627,9 @@ def test_wrong_initial_layout(self): QuantumRegister(3, "q")[2], ] - with self.assertRaises(TranspilerError) as cm: + with self.assertRaisesRegex(TranspilerError, "different numbers of qubits"): transpile(qc, backend, initial_layout=bad_initial_layout) - self.assertEqual( - "FullAncillaAllocation: The layout refers to a qubit that does not exist in circuit.", - cm.exception.message, - ) - def test_parameterized_circuit_for_simulator(self): """Verify that a parameterized circuit can be transpiled for a simulator backend.""" qr = QuantumRegister(2, name="qr") @@ -1616,6 +1611,30 @@ def test_transpile_identity_circuit_no_target(self, opt_level): result = transpile(qc, optimization_level=opt_level) self.assertEqual(empty_qc, result) + @data(0, 1, 2, 3) + def test_initial_layout_with_loose_qubits(self, opt_level): + """Regression test of gh-10125.""" + qc = QuantumCircuit([Qubit(), Qubit()]) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + + @data(0, 1, 2, 3) + def test_initial_layout_with_overlapping_qubits(self, opt_level): + """Regression test of gh-10125.""" + qr1 = QuantumRegister(2, "qr1") + qr2 = QuantumRegister(bits=qr1[:]) + qc = QuantumCircuit(qr1, qr2) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + @ddt class TestPostTranspileIntegration(QiskitTestCase): From 5013fe2239290414f2cfaafae13c6a9c09ddbbda Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Thu, 25 May 2023 15:55:34 +0300 Subject: [PATCH 03/13] Minor docs correction for SymbolicPulse library (#10161) * Correct doc string for Sawtooth,Triangle, Cos, Sin * Added a few missing \ --- qiskit/pulse/library/symbolic_pulses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 2718792ad153..878f6a4741f5 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -1450,7 +1450,7 @@ def Sin( .. math:: - f(x) &= \\text{A}\\sin\\left(2\\pi\text{freq}x+\\text{phase}\\right) , 0 <= x < duration + f(x) = \\text{A}\\sin\\left(2\\pi\\text{freq}x+\\text{phase}\\right) , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`. @@ -1517,7 +1517,7 @@ def Cos( .. math:: - f(x) &= \\text{A}\\cos\\left(2\\pi\text{freq}x+\\text{phase}\\right) , 0 <= x < duration + f(x) = \\text{A}\\cos\\left(2\\pi\\text{freq}x+\\text{phase}\\right) , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`. @@ -1584,7 +1584,7 @@ def Sawtooth( .. math:: - f(x) &= 2\\text{A}\\left[g\\left(x\\right)- + f(x) = 2\\text{A}\\left[g\\left(x\\right)- \\lfloor g\\left(x\\right)+\\frac{1}{2}\\rfloor\\right] where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`, @@ -1655,7 +1655,7 @@ def Triangle( .. math:: - f(x) &= \\text{A}\\left[\\text{sawtooth}\\left(x\\right)right] , 0 <= x < duration + f(x) = \\text{A}\\left[\\text{sawtooth}\\left(x\\right)\\right] , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`, and :math:`\\text{sawtooth}\\left(x\\right)` is a sawtooth wave with the same frequency From 788b89d9855dc89eb7770435f8b500790fe3b5bc Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Fri, 26 May 2023 21:49:15 +0300 Subject: [PATCH 04/13] Fixing BlockCollapser with Clbits (#9823) * Bug fix: collapsing blocks with clbits Co-authored-by: chriseclectic * release notes * similar fix to DAGDependency * additional fixes to handle classical bits in conditions * fixing lint issue * Fixing handling of conditions over full registers, following review comments --------- Co-authored-by: chriseclectic --- qiskit/dagcircuit/collect_blocks.py | 41 ++- qiskit/dagcircuit/dagcircuit.py | 7 +- qiskit/dagcircuit/dagdependency.py | 14 +- ...collapse-with-clbits-e14766353303d442.yaml | 9 + test/python/dagcircuit/test_collect_blocks.py | 324 +++++++++++++++++- 5 files changed, 375 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml diff --git a/qiskit/dagcircuit/collect_blocks.py b/qiskit/dagcircuit/collect_blocks.py index ed4df4c38b30..3c09d5dcb82b 100644 --- a/qiskit/dagcircuit/collect_blocks.py +++ b/qiskit/dagcircuit/collect_blocks.py @@ -14,7 +14,8 @@ """Various ways to divide a DAG into blocks of nodes, to split blocks of nodes into smaller sub-blocks, and to consolidate blocks.""" -from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit.circuit import QuantumCircuit, CircuitInstruction, ClassicalRegister +from qiskit.circuit.controlflow.condition import condition_bits from . import DAGOpNode, DAGCircuit, DAGDependency from .exceptions import DAGCircuitError @@ -254,22 +255,46 @@ def collapse_to_operation(self, blocks, collapse_fn): then uses collapse_fn to collapse this circuit into a single operation. """ global_index_map = {wire: idx for idx, wire in enumerate(self.dag.qubits)} + global_index_map.update({wire: idx for idx, wire in enumerate(self.dag.clbits)}) + for block in blocks: - # Find the set of qubits used in this block (which might be much smaller than - # the set of all qubits). + # Find the sets of qubits/clbits used in this block (which might be much smaller + # than the set of all qubits/clbits). cur_qubits = set() + cur_clbits = set() + + # Additionally, find the set of classical registers used in conditions over full registers + # (in such a case, we need to add that register to the block circuit, not just its clbits). + cur_clregs = [] + for node in block: cur_qubits.update(node.qargs) - - # For reproducibility, order these qubits compatibly with the global order. + cur_clbits.update(node.cargs) + cond = getattr(node.op, "condition", None) + if cond is not None: + cur_clbits.update(condition_bits(cond)) + if isinstance(cond[0], ClassicalRegister): + cur_clregs.append(cond[0]) + + # For reproducibility, order these qubits/clbits compatibly with the global order. sorted_qubits = sorted(cur_qubits, key=lambda x: global_index_map[x]) + sorted_clbits = sorted(cur_clbits, key=lambda x: global_index_map[x]) + + qc = QuantumCircuit(sorted_qubits, sorted_clbits) + + # Add classical registers used in conditions over registers + for reg in cur_clregs: + qc.add_register(reg) # Construct a quantum circuit from the nodes in the block, remapping the qubits. wire_pos_map = {qb: ix for ix, qb in enumerate(sorted_qubits)} - qc = QuantumCircuit(len(cur_qubits)) + wire_pos_map.update({qb: ix for ix, qb in enumerate(sorted_clbits)}) + for node in block: - remapped_qubits = [wire_pos_map[qarg] for qarg in node.qargs] - qc.append(CircuitInstruction(node.op, remapped_qubits, node.cargs)) + instructions = qc.append(CircuitInstruction(node.op, node.qargs, node.cargs)) + cond = getattr(node.op, "condition", None) + if cond is not None: + instructions.c_if(*cond) # Collapse this quantum circuit into an operation. op = collapse_fn(qc) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index fbc60591d1f7..f40015d7f078 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -30,6 +30,7 @@ import rustworkx as rx from qiskit.circuit import ControlFlowOp, ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp +from qiskit.circuit.controlflow.condition import condition_bits from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit @@ -1129,8 +1130,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): for nd in node_block: block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): - block_cargs |= set(nd.cargs) + block_cargs |= set(nd.cargs) + cond = getattr(nd.op, "condition", None) + if cond is not None: + block_cargs.update(condition_bits(cond)) # Create replacement node new_node = DAGOpNode( diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 1ed5eb96e113..128129e76372 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -19,6 +19,7 @@ import rustworkx as rx +from qiskit.circuit.controlflow.condition import condition_bits from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.dagcircuit.exceptions import DAGDependencyError @@ -394,11 +395,8 @@ def _create_op_node(self, operation, qargs, cargs): # (1) cindices_list are specific to template optimization and should not be computed # in this place. # (2) Template optimization pass needs currently does not handle general conditions. - if isinstance(operation.condition[0], Clbit): - condition_bits = [operation.condition[0]] - else: - condition_bits = operation.condition[0] - cindices_list = [self.clbits.index(clbit) for clbit in condition_bits] + cond_bits = condition_bits(operation.condition) + cindices_list = [self.clbits.index(clbit) for clbit in cond_bits] else: cindices_list = [] else: @@ -591,8 +589,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): for nd in node_block: block_qargs |= set(nd.qargs) - if nd.op.condition: - block_cargs |= set(nd.cargs) + block_cargs |= set(nd.cargs) + cond = getattr(nd.op, "condition", None) + if cond is not None: + block_cargs.update(condition_bits(cond)) # Create replacement node new_node = self._create_op_node( diff --git a/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml b/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml new file mode 100644 index 000000000000..ef3e073e28af --- /dev/null +++ b/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed a bug in :class:`~BlockCollapser` where classical bits were ignored when collapsing + a block of nodes. + - | + Fixed a bug in :meth:`~qiskit.dagcircuit.DAGCircuit.replace_block_with_op` and + :meth:`~qiskit.dagcircuit.DAGDependency.replace_block_with_op` + that led to ignoring classical bits. diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py index 6bfc5756d9df..fcafafa753af 100644 --- a/test/python/dagcircuit/test_collect_blocks.py +++ b/test/python/dagcircuit/test_collect_blocks.py @@ -15,10 +15,17 @@ import unittest -from qiskit.converters import circuit_to_dag, circuit_to_dagdependency +from qiskit import QuantumRegister, ClassicalRegister +from qiskit.converters import ( + circuit_to_dag, + circuit_to_dagdependency, + circuit_to_instruction, + dag_to_circuit, + dagdependency_to_circuit, +) from qiskit.test import QiskitTestCase -from qiskit.circuit import QuantumCircuit -from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter +from qiskit.circuit import QuantumCircuit, Measure, Clbit +from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter, BlockCollapser class TestCollectBlocks(QiskitTestCase): @@ -449,6 +456,317 @@ def test_do_not_split_blocks(self): ) self.assertEqual(len(blocks), 1) + def test_collect_blocks_with_cargs(self): + """Test collecting and collapsing blocks with classical bits appearing as cargs.""" + + qc = QuantumCircuit(3) + qc.h(0) + qc.h(1) + qc.h(2) + qc.measure_all() + + dag = circuit_to_dag(qc) + + # Collect all measure instructions + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of 3 measures + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + self.assertEqual(blocks[0][0].op, Measure()) + self.assertEqual(blocks[0][1].op, Measure()) + self.assertEqual(blocks[0][2].op, Measure()) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 5) + self.assertEqual(collapsed_qc.data[0].operation.name, "h") + self.assertEqual(collapsed_qc.data[1].operation.name, "h") + self.assertEqual(collapsed_qc.data[2].operation.name, "h") + self.assertEqual(collapsed_qc.data[3].operation.name, "barrier") + self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cargs_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing as cargs, + using DAGDependency.""" + + qc = QuantumCircuit(3) + qc.h(0) + qc.h(1) + qc.h(2) + qc.measure_all() + + dag = circuit_to_dagdependency(qc) + + # Collect all measure instructions + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of 3 measures + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + self.assertEqual(blocks[0][0].op, Measure()) + self.assertEqual(blocks[0][1].op, Measure()) + self.assertEqual(blocks[0][2].op, Measure()) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 5) + self.assertEqual(collapsed_qc.data[0].operation.name, "h") + self.assertEqual(collapsed_qc.data[1].operation.name, "h") + self.assertEqual(collapsed_qc.data[2].operation.name, "h") + self.assertEqual(collapsed_qc.data[3].operation.name, "barrier") + self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_clbits(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition.""" + + qc = QuantumCircuit(4, 3) + qc.cx(0, 1).c_if(0, 1) + qc.cx(2, 3) + qc.cx(1, 2) + qc.cx(0, 1) + qc.cx(2, 3).c_if(1, 0) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 5) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2) + + def test_collect_blocks_with_clbits_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing + under conditions, using DAGDependency.""" + + qc = QuantumCircuit(4, 3) + qc.cx(0, 1).c_if(0, 1) + qc.cx(2, 3) + qc.cx(1, 2) + qc.cx(0, 1) + qc.cx(2, 3).c_if(1, 0) + + dag = circuit_to_dagdependency(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 5) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2) + + def test_collect_blocks_with_clbits2(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + cbit = Clbit() + + qc = QuantumCircuit(qreg, creg, [cbit]) + qc.cx(0, 1).c_if(creg[1], 1) + qc.cx(2, 3).c_if(cbit, 0) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 4) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_clbits2_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition, using DAGDependency.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + cbit = Clbit() + + qc = QuantumCircuit(qreg, creg, [cbit]) + qc.cx(0, 1).c_if(creg[1], 1) + qc.cx(2, 3).c_if(cbit, 0) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 4) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cregs(self): + """Test collecting and collapsing blocks with classical registers appearing under + condition.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + creg2 = ClassicalRegister(2, "cr2") + + qc = QuantumCircuit(qreg, creg, creg2) + qc.cx(0, 1).c_if(creg, 3) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cregs_dagdependency(self): + """Test collecting and collapsing blocks with classical registers appearing under + condition, using DAGDependency.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + creg2 = ClassicalRegister(2, "cr2") + + qc = QuantumCircuit(qreg, creg, creg2) + qc.cx(0, 1).c_if(creg, 3) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dagdependency(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + if __name__ == "__main__": unittest.main() From 68a603d140a92ea6ce7173b1886aeaad2206e585 Mon Sep 17 00:00:00 2001 From: Blesso Abraham <73220998+blesso1quanta@users.noreply.github.com> Date: Tue, 30 May 2023 20:40:18 +0530 Subject: [PATCH 05/13] Update __init__.py (#10175) I cleared a small typo in this file as as to as a --- qiskit/circuit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 083325efad2f..d319cabc418c 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -26,7 +26,7 @@ gates, measurements and resets, which may be conditioned on real-time classical computation. A set of quantum gates is said to be universal if any unitary transformation of the quantum data can be efficiently approximated arbitrarily well -as as sequence of gates in the set. Any quantum program can be represented by a +as a sequence of gates in the set. Any quantum program can be represented by a sequence of quantum circuits and classical near-time computation. In Qiskit, this core element is represented by the :class:`QuantumCircuit` class. From 76850e1fdf9135f2072c8a9624d3c58b6f989ef9 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 30 May 2023 17:33:06 +0100 Subject: [PATCH 06/13] Fix top-level `switch` statements in `QuantumCircuit.compose` (#10164) * Fix top-level `switch` statements in `QuantumCircuit.compose` The register-mapping code was not being applied to `SwitchCaseOp.target` in the same way that it is for conditions. This commit does not change any behaviour about recursing into _nested_ control-flow blocks, which still likely have problems with composition. * Apply suggestions from review --- qiskit/circuit/quantumcircuit.py | 41 +++++++++++++------ .../fix-compose-switch-19ada3828d939353.yaml | 5 +++ test/python/circuit/test_compose.py | 41 +++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 9299ae054d73..98e056c2a2a8 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -877,6 +877,9 @@ def compose( lcr_1: 0 ═══════════ lcr_1: 0 ═══════════════════════ """ + # pylint: disable=cyclic-import + from qiskit.circuit.controlflow.switch_case import SwitchCaseOp + if inplace and front and self._control_flow_scopes: # If we're composing onto ourselves while in a stateful control-flow builder context, # there's no clear meaning to composition to the "front" of the circuit. @@ -955,30 +958,42 @@ def compose( ) edge_map.update(zip(other.clbits, dest.cbit_argument_conversion(clbits))) + # Cache for `map_register_to_dest`. + _map_register_cache = {} + + def map_register_to_dest(theirs): + """Map the target's registers to suitable equivalents in the destination, adding an + extra one if there's no exact match.""" + if theirs.name in _map_register_cache: + return _map_register_cache[theirs.name] + mapped_bits = [edge_map[bit] for bit in theirs] + for ours in dest.cregs: + if mapped_bits == list(ours): + mapped_theirs = ours + break + else: + mapped_theirs = ClassicalRegister(bits=mapped_bits) + dest.add_register(mapped_theirs) + _map_register_cache[theirs.name] = mapped_theirs + return mapped_theirs + mapped_instrs: list[CircuitInstruction] = [] - condition_register_map = {} for instr in other.data: n_qargs: list[Qubit] = [edge_map[qarg] for qarg in instr.qubits] n_cargs: list[Clbit] = [edge_map[carg] for carg in instr.clbits] n_op = instr.operation.copy() - # Map their registers over to ours, adding an extra one if there's no exact match. if getattr(n_op, "condition", None) is not None: target, value = n_op.condition if isinstance(target, Clbit): n_op.condition = (edge_map[target], value) else: - if target.name not in condition_register_map: - mapped_bits = [edge_map[bit] for bit in target] - for our_creg in dest.cregs: - if mapped_bits == list(our_creg): - new_target = our_creg - break - else: - new_target = ClassicalRegister(bits=[edge_map[bit] for bit in target]) - dest.add_register(new_target) - condition_register_map[target.name] = new_target - n_op.condition = (condition_register_map[target.name], value) + n_op.condition = (map_register_to_dest(target), value) + elif isinstance(n_op, SwitchCaseOp): + if isinstance(n_op.target, Clbit): + n_op.target = edge_map[n_op.target] + else: + n_op.target = map_register_to_dest(n_op.target) mapped_instrs.append(CircuitInstruction(n_op, n_qargs, n_cargs)) diff --git a/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml b/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml new file mode 100644 index 000000000000..da83aafe33c7 --- /dev/null +++ b/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a bug in :meth:`.QuantumCircuit.compose` where the :attr:`.SwitchCaseOp.target` attribute + in the subcircuit would not get mapped to a register in the base circuit correctly. diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 3fc745a9a705..0f77493361fb 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -29,6 +29,8 @@ Parameter, Gate, Instruction, + CASE_DEFAULT, + SwitchCaseOp, ) from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal from qiskit.test import QiskitTestCase @@ -477,6 +479,45 @@ def test_compose_conditional_no_match(self): self.assertEqual(z.condition[1], 1) self.assertIs(x.condition[0][0], test.clbits[1]) + def test_compose_switch_match(self): + """Test that composition containing a `switch` with a register that matches proceeds + correctly.""" + case_0 = QuantumCircuit(1, 2) + case_0.x(0) + case_1 = QuantumCircuit(1, 2) + case_1.z(0) + case_default = QuantumCircuit(1, 2) + cr = ClassicalRegister(2, "target") + right = QuantumCircuit(QuantumRegister(1), cr) + right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1]) + + test = QuantumCircuit(QuantumRegister(3), cr, ClassicalRegister(2)).compose( + right, [1], [0, 1] + ) + + expected = test.copy_empty_like() + expected.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [1], [0, 1]) + self.assertEqual(test, expected) + + def test_compose_switch_no_match(self): + """Test that composition containing a `switch` with a register that matches proceeds + correctly.""" + case_0 = QuantumCircuit(1, 2) + case_0.x(0) + case_1 = QuantumCircuit(1, 2) + case_1.z(0) + case_default = QuantumCircuit(1, 2) + cr = ClassicalRegister(2, "target") + right = QuantumCircuit(QuantumRegister(1), cr) + right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1]) + test = QuantumCircuit(3, 3).compose(right, [1], [0, 1]) + + self.assertEqual(len(test.data), 1) + self.assertIsInstance(test.data[0].operation, SwitchCaseOp) + target = test.data[0].operation.target + self.assertIn(target, test.cregs) + self.assertEqual(list(target), test.clbits[0:2]) + def test_compose_gate(self): """Composing with a gate. From 4762e26708fbc11d3b051dade4cf90b1d16bfb25 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 30 May 2023 13:21:47 -0400 Subject: [PATCH 07/13] Fix PassManagerConfig.from_backend with BackendV1 and no CouplingMap (#10172) * Fix PassManagerConfig.from_backend with BackendV1 and no CouplingMap This commit fixes an issue in the PassManagerConfig.from_backend constructor method when using BackendV1 based simulator backends. The pass was incorrectly handling the case when the backend configuration didn't have a coupling map defined and incorrectly creating a coupling map with 0 qubits instead of using None to indicate the lack of connectivity. This has been fixed so the coupling map creation is skipped if there is no coupling map attribute in the backend's configuration. Fixes #10171 * Fix tests --- qiskit/transpiler/passmanager_config.py | 4 +++- .../fix-pm-config-from-backend-f3b71b11858b4f08.yaml | 9 +++++++++ test/python/transpiler/test_passmanager_config.py | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 5c1720ccb1d6..ae1f43b3c243 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -144,7 +144,9 @@ def from_backend(cls, backend, **pass_manager_options): res.inst_map = backend.instruction_schedule_map if res.coupling_map is None: if backend_version < 2: - res.coupling_map = CouplingMap(getattr(config, "coupling_map", None)) + cmap_edge_list = getattr(config, "coupling_map", None) + if cmap_edge_list is not None: + res.coupling_map = CouplingMap(cmap_edge_list) else: res.coupling_map = backend.coupling_map if res.instruction_durations is None: diff --git a/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml b/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml new file mode 100644 index 000000000000..c85355478f06 --- /dev/null +++ b/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue with the :meth:`.PassManagerConfig.from_backend` constructor + when building a :class:`~.PassManagerConfig` object from a :class:`~.BackendV1` + instance that didn't have a coupling map attribute defined. Previously, the + constructor would incorrectly create a :class:`~.CouplingMap` object with + 0 qubits instead of using ``None``. + Fixed `#10171 `__ diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py index d78b52e86542..96721e1884a3 100644 --- a/test/python/transpiler/test_passmanager_config.py +++ b/test/python/transpiler/test_passmanager_config.py @@ -86,6 +86,7 @@ def test_simulator_backend_v1(self): config = PassManagerConfig.from_backend(backend) self.assertIsInstance(config, PassManagerConfig) self.assertIsNone(config.inst_map) + self.assertIsNone(config.coupling_map) def test_invalid_user_option(self): """Test from_backend() with an invalid user option.""" @@ -103,7 +104,7 @@ def test_str(self): initial_layout: None basis_gates: ['id', 'rz', 'sx', 'x'] inst_map: None - coupling_map: + coupling_map: None layout_method: None routing_method: None translation_method: None From 291824a392f131e3b7d7ffca4c6fafdecf72c007 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 30 May 2023 21:44:48 +0200 Subject: [PATCH 08/13] some phrasing in the migration guides (#10083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * some phrasing in the migration guides * more changes * Update docs/migration_guides/algorithms_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Abby Mitchell <23662430+javabster@users.noreply.github.com> * rephrase a description for BackendEstimator and BackendSampler * redo intro to qi_migration * readjust opflow * Update docs/migration_guides/algorithms_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * algorithm pass * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/qi_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/algorithms_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/migration_guides/opflow_migration.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * services with native primitive implementations * typo --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Abby Mitchell <23662430+javabster@users.noreply.github.com> --- .../migration_guides/algorithms_migration.rst | 20 +++++----- docs/migration_guides/opflow_migration.rst | 7 +--- docs/migration_guides/qi_migration.rst | 39 ++++++++++--------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/docs/migration_guides/algorithms_migration.rst b/docs/migration_guides/algorithms_migration.rst index 033c662cc547..b7b7ccb11380 100644 --- a/docs/migration_guides/algorithms_migration.rst +++ b/docs/migration_guides/algorithms_migration.rst @@ -74,15 +74,17 @@ How to choose a primitive configuration for your algorithm *Back to* `TL;DR`_ -The classes in :mod:`qiskit.algorithms` state the base class primitive type (``Sampler``/``Estimator``) -they require for their initialization. Once the primitive type is known, you can choose between -four different primitive implementations, depending on how you want to configure your execution: +The classes in +:mod:`qiskit.algorithms` are initialized with any implementation of :class:`qiskit.primitive.BaseSampler` or class:`qiskit.primitive.BaseEstimator`. - a. Using **local** statevector simulators for quick prototyping: **Reference Primitives** in :mod:`qiskit.primitives` - b. Using **local** Aer simulators for finer algorithm tuning: **Aer Primitives** in :mod:`qiskit_aer.primitives` - c. Accessing backends using the **Qiskit Runtime Service**: **Runtime Primitives** in :mod:`qiskit_ibm_runtime` - d. Accessing backends using a **non-Runtime-enabled provider**: **Backend Primitives** in :mod:`qiskit.primitives` +Once the kind of primitive is known, you can choose between the primitive implementations that better adjust to your case. For example: + a. For quick prototyping, you can use the **reference implementations of primitives** included in Qiskit: :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator`. + b. For finer algorithm tuning, a local simulator such as the **primitive implementation in Aer**: :class:`qiskit_aer.primitives.Sampler` and :class:`qiskit_aer.primitives.Estimator`. + c. For executing in quantum hardware you can: + + * access services with native primitive implementations, such as **IBM's Qiskit Runtime service** via :class:`qiskit_ibm_runtime.Sampler` and :class:`qiskit_ibm_runtime.Estimator` + * Wrap any backend with **Backend Primitives** (:class:`~qiskit.primitives.BackendSampler` and :class:`~qiskit.primitives.BackendEstimator`). These wrappers implement a primitive interface on top of a backend that only supports ``Backend.run()``. For more detailed information and examples, particularly on the use of the **Backend Primitives**, please refer to the `Quantum Instance migration guide `_. @@ -133,7 +135,7 @@ In this guide, we will cover 3 different common configurations for algorithms th from qiskit_aer.primitives import Sampler, Estimator - - Runtime Primitives with default configuration (see `VQD`_ example): + - IBM's Qiskit Runtime Primitives with default configuration (see `VQD`_ example): .. code-block:: python @@ -249,7 +251,7 @@ The legacy :class:`qiskit.algorithms.minimum_eigen_solvers.VQE` class has now be .. testcode:: - from qiskit.algorithms.minimum_eigensolvers import VQE # new import!!! + from qiskit.algorithms.minimum_eigensolvers import VQE # new import!!! from qiskit.algorithms.optimizers import SPSA from qiskit.circuit.library import TwoLocal from qiskit.quantum_info import SparsePauliOp diff --git a/docs/migration_guides/opflow_migration.rst b/docs/migration_guides/opflow_migration.rst index c6b899dae5cd..34554934a9cd 100644 --- a/docs/migration_guides/opflow_migration.rst +++ b/docs/migration_guides/opflow_migration.rst @@ -21,11 +21,8 @@ the :mod:`~qiskit.opflow` module to the :mod:`~qiskit.primitives` and :mod:`~qis .. attention:: Most references to the :class:`qiskit.primitives.Sampler` or :class:`qiskit.primitives.Estimator` in this guide - can be replaced with instances of the: - - - Aer primitives (:class:`qiskit_aer.primitives.Sampler`, :class:`qiskit_aer.primitives.Estimator`) - - Runtime primitives (:class:`qiskit_ibm_runtime.Sampler`, :class:`qiskit_ibm_runtime.Estimator`) - - Terra backend primitives (:class:`qiskit.primitives.BackendSampler`, :class:`qiskit.primitives.BackendEstimator`) + can be replaced with instances of any primitive implementation. For example Aer primitives (:class:`qiskit_aer.primitives.Sampler`/:class:`qiskit_aer.primitives.Estimator`) or IBM's Qiskit Runtime primitives (:class:`qiskit_ibm_runtime.Sampler`/:class:`qiskit_ibm_runtime.Estimator`). + Specific backends can be wrapped with (:class:`qiskit.primitives.BackendSampler`, :class:`qiskit.primitives.BackendEstimator`) to also present primitive-compatible interfaces. Certain classes, such as the :class:`~qiskit.opflow.expectations.AerPauliExpectation`, can only be replaced by a specific primitive instance diff --git a/docs/migration_guides/qi_migration.rst b/docs/migration_guides/qi_migration.rst index 09708f6848c8..1f27a468518e 100644 --- a/docs/migration_guides/qi_migration.rst +++ b/docs/migration_guides/qi_migration.rst @@ -50,20 +50,23 @@ Contents The Qiskit Primitives are algorithmic abstractions that encapsulate the access to backends or simulators for an easy integration into algorithm workflows. - The current pool of primitives includes **two** different **classes** (:class:`~qiskit.primitives.Sampler` and - :class:`~qiskit.primitives.Estimator`) that can be imported from **three** different locations ( - :mod:`qiskit.primitives`, :mod:`qiskit_aer.primitives` and :mod:`qiskit_ibm_runtime` ). In addition to the - reference Sampler and Estimator, :mod:`qiskit.primitives` also contains a - :class:`~qiskit.primitives.BackendSampler` and a :class:`~qiskit.primitives.BackendEstimator` class. These are + The current pool of primitives includes **two** different types of primitives: Sampler and + Estimator. + + Qiskit provides reference implementations in :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator`. Additionally, + :class:`qiskit.primitives.BackendSampler` and a :class:`qiskit.primitives.BackendEstimator` are wrappers for ``backend.run()`` that follow the primitives interface. - This guide uses the following naming standard to refer to the primitives: + Providers can implement these primitives as subclasses of :class:`~qiskit.primitives.BaseSampler` and :class:`~qiskit.primitives.BaseEstimator` respectively. + IBM's Qiskit Runtime (:mod:`qiskit_ibm_runtime`) and Aer (:mod:`qiskit_aer.primitives`) are examples of native implementations of primitives. + + This guide uses the following naming convention: - - *Primitives* - Any Sampler/Estimator implementation - - *Reference Primitives* - The Sampler and Estimator in :mod:`qiskit.primitives` --> ``from qiskit.primitives import Sampler/Estimator`` - - *Aer Primitives* - The Sampler and Estimator in :mod:`qiskit_aer.primitives` --> ``from qiskit_aer.primitives import Sampler/Estimator`` - - *Runtime Primitives* - The Sampler and Estimator in :mod:`qiskit_ibm_runtime` --> ``from qiskit_ibm_runtime import Sampler/Estimator`` - - *Backend Primitives* - The BackendSampler and BackendEstimator in :mod:`qiskit.primitives` --> ``from qiskit import BackendSampler/BackendEstimator`` + - *Primitives* - Any Sampler/Estimator implementation using base classes :class:`qiskit.primitives.BackendSampler` and a :class:`qiskit.primitives.BackendEstimator`. + - *Reference Primitives* - :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator` are reference implementations that come with Qiskit. + - *Aer Primitives* - The `Aer `_ primitive implementations: class:`qiskit_aer.primitives.Sampler` and :class:`qiskit_aer.primitives.Estimator`. + - *Qiskit Runtime Primitives* - IBM's Qiskit Runtime primitive implementations: class:`qiskit_ibm_runtime.Sampler` and :class:`qiskit_ibm_runtime.Estimator`. + - *Backend Primitives* - Instances of :class:`qiskit.primitives.BackendSampler` and :class:`qiskit.primitives.BackendEstimator`. These allow any backend to implement primitive interfaces For guidelines on which primitives to choose for your task, please continue reading. @@ -103,7 +106,7 @@ yourself two questions: a. Using **local** statevector simulators for quick prototyping: **Reference Primitives** b. Using **local** noisy simulations for finer algorithm tuning: **Aer Primitives** - c. Accessing **runtime-enabled backends** (or cloud simulators): **Runtime Primitives** + c. Accessing **runtime-enabled backends** (or cloud simulators): **Qiskit Runtime Primitives** d. Accessing **non runtime-enabled backends** : **Backend Primitives** Arguably, the ``Sampler`` is the closest primitive to :class:`~qiskit.utils.QuantumInstance`, as they @@ -136,7 +139,7 @@ primitives **expose a similar setting through their interface**: * - QuantumInstance - Reference Primitives - Aer Primitives - - Runtime Primitives + - Qiskit Runtime Primitives - Backend Primitives * - Select ``backend`` - No @@ -186,7 +189,7 @@ primitives **expose a similar setting through their interface**: - No -(*) For more information on error mitigation and setting options on Runtime Primitives, visit +(*) For more information on error mitigation and setting options on Qiskit Runtime Primitives, visit `this link `_. (**) For more information on Runtime sessions, visit `this how-to `_. @@ -447,12 +450,12 @@ Code examples **Using Primitives** - The Runtime Primitives offer a suite of error mitigation methods that can be easily turned on with the + The Qiskit Runtime Primitives offer a suite of error mitigation methods that can be easily turned on with the ``resilience_level`` option. These are, however, not configurable. The sampler's ``resilience_level=1`` is the closest alternative to the Quantum Instance's measurement error mitigation implementation, but this is not a 1-1 replacement. - For more information on the error mitigation options in the Runtime Primitives, you can check out the following + For more information on the error mitigation options in the Qiskit Runtime Primitives, you can check out the following `link `_. .. code-block:: python @@ -503,7 +506,7 @@ Code examples * You cannot explicitly access their transpilation routine. * The mechanism to apply custom transpilation passes to the Aer, Runtime and Backend primitives is to pre-transpile locally and set ``skip_transpilation=True`` in the corresponding primitive. - * The only primitives that currently accept a custom **bound** transpiler pass manager are the **Backend Primitives**. + * The only primitives that currently accept a custom **bound** transpiler pass manager are instances of :class:`~qiskit.primitives.BackendSampler` or :class:`~qiskit.primitives.BackendEstimator`. If a ``bound_pass_manager`` is defined, the ``skip_transpilation=True`` option will **not** skip this bound pass. .. attention:: @@ -518,7 +521,7 @@ Code examples so if the circuit ended up on more qubits it did not matter. Note that the primitives **do** handle parameter bindings, meaning that even if a ``bound_pass_manager`` is defined in a - Backend Primitive, you do not have to manually assign parameters as expected in the Quantum Instance workflow. + :class:`~qiskit.primitives.BackendSampler` or :class:`~qiskit.primitives.BackendEstimator`, you do not have to manually assign parameters as expected in the Quantum Instance workflow. The use-case that motivated the addition of the two-stage transpilation to the ``QuantumInstance`` was to allow running pulse-efficient transpilation passes with the :class:`~qiskit.opflow.CircuitSampler` class. The following From 901e3b89731f9437ccbbe5d9eb5dff657854568f Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 30 May 2023 18:43:36 -0400 Subject: [PATCH 09/13] Add ruff to local tests and CI (#10116) * Add ruff to local tests and CI This adds linting using ruff to the relevant configuration files. Only a few rules are enabled and none of them trigger an error in the current state of the repo. * Add comments on running black separately from tox * Simplify and remove potentially bug causing instructions in CONTRIBUTING * Update pyproject.toml Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> --- .azure/lint-linux.yml | 2 ++ CONTRIBUTING.md | 39 ++++++++++++++++++++++++--------------- Makefile | 7 +++++-- pyproject.toml | 8 ++++++++ requirements-dev.txt | 1 + tox.ini | 2 ++ 6 files changed, 42 insertions(+), 17 deletions(-) diff --git a/.azure/lint-linux.yml b/.azure/lint-linux.yml index aac89bc005ce..5b79db09ae1b 100644 --- a/.azure/lint-linux.yml +++ b/.azure/lint-linux.yml @@ -43,6 +43,8 @@ jobs: - bash: | set -e source test-job/bin/activate + echo "Running ruff" + ruff qiskit test tools examples setup.py echo "Running pylint" pylint -rn qiskit test tools echo "Running Cargo Clippy" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73eacde8cf57..49d0d0ee376a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -422,25 +422,34 @@ Note: If you have run `test/ipynb/mpl_tester.ipynb` locally it is possible some ## Style and lint -Qiskit Terra uses 2 tools for verify code formatting and lint checking. The +Qiskit Terra uses three tools for verify code formatting and lint checking. The first tool is [black](https://github.com/psf/black) which is a code formatting tool that will automatically update the code formatting to a consistent style. The second tool is [pylint](https://www.pylint.org/) which is a code linter which does a deeper analysis of the Python code to find both style issues and -potential bugs and other common issues in Python. - -You can check that your local modifications conform to the style rules -by running `tox -elint` which will run `black` and `pylint` to check the local -code formatting and lint. If black returns a code formatting error you can -run `tox -eblack` to automatically update the code formatting to conform to -the style. However, if `pylint` returns any error you will have to fix these -issues by manually updating your code. - -Because `pylint` analysis can be slow, there is also a `tox -elint-incr` target, which only applies -`pylint` to files which have changed from the source github. On rare occasions this will miss some -issues that would have been caught by checking the complete source tree, but makes up for this by -being much faster (and those rare oversights will still be caught by the CI after you open a pull -request). +potential bugs and other common issues in Python. The third tool is the linter +[ruff](https://github.com/charliermarsh/ruff), which has been recently +introduced into Qiskit Terra on an experimental basis. Only a very small number +of rules are enabled. + +You can check that your local modifications conform to the style rules by +running `tox -elint` which will run `black`, `ruff`, and `pylint` to check the +local code formatting and lint. If black returns a code formatting error you can +run `tox -eblack` to automatically update the code formatting to conform to the +style. However, if `ruff` or `pylint` return any error you will have to fix +these issues by manually updating your code. + +Because `pylint` analysis can be slow, there is also a `tox -elint-incr` target, +which runs `black` and `ruff` just as `tox -elint` does, but only applies +`pylint` to files which have changed from the source github. On rare occasions +this will miss some issues that would have been caught by checking the complete +source tree, but makes up for this by being much faster (and those rare +oversights will still be caught by the CI after you open a pull request). + +Because they are so fast, it is sometimes convenient to run the tools `black` and `ruff` separately +rather than via `tox`. If you have installed the development packages in your python environment via +`pip install -r requirements-dev.txt`, then `ruff` and `black` will be available and can be run from +the command line. See [`tox.ini`](tox.ini) for how `tox` invokes them. ## Development Cycle diff --git a/Makefile b/Makefile index e6b8f5ce23d6..bea8a880e274 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,9 @@ OS := $(shell uname -s) -.PHONY: default env lint lint-incr style black test test_randomized pytest pytest_randomized test_ci coverage coverage_erase clean +.PHONY: default ruff env lint lint-incr style black test test_randomized pytest pytest_randomized test_ci coverage coverage_erase clean -default: style lint-incr test ; +default: ruff style lint-incr test ; # Dependencies need to be installed on the Anaconda virtual environment. env: @@ -41,6 +41,9 @@ lint-incr: tools/verify_headers.py qiskit test tools examples tools/find_optional_imports.py +ruff: + ruff qiskit test tools examples setup.py + style: black --check qiskit test tools examples setup.py diff --git a/pyproject.toml b/pyproject.toml index 57400392b35e..53b83021584a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,14 @@ environment = 'RUSTUP_TOOLCHAIN="stable"' before-all = "yum install -y wget && {package}/tools/install_rust.sh" environment = 'PATH="$PATH:$HOME/.cargo/bin" CARGO_NET_GIT_FETCH_WITH_CLI="true" RUSTUP_TOOLCHAIN="stable"' +[tool.ruff] +select = [ + "F631", + "F632", + "F634", + "F823", +] + [tool.pylint.main] extension-pkg-allow-list = [ "numpy", diff --git a/requirements-dev.txt b/requirements-dev.txt index cf0990e6a36a..fe67acb6ef98 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,6 +11,7 @@ black[jupyter]~=22.0 pydot astroid==2.14.2 pylint==2.16.2 +ruff==0.0.267 stestr>=2.0.0,!=4.0.0 pylatexenc>=1.4 ddt>=1.2.0,!=1.4.0,!=1.4.3 diff --git a/tox.ini b/tox.ini index 89c6ad734c03..764253f46d4b 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ commands = [testenv:lint] basepython = python3 commands = + ruff check qiskit test tools examples setup.py black --check {posargs} qiskit test tools examples setup.py pylint -rn qiskit test tools # This line is commented out until #6649 merges. We can't run this currently @@ -38,6 +39,7 @@ commands = basepython = python3 allowlist_externals = git commands = + ruff check qiskit test tools examples setup.py black --check {posargs} qiskit test tools examples setup.py -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py From f409015bd7e6bbff61942cf338fcaef0f1f0eed8 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 31 May 2023 17:25:39 -0400 Subject: [PATCH 10/13] Bump pyo3 and rust numpy version to 0.19.0 (#10186) * Bump pyo3 and rust numpy version to 0.19.0 PyO3 0.19.0 and rust-numpy 0.19.0 were just released. This commit updates the version used in qiskit to these latest releases. At the same time this updates usage of text signature for classes that was deprecated in the PyO3 0.19.0 release. While not fatal for normal builds this would have failed clippy in CI because we treat warnings as errors. * Apply suggestions from code review --- Cargo.lock | 110 ++++++++++-------- crates/accelerate/Cargo.toml | 4 +- crates/accelerate/src/edge_collections.rs | 2 +- crates/accelerate/src/error_map.rs | 2 +- crates/accelerate/src/nlayout.rs | 2 +- .../src/sabre_swap/neighbor_table.rs | 2 +- crates/accelerate/src/sabre_swap/sabre_dag.rs | 2 +- crates/qasm2/Cargo.toml | 2 +- crates/qasm2/src/lib.rs | 3 +- 9 files changed, 70 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5528c202c0d..a0573c59a892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -73,7 +73,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.8.0", "scopeguard", ] @@ -100,9 +100,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -163,15 +163,15 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "lock_api" @@ -185,10 +185,11 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ + "autocfg", "rawpointer", ] @@ -201,6 +202,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "ndarray" version = "0.15.6" @@ -267,9 +277,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0fee4571867d318651c24f4a570c3f18408cf95f16ccb576b3ce85496a46e" +checksum = "437213adf41bbccf4aeae535fbfcdad0f6fed241e1ae182ebe97fa1f3ce19389" dependencies = [ "libc", "ndarray", @@ -282,9 +292,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "parking_lot" @@ -337,25 +347,25 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" +checksum = "cffef52f74ec3b1a1baf295d9b8fcc3070327aefc39a6d00656b13c1d0b8885c" dependencies = [ "cfg-if", "hashbrown 0.13.2", "indexmap", "indoc", "libc", - "memoffset", + "memoffset 0.9.0", "num-bigint", "num-complex", "parking_lot", @@ -367,9 +377,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" +checksum = "713eccf888fb05f1a96eb78c0dbc51907fee42b3377272dc902eb38985f418d5" dependencies = [ "once_cell", "target-lexicon", @@ -377,9 +387,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" +checksum = "5b2ecbdcfb01cbbf56e179ce969a048fd7305a66d4cdf3303e0da09d69afe4c3" dependencies = [ "libc", "pyo3-build-config", @@ -387,9 +397,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" +checksum = "b78fdc0899f2ea781c463679b20cb08af9247febc8d052de941951024cd8aea0" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -399,9 +409,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" +checksum = "60da7b84f1227c3e2fe7593505de274dcf4c8928b4e0a1c23d551a14e4e80a0f" dependencies = [ "proc-macro2", "quote", @@ -437,9 +447,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -577,15 +587,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unindent" @@ -616,9 +626,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -631,42 +641,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 7f057196b6a0..983729ddeb1b 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["cdylib"] [dependencies] rayon = "1.7" -numpy = "0.18.0" +numpy = "0.19.0" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" @@ -25,7 +25,7 @@ rustworkx-core = "0.12" # The base version of PyO3 and setting a minimum feature set (e.g. probably just 'extension-module') # can be done in the workspace and inherited once we hit Rust 1.64. [dependencies.pyo3] -version = "0.18.3" +version = "0.19.0" features = ["extension-module", "hashbrown", "indexmap", "num-complex", "num-bigint"] [dependencies.ndarray] diff --git a/crates/accelerate/src/edge_collections.rs b/crates/accelerate/src/edge_collections.rs index 103d0db5d4cf..2fd06e5116f2 100644 --- a/crates/accelerate/src/edge_collections.rs +++ b/crates/accelerate/src/edge_collections.rs @@ -17,7 +17,6 @@ use pyo3::Python; /// A simple container that contains a vector representing edges in the /// coupling map that are found to be optimal by the swap mapper. #[pyclass(module = "qiskit._accelerate.stochastic_swap")] -#[pyo3(text_signature = "(/)")] #[derive(Clone, Debug)] pub struct EdgeCollection { pub edges: Vec, @@ -32,6 +31,7 @@ impl Default for EdgeCollection { #[pymethods] impl EdgeCollection { #[new] + #[pyo3(text_signature = "(/)")] pub fn new() -> Self { EdgeCollection { edges: Vec::new() } } diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index fca477298758..d699d383a7d0 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -32,7 +32,6 @@ use hashbrown::HashMap; /// qubit index. If an edge or qubit is ideal and has no error rate, you can /// either set it to ``0.0`` explicitly or as ``NaN``. #[pyclass(mapping, module = "qiskit._accelerate.error_map")] -#[pyo3(text_signature = "(num_qubits, num_edges, /")] #[derive(Clone, Debug)] pub struct ErrorMap { pub error_map: HashMap<[usize; 2], f64>, @@ -41,6 +40,7 @@ pub struct ErrorMap { #[pymethods] impl ErrorMap { #[new] + #[pyo3(text_signature = "(/, size=None)")] fn new(size: Option) -> Self { match size { Some(size) => ErrorMap { diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index c0306dce4207..87ae47a7fb43 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -25,7 +25,6 @@ use hashbrown::HashMap; /// logical_qubits (int): The number of logical qubits in the layout /// physical_qubits (int): The number of physical qubits in the layout #[pyclass(module = "qiskit._accelerate.stochastic_swap")] -#[pyo3(text_signature = "(qubit_indices, logical_qubits, physical_qubits, /)")] #[derive(Clone, Debug)] pub struct NLayout { pub logic_to_phys: Vec, @@ -43,6 +42,7 @@ impl NLayout { #[pymethods] impl NLayout { #[new] + #[pyo3(text_signature = "(qubit_indices, logical_qubits, physical_qubits, /)")] fn new( qubit_indices: HashMap, logical_qubits: usize, diff --git a/crates/accelerate/src/sabre_swap/neighbor_table.rs b/crates/accelerate/src/sabre_swap/neighbor_table.rs index 7568707da8bf..528d90a4c8cf 100644 --- a/crates/accelerate/src/sabre_swap/neighbor_table.rs +++ b/crates/accelerate/src/sabre_swap/neighbor_table.rs @@ -27,7 +27,6 @@ use rayon::prelude::*; /// and used solely to represent neighbors of each node in qiskit-terra's rust /// module. #[pyclass(module = "qiskit._accelerate.sabre_swap")] -#[pyo3(text_signature = "(/)")] #[derive(Clone, Debug)] pub struct NeighborTable { pub neighbors: Vec>, @@ -36,6 +35,7 @@ pub struct NeighborTable { #[pymethods] impl NeighborTable { #[new] + #[pyo3(text_signature = "(/, adjacency_matrix=None)")] pub fn new(adjacency_matrix: Option>) -> Self { let run_in_parallel = getenv_use_multiple_threads(); let neighbors = match adjacency_matrix { diff --git a/crates/accelerate/src/sabre_swap/sabre_dag.rs b/crates/accelerate/src/sabre_swap/sabre_dag.rs index c686520debb1..26a16f485c57 100644 --- a/crates/accelerate/src/sabre_swap/sabre_dag.rs +++ b/crates/accelerate/src/sabre_swap/sabre_dag.rs @@ -19,7 +19,6 @@ use rustworkx_core::petgraph::prelude::*; /// DAGCircuit, but the contents of the node are a tuple of DAGCircuit node ids, /// a list of qargs and a list of cargs #[pyclass(module = "qiskit._accelerate.sabre_swap")] -#[pyo3(text_signature = "(num_qubits, num_clbits, nodes, /)")] #[derive(Clone, Debug)] pub struct SabreDAG { pub dag: DiGraph<(usize, Vec), ()>, @@ -29,6 +28,7 @@ pub struct SabreDAG { #[pymethods] impl SabreDAG { #[new] + #[pyo3(text_signature = "(num_qubits, num_clbits, nodes, /)")] pub fn new( num_qubits: usize, num_clbits: usize, diff --git a/crates/qasm2/Cargo.toml b/crates/qasm2/Cargo.toml index 67b567ea1121..5a82991f3f02 100644 --- a/crates/qasm2/Cargo.toml +++ b/crates/qasm2/Cargo.toml @@ -13,4 +13,4 @@ crate-type = ["cdylib"] [dependencies] hashbrown = "0.13.2" -pyo3 = { version = "0.18.3", features = ["extension-module"] } +pyo3 = { version = "0.19.0", features = ["extension-module"] } diff --git a/crates/qasm2/src/lib.rs b/crates/qasm2/src/lib.rs index fab26c5b8e93..b71a30ef96a7 100644 --- a/crates/qasm2/src/lib.rs +++ b/crates/qasm2/src/lib.rs @@ -51,7 +51,7 @@ impl CustomInstruction { /// The given `callable` must be a Python function that takes `num_params` floats, and returns a /// float. The `name` is the identifier that refers to it in the OpenQASM 2 program. This cannot /// clash with any defined gates. -#[pyclass(text_signature = "(name, num_params, callable, /)")] +#[pyclass()] #[derive(Clone)] pub struct CustomClassical { pub name: String, @@ -62,6 +62,7 @@ pub struct CustomClassical { #[pymethods] impl CustomClassical { #[new] + #[pyo3(text_signature = "(name, num_params, callable, /)")] fn __new__(name: String, num_params: usize, callable: PyObject) -> Self { Self { name, From 1b92ae5cc926f4a6e96d7dd8d595e9e7f61b72aa Mon Sep 17 00:00:00 2001 From: Guillermo-Mijares-Vilarino <106545082+Guillermo-Mijares-Vilarino@users.noreply.github.com> Date: Thu, 1 Jun 2023 09:41:58 +0200 Subject: [PATCH 11/13] Added explanation section to Qiskit-terra (#8685) * Created explanation page * Added explanation about qubit ordering * Fix typos and minor rephrases * changed explanation.rst to explanation/index.rst * Removed jupyter-sphinx * Changed header symbol from index * Grammar suggestion Co-authored-by: Frank Harkins * Add reference to YouTube video about qubit ordering --------- Co-authored-by: Frank Harkins Co-authored-by: Junye Huang --- docs/explanation/endianness.rst | 47 +++++++++++++++++++++++++++++++++ docs/explanation/index.rst | 12 +++++++++ docs/index.rst | 1 + 3 files changed, 60 insertions(+) create mode 100644 docs/explanation/endianness.rst create mode 100644 docs/explanation/index.rst diff --git a/docs/explanation/endianness.rst b/docs/explanation/endianness.rst new file mode 100644 index 000000000000..ee78ce4d17e0 --- /dev/null +++ b/docs/explanation/endianness.rst @@ -0,0 +1,47 @@ +######################### +Order of qubits in Qiskit +######################### + +While most physics textbooks represent an :math:`n`-qubit system as the tensor product :math:`Q_0\otimes Q_1 \otimes ... \otimes Q_{n-1}`, where :math:`Q_j` is the :math:`j^{\mathrm{th}}` qubit, Qiskit uses the inverse order, that is, :math:`Q_{n-1}\otimes ... \otimes Q_1 \otimes Q_{0}`. As explained in `this video `_ from `Qiskit's YouTube channel `_, this is done to follow the convention in classical computing, in which the :math:`n^{\mathrm{th}}` bit or most significant bit (MSB) is placed on the left (with index 0) while the least significant bit (LSB) is placed on the right (index :math:`n-1`). This ordering convention is called little-endian while the one from the physics textbooks is called big-endian. + +This means that if we have, for example, a 3-qubit system with qubit 0 in state :math:`|1\rangle` and qubits 1 and 2 in state :math:`|0\rangle`, Qiskit would represent this state as :math:`|001\rangle` while most physics textbooks would represent this state as :math:`|100\rangle`. + +The matrix representation of any multi-qubit gate is also affected by this different qubit ordering. For example, if we consider the single-qubit gate + +.. math:: + + U = \begin{pmatrix} u_{00} & u_{01} \\ u_{10} & u_{11} \end{pmatrix} + +And we want a controlled version :math:`C_U` whose control qubit is qubit 0 and whose target is qubit 1, following Qiskit's ordering its matrix representation would be + +.. math:: + + C_U = \begin{pmatrix} 1 & 0 & 0 & 0 \\0 & u_{00} & 0 & u_{01} \\ 0 & 0 & 1 & 0 \\ 0 & u_{10} & 0& u_{11} \end{pmatrix} + +while in a physics textbook it would be written as + +.. math:: + + C_U = \begin{pmatrix} 1 & 0 & 0 & 0 \\0 & 1 & 0 & 0 \\ 0 & 0 & u_{00} & u_{01} \\ 0 & 0 & u_{00} & u_{01} \end{pmatrix} + + +For more details about how this ordering of MSB and LSB affects the matrix representation of any particular gate, check its entry in the circuit :mod:`~qiskit.circuit.library`. + +This different order can also make the circuit corresponding to an algorithm from a textbook a bit more complicated to visualize. Fortunately, Qiskit provides a way to represent a :class:`~.QuantumCircuit` with the most significant qubits on top, just like in the textbooks. This can be done by setting the ``reverse_bits`` argument of the :meth:`~.QuantumCircuit.draw` method to ``True``. + +Let's try this for a 3-qubit Quantum Fourier Transform (:class:`~.QFT`). + +.. plot:: + :include-source: + :context: + + from qiskit.circuit.library import QFT + + qft = QFT(3) + qft.decompose().draw('mpl') + +.. plot:: + :include-source: + :context: close-figs + + qft.decompose().draw('mpl', reverse_bits=True) \ No newline at end of file diff --git a/docs/explanation/index.rst b/docs/explanation/index.rst new file mode 100644 index 000000000000..4a2d55c82ccb --- /dev/null +++ b/docs/explanation/index.rst @@ -0,0 +1,12 @@ +.. _explanation: + +########### +Explanation +########### + + + +.. toctree:: + :maxdepth: 1 + + Order of qubits in Qiskit \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 135d5364eee6..903fc64030c0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Qiskit Terra documentation How-to Guides API References + Explanation Migration Guides Release Notes From 174b661d57f4b888796c0895ac6b1ba79db11d52 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 1 Jun 2023 09:17:20 -0400 Subject: [PATCH 12/13] Fix unitary synthesis for parameterized basis gates in Target (#10090) * Fix unitary synthesis for parameterized basis gates in Target Replace parameterized RXX with RXX(pi / 2) and parameterized RZX with RZX(pi / 4). * Run black * Fix dict ordering problem between linux and windows * Remove unused import * Try using seed in call to transpile to fix CI fail Linux and MacOS give one result and Windows another * Add comments explaining no symbolic gates can be sent to 2q decomposers * Make test for synthesis in with parameterized basis gate less strict Windows on the one hand, and MacOS and Linux on other give different circuits in a simple test of synthesis. Both are correct. This commit makes the test less strict. * Add release note for fix-issue-10082 --- .../passes/synthesis/unitary_synthesis.py | 17 ++++++++++++++--- ...l-with-symbolic-angles-a070b9973a16b8c3.yaml | 6 ++++++ .../python/transpiler/test_unitary_synthesis.py | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index e4555c7fba04..4dbce78d282a 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -31,7 +31,7 @@ TwoQubitWeylDecomposition, ) from qiskit.quantum_info import Operator -from qiskit.circuit import ControlFlowOp, Gate +from qiskit.circuit import ControlFlowOp, Gate, Parameter from qiskit.circuit.library.standard_gates import ( iSwapGate, CXGate, @@ -673,13 +673,24 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree): # available instructions on this qubit pair, and their associated property. available_2q_basis = {} available_2q_props = {} + + # 2q gates sent to 2q decomposers must not have any symbolic parameters. The + # gates must be convertable to a numeric matrix. If a basis gate supports an arbitrary + # angle, we have to choose one angle (or more.) + def _replace_parameterized_gate(op): + if isinstance(op, RXXGate) and isinstance(op.params[0], Parameter): + op = RXXGate(pi / 2) + elif isinstance(op, RZXGate) and isinstance(op.params[0], Parameter): + op = RZXGate(pi / 4) + return op + try: keys = target.operation_names_for_qargs(qubits_tuple) for key in keys: op = target.operation_from_name(key) if not isinstance(op, Gate): continue - available_2q_basis[key] = op + available_2q_basis[key] = _replace_parameterized_gate(op) available_2q_props[key] = target[key][qubits_tuple] except KeyError: pass @@ -690,7 +701,7 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree): op = target.operation_from_name(key) if not isinstance(op, Gate): continue - available_2q_basis[key] = op + available_2q_basis[key] = _replace_parameterized_gate(op) available_2q_props[key] = target[key][reverse_tuple] except KeyError: pass diff --git a/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml b/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml new file mode 100644 index 000000000000..a58af12280b2 --- /dev/null +++ b/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes a bug introduced in Qiskit 0.24.0 where numeric rotation angles were no longer substituted + for symbolic ones before preparing for two-qubit synthesis. This caused an exception to be + raised because the synthesis routines require numberic matrices. diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index 39a2a5880172..4aaecbf2e1a3 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -50,6 +50,7 @@ IGate, CXGate, RZGate, + RXGate, SXGate, XGate, iSwapGate, @@ -914,6 +915,21 @@ def test_iswap_no_cx_synthesis_succeeds(self): result_qc = dag_to_circuit(result_dag) self.assertTrue(np.allclose(Operator(result_qc.to_gate()).to_matrix(), cxmat)) + def test_parameterized_basis_gate_in_target(self): + """Test synthesis with parameterized RXX gate.""" + theta = Parameter("θ") + lam = Parameter("λ") + target = Target(num_qubits=2) + target.add_instruction(RZGate(lam)) + target.add_instruction(RXGate(theta)) + target.add_instruction(RXXGate(theta)) + qc = QuantumCircuit(2) + qc.cp(np.pi / 2, 0, 1) + qc_transpiled = transpile(qc, target=target, optimization_level=3, seed_transpiler=42) + opcount = qc_transpiled.count_ops() + self.assertTrue(set(opcount).issubset({"rz", "rx", "rxx"})) + self.assertTrue(np.allclose(Operator(qc_transpiled), Operator(qc))) + if __name__ == "__main__": unittest.main() From 332bd9fe0bea82c0fdf7329cea3da115d86e3fc2 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Thu, 1 Jun 2023 15:00:25 -0400 Subject: [PATCH 13/13] Assign values directly to fully bound parameters in quantum circuits (#10183) * Assign circuit parameters as int/float to instructions * Do not test that fully bound parameters are still ParameterExpressions * Change int to float in qasm output pi_check casts integers to floats but not integers inside ParameterExpressions. * Workaround symengine ComplexDouble not supporting float * black * Update releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml Co-authored-by: Jake Lishman * Update releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml Co-authored-by: Jake Lishman * Restore assigned parameter value type check to tests * Add test to check type and value of simple circuit parameter assignment * Add consistency check between assigned instruction data and calibrations dict keys * Add regression test * Add upgrade note * Remove support for complex instruction parameter assignment * Restore complex assignment Complex assignment maybe not be supported but allowing it in the parameter assignment step lets validate_parameters get the value and raise an appropriate exception. * black * More specific assertion methods * Use exact floating-point check --------- Co-authored-by: Jake Lishman Co-authored-by: John Lapeyre Co-authored-by: Jake Lishman --- qiskit/circuit/quantumcircuit.py | 18 +++++- ...er-to-concrete-value-7cad75c97183257f.yaml | 29 ++++++++++ .../circuit/test_circuit_load_from_qpy.py | 35 +++++++++++- test/python/circuit/test_parameters.py | 55 +++++++++++++++---- test/python/qasm3/test_export.py | 2 +- 5 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 98e056c2a2a8..37cb1c508575 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -2854,7 +2854,17 @@ def _assign_parameter(self, parameter: Parameter, value: ParameterValueType) -> new_param = assignee.assign(parameter, value) # if fully bound, validate if len(new_param.parameters) == 0: - instr.params[param_index] = instr.validate_parameter(new_param) + if new_param._symbol_expr.is_integer and new_param.is_real(): + val = int(new_param) + elif new_param.is_real(): + # Workaround symengine not supporting float() + val = complex(new_param).real + else: + # complex values may no longer be supported but we + # defer raising an exception to validdate_parameter + # below for now. + val = complex(new_param) + instr.params[param_index] = instr.validate_parameter(val) else: instr.params[param_index] = new_param @@ -2911,7 +2921,11 @@ def _assign_calibration_parameters( if isinstance(p, ParameterExpression) and parameter in p.parameters: new_param = p.assign(parameter, value) if not new_param.parameters: - new_param = float(new_param) + if new_param._symbol_expr.is_integer: + new_param = int(new_param) + else: + # Workaround symengine not supporting float() + new_param = complex(new_param).real new_cal_params.append(new_param) else: new_cal_params.append(p) diff --git a/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml b/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml new file mode 100644 index 000000000000..036d672e480d --- /dev/null +++ b/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml @@ -0,0 +1,29 @@ +--- +fixes: + - | + Changed the binding of numeric values with + :meth:`.QuantumCircuit.assign_parameters` to avoid a mismatch between the + values of circuit instruction parameters and corresponding parameter keys + in the circuit's calibration dictionary. Fixed `#9764 + `_ and `#10166 + `_. See also the + related upgrade note regarding :meth:`.QuantumCircuit.assign_parameters`. +upgrade: + - | + Changed :meth:`.QuantumCircuit.assign_parameters` to bind + assigned integer and float values directly into the parameters of + :class:`~qiskit.circuit.Instruction` instances in the circuit rather than + binding the values wrapped within a + :class:`~qiskit.circuit.ParameterExpression`. This change should have + little user impact as ``float(QuantumCircuit.data[i].operation.params[j])`` + still produces a ``float`` (and is the only way to access the value of a + :class:`~qiskit.circuit.ParameterExpression`). Also, + :meth:`~qiskit.circuit.Instruction` parameters could already be ``float`` + as well as a :class:`~qiskit.circuit.ParameterExpression`, so code dealing + with instruction parameters should already handle both cases. The most + likely chance for user impact is in code that uses ``isinstance`` to check + for :class:`~qiskit.circuit.ParameterExpression` and behaves differently + depending on the result. Additionally, qpy serializes the numeric value in + a bound :class:`~qiskit.circuit.ParameterExpression` at a different + precision than a ``float`` (see also the related bug fix note about + :meth:`.QuantumCircuit.assign_parameters`). diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 3bedc5cb9da0..331c30471032 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -19,7 +19,7 @@ import numpy as np -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, pulse from qiskit.circuit import CASE_DEFAULT from qiskit.circuit.classicalregister import Clbit from qiskit.circuit.quantumregister import Qubit @@ -274,6 +274,39 @@ def test_bound_parameter(self): self.assertEqual(qc, new_circ) self.assertDeprecatedBitProperties(qc, new_circ) + def test_bound_calibration_parameter(self): + """Test a circuit with a bound calibration parameter is correctly serialized. + + In particular, this test ensures that parameters on a circuit + instruction are consistent with the circuit's calibrations dictionary + after serialization. + """ + amp = Parameter("amp") + + with pulse.builder.build() as sched: + pulse.builder.play(pulse.Constant(100, amp), pulse.DriveChannel(0)) + + gate = Gate("custom", 1, [amp]) + + qc = QuantumCircuit(1) + qc.append(gate, (0,)) + qc.add_calibration(gate, (0,), sched) + qc.assign_parameters({amp: 1 / 3}, inplace=True) + + qpy_file = io.BytesIO() + dump(qc, qpy_file) + qpy_file.seek(0) + new_circ = load(qpy_file)[0] + self.assertEqual(qc, new_circ) + instruction = new_circ.data[0] + cal_key = ( + tuple(new_circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + # Make sure that looking for a calibration based on the instruction's + # parameters succeeds + self.assertIn(cal_key, new_circ.calibrations[gate.name]) + def test_parameter_expression(self): """Test a circuit with a parameter expression.""" theta = Parameter("theta") diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index e19fbd205166..d610a3353067 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -21,7 +21,7 @@ from test import combine import numpy -from ddt import data, ddt +from ddt import data, ddt, named_data import qiskit import qiskit.circuit.library as circlib @@ -346,6 +346,23 @@ def test_multiple_named_parameters(self): self.assertEqual(theta.name, "θ") self.assertEqual(qc.parameters, {theta, x}) + @named_data( + ["int", 2, int], + ["float", 2.5, float], + ["float16", numpy.float16(2.5), float], + ["float32", numpy.float32(2.5), float], + ["float64", numpy.float64(2.5), float], + ) + def test_circuit_assignment_to_numeric(self, value, type_): + """Test binding a numeric value to a circuit instruction""" + x = Parameter("x") + qc = QuantumCircuit(1) + qc.append(Instruction("inst", 1, 0, [x]), (0,)) + qc.assign_parameters({x: value}, inplace=True) + bound = qc.data[0].operation.params[0] + self.assertIsInstance(bound, type_) + self.assertEqual(bound, value) + def test_partial_binding(self): """Test that binding a subset of circuit parameters returns a new parameterized circuit.""" theta = Parameter("θ") @@ -401,10 +418,10 @@ def test_expression_partial_binding(self): self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) self.assertEqual(str(pqc.data[0].operation.params[0]), "phi + 2") - fbqc = getattr(pqc, assign_fun)({phi: 1}) + fbqc = getattr(pqc, assign_fun)({phi: 1.0}) self.assertEqual(fbqc.parameters, set()) - self.assertTrue(isinstance(fbqc.data[0].operation.params[0], ParameterExpression)) + self.assertIsInstance(fbqc.data[0].operation.params[0], float) self.assertEqual(float(fbqc.data[0].operation.params[0]), 3) def test_two_parameter_expression_binding(self): @@ -448,7 +465,7 @@ def test_expression_partial_binding_zero(self): fbqc = getattr(pqc, assign_fun)({phi: 1}) self.assertEqual(fbqc.parameters, set()) - self.assertTrue(isinstance(fbqc.data[0].operation.params[0], ParameterExpression)) + self.assertIsInstance(fbqc.data[0].operation.params[0], int) self.assertEqual(float(fbqc.data[0].operation.params[0]), 0) def test_raise_if_assigning_params_not_in_circuit(self): @@ -505,8 +522,15 @@ def test_calibration_assignment(self): circ.add_calibration("rxt", [0], rxt_q0, [theta]) circ = circ.assign_parameters({theta: 3.14}) - self.assertTrue(((0,), (3.14,)) in circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][((0,), (3.14,))] + instruction = circ.data[0] + cal_key = ( + tuple(circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + self.assertEqual(cal_key, ((0,), (3.14,))) + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) def test_calibration_assignment_doesnt_mutate(self): @@ -531,11 +555,11 @@ def test_calibration_assignment_doesnt_mutate(self): self.assertNotEqual(assigned_circ.calibrations, circ.calibrations) def test_calibration_assignment_w_expressions(self): - """That calibrations with multiple parameters and more expressions.""" + """That calibrations with multiple parameters are assigned correctly""" theta = Parameter("theta") sigma = Parameter("sigma") circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [theta, sigma]), [0]) + circ.append(Gate("rxt", 1, [theta / 2, sigma]), [0]) circ.measure(0, 0) rxt_q0 = pulse.Schedule( @@ -548,8 +572,15 @@ def test_calibration_assignment_w_expressions(self): circ.add_calibration("rxt", [0], rxt_q0, [theta / 2, sigma]) circ = circ.assign_parameters({theta: 3.14, sigma: 4}) - self.assertTrue(((0,), (3.14 / 2, 4)) in circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][((0,), (3.14 / 2, 4))] + instruction = circ.data[0] + cal_key = ( + tuple(circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + self.assertEqual(cal_key, ((0,), (3.14 / 2, 4))) + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) self.assertEqual(sched.instructions[0][1].pulse.sigma, 16) @@ -789,7 +820,7 @@ def test_binding_parameterized_circuits_built_in_multiproc(self): for qc in results: circuit.compose(qc, inplace=True) - parameter_values = [{x: 1 for x in parameters}] + parameter_values = [{x: 1.0 for x in parameters}] qobj = assemble( circuit, @@ -802,7 +833,7 @@ def test_binding_parameterized_circuits_built_in_multiproc(self): self.assertTrue( all( len(inst.params) == 1 - and isinstance(inst.params[0], ParameterExpression) + and isinstance(inst.params[0], float) and float(inst.params[0]) == 1 for inst in qobj.experiments[0].instructions ) diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 2f3030ca3ce6..9978426e6d9e 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -469,7 +469,7 @@ def test_reused_custom_parameter(self): " rx(0.5) _gate_q_0;", "}", f"gate {circuit_name_1} _gate_q_0 {{", - " rx(1) _gate_q_0;", + " rx(1.0) _gate_q_0;", "}", "qubit[1] _all_qubits;", "let q = _all_qubits[0:0];",