diff --git a/.mergify.yml b/.mergify.yml index 1b37ce208d58..80346d9b03d0 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -6,4 +6,4 @@ pull_request_rules: actions: backport: branches: - - stable/0.24 + - stable/0.25 diff --git a/Cargo.lock b/Cargo.lock index d144b19bee25..865c336cf0b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "qiskit-qasm2" -version = "0.25.0" +version = "0.45.0" dependencies = [ "hashbrown", "pyo3", @@ -421,7 +421,7 @@ dependencies = [ [[package]] name = "qiskit_accelerate" -version = "0.25.0" +version = "0.45.0" dependencies = [ "ahash 0.8.3", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 823ac3a6b80a..ac9e766348f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = ["crates/*"] # to inherit from this rather than needing to duplicate its content themselves. Until we get there, # keep this in sync with each subpackage `Cargo.toml`'s `package` key. [workspace.package] -version = "0.25.0" +version = "0.45.0" edition = "2021" rust-version = "1.61" # Keep in sync with README.md and rust-toolchain.toml. license = "Apache-2.0" diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index a78c2a03da1e..807b9d145f18 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -2,7 +2,7 @@ name = "qiskit_accelerate" # The following options can be inherited with (e.g.) `version.workspace = true` once we hit Rust # 1.64. Until then, keep in sync with the root `Cargo.toml`. -version = "0.25.0" +version = "0.45.0" edition = "2021" rust-version = "1.61" license = "Apache-2.0" diff --git a/crates/qasm2/Cargo.toml b/crates/qasm2/Cargo.toml index 5f5a84ed80dd..0552d5126aca 100644 --- a/crates/qasm2/Cargo.toml +++ b/crates/qasm2/Cargo.toml @@ -2,7 +2,7 @@ name = "qiskit-qasm2" # The following options can be inherited with (e.g.) `version.workspace = true` once we hit Rust # 1.64. Until then, keep in sync with the root `Cargo.toml`. -version = "0.25.0" +version = "0.45.0" edition = "2021" rust-version = "1.61" license = "Apache-2.0" diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index 9960258074a0..e4c749841124 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -1351,6 +1351,15 @@ impl State { } self.check_trailing_comma(comma.as_ref())?; qubits + } else if self.strict { + return Err(QASM2ParseError::new_err(message_generic( + Some(&Position::new( + self.current_filename(), + barrier_token.line, + barrier_token.col, + )), + "[strict] barrier statements must have at least one argument", + ))); } else if let Some(num_gate_qubits) = num_gate_qubits { (0..num_gate_qubits).map(QubitId::new).collect::>() } else { diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst index 5b3ebf3bdf03..daff18c1e280 100644 --- a/docs/_templates/autosummary/class.rst +++ b/docs/_templates/autosummary/class.rst @@ -1,6 +1,5 @@ {# - The general principle of this is that we manually document attributes here in - the same file, but give all methods their own page. By default, we document + We show all the class's methods and attributes on the same page. By default, we document all methods, including those defined by parent classes. -#} @@ -9,33 +8,24 @@ .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} -{#- - Avoid having autodoc populate the class with the members we're about to - summarize to avoid duplication. -#} :no-members: :show-inheritance: -{# - Methods all get their own separate page, with their names and the first lines - of their docstrings tabulated. The documentation from `__init__` is - automatically included in the standard class documentation, so we don't want - to repeat it. --#} -{% block methods_summary %}{% set wanted_methods = (methods | reject('==', '__init__') | list) %}{% if wanted_methods %} - .. rubric:: Methods - .. autosummary:: - :nosignatures: - :toctree: ../stubs/ -{% for item in wanted_methods %} - ~{{ name }}.{{ item }} -{%- endfor %} -{% endif %}{% endblock %} - -{% block attributes_summary %}{% if attributes %} +{% block attributes_summary %} + {% if attributes %} .. rubric:: Attributes -{# Attributes should all be summarized directly on the same page. -#} -{% for item in attributes %} + {% for item in attributes %} .. autoattribute:: {{ item }} -{%- endfor %} -{% endif %}{% endblock -%} + {%- endfor %} + {% endif %} +{% endblock -%} + +{% block methods_summary %} + {% set wanted_methods = (methods | reject('==', '__init__') | list) %} + {% if wanted_methods %} + .. rubric:: Methods + {% for item in wanted_methods %} + .. automethod:: {{ item }} + {%- endfor %} + {% endif %} +{% endblock %} diff --git a/docs/_templates/autosummary/class_no_inherited_members.rst b/docs/_templates/autosummary/class_no_inherited_members.rst index 6cde3cf5328d..269a89b70718 100644 --- a/docs/_templates/autosummary/class_no_inherited_members.rst +++ b/docs/_templates/autosummary/class_no_inherited_members.rst @@ -1,38 +1,28 @@ -{# - This is very similar to the default class template, except this one is used - when we don't want to generate any inherited methods. --#} +{# This is identical to class.rst, except for the filtering in `set wanted_methods`. -#} {{ objname | escape | underline }} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} -{#- - Avoid having autodoc populate the class with the members we're about to - summarize to avoid duplication. -#} :no-members: :show-inheritance: -{# - Methods all get their own separate page, with their names and the first lines - of their docstrings tabulated. --#} -{% block methods_summary %}{% set wanted_methods = (methods | reject('in', inherited_members) | reject('==', '__init__') | list) %}{% if wanted_methods %} - .. rubric:: Methods Defined Here - .. autosummary:: - :nosignatures: - :toctree: ../stubs/ -{% for item in wanted_methods %} - ~{{ name }}.{{ item }} -{%- endfor %} -{% endif %}{% endblock %} - -{% block attributes_summary %}{% if attributes %} +{% block attributes_summary %} + {% if attributes %} .. rubric:: Attributes -{# Attributes should all be summarized directly on the same page. -#} -{% for item in attributes %} + {% for item in attributes %} .. autoattribute:: {{ item }} -{%- endfor %} -{% endif %}{% endblock -%} + {%- endfor %} + {% endif %} +{% endblock -%} + +{% block methods_summary %} + {% set wanted_methods = (methods | reject('in', inherited_members) | reject('==', '__init__') | list) %} + {% if wanted_methods %} + .. rubric:: Methods + {% for item in wanted_methods %} + .. automethod:: {{ item }} + {%- endfor %} + {% endif %} +{% endblock %} diff --git a/docs/apidocs/passmanager.rst b/docs/apidocs/passmanager.rst new file mode 100644 index 000000000000..f11794df3f3f --- /dev/null +++ b/docs/apidocs/passmanager.rst @@ -0,0 +1,6 @@ +.. _qiskit-passmanager: + +.. automodule:: qiskit.passmanager + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/terra.rst b/docs/apidocs/terra.rst index cde921096f18..33795079383e 100644 --- a/docs/apidocs/terra.rst +++ b/docs/apidocs/terra.rst @@ -18,6 +18,7 @@ Qiskit Terra API Reference assembler dagcircuit extensions + passmanager providers_basicaer providers providers_fake_provider diff --git a/docs/conf.py b/docs/conf.py index ab8f3fb0ae16..9dfbb3f73fcf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,9 +23,9 @@ author = "Qiskit Development Team" # The short X.Y version -version = "0.25" +version = "0.45" # The full version, including alpha/beta/rc tags -release = "0.25.0" +release = "0.45.0" extensions = [ "sphinx.ext.napoleon", @@ -57,8 +57,13 @@ pygments_style = "colorful" -# Whether module names are included in crossrefs of functions, classes, etc. -add_module_names = False +# This adds the module name to e.g. function API docs. We use the default of True because our +# module pages sometimes have functions from submodules on the page, and we want to make clear +# that you must include the submodule to import it. We should strongly consider reorganizing our +# code to avoid this, i.e. re-exporting the submodule members from the top-level module. Once fixed +# and verified by only having a single `.. currentmodule::` in the file, we can turn this back to +# False. +add_module_names = True # A list of prefixes that are ignored for sorting the Python module index # (e.g., if this is set to ['foo.'], then foo.bar is shown under B, not F). diff --git a/qiskit/VERSION.txt b/qiskit/VERSION.txt index d21d277be513..bcce5d06b8a0 100644 --- a/qiskit/VERSION.txt +++ b/qiskit/VERSION.txt @@ -1 +1 @@ -0.25.0 +0.45.0 diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 272d7be20bc5..18767f1fdd01 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -20,8 +20,11 @@ The :mod:`qiskit.algorithms` module has been migrated to an independent package: https://github.com/qiskit-community/qiskit-algorithms. The current import path is deprecated and will be removed no earlier - than 3 months after the release date. You can run ``pip install qiskit_algorithms`` - and import ``from qiskit_algorithms`` instead. + than 3 months after the release date. If your code uses primitives, you can run + ``pip install qiskit_algorithms`` and import ``from qiskit_algorithms`` instead. + If you use opflow/quantum instance-based algorithms, please update your code to + use primitives following: https://qisk.it/algo_migration before migrating to + the new package. It contains a collection of quantum algorithms, for use with quantum computers, to carry out research and investigate how to solve problems in different domains on @@ -277,23 +280,7 @@ Exceptions ---------- -.. autosummary:: - :toctree: ../stubs/ - - AlgorithmError - - -Utility methods ---------------- - -Utility methods used by algorithms. - -.. autosummary:: - :toctree: ../stubs/ - - eval_observables - estimate_observables - +.. autoexception:: AlgorithmError Utility classes --------------- @@ -305,6 +292,14 @@ AlgorithmJob +Utility functions +----------------- + +Utility functions used by algorithms. + +.. autofunction:: eval_observables +.. autofunction:: estimate_observables + """ import warnings diff --git a/qiskit/algorithms/optimizers/nft.py b/qiskit/algorithms/optimizers/nft.py index 07827007e79e..2a7503137daf 100644 --- a/qiskit/algorithms/optimizers/nft.py +++ b/qiskit/algorithms/optimizers/nft.py @@ -106,9 +106,10 @@ def nakanishi_fujii_todo( `OptimizeResult` for a description of other attributes. Notes: In this optimization method, the optimization function have to satisfy - three conditions written in [1]. + three conditions written in [2]_. + References: - .. [1] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. + .. [2] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. Sequential minimal optimization for quantum-classical hybrid algorithms. arXiv preprint arXiv:1903.12166. """ diff --git a/qiskit/assembler/__init__.py b/qiskit/assembler/__init__.py index ee4dfa7e2196..a356501a263c 100644 --- a/qiskit/assembler/__init__.py +++ b/qiskit/assembler/__init__.py @@ -20,26 +20,17 @@ Circuit Assembler ================= -.. autosummary:: - :toctree: ../stubs/ - - assemble_circuits +.. autofunction:: assemble_circuits Schedule Assembler ================== -.. autosummary:: - :toctree: ../stubs/ - - assemble_schedules +.. autofunction:: assemble_schedules Disassembler ============ -.. autosummary:: - :toctree: ../stubs/ - - disassemble +.. autofunction:: disassemble RunConfig ========= diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index d319cabc418c..079ead121c85 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -296,9 +296,41 @@ The :class:`.SwitchCaseOp` also understands a special value: -.. py:data: CASE_DEFAULT - Used as a possible "label" in a :class:`.SwitchCaseOp` to represent the default case. This will - always match, if it is tried. +.. py:data:: CASE_DEFAULT + + A special object that represents the "default" case of a switch statement. If you use this as a + case target, it must be the last case, and will match anything that wasn't already matched. For + example:: + + from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister + from qiskit.circuit import SwitchCaseOp, CASE_DEFAULT + + body0 = QuantumCircuit(2, 2) + body0.x(0) + body1 = QuantumCircuit(2, 2) + body1.z(0) + body2 = QuantumCircuit(2, 2) + body2.cx(0, 1) + + qr, cr = QuantumRegister(2), ClassicalRegister(2) + qc = QuantumCircuit(qr, cr) + qc.switch(cr, [(0, body0), (1, body1), (CASE_DEFAULT, body2)], qr, cr) + + When using the builder interface of :meth:`.QuantumCircuit.switch`, this can also be accessed as + the ``DEFAULT`` attribute of the bound case-builder object, such as:: + + from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister + + qr, cr = QuantumRegister(2), ClassicalRegister(2) + qc = QuantumCircuit(qr, cr) + with qc.switch(cr) as case: + with case(0): + qc.x(0) + with case(1): + qc.z(0) + with case(case.DEFAULT): + qc.cx(0, 1) + Parametric Quantum Circuits --------------------------- @@ -313,10 +345,9 @@ Random Circuits --------------- -.. autosummary:: - :toctree: ../stubs/ - - random.random_circuit +.. currentmodule:: qiskit.circuit.random +.. autofunction:: random_circuit +.. currentmodule:: qiskit.circuit """ from .quantumcircuit import QuantumCircuit from .classicalregister import ClassicalRegister, Clbit diff --git a/qiskit/circuit/commutation_checker.py b/qiskit/circuit/commutation_checker.py index 5edceab1080f..ad9d4830997a 100644 --- a/qiskit/circuit/commutation_checker.py +++ b/qiskit/circuit/commutation_checker.py @@ -65,10 +65,20 @@ def _hashable_parameters(self, params): return ("fallback", str(params)) def commute( - self, op1: Operation, qargs1: List, cargs1: List, op2: Operation, qargs2: List, cargs2: List - ): + self, + op1: Operation, + qargs1: List, + cargs1: List, + op2: Operation, + qargs2: List, + cargs2: List, + max_num_qubits: int = 3, + ) -> bool: """ - Checks if two Operations commute. + Checks if two Operations commute. The return value of `True` means that the operations + truly commute, and the return value of `False` means that either the operations do not + commute or that the commutation check was skipped (for example, when the operations + have conditions or have too many qubits). Args: op1: first operation. @@ -77,10 +87,14 @@ def commute( op2: second operation. qargs2: second operation's qubits. cargs2: second operation's clbits. + max_num_qubits: the maximum number of qubits to consider, the check may be skipped if + the number of qubits for either operation exceeds this amount. Returns: bool: whether two operations commute. """ + # pylint: disable=too-many-return-statements + # We don't support commutation of conditional gates for now due to bugs in # CommutativeCancellation. See gh-8553. if ( @@ -105,6 +119,10 @@ def commute( if not (intersection_q or intersection_c): return True + # Skip the check if the number of qubits for either operation is too large + if len(qargs1) > max_num_qubits or len(qargs2) > max_num_qubits: + return False + # These lines are adapted from commutation_analysis, which is more restrictive than the # check from dag_dependency when considering nodes with "_directive". It would be nice to # think which optimizations from dag_dependency can indeed be used. diff --git a/qiskit/circuit/controlflow/switch_case.py b/qiskit/circuit/controlflow/switch_case.py index 5fc4aac27bb9..0f215a9bcbb8 100644 --- a/qiskit/circuit/controlflow/switch_case.py +++ b/qiskit/circuit/controlflow/switch_case.py @@ -41,8 +41,7 @@ def __repr__(self): """A special object that represents the "default" case of a switch statement. If you use this as a case target, it must be the last case, and will match anything that wasn't already matched. When using the builder interface of :meth:`.QuantumCircuit.switch`, this can also be accessed as the -``DEFAULT`` attribute of the bound case-builder object. -""" +``DEFAULT`` attribute of the bound case-builder object.""" class SwitchCaseOp(ControlFlowOp): @@ -51,12 +50,12 @@ class SwitchCaseOp(ControlFlowOp): be used to represent a default condition. This is the low-level interface for creating a switch-case statement; in general, the circuit - method :meth:`.QuantumCircuit.switch_case` should be used as a context manager to access the + method :meth:`.QuantumCircuit.switch` should be used as a context manager to access the builder interface. At the low level, you must ensure that all the circuit blocks contain equal numbers of qubits and clbits, and that the order the virtual bits of the containing circuit should be bound is the same for all blocks. This will likely mean that each circuit block is wider than its natural width, as each block must span the union of all the spaces covered by - _any_ of the blocks. + *any* of the blocks. Args: target: the runtime value to switch on. diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 4d530fc79ca5..2e3637924dab 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -394,98 +394,91 @@ Techniques for the synthesis of reversible Toffoli networks, 2007 http://dx.doi.org/10.1145/1278349.1278355 -.. autosummary:: - :toctree: ../stubs/ - - templates.nct.template_nct_2a_1 - templates.nct.template_nct_2a_2 - templates.nct.template_nct_2a_3 - templates.nct.template_nct_4a_1 - templates.nct.template_nct_4a_2 - templates.nct.template_nct_4a_3 - templates.nct.template_nct_4b_1 - templates.nct.template_nct_4b_2 - templates.nct.template_nct_5a_1 - templates.nct.template_nct_5a_2 - templates.nct.template_nct_5a_3 - templates.nct.template_nct_5a_4 - templates.nct.template_nct_6a_1 - templates.nct.template_nct_6a_2 - templates.nct.template_nct_6a_3 - templates.nct.template_nct_6a_4 - templates.nct.template_nct_6b_1 - templates.nct.template_nct_6b_2 - templates.nct.template_nct_6c_1 - templates.nct.template_nct_7a_1 - templates.nct.template_nct_7b_1 - templates.nct.template_nct_7c_1 - templates.nct.template_nct_7d_1 - templates.nct.template_nct_7e_1 - templates.nct.template_nct_9a_1 - templates.nct.template_nct_9c_1 - templates.nct.template_nct_9c_2 - templates.nct.template_nct_9c_3 - templates.nct.template_nct_9c_4 - templates.nct.template_nct_9c_5 - templates.nct.template_nct_9c_6 - templates.nct.template_nct_9c_7 - templates.nct.template_nct_9c_8 - templates.nct.template_nct_9c_9 - templates.nct.template_nct_9c_10 - templates.nct.template_nct_9c_11 - templates.nct.template_nct_9c_12 - templates.nct.template_nct_9d_1 - templates.nct.template_nct_9d_2 - templates.nct.template_nct_9d_3 - templates.nct.template_nct_9d_4 - templates.nct.template_nct_9d_5 - templates.nct.template_nct_9d_6 - templates.nct.template_nct_9d_7 - templates.nct.template_nct_9d_8 - templates.nct.template_nct_9d_9 - templates.nct.template_nct_9d_10 +.. currentmodule:: qiskit.circuit.library.templates.nct +.. autofunction:: template_nct_2a_1 +.. autofunction:: template_nct_2a_2 +.. autofunction:: template_nct_2a_3 +.. autofunction:: template_nct_4a_1 +.. autofunction:: template_nct_4a_2 +.. autofunction:: template_nct_4a_3 +.. autofunction:: template_nct_4b_1 +.. autofunction:: template_nct_4b_2 +.. autofunction:: template_nct_5a_1 +.. autofunction:: template_nct_5a_2 +.. autofunction:: template_nct_5a_3 +.. autofunction:: template_nct_5a_4 +.. autofunction:: template_nct_6a_1 +.. autofunction:: template_nct_6a_2 +.. autofunction:: template_nct_6a_3 +.. autofunction:: template_nct_6a_4 +.. autofunction:: template_nct_6b_1 +.. autofunction:: template_nct_6b_2 +.. autofunction:: template_nct_6c_1 +.. autofunction:: template_nct_7a_1 +.. autofunction:: template_nct_7b_1 +.. autofunction:: template_nct_7c_1 +.. autofunction:: template_nct_7d_1 +.. autofunction:: template_nct_7e_1 +.. autofunction:: template_nct_9a_1 +.. autofunction:: template_nct_9c_1 +.. autofunction:: template_nct_9c_2 +.. autofunction:: template_nct_9c_3 +.. autofunction:: template_nct_9c_4 +.. autofunction:: template_nct_9c_5 +.. autofunction:: template_nct_9c_6 +.. autofunction:: template_nct_9c_7 +.. autofunction:: template_nct_9c_8 +.. autofunction:: template_nct_9c_9 +.. autofunction:: template_nct_9c_10 +.. autofunction:: template_nct_9c_11 +.. autofunction:: template_nct_9c_12 +.. autofunction:: template_nct_9d_1 +.. autofunction:: template_nct_9d_2 +.. autofunction:: template_nct_9d_3 +.. autofunction:: template_nct_9d_4 +.. autofunction:: template_nct_9d_5 +.. autofunction:: template_nct_9d_6 +.. autofunction:: template_nct_9d_7 +.. autofunction:: template_nct_9d_8 +.. autofunction:: template_nct_9d_9 +.. autofunction:: template_nct_9d_10 +.. currentmodule:: qiskit.circuit.library Clifford template circuits -------------------------- Template circuits over Clifford gates. -.. autosummary:: - :toctree: ../stubs/ - - clifford_2_1 - clifford_2_2 - clifford_2_3 - clifford_2_4 - clifford_3_1 - clifford_4_1 - clifford_4_2 - clifford_4_3 - clifford_4_4 - clifford_5_1 - clifford_6_1 - clifford_6_2 - clifford_6_3 - clifford_6_4 - clifford_6_5 - clifford_8_1 - clifford_8_2 - clifford_8_3 +.. autofunction:: clifford_2_1 +.. autofunction:: clifford_2_2 +.. autofunction:: clifford_2_3 +.. autofunction:: clifford_2_4 +.. autofunction:: clifford_3_1 +.. autofunction:: clifford_4_1 +.. autofunction:: clifford_4_2 +.. autofunction:: clifford_4_3 +.. autofunction:: clifford_4_4 +.. autofunction:: clifford_5_1 +.. autofunction:: clifford_6_1 +.. autofunction:: clifford_6_2 +.. autofunction:: clifford_6_3 +.. autofunction:: clifford_6_4 +.. autofunction:: clifford_6_5 +.. autofunction:: clifford_8_1 +.. autofunction:: clifford_8_2 +.. autofunction:: clifford_8_3 RZXGate template circuits ------------------------- Template circuits with :class:`~qiskit.circuit.library.RZXGate`. -.. autosummary:: - :toctree: ../stubs/ - - rzx_yz - rzx_xz - rzx_cy - rzx_zz1 - rzx_zz2 - rzx_zz3 +.. autofunction:: rzx_yz +.. autofunction:: rzx_xz +.. autofunction:: rzx_cy +.. autofunction:: rzx_zz1 +.. autofunction:: rzx_zz2 +.. autofunction:: rzx_zz3 """ diff --git a/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index 5bde9c505698..5dd1914543b8 100644 --- a/qiskit/circuit/library/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/evolved_operator_ansatz.py @@ -14,7 +14,6 @@ from __future__ import annotations from collections.abc import Sequence -from typing import Optional import numpy as np @@ -41,7 +40,7 @@ def __init__( name: str = "EvolvedOps", parameter_prefix: str | Sequence[str] = "t", initial_state: QuantumCircuit | None = None, - flatten: Optional[bool] = None, + flatten: bool | None = None, ): """ Args: diff --git a/qiskit/circuit/library/n_local/efficient_su2.py b/qiskit/circuit/library/n_local/efficient_su2.py index be7b346af862..50ac4626048f 100644 --- a/qiskit/circuit/library/n_local/efficient_su2.py +++ b/qiskit/circuit/library/n_local/efficient_su2.py @@ -12,13 +12,19 @@ """The EfficientSU2 2-local circuit.""" -from typing import Union, Optional, List, Tuple, Callable, Any +from __future__ import annotations +import typing +from collections.abc import Callable + from numpy import pi -from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.standard_gates import RYGate, RZGate, CXGate from .two_local import TwoLocal +if typing.TYPE_CHECKING: + import qiskit # pylint: disable=cyclic-import + class EfficientSU2(TwoLocal): r"""The hardware efficient SU(2) 2-local circuit. @@ -76,28 +82,24 @@ class EfficientSU2(TwoLocal): def __init__( self, - num_qubits: Optional[int] = None, - su2_gates: Optional[ - Union[ - str, - type, - Instruction, - QuantumCircuit, - List[Union[str, type, Instruction, QuantumCircuit]], - ] - ] = None, - entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "reverse_linear", + num_qubits: int | None = None, + su2_gates: str + | type + | qiskit.circuit.Instruction + | QuantumCircuit + | list[str | type | qiskit.circuit.Instruction | QuantumCircuit] + | None = None, + entanglement: str | list[list[int]] | Callable[[int], list[int]] = "reverse_linear", reps: int = 3, skip_unentangled_qubits: bool = False, skip_final_rotation_layer: bool = False, parameter_prefix: str = "θ", insert_barriers: bool = False, - initial_state: Optional[Any] = None, + initial_state: QuantumCircuit | None = None, name: str = "EfficientSU2", - flatten: Optional[bool] = None, + flatten: bool | None = None, ) -> None: - """Create a new EfficientSU2 2-local circuit. - + """ Args: num_qubits: The number of qubits of the EfficientSU2 circuit. reps: Specifies how often the structure of a rotation layer followed by an entanglement @@ -151,7 +153,7 @@ def __init__( ) @property - def parameter_bounds(self) -> List[Tuple[float, float]]: + def parameter_bounds(self) -> list[tuple[float, float]]: """Return the parameter bounds. Returns: diff --git a/qiskit/circuit/library/n_local/excitation_preserving.py b/qiskit/circuit/library/n_local/excitation_preserving.py index 3fc2b28bfb2a..7b01f825ecdb 100644 --- a/qiskit/circuit/library/n_local/excitation_preserving.py +++ b/qiskit/circuit/library/n_local/excitation_preserving.py @@ -12,7 +12,8 @@ """The ExcitationPreserving 2-local circuit.""" -from typing import Union, Optional, List, Tuple, Callable, Any +from __future__ import annotations +from collections.abc import Callable from numpy import pi from qiskit.circuit import QuantumCircuit, Parameter @@ -90,20 +91,19 @@ class ExcitationPreserving(TwoLocal): def __init__( self, - num_qubits: Optional[int] = None, + num_qubits: int | None = None, mode: str = "iswap", - entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "full", + entanglement: str | list[list[int]] | Callable[[int], list[int]] = "full", reps: int = 3, skip_unentangled_qubits: bool = False, skip_final_rotation_layer: bool = False, parameter_prefix: str = "θ", insert_barriers: bool = False, - initial_state: Optional[Any] = None, + initial_state: QuantumCircuit | None = None, name: str = "ExcitationPreserving", - flatten: Optional[bool] = None, + flatten: bool | None = None, ) -> None: - """Create a new ExcitationPreserving 2-local circuit. - + """ Args: num_qubits: The number of qubits of the ExcitationPreserving circuit. mode: Choose the entangler mode, can be `'iswap'` or `'fsim'`. @@ -167,7 +167,7 @@ def __init__( ) @property - def parameter_bounds(self) -> List[Tuple[float, float]]: + def parameter_bounds(self) -> list[tuple[float, float]]: """Return the parameter bounds. Returns: diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index d3d601a406f7..aca3ea65bb3b 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -13,9 +13,9 @@ """The n-local circuit class.""" from __future__ import annotations - import typing -from typing import Union, Optional, Any, Sequence, Callable, Mapping +from collections.abc import Callable, Mapping, Sequence + from itertools import combinations import numpy @@ -90,10 +90,9 @@ def __init__( skip_unentangled_qubits: bool = False, initial_state: QuantumCircuit | None = None, name: str | None = "nlocal", - flatten: Optional[bool] = None, + flatten: bool | None = None, ) -> None: - """Create a new n-local circuit. - + """ Args: num_qubits: The number of qubits of the circuit. rotation_blocks: The blocks used in the rotation layers. If multiple are passed, @@ -123,9 +122,6 @@ def __init__( to set this flag to ``True`` to avoid a large performance overhead for parameter binding. - Examples: - TODO - Raises: ValueError: If ``reps`` parameter is less than or equal to 0. TypeError: If ``reps`` parameter is not an int value. @@ -207,7 +203,7 @@ def flatten(self, flatten: bool) -> None: self._invalidate() self._flatten = flatten - def _convert_to_block(self, layer: Any) -> QuantumCircuit: + def _convert_to_block(self, layer: typing.Any) -> QuantumCircuit: """Try to convert ``layer`` to a QuantumCircuit. Args: @@ -289,17 +285,9 @@ def entanglement_blocks( @property def entanglement( self, - ) -> Union[ - str, - list[str], - list[list[str]], - list[int], - list[list[int]], - list[list[list[int]]], - list[list[list[list[int]]]], - Callable[[int], str], - Callable[[int], list[list[int]]], - ]: + ) -> str | list[str] | list[list[str]] | list[int] | list[list[int]] | list[ + list[list[int]] + ] | list[list[list[list[int]]]] | Callable[[int], str] | Callable[[int], list[list[int]]]: """Get the entanglement strategy. Returns: @@ -311,19 +299,16 @@ def entanglement( @entanglement.setter def entanglement( self, - entanglement: Optional[ - Union[ - str, - list[str], - list[list[str]], - list[int], - list[list[int]], - list[list[list[int]]], - list[list[list[list[int]]]], - Callable[[int], str], - Callable[[int], list[list[int]]], - ] - ], + entanglement: str + | list[str] + | list[list[str]] + | list[int] + | list[list[int]] + | list[list[list[int]]] + | list[list[list[list[int]]]] + | Callable[[int], str] + | Callable[[int], list[list[int]]] + | None, ) -> None: """Set the entanglement strategy. @@ -730,7 +715,7 @@ def parameter_bounds(self, bounds: list[tuple[float, float]]) -> None: def add_layer( self, - other: Union["NLocal", qiskit.circuit.Instruction, QuantumCircuit], + other: QuantumCircuit | qiskit.circuit.Instruction, entanglement: list[int] | str | list[list[int]] | None = None, front: bool = False, ) -> "NLocal": diff --git a/qiskit/circuit/library/n_local/qaoa_ansatz.py b/qiskit/circuit/library/n_local/qaoa_ansatz.py index 8161a1d9c8b4..b05507ea5068 100644 --- a/qiskit/circuit/library/n_local/qaoa_ansatz.py +++ b/qiskit/circuit/library/n_local/qaoa_ansatz.py @@ -14,7 +14,6 @@ # pylint: disable=cyclic-import from __future__ import annotations -from typing import Optional import numpy as np @@ -41,7 +40,7 @@ def __init__( initial_state: QuantumCircuit | None = None, mixer_operator=None, name: str = "QAOA", - flatten: Optional[bool] = None, + flatten: bool | None = None, ): r""" Args: diff --git a/qiskit/circuit/library/n_local/real_amplitudes.py b/qiskit/circuit/library/n_local/real_amplitudes.py index 5bc1a87b0c43..8fd8306f222e 100644 --- a/qiskit/circuit/library/n_local/real_amplitudes.py +++ b/qiskit/circuit/library/n_local/real_amplitudes.py @@ -12,9 +12,12 @@ """The real-amplitudes 2-local circuit.""" -from typing import Union, Optional, List, Tuple, Callable, Any +from __future__ import annotations +from collections.abc import Callable + import numpy as np +from qiskit.circuit import QuantumCircuit from qiskit.circuit.library.standard_gates import RYGate, CXGate from .two_local import TwoLocal @@ -115,19 +118,18 @@ class RealAmplitudes(TwoLocal): def __init__( self, - num_qubits: Optional[int] = None, - entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "reverse_linear", + num_qubits: int | None = None, + entanglement: str | list[list[int]] | Callable[[int], list[int]] = "reverse_linear", reps: int = 3, skip_unentangled_qubits: bool = False, skip_final_rotation_layer: bool = False, parameter_prefix: str = "θ", insert_barriers: bool = False, - initial_state: Optional[Any] = None, + initial_state: QuantumCircuit | None = None, name: str = "RealAmplitudes", - flatten: Optional[bool] = None, + flatten: bool | None = None, ) -> None: - """Create a new RealAmplitudes 2-local circuit. - + """ Args: num_qubits: The number of qubits of the RealAmplitudes circuit. reps: Specifies how often the structure of a rotation layer followed by an entanglement @@ -178,7 +180,7 @@ def __init__( ) @property - def parameter_bounds(self) -> List[Tuple[float, float]]: + def parameter_bounds(self) -> list[tuple[float, float]]: """Return the parameter bounds. Returns: diff --git a/qiskit/circuit/library/n_local/two_local.py b/qiskit/circuit/library/n_local/two_local.py index 27a0c67ef6d7..e4e323d65052 100644 --- a/qiskit/circuit/library/n_local/two_local.py +++ b/qiskit/circuit/library/n_local/two_local.py @@ -13,7 +13,8 @@ """The two-local gate circuit.""" from __future__ import annotations -from typing import Union, Optional, List, Callable, Any, Sequence +import typing +from collections.abc import Callable, Sequence from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit import Gate, Instruction, Parameter @@ -46,6 +47,9 @@ CHGate, ) +if typing.TYPE_CHECKING: + import qiskit # pylint: disable=cyclic-import + class TwoLocal(NLocal): r"""The two-local circuit. @@ -158,25 +162,30 @@ class TwoLocal(NLocal): def __init__( self, - num_qubits: Optional[int] = None, - rotation_blocks: Optional[ - Union[str, List[str], type, List[type], QuantumCircuit, List[QuantumCircuit]] - ] = None, - entanglement_blocks: Optional[ - Union[str, List[str], type, List[type], QuantumCircuit, List[QuantumCircuit]] - ] = None, - entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "full", + num_qubits: int | None = None, + rotation_blocks: str + | type + | qiskit.circuit.Instruction + | QuantumCircuit + | list[str | type | qiskit.circuit.Instruction | QuantumCircuit] + | None = None, + entanglement_blocks: str + | type + | qiskit.circuit.Instruction + | QuantumCircuit + | list[str | type | qiskit.circuit.Instruction | QuantumCircuit] + | None = None, + entanglement: str | list[list[int]] | Callable[[int], list[int]] = "full", reps: int = 3, skip_unentangled_qubits: bool = False, skip_final_rotation_layer: bool = False, parameter_prefix: str = "θ", insert_barriers: bool = False, - initial_state: Optional[Any] = None, + initial_state: QuantumCircuit | None = None, name: str = "TwoLocal", - flatten: Optional[bool] = None, + flatten: bool | None = None, ) -> None: - """Construct a new two-local circuit. - + """ Args: num_qubits: The number of qubits of the two-local circuit. rotation_blocks: The gates used in the rotation layer. Can be specified via the name of @@ -233,7 +242,7 @@ def __init__( flatten=flatten, ) - def _convert_to_block(self, layer: Union[str, type, Gate, QuantumCircuit]) -> QuantumCircuit: + def _convert_to_block(self, layer: str | type | Gate | QuantumCircuit) -> QuantumCircuit: """For a layer provided as str (e.g. ``'ry'``) or type (e.g. :class:`.RYGate`) this function returns the according layer type along with the number of parameters (e.g. ``(RYGate, 1)``). diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 5dfea905bdc3..841d09b1b6bc 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1716,6 +1716,10 @@ def qasm( elif operation.name == "reset": instruction_qasm = f"reset {bit_labels[instruction.qubits[0]]};" elif operation.name == "barrier": + if not instruction.qubits: + # Barriers with no operands are invalid in (strict) OQ2, and the statement + # would have no meaning anyway. + continue qargs = ",".join(bit_labels[q] for q in instruction.qubits) instruction_qasm = "barrier;" if not qargs else f"barrier {qargs};" else: @@ -1790,6 +1794,13 @@ def draw( **latex_source**: raw uncompiled latex output. + .. warning:: + + Support for :class:`~.expr.Expr` nodes in conditions and :attr:`.SwitchCaseOp.target` + fields is preliminary and incomplete. The ``text`` and ``mpl`` drawers will make a + best-effort attempt to show data dependencies, but the LaTeX-based drawers will skip + these completely. + Args: output (str): select the output method to use for drawing the circuit. Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``. @@ -5025,7 +5036,9 @@ def _qasm2_define_custom_operation(operation, existing_gate_names, gates_to_defi Returns a potentially new :class:`.Instruction`, which should be used for the :meth:`~.Instruction.qasm` call (it may have been renamed).""" - from qiskit.circuit import library as lib # pylint: disable=cyclic-import + # pylint: disable=cyclic-import + from qiskit.circuit import library as lib + from qiskit.qasm2 import QASM2ExportError if operation.name in existing_gate_names: return operation @@ -5086,6 +5099,17 @@ def _qasm2_define_custom_operation(operation, existing_gate_names, gates_to_defi ) else: parameters_qasm = "" + + if operation.num_qubits == 0: + raise QASM2ExportError( + f"OpenQASM 2 cannot represent '{operation.name}, which acts on zero qubits." + ) + if operation.num_clbits != 0: + raise QASM2ExportError( + f"OpenQASM 2 cannot represent '{operation.name}', which acts on {operation.num_clbits}" + " classical bits." + ) + qubits_qasm = ",".join(f"q{i}" for i in range(parameterized_operation.num_qubits)) parameterized_definition = getattr(parameterized_operation, "definition", None) if parameterized_definition is None: diff --git a/qiskit/compiler/__init__.py b/qiskit/compiler/__init__.py index fd6429260db4..4974fe68a4cd 100644 --- a/qiskit/compiler/__init__.py +++ b/qiskit/compiler/__init__.py @@ -20,13 +20,10 @@ Circuit and Pulse Compilation Functions ======================================= -.. autosummary:: - :toctree: ../stubs/ - - assemble - schedule - transpile - sequence +.. autofunction:: assemble +.. autofunction:: schedule +.. autofunction:: transpile +.. autofunction:: sequence """ diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index 2e7c0a9092f0..afac4cd2231f 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -17,18 +17,15 @@ .. currentmodule:: qiskit.converters -.. autosummary:: - :toctree: ../stubs/ - - circuit_to_dag - dag_to_circuit - circuit_to_instruction - circuit_to_gate - ast_to_dag - dagdependency_to_circuit - circuit_to_dagdependency - dag_to_dagdependency - dagdependency_to_dag +.. autofunction:: circuit_to_dag +.. autofunction:: dag_to_circuit +.. autofunction:: circuit_to_instruction +.. autofunction:: circuit_to_gate +.. autofunction:: ast_to_dag +.. autofunction:: dagdependency_to_circuit +.. autofunction:: circuit_to_dagdependency +.. autofunction:: dag_to_dagdependency +.. autofunction:: dagdependency_to_dag """ from .circuit_to_dag import circuit_to_dag diff --git a/qiskit/opflow/__init__.py b/qiskit/opflow/__init__.py index 9551dd3f318b..babed3da26b5 100644 --- a/qiskit/opflow/__init__.py +++ b/qiskit/opflow/__init__.py @@ -149,21 +149,15 @@ Utility functions ================= -.. autosummary:: - :toctree: ../stubs/ - - commutator - anti_commutator - double_commutator +.. autofunction:: commutator +.. autofunction:: anti_commutator +.. autofunction:: double_commutator Exceptions ========== -.. autosummary:: - :toctree: ../stubs/ - - OpflowError +.. autoexception:: OpflowError """ import warnings diff --git a/qiskit/passmanager/__init__.py b/qiskit/passmanager/__init__.py new file mode 100644 index 000000000000..24f94292bea6 --- /dev/null +++ b/qiskit/passmanager/__init__.py @@ -0,0 +1,106 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +======================================= +Passmanager (:mod:`qiskit.passmanager`) +======================================= + +.. currentmodule:: qiskit.passmanager + +Overview +======== + +Qiskit pass manager is somewhat inspired by the `LLVM compiler `_, +but it is designed to take Qiskit object as an input instead of plain source code. + +The pass manager converts the input object into an intermediate representation (IR), +and it can be optimized and get lowered with a variety of transformations over multiple passes. +This representation must be preserved throughout the transformation. +The passes may consume the hardware constraints that Qiskit backend may provide. +Finally, the IR is converted back to some Qiskit object. +Note that the input type and output type don't need to match. + +Execution of passes is managed by the :class:`.FlowController`, +which is initialized with a set of transform and analysis passes and provides an iterator of them. +This iterator can be conditioned on the :class:`.PropertySet`, which is a namespace +storing the intermediate data necessary for the transformation. +A pass has read and write access to the property set namespace, +and the stored data is shared among scheduled passes. + +The :class:`BasePassManager` provides a user interface to build and execute transform passes. +It internally spawns a :class:`BasePassRunner` instance to apply transforms to +the input object. In this sense, the pass manager itself is unaware of the +underlying IR, but it is indirectly tied to a particular IR through the pass runner class. + +The responsibilities of the pass runner are the following: + +* Defining the input type / pass manager IR / output type. +* Converting an input object to a particular pass manager IR. +* Preparing own property set namespace. +* Running scheduled flow controllers to apply a series of transformations to the IR. +* Converting the IR back to an output object. + +A single pass runner always takes a single input object and returns a single output object. +Parallelism for multiple input objects is supported by the :class:`BasePassManager` by +broadcasting the pass runner via the :mod:`qiskit.tools.parallel_map` function. + +The base class :class:`BasePassRunner` doesn't define any associated type by itself, +and a developer needs to implement a subclass for a particular object type to optimize. +This `veil of ignorance` allows us to choose the most efficient data representation +for a particular optimization task, while we can reuse the pass flow control machinery +for different input and output types. + + +Base classes +------------ + +.. autosummary:: + :toctree: ../stubs/ + + BasePassRunner + BasePassManager + +Flow controllers +---------------- + +.. autosummary:: + :toctree: ../stubs/ + + FlowController + ConditionalController + DoWhileController + +PropertySet +----------- + +.. autosummary:: + :toctree: ../stubs/ + + PropertySet + +Exceptions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + PassManagerError + +""" + +from .passrunner import BasePassRunner +from .passmanager import BasePassManager +from .flow_controllers import FlowController, ConditionalController, DoWhileController +from .base_pass import GenericPass +from .propertyset import PropertySet +from .exceptions import PassManagerError diff --git a/qiskit/passmanager/base_pass.py b/qiskit/passmanager/base_pass.py new file mode 100644 index 000000000000..f6cc27fc9b30 --- /dev/null +++ b/qiskit/passmanager/base_pass.py @@ -0,0 +1,80 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Metaclass of Qiskit pass manager pass.""" + +from abc import abstractmethod +from collections.abc import Hashable +from inspect import signature +from typing import Any + +from .propertyset import PropertySet + + +class MetaPass(type): + """Metaclass for transpiler passes. + + Enforces the creation of some fields in the pass while allowing passes to + override ``__init__``. + """ + + def __call__(cls, *args, **kwargs): + pass_instance = type.__call__(cls, *args, **kwargs) + pass_instance._hash = hash(MetaPass._freeze_init_parameters(cls, args, kwargs)) + return pass_instance + + @staticmethod + def _freeze_init_parameters(class_, args, kwargs): + self_guard = object() + init_signature = signature(class_.__init__) + bound_signature = init_signature.bind(self_guard, *args, **kwargs) + arguments = [("class_.__name__", class_.__name__)] + for name, value in bound_signature.arguments.items(): + if value == self_guard: + continue + if isinstance(value, Hashable): + arguments.append((name, type(value), value)) + else: + arguments.append((name, type(value), repr(value))) + return frozenset(arguments) + + +class GenericPass(metaclass=MetaPass): + """Generic pass class with IR-agnostic minimal functionality.""" + + def __init__(self): + self.requires = [] # List of passes that requires + self.preserves = [] # List of passes that preserves + self.property_set = PropertySet() # This pass's pointer to the pass manager's property set. + self._hash = hash(None) + + def __hash__(self): + return self._hash + + def __eq__(self, other): + return hash(self) == hash(other) + + def name(self): + """Return the name of the pass.""" + return self.__class__.__name__ + + @abstractmethod + def run(self, passmanager_ir: Any): + """Run a pass on the pass manager IR. This is implemented by the pass developer. + + Args: + passmanager_ir: the dag on which the pass is run. + + Raises: + NotImplementedError: when this is left unimplemented for a pass. + """ + raise NotImplementedError diff --git a/qiskit/passmanager/exceptions.py b/qiskit/passmanager/exceptions.py new file mode 100644 index 000000000000..c1bca0563d7a --- /dev/null +++ b/qiskit/passmanager/exceptions.py @@ -0,0 +1,19 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Qiskit pass manager exceptions.""" + +from qiskit.exceptions import QiskitError + + +class PassManagerError(QiskitError): + """Pass manager error.""" diff --git a/qiskit/passmanager/flow_controllers.py b/qiskit/passmanager/flow_controllers.py new file mode 100644 index 000000000000..6fe795222ac8 --- /dev/null +++ b/qiskit/passmanager/flow_controllers.py @@ -0,0 +1,158 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019, 2023 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Pass flow controllers to provide pass iterator conditioned on the property set.""" +from __future__ import annotations +from collections import OrderedDict +from collections.abc import Sequence +from typing import Union, List +import logging + +from .base_pass import GenericPass +from .exceptions import PassManagerError + +logger = logging.getLogger(__name__) + + +class FlowController: + """Base class for multiple types of working list. + + This class is a base class for multiple types of working list. When you iterate on it, it + returns the next pass to run. + """ + + registered_controllers = OrderedDict() + + def __init__(self, passes, options, **partial_controller): + self._passes = passes + self.passes = FlowController.controller_factory(passes, options, **partial_controller) + self.options = options + + def __iter__(self): + yield from self.passes + + def dump_passes(self): + """Fetches the passes added to this flow controller. + + Returns: + dict: {'options': self.options, 'passes': [passes], 'type': type(self)} + """ + # TODO remove + ret = {"options": self.options, "passes": [], "type": type(self)} + for pass_ in self._passes: + if isinstance(pass_, FlowController): + ret["passes"].append(pass_.dump_passes()) + else: + ret["passes"].append(pass_) + return ret + + @classmethod + def add_flow_controller(cls, name, controller): + """Adds a flow controller. + + Args: + name (string): Name of the controller to add. + controller (type(FlowController)): The class implementing a flow controller. + """ + cls.registered_controllers[name] = controller + + @classmethod + def remove_flow_controller(cls, name): + """Removes a flow controller. + + Args: + name (string): Name of the controller to remove. + Raises: + KeyError: If the controller to remove was not registered. + """ + if name not in cls.registered_controllers: + raise KeyError("Flow controller not found: %s" % name) + del cls.registered_controllers[name] + + @classmethod + def controller_factory( + cls, + passes: Sequence[GenericPass | "FlowController"], + options: dict, + **partial_controller, + ): + """Constructs a flow controller based on the partially evaluated controller arguments. + + Args: + passes: passes to add to the flow controller. + options: PassManager options. + **partial_controller: Partially evaluated controller arguments in the form `{name:partial}` + + Raises: + PassManagerError: When partial_controller is not well-formed. + + Returns: + FlowController: A FlowController instance. + """ + if None in partial_controller.values(): + raise PassManagerError("The controller needs a condition.") + + if partial_controller: + for registered_controller in cls.registered_controllers.keys(): + if registered_controller in partial_controller: + return cls.registered_controllers[registered_controller]( + passes, options, **partial_controller + ) + raise PassManagerError("The controllers for %s are not registered" % partial_controller) + + return FlowControllerLinear(passes, options) + + +class FlowControllerLinear(FlowController): + """The basic controller runs the passes one after the other.""" + + def __init__(self, passes, options): # pylint: disable=super-init-not-called + self.passes = self._passes = passes + self.options = options + + +class DoWhileController(FlowController): + """Implements a set of passes in a do-while loop.""" + + def __init__(self, passes, options=None, do_while=None, **partial_controller): + self.do_while = do_while + self.max_iteration = options["max_iteration"] if options else 1000 + super().__init__(passes, options, **partial_controller) + + def __iter__(self): + for _ in range(self.max_iteration): + yield from self.passes + + if not self.do_while(): + return + + raise PassManagerError("Maximum iteration reached. max_iteration=%i" % self.max_iteration) + + +class ConditionalController(FlowController): + """Implements a set of passes under a certain condition.""" + + def __init__(self, passes, options=None, condition=None, **partial_controller): + self.condition = condition + super().__init__(passes, options, **partial_controller) + + def __iter__(self): + if self.condition(): + yield from self.passes + + +# Alias to a sequence of all kind of pass elements +PassSequence = Union[Union[GenericPass, FlowController], List[Union[GenericPass, FlowController]]] + +# Default controllers +FlowController.add_flow_controller("condition", ConditionalController) +FlowController.add_flow_controller("do_while", DoWhileController) diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py new file mode 100644 index 000000000000..935a3afb3918 --- /dev/null +++ b/qiskit/passmanager/passmanager.py @@ -0,0 +1,268 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Manager for a set of Passes and their scheduling during transpilation.""" +from __future__ import annotations +from abc import ABC +from collections.abc import Callable, Sequence +from typing import Any +import logging +import dill +from qiskit.tools.parallel import parallel_map + +from .base_pass import GenericPass +from .passrunner import BasePassRunner +from .exceptions import PassManagerError +from .flow_controllers import FlowController, PassSequence + +logger = logging.getLogger(__name__) + + +class BasePassManager(ABC): + """Pass manager base class.""" + + PASS_RUNNER = BasePassRunner + + def __init__( + self, + passes: PassSequence | None = None, + max_iteration: int = 1000, + ): + """Initialize an empty pass manager object. + + Args: + passes: A pass set to be added to the pass manager schedule. + max_iteration: The maximum number of iterations the schedule will be looped if the + condition is not met. + """ + # the pass manager's schedule of passes, including any control-flow. + # Populated via PassManager.append(). + self._pass_sets: list[dict[str, Any]] = [] + self.max_iteration = max_iteration + + if passes is not None: + self.append(passes) + + def append( + self, + passes: PassSequence, + **flow_controller_conditions: Callable, + ) -> None: + """Append a Pass Set to the schedule of passes. + + Args: + passes: A set of passes (a pass set) to be added to schedule. A pass set is a list of + passes that are controlled by the same flow controller. If a single pass is + provided, the pass set will only have that pass a single element. + It is also possible to append a + :class:`~qiskit.transpiler.runningpassmanager.FlowController` instance and the + rest of the parameter will be ignored. + flow_controller_conditions: Dictionary of control flow plugins. Default: + + * do_while (callable property_set -> boolean): The passes repeat until the + callable returns False. + Default: `lambda x: False # i.e. passes run once` + + * condition (callable property_set -> boolean): The passes run only if the + callable returns True. + Default: `lambda x: True # i.e. passes run` + """ + passes = self._normalize_passes(passes) + self._pass_sets.append({"passes": passes, "flow_controllers": flow_controller_conditions}) + + def replace( + self, + index: int, + passes: PassSequence, + **flow_controller_conditions: Any, + ) -> None: + """Replace a particular pass in the scheduler. + + Args: + index: Pass index to replace, based on the position in passes(). + passes: A pass set (as defined in :py:func:`qiskit.transpiler.PassManager.append`) + to be added to the pass manager schedule. + flow_controller_conditions: control flow plugins. + + Raises: + PassManagerError: if a pass in passes is not a proper pass or index not found. + """ + passes = self._normalize_passes(passes) + + try: + self._pass_sets[index] = { + "passes": passes, + "flow_controllers": flow_controller_conditions, + } + except IndexError as ex: + raise PassManagerError(f"Index to replace {index} does not exists") from ex + + def remove(self, index: int) -> None: + """Removes a particular pass in the scheduler. + + Args: + index: Pass index to replace, based on the position in passes(). + + Raises: + PassManagerError: if the index is not found. + """ + try: + del self._pass_sets[index] + except IndexError as ex: + raise PassManagerError(f"Index to replace {index} does not exists") from ex + + def __setitem__(self, index, item): + self.replace(index, item) + + def __len__(self): + return len(self._pass_sets) + + def __getitem__(self, index): + new_passmanager = self.__class__(max_iteration=self.max_iteration) + _pass_sets = self._pass_sets[index] + if isinstance(_pass_sets, dict): + _pass_sets = [_pass_sets] + new_passmanager._pass_sets = _pass_sets + return new_passmanager + + def __add__(self, other): + if isinstance(other, self.__class__): + new_passmanager = self.__class__(max_iteration=self.max_iteration) + new_passmanager._pass_sets = self._pass_sets + other._pass_sets + return new_passmanager + else: + try: + new_passmanager = self.__class__(max_iteration=self.max_iteration) + new_passmanager._pass_sets += self._pass_sets + new_passmanager.append(other) + return new_passmanager + except PassManagerError as ex: + raise TypeError( + "unsupported operand type + for %s and %s" % (self.__class__, other.__class__) + ) from ex + + def _normalize_passes( + self, + passes: PassSequence, + ) -> Sequence[GenericPass | FlowController] | FlowController: + if isinstance(passes, FlowController): + return passes + if isinstance(passes, GenericPass): + passes = [passes] + for pass_ in passes: + if isinstance(pass_, FlowController): + # Normalize passes in nested FlowController. + # TODO: Internal renormalisation should be the responsibility of the + # `FlowController`, but the separation between `FlowController`, + # `RunningPassManager` and `PassManager` is so muddled right now, it would be better + # to do this as part of more top-down refactoring. ---Jake, 2022-10-03. + pass_.passes = self._normalize_passes(pass_.passes) + elif not isinstance(pass_, GenericPass): + raise PassManagerError( + "%s is not a pass or FlowController instance " % pass_.__class__ + ) + return passes + + def run( + self, + in_programs: Any, + callback: Callable | None = None, + **metadata, + ) -> Any: + """Run all the passes on the specified ``circuits``. + + Args: + in_programs: Input programs to transform via all the registered passes. + callback: A callback function that will be called after each pass execution. The + function will be called with 5 keyword arguments:: + + pass_ (Pass): the pass being run + passmanager_ir (Any): depending on pass manager subclass + time (float): the time to execute the pass + property_set (PropertySet): the property set + count (int): the index for the pass execution + + The exact arguments pass expose the internals of the pass + manager and are subject to change as the pass manager internals + change. If you intend to reuse a callback function over + multiple releases be sure to check that the arguments being + passed are the same. + + To use the callback feature you define a function that will + take in kwargs dict and access the variables. For example:: + + def callback_func(**kwargs): + pass_ = kwargs['pass_'] + dag = kwargs['dag'] + time = kwargs['time'] + property_set = kwargs['property_set'] + count = kwargs['count'] + ... + + metadata: Metadata which might be attached to output program. + + Returns: + The transformed program(s). + """ + if not self._pass_sets and not metadata and callback is None: + return in_programs + + is_list = True + if isinstance(in_programs, self.PASS_RUNNER.IN_PROGRAM_TYPE): + in_programs = [in_programs] + is_list = False + + if len(in_programs) == 1: + out_program = self._run_single_circuit(in_programs[0], callback, **metadata) + if is_list: + return [out_program] + return out_program + + # TODO support for List(output_name) and List(callback) + del metadata + del callback + + return self._run_several_circuits(in_programs) + + def _create_running_passmanager(self) -> BasePassRunner: + # Must be implemented by followup PR. + # BasePassRunner.append assumes normalized pass input, which is not pass_sets. + raise NotImplementedError + + def _run_single_circuit( + self, + input_program: Any, + callback: Callable | None = None, + **metadata, + ) -> Any: + pass_runner = self._create_running_passmanager() + return pass_runner.run(input_program, callback=callback, **metadata) + + def _run_several_circuits( + self, + input_programs: Sequence[Any], + ) -> Any: + # Pass runner may contain callable and we need to serialize through dill rather than pickle. + # See https://github.com/Qiskit/qiskit-terra/pull/3290 + # Note that serialized object is deserialized as a different object. + # Thus, we can resue the same runner without state collision, without building it per thread. + return parallel_map( + self._in_parallel, input_programs, task_kwargs={"pm_dill": dill.dumps(self)} + ) + + @staticmethod + def _in_parallel( + in_program: Any, + pm_dill: bytes = None, + ) -> Any: + pass_runner = dill.loads(pm_dill)._create_running_passmanager() + return pass_runner.run(in_program) diff --git a/qiskit/passmanager/passrunner.py b/qiskit/passmanager/passrunner.py new file mode 100644 index 000000000000..9ce983ce6591 --- /dev/null +++ b/qiskit/passmanager/passrunner.py @@ -0,0 +1,250 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Pass runner to apply transformation on passmanager IR.""" +from __future__ import annotations +import logging +import time +from abc import ABC, abstractmethod +from functools import partial +from collections.abc import Callable +from typing import Any + +from .base_pass import GenericPass +from .exceptions import PassManagerError +from .flow_controllers import FlowController, ConditionalController, DoWhileController +from .propertyset import PropertySet + +logger = logging.getLogger(__name__) + +# NoneType is removed from types module in < Python3.10. +NoneType = type(None) + + +class BasePassRunner(ABC): + """Pass runner base class.""" + + IN_PROGRAM_TYPE = NoneType + OUT_PROGRAM_TYPE = NoneType + IR_TYPE = NoneType + + def __init__(self, max_iteration: int): + """Initialize an empty pass runner object. + + Args: + max_iteration: The schedule looping iterates until the condition is met or until + max_iteration is reached. + """ + self.callback = None + self.count = None + self.metadata = None + + # the pass manager's schedule of passes, including any control-flow. + # Populated via PassManager.append(). + self.working_list = [] + + # global property set is the context of the circuit held by the pass manager + # as it runs through its scheduled passes. The flow controller + # have read-only access (via the fenced_property_set). + self.property_set = PropertySet() + + # passes already run that have not been invalidated + self.valid_passes = set() + + # pass manager's overriding options for the passes it runs (for debugging) + self.passmanager_options = {"max_iteration": max_iteration} + + def append( + self, + flow_controller: FlowController, + ): + """Append a flow controller to the schedule of controllers. + + Args: + flow_controller: A normalized flow controller instance. + """ + # We assume flow controller is already normalized. + self.working_list.append(flow_controller) + + @abstractmethod + def _to_passmanager_ir(self, in_program): + """Convert input program into pass manager IR. + + Args: + in_program: Input program. + + Returns: + Pass manager IR. + """ + pass + + @abstractmethod + def _to_target(self, passmanager_ir): + """Convert pass manager IR into output program. + + Args: + passmanager_ir: Pass manager IR after optimization. + + Returns: + Output program. + """ + pass + + @abstractmethod + def _run_base_pass( + self, + pass_: GenericPass, + passmanager_ir: Any, + ) -> Any: + """Do a single base pass. + + Args: + pass_: A base pass to run. + passmanager_ir: Pass manager IR. + + Returns: + Pass manager IR with optimization. + """ + pass + + def _run_pass_generic( + self, + pass_sequence: GenericPass | FlowController, + passmanager_ir: Any, + options: dict[str, Any] | None = None, + ) -> Any: + """Do either base pass or single flow controller. + + Args: + pass_sequence: Base pass or flow controller to run. + passmanager_ir: Pass manager IR. + options: PassManager options. + + Returns: + Pass manager IR with optimization. + + Raises: + PassManagerError: When pass_sequence is not a valid class. + TypeError: When IR type changed during transformation. + """ + if isinstance(pass_sequence, GenericPass): + # First, do the requirements of this pass + for required_pass in pass_sequence.requires: + passmanager_ir = self._run_pass_generic( + pass_sequence=required_pass, + passmanager_ir=passmanager_ir, + options=options, + ) + # Run the pass itself, if not already run + if pass_sequence not in self.valid_passes: + start_time = time.time() + try: + passmanager_ir = self._run_base_pass( + pass_=pass_sequence, + passmanager_ir=passmanager_ir, + ) + finally: + run_time = time.time() - start_time + log_msg = f"Pass: {pass_sequence.name()} - {run_time * 1000:.5f} (ms)" + logger.info(log_msg) + if self.callback: + self.callback( + pass_=pass_sequence, + passmanager_ir=passmanager_ir, + time=run_time, + property_set=self.property_set, + count=self.count, + ) + self.count += 1 + self._update_valid_passes(pass_sequence) + if not isinstance(passmanager_ir, self.IR_TYPE): + raise TypeError( + f"A transformed object {passmanager_ir} is not valid IR in this pass manager. " + "Object representation type must be preserved during transformation. " + f"The pass {pass_sequence.name()} returns invalid object." + ) + return passmanager_ir + + if isinstance(pass_sequence, FlowController): + # This will be removed in followup PR. Code is temporary. + fenced_property_set = getattr(self, "fenced_property_set") + + if isinstance(pass_sequence, ConditionalController) and not isinstance( + pass_sequence.condition, partial + ): + pass_sequence.condition = partial(pass_sequence.condition, fenced_property_set) + if isinstance(pass_sequence, DoWhileController) and not isinstance( + pass_sequence.do_while, partial + ): + pass_sequence.do_while = partial(pass_sequence.do_while, fenced_property_set) + for pass_ in pass_sequence: + passmanager_ir = self._run_pass_generic( + pass_sequence=pass_, + passmanager_ir=passmanager_ir, + options=pass_sequence.options, + ) + return passmanager_ir + + raise PassManagerError( + f"{pass_sequence.__class__} is not a valid base pass nor flow controller." + ) + + def run( + self, + in_program: Any, + callback: Callable | None = None, + **metadata, + ) -> Any: + """Run all the passes on an input program. + + Args: + in_program: Input program to compile via all the registered passes. + callback: A callback function that will be called after each pass execution. + **metadata: Metadata attached to the output program. + + Returns: + Compiled or optimized program. + + Raises: + TypeError: When input or output object is unexpected type. + """ + if not isinstance(in_program, self.IN_PROGRAM_TYPE): + raise TypeError( + f"Input object {in_program} is not valid type for this pass manager. " + f"This pass manager accepts {self.IN_PROGRAM_TYPE}." + ) + + if callback: + self.callback = callback + self.count = 0 + self.metadata = metadata + + passmanager_ir = self._to_passmanager_ir(in_program) + del in_program + + for controller in self.working_list: + passmanager_ir = self._run_pass_generic( + pass_sequence=controller, + passmanager_ir=passmanager_ir, + options=self.passmanager_options, + ) + out_program = self._to_target(passmanager_ir) + + if not isinstance(out_program, self.OUT_PROGRAM_TYPE): + raise TypeError( + f"Output object {out_program} is not valid type for this pass manager. " + f"This pass manager must return {self.OUT_PROGRAM_TYPE}." + ) + return out_program + + def _update_valid_passes(self, pass_): + self.valid_passes.add(pass_) diff --git a/qiskit/passmanager/propertyset.py b/qiskit/passmanager/propertyset.py new file mode 100644 index 000000000000..d13eeb7c0032 --- /dev/null +++ b/qiskit/passmanager/propertyset.py @@ -0,0 +1,24 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""A property set is maintained by the pass runner. + + +This is sort of shared memory space among passes. +""" + + +class PropertySet(dict): + """A default dictionary-like object""" + + def __missing__(self, key): + return None diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index e581c9721fe0..7b1ef8527049 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -125,13 +125,10 @@ Exceptions ---------- -.. autosummary:: - :toctree: ../stubs/ - - QiskitBackendNotFoundError - BackendPropertyError - JobError - JobTimeoutError +.. autoexception:: QiskitBackendNotFoundError +.. autoexception:: BackendPropertyError +.. autoexception:: JobError +.. autoexception:: JobTimeoutError ====================== Writing a New Provider diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index d61810f765fd..d570bfd4db60 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -88,6 +88,7 @@ def __init__(self, configuration, provider=None, **fields): private methods: .. automethod:: _default_options + :noindex: """ self._configuration = configuration self._options = self._default_options() diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index 07ebcba99b7b..d8c42734cebf 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -54,7 +54,7 @@ Exceptions ========== -.. autoclass:: PulseError +.. autoexception:: PulseError """ # Builder imports. diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index b5410776df14..913384a56f3c 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -231,10 +231,7 @@ The above is just a small taste of what is possible with the builder. See the rest of the module documentation for more information on its capabilities. -.. autosummary:: - :toctree: ../stubs/ - - build +.. autofunction:: build Channels @@ -257,13 +254,10 @@ DriveChannel(0) -.. autosummary:: - :toctree: ../stubs/ - - acquire_channel - control_channels - drive_channel - measure_channel +.. autofunction:: acquire_channel +.. autofunction:: control_channels +.. autofunction:: drive_channel +.. autofunction:: measure_channel Instructions @@ -299,21 +293,17 @@ drive_sched.draw() - -.. autosummary:: - :toctree: ../stubs/ - - acquire - barrier - call - delay - play - reference - set_frequency - set_phase - shift_frequency - shift_phase - snapshot +.. autofunction:: acquire +.. autofunction:: barrier +.. autofunction:: call +.. autofunction:: delay +.. autofunction:: play +.. autofunction:: reference +.. autofunction:: set_frequency +.. autofunction:: set_phase +.. autofunction:: shift_frequency +.. autofunction:: shift_phase +.. autofunction:: snapshot Contexts @@ -340,18 +330,15 @@ pulse_prog.draw() -.. autosummary:: - :toctree: ../stubs/ - - align_equispaced - align_func - align_left - align_right - align_sequential - circuit_scheduler_settings - frequency_offset - phase_offset - transpiler_settings +.. autofunction:: align_equispaced +.. autofunction:: align_func +.. autofunction:: align_left +.. autofunction:: align_right +.. autofunction:: align_sequential +.. autofunction:: circuit_scheduler_settings +.. autofunction:: frequency_offset +.. autofunction:: phase_offset +.. autofunction:: transpiler_settings Macros @@ -374,12 +361,9 @@ MemorySlot(0) -.. autosummary:: - :toctree: ../stubs/ - - measure - measure_all - delay_qubits +.. autofunction:: measure +.. autofunction:: measure_all +.. autofunction:: delay_qubits Circuit Gates @@ -405,14 +389,11 @@ with pulse.build(backend) as u3_sched: pulse.u3(math.pi, 0, math.pi, 0) -.. autosummary:: - :toctree: ../stubs/ - - cx - u1 - u2 - u3 - x +.. autofunction:: cx +.. autofunction:: u1 +.. autofunction:: u2 +.. autofunction:: u3 +.. autofunction:: x Utilities @@ -446,16 +427,13 @@ There are 160 samples in 3.5555555555555554e-08 seconds There are 1e-06 seconds in 4500 samples. -.. autosummary:: - :toctree: ../stubs/ - - active_backend - active_transpiler_settings - active_circuit_scheduler_settings - num_qubits - qubit_channels - samples_to_seconds - seconds_to_samples +.. autofunction:: active_backend +.. autofunction:: active_transpiler_settings +.. autofunction:: active_circuit_scheduler_settings +.. autofunction:: num_qubits +.. autofunction:: qubit_channels +.. autofunction:: samples_to_seconds +.. autofunction:: seconds_to_samples """ import collections import contextvars diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index c88fddadd77b..687b837700d3 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -121,7 +121,7 @@ def _validate_index(self, index: Any) -> None: if index.is_integer(): index = int(index) - if not isinstance(index, (int, np.integer)) and index < 0: + if not isinstance(index, (int, np.integer)) or index < 0: raise PulseError("Channel index must be a nonnegative integer") @property diff --git a/qiskit/pulse/library/__init__.py b/qiskit/pulse/library/__init__.py index 2562f96f24ef..d8809607e3d9 100644 --- a/qiskit/pulse/library/__init__.py +++ b/qiskit/pulse/library/__init__.py @@ -57,22 +57,19 @@ Waveform Pulse Representation ============================= -.. autosummary:: - :toctree: ../stubs/ - - constant - zero - square - sawtooth - triangle - cos - sin - gaussian - gaussian_deriv - sech - sech_deriv - gaussian_square - drag +.. autofunction:: constant +.. autofunction:: zero +.. autofunction:: square +.. autofunction:: sawtooth +.. autofunction:: triangle +.. autofunction:: cos +.. autofunction:: sin +.. autofunction:: gaussian +.. autofunction:: gaussian_deriv +.. autofunction:: sech +.. autofunction:: sech_deriv +.. autofunction:: gaussian_square +.. autofunction:: drag .. _symbolic_pulses: diff --git a/qiskit/pulse/transforms/__init__.py b/qiskit/pulse/transforms/__init__.py index fcc5557bf565..4fb4bf40e9ef 100644 --- a/qiskit/pulse/transforms/__init__.py +++ b/qiskit/pulse/transforms/__init__.py @@ -47,18 +47,15 @@ The canonicalization transforms convert schedules to a form amenable for execution on OpenPulse backends. -.. autosummary:: - :toctree: ../stubs/ - - add_implicit_acquires - align_measures - block_to_schedule - compress_pulses - flatten - inline_subroutines - pad - remove_directives - remove_trivial_barriers +.. autofunction:: add_implicit_acquires +.. autofunction:: align_measures +.. autofunction:: block_to_schedule +.. autofunction:: compress_pulses +.. autofunction:: flatten +.. autofunction:: inline_subroutines +.. autofunction:: pad +.. autofunction:: remove_directives +.. autofunction:: remove_trivial_barriers .. _pulse_dag: @@ -69,10 +66,7 @@ The DAG transforms create DAG representation of input program. This can be used for optimization of instructions and equality checks. -.. autosummary:: - :toctree: ../stubs/ - - block_to_dag +.. autofunction:: block_to_dag .. _pulse_transform_chain: @@ -82,10 +76,7 @@ A sequence of transformations to generate a target code. -.. autosummary:: - :toctree: ../stubs/ - - target_qobj_transform +.. autofunction:: target_qobj_transform """ diff --git a/qiskit/qasm/__init__.py b/qiskit/qasm/__init__.py index f442aaef845f..322d230343c6 100644 --- a/qiskit/qasm/__init__.py +++ b/qiskit/qasm/__init__.py @@ -20,10 +20,7 @@ QASM Routines ============= -.. autosummary:: - :toctree: ../stubs/ - - Qasm +.. autoclass:: Qasm Pygments diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 631b6865fea1..4482fb0753bd 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -71,11 +71,8 @@ API documentation ================= -.. autosummary:: - :toctree: ../stubs/ - - load - dump +.. autofunction:: load +.. autofunction:: dump QPY Compatibility ================= diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index c507e0d2bed4..6021955e8d54 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -62,59 +62,51 @@ Measures ======== -.. autosummary:: - :toctree: ../stubs/ - - average_gate_fidelity - process_fidelity - gate_error - diamond_norm - state_fidelity - purity - concurrence - entropy - entanglement_of_formation - mutual_information +.. autofunction:: average_gate_fidelity +.. autofunction:: process_fidelity +.. autofunction:: gate_error +.. autofunction:: diamond_norm +.. autofunction:: state_fidelity +.. autofunction:: purity +.. autofunction:: concurrence +.. autofunction:: entropy +.. autofunction:: entanglement_of_formation +.. autofunction:: mutual_information Utility Functions ================= -.. autosummary:: - :toctree: ../stubs/ - - partial_trace - schmidt_decomposition - shannon_entropy - commutator - anti_commutator - double_commutator +.. autofunction:: partial_trace +.. autofunction:: schmidt_decomposition +.. autofunction:: shannon_entropy +.. autofunction:: commutator +.. autofunction:: anti_commutator +.. autofunction:: double_commutator Random ====== -.. autosummary:: - :toctree: ../stubs/ - - random_statevector - random_density_matrix - random_unitary - random_hermitian - random_pauli - random_clifford - random_quantum_channel - random_cnotdihedral - random_pauli_table - random_pauli_list - random_stabilizer_table +.. autofunction:: random_statevector +.. autofunction:: random_density_matrix +.. autofunction:: random_unitary +.. autofunction:: random_hermitian +.. autofunction:: random_pauli +.. autofunction:: random_clifford +.. autofunction:: random_quantum_channel +.. autofunction:: random_cnotdihedral +.. autofunction:: random_pauli_table +.. autofunction:: random_pauli_list +.. autofunction:: random_stabilizer_table Analysis ========= +.. autofunction:: hellinger_distance +.. autofunction:: hellinger_fidelity + .. autosummary:: :toctree: ../stubs/ - hellinger_distance - hellinger_fidelity Z2Symmetries Synthesis @@ -125,10 +117,11 @@ OneQubitEulerDecomposer TwoQubitBasisDecomposer - two_qubit_cnot_decompose Quaternion - decompose_clifford XXDecomposer + +.. autofunction:: two_qubit_cnot_decompose +.. autofunction:: decompose_clifford """ from __future__ import annotations diff --git a/qiskit/result/__init__.py b/qiskit/result/__init__.py index 024df50506b1..08b43f704939 100644 --- a/qiskit/result/__init__.py +++ b/qiskit/result/__init__.py @@ -23,9 +23,10 @@ Result ResultError Counts - marginal_counts - marginal_distribution - marginal_memory + +.. autofunction:: marginal_counts +.. autofunction:: marginal_distribution +.. autofunction:: marginal_memory Distributions ============= @@ -39,10 +40,7 @@ Expectation values ================== -.. autosummary:: - :toctree: ../stubs/ - - sampled_expectation_value +.. autofunction:: sampled_expectation_value Mitigation ========== diff --git a/qiskit/scheduler/__init__.py b/qiskit/scheduler/__init__.py index b9f2ef97ada3..b85ee77d3764 100644 --- a/qiskit/scheduler/__init__.py +++ b/qiskit/scheduler/__init__.py @@ -19,11 +19,11 @@ A circuit scheduler compiles a circuit program to a pulse program. -.. autosummary:: - :toctree: ../stubs/ +.. autoclass:: ScheduleConfig - schedule_circuit - ScheduleConfig +.. currentmodule:: qiskit.scheduler.schedule_circuit +.. autofunction:: schedule_circuit +.. currentmodule:: qiskit.scheduler .. automodule:: qiskit.scheduler.methods """ diff --git a/qiskit/scheduler/methods/__init__.py b/qiskit/scheduler/methods/__init__.py index b41135d6a5c6..1fe4b301b7ab 100644 --- a/qiskit/scheduler/methods/__init__.py +++ b/qiskit/scheduler/methods/__init__.py @@ -15,10 +15,8 @@ Pulse scheduling methods. -.. autosummary:: - :toctree: ../stubs/ - - basic +.. autofunction:: as_soon_as_possible +.. autofunction:: as_late_as_possible """ from qiskit.scheduler.methods.basic import as_soon_as_possible, as_late_as_possible diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index ff7a3b025a8f..c312ae0dfebd 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -34,61 +34,45 @@ Linear Function Synthesis ========================= -.. autosummary:: - :toctree: ../stubs/ - synth_cnot_count_full_pmh - synth_cnot_depth_line_kms +.. autofunction:: synth_cnot_count_full_pmh +.. autofunction:: synth_cnot_depth_line_kms Linear-Phase Synthesis ====================== -.. autosummary:: - :toctree: ../stubs/ - synth_cz_depth_line_mr - synth_cx_cz_depth_line_my +.. autofunction:: synth_cz_depth_line_mr +.. autofunction:: synth_cx_cz_depth_line_my Permutation Synthesis ===================== -.. autosummary:: - :toctree: ../stubs/ - - synth_permutation_depth_lnn_kms - synth_permutation_basic - synth_permutation_acg +.. autofunction:: synth_permutation_depth_lnn_kms +.. autofunction:: synth_permutation_basic +.. autofunction:: synth_permutation_acg Clifford Synthesis ================== -.. autosummary:: - :toctree: ../stubs/ - - synth_clifford_full - synth_clifford_ag - synth_clifford_bm - synth_clifford_greedy - synth_clifford_layers - synth_clifford_depth_lnn +.. autofunction:: synth_clifford_full +.. autofunction:: synth_clifford_ag +.. autofunction:: synth_clifford_bm +.. autofunction:: synth_clifford_greedy +.. autofunction:: synth_clifford_layers +.. autofunction:: synth_clifford_depth_lnn CNOTDihedral Synthesis ====================== -.. autosummary:: - :toctree: ../stubs/ - - synth_cnotdihedral_full - synth_cnotdihedral_two_qubits - synth_cnotdihedral_general +.. autofunction:: synth_cnotdihedral_full +.. autofunction:: synth_cnotdihedral_two_qubits +.. autofunction:: synth_cnotdihedral_general Stabilizer State Synthesis ========================== -.. autosummary:: - :toctree: ../stubs/ - - synth_stabilizer_layers - synth_stabilizer_depth_lnn +.. autofunction:: synth_stabilizer_layers +.. autofunction:: synth_stabilizer_depth_lnn Discrete Basis Synthesis ======================== @@ -97,7 +81,8 @@ :toctree: ../stubs/ SolovayKitaevDecomposition - generate_basic_approximations + +.. autofunction:: generate_basic_approximations """ diff --git a/qiskit/tools/__init__.py b/qiskit/tools/__init__.py index 4f6f8e02879b..6bddd22ba889 100644 --- a/qiskit/tools/__init__.py +++ b/qiskit/tools/__init__.py @@ -24,22 +24,16 @@ Tasks can be executed in parallel using this function. It has a built-in event publisher to show the progress of the parallel tasks. -.. autosummary:: - :toctree: ../stubs/ - - parallel_map +.. autofunction:: parallel_map Monitoring ---------- A helper module to get IBM backend information and submitted job status. -.. autosummary:: - :toctree: ../stubs/ - - job_monitor - backend_monitor - backend_overview +.. autofunction:: job_monitor +.. autofunction:: backend_monitor +.. autofunction:: backend_overview .. automodule:: qiskit.tools.events diff --git a/qiskit/tools/events/__init__.py b/qiskit/tools/events/__init__.py index 9ada164d8d06..d15a537bb942 100644 --- a/qiskit/tools/events/__init__.py +++ b/qiskit/tools/events/__init__.py @@ -19,15 +19,7 @@ .. currentmodule:: qiskit.tools.events -TextProgressBar ---------------- - -A text based progress bar, which also enables Jupyter magics. - -.. autosummary:: - :toctree: ../stubs/ - - TextProgressBar +.. autoclass:: TextProgressBar """ from .progressbar import TextProgressBar diff --git a/qiskit/tools/jupyter/version_table.py b/qiskit/tools/jupyter/version_table.py index 67681ee51674..959e155239c4 100644 --- a/qiskit/tools/jupyter/version_table.py +++ b/qiskit/tools/jupyter/version_table.py @@ -36,7 +36,14 @@ def qiskit_version_table(self, line="", cell=None): html += "" html += "" - packages = {} + packages = {"qiskit": None} + + packages["qiskit-terra"] = qiskit.__version__ + + qiskit_modules = {module.split(".")[0] for module in modules.keys() if "qiskit" in module} + + for qiskit_module in qiskit_modules: + packages[qiskit_module] = getattr(modules[qiskit_module], "__version__", None) from importlib.metadata import metadata, PackageNotFoundError @@ -45,14 +52,9 @@ def qiskit_version_table(self, line="", cell=None): except PackageNotFoundError: packages["qiskit"] = None - packages["qiskit-terra"] = qiskit.__version__ - - qiskit_modules = {module.split(".")[0] for module in modules.keys() if "qiskit" in module} - for qiskit_module in qiskit_modules: - packages[qiskit_module] = getattr(modules[qiskit_module], "__version__", None) - for name, version in packages.items(): - html += f"" + if name == "qiskit" or version: + html += f"" html += "" diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 76cd6fc3d6f4..3d32c911566f 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -1197,10 +1197,6 @@ StagedPassManager PassManager PassManagerConfig - PropertySet - FlowController - ConditionalController - DoWhileController Layout and Topology ------------------- @@ -1248,11 +1244,16 @@ TranspilerAccessError """ -from .runningpassmanager import FlowController, ConditionalController, DoWhileController -from .passmanager import PassManager +# For backward compatibility +from qiskit.passmanager import ( + FlowController, + ConditionalController, + DoWhileController, +) + +from .passmanager import PassManager, StagedPassManager from .passmanager_config import PassManagerConfig -from .passmanager import StagedPassManager -from .propertyset import PropertySet +from .propertyset import PropertySet # pylint: disable=no-name-in-module from .exceptions import TranspilerError, TranspilerAccessError from .fencedobjs import FencedDAGCircuit, FencedPropertySet from .basepasses import AnalysisPass, TransformationPass diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index f54fb8f27ab9..46b10b6510da 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -13,61 +13,18 @@ """Base transpiler passes.""" from abc import abstractmethod -from collections.abc import Hashable -from inspect import signature -from .propertyset import PropertySet -from .layout import TranspileLayout - - -class MetaPass(type): - """Metaclass for transpiler passes. - Enforces the creation of some fields in the pass while allowing passes to - override ``__init__``. - """ +from qiskit.passmanager.base_pass import GenericPass +from qiskit.passmanager.propertyset import PropertySet - def __call__(cls, *args, **kwargs): - pass_instance = type.__call__(cls, *args, **kwargs) - pass_instance._hash = hash(MetaPass._freeze_init_parameters(cls, args, kwargs)) - return pass_instance - - @staticmethod - def _freeze_init_parameters(class_, args, kwargs): - self_guard = object() - init_signature = signature(class_.__init__) - bound_signature = init_signature.bind(self_guard, *args, **kwargs) - arguments = [("class_.__name__", class_.__name__)] - for name, value in bound_signature.arguments.items(): - if value == self_guard: - continue - if isinstance(value, Hashable): - arguments.append((name, type(value), value)) - else: - arguments.append((name, type(value), repr(value))) - return frozenset(arguments) +from .layout import TranspileLayout -class BasePass(metaclass=MetaPass): +class BasePass(GenericPass): """Base class for transpiler passes.""" - def __init__(self): - self.requires = [] # List of passes that requires - self.preserves = [] # List of passes that preserves - self.property_set = PropertySet() # This pass's pointer to the pass manager's property set. - self._hash = hash(None) - - def __hash__(self): - return self._hash - - def __eq__(self, other): - return hash(self) == hash(other) - - def name(self): - """Return the name of the pass.""" - return self.__class__.__name__ - @abstractmethod - def run(self, dag): + def run(self, dag): # pylint: disable=arguments-differ """Run a pass on the DAGCircuit. This is implemented by the pass developer. Args: diff --git a/qiskit/transpiler/exceptions.py b/qiskit/transpiler/exceptions.py index c30aadedf173..ef79603bfed2 100644 --- a/qiskit/transpiler/exceptions.py +++ b/qiskit/transpiler/exceptions.py @@ -14,9 +14,10 @@ Exception for errors raised by the transpiler. """ from qiskit.exceptions import QiskitError +from qiskit.passmanager.exceptions import PassManagerError -class TranspilerAccessError(QiskitError): +class TranspilerAccessError(PassManagerError): """DEPRECATED: Exception of access error in the transpiler passes.""" diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index 2f9305b23244..a69d90f22163 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -83,9 +83,11 @@ def run(self, dag): full_layout = Layout() old_phys_to_virtual = layout.get_physical_bits() new_virtual_to_physical = post_layout.get_virtual_bits() + phys_map = list(range(len(new_dag.qubits))) for new_virt, new_phys in new_virtual_to_physical.items(): old_phys = dag.find_bit(new_virt).index old_virt = old_phys_to_virtual[old_phys] + phys_map[old_phys] = new_phys full_layout.add(old_virt, new_phys) for reg in layout.get_registers(): full_layout.add_register(reg) @@ -94,6 +96,13 @@ def run(self, dag): qargs = [q[new_virtual_to_physical[qarg]] for qarg in node.qargs] new_dag.apply_operation_back(node.op, qargs, node.cargs) self.property_set["layout"] = full_layout + if (final_layout := self.property_set["final_layout"]) is not None: + final_layout_mapping = { + new_dag.qubits[phys_map[dag.find_bit(old_virt).index]]: phys_map[old_phys] + for old_virt, old_phys in final_layout.get_virtual_bits().items() + } + out_layout = Layout(final_layout_mapping) + self.property_set["final_layout"] = out_layout new_dag._global_phase = dag._global_phase return new_dag diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index b093b21b4c8d..d8d873cbd403 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -90,15 +90,11 @@ def run(self, dag): if self.decomposer is None: return dag - # compute ordered indices for the global circuit wires - global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits)} blocks = self.property_set["block_list"] or [] basis_gate_name = self.decomposer.gate.name all_block_gates = set() for block in blocks: - if len(block) == 1 and self._check_not_in_basis( - block[0].name, block[0].qargs, global_index_map - ): + if len(block) == 1 and self._check_not_in_basis(dag, block[0].name, block[0].qargs): all_block_gates.add(block[0]) dag.substitute_node(block[0], UnitaryGate(block[0].op.to_matrix())) else: @@ -111,11 +107,11 @@ def run(self, dag): if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): block_cargs |= set(getattr(nd.op, "condition", None)[0]) all_block_gates.add(nd) - block_index_map = self._block_qargs_to_indices(block_qargs, global_index_map) + block_index_map = self._block_qargs_to_indices(dag, block_qargs) for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 - if self._check_not_in_basis(nd.op.name, nd.qargs, global_index_map): + if self._check_not_in_basis(dag, nd.op.name, nd.qargs): outside_basis = True if len(block_qargs) > 2: q = QuantumRegister(len(block_qargs)) @@ -153,9 +149,7 @@ def run(self, dag): for run in runs: if any(gate in all_block_gates for gate in run): continue - if len(run) == 1 and not self._check_not_in_basis( - run[0].name, run[0].qargs, global_index_map - ): + if len(run) == 1 and not self._check_not_in_basis(dag, run[0].name, run[0].qargs): dag.substitute_node(run[0], UnitaryGate(run[0].op.to_matrix())) else: qubit = run[0].qargs[0] @@ -201,15 +195,15 @@ def _handle_control_flow_ops(self, dag): node.op = node.op.replace_blocks(pass_manager.run(block) for block in node.op.blocks) return dag - def _check_not_in_basis(self, gate_name, qargs, global_index_map): + def _check_not_in_basis(self, dag, gate_name, qargs): if self.target is not None: return not self.target.instruction_supported( - gate_name, tuple(global_index_map[qubit] for qubit in qargs) + gate_name, tuple(dag.find_bit(qubit).index for qubit in qargs) ) else: return self.basis_gates and gate_name not in self.basis_gates - def _block_qargs_to_indices(self, block_qargs, global_index_map): + def _block_qargs_to_indices(self, dag, block_qargs): """Map each qubit in block_qargs to its wire position among the block's wires. Args: block_qargs (list): list of qubits that a block acts on @@ -218,7 +212,7 @@ def _block_qargs_to_indices(self, block_qargs, global_index_map): Returns: dict: mapping from qarg to position in block """ - block_indices = [global_index_map[q] for q in block_qargs] + block_indices = [dag.find_bit(q).index for q in block_qargs] ordered_block_indices = {bit: index for index, bit in enumerate(sorted(block_indices))} - block_positions = {q: ordered_block_indices[global_index_map[q]] for q in block_qargs} + block_positions = {q: ordered_block_indices[dag.find_bit(q).index] for q in block_qargs} return block_positions diff --git a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py index 730b418fb2cf..155338d3ad28 100644 --- a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py +++ b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py @@ -136,7 +136,6 @@ def __init__( self.model = None self.dag = None self.parse_backend_properties() - self.qubit_indices = None def powerset(self, iterable): """ @@ -189,8 +188,8 @@ def cx_tuple(self, gate): Note: current implementation assumes that the CX error rates and crosstalk behavior are independent of gate direction """ - physical_q_0 = self.qubit_indices[gate.qargs[0]] - physical_q_1 = self.qubit_indices[gate.qargs[1]] + physical_q_0 = self.dag.find_bit(gate.qargs[0]).index + physical_q_1 = self.dag.find_bit(gate.qargs[1]).index r_0 = min(physical_q_0, physical_q_1) r_1 = max(physical_q_0, physical_q_1) return (r_0, r_1) @@ -199,7 +198,7 @@ def singleq_tuple(self, gate): """ Representation for single-qubit gate """ - physical_q_0 = self.qubit_indices[gate.qargs[0]] + physical_q_0 = self.dag.find_bit(gate.qargs[0]).index tup = (physical_q_0,) return tup @@ -310,7 +309,7 @@ def create_z3_vars(self): active_qubits_list = [] for gate in self.dag.gate_nodes(): for q in gate.qargs: - active_qubits_list.append(self.qubit_indices[q]) + active_qubits_list.append(self.dag.find_bit(q).index) for active_qubit in list(set(active_qubits_list)): q_var_name = "l_" + str(active_qubit) self.qubit_lifetime[active_qubit] = z3.Real(q_var_name) @@ -318,7 +317,7 @@ def create_z3_vars(self): meas_q = [] for node in self.dag.op_nodes(): if isinstance(node.op, Measure): - meas_q.append(self.qubit_indices[node.qargs[0]]) + meas_q.append(self.dag.find_bit(node.qargs[0]).index) self.measured_qubits = list(set(self.input_measured_qubits).union(set(meas_q))) self.measure_start = z3.Real("meas_start") @@ -330,7 +329,7 @@ def basic_bounds(self): for gate in self.gate_start_time: self.opt.add(self.gate_start_time[gate] >= 0) for gate in self.gate_duration: - q_0 = self.qubit_indices[gate.qargs[0]] + q_0 = self.dag.find_bit(gate.qargs[0]).index if isinstance(gate.op, U1Gate): dur = self.bp_u1_dur[q_0] elif isinstance(gate.op, U2Gate): @@ -384,7 +383,7 @@ def fidelity_constraints(self): import z3 for gate in self.gate_start_time: - q_0 = self.qubit_indices[gate.qargs[0]] + q_0 = self.dag.find_bit(gate.qargs[0]).index no_xtalk = False if gate not in self.xtalk_overlap_set: no_xtalk = True @@ -439,23 +438,23 @@ def coherence_constraints(self): if isinstance(gate.op, Barrier): continue if len(gate.qargs) == 1: - q_0 = self.qubit_indices[gate.qargs[0]] + q_0 = self.dag.find_bit(gate.qargs[0]).index self.last_gate_on_qubit[q_0] = gate else: - q_0 = self.qubit_indices[gate.qargs[0]] - q_1 = self.qubit_indices[gate.qargs[1]] + q_0 = self.dag.find_bit(gate.qargs[0]).index + q_1 = self.dag.find_bit(gate.qargs[1]).index self.last_gate_on_qubit[q_0] = gate self.last_gate_on_qubit[q_1] = gate self.first_gate_on_qubit = {} for gate in self.dag.topological_op_nodes(): if len(gate.qargs) == 1: - q_0 = self.qubit_indices[gate.qargs[0]] + q_0 = self.dag.find_bit(gate.qargs[0]).index if q_0 not in self.first_gate_on_qubit: self.first_gate_on_qubit[q_0] = gate else: - q_0 = self.qubit_indices[gate.qargs[0]] - q_1 = self.qubit_indices[gate.qargs[1]] + q_0 = self.dag.find_bit(gate.qargs[0]).index + q_1 = self.dag.find_bit(gate.qargs[1]).index if q_0 not in self.first_gate_on_qubit: self.first_gate_on_qubit[q_0] = gate if q_1 not in self.first_gate_on_qubit: @@ -719,7 +718,6 @@ def run(self, dag): """ self.dag = dag - self.qubit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} # process input program self.assign_gate_id(self.dag) self.extract_dag_overlap_sets(self.dag) diff --git a/qiskit/transpiler/passes/routing/algorithms/bip_model.py b/qiskit/transpiler/passes/routing/algorithms/bip_model.py index 7a37ff6a949b..972cf8242918 100644 --- a/qiskit/transpiler/passes/routing/algorithms/bip_model.py +++ b/qiskit/transpiler/passes/routing/algorithms/bip_model.py @@ -87,7 +87,6 @@ def __init__(self, dag, coupling_map, qubit_subset, dummy_timesteps=None): ) self._index_to_virtual = dict(enumerate(dag.qubits)) - self._virtual_to_index = {v: i for i, v in self._index_to_virtual.items()} # Construct internal circuit model # Extract layers with 2-qubit gates @@ -96,8 +95,8 @@ def __init__(self, dag, coupling_map, qubit_subset, dummy_timesteps=None): for lay in dag.layers(): laygates = [] for node in lay["graph"].two_qubit_ops(): - i1 = self._virtual_to_index[node.qargs[0]] - i2 = self._virtual_to_index[node.qargs[1]] + i1 = self._dag.find_bit(node.qargs[0]).index + i2 = self._dag.find_bit(node.qargs[1]).index laygates.append(((i1, i2), node)) if laygates: self._to_su4layer.append(len(self.su4layers)) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index da804d97b2f0..ea669aba0bed 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -17,8 +17,9 @@ import rustworkx -from qiskit.circuit import ControlFlowOp +from qiskit.circuit import SwitchCaseOp, ControlFlowOp, Clbit, ClassicalRegister from qiskit.circuit.library.standard_gates import SwapGate +from qiskit.circuit.controlflow import condition_resources, node_resources from qiskit.converters import dag_to_circuit from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.coupling import CouplingMap @@ -282,14 +283,21 @@ def recurse(block, block_qubit_indices): return process_dag(block_dag, block_qubit_indices) def process_dag(block_dag, wire_map): - clbit_indices = {bit: idx for idx, bit in enumerate(block_dag.clbits)} dag_list = [] node_blocks = {} for node in block_dag.topological_op_nodes(): - cargs = {clbit_indices[x] for x in node.cargs} + cargs_bits = set(node.cargs) if node.op.condition is not None: - for clbit in block_dag._bits_in_operation(node.op): - cargs.add(clbit_indices[clbit]) + cargs_bits.update(condition_resources(node.op.condition).clbits) + if isinstance(node.op, SwitchCaseOp): + target = node.op.target + if isinstance(target, Clbit): + cargs_bits.add(target) + elif isinstance(target, ClassicalRegister): + cargs_bits.update(target) + else: # Expr + cargs_bits.update(node_resources(target).clbits) + cargs = {block_dag.find_bit(x).index for x in cargs_bits} if isinstance(node.op, ControlFlowOp): node_blocks[node._node_id] = [ recurse( diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 067d955f91b7..5bc7c97f31f7 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -18,6 +18,7 @@ import numpy as np from qiskit.converters import dag_to_circuit, circuit_to_dag +from qiskit.circuit.classical import expr, types from qiskit.circuit.quantumregister import QuantumRegister from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError @@ -474,7 +475,16 @@ def _controlflow_exhaustive_acyclic(operation: ControlFlowOp): return len(operation.blocks) == 2 if isinstance(operation, SwitchCaseOp): cases = operation.cases() - max_matches = 2 if isinstance(operation.target, Clbit) else 1 << len(operation.target) + if isinstance(operation.target, expr.Expr): + type_ = operation.target.type + if type_.kind is types.Bool: + max_matches = 2 + elif type_.kind is types.Uint: + max_matches = 1 << type_.width + else: + raise RuntimeError(f"unhandled target type: '{type_}'") + else: + max_matches = 2 if isinstance(operation.target, Clbit) else 1 << len(operation.target) return CASE_DEFAULT in cases or len(cases) == max_matches return False diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 0265aae7a1ff..063b94611bdc 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -62,9 +62,8 @@ def run(self, dag): new_dag.add_creg(creg) idle_before = {q: 0 for q in dag.qubits + dag.clbits} - bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in reversed(list(dag.topological_op_nodes())): - op_duration = self._get_node_duration(node, bit_indices, dag) + op_duration = self._get_node_duration(node, dag) # compute t0, t1: instruction interval, note that # t0: start time of instruction @@ -131,7 +130,7 @@ def run(self, dag): for bit in node.qargs: delta = t0 - idle_before[bit] - if delta > 0 and self._delay_supported(bit_indices[bit]): + if delta > 0 and self._delay_supported(dag.find_bit(bit).index): new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) idle_before[bit] = t1 @@ -142,7 +141,7 @@ def run(self, dag): delta = circuit_duration - before if not (delta > 0 and isinstance(bit, Qubit)): continue - if self._delay_supported(bit_indices[bit]): + if self._delay_supported(dag.find_bit(bit).index): new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) new_dag.name = dag.name diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index a3a729b34622..cebc32af71a8 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -69,9 +69,8 @@ def run(self, dag): new_dag.add_creg(creg) idle_after = {q: 0 for q in dag.qubits + dag.clbits} - bit_indices = {q: index for index, q in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): - op_duration = self._get_node_duration(node, bit_indices, dag) + op_duration = self._get_node_duration(node, dag) # compute t0, t1: instruction interval, note that # t0: start time of instruction @@ -150,7 +149,11 @@ def run(self, dag): # Add delay to qubit wire for bit in node.qargs: delta = t0 - idle_after[bit] - if delta > 0 and isinstance(bit, Qubit) and self._delay_supported(bit_indices[bit]): + if ( + delta > 0 + and isinstance(bit, Qubit) + and self._delay_supported(dag.find_bit(bit).index) + ): new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) idle_after[bit] = t1 @@ -161,7 +164,7 @@ def run(self, dag): delta = circuit_duration - after if not (delta > 0 and isinstance(bit, Qubit)): continue - if self._delay_supported(bit_indices[bit]): + if self._delay_supported(dag.find_bit(bit).index): new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) new_dag.name = dag.name diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 6bcf1f5e9e18..78e2660e505d 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -11,7 +11,6 @@ # that they have been altered from the originals. """Base circuit scheduling pass.""" -from typing import Dict from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion @@ -258,11 +257,10 @@ def __init__( @staticmethod def _get_node_duration( node: DAGOpNode, - bit_index_map: Dict, dag: DAGCircuit, ) -> int: """A helper method to get duration from node or calibration.""" - indices = [bit_index_map[qarg] for qarg in node.qargs] + indices = [dag.find_bit(qarg).index for qarg in node.qargs] if dag.has_calibration_for(node): # If node has calibration, this value should be the highest priority diff --git a/qiskit/transpiler/passes/scheduling/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/scheduling/alap.py index 781c039f91e0..2001c3d66f2f 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/alap.py @@ -46,9 +46,8 @@ def run(self, dag): node_start_time = {} idle_before = {q: 0 for q in dag.qubits + dag.clbits} - bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in reversed(list(dag.topological_op_nodes())): - op_duration = self._get_node_duration(node, bit_indices, dag) + op_duration = self._get_node_duration(node, dag) # compute t0, t1: instruction interval, note that # t0: start time of instruction diff --git a/qiskit/transpiler/passes/scheduling/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/scheduling/asap.py index bec66a994a28..ce0e1d1948a0 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/asap.py @@ -46,9 +46,8 @@ def run(self, dag): node_start_time = {} idle_after = {q: 0 for q in dag.qubits + dag.clbits} - bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): - op_duration = self._get_node_duration(node, bit_indices, dag) + op_duration = self._get_node_duration(node, dag) # compute t0, t1: instruction interval, note that # t0: start time of instruction diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index 01227350919e..f83aed800009 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -13,7 +13,7 @@ """Base circuit scheduling pass.""" import warnings -from typing import Dict + from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion @@ -59,11 +59,10 @@ def __init__(self, durations: InstructionDurations = None, target: Target = None @staticmethod def _get_node_duration( node: DAGOpNode, - bit_index_map: Dict, dag: DAGCircuit, ) -> int: """A helper method to get duration from node or calibration.""" - indices = [bit_index_map[qarg] for qarg in node.qargs] + indices = [dag.find_bit(qarg).index for qarg in node.qargs] if dag.has_calibration_for(node): # If node has calibration, this value should be the highest priority diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 03019e872ff4..0221f429cad7 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -12,26 +12,34 @@ """Manager for a set of Passes and their scheduling during transpilation.""" from __future__ import annotations +import inspect import io import re +from functools import wraps from collections.abc import Iterator, Iterable, Callable, Sequence from typing import Union, List, Any -import dill - -from qiskit.tools.parallel import parallel_map from qiskit.circuit import QuantumCircuit +from qiskit.passmanager import BasePassManager +from qiskit.passmanager.flow_controllers import PassSequence, FlowController +from qiskit.passmanager.exceptions import PassManagerError from .basepasses import BasePass from .exceptions import TranspilerError -from .runningpassmanager import RunningPassManager, FlowController +from .runningpassmanager import RunningPassManager _CircuitsT = Union[List[QuantumCircuit], QuantumCircuit] -class PassManager: +class PassManager(BasePassManager): """Manager for a set of Passes and their scheduling during transpilation.""" - def __init__(self, passes: BasePass | list[BasePass] | None = None, max_iteration: int = 1000): + PASS_RUNNER = RunningPassManager + + def __init__( + self, + passes: PassSequence | None = None, + max_iteration: int = 1000, + ): """Initialize an empty `PassManager` object (with no passes scheduled). Args: @@ -40,18 +48,12 @@ def __init__(self, passes: BasePass | list[BasePass] | None = None, max_iteratio max_iteration: The maximum number of iterations the schedule will be looped if the condition is not met. """ - # the pass manager's schedule of passes, including any control-flow. - # Populated via PassManager.append(). - - self._pass_sets: list[dict[str, Any]] = [] - if passes is not None: - self.append(passes) - self.max_iteration = max_iteration + super().__init__(passes, max_iteration) self.property_set = None def append( self, - passes: BasePass | Sequence[BasePass | FlowController], + passes: PassSequence, max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -65,26 +67,28 @@ def append( :class:`~qiskit.transpiler.runningpassmanager.FlowController` instance and the rest of the parameter will be ignored. max_iteration: max number of iterations of passes. - flow_controller_conditions: control flow plugins. + flow_controller_conditions: Dictionary of control flow plugins. Default: + + * do_while (callable property_set -> boolean): The passes repeat until the + callable returns False. + Default: `lambda x: False # i.e. passes run once` + + * condition (callable property_set -> boolean): The passes run only if the + callable returns True. + Default: `lambda x: True # i.e. passes run` Raises: TranspilerError: if a pass in passes is not a proper pass. - - See Also: - ``RunningPassManager.add_flow_controller()`` for more information about the control - flow plugins. """ if max_iteration: # TODO remove this argument from append self.max_iteration = max_iteration - - passes = PassManager._normalize_passes(passes) - self._pass_sets.append({"passes": passes, "flow_controllers": flow_controller_conditions}) + super().append(passes, **flow_controller_conditions) def replace( self, index: int, - passes: BasePass | list[BasePass], + passes: PassSequence, max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -99,91 +103,13 @@ def replace( Raises: TranspilerError: if a pass in passes is not a proper pass or index not found. - - See Also: - ``RunningPassManager.add_flow_controller()`` for more information about the control - flow plugins. """ if max_iteration: # TODO remove this argument from append self.max_iteration = max_iteration + super().replace(index, passes, **flow_controller_conditions) - passes = PassManager._normalize_passes(passes) - - try: - self._pass_sets[index] = { - "passes": passes, - "flow_controllers": flow_controller_conditions, - } - except IndexError as ex: - raise TranspilerError(f"Index to replace {index} does not exists") from ex - - def remove(self, index: int) -> None: - """Removes a particular pass in the scheduler. - - Args: - index: Pass index to replace, based on the position in passes(). - - Raises: - TranspilerError: if the index is not found. - """ - try: - del self._pass_sets[index] - except IndexError as ex: - raise TranspilerError(f"Index to replace {index} does not exists") from ex - - def __setitem__(self, index, item): - self.replace(index, item) - - def __len__(self): - return len(self._pass_sets) - - def __getitem__(self, index): - new_passmanager = PassManager(max_iteration=self.max_iteration) - _pass_sets = self._pass_sets[index] - if isinstance(_pass_sets, dict): - _pass_sets = [_pass_sets] - new_passmanager._pass_sets = _pass_sets - return new_passmanager - - def __add__(self, other): - if isinstance(other, PassManager): - new_passmanager = PassManager(max_iteration=self.max_iteration) - new_passmanager._pass_sets = self._pass_sets + other._pass_sets - return new_passmanager - else: - try: - new_passmanager = PassManager(max_iteration=self.max_iteration) - new_passmanager._pass_sets += self._pass_sets - new_passmanager.append(other) - return new_passmanager - except TranspilerError as ex: - raise TypeError( - f"unsupported operand type + for {self.__class__} and {other.__class__}" - ) from ex - - @staticmethod - def _normalize_passes( - passes: BasePass | Sequence[BasePass | FlowController] | FlowController, - ) -> Sequence[BasePass | FlowController] | FlowController: - if isinstance(passes, FlowController): - return passes - if isinstance(passes, BasePass): - passes = [passes] - for pass_ in passes: - if isinstance(pass_, FlowController): - # Normalize passes in nested FlowController. - # TODO: Internal renormalisation should be the responsibility of the - # `FlowController`, but the separation between `FlowController`, - # `RunningPassManager` and `PassManager` is so muddled right now, it would be better - # to do this as part of more top-down refactoring. ---Jake, 2022-10-03. - pass_.passes = PassManager._normalize_passes(pass_.passes) - elif not isinstance(pass_, BasePass): - raise TranspilerError( - "%s is not a BasePass or FlowController instance " % pass_.__class__ - ) - return passes - + # pylint: disable=arguments-differ def run( self, circuits: _CircuitsT, @@ -225,73 +151,30 @@ def callback_func(**kwargs): Returns: The transformed circuit(s). """ - if not self._pass_sets and output_name is None and callback is None: - return circuits - if isinstance(circuits, QuantumCircuit): - return self._run_single_circuit(circuits, output_name, callback) - if len(circuits) == 1: - return [self._run_single_circuit(circuits[0], output_name, callback)] - return self._run_several_circuits(circuits, output_name, callback) + return super().run( + in_programs=circuits, + callback=callback, + output_name=output_name, + ) def _create_running_passmanager(self) -> RunningPassManager: - running_passmanager = RunningPassManager(self.max_iteration) + running_passmanager = self.PASS_RUNNER(self.max_iteration) for pass_set in self._pass_sets: running_passmanager.append(pass_set["passes"], **pass_set["flow_controllers"]) return running_passmanager - @staticmethod - def _in_parallel(circuit, pm_dill=None) -> QuantumCircuit: - """Task used by the parallel map tools from ``_run_several_circuits``.""" - running_passmanager = dill.loads(pm_dill)._create_running_passmanager() - result = running_passmanager.run(circuit) - return result - - def _run_several_circuits( - self, - circuits: List[QuantumCircuit], - output_name: str | None = None, - callback: Callable | None = None, - ) -> List[QuantumCircuit]: - """Run all the passes on the specified ``circuits``. - - Args: - circuits: Circuits to transform via all the registered passes. - output_name: The output circuit name. If ``None``, it will be set to the same as the - input circuit name. - callback: A callback function that will be called after each pass execution. - - Returns: - The transformed circuits. - """ - # TODO support for List(output_name) and List(callback) - del output_name - del callback - - return parallel_map( - PassManager._in_parallel, circuits, task_kwargs={"pm_dill": dill.dumps(self)} - ) - def _run_single_circuit( self, - circuit: QuantumCircuit, - output_name: str | None = None, + input_program: QuantumCircuit, callback: Callable | None = None, + **metadata, ) -> QuantumCircuit: - """Run all the passes on a ``circuit``. - - Args: - circuit: Circuit to transform via all the registered passes. - output_name: The output circuit name. If ``None``, it will be set to the same as the - input circuit name. - callback: A callback function that will be called after each pass execution. + pass_runner = self._create_running_passmanager() + out_program = pass_runner.run(input_program, callback=callback, **metadata) + # Store property set of pass runner for backward compatibility + self.property_set = pass_runner.property_set - Returns: - The transformed circuit. - """ - running_passmanager = self._create_running_passmanager() - result = running_passmanager.run(circuit, output_name=output_name, callback=callback) - self.property_set = running_passmanager.property_set - return result + return out_program def draw(self, filename=None, style=None, raw=False): """Draw the pass manager. @@ -507,7 +390,15 @@ def remove(self, index: int) -> None: def __getitem__(self, index): self._update_passmanager() - return super().__getitem__(index) + + # Do not inherit from the PassManager, i.e. super() + # It returns instance of self.__class__ which is StagedPassManager. + new_passmanager = PassManager(max_iteration=self.max_iteration) + _pass_sets = self._pass_sets[index] + if isinstance(_pass_sets, dict): + _pass_sets = [_pass_sets] + new_passmanager._pass_sets = _pass_sets + return new_passmanager def __len__(self): self._update_passmanager() @@ -541,3 +432,29 @@ def draw(self, filename=None, style=None, raw=False): from qiskit.visualization import staged_pass_manager_drawer return staged_pass_manager_drawer(self, filename=filename, style=style, raw=raw) + + +# A temporary error handling with slight overhead at class loading. +# This method wraps all class methods to replace PassManagerError with TranspilerError. +# The pass flow controller mechanics raises PassManagerError, as it has been moved to base class. +# PassManagerError is not caught by TranspilerError due to the hierarchy. + + +def _replace_error(meth): + @wraps(meth) + def wrapper(*meth_args, **meth_kwargs): + try: + return meth(*meth_args, **meth_kwargs) + except PassManagerError as ex: + raise TranspilerError(ex.message) from ex + + return wrapper + + +for _name, _method in inspect.getmembers(PassManager, predicate=inspect.isfunction): + if _name.startswith("_"): + # Ignore protected and private. + # User usually doesn't directly execute and catch error from these methods. + continue + _wrapped = _replace_error(_method) + setattr(PassManager, _name, _wrapped) diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index e38b1effd57c..4a0ddc10dc0f 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -34,31 +34,27 @@ Preset Pass Manager Generation ------------------------------ -.. autosummary:: - :toctree: ../stubs/ - - generate_preset_pass_manager - level_0_pass_manager - level_1_pass_manager - level_2_pass_manager - level_3_pass_manager +.. autofunction:: generate_preset_pass_manager +.. autofunction:: level_0_pass_manager +.. autofunction:: level_1_pass_manager +.. autofunction:: level_2_pass_manager +.. autofunction:: level_3_pass_manager .. _stage_generators: Stage Generator Functions ------------------------- -.. autosummary:: - :toctree: ../stubs/ - - ~qiskit.transpiler.preset_passmanagers.common.generate_control_flow_options_check - ~qiskit.transpiler.preset_passmanagers.common.generate_error_on_control_flow - ~qiskit.transpiler.preset_passmanagers.common.generate_unroll_3q - ~qiskit.transpiler.preset_passmanagers.common.generate_embed_passmanager - ~qiskit.transpiler.preset_passmanagers.common.generate_routing_passmanager - ~qiskit.transpiler.preset_passmanagers.common.generate_pre_op_passmanager - ~qiskit.transpiler.preset_passmanagers.common.generate_translation_passmanager - ~qiskit.transpiler.preset_passmanagers.common.generate_scheduling +.. currentmodule:: qiskit.transpiler.preset_passmanagers.common +.. autofunction:: generate_control_flow_options_check +.. autofunction:: generate_error_on_control_flow +.. autofunction:: generate_unroll_3q +.. autofunction:: generate_embed_passmanager +.. autofunction:: generate_routing_passmanager +.. autofunction:: generate_pre_op_passmanager +.. autofunction:: generate_translation_passmanager +.. autofunction:: generate_scheduling +.. currentmodule:: qiskit.transpiler.preset_passmanagers """ from qiskit.transpiler.passmanager_config import PassManagerConfig diff --git a/qiskit/transpiler/preset_passmanagers/plugin.py b/qiskit/transpiler/preset_passmanagers/plugin.py index 83d66ceae6a2..53fd7b8389bf 100644 --- a/qiskit/transpiler/preset_passmanagers/plugin.py +++ b/qiskit/transpiler/preset_passmanagers/plugin.py @@ -160,8 +160,9 @@ def pass_manager(self, pass_manager_config, optimization_level): PassManagerStagePlugin PassManagerStagePluginManager - list_stage_plugins - passmanager_stage_plugins + +.. autofunction:: list_stage_plugins +.. autofunction:: passmanager_stage_plugins """ import abc diff --git a/qiskit/transpiler/propertyset.py b/qiskit/transpiler/propertyset.py index 9cba8fc886e3..6244a49d2c28 100644 --- a/qiskit/transpiler/propertyset.py +++ b/qiskit/transpiler/propertyset.py @@ -14,8 +14,9 @@ about the current state of the circuit """ -class PropertySet(dict): - """A default dictionary-like object""" +from qiskit.passmanager import propertyset as passmanager_propertyset - def __missing__(self, key): - return None + +def __getattr__(name): + # Just redirect to new module. This will be deprecated. + return getattr(passmanager_propertyset, name) diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index 6290ed4d52bc..aa574414667a 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -13,57 +13,57 @@ """RunningPassManager class for the transpiler. This object holds the state of a pass manager during running-time.""" from __future__ import annotations -from functools import partial -from collections import OrderedDict import logging -from time import time +import inspect +from functools import partial, wraps +from typing import Callable -from qiskit.dagcircuit import DAGCircuit +from qiskit.circuit import QuantumCircuit from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.dagcircuit import DAGCircuit +from qiskit.passmanager import BasePassRunner +from qiskit.passmanager.flow_controllers import ( + PassSequence, + FlowController, + DoWhileController, + ConditionalController, +) +from qiskit.passmanager.exceptions import PassManagerError from qiskit.transpiler.basepasses import BasePass -from .propertyset import PropertySet -from .fencedobjs import FencedPropertySet, FencedDAGCircuit from .exceptions import TranspilerError +from .fencedobjs import FencedPropertySet, FencedDAGCircuit from .layout import TranspileLayout logger = logging.getLogger(__name__) -class RunningPassManager: +class RunningPassManager(BasePassRunner): """A RunningPassManager is a running pass manager.""" - def __init__(self, max_iteration): + IN_PROGRAM_TYPE = QuantumCircuit + OUT_PROGRAM_TYPE = QuantumCircuit + IR_TYPE = DAGCircuit + + def __init__(self, max_iteration: int): """Initialize an empty PassManager object (with no passes scheduled). Args: - max_iteration (int): The schedule looping iterates until the condition is met or until + max_iteration: The schedule looping iterates until the condition is met or until max_iteration is reached. """ - self.callback = None - # the pass manager's schedule of passes, including any control-flow. - # Populated via PassManager.append(). - self.working_list = [] - - # global property set is the context of the circuit held by the pass manager - # as it runs through its scheduled passes. The flow controller - # have read-only access (via the fenced_property_set). - self.property_set = PropertySet() + super().__init__(max_iteration) self.fenced_property_set = FencedPropertySet(self.property_set) - # passes already run that have not been invalidated - self.valid_passes = set() - - # pass manager's overriding options for the passes it runs (for debugging) - self.passmanager_options = {"max_iteration": max_iteration} - - self.count = 0 - - def append(self, passes: list[BasePass], **flow_controller_conditions): - """Append a Pass to the schedule of passes. + def append( + self, + passes: PassSequence, + **flow_controller_conditions, + ): + """Append a passes to the schedule of passes. Args: - passes (list[TBasePass]): passes to be added to schedule - flow_controller_conditions (kwargs): See add_flow_controller(): Dictionary of + passes: passes to be added to schedule + flow_controller_conditions: See add_flow_controller(): Dictionary of control flow plugins. Default: * do_while (callable property_set -> boolean): The passes repeat until the @@ -73,26 +73,19 @@ def append(self, passes: list[BasePass], **flow_controller_conditions): * condition (callable property_set -> boolean): The passes run only if the callable returns True. Default: `lambda x: True # i.e. passes run` - - Raises: - TranspilerError: if a pass in passes is not a proper pass. """ # attaches the property set to the controller so it has access to it. if isinstance(passes, ConditionalController): passes.condition = partial(passes.condition, self.fenced_property_set) - self.working_list.append(passes) - if isinstance(passes, DoWhileController): + elif isinstance(passes, DoWhileController): if not isinstance(passes.do_while, partial): passes.do_while = partial(passes.do_while, self.fenced_property_set) - self.working_list.append(passes) else: flow_controller_conditions = self._normalize_flow_controller(flow_controller_conditions) - self.working_list.append( - FlowController.controller_factory( - passes, self.passmanager_options, **flow_controller_conditions - ) + passes = FlowController.controller_factory( + passes, self.passmanager_options, **flow_controller_conditions ) - pass + super().append(passes) def _normalize_flow_controller(self, flow_controller): for name, param in flow_controller.items(): @@ -102,33 +95,18 @@ def _normalize_flow_controller(self, flow_controller): raise TranspilerError("The flow controller parameter %s is not callable" % name) return flow_controller - def run(self, circuit, output_name=None, callback=None): - """Run all the passes on a QuantumCircuit + def _to_passmanager_ir(self, in_program: QuantumCircuit) -> DAGCircuit: + if not isinstance(in_program, QuantumCircuit): + raise TranspilerError(f"Input {in_program.__class__} is not QuantumCircuit.") + return circuit_to_dag(in_program) - Args: - circuit (QuantumCircuit): circuit to transform via all the registered passes - output_name (str): The output circuit name. If not given, the same as the - input circuit - callback (callable): A callback function that will be called after each pass execution. - Returns: - QuantumCircuit: Transformed circuit. - """ - name = circuit.name - dag = circuit_to_dag(circuit) - del circuit - - if callback: - self.callback = callback + def _to_target(self, passmanager_ir: DAGCircuit) -> QuantumCircuit: + if not isinstance(passmanager_ir, DAGCircuit): + raise TranspilerError(f"Input {passmanager_ir.__class__} is not DAGCircuit.") - for passset in self.working_list: - for pass_ in passset: - dag = self._do_pass(pass_, dag, passset.options) + circuit = dag_to_circuit(passmanager_ir, copy_operations=False) + circuit.name = self.metadata["output_name"] - circuit = dag_to_circuit(dag, copy_operations=False) - if output_name: - circuit.name = output_name - else: - circuit.name = name if self.property_set["layout"] is not None: circuit._layout = TranspileLayout( initial_layout=self.property_set["layout"], @@ -144,237 +122,118 @@ def run(self, circuit, output_name=None, callback=None): # also converted into list with the same ordering with circuit.data. topological_start_times = [] start_times = self.property_set["node_start_time"] - for dag_node in dag.topological_op_nodes(): + for dag_node in passmanager_ir.topological_op_nodes(): topological_start_times.append(start_times[dag_node]) circuit._op_start_times = topological_start_times return circuit - def _do_pass(self, pass_, dag, options): - """Do either a pass and its "requires" or FlowController. + # pylint: disable=arguments-differ + def run( + self, + circuit: QuantumCircuit, + output_name: str = None, + callback: Callable = None, + ) -> QuantumCircuit: + """Run all the passes on a QuantumCircuit Args: - pass_ (BasePass or FlowController): Pass to do. - dag (DAGCircuit): The dag on which the pass is ran. - options (dict): PassManager options. + circuit: Circuit to transform via all the registered passes. + output_name: The output circuit name. If not given, the same as the input circuit. + callback: A callback function that will be called after each pass execution. + Returns: - DAGCircuit: The transformed dag in case of a transformation pass. - The same input dag in case of an analysis pass. - Raises: - TranspilerError: If the pass is not a proper pass instance. + QuantumCircuit: Transformed circuit. """ - if isinstance(pass_, BasePass): - # First, do the requires of pass_ - for required_pass in pass_.requires: - dag = self._do_pass(required_pass, dag, options) - - # Run the pass itself, if not already run - if pass_ not in self.valid_passes: - dag = self._run_this_pass(pass_, dag) - - # update the valid_passes property - self._update_valid_passes(pass_) - - # if provided a nested flow controller - elif isinstance(pass_, FlowController): - - if isinstance(pass_, ConditionalController) and not isinstance( - pass_.condition, partial - ): - pass_.condition = partial(pass_.condition, self.fenced_property_set) + return super().run( + in_program=circuit, + callback=_rename_callback_args(callback), + output_name=output_name or circuit.name, + ) + + def _run_base_pass( + self, + pass_: BasePass, + passmanager_ir: DAGCircuit, + ) -> DAGCircuit: + """Do either a pass and its "requires" or FlowController. - elif isinstance(pass_, DoWhileController) and not isinstance(pass_.do_while, partial): - pass_.do_while = partial(pass_.do_while, self.fenced_property_set) + Args: + pass_: A base pass to run. + passmanager_ir: Pass manager IR, i.e. DAGCircuit for this class. - for _pass in pass_: - dag = self._do_pass(_pass, dag, pass_.options) - else: - raise TranspilerError( - "Expecting type BasePass or FlowController, got %s." % type(pass_) - ) - return dag + Returns: + The transformed dag in case of a transformation pass. + The same input dag in case of an analysis pass. - def _run_this_pass(self, pass_, dag): + Raises: + TranspilerError: When transform pass returns non DAGCircuit. + TranspilerError: When pass is neither transform pass nor analysis pass. + """ pass_.property_set = self.property_set + if pass_.is_transformation_pass: # Measure time if we have a callback or logging set - start_time = time() - new_dag = pass_.run(dag) - end_time = time() - run_time = end_time - start_time - # Execute the callback function if one is set - if self.callback: - self.callback( - pass_=pass_, - dag=new_dag, - time=run_time, - property_set=self.property_set, - count=self.count, - ) - self.count += 1 - self._log_pass(start_time, end_time, pass_.name()) + new_dag = pass_.run(passmanager_ir) if isinstance(new_dag, DAGCircuit): - new_dag.calibrations = dag.calibrations + new_dag.calibrations = passmanager_ir.calibrations else: raise TranspilerError( "Transformation passes should return a transformed dag." "The pass %s is returning a %s" % (type(pass_).__name__, type(new_dag)) ) - dag = new_dag + passmanager_ir = new_dag elif pass_.is_analysis_pass: # Measure time if we have a callback or logging set - start_time = time() - pass_.run(FencedDAGCircuit(dag)) - end_time = time() - run_time = end_time - start_time - # Execute the callback function if one is set - if self.callback: - self.callback( - pass_=pass_, - dag=dag, - time=run_time, - property_set=self.property_set, - count=self.count, - ) - self.count += 1 - self._log_pass(start_time, end_time, pass_.name()) + pass_.run(FencedDAGCircuit(passmanager_ir)) else: raise TranspilerError("I dont know how to handle this type of pass") - return dag - - def _log_pass(self, start_time, end_time, name): - log_msg = f"Pass: {name} - {(end_time - start_time) * 1000:.5f} (ms)" - logger.info(log_msg) + return passmanager_ir def _update_valid_passes(self, pass_): - self.valid_passes.add(pass_) + super()._update_valid_passes(pass_) if not pass_.is_analysis_pass: # Analysis passes preserve all self.valid_passes.intersection_update(set(pass_.preserves)) -class FlowController: - """Base class for multiple types of working list. - - This class is a base class for multiple types of working list. When you iterate on it, it - returns the next pass to run. - """ - - registered_controllers = OrderedDict() - - def __init__(self, passes, options, **partial_controller): - self._passes = passes - self.passes = FlowController.controller_factory(passes, options, **partial_controller) - self.options = options - - def __iter__(self): - yield from self.passes - - def dump_passes(self): - """Fetches the passes added to this flow controller. - - Returns: - dict: {'options': self.options, 'passes': [passes], 'type': type(self)} - """ - # TODO remove - ret = {"options": self.options, "passes": [], "type": type(self)} - for pass_ in self._passes: - if isinstance(pass_, FlowController): - ret["passes"].append(pass_.dump_passes()) - else: - ret["passes"].append(pass_) - return ret - - @classmethod - def add_flow_controller(cls, name, controller): - """Adds a flow controller. - - Args: - name (string): Name of the controller to add. - controller (type(FlowController)): The class implementing a flow controller. - """ - cls.registered_controllers[name] = controller - - @classmethod - def remove_flow_controller(cls, name): - """Removes a flow controller. - - Args: - name (string): Name of the controller to remove. - Raises: - KeyError: If the controller to remove was not registered. - """ - if name not in cls.registered_controllers: - raise KeyError("Flow controller not found: %s" % name) - del cls.registered_controllers[name] - - @classmethod - def controller_factory(cls, passes: list[BasePass], options, **partial_controller): - """Constructs a flow controller based on the partially evaluated controller arguments. - - Args: - passes (list[TBasePass]): passes to add to the flow controller. - options (dict): PassManager options. - **partial_controller (dict): Partially evaluated controller arguments in the form - `{name:partial}` - - Raises: - TranspilerError: When partial_controller is not well-formed. - - Returns: - FlowController: A FlowController instance. - """ - if None in partial_controller.values(): - raise TranspilerError("The controller needs a condition.") - - if partial_controller: - for registered_controller in cls.registered_controllers.keys(): - if registered_controller in partial_controller: - return cls.registered_controllers[registered_controller]( - passes, options, **partial_controller - ) - raise TranspilerError("The controllers for %s are not registered" % partial_controller) - - return FlowControllerLinear(passes, options) - - -class FlowControllerLinear(FlowController): - """The basic controller runs the passes one after the other.""" - - def __init__(self, passes, options): # pylint: disable=super-init-not-called - self.passes = self._passes = passes - self.options = options - - -class DoWhileController(FlowController): - """Implements a set of passes in a do-while loop.""" - - def __init__(self, passes, options=None, do_while=None, **partial_controller): - self.do_while = do_while - self.max_iteration = options["max_iteration"] if options else 1000 - super().__init__(passes, options, **partial_controller) +def _rename_callback_args(callback): + """A helper function to run callback with conventional argument names.""" + if callback is None: + return callback - def __iter__(self): - for _ in range(self.max_iteration): - yield from self.passes + def _call_with_dag(pass_, passmanager_ir, time, property_set, count): + callback( + pass_=pass_, + dag=passmanager_ir, + time=time, + property_set=property_set, + count=count, + ) - if not self.do_while(): - return + return _call_with_dag - raise TranspilerError("Maximum iteration reached. max_iteration=%i" % self.max_iteration) +# A temporary error handling with slight overhead at class loading. +# This method wraps all class methods to replace PassManagerError with TranspilerError. +# The pass flow controller mechanics raises PassManagerError, as it has been moved to base class. +# PassManagerError is not caught by TranspilerError due to the hierarchy. -class ConditionalController(FlowController): - """Implements a set of passes under a certain condition.""" - def __init__(self, passes, options=None, condition=None, **partial_controller): - self.condition = condition - super().__init__(passes, options, **partial_controller) +def _replace_error(meth): + @wraps(meth) + def wrapper(*meth_args, **meth_kwargs): + try: + return meth(*meth_args, **meth_kwargs) + except PassManagerError as ex: + raise TranspilerError(ex.message) from ex - def __iter__(self): - if self.condition(): - yield from self.passes + return wrapper -# Default controllers -FlowController.add_flow_controller("condition", ConditionalController) -FlowController.add_flow_controller("do_while", DoWhileController) +for _name, _method in inspect.getmembers(RunningPassManager, predicate=inspect.isfunction): + if _name.startswith("_"): + # Ignore protected and private. + # User usually doesn't directly execute and catch error from these methods. + continue + _wrapped = _replace_error(_method) + setattr(RunningPassManager, _name, _wrapped) diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 8d06dd5f48d1..a1329457f8f9 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -18,34 +18,27 @@ .. currentmodule:: qiskit.utils -.. autosummary:: - :toctree: ../stubs/ - - add_deprecation_to_docstring - deprecate_arg - deprecate_arguments - deprecate_func - deprecate_function - local_hardware_info - is_main_process - apply_prefix - detach_prefix - wrap_method +.. autofunction:: add_deprecation_to_docstring +.. autofunction:: deprecate_arg +.. autofunction:: deprecate_arguments +.. autofunction:: deprecate_func +.. autofunction:: deprecate_function +.. autofunction:: local_hardware_info +.. autofunction:: is_main_process +.. autofunction:: apply_prefix +.. autofunction:: detach_prefix +.. autofunction:: wrap_method Algorithm Utilities =================== -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - summarize_circuits - get_entangler_map - validate_entangler_map - has_ibmq - has_aer - name_args - algorithm_globals +.. autofunction:: summarize_circuits +.. autofunction:: get_entangler_map +.. autofunction:: validate_entangler_map +.. autofunction:: has_ibmq +.. autofunction:: has_aer +.. autofunction:: name_args +.. autodata:: algorithm_globals .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/visualization/__init__.py b/qiskit/visualization/__init__.py index 539714f04f19..29eb5cf2c0cc 100644 --- a/qiskit/visualization/__init__.py +++ b/qiskit/visualization/__init__.py @@ -260,10 +260,7 @@ Exceptions ========== -.. autosummary:: - :toctree: ../stubs/ - - VisualizationError +.. autoexception:: VisualizationError """ import os diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index 883e9cb6527c..3d8d5dc79edb 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -25,6 +25,7 @@ Instruction, Measure, ) +from qiskit.circuit.controlflow import condition_resources from qiskit.circuit.library import PauliEvolutionGate from qiskit.circuit import ClassicalRegister, QuantumCircuit, Qubit, ControlFlowOp from qiskit.circuit.tools import pi_check @@ -549,16 +550,11 @@ def slide_from_left(self, node, index): curr_index = index last_insertable_index = -1 index_stop = -1 - if getattr(node.op, "condition", None): - if isinstance(node.op.condition[0], Clbit): - cond_bit = [clbit for clbit in self.clbits if node.op.condition[0] == clbit] - index_stop = self.measure_map[cond_bit[0]] - else: - for bit in node.op.condition[0]: - max_index = -1 - if bit in self.measure_map: - if self.measure_map[bit] > max_index: - index_stop = max_index = self.measure_map[bit] + if (condition := getattr(node.op, "condition", None)) is not None: + index_stop = max( + (self.measure_map[bit] for bit in condition_resources(condition).clbits), + default=index_stop, + ) if node.cargs: for carg in node.cargs: try: diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index be5d2c423d7b..0ba53fea64c3 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -73,6 +73,12 @@ def circuit_drawer( **latex_source**: raw uncompiled latex output. + .. warning:: + + Support for :class:`~.expr.Expr` nodes in conditions and :attr:`.SwitchCaseOp.target` fields + is preliminary and incomplete. The ``text`` and ``mpl`` drawers will make a best-effort + attempt to show data dependencies, but the LaTeX-based drawers will skip these completely. + Args: circuit (QuantumCircuit): the quantum circuit to draw scale (float): scale of image to draw (shrink if < 1.0). Only used by diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py index f7743f59ad88..67a6afe387c8 100644 --- a/qiskit/visualization/circuit/latex.py +++ b/qiskit/visualization/circuit/latex.py @@ -20,6 +20,7 @@ import numpy as np from qiskit.circuit import Clbit, Qubit, ClassicalRegister, QuantumRegister, QuantumCircuit +from qiskit.circuit.classical import expr from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.library.standard_gates import SwapGate, XGate, ZGate, RZZGate, U1Gate, PhaseGate from qiskit.circuit.measure import Measure @@ -416,7 +417,10 @@ def _build_latex_array(self): num_cols_op = 1 wire_list = [self._wire_map[qarg] for qarg in node.qargs if qarg in self._qubits] if getattr(op, "condition", None): - self._add_condition(op, wire_list, column) + if isinstance(op.condition, expr.Expr): + warn("ignoring expression condition, which is not supported yet") + else: + self._add_condition(op, wire_list, column) if isinstance(op, Measure): self._build_measure(node, column) @@ -619,7 +623,6 @@ def _add_condition(self, op, wire_list, col): # cwire - the wire number for the first wire for the condition register # or if cregbundle, wire number of the condition register itself # gap - the number of wires from cwire to the bottom gate qubit - label, val_bits = get_condition_label_val(op.condition, self._circuit, self._cregbundle) cond_is_bit = isinstance(op.condition[0], Clbit) cond_reg = op.condition[0] diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 983311f067c8..fb61c7cf6d3b 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -14,6 +14,7 @@ """mpl circuit visualization backend.""" +import collections import itertools import re from warnings import warn @@ -33,6 +34,8 @@ ForLoopOp, SwitchCaseOp, ) +from qiskit.circuit.controlflow import condition_resources +from qiskit.circuit.classical import expr from qiskit.circuit.library.standard_gates import ( SwapGate, RZZGate, @@ -294,9 +297,7 @@ def draw(self, filename=None, verbose=False): # load the wire map wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) - # node_data per node with "width", "gate_text", "raw_gate_text", "ctrl_text", - # "param_text", "nest_depth", "inside_flow", "x_index", "indexset", "jump_values", - # "case_num", "q_xy", "c_xy", and colors "fc", "ec", "lc", "sc", "gt", and "tc" + # node_data per node filled with class NodeData attributes node_data = {} # dicts for the names and locations of register/bit labels @@ -412,23 +413,23 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): layer_widths[node] = [1, layer_num, self._flow_parent] op = node.op - node_data[node] = {} - node_data[node]["width"] = WID + node_data[node] = NodeData() + node_data[node].width = WID num_ctrl_qubits = 0 if not hasattr(op, "num_ctrl_qubits") else op.num_ctrl_qubits if ( getattr(op, "_directive", False) and (not op.label or not self._plot_barriers) ) or isinstance(op, Measure): - node_data[node]["raw_gate_text"] = op.name + node_data[node].raw_gate_text = op.name continue base_type = None if not hasattr(op, "base_gate") else op.base_gate gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text( op, "mpl", style=self._style, calibrations=self._calibrations ) - node_data[node]["gate_text"] = gate_text - node_data[node]["ctrl_text"] = ctrl_text - node_data[node]["raw_gate_text"] = raw_gate_text - node_data[node]["param_text"] = "" + node_data[node].gate_text = gate_text + node_data[node].ctrl_text = ctrl_text + node_data[node].raw_gate_text = raw_gate_text + node_data[node].param_text = "" # if single qubit, no params, and no labels, layer_width is 1 if ( @@ -457,7 +458,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): param_text = get_param_str(op, "mpl", ndigits=3) if isinstance(op, Initialize): param_text = f"$[{param_text.replace('$', '')}]$" - node_data[node]["param_text"] = param_text + node_data[node].param_text = param_text raw_param_width = self._get_text_width( param_text, glob_data, fontsize=self._style["sfs"], param=True ) @@ -480,8 +481,8 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # Check if a ControlFlowOp - node_data load for these gates is done here elif isinstance(node.op, ControlFlowOp): self._flow_drawers[node] = [] - node_data[node]["width"] = [] - node_data[node]["nest_depth"] = 0 + node_data[node].width = [] + node_data[node].nest_depth = 0 gate_width = 0.0 # Get the list of circuits to iterate over from the blocks @@ -490,15 +491,15 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # params is [indexset, loop_param, circuit] for for_loop, # op.cases_specifier() returns jump tuple and circuit for switch/case if isinstance(op, ForLoopOp): - node_data[node]["indexset"] = op.params[0] + node_data[node].indexset = op.params[0] elif isinstance(op, SwitchCaseOp): - node_data[node]["jump_values"] = [] + node_data[node].jump_values = [] cases = list(op.cases_specifier()) # Create an empty circuit at the head of the circuit_list if a Switch box circuit_list.insert(0, cases[0][1].copy_empty_like()) for jump_values, _ in cases: - node_data[node]["jump_values"].append(jump_values) + node_data[node].jump_values.append(jump_values) # Now process the circuits inside the ControlFlowOps for circ_num, circuit in enumerate(circuit_list): @@ -506,9 +507,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # Depth of nested ControlFlowOp used for color of box if self._flow_parent is not None: - node_data[node]["nest_depth"] = ( - node_data[self._flow_parent]["nest_depth"] + 1 - ) + node_data[node].nest_depth = node_data[self._flow_parent].nest_depth + 1 # Update the wire_map with the qubits from the inner circuit flow_wire_map = { inner: wire_map[outer] @@ -547,7 +546,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): for flow_layer in nodes: for flow_node in flow_layer: if isinstance(node.op, SwitchCaseOp): - node_data[flow_node]["case_num"] = circ_num + node_data[flow_node].case_num = circ_num # Add up the width values of the same flow_parent that are not -1 # to get the raw_gate_width @@ -561,7 +560,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # Minor adjustment so else and case section gates align with indexes if circ_num > 0: raw_gate_width += 0.045 - node_data[node]["width"].append(raw_gate_width) + node_data[node].width.append(raw_gate_width) # Otherwise, standard gate or multiqubit gate else: @@ -577,7 +576,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): if box_width > widest_box: widest_box = box_width if not isinstance(node.op, ControlFlowOp): - node_data[node]["width"] = max(raw_gate_width, raw_param_width) + node_data[node].width = max(raw_gate_width, raw_param_width) for node in layer: layer_widths[node][0] = int(widest_box) + 1 @@ -682,19 +681,15 @@ def _get_coords( # else or case increment by if width. For additional cases increment by # width of previous cases. if flow_parent is not None: - node_data[node]["x_index"] = ( - node_data[flow_parent]["x_index"] + curr_x_index + 1 - ) + node_data[node].x_index = node_data[flow_parent].x_index + curr_x_index + 1 if is_not_first_block: # Add index space for else or first case if switch/case - node_data[node]["x_index"] += int(node_data[flow_parent]["width"][0]) + 1 + node_data[node].x_index += int(node_data[flow_parent].width[0]) + 1 # Add index space for remaining cases for switch/case - if "case_num" in node_data[node] and node_data[node]["case_num"] > 1: - for width in node_data[flow_parent]["width"][ - 1 : node_data[node]["case_num"] - ]: - node_data[node]["x_index"] += int(width) + 1 + if node_data[node].case_num > 1: + for width in node_data[flow_parent].width[1 : node_data[node].case_num]: + node_data[node].x_index += int(width) + 1 # get qubit indexes q_indxs = [] @@ -714,14 +709,14 @@ def _get_coords( flow_op = isinstance(node.op, ControlFlowOp) if flow_parent is not None: - node_data[node]["inside_flow"] = True - x_index = node_data[node]["x_index"] + node_data[node].inside_flow = True + x_index = node_data[node].x_index else: - node_data[node]["inside_flow"] = False + node_data[node].inside_flow = False x_index = curr_x_index # qubit coordinates - node_data[node]["q_xy"] = [ + node_data[node].q_xy = [ self._plot_coord( x_index, qubits_dict[ii]["y"], @@ -732,7 +727,7 @@ def _get_coords( for ii in q_indxs ] # clbit coordinates - node_data[node]["c_xy"] = [ + node_data[node].c_xy = [ self._plot_coord( x_index, clbits_dict[ii]["y"], @@ -747,7 +742,7 @@ def _get_coords( if flow_parent is None: curr_x_index = glob_data["next_x_index"] l_width.append(layer_widths[node][0]) - node_data[node]["x_index"] = x_index + node_data[node].x_index = x_index # adjust the column if there have been barriers encountered, but not plotted barrier_offset = 0 @@ -996,7 +991,7 @@ def _draw_ops( if getattr(op, "condition", None) or isinstance(op, SwitchCaseOp): cond_xy = [ self._plot_coord( - node_data[node]["x_index"], + node_data[node].x_index, clbits_dict[ii]["y"], layer_widths[node][0], glob_data, @@ -1020,7 +1015,7 @@ def _draw_ops( self._flow_op_gate(node, node_data, glob_data) # draw single qubit gates - elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: + elif len(node_data[node].q_xy) == 1 and not node.cargs: self._gate(node, node_data, glob_data) # draw controlled gates @@ -1032,7 +1027,7 @@ def _draw_ops( self._multiqubit_gate(node, node_data, glob_data) # Determine the max width of the circuit only at the top level - if not node_data[node]["inside_flow"]: + if not node_data[node].inside_flow: l_width.append(layer_widths[node][0]) # adjust the column if there have been barriers encountered, but not plotted @@ -1050,8 +1045,8 @@ def _get_colors(self, node, node_data): op = node.op base_name = None if not hasattr(op, "base_gate") else op.base_gate.name color = None - if node_data[node]["raw_gate_text"] in self._style["dispcol"]: - color = self._style["dispcol"][node_data[node]["raw_gate_text"]] + if node_data[node].raw_gate_text in self._style["dispcol"]: + color = self._style["dispcol"][node_data[node].raw_gate_text] elif op.name in self._style["dispcol"]: color = self._style["dispcol"][op.name] if color is not None: @@ -1085,12 +1080,12 @@ def _get_colors(self, node, node_data): lc = fc # Subtext needs to be same color as gate text sc = gt - node_data[node]["fc"] = fc - node_data[node]["ec"] = ec - node_data[node]["gt"] = gt - node_data[node]["tc"] = self._style["tc"] - node_data[node]["sc"] = sc - node_data[node]["lc"] = lc + node_data[node].fc = fc + node_data[node].ec = ec + node_data[node].gt = gt + node_data[node].tc = self._style["tc"] + node_data[node].sc = sc + node_data[node].lc = lc def _condition(self, node, node_data, wire_map, cond_xy, glob_data): """Add a conditional to a gate""" @@ -1098,45 +1093,66 @@ def _condition(self, node, node_data, wire_map, cond_xy, glob_data): # For SwitchCaseOp convert the target to a fully closed Clbit or register # in condition format if isinstance(node.op, SwitchCaseOp): - if isinstance(node.op.target, Clbit): + if isinstance(node.op.target, expr.Expr): + condition = node.op.target + elif isinstance(node.op.target, Clbit): condition = (node.op.target, 1) else: condition = (node.op.target, 2 ** (node.op.target.size) - 1) else: condition = node.op.condition - label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle) - cond_bit_reg = condition[0] - cond_bit_val = int(condition[1]) + override_fc = False first_clbit = len(self._qubits) cond_pos = [] - # In the first case, multiple bits are indicated on the drawing. In all - # other cases, only one bit is shown. - if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister): - for idx in range(cond_bit_reg.size): - cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit]) - - # If it's a register bit and cregbundle, need to use the register to find the location - elif self._cregbundle and isinstance(cond_bit_reg, Clbit): - register = get_bit_register(self._circuit, cond_bit_reg) - if register is not None: - cond_pos.append(cond_xy[wire_map[register] - first_clbit]) + if isinstance(condition, expr.Expr): + # If fixing this, please update the docstrings of `QuantumCircuit.draw` and + # `visualization.circuit_drawer` to remove warnings. + condition_bits = condition_resources(condition).clbits + label = "[expression]" + override_fc = True + registers = collections.defaultdict(list) + for bit in condition_bits: + registers[get_bit_register(self._circuit, bit)].append(bit) + # Registerless bits don't care whether cregbundle is set. + cond_pos.extend(cond_xy[wire_map[bit] - first_clbit] for bit in registers.pop(None, ())) + if self._cregbundle: + cond_pos.extend( + cond_xy[wire_map[register[0]] - first_clbit] for register in registers + ) else: - cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) + cond_pos.extend( + cond_xy[wire_map[bit] - first_clbit] + for register, bits in registers.items() + for bit in bits + ) + val_bits = ["1"] * len(cond_pos) else: - cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) + label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle) + cond_bit_reg = condition[0] + cond_bit_val = int(condition[1]) + override_fc = cond_bit_val != 0 + + # In the first case, multiple bits are indicated on the drawing. In all + # other cases, only one bit is shown. + if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister): + for idx in range(cond_bit_reg.size): + cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit]) + + # If it's a register bit and cregbundle, need to use the register to find the location + elif self._cregbundle and isinstance(cond_bit_reg, Clbit): + register = get_bit_register(self._circuit, cond_bit_reg) + if register is not None: + cond_pos.append(cond_xy[wire_map[register] - first_clbit]) + else: + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) + else: + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) xy_plot = [] - for idx, xy in enumerate(cond_pos): - if val_bits[idx] == "1" or ( - isinstance(cond_bit_reg, ClassicalRegister) - and cond_bit_val != 0 - and self._cregbundle - ): - fc = self._style["lc"] - else: - fc = self._style["bg"] + for val_bit, xy in zip(val_bits, cond_pos): + fc = self._style["lc"] if override_fc or val_bit == "1" else self._style["bg"] box = glob_data["patches_mod"].Circle( xy=xy, radius=WID * 0.15, @@ -1148,7 +1164,7 @@ def _condition(self, node, node_data, wire_map, cond_xy, glob_data): self._ax.add_patch(box) xy_plot.append(xy) - qubit_b = min(node_data[node]["q_xy"], key=lambda xy: xy[1]) + qubit_b = min(node_data[node].q_xy, key=lambda xy: xy[1]) clbit_b = min(xy_plot, key=lambda xy: xy[1]) # For IfElseOp, WhileLoopOp or SwitchCaseOp, place the condition @@ -1175,8 +1191,8 @@ def _condition(self, node, node_data, wire_map, cond_xy, glob_data): def _measure(self, node, node_data, glob_data): """Draw the measure symbol and the line to the clbit""" - qx, qy = node_data[node]["q_xy"][0] - cx, cy = node_data[node]["c_xy"][0] + qx, qy = node_data[node].q_xy[0] + cx, cy = node_data[node].c_xy[0] register, _, reg_index = get_bit_reg_index(self._circuit, node.cargs[0]) # draw gate box @@ -1190,7 +1206,7 @@ def _measure(self, node, node_data, glob_data): theta1=0, theta2=180, fill=False, - ec=node_data[node]["gt"], + ec=node_data[node].gt, linewidth=self._lwidth2, zorder=PORDER_GATE, ) @@ -1198,13 +1214,13 @@ def _measure(self, node, node_data, glob_data): self._ax.plot( [qx, qx + 0.35 * WID], [qy - 0.15 * HIG, qy + 0.20 * HIG], - color=node_data[node]["gt"], + color=node_data[node].gt, linewidth=self._lwidth2, zorder=PORDER_GATE, ) # arrow self._line( - node_data[node]["q_xy"][0], + node_data[node].q_xy[0], [cx, cy + 0.35 * WID], lc=self._style["cc"], ls=self._style["cline"], @@ -1235,7 +1251,7 @@ def _measure(self, node, node_data, glob_data): def _barrier(self, node, node_data, glob_data): """Draw a barrier""" - for i, xy in enumerate(node_data[node]["q_xy"]): + for i, xy in enumerate(node_data[node].q_xy): xpos, ypos = xy # For the topmost barrier, reduce the rectangle if there's a label to allow for the text. if i == 0 and node.op.label is not None: @@ -1272,7 +1288,7 @@ def _barrier(self, node, node_data, glob_data): ha="center", va="top", fontsize=self._style["fs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_TEXT, ) @@ -1280,44 +1296,44 @@ def _barrier(self, node, node_data, glob_data): def _gate(self, node, node_data, glob_data, xy=None): """Draw a 1-qubit gate""" if xy is None: - xy = node_data[node]["q_xy"][0] + xy = node_data[node].q_xy[0] xpos, ypos = xy - wid = max(node_data[node]["width"], WID) + wid = max(node_data[node].width, WID) box = glob_data["patches_mod"].Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=HIG, - fc=node_data[node]["fc"], - ec=node_data[node]["ec"], + fc=node_data[node].fc, + ec=node_data[node].ec, linewidth=self._lwidth15, zorder=PORDER_GATE, ) self._ax.add_patch(box) - if "gate_text" in node_data[node]: + if node_data[node].gate_text: gate_ypos = ypos - if "param_text" in node_data[node] and node_data[node]["param_text"] != "": + if node_data[node].param_text: gate_ypos = ypos + 0.15 * HIG self._ax.text( xpos, ypos - 0.3 * HIG, - node_data[node]["param_text"], + node_data[node].param_text, ha="center", va="center", fontsize=self._style["sfs"], - color=node_data[node]["sc"], + color=node_data[node].sc, clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos, gate_ypos, - node_data[node]["gate_text"], + node_data[node].gate_text, ha="center", va="center", fontsize=self._style["fs"], - color=node_data[node]["gt"], + color=node_data[node].gt, clip_on=True, zorder=PORDER_TEXT, ) @@ -1326,11 +1342,11 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): """Draw a gate covering more than one qubit""" op = node.op if xy is None: - xy = node_data[node]["q_xy"] + xy = node_data[node].q_xy # Swap gate if isinstance(op, SwapGate): - self._swap(xy, node, node_data, node_data[node]["lc"]) + self._swap(xy, node, node_data, node_data[node].lc) return # RZZ Gate @@ -1338,7 +1354,7 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): self._symmetric_gate(node, node_data, RZZGate, glob_data) return - c_xy = node_data[node]["c_xy"] + c_xy = node_data[node].c_xy xpos = min(x[0] for x in xy) ypos = min(y[1] for y in xy) ypos_max = max(y[1] for y in xy) @@ -1347,7 +1363,7 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): cypos = min(y[1] for y in c_xy) ypos = min(ypos, cypos) - wid = max(node_data[node]["width"] + 0.21, WID) + wid = max(node_data[node].width + 0.21, WID) qubit_span = abs(ypos) - abs(ypos_max) height = HIG + qubit_span @@ -1355,8 +1371,8 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=height, - fc=node_data[node]["fc"], - ec=node_data[node]["ec"], + fc=node_data[node].fc, + ec=node_data[node].ec, linewidth=self._lwidth15, zorder=PORDER_GATE, ) @@ -1371,7 +1387,7 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): ha="left", va="center", fontsize=self._style["fs"], - color=node_data[node]["gt"], + color=node_data[node].gt, clip_on=True, zorder=PORDER_TEXT, ) @@ -1385,48 +1401,48 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): ha="left", va="center", fontsize=self._style["fs"], - color=node_data[node]["gt"], + color=node_data[node].gt, clip_on=True, zorder=PORDER_TEXT, ) - if "gate_text" in node_data[node] and node_data[node]["gate_text"] != "": + if node_data[node].gate_text: gate_ypos = ypos + 0.5 * qubit_span - if "param_text" in node_data[node] and node_data[node]["param_text"] != "": + if node_data[node].param_text: gate_ypos = ypos + 0.4 * height self._ax.text( xpos + 0.11, ypos + 0.2 * height, - node_data[node]["param_text"], + node_data[node].param_text, ha="center", va="center", fontsize=self._style["sfs"], - color=node_data[node]["sc"], + color=node_data[node].sc, clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos + 0.11, gate_ypos, - node_data[node]["gate_text"], + node_data[node].gate_text, ha="center", va="center", fontsize=self._style["fs"], - color=node_data[node]["gt"], + color=node_data[node].gt, clip_on=True, zorder=PORDER_TEXT, ) def _flow_op_gate(self, node, node_data, glob_data): """Draw the box for a flow op circuit""" - xy = node_data[node]["q_xy"] + xy = node_data[node].q_xy xpos = min(x[0] for x in xy) ypos = min(y[1] for y in xy) ypos_max = max(y[1] for y in xy) - if_width = node_data[node]["width"][0] + WID + if_width = node_data[node].width[0] + WID box_width = if_width # Add the else and case widths to the if_width - for ewidth in node_data[node]["width"][1:]: + for ewidth in node_data[node].width[1:]: if ewidth > 0.0: box_width += ewidth + WID + 0.3 @@ -1458,7 +1474,7 @@ def _flow_op_gate(self, node, node_data, glob_data): height=height, boxstyle="round, pad=0.1", fc="none", - ec=colors[node_data[node]["nest_depth"] % 4], + ec=colors[node_data[node].nest_depth % 4], linewidth=self._lwidth3, zorder=PORDER_FLOW, ) @@ -1480,12 +1496,12 @@ def _flow_op_gate(self, node, node_data, glob_data): ha="left", va="center", fontsize=self._style["fs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_FLOW, ) if isinstance(node.op, ForLoopOp): - idx_set = str(node_data[node]["indexset"]) + idx_set = str(node_data[node].indexset) # If a range was used display 'range' and grab the range value # to be displayed below if "range" in idx_set: @@ -1498,13 +1514,13 @@ def _flow_op_gate(self, node, node_data, glob_data): ha="left", va="center", fontsize=self._style["sfs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_FLOW, ) else: # If a tuple, show first 4 elements followed by '...' - idx_set = str(node_data[node]["indexset"])[1:-1].split(",")[:5] + idx_set = str(node_data[node].indexset)[1:-1].split(",")[:5] if len(idx_set) > 4: idx_set[4] = "..." idx_set = f"{', '.join(idx_set)}" @@ -1515,19 +1531,19 @@ def _flow_op_gate(self, node, node_data, glob_data): ha="left", va="center", fontsize=self._style["sfs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_FLOW, ) # If there's an else or a case draw the vertical line and the name else_case_text = "Else" if isinstance(node.op, IfElseOp) else "Case" ewidth_incr = if_width - for case_num, ewidth in enumerate(node_data[node]["width"][1:]): + for case_num, ewidth in enumerate(node_data[node].width[1:]): if ewidth > 0.0: self._ax.plot( [xpos + ewidth_incr + 0.3 - x_shift, xpos + ewidth_incr + 0.3 - x_shift], [ypos - 0.5 * HIG - 0.08 - y_shift, ypos + height - 0.22 - y_shift], - color=colors[node_data[node]["nest_depth"] % 4], + color=colors[node_data[node].nest_depth % 4], linewidth=3.0, linestyle="solid", zorder=PORDER_FLOW, @@ -1539,12 +1555,12 @@ def _flow_op_gate(self, node, node_data, glob_data): ha="left", va="center", fontsize=self._style["fs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_FLOW, ) if isinstance(node.op, SwitchCaseOp): - jump_val = node_data[node]["jump_values"][case_num] + jump_val = node_data[node].jump_values[case_num] # If only one value, e.g. (0,) if len(str(jump_val)) == 4: jump_text = str(jump_val)[1] @@ -1563,7 +1579,7 @@ def _flow_op_gate(self, node, node_data, glob_data): ha="left", va="center", fontsize=self._style["sfs"], - color=node_data[node]["tc"], + color=node_data[node].tc, clip_on=True, zorder=PORDER_FLOW, ) @@ -1574,7 +1590,7 @@ def _flow_op_gate(self, node, node_data, glob_data): def _control_gate(self, node, node_data, glob_data): """Draw a controlled gate""" op = node.op - xy = node_data[node]["q_xy"] + xy = node_data[node].q_xy base_type = None if not hasattr(op, "base_gate") else op.base_gate qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) @@ -1585,12 +1601,12 @@ def _control_gate(self, node, node_data, glob_data): num_ctrl_qubits, xy, glob_data, - ec=node_data[node]["ec"], - tc=node_data[node]["tc"], - text=node_data[node]["ctrl_text"], + ec=node_data[node].ec, + tc=node_data[node].tc, + text=node_data[node].ctrl_text, qargs=node.qargs, ) - self._line(qubit_b, qubit_t, lc=node_data[node]["lc"]) + self._line(qubit_b, qubit_t, lc=node_data[node].lc) if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)): self._symmetric_gate(node, node_data, base_type, glob_data) @@ -1598,13 +1614,13 @@ def _control_gate(self, node, node_data, glob_data): elif num_qargs == 1 and isinstance(base_type, XGate): tgt_color = self._style["dispcol"]["target"] tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0] - self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node]["ec"], ac=tgt) + self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node].ec, ac=tgt) elif num_qargs == 1: self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0]) elif isinstance(base_type, SwapGate): - self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node]["lc"]) + self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node].lc) else: self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:]) @@ -1707,13 +1723,13 @@ def _x_tgt_qubit(self, xy, glob_data, ec=None, ac=None): def _symmetric_gate(self, node, node_data, base_type, glob_data): """Draw symmetric gates for cz, cu1, cp, and rzz""" op = node.op - xy = node_data[node]["q_xy"] + xy = node_data[node].q_xy qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) base_type = None if not hasattr(op, "base_gate") else op.base_gate - ec = node_data[node]["ec"] - tc = node_data[node]["tc"] - lc = node_data[node]["lc"] + ec = node_data[node].ec + tc = node_data[node].tc + lc = node_data[node].lc # cz and mcz gates if not isinstance(op, ZGate) and isinstance(base_type, ZGate): @@ -1724,7 +1740,7 @@ def _symmetric_gate(self, node, node_data, base_type, glob_data): # cu1, cp, rzz, and controlled rzz gates (sidetext gates) elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)): num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits - gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node]["gate_text"] + gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node].gate_text self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc) if not isinstance(base_type, (U1Gate, PhaseGate)): @@ -1735,7 +1751,7 @@ def _symmetric_gate(self, node, node_data, base_type, glob_data): node_data, qubit_b, tc=tc, - text=f"{gate_text} ({node_data[node]['param_text']})", + text=f"{gate_text} ({node_data[node].param_text})", ) self._line(qubit_b, qubit_t, lc=lc) @@ -1746,8 +1762,8 @@ def _swap(self, xy, node, node_data, color=None): self._line(xy[0], xy[1], lc=color) # add calibration text - gate_text = node_data[node]["gate_text"].split("\n")[-1] - if node_data[node]["raw_gate_text"] in self._calibrations: + gate_text = node_data[node].gate_text.split("\n")[-1] + if node_data[node].raw_gate_text in self._calibrations: xpos, ypos = xy[0] self._ax.text( xpos, @@ -1785,7 +1801,7 @@ def _sidetext(self, node, node_data, xy, tc=None, text=""): xpos, ypos = xy # 0.11 = the initial gap, add 1/2 text width to place on the right - xp = xpos + 0.11 + node_data[node]["width"] / 2 + xp = xpos + 0.11 + node_data[node].width / 2 self._ax.text( xp, ypos + HIG, @@ -1855,3 +1871,30 @@ def _plot_coord(self, x_index, y_index, gate_width, glob_data, flow_op=False): # x_index could have been updated, so need to store glob_data["next_x_index"] = x_index return x_pos, y_pos + + +class NodeData: + """Class containing drawing data on a per node basis""" + + def __init__(self): + # Node data for positioning + self.width = 0.0 + self.x_index = 0 + self.q_xy = [] + self.c_xy = [] + + # Node data for text + self.gate_text = "" + self.raw_gate_text = "" + self.ctrl_text = "" + self.param_text = "" + + # Node data for color + self.fc = self.ec = self.lc = self.sc = self.gt = self.tc = 0 + + # Special values stored for ControlFlowOps + self.nest_depth = 0 + self.inside_flow = False + self.indexset = () # List of indices used for ForLoopOp + self.jump_values = [] # List of jump values used for SwitchCaseOp + self.case_num = 0 # Used for SwitchCaseOp diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py index f844a5e7ca25..10777e6de272 100644 --- a/qiskit/visualization/circuit/text.py +++ b/qiskit/visualization/circuit/text.py @@ -16,12 +16,15 @@ from warnings import warn from shutil import get_terminal_size +import collections import itertools import sys from qiskit.circuit import Qubit, Clbit, ClassicalRegister from qiskit.circuit import ControlledGate, Reset, Measure from qiskit.circuit import ControlFlowOp, WhileLoopOp, IfElseOp, ForLoopOp, SwitchCaseOp +from qiskit.circuit.classical import expr +from qiskit.circuit.controlflow import node_resources from qiskit.circuit.library.standard_gates import IGate, RZZGate, SwapGate, SXGate, SXdgGate from qiskit.circuit.tools.pi_check import pi_check @@ -1616,6 +1619,27 @@ def set_cl_multibox(self, condition, top_connect="┴"): Returns: List: list of tuples of connections between clbits for multi-bit conditions """ + if isinstance(condition, expr.Expr): + # If fixing this, please update the docstrings of `QuantumCircuit.draw` and + # `visualization.circuit_drawer` to remove warnings. + label = "" + out = [] + condition_bits = node_resources(condition).clbits + registers = collections.defaultdict(list) + for bit in condition_bits: + registers[get_bit_register(self._circuit, bit)].append(bit) + if registerless := registers.pop(None, ()): + out.extend(self.set_cond_bullets(label, ["1"] * len(registerless), registerless)) + if self.cregbundle: + # It's hard to do something properly sensible here without more major rewrites, so + # as a minimum to *not crash* we'll just treat a condition that touches part of a + # register like it touched the whole register. + for register in registers: + self.set_clbit(register[0], BoxOnClWire(label=label, top_connect=top_connect)) + else: + for register, bits in registers.items(): + out.extend(self.set_cond_bullets(label, ["1"] * len(bits), bits)) + return out label, val_bits = get_condition_label_val(condition, self._circuit, self.cregbundle) if isinstance(condition[0], ClassicalRegister): cond_reg = condition[0] diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 62995cb6de5f..8e3de6e1186a 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -105,8 +105,6 @@ def node_attr_func(node): edge_attr_func = None else: - qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)} register_bit_labels = { bit: f"{reg.name}[{idx}]" for reg in list(dag.qregs.values()) + list(dag.cregs.values()) @@ -127,18 +125,26 @@ def node_attr_func(node): n["fillcolor"] = "lightblue" if isinstance(node, DAGInNode): if isinstance(node.wire, Qubit): - label = register_bit_labels.get(node.wire, f"q_{qubit_indices[node.wire]}") + label = register_bit_labels.get( + node.wire, f"q_{dag.find_bit(node.wire).index}" + ) else: - label = register_bit_labels.get(node.wire, f"c_{clbit_indices[node.wire]}") + label = register_bit_labels.get( + node.wire, f"c_{dag.find_bit(node.wire).index}" + ) n["label"] = label n["color"] = "black" n["style"] = "filled" n["fillcolor"] = "green" if isinstance(node, DAGOutNode): if isinstance(node.wire, Qubit): - label = register_bit_labels.get(node.wire, f"q[{qubit_indices[node.wire]}]") + label = register_bit_labels.get( + node.wire, f"q[{dag.find_bit(node.wire).index}]" + ) else: - label = register_bit_labels.get(node.wire, f"c[{clbit_indices[node.wire]}]") + label = register_bit_labels.get( + node.wire, f"c[{dag.find_bit(node.wire).index}]" + ) n["label"] = label n["color"] = "black" n["style"] = "filled" @@ -150,9 +156,9 @@ def node_attr_func(node): def edge_attr_func(edge): e = {} if isinstance(edge, Qubit): - label = register_bit_labels.get(edge, f"q_{qubit_indices[edge]}") + label = register_bit_labels.get(edge, f"q_{dag.find_bit(edge).index}") else: - label = register_bit_labels.get(edge, f"c_{clbit_indices[edge]}") + label = register_bit_labels.get(edge, f"c_{dag.find_bit(edge).index}") e["label"] = label return e diff --git a/releasenotes/notes/433_qubit_coordinates_map-8abc318fefdb99ac.yaml b/releasenotes/notes/0.25/433_qubit_coordinates_map-8abc318fefdb99ac.yaml similarity index 100% rename from releasenotes/notes/433_qubit_coordinates_map-8abc318fefdb99ac.yaml rename to releasenotes/notes/0.25/433_qubit_coordinates_map-8abc318fefdb99ac.yaml diff --git a/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml b/releasenotes/notes/0.25/add-abs-to-parameterexpression-347ffef62946b38b.yaml similarity index 100% rename from releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml rename to releasenotes/notes/0.25/add-abs-to-parameterexpression-347ffef62946b38b.yaml diff --git a/releasenotes/notes/add-block-collection-options-359d5e496313acdb.yaml b/releasenotes/notes/0.25/add-block-collection-options-359d5e496313acdb.yaml similarity index 100% rename from releasenotes/notes/add-block-collection-options-359d5e496313acdb.yaml rename to releasenotes/notes/0.25/add-block-collection-options-359d5e496313acdb.yaml diff --git a/releasenotes/notes/add-classical-predecessors-9ecef0561822e934.yaml b/releasenotes/notes/0.25/add-classical-predecessors-9ecef0561822e934.yaml similarity index 100% rename from releasenotes/notes/add-classical-predecessors-9ecef0561822e934.yaml rename to releasenotes/notes/0.25/add-classical-predecessors-9ecef0561822e934.yaml diff --git a/releasenotes/notes/add-control-flow-to-commutative-cancellation-pass-85fe310d911d9a00.yaml b/releasenotes/notes/0.25/add-control-flow-to-commutative-cancellation-pass-85fe310d911d9a00.yaml similarity index 100% rename from releasenotes/notes/add-control-flow-to-commutative-cancellation-pass-85fe310d911d9a00.yaml rename to releasenotes/notes/0.25/add-control-flow-to-commutative-cancellation-pass-85fe310d911d9a00.yaml diff --git a/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml b/releasenotes/notes/0.25/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml similarity index 100% rename from releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml rename to releasenotes/notes/0.25/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml diff --git a/releasenotes/notes/add-dag-causal-cone-5a19311e40fbb3af.yaml b/releasenotes/notes/0.25/add-dag-causal-cone-5a19311e40fbb3af.yaml similarity index 100% rename from releasenotes/notes/add-dag-causal-cone-5a19311e40fbb3af.yaml rename to releasenotes/notes/0.25/add-dag-causal-cone-5a19311e40fbb3af.yaml diff --git a/releasenotes/notes/add-diagonal-to-DiagonalGate-c945e0f8adcd2940.yaml b/releasenotes/notes/0.25/add-diagonal-to-DiagonalGate-c945e0f8adcd2940.yaml similarity index 100% rename from releasenotes/notes/add-diagonal-to-DiagonalGate-c945e0f8adcd2940.yaml rename to releasenotes/notes/0.25/add-diagonal-to-DiagonalGate-c945e0f8adcd2940.yaml diff --git a/releasenotes/notes/add-feature-negativity-logarithmic-negativity-fce5d8392460a0e9.yaml b/releasenotes/notes/0.25/add-feature-negativity-logarithmic-negativity-fce5d8392460a0e9.yaml similarity index 100% rename from releasenotes/notes/add-feature-negativity-logarithmic-negativity-fce5d8392460a0e9.yaml rename to releasenotes/notes/0.25/add-feature-negativity-logarithmic-negativity-fce5d8392460a0e9.yaml diff --git a/releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml b/releasenotes/notes/0.25/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml similarity index 100% rename from releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml rename to releasenotes/notes/0.25/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml diff --git a/releasenotes/notes/add-pauli-equivalences-74c635ec5c23ee33.yaml b/releasenotes/notes/0.25/add-pauli-equivalences-74c635ec5c23ee33.yaml similarity index 100% rename from releasenotes/notes/add-pauli-equivalences-74c635ec5c23ee33.yaml rename to releasenotes/notes/0.25/add-pauli-equivalences-74c635ec5c23ee33.yaml diff --git a/releasenotes/notes/add-schmidt-decomposition-c196cff16381b305.yaml b/releasenotes/notes/0.25/add-schmidt-decomposition-c196cff16381b305.yaml similarity index 100% rename from releasenotes/notes/add-schmidt-decomposition-c196cff16381b305.yaml rename to releasenotes/notes/0.25/add-schmidt-decomposition-c196cff16381b305.yaml diff --git a/releasenotes/notes/ancilla_allocation_no_cmap-ac3ff65b3639988e.yaml b/releasenotes/notes/0.25/ancilla_allocation_no_cmap-ac3ff65b3639988e.yaml similarity index 100% rename from releasenotes/notes/ancilla_allocation_no_cmap-ac3ff65b3639988e.yaml rename to releasenotes/notes/0.25/ancilla_allocation_no_cmap-ac3ff65b3639988e.yaml diff --git a/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml b/releasenotes/notes/0.25/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml similarity index 100% rename from releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml rename to releasenotes/notes/0.25/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml diff --git a/releasenotes/notes/clifford-no-circuly-c7c4a1c9c5472af7.yaml b/releasenotes/notes/0.25/clifford-no-circuly-c7c4a1c9c5472af7.yaml similarity index 100% rename from releasenotes/notes/clifford-no-circuly-c7c4a1c9c5472af7.yaml rename to releasenotes/notes/0.25/clifford-no-circuly-c7c4a1c9c5472af7.yaml diff --git a/releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml b/releasenotes/notes/0.25/ctrl-flow-o2-o3-83f660d704226848.yaml similarity index 100% rename from releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml rename to releasenotes/notes/0.25/ctrl-flow-o2-o3-83f660d704226848.yaml diff --git a/releasenotes/notes/cx_cz_synthesis-3d5ec98372ce1608.yaml b/releasenotes/notes/0.25/cx_cz_synthesis-3d5ec98372ce1608.yaml similarity index 100% rename from releasenotes/notes/cx_cz_synthesis-3d5ec98372ce1608.yaml rename to releasenotes/notes/0.25/cx_cz_synthesis-3d5ec98372ce1608.yaml diff --git a/releasenotes/notes/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml b/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml similarity index 100% rename from releasenotes/notes/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml rename to releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/0.25/dagcircuit-separable-circuits-142853e69f530a16.yaml similarity index 100% rename from releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml rename to releasenotes/notes/0.25/dagcircuit-separable-circuits-142853e69f530a16.yaml diff --git a/releasenotes/notes/deprecate-algorithms-7149dee2da586549.yaml b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml similarity index 72% rename from releasenotes/notes/deprecate-algorithms-7149dee2da586549.yaml rename to releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml index 646b2dfd62d7..4edaefe8bb7c 100644 --- a/releasenotes/notes/deprecate-algorithms-7149dee2da586549.yaml +++ b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml @@ -12,11 +12,13 @@ deprecations: of new features has moved to the new package. If you're relying on :mod:`qiskit.algorithms` you should update your requirements to also include ``qiskit-algorithms`` and update the imports - from ``qiskit.algorithms`` to ``qiskit_algorithms``. If you have not yet + from ``qiskit.algorithms`` to ``qiskit_algorithms``. Please note that this + new package does not include already deprecated algorithms code, including + ``opflow`` and ``QuantumInstance``-based algorithms. If you have not yet migrated from ``QuantumInstance``-based to primitives-based algorithms, you should follow the migration guidelines in https://qisk.it/algo_migration. - The decision to migrate the :mod:`~.algorithms` module to a - separate package was made to clarify the purpose Qiskit and + The decision to migrate the :mod:`~.algorithms` module to a + separate package was made to clarify the purpose Qiskit and make a distinction between the tools and libraries built on top of it. diff --git a/releasenotes/notes/deprecate-circuit-library-jupyter-629f927e8dd5cc22.yaml b/releasenotes/notes/0.25/deprecate-circuit-library-jupyter-629f927e8dd5cc22.yaml similarity index 100% rename from releasenotes/notes/deprecate-circuit-library-jupyter-629f927e8dd5cc22.yaml rename to releasenotes/notes/0.25/deprecate-circuit-library-jupyter-629f927e8dd5cc22.yaml diff --git a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml b/releasenotes/notes/0.25/deprecate-complex-amp-41381bd9722bc878.yaml similarity index 100% rename from releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml rename to releasenotes/notes/0.25/deprecate-complex-amp-41381bd9722bc878.yaml diff --git a/releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml b/releasenotes/notes/0.25/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml similarity index 100% rename from releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml rename to releasenotes/notes/0.25/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml diff --git a/releasenotes/notes/deprecate-instruction-qasm-9380f721e7bdaf6b.yaml b/releasenotes/notes/0.25/deprecate-instruction-qasm-9380f721e7bdaf6b.yaml similarity index 100% rename from releasenotes/notes/deprecate-instruction-qasm-9380f721e7bdaf6b.yaml rename to releasenotes/notes/0.25/deprecate-instruction-qasm-9380f721e7bdaf6b.yaml diff --git a/releasenotes/notes/deprecate-namespace-a2ac600f140755e2.yaml b/releasenotes/notes/0.25/deprecate-namespace-a2ac600f140755e2.yaml similarity index 100% rename from releasenotes/notes/deprecate-namespace-a2ac600f140755e2.yaml rename to releasenotes/notes/0.25/deprecate-namespace-a2ac600f140755e2.yaml diff --git a/releasenotes/notes/deprecate-pulse-Call-instruction-538802d8fad7e257.yaml b/releasenotes/notes/0.25/deprecate-pulse-Call-instruction-538802d8fad7e257.yaml similarity index 100% rename from releasenotes/notes/deprecate-pulse-Call-instruction-538802d8fad7e257.yaml rename to releasenotes/notes/0.25/deprecate-pulse-Call-instruction-538802d8fad7e257.yaml diff --git a/releasenotes/notes/discrete-pulse-library-deprecation-3a95eba7e29d8d49.yaml b/releasenotes/notes/0.25/discrete-pulse-library-deprecation-3a95eba7e29d8d49.yaml similarity index 100% rename from releasenotes/notes/discrete-pulse-library-deprecation-3a95eba7e29d8d49.yaml rename to releasenotes/notes/0.25/discrete-pulse-library-deprecation-3a95eba7e29d8d49.yaml diff --git a/releasenotes/notes/display-control-flow-mpl-drawer-2dbc7b57ac52d138.yaml b/releasenotes/notes/0.25/display-control-flow-mpl-drawer-2dbc7b57ac52d138.yaml similarity index 100% rename from releasenotes/notes/display-control-flow-mpl-drawer-2dbc7b57ac52d138.yaml rename to releasenotes/notes/0.25/display-control-flow-mpl-drawer-2dbc7b57ac52d138.yaml diff --git a/releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml b/releasenotes/notes/0.25/drop-python3.7-8689e1fa349a49df.yaml similarity index 100% rename from releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml rename to releasenotes/notes/0.25/drop-python3.7-8689e1fa349a49df.yaml diff --git a/releasenotes/notes/enable_target_aware_meas_map-0d8542402a74e9d8.yaml b/releasenotes/notes/0.25/enable_target_aware_meas_map-0d8542402a74e9d8.yaml similarity index 100% rename from releasenotes/notes/enable_target_aware_meas_map-0d8542402a74e9d8.yaml rename to releasenotes/notes/0.25/enable_target_aware_meas_map-0d8542402a74e9d8.yaml diff --git a/releasenotes/notes/faster-parameter-rebind-3c799e74456469d9.yaml b/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml similarity index 100% rename from releasenotes/notes/faster-parameter-rebind-3c799e74456469d9.yaml rename to releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml diff --git a/releasenotes/notes/filter-schedule-block-29d392ca351f1fb1.yaml b/releasenotes/notes/0.25/filter-schedule-block-29d392ca351f1fb1.yaml similarity index 100% rename from releasenotes/notes/filter-schedule-block-29d392ca351f1fb1.yaml rename to releasenotes/notes/0.25/filter-schedule-block-29d392ca351f1fb1.yaml diff --git a/releasenotes/notes/fix-0q-operator-statevector-79199c65c24637c4.yaml b/releasenotes/notes/0.25/fix-0q-operator-statevector-79199c65c24637c4.yaml similarity index 100% rename from releasenotes/notes/fix-0q-operator-statevector-79199c65c24637c4.yaml rename to releasenotes/notes/0.25/fix-0q-operator-statevector-79199c65c24637c4.yaml diff --git a/releasenotes/notes/fix-1q-matrix-bug-in-quantum-shannon-decomposer-c99ce6509f03715b.yaml b/releasenotes/notes/0.25/fix-1q-matrix-bug-in-quantum-shannon-decomposer-c99ce6509f03715b.yaml similarity index 100% rename from releasenotes/notes/fix-1q-matrix-bug-in-quantum-shannon-decomposer-c99ce6509f03715b.yaml rename to releasenotes/notes/0.25/fix-1q-matrix-bug-in-quantum-shannon-decomposer-c99ce6509f03715b.yaml diff --git a/releasenotes/notes/fix-basicswap-fakerun-7469835327f6c8a1.yaml b/releasenotes/notes/0.25/fix-basicswap-fakerun-7469835327f6c8a1.yaml similarity index 100% rename from releasenotes/notes/fix-basicswap-fakerun-7469835327f6c8a1.yaml rename to releasenotes/notes/0.25/fix-basicswap-fakerun-7469835327f6c8a1.yaml diff --git a/releasenotes/notes/fix-bit-copy-4b2f7349683f616a.yaml b/releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml similarity index 100% rename from releasenotes/notes/fix-bit-copy-4b2f7349683f616a.yaml rename to releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml diff --git a/releasenotes/notes/fix-checkmap-nested-condition-1776f952f6c6722a.yaml b/releasenotes/notes/0.25/fix-checkmap-nested-condition-1776f952f6c6722a.yaml similarity index 100% rename from releasenotes/notes/fix-checkmap-nested-condition-1776f952f6c6722a.yaml rename to releasenotes/notes/0.25/fix-checkmap-nested-condition-1776f952f6c6722a.yaml diff --git a/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml b/releasenotes/notes/0.25/fix-collapse-with-clbits-e14766353303d442.yaml similarity index 100% rename from releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml rename to releasenotes/notes/0.25/fix-collapse-with-clbits-e14766353303d442.yaml diff --git a/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml b/releasenotes/notes/0.25/fix-compose-switch-19ada3828d939353.yaml similarity index 100% rename from releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml rename to releasenotes/notes/0.25/fix-compose-switch-19ada3828d939353.yaml diff --git a/releasenotes/notes/fix-controlflow-builder-nested-switch-008b8c43b2153a1f.yaml b/releasenotes/notes/0.25/fix-controlflow-builder-nested-switch-008b8c43b2153a1f.yaml similarity index 100% rename from releasenotes/notes/fix-controlflow-builder-nested-switch-008b8c43b2153a1f.yaml rename to releasenotes/notes/0.25/fix-controlflow-builder-nested-switch-008b8c43b2153a1f.yaml diff --git a/releasenotes/notes/fix-decompose-name-f83f5e4e64918aa9.yaml b/releasenotes/notes/0.25/fix-decompose-name-f83f5e4e64918aa9.yaml similarity index 100% rename from releasenotes/notes/fix-decompose-name-f83f5e4e64918aa9.yaml rename to releasenotes/notes/0.25/fix-decompose-name-f83f5e4e64918aa9.yaml diff --git a/releasenotes/notes/fix-delay-padding-75937bda37ebc3fd.yaml b/releasenotes/notes/0.25/fix-delay-padding-75937bda37ebc3fd.yaml similarity index 100% rename from releasenotes/notes/fix-delay-padding-75937bda37ebc3fd.yaml rename to releasenotes/notes/0.25/fix-delay-padding-75937bda37ebc3fd.yaml diff --git a/releasenotes/notes/fix-dispatching-backends-28aff96f726ca9c5.yaml b/releasenotes/notes/0.25/fix-dispatching-backends-28aff96f726ca9c5.yaml similarity index 100% rename from releasenotes/notes/fix-dispatching-backends-28aff96f726ca9c5.yaml rename to releasenotes/notes/0.25/fix-dispatching-backends-28aff96f726ca9c5.yaml diff --git a/releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml b/releasenotes/notes/0.25/fix-exception-decription-3ba0b5db82c576cf.yaml similarity index 100% rename from releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml rename to releasenotes/notes/0.25/fix-exception-decription-3ba0b5db82c576cf.yaml diff --git a/releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml b/releasenotes/notes/0.25/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml similarity index 100% rename from releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml rename to releasenotes/notes/0.25/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml diff --git a/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml b/releasenotes/notes/0.25/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml similarity index 100% rename from releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml rename to releasenotes/notes/0.25/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml diff --git a/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml similarity index 100% rename from releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml rename to releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml diff --git a/releasenotes/notes/fix-outputs-of-measure_v2-8959ebbbf5f87294.yaml b/releasenotes/notes/0.25/fix-outputs-of-measure_v2-8959ebbbf5f87294.yaml similarity index 100% rename from releasenotes/notes/fix-outputs-of-measure_v2-8959ebbbf5f87294.yaml rename to releasenotes/notes/0.25/fix-outputs-of-measure_v2-8959ebbbf5f87294.yaml diff --git a/releasenotes/notes/fix-partial-transpose-output-dims-3082fcf4147055dc.yaml b/releasenotes/notes/0.25/fix-partial-transpose-output-dims-3082fcf4147055dc.yaml similarity index 100% rename from releasenotes/notes/fix-partial-transpose-output-dims-3082fcf4147055dc.yaml rename to releasenotes/notes/0.25/fix-partial-transpose-output-dims-3082fcf4147055dc.yaml diff --git a/releasenotes/notes/fix-plot-legend-not-showing-up-3202bec143529e49.yaml b/releasenotes/notes/0.25/fix-plot-legend-not-showing-up-3202bec143529e49.yaml similarity index 100% rename from releasenotes/notes/fix-plot-legend-not-showing-up-3202bec143529e49.yaml rename to releasenotes/notes/0.25/fix-plot-legend-not-showing-up-3202bec143529e49.yaml diff --git a/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml b/releasenotes/notes/0.25/fix-pm-config-from-backend-f3b71b11858b4f08.yaml similarity index 100% rename from releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml rename to releasenotes/notes/0.25/fix-pm-config-from-backend-f3b71b11858b4f08.yaml diff --git a/releasenotes/notes/fix-primitives-import-warnings-439e3e237fdb9d7b.yaml b/releasenotes/notes/0.25/fix-primitives-import-warnings-439e3e237fdb9d7b.yaml similarity index 100% rename from releasenotes/notes/fix-primitives-import-warnings-439e3e237fdb9d7b.yaml rename to releasenotes/notes/0.25/fix-primitives-import-warnings-439e3e237fdb9d7b.yaml diff --git a/releasenotes/notes/fix-qasm-circuit-export-943394536bc0d292.yaml b/releasenotes/notes/0.25/fix-qasm-circuit-export-943394536bc0d292.yaml similarity index 100% rename from releasenotes/notes/fix-qasm-circuit-export-943394536bc0d292.yaml rename to releasenotes/notes/0.25/fix-qasm-circuit-export-943394536bc0d292.yaml diff --git a/releasenotes/notes/fix-regression-in-the-LaTeX-drawer-of-QuantumCircuit-7dd3e84e1dea1abd.yaml b/releasenotes/notes/0.25/fix-regression-in-the-LaTeX-drawer-of-QuantumCircuit-7dd3e84e1dea1abd.yaml similarity index 100% rename from releasenotes/notes/fix-regression-in-the-LaTeX-drawer-of-QuantumCircuit-7dd3e84e1dea1abd.yaml rename to releasenotes/notes/0.25/fix-regression-in-the-LaTeX-drawer-of-QuantumCircuit-7dd3e84e1dea1abd.yaml diff --git a/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml b/releasenotes/notes/0.25/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml similarity index 100% rename from releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml rename to releasenotes/notes/0.25/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml diff --git a/releasenotes/notes/fix-synthesis-cf-mapping-fe9bd2e5fbd56dfb.yaml b/releasenotes/notes/0.25/fix-synthesis-cf-mapping-fe9bd2e5fbd56dfb.yaml similarity index 100% rename from releasenotes/notes/fix-synthesis-cf-mapping-fe9bd2e5fbd56dfb.yaml rename to releasenotes/notes/0.25/fix-synthesis-cf-mapping-fe9bd2e5fbd56dfb.yaml diff --git a/releasenotes/notes/fix-transpile-pickle-4045805b67c0c11b.yaml b/releasenotes/notes/0.25/fix-transpile-pickle-4045805b67c0c11b.yaml similarity index 100% rename from releasenotes/notes/fix-transpile-pickle-4045805b67c0c11b.yaml rename to releasenotes/notes/0.25/fix-transpile-pickle-4045805b67c0c11b.yaml diff --git a/releasenotes/notes/fix-update-from-instruction-schedule-map-d1cba4e4db4b679e.yaml b/releasenotes/notes/0.25/fix-update-from-instruction-schedule-map-d1cba4e4db4b679e.yaml similarity index 100% rename from releasenotes/notes/fix-update-from-instruction-schedule-map-d1cba4e4db4b679e.yaml rename to releasenotes/notes/0.25/fix-update-from-instruction-schedule-map-d1cba4e4db4b679e.yaml diff --git a/releasenotes/notes/fix-vf2-scoring-1q-e2ac29075831d64d.yaml b/releasenotes/notes/0.25/fix-vf2-scoring-1q-e2ac29075831d64d.yaml similarity index 100% rename from releasenotes/notes/fix-vf2-scoring-1q-e2ac29075831d64d.yaml rename to releasenotes/notes/0.25/fix-vf2-scoring-1q-e2ac29075831d64d.yaml diff --git a/releasenotes/notes/fix-vqd-result-27b26f0a6d49e7c7.yaml b/releasenotes/notes/0.25/fix-vqd-result-27b26f0a6d49e7c7.yaml similarity index 100% rename from releasenotes/notes/fix-vqd-result-27b26f0a6d49e7c7.yaml rename to releasenotes/notes/0.25/fix-vqd-result-27b26f0a6d49e7c7.yaml diff --git a/releasenotes/notes/fix_9016-2e8bc2cb10b5e204.yaml b/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml similarity index 100% rename from releasenotes/notes/fix_9016-2e8bc2cb10b5e204.yaml rename to releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml diff --git a/releasenotes/notes/fixes_8060-ae91e0da9d53a288.yaml b/releasenotes/notes/0.25/fixes_8060-ae91e0da9d53a288.yaml similarity index 100% rename from releasenotes/notes/fixes_8060-ae91e0da9d53a288.yaml rename to releasenotes/notes/0.25/fixes_8060-ae91e0da9d53a288.yaml diff --git a/releasenotes/notes/flatten-nlocal-family-292b23b99947f3c9.yaml b/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml similarity index 100% rename from releasenotes/notes/flatten-nlocal-family-292b23b99947f3c9.yaml rename to releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml diff --git a/releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml b/releasenotes/notes/0.25/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml similarity index 100% rename from releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml rename to releasenotes/notes/0.25/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml diff --git a/releasenotes/notes/has-pygments-tester-3fb9f9c34907d45d.yaml b/releasenotes/notes/0.25/has-pygments-tester-3fb9f9c34907d45d.yaml similarity index 100% rename from releasenotes/notes/has-pygments-tester-3fb9f9c34907d45d.yaml rename to releasenotes/notes/0.25/has-pygments-tester-3fb9f9c34907d45d.yaml diff --git a/releasenotes/notes/improve-quantum-circuit-assign-parameters-typing-70c9623405cbd420.yaml b/releasenotes/notes/0.25/improve-quantum-circuit-assign-parameters-typing-70c9623405cbd420.yaml similarity index 100% rename from releasenotes/notes/improve-quantum-circuit-assign-parameters-typing-70c9623405cbd420.yaml rename to releasenotes/notes/0.25/improve-quantum-circuit-assign-parameters-typing-70c9623405cbd420.yaml diff --git a/releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml b/releasenotes/notes/0.25/linear-functions-usability-45265f293a80a6e5.yaml similarity index 100% rename from releasenotes/notes/linear-functions-usability-45265f293a80a6e5.yaml rename to releasenotes/notes/0.25/linear-functions-usability-45265f293a80a6e5.yaml diff --git a/releasenotes/notes/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml b/releasenotes/notes/0.25/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml similarity index 100% rename from releasenotes/notes/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml rename to releasenotes/notes/0.25/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml diff --git a/releasenotes/notes/normalize-stateprep-e21972dce8695509.yaml b/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml similarity index 100% rename from releasenotes/notes/normalize-stateprep-e21972dce8695509.yaml rename to releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml diff --git a/releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml b/releasenotes/notes/0.25/optimize-consolidate-blocks-3ea60c18bc546273.yaml similarity index 100% rename from releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml rename to releasenotes/notes/0.25/optimize-consolidate-blocks-3ea60c18bc546273.yaml diff --git a/releasenotes/notes/parameter-float-cast-48f3731fec5e47cd.yaml b/releasenotes/notes/0.25/parameter-float-cast-48f3731fec5e47cd.yaml similarity index 100% rename from releasenotes/notes/parameter-float-cast-48f3731fec5e47cd.yaml rename to releasenotes/notes/0.25/parameter-float-cast-48f3731fec5e47cd.yaml diff --git a/releasenotes/notes/pauli-rotation-equivalences-6b2449c93c042dc9.yaml b/releasenotes/notes/0.25/pauli-rotation-equivalences-6b2449c93c042dc9.yaml similarity index 100% rename from releasenotes/notes/pauli-rotation-equivalences-6b2449c93c042dc9.yaml rename to releasenotes/notes/0.25/pauli-rotation-equivalences-6b2449c93c042dc9.yaml diff --git a/releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml b/releasenotes/notes/0.25/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml similarity index 100% rename from releasenotes/notes/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml rename to releasenotes/notes/0.25/preset-pm-vf2-max-trials-958bb8a36fff472f.yaml diff --git a/releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml b/releasenotes/notes/0.25/qasm3-alias-refactor-3389bfce3e29e4cf.yaml similarity index 100% rename from releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml rename to releasenotes/notes/0.25/qasm3-alias-refactor-3389bfce3e29e4cf.yaml diff --git a/releasenotes/notes/qasm3-no-subroutine-b69c5ed7c65ce9ac.yaml b/releasenotes/notes/0.25/qasm3-no-subroutine-b69c5ed7c65ce9ac.yaml similarity index 100% rename from releasenotes/notes/qasm3-no-subroutine-b69c5ed7c65ce9ac.yaml rename to releasenotes/notes/0.25/qasm3-no-subroutine-b69c5ed7c65ce9ac.yaml diff --git a/releasenotes/notes/qiskit_version-956916f7b8d7bbb9.yaml b/releasenotes/notes/0.25/qiskit_version-956916f7b8d7bbb9.yaml similarity index 100% rename from releasenotes/notes/qiskit_version-956916f7b8d7bbb9.yaml rename to releasenotes/notes/0.25/qiskit_version-956916f7b8d7bbb9.yaml diff --git a/releasenotes/notes/qpy-layout-927ab34f2b47f4aa.yaml b/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml similarity index 100% rename from releasenotes/notes/qpy-layout-927ab34f2b47f4aa.yaml rename to releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml diff --git a/releasenotes/notes/qpy_supports_discriminator_and_kernel-3b6048bf1499f9d3.yaml b/releasenotes/notes/0.25/qpy_supports_discriminator_and_kernel-3b6048bf1499f9d3.yaml similarity index 100% rename from releasenotes/notes/qpy_supports_discriminator_and_kernel-3b6048bf1499f9d3.yaml rename to releasenotes/notes/0.25/qpy_supports_discriminator_and_kernel-3b6048bf1499f9d3.yaml diff --git a/releasenotes/notes/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml b/releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml similarity index 100% rename from releasenotes/notes/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml rename to releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml diff --git a/releasenotes/notes/remove-deprecate-instructionset-circuit-cregs-91617e4b0993db9a.yaml b/releasenotes/notes/0.25/remove-deprecate-instructionset-circuit-cregs-91617e4b0993db9a.yaml similarity index 100% rename from releasenotes/notes/remove-deprecate-instructionset-circuit-cregs-91617e4b0993db9a.yaml rename to releasenotes/notes/0.25/remove-deprecate-instructionset-circuit-cregs-91617e4b0993db9a.yaml diff --git a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml b/releasenotes/notes/0.25/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml similarity index 100% rename from releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml rename to releasenotes/notes/0.25/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml diff --git a/releasenotes/notes/remove-transpile-broadcast-1dfde28d508efa0d.yaml b/releasenotes/notes/0.25/remove-transpile-broadcast-1dfde28d508efa0d.yaml similarity index 100% rename from releasenotes/notes/remove-transpile-broadcast-1dfde28d508efa0d.yaml rename to releasenotes/notes/0.25/remove-transpile-broadcast-1dfde28d508efa0d.yaml diff --git a/releasenotes/notes/remove-util-3cd9eae2efc95176.yaml b/releasenotes/notes/0.25/remove-util-3cd9eae2efc95176.yaml similarity index 100% rename from releasenotes/notes/remove-util-3cd9eae2efc95176.yaml rename to releasenotes/notes/0.25/remove-util-3cd9eae2efc95176.yaml diff --git a/releasenotes/notes/sabre-control-flow-3772af2c5b02c6d5.yaml b/releasenotes/notes/0.25/sabre-control-flow-3772af2c5b02c6d5.yaml similarity index 100% rename from releasenotes/notes/sabre-control-flow-3772af2c5b02c6d5.yaml rename to releasenotes/notes/0.25/sabre-control-flow-3772af2c5b02c6d5.yaml diff --git a/releasenotes/notes/sabre-ctrl-flow-o1-431cd25a19adbcdc.yaml b/releasenotes/notes/0.25/sabre-ctrl-flow-o1-431cd25a19adbcdc.yaml similarity index 100% rename from releasenotes/notes/sabre-ctrl-flow-o1-431cd25a19adbcdc.yaml rename to releasenotes/notes/0.25/sabre-ctrl-flow-o1-431cd25a19adbcdc.yaml diff --git a/releasenotes/notes/support-SparsePauliOp-Parameter-multiplication-245173f0b232f59b.yaml b/releasenotes/notes/0.25/support-SparsePauliOp-Parameter-multiplication-245173f0b232f59b.yaml similarity index 100% rename from releasenotes/notes/support-SparsePauliOp-Parameter-multiplication-245173f0b232f59b.yaml rename to releasenotes/notes/0.25/support-SparsePauliOp-Parameter-multiplication-245173f0b232f59b.yaml diff --git a/releasenotes/notes/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml b/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml similarity index 100% rename from releasenotes/notes/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml rename to releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml diff --git a/releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml b/releasenotes/notes/0.25/umda-callback-eb644a49c5a9ad37.yaml similarity index 100% rename from releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml rename to releasenotes/notes/0.25/umda-callback-eb644a49c5a9ad37.yaml diff --git a/releasenotes/notes/unintended-rounding-with-max-size-1498af5f9a467990.yaml b/releasenotes/notes/0.25/unintended-rounding-with-max-size-1498af5f9a467990.yaml similarity index 100% rename from releasenotes/notes/unintended-rounding-with-max-size-1498af5f9a467990.yaml rename to releasenotes/notes/0.25/unintended-rounding-with-max-size-1498af5f9a467990.yaml diff --git a/releasenotes/notes/use-abi3-4a935e0557d3833b.yaml b/releasenotes/notes/0.25/use-abi3-4a935e0557d3833b.yaml similarity index 100% rename from releasenotes/notes/use-abi3-4a935e0557d3833b.yaml rename to releasenotes/notes/0.25/use-abi3-4a935e0557d3833b.yaml diff --git a/releasenotes/notes/add-limits-in-commutation-checker-66a1b9e90921621f.yaml b/releasenotes/notes/add-limits-in-commutation-checker-66a1b9e90921621f.yaml new file mode 100644 index 000000000000..9b8bc6411083 --- /dev/null +++ b/releasenotes/notes/add-limits-in-commutation-checker-66a1b9e90921621f.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added a new option ``max_num_qubits`` to :meth:`qiskit.circuit.CommutationChecker.commute` + that specifies the maximum number of qubits to consider for the more expensive + matrix multiplication-based commutativity check. This avoids trying to + internally allocate arrays of size :math:`2^N \times 2^N`. Simpler versions of commutativity + check (for instance, two quantum operations commute when they are over disjoint sets of qubits) + continue to work without this limit. +fixes: + - | + The maximum number of qubits to consider for matrix multiplication-based commutativity check + in :class:`~.CommutationChecker` is now limited to 3 by default. + Fixed `#10488 `__ diff --git a/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml b/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml new file mode 100644 index 000000000000..a3df013fcf40 --- /dev/null +++ b/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml @@ -0,0 +1,26 @@ +--- +features: + - | + A new module :mod:`qiskit.passmanager` is added. + This module implements a generic pass manager and flow controllers, + and provides an infrastructure to manage execution of transform passes. + The pass manager is a baseclass and not aware of the input and output object types, + and you need to create a subclass of the pass manager + for a particular program data to optimize. + The :mod:`qiskit.transpiler` module is also reorganized to rebuild the existing + quantum circuit pass manager based off of new generic pass manager. + See upgrade notes for more details. +upgrade: + - | + :class:`qiskit.transpiler.PassManager` is now a subclass of + :class:`qiskit.passmanager.BasePassManager`. There is no functional modification + due to this class hierarchy change. + - | + New error baseclass :class:`~qiskit.passmanager.PassManagerError` is introduced. + This will replace :class:`~qiskit.transpiler.TranspilerError` raised in the + pass handling machinery. The TranspilerError is now only used for the errors + related to the failure in handling the quantum circuit or DAG circuit object. + Note that the TranspilerError can be caught by the PassManagerError + because of their class hierarchy. For backward compatibility, + :class:`qiskit.transpiler.PassManager` catches PassManagerError and + re-raises the TranspilerError. This error replacement will be dropped in future. diff --git a/releasenotes/notes/channel-validation-bug-fix-c06f8445cecc8478.yaml b/releasenotes/notes/channel-validation-bug-fix-c06f8445cecc8478.yaml new file mode 100644 index 000000000000..b68ae2a68dfe --- /dev/null +++ b/releasenotes/notes/channel-validation-bug-fix-c06f8445cecc8478.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a bug in :class:`.pulse.Channel` where index validation was done incorrectly and only + raised an error when the index was both non-integer and negative, instead of either. diff --git a/releasenotes/notes/expr-rvalue-conditions-8b5d5f7c015658c0.yaml b/releasenotes/notes/expr-rvalue-conditions-8b5d5f7c015658c0.yaml new file mode 100644 index 000000000000..234888b2ac9b --- /dev/null +++ b/releasenotes/notes/expr-rvalue-conditions-8b5d5f7c015658c0.yaml @@ -0,0 +1,83 @@ +--- +features: + - | + The fields :attr:`.IfElseOp.condition`, :attr:`.WhileLoopOp.condition` and + :attr:`.SwitchCaseOp.target` can now be instances of the new runtime classical-expression type + :class:`.expr.Expr`. This is distinct from :class:`.ParameterExpression` because it is + evaluated *at runtime* for backends that support such operations. + + These new expressions have significantly more power than the old two-tuple form of supplying + classical conditions. For example, one can now represent equality constraints between two + different classical registers, or the logic "or" of two classical bits. These two examples + would look like:: + + from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister + from qiskit.circuit.classical import expr + + qr = QuantumRegister(4) + cr1 = ClassicalRegister(2) + cr2 = ClassicalRegister(2) + qc = QuantumCircuit(qr, cr1, cr2) + qc.h(0) + qc.cx(0, 1) + qc.h(2) + qc.cx(2, 3) + qc.measure([0, 1, 2, 3], [0, 1, 2, 3]) + + # If the two registers are equal to each other. + with qc.if_test(expr.equal(cr1, cr2)): + qc.x(0) + + # While either of two bits are set. + with qc.while_loop(expr.logic_or(cr1[0], cr1[1])): + qc.reset(0) + qc.reset(1) + qc.measure([0, 1], cr1) + + For more examples, see the documentation for :mod:`qiskit.circuit.classical`. + + This is a feature that hardware is only just beginning to support, so Qiskit's support is + starting conservatively, and you may well find rough edges in places where hardware doesn't + support expressions that look simple in Qiskit. Please bear with us, and your hardware vendors! + + In this initial release, Qiskit has added the operations: + + * :func:`~.expr.bit_not` + * :func:`~.expr.logic_not` + * :func:`~.expr.bit_and` + * :func:`~.expr.bit_or` + * :func:`~.expr.bit_xor` + * :func:`~.expr.logic_and` + * :func:`~.expr.logic_or` + * :func:`~.expr.equal` + * :func:`~.expr.not_equal` + * :func:`~.expr.less` + * :func:`~.expr.less_equal` + * :func:`~.expr.greater` + * :func:`~.expr.greater_equal` + + These can act on Python integer and Boolean literals, or on :class:`.ClassicalRegister` + and :class:`.Clbit` instances. + + All these classical expressions are fully supported through the Qiskit transpiler stack, through + QPY serialisation (:mod:`qiskit.qpy`) and for export to OpenQASM 3 (:mod:`qiskit.qasm3`). Import + from OpenQASM 3 is currently managed by `a separate package `__ + (which is re-exposed via :mod:`qiskit.qasm3`), which we hope will be extended to match the new + features in Qiskit. + - | + In addition to the new representations of classical runtime expressions, some tools for working + with these objects have also been added. A general :class:`~.expr.ExprVisitor` is provided for + consumers of these expressions to subclass. Two utilities based on this structure, + :func:`~.expr.iter_vars` and :func:`~.expr.structurally_equivalent`, are also provided, which + respectively produce an iterator through the :class:`~.expr.Var` nodes and check whether two + :class:`~.expr.Expr` instances are structurally the same, up to some mapping of the + :class:`~.expr.Var` nodes contained. + - | + For those who wish to convert all old-style conditions into new-style :class:`~.expr.Expr` nodes + immediately, the function :func:`~.expr.lift_legacy_condition` is provided, though these new + expression nodes are not permitted in old-style :attr:`.Instruction.condition` fields, which are + due to be replaced by more advanced classical handling such as :class:`.IfElseOp`. +issues: + - | + Circuits containing classical expressions made with the :mod:`~.classical.expr` module are not + yet supported by the circuit visualizers. diff --git a/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml b/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml new file mode 100644 index 000000000000..f4297f55d8a7 --- /dev/null +++ b/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml @@ -0,0 +1,15 @@ +--- +fixes: + - | + Fixed an issue with the :func:`~.transpile` function and all the preset + pass managers generated via :func:`~.generate_preset_pass_manager` where + the output :class:`~.QuantumCircuit` object's :attr:`~.QuantumCircuit.layout` + attribute would have an invalid :attr:`.TranspileLayout.final_layout` + attribute. This would occur in scenarios when the :class:`~.VF2PostLayout` + pass would run and find an alternative initial layout that has lower + reported error rates. When altering the initial layout the + :attr:`~.TranspileLayout.final_layout` attribute was never updated to + reflect this change. This has been corrected so that the ``final_layout`` + is always correctly reflecting the output permutation caused by the routing + stage. + Fixed `#10457 `__ diff --git a/releasenotes/notes/qasm2-fix-zero-op-barrier-4af211b119d5b24d.yaml b/releasenotes/notes/qasm2-fix-zero-op-barrier-4af211b119d5b24d.yaml new file mode 100644 index 000000000000..9b86441fe814 --- /dev/null +++ b/releasenotes/notes/qasm2-fix-zero-op-barrier-4af211b119d5b24d.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + The OpenQASM 2 parser (:func:`.qasm2.load` and :func:`~.qasm2.loads`) running in ``strict`` mode + will now correctly emit an error if a ``barrier`` statement has no arguments. When running in + the (default) more permissive mode, an argument-less ``barrier`` statement will continue to + cause a barrier on all qubits currently in scope (the qubits a gate definition affects, or all + the qubits defined by a program, if the statement is in a gate body or in the global scope, + respectively). + - | + The OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`) will now no longer attempt + to output ``barrier`` statements that act on no qubits. Such a barrier statement has no effect + in Qiskit either, but is invalid OpenQASM 2. diff --git a/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml b/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml new file mode 100644 index 000000000000..545032c17a17 --- /dev/null +++ b/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Qiskit can represent custom instructions that act on zero qubits, or on a non-zero number of + classical bits. These cannot be exported to OpenQASM 2, but previously :meth:`.QuantumCircuit.qasm` + would try, and output invalid OpenQASM 2. Instead, a :exc:`.QASM2ExportError` will now correctly + be raised. See `#7351 `__ and + `#10435 `__. diff --git a/setup.py b/setup.py index ec0420f70691..21f64a17b94d 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ setup( name="qiskit-terra", - version="0.25.0", + version="0.45.0", description="Software for developing quantum computing programs", long_description=README, long_description_content_type="text/markdown", diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 32f12e2c89ef..0aa6ac6ececc 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -657,6 +657,30 @@ def test_circuit_raises_on_single_bit_condition(self): with self.assertRaisesRegex(QasmError, "OpenQASM 2 can only condition on registers"): qc.qasm() + def test_circuit_raises_invalid_custom_gate_no_qubits(self): + """OpenQASM 2 exporter of custom gates with no qubits. + See: https://github.com/Qiskit/qiskit-terra/issues/10435""" + legit_circuit = QuantumCircuit(5, name="legit_circuit") + empty_circuit = QuantumCircuit(name="empty_circuit") + legit_circuit.append(empty_circuit) + + with self.assertRaisesRegex(QasmError, "acts on zero qubits"): + legit_circuit.qasm() + + def test_circuit_raises_invalid_custom_gate_clbits(self): + """OpenQASM 2 exporter of custom instruction. + See: https://github.com/Qiskit/qiskit-terra/issues/7351""" + instruction = QuantumCircuit(2, 2, name="inst") + instruction.cx(0, 1) + instruction.measure([0, 1], [0, 1]) + custom_instruction = instruction.to_instruction() + + qc = QuantumCircuit(2, 2) + qc.append(custom_instruction, [0, 1], [0, 1]) + + with self.assertRaisesRegex(QasmError, "acts on 2 classical bits"): + qc.qasm() + def test_circuit_qasm_with_permutations(self): """Test circuit qasm() method with Permutation gates.""" @@ -805,6 +829,23 @@ def test_sequencial_inner_gates_with_same_name(self): self.assertEqual(qc.qasm(), expected_output) + def test_empty_barrier(self): + """Test that a blank barrier statement in _Qiskit_ acts over all qubits, while an explicitly + no-op barrier (assuming Qiskit continues to allow this) is not output to OQ2 at all, since + the statement requires an argument in the spec.""" + qc = QuantumCircuit(QuantumRegister(2, "qr1"), QuantumRegister(3, "qr2")) + qc.barrier() # In Qiskit land, this affects _all_ qubits. + qc.barrier([]) # This explicitly affects _no_ qubits (so is totally meaningless). + + expected = """\ +OPENQASM 2.0; +include "qelib1.inc"; +qreg qr1[2]; +qreg qr2[3]; +barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2]; +""" + self.assertEqual(qc.qasm(), expected) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_commutation_checker.py b/test/python/circuit/test_commutation_checker.py index cd1b08d0ceb9..bfb41e649a12 100644 --- a/test/python/circuit/test_commutation_checker.py +++ b/test/python/circuit/test_commutation_checker.py @@ -25,6 +25,7 @@ XGate, CXGate, CCXGate, + MCXGate, RZGate, Measure, Barrier, @@ -362,6 +363,20 @@ def test_c7x_gate(self): res = CommutationChecker().commute(XGate(), qargs[:1], [], XGate().control(7), qargs, []) self.assertFalse(res) + def test_wide_gates_over_nondisjoint_qubits(self): + """Test that checking wide gates does not lead to memory problems.""" + res = CommutationChecker().commute(MCXGate(29), list(range(30)), [], XGate(), [0], []) + self.assertFalse(res) + res = CommutationChecker().commute(XGate(), [0], [], MCXGate(29), list(range(30)), []) + self.assertFalse(res) + + def test_wide_gates_over_disjoint_qubits(self): + """Test that wide gates still commute when they are over disjoint sets of qubits.""" + res = CommutationChecker().commute(MCXGate(29), list(range(30)), [], XGate(), [30], []) + self.assertTrue(res) + res = CommutationChecker().commute(XGate(), [30], [], MCXGate(29), list(range(30)), []) + self.assertTrue(res) + if __name__ == "__main__": unittest.main() diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 92187ec74999..307e90d914c4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -39,6 +39,7 @@ SwitchCaseOp, WhileLoopOp, ) +from qiskit.circuit.classical import expr from qiskit.circuit.delay import Delay from qiskit.circuit.library import ( CXGate, @@ -67,6 +68,7 @@ FakeNairobiV2, FakeRueschlikon, FakeSherbrooke, + FakeVigo, ) from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap @@ -75,7 +77,7 @@ from qiskit.tools import parallel from qiskit.transpiler import CouplingMap, Layout, PassManager, TransformationPass from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection +from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection, VF2PostLayout from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager, level_0_pass_manager from qiskit.transpiler.target import InstructionProperties, Target @@ -1721,6 +1723,42 @@ def _control_flow_circuit(self): base.append(CustomCX(), [3, 4]) return base + def _control_flow_expr_circuit(self): + a = Parameter("a") + regs = [ + QuantumRegister(2, name="q0"), + QuantumRegister(3, name="q1"), + ClassicalRegister(2, name="c0"), + ] + bits = [Qubit(), Qubit(), Clbit()] + base = QuantumCircuit(*regs, bits) + base.h(0) + base.measure(0, 0) + with base.if_test(expr.equal(base.cregs[0], 1)) as else_: + base.cx(0, 1) + base.cz(0, 2) + base.cz(0, 3) + with else_: + base.cz(1, 4) + with base.for_loop((1, 2)): + base.cx(1, 5) + base.measure(2, 2) + with base.while_loop(expr.logic_not(bits[2])): + base.append(CustomCX(), [3, 6]) + base.append(CustomCX(), [5, 4]) + base.append(CustomCX(), [5, 3]) + base.append(CustomCX(), [2, 4]) + base.ry(a, 4) + base.measure(4, 2) + with base.switch(expr.bit_and(base.cregs[0], 2)) as case_: + with case_(0, 1): + base.cz(3, 5) + with case_(case_.DEFAULT): + base.cz(1, 4) + base.append(CustomCX(), [2, 4]) + base.append(CustomCX(), [3, 4]) + return base + @data(0, 1, 2, 3) def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" @@ -1807,6 +1845,51 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): round_tripped = qpy.load(buffer)[0] self.assertEqual(round_tripped, transpiled) + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) + backend = FakeMelbourne() + transpiled = transpile( + self._control_flow_expr_circuit(), + backend=backend, + basis_gates=backend.configuration().basis_gates + + ["if_else", "for_loop", "while_loop", "switch_case"], + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + backend = FakeMumbaiV2() + backend.target.add_instruction(IfElseOp, name="if_else") + backend.target.add_instruction(ForLoopOp, name="for_loop") + backend.target.add_instruction(WhileLoopOp, name="while_loop") + backend.target.add_instruction(SwitchCaseOp, name="switch_case") + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + @data(0, 1, 2, 3) def test_qasm3_output(self, optimization_level): """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" @@ -1844,6 +1927,29 @@ def test_qasm3_output_control_flow(self, optimization_level): str, ) + @data(0, 1, 2, 3) + def test_qasm3_output_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be + dumped into OpenQASM 3.""" + backend = FakeMumbaiV2() + backend.target.add_instruction(IfElseOp, name="if_else") + backend.target.add_instruction(ForLoopOp, name="for_loop") + backend.target.add_instruction(WhileLoopOp, name="while_loop") + backend.target.add_instruction(SwitchCaseOp, name="switch_case") + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance( + qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + str, + ) + @data(0, 1, 2, 3) def test_transpile_target_no_measurement_error(self, opt_level): """Test that transpile with a target which contains ideal measurement works @@ -1857,6 +1963,45 @@ def test_transpile_target_no_measurement_error(self, opt_level): res = transpile(qc, target=target, optimization_level=opt_level) self.assertEqual(qc, res) + def test_transpile_final_layout_updated_with_post_layout(self): + """Test that the final layout is correctly set when vf2postlayout runs. + + Reproduce from #10457 + """ + + def _get_index_layout(transpiled_circuit: QuantumCircuit, num_source_qubits: int): + """Return the index layout of a transpiled circuit""" + layout = transpiled_circuit.layout + if layout is None: + return list(range(num_source_qubits)) + + pos_to_virt = {v: k for k, v in layout.input_qubit_mapping.items()} + qubit_indices = [] + for index in range(num_source_qubits): + qubit_idx = layout.initial_layout[pos_to_virt[index]] + if layout.final_layout is not None: + qubit_idx = layout.final_layout[transpiled_circuit.qubits[qubit_idx]] + qubit_indices.append(qubit_idx) + return qubit_indices + + vf2_post_layout_called = False + + def callback(**kwargs): + nonlocal vf2_post_layout_called + if isinstance(kwargs["pass_"], VF2PostLayout): + vf2_post_layout_called = True + self.assertIsNotNone(kwargs["property_set"]["post_layout"]) + + backend = FakeVigo() + qubits = 3 + qc = QuantumCircuit(qubits) + for i in range(5): + qc.cx(i % qubits, int(i + qubits / 2) % qubits) + + tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) + self.assertTrue(vf2_post_layout_called) + self.assertEqual([3, 2, 1], _get_index_layout(tqc, qubits)) + class StreamHandlerRaiseException(StreamHandler): """Handler class that will raise an exception on formatting errors.""" diff --git a/test/python/pulse/test_channels.py b/test/python/pulse/test_channels.py index 604202fb5754..2ad0d08fe259 100644 --- a/test/python/pulse/test_channels.py +++ b/test/python/pulse/test_channels.py @@ -25,6 +25,7 @@ PulseChannel, RegisterSlot, SnapshotChannel, + PulseError, ) from qiskit.test import QiskitTestCase @@ -88,6 +89,13 @@ def test_default(self): self.assertEqual(memory_slot.name, "m123") self.assertTrue(isinstance(memory_slot, ClassicalIOChannel)) + def test_validation(self): + """Test channel validation""" + with self.assertRaises(PulseError): + MemorySlot(0.5) + with self.assertRaises(PulseError): + MemorySlot(-1) + class TestRegisterSlot(QiskitTestCase): """RegisterSlot tests.""" @@ -100,6 +108,13 @@ def test_default(self): self.assertEqual(register_slot.name, "c123") self.assertTrue(isinstance(register_slot, ClassicalIOChannel)) + def test_validation(self): + """Test channel validation""" + with self.assertRaises(PulseError): + RegisterSlot(0.5) + with self.assertRaises(PulseError): + RegisterSlot(-1) + class TestSnapshotChannel(QiskitTestCase): """SnapshotChannel tests.""" @@ -123,6 +138,13 @@ def test_default(self): self.assertEqual(drive_channel.index, 123) self.assertEqual(drive_channel.name, "d123") + def test_validation(self): + """Test channel validation""" + with self.assertRaises(PulseError): + DriveChannel(0.5) + with self.assertRaises(PulseError): + DriveChannel(-1) + class TestControlChannel(QiskitTestCase): """ControlChannel tests.""" @@ -134,6 +156,13 @@ def test_default(self): self.assertEqual(control_channel.index, 123) self.assertEqual(control_channel.name, "u123") + def test_validation(self): + """Test channel validation""" + with self.assertRaises(PulseError): + ControlChannel(0.5) + with self.assertRaises(PulseError): + ControlChannel(-1) + class TestMeasureChannel(QiskitTestCase): """MeasureChannel tests.""" @@ -145,6 +174,13 @@ def test_default(self): self.assertEqual(measure_channel.index, 123) self.assertEqual(measure_channel.name, "m123") + def test_validation(self): + """Test channel validation""" + with self.assertRaises(PulseError): + MeasureChannel(0.5) + with self.assertRaises(PulseError): + MeasureChannel(-1) + if __name__ == "__main__": unittest.main() diff --git a/test/python/qasm2/test_parse_errors.py b/test/python/qasm2/test_parse_errors.py index 1cc90cdaef3c..1963fc64b3b6 100644 --- a/test/python/qasm2/test_parse_errors.py +++ b/test/python/qasm2/test_parse_errors.py @@ -865,3 +865,10 @@ def test_required_version_empty(self): qiskit.qasm2.QASM2ParseError, r"\[strict\] .*needed a version statement" ): qiskit.qasm2.loads("", strict=True) + + def test_barrier_requires_args(self): + program = "OPENQASM 2.0; qreg q[2]; barrier;" + with self.assertRaisesRegex( + qiskit.qasm2.QASM2ParseError, r"\[strict\] barrier statements must have at least one" + ): + qiskit.qasm2.loads(program, strict=True) diff --git a/test/python/transpiler/test_apply_layout.py b/test/python/transpiler/test_apply_layout.py index ae950912840d..d156db19dc18 100644 --- a/test/python/transpiler/test_apply_layout.py +++ b/test/python/transpiler/test_apply_layout.py @@ -18,8 +18,11 @@ from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.transpiler.layout import Layout -from qiskit.transpiler.passes import ApplyLayout +from qiskit.transpiler.passes import ApplyLayout, SetLayout from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.preset_passmanagers import common +from qiskit.providers.fake_provider import FakeVigoV2 +from qiskit.transpiler import PassManager class TestApplyLayout(QiskitTestCase): @@ -115,6 +118,54 @@ def test_circuit_with_swap_gate(self): self.assertEqual(circuit_to_dag(expected), after) + def test_final_layout_is_updated(self): + """Test that if vf2postlayout runs that we've updated the final layout.""" + qubits = 3 + qc = QuantumCircuit(qubits) + for i in range(5): + qc.cx(i % qubits, int(i + qubits / 2) % qubits) + initial_pm = PassManager([SetLayout([1, 3, 4])]) + cmap = FakeVigoV2().coupling_map + initial_pm += common.generate_embed_passmanager(cmap) + first_layout_circ = initial_pm.run(qc) + out_pass = ApplyLayout() + out_pass.property_set["layout"] = first_layout_circ.layout.initial_layout + out_pass.property_set[ + "original_qubit_indices" + ] = first_layout_circ.layout.input_qubit_mapping + out_pass.property_set["final_layout"] = Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[1]: 3, + first_layout_circ.qubits[2]: 2, + first_layout_circ.qubits[3]: 4, + first_layout_circ.qubits[4]: 1, + } + ) + # Set a post layout like vf2postlayout would: + out_pass.property_set["post_layout"] = Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[2]: 4, + first_layout_circ.qubits[1]: 2, + first_layout_circ.qubits[3]: 1, + first_layout_circ.qubits[4]: 3, + } + ) + out_pass(first_layout_circ) + self.assertEqual( + out_pass.property_set["final_layout"], + Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[2]: 1, + first_layout_circ.qubits[4]: 4, + first_layout_circ.qubits[1]: 3, + first_layout_circ.qubits[3]: 2, + } + ), + ) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 9b4ee00dfef8..39ca93628a75 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -18,8 +18,9 @@ import ddt import numpy.random -from qiskit.circuit import Clbit, ControlFlowOp +from qiskit.circuit import Clbit, ControlFlowOp, Qubit from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate +from qiskit.circuit.classical import expr from qiskit.circuit.random import random_circuit from qiskit.compiler.transpiler import transpile from qiskit.converters import circuit_to_dag, dag_to_circuit @@ -730,6 +731,44 @@ def test_pre_intra_post_if_else(self): expected.measure(qreg, creg[[1, 2, 0, 3, 4]]) self.assertEqual(dag_to_circuit(cdag), expected) + def test_if_expr(self): + """Test simple if conditional with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + body = QuantumCircuit(4) + body.cx(0, 1) + body.cx(0, 2) + body.cx(0, 3) + qc = QuantumCircuit(4, 2) + qc.if_test(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = SabreSwap(coupling, "lookahead", seed=58, trials=1).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_if_else_expr(self): + """Test simple if/else conditional with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + true = QuantumCircuit(4) + true.cx(0, 1) + true.cx(0, 2) + true.cx(0, 3) + false = QuantumCircuit(4) + false.cx(3, 0) + false.cx(3, 1) + false.cx(3, 2) + qc = QuantumCircuit(4, 2) + qc.if_else(expr.logic_and(qc.clbits[0], qc.clbits[1]), true, false, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = SabreSwap(coupling, "lookahead", seed=58, trials=1).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + def test_no_layout_change(self): """test controlflow with no layout change needed""" num_qubits = 5 @@ -841,6 +880,54 @@ def test_while_loop(self): expected.measure(qreg, creg) self.assertEqual(dag_to_circuit(cdag), expected) + def test_while_loop_expr(self): + """Test simple while loop with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + body = QuantumCircuit(4) + body.cx(0, 1) + body.cx(0, 2) + body.cx(0, 3) + qc = QuantumCircuit(4, 2) + qc.while_loop(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_switch_implicit_carg_use(self): + """Test that a switch statement that uses cargs only implicitly via its ``target`` attribute + and not explicitly in bodies of the cases is routed correctly, with the dependencies + fulfilled correctly.""" + coupling = CouplingMap.from_line(4) + pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1) + + body = QuantumCircuit([Qubit()]) + body.x(0) + + # If the classical wire condition isn't respected, then the switch would appear in the front + # layer and be immediately eligible for routing, which would produce invalid output. + qc = QuantumCircuit(4, 1) + qc.cx(0, 1) + qc.cx(1, 2) + qc.cx(0, 2) + qc.measure(2, 0) + qc.switch(expr.lift(qc.clbits[0]), [(False, body.copy()), (True, body.copy())], [3], []) + + expected = QuantumCircuit(4, 1) + expected.cx(0, 1) + expected.cx(1, 2) + expected.swap(2, 1) + expected.cx(0, 1) + expected.measure(1, 0) + expected.switch( + expr.lift(expected.clbits[0]), [(False, body.copy()), (True, body.copy())], [3], [] + ) + + self.assertEqual(pass_(qc), expected) + def test_switch_single_case(self): """Test routing of 'switch' with just a single case.""" qreg = QuantumRegister(5, "q") @@ -923,6 +1010,88 @@ def test_switch_nonexhaustive(self): self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + def test_switch_expr_single_case(self): + """Test routing of 'switch' with an `Expr` target and just a single case.""" + qreg = QuantumRegister(5, "q") + creg = ClassicalRegister(3, "c") + qc = QuantumCircuit(qreg, creg) + + case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.cx(2, 0) + qc.switch(expr.bit_or(creg, 5), [(0, case0)], qreg[[0, 1, 2]], creg) + + coupling = CouplingMap.from_line(len(qreg)) + pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1) + test = pass_(qc) + + check = CheckMap(coupling) + check(test) + self.assertTrue(check.property_set["is_swap_mapped"]) + + expected = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.swap(0, 1) + case0.cx(2, 1) + case0.swap(0, 1) + expected.switch(expr.bit_or(creg, 5), [(0, case0)], qreg[[0, 1, 2]], creg[:]) + + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + + def test_switch_expr_nonexhaustive(self): + """Test routing of 'switch' with an `Expr` target and several but nonexhaustive cases.""" + qreg = QuantumRegister(5, "q") + creg = ClassicalRegister(3, "c") + + qc = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg, creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.cx(2, 0) + case1 = QuantumCircuit(qreg, creg[:]) + case1.cx(1, 2) + case1.cx(2, 3) + case1.cx(3, 1) + case2 = QuantumCircuit(qreg, creg[:]) + case2.cx(2, 3) + case2.cx(3, 4) + case2.cx(4, 2) + qc.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) + + coupling = CouplingMap.from_line(len(qreg)) + pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1) + test = pass_(qc) + + check = CheckMap(coupling) + check(test) + self.assertTrue(check.property_set["is_swap_mapped"]) + + expected = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg, creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.swap(0, 1) + case0.cx(2, 1) + case0.swap(0, 1) + case1 = QuantumCircuit(qreg, creg[:]) + case1.cx(1, 2) + case1.cx(2, 3) + case1.swap(1, 2) + case1.cx(3, 2) + case1.swap(1, 2) + case2 = QuantumCircuit(qreg, creg[:]) + case2.cx(2, 3) + case2.cx(3, 4) + case2.swap(2, 3) + case2.cx(4, 3) + case2.swap(2, 3) + expected.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) + + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + def test_nested_inner_cnot(self): """test swap in nested if else controlflow construct; swap in inner""" num_qubits = 3 diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 152889f9512a..6ef3c01f2924 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -29,6 +29,7 @@ from qiskit.providers.fake_provider import FakeMumbai, FakeMumbaiV2 from qiskit.compiler.transpiler import transpile from qiskit.circuit import ControlFlowOp, Clbit, CASE_DEFAULT +from qiskit.circuit.classical import expr @ddt @@ -882,6 +883,44 @@ def test_pre_intra_post_if_else(self): expected.measure(qreg, creg[[2, 4, 0, 3, 1]]) self.assertEqual(dag_to_circuit(cdag), expected) + def test_if_expr(self): + """Test simple if conditional with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + body = QuantumCircuit(4) + body.cx(0, 1) + body.cx(0, 2) + body.cx(0, 3) + qc = QuantumCircuit(4, 2) + qc.if_test(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=58).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_if_else_expr(self): + """Test simple if/else conditional with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + true = QuantumCircuit(4) + true.cx(0, 1) + true.cx(0, 2) + true.cx(0, 3) + false = QuantumCircuit(4) + false.cx(3, 0) + false.cx(3, 1) + false.cx(3, 2) + qc = QuantumCircuit(4, 2) + qc.if_else(expr.logic_and(qc.clbits[0], qc.clbits[1]), true, false, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=58).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + def test_no_layout_change(self): """test controlflow with no layout change needed""" num_qubits = 5 @@ -996,6 +1035,23 @@ def test_while_loop(self): expected.measure(qreg, creg) self.assertEqual(dag_to_circuit(cdag), expected) + def test_while_loop_expr(self): + """Test simple while loop with an `Expr` condition.""" + coupling = CouplingMap.from_line(4) + + body = QuantumCircuit(4) + body.cx(0, 1) + body.cx(0, 2) + body.cx(0, 3) + qc = QuantumCircuit(4, 2) + qc.while_loop(expr.logic_and(qc.clbits[0], qc.clbits[1]), body, [0, 1, 2, 3], []) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=58).run(dag) + check_map_pass = CheckMap(coupling) + check_map_pass.run(cdag) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + def test_switch_single_case(self): """Test routing of 'switch' with just a single case.""" qreg = QuantumRegister(5, "q") @@ -1110,6 +1166,89 @@ def test_switch_exhaustive(self, labels): self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + def test_switch_nonexhaustive_expr(self): + """Test routing of 'switch' with an `Expr` target and several but nonexhaustive cases.""" + qreg = QuantumRegister(5, "q") + creg = ClassicalRegister(3, "c") + + qc = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg, creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.cx(2, 0) + case1 = QuantumCircuit(qreg, creg[:]) + case1.cx(1, 2) + case1.cx(2, 3) + case1.cx(3, 1) + case2 = QuantumCircuit(qreg, creg[:]) + case2.cx(2, 3) + case2.cx(3, 4) + case2.cx(4, 2) + qc.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) + + coupling = CouplingMap.from_line(len(qreg)) + pass_ = StochasticSwap(coupling, seed=58) + test = pass_(qc) + + check = CheckMap(coupling) + check(test) + self.assertTrue(check.property_set["is_swap_mapped"]) + + expected = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg, creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.swap(0, 1) + case0.cx(2, 1) + case0.swap(0, 1) + case1 = QuantumCircuit(qreg, creg[:]) + case1.cx(1, 2) + case1.cx(2, 3) + case1.swap(1, 2) + case1.cx(3, 2) + case1.swap(1, 2) + case2 = QuantumCircuit(qreg, creg[:]) + case2.cx(2, 3) + case2.cx(3, 4) + case2.swap(3, 4) + case2.cx(3, 2) + case2.swap(3, 4) + expected.switch(expr.bit_or(creg, 5), [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg) + + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + + @data((0, 1, 2, 3), (CASE_DEFAULT,)) + def test_switch_exhaustive_expr(self, labels): + """Test routing of 'switch' with exhaustive cases on an `Expr` target; we should not require + restoring the layout afterwards.""" + qreg = QuantumRegister(5, "q") + creg = ClassicalRegister(2, "c") + + qc = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.cx(2, 0) + qc.switch(expr.bit_or(creg, 3), [(labels, case0)], qreg[[0, 1, 2]], creg) + + coupling = CouplingMap.from_line(len(qreg)) + pass_ = StochasticSwap(coupling, seed=58) + test = pass_(qc) + + check = CheckMap(coupling) + check(test) + self.assertTrue(check.property_set["is_swap_mapped"]) + + expected = QuantumCircuit(qreg, creg) + case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:]) + case0.cx(0, 1) + case0.cx(1, 2) + case0.swap(0, 1) + case0.cx(2, 1) + expected.switch(expr.bit_or(creg, 3), [(labels, case0)], qreg[[0, 1, 2]], creg) + + self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected)) + def test_nested_inner_cnot(self): """test swap in nested if else controlflow construct; swap in inner""" seed = 1
SoftwareVersion
{name}{version}
{name}{version}
System information