From 1d844eccae743032c4416fa67069dbc3478144a9 Mon Sep 17 00:00:00 2001 From: Kazuki Tsuoka Date: Wed, 17 May 2023 18:41:48 +0900 Subject: [PATCH 01/12] add abs method to ParameterExpression (#9309) * added abs method to parameterexpression * fixed wrong call in abs function * changed definition of abs to __abs__ * additionally implemented __le__, __ge__, __lt__, __gt__ operators * fixed requested features and implemented more tests for __lt__, __gt__ * fixed lint errors in parameterexpression and its tests * fixed formatting error in docstrings * removed __lt__ and friends, fixed recommendations * added more tests for abs function * fixed lint * added a releasenote to document change * mod function & add more tests * add new line at the end of file * fix lint * add alias of __abs__ for doc * mod test_compile_with_ufunc * add test * fix test * fix test * fix test * fix test * fix test * Update test_parameters.py * Update releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml Co-authored-by: Julien Gacon --------- Co-authored-by: Christopher Zachow Co-authored-by: Julien Gacon --- qiskit/circuit/__init__.py | 6 +- qiskit/circuit/parameterexpression.py | 15 +++++ ...-parameterexpression-347ffef62946b38b.yaml | 14 ++++ test/python/circuit/test_parameters.py | 65 +++++++++++++++++-- 4 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 52848e2e77b1..083325efad2f 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -306,9 +306,9 @@ .. autosummary:: :toctree: ../stubs/ - Parameter - ParameterVector - ParameterExpression + Parameter + ParameterVector + ParameterExpression Random Circuits --------------- diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 0d5015a29259..6858efa1301d 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -498,6 +498,21 @@ def __copy__(self): def __deepcopy__(self, memo=None): return self + def __abs__(self): + """Absolute of a ParameterExpression""" + if _optionals.HAS_SYMENGINE: + import symengine + + return self._call(symengine.Abs) + else: + from sympy import Abs as _abs + + return self._call(_abs) + + def abs(self): + """Absolute of a ParameterExpression""" + return self.__abs__() + def __eq__(self, other): """Check if this parameter expression is equal to another parameter expression or a fixed value (only if this is a bound expression). diff --git a/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml b/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml new file mode 100644 index 000000000000..1385724d8d1f --- /dev/null +++ b/releasenotes/notes/add-abs-to-parameterexpression-347ffef62946b38b.yaml @@ -0,0 +1,14 @@ + +issues: + - | + Added support for taking absolute values of :class:`.ParameterExpression`\s. For example, + the following is now possible:: + + from qiskit.circuit import QuantumCircuit, Parameter + + x = Parameter("x") + circuit = QuantumCircuit(1) + circuit.rx(abs(x), 0) + + bound = circuit.bind_parameters({x: -1}) + diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 5f49f026a7a5..e19fbd205166 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1113,6 +1113,7 @@ def test_circuit_with_ufunc(self): theta = Parameter(name="theta") qc = QuantumCircuit(2) + qc.p(numpy.abs(-phi), 0) qc.p(numpy.cos(phi), 0) qc.p(numpy.sin(phi), 0) qc.p(numpy.tan(phi), 0) @@ -1123,6 +1124,7 @@ def test_circuit_with_ufunc(self): qc.assign_parameters({phi: pi, theta: 1}, inplace=True) qc_ref = QuantumCircuit(2) + qc_ref.p(pi, 0) qc_ref.p(-1, 0) qc_ref.p(0, 0) qc_ref.p(0, 0) @@ -1133,14 +1135,40 @@ def test_circuit_with_ufunc(self): self.assertEqual(qc, qc_ref) def test_compile_with_ufunc(self): - """Test compiling of circuit with unbounded parameters + """Test compiling of circuit with unbound parameters after we apply universal functions.""" - phi = Parameter("phi") - qc = QuantumCircuit(1) - qc.rx(numpy.cos(phi), 0) - backend = BasicAer.get_backend("qasm_simulator") - qc_aer = transpile(qc, backend) - self.assertIn(phi, qc_aer.parameters) + from math import pi + + theta = ParameterVector("theta", length=7) + + qc = QuantumCircuit(7) + qc.rx(numpy.abs(theta[0]), 0) + qc.rx(numpy.cos(theta[1]), 1) + qc.rx(numpy.sin(theta[2]), 2) + qc.rx(numpy.tan(theta[3]), 3) + qc.rx(numpy.arccos(theta[4]), 4) + qc.rx(numpy.arctan(theta[5]), 5) + qc.rx(numpy.arcsin(theta[6]), 6) + + # transpile to different basis + transpiled = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=0) + + for x in theta: + self.assertIn(x, transpiled.parameters) + + bound = transpiled.bind_parameters({theta: [-1, pi, pi, pi, 1, 1, 1]}) + + expected = QuantumCircuit(7) + expected.rx(1.0, 0) + expected.rx(-1.0, 1) + expected.rx(0.0, 2) + expected.rx(0.0, 3) + expected.rx(0.0, 4) + expected.rx(pi / 4, 5) + expected.rx(pi / 2, 6) + expected = transpile(expected, basis_gates=["rz", "sx", "x", "cx"], optimization_level=0) + + self.assertEqual(expected, bound) def test_parametervector_resize(self): """Test the resize method of the parameter vector.""" @@ -1207,6 +1235,29 @@ def test_compare_to_value_when_bound(self): bound_expr = x.bind({x: 2.3}) self.assertEqual(bound_expr, 2.3) + def test_abs_function_when_bound(self): + """Verify expression can be used with + abs functions when bound.""" + + x = Parameter("x") + xb_1 = x.bind({x: 2.0}) + xb_2 = x.bind({x: 3.0 + 4.0j}) + + self.assertEqual(abs(xb_1), 2.0) + self.assertEqual(abs(-xb_1), 2.0) + self.assertEqual(abs(xb_2), 5.0) + + def test_abs_function_when_not_bound(self): + """Verify expression can be used with + abs functions when not bound.""" + + x = Parameter("x") + y = Parameter("y") + + self.assertEqual(abs(x), abs(-x)) + self.assertEqual(abs(x) * abs(y), abs(x * y)) + self.assertEqual(abs(x) / abs(y), abs(x / y)) + def test_cast_to_float_when_bound(self): """Verify expression can be cast to a float when fully bound.""" From 591116f584fb462cab7cd4c4ecf879b0b89e8d3f Mon Sep 17 00:00:00 2001 From: Guillermo-Mijares-Vilarino <106545082+Guillermo-Mijares-Vilarino@users.noreply.github.com> Date: Fri, 19 May 2023 14:14:28 +0200 Subject: [PATCH 02/12] Add how-to guides about primitives (#9716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added how-to section * added how to compose circuits guide * added how to visualize circuit guide * add how to create parameterized circuit guide * add guides * removed latex circuit visualization * fixed title underlines * Fix typo * explicitly set cregbundle to False when appending circuit with classical register * Fix typo * Added explanation about needed latex distribution * added link to qiskit.visualization module * Simplified references * Change 'we' to 'you' * Changed how_to.rst to how_to/index.rst * Removed jupyter-execute from create_a_quantum_circuit * Removed jupyter-execute from create_a_parameterized_circuit * Removed jupyter-execute from compose_quantum_circuits * Removed jupyter-execute from visualize_a_quantum_circuit * Add sphinx.ext.doctest to conf.py * Change header symbol for index and visualization guide * Added guides for sampler and estimator and simplified toctree * Fixed underline * Add links to how-to create circuit * Added section about parameterized circuits to primitives how-tos * Remove other how-tos * Remove links * Added reference to other implementations of primitives * Update docs/how_to/use_estimator.rst Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> * Improved titles * set global doctest flags and disable automatic doctests * Added part about changing options * Add new aer docs page to intersphinx * Added notes about circuit measurements * Improved notes about circuit measurements * Update docs/how_to/use_estimator.rst Co-authored-by: Junye Huang * Update docs/how_to/use_sampler.rst Co-authored-by: Junye Huang * Update docs/how_to/use_sampler.rst Co-authored-by: Junye Huang * Update docs/how_to/use_sampler.rst Co-authored-by: Junye Huang * Added numpy to intersphinx and note about doctest and plot directive * Added note about quasi-probabilities * rename how to to How-to Guides * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_estimator.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Update docs/how_to/use_sampler.rst Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Remove generally * Changed sampler initial note to mention BackendSampler * Update docs/how_to/use_sampler.rst Co-authored-by: Luciano Bello * Added code example for backend primitives and link to providers page * Update intersphinx mapping to ecosystem for aer and runtime * Change rustworkx link * Add "primitive" to estimator how-to title Co-authored-by: Luciano Bello * Change hypothetical import to avoid confusion Co-authored-by: Luciano Bello * Update docs/how_to/use_estimator.rst Co-authored-by: Luciano Bello * Include "primitive" word in sampler how-to title Co-authored-by: Luciano Bello * Improve phrasing Co-authored-by: Luciano Bello * Include notice about doctest and plot directive incompatibility in sampler guide * change->modify sampler options * change qiskit_provider to * shots->amount of shots in estimator how-to * Add quick explanation about using multiple circuits and observables and adjust plurals * singular * singular --------- Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> Co-authored-by: Junye Huang Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Luciano Bello --- docs/conf.py | 16 +- docs/how_to/index.rst | 15 ++ docs/how_to/use_estimator.rst | 269 ++++++++++++++++++++++++++++++++++ docs/how_to/use_sampler.rst | 255 ++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 5 files changed, 546 insertions(+), 10 deletions(-) create mode 100644 docs/how_to/index.rst create mode 100644 docs/how_to/use_estimator.rst create mode 100644 docs/how_to/use_sampler.rst diff --git a/docs/conf.py b/docs/conf.py index b443e895e9d0..b3ec73843a54 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,9 +65,9 @@ modindex_common_prefix = ["qiskit."] intersphinx_mapping = { - "retworkx": ("https://qiskit.org/documentation/retworkx/", None), - "qiskit-ibm-runtime": ("https://qiskit.org/documentation/partners/qiskit_ibm_runtime/", None), - "qiskit-aer": ("https://qiskit.org/documentation/aer/", None), + "rustworkx": ("https://qiskit.org/ecosystem/rustworkx/", None), + "qiskit-ibm-runtime": ("https://qiskit.org/ecosystem/ibm-runtime/", None), + "qiskit-aer": ("https://qiskit.org/ecosystem/aer/", None), "numpy": ("https://numpy.org/doc/stable/", None) } @@ -116,17 +116,13 @@ autoclass_content = "both" -# -- Options for Doctest -------------------------------------------------------- -import sphinx.ext.doctest +# -- Options for Doctest -------------------------------------------------------- -# This option will make doctest ignore whitespace when testing code. -# It's specially important for circuit representation as it gives an -# error otherwise -doctest_default_flags = sphinx.ext.doctest.doctest.NORMALIZE_WHITESPACE +doctest_default_flags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.IGNORE_EXCEPTION_DETAIL | doctest.DONT_ACCEPT_TRUE_FOR_1 # Leaving this string empty disables testing of doctest blocks from docstrings. # Doctest blocks are structures like this one: # >> code # output -doctest_test_doctest_blocks = "" +doctest_test_doctest_blocks = "" \ No newline at end of file diff --git a/docs/how_to/index.rst b/docs/how_to/index.rst new file mode 100644 index 000000000000..a20d20870d99 --- /dev/null +++ b/docs/how_to/index.rst @@ -0,0 +1,15 @@ +.. _how_to: + +############# +How-to Guides +############# + + +Use the primitives +================== + +.. toctree:: + :maxdepth: 1 + + use_sampler + use_estimator \ No newline at end of file diff --git a/docs/how_to/use_estimator.rst b/docs/how_to/use_estimator.rst new file mode 100644 index 000000000000..61a69ab5ffbc --- /dev/null +++ b/docs/how_to/use_estimator.rst @@ -0,0 +1,269 @@ +######################################################### +Compute an expectation value with ``Estimator`` primitive +######################################################### + +This guide shows how to get the expected value of an observable for a given quantum circuit with the :class:`~qiskit.primitives.Estimator` primitive. + +.. note:: + + While this guide uses Qiskit’s reference implementation, the ``Estimator`` primitive can be run with any provider using :class:`~qiskit.primitives.BackendEstimator` . + + .. code-block:: + + from qiskit.primitives import BackendEstimator + from import QiskitProvider + + provider = QiskitProvider() + backend = provider.get_backend('backend_name') + estimator = BackendEstimator(backend) + + There are some providers that implement primitives natively (see `this page `_ for more details). + + +Initialize observables +====================== + +The first step is to define the observables whose expected value you want to compute. Each observable can be any ``BaseOperator``, like the operators from :mod:`qiskit.quantum_info`. +Among them it is preferable to use :class:`~qiskit.quantum_info.SparsePauliOp`. + +.. testcode:: + + from qiskit.quantum_info import SparsePauliOp + + observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1]) + +Initialize quantum circuit +========================== + +Then you need to create the :class:`~qiskit.circuit.QuantumCircuit`\ s for which you want to obtain the expected value. + +.. plot:: + :include-source: + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0,1) + qc.draw("mpl") + +.. testsetup:: + + # This code is repeated (but hidden) because we will need to use the variables with the extension sphinx.ext.doctest (testsetup/testcode/testoutput directives) + # and we can't reuse the variables from the plot directive above because they are incompatible. + # The plot directive is used to draw the circuit with matplotlib and the code is shown because of the include-source flag. + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0,1) + +.. note:: + + The :class:`~qiskit.circuit.QuantumCircuit` you pass to :class:`~qiskit.primitives.Estimator` must not include any measurements. + +Initialize the ``Estimator`` +============================ + +Then, you need to instantiate an :class:`~qiskit.primitives.Estimator`. + +.. testcode:: + + from qiskit.primitives import Estimator + + estimator = Estimator() + +Run and get results +=================== + +Now that you have defined your ``estimator``, you can run your estimation by calling the :meth:`~qiskit.primitives.Estimator.run` method, +which returns an instance of :class:`~.PrimitiveJob` (subclass of :class:`~qiskit.providers.JobV1`). You can get the results from the job (as a :class:`~qiskit.primitives.EstimatorResult` object) +with the :meth:`~qiskit.providers.JobV1.result` method. + +.. testcode:: + + job = estimator.run(qc, observable) + result = job.result() + print(result) + +.. testoutput:: + + EstimatorResult(values=array([4.]), metadata=[{}]) + +While this example only uses one :class:`~qiskit.circuit.QuantumCircuit` and one observable, if you want to get expectation values for multiple circuits and observables you can +pass a ``list`` of :class:`~qiskit.circuit.QuantumCircuit`\ s and a list of ``BaseOperator``\ s to the :meth:`~qiskit.primitives.Estimator.run` method. Both ``list``\ s must have +the same length. + +Get the expected value +---------------------- + +From these results you can extract the expected values with the attribute :attr:`~qiskit.primitives.EstimatorResult.values`. + +:attr:`~qiskit.primitives.EstimatorResult.values` returns a :class:`numpy.ndarray` +whose ``i``-th element is the expectation value corresponding to the ``i``-th circuit and ``i``-th observable. + +.. testcode:: + + exp_value = result.values[0] + print(exp_value) + +.. testoutput:: + + 3.999999999999999 + +Parameterized circuit with ``Estimator`` +======================================== + +The :class:`~qiskit.primitives.Estimator` primitive can be run with unbound parameterized circuits like the one below. +You can also manually bind values to the parameters of the circuit and follow the steps +of the previous example. + +.. testcode:: + + from qiskit.circuit import Parameter + + theta = Parameter('θ') + param_qc = QuantumCircuit(2) + param_qc.ry(theta, 0) + param_qc.cx(0,1) + print(param_qc.draw()) + +.. testoutput:: + + ┌───────┐ + q_0: ┤ Ry(θ) ├──■── + └───────┘┌─┴─┐ + q_1: ─────────┤ X ├ + └───┘ + +The main difference with the previous case is that now you need to specify the sets of parameter values +for which you want to evaluate the expectation value as a ``list`` of ``list``\ s of ``float``\ s. +The ``i``-th element of the outer``list`` is the set of parameter values +that corresponds to the ``i``-th circuit and observable. + +.. testcode:: + + import numpy as np + + parameter_values = [[0], [np.pi/6], [np.pi/2]] + + job = estimator.run([param_qc]*3, [observable]*3, parameter_values=parameter_values) + values = job.result().values + + for i in range(3): + print(f"Parameter: {parameter_values[i][0]:.5f}\t Expectation value: {values[i]}") + +.. testoutput:: + + Parameter: 0.00000 Expectation value: 2.0 + Parameter: 0.52360 Expectation value: 3.0 + Parameter: 1.57080 Expectation value: 4.0 + +Change run options +================== + +Your workflow might require tuning primitive run options, such as the amount of shots. + +By default, the reference :class:`~qiskit.primitives.Estimator` class performs an exact statevector +calculation based on the :class:`~qiskit.quantum_info.Statevector` class. However, this can be +modified to include shot noise if the number of ``shots`` is set. +For reproducibility purposes, a ``seed`` will also be set in the following examples. + +There are two main ways of setting options in the :class:`~qiskit.primitives.Estimator`: + +* Set keyword arguments in the :meth:`~qiskit.primitives.Estimator.run` method. +* Modify :class:`~qiskit.primitives.Estimator` options. + +Set keyword arguments for :meth:`~qiskit.primitives.Estimator.run` +------------------------------------------------------------------ + +If you only want to change the settings for a specific run, it can be more convenient to +set the options inside the :meth:`~qiskit.primitives.Estimator.run` method. You can do this by +passing them as keyword arguments. + +.. testcode:: + + job = estimator.run(qc, observable, shots=2048, seed=123) + result = job.result() + print(result) + +.. testoutput:: + + EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}]) + +.. testcode:: + + print(result.values[0]) + +.. testoutput:: + + 3.999999998697238 + +Modify :class:`~qiskit.primitives.Estimator` options +----------------------------------------------------- + +If you want to keep some configuration values for several runs, it can be better to +change the :class:`~qiskit.primitives.Estimator` options. That way you can use the same +:class:`~qiskit.primitives.Estimator` object as many times as you wish without having to +rewrite the configuration values every time you use :meth:`~qiskit.primitives.Estimator.run`. + +Modify existing :class:`~qiskit.primitives.Estimator` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer to change the options of an already-defined :class:`~qiskit.primitives.Estimator`, you can use +:meth:`~qiskit.primitives.Estimator.set_options` and introduce the new options as keyword arguments. + +.. testcode:: + + estimator.set_options(shots=2048, seed=123) + + job = estimator.run(qc, observable) + result = job.result() + print(result) + +.. testoutput:: + + EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}]) + +.. testcode:: + + print(result.values[0]) + +.. testoutput:: + + 3.999999998697238 + + +Define a new :class:`~qiskit.primitives.Estimator` with the options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer to define a new :class:`~qiskit.primitives.Estimator` with new options, you need to +define a ``dict`` like this one: + +.. testcode:: + + options = {"shots": 2048, "seed": 123} + +And then you can introduce it into your new :class:`~qiskit.primitives.Estimator` with the +``options`` argument. + +.. testcode:: + + estimator = Estimator(options=options) + + job = estimator.run(qc, observable) + result = job.result() + print(result) + +.. testoutput:: + + EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}]) + +.. testcode:: + + print(result.values[0]) + +.. testoutput:: + + 3.999999998697238 \ No newline at end of file diff --git a/docs/how_to/use_sampler.rst b/docs/how_to/use_sampler.rst new file mode 100644 index 000000000000..da7257c28fa1 --- /dev/null +++ b/docs/how_to/use_sampler.rst @@ -0,0 +1,255 @@ +############################################################### +Compute circuit output probabilities with ``Sampler`` primitive +############################################################### + +This guide shows how to get the probability distribution of a quantum circuit with the :class:`~qiskit.primitives.Sampler` primitive. + +.. note:: + + While this guide uses Qiskit’s reference implementation, the ``Sampler`` primitive can be run with any provider using :class:`~qiskit.primitives.BackendSampler`. + + .. code-block:: + + from qiskit.primitives import BackendSampler + from import QiskitProvider + + provider = QiskitProvider() + backend = provider.get_backend('backend_name') + sampler = BackendSampler(backend) + + There are some providers that implement primitives natively (see `this page `_ for more details). + +Initialize quantum circuits +=========================== + +The first step is to create the :class:`~qiskit.circuit.QuantumCircuit`\ s from which you want to obtain the probability distribution. + +.. plot:: + :include-source: + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0,1) + qc.measure_all() + qc.draw("mpl") + +.. testsetup:: + + # This code is repeated (but hidden) because we will need to use the variables with the extension sphinx.ext.doctest (testsetup/testcode/testoutput directives) + # and we can't reuse the variables from the plot directive above because they are incompatible. + # The plot directive is used to draw the circuit with matplotlib and the code is shown because of the include-source flag. + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0,1) + qc.measure_all() + +.. note:: + + The :class:`~qiskit.circuit.QuantumCircuit` you pass to :class:`~qiskit.primitives.Sampler` has to include measurements. + +Initialize the ``Sampler`` +========================== + +Then, you need to create a :class:`~qiskit.primitives.Sampler` instance. + +.. testcode:: + + from qiskit.primitives import Sampler + + sampler = Sampler() + +Run and get results +=================== + +Now that you have defined your ``sampler``, you can run it by calling the :meth:`~qiskit.primitives.Sampler.run` method, +which returns an instance of :class:`~.PrimitiveJob` (subclass of :class:`~qiskit.providers.JobV1`). You can get the results from the job (as a :class:`~qiskit.primitives.SamplerResult` object) +with the :meth:`~qiskit.providers.JobV1.result` method. + +.. testcode:: + + job = sampler.run(qc) + result = job.result() + print(result) + +.. testoutput:: + + SamplerResult(quasi_dists=[{0: 0.4999999999999999, 3: 0.4999999999999999}], metadata=[{}]) + +While this example only uses one :class:`~qiskit.circuit.QuantumCircuit`, if you want to sample multiple circuits you can +pass a ``list`` of :class:`~qiskit.circuit.QuantumCircuit` instances to the :meth:`~qiskit.primitives.Sampler.run` method. + +Get the probability distribution +-------------------------------- + +From these results you can extract the quasi-probability distributions with the attribute :attr:`~qiskit.primitives.SamplerResult.quasi_dists`. + +Even though there is only one circuit in this example, :attr:`~qiskit.primitives.SamplerResult.quasi_dists` returns a list of :class:`~qiskit.result.QuasiDistribution`\ s. +``result.quasi_dists[i]`` is the quasi-probability distribution of the ``i``-th circuit. + +.. note:: + + A quasi-probability distribution differs from a probability distribution in that negative values are also allowed. + However the quasi-probabilities must sum up to 1 like probabilities. + Negative quasi-probabilities may appear when using error mitigation techniques. + +.. testcode:: + + quasi_dist = result.quasi_dists[0] + print(quasi_dist) + +.. testoutput:: + + {0: 0.4999999999999999, 3: 0.4999999999999999} + +Probability distribution with binary outputs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer to see the output keys as binary strings instead of decimal numbers, you can use the +:meth:`~qiskit.result.QuasiDistribution.binary_probabilities` method. + +.. testcode:: + + print(quasi_dist.binary_probabilities()) + +.. testoutput:: + + {'00': 0.4999999999999999, '11': 0.4999999999999999} + +Parameterized circuit with ``Sampler`` +======================================== + +The :class:`~qiskit.primitives.Sampler` primitive can be run with unbound parameterized circuits like the one below. +You can also manually bind values to the parameters of the circuit and follow the steps +of the previous example. + +.. testcode:: + + from qiskit.circuit import Parameter + + theta = Parameter('θ') + param_qc = QuantumCircuit(2) + param_qc.ry(theta, 0) + param_qc.cx(0,1) + param_qc.measure_all() + print(param_qc.draw()) + +.. testoutput:: + + ┌───────┐ ░ ┌─┐ + q_0: ┤ Ry(θ) ├──■───░─┤M├─── + └───────┘┌─┴─┐ ░ └╥┘┌─┐ + q_1: ─────────┤ X ├─░──╫─┤M├ + └───┘ ░ ║ └╥┘ + meas: 2/══════════════════╩══╩═ + 0 1 + +The main difference from the previous case is that now you need to specify the sets of parameter values +for which you want to evaluate the expectation value as a ``list`` of ``list``\ s of ``float``\ s. +The ``i``-th element of the outer ``list`` is the set of parameter values +that corresponds to the ``i``-th circuit. + +.. testcode:: + + import numpy as np + + parameter_values = [[0], [np.pi/6], [np.pi/2]] + + job = sampler.run([param_qc]*3, parameter_values=parameter_values) + dists = job.result().quasi_dists + + for i in range(3): + print(f"Parameter: {parameter_values[i][0]:.5f}\t Probabilities: {dists[i]}") + +.. testoutput:: + + Parameter: 0.00000 Probabilities: {0: 1.0} + Parameter: 0.52360 Probabilities: {0: 0.9330127018922194, 3: 0.0669872981077807} + Parameter: 1.57080 Probabilities: {0: 0.5000000000000001, 3: 0.4999999999999999} + +Change run options +================== + +Your workflow might require tuning primitive run options, such as the amount of shots. + +By default, the reference :class:`~qiskit.primitives.Sampler` class performs an exact statevector +calculation based on the :class:`~qiskit.quantum_info.Statevector` class. However, this can be +modified to include shot noise if the number of ``shots`` is set. +For reproducibility purposes, a ``seed`` will also be set in the following examples. + +There are two main ways of setting options in the :class:`~qiskit.primitives.Sampler`: + +* Set keyword arguments in the :meth:`~qiskit.primitives.Sampler.run` method. +* Modify :class:`~qiskit.primitives.Sampler` options. + +Set keyword arguments for :meth:`~qiskit.primitives.Sampler.run` +---------------------------------------------------------------- + +If you only want to change the settings for a specific run, it can be more convenient to +set the options inside the :meth:`~qiskit.primitives.Sampler.run` method. You can do this by +passing them as keyword arguments. + +.. testcode:: + + job = sampler.run(qc, shots=2048, seed=123) + result = job.result() + print(result) + +.. testoutput:: + + SamplerResult(quasi_dists=[{0: 0.5205078125, 3: 0.4794921875}], metadata=[{'shots': 2048}]) + +Modify :class:`~qiskit.primitives.Sampler` options +--------------------------------------------------- + +If you want to keep some configuration values for several runs, it can be better to +change the :class:`~qiskit.primitives.Sampler` options. That way you can use the same +:class:`~qiskit.primitives.Sampler` object as many times as you wish without having to +rewrite the configuration values every time you use :meth:`~qiskit.primitives.Sampler.run`. + +Modify existing :class:`~qiskit.primitives.Sampler` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer to change the options of an already-defined :class:`~qiskit.primitives.Sampler`, you can use +:meth:`~qiskit.primitives.Sampler.set_options` and introduce the new options as keyword arguments. + +.. testcode:: + + sampler.set_options(shots=2048, seed=123) + + job = sampler.run(qc) + result = job.result() + print(result) + +.. testoutput:: + + SamplerResult(quasi_dists=[{0: 0.5205078125, 3: 0.4794921875}], metadata=[{'shots': 2048}]) + +Define a new :class:`~qiskit.primitives.Sampler` with the options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer to define a new :class:`~qiskit.primitives.Sampler` with new options, you need to +define a ``dict`` like this one: + +.. testcode:: + + options = {"shots": 2048, "seed": 123} + +And then you can introduce it into your new :class:`~qiskit.primitives.Sampler` with the +``options`` argument. + +.. testcode:: + + sampler = Sampler(options=options) + + job = sampler.run(qc) + result = job.result() + print(result) + +.. testoutput:: + + SamplerResult(quasi_dists=[{0: 0.5205078125, 3: 0.4794921875}], metadata=[{'shots': 2048}]) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 5dd462fe2ca2..135d5364eee6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,7 @@ Qiskit Terra documentation :maxdepth: 2 :hidden: + How-to Guides API References Migration Guides Release Notes From 81d38340d12f057f73c60d26723b05176c259289 Mon Sep 17 00:00:00 2001 From: "Vicente P. Soloviev" Date: Fri, 19 May 2023 17:23:28 +0200 Subject: [PATCH 03/12] Allow access to UDMA optimizer history information (#8695) * fix issue 8687 by moving history attribute tu public by a getter * callback function * tests * add test and reno * lint --------- Co-authored-by: Julien Gacon --- qiskit/algorithms/optimizers/umda.py | 20 ++++++++++++- .../notes/umda-callback-eb644a49c5a9ad37.yaml | 7 +++++ .../python/algorithms/optimizers/test_umda.py | 30 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml diff --git a/qiskit/algorithms/optimizers/umda.py b/qiskit/algorithms/optimizers/umda.py index 3e84d59b7ad8..dd8739c2fac1 100644 --- a/qiskit/algorithms/optimizers/umda.py +++ b/qiskit/algorithms/optimizers/umda.py @@ -11,6 +11,7 @@ # that they have been altered from the originals. """Univariate Marginal Distribution Algorithm (Estimation-of-Distribution-Algorithm).""" + from __future__ import annotations from collections.abc import Callable @@ -122,12 +123,21 @@ class UMDA(Optimizer): ELITE_FACTOR = 0.4 STD_BOUND = 0.3 - def __init__(self, maxiter: int = 100, size_gen: int = 20, alpha: float = 0.5) -> None: + def __init__( + self, + maxiter: int = 100, + size_gen: int = 20, + alpha: float = 0.5, + callback: Callable[[int, np.array, float], None] | None = None, + ) -> None: r""" Args: maxiter: Maximum number of iterations. size_gen: Population size of each generation. alpha: Percentage (0, 1] of the population to be selected as elite selection. + callback: A callback function passed information in each iteration step. The + information is, in this order: the number of function evaluations, the parameters, + the best function value in this iteration. """ self.size_gen = size_gen @@ -148,6 +158,8 @@ def __init__(self, maxiter: int = 100, size_gen: int = 20, alpha: float = 0.5) - self._n_variables: int | None = None + self.callback = callback + def _initialization(self) -> np.ndarray: vector = np.zeros((4, self._n_variables)) @@ -243,6 +255,11 @@ def minimize( if not_better_count >= self._dead_iter: break + if self.callback is not None: + self.callback( + len(history) * self._size_gen, self._best_ind_global, self._best_cost_global + ) + self._new_generation() result.x = self._best_ind_global @@ -321,6 +338,7 @@ def settings(self) -> dict[str, Any]: "maxiter": self.maxiter, "alpha": self.alpha, "size_gen": self.size_gen, + "callback": self.callback, } def get_support_level(self): diff --git a/releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml b/releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml new file mode 100644 index 000000000000..f79a13a164e4 --- /dev/null +++ b/releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added the option to pass a callback to the :class:`.UMDA` optimizer, which allows + to keep track of the number of function evaluations, the current parameters, and the + best achieved function value. + diff --git a/test/python/algorithms/optimizers/test_umda.py b/test/python/algorithms/optimizers/test_umda.py index b767f4781231..8d05c1cd2454 100644 --- a/test/python/algorithms/optimizers/test_umda.py +++ b/test/python/algorithms/optimizers/test_umda.py @@ -14,6 +14,8 @@ from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np + from qiskit.algorithms.optimizers.umda import UMDA @@ -45,6 +47,7 @@ def test_settings(self): "maxiter": 100, "alpha": 0.6, "size_gen": 30, + "callback": None, } assert umda.settings == set_ @@ -52,7 +55,6 @@ def test_settings(self): def test_minimize(self): """optimize function test""" from scipy.optimize import rosen - import numpy as np from qiskit.utils import algorithm_globals # UMDA is volatile so we need to set the seeds for the execution @@ -66,3 +68,29 @@ def test_minimize(self): assert len(res.x) == len(x_0) np.testing.assert_array_almost_equal(res.x, [1.0] * len(x_0), decimal=2) + + def test_callback(self): + """Test the callback.""" + + def objective(x): + return np.linalg.norm(x) - 1 + + nfevs, parameters, fvals = [], [], [] + + def store_history(*args): + nfevs.append(args[0]) + parameters.append(args[1]) + fvals.append(args[2]) + + optimizer = UMDA(maxiter=1, callback=store_history) + _ = optimizer.minimize(objective, x0=np.arange(5)) + + self.assertEqual(len(nfevs), 1) + self.assertIsInstance(nfevs[0], int) + + self.assertEqual(len(parameters), 1) + self.assertIsInstance(parameters[0], np.ndarray) + self.assertEqual(parameters[0].size, 5) + + self.assertEqual(len(fvals), 1) + self.assertIsInstance(fvals[0], float) From c3b0afaab2a3e1331c6b99903a0fe02e8848ca15 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Fri, 19 May 2023 11:59:25 -0400 Subject: [PATCH 04/12] Deprecate get_vf2_call_limit in preset_passmanagers (#10065) * Deprecate get_vf2_call_limit in preset_passmanagers This has been replaced by get_vf2_limits * Black formatting * Add release note for #10065 --- qiskit/transpiler/preset_passmanagers/common.py | 5 +++++ .../deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml | 8 ++++++++ test/python/transpiler/test_preset_passmanagers.py | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100644 releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index e801d04500fc..4645b4ca1385 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -17,6 +17,7 @@ from typing import Optional from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel +from qiskit.utils.deprecation import deprecate_func from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passes import Error @@ -546,6 +547,10 @@ def _require_alignment(property_set): return scheduling +@deprecate_func( + additional_msg="Instead, use :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_limits`.", + since="0.25.0", +) def get_vf2_call_limit( optimization_level: int, layout_method: Optional[str] = None, diff --git a/releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml b/releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml new file mode 100644 index 000000000000..686ffe004233 --- /dev/null +++ b/releasenotes/notes/deprecate-get_vf2_call_limit-826e0f9212fb27b9.yaml @@ -0,0 +1,8 @@ +--- +deprecations: + - | + The function ``get_vf2_call_limit`` available via the module + :mod:`qiskit.transpiler.preset_passmanagers.common` has been + deprecated. This will likely affect very few users since this function was + neither explicitly exported nor documented. Its functionality has been + replaced and extended by a function in the same module. diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 55eba7eabb13..e1dc7091aa3d 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -19,6 +19,7 @@ import numpy as np +import qiskit from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister from qiskit.circuit import Qubit, Gate, ControlFlowOp, ForLoopOp from qiskit.compiler import transpile, assemble @@ -263,6 +264,11 @@ def counting_callback_func(pass_, dag, time, property_set, count): ) self.assertEqual(gates_in_basis_true_count + 1, collect_2q_blocks_count) + def test_get_vf2_call_limit_deprecated(self): + """Test that calling test_get_vf2_call_limit emits deprecation warning.""" + with self.assertWarns(DeprecationWarning): + qiskit.transpiler.preset_passmanagers.common.get_vf2_call_limit(optimization_level=3) + @ddt class TestTranspileLevels(QiskitTestCase): From bedecbdce563f4e83acc11c7ec5ca6878a36a4b7 Mon Sep 17 00:00:00 2001 From: Mirko Amico <31739486+miamico@users.noreply.github.com> Date: Fri, 19 May 2023 19:58:45 -0600 Subject: [PATCH 05/12] add gaussian square echo pulse (#9370) * add gaussian square echo pulse * address some PR comments * fixed parameters of symbolicpulse * adding math description and reference * change variable names * fix lint * remove width_echo parameter * fix lint * add release notes * fix conflicts * address some PR comments * fixed parameters of symbolicpulse * adding math description and reference * change variable names * fix lint * remove width_echo parameter * fix lint * add release notes * Update first paragraph Co-authored-by: Naoki Kanazawa * Update amp param to float Co-authored-by: Naoki Kanazawa * Update angle def param to float Co-authored-by: Naoki Kanazawa * Update amp constrain Co-authored-by: Naoki Kanazawa * drafting tests * update docstring * finish up test * fix missing pulses from init * addding Cos * missing sin * fixed tests * black formatting * break string * fixing strings * reformatting * fix lint * fixing last things * fix tests * fix black * fix another test * fix amp validation * fixing docstring * Update qiskit/pulse/library/symbolic_pulses.py Co-authored-by: Will Shanks * rename to gaussian_square_echo * fix lint * Update qiskit/qobj/converters/pulse_instruction.py Co-authored-by: Will Shanks * Update releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml Co-authored-by: Will Shanks --------- Co-authored-by: Naoki Kanazawa Co-authored-by: Will Shanks --- qiskit/pulse/__init__.py | 1 + qiskit/pulse/library/__init__.py | 2 + qiskit/pulse/library/symbolic_pulses.py | 201 ++++++++++++++++++ qiskit/qobj/converters/pulse_instruction.py | 1 + ...an-square-echo-pulse-84306f1a02e2bb28.yaml | 9 + test/python/pulse/test_pulse_lib.py | 106 +++++++++ 6 files changed, 320 insertions(+) create mode 100644 releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index d5432eaa7be7..5fe159b947fb 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -142,6 +142,7 @@ Gaussian, GaussianSquare, GaussianSquareDrag, + gaussian_square_echo, Sin, Cos, Sawtooth, diff --git a/qiskit/pulse/library/__init__.py b/qiskit/pulse/library/__init__.py index 8cd80a3b32c9..c7103b7a0d0a 100644 --- a/qiskit/pulse/library/__init__.py +++ b/qiskit/pulse/library/__init__.py @@ -88,6 +88,7 @@ Gaussian GaussianSquare GaussianSquareDrag + gaussian_square_echo Sin Cos Sawtooth @@ -117,6 +118,7 @@ Gaussian, GaussianSquare, GaussianSquareDrag, + gaussian_square_echo, Drag, Constant, Sin, diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 9102df2355e3..2718792ad153 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -1068,6 +1068,207 @@ def GaussianSquareDrag( return instance +def gaussian_square_echo( + duration: Union[int, ParameterExpression], + amp: Union[float, ParameterExpression], + sigma: Union[float, ParameterExpression], + width: Optional[Union[float, ParameterExpression]] = None, + angle: Optional[Union[float, ParameterExpression]] = 0.0, + active_amp: Optional[Union[float, ParameterExpression]] = 0.0, + active_angle: Optional[Union[float, ParameterExpression]] = 0.0, + risefall_sigma_ratio: Optional[Union[float, ParameterExpression]] = None, + name: Optional[str] = None, + limit_amplitude: Optional[bool] = None, +) -> SymbolicPulse: + """An echoed Gaussian square pulse with an active tone overlaid on it. + + The Gaussian Square Echo pulse is composed of three pulses. First, a Gaussian Square pulse + :math:`f_{echo}(x)` with amplitude ``amp`` and phase ``angle`` playing for half duration, + followed by a second Gaussian Square pulse :math:`-f_{echo}(x)` with opposite amplitude + and same phase playing for the rest of the duration. Third a Gaussian Square pulse + :math:`f_{active}(x)` with amplitude ``active_amp`` and phase ``active_angle`` + playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()` + can be written as: + + .. math:: + + g_e(x) &= \\begin{cases}\ + f_{\\text{active}} + f_{\\text{echo}}(x)\ + & x < \\frac{\\text{duration}}{2}\\\\ + f_{\\text{active}} - f_{\\text{echo}}(x)\ + & \\frac{\\text{duration}}{2} < x\ + \\end{cases}\\\\ + + One case where this pulse can be used is when implementing a direct CNOT gate with + a cross-resonance superconducting qubit architecture. When applying this pulse to + the target qubit, the active portion can be used to cancel IX terms from the + cross-resonance drive while the echo portion can reduce the impact of a static ZZ coupling. + + Exactly one of the ``risefall_sigma_ratio`` and ``width`` parameters has to be specified. + + If ``risefall_sigma_ratio`` is not ``None`` and ``width`` is ``None``: + + .. math:: + + \\text{risefall} &= \\text{risefall_sigma_ratio} \\times \\text{sigma}\\\\ + \\text{width} &= \\text{duration} - 2 \\times \\text{risefall} + + If ``width`` is not None and ``risefall_sigma_ratio`` is None: + + .. math:: \\text{risefall} = \\frac{\\text{duration} - \\text{width}}{2} + + References: + 1. |citation1|_ + + .. _citation1: https://iopscience.iop.org/article/10.1088/2058-9565/abe519 + + .. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S., + Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O., + Itoko, T., Kanazawa, N. & others + Demonstration of quantum volume 64 on a superconducting quantum + computing system. (Section V)* + Args: + duration: Pulse length in terms of the sampling period `dt`. + amp: The amplitude of the rise and fall and of the echoed pulse. + sigma: A measure of how wide or narrow the risefall is; see the class + docstring for more details. + width: The duration of the embedded square pulse. + angle: The angle in radians of the complex phase factor uniformly + scaling the echoed pulse. Default value 0. + active_amp: The amplitude of the active pulse. + active_angle: The angle in radian of the complex phase factor uniformly + scaling the active pulse. Default value 0. + risefall_sigma_ratio: The ratio of each risefall duration to sigma. + name: Display name for this pulse envelope. + limit_amplitude: If ``True``, then limit the amplitude of the + waveform to 1. The default is ``True`` and the amplitude is constrained to 1. + Returns: + ScalableSymbolicPulse instance. + Raises: + PulseError: When width and risefall_sigma_ratio are both empty or both non-empty. + """ + # Convert risefall_sigma_ratio into width which is defined in OpenPulse spec + if width is None and risefall_sigma_ratio is None: + raise PulseError( + "Either the pulse width or the risefall_sigma_ratio parameter must be specified." + ) + if width is not None and risefall_sigma_ratio is not None: + raise PulseError( + "Either the pulse width or the risefall_sigma_ratio parameter can be specified" + " but not both." + ) + + if width is None and risefall_sigma_ratio is not None: + width = duration - 2.0 * risefall_sigma_ratio * sigma + + parameters = { + "amp": amp, + "angle": angle, + "sigma": sigma, + "width": width, + "active_amp": active_amp, + "active_angle": active_angle, + } + + # Prepare symbolic expressions + ( + _t, + _duration, + _amp, + _sigma, + _active_amp, + _width, + _angle, + _active_angle, + ) = sym.symbols("t, duration, amp, sigma, active_amp, width, angle, active_angle") + + # gaussian square echo for rotary tone + _center = _duration / 4 + + _width_echo = (_duration - 2 * (_duration - _width)) / 2 + + _sq_t0 = _center - _width_echo / 2 + _sq_t1 = _center + _width_echo / 2 + + _gaussian_ledge = _lifted_gaussian(_t, _sq_t0, -1, _sigma) + _gaussian_redge = _lifted_gaussian(_t, _sq_t1, _duration / 2 + 1, _sigma) + + envelope_expr_p = ( + _amp + * sym.exp(sym.I * _angle) + * sym.Piecewise( + (_gaussian_ledge, _t <= _sq_t0), + (_gaussian_redge, _t >= _sq_t1), + (1, True), + ) + ) + + _center_echo = _duration / 2 + _duration / 4 + + _sq_t0_echo = _center_echo - _width_echo / 2 + _sq_t1_echo = _center_echo + _width_echo / 2 + + _gaussian_ledge_echo = _lifted_gaussian(_t, _sq_t0_echo, _duration / 2 - 1, _sigma) + _gaussian_redge_echo = _lifted_gaussian(_t, _sq_t1_echo, _duration + 1, _sigma) + + envelope_expr_echo = ( + -1 + * _amp + * sym.exp(sym.I * _angle) + * sym.Piecewise( + (_gaussian_ledge_echo, _t <= _sq_t0_echo), + (_gaussian_redge_echo, _t >= _sq_t1_echo), + (1, True), + ) + ) + + envelope_expr = sym.Piecewise( + (envelope_expr_p, _t <= _duration / 2), (envelope_expr_echo, _t >= _duration / 2), (0, True) + ) + + # gaussian square for active cancellation tone + _center_active = _duration / 2 + + _sq_t0_active = _center_active - _width / 2 + _sq_t1_active = _center_active + _width / 2 + + _gaussian_ledge_active = _lifted_gaussian(_t, _sq_t0_active, -1, _sigma) + _gaussian_redge_active = _lifted_gaussian(_t, _sq_t1_active, _duration + 1, _sigma) + + envelope_expr_active = ( + _active_amp + * sym.exp(sym.I * _active_angle) + * sym.Piecewise( + (_gaussian_ledge_active, _t <= _sq_t0_active), + (_gaussian_redge_active, _t >= _sq_t1_active), + (1, True), + ) + ) + + envelop_expr_total = envelope_expr + envelope_expr_active + + consts_expr = sym.And( + _sigma > 0, _width >= 0, _duration >= _width, _duration / 2 >= _width_echo + ) + + # Check validity of amplitudes + valid_amp_conditions_expr = sym.And(sym.Abs(_amp) + sym.Abs(_active_amp) <= 1.0) + + instance = SymbolicPulse( + pulse_type="gaussian_square_echo", + duration=duration, + parameters=parameters, + name=name, + limit_amplitude=limit_amplitude, + envelope=envelop_expr_total, + constraints=consts_expr, + valid_amp_conditions=valid_amp_conditions_expr, + ) + instance.validate_parameters() + + return instance + + class Drag(metaclass=_PulseType): """The Derivative Removal by Adiabatic Gate (DRAG) pulse is a standard Gaussian pulse with an additional Gaussian derivative component and lifting applied. diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 9d4635ad7e7b..a278d7cca3e1 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -43,6 +43,7 @@ class ParametricPulseShapes(Enum): gaussian = "Gaussian" gaussian_square = "GaussianSquare" gaussian_square_drag = "GaussianSquareDrag" + gaussian_square_echo = "gaussian_square_echo" drag = "Drag" constant = "Constant" diff --git a/releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml b/releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml new file mode 100644 index 000000000000..b733f6ba6228 --- /dev/null +++ b/releasenotes/notes/gaussian-square-echo-pulse-84306f1a02e2bb28.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add new :meth:`~qiskit.pulse.gaussian_square_echo` pulse shape. This pulse + is composed by three :class:`~qiskit.pulse.GaussianSquare` pulses. The + first two are echo pulses with duration half of the total duration and + implement rotary tones. The third pulse is a cancellation tone that lasts + the full duration of the pulse and implements correcting single qubit + rotations. \ No newline at end of file diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index f5485762ddd9..a289ad018955 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -24,6 +24,7 @@ Gaussian, GaussianSquare, GaussianSquareDrag, + gaussian_square_echo, Drag, Sin, Cos, @@ -288,6 +289,65 @@ def test_gaussian_square_drag_validation(self): with self.assertRaises(PulseError): GaussianSquareDrag(duration=50, width=0, sigma=4, amp=0.8, beta=-20) + def test_gaussian_square_echo_pulse(self): + """Test that gaussian_square_echo sample pulse matches expectations. + + Test that the real part of the envelop matches GaussianSquare with + given amplitude and phase active for half duration with another + GaussianSquare active for the other half duration with opposite + amplitude and a GaussianSquare active on the entire duration with + its own amplitude and phase + """ + risefall = 32 + sigma = 4 + amp = 0.5 + width = 100 + duration = width + 2 * risefall + active_amp = 0.1 + width_echo = (duration - 2 * (duration - width)) / 2 + + gse = gaussian_square_echo( + duration=duration, sigma=sigma, amp=amp, width=width, active_amp=active_amp + ) + gse_samples = gse.get_waveform().samples + + gs_echo_pulse_pos = GaussianSquare( + duration=duration / 2, sigma=sigma, amp=amp, width=width_echo + ) + gs_echo_pulse_neg = GaussianSquare( + duration=duration / 2, sigma=sigma, amp=-amp, width=width_echo + ) + gs_active_pulse = GaussianSquare( + duration=duration, sigma=sigma, amp=active_amp, width=width + ) + gs_echo_pulse_pos_samples = np.array( + gs_echo_pulse_pos.get_waveform().samples.tolist() + [0] * int(duration / 2) + ) + gs_echo_pulse_neg_samples = np.array( + [0] * int(duration / 2) + gs_echo_pulse_neg.get_waveform().samples.tolist() + ) + gs_active_pulse_samples = gs_active_pulse.get_waveform().samples + + np.testing.assert_almost_equal( + gse_samples, + gs_echo_pulse_pos_samples + gs_echo_pulse_neg_samples + gs_active_pulse_samples, + ) + + def test_gaussian_square_echo_active_amp_validation(self): + """Test gaussian square echo active amp parameter validation.""" + + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.2) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.4) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.5, active_amp=0.3) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=-0.1, active_amp=0.2) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=-0.2) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=0.6) + gaussian_square_echo(duration=50, width=0, sigma=16, amp=-0.5, angle=1.5, active_amp=0.25) + with self.assertRaises(PulseError): + gaussian_square_echo(duration=50, width=0, sigma=16, amp=0.1, active_amp=1.1) + with self.assertRaises(PulseError): + gaussian_square_echo(duration=50, width=0, sigma=4, amp=-0.8, active_amp=-0.3) + def test_drag_pulse(self): """Test that the Drag sample pulse matches the pulse library.""" drag = Drag(duration=25, sigma=4, amp=0.5j, beta=1) @@ -451,6 +511,22 @@ def test_repr(self): repr(gsd), "GaussianSquareDrag(duration=20, sigma=30, width=14.0, beta=1, amp=1.0, angle=0.0)", ) + gse = gaussian_square_echo(duration=20, sigma=30, amp=1.0, width=3) + self.assertEqual( + repr(gse), + ( + "gaussian_square_echo(duration=20, amp=1.0, angle=0.0, sigma=30, width=3," + " active_amp=0.0, active_angle=0.0)" + ), + ) + gse = gaussian_square_echo(duration=20, sigma=30, amp=1.0, risefall_sigma_ratio=0.1) + self.assertEqual( + repr(gse), + ( + "gaussian_square_echo(duration=20, amp=1.0, angle=0.0, sigma=30, width=14.0," + " active_amp=0.0, active_angle=0.0)" + ), + ) drag = Drag(duration=5, amp=0.5, sigma=7, beta=1) self.assertEqual(repr(drag), "Drag(duration=5, sigma=7, beta=1, amp=0.5, angle=0)") const = Constant(duration=150, amp=0.1, angle=0.3) @@ -476,6 +552,17 @@ def test_param_validation(self): with self.assertRaises(PulseError): GaussianSquareDrag(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10, beta=1) + with self.assertRaises(PulseError): + gaussian_square_echo( + duration=150, + amp=0.2, + sigma=8, + ) + with self.assertRaises(PulseError): + gaussian_square_echo(duration=150, amp=0.2, sigma=8, width=160) + with self.assertRaises(PulseError): + gaussian_square_echo(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10) + with self.assertRaises(PulseError): Constant(duration=150, amp=0.9 + 0.8j) with self.assertRaises(PulseError): @@ -536,6 +623,25 @@ def test_gaussian_square_drag_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) + def test_gaussian_square_echo_limit_amplitude(self): + """Test that the check for amplitude less than or equal to 1 can be disabled.""" + with self.assertRaises(PulseError): + gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100) + + with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): + waveform = gaussian_square_echo(duration=100, sigma=1.0, amp=1.1, width=10) + self.assertGreater(np.abs(waveform.amp), 1.0) + + def test_gaussian_square_echo_limit_amplitude_per_instance(self): + """Test that the check for amplitude per instance.""" + with self.assertRaises(PulseError): + gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100) + + waveform = gaussian_square_echo( + duration=1000, sigma=4.0, amp=1.01, width=100, limit_amplitude=False + ) + self.assertGreater(np.abs(waveform.amp), 1.0) + def test_drag_limit_amplitude(self): """Test that the check for amplitude less than or equal to 1 can be disabled.""" with self.assertRaises(PulseError): From 9f647c7e420e02f829cfee54303520e1abe11521 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 22 May 2023 03:01:06 -0400 Subject: [PATCH 06/12] Fix tweedledum runtime detection in BooleanExpression.from_dimacs_file (#10132) This commit fixes an oversight in the 0.24.0 release that caused an accidental change in the exception raised when attempting to use BooleanExpression.from_dimacs_file without having tweedledum installed. In #9754 the detection of tweedledum was updated to avoid import time detection so that the module can be imported even if tweedledum isn't installed. This was done through the use of the optionals decorators so that tweedledum is only attempted to be imported when the classicalfunction modules is used. However, the decorators don't wrap classmethod constructors by default and this caused the incorrect exception type to be raised. This commit fixes this by doing the runtime checking manually inside the from_dimacs_file constructor. --- qiskit/circuit/classicalfunction/boolean_expression.py | 1 + .../fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml diff --git a/qiskit/circuit/classicalfunction/boolean_expression.py b/qiskit/circuit/classicalfunction/boolean_expression.py index 1ed820058b18..0f4a53494af4 100644 --- a/qiskit/circuit/classicalfunction/boolean_expression.py +++ b/qiskit/circuit/classicalfunction/boolean_expression.py @@ -110,6 +110,7 @@ def from_dimacs_file(cls, filename: str): Raises: FileNotFoundError: If filename is not found. """ + HAS_TWEEDLEDUM.require_now("BooleanExpression") from tweedledum import BoolFunction # pylint: disable=import-error diff --git a/releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml b/releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml new file mode 100644 index 000000000000..b7f2d34d5cda --- /dev/null +++ b/releasenotes/notes/fix-exception-from_dimacs_file-b9338f3c913a9bff.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an issue with the :meth:`.BooleanExpression.from_dimacs_file` + constructor method where the exception type raised when tweedledum wasn't + installed was not the expected :class:`~.MissingOptionalLibrary`. + Fixed `#10079 `__ From 17590b64b1927bd9842164eea59510c42f33e178 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 22 May 2023 15:23:40 +0200 Subject: [PATCH 07/12] Replace `assert` in tests with unittest methods (#10136) * Replace `assert` with unittest methods * Update test/python/algorithms/optimizers/test_umda.py Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- .../python/algorithms/optimizers/test_umda.py | 20 +++++++++---------- test/python/circuit/library/test_nlocal.py | 3 ++- .../operators/symplectic/test_pauli_list.py | 2 +- test/python/result/test_quasi.py | 5 +++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/python/algorithms/optimizers/test_umda.py b/test/python/algorithms/optimizers/test_umda.py index 8d05c1cd2454..e33e11bda60d 100644 --- a/test/python/algorithms/optimizers/test_umda.py +++ b/test/python/algorithms/optimizers/test_umda.py @@ -15,8 +15,10 @@ from test.python.algorithms import QiskitAlgorithmsTestCase import numpy as np +from scipy.optimize import rosen from qiskit.algorithms.optimizers.umda import UMDA +from qiskit.utils import algorithm_globals class TestUMDA(QiskitAlgorithmsTestCase): @@ -30,10 +32,10 @@ def test_get_set(self): umda.alpha = 0.6 umda.maxiter = 100 - assert umda.disp is True - assert umda.size_gen == 30 - assert umda.alpha == 0.6 - assert umda.maxiter == 100 + self.assertTrue(umda.disp) + self.assertEqual(umda.size_gen, 30) + self.assertEqual(umda.alpha, 0.6) + self.assertEqual(umda.maxiter, 100) def test_settings(self): """Test if the settings display works well""" @@ -50,13 +52,10 @@ def test_settings(self): "callback": None, } - assert umda.settings == set_ + self.assertEqual(umda.settings, set_) def test_minimize(self): """optimize function test""" - from scipy.optimize import rosen - from qiskit.utils import algorithm_globals - # UMDA is volatile so we need to set the seeds for the execution algorithm_globals.random_seed = 52 @@ -64,9 +63,8 @@ def test_minimize(self): x_0 = [1.3, 0.7, 1.5] res = optimizer.minimize(rosen, x_0) - assert res.fun is not None - assert len(res.x) == len(x_0) - + self.assertIsNotNone(res.fun) + self.assertEqual(len(res.x), len(x_0)) np.testing.assert_array_almost_equal(res.x, [1.0] * len(x_0), decimal=2) def test_callback(self): diff --git a/test/python/circuit/library/test_nlocal.py b/test/python/circuit/library/test_nlocal.py index 5c77bac358aa..f20377368ed4 100644 --- a/test/python/circuit/library/test_nlocal.py +++ b/test/python/circuit/library/test_nlocal.py @@ -913,7 +913,8 @@ def test_full_vs_reverse_linear(self, num_qubits): reverse = RealAmplitudes(num_qubits=num_qubits, entanglement="reverse_linear", reps=reps) full.assign_parameters(params, inplace=True) reverse.assign_parameters(params, inplace=True) - assert Operator(full) == Operator(reverse) + + self.assertEqual(Operator(full), Operator(reverse)) if __name__ == "__main__": diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index 28e4d30b215f..379111df5eaa 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -2106,8 +2106,8 @@ def qubitwise_commutes(left: Pauli, right: Pauli) -> bool: # checking that every input Pauli in pauli_list is in a group in the ouput output_labels = [pauli.to_label() for group in groups for pauli in group] - # assert sorted(output_labels) == sorted(input_labels) self.assertListEqual(sorted(output_labels), sorted(input_labels)) + # Within each group, every operator qubit-wise commutes with every other operator. for group in groups: self.assertTrue( diff --git a/test/python/result/test_quasi.py b/test/python/result/test_quasi.py index 3c9b7d4c59a8..e124c569a81f 100644 --- a/test/python/result/test_quasi.py +++ b/test/python/result/test_quasi.py @@ -191,9 +191,10 @@ def test_known_quasi_conversion(self): ans = {0: 9 / 20, 1: 7 / 20, 2: 1 / 5} # Check probs are correct for key, val in closest.items(): - assert abs(ans[key] - val) < 1e-14 + self.assertAlmostEqual(ans[key], val, places=14) + # Check if distance calculation is correct - assert abs(dist - sqrt(0.38)) < 1e-14 + self.assertAlmostEqual(dist, sqrt(0.38), places=14) def test_marginal_distribution(self): """Test marginal_distribution with float value.""" From 7b677edf153a78d2765c05ee87a8ded0201bc3b2 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 22 May 2023 10:49:37 -0300 Subject: [PATCH 08/12] Fix wrong relative phase of MCRZ (#9836) * efficient multicontrolled su2 gate decomposition Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> * removed optimization flag Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> * tox -eblack * updated docstrings * Adds `MCSU2Gate` to `__init__` * fixed circular import * defined control and inverse methods * changed MCSU2Gate from Gate to ControlledGate * adjusted some tests for controlled gates * reformatting * Fix regarding the integer `ctrl_state` parameter * Tests to check the CX count upper bound * Gate's `label` in the `control` function * Upd. Qiskit tests to include cases for MCSU2Gate * Upd. Qiskit tests to include cases for MCSU2Gate * Revert "Upd. Qiskit tests to include cases for MCSU2Gate" This reverts commit c1ceaf6b28830caa5709cdb6e8d4db209acc8c84. * Revert "Upd. Qiskit tests to include cases for MCSU2Gate" This reverts commit 7c756111a31e22e6c7d7aba0a3df2584fd00817f. * Revert "Tests to check the CX count upper bound" This reverts commit 100a690c3a83556fd280cc4ede9e2c0f5d838455. * Update test_controlled_gate.py * Update test_circuit_operations.py * remove mcsu2gate class * remove mcsu2gate class * fix mcry * lint * fix mcrx * add reference * Create `s_gate` directly * Revert "Create `s_gate` directly" This reverts commit b762b393428f8c6889a055a3217779d84e8754bf. * review * release notes * review 2 * backwards compat * function signature and number of controls * fix mcrz * Update multi_control_rotation_gates.py * review * Update test_qpy.py * Revert "Update test_qpy.py" This reverts commit fab1c80ab44be86812fb2b95b538918400966f7e. * Update test_qpy.py * Update multi_control_rotation_gates.py * Fix `use_basis_gates=True` case * lint --------- Co-authored-by: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Rafaella Vale Co-authored-by: Julien Gacon --- qiskit/circuit/add_control.py | 13 +- .../multi_control_rotation_gates.py | 112 ++++++++++++------ ...-mcrz-relative-phase-6ea81a369f8bda38.yaml | 7 ++ test/python/circuit/test_controlled_gate.py | 10 +- test/qpy_compat/test_qpy.py | 7 +- 5 files changed, 97 insertions(+), 52 deletions(-) create mode 100644 releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index 39a91eba36ef..9a9837673d1a 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -148,6 +148,7 @@ def control( q_target[bit_indices[qargs[0]]], use_basis_gates=True, ) + continue elif gate.name == "p": from qiskit.circuit.library import MCPhaseGate @@ -184,13 +185,9 @@ def control( use_basis_gates=True, ) elif theta == 0 and phi == 0: - controlled_circ.mcrz( - lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]]) else: - controlled_circ.mcrz( - lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]]) controlled_circ.mcry( theta, q_control, @@ -198,9 +195,7 @@ def control( q_ancillae, use_basis_gates=True, ) - controlled_circ.mcrz( - phi, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(phi, q_control, q_target[bit_indices[qargs[0]]]) elif gate.name == "z": controlled_circ.h(q_target[bit_indices[qargs[0]]]) controlled_circ.mcx(q_control, q_target[bit_indices[qargs[0]]], q_ancillae) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 547883a6f7a9..ce7f7861440b 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -84,36 +84,44 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): def _mcsu2_real_diagonal( - circuit, unitary: np.ndarray, - controls: Union[QuantumRegister, List[Qubit]], - target: Union[Qubit, int], - ctrl_state: str = None, -): + num_controls: int, + ctrl_state: Optional[str] = None, + use_basis_gates: bool = False, +) -> QuantumCircuit: """ - Apply multi-controlled SU(2) gate with a real main diagonal or secondary diagonal. - https://arxiv.org/abs/2302.06377 + Return a multi-controlled SU(2) gate [1]_ with a real main diagonal or secondary diagonal. Args: - circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - unitary (ndarray): SU(2) unitary matrix with one real diagonal - controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit or int): The target qubit - ctrl_state (str): control state of the operator SU(2) operator + unitary: SU(2) unitary matrix with one real diagonal. + num_controls: The number of control qubits. + ctrl_state: The state on which the SU(2) operation is controlled. Defaults to all + control qubits being in state 1. + use_basis_gates: If ``True``, use ``[p, u, cx]`` gates to implement the decomposition. + + Returns: + A :class:`.QuantumCircuit` implementing the multi-controlled SU(2) gate. Raises: - QiskitError: parameter errors + QiskitError: If the input matrix is invalid. + + References: + + .. [1]: R. Vale et al. Decomposition of Multi-controlled Special Unitary Single-Qubit Gates + `arXiv:2302.06377 (2023) `__ + """ # pylint: disable=cyclic-import - from qiskit.circuit.library import MCXVChain + from .x import MCXVChain from qiskit.extensions import UnitaryGate from qiskit.quantum_info.operators.predicates import is_unitary_matrix - - if not is_unitary_matrix(unitary): - raise QiskitError("parameter unitary in mcsu2_real_diagonal must be an unitary matrix") + from qiskit.compiler import transpile if unitary.shape != (2, 2): - raise QiskitError("parameter unitary in mcsu2_real_diagonal must be a 2x2 matrix") + raise QiskitError(f"The unitary must be a 2x2 matrix, but has shape {unitary.shape}.") + + if not is_unitary_matrix(unitary): + raise QiskitError(f"The unitary in must be an unitary matrix, but is {unitary}.") is_main_diag_real = np.isclose(unitary[0, 0].imag, 0.0) and np.isclose(unitary[1, 1].imag, 0.0) is_secondary_diag_real = np.isclose(unitary[0, 1].imag, 0.0) and np.isclose( @@ -121,7 +129,7 @@ def _mcsu2_real_diagonal( ) if not is_main_diag_real and not is_secondary_diag_real: - raise QiskitError("parameter unitary in mcsu2_real_diagonal must have one real diagonal") + raise QiskitError("The unitary must have one real diagonal.") if is_secondary_diag_real: x = unitary[0, 1] @@ -130,27 +138,34 @@ def _mcsu2_real_diagonal( x = -unitary[0, 1].real z = unitary[1, 1] - unitary[0, 1].imag * 1.0j - alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) - alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - alpha = alpha_r + 1.0j * alpha_i - beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + if np.isclose(z, -1): + s_op = [[1.0, 0.0], [0.0, 1.0j]] + else: + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) + alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + alpha = alpha_r + 1.0j * alpha_i + beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + + # S gate definition + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) - # S gate definition - s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) s_gate = UnitaryGate(s_op) - num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.0)) - k_2 = int(np.floor(num_ctrl / 2.0)) + k_1 = int(np.ceil(num_controls / 2.0)) + k_2 = int(np.floor(num_controls / 2.0)) ctrl_state_k_1 = None ctrl_state_k_2 = None if ctrl_state is not None: - str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + str_ctrl_state = f"{ctrl_state:0{num_controls}b}" ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] + circuit = QuantumCircuit(num_controls + 1, name="MCSU2") + controls = list(range(num_controls)) # control indices, defined for code legibility + target = num_controls # target index, defined for code legibility + if not is_secondary_diag_real: circuit.h(target) @@ -178,6 +193,11 @@ def _mcsu2_real_diagonal( if not is_secondary_diag_real: circuit.h(target) + if use_basis_gates: + circuit = transpile(circuit, basis_gates=["p", "u", "cx"]) + + return circuit + def mcrx( self, @@ -232,7 +252,12 @@ def mcrx( use_basis_gates=use_basis_gates, ) else: - _mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit) + cgate = _mcsu2_real_diagonal( + RXGate(theta).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, + ) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) def mcry( @@ -302,7 +327,12 @@ def mcry( use_basis_gates=use_basis_gates, ) else: - _mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit) + cgate = _mcsu2_real_diagonal( + RYGate(theta).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, + ) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") @@ -327,6 +357,8 @@ def mcrz( Raises: QiskitError: parameter errors """ + from .rz import CRZGate, RZGate + control_qubits = self.qbit_argument_conversion(q_controls) target_qubit = self.qbit_argument_conversion(q_target) if len(target_qubit) != 1: @@ -336,13 +368,21 @@ def mcrz( self._check_dups(all_qubits) n_c = len(control_qubits) - if n_c == 1: # cu - _apply_cu(self, 0, 0, lam, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates) + if n_c == 1: + if use_basis_gates: + self.u(0, 0, lam / 2, target_qubit) + self.cx(control_qubits[0], target_qubit) + self.u(0, 0, -lam / 2, target_qubit) + self.cx(control_qubits[0], target_qubit) + else: + self.append(CRZGate(lam), control_qubits + [target_qubit]) else: - lam_step = lam * (1 / (2 ** (n_c - 1))) - _apply_mcu_graycode( - self, 0, 0, lam_step, control_qubits, target_qubit, use_basis_gates=use_basis_gates + cgate = _mcsu2_real_diagonal( + RZGate(lam).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, ) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) QuantumCircuit.mcrx = mcrx diff --git a/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml new file mode 100644 index 000000000000..aa89abeb662d --- /dev/null +++ b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed the gate decomposition of multi-controlled Z rotation gates added via + :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled + phase gate, which has a relative phase difference to the Z rotation. To obtain the + previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index a40f7747e2e7..248aabab7839 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -610,9 +610,8 @@ def test_mcsu2_real_diagonal(self): """Test mcsu2_real_diagonal""" num_ctrls = 6 theta = 0.3 - qc = QuantumCircuit(num_ctrls + 1) ry_matrix = RYGate(theta).to_matrix() - _mcsu2_real_diagonal(qc, ry_matrix, list(range(num_ctrls)), num_ctrls) + qc = _mcsu2_real_diagonal(ry_matrix, num_ctrls) mcry_matrix = _compute_control_matrix(ry_matrix, 6) self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix())) @@ -657,6 +656,11 @@ def test_multi_controlled_rotation_gate_matrices( if bit == "0": qc.x(q_controls[idx]) + if use_basis_gates: + with self.subTest(msg="check only basis gates used"): + gates_used = set(qc.count_ops().keys()) + self.assertTrue(gates_used.issubset({"x", "u", "p", "cx"})) + backend = BasicAer.get_backend("unitary_simulator") simulated = execute(qc, backend).result().get_unitary(qc) @@ -665,7 +669,7 @@ def test_multi_controlled_rotation_gate_matrices( elif base_gate_name == "y": rot_mat = RYGate(theta).to_matrix() else: # case 'z' - rot_mat = U1Gate(theta).to_matrix() + rot_mat = RZGate(theta).to_matrix() expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state) with self.subTest(msg=f"control state = {ctrl_state}"): diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index ef01d3873e39..2a4b112e1508 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -603,15 +603,14 @@ def generate_circuits(version_parts): if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() if version_parts >= (0, 21, 0): - output_circuits["controlled_gates.qpy"] = generate_controlled_gates() output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() - if version_parts >= (0, 21, 2): - output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() if version_parts >= (0, 24, 0): output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() - + if version_parts >= (0, 25, 0): + output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() + output_circuits["controlled_gates.qpy"] = generate_controlled_gates() return output_circuits From e1056ee62c895b33406f69823fb5073cfdcba9bd Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Mon, 22 May 2023 22:21:27 +0300 Subject: [PATCH 09/12] Fix mypy errors (transpiler) (#8266) * Fix transpiler mypy errors * Fix review comments, add a bit more annotations * Fix annotation * Fix sphinx * Fix lint * Update dynamical_decoupling.py * Replace deprecated function call * Update qiskit/transpiler/passes/layout/disjoint_utils.py Co-authored-by: Luciano Bello * Fix type definition --------- Co-authored-by: Luciano Bello --- qiskit/transpiler/instruction_durations.py | 30 ++++++----- qiskit/transpiler/layout.py | 9 ++-- .../passes/calibration/rzx_builder.py | 15 +++--- .../passes/layout/disjoint_utils.py | 22 +++++--- .../routing/algorithms/token_swapper.py | 11 ++-- .../passes/routing/algorithms/util.py | 11 ++-- .../commuting_2q_block.py | 8 +-- .../commuting_2q_gate_router.py | 38 ++++++++------ .../swap_strategy.py | 32 ++++++------ .../passes/routing/layout_transformation.py | 10 ++-- .../scheduling/alignments/align_measures.py | 21 +++++--- .../scheduling/alignments/reschedule.py | 6 +-- .../passes/scheduling/padding/base_padding.py | 7 +-- .../padding/dynamical_decoupling.py | 14 ++--- .../passes/synthesis/unitary_synthesis.py | 16 +++--- qiskit/transpiler/passmanager.py | 52 +++++++++---------- .../transpiler/preset_passmanagers/level0.py | 3 +- .../transpiler/preset_passmanagers/level1.py | 13 ++--- .../transpiler/preset_passmanagers/level2.py | 13 ++--- .../transpiler/preset_passmanagers/level3.py | 16 +++--- qiskit/transpiler/runningpassmanager.py | 10 ++-- .../transpiler/synthesis/aqc/approximate.py | 8 +-- .../synthesis/aqc/cnot_unit_circuit.py | 4 +- .../synthesis/aqc/cnot_unit_objective.py | 18 ++++--- .../aqc/fast_gradient/fast_grad_utils.py | 4 +- .../synthesis/aqc/fast_gradient/layer.py | 10 ++-- qiskit/transpiler/target.py | 42 +++++++-------- 27 files changed, 239 insertions(+), 204 deletions(-) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index a56a39b97ddf..d1611840a040 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -11,8 +11,10 @@ # that they have been altered from the originals. """Durations of instructions, one of transpiler configurations.""" -from typing import Optional, List, Tuple, Union, Iterable, Set +from __future__ import annotations +from typing import Optional, List, Tuple, Union, Iterable +import qiskit.circuit from qiskit.circuit import Barrier, Delay from qiskit.circuit import Instruction, Qubit, ParameterExpression from qiskit.circuit.duration import duration_in_dt @@ -22,7 +24,7 @@ from qiskit.utils.units import apply_prefix -def _is_deprecated_qubits_argument(qubits: Union[int, List[int], Qubit, List[Qubit]]) -> bool: +def _is_deprecated_qubits_argument(qubits: Union[int, list[int], Qubit, list[Qubit]]) -> bool: if isinstance(qubits, (int, Qubit)): qubits = [qubits] return isinstance(qubits[0], Qubit) @@ -41,11 +43,13 @@ class InstructionDurations: """ def __init__( - self, instruction_durations: Optional["InstructionDurationsType"] = None, dt: float = None + self, instruction_durations: "InstructionDurationsType" | None = None, dt: float = None ): - self.duration_by_name = {} - self.duration_by_name_qubits = {} - self.duration_by_name_qubits_params = {} + self.duration_by_name: dict[str, tuple[float, str]] = {} + self.duration_by_name_qubits: dict[tuple[str, tuple[int, ...]], tuple[float, str]] = {} + self.duration_by_name_qubits_params: dict[ + tuple[str, tuple[int, ...], tuple[float, ...]], tuple[float, str] + ] = {} self.dt = dt if instruction_durations: self.update(instruction_durations) @@ -99,7 +103,7 @@ def from_backend(cls, backend: Backend): return InstructionDurations(instruction_durations, dt=dt) - def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float = None): + def update(self, inst_durations: "InstructionDurationsType" | None, dt: float = None): """Update self with inst_durations (inst_durations overwrite self). Args: @@ -177,10 +181,10 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float ) def get( self, - inst: Union[str, Instruction], - qubits: Union[int, List[int], Qubit, List[Qubit]], + inst: str | qiskit.circuit.Instruction, + qubits: int | list[int] | Qubit | list[Qubit] | list[int | Qubit], unit: str = "dt", - parameters: Optional[List[float]] = None, + parameters: list[float] | None = None, ) -> float: """Get the duration of the instruction with the name, qubits, and parameters. @@ -224,9 +228,9 @@ def get( def _get( self, name: str, - qubits: List[int], + qubits: list[int], to_unit: str, - parameters: Optional[Iterable[float]] = None, + parameters: Iterable[float] | None = None, ) -> float: """Get the duration of the instruction with the name, qubits, and parameters.""" if name == "barrier": @@ -270,7 +274,7 @@ def _convert_unit(self, duration: float, from_unit: str, to_unit: str) -> float: else: raise TranspilerError(f"Conversion from '{from_unit}' to '{to_unit}' is not supported") - def units_used(self) -> Set[str]: + def units_used(self) -> set[str]: """Get the set of all units used in this instruction durations. Returns: diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 56f8f7f50954..0e318e6a8db1 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -17,9 +17,8 @@ Virtual (qu)bits are tuples, e.g. `(QuantumRegister(3, 'qr'), 2)` or simply `qr[2]`. Physical (qu)bits are integers. """ - +from __future__ import annotations from dataclasses import dataclass -from typing import Dict, Optional from qiskit.circuit.quantumregister import Qubit, QuantumRegister from qiskit.transpiler.exceptions import LayoutError @@ -262,7 +261,7 @@ def combine_into_edge_map(self, another_layout): return edge_map - def reorder_bits(self, bits): + def reorder_bits(self, bits) -> list[int]: """Given an ordered list of bits, reorder them according to this layout. The list of bits must exactly match the virtual bits in this layout. @@ -401,5 +400,5 @@ class TranspileLayout: """ initial_layout: Layout - input_qubit_mapping: Dict[Qubit, int] - final_layout: Optional[Layout] = None + input_qubit_mapping: dict[Qubit, int] + final_layout: Layout | None = None diff --git a/qiskit/transpiler/passes/calibration/rzx_builder.py b/qiskit/transpiler/passes/calibration/rzx_builder.py index 0b519a5a375a..97799994fd08 100644 --- a/qiskit/transpiler/passes/calibration/rzx_builder.py +++ b/qiskit/transpiler/passes/calibration/rzx_builder.py @@ -11,11 +11,12 @@ # that they have been altered from the originals. """RZX calibration builders.""" +from __future__ import annotations import enum import warnings +from collections.abc import Sequence from math import pi, erf -from typing import List, Tuple, Union import numpy as np from qiskit.circuit import Instruction as CircuitInst @@ -68,7 +69,7 @@ class RZXCalibrationBuilder(CalibrationBuilder): def __init__( self, instruction_schedule_map: InstructionScheduleMap = None, - qubit_channel_mapping: List[List[str]] = None, + qubit_channel_mapping: list[list[str]] = None, verbose: bool = True, target: Target = None, ): @@ -97,7 +98,7 @@ def __init__( if self._inst_map is None: raise QiskitError("Calibrations can only be added to Pulse-enabled backends") - def supported(self, node_op: CircuitInst, qubits: List) -> bool: + def supported(self, node_op: CircuitInst, qubits: list) -> bool: """Determine if a given node supports the calibration. Args: @@ -166,7 +167,7 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> i return round_duration - def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]: + def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | ScheduleBlock: """Builds the calibration schedule for the RZXGate(theta) with echos. Args: @@ -260,7 +261,7 @@ class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder): of the CX gate. """ - def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]: + def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | ScheduleBlock: """Builds the calibration schedule for the RZXGate(theta) without echos. Args: @@ -336,8 +337,8 @@ def _filter_comp_tone(time_inst_tup): def _check_calibration_type( - inst_sched_map: InstructionScheduleMap, qubits: List[int] -) -> Tuple[CRCalType, List[Play], List[Play]]: + inst_sched_map: InstructionScheduleMap, qubits: Sequence[int] +) -> tuple[CRCalType, list[Play], list[Play]]: """A helper function to check type of CR calibration. Args: diff --git a/qiskit/transpiler/passes/layout/disjoint_utils.py b/qiskit/transpiler/passes/layout/disjoint_utils.py index 2afef62955a3..4a7975200fbd 100644 --- a/qiskit/transpiler/passes/layout/disjoint_utils.py +++ b/qiskit/transpiler/passes/layout/disjoint_utils.py @@ -11,12 +11,13 @@ # that they have been altered from the originals. """This module contains common utils for disjoint coupling maps.""" - +from __future__ import annotations from collections import defaultdict from typing import List, Callable, TypeVar, Dict, Union import uuid import rustworkx as rx +from qiskit.dagcircuit import DAGOpNode from qiskit.circuit import Qubit, Barrier, Clbit from qiskit.dagcircuit.dagcircuit import DAGCircuit @@ -65,7 +66,7 @@ def run_pass_over_connected_components( for qreg in dag.qregs: out_dag.add_qreg(qreg) for creg in dag.cregs: - out_dag.add_cregs(creg) + out_dag.add_creg(creg) out_dag.compose(dag, qubits=dag.qubits, clbits=dag.clbits) out_component_pairs.append((out_dag, cmap_components[cmap_index])) res = [run_func(out_dag, cmap) for out_dag, cmap in out_component_pairs] @@ -119,7 +120,7 @@ def split_barriers(dag: DAGCircuit): def combine_barriers(dag: DAGCircuit, retain_uuid: bool = True): """Mutate input dag to combine barriers with UUID labels into a single barrier.""" qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - uuid_map = {} + uuid_map: dict[uuid.UUID, DAGOpNode] = {} for node in dag.op_nodes(Barrier): if isinstance(node.op.label, uuid.UUID): barrier_uuid = node.op.label @@ -139,9 +140,18 @@ def combine_barriers(dag: DAGCircuit, retain_uuid: bool = True): def require_layout_isolated_to_component( dag: DAGCircuit, components_source: Union[Target, CouplingMap] -) -> bool: - """Check that the layout of the dag does not require connectivity across connected components - in the CouplingMap""" +): + """ + Check that the layout of the dag does not require connectivity across connected components + in the CouplingMap + + Args: + dag: DAGCircuit to check. + components_source: Target to check against. + + Raises: + TranspilerError: Chosen layout is not valid for the target disjoint connectivity. + """ if isinstance(components_source, Target): coupling_map = components_source.build_coupling_map(filter_idle_qubits=True) else: diff --git a/qiskit/transpiler/passes/routing/algorithms/token_swapper.py b/qiskit/transpiler/passes/routing/algorithms/token_swapper.py index 63dd1e1f0d49..ffb995018fa4 100644 --- a/qiskit/transpiler/passes/routing/algorithms/token_swapper.py +++ b/qiskit/transpiler/passes/routing/algorithms/token_swapper.py @@ -26,9 +26,10 @@ """Permutation algorithms for general graphs.""" +from __future__ import annotations import copy import logging -from typing import Iterator, Mapping, MutableMapping, MutableSet, List, Iterable, Union +from collections.abc import Mapping, MutableSet, MutableMapping, Iterator, Iterable import numpy as np import rustworkx as rx @@ -46,9 +47,7 @@ class ApproximateTokenSwapper: Internally caches the graph and associated datastructures for re-use. """ - def __init__( - self, graph: rx.PyGraph, seed: Union[int, np.random.Generator, None] = None - ) -> None: + def __init__(self, graph: rx.PyGraph, seed: int | np.random.Generator | None = None) -> None: """Construct an ApproximateTokenSwapping object. Args: @@ -80,7 +79,7 @@ def permutation_circuit(self, permutation: Permutation, trials: int = 4) -> Perm parallel_swaps = [[swap] for swap in sequential_swaps] return permutation_circuit(parallel_swaps) - def map(self, mapping: Mapping[int, int], trials: int = 4) -> List[Swap[int]]: + def map(self, mapping: Mapping[int, int], trials: int = 4) -> list[Swap[int]]: """Perform an approximately optimal Token Swapping algorithm to implement the permutation. Supports partial mappings (i.e. not-permutations) for graphs with missing tokens. @@ -113,7 +112,7 @@ def map(self, mapping: Mapping[int, int], trials: int = 4) -> List[Swap[int]]: ) # Once we find a zero solution we stop. - def take_until_zero(results: Iterable[List[int]]) -> Iterator[List[int]]: + def take_until_zero(results: Iterable[list[Swap[int]]]) -> Iterable[list[Swap[int]]]: """Take results until one is emitted of length zero (and also emit that).""" for result in results: yield result diff --git a/qiskit/transpiler/passes/routing/algorithms/util.py b/qiskit/transpiler/passes/routing/algorithms/util.py index 096b9fd11f03..930c835603dd 100644 --- a/qiskit/transpiler/passes/routing/algorithms/util.py +++ b/qiskit/transpiler/passes/routing/algorithms/util.py @@ -26,7 +26,10 @@ """Utility functions shared between permutation functionality.""" -from typing import List, TypeVar, Iterable, MutableMapping +from __future__ import annotations + +from collections.abc import Iterable +from typing import TypeVar, MutableMapping from qiskit.circuit import QuantumRegister from qiskit.dagcircuit import DAGCircuit @@ -53,8 +56,8 @@ def swap_permutation( for swap_step in swaps: for sw1, sw2 in swap_step: # Take into account non-existent keys. - val1 = None # type: Optional[_V] - val2 = None # type: Optional[_V] + val1: _V | None = None + val2: _V | None = None if allow_missing_keys: val1 = mapping.pop(sw1, None) val2 = mapping.pop(sw2, None) @@ -68,7 +71,7 @@ def swap_permutation( mapping[sw1] = val2 -def permutation_circuit(swaps: Iterable[List[Swap[_V]]]) -> PermutationCircuit: +def permutation_circuit(swaps: Iterable[list[Swap[_V]]]) -> PermutationCircuit: """Produce a circuit description of a list of swaps. With a given permutation and permuter you can compute the swaps using the permuter function then feed it into this circuit function to obtain a circuit description. diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_block.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_block.py index d121b70baa3f..bc3d7f794bf0 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_block.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_block.py @@ -11,11 +11,12 @@ # that they have been altered from the originals. """A gate made of commuting two-qubit gates.""" +from __future__ import annotations -from typing import Iterable +from collections.abc import Iterable from qiskit.exceptions import QiskitError -from qiskit.circuit import Gate +from qiskit.circuit import Gate, Qubit, Clbit from qiskit.dagcircuit import DAGOpNode @@ -34,7 +35,8 @@ def __init__(self, node_block: Iterable[DAGOpNode]) -> None: Raises: QiskitError: If the nodes in the node block do not apply to two-qubits. """ - qubits, cbits = set(), set() + qubits: set[Qubit] = set() + cbits: set[Clbit] = set() for node in node_block: if len(node.qargs) != 2: raise QiskitError(f"Node {node.name} does not apply to two-qubits.") diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index 11919bf18b6c..1b0c734bd0b8 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -11,11 +11,10 @@ # that they have been altered from the originals. """A swap strategy pass for blocks of commuting gates.""" - +from __future__ import annotations from collections import defaultdict -from typing import Dict, List, Optional, Tuple -from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit import Gate, QuantumCircuit, Qubit from qiskit.converters import circuit_to_dag from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.transpiler import TransformationPass, Layout, TranspilerError @@ -105,8 +104,8 @@ class Commuting2qGateRouter(TransformationPass): def __init__( self, - swap_strategy: Optional[SwapStrategy] = None, - edge_coloring: Optional[Dict[Tuple[int, int], int]] = None, + swap_strategy: SwapStrategy | None = None, + edge_coloring: dict[tuple[int, int], int] | None = None, ) -> None: r""" Args: @@ -127,7 +126,7 @@ def __init__( """ super().__init__() self._swap_strategy = swap_strategy - self._bit_indices = None + self._bit_indices: dict[Qubit, int] | None = None self._edge_coloring = edge_coloring def run(self, dag: DAGCircuit) -> DAGCircuit: @@ -206,7 +205,7 @@ def _compose_non_swap_nodes( """ # Add all the non-swap strategy nodes that we have accumulated up to now. order = layout.reorder_bits(new_dag.qubits) - order_bits = [None] * len(layout) + order_bits: list[int | None] = [None] * len(layout) for idx, val in enumerate(order): order_bits[val] = idx @@ -215,7 +214,7 @@ def _compose_non_swap_nodes( # Re-initialize the node accumulator return new_dag.copy_empty_like() - def _position_in_cmap(self, j: int, k: int, layout: Layout) -> Tuple[int, ...]: + def _position_in_cmap(self, j: int, k: int, layout: Layout) -> tuple[int, ...]: """A helper function to track the movement of virtual qubits through the swaps. Args: @@ -231,7 +230,9 @@ def _position_in_cmap(self, j: int, k: int, layout: Layout) -> Tuple[int, ...]: return bit0, bit1 - def _build_sub_layers(self, current_layer: Dict[tuple, Gate]) -> List[Dict[tuple, Gate]]: + def _build_sub_layers( + self, current_layer: dict[tuple[int, int], Gate] + ) -> list[dict[tuple[int, int], Gate]]: """A helper method to build-up sets of gates to simultaneously apply. This is done with an edge coloring if the ``edge_coloring`` init argument was given or with @@ -256,10 +257,12 @@ def _build_sub_layers(self, current_layer: Dict[tuple, Gate]) -> List[Dict[tuple return self._greedy_build_sub_layers(current_layer) def _edge_coloring_build_sub_layers( - self, current_layer: Dict[tuple, Gate] - ) -> List[Dict[tuple, Gate]]: + self, current_layer: dict[tuple[int, int], Gate] + ) -> list[dict[tuple[int, int], Gate]]: """The edge coloring method of building sub-layers of commuting gates.""" - sub_layers = [{} for _ in set(self._edge_coloring.values())] + sub_layers: list[dict[tuple[int, int], Gate]] = [ + {} for _ in set(self._edge_coloring.values()) + ] for edge, gate in current_layer.items(): color = self._edge_coloring[edge] sub_layers[color][edge] = gate @@ -267,11 +270,14 @@ def _edge_coloring_build_sub_layers( return sub_layers @staticmethod - def _greedy_build_sub_layers(current_layer: Dict[tuple, Gate]) -> List[Dict[tuple, Gate]]: + def _greedy_build_sub_layers( + current_layer: dict[tuple[int, int], Gate] + ) -> list[dict[tuple[int, int], Gate]]: """The greedy method of building sub-layers of commuting gates.""" sub_layers = [] while len(current_layer) > 0: - current_sub_layer, remaining_gates, blocked_vertices = {}, {}, set() + current_sub_layer, remaining_gates = {}, {} + blocked_vertices: set[tuple] = set() for edge, evo_gate in current_layer.items(): if blocked_vertices.isdisjoint(edge): @@ -342,10 +348,10 @@ def swap_decompose( def _make_op_layers( self, dag: DAGCircuit, op: Commuting2qBlock, layout: Layout, swap_strategy: SwapStrategy - ) -> Dict[int, Dict[tuple, Gate]]: + ) -> dict[int, dict[tuple, Gate]]: """Creates layers of two-qubit gates based on the distance in the swap strategy.""" - gate_layers = defaultdict(dict) + gate_layers: dict[int, dict[tuple, Gate]] = defaultdict(dict) for node in op.node_block: edge = (self._bit_indices[node.qargs[0]], self._bit_indices[node.qargs[1]]) diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py index a3ec6d4b5653..72b62dc49a81 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py @@ -11,8 +11,8 @@ # that they have been altered from the originals. """Defines a swap strategy class.""" - -from typing import Any, List, Optional, Set, Tuple +from __future__ import annotations +from typing import Any import copy import numpy as np @@ -48,7 +48,7 @@ class SwapStrategy: """ def __init__( - self, coupling_map: CouplingMap, swap_layers: Tuple[Tuple[Tuple[int, int], ...], ...] + self, coupling_map: CouplingMap, swap_layers: tuple[tuple[tuple[int, int], ...], ...] ) -> None: """ Args: @@ -66,9 +66,9 @@ def __init__( self._coupling_map = coupling_map self._num_vertices = coupling_map.size() self._swap_layers = swap_layers - self._distance_matrix = None - self._possible_edges = None - self._missing_couplings = None + self._distance_matrix: np.ndarray | None = None + self._possible_edges: set[tuple[int, int]] | None = None + self._missing_couplings: set[tuple[int, int]] | None = None self._inverse_composed_permutation = {0: list(range(self._num_vertices))} edge_set = set(self._coupling_map.get_edges()) @@ -86,7 +86,7 @@ def __init__( raise QiskitError(f"The {i}th swap layer contains a qubit with multiple swaps.") @classmethod - def from_line(cls, line: List[int], num_swap_layers: Optional[int] = None) -> "SwapStrategy": + def from_line(cls, line: list[int], num_swap_layers: int | None = None) -> "SwapStrategy": """Creates a swap strategy for a line graph with the specified number of SWAP layers. This SWAP strategy will use the full line if instructed to do so (i.e. num_variables @@ -151,7 +151,7 @@ def __repr__(self) -> str: return description - def swap_layer(self, idx: int) -> List[Tuple[int, int]]: + def swap_layer(self, idx: int) -> list[tuple[int, int]]: """Return the layer of swaps at the given index. Args: @@ -164,7 +164,7 @@ def swap_layer(self, idx: int) -> List[Tuple[int, int]]: return list(self._swap_layers[idx]) @property - def distance_matrix(self) -> np.array: + def distance_matrix(self) -> np.ndarray: """A matrix describing when qubits become adjacent in the swap strategy. Returns: @@ -190,7 +190,7 @@ def distance_matrix(self) -> np.array: return self._distance_matrix - def new_connections(self, idx: int) -> List[Set[int]]: + def new_connections(self, idx: int) -> list[set[int]]: """ Returns the new connections obtained after applying the SWAP layer specified by idx, i.e. a list of qubit pairs that are adjacent to one another after idx steps of the SWAP strategy. @@ -209,7 +209,7 @@ def new_connections(self, idx: int) -> List[Set[int]]: connections.append({i, j}) return connections - def _build_edges(self) -> Set[Tuple[int, int]]: + def _build_edges(self) -> set[tuple[int, int]]: """Build the possible edges that the swap strategy accommodates.""" possible_edges = set() @@ -221,7 +221,7 @@ def _build_edges(self) -> Set[Tuple[int, int]]: return possible_edges @property - def possible_edges(self) -> Set[Tuple[int, int]]: + def possible_edges(self) -> set[tuple[int, int]]: """Return the qubit connections that can be generated. Returns: @@ -233,7 +233,7 @@ def possible_edges(self) -> Set[Tuple[int, int]]: return self._possible_edges @property - def missing_couplings(self) -> Set[Tuple[int, int]]: + def missing_couplings(self) -> set[tuple[int, int]]: """Return the set of couplings that cannot be reached. Returns: @@ -262,8 +262,8 @@ def swapped_coupling_map(self, idx: int) -> CouplingMap: return CouplingMap(couplinglist=edges) def apply_swap_layer( - self, list_to_swap: List[Any], idx: int, inplace: bool = False - ) -> List[Any]: + self, list_to_swap: list[Any], idx: int, inplace: bool = False + ) -> list[Any]: """Permute the elements of ``list_to_swap`` based on layer indexed by ``idx``. Args: @@ -285,7 +285,7 @@ def apply_swap_layer( return x - def inverse_composed_permutation(self, idx: int) -> List[int]: + def inverse_composed_permutation(self, idx: int) -> list[int]: """ Returns the inversed composed permutation of all swap layers applied up to layer ``idx``. Permutations are represented by list of integers where the ith element diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 3d67e0ffb572..218e6ca958d3 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -11,7 +11,7 @@ # that they have been altered from the originals. """Map (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.""" -from typing import Union +from __future__ import annotations import numpy as np @@ -31,10 +31,10 @@ class LayoutTransformation(TransformationPass): def __init__( self, - coupling_map: Union[CouplingMap, Target, None], - from_layout: Union[Layout, str], - to_layout: Union[Layout, str], - seed: Union[int, np.random.default_rng] = None, + coupling_map: CouplingMap | Target | None, + from_layout: Layout | str, + to_layout: Layout | str, + seed: int | np.random.Generator | None = None, trials=4, ): """LayoutTransformation initializer. diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py index 9a1b3c94c5e5..164e9557fbb7 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -11,13 +11,16 @@ # that they have been altered from the originals. """Align measurement instructions.""" +from __future__ import annotations import itertools import warnings from collections import defaultdict -from typing import List, Union +from collections.abc import Iterable +from typing import Type + +from qiskit.circuit.quantumcircuit import ClbitSpecifier, QubitSpecifier from qiskit.circuit.delay import Delay -from qiskit.circuit.instruction import Instruction from qiskit.circuit.measure import Measure from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.dagcircuit import DAGCircuit @@ -141,12 +144,14 @@ def run(self, dag: DAGCircuit): # * pad_with_delay is called only with non-delay node to avoid consecutive delay new_dag = dag.copy_empty_like() - qubit_time_available = defaultdict(int) # to track op start time - qubit_stop_times = defaultdict(int) # to track delay start time for padding - clbit_readable = defaultdict(int) - clbit_writeable = defaultdict(int) + qubit_time_available: dict[QubitSpecifier, int] = defaultdict(int) # to track op start time + qubit_stop_times: dict[QubitSpecifier, int] = defaultdict( + int + ) # to track delay start time for padding + clbit_readable: dict[ClbitSpecifier, int] = defaultdict(int) + clbit_writeable: dict[ClbitSpecifier, int] = defaultdict(int) - def pad_with_delays(qubits: List[int], until, unit) -> None: + def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" for q in qubits: if qubit_stop_times[q] < until: @@ -206,7 +211,7 @@ def pad_with_delays(qubits: List[int], until, unit) -> None: def _check_alignment_required( dag: DAGCircuit, alignment: int, - instructions: Union[Instruction, List[Instruction]], + instructions: Type | list[Type], ) -> bool: """Check DAG nodes and return a boolean representing if instruction scheduling is necessary. diff --git a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py index 325cb4c96546..b53d0f864cef 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py +++ b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py @@ -11,8 +11,8 @@ # that they have been altered from the originals. """Rescheduler pass to adjust node start times.""" - -from typing import List +from __future__ import annotations +from collections.abc import Generator from qiskit.circuit.gate import Gate from qiskit.circuit.delay import Delay @@ -79,7 +79,7 @@ def __init__( self.pulse_align = pulse_alignment @classmethod - def _get_next_gate(cls, dag: DAGCircuit, node: DAGOpNode) -> List[DAGOpNode]: + def _get_next_gate(cls, dag: DAGCircuit, node: DAGOpNode) -> Generator[DAGOpNode, None, None]: """Get next non-delay nodes. Args: diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 5fb25790cfbb..16d58dbae25e 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -11,9 +11,10 @@ # that they have been altered from the originals. """Padding pass to fill empty timeslot.""" +from __future__ import annotations +from collections.abc import Iterable import logging -from typing import List, Optional, Union from qiskit.circuit import Qubit, Clbit, Instruction from qiskit.circuit.delay import Delay @@ -195,8 +196,8 @@ def _apply_scheduled_op( dag: DAGCircuit, t_start: int, oper: Instruction, - qubits: Union[Qubit, List[Qubit]], - clbits: Optional[Union[Clbit, List[Clbit]]] = None, + qubits: Qubit | Iterable[Qubit], + clbits: Clbit | Iterable[Clbit] | None = None, ): """Add new operation to DAG with scheduled information. diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index e3923ed8b26e..bbb1b39bbd72 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -11,11 +11,11 @@ # that they have been altered from the originals. """Dynamical Decoupling insertion pass.""" +from __future__ import annotations import logging -from typing import List, Optional - import numpy as np + from qiskit.circuit import Qubit, Gate from qiskit.circuit.delay import Delay from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate @@ -106,9 +106,9 @@ def uhrig_pulse_location(k): def __init__( self, durations: InstructionDurations = None, - dd_sequence: List[Gate] = None, - qubits: Optional[List[int]] = None, - spacing: Optional[List[float]] = None, + dd_sequence: list[Gate] = None, + qubits: list[int] | None = None, + spacing: list[float] | None = None, skip_reset_qubits: bool = True, pulse_alignment: int = 1, extra_slack_distribution: str = "middle", @@ -166,8 +166,8 @@ def __init__( self._spacing = spacing self._extra_slack_distribution = extra_slack_distribution - self._no_dd_qubits = set() - self._dd_sequence_lengths = {} + self._no_dd_qubits: set[int] = set() + self._dd_sequence_lengths: dict[Qubit, list[int]] = {} self._sequence_phase = 0 if target is not None: self._durations = target.durations() diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 881d3cc10553..e4555c7fba04 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -11,9 +11,9 @@ # that they have been altered from the originals. """Synthesize UnitaryGates.""" - +from __future__ import annotations from math import pi, inf, isclose -from typing import List, Union, Optional +from typing import Any from copy import deepcopy from itertools import product from functools import partial @@ -265,13 +265,13 @@ class UnitarySynthesis(TransformationPass): def __init__( self, - basis_gates: List[str] = None, - approximation_degree: Optional[float] = 1.0, + basis_gates: list[str] = None, + approximation_degree: float | None = 1.0, coupling_map: CouplingMap = None, backend_props: BackendProperties = None, - pulse_optimize: Union[bool, None] = None, - natural_direction: Union[bool, None] = None, - synth_gates: Union[List[str], None] = None, + pulse_optimize: bool | None = None, + natural_direction: bool | None = None, + synth_gates: list[str] | None = None, method: str = "default", min_qubits: int = None, plugin_config: dict = None, @@ -384,7 +384,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugin_method = self.plugins.ext_plugins[self.method].obj else: plugin_method = DefaultUnitarySynthesis() - plugin_kwargs = {"config": self._plugin_config} + plugin_kwargs: dict[str, Any] = {"config": self._plugin_config} _gate_lengths = _gate_errors = None _gate_lengths_by_qubit = _gate_errors_by_qubit = None diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index eb339aec9018..03019e872ff4 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -11,10 +11,11 @@ # that they have been altered from the originals. """Manager for a set of Passes and their scheduling during transpilation.""" - +from __future__ import annotations import io import re -from typing import Union, List, Tuple, Callable, Dict, Any, Optional, Iterator, Iterable, TypeVar +from collections.abc import Iterator, Iterable, Callable, Sequence +from typing import Union, List, Any import dill @@ -24,14 +25,13 @@ from .exceptions import TranspilerError from .runningpassmanager import RunningPassManager, FlowController - -_CircuitsT = TypeVar("_CircuitsT", bound=Union[List[QuantumCircuit], QuantumCircuit]) +_CircuitsT = Union[List[QuantumCircuit], QuantumCircuit] class PassManager: """Manager for a set of Passes and their scheduling during transpilation.""" - def __init__(self, passes: Union[BasePass, List[BasePass]] = None, max_iteration: int = 1000): + def __init__(self, passes: BasePass | list[BasePass] | None = None, max_iteration: int = 1000): """Initialize an empty `PassManager` object (with no passes scheduled). Args: @@ -43,7 +43,7 @@ def __init__(self, passes: Union[BasePass, List[BasePass]] = None, max_iteration # the pass manager's schedule of passes, including any control-flow. # Populated via PassManager.append(). - self._pass_sets = [] + self._pass_sets: list[dict[str, Any]] = [] if passes is not None: self.append(passes) self.max_iteration = max_iteration @@ -51,7 +51,7 @@ def __init__(self, passes: Union[BasePass, List[BasePass]] = None, max_iteration def append( self, - passes: Union[BasePass, List[BasePass]], + passes: BasePass | Sequence[BasePass | FlowController], max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -84,7 +84,7 @@ def append( def replace( self, index: int, - passes: Union[BasePass, List[BasePass]], + passes: BasePass | list[BasePass], max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -164,8 +164,8 @@ def __add__(self, other): @staticmethod def _normalize_passes( - passes: Union[BasePass, List[BasePass], FlowController] - ) -> List[BasePass]: + passes: BasePass | Sequence[BasePass | FlowController] | FlowController, + ) -> Sequence[BasePass | FlowController] | FlowController: if isinstance(passes, FlowController): return passes if isinstance(passes, BasePass): @@ -187,8 +187,8 @@ def _normalize_passes( def run( self, circuits: _CircuitsT, - output_name: Optional[str] = None, - callback: Optional[Callable] = None, + output_name: str | None = None, + callback: Callable | None = None, ) -> _CircuitsT: """Run all the passes on the specified ``circuits``. @@ -249,8 +249,8 @@ def _in_parallel(circuit, pm_dill=None) -> QuantumCircuit: def _run_several_circuits( self, circuits: List[QuantumCircuit], - output_name: Optional[str] = None, - callback: Optional[Callable] = None, + output_name: str | None = None, + callback: Callable | None = None, ) -> List[QuantumCircuit]: """Run all the passes on the specified ``circuits``. @@ -274,8 +274,8 @@ def _run_several_circuits( def _run_single_circuit( self, circuit: QuantumCircuit, - output_name: Optional[str] = None, - callback: Optional[Callable] = None, + output_name: str | None = None, + callback: Callable | None = None, ) -> QuantumCircuit: """Run all the passes on a ``circuit``. @@ -319,7 +319,7 @@ def draw(self, filename=None, style=None, raw=False): return pass_manager_drawer(self, filename=filename, style=style, raw=raw) - def passes(self) -> List[Dict[str, BasePass]]: + def passes(self) -> list[dict[str, BasePass]]: """Return a list structure of the appended passes and its options. Returns: @@ -400,7 +400,7 @@ class StagedPassManager(PassManager): r"\s|\+|\-|\*|\/|\\|\%|\<|\>|\@|\!|\~|\^|\&|\:|\[|\]|\{|\}|\(|\)" ) - def __init__(self, stages: Optional[Iterable[str]] = None, **kwargs) -> None: + def __init__(self, stages: Iterable[str] | None = None, **kwargs) -> None: """Initialize a new StagedPassManager object Args: @@ -448,19 +448,19 @@ def _validate_stages(self, stages: Iterable[str]) -> None: msg.write(f", {invalid_stage}") raise ValueError(msg.getvalue()) - def _validate_init_kwargs(self, kwargs: Dict[str, Any]) -> None: + def _validate_init_kwargs(self, kwargs: dict[str, Any]) -> None: expanded_stages = set(self.expanded_stages) for stage in kwargs.keys(): if stage not in expanded_stages: raise AttributeError(f"{stage} is not a valid stage.") @property - def stages(self) -> Tuple[str, ...]: + def stages(self) -> tuple[str, ...]: """Pass manager stages""" return self._stages # pylint: disable=no-member @property - def expanded_stages(self) -> Tuple[str, ...]: + def expanded_stages(self) -> tuple[str, ...]: """Expanded Pass manager stages including ``pre_`` and ``post_`` phases.""" return self._expanded_stages # pylint: disable=no-member @@ -486,7 +486,7 @@ def __setattr__(self, attr, value): def append( self, - passes: Union[BasePass, List[BasePass]], + passes: BasePass | Sequence[BasePass | FlowController], max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -495,7 +495,7 @@ def append( def replace( self, index: int, - passes: Union[BasePass, List[BasePass]], + passes: BasePass | list[BasePass], max_iteration: int = None, **flow_controller_conditions: Any, ) -> None: @@ -523,15 +523,15 @@ def _create_running_passmanager(self) -> RunningPassManager: self._update_passmanager() return super()._create_running_passmanager() - def passes(self) -> List[Dict[str, BasePass]]: + def passes(self) -> list[dict[str, BasePass]]: self._update_passmanager() return super().passes() def run( self, circuits: _CircuitsT, - output_name: Optional[str] = None, - callback: Optional[Callable] = None, + output_name: str | None = None, + callback: Callable | None = None, ) -> _CircuitsT: self._update_passmanager() return super().run(circuits, output_name, callback) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 36b62d2c22fe..b0790f1026f2 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -14,6 +14,7 @@ Level 0 pass manager: no explicit optimization other than mapping to backend. """ +from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints @@ -83,7 +84,7 @@ def _choose_layout_condition(property_set): coupling_map_layout = target if layout_method == "trivial": - _choose_layout = TrivialLayout(coupling_map_layout) + _choose_layout: BasePass = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 46045b54ef54..db8b09b716b0 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -14,12 +14,13 @@ Level 1 pass manager: light optimization by simple adjacent gate collapsing. """ - +from __future__ import annotations +from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler import ConditionalController +from qiskit.transpiler import ConditionalController, FlowController from qiskit.transpiler.passes import CXCancellation from qiskit.transpiler.passes import SetLayout @@ -118,13 +119,13 @@ def _vf2_match_not_found(property_set): else: coupling_map_layout = target - _choose_layout_0 = ( + _choose_layout_0: list[BasePass] = ( [] if pass_manager_config.layout_method else [TrivialLayout(coupling_map_layout), CheckMap(coupling_map_layout)] ) - _choose_layout_1 = ( + _choose_layout_1: list[BasePass] | BasePass = ( [] if pass_manager_config.layout_method else VF2Layout( @@ -138,7 +139,7 @@ def _vf2_match_not_found(property_set): ) if layout_method == "trivial": - _improve_layout = TrivialLayout(coupling_map_layout) + _improve_layout: BasePass = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _improve_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": @@ -275,7 +276,7 @@ def _unroll_condition(property_set): return not property_set["all_gates_in_basis"] # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis = [ + _unroll_if_out_of_basis: list[BasePass | FlowController] = [ GatesInBasis(basis_gates, target=target), ConditionalController(unroll, condition=_unroll_condition), ] diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 900cb2668e93..743018881da3 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -15,12 +15,13 @@ Level 2 pass manager: medium optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules. """ - +from __future__ import annotations +from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler import ConditionalController +from qiskit.transpiler import ConditionalController, FlowController from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout @@ -109,7 +110,7 @@ def _vf2_match_not_found(property_set): return False # Try using VF2 layout to find a perfect layout - _choose_layout_0 = ( + _choose_layout_0: list[BasePass] | BasePass = ( [] if pass_manager_config.layout_method else VF2Layout( @@ -128,7 +129,7 @@ def _vf2_match_not_found(property_set): coupling_map_layout = target if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map_layout) + _choose_layout_1: BasePass = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": @@ -160,7 +161,7 @@ def _vf2_match_not_found(property_set): def _opt_control(property_set): return (not property_set["depth_fixed_point"]) or (not property_set["size_fixed_point"]) - _opt = [ + _opt: list[BasePass] = [ Optimize1qGatesDecomposition(basis=basis_gates, target=target), CommutativeCancellation(basis_gates=basis_gates, target=target), ] @@ -230,7 +231,7 @@ def _unroll_condition(property_set): return not property_set["all_gates_in_basis"] # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis = [ + _unroll_if_out_of_basis: list[BasePass | FlowController] = [ GatesInBasis(basis_gates, target=target), ConditionalController(unroll, condition=_unroll_condition), ] diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 57cfa5d06201..7fe64eed521d 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -15,8 +15,8 @@ Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules and unitary synthesis. """ - - +from __future__ import annotations +from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager @@ -41,7 +41,7 @@ from qiskit.transpiler.passes import UnitarySynthesis from qiskit.transpiler.passes import GatesInBasis from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements -from qiskit.transpiler.runningpassmanager import ConditionalController +from qiskit.transpiler.runningpassmanager import ConditionalController, FlowController from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from qiskit.transpiler.preset_passmanagers.plugin import ( @@ -115,7 +115,7 @@ def _vf2_match_not_found(property_set): return False # 2a. If layout method is not set, first try VF2Layout - _choose_layout_0 = ( + _choose_layout_0: list[BasePass] | BasePass = ( [] if pass_manager_config.layout_method else VF2Layout( @@ -135,7 +135,7 @@ def _vf2_match_not_found(property_set): # 2b. if VF2 didn't converge on a solution use layout_method (dense). if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map_layout) + _choose_layout_1: BasePass = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": @@ -161,7 +161,7 @@ def _vf2_match_not_found(property_set): # 8. Optimize iteratively until no more change in depth. Removes useless gates # after reset and before measure, commutes gates and optimizes contiguous blocks. - _minimum_point_check = [ + _minimum_point_check: list[BasePass | FlowController] = [ Depth(recurse=True), Size(recurse=True), MinimumPoint(["depth", "size"], "optimization_loop"), @@ -170,7 +170,7 @@ def _vf2_match_not_found(property_set): def _opt_control(property_set): return not property_set["optimization_loop_minimum_point"] - _opt = [ + _opt: list[BasePass | FlowController] = [ Collect2qBlocks(), ConsolidateBlocks( basis_gates=basis_gates, target=target, approximation_degree=approximation_degree @@ -257,7 +257,7 @@ def _unroll_condition(property_set): return not property_set["all_gates_in_basis"] # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis = [ + _unroll_if_out_of_basis: list[BasePass | FlowController] = [ GatesInBasis(basis_gates, target=target), ConditionalController(unroll, condition=_unroll_condition), ] diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index a89427af4ba4..6290ed4d52bc 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -12,7 +12,7 @@ """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 @@ -58,11 +58,11 @@ def __init__(self, max_iteration): self.count = 0 - def append(self, passes, **flow_controller_conditions): + def append(self, passes: list[BasePass], **flow_controller_conditions): """Append a Pass to the schedule of passes. Args: - passes (list[BasePass]): passes to be added to schedule + passes (list[TBasePass]): passes to be added to schedule flow_controller_conditions (kwargs): See add_flow_controller(): Dictionary of control flow plugins. Default: @@ -308,11 +308,11 @@ def remove_flow_controller(cls, name): del cls.registered_controllers[name] @classmethod - def controller_factory(cls, passes, options, **partial_controller): + def controller_factory(cls, passes: list[BasePass], options, **partial_controller): """Constructs a flow controller based on the partially evaluated controller arguments. Args: - passes (list[BasePass]): passes to add to the flow controller. + 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}` diff --git a/qiskit/transpiler/synthesis/aqc/approximate.py b/qiskit/transpiler/synthesis/aqc/approximate.py index 418d766dc356..6c3f11fb71fc 100644 --- a/qiskit/transpiler/synthesis/aqc/approximate.py +++ b/qiskit/transpiler/synthesis/aqc/approximate.py @@ -10,9 +10,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """Base classes for an approximate circuit definition.""" - +from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional +from typing import Optional, SupportsFloat import numpy as np from qiskit import QuantumCircuit @@ -61,10 +61,10 @@ class ApproximatingObjective(ABC): def __init__(self) -> None: # must be set before optimization - self._target_matrix = None + self._target_matrix: np.ndarray | None = None @abstractmethod - def objective(self, param_values: np.ndarray) -> float: + def objective(self, param_values: np.ndarray) -> SupportsFloat: """ Computes a value of the objective function given a vector of parameter values. diff --git a/qiskit/transpiler/synthesis/aqc/cnot_unit_circuit.py b/qiskit/transpiler/synthesis/aqc/cnot_unit_circuit.py index 8ece18d47c48..6973ae55d72f 100644 --- a/qiskit/transpiler/synthesis/aqc/cnot_unit_circuit.py +++ b/qiskit/transpiler/synthesis/aqc/cnot_unit_circuit.py @@ -13,7 +13,7 @@ This is the Parametric Circuit class: anything that you need for a circuit to be parametrized and used for approximate compiling optimization. """ - +from __future__ import annotations from typing import Optional import numpy as np @@ -53,7 +53,7 @@ def __init__( self._tol = tol # Thetas to be optimized by the AQC algorithm - self._thetas = None + self._thetas: np.ndarray | None = None @property def thetas(self) -> np.ndarray: diff --git a/qiskit/transpiler/synthesis/aqc/cnot_unit_objective.py b/qiskit/transpiler/synthesis/aqc/cnot_unit_objective.py index 46ee7ad45a6a..b8bcd6ea9abd 100644 --- a/qiskit/transpiler/synthesis/aqc/cnot_unit_objective.py +++ b/qiskit/transpiler/synthesis/aqc/cnot_unit_objective.py @@ -13,6 +13,8 @@ A definition of the approximate circuit compilation optimization problem based on CNOT unit definition. """ +from __future__ import annotations +import typing from abc import ABC import numpy as np @@ -68,13 +70,13 @@ def __init__(self, num_qubits: int, cnots: np.ndarray) -> None: super().__init__(num_qubits, cnots) # last objective computations to be re-used by gradient - self._last_thetas = None - self._cnot_right_collection = None - self._cnot_left_collection = None - self._rotation_matrix = None - self._cnot_matrix = None + self._last_thetas: np.ndarray | None = None + self._cnot_right_collection: np.ndarray | None = None + self._cnot_left_collection: np.ndarray | None = None + self._rotation_matrix: int | np.ndarray | None = None + self._cnot_matrix: np.ndarray | None = None - def objective(self, param_values: np.ndarray) -> float: + def objective(self, param_values: np.ndarray) -> typing.SupportsFloat: # rename parameters just to make shorter and make use of our dictionary thetas = param_values n = self._num_qubits @@ -143,7 +145,7 @@ def objective(self, param_values: np.ndarray) -> float: # this is the matrix corresponding to the initial rotations # we start with 1 and kronecker product each qubit's rotations - rotation_matrix = 1 + rotation_matrix: int | np.ndarray = 1 for q in range(n): theta_index = 4 * num_cnots + 3 * q rz0 = rz_matrix(thetas[0 + theta_index]) @@ -268,7 +270,7 @@ def gradient(self, param_values: np.ndarray) -> np.ndarray: # now we compute the partial derivatives in the rotation part # we start with 1 and kronecker product each qubit's rotations for i in range(3 * n): - der_rotation_matrix = 1 + der_rotation_matrix: int | np.ndarray = 1 for q in range(n): theta_index = 4 * num_cnots + 3 * q rz0 = rz_matrix(thetas[0 + theta_index]) diff --git a/qiskit/transpiler/synthesis/aqc/fast_gradient/fast_grad_utils.py b/qiskit/transpiler/synthesis/aqc/fast_gradient/fast_grad_utils.py index a3b61042b432..b13c96ea3fe5 100644 --- a/qiskit/transpiler/synthesis/aqc/fast_gradient/fast_grad_utils.py +++ b/qiskit/transpiler/synthesis/aqc/fast_gradient/fast_grad_utils.py @@ -13,7 +13,7 @@ """ Utility functions in the fast gradient implementation. """ - +from __future__ import annotations from typing import Union import numpy as np @@ -58,7 +58,7 @@ def reverse_bits(x: Union[int, np.ndarray], nbits: int, enable: bool) -> Union[i return x if isinstance(x, int): - res = int(0) + res: int | np.ndarray = int(0) else: x = x.copy() res = np.full_like(x, fill_value=0) diff --git a/qiskit/transpiler/synthesis/aqc/fast_gradient/layer.py b/qiskit/transpiler/synthesis/aqc/fast_gradient/layer.py index eb4a2cf5aa9b..c567260fad51 100644 --- a/qiskit/transpiler/synthesis/aqc/fast_gradient/layer.py +++ b/qiskit/transpiler/synthesis/aqc/fast_gradient/layer.py @@ -13,9 +13,9 @@ """ Layer classes for the fast gradient implementation. """ - +from __future__ import annotations from abc import abstractmethod, ABC -from typing import Tuple, Optional +from typing import Optional import numpy as np from .fast_grad_utils import ( bit_permutation_1q, @@ -46,7 +46,7 @@ def set_from_matrix(self, mat: np.ndarray): raise NotImplementedError() @abstractmethod - def get_attr(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + def get_attr(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Returns gate matrix, direct and inverse permutations. @@ -91,7 +91,7 @@ def set_from_matrix(self, mat: np.ndarray): """See base class description.""" np.copyto(self._gmat, mat) - def get_attr(self) -> (np.ndarray, np.ndarray, np.ndarray): + def get_attr(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """See base class description.""" return self._gmat, self._perm, self._inv_perm @@ -132,7 +132,7 @@ def set_from_matrix(self, mat: np.ndarray): """See base class description.""" np.copyto(self._gmat, mat) - def get_attr(self) -> (np.ndarray, np.ndarray, np.ndarray): + def get_attr(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """See base class description.""" return self._gmat, self._perm, self._inv_perm diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 61999e6e7418..08c5dfdfe38d 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -21,7 +21,7 @@ import itertools -from typing import Tuple, Union, Optional, Dict, List, Any +from typing import Any from collections.abc import Mapping from collections import defaultdict import datetime @@ -70,9 +70,9 @@ class InstructionProperties: def __init__( self, - duration: float = None, - error: float = None, - calibration: Union[Schedule, ScheduleBlock, CalibrationEntry] = None, + duration: float | None = None, + error: float | None = None, + calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): """Create a new ``InstructionProperties`` object @@ -83,7 +83,7 @@ def __init__( set of qubits. calibration: The pulse representation of the instruction. """ - self._calibration = None + self._calibration: CalibrationEntry | None = None self.duration = duration self.error = error @@ -122,7 +122,7 @@ def calibration(self): return self._calibration.get_schedule() @calibration.setter - def calibration(self, calibration: Union[Schedule, ScheduleBlock, CalibrationEntry]): + def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): if isinstance(calibration, (Schedule, ScheduleBlock)): new_entry = ScheduleDef() new_entry.define(calibration, user_provided=True) @@ -860,7 +860,7 @@ def check_obj_params(parameters, obj): def has_calibration( self, operation_name: str, - qargs: Tuple[int, ...], + qargs: tuple[int, ...], ) -> bool: """Return whether the instruction (operation + qubits) defines a calibration. @@ -881,10 +881,10 @@ def has_calibration( def get_calibration( self, operation_name: str, - qargs: Tuple[int, ...], + qargs: tuple[int, ...], *args: ParameterValueType, **kwargs: ParameterValueType, - ) -> Union[Schedule, ScheduleBlock]: + ) -> Schedule | ScheduleBlock: """Get calibrated pulse schedule for the instruction. If calibration is templated with parameters, one can also provide those values @@ -1209,15 +1209,15 @@ def __str__(self): @classmethod def from_configuration( cls, - basis_gates: List[str], - num_qubits: Optional[int] = None, - coupling_map: Optional[CouplingMap] = None, - inst_map: Optional[InstructionScheduleMap] = None, - backend_properties: Optional[BackendProperties] = None, - instruction_durations: Optional[InstructionDurations] = None, - dt: Optional[float] = None, - timing_constraints: Optional[TimingConstraints] = None, - custom_name_mapping: Optional[Dict[str, Any]] = None, + basis_gates: list[str], + num_qubits: int | None = None, + coupling_map: CouplingMap | None = None, + inst_map: InstructionScheduleMap | None = None, + backend_properties: BackendProperties | None = None, + instruction_durations: InstructionDurations | None = None, + dt: float | None = None, + timing_constraints: TimingConstraints | None = None, + custom_name_mapping: dict[str, Any] | None = None, ) -> Target: """Create a target object from the individual global configuration @@ -1351,7 +1351,7 @@ def from_configuration( "with <= 2 qubits (because connectivity is defined on a CouplingMap)." ) for gate in one_qubit_gates: - gate_properties = {} + gate_properties: dict[tuple, InstructionProperties] = {} for qubit in range(num_qubits): error = None duration = None @@ -1441,7 +1441,7 @@ def from_configuration( def target_to_backend_properties(target: Target): """Convert a :class:`~.Target` object into a legacy :class:`~.BackendProperties`""" - properties_dict = { + properties_dict: dict[str, Any] = { "backend_name": "", "backend_version": "", "last_update_date": None, @@ -1481,7 +1481,7 @@ def target_to_backend_properties(target: Target): } ) else: - qubit_props = {x: None for x in range(target.num_qubits)} + qubit_props: dict[int, Any] = {x: None for x in range(target.num_qubits)} for qargs, props in qargs_list.items(): if qargs is None: continue From a7cad0e6d4a5c3e67c30c8a15e9459f4ea84ff3a Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Tue, 23 May 2023 07:50:52 -0600 Subject: [PATCH 10/12] Avoid installing deps with `tox -e docs-clean` (#10121) --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index be1b1337a9bc..89c6ad734c03 100644 --- a/tox.ini +++ b/tox.ini @@ -85,6 +85,7 @@ commands = [testenv:docs-clean] skip_install = true +deps = allowlist_externals = rm commands = From 631b6f3acea7149b1dc0964b55fe74a9822ad77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 23 May 2023 15:59:15 +0200 Subject: [PATCH 11/12] Attempt to fix backend primitives unittest failure on slow tests (#10142) * Add subtests * Remove nested subtests --- .../primitives/test_backend_estimator.py | 8 +++-- .../python/primitives/test_backend_sampler.py | 32 +++++++++++-------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/test/python/primitives/test_backend_estimator.py b/test/python/primitives/test_backend_estimator.py index 465a8aa9d95f..df9387ee070b 100644 --- a/test/python/primitives/test_backend_estimator.py +++ b/test/python/primitives/test_backend_estimator.py @@ -329,12 +329,13 @@ def test_no_max_circuits(self): def test_bound_pass_manager(self): """Test bound pass manager.""" - dummy_pass = DummyTP() - qc = QuantumCircuit(2) op = SparsePauliOp.from_list([("II", 1)]) with self.subTest("Test single circuit"): + + dummy_pass = DummyTP() + with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: bound_pass = PassManager(dummy_pass) estimator = BackendEstimator(backend=FakeNairobi(), bound_pass_manager=bound_pass) @@ -342,6 +343,9 @@ def test_bound_pass_manager(self): self.assertEqual(mock_pass.call_count, 1) with self.subTest("Test circuit batch"): + + dummy_pass = DummyTP() + with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: bound_pass = PassManager(dummy_pass) estimator = BackendEstimator(backend=FakeNairobi(), bound_pass_manager=bound_pass) diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index cf8cd90556c9..86dc709f8983 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -386,19 +386,25 @@ def test_outcome_bitstring_size(self): def test_bound_pass_manager(self): """Test bound pass manager.""" - dummy_pass = DummyTP() - - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) - sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) - _ = sampler.run(self._circuit[0]).result() - self.assertEqual(mock_pass.call_count, 1) - - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) - sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) - _ = sampler.run([self._circuit[0], self._circuit[0]]).result() - self.assertEqual(mock_pass.call_count, 2) + with self.subTest("Test single circuit"): + + dummy_pass = DummyTP() + + with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: + bound_pass = PassManager(dummy_pass) + sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) + _ = sampler.run(self._circuit[0]).result() + self.assertEqual(mock_pass.call_count, 1) + + with self.subTest("Test circuit batch"): + + dummy_pass = DummyTP() + + with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: + bound_pass = PassManager(dummy_pass) + sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) + _ = sampler.run([self._circuit[0], self._circuit[0]]).result() + self.assertEqual(mock_pass.call_count, 2) if __name__ == "__main__": From 02502b5d98796dcc2c8b80ac5d0f2af85e978e5c Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Wed, 24 May 2023 15:28:01 +0300 Subject: [PATCH 12/12] Remove undefined variable (#10117) * Remove undefined variable (fix https://github.com/Qiskit/qiskit-terra/issues/10113) * Add test and bugfix description --- .../passes/calibration/rzx_builder.py | 5 ++++- ...exception-decription-3ba0b5db82c576cf.yaml | 4 ++++ .../transpiler/test_calibrationbuilder.py | 22 ++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml diff --git a/qiskit/transpiler/passes/calibration/rzx_builder.py b/qiskit/transpiler/passes/calibration/rzx_builder.py index 97799994fd08..4ad964758429 100644 --- a/qiskit/transpiler/passes/calibration/rzx_builder.py +++ b/qiskit/transpiler/passes/calibration/rzx_builder.py @@ -361,7 +361,10 @@ def _check_calibration_type( cr_sched = inst_sched_map.get("ecr", tuple(reversed(qubits))) cal_type = CRCalType.ECR_REVERSE else: - raise QiskitError(f"{repr(cr_sched)} native direction cannot be determined.") + raise QiskitError( + f"Native direction cannot be determined: operation on qubits {qubits} " + f"for the following instruction schedule map:\n{inst_sched_map}" + ) cr_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_cr_tone]).instructions] comp_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_comp_tone]).instructions] diff --git a/releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml b/releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml new file mode 100644 index 000000000000..5733e1c85287 --- /dev/null +++ b/releasenotes/notes/fix-exception-decription-3ba0b5db82c576cf.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fix a bug in :class:`~.RZXCalibrationBuilder` where calling calibration with wrong parameters + would crash instead of raising exception. diff --git a/test/python/transpiler/test_calibrationbuilder.py b/test/python/transpiler/test_calibrationbuilder.py index aadbc25c0332..7baa39c8ad6a 100644 --- a/test/python/transpiler/test_calibrationbuilder.py +++ b/test/python/transpiler/test_calibrationbuilder.py @@ -16,10 +16,12 @@ import numpy as np from ddt import data, ddt +from qiskit.converters import circuit_to_dag -from qiskit import circuit, schedule +from qiskit import circuit, schedule, QiskitError from qiskit.circuit.library.standard_gates import SXGate, RZGate from qiskit.providers.fake_provider import FakeHanoi # TODO - include FakeHanoiV2, FakeSherbrooke +from qiskit.providers.fake_provider import FakeArmonk from qiskit.pulse import ( ControlChannel, DriveChannel, @@ -247,6 +249,24 @@ def test_rzx_calibration_rotary_pulse_stretch(self, theta: float): test_sched.duration, self.compute_stretch_duration(self.d1p_play(cr_schedule), theta) ) + def test_raise(self): + """Test that the correct error is raised.""" + theta = np.pi / 4 + + qc = circuit.QuantumCircuit(2) + qc.rzx(theta, 0, 1) + dag = circuit_to_dag(qc) + + backend = FakeArmonk() + inst_map = backend.defaults().instruction_schedule_map + _pass = RZXCalibrationBuilder(inst_map) + + qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)} + with self.assertRaises(QiskitError): + for node in dag.gate_nodes(): + qubits = [qubit_map[q] for q in node.qargs] + _pass.get_calibration(node.op, qubits) + def test_ecr_cx_forward(self): """Test that correct pulse sequence is generated for native CR pair.""" # Sufficiently large angle to avoid minimum duration, i.e. amplitude rescaling