From c7ecb5fd88f136474f91880f1530e256ab82b527 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 23 Nov 2023 13:51:10 +0000 Subject: [PATCH 01/17] Fix flakiness in pulse-optimal `UnitarySynthesis` test (#11307) The previous iteration of this test asserted that the `sx` count for non-optimal synthesis was higher than a certain particular value. This value did not have any fundamental properties, it was just the value that happened to be returned for some time. Recent OpenBLAS support for x86_64 instructions from the AVX512 SkylakeX set meant that supporting processors can now return slightly fewer `sx` gates in the non-optimal path, despite the pulse-optimal synthesis still not being in use. This caused flaky CI, when we were allocated an Azure VM that had access to the new instructions. --- .../transpiler/test_unitary_synthesis.py | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index d93b03934173..93302ca78fd1 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -344,25 +344,36 @@ def test_two_qubit_synthesis_not_pulse_optimal(self): backend = FakeVigo() conf = backend.configuration() qr = QuantumRegister(2) - coupling_map = CouplingMap([[0, 1], [1, 2], [1, 3], [3, 4]]) - triv_layout_pass = TrivialLayout(coupling_map) qc = QuantumCircuit(qr) qc.unitary(random_unitary(4, seed=12), [0, 1]) - unisynth_pass = UnitarySynthesis( - basis_gates=conf.basis_gates, - coupling_map=coupling_map, - backend_props=backend.properties(), - pulse_optimize=False, - natural_direction=True, + coupling_map = CouplingMap([[0, 1]]) + pm_nonoptimal = PassManager( + [ + TrivialLayout(coupling_map), + UnitarySynthesis( + basis_gates=conf.basis_gates, + coupling_map=coupling_map, + backend_props=backend.properties(), + pulse_optimize=False, + natural_direction=True, + ), + ] ) - pm = PassManager([triv_layout_pass, unisynth_pass]) - qc_out = pm.run(qc) - if isinstance(qc_out, QuantumCircuit): - num_ops = qc_out.count_ops() - else: - num_ops = qc_out[0].count_ops() - self.assertIn("sx", num_ops) - self.assertGreaterEqual(num_ops["sx"], 16) + pm_optimal = PassManager( + [ + TrivialLayout(coupling_map), + UnitarySynthesis( + basis_gates=conf.basis_gates, + coupling_map=coupling_map, + backend_props=backend.properties(), + pulse_optimize=True, + natural_direction=True, + ), + ] + ) + qc_nonoptimal = pm_nonoptimal.run(qc) + qc_optimal = pm_optimal.run(qc) + self.assertGreater(qc_nonoptimal.count_ops()["sx"], qc_optimal.count_ops()["sx"]) def test_two_qubit_pulse_optimal_true_raises(self): """Verify raises if pulse optimal==True but cx is not in the backend basis.""" From 7f6d43794e12a8b392949c71942c159e8da91f29 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 23 Nov 2023 16:39:18 +0100 Subject: [PATCH 02/17] Remove deprecated `IntegerComparator.num_ancilla_qubits` (#11306) * remove deprecated IntegerComparator.num_ancilla_qubits * reno --- .../circuit/library/arithmetic/integer_comparator.py | 11 ----------- ...omparator_num_ancilla_qubits-bd1cff3366c345ae.yaml | 6 ++++++ 2 files changed, 6 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml diff --git a/qiskit/circuit/library/arithmetic/integer_comparator.py b/qiskit/circuit/library/arithmetic/integer_comparator.py index 699e8c221ff3..1324d512ea6f 100644 --- a/qiskit/circuit/library/arithmetic/integer_comparator.py +++ b/qiskit/circuit/library/arithmetic/integer_comparator.py @@ -14,7 +14,6 @@ """Integer Comparator.""" from __future__ import annotations -import warnings import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister @@ -100,16 +99,6 @@ def geq(self, geq: bool) -> None: self._invalidate() self._geq = geq - @property - def num_ancilla_qubits(self): - """Deprecated. Use num_ancillas instead.""" - warnings.warn( - "The IntegerComparator.num_ancilla_qubits property is deprecated " - "as of 0.16.0. It will be removed no earlier than 3 months after the release " - "date. You should use the num_ancillas property instead." - ) - return self.num_ancillas - @property def num_state_qubits(self) -> int: """The number of qubits encoding the state for the comparison. diff --git a/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml b/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml new file mode 100644 index 000000000000..9364ffd605b3 --- /dev/null +++ b/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The property ``IntegerComparator.num_ancilla_qubits`` is removed, which was + deprecated in Qiskit 0.23 (released on Oct 2020). Its functionality is fully covered + by :attr:`.IntegerComparator.num_ancilla`. From 5a5c9e33f55d2de3adb7f7377ce835cc65e2204d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 23 Nov 2023 13:17:14 -0500 Subject: [PATCH 03/17] Add support for Python 3.12 (#11262) * Add support for Python 3.12 Python 3.12.0 was released on 10-02-2023, this commit marks the start of support for Python 3.12 in Qiskit. It adds the supported Pythonv ersion in the package metadata and updates the CI configuration to run test jobs on Python 3.12 and build Python 3.12 wheels on release. Fixes: #10887 * Add release note * Avoid deprecated `datetime.datetime.utcnow()` usage In Python 3.12 `datetime.datetime.utcnow()` has been deprecated, being replaced by: `datetime.datetime.now(datetime.UTC)`. This commit updates the usage of `utcnow()` to follow the new convention. * Adjust UTC usage to support Python 3.8 The recommended alternative for using utcnow() in the deprecation warnings emitted by Python 3.12 are not compatible with Python 3.8. The datetime.UTC alias was not added to Python until Python 3.11. To ensure that the code is compatible with Python < 3.11 this commit updates all the usage of datetime.UTC to use datetime.timezone.utc instead, which is what datetime.UTC aliases to in Python >=3.11. --- .github/workflows/wheels.yml | 10 +++++----- azure-pipelines.yml | 4 ++-- constraints.txt | 4 ++-- qiskit/providers/fake_provider/fake_backend_v2.py | 6 +++--- qiskit/providers/fake_provider/fake_mumbai_v2.py | 2 +- qiskit/transpiler/target.py | 8 ++++---- qiskit_pkg/setup.py | 1 + .../notes/add-py312-support-7077426af34ac5da.yaml | 4 ++++ requirements.txt | 2 +- setup.py | 1 + test/python/providers/test_fake_backends.py | 12 ++++++------ tox.ini | 2 +- 12 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/add-py312-support-7077426af34ac5da.yaml diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 336b86c8db52..41b15d89653b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -21,7 +21,7 @@ jobs: python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl @@ -42,7 +42,7 @@ jobs: python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin CIBW_ARCHS_MACOS: arm64 universal2 @@ -91,7 +91,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" @@ -124,7 +124,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" @@ -157,7 +157,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: aarch64 - uses: actions/upload-artifact@v3 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1c85874e9f10..8fc21b7d8db1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,7 +31,7 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.8", "3.9", "3.10", "3.11"] + default: ["3.8", "3.9", "3.10", "3.11", "3.12"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" @@ -41,7 +41,7 @@ parameters: - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" type: string - default: "3.11" + default: "3.12" # These two versions of Python can be chosen somewhat arbitrarily, but we get # slightly better coverage per PR if they're neither the maximum nor minimum diff --git a/constraints.txt b/constraints.txt index 07b9862e426c..ef4197223893 100644 --- a/constraints.txt +++ b/constraints.txt @@ -5,12 +5,12 @@ jsonschema==3.2.0 # Numpy 1.25 deprecated some behaviours that we used, and caused the isometry # tests to flake. See https://github.com/Qiskit/qiskit-terra/issues/10305, # remove pin when resolving that. -numpy<1.25 +numpy<1.25; python_version<'3.12' # Scipy 1.11 seems to have caused an instability in the Weyl coordinates # eigensystem code for one of the test cases. See # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. -scipy<1.11 +scipy<1.11; python_version<'3.12' # Aer 0.13 causes several randomised tests to begin failing, and some # `QuantumInstance` use of noise models to raise exceptions. These need fixes diff --git a/qiskit/providers/fake_provider/fake_backend_v2.py b/qiskit/providers/fake_provider/fake_backend_v2.py index 23b0c58dadce..96cd6dff9d37 100644 --- a/qiskit/providers/fake_provider/fake_backend_v2.py +++ b/qiskit/providers/fake_provider/fake_backend_v2.py @@ -42,7 +42,7 @@ def __init__(self): None, name="FakeV2", description="A fake BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) self._qubit_properties = [ @@ -116,7 +116,7 @@ def __init__(self, bidirectional=True): None, name="Fake5QV2", description="A fake BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) qubit_properties = [ @@ -188,7 +188,7 @@ def __init__(self): None, name="FakeSimpleV2", description="A fake simple BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) self._lam = Parameter("lambda") diff --git a/qiskit/providers/fake_provider/fake_mumbai_v2.py b/qiskit/providers/fake_provider/fake_mumbai_v2.py index 1a5f1c24d4a3..8474319dfa62 100644 --- a/qiskit/providers/fake_provider/fake_mumbai_v2.py +++ b/qiskit/providers/fake_provider/fake_mumbai_v2.py @@ -39,7 +39,7 @@ def __init__(self): super().__init__( name="FakeMumbaiFractionalCX", description="A fake BackendV2 example based on IBM Mumbai", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) dt = 0.2222222222222222e-9 diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 5171951119d9..bc42a9c11e99 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1477,7 +1477,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "duration", None) is not None: property_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "gate_length", "unit": "s", "value": props.duration, @@ -1486,7 +1486,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "error", None) is not None: property_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "gate_error", "unit": "", "value": props.error, @@ -1511,7 +1511,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "error", None) is not None: props_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "readout_error", "unit": "", "value": props.error, @@ -1520,7 +1520,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "duration", None) is not None: props_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "readout_length", "unit": "s", "value": props.duration, diff --git a/qiskit_pkg/setup.py b/qiskit_pkg/setup.py index 4846b2d0b9f6..ecff044d2ac5 100644 --- a/qiskit_pkg/setup.py +++ b/qiskit_pkg/setup.py @@ -52,6 +52,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", ], keywords="qiskit sdk quantum", diff --git a/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml b/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml new file mode 100644 index 000000000000..4e2be1e92983 --- /dev/null +++ b/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added support for using Qiskit with Python 3.12. diff --git a/requirements.txt b/requirements.txt index 7620b00ee3ec..31206e951128 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ dill>=0.3 python-dateutil>=2.8.0 stevedore>=3.0.0 typing-extensions; python_version<'3.11' -symengine>=0.9, <0.10 +symengine>=0.9,!=0.10.0 diff --git a/setup.py b/setup.py index 97ca604fd757..751a151f21fc 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", ], keywords="qiskit sdk quantum", diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 1b7668a5b9f4..078a5110f818 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -497,7 +497,7 @@ def test_filter_faulty_qubits_backend_v2_converter(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -518,7 +518,7 @@ def test_filter_faulty_qubits_backend_v2_converter_with_delay(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -539,7 +539,7 @@ def test_filter_faulty_qubits_and_gates_backend_v2_converter(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -556,7 +556,7 @@ def test_filter_faulty_qubits_and_gates_backend_v2_converter(self): (34, 24), } non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -591,7 +591,7 @@ def test_filter_faulty_gates_v2_converter(self): (34, 24), } non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -618,7 +618,7 @@ def test_filter_faulty_no_faults_v2_converter(self): def test_faulty_full_path_transpile_connected_cmap(self, opt_level): backend = FakeYorktown() non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, diff --git a/tox.ini b/tox.ini index 0c799d28ba9a..5c517b1d2cc9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.3.0 -envlist = py38, py39, py310, py311, lint-incr +envlist = py38, py39, py310, py311, py312, lint-incr isolated_build = true [testenv] From b2a1f7544b70b23478de742f06cf788dab053461 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 24 Nov 2023 10:31:09 +0100 Subject: [PATCH 04/17] Remove __qiskit_version__ (deprecated in Qiskit Terra 0.25) (#11305) * remove __qiskit_version__ * reno --- qiskit/__init__.py | 4 - qiskit/version.py | 97 ------------------- ...iskit_version__11305-bcf134513641462b.yaml | 8 ++ test/python/test_version.py | 26 ----- 4 files changed, 8 insertions(+), 127 deletions(-) create mode 100644 releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml delete mode 100644 test/python/test_version.py diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 219d305321f3..664cd8c57c5c 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,10 +80,6 @@ from qiskit.compiler import transpile, assemble, schedule, sequence from .version import __version__ -from .version import QiskitVersion - - -__qiskit_version__ = QiskitVersion() class AerWrapper: diff --git a/qiskit/version.py b/qiskit/version.py index 79cadf189742..b460280f58dd 100644 --- a/qiskit/version.py +++ b/qiskit/version.py @@ -16,9 +16,6 @@ import os import subprocess -from collections.abc import Mapping - -import warnings ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -85,97 +82,3 @@ def get_version_info(): __version__ = get_version_info() - - -class QiskitVersion(Mapping): - """DEPRECATED in 0.25.0 use qiskit.__version__""" - - __slots__ = ["_version_dict", "_loaded"] - - def __init__(self): - self._version_dict = { - "qiskit": __version__, - } - self._loaded = False - - def _load_versions(self): - warnings.warn( - "qiskit.__qiskit_version__ is deprecated since " - "Qiskit Terra 0.25.0, and will be removed 3 months or more later. " - "Instead, you should use qiskit.__version__. The other packages listed in the" - "former qiskit.__qiskit_version__ have their own __version__ module level dunder, " - "as standard in PEP 8.", - category=DeprecationWarning, - stacklevel=3, - ) - try: - # TODO: Update to use qiskit_aer instead when we remove the - # namespace redirect - from qiskit.providers import aer - - self._version_dict["qiskit-aer"] = aer.__version__ - except Exception: - self._version_dict["qiskit-aer"] = None - try: - from qiskit import ignis - - self._version_dict["qiskit-ignis"] = ignis.__version__ - except Exception: - self._version_dict["qiskit-ignis"] = None - try: - from qiskit.providers import ibmq - - self._version_dict["qiskit-ibmq-provider"] = ibmq.__version__ - except Exception: - self._version_dict["qiskit-ibmq-provider"] = None - try: - import qiskit_nature - - self._version_dict["qiskit-nature"] = qiskit_nature.__version__ - except Exception: - self._version_dict["qiskit-nature"] = None - try: - import qiskit_finance - - self._version_dict["qiskit-finance"] = qiskit_finance.__version__ - except Exception: - self._version_dict["qiskit-finance"] = None - try: - import qiskit_optimization - - self._version_dict["qiskit-optimization"] = qiskit_optimization.__version__ - except Exception: - self._version_dict["qiskit-optimization"] = None - try: - import qiskit_machine_learning - - self._version_dict["qiskit-machine-learning"] = qiskit_machine_learning.__version__ - except Exception: - self._version_dict["qiskit-machine-learning"] = None - self._loaded = True - - def __repr__(self): - if not self._loaded: - self._load_versions() - return repr(self._version_dict) - - def __str__(self): - if not self._loaded: - self._load_versions() - return str(self._version_dict) - - def __getitem__(self, key): - if not self._loaded: - self._load_versions() - return self._version_dict[key] - - def __iter__(self): - if not self._loaded: - self._load_versions() - return iter(self._version_dict) - - def __len__(self): - return len(self._version_dict) - - -__qiskit_version__ = QiskitVersion() diff --git a/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml b/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml new file mode 100644 index 000000000000..c1cae7fde91b --- /dev/null +++ b/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The variable ``qiskit.__qiskit_version__`` is removed as it was deprecated since + Qiskit 0.44 (released on July 2023). + Instead, you should use ``qiskit.__version__``. The other packages listed in the + former ``qiskit.__qiskit_version__`` have their own ``__version__`` module level dunder, + as standard in PEP 8. diff --git a/test/python/test_version.py b/test/python/test_version.py deleted file mode 100644 index 3d260d6a3a8e..000000000000 --- a/test/python/test_version.py +++ /dev/null @@ -1,26 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for qiskit/version.py""" - -from qiskit import __qiskit_version__ -from qiskit import __version__ -from qiskit.test import QiskitTestCase - - -class TestVersion(QiskitTestCase): - """Tests for qiskit/version.py""" - - def test_qiskit_version(self): - """Test qiskit-version sets the correct version for terra.""" - with self.assertWarnsRegex(DeprecationWarning, "__qiskit_version__"): - self.assertEqual(__version__, __qiskit_version__["qiskit"]) From 20839da712b6ca3f9a0e68d5540ecfc717b6052c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 24 Nov 2023 10:47:07 +0100 Subject: [PATCH 05/17] Remove deprecated `opflow` module, `QuantumInstance` and related `utils` (#11111) * Remove code, tests, fix tests * Remove opflow from docs * Remove opflow references * Fix lint, fix docs * Fix test * Remove redundant test * Add release note * Remove redirects * Update docs index * Fix conflict * Fix docs * Apply suggestions from Jake's code review Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- docs/api_redirects.txt | 9 - docs/apidoc/index.rst | 13 - docs/apidoc/opflow.rst | 6 - docs/apidoc/utils_mitigation.rst | 6 - docs/migration_guides/opflow_migration.rst | 4 +- .../library/evolved_operator_ansatz.py | 35 +- qiskit/circuit/library/pauli_evolution.py | 24 +- qiskit/opflow/__init__.py | 332 ---- qiskit/opflow/converters/__init__.py | 85 - qiskit/opflow/converters/abelian_grouper.py | 165 -- qiskit/opflow/converters/circuit_sampler.py | 469 ----- qiskit/opflow/converters/converter_base.py | 52 - .../opflow/converters/dict_to_circuit_sum.py | 69 - .../opflow/converters/pauli_basis_change.py | 555 ------ .../opflow/converters/two_qubit_reduction.py | 101 -- qiskit/opflow/evolutions/__init__.py | 108 -- qiskit/opflow/evolutions/evolution_base.py | 58 - qiskit/opflow/evolutions/evolution_factory.py | 58 - qiskit/opflow/evolutions/evolved_op.py | 180 -- qiskit/opflow/evolutions/matrix_evolution.py | 74 - .../evolutions/pauli_trotter_evolution.py | 204 --- .../evolutions/trotterizations/__init__.py | 24 - .../evolutions/trotterizations/qdrift.py | 88 - .../evolutions/trotterizations/suzuki.py | 122 -- .../evolutions/trotterizations/trotter.py | 35 - .../trotterizations/trotterization_base.py | 68 - .../trotterizations/trotterization_factory.py | 53 - qiskit/opflow/exceptions.py | 29 - qiskit/opflow/expectations/__init__.py | 80 - .../expectations/aer_pauli_expectation.py | 163 -- .../opflow/expectations/cvar_expectation.py | 127 -- .../opflow/expectations/expectation_base.py | 73 - .../expectations/expectation_factory.py | 127 -- .../opflow/expectations/matrix_expectation.py | 77 - .../opflow/expectations/pauli_expectation.py | 118 -- qiskit/opflow/gradients/__init__.py | 194 -- .../gradients/circuit_gradients/__init__.py | 20 - .../circuit_gradients/circuit_gradient.py | 109 -- .../gradients/circuit_gradients/lin_comb.py | 911 ---------- .../circuit_gradients/param_shift.py | 430 ----- .../opflow/gradients/circuit_qfis/__init__.py | 20 - .../gradients/circuit_qfis/circuit_qfi.py | 66 - .../gradients/circuit_qfis/lin_comb_full.py | 228 --- .../circuit_qfis/overlap_block_diag.py | 195 -- .../gradients/circuit_qfis/overlap_diag.py | 275 --- qiskit/opflow/gradients/derivative_base.py | 247 --- qiskit/opflow/gradients/gradient.py | 231 --- qiskit/opflow/gradients/gradient_base.py | 77 - qiskit/opflow/gradients/hessian.py | 293 --- qiskit/opflow/gradients/hessian_base.py | 75 - qiskit/opflow/gradients/natural_gradient.py | 561 ------ qiskit/opflow/gradients/qfi.py | 75 - qiskit/opflow/gradients/qfi_base.py | 79 - qiskit/opflow/list_ops/__init__.py | 96 - qiskit/opflow/list_ops/composed_op.py | 198 --- qiskit/opflow/list_ops/list_op.py | 641 ------- qiskit/opflow/list_ops/summed_op.py | 251 --- qiskit/opflow/list_ops/tensored_op.py | 130 -- qiskit/opflow/mixins/__init__.py | 18 - qiskit/opflow/mixins/star_algebra.py | 133 -- qiskit/opflow/mixins/tensor.py | 58 - qiskit/opflow/operator_base.py | 516 ------ qiskit/opflow/operator_globals.py | 80 - qiskit/opflow/primitive_ops/__init__.py | 79 - qiskit/opflow/primitive_ops/circuit_op.py | 252 --- qiskit/opflow/primitive_ops/matrix_op.py | 237 --- qiskit/opflow/primitive_ops/pauli_op.py | 356 ---- qiskit/opflow/primitive_ops/pauli_sum_op.py | 464 ----- qiskit/opflow/primitive_ops/primitive_op.py | 324 ---- .../primitive_ops/tapered_pauli_sum_op.py | 590 ------- qiskit/opflow/state_fns/__init__.py | 78 - qiskit/opflow/state_fns/circuit_state_fn.py | 404 ----- qiskit/opflow/state_fns/cvar_measurement.py | 386 ---- qiskit/opflow/state_fns/dict_state_fn.py | 346 ---- qiskit/opflow/state_fns/operator_state_fn.py | 260 --- .../state_fns/sparse_vector_state_fn.py | 234 --- qiskit/opflow/state_fns/state_fn.py | 460 ----- qiskit/opflow/state_fns/vector_state_fn.py | 260 --- qiskit/opflow/utils.py | 119 -- qiskit/primitives/backend_estimator.py | 6 +- qiskit/primitives/base/base_estimator.py | 10 +- qiskit/primitives/estimator.py | 6 +- qiskit/primitives/utils.py | 28 +- qiskit/qpy/__init__.py | 2 +- qiskit/result/sampled_expval.py | 8 - .../commuting_2q_gate_router.py | 4 +- .../pauli_2q_evolution_commutation.py | 2 +- qiskit/utils/__init__.py | 20 +- qiskit/utils/backend_utils.py | 296 ---- qiskit/utils/measurement_error_mitigation.py | 272 --- qiskit/utils/mitigation/__init__.py | 58 - qiskit/utils/mitigation/_filters.py | 510 ------ qiskit/utils/mitigation/circuits.py | 253 --- qiskit/utils/mitigation/fitters.py | 493 ------ qiskit/utils/quantum_instance.py | 947 ---------- qiskit/utils/run_circuits.py | 413 ----- ...move-opflow-qi-utils-3debd943c65b17da.yaml | 20 + .../circuit/library/test_evolution_gate.py | 91 +- .../circuit/library/test_evolved_op_ansatz.py | 72 +- .../circuit/library/test_qaoa_ansatz.py | 4 +- test/python/opflow/__init__.py | 17 - test/python/opflow/opflow_test_case.py | 30 - test/python/opflow/test_abelian_grouper.py | 133 -- .../opflow/test_aer_pauli_expectation.py | 297 ---- test/python/opflow/test_cvar.py | 261 --- test/python/opflow/test_evolution.py | 384 ---- .../python/opflow/test_expectation_factory.py | 39 - test/python/opflow/test_gradients.py | 1571 ----------------- test/python/opflow/test_matrix_expectation.py | 184 -- test/python/opflow/test_op_construction.py | 1385 --------------- test/python/opflow/test_pauli_basis_change.py | 156 -- test/python/opflow/test_pauli_expectation.py | 317 ---- test/python/opflow/test_pauli_sum_op.py | 365 ---- test/python/opflow/test_state_construction.py | 250 --- .../python/opflow/test_state_op_meas_evals.py | 248 --- test/python/opflow/test_tapered_pauli.py | 57 - .../python/opflow/test_two_qubit_reduction.py | 64 - test/python/opflow/test_z2_symmetries.py | 112 -- test/python/primitives/test_estimator.py | 8 +- test/python/result/test_sampled_expval.py | 13 +- test/python/utils/mitigation/__init__.py | 13 - test/python/utils/mitigation/test_meas.py | 709 -------- 122 files changed, 113 insertions(+), 24862 deletions(-) delete mode 100644 docs/apidoc/opflow.rst delete mode 100644 docs/apidoc/utils_mitigation.rst delete mode 100644 qiskit/opflow/__init__.py delete mode 100644 qiskit/opflow/converters/__init__.py delete mode 100644 qiskit/opflow/converters/abelian_grouper.py delete mode 100644 qiskit/opflow/converters/circuit_sampler.py delete mode 100644 qiskit/opflow/converters/converter_base.py delete mode 100644 qiskit/opflow/converters/dict_to_circuit_sum.py delete mode 100644 qiskit/opflow/converters/pauli_basis_change.py delete mode 100644 qiskit/opflow/converters/two_qubit_reduction.py delete mode 100644 qiskit/opflow/evolutions/__init__.py delete mode 100644 qiskit/opflow/evolutions/evolution_base.py delete mode 100644 qiskit/opflow/evolutions/evolution_factory.py delete mode 100644 qiskit/opflow/evolutions/evolved_op.py delete mode 100644 qiskit/opflow/evolutions/matrix_evolution.py delete mode 100644 qiskit/opflow/evolutions/pauli_trotter_evolution.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/__init__.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/qdrift.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/suzuki.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/trotter.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/trotterization_base.py delete mode 100644 qiskit/opflow/evolutions/trotterizations/trotterization_factory.py delete mode 100644 qiskit/opflow/exceptions.py delete mode 100644 qiskit/opflow/expectations/__init__.py delete mode 100644 qiskit/opflow/expectations/aer_pauli_expectation.py delete mode 100644 qiskit/opflow/expectations/cvar_expectation.py delete mode 100644 qiskit/opflow/expectations/expectation_base.py delete mode 100644 qiskit/opflow/expectations/expectation_factory.py delete mode 100644 qiskit/opflow/expectations/matrix_expectation.py delete mode 100644 qiskit/opflow/expectations/pauli_expectation.py delete mode 100644 qiskit/opflow/gradients/__init__.py delete mode 100644 qiskit/opflow/gradients/circuit_gradients/__init__.py delete mode 100644 qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py delete mode 100644 qiskit/opflow/gradients/circuit_gradients/lin_comb.py delete mode 100644 qiskit/opflow/gradients/circuit_gradients/param_shift.py delete mode 100644 qiskit/opflow/gradients/circuit_qfis/__init__.py delete mode 100644 qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py delete mode 100644 qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py delete mode 100644 qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py delete mode 100644 qiskit/opflow/gradients/circuit_qfis/overlap_diag.py delete mode 100644 qiskit/opflow/gradients/derivative_base.py delete mode 100644 qiskit/opflow/gradients/gradient.py delete mode 100644 qiskit/opflow/gradients/gradient_base.py delete mode 100644 qiskit/opflow/gradients/hessian.py delete mode 100644 qiskit/opflow/gradients/hessian_base.py delete mode 100644 qiskit/opflow/gradients/natural_gradient.py delete mode 100644 qiskit/opflow/gradients/qfi.py delete mode 100644 qiskit/opflow/gradients/qfi_base.py delete mode 100644 qiskit/opflow/list_ops/__init__.py delete mode 100644 qiskit/opflow/list_ops/composed_op.py delete mode 100644 qiskit/opflow/list_ops/list_op.py delete mode 100644 qiskit/opflow/list_ops/summed_op.py delete mode 100644 qiskit/opflow/list_ops/tensored_op.py delete mode 100644 qiskit/opflow/mixins/__init__.py delete mode 100644 qiskit/opflow/mixins/star_algebra.py delete mode 100644 qiskit/opflow/mixins/tensor.py delete mode 100644 qiskit/opflow/operator_base.py delete mode 100644 qiskit/opflow/operator_globals.py delete mode 100644 qiskit/opflow/primitive_ops/__init__.py delete mode 100644 qiskit/opflow/primitive_ops/circuit_op.py delete mode 100644 qiskit/opflow/primitive_ops/matrix_op.py delete mode 100644 qiskit/opflow/primitive_ops/pauli_op.py delete mode 100644 qiskit/opflow/primitive_ops/pauli_sum_op.py delete mode 100644 qiskit/opflow/primitive_ops/primitive_op.py delete mode 100644 qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py delete mode 100644 qiskit/opflow/state_fns/__init__.py delete mode 100644 qiskit/opflow/state_fns/circuit_state_fn.py delete mode 100644 qiskit/opflow/state_fns/cvar_measurement.py delete mode 100644 qiskit/opflow/state_fns/dict_state_fn.py delete mode 100644 qiskit/opflow/state_fns/operator_state_fn.py delete mode 100644 qiskit/opflow/state_fns/sparse_vector_state_fn.py delete mode 100644 qiskit/opflow/state_fns/state_fn.py delete mode 100644 qiskit/opflow/state_fns/vector_state_fn.py delete mode 100644 qiskit/opflow/utils.py delete mode 100644 qiskit/utils/backend_utils.py delete mode 100644 qiskit/utils/measurement_error_mitigation.py delete mode 100644 qiskit/utils/mitigation/__init__.py delete mode 100644 qiskit/utils/mitigation/_filters.py delete mode 100644 qiskit/utils/mitigation/circuits.py delete mode 100644 qiskit/utils/mitigation/fitters.py delete mode 100644 qiskit/utils/quantum_instance.py delete mode 100644 qiskit/utils/run_circuits.py create mode 100644 releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml delete mode 100644 test/python/opflow/__init__.py delete mode 100644 test/python/opflow/opflow_test_case.py delete mode 100644 test/python/opflow/test_abelian_grouper.py delete mode 100644 test/python/opflow/test_aer_pauli_expectation.py delete mode 100644 test/python/opflow/test_cvar.py delete mode 100644 test/python/opflow/test_evolution.py delete mode 100644 test/python/opflow/test_expectation_factory.py delete mode 100644 test/python/opflow/test_gradients.py delete mode 100644 test/python/opflow/test_matrix_expectation.py delete mode 100644 test/python/opflow/test_op_construction.py delete mode 100644 test/python/opflow/test_pauli_basis_change.py delete mode 100644 test/python/opflow/test_pauli_expectation.py delete mode 100644 test/python/opflow/test_pauli_sum_op.py delete mode 100644 test/python/opflow/test_state_construction.py delete mode 100644 test/python/opflow/test_state_op_meas_evals.py delete mode 100644 test/python/opflow/test_tapered_pauli.py delete mode 100644 test/python/opflow/test_two_qubit_reduction.py delete mode 100644 test/python/opflow/test_z2_symmetries.py delete mode 100644 test/python/utils/mitigation/__init__.py delete mode 100644 test/python/utils/mitigation/test_meas.py diff --git a/docs/api_redirects.txt b/docs/api_redirects.txt index dd0bf35b1a9b..ff642bb8a94c 100644 --- a/docs/api_redirects.txt +++ b/docs/api_redirects.txt @@ -1,7 +1,3 @@ -qiskit.algorithms.AlgorithmError algorithms -qiskit.algorithms.eval_observables algorithms -qiskit.algorithms.estimate_observables algorithms - qiskit.assembler.assemble_circuits assembler qiskit.assembler.assemble_schedules assembler qiskit.assembler.disassemble assembler @@ -97,11 +93,6 @@ qiskit.converters.dagdependency_to_dag converters qiskit.dagcircuit.DAGCircuitError dagcircuit -qiskit.opflow.commutator opflow -qiskit.opflow.anti_commutator opflow -qiskit.opflow.double_commutator opflow -qiskit.opflow.OpflowError opflow - qiskit.providers.QiskitBackendNotFoundError providers qiskit.providers.BackendPropertyError providers qiskit.providers.JobError providers diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index e957cabb12f3..eb5ed9e3c2c5 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -44,17 +44,4 @@ API Reference transpiler_synthesis_plugins transpiler_builtin_plugins utils - utils_mitigation exceptions - -Deprecated Modules -================== - -.. warning:: - - These modules are going to be removed in Qiskit 1.0. Consider pinning ``qiskit~=0.45`` in your dependencies if you need them. - -.. toctree:: - :maxdepth: 1 - - opflow diff --git a/docs/apidoc/opflow.rst b/docs/apidoc/opflow.rst deleted file mode 100644 index f208e883a6d0..000000000000 --- a/docs/apidoc/opflow.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-opflow: - -.. automodule:: qiskit.opflow - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/apidoc/utils_mitigation.rst b/docs/apidoc/utils_mitigation.rst deleted file mode 100644 index a8af5992fd6a..000000000000 --- a/docs/apidoc/utils_mitigation.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-utils-mitigation: - -.. automodule:: qiskit.utils.mitigation - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/migration_guides/opflow_migration.rst b/docs/migration_guides/opflow_migration.rst index 3d0f7fa67d22..4b5da682ad8d 100644 --- a/docs/migration_guides/opflow_migration.rst +++ b/docs/migration_guides/opflow_migration.rst @@ -54,7 +54,7 @@ The functional equivalency can be roughly summarized as follows: * - Opflow Module - Alternative - * - Operators (:class:`~qiskit.opflow.OperatorBase`, :ref:`operator_globals`, + * - Operators (:class:`~qiskit.opflow.OperatorBase`, ``operator_globals``, :mod:`~qiskit.opflow.primitive_ops`, :mod:`~qiskit.opflow.list_ops`) - ``qiskit.quantum_info`` :ref:`Operators ` @@ -134,7 +134,7 @@ Operator Globals *Back to* `Contents`_ Opflow provided shortcuts to define common single qubit states, operators, and non-parametrized gates in the -:ref:`operator_globals` module. +``operator_globals`` module. These were mainly used for didactic purposes or quick prototyping, and can easily be replaced by their corresponding :mod:`~qiskit.quantum_info` class: :class:`~qiskit.quantum_info.Pauli`, :class:`~qiskit.quantum_info.Clifford` or diff --git a/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index a3832bfb71a6..572213089451 100644 --- a/qiskit/circuit/library/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/evolved_operator_ansatz.py @@ -20,7 +20,6 @@ from qiskit.circuit.parameter import Parameter from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError from qiskit.quantum_info import Operator, Pauli, SparsePauliOp from qiskit.synthesis.evolution import LieTrotter @@ -44,15 +43,14 @@ def __init__( ): """ Args: - operators (BaseOperator | OperatorBase | QuantumCircuit | list | None): The operators + operators (BaseOperator | QuantumCircuit | list | None): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). reps: The number of times to repeat the evolved operators. evolution (EvolutionBase | EvolutionSynthesis | None): A specification of which evolution synthesis to use for the - :class:`.PauliEvolutionGate`, if the operator is from :mod:`qiskit.quantum_info` - or an opflow converter object if the operator is from :mod:`qiskit.opflow`. + :class:`.PauliEvolutionGate`. Defaults to first order Trotterization. insert_barriers: Whether to insert barriers in between each evolution. name: The name of the circuit. @@ -113,13 +111,8 @@ def evolution(self): """The evolution converter used to compute the evolution. Returns: - EvolutionBase or EvolutionSynthesis: The evolution converter used to compute the evolution. + EvolutionSynthesis: The evolution converter used to compute the evolution. """ - if self._evolution is None: - # pylint: disable=cyclic-import - from qiskit.opflow import PauliTrotterEvolution - - return PauliTrotterEvolution() return self._evolution @@ -128,8 +121,7 @@ def evolution(self, evol) -> None: """Sets the evolution converter used to compute the evolution. Args: - evol (EvolutionBase | EvolutionSynthesis): An evolution synthesis object or - opflow converter object to construct the evolution. + evol (EvolutionSynthesis): An evolution synthesis object """ self._invalidate() self._evolution = evol @@ -147,7 +139,7 @@ def operators(self): def operators(self, operators=None) -> None: """Set the operators to be evolved. - operators (Optional[Union[OperatorBase, QuantumCircuit, list]): The operators to evolve. + operators (Optional[Union[QuantumCircuit, list]]): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). @@ -174,21 +166,10 @@ def preferred_init_points(self): return np.zeros(self.reps * len(self.operators), dtype=float) def _evolve_operator(self, operator, time): - from qiskit.opflow import OperatorBase, EvolutionBase # pylint: disable=cyclic-import from qiskit.circuit.library.hamiltonian_gate import HamiltonianGate - if isinstance(operator, OperatorBase): - if not isinstance(self.evolution, EvolutionBase): - raise QiskitError( - "If qiskit.opflow operators are evolved the evolution must be a " - f"qiskit.opflow.EvolutionBase, not a {type(self.evolution)}." - ) - - evolved = self.evolution.convert((time * operator).exp_i()) - return evolved.reduce().to_circuit() - # if the operator is specified as matrix use exact matrix exponentiation if isinstance(operator, Operator): gate = HamiltonianGate(operator, time) @@ -254,17 +235,11 @@ def _validate_prefix(parameter_prefix, operators): def _is_pauli_identity(operator): - from qiskit.opflow import PauliOp, PauliSumOp - - if isinstance(operator, PauliSumOp): - operator = operator.to_pauli_op() if isinstance(operator, SparsePauliOp): if len(operator.paulis) == 1: operator = operator.paulis[0] # check if the single Pauli is identity below else: return False - if isinstance(operator, PauliOp): - operator = operator.primitive if isinstance(operator, Pauli): return not np.any(np.logical_or(operator.x, operator.z)) return False diff --git a/qiskit/circuit/library/pauli_evolution.py b/qiskit/circuit/library/pauli_evolution.py index 3fa52c00569f..88f090f29a5d 100644 --- a/qiskit/circuit/library/pauli_evolution.py +++ b/qiskit/circuit/library/pauli_evolution.py @@ -50,7 +50,10 @@ class PauliEvolutionGate(Gate): from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate - from qiskit.opflow import I, Z, X + from qiskit.quantum_info import SparsePauliOp + + X = SparsePauliOp("X") + Z = SparsePauliOp("Z") # build the evolution gate operator = (Z ^ Z) - 0.1 * (X ^ I) @@ -86,7 +89,7 @@ def __init__( ) -> None: """ Args: - operator (Pauli | PauliOp | SparsePauliOp | PauliSumOp | list): + operator (Pauli | SparsePauliOp | list): The operator to evolve. Can also be provided as list of non-commuting operators where the elements are sums of commuting operators. For example: ``[XY + YX, ZZ + ZI + IZ, YY]``. @@ -147,22 +150,9 @@ def validate_parameter( def _to_sparse_pauli_op(operator): - """Cast the operator to a SparsePauliOp. + """Cast the operator to a SparsePauliOp.""" - For Opflow objects, return a global coefficient that must be multiplied to the evolution time. - Since this coefficient might contain unbound parameters it cannot be absorbed into the - coefficients of the SparsePauliOp. - """ - # pylint: disable=cyclic-import - from qiskit.opflow import PauliSumOp, PauliOp - - if isinstance(operator, PauliSumOp): - sparse_pauli = operator.primitive - sparse_pauli._coeffs *= operator.coeff - elif isinstance(operator, PauliOp): - sparse_pauli = SparsePauliOp(operator.primitive) - sparse_pauli._coeffs *= operator.coeff - elif isinstance(operator, Pauli): + if isinstance(operator, Pauli): sparse_pauli = SparsePauliOp(operator) elif isinstance(operator, SparsePauliOp): sparse_pauli = operator diff --git a/qiskit/opflow/__init__.py b/qiskit/opflow/__init__.py deleted file mode 100644 index babed3da26b5..000000000000 --- a/qiskit/opflow/__init__.py +++ /dev/null @@ -1,332 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -r""" -================================ -Operators (:mod:`qiskit.opflow`) -================================ - -.. currentmodule:: qiskit.opflow - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Operators and State functions are the building blocks of Quantum Algorithms. - -A library for Quantum Algorithms & Applications is more than a collection of -algorithms wrapped in Python functions. It needs to provide tools to make writing -algorithms simple and easy. This is the layer of modules between the circuits and algorithms, -providing the language and computational primitives for QA&A research. - -We call this layer the Operator Flow. It works by unifying computation with theory -through the common language of functions and operators, in a way which preserves physical -intuition and programming freedom. In the Operator Flow, we construct functions over binary -variables, manipulate those functions with operators, and evaluate properties of these functions -with measurements. - -The Operator Flow is meant to serve as a lingua franca between the theory and implementation -of Quantum Algorithms & Applications. Meaning, the ultimate goal is that when theorists speak -their theory in the Operator Flow, they are speaking valid implementation, and when the engineers -speak their implementation in the Operator Flow, they are speaking valid physical formalism. To -be successful, it must be fast and physically formal enough for theorists to find it easier and -more natural than hacking Matlab or NumPy, and the engineers must find it straightforward enough -that they can learn it as a typical software library, and learn the physics naturally and -effortlessly as they learn the code. There can never be a point where we say "below this level -this is all hacked out, don't come down here, stay in the interface layer above." It all must -be clear and learnable. - -Before getting into the details of the code, it's important to note that three mathematical -concepts unpin the Operator Flow. We derive most of the inspiration for the code structure from -`John Watrous's formalism `__ (but do not follow it exactly), -so it may be worthwhile to review Chapters I and II, which are free online, if you feel the -concepts are not clicking. - -1. An n-qubit State function is a complex function over n binary variables, which we will -often refer to as *n-qubit binary strings*. For example, the traditional quantum "zero state" is -a 1-qubit state function, with a definition of f(0) = 1 and f(1) = 0. - -2. An n-qubit Operator is a linear function taking n-qubit state functions to n-qubit state -functions. For example, the Pauli X Operator is defined by f(Zero) = One and f(One) = Zero. -Equivalently, an Operator can be defined as a complex function over two n-qubit binary strings, -and it is sometimes convenient to picture things this way. By this definition, our Pauli X can -be defined by its typical matrix elements, f(0, 0) = 0, f(1, 0) = 1, f(0, 1) = 1, -f(1, 1) = 0. - -3. An n-qubit Measurement is a functional taking n-qubit State functions to complex values. -For example, a Pauli Z Measurement can be defined by f(Zero) = 0 and f(One) = 1. - -.. note:: - - While every effort has been made to make programming the Operator Flow similar to mathematical - notation, in some places our hands are tied by the design of Python. In particular, when using - mathematical operators such as ``+`` and ``^`` (tensor product), beware that these follow - `Python operator precedence rules - `__. For example, - ``I^X + X^I`` will actually be interpreted as ``I ^ (X+X) ^ I == 2 * I^X^I``. In these cases, - you should use extra parentheses, like ``(I ^ X) + (X ^ I)``, or use the relevant method calls. - -Below, you'll find a base class for all Operators, some convenience immutable global variables -which simplify Operator construction, and two groups of submodules: Operators and Converters. - -Operator Base Class -=================== - -The OperatorBase serves as the base class for all Operators, State functions -and measurements, and enforces the presence and consistency of methods to manipulate these -objects conveniently. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - OperatorBase - -.. _operator_globals: - -Operator Globals -================ - -The :mod:`operator_globals` is a set of immutable Operator instances that are -convenient building blocks to reach for while working with the Operator flow. - -One qubit Pauli operators: - :attr:`X`, :attr:`Y`, :attr:`Z`, :attr:`I` - -Clifford+T, and some other common non-parameterized gates: - :attr:`CX`, :attr:`S`, :attr:`H`, :attr:`T`, :attr:`Swap`, :attr:`CZ` - -One qubit states: - :attr:`Zero`, :attr:`One`, :attr:`Plus`, :attr:`Minus` - -Submodules -========== - -Operators ---------- - -The Operators submodules include the PrimitiveOp, ListOp, and StateFn class -groups which represent the primary Operator modules. - -.. autosummary:: - :toctree: ../stubs/ - - primitive_ops - list_ops - state_fns - - -Converters ----------- - -The Converter submodules include objects which manipulate Operators, -usually recursing over an Operator structure and changing certain Operators' representation. -For example, the :class:`~.expectations.PauliExpectation` traverses an Operator structure, and -replaces all of the :class:`~.state_fns.OperatorStateFn` measurements containing non-diagonal -Pauli terms into diagonalizing circuits following by :class:`~.state_fns.OperatorStateFn` -measurement containing only diagonal Paulis. - -.. autosummary:: - :toctree: ../stubs/ - - converters - evolutions - expectations - gradients - - -Utility functions -================= - -.. autofunction:: commutator -.. autofunction:: anti_commutator -.. autofunction:: double_commutator - - -Exceptions -========== - -.. autoexception:: OpflowError -""" -import warnings - -# New Operators -from .operator_base import OperatorBase -from .primitive_ops import ( - PrimitiveOp, - PauliOp, - MatrixOp, - CircuitOp, - PauliSumOp, - TaperedPauliSumOp, - Z2Symmetries, -) -from .state_fns import ( - StateFn, - DictStateFn, - VectorStateFn, - CVaRMeasurement, - CircuitStateFn, - OperatorStateFn, - SparseVectorStateFn, -) -from .list_ops import ListOp, SummedOp, ComposedOp, TensoredOp -from .converters import ( - ConverterBase, - CircuitSampler, - PauliBasisChange, - DictToCircuitSum, - AbelianGrouper, - TwoQubitReduction, -) -from .expectations import ( - ExpectationBase, - ExpectationFactory, - PauliExpectation, - MatrixExpectation, - AerPauliExpectation, - CVaRExpectation, -) -from .evolutions import ( - EvolutionBase, - EvolutionFactory, - EvolvedOp, - PauliTrotterEvolution, - MatrixEvolution, - TrotterizationBase, - TrotterizationFactory, - Trotter, - Suzuki, - QDrift, -) -from .utils import commutator, anti_commutator, double_commutator - -# Convenience immutable instances -from .operator_globals import ( - EVAL_SIG_DIGITS, - X, - Y, - Z, - I, - CX, - S, - H, - T, - Swap, - CZ, - Zero, - One, - Plus, - Minus, -) - -# Gradients -from .gradients import ( - DerivativeBase, - GradientBase, - Gradient, - NaturalGradient, - HessianBase, - Hessian, - QFIBase, - QFI, - CircuitGradient, - CircuitQFI, -) - -# Exceptions -from .exceptions import OpflowError - -__all__ = [ - # Operators - "OperatorBase", - "PrimitiveOp", - "PauliOp", - "MatrixOp", - "CircuitOp", - "PauliSumOp", - "TaperedPauliSumOp", - "StateFn", - "DictStateFn", - "VectorStateFn", - "CircuitStateFn", - "OperatorStateFn", - "SparseVectorStateFn", - "CVaRMeasurement", - "ListOp", - "SummedOp", - "ComposedOp", - "TensoredOp", - # Converters - "ConverterBase", - "CircuitSampler", - "AbelianGrouper", - "DictToCircuitSum", - "PauliBasisChange", - "ExpectationBase", - "ExpectationFactory", - "PauliExpectation", - "MatrixExpectation", - "AerPauliExpectation", - "CVaRExpectation", - "EvolutionBase", - "EvolvedOp", - "EvolutionFactory", - "PauliTrotterEvolution", - "MatrixEvolution", - "TrotterizationBase", - "TrotterizationFactory", - "Trotter", - "Suzuki", - "QDrift", - "TwoQubitReduction", - "Z2Symmetries", - # Convenience immutable instances - "X", - "Y", - "Z", - "I", - "CX", - "S", - "H", - "T", - "Swap", - "CZ", - "Zero", - "One", - "Plus", - "Minus", - # Gradients - "DerivativeBase", - "GradientBase", - "Gradient", - "NaturalGradient", - "HessianBase", - "Hessian", - "QFIBase", - "QFI", - "OpflowError", - # utils - "commutator", - "anti_commutator", - "double_commutator", -] - -warnings.warn( - "The ``qiskit.opflow`` module is deprecated as of qiskit-terra 0.24.0. " - "It will be removed no earlier than 3 months after the release date. " - "For code migration guidelines, visit https://qisk.it/opflow_migration.", - category=DeprecationWarning, - stacklevel=2, -) diff --git a/qiskit/opflow/converters/__init__.py b/qiskit/opflow/converters/__init__.py deleted file mode 100644 index 8c2236eaec77..000000000000 --- a/qiskit/opflow/converters/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Converters (:mod:`qiskit.opflow.converters`) -============================================ - -.. currentmodule:: qiskit.opflow.converters - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Converters are objects which manipulate Operators, usually traversing an Operator to -change certain sub-Operators into a desired representation. Often the converted Operator is -isomorphic or approximate to the original Operator in some way, but not always. For example, -a converter may accept :class:`~qiskit.opflow.primitive_ops.CircuitOp` and return a -:class:`~qiskit.opflow.list_ops.SummedOp` of -:class:`~qiskit.opflow.primitive_ops.PauliOp`'s representing the -circuit unitary. Converters may not have polynomial space or time scaling in their operations. -On the contrary, many converters, such as a -:class:`~qiskit.opflow.expectations.MatrixExpectation` or -:class:`~qiskit.opflow.evolutions.MatrixEvolution`, -which convert :class:`~qiskit.opflow.primitive_ops.PauliOp`'s to -:class:`~qiskit.opflow.primitive_ops.MatrixOp`'s internally, will require time or space -exponential in the number of qubits unless a clever trick is known -(such as the use of sparse matrices). - - -Note: - Not all converters are in this module, as :mod:`~qiskit.opflow.expectations` - and :mod:`~qiskit.opflow.evolutions` are also converters. - -Converter Base Class --------------------- -The converter base class simply enforces the presence of a :meth:`~ConverterBase.convert` method. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ConverterBase - -Converters ----------- -In addition to the base class, directory holds a few miscellaneous converters which are used -frequently around the Operator flow. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - CircuitSampler - AbelianGrouper - DictToCircuitSum - PauliBasisChange - TwoQubitReduction -""" - -from .converter_base import ConverterBase -from .circuit_sampler import CircuitSampler -from .pauli_basis_change import PauliBasisChange -from .dict_to_circuit_sum import DictToCircuitSum -from .abelian_grouper import AbelianGrouper -from .two_qubit_reduction import TwoQubitReduction - -__all__ = [ - "ConverterBase", - "CircuitSampler", - "PauliBasisChange", - "DictToCircuitSum", - "AbelianGrouper", - "TwoQubitReduction", -] diff --git a/qiskit/opflow/converters/abelian_grouper.py b/qiskit/opflow/converters/abelian_grouper.py deleted file mode 100644 index b267fc699411..000000000000 --- a/qiskit/opflow/converters/abelian_grouper.py +++ /dev/null @@ -1,165 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""AbelianGrouper Class""" - -from collections import defaultdict -from typing import List, Tuple, Union, cast - -import numpy as np -import rustworkx as rx - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class AbelianGrouper(ConverterBase): - """Deprecated: The AbelianGrouper converts SummedOps into a sum of Abelian sums. - - Meaning, it will traverse the Operator, and when it finds a SummedOp, it will evaluate which of - the summed sub-Operators commute with one another. It will then convert each of the groups of - commuting Operators into their own SummedOps, and return the sum-of-commuting-SummedOps. - This is particularly useful for cases where mutually commuting groups can be handled - similarly, as in the case of Pauli Expectations, where commuting Paulis have the same - diagonalizing circuit rotation, or Pauli Evolutions, where commuting Paulis can be - diagonalized together. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, traverse: bool = True) -> None: - """ - Args: - traverse: Whether to convert only the Operator passed to ``convert``, or traverse - down that Operator. - """ - super().__init__() - self._traverse = traverse - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Check if operator is a SummedOp, in which case covert it into a sum of mutually - commuting sums, or if the Operator contains sub-Operators and ``traverse`` is True, - attempt to convert any sub-Operators. - - Args: - operator: The Operator to attempt to convert. - - Returns: - The converted Operator. - """ - if isinstance(operator, PauliSumOp): - return self.group_subops(operator) - - if isinstance(operator, ListOp): - if isinstance(operator, SummedOp) and all( - isinstance(op, PauliOp) for op in operator.oplist - ): - # For now, we only support graphs over Paulis. - return self.group_subops(operator) - elif self._traverse: - return operator.traverse(self.convert) - elif isinstance(operator, OperatorStateFn) and self._traverse: - return OperatorStateFn( - self.convert(operator.primitive), - is_measurement=operator.is_measurement, - coeff=operator.coeff, - ) - elif isinstance(operator, EvolvedOp) and self._traverse: - return EvolvedOp(self.convert(operator.primitive), coeff=operator.coeff) - return operator - - @classmethod - def group_subops(cls, list_op: Union[ListOp, PauliSumOp]) -> ListOp: - """Given a ListOp, attempt to group into Abelian ListOps of the same type. - - Args: - list_op: The Operator to group into Abelian groups - - Returns: - The grouped Operator. - - Raises: - OpflowError: If any of list_op's sub-ops is not ``PauliOp``. - """ - if isinstance(list_op, ListOp): - for op in list_op.oplist: - if not isinstance(op, PauliOp): - raise OpflowError( - "Cannot determine Abelian groups if any Operator in list_op is not " - f"`PauliOp`. E.g., {op} ({type(op)})" - ) - - edges = cls._anti_commutation_graph(list_op) - nodes = range(len(list_op)) - - graph = rx.PyGraph() - graph.add_nodes_from(nodes) - graph.add_edges_from_no_data(edges) - # Keys in coloring_dict are nodes, values are colors - coloring_dict = rx.graph_greedy_color(graph) - groups = defaultdict(list) - for idx, color in coloring_dict.items(): - groups[color].append(idx) - - if isinstance(list_op, PauliSumOp): - primitive = list_op.primitive - return SummedOp( - [PauliSumOp(primitive[group], grouping_type="TPB") for group in groups.values()], - coeff=list_op.coeff, - ) - - group_ops: List[ListOp] = [ - list_op.__class__([list_op[idx] for idx in group], abelian=True) - for group in groups.values() - ] - if len(group_ops) == 1: - return group_ops[0].mul(list_op.coeff) - return list_op.__class__(group_ops, coeff=list_op.coeff) - - @staticmethod - def _anti_commutation_graph(ops: Union[ListOp, PauliSumOp]) -> List[Tuple[int, int]]: - """Create edges (i, j) if i and j are not commutable. - - Note: - This method is applicable to only PauliOps. - - Args: - ops: operators - - Returns: - A list of pairs of indices of the operators that are not commutable - """ - # convert a Pauli operator into int vector where {I: 0, X: 2, Y: 3, Z: 1} - if isinstance(ops, PauliSumOp): - mat1 = np.array( - [op.primitive.paulis.z[0] + 2 * op.primitive.paulis.x[0] for op in ops], - dtype=np.int8, - ) - else: - mat1 = np.array([op.primitive.z + 2 * op.primitive.x for op in ops], dtype=np.int8) - - mat2 = mat1[:, None] - # mat3[i, j] is True if i and j are commutable with TPB - mat3 = (((mat1 * mat2) * (mat1 - mat2)) == 0).all(axis=2) - # return [(i, j) if mat3[i, j] is False and i < j] - return cast(List[Tuple[int, int]], list(zip(*np.where(np.triu(np.logical_not(mat3), k=1))))) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py deleted file mode 100644 index 04116c93b31f..000000000000 --- a/qiskit/opflow/converters/circuit_sampler.py +++ /dev/null @@ -1,469 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CircuitSampler Class""" - - -import logging -from functools import partial -from time import time -from typing import Any, Dict, List, Optional, Tuple, Union, cast - -import numpy as np - -from qiskit import QiskitError -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.providers import Backend -from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class CircuitSampler(ConverterBase): - """ - Deprecated: The CircuitSampler traverses an Operator and converts any CircuitStateFns into - approximations of the state function by a DictStateFn or VectorStateFn using a quantum - backend. Note that in order to approximate the value of the CircuitStateFn, it must 1) send - state function through a depolarizing channel, which will destroy all phase information and - 2) replace the sampled frequencies with **square roots** of the frequency, rather than the raw - probability of sampling (which would be the equivalent of sampling the **square** of the - state function, per the Born rule. - - The CircuitSampler aggressively caches transpiled circuits to handle re-parameterization of - the same circuit efficiently. If you are converting multiple different Operators, - you are better off using a different CircuitSampler for each Operator to avoid cache thrashing. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - backend: Union[Backend, QuantumInstance], - statevector: Optional[bool] = None, - param_qobj: bool = False, - attach_results: bool = False, - caching: str = "last", - ) -> None: - """ - Args: - backend: The quantum backend or QuantumInstance to use to sample the circuits. - statevector: If backend is a statevector backend, whether to replace the - CircuitStateFns with DictStateFns (from the counts) or VectorStateFns (from the - statevector). ``None`` will set this argument automatically based on the backend. - attach_results: Whether to attach the data from the backend ``Results`` object for - a given ``CircuitStateFn``` to an ``execution_results`` field added the converted - ``DictStateFn`` or ``VectorStateFn``. - param_qobj: Whether to use Aer's parameterized Qobj capability to avoid re-assembling - the circuits. - caching: The caching strategy. Can be `'last'` (default) to store the last operator - that was converted, set to `'all'` to cache all processed operators. - - Raises: - ValueError: Set statevector or param_qobj True when not supported by backend. - """ - super().__init__() - - self._quantum_instance = ( - backend if isinstance(backend, QuantumInstance) else QuantumInstance(backend=backend) - ) - self._statevector = ( - statevector if statevector is not None else self.quantum_instance.is_statevector - ) - self._param_qobj = param_qobj - self._attach_results = attach_results - - self._check_quantum_instance_and_modes_consistent() - - # Object state variables - self._caching = caching - self._cached_ops: Dict[int, OperatorCache] = {} - - self._last_op: Optional[OperatorBase] = None - self._reduced_op_cache = None - self._circuit_ops_cache: Dict[int, CircuitStateFn] = {} - self._transpiled_circ_cache: Optional[List[Any]] = None - self._transpiled_circ_templates: Optional[List[Any]] = None - self._transpile_before_bind = True - - def _check_quantum_instance_and_modes_consistent(self) -> None: - """Checks whether the statevector and param_qobj settings are compatible with the - backend - - Raises: - ValueError: statevector or param_qobj are True when not supported by backend. - """ - if self._statevector and not is_statevector_backend(self.quantum_instance.backend): - raise ValueError( - "Statevector mode for circuit sampling requires statevector " - "backend, not {}.".format(self.quantum_instance.backend) - ) - - if self._param_qobj and not is_aer_provider(self.quantum_instance.backend): - raise ValueError( - "Parameterized Qobj mode requires Aer " - "backend, not {}.".format(self.quantum_instance.backend) - ) - - @property - def quantum_instance(self) -> QuantumInstance: - """Returns the quantum instance. - - Returns: - The QuantumInstance used by the CircuitSampler - """ - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Sets the QuantumInstance. - - Raises: - ValueError: statevector or param_qobj are True when not supported by backend. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - self._check_quantum_instance_and_modes_consistent() - - def convert( - self, - operator: OperatorBase, - params: Optional[Dict[Parameter, Union[float, List[float], List[List[float]]]]] = None, - ) -> OperatorBase: - r""" - Converts the Operator to one in which the CircuitStateFns are replaced by - DictStateFns or VectorStateFns. Extracts the CircuitStateFns out of the Operator, - caches them, calls ``sample_circuits`` below to get their converted replacements, - and replaces the CircuitStateFns in operator with the replacement StateFns. - - Args: - operator: The Operator to convert - params: A dictionary mapping parameters to either single binding values or lists of - binding values. - - Returns: - The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns. - Raises: - OpflowError: if extracted circuits are empty. - """ - # check if the operator should be cached - op_id = operator.instance_id - # op_id = id(operator) - if op_id not in self._cached_ops.keys(): - # delete cache if we only want to cache one operator - if self._caching == "last": - self.clear_cache() - - # convert to circuit and reduce - operator_dicts_replaced = operator.to_circuit_op() - self._reduced_op_cache = operator_dicts_replaced.reduce() - - # extract circuits - self._circuit_ops_cache = {} - self._extract_circuitstatefns(self._reduced_op_cache) - if not self._circuit_ops_cache: - raise OpflowError( - "Circuits are empty. " - "Check that the operator is an instance of CircuitStateFn or its ListOp." - ) - self._transpiled_circ_cache = None - self._transpile_before_bind = True - else: - # load the cached circuits - self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache - self._circuit_ops_cache = self._cached_ops[op_id].circuit_ops_cache - self._transpiled_circ_cache = self._cached_ops[op_id].transpiled_circ_cache - self._transpile_before_bind = self._cached_ops[op_id].transpile_before_bind - self._transpiled_circ_templates = self._cached_ops[op_id].transpiled_circ_templates - - return_as_list = False - if params is not None and len(params.keys()) > 0: - p_0 = list(params.values())[0] - if isinstance(p_0, (list, np.ndarray)): - num_parameterizations = len(p_0) - param_bindings = [ - {param: value_list[i] for param, value_list in params.items()} # type: ignore - for i in range(num_parameterizations) - ] - return_as_list = True - else: - num_parameterizations = 1 - param_bindings = [params] - - else: - param_bindings = None - num_parameterizations = 1 - - # Don't pass circuits if we have in the cache, the sampling function knows to use the cache - circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None - p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) - - def replace_circuits_with_dicts(operator, param_index=0): - if isinstance(operator, CircuitStateFn): - return sampled_statefn_dicts[id(operator)][param_index] - elif isinstance(operator, ListOp): - return operator.traverse( - partial(replace_circuits_with_dicts, param_index=param_index) - ) - else: - return operator - - # store the operator we constructed, if it isn't stored already - if op_id not in self._cached_ops.keys(): - op_cache = OperatorCache() - op_cache.reduced_op_cache = self._reduced_op_cache - op_cache.circuit_ops_cache = self._circuit_ops_cache - op_cache.transpiled_circ_cache = self._transpiled_circ_cache - op_cache.transpile_before_bind = self._transpile_before_bind - op_cache.transpiled_circ_templates = self._transpiled_circ_templates - self._cached_ops[op_id] = op_cache - - if return_as_list: - return ListOp( - [ - replace_circuits_with_dicts(self._reduced_op_cache, param_index=i) - for i in range(num_parameterizations) - ] - ) - else: - return replace_circuits_with_dicts(self._reduced_op_cache, param_index=0) - - def clear_cache(self) -> None: - """Clear the cache of sampled operator expressions.""" - self._cached_ops = {} - - def _extract_circuitstatefns(self, operator: OperatorBase) -> None: - r""" - Recursively extract the ``CircuitStateFns`` contained in operator into the - ``_circuit_ops_cache`` field. - """ - if isinstance(operator, CircuitStateFn): - self._circuit_ops_cache[id(operator)] = operator - elif isinstance(operator, ListOp): - for op in operator.oplist: - self._extract_circuitstatefns(op) - - def sample_circuits( - self, - circuit_sfns: Optional[List[CircuitStateFn]] = None, - param_bindings: Optional[List[Dict[Parameter, float]]] = None, - ) -> Dict[int, List[StateFn]]: - r""" - Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their - replacement DictStateFn or VectorStateFn. If param_bindings is provided, - the CircuitStateFns are broken into their parameterizations, and a list of StateFns is - returned in the dict for each circuit ``id()``. Note that param_bindings is provided here - in a different format than in ``convert``, and lists of parameters within the dict is not - supported, and only binding dicts which are valid to be passed into Terra can be included - in this list. - - Args: - circuit_sfns: The list of CircuitStateFns to sample. - param_bindings: The parameterizations to bind to each CircuitStateFn. - - Returns: - The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. - Raises: - OpflowError: if extracted circuits are empty. - """ - if not circuit_sfns and not self._transpiled_circ_cache: - raise OpflowError("CircuitStateFn is empty and there is no cache.") - - if circuit_sfns: - self._transpiled_circ_templates = None - if self._statevector or circuit_sfns[0].from_operator: - circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns] - else: - circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] - - try: - self._transpiled_circ_cache = self.quantum_instance.transpile( - circuits, pass_manager=self.quantum_instance.unbound_pass_manager - ) - except QiskitError: - logger.debug( - r"CircuitSampler failed to transpile circuits with unbound " - r"parameters. Attempting to transpile only when circuits are bound " - r"now, but this can hurt performance due to repeated transpilation." - ) - self._transpile_before_bind = False - self._transpiled_circ_cache = circuits - else: - circuit_sfns = list(self._circuit_ops_cache.values()) - - if param_bindings is not None: - if self._param_qobj: - start_time = time() - ready_circs = self._prepare_parameterized_run_config(param_bindings) - end_time = time() - logger.debug("Parameter conversion %.5f (ms)", (end_time - start_time) * 1000) - else: - start_time = time() - ready_circs = [ - circ.assign_parameters(_filter_params(circ, binding)) - for circ in self._transpiled_circ_cache - for binding in param_bindings - ] - end_time = time() - logger.debug("Parameter binding %.5f (ms)", (end_time - start_time) * 1000) - else: - ready_circs = self._transpiled_circ_cache - - # run transpiler passes on bound circuits - if self._transpile_before_bind and self.quantum_instance.bound_pass_manager is not None: - ready_circs = self.quantum_instance.transpile( - ready_circs, pass_manager=self.quantum_instance.bound_pass_manager - ) - - results = self.quantum_instance.execute( - ready_circs, had_transpiled=self._transpile_before_bind - ) - - if param_bindings is not None and self._param_qobj: - self._clean_parameterized_run_config() - - # Wipe parameterizations, if any - # self.quantum_instance._run_config.parameterizations = None - - sampled_statefn_dicts = {} - for i, op_c in enumerate(circuit_sfns): - # Taking square root because we're replacing a statevector - # representation of probabilities. - reps = len(param_bindings) if param_bindings is not None else 1 - c_statefns = [] - for j in range(reps): - circ_index = (i * reps) + j - circ_results = results.data(circ_index) - - if "expval_measurement" in circ_results: - avg = circ_results["expval_measurement"] - # Will be replaced with just avg when eval is called later - num_qubits = circuit_sfns[0].num_qubits - result_sfn = DictStateFn( - "0" * num_qubits, - coeff=avg * op_c.coeff, - is_measurement=op_c.is_measurement, - from_operator=op_c.from_operator, - ) - elif self._statevector: - result_sfn = StateFn( - op_c.coeff * results.get_statevector(circ_index), - is_measurement=op_c.is_measurement, - ) - else: - shots = self.quantum_instance._run_config.shots - result_sfn = DictStateFn( - { - b: (v / shots) ** 0.5 * op_c.coeff - for (b, v) in results.get_counts(circ_index).items() - }, - is_measurement=op_c.is_measurement, - from_operator=op_c.from_operator, - ) - if self._attach_results: - result_sfn.execution_results = circ_results - c_statefns.append(result_sfn) - sampled_statefn_dicts[id(op_c)] = c_statefns - return sampled_statefn_dicts - - def _build_aer_params( - self, - circuit: QuantumCircuit, - building_param_tables: Dict[Tuple[int, int], List[float]], - input_params: Dict[Parameter, float], - ) -> None: - def resolve_param(inst_param): - if not isinstance(inst_param, ParameterExpression): - return None - param_mappings = {} - for param in inst_param._parameter_symbols.keys(): - if param not in input_params: - raise ValueError(f"unexpected parameter: {param}") - param_mappings[param] = input_params[param] - return float(inst_param.bind(param_mappings)) - - gate_index = 0 - for instruction in circuit.data: - param_index = 0 - for inst_param in instruction.operation.params: - val = resolve_param(inst_param) - if val is not None: - param_key = (gate_index, param_index) - if param_key in building_param_tables: - building_param_tables[param_key].append(val) - else: - building_param_tables[param_key] = [val] - param_index += 1 - gate_index += 1 - - def _prepare_parameterized_run_config( - self, param_bindings: List[Dict[Parameter, float]] - ) -> List[Any]: - - self.quantum_instance._run_config.parameterizations = [] - - if self._transpiled_circ_templates is None or len(self._transpiled_circ_templates) != len( - self._transpiled_circ_cache - ): - - # temporally resolve parameters of self._transpiled_circ_cache - # They will be overridden in Aer from the next iterations - self._transpiled_circ_templates = [ - circ.assign_parameters(_filter_params(circ, param_bindings[0])) - for circ in self._transpiled_circ_cache - ] - - for circ in self._transpiled_circ_cache: - building_param_tables: Dict[Tuple[int, int], List[float]] = {} - for param_binding in param_bindings: - self._build_aer_params(circ, building_param_tables, param_binding) - param_tables = [] - for gate_and_param_indices in building_param_tables: - gate_index = gate_and_param_indices[0] - param_index = gate_and_param_indices[1] - param_tables.append( - [[gate_index, param_index], building_param_tables[(gate_index, param_index)]] - ) - self.quantum_instance._run_config.parameterizations.append(param_tables) - - return self._transpiled_circ_templates - - def _clean_parameterized_run_config(self) -> None: - self.quantum_instance._run_config.parameterizations = [] - - -def _filter_params(circuit, param_dict): - """Remove all parameters from ``param_dict`` that are not in ``circuit``.""" - return {param: value for param, value in param_dict.items() if param in circuit.parameters} - - -class OperatorCache: - """A struct to cache an operator along with the circuits in contains.""" - - reduced_op_cache = None # the reduced operator - circuit_ops_cache: Optional[Dict[int, CircuitStateFn]] = None # the extracted circuits - transpiled_circ_cache = None # the transpiled circuits - transpile_before_bind = True # whether to transpile before binding parameters in the operator - transpiled_circ_templates: Optional[List[Any]] = None # transpiled circuit templates for Aer diff --git a/qiskit/opflow/converters/converter_base.py b/qiskit/opflow/converters/converter_base.py deleted file mode 100644 index e1ed07f2e1c4..000000000000 --- a/qiskit/opflow/converters/converter_base.py +++ /dev/null @@ -1,52 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ConverterBase Class""" - -from abc import ABC, abstractmethod - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class ConverterBase(ABC): - r""" - Deprecated: Converters take an Operator and return a new Operator, generally isomorphic - in some way with the first, but with certain desired properties. For example, - a converter may accept ``CircuitOp`` and return a ``SummedOp`` of - ``PauliOps`` representing the circuit unitary. Converters may not - have polynomial space or time scaling in their operations. On the contrary, many - converters, such as a ``MatrixExpectation`` or ``MatrixEvolution``, which convert - ``PauliOps`` to ``MatrixOps`` internally, will require time or space exponential - in the number of qubits unless a clever trick is known (such as the use of sparse - matrices).""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept the Operator and return the converted Operator - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator. - - """ - raise NotImplementedError diff --git a/qiskit/opflow/converters/dict_to_circuit_sum.py b/qiskit/opflow/converters/dict_to_circuit_sum.py deleted file mode 100644 index 04facc6fe506..000000000000 --- a/qiskit/opflow/converters/dict_to_circuit_sum.py +++ /dev/null @@ -1,69 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""DictToCircuitSum Class""" - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class DictToCircuitSum(ConverterBase): - r""" - Deprecated: Converts ``DictStateFns`` or ``VectorStateFns`` to equivalent ``CircuitStateFns`` - or sums thereof. The behavior of this class can be mostly replicated by calling ``to_circuit_op`` - on an Operator, but with the added control of choosing whether to convert only ``DictStateFns`` - or ``VectorStateFns``, rather than both. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, traverse: bool = True, convert_dicts: bool = True, convert_vectors: bool = True - ) -> None: - """ - Args: - traverse: Whether to recurse down into Operators with internal sub-operators for - conversion. - convert_dicts: Whether to convert VectorStateFn. - convert_vectors: Whether to convert DictStateFns. - """ - super().__init__() - self._traverse = traverse - self._convert_dicts = convert_dicts - self._convert_vectors = convert_vectors - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Convert the Operator to ``CircuitStateFns``, recursively if ``traverse`` is True. - - Args: - operator: The Operator to convert - - Returns: - The converted Operator. - """ - - if isinstance(operator, DictStateFn) and self._convert_dicts: - return CircuitStateFn.from_dict(operator.primitive) - if isinstance(operator, VectorStateFn) and self._convert_vectors: - return CircuitStateFn.from_vector(operator.to_matrix(massive=True)) - elif isinstance(operator, ListOp) and "Dict" in operator.primitive_strings(): - return operator.traverse(self.convert) - else: - return operator diff --git a/qiskit/opflow/converters/pauli_basis_change.py b/qiskit/opflow/converters/pauli_basis_change.py deleted file mode 100644 index 0d2213a761dc..000000000000 --- a/qiskit/opflow/converters/pauli_basis_change.py +++ /dev/null @@ -1,555 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PauliBasisChange Class""" - -from functools import partial, reduce -from typing import Callable, List, Optional, Tuple, Union, cast - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.operator_globals import H, I, S -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.quantum_info import Pauli -from qiskit.utils.deprecation import deprecate_func - - -class PauliBasisChange(ConverterBase): - r""" - Deprecated: Converter for changing Paulis into other bases. By default, the diagonal basis - composed only of Pauli {Z, I}^n is used as the destination basis to which to convert. - Meaning, if a Pauli containing X or Y terms is passed in, which cannot be - sampled or evolved natively on some Quantum hardware, the Pauli can be replaced by a - composition of a change of basis circuit and a Pauli composed of only Z - and I terms (diagonal), which can be evolved or sampled natively on the Quantum - hardware. - - The replacement function determines how the ``PauliOps`` should be replaced by their computed - change-of-basis ``CircuitOps`` and destination ``PauliOps``. Several convenient out-of-the-box - replacement functions have been added as static methods, such as ``measurement_replacement_fn``. - - This class uses the typical basis change method found in most Quantum Computing textbooks - (such as on page 210 of Nielsen and Chuang's, "Quantum Computation and Quantum Information", - ISBN: 978-1-107-00217-3), which involves diagonalizing the single-qubit Paulis with H and S† - gates, mapping the eigenvectors of the diagonalized origin Pauli to the diagonalized - destination Pauli using CNOTS, and then de-diagonalizing any single qubit Paulis to their - non-diagonal destination values. Many other methods are possible, as well as variations on - this method, such as the placement of the CNOT chains. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - destination_basis: Optional[Union[Pauli, PauliOp]] = None, - traverse: bool = True, - replacement_fn: Optional[Callable] = None, - ) -> None: - """ - Args: - destination_basis: The Pauli into the basis of which the operators - will be converted. If None is specified, the destination basis will be the - diagonal ({I, Z}^n) basis requiring only single qubit rotations. - traverse: If true and the operator passed into convert contains sub-Operators, - such as ListOp, traverse the Operator and apply the conversion to every - applicable sub-operator within it. - replacement_fn: A function specifying what to do with the basis-change - ``CircuitOp`` and destination ``PauliOp`` when converting an Operator and - replacing converted values. By default, this will be - - 1) For StateFns (or Measurements): replacing the StateFn with - ComposedOp(StateFn(d), c) where c is the conversion circuit and d is the - destination Pauli, so the overall beginning and ending operators are - equivalent. - - 2) For non-StateFn Operators: replacing the origin p with c·d·c†, where c - is the conversion circuit and d is the destination, so the overall - beginning and ending operators are equivalent. - - """ - super().__init__() - if destination_basis is not None: - self.destination = destination_basis # type: ignore - else: - self._destination = None # type: Optional[PauliOp] - self._traverse = traverse - self._replacement_fn = replacement_fn or PauliBasisChange.operator_replacement_fn - - @property - def destination(self) -> Optional[PauliOp]: - r""" - The destination ``PauliOp``, or ``None`` if using the default destination, the diagonal - basis. - """ - return self._destination - - @destination.setter - def destination(self, dest: Union[Pauli, PauliOp]) -> None: - r""" - The destination ``PauliOp``, or ``None`` if using the default destination, the diagonal - basis. - """ - if isinstance(dest, Pauli): - dest = PauliOp(dest) - - if not isinstance(dest, PauliOp): - raise TypeError( - f"PauliBasisChange can only convert into Pauli bases, not {type(dest)}." - ) - self._destination = dest - - # TODO see whether we should make this performant by handling ListOps of Paulis later. - # pylint: disable=too-many-return-statements - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Given a ``PauliOp``, or an Operator containing ``PauliOps`` if ``_traverse`` is True, - converts each Pauli into the basis specified by self._destination and a - basis-change-circuit, calls ``replacement_fn`` with these two Operators, and replaces - the ``PauliOps`` with the output of ``replacement_fn``. For example, for the built-in - ``operator_replacement_fn`` below, each PauliOp p will be replaced by the composition - of the basis-change Clifford ``CircuitOp`` c with the destination PauliOp d and c†, - such that p = c·d·c†, up to global phase. - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator. - - """ - if ( - isinstance(operator, OperatorStateFn) - and isinstance(operator.primitive, PauliSumOp) - and operator.primitive.grouping_type == "TPB" - ): - primitive = operator.primitive.primitive.copy() - origin_x = reduce(np.logical_or, primitive.paulis.x) - origin_z = reduce(np.logical_or, primitive.paulis.z) - origin_pauli = Pauli((origin_z, origin_x)) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - primitive.paulis.z = np.logical_or(primitive.paulis.x, primitive.paulis.z) - primitive.paulis.x = False - # The following line is because the deprecated PauliTable did not have a phase - # and did not track it, so phase=0 was always guaranteed. - # But the new PauliList may change phase. - primitive.paulis.phase = 0 - dest_pauli_sum_op = PauliSumOp(primitive, coeff=operator.coeff, grouping_type="TPB") - return self._replacement_fn(cob_instr_op, dest_pauli_sum_op) - - if ( - isinstance(operator, OperatorStateFn) - and isinstance(operator.primitive, SummedOp) - and all( - isinstance(op, PauliSumOp) and op.grouping_type == "TPB" - for op in operator.primitive.oplist - ) - ): - sf_list: List[OperatorBase] = [ - StateFn(op, is_measurement=operator.is_measurement) - for op in operator.primitive.oplist - ] - listop_of_statefns = SummedOp(oplist=sf_list, coeff=operator.coeff) - return listop_of_statefns.traverse(self.convert) - - if isinstance(operator, OperatorStateFn) and isinstance(operator.primitive, PauliSumOp): - operator = OperatorStateFn( - operator.primitive.to_pauli_op(), - coeff=operator.coeff, - is_measurement=operator.is_measurement, - ) - - if isinstance(operator, PauliSumOp): - operator = operator.to_pauli_op() - - if isinstance(operator, (Pauli, PauliOp)): - cob_instr_op, dest_pauli_op = self.get_cob_circuit(operator) - return self._replacement_fn(cob_instr_op, dest_pauli_op) - if isinstance(operator, StateFn) and "Pauli" in operator.primitive_strings(): - # If the StateFn/Meas only contains a Pauli, use it directly. - if isinstance(operator.primitive, PauliOp): - cob_instr_op, dest_pauli_op = self.get_cob_circuit(operator.primitive) - return self._replacement_fn(cob_instr_op, dest_pauli_op * operator.coeff) - # TODO make a canonical "distribute" or graph swap as method in ListOp? - elif operator.primitive.distributive: - if operator.primitive.abelian: - origin_pauli = self.get_tpb_pauli(operator.primitive) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - diag_ops: List[OperatorBase] = [ - self.get_diagonal_pauli_op(op) for op in operator.primitive.oplist - ] - dest_pauli_op = operator.primitive.__class__( - diag_ops, coeff=operator.coeff, abelian=True - ) - return self._replacement_fn(cob_instr_op, dest_pauli_op) - else: - sf_list = [ - StateFn(op, is_measurement=operator.is_measurement) - for op in operator.primitive.oplist - ] - listop_of_statefns = operator.primitive.__class__( - oplist=sf_list, coeff=operator.coeff - ) - return listop_of_statefns.traverse(self.convert) - - elif ( - isinstance(operator, ListOp) - and self._traverse - and "Pauli" in operator.primitive_strings() - ): - # If ListOp is abelian we can find a single post-rotation circuit - # for the whole set. For now, - # assume operator can only be abelian if all elements are - # Paulis (enforced in AbelianGrouper). - if operator.abelian: - origin_pauli = self.get_tpb_pauli(operator) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - oplist = cast(List[PauliOp], operator.oplist) - diag_ops = [self.get_diagonal_pauli_op(op) for op in oplist] - dest_list_op = operator.__class__(diag_ops, coeff=operator.coeff, abelian=True) - return self._replacement_fn(cob_instr_op, dest_list_op) - else: - return operator.traverse(self.convert) - - return operator - - @staticmethod - def measurement_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces measurements - isomorphic to an ``OperatorStateFn`` measurement holding the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination Pauli type operator. - - Returns: - The ``~StateFn @ CircuitOp`` composition equivalent to a measurement by the original - ``PauliOp``. - """ - return ComposedOp([StateFn(dest_pauli_op, is_measurement=True), cob_instr_op]) - - @staticmethod - def statefn_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces state functions - isomorphic to an ``OperatorStateFn`` state function holding the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination Pauli type operator. - - Returns: - The ``~CircuitOp @ StateFn`` composition equivalent to a state function defined by the - original ``PauliOp``. - """ - return ComposedOp([cob_instr_op.adjoint(), StateFn(dest_pauli_op)]) - - @staticmethod - def operator_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces Operators - isomorphic to the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination ``PauliOp``. - - Returns: - The ``~CircuitOp @ PauliOp @ CircuitOp`` composition isomorphic to the - original ``PauliOp``. - """ - return ComposedOp([cob_instr_op.adjoint(), dest_pauli_op, cob_instr_op]) - - def get_tpb_pauli(self, list_op: ListOp) -> Pauli: - r""" - Gets the Pauli (not ``PauliOp``!) whose diagonalizing single-qubit rotations is a - superset of the diagonalizing single-qubit rotations for each of the Paulis in - ``list_op``. TPB stands for `Tensor Product Basis`. - - Args: - list_op: the :class:`ListOp` whose TPB Pauli to return. - - Returns: - The TBP Pauli. - - """ - oplist = cast(List[PauliOp], list_op.oplist) - origin_z = reduce(np.logical_or, [p_op.primitive.z for p_op in oplist]) - origin_x = reduce(np.logical_or, [p_op.primitive.x for p_op in oplist]) - return Pauli((origin_z, origin_x)) - - def get_diagonal_pauli_op(self, pauli_op: PauliOp) -> PauliOp: - """Get the diagonal ``PualiOp`` to which ``pauli_op`` could be rotated with only - single-qubit operations. - - Args: - pauli_op: The ``PauliOp`` whose diagonal to compute. - - Returns: - The diagonal ``PauliOp``. - """ - return PauliOp( - Pauli( - ( - np.logical_or(pauli_op.primitive.z, pauli_op.primitive.x), - [False] * pauli_op.num_qubits, - ) - ), - coeff=pauli_op.coeff, - ) - - def get_diagonalizing_clifford(self, pauli: Union[Pauli, PauliOp]) -> OperatorBase: - r""" - Construct a ``CircuitOp`` with only single-qubit gates which takes the eigenvectors - of ``pauli`` to eigenvectors composed only of \|0⟩ and \|1⟩ tensor products. Equivalently, - finds the basis-change circuit to take ``pauli`` to a diagonal ``PauliOp`` composed only - of Z and I tensor products. - - Note, underlying Pauli bits are in Qiskit endianness, so we need to reverse before we - begin composing with Operator flow. - - Args: - pauli: the ``Pauli`` or ``PauliOp`` to whose diagonalizing circuit to compute. - - Returns: - The diagonalizing ``CircuitOp``. - - """ - if isinstance(pauli, PauliOp): - pauli = pauli.primitive - - tensorall = cast( - Callable[[List[PrimitiveOp]], PrimitiveOp], partial(reduce, lambda x, y: x.tensor(y)) - ) - - y_to_x_origin = tensorall( - [S if has_y else I for has_y in reversed(np.logical_and(pauli.x, pauli.z))] - ).adjoint() - x_to_z_origin = tensorall( # pylint: disable=assignment-from-no-return - [H if has_x else I for has_x in reversed(pauli.x)] - ) - return x_to_z_origin.compose(y_to_x_origin) - - def pad_paulis_to_equal_length( - self, pauli_op1: PauliOp, pauli_op2: PauliOp - ) -> Tuple[PauliOp, PauliOp]: - r""" - If ``pauli_op1`` and ``pauli_op2`` do not act over the same number of qubits, pad - identities to the end of the shorter of the two so they are of equal length. Padding is - applied to the end of the Paulis. Note that the Terra represents Paulis in big-endian - order, so this will appear as padding to the beginning of the Pauli x and z bit arrays. - - Args: - pauli_op1: A pauli_op to possibly pad. - pauli_op2: A pauli_op to possibly pad. - - Returns: - A tuple containing the padded PauliOps. - - """ - num_qubits = max(pauli_op1.num_qubits, pauli_op2.num_qubits) - pauli_1, pauli_2 = pauli_op1.primitive, pauli_op2.primitive - - # Padding to the end of the Pauli, but remember that Paulis are in reverse endianness. - if not len(pauli_1.z) == num_qubits: - missing_qubits = num_qubits - len(pauli_1.z) - pauli_1 = Pauli( - ( - ([False] * missing_qubits) + pauli_1.z.tolist(), - ([False] * missing_qubits) + pauli_1.x.tolist(), - ) - ) - if not len(pauli_2.z) == num_qubits: - missing_qubits = num_qubits - len(pauli_2.z) - pauli_2 = Pauli( - ( - ([False] * missing_qubits) + pauli_2.z.tolist(), - ([False] * missing_qubits) + pauli_2.x.tolist(), - ) - ) - - return PauliOp(pauli_1, coeff=pauli_op1.coeff), PauliOp(pauli_2, coeff=pauli_op2.coeff) - - def construct_cnot_chain(self, diag_pauli_op1: PauliOp, diag_pauli_op2: PauliOp) -> PrimitiveOp: - r""" - Construct a ``CircuitOp`` (or ``PauliOp`` if equal to the identity) which takes the - eigenvectors of ``diag_pauli_op1`` to the eigenvectors of ``diag_pauli_op2``, - assuming both are diagonal (or performing this operation on their diagonalized Paulis - implicitly if not). This works by the insight that the eigenvalue of a diagonal Pauli's - eigenvector is equal to or -1 if the parity is 1 and 1 if the parity is 0, or - 1 - (2 * parity). Therefore, using CNOTs, we can write the parity of diag_pauli_op1's - significant bits onto some qubit, and then write out that parity onto diag_pauli_op2's - significant bits. - - Args: - diag_pauli_op1: The origin ``PauliOp``. - diag_pauli_op2: The destination ``PauliOp``. - - Return: - The ``PrimitiveOp`` performs the mapping. - """ - # TODO be smarter about connectivity and actual distance between pauli and destination - # TODO be smarter in general - - pauli_1 = ( - diag_pauli_op1.primitive if isinstance(diag_pauli_op1, PauliOp) else diag_pauli_op1 - ) - pauli_2 = ( - diag_pauli_op2.primitive if isinstance(diag_pauli_op2, PauliOp) else diag_pauli_op2 - ) - origin_sig_bits = np.logical_or(pauli_1.z, pauli_1.x) - destination_sig_bits = np.logical_or(pauli_2.z, pauli_2.x) - num_qubits = max(len(pauli_1.z), len(pauli_2.z)) - - sig_equal_sig_bits = np.logical_and(origin_sig_bits, destination_sig_bits) - non_equal_sig_bits = np.logical_not(origin_sig_bits == destination_sig_bits) - # Equivalent to np.logical_xor(origin_sig_bits, destination_sig_bits) - - if not any(non_equal_sig_bits): - return I ^ num_qubits - - # I am deeply sorry for this code, but I don't know another way to do it. - sig_in_origin_only_indices = np.extract( - np.logical_and(non_equal_sig_bits, origin_sig_bits), np.arange(num_qubits) - ) - sig_in_dest_only_indices = np.extract( - np.logical_and(non_equal_sig_bits, destination_sig_bits), np.arange(num_qubits) - ) - - if len(sig_in_origin_only_indices) > 0 and len(sig_in_dest_only_indices) > 0: - origin_anchor_bit = min(sig_in_origin_only_indices) - dest_anchor_bit = min(sig_in_dest_only_indices) - else: - # Set to lowest equal bit - origin_anchor_bit = min(np.extract(sig_equal_sig_bits, np.arange(num_qubits))) - dest_anchor_bit = origin_anchor_bit - - cnots = QuantumCircuit(num_qubits) - # Step 3) Take the indices of bits which are sig_bits in - # pauli but but not in dest, and cnot them to the pauli anchor. - for i in sig_in_origin_only_indices: - if not i == origin_anchor_bit: - cnots.cx(i, origin_anchor_bit) - - # Step 4) - if not origin_anchor_bit == dest_anchor_bit: - cnots.swap(origin_anchor_bit, dest_anchor_bit) - - # Need to do this or a Terra bug sometimes flips cnots. No time to investigate. - cnots.id(0) - - # Step 6) - for i in sig_in_dest_only_indices: - if not i == dest_anchor_bit: - cnots.cx(i, dest_anchor_bit) - - return PrimitiveOp(cnots) - - def get_cob_circuit(self, origin: Union[Pauli, PauliOp]) -> Tuple[PrimitiveOp, PauliOp]: - r""" - Construct an Operator which maps the +1 and -1 eigenvectors - of the origin Pauli to the +1 and -1 eigenvectors of the destination Pauli. It does so by - - 1) converting any \|i+⟩ or \|i+⟩ eigenvector bits in the origin to - \|+⟩ and \|-⟩ with S†s, then - - 2) converting any \|+⟩ or \|+⟩ eigenvector bits in the converted origin to - \|0⟩ and \|1⟩ with Hs, then - - 3) writing the parity of the significant (Z-measured, rather than I) - bits in the origin to a single - "origin anchor bit," using cnots, which will hold the parity of these bits, - - 4) swapping the parity of the pauli anchor bit into a destination anchor bit using - a swap gate (only if they are different, if there are any bits which are significant - in both origin and dest, we set both anchors to one of these bits to avoid a swap). - - 5) writing the parity of the destination anchor bit into the other significant bits - of the destination, - - 6) converting the \|0⟩ and \|1⟩ significant eigenvector bits to \|+⟩ and \|-⟩ eigenvector - bits in the destination where the destination demands it - (e.g. pauli.x == true for a bit), using Hs 8) converting the \|+⟩ and \|-⟩ - significant eigenvector bits to \|i+⟩ and \|i-⟩ eigenvector bits in the - destination where the destination demands it - (e.g. pauli.x == true and pauli.z == true for a bit), using Ss - - Args: - origin: The ``Pauli`` or ``PauliOp`` to map. - - Returns: - A tuple of a ``PrimitiveOp`` which equals the basis change mapping and a ``PauliOp`` - which equals the destination basis. - - Raises: - TypeError: Attempting to convert from non-Pauli origin. - ValueError: Attempting to change a non-identity Pauli to an identity Pauli, or vice - versa. - - """ - - # If pauli is an PrimitiveOp, extract the Pauli - if isinstance(origin, Pauli): - origin = PauliOp(origin) - - if not isinstance(origin, PauliOp): - raise TypeError( - f"PauliBasisChange can only convert Pauli-based OpPrimitives, not {type(origin)}" - ) - - # If no destination specified, assume nearest Pauli in {Z,I}^n basis, - # the standard basis change for expectations. - destination = self.destination or self.get_diagonal_pauli_op(origin) - - # Pad origin or destination if either are not as long as the other - origin, destination = self.pad_paulis_to_equal_length(origin, destination) - - origin_sig_bits = np.logical_or(origin.primitive.x, origin.primitive.z) - destination_sig_bits = np.logical_or(destination.primitive.x, destination.primitive.z) - if not any(origin_sig_bits) or not any(destination_sig_bits): - if not (any(origin_sig_bits) or any(destination_sig_bits)): - # Both all Identity, just return Identities - return I ^ origin.num_qubits, destination - else: - # One is Identity, one is not - raise ValueError("Cannot change to or from a fully Identity Pauli.") - - # Steps 1 and 2 - cob_instruction = self.get_diagonalizing_clifford(origin) - - # Construct CNOT chain, assuming full connectivity... - Steps 3)-5) - cob_instruction = self.construct_cnot_chain(origin, destination).compose(cob_instruction) - - # Step 6 and 7 - dest_diagonlizing_clifford = self.get_diagonalizing_clifford(destination).adjoint() - cob_instruction = dest_diagonlizing_clifford.compose(cob_instruction) - - return cast(PrimitiveOp, cob_instruction), destination diff --git a/qiskit/opflow/converters/two_qubit_reduction.py b/qiskit/opflow/converters/two_qubit_reduction.py deleted file mode 100644 index aa34d2cef4ca..000000000000 --- a/qiskit/opflow/converters/two_qubit_reduction.py +++ /dev/null @@ -1,101 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Z2 Symmetry Tapering Converter Class""" - -import logging -from typing import List, Tuple, Union, cast - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.tapered_pauli_sum_op import Z2Symmetries -from qiskit.quantum_info import Pauli -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class TwoQubitReduction(ConverterBase): - """ - Deprecated: Two qubit reduction converter which eliminates the central and last - qubit in a list of Pauli that has diagonal operators (Z,I) at those positions. - - Chemistry specific method: - It can be used to taper two qubits in parity and binary-tree mapped - fermionic Hamiltonians when the spin orbitals are ordered in two spin - sectors, (block spin order) according to the number of particles in the system. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, num_particles: Union[int, List[int], Tuple[int, int]]): - """ - Args: - num_particles: number of particles, if it is a list, - the first number is alpha and the second number if beta. - """ - super().__init__() - if isinstance(num_particles, (tuple, list)): - num_alpha = num_particles[0] - num_beta = num_particles[1] - else: - num_alpha = num_particles // 2 - num_beta = num_particles // 2 - - par_1 = 1 if (num_alpha + num_beta) % 2 == 0 else -1 - par_2 = 1 if num_alpha % 2 == 0 else -1 - self._tapering_values = [par_2, par_1] - - def convert(self, operator: OperatorBase) -> OperatorBase: - """ - Converts the Operator to tapered one by Z2 symmetries. - - Args: - operator: the operator - Returns: - A new operator whose qubit number is reduced by 2. - """ - if not isinstance(operator, PauliSumOp): - return operator - - operator = cast(PauliSumOp, operator) - - if operator.is_zero(): - logger.info( - "Operator is empty, can not do two qubit reduction. Return the empty operator back." - ) - return PauliSumOp.from_list([("I" * (operator.num_qubits - 2), 0)]) - - num_qubits = operator.num_qubits - last_idx = num_qubits - 1 - mid_idx = num_qubits // 2 - 1 - sq_list = [mid_idx, last_idx] - - # build symmetries, sq_paulis: - symmetries, sq_paulis = [], [] - for idx in sq_list: - pauli_str = ["I"] * num_qubits - - pauli_str[idx] = "Z" - z_sym = Pauli("".join(pauli_str)[::-1]) - symmetries.append(z_sym) - - pauli_str[idx] = "X" - sq_pauli = Pauli("".join(pauli_str)[::-1]) - sq_paulis.append(sq_pauli) - - z2_symmetries = Z2Symmetries(symmetries, sq_paulis, sq_list, self._tapering_values) - return z2_symmetries.taper(operator) diff --git a/qiskit/opflow/evolutions/__init__.py b/qiskit/opflow/evolutions/__init__.py deleted file mode 100644 index 6bfeb7d1490d..000000000000 --- a/qiskit/opflow/evolutions/__init__.py +++ /dev/null @@ -1,108 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Operator Evolutions (:mod:`qiskit.opflow.evolutions`) -===================================================== - -.. currentmodule:: qiskit.opflow.evolutions - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Evolutions are converters which traverse an Operator tree, replacing -any :class:`EvolvedOp` `e` with a Schrodinger equation-style evolution -:class:`~qiskit.opflow.primitive_ops.CircuitOp` -equalling or approximating the matrix exponential of -i * the Operator contained inside -(`e.primitive`). The Evolutions are essentially implementations of Hamiltonian Simulation -algorithms, including various methods for Trotterization. - -The :class:`EvolvedOp` is simply a placeholder signifying that the Operator inside it should be -converted to its exponential by the Evolution converter. All Operators -(not :mod:`~qiskit.opflow.state_fns`) have -``.exp_i()`` methods which either return the exponential of the Operator directly, -or an :class:`EvolvedOp` containing the Operator. - - -Note: - Evolutions work with parameterized Operator coefficients, so - ``my_expectation.convert((t * H).exp_i())``, where t is a scalar or Terra Parameter and H - is an Operator, will produce a :class:`~qiskit.opflow.primitive_ops.CircuitOp` - equivalent to e^iHt. - -Evolution Base Class --------------------- - -The EvolutionBase class gives an interface for algorithms to ask for Evolutions as -execution settings. For example, if an algorithm contains an Operator evolution step within it, -such as :class:`~qiskit.algorithms.QAOA`, the algorithm can give the opportunity for the user -to pass an EvolutionBase of their choice to be used in that evolution step. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - EvolutionBase - -Evolutions ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - EvolutionFactory - EvolvedOp - MatrixEvolution - PauliTrotterEvolution - -Trotterizations ---------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - TrotterizationBase - TrotterizationFactory - Trotter - Suzuki - QDrift -""" - -from .evolution_base import EvolutionBase -from .evolution_factory import EvolutionFactory -from .evolved_op import EvolvedOp -from .pauli_trotter_evolution import PauliTrotterEvolution -from .matrix_evolution import MatrixEvolution -from .trotterizations import TrotterizationBase, TrotterizationFactory, Trotter, Suzuki, QDrift - -# TODO co-diagonalization of Abelian groups in PauliTrotterEvolution -# TODO quantum signal processing/qubitization -# TODO evolve by density matrix (need to add iexp to operator_state_fn) -# TODO linear combination evolution - -__all__ = [ - "EvolutionBase", - "EvolutionFactory", - "EvolvedOp", - "PauliTrotterEvolution", - "MatrixEvolution", - "TrotterizationBase", - "TrotterizationFactory", - "Trotter", - "Suzuki", - "QDrift", -] diff --git a/qiskit/opflow/evolutions/evolution_base.py b/qiskit/opflow/evolutions/evolution_base.py deleted file mode 100644 index 4c168078e0de..000000000000 --- a/qiskit/opflow/evolutions/evolution_base.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""EvolutionBase Class""" - -from abc import ABC, abstractmethod - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.utils.deprecation import deprecate_func - - -class EvolutionBase(ConverterBase, ABC): - r""" - Deprecated: A base for Evolution converters. - Evolutions are converters which traverse an Operator tree, replacing any ``EvolvedOp`` `e` - with a Schrodinger equation-style evolution ``CircuitOp`` equalling or approximating the - matrix exponential of -i * the Operator contained inside (`e.primitive`). The Evolutions are - essentially implementations of Hamiltonian Simulation algorithms, including various methods - for Trotterization. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Traverse the operator, replacing any ``EvolutionOps`` with their equivalent evolution - ``CircuitOps``. - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator, with ``EvolutionOps`` replaced by ``CircuitOps``. - - """ - raise NotImplementedError - - # TODO @abstractmethod - # def error_bounds(self): - # """ error bounds """ - # raise NotImplementedError diff --git a/qiskit/opflow/evolutions/evolution_factory.py b/qiskit/opflow/evolutions/evolution_factory.py deleted file mode 100644 index 1f9a64e0ad7f..000000000000 --- a/qiskit/opflow/evolutions/evolution_factory.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""EvolutionFactory Class""" - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.pauli_trotter_evolution import PauliTrotterEvolution -from qiskit.opflow.evolutions.matrix_evolution import MatrixEvolution -from qiskit.utils.deprecation import deprecate_func - - -class EvolutionFactory: - """Deprecated: A factory class for convenient automatic selection of an - Evolution algorithm based on the Operator to be converted. - """ - - @staticmethod - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def build(operator: OperatorBase = None) -> EvolutionBase: - r""" - A factory method for convenient automatic selection of an Evolution algorithm based on the - Operator to be converted. - - Args: - operator: the Operator being evolved - - Returns: - EvolutionBase: the ``EvolutionBase`` best suited to evolve operator. - - Raises: - ValueError: If operator is not of a composition for which we know the best Evolution - method. - - """ - primitive_strings = operator.primitive_strings() - if "Matrix" in primitive_strings: - return MatrixEvolution() - - elif "Pauli" in primitive_strings or "SparsePauliOp" in primitive_strings: - # TODO figure out what to do based on qubits and hamming weight. - return PauliTrotterEvolution() - - else: - raise ValueError("Evolutions of mixed Operators not yet supported.") diff --git a/qiskit/opflow/evolutions/evolved_op.py b/qiskit/opflow/evolutions/evolved_op.py deleted file mode 100644 index cf3a8ef53ae8..000000000000 --- a/qiskit/opflow/evolutions/evolved_op.py +++ /dev/null @@ -1,180 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""EvolutionOp Class""" - -from typing import List, Optional, Set, Union, cast - -import numpy as np -import scipy - -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class EvolvedOp(PrimitiveOp): - r""" - Deprecated: Class for wrapping Operator Evolutions for compilation (``convert``) by an EvolutionBase - method later, essentially acting as a placeholder. Note that EvolvedOp is a weird case of - PrimitiveOp. It happens to be that it fits into the PrimitiveOp interface nearly perfectly, - and it essentially represents a placeholder for a PrimitiveOp later, even though it doesn't - actually hold a primitive object. We could have chosen for it to be an OperatorBase, - but would have ended up copying and pasting a lot of code from PrimitiveOp.""" - primitive: PrimitiveOp - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, primitive: OperatorBase, coeff: Union[complex, ParameterExpression] = 1.0 - ) -> None: - """ - Args: - primitive: The operator being wrapped to signify evolution later. - coeff: A coefficient multiplying the operator - """ - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return self.primitive.primitive_strings() - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> Union["EvolvedOp", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, EvolvedOp) and self.primitive == other.primitive: - return EvolvedOp(self.primitive, coeff=self.coeff + other.coeff) - - if isinstance(other, SummedOp): - op_list = [cast(OperatorBase, self)] + other.oplist - return SummedOp(op_list) - - return SummedOp([self, other]) - - def adjoint(self) -> "EvolvedOp": - return EvolvedOp(self.primitive.adjoint() * -1, coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, EvolvedOp) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - - def tensor(self, other: OperatorBase) -> TensoredOp: - if isinstance(other, TensoredOp): - return TensoredOp([cast(OperatorBase, self)] + other.oplist) - - return TensoredOp([self, other]) - - def _expand_dim(self, num_qubits: int) -> TensoredOp: - # pylint: disable=cyclic-import - from ..operator_globals import I - - return self.tensor(I ^ num_qubits) - - def permute(self, permutation: List[int]) -> "EvolvedOp": - return EvolvedOp(self.primitive.permute(permutation), coeff=self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - if front: - return other.compose(new_self) - if isinstance(other, ComposedOp): - return ComposedOp([new_self] + other.oplist) - - return ComposedOp([new_self, other]) - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return f"e^(-i*{prim_str})" - else: - return f"{self.coeff} * e^(-i*{prim_str})" - - def __repr__(self) -> str: - return f"EvolvedOp({repr(self.primitive)}, coeff={self.coeff})" - - def reduce(self) -> "EvolvedOp": - return EvolvedOp(self.primitive.reduce(), coeff=self.coeff) - - def assign_parameters(self, param_dict: dict) -> Union["EvolvedOp", ListOp]: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return EvolvedOp(self.primitive.bind_parameters(param_dict), coeff=param_value) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front)) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - if ( - isinstance(self.primitive, ListOp) - and self.primitive.__class__.__name__ == ListOp.__name__ - ): - return np.array( - [ - op.exp_i().to_matrix(massive=massive) * self.primitive.coeff * self.coeff - for op in self.primitive.oplist - ], - dtype=complex, - ) - - prim_mat = -1.0j * self.primitive.to_matrix() - return scipy.linalg.expm(prim_mat) * self.coeff - - def to_matrix_op(self, massive: bool = False) -> Union[ListOp, MatrixOp]: - """Returns a ``MatrixOp`` equivalent to this Operator.""" - primitive = self.primitive - if isinstance(primitive, ListOp) and primitive.__class__.__name__ == ListOp.__name__: - return ListOp( - [op.exp_i().to_matrix_op() for op in primitive.oplist], - coeff=primitive.coeff * self.coeff, - ) - - prim_mat = EvolvedOp(primitive).to_matrix(massive=massive) - return MatrixOp(prim_mat, coeff=self.coeff) - - def log_i(self, massive: bool = False) -> OperatorBase: - return self.primitive * self.coeff - - def to_instruction(self, massive: bool = False) -> Instruction: - mat_op = self.to_matrix_op(massive=massive) - if not isinstance(mat_op, MatrixOp): - raise OpflowError("to_instruction is not allowed for ListOp.") - return mat_op.to_instruction() diff --git a/qiskit/opflow/evolutions/matrix_evolution.py b/qiskit/opflow/evolutions/matrix_evolution.py deleted file mode 100644 index 7ecdfa79ee98..000000000000 --- a/qiskit/opflow/evolutions/matrix_evolution.py +++ /dev/null @@ -1,74 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""MatrixEvolution Class""" - -import logging - -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class MatrixEvolution(EvolutionBase): - r""" - Deprecated: Performs Evolution by classical matrix exponentiation, constructing a circuit with - ``UnitaryGates`` or ``HamiltonianGates`` containing the exponentiation of the Operator. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Traverse the operator, replacing ``EvolvedOps`` with ``CircuitOps`` containing - ``UnitaryGates`` or ``HamiltonianGates`` (if self.coeff is a ``ParameterExpression``) - equalling the exponentiation of -i * operator. This is done by converting the - ``EvolvedOp.primitive`` to a ``MatrixOp`` and simply calling ``.exp_i()`` on that. - - Args: - operator: The Operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, EvolvedOp): - if not {"Matrix"} == operator.primitive_strings(): - logger.warning( - "Evolved Hamiltonian is not composed of only MatrixOps, converting " - "to Matrix representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Hamiltonian with massive=True explicitly if they so choose. - # TODO explore performance to see whether we should avoid doing this repeatedly - matrix_ham = operator.primitive.to_matrix_op(massive=False) - operator = EvolvedOp(matrix_ham, coeff=operator.coeff) - - if isinstance(operator.primitive, ListOp): - return operator.primitive.exp_i() * operator.coeff - elif isinstance(operator.primitive, (MatrixOp, PauliOp)): - return operator.primitive.exp_i() - elif isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - return operator diff --git a/qiskit/opflow/evolutions/pauli_trotter_evolution.py b/qiskit/opflow/evolutions/pauli_trotter_evolution.py deleted file mode 100644 index 7ed93d04770c..000000000000 --- a/qiskit/opflow/evolutions/pauli_trotter_evolution.py +++ /dev/null @@ -1,204 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PauliTrotterEvolution Class""" - -import logging -from typing import Optional, Union, cast - -import numpy as np - -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.synthesis import LieTrotter, SuzukiTrotter -from qiskit.opflow.converters.pauli_basis_change import PauliBasisChange -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.evolutions.trotterizations.trotterization_factory import TrotterizationFactory -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.operator_globals import I, Z -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils.deprecation import deprecate_func - -# TODO uncomment when we implement Abelian grouped evolution. -# from qiskit.opflow.converters.abelian_grouper import AbelianGrouper - -logger = logging.getLogger(__name__) - - -class PauliTrotterEvolution(EvolutionBase): - r""" - Deprecated: An Evolution algorithm replacing exponentiated sums of Paulis by changing - them each to the Z basis, rotating with an rZ, changing back, and Trotterizing. - - More specifically, we compute basis change circuits for each Pauli into a single-qubit Z, - evolve the Z by the desired evolution time with an rZ gate, and change the basis back using - the adjoint of the original basis change circuit. For sums of Paulis, the individual Pauli - evolution circuits are composed together by Trotterization scheme. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - trotter_mode: Optional[Union[str, TrotterizationBase]] = "trotter", - reps: Optional[int] = 1, - # TODO uncomment when we implement Abelian grouped evolution. - # group_paulis: Optional[bool] = False - ) -> None: - """ - Args: - trotter_mode: A string ('trotter', 'suzuki', or 'qdrift') to pass to the - TrotterizationFactory, or a TrotterizationBase, indicating how to combine - individual Pauli evolution circuits to equal the exponentiation of the Pauli sum. - reps: How many Trotterization repetitions to make, to improve the approximation - accuracy. - # TODO uncomment when we implement Abelian grouped evolution. - # group_paulis: Whether to group Pauli sums into Abelian - # sub-groups, so a single diagonalization circuit can be used for each group - # rather than each Pauli. - """ - super().__init__() - if isinstance(trotter_mode, TrotterizationBase): - self._trotter = trotter_mode - else: - self._trotter = TrotterizationFactory.build(mode=trotter_mode, reps=reps) - - # TODO uncomment when we implement Abelian grouped evolution. - # self._grouper = AbelianGrouper() if group_paulis else None - - @property - def trotter(self) -> TrotterizationBase: - """TrotterizationBase used to evolve SummedOps.""" - return self._trotter - - @trotter.setter - def trotter(self, trotter: TrotterizationBase) -> None: - """Set TrotterizationBase used to evolve SummedOps.""" - self._trotter = trotter - - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Traverse the operator, replacing ``EvolvedOps`` with ``CircuitOps`` containing - Trotterized evolutions equalling the exponentiation of -i * operator. - - Args: - operator: The Operator to convert. - - Returns: - The converted operator. - """ - # TODO uncomment when we implement Abelian grouped evolution. - # if self._grouper: - # # Sort into commuting groups - # operator = self._grouper.convert(operator).reduce() - return self._recursive_convert(operator) - - def _get_evolution_synthesis(self): - """Return the ``EvolutionSynthesis`` corresponding to this Trotterization.""" - if self.trotter.order == 1: - return LieTrotter(reps=self.trotter.reps) - return SuzukiTrotter(reps=self.trotter.reps, order=self.trotter.order) - - def _recursive_convert(self, operator: OperatorBase) -> OperatorBase: - if isinstance(operator, EvolvedOp): - if isinstance(operator.primitive, (PauliOp, PauliSumOp)): - pauli = operator.primitive.primitive - time = operator.coeff * operator.primitive.coeff - evo = PauliEvolutionGate( - pauli, time=time, synthesis=self._get_evolution_synthesis() - ) - return CircuitOp(evo) - # operator = EvolvedOp(operator.primitive.to_pauli_op(), coeff=operator.coeff) - if not {"Pauli"} == operator.primitive_strings(): - logger.warning( - "Evolved Hamiltonian is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Hamiltonian with massive=True explicitly if they so choose. - # TODO explore performance to see whether we should avoid doing this repeatedly - pauli_ham = operator.primitive.to_pauli_op(massive=False) - operator = EvolvedOp(pauli_ham, coeff=operator.coeff) - - if isinstance(operator.primitive, SummedOp): - # TODO uncomment when we implement Abelian grouped evolution. - # if operator.primitive.abelian: - # return self.evolution_for_abelian_paulisum(operator.primitive) - # else: - # Collect terms that are not the identity. - oplist = [ - x - for x in operator.primitive - if not isinstance(x, PauliOp) or sum(x.primitive.x + x.primitive.z) != 0 - ] - # Collect the coefficients of any identity terms, - # which become global phases when exponentiated. - identity_phases = [ - x.coeff - for x in operator.primitive - if isinstance(x, PauliOp) and sum(x.primitive.x + x.primitive.z) == 0 - ] - # Construct sum without the identity operators. - new_primitive = SummedOp(oplist, coeff=operator.primitive.coeff) - trotterized = self.trotter.convert(new_primitive) - circuit_no_identities = self._recursive_convert(trotterized) - # Set the global phase of the QuantumCircuit to account for removed identity terms. - global_phase = -sum(identity_phases) * operator.primitive.coeff - circuit_no_identities.primitive.global_phase = global_phase - return circuit_no_identities - # Covers ListOp, ComposedOp, TensoredOp - elif isinstance(operator.primitive, ListOp): - converted_ops = [self._recursive_convert(op) for op in operator.primitive.oplist] - return operator.primitive.__class__(converted_ops, coeff=operator.coeff) - elif isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - return operator - - def evolution_for_pauli(self, pauli_op: PauliOp) -> PrimitiveOp: - r""" - Compute evolution Operator for a single Pauli using a ``PauliBasisChange``. - - Args: - pauli_op: The ``PauliOp`` to evolve. - - Returns: - A ``PrimitiveOp``, either the evolution ``CircuitOp`` or a ``PauliOp`` equal to the - identity if pauli_op is the identity. - """ - - def replacement_fn(cob_instr_op, dest_pauli_op): - z_evolution = dest_pauli_op.exp_i() - # Remember, circuit composition order is mirrored operator composition order. - return cob_instr_op.adjoint().compose(z_evolution).compose(cob_instr_op) - - # Note: PauliBasisChange will pad destination with identities - # to produce correct CoB circuit - sig_bits = np.logical_or(pauli_op.primitive.z, pauli_op.primitive.x) - a_sig_bit = int(max(np.extract(sig_bits, np.arange(pauli_op.num_qubits)[::-1]))) - destination = (I.tensorpower(a_sig_bit)) ^ (Z * pauli_op.coeff) - cob = PauliBasisChange(destination_basis=destination, replacement_fn=replacement_fn) - return cast(PrimitiveOp, cob.convert(pauli_op)) - - # TODO implement Abelian grouped evolution. - def evolution_for_abelian_paulisum(self, op_sum: SummedOp) -> PrimitiveOp: - """Evolution for abelian pauli sum""" - raise NotImplementedError diff --git a/qiskit/opflow/evolutions/trotterizations/__init__.py b/qiskit/opflow/evolutions/trotterizations/__init__.py deleted file mode 100644 index a721bec2fee4..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Trotterization methods - Algorithms for -approximating Exponentials of Operator Sums. -""" - -from .trotterization_base import TrotterizationBase -from .trotterization_factory import TrotterizationFactory -from .trotter import Trotter -from .suzuki import Suzuki -from .qdrift import QDrift - -__all__ = ["TrotterizationBase", "TrotterizationFactory", "Trotter", "Suzuki", "QDrift"] diff --git a/qiskit/opflow/evolutions/trotterizations/qdrift.py b/qiskit/opflow/evolutions/trotterizations/qdrift.py deleted file mode 100644 index ca54d2592f4b..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/qdrift.py +++ /dev/null @@ -1,88 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -QDrift Class - -""" - -import warnings -from typing import List, Union, cast - -import numpy as np - -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - -# pylint: disable=invalid-name - - -class QDrift(TrotterizationBase): - """Deprecated: The QDrift Trotterization method, which selects each each term in the - Trotterization randomly, with a probability proportional to its weight. Based on the work - of Earl Campbell in https://arxiv.org/abs/1811.08017. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, reps: int = 1) -> None: - r""" - Args: - reps: The number of times to repeat the Trotterization circuit. - """ - super().__init__(reps=reps) - - def convert(self, operator: OperatorBase) -> OperatorBase: - if not isinstance(operator, (SummedOp, PauliSumOp)): - raise TypeError("Trotterization converters can only convert SummedOp or PauliSumOp.") - - if not isinstance(operator.coeff, (float, int)): - raise TypeError( - "Trotterization converters can only convert operators with real coefficients." - ) - - operator_iter: Union[PauliSumOp, List[PrimitiveOp]] - - if isinstance(operator, PauliSumOp): - operator_iter = operator - coeffs = operator.primitive.coeffs - coeff = operator.coeff - else: - operator_iter = cast(List[PrimitiveOp], operator.oplist) - coeffs = [op.coeff for op in operator_iter] - coeff = operator.coeff - - # We artificially make the weights positive, TODO check approximation performance - weights = np.abs(coeffs) - lambd = np.sum(weights) - - N = 2 * (lambd**2) * (coeff**2) - factor = lambd * coeff / (N * self.reps) - # The protocol calls for the removal of the individual coefficients, - # and multiplication by a constant factor. - scaled_ops = [(op * (factor / op.coeff)).exp_i() for op in operator_iter] - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - sampled_ops = algorithm_globals.random.choice( - scaled_ops, size=(int(N * self.reps),), p=weights / lambd - ) - - return ComposedOp(sampled_ops).reduce() diff --git a/qiskit/opflow/evolutions/trotterizations/suzuki.py b/qiskit/opflow/evolutions/trotterizations/suzuki.py deleted file mode 100644 index 884034f12b35..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/suzuki.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Suzuki Class""" - -from typing import List, Union, cast - -from numpy import isreal - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils.deprecation import deprecate_func - - -class Suzuki(TrotterizationBase): - r""" - Deprecated: Suzuki Trotter expansion, composing the evolution circuits of each Operator in the sum - together by a recursive "bookends" strategy, repeating the whole composed circuit - ``reps`` times. - - Detailed in https://arxiv.org/pdf/quant-ph/0508139.pdf. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, reps: int = 1, order: int = 2) -> None: - """ - Args: - reps: The number of times to repeat the expansion circuit. - order: The order of the expansion to perform. - - """ - super().__init__(reps=reps) - self._order = order - - @property - def order(self) -> int: - """returns order""" - return self._order - - @order.setter - def order(self, order: int) -> None: - """sets order""" - self._order = order - - def convert(self, operator: OperatorBase) -> OperatorBase: - if not isinstance(operator, (SummedOp, PauliSumOp)): - raise TypeError("Trotterization converters can only convert SummedOp or PauliSumOp.") - - if isinstance(operator.coeff, (float, ParameterExpression)): - coeff = operator.coeff - else: - if isreal(operator.coeff): - coeff = operator.coeff.real - else: - raise TypeError( - "Coefficient of the operator must be float or ParameterExpression, " - f"but {operator.coeff}:{type(operator.coeff)} is given." - ) - - if isinstance(operator, PauliSumOp): - comp_list = self._recursive_expansion(operator, coeff, self.order, self.reps) - if isinstance(operator, SummedOp): - comp_list = Suzuki._recursive_expansion(operator.oplist, coeff, self.order, self.reps) - - single_rep = ComposedOp(cast(List[OperatorBase], comp_list)) - full_evo = single_rep.power(self.reps) - return full_evo.reduce() - - @staticmethod - def _recursive_expansion( - op_list: Union[List[OperatorBase], PauliSumOp], - evo_time: Union[float, ParameterExpression], - expansion_order: int, - reps: int, - ) -> List[PrimitiveOp]: - """ - Compute the list of pauli terms for a single slice of the Suzuki expansion - following the paper https://arxiv.org/pdf/quant-ph/0508139.pdf. - - Args: - op_list: The slice's weighted Pauli list for the Suzuki expansion - evo_time: The parameter lambda as defined in said paper, - adjusted for the evolution time and the number of time slices - expansion_order: The order for the Suzuki expansion. - reps: The number of times to repeat the expansion circuit. - - Returns: - The evolution list after expansion. - """ - if expansion_order == 1: - # Base first-order Trotter case - return [(op * (evo_time / reps)).exp_i() for op in op_list] # type: ignore - if expansion_order == 2: - half = Suzuki._recursive_expansion(op_list, evo_time / 2, expansion_order - 1, reps) - return list(reversed(half)) + half - else: - p_k = (4 - 4 ** (1 / (2 * expansion_order - 1))) ** -1 - side = 2 * Suzuki._recursive_expansion( - op_list, evo_time * p_k, expansion_order - 2, reps - ) - middle = Suzuki._recursive_expansion( - op_list, evo_time * (1 - 4 * p_k), expansion_order - 2, reps - ) - return side + middle + side diff --git a/qiskit/opflow/evolutions/trotterizations/trotter.py b/qiskit/opflow/evolutions/trotterizations/trotter.py deleted file mode 100644 index 406598f845bb..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotter.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Trotter Class""" - -from qiskit.opflow.evolutions.trotterizations.suzuki import Suzuki -from qiskit.utils.deprecation import deprecate_func - - -class Trotter(Suzuki): - r""" - Deprecated: Simple Trotter expansion, composing the evolution circuits of each Operator in the sum - together ``reps`` times and dividing the evolution time of each by ``reps``. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, reps: int = 1) -> None: - r""" - Args: - reps: The number of times to repeat the Trotterization circuit. - """ - super().__init__(order=1, reps=reps) diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py b/qiskit/opflow/evolutions/trotterizations/trotterization_base.py deleted file mode 100644 index 9866209ecbab..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Trotterization Algorithm Base""" - -from abc import abstractmethod - -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - -# TODO centralize handling of commuting groups - - -class TrotterizationBase(EvolutionBase): - """Deprecated: A base for Trotterization methods, algorithms for approximating exponentiations of - operator sums by compositions of exponentiations. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, reps: int = 1) -> None: - super().__init__() - self._reps = reps - - @property - def reps(self) -> int: - """The number of repetitions to use in the Trotterization, improving the approximation - accuracy. - """ - return self._reps - - @reps.setter - def reps(self, reps: int) -> None: - r"""Set the number of repetitions to use in the Trotterization.""" - self._reps = reps - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Convert a ``SummedOp`` into a ``ComposedOp`` or ``CircuitOp`` representing an - approximation of e^-i*``op_sum``. - - Args: - operator: The ``SummedOp`` to evolve. - - Returns: - The Operator approximating op_sum's evolution. - - Raises: - TypeError: A non-SummedOps Operator is passed into ``convert``. - - """ - raise NotImplementedError - - # TODO @abstractmethod - trotter_error_bound diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py b/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py deleted file mode 100644 index e82ccd3b94be..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""TrotterizationFactory Class""" - -from qiskit.opflow.evolutions.trotterizations.qdrift import QDrift -from qiskit.opflow.evolutions.trotterizations.suzuki import Suzuki -from qiskit.opflow.evolutions.trotterizations.trotter import Trotter -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.utils.deprecation import deprecate_func - - -class TrotterizationFactory: - """Deprecated: A factory for conveniently creating TrotterizationBase instances.""" - - @staticmethod - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def build(mode: str = "trotter", reps: int = 1) -> TrotterizationBase: - """A factory for conveniently creating TrotterizationBase instances. - - Args: - mode: One of 'trotter', 'suzuki', 'qdrift' - reps: The number of times to repeat the Trotterization circuit. - - Returns: - The desired TrotterizationBase instance. - - Raises: - ValueError: A string not in ['trotter', 'suzuki', 'qdrift'] is given for mode. - """ - if mode == "trotter": - return Trotter(reps=reps) - - elif mode == "suzuki": - return Suzuki(reps=reps) - - elif mode == "qdrift": - return QDrift(reps=reps) - - raise ValueError(f"Trotter mode {mode} not supported") diff --git a/qiskit/opflow/exceptions.py b/qiskit/opflow/exceptions.py deleted file mode 100644 index de0b526cc79e..000000000000 --- a/qiskit/opflow/exceptions.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised by Opflow module.""" - -from qiskit.exceptions import QiskitError -from qiskit.utils.deprecation import deprecate_func - - -class OpflowError(QiskitError): - """Deprecated: For Opflow specific errors.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, *message): - """Set the error message.""" - super().__init__(*message) diff --git a/qiskit/opflow/expectations/__init__.py b/qiskit/opflow/expectations/__init__.py deleted file mode 100644 index 885d4b7e36f6..000000000000 --- a/qiskit/opflow/expectations/__init__.py +++ /dev/null @@ -1,80 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Expectations (:mod:`qiskit.opflow.expectations`) -================================================ - -.. currentmodule:: qiskit.opflow.expectations - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Expectations are converters which enable the computation of the expectation -value of an Observable with respect to some state function. They traverse an Operator tree, -replacing :class:`~qiskit.opflow.state_fns.OperatorStateFn` measurements with equivalent -measurements which are more amenable to computation on quantum or classical hardware. -For example, if one would like to measure the -expectation value of an Operator ``o`` expressed as a sum of Paulis with respect to some state -function, but only has access to diagonal measurements on Quantum hardware, we can create a -measurement ~StateFn(o), use a :class:`PauliExpectation` to convert it to a diagonal measurement -and circuit pre-rotations to append to the state, and sample this circuit on Quantum hardware with -a :class:`~qiskit.opflow.converters.CircuitSampler`. All in all, this would be: -``my_sampler.convert(my_expect.convert(~StateFn(o)) @ my_state).eval()``. - - -Expectation Base Class ----------------------- - -The ExpectationBase class gives an interface for algorithms to ask for Expectations as -execution settings. For example, if an algorithm contains an expectation value step within it, -such as :class:`~qiskit.algorithms.VQE`, the algorithm can give the opportunity for the user -to pass an ExpectationBase of their choice to be used in that expectation value step. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ExpectationBase - -Expectations ------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ExpectationFactory - AerPauliExpectation - MatrixExpectation - PauliExpectation - CVaRExpectation -""" - -from .expectation_base import ExpectationBase -from .expectation_factory import ExpectationFactory -from .pauli_expectation import PauliExpectation -from .aer_pauli_expectation import AerPauliExpectation -from .matrix_expectation import MatrixExpectation -from .cvar_expectation import CVaRExpectation - -__all__ = [ - "ExpectationBase", - "ExpectationFactory", - "PauliExpectation", - "AerPauliExpectation", - "CVaRExpectation", - "MatrixExpectation", -] diff --git a/qiskit/opflow/expectations/aer_pauli_expectation.py b/qiskit/opflow/expectations/aer_pauli_expectation.py deleted file mode 100644 index 6eaa77d3d644..000000000000 --- a/qiskit/opflow/expectations/aer_pauli_expectation.py +++ /dev/null @@ -1,163 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""AerPauliExpectation Class""" - -import logging -from functools import reduce -from operator import add -from typing import Union - -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.quantum_info import SparsePauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class AerPauliExpectation(ExpectationBase): - r"""An Expectation converter for using Aer's operator snapshot to - take expectations of quantum state circuits over Pauli observables. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the Pauli measurements replaced by - AerSnapshot-based expectation circuits. - - Args: - operator: The operator to convert. If it contains non-hermitian terms, the - operator is decomposed into hermitian and anti-hermitian parts. - - Returns: - The converted operator. - """ - - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - if isinstance(operator.primitive, ListOp): - is_herm = all((op.is_hermitian() for op in operator.primitive.oplist)) - else: - is_herm = operator.primitive.is_hermitian() - - if not is_herm: - pauli_sum_re = ( - self._replace_pauli_sums( - 1 / 2 * (operator.primitive + operator.primitive.adjoint()).reduce() - ) - * operator.coeff - ) - pauli_sum_im = ( - self._replace_pauli_sums( - 1 / 2j * (operator.primitive - operator.primitive.adjoint()).reduce() - ) - * operator.coeff - ) - pauli_sum = (pauli_sum_re + 1j * pauli_sum_im).reduce() - else: - pauli_sum = self._replace_pauli_sums(operator.primitive) * operator.coeff - return pauli_sum - elif isinstance(operator, ListOp): - return operator.traverse(self.convert) - else: - return operator - - @classmethod - def _replace_pauli_sums(cls, operator): - try: - from qiskit.providers.aer.library import SaveExpectationValue - except ImportError as ex: - raise MissingOptionalLibraryError( - libname="qiskit-aer", - name="AerPauliExpectation", - pip_install="pip install qiskit-aer", - ) from ex - # The 'expval_measurement' label on the save instruction is special - the - # CircuitSampler will look for it to know that the circuit is a Expectation - # measurement, and not simply a - # circuit to replace with a DictStateFn - if operator.__class__ == ListOp: - return operator.traverse(cls._replace_pauli_sums) - - if isinstance(operator, PauliSumOp): - save_instruction = SaveExpectationValue(operator.primitive, "expval_measurement") - return CircuitStateFn( - save_instruction, coeff=operator.coeff, is_measurement=True, from_operator=True - ) - - # Change to Pauli representation if necessary - if {"Pauli"} != operator.primitive_strings(): - logger.warning( - "Measured Observable is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Observable with massive=True explicitly if they so choose. - operator = operator.to_pauli_op(massive=False) - - if isinstance(operator, SummedOp): - sparse_pauli = reduce( - add, (meas.coeff * SparsePauliOp(meas.primitive) for meas in operator.oplist) - ) - save_instruction = SaveExpectationValue(sparse_pauli, "expval_measurement") - return CircuitStateFn( - save_instruction, coeff=operator.coeff, is_measurement=True, from_operator=True - ) - - if isinstance(operator, PauliOp): - sparse_pauli = operator.coeff * SparsePauliOp(operator.primitive) - save_instruction = SaveExpectationValue(sparse_pauli, "expval_measurement") - return CircuitStateFn(save_instruction, is_measurement=True, from_operator=True) - - raise TypeError( - f"Conversion of OperatorStateFn of {operator.__class__.__name__} is not defined." - ) - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - r""" - Compute the variance of the expectation estimator. Because Aer takes this expectation - with matrix multiplication, the estimation is exact and the variance is always 0, - but we need to return those values in a way which matches the Operator's structure. - - Args: - exp_op: The full expectation value Operator after sampling. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation, equal to 0. - """ - - # Need to do this to mimic Op structure - def sum_variance(operator): - if isinstance(operator, ComposedOp): - return 0.0 - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - raise TypeError(f"Variance cannot be computed for {operator.__class__.__name__}.") - - return sum_variance(exp_op) diff --git a/qiskit/opflow/expectations/cvar_expectation.py b/qiskit/opflow/expectations/cvar_expectation.py deleted file mode 100644 index 8de1a2897c87..000000000000 --- a/qiskit/opflow/expectations/cvar_expectation.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The CVaR (Conditional Value at Risk) expectation class.""" - -from typing import Optional, Union - -from qiskit.opflow.expectations.aer_pauli_expectation import AerPauliExpectation -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.expectations.pauli_expectation import PauliExpectation -from qiskit.opflow.list_ops import ComposedOp, ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns import CVaRMeasurement, OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class CVaRExpectation(ExpectationBase): - r"""Deprecated: Compute the Conditional Value at Risk (CVaR) expectation value. - - The standard approach to calculating the expectation value of a Hamiltonian w.r.t. a - state is to take the sample mean of the measurement outcomes. This corresponds to an estimator - of the energy. However in several problem settings with a diagonal Hamiltonian, e.g. - in combinatorial optimization where the Hamiltonian encodes a cost function, we are not - interested in calculating the energy but in the lowest possible value we can find. - - To this end, we might consider using the best observed sample as a cost function during - variational optimization. The issue here, is that this can result in a non-smooth optimization - surface. To resolve this issue, we can smooth the optimization surface by using not just the - best observed sample, but instead average over some fraction of best observed samples. - This is exactly what the CVaR estimator accomplishes [1]. - - It is empirically shown, that this can lead to faster convergence for combinatorial - optimization problems. - - Let :math:`\alpha` be a real number in :math:`[0,1]` which specifies the fraction of best - observed samples which are used to compute the objective function. Observe that if - :math:`\alpha = 1`, CVaR is equivalent to a standard expectation value. Similarly, - if :math:`\alpha = 0`, then CVaR corresponds to using the best observed sample. - Intermediate values of :math:`\alpha` interpolate between these two objective functions. - - References: - - [1]: Barkoutsos, P. K., Nannicini, G., Robert, A., Tavernelli, I., and Woerner, S., - "Improving Variational Quantum Optimization using CVaR" - `arXiv:1907.04769 `_ - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, alpha: float, expectation: Optional[ExpectationBase] = None) -> None: - """ - Args: - alpha: The alpha value describing the quantile considered in the expectation value. - expectation: An expectation object to compute the expectation value. Defaults - to the PauliExpectation calculation. - - Raises: - NotImplementedError: If the ``expectation`` is an AerPauliExpecation. - """ - super().__init__() - self.alpha = alpha - if isinstance(expectation, AerPauliExpectation): - raise NotImplementedError("AerPauliExpecation currently not supported.") - if expectation is None: - expectation = PauliExpectation() - self.expectation = expectation - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Return an expression that computes the CVaR expectation upon calling ``eval``. - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - expectation = self.expectation.convert(operator) - - # replace OperatorMeasurements by CVaRMeasurement - def replace_with_cvar(operator): - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - return CVaRMeasurement(operator.primitive, alpha=self.alpha) - elif isinstance(operator, ListOp): - return operator.traverse(replace_with_cvar) - return operator - - return replace_with_cvar(expectation) - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - """Returns the variance of the CVaR calculation - - Args: - exp_op: The operator whose evaluation yields an expectation - of some StateFn against a diagonal observable. - - Returns: - The variance of the CVaR estimate corresponding to the converted - exp_op. - Raises: - ValueError: If the exp_op does not correspond to an expectation value. - """ - - def cvar_variance(operator): - if isinstance(operator, ComposedOp): - sfdict = operator.oplist[1] - measurement = operator.oplist[0] - return measurement.eval_variance(sfdict) - - elif isinstance(operator, ListOp): - return operator.combo_fn([cvar_variance(op) for op in operator.oplist]) - - raise ValueError("Input operator does not correspond to a value expectation value.") - - cvar_op = self.convert(exp_op) - return cvar_variance(cvar_op) diff --git a/qiskit/opflow/expectations/expectation_base.py b/qiskit/opflow/expectations/expectation_base.py deleted file mode 100644 index 330e739f1439..000000000000 --- a/qiskit/opflow/expectations/expectation_base.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ExpectationBase Class""" - -from abc import abstractmethod -from typing import Union - -import numpy as np - -from qiskit.opflow.converters import ConverterBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class ExpectationBase(ConverterBase): - r""" - Deprecated: A base for Expectation value converters. Expectations are converters which enable the - computation of the expectation value of an Observable with respect to some state function. - They traverse an Operator tree, replacing OperatorStateFn measurements with equivalent - measurements which are more amenable to computation on quantum or classical hardware. For - example, if one would like to measure the expectation value of an Operator ``o`` expressed - as a sum of Paulis with respect to some state function, but only has access to diagonal - measurements on Quantum hardware, we can create a measurement ~StateFn(o), - use a ``PauliExpectation`` to convert it to a diagonal measurement and circuit - pre-rotations to a append to the state, and sample this circuit on Quantum hardware with - a CircuitSampler. All in all, this would be: - ``my_sampler.convert(my_expect.convert(~StateFn(o)) @ my_state).eval()``. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the measurements replaced by - alternate methods to compute the expectation value. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - raise NotImplementedError - - @abstractmethod - def compute_variance(self, exp_op: OperatorBase) -> Union[list, complex, np.ndarray]: - """Compute the variance of the expectation estimator. - - Args: - exp_op: The full expectation value Operator after sampling. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation. - """ - raise NotImplementedError diff --git a/qiskit/opflow/expectations/expectation_factory.py b/qiskit/opflow/expectations/expectation_factory.py deleted file mode 100644 index 68bf55143b63..000000000000 --- a/qiskit/opflow/expectations/expectation_factory.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ExpectationFactory Class""" - -import logging -from typing import Optional, Union - -from qiskit import BasicAer -from qiskit.opflow.expectations.aer_pauli_expectation import AerPauliExpectation -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.expectations.matrix_expectation import MatrixExpectation -from qiskit.opflow.expectations.pauli_expectation import PauliExpectation -from qiskit.opflow.operator_base import OperatorBase -from qiskit.providers import Backend -from qiskit.utils.backend_utils import is_aer_qasm, is_statevector_backend -from qiskit.utils import QuantumInstance, optionals -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class ExpectationFactory: - - """Deprecated: factory class for convenient automatic selection of an Expectation based on the - Operator to be converted and backend used to sample the expectation value. - """ - - @staticmethod - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def build( - operator: OperatorBase, - backend: Optional[Union[Backend, QuantumInstance]] = None, - include_custom: bool = True, - ) -> ExpectationBase: - """ - A factory method for convenient automatic selection of an Expectation based on the - Operator to be converted and backend used to sample the expectation value. - - Args: - operator: The Operator whose expectation value will be taken. - backend: The backend which will be used to sample the expectation value. - include_custom: Whether the factory will include the (Aer) specific custom - expectations if their behavior against the backend might not be as expected. - For instance when using Aer qasm_simulator with paulis the Aer snapshot can - be used but the outcome lacks shot noise and hence does not intuitively behave - overall as people might expect when choosing a qasm_simulator. It is however - fast as long as the more state vector like behavior is acceptable. - - Returns: - The expectation algorithm which best fits the Operator and backend. - - Raises: - ValueError: If operator is not of a composition for which we know the best Expectation - method. - """ - backend_to_check = backend.backend if isinstance(backend, QuantumInstance) else backend - - # pylint: disable=cyclic-import - primitives = operator.primitive_strings() - if primitives in ({"Pauli"}, {"SparsePauliOp"}): - - if backend_to_check is None: - # If user has Aer but didn't specify a backend, use the Aer fast expectation - if optionals.HAS_AER: - from qiskit_aer import AerSimulator - - backend_to_check = AerSimulator() - # If user doesn't have Aer, use statevector_simulator - # for < 16 qubits, and qasm with warning for more. - else: - if operator.num_qubits <= 16: - backend_to_check = BasicAer.get_backend("statevector_simulator") - else: - logger.warning( - "%d qubits is a very large expectation value. " - "Consider installing Aer to use " - "Aer's fast expectation, which will perform better here. We'll use " - "the BasicAer qasm backend for this expectation to avoid having to " - "construct the %dx%d operator matrix.", - operator.num_qubits, - 2**operator.num_qubits, - 2**operator.num_qubits, - ) - backend_to_check = BasicAer.get_backend("qasm_simulator") - - # If the user specified Aer qasm backend and is using a - # Pauli operator, use the Aer fast expectation if we are including such - # custom behaviors. - if is_aer_qasm(backend_to_check) and include_custom: - return AerPauliExpectation() - - # If the user specified a statevector backend (either Aer or BasicAer), - # use a converter to produce a - # Matrix operator and compute using matmul - elif is_statevector_backend(backend_to_check): - if operator.num_qubits >= 16: - logger.warning( - "Note: Using a statevector_simulator with %d qubits can be very expensive. " - "Consider using the Aer qasm_simulator instead to take advantage of Aer's " - "built-in fast Pauli Expectation", - operator.num_qubits, - ) - return MatrixExpectation() - - # All other backends, including IBMQ, BasicAer QASM, go here. - else: - return PauliExpectation() - - elif primitives == {"Matrix"}: - return MatrixExpectation() - - else: - raise ValueError("Expectations of Mixed Operators not yet supported.") diff --git a/qiskit/opflow/expectations/matrix_expectation.py b/qiskit/opflow/expectations/matrix_expectation.py deleted file mode 100644 index 53c1c8912152..000000000000 --- a/qiskit/opflow/expectations/matrix_expectation.py +++ /dev/null @@ -1,77 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""MatrixExpectation Class""" - -from typing import Union - -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops import ComposedOp, ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class MatrixExpectation(ExpectationBase): - """An Expectation converter which converts Operator measurements to - be matrix-based so they can be evaluated by matrix multiplication.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the Pauli measurements replaced by - Matrix based measurements. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - return operator.to_matrix_op() - elif isinstance(operator, ListOp): - return operator.traverse(self.convert) - else: - return operator - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - r""" - Compute the variance of the expectation estimator. Because this expectation - works by matrix multiplication, the estimation is exact and the variance is - always 0, but we need to return those values in a way which matches the Operator's - structure. - - Args: - exp_op: The full expectation value Operator. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation, equal to 0. - """ - - # Need to do this to mimic Op structure - def sum_variance(operator): - if isinstance(operator, ComposedOp): - return 0.0 - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - else: - return 0.0 - - return sum_variance(exp_op) diff --git a/qiskit/opflow/expectations/pauli_expectation.py b/qiskit/opflow/expectations/pauli_expectation.py deleted file mode 100644 index bc297afccce6..000000000000 --- a/qiskit/opflow/expectations/pauli_expectation.py +++ /dev/null @@ -1,118 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PauliExpectation Class""" - -import logging -from typing import Union - -import numpy as np - -from qiskit.opflow.converters.abelian_grouper import AbelianGrouper -from qiskit.opflow.converters.pauli_basis_change import PauliBasisChange -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class PauliExpectation(ExpectationBase): - r""" - An Expectation converter for Pauli-basis observables by changing Pauli measurements to a - diagonal ({Z, I}^n) basis and appending circuit post-rotations to the measured state function. - Optionally groups the Paulis with the same post-rotations (those that commute with one - another, or form Abelian groups) into single measurements to reduce circuit execution - overhead. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, group_paulis: bool = True) -> None: - """ - Args: - group_paulis: Whether to group the Pauli measurements into commuting sums, which all - have the same diagonalizing circuit. - - """ - super().__init__() - self._grouper = AbelianGrouper() if group_paulis else None - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accepts an Operator and returns a new Operator with the Pauli measurements replaced by - diagonal Pauli post-rotation based measurements so they can be evaluated by sampling and - averaging. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - # Change to Pauli representation if necessary - if ( - isinstance(operator.primitive, (ListOp, PrimitiveOp)) - and not isinstance(operator.primitive, PauliSumOp) - and {"Pauli", "SparsePauliOp"} < operator.primitive_strings() - ): - logger.warning( - "Measured Observable is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Observable with massive=True explicitly if they so choose. - pauli_obsv = operator.primitive.to_pauli_op(massive=False) - operator = StateFn(pauli_obsv, is_measurement=True, coeff=operator.coeff) - - if self._grouper and isinstance(operator.primitive, (ListOp, PauliSumOp)): - grouped = self._grouper.convert(operator.primitive) - operator = StateFn(grouped, is_measurement=True, coeff=operator.coeff) - - # Convert the measurement into diagonal basis (PauliBasisChange chooses - # this basis by default). - cob = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - return cob.convert(operator).reduce() - - return operator - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float, np.ndarray]: - def sum_variance(operator): - if isinstance(operator, ComposedOp): - sfdict = operator.oplist[1] - measurement = operator.oplist[0] - average = np.asarray(measurement.eval(sfdict)) - variance = sum( - (v * (np.asarray(measurement.eval(b)) - average)) ** 2 - for (b, v) in sfdict.primitive.items() - ) - return operator.coeff * variance - - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - - return 0.0 - - return sum_variance(exp_op) diff --git a/qiskit/opflow/gradients/__init__.py b/qiskit/opflow/gradients/__init__.py deleted file mode 100644 index 9c2625d078ca..000000000000 --- a/qiskit/opflow/gradients/__init__.py +++ /dev/null @@ -1,194 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -r""" -Gradients (:mod:`qiskit.opflow.gradients`) -========================================== - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Given an operator that represents either a quantum state resp. an expectation value, -the gradient framework enables the evaluation of gradients, natural gradients, -Hessians, as well as the Quantum Fisher Information. - -Suppose a parameterized quantum state `|ψ(θ)〉 = V(θ)|ψ〉` with input state `|ψ〉` and parameterized -Ansatz `V(θ)`, and an Operator `O(ω)`. - - -**Gradients** - -We want to compute one of: -* :math:`d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dω` -* :math:`d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ` -* :math:`d⟨ψ(θ)|i〉⟨i|ψ(θ)〉/ dθ` - -The last case corresponds to the gradient w.r.t. the sampling probabilities of `|ψ(θ)`. -These gradients can be computed with different methods, i.e. a parameter shift, a linear combination -of unitaries and a finite difference method. - -**Examples** - -.. code-block:: - - x = Parameter('x') - ham = x * X - a = Parameter('a') - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(params[0], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - - value_dict = {x: 0.1, a: np.pi / 4} - - ham_grad = Gradient(grad_method='param_shift').convert(operator=op, params=[x]) - ham_grad.assign_parameters(value_dict).eval() - - state_grad = Gradient(grad_method='lin_comb').convert(operator=op, params=[a]) - state_grad.assign_parameters(value_dict).eval() - - prob_grad = Gradient(grad_method='fin_diff').convert( - operator=CircuitStateFn(primitive=qc, coeff=1.), params=[a] - ) - prob_grad.assign_parameters(value_dict).eval() - -**Hessians** - -We want to compute one of: -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dω^2` -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ^2` -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ dω` -* :math:`d^2⟨ψ(θ)|i〉⟨i|ψ(θ)〉/ dθ^2` - -The last case corresponds to the Hessian w.r.t. the sampling probabilities of `|ψ(θ)〉`. -Just as the first order gradients, the Hessians can be evaluated with different methods, i.e. a -parameter shift, a linear combination of unitaries and a finite difference method. -Given a tuple of parameters ``Hessian().convert(op, param_tuple)`` returns the value for the second -order derivative. -If a list of parameters is given ``Hessian().convert(op, param_list)`` returns the full Hessian for -all the given parameters according to the given parameter order. - -**QFI** - -The Quantum Fisher Information `QFI` is a metric tensor which is representative for the -representation capacity of a parameterized quantum state `|ψ(θ)〉 = V(θ)|ψ〉` generated by an -input state `|ψ〉` and a parameterized Ansatz `V(θ)`. -The entries of the `QFI` for a pure state read -:math:`\mathrm{QFI}_{kl} = 4 \mathrm{Re}[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉]`. - -Just as for the previous derivative types, the QFI can be computed using different methods: a full -representation based on a linear combination of unitaries implementation, a block-diagonal and a -diagonal representation based on an overlap method. - -**Examples** - -.. code-block:: - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(params[0], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - - value_dict = {x: 0.1, a: np.pi / 4} - - qfi = QFI('lin_comb_full').convert( - operator=CircuitStateFn(primitive=qc, coeff=1.), params=[a] - ) - qfi.assign_parameters(value_dict).eval() - -**NaturalGradients** - -The natural gradient is a special gradient method which re-scales a gradient w.r.t. a state -parameter with the inverse of the corresponding Quantum Fisher Information (QFI) -:math:`\mathrm{QFI}^{-1} d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ`. -Hereby, we can choose a gradient as well as a QFI method and a regularization method which is used -together with a least square solver instead of exact inversion of the QFI: - -**Examples** - -.. code-block:: - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - nat_grad = NaturalGradient(grad_method='lin_comb, - qfi_method='lin_comb_full', - regularization='ridge').convert(operator=op, params=params) - -The derivative classes come with a `gradient_wrapper()` function which returns the corresponding -callable and are thus compatible with the optimizers. - -.. currentmodule:: qiskit.opflow.gradients - -Base Classes ------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - DerivativeBase - GradientBase - HessianBase - QFIBase - -Converters ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - CircuitGradient - CircuitQFI - -Derivatives ------------ - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - Gradient - Hessian - NaturalGradient - QFI - -""" - -from .circuit_gradients.circuit_gradient import CircuitGradient -from .circuit_qfis.circuit_qfi import CircuitQFI -from .derivative_base import DerivativeBase -from .gradient_base import GradientBase -from .gradient import Gradient -from .natural_gradient import NaturalGradient -from .hessian_base import HessianBase -from .hessian import Hessian -from .qfi_base import QFIBase -from .qfi import QFI - -__all__ = [ - "DerivativeBase", - "CircuitGradient", - "GradientBase", - "Gradient", - "NaturalGradient", - "HessianBase", - "Hessian", - "QFIBase", - "QFI", - "CircuitQFI", -] diff --git a/qiskit/opflow/gradients/circuit_gradients/__init__.py b/qiskit/opflow/gradients/circuit_gradients/__init__.py deleted file mode 100644 index 16953b57ff21..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -The module for first order derivatives. -""" -from .circuit_gradient import CircuitGradient -from .lin_comb import LinComb -from .param_shift import ParamShift - -__all__ = ["CircuitGradient", "LinComb", "ParamShift"] diff --git a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py b/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py deleted file mode 100644 index 52821d7ef1f9..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py +++ /dev/null @@ -1,109 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CircuitGradient Class""" - -from abc import abstractmethod -from typing import List, Union, Optional, Tuple, Set - -from qiskit import QuantumCircuit, QiskitError, transpile -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from ...converters.converter_base import ConverterBase -from ...operator_base import OperatorBase - - -class CircuitGradient(ConverterBase): - r"""Deprecated: Circuit to gradient operator converter. - - Converter for changing parameterized circuits into operators - whose evaluation yields the gradient with respect to the circuit parameters. - - This is distinct from DerivativeBase converters which take gradients of composite - operators and handle things like differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - CircuitGradient - uses quantum techniques to get derivatives of circuits - DerivativeBase - uses classical techniques to differentiate operator flow data structures - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - - Returns: - An operator whose evaluation yields the Gradient. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError - - @staticmethod - def _transpile_to_supported_operations( - circuit: QuantumCircuit, supported_gates: Set[str] - ) -> QuantumCircuit: - """Transpile the given circuit into a gate set for which the gradients may be computed. - - Args: - circuit: Quantum circuit to be transpiled into supported operations. - supported_gates: Set of quantum operations supported by a gradient method intended to - be used on the quantum circuit. - - Returns: - Quantum circuit which is transpiled into supported operations. - - Raises: - QiskitError: when circuit transpiling fails. - - """ - unique_ops = set(circuit.count_ops()) - if not unique_ops.issubset(supported_gates): - try: - circuit = transpile( - circuit, basis_gates=list(supported_gates), optimization_level=0 - ) - except Exception as exc: - raise QiskitError( - f"Could not transpile the circuit provided {circuit} into supported gates " - f"{supported_gates}." - ) from exc - return circuit diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py deleted file mode 100644 index 44394b874618..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ /dev/null @@ -1,911 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module to compute the state gradient with the linear combination method.""" - -from collections.abc import Iterable -from copy import deepcopy -from functools import partial -from itertools import product -from typing import List, Optional, Tuple, Union, Callable - -import scipy -import numpy as np - -from qiskit.circuit import Gate, Instruction -from qiskit.circuit import ( - CircuitInstruction, - QuantumCircuit, - QuantumRegister, - ParameterVector, - ParameterExpression, - Parameter, -) -from qiskit.circuit.parametertable import ParameterReferences, ParameterTable -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.library import SGate, SdgGate, XGate -from qiskit.circuit.library.standard_gates import ( - CXGate, - CYGate, - CZGate, - IGate, - RXGate, - RXXGate, - RYGate, - RYYGate, - RZGate, - RZXGate, - RZZGate, - PhaseGate, - UGate, - ZGate, -) -from qiskit.quantum_info import partial_trace -from qiskit.utils.deprecation import deprecate_func -from ...operator_base import OperatorBase -from ...list_ops.list_op import ListOp -from ...list_ops.composed_op import ComposedOp -from ...list_ops.summed_op import SummedOp -from ...operator_globals import Z, I, Y, One, Zero -from ...primitive_ops.primitive_op import PrimitiveOp -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...state_fns.dict_state_fn import DictStateFn -from ...state_fns.vector_state_fn import VectorStateFn -from ...state_fns.sparse_vector_state_fn import SparseVectorStateFn -from ...exceptions import OpflowError -from .circuit_gradient import CircuitGradient -from ...converters import PauliBasisChange - - -class LinComb(CircuitGradient): - """Deprecated: Compute the state gradient d⟨ψ(ω)|O(θ)|ψ(ω)〉/ dω respectively the gradients of the - sampling probabilities of the basis states of - a state |ψ(ω)〉w.r.t. ω. - This method employs a linear combination of unitaries, - see e.g. https://arxiv.org/pdf/1811.11184.pdf - """ - - SUPPORTED_GATES = { - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "p", - "u", - "controlledgate", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "t", - "s", - "sdg", - "x", - "y", - "z", - } - - # pylint: disable=signature-differs, arguments-differ - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, aux_meas_op: OperatorBase = Z): - """ - Args: - aux_meas_op: The operator that the auxiliary qubit is measured with respect to. - For ``aux_meas_op = Z`` we compute 2Re[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], - for ``aux_meas_op = -Y`` we compute 2Im[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], and - for ``aux_meas_op = Z - 1j * Y`` we compute 2(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉. - Raises: - ValueError: If the provided auxiliary measurement operator is not supported. - """ - super().__init__() - if aux_meas_op not in [Z, -Y, (Z - 1j * Y)]: - raise ValueError( - "This auxiliary measurement operator is currently not supported. Please choose " - "either Z, -Y, or Z - 1j * Y. " - ) - self._aux_meas_op = aux_meas_op - - def convert( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """Convert ``operator`` into an operator that represents the gradient w.r.t. ``params``. - - Args: - operator: The operator we are taking the gradient of: ⟨ψ(ω)|O(θ)|ψ(ω)〉 - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - Returns: - An operator corresponding to the gradient resp. Hessian. The order is in accordance with - the order of the given parameters. - """ - return self._prepare_operator(operator, params) - - # pylint: disable=too-many-return-statements - def _prepare_operator( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """Traverse ``operator`` to get back the adapted operator representing the gradient. - - Args: - operator: The operator we are taking the gradient of: ⟨ψ(ω)|O(θ)|ψ(ω)〉. - params: The parameters we are taking the gradient wrt: ω. - If a ``ParameterExpression```, ``ParameterVector`` or ``List[ParameterExpression]`` - is given, then the 1st order derivative of the operator is calculated. - If a ``Tuple[ParameterExpression, ParameterExpression]`` or - ``List[Tuple[ParameterExpression, ParameterExpression]]`` - is given, then the 2nd order derivative of the operator is calculated. - Returns: - The adapted operator. - Measurement operators are attached with an additional Z term acting - on an additional working qubit. - Quantum states - which must be given as circuits - are adapted. An additional - working qubit controls intercepting gates. - See e.g. [1]. - - Raises: - ValueError: If ``operator`` does not correspond to an expectation value. - TypeError: If the ``StateFn`` corresponding to the quantum state could not be extracted - from ``operator``. - OpflowError: If third or higher order gradients are requested. - - References: - [1]: Evaluating analytic gradients on quantum hardware - Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran - Phys. Rev. A 99, 032331 – Published 21 March 2019 - - """ - - if isinstance(operator, ComposedOp): - # Get the measurement and the state operator - if not isinstance(operator[0], StateFn) or not operator[0].is_measurement: - raise ValueError("The given operator does not correspond to an expectation value") - if not isinstance(operator[-1], StateFn) or operator[-1].is_measurement: - raise ValueError("The given operator does not correspond to an expectation value") - if operator[0].is_measurement: - meas = deepcopy(operator.oplist[0]) - meas = meas.primitive * meas.coeff - if len(operator.oplist) == 2: - state_op = operator[1] - if not isinstance(state_op, StateFn): - raise TypeError( - "The StateFn representing the quantum state could not be extracted." - ) - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - - return self._gradient_states( - state_op, - meas_op=(2 * meas), - target_params=params, - ) - elif isinstance(params, tuple) or ( - isinstance(params, list) - and all(isinstance(param, tuple) for param in params) - ): - return self._hessian_states( - state_op, - meas_op=(4 * (I ^ meas)), - target_params=params, - ) # type: ignore - else: - raise OpflowError( - "The linear combination gradient does only support the " - "computation of 1st gradients and 2nd order gradients." - ) - else: - state_op = deepcopy(operator) - state_op.oplist.pop(0) - if not isinstance(state_op, StateFn): - raise TypeError( - "The StateFn representing the quantum state could not be extracted." - ) - - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - return state_op.traverse( - partial( - self._gradient_states, - meas_op=(2 * meas), - target_params=params, - ) - ) - elif isinstance(params, tuple) or ( - isinstance(params, list) - and all(isinstance(param, tuple) for param in params) - ): - return state_op.traverse( - partial( - self._hessian_states, - meas_op=(4 * I ^ meas), - target_params=params, - ) - ) - - raise OpflowError( - "The linear combination gradient only supports the " - "computation of 1st and 2nd order gradients." - ) - else: - return operator.traverse(partial(self._prepare_operator, params=params)) - elif isinstance(operator, ListOp): - return operator.traverse(partial(self._prepare_operator, params=params)) - elif isinstance(operator, StateFn): - if operator.is_measurement: - return operator.traverse(partial(self._prepare_operator, params=params)) - else: - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - return self._gradient_states(operator, target_params=params) - elif isinstance(params, tuple) or ( - isinstance(params, list) and all(isinstance(param, tuple) for param in params) - ): - return self._hessian_states(operator, target_params=params) # type: ignore - else: - raise OpflowError( - "The linear combination gradient does only support the computation " - "of 1st gradients and 2nd order gradients." - ) - elif isinstance(operator, PrimitiveOp): - return operator - return operator - - @staticmethod - def _grad_combo_fn(x, state_op): - def get_result(item): - if isinstance(item, (DictStateFn, SparseVectorStateFn)): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - - if isinstance(item, dict): - prob_dict = {} - for key, val in item.items(): - prob_counts = val * np.conj(val) - if int(key[0]) == 1: - prob_counts *= -1 - suffix = key[1:] - prob_dict[suffix] = prob_dict.get(suffix, 0) + prob_counts - for key in prob_dict: - prob_dict[key] *= 2 - return prob_dict - elif isinstance(item, scipy.sparse.spmatrix): - # Generate the operator which computes the linear combination - trace = _z_exp(item) - return trace - elif isinstance(item, Iterable): - # Generate the operator which computes the linear combination - lin_comb_op = 2 * Z ^ (I ^ state_op.num_qubits) - lin_comb_op = lin_comb_op.to_matrix() - outer = np.outer(item, item.conj()) - return list( - np.diag(partial_trace(lin_comb_op.dot(outer), [state_op.num_qubits]).data) - ) - else: - raise TypeError( - "The state result should be either a DictStateFn or a VectorStateFn." - ) - - if not isinstance(x, Iterable): - return get_result(x) - elif len(x) == 1: - return get_result(x[0]) - else: - result = [] - for item in x: - result.append(get_result(item)) - return result - - @staticmethod - def _hess_combo_fn(x, state_op): - def get_result(item): - if isinstance(item, DictStateFn): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - if isinstance(item, Iterable): - # Generate the operator which computes the linear combination - lin_comb_op = 4 * (I ^ (state_op.num_qubits + 1)) ^ Z - lin_comb_op = lin_comb_op.to_matrix() - return list( - np.diag( - partial_trace(lin_comb_op.dot(np.outer(item, np.conj(item))), [0, 1]).data - ) - ) - elif isinstance(item, scipy.sparse.spmatrix): - # Generate the operator which computes the linear combination - trace = _z_exp(item) - return trace - elif isinstance(item, dict): - prob_dict = {} - for key, val in item.values(): - prob_counts = val * np.conj(val) - if int(key[-1]) == 1: - prob_counts *= -1 - prefix = key[:-2] - prob_dict[prefix] = prob_dict.get(prefix, 0) + prob_counts - for key in prob_dict: - prob_dict[key] *= 4 - return prob_dict - else: - raise TypeError( - "The state result should be either a DictStateFn or a VectorStateFn." - ) - - if not isinstance(x, Iterable): - return get_result(x) - elif len(x) == 1: - return get_result(x[0]) - else: - result = [] - for item in x: - result.append(get_result(item)) - return result - - @staticmethod - def _gate_gradient_dict(gate: Gate) -> List[Tuple[List[complex], List[Instruction]]]: - r"""Given a parameterized gate U(theta) with derivative - dU(theta)/dtheta = sum_ia_iU(theta)V_i. - This function returns a:=[a_0, ...] and V=[V_0, ...] - Suppose U takes multiple parameters, i.e., U(theta^0, ... theta^k). - The returned coefficients and gates are ordered accordingly. - Only parameterized Qiskit gates are supported. - - Args: - gate: The gate for which the derivative is being computed. - - Returns: - The coefficients and the gates used for the metric computation for each parameter of - the respective gates ``[([a^0], [V^0]) ..., ([a^k], [V^k])]``. - - Raises: - OpflowError: If the input gate is controlled by another state but '|1>^{\otimes k}' - TypeError: If the input gate is not a supported parameterized gate. - """ - - # pylint: disable=too-many-return-statements - if isinstance(gate, PhaseGate): - # theta - return [([0.5j, -0.5j], [IGate(), CZGate()])] - if isinstance(gate, UGate): - # theta, lambda, phi - return [([-0.5j], [CZGate()]), ([-0.5j], [CZGate()]), ([-0.5j], [CZGate()])] - if isinstance(gate, RXGate): - # theta - return [([-0.5j], [CXGate()])] - if isinstance(gate, RYGate): - # theta - return [([-0.5j], [CYGate()])] - if isinstance(gate, RZGate): - # theta - return [([-0.5j], [CZGate()])] - if isinstance(gate, RXXGate): - # theta - cxx_circ = QuantumCircuit(3) - cxx_circ.cx(0, 1) - cxx_circ.cx(0, 2) - cxx = cxx_circ.to_instruction() - return [([-0.5j], [cxx])] - if isinstance(gate, RYYGate): - # theta - cyy_circ = QuantumCircuit(3) - cyy_circ.cy(0, 1) - cyy_circ.cy(0, 2) - cyy = cyy_circ.to_instruction() - return [([-0.5j], [cyy])] - if isinstance(gate, RZZGate): - # theta - czz_circ = QuantumCircuit(3) - czz_circ.cz(0, 1) - czz_circ.cz(0, 2) - czz = czz_circ.to_instruction() - return [([-0.5j], [czz])] - if isinstance(gate, RZXGate): - # theta - czx_circ = QuantumCircuit(3) - czx_circ.cx(0, 2) - czx_circ.cz(0, 1) - czx = czx_circ.to_instruction() - return [([-0.5j], [czx])] - if isinstance(gate, ControlledGate): - # TODO support arbitrary control states - if gate.ctrl_state != 2**gate.num_ctrl_qubits - 1: - raise OpflowError( - "Function only support controlled gates with control state `1` on all control " - "qubits." - ) - - base_coeffs_gates = LinComb._gate_gradient_dict(gate.base_gate) - coeffs_gates = [] - # The projectors needed for the gradient of a controlled gate are integrated by a sum - # of gates. - # The following line generates the decomposition gates. - - proj_gates_controlled = [ - [(-1) ** p.count(ZGate()), p] - for p in product([IGate(), ZGate()], repeat=gate.num_ctrl_qubits) - ] - for base_coeffs, base_gates in base_coeffs_gates: # loop over parameters - coeffs = [] - gates = [] - for phase, proj_gates in proj_gates_controlled: - coeffs.extend([phase * c / (2**gate.num_ctrl_qubits) for c in base_coeffs]) - for base_gate in base_gates: - controlled_circ = QuantumCircuit(gate.num_ctrl_qubits + gate.num_qubits) - for i, proj_gate in enumerate(proj_gates): - if isinstance(proj_gate, ZGate): - controlled_circ.cz(0, i + 1) - if not isinstance(base_gate, IGate): - controlled_circ.append( - base_gate, - [ - 0, - range( - gate.num_ctrl_qubits + 1, - gate.num_ctrl_qubits + gate.num_qubits, - ), - ], - ) - gates.append(controlled_circ.to_instruction()) - c_g = (coeffs, gates) - coeffs_gates.append(c_g) - return coeffs_gates - raise TypeError(f"Unrecognized parameterized gate, {gate}") - - @staticmethod - def apply_grad_gate( - circuit, - gate, - param_index, - grad_gate, - grad_coeff, - qr_superpos, - open_ctrl=False, - trim_after_grad_gate=False, - ): - """Util function to apply a gradient gate for the linear combination of unitaries method. - Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as - superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit. - - Args: - circuit (QuantumCircuit): The circuit in which to do the replacements. - gate (Gate): The gate instance to replace. - param_index (int): The index of the parameter in ``gate``. - grad_gate (Gate): A controlled gate encoding the gradient of ``gate``. - grad_coeff (float): A coefficient to the gradient component. Might not be one if the - gradient contains multiple summed terms. - qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit`` - that is used as control for ``grad_gate``. - open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed. - trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can - be used to reduce the circuit depth in e.g. computing an overlap of gradients. - - Returns: - QuantumCircuit: A copy of the original circuit with the gradient gate added. - - Raises: - RuntimeError: If ``gate`` is not in ``circuit``. - """ - qr_superpos_qubits = tuple(qr_superpos) - # copy the input circuit taking the gates by reference - out = QuantumCircuit(*circuit.qregs) - out._data.reserve(len(circuit._data)) - out._data.extend(circuit._data) - out._parameter_table = ParameterTable( - {param: values.copy() for param, values in circuit._parameter_table.items()} - ) - - # get the data index and qubits of the target gate TODO use built-in - gate_idx, gate_qubits = None, None - for i, instruction in enumerate(out._data): - if instruction.operation is gate: - gate_idx, gate_qubits = i, instruction.qubits - break - if gate_idx is None: - raise RuntimeError("The specified gate could not be found in the circuit data.") - - # initialize replacement instructions - replacement = [] - - # insert the phase fix before the target gate better documentation - sign = np.sign(grad_coeff) - is_complex = np.iscomplex(grad_coeff) - - if sign < 0 and is_complex: - replacement.append(CircuitInstruction(SdgGate(), qr_superpos_qubits, ())) - elif sign < 0: - replacement.append(CircuitInstruction(ZGate(), qr_superpos_qubits, ())) - elif is_complex: - replacement.append(CircuitInstruction(SGate(), qr_superpos_qubits, ())) - # else no additional gate required - - # open control if specified - if open_ctrl: - replacement += [CircuitInstruction(XGate(), qr_superpos_qubits, [])] - - # compute the replacement - if isinstance(gate, UGate) and param_index == 0: - theta = gate.params[2] - rz_plus, rz_minus = RZGate(theta), RZGate(-theta) - replacement += [CircuitInstruction(rz_plus, (qubit,), ()) for qubit in gate_qubits] - replacement += [ - CircuitInstruction(RXGate(np.pi / 2), (qubit,), ()) for qubit in gate_qubits - ] - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, [])) - replacement += [ - CircuitInstruction(RXGate(-np.pi / 2), (qubit,), ()) for qubit in gate_qubits - ] - replacement += [CircuitInstruction(rz_minus, (qubit,), ()) for qubit in gate_qubits] - - # update parametertable if necessary - if isinstance(theta, ParameterExpression): - # This dangerously subverts ParameterTable by abusing the fact that binding will - # mutate the exact instruction instance, and relies on all instances of `rz_plus` - # that were added before being the same in memory, which QuantumCircuit usually - # ensures is not the case. I'm leaving this as close to its previous form as - # possible, to avoid introducing further complications, but this whole method - # accesses internal attributes of `QuantumCircuit` and needs rewriting. - # - Jake Lishman, 2022-03-02. - out._update_parameter_table(CircuitInstruction(rz_plus, (gate_qubits[0],), ())) - out._update_parameter_table(CircuitInstruction(rz_minus, (gate_qubits[0],), ())) - - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - - if not trim_after_grad_gate: - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - - elif isinstance(gate, UGate) and param_index == 1: - # gradient gate is applied after the original gate in this case - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - - else: - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - if not trim_after_grad_gate: - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - - # replace the parameter we compute the derivative of with the replacement - # TODO can this be done more efficiently? - if trim_after_grad_gate: # remove everything after the gradient gate - out._data[gate_idx:] = replacement - # reset parameter table - table = ParameterTable() - for instruction in out._data: - for idx, param_expression in enumerate(instruction.operation.params): - if isinstance(param_expression, ParameterExpression): - for param in param_expression.parameters: - if param not in table.keys(): - table[param] = ParameterReferences(((instruction.operation, idx),)) - else: - table[param].add((instruction.operation, idx)) - - out._parameter_table = table - - else: - out._data[gate_idx : gate_idx + 1] = replacement - - return out - - def _aux_meas_basis_trafo( - self, aux_meas_op: OperatorBase, state: StateFn, state_op: StateFn, combo_fn: Callable - ) -> ListOp: - """ - This function applies the necessary basis transformation to measure the quantum state in - a different basis -- given by the auxiliary measurement operator ``aux_meas_op``. - - Args: - aux_meas_op: The auxiliary measurement operator defines the necessary measurement basis. - state: This operator represents the gradient or Hessian before the basis transformation. - state_op: The operator representing the quantum state for which we compute the gradient - or Hessian. - combo_fn: This ``combo_fn`` defines whether the target is a gradient or Hessian. - - - Returns: - Operator representing the gradient or Hessian. - - Raises: - ValueError: If ``aux_meas_op`` is neither ``Z`` nor ``-Y`` nor ``Z - 1j * Y``. - - """ - if aux_meas_op == Z - 1j * Y: - state_z = ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - pbc = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - pbc = pbc.convert(-Y ^ (I ^ (state.num_qubits - 1))) - state_y = pbc[-1] @ state - state_y = ListOp( - [state_y], - combo_fn=partial(combo_fn, state_op=state_op), - ) - return state_z - 1j * state_y - - elif aux_meas_op == -Y: - pbc = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - pbc = pbc.convert(aux_meas_op ^ (I ^ (state.num_qubits - 1))) - state = pbc[-1] @ state - return -1 * ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - elif aux_meas_op == Z: - return ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - else: - raise ValueError( - f"The auxiliary measurement operator passed {aux_meas_op} is not supported. " - "Only Y, Z, or Z - 1j * Y are valid." - ) - - def _gradient_states( - self, - state_op: StateFn, - meas_op: Optional[OperatorBase] = None, - target_params: Optional[Union[Parameter, List[Parameter]]] = None, - open_ctrl: bool = False, - trim_after_grad_gate: bool = False, - ) -> ListOp: - """Generate the gradient states. - - Args: - state_op: The operator representing the quantum state for which we compute the gradient. - meas_op: The operator representing the observable for which we compute the gradient. - target_params: The parameters we are taking the gradient wrt: ω - open_ctrl: If True use an open control for ``grad_gate`` instead of closed. - trim_after_grad_gate: If True remove all gates after the ``grad_gate``. Can - be used to reduce the circuit depth in e.g. computing an overlap of gradients. - - Returns: - ListOp of StateFns as quantum circuits which are the states w.r.t. which we compute the - gradient. If a parameter appears multiple times, one circuit is created per - parameterized gates to compute the product rule. - - Raises: - QiskitError: If one of the circuits could not be constructed. - TypeError: If the operators is of unsupported type. - ValueError: If the auxiliary operator preparation fails. - """ - # unroll separately from the H gate since we need the H gate to be the first - # operation in the data attributes of the circuit - unrolled = self._transpile_to_supported_operations(state_op.primitive, self.SUPPORTED_GATES) - qr_superpos = QuantumRegister(1) - state_qc = QuantumCircuit(*state_op.primitive.qregs, qr_superpos) - state_qc.h(qr_superpos) - - state_qc.compose(unrolled, inplace=True) - - # Define the working qubit to realize the linear combination of unitaries - if not isinstance(target_params, (list, np.ndarray)): - target_params = [target_params] - - oplist = [] - for param in target_params: - if param not in state_qc.parameters: - oplist += [~Zero @ One] - else: - param_gates = state_qc._parameter_table[param] - sub_oplist = [] - for gate, idx in param_gates: - grad_coeffs, grad_gates = self._gate_gradient_dict(gate)[idx] - - # construct the states - for grad_coeff, grad_gate in zip(grad_coeffs, grad_gates): - grad_circuit = self.apply_grad_gate( - state_qc, - gate, - idx, - grad_gate, - grad_coeff, - qr_superpos, - open_ctrl, - trim_after_grad_gate, - ) - # apply final Hadamard on superposition qubit - grad_circuit.h(qr_superpos) - - # compute the correct coefficient and append to list of circuits - coeff = np.sqrt(np.abs(grad_coeff)) * state_op.coeff - state = CircuitStateFn(grad_circuit, coeff=coeff) - - # apply the chain rule if the parameter expression if required - param_expression = gate.params[idx] - - if isinstance(meas_op, OperatorBase): - state = ( - StateFn(self._aux_meas_op ^ meas_op, is_measurement=True) @ state - ) - - else: - state = self._aux_meas_basis_trafo( - self._aux_meas_op, state, state_op, self._grad_combo_fn - ) - - if param_expression != param: # parameter is not identity, apply chain rule - param_grad = param_expression.gradient(param) - state *= param_grad - - sub_oplist += [state] - - oplist += [SummedOp(sub_oplist) if len(sub_oplist) > 1 else sub_oplist[0]] - - return ListOp(oplist) if len(oplist) > 1 else oplist[0] - - def _hessian_states( - self, - state_op: StateFn, - meas_op: Optional[OperatorBase] = None, - target_params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - ) -> OperatorBase: - """Generate the operator states whose evaluation returns the Hessian (items). - - Args: - state_op: The operator representing the quantum state for which we compute the Hessian. - meas_op: The operator representing the observable for which we compute the gradient. - target_params: The parameters we are computing the Hessian wrt: ω - - Returns: - Operators which give the Hessian. If a parameter appears multiple times, one circuit is - created per parameterized gates to compute the product rule. - - Raises: - QiskitError: If one of the circuits could not be constructed. - TypeError: If ``operator`` is of unsupported type. - ValueError: If the auxiliary operator preparation fails. - """ - if not isinstance(target_params, list): - target_params = [target_params] - - if not all(isinstance(params, tuple) for params in target_params): - raise TypeError( - "Please define in the parameters for which the Hessian is evaluated " - "either as parameter tuple or a list of parameter tuples" - ) - - # create circuit with two additional qubits - qr_add0 = QuantumRegister(1, "s0") - qr_add1 = QuantumRegister(1, "s1") - state_qc = QuantumCircuit(*state_op.primitive.qregs, qr_add0, qr_add1) - - # add Hadamards - state_qc.h(qr_add0) - state_qc.h(qr_add1) - - # compose with the original circuit - state_qc.compose(state_op.primitive, inplace=True) - - # create a copy of the original circuit with an additional working qubit register - oplist = [] - for param_a, param_b in target_params: - if param_a not in state_qc.parameters or param_b not in state_qc.parameters: - oplist += [~Zero @ One] - else: - sub_oplist = [] - param_gates_a = state_qc._parameter_table[param_a] - param_gates_b = state_qc._parameter_table[param_b] - for gate_a, idx_a in param_gates_a: - grad_coeffs_a, grad_gates_a = self._gate_gradient_dict(gate_a)[idx_a] - - for grad_coeff_a, grad_gate_a in zip(grad_coeffs_a, grad_gates_a): - grad_circuit = self.apply_grad_gate( - state_qc, gate_a, idx_a, grad_gate_a, grad_coeff_a, qr_add0 - ) - - for gate_b, idx_b in param_gates_b: - grad_coeffs_b, grad_gates_b = self._gate_gradient_dict(gate_b)[idx_b] - - for grad_coeff_b, grad_gate_b in zip(grad_coeffs_b, grad_gates_b): - hessian_circuit = self.apply_grad_gate( - grad_circuit, gate_b, idx_b, grad_gate_b, grad_coeff_b, qr_add1 - ) - - # final Hadamards and CZ - hessian_circuit.h(qr_add0) - hessian_circuit.cz(qr_add1[0], qr_add0[0]) - hessian_circuit.h(qr_add1) - - coeff = state_op.coeff - coeff *= np.sqrt(np.abs(grad_coeff_a) * np.abs(grad_coeff_b)) - state = CircuitStateFn(hessian_circuit, coeff=coeff) - - if meas_op is not None: - state = ( - StateFn(self._aux_meas_op ^ meas_op, is_measurement=True) - @ state - ) - else: - state = self._aux_meas_basis_trafo( - self._aux_meas_op, state, state_op, self._hess_combo_fn - ) - - # Chain Rule Parameter Expression - param_grad = 1 - for gate, idx, param in zip( - [gate_a, gate_b], [idx_a, idx_b], [param_a, param_b] - ): - param_expression = gate.params[idx] - if param_expression != param: # need to apply chain rule - param_grad *= param_expression.gradient(param) - - if param_grad != 1: - state *= param_grad - - sub_oplist += [state] - - oplist += [SummedOp(sub_oplist) if len(sub_oplist) > 1 else sub_oplist[0]] - - return ListOp(oplist) if len(oplist) > 1 else oplist[0] - - -def _z_exp(spmatrix): - """Compute the sampling probabilities of the qubits after applying measurement on the - auxiliary qubit.""" - - dok = spmatrix.todok() - num_qubits = int(np.log2(dok.shape[1])) - exp = scipy.sparse.dok_matrix((1, 2 ** (num_qubits - 1))) - - for index, amplitude in dok.items(): - binary = bin(index[1])[2:].zfill(num_qubits) - sign = -1 if binary[0] == "1" else 1 - new_index = int(binary[1:], 2) - exp[(0, new_index)] = exp[(0, new_index)] + 2 * sign * np.abs(amplitude) ** 2 - - return exp diff --git a/qiskit/opflow/gradients/circuit_gradients/param_shift.py b/qiskit/opflow/gradients/circuit_gradients/param_shift.py deleted file mode 100644 index dcbb58052aa0..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ /dev/null @@ -1,430 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module to compute the state gradient with the parameter shift rule.""" - -from collections.abc import Iterable -from copy import deepcopy -from functools import partial -from typing import List, Union, Tuple, Dict - -import scipy -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradient import CircuitGradient -from ...operator_base import OperatorBase -from ...state_fns.state_fn import StateFn -from ...operator_globals import Zero, One -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...primitive_ops.circuit_op import CircuitOp -from ...list_ops.summed_op import SummedOp -from ...list_ops.list_op import ListOp -from ...list_ops.composed_op import ComposedOp -from ...state_fns.dict_state_fn import DictStateFn -from ...state_fns.vector_state_fn import VectorStateFn -from ...state_fns.sparse_vector_state_fn import SparseVectorStateFn -from ...exceptions import OpflowError -from ..derivative_base import _coeff_derivative - - -class ParamShift(CircuitGradient): - """Deprecated: Compute the gradient d⟨ψ(ω)|O(θ)|ψ(ω)〉/ dω respectively the gradients of the sampling - probabilities of the basis states of a state |ψ(ω)〉w.r.t. ω with the parameter shift - method. - """ - - SUPPORTED_GATES = {"x", "y", "z", "h", "rx", "ry", "rz", "p", "u", "cx", "cy", "cz"} - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, analytic: bool = True, epsilon: float = 1e-6): - r""" - Args: - analytic: If True use the parameter shift rule to compute analytic gradients, - else use a finite difference approach - epsilon: The offset size to use when computing finite difference gradients. - Ignored if analytic == True - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - self._analytic = analytic - self._epsilon = epsilon - - @property - def analytic(self) -> bool: - """Returns ``analytic`` flag. - - Returns: - ``analytic`` flag. - - """ - return self._analytic - - @property - def epsilon(self) -> float: - """Returns ``epsilon``. - - Returns: - ``epsilon``. - - """ - return self._epsilon - - # pylint: disable=signature-differs - def convert( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """ - Args: - operator: The operator corresponding to our quantum state we are taking the - gradient of: |ψ(ω)〉 - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - - Returns: - An operator corresponding to the gradient resp. Hessian. The order is in accordance with - the order of the given parameters. - - Raises: - OpflowError: If the parameters are given in an invalid format. - - """ - if isinstance(params, (ParameterExpression, ParameterVector)): - return self._parameter_shift(operator, params) - elif isinstance(params, tuple): - return self._parameter_shift(self._parameter_shift(operator, params[0]), params[1]) - elif isinstance(params, Iterable): - if all(isinstance(param, ParameterExpression) for param in params): - return self._parameter_shift(operator, params) - elif all(isinstance(param, tuple) for param in params): - return ListOp( - [ - self._parameter_shift(self._parameter_shift(operator, pair[0]), pair[1]) - for pair in params - ] - ) - else: - raise OpflowError( - "The linear combination gradient does only support " - "the computation " - "of 1st gradients and 2nd order gradients." - ) - else: - raise OpflowError( - "The linear combination gradient does only support the computation " - "of 1st gradients and 2nd order gradients." - ) - - # pylint: disable=too-many-return-statements - def _parameter_shift( - self, operator: OperatorBase, params: Union[ParameterExpression, ParameterVector, List] - ) -> OperatorBase: - r""" - Args: - operator: The operator containing circuits we are taking the derivative of. - params: The parameters (ω) we are taking the derivative with respect to. If - a ParameterVector is provided, each parameter will be shifted. - - Returns: - param_shifted_op: An operator object which evaluates to the respective gradients. - - Raises: - ValueError: If the given parameters do not occur in the provided operator - TypeError: If the operator has more than one circuit representing the quantum state - """ - if isinstance(params, (ParameterVector, list)): - param_grads = [self._parameter_shift(operator, param) for param in params] - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(absent_params) - - # By this point, it's only one parameter - param = params - - if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): - return_op = operator.traverse(partial(self._parameter_shift, params=param)) - - # Remove any branch of the tree where the relevant parameter does not occur - trimmed_oplist = [op for op in return_op.oplist if op is not None] - # If all branches are None, remove the parent too - if len(trimmed_oplist) == 0: - return None - # Rebuild the operator with the trimmed down oplist - properties = {"coeff": return_op._coeff, "abelian": return_op._abelian} - if return_op.__class__ == ListOp: - properties["combo_fn"] = return_op.combo_fn - return return_op.__class__(oplist=trimmed_oplist, **properties) - - else: - circs = self.get_unique_circuits(operator) - - if len(circs) > 1: - raise TypeError( - "Please define an operator with a single circuit representing " - "the quantum state." - ) - if len(circs) == 0: - return operator - circ = circs[0] - - if self.analytic: - # Unroll the circuit into a gate set for which the gradient may be computed - # using pi/2 shifts. - circ = ParamShift._transpile_to_supported_operations(circ, self.SUPPORTED_GATES) - operator = ParamShift._replace_operator_circuit(operator, circ) - - if param not in circ._parameter_table: - return ~Zero @ One - - shifted_ops = [] - summed_shifted_op = None - - iref_to_data_index = {id(inst.operation): idx for idx, inst in enumerate(circ.data)} - - for param_reference in circ._parameter_table[param]: - original_gate, param_index = param_reference - m = iref_to_data_index[id(original_gate)] - - pshift_op = deepcopy(operator) - mshift_op = deepcopy(operator) - - # We need the circuit objects of the newly instantiated operators - pshift_circ = self.get_unique_circuits(pshift_op)[0] - mshift_circ = self.get_unique_circuits(mshift_op)[0] - - pshift_gate = pshift_circ.data[m].operation - mshift_gate = mshift_circ.data[m].operation - - p_param = pshift_gate.params[param_index] - m_param = mshift_gate.params[param_index] - # For analytic gradients the circuit parameters are shifted once by +pi/2 and - # once by -pi/2. - if self.analytic: - shift_constant = 0.5 - pshift_gate.params[param_index] = p_param + (np.pi / (4 * shift_constant)) - mshift_gate.params[param_index] = m_param - (np.pi / (4 * shift_constant)) - # For finite difference gradients the circuit parameters are shifted once by - # +epsilon and once by -epsilon. - else: - shift_constant = 1.0 / (2 * self._epsilon) - pshift_gate.params[param_index] = p_param + self._epsilon - mshift_gate.params[param_index] = m_param - self._epsilon - # The results of the shifted operators are now evaluated according the parameter - # shift / finite difference formula. - if isinstance(operator, ComposedOp): - shifted_op = shift_constant * (pshift_op - mshift_op) - # If the operator represents a quantum state then we apply a special combo - # function to evaluate probability gradients. - elif isinstance(operator, StateFn): - shifted_op = ListOp( - [pshift_op, mshift_op], - combo_fn=partial(self._prob_combo_fn, shift_constant=shift_constant), - ) - else: - raise TypeError( - "Probability gradients are not supported for the given operator type" - ) - - if isinstance(p_param, ParameterExpression) and not isinstance(p_param, Parameter): - expr_grad = _coeff_derivative(p_param, param) - shifted_op *= expr_grad - if not summed_shifted_op: - summed_shifted_op = shifted_op - else: - summed_shifted_op += shifted_op - - shifted_ops.append(summed_shifted_op) - - if not SummedOp(shifted_ops).reduce(): - return ~StateFn(Zero) @ One - else: - return SummedOp(shifted_ops).reduce() - - @staticmethod - def _prob_combo_fn( - x: Union[ - DictStateFn, - VectorStateFn, - SparseVectorStateFn, - List[Union[DictStateFn, VectorStateFn, SparseVectorStateFn]], - ], - shift_constant: float, - ) -> Union[Dict, np.ndarray]: - """Implement the combo_fn used to evaluate probability gradients - - Args: - x: Output of an operator evaluation - shift_constant: Shifting constant factor needed for proper rescaling - - Returns: - Array representing the probability gradients w.r.t. the given operator and parameters - - Raises: - TypeError: if ``x`` is not DictStateFn, VectorStateFn or their list. - - """ - # Note: In the probability gradient case, the amplitudes still need to be converted - # into sampling probabilities. - - def get_primitives(item): - if isinstance(item, (DictStateFn, SparseVectorStateFn)): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - return item - - is_statefn = False - if isinstance(x, list): - # Check if all items in x are a StateFn items - if all(isinstance(item, StateFn) for item in x): - is_statefn = True - items = [get_primitives(item) for item in x] - else: - # Check if x is a StateFn item - if isinstance(x, StateFn): - is_statefn = True - items = [get_primitives(x)] - if isinstance(items[0], dict): - prob_dict: Dict[str, float] = {} - for i, item in enumerate(items): - for key, prob_counts in item.items(): - prob_dict[key] = ( - prob_dict.get(key, 0) + shift_constant * ((-1) ** i) * prob_counts - ) - return prob_dict - elif isinstance(items[0], scipy.sparse.spmatrix): - # If x was given as StateFn the state amplitudes need to be multiplied in order to - # evaluate the sampling probabilities which are then subtracted according to the - # parameter shift rule. - if is_statefn: - return shift_constant * np.subtract( - items[0].multiply(np.conj(items[0])), items[1].multiply(np.conj(items[1])) - ) - # If x was not given as a StateFn the state amplitudes were already converted into - # sampling probabilities which are then only subtracted according to the - # parameter shift rule. - else: - return shift_constant * np.subtract(items[0], items[1]) - elif isinstance(items[0], Iterable): - # If x was given as StateFn the state amplitudes need to be multiplied in order to - # evaluate the sampling probabilities which are then subtracted according to the - # parameter shift rule. - if is_statefn: - return shift_constant * np.subtract( - np.multiply(items[0], np.conj(items[0])), - np.multiply(items[1], np.conj(items[1])), - ) - # If x was not given as a StateFn the state amplitudes were already converted into - # sampling probabilities which are then only subtracted according to the - # parameter shift rule. - else: - return shift_constant * np.subtract(items[0], items[1]) - raise TypeError( - "Probability gradients can only be evaluated from VectorStateFs or DictStateFns." - ) - - @staticmethod - def _replace_operator_circuit(operator: OperatorBase, circuit: QuantumCircuit) -> OperatorBase: - """Replace a circuit element in an operator with a single element given as circuit - - Args: - operator: Operator for which the circuit representing the quantum state shall be - replaced - circuit: Circuit which shall replace the circuit in the given operator - - Returns: - Operator with replaced circuit quantum state function - - """ - if isinstance(operator, CircuitStateFn): - return CircuitStateFn(circuit, coeff=operator.coeff) - elif isinstance(operator, CircuitOp): - return CircuitOp(circuit, coeff=operator.coeff) - elif isinstance(operator, (ComposedOp, ListOp)): - return operator.traverse(partial(ParamShift._replace_operator_circuit, circuit=circuit)) - else: - return operator - - @classmethod - def get_unique_circuits(cls, operator: OperatorBase) -> List[QuantumCircuit]: - """Traverse the operator and return all unique circuits - - Args: - operator: An operator that potentially includes QuantumCircuits - - Returns: - A list of all unique quantum circuits that appear in the operator - - """ - if isinstance(operator, CircuitStateFn): - return [operator.primitive] - - def get_circuit(op): - return op.primitive if isinstance(op, (CircuitStateFn, CircuitOp)) else None - - unrolled_op = cls.unroll_operator(operator) - circuits = [] - for ops in unrolled_op: - if not isinstance(ops, list): - ops = [ops] - for op in ops: - if isinstance(op, (CircuitStateFn, CircuitOp, QuantumCircuit)): - c = get_circuit(op) - if c and c not in circuits: - circuits.append(c) - return circuits - - @classmethod - def unroll_operator(cls, operator: OperatorBase) -> Union[OperatorBase, List[OperatorBase]]: - """Traverse the operator and return all OperatorBase objects flattened - into a single list. This is used as a subroutine to extract all - circuits within a large composite operator. - - Args: - operator: An OperatorBase type object - - Returns: - A single flattened list of all OperatorBase objects within the - input operator - - """ - if isinstance(operator, ListOp): - return [cls.unroll_operator(op) for op in operator] - if hasattr(operator, "primitive") and isinstance(operator.primitive, ListOp): - return [operator.__class__(op) for op in operator.primitive] - return operator diff --git a/qiskit/opflow/gradients/circuit_qfis/__init__.py b/qiskit/opflow/gradients/circuit_qfis/__init__.py deleted file mode 100644 index d32126acd523..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for first order derivatives.""" - -from .circuit_qfi import CircuitQFI -from .lin_comb_full import LinCombFull -from .overlap_diag import OverlapDiag -from .overlap_block_diag import OverlapBlockDiag - -__all__ = ["CircuitQFI", "LinCombFull", "OverlapDiag", "OverlapBlockDiag"] diff --git a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py deleted file mode 100644 index 034492aca523..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ /dev/null @@ -1,66 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CircuitQFI Class""" - -from abc import abstractmethod -from typing import List, Union - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from ...converters.converter_base import ConverterBase -from ...operator_base import OperatorBase - - -class CircuitQFI(ConverterBase): - r"""Deprecated: Circuit to Quantum Fisher Information operator converter. - - Converter for changing parameterized circuits into operators - whose evaluation yields Quantum Fisher Information metric tensor - with respect to the given circuit parameters - - This is distinct from DerivativeBase converters which take gradients of composite - operators and handle things like differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - CircuitQFI - uses quantum techniques to get the QFI of circuits - DerivativeBase - uses classical techniques to differentiate opflow data structures - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> OperatorBase: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - An operator whose evaluation yields the QFI metric tensor. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError diff --git a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py deleted file mode 100644 index 91793e03ac1b..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ /dev/null @@ -1,228 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for Quantum the Fisher Information.""" - -from typing import List, Union - -import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, ParameterVector, ParameterExpression -from qiskit.utils.arithmetic import triu_to_dense -from qiskit.utils.deprecation import deprecate_func -from ...operator_base import OperatorBase -from ...list_ops.list_op import ListOp -from ...list_ops.summed_op import SummedOp -from ...operator_globals import I, Z, Y -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ..circuit_gradients.lin_comb import LinComb -from .circuit_qfi import CircuitQFI - - -class LinCombFull(CircuitQFI): - r"""Deprecated: Compute the full Quantum Fisher Information (QFI). - - Given a pure, parameterized quantum state this class uses the linear combination of unitaries - See also :class:`~qiskit.opflow.QFI`. - """ - - # pylint: disable=signature-differs, arguments-differ - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - aux_meas_op: OperatorBase = Z, - phase_fix: bool = True, - ): - """ - Args: - aux_meas_op: The operator that the auxiliary qubit is measured with respect to. - For ``aux_meas_op = Z`` we compute 4Re[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], - for ``aux_meas_op = -Y`` we compute 4Im[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], and - for ``aux_meas_op = Z - 1j * Y`` we compute 4(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉. - phase_fix: Whether or not to compute and add the additional phase fix term - Re[(dω⟨<ψ(ω)|)|ψ(ω)><ψ(ω)|(dω|ψ(ω))>]. - Raises: - ValueError: If the provided auxiliary measurement operator is not supported. - """ - super().__init__() - if aux_meas_op not in [Z, -Y, (Z - 1j * Y)]: - raise ValueError( - "This auxiliary measurement operator is currently not supported. Please choose " - "either Z, -Y, or Z - 1j * Y. " - ) - self._aux_meas_op = aux_meas_op - self._phase_fix = phase_fix - - def convert( - self, - operator: CircuitStateFn, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - TypeError: If ``operator`` is an unsupported type. - """ - # QFI & phase fix observable - qfi_observable = StateFn( - 4 * self._aux_meas_op ^ (I ^ operator.num_qubits), is_measurement=True - ) - - # Check if the given operator corresponds to a quantum state given as a circuit. - if not isinstance(operator, CircuitStateFn): - raise TypeError( - "LinCombFull is only compatible with states that are given as " - f"CircuitStateFn, not {type(operator)}" - ) - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - elif isinstance(params, ParameterVector): - params = params[:] # unroll to list - - if self._phase_fix: - # First, the operators are computed which can compensate for a potential phase-mismatch - # between target and trained state, i.e.〈ψ|∂lψ〉 - phase_fix_observable = I ^ operator.num_qubits - gradient_states = LinComb(aux_meas_op=(Z - 1j * Y))._gradient_states( - operator, - meas_op=phase_fix_observable, - target_params=params, - open_ctrl=False, - trim_after_grad_gate=True, - ) - - # pylint: disable=unidiomatic-typecheck - if type(gradient_states) == ListOp: - phase_fix_states = gradient_states.oplist - else: - phase_fix_states = [gradient_states] - - # Get 4 * Re[〈∂kψ|∂lψ] - qfi_operators = [] - # Add a working qubit - qr_work = QuantumRegister(1, "work_qubit") - state_qc = QuantumCircuit(*operator.primitive.qregs, qr_work) - state_qc.h(qr_work) - # unroll separately from the H gate since we need the H gate to be the first - # operation in the data attributes of the circuit - unrolled = LinComb._transpile_to_supported_operations( - operator.primitive, LinComb.SUPPORTED_GATES - ) - state_qc.compose(unrolled, inplace=True) - - # Get the circuits needed to compute〈∂iψ|∂jψ〉 - for i, param_i in enumerate(params): # loop over parameters - qfi_ops = [] - for j, param_j in enumerate(params[i:], i): - # Get the gates of the quantum state which are parameterized by param_i - qfi_op = [] - param_gates_i = state_qc._parameter_table[param_i] - for gate_i, idx_i in param_gates_i: - grad_coeffs_i, grad_gates_i = LinComb._gate_gradient_dict(gate_i)[idx_i] - - # get the location of gate_i, used for trimming - location_i = None - for idx, instruction in enumerate(state_qc._data): - if instruction.operation is gate_i: - location_i = idx - break - - for grad_coeff_i, grad_gate_i in zip(grad_coeffs_i, grad_gates_i): - - # Get the gates of the quantum state which are parameterized by param_j - param_gates_j = state_qc._parameter_table[param_j] - for gate_j, idx_j in param_gates_j: - grad_coeffs_j, grad_gates_j = LinComb._gate_gradient_dict(gate_j)[idx_j] - - # get the location of gate_j, used for trimming - location_j = None - for idx, instruction in enumerate(state_qc._data): - if instruction.operation is gate_j: - location_j = idx - break - - for grad_coeff_j, grad_gate_j in zip(grad_coeffs_j, grad_gates_j): - - grad_coeff_ij = np.conj(grad_coeff_i) * grad_coeff_j - qfi_circuit = LinComb.apply_grad_gate( - state_qc, - gate_i, - idx_i, - grad_gate_i, - grad_coeff_ij, - qr_work, - open_ctrl=True, - trim_after_grad_gate=(location_j < location_i), - ) - - # create a copy of the original circuit with the same registers - qfi_circuit = LinComb.apply_grad_gate( - qfi_circuit, - gate_j, - idx_j, - grad_gate_j, - 1, - qr_work, - open_ctrl=False, - trim_after_grad_gate=(location_j >= location_i), - ) - - qfi_circuit.h(qr_work) - # Convert the quantum circuit into a CircuitStateFn and add the - # coefficients i, j and the original operator coefficient - coeff = operator.coeff - coeff *= np.sqrt(np.abs(grad_coeff_i) * np.abs(grad_coeff_j)) - state = CircuitStateFn(qfi_circuit, coeff=coeff) - - param_grad = 1 - for gate, idx, param in zip( - [gate_i, gate_j], [idx_i, idx_j], [param_i, param_j] - ): - param_expression = gate.params[idx] - param_grad *= param_expression.gradient(param) - meas = param_grad * qfi_observable - - term = meas @ state - - qfi_op.append(term) - - # Compute −4 * Re(〈∂kψ|ψ〉〈ψ|∂lψ〉) - def phase_fix_combo_fn(x): - return -4 * np.real(x[0] * np.conjugate(x[1])) - - if self._phase_fix: - phase_fix_op = ListOp( - [phase_fix_states[i], phase_fix_states[j]], combo_fn=phase_fix_combo_fn - ) - # Add the phase fix quantities to the entries of the QFI - # Get 4 * Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉] - qfi_ops += [SummedOp(qfi_op) + phase_fix_op] - else: - qfi_ops += [SummedOp(qfi_op)] - - qfi_operators.append(ListOp(qfi_ops)) - - # Return estimate of the full QFI -- A QFI is by definition positive semi-definite. - return ListOp(qfi_operators, combo_fn=triu_to_dense) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py deleted file mode 100644 index 8e6e41dbd2f0..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py +++ /dev/null @@ -1,195 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for the Quantum Fisher Information.""" - -from typing import List, Union - -import numpy as np -from scipy.linalg import block_diag -from qiskit.circuit import Parameter, ParameterVector, ParameterExpression -from qiskit.utils.arithmetic import triu_to_dense -from qiskit.utils.deprecation import deprecate_func -from ...list_ops.list_op import ListOp -from ...primitive_ops.circuit_op import CircuitOp -from ...expectations.pauli_expectation import PauliExpectation -from ...operator_globals import Zero -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...exceptions import OpflowError - -from .circuit_qfi import CircuitQFI -from ..derivative_base import _coeff_derivative -from .overlap_diag import _get_generators, _partition_circuit - - -class OverlapBlockDiag(CircuitQFI): - r"""Deprecated: Compute the block-diagonal of the QFI given a pure, parameterized quantum state. - - The blocks are given by all parameterized gates in quantum circuit layer. - See also :class:`~qiskit.opflow.QFI`. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - def convert( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If ``operator`` is neither ``CircuitOp`` nor ``CircuitStateFn``. - """ - if not isinstance(operator, (CircuitOp, CircuitStateFn)): - raise NotImplementedError("operator must be a CircuitOp or CircuitStateFn") - return self._block_diag_approx(operator=operator, params=params) - - def _block_diag_approx( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If a circuit is found such that one parameter controls multiple - gates, or one gate contains multiple parameters. - OpflowError: If there are more than one parameter. - - """ - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - - circuit = operator.primitive - # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ - layers = _partition_circuit(circuit) - if layers[-1].num_parameters == 0: - layers.pop(-1) - - block_params = [list(layer.parameters) for layer in layers] - # Remove any parameters found which are not in params - block_params = [[param for param in block if param in params] for block in block_params] - - # Determine the permutation needed to ensure that the final - # operator is consistent with the ordering of the input parameters - perm = [params.index(param) for block in block_params for param in block] - - psis = [CircuitOp(layer) for layer in layers] - for i, psi in enumerate(psis): - if i == 0: - continue - psis[i] = psi @ psis[i - 1] - - # Get generators - # TODO: make this work for other types of rotations - # NOTE: This assumes that each parameter only affects one rotation. - # we need to think more about what happens if multiple rotations - # are controlled with a single parameter. - - generators = _get_generators(params, circuit) - - blocks = [] - - # Psi_i = layer_i @ layer_i-1 @ ... @ layer_0 @ Zero - for k, psi_i in enumerate(psis): - params = block_params[k] - block = np.zeros((len(params), len(params))).tolist() - - # calculate all single-operator terms - single_terms = np.zeros(len(params)).tolist() - for i, p_i in enumerate(params): - generator = generators[p_i] - psi_gen_i = ~StateFn(generator) @ psi_i @ Zero - psi_gen_i = PauliExpectation().convert(psi_gen_i) - single_terms[i] = psi_gen_i - - def get_parameter_expression(circuit, param): - if len(circuit._parameter_table[param]) > 1: - raise NotImplementedError( - "OverlapDiag does not yet support multiple " - "gates parameterized by a single parameter. For such " - "circuits use LinCombFull" - ) - gate = next(iter(circuit._parameter_table[param]))[0] - if len(gate.params) > 1: - raise OpflowError( - "OverlapDiag cannot yet support gates with more than one parameter." - ) - - param_value = gate.params[0] - return param_value - - # Calculate all double-operator terms - # and build composite operators for each matrix entry - for i, p_i in enumerate(params): - generator_i = generators[p_i] - param_expr_i = get_parameter_expression(circuit, p_i) - for j, p_j in enumerate(params[i:], i): - if i == j: - block[i][i] = ListOp([single_terms[i]], combo_fn=lambda x: 1 - x[0] ** 2) - if isinstance(param_expr_i, ParameterExpression) and not isinstance( - param_expr_i, Parameter - ): - expr_grad_i = _coeff_derivative(param_expr_i, p_i) - block[i][i] *= expr_grad_i * expr_grad_i - continue - - generator_j = generators[p_j] - generator = ~generator_j @ generator_i - param_expr_j = get_parameter_expression(circuit, p_j) - - psi_gen_ij = ~StateFn(generator) @ psi_i @ Zero - psi_gen_ij = PauliExpectation().convert(psi_gen_ij) - cross_term = ListOp([single_terms[i], single_terms[j]], combo_fn=np.prod) - block[i][j] = psi_gen_ij - cross_term - - # pylint: disable=unidiomatic-typecheck - if type(param_expr_i) == ParameterExpression: - expr_grad_i = _coeff_derivative(param_expr_i, p_i) - block[i][j] *= expr_grad_i - if type(param_expr_j) == ParameterExpression: - expr_grad_j = _coeff_derivative(param_expr_j, p_j) - block[i][j] *= expr_grad_j - - wrapped_block = ListOp( - [ListOp([block[i][j] for j in range(i, len(params))]) for i in range(len(params))], - combo_fn=triu_to_dense, - ) - blocks.append(wrapped_block) - - return ListOp(oplist=blocks, combo_fn=lambda x: np.real(block_diag(*x))[:, perm][perm, :]) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py deleted file mode 100644 index b579c1930713..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ /dev/null @@ -1,275 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for Quantum the Fisher Information.""" - -import copy -from typing import List, Union - -import numpy as np -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit.library import RZGate, RXGate, RYGate -from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.utils.deprecation import deprecate_func -from ...list_ops.list_op import ListOp -from ...primitive_ops.circuit_op import CircuitOp -from ...expectations.pauli_expectation import PauliExpectation -from ...operator_globals import I, Z, Y, X, Zero -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn - - -from .circuit_qfi import CircuitQFI -from ..derivative_base import _coeff_derivative - - -class OverlapDiag(CircuitQFI): - r"""Deprecated: Compute the diagonal of the QFI given a pure, parameterized quantum state. - - See also :class:`~qiskit.opflow.QFI`. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - def convert( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If ``operator`` is neither ``CircuitOp`` nor ``CircuitStateFn``. - - """ - - if not isinstance(operator, CircuitStateFn): - raise NotImplementedError("operator must be a CircuitStateFn") - - return self._diagonal_approx(operator=operator, params=params) - - # TODO, for some reason diagonal_approx doesn't use the same get_parameter_expression method. - # This should be fixed. - def _diagonal_approx( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List], - ) -> ListOp: - """ - Args: - operator: The operator corresponding to the quantum state |ψ(ω)〉for which we compute - the QFI - params: The parameters we are computing the QFI wrt: ω - - Returns: - ListOp where the operator at position k corresponds to QFI_k,k - - Raises: - NotImplementedError: If a circuit is found such that one parameter controls multiple - gates, or one gate contains multiple parameters. - TypeError: If a circuit is found that includes more than one parameter as they are - currently not supported for the overlap diagonal QFI method. - - """ - - if not isinstance(operator, (CircuitOp, CircuitStateFn)): - raise NotImplementedError("operator must be a CircuitOp or CircuitStateFn") - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - - circuit = operator.primitive - - # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ - layers = _partition_circuit(circuit) - if layers[-1].num_parameters == 0: - layers.pop(-1) - - psis = [CircuitOp(layer) for layer in layers] - for i, psi in enumerate(psis): - if i == 0: - continue - psis[i] = psi @ psis[i - 1] - - # TODO: make this work for other types of rotations - # NOTE: This assumes that each parameter only affects one rotation. - # we need to think more about what happens if multiple rotations - # are controlled with a single parameter. - generators = _get_generators(params, circuit) - - diag = [] - for param in params: - if len(circuit._parameter_table[param]) > 1: - raise NotImplementedError( - "OverlapDiag does not yet support multiple " - "gates parameterized by a single parameter. For such " - "circuits use LinCombFull" - ) - - gate = next(iter(circuit._parameter_table[param]))[0] - - if len(gate.params) != 1: - raise TypeError( - "OverlapDiag cannot yet support gates with more than one parameter." - ) - - param_value = gate.params[0] - generator = generators[param] - meas_op = ~StateFn(generator) - - # get appropriate psi_i - psi = [(psi) for psi in psis if param in psi.primitive.parameters][0] - - op = meas_op @ psi @ Zero - if type(param_value) == ParameterExpression: # pylint: disable=unidiomatic-typecheck - expr_grad = _coeff_derivative(param_value, param) - op *= expr_grad - rotated_op = PauliExpectation().convert(op) - diag.append(rotated_op) - - grad_op = ListOp(diag, combo_fn=lambda x: np.diag(np.real([1 - y**2 for y in x]))) - return grad_op - - -def _partition_circuit(circuit): - dag = circuit_to_dag(circuit) - dag_layers = [i["graph"] for i in dag.serial_layers()] - num_qubits = circuit.num_qubits - layers = list( - zip(dag_layers, [{x: False for x in range(0, num_qubits)} for layer in dag_layers]) - ) - - # initialize the ledger - # The ledger tracks which qubits in each layer are available to have - # gates from subsequent layers shifted backward. - # The idea being that all parameterized gates should have - # no descendants within their layer - bit_indices = {bit: index for index, bit in enumerate(circuit.qubits)} - for i, (layer, ledger) in enumerate(layers): - op_node = layer.op_nodes()[0] - is_param = op_node.op.is_parameterized() - qargs = op_node.qargs - indices = [bit_indices[qarg] for qarg in qargs] - if is_param: - for index in indices: - ledger[index] = True - - def apply_node_op(node, dag, back=True): - op = copy.copy(node.op) - qargs = copy.copy(node.qargs) - cargs = copy.copy(node.cargs) - if back: - dag.apply_operation_back(op, qargs, cargs) - else: - dag.apply_operation_front(op, qargs, cargs) - - converged = False - - for _ in range(dag.depth() + 1): - if converged: - break - - converged = True - - for i, (layer, ledger) in enumerate(layers): - if i == len(layers) - 1: - continue - - (next_layer, next_ledger) = layers[i + 1] - for next_node in next_layer.op_nodes(): - is_param = next_node.op.is_parameterized() - qargs = next_node.qargs - indices = [bit_indices[qarg] for qarg in qargs] - - # If the next_node can be moved back a layer without - # without becoming the descendant of a parameterized gate, - # then do it. - if not any(ledger[x] for x in indices): - - apply_node_op(next_node, layer) - next_layer.remove_op_node(next_node) - - if is_param: - for index in indices: - ledger[index] = True - next_ledger[index] = False - - converged = False - - # clean up empty layers left behind. - if len(next_layer.op_nodes()) == 0: - layers.pop(i + 1) - - partitioned_circs = [dag_to_circuit(layer[0]) for layer in layers] - return partitioned_circs - - -def _get_generators(params, circuit): - dag = circuit_to_dag(circuit) - layers = list(dag.serial_layers()) - - generators = {} - num_qubits = dag.num_qubits() - bit_indices = {bit: index for index, bit in enumerate(circuit.qubits)} - - for layer in layers: - instr = layer["graph"].op_nodes()[0].op - # if no gate is parameterized, skip - if not any(isinstance(param, ParameterExpression) for param in instr.params): - continue - - if len(instr.params) != 1: - raise NotImplementedError( - "The QFI diagonal approximation currently only supports " - "gates with a single free parameter." - ) - param_value = instr.params[0] - - for param in params: - if param in param_value.parameters: - - if isinstance(instr, RYGate): - generator = Y - elif isinstance(instr, RZGate): - generator = Z - elif isinstance(instr, RXGate): - generator = X - else: - raise NotImplementedError(f"Generator for gate {instr.name} not implemented.") - - # get all qubit indices in this layer where the param parameterizes - # an operation. - indices = [[bit_indices[q] for q in qreg] for qreg in layer["partition"]] - indices = [item for sublist in indices for item in sublist] - - if len(indices) > 1: - raise NotImplementedError - index = indices[0] - generator = (I ^ (index)) ^ generator ^ (I ^ (num_qubits - index - 1)) - generators[param] = generator - - return generators diff --git a/qiskit/opflow/gradients/derivative_base.py b/qiskit/opflow/gradients/derivative_base.py deleted file mode 100644 index 4ba0ffed2418..000000000000 --- a/qiskit/opflow/gradients/derivative_base.py +++ /dev/null @@ -1,247 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""DerivativeBase Class""" - -from abc import abstractmethod -from typing import Callable, Iterable, List, Optional, Tuple, Union - -import numpy as np -from qiskit.utils.deprecation import deprecate_func -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.providers import Backend -from ..converters.converter_base import ConverterBase -from ..expectations import ExpectationBase, PauliExpectation -from ..list_ops.composed_op import ComposedOp -from ..list_ops.list_op import ListOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from ..primitive_ops.primitive_op import PrimitiveOp -from ..state_fns import StateFn, OperatorStateFn - -OperatorType = Union[StateFn, PrimitiveOp, ListOp] - - -class DerivativeBase(ConverterBase): - r"""Deprecated: Base class for differentiating opflow objects. - - Converter for differentiating opflow objects and handling - things like properly differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - This is distinct from CircuitGradient converters which use quantum - techniques such as parameter shifts and linear combination of unitaries - to compute derivatives of circuits. - - CircuitGradient - uses quantum techniques to get derivatives of circuits - DerivativeBase - uses classical techniques to differentiate opflow data structures - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient, Hessian or QFI of - params: The parameters we are taking the gradient, Hessian or QFI with respect to. - - Returns: - An operator whose evaluation yields the gradient, Hessian or QFI. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError - - def gradient_wrapper( - self, - operator: OperatorBase, - bind_params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - grad_params: Optional[ - Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - backend: Optional[Union[Backend, QuantumInstance]] = None, - expectation: Optional[ExpectationBase] = None, - ) -> Callable[[Iterable], np.ndarray]: - """Get a callable function which provides the respective gradient, Hessian or QFI for given - parameter values. This callable can be used as gradient function for optimizers. - - Args: - operator: The operator for which we want to get the gradient, Hessian or QFI. - bind_params: The operator parameters to which the parameter values are assigned. - grad_params: The parameters with respect to which we are taking the gradient, Hessian - or QFI. If grad_params = None, then grad_params = bind_params - backend: The quantum backend or QuantumInstance to use to evaluate the gradient, - Hessian or QFI. - expectation: The expectation converter to be used. If none is set then - `PauliExpectation()` is used. - - Returns: - Function to compute a gradient, Hessian or QFI. The function - takes an iterable as argument which holds the parameter values. - """ - from ..converters import CircuitSampler - - if grad_params is None: - grad_params = bind_params - - grad = self.convert(operator, grad_params) - if expectation is None: - expectation = PauliExpectation() - grad = expectation.convert(grad) - - sampler = CircuitSampler(backend=backend) if backend is not None else None - - def gradient_fn(p_values): - p_values_dict = dict(zip(bind_params, p_values)) - if not backend: - converter = grad.assign_parameters(p_values_dict) - return np.real(converter.eval()) - else: - p_values_list = {k: [v] for k, v in p_values_dict.items()} - sampled = sampler.convert(grad, p_values_list) - fully_bound = sampled.bind_parameters(p_values_dict) - return np.real(fully_bound.eval()[0]) - - return gradient_fn - - @staticmethod - @deprecate_func( - since="0.18.0", - package_name="qiskit-terra", - additional_msg="Instead, use the ParameterExpression.gradient method.", - ) - def parameter_expression_grad( - param_expr: ParameterExpression, param: ParameterExpression - ) -> Union[ParameterExpression, float]: - """Get the derivative of a parameter expression w.r.t. the given parameter. - - Args: - param_expr: The Parameter Expression for which we compute the derivative - param: Parameter w.r.t. which we want to take the derivative - - Returns: - ParameterExpression representing the gradient of param_expr w.r.t. param - """ - return _coeff_derivative(param_expr, param) - - @classmethod - def _erase_operator_coeffs(cls, operator: OperatorBase) -> OperatorBase: - """This method traverses an input operator and deletes all of the coefficients - - Args: - operator: An operator type object. - - Returns: - An operator which is equal to the input operator but whose coefficients - have all been set to 1.0 - - Raises: - TypeError: If unknown operator type is reached. - """ - if isinstance(operator, PrimitiveOp): - return operator / operator.coeff - op_coeff = operator.coeff # type: ignore - return (operator / op_coeff).traverse(cls._erase_operator_coeffs) - - @classmethod - def _factor_coeffs_out_of_composed_op(cls, operator: OperatorBase) -> OperatorBase: - """Factor all coefficients of ComposedOp out into a single global coefficient. - - Part of the automatic differentiation logic inside of Gradient and Hessian - counts on the fact that no product or chain rules need to be computed between - operators or coefficients within a ComposedOp. To ensure this condition is met, - this function traverses an operator and replaces each ComposedOp with an equivalent - ComposedOp, but where all coefficients have been factored out and placed onto the - ComposedOp. Note that this cannot be done properly if an OperatorMeasurement contains - a SummedOp as it's primitive. - - Args: - operator: The operator whose coefficients are being re-organized - - Returns: - An operator equivalent to the input operator, but whose coefficients have been - reorganized - - Raises: - ValueError: If an element within a ComposedOp has a primitive of type ListOp, - then it is not possible to factor all coefficients out of the ComposedOp. - """ - if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): - return operator.traverse(cls._factor_coeffs_out_of_composed_op) - if isinstance(operator, ComposedOp): - total_coeff = operator.coeff - take_norm_of_coeffs = False - for k, op in enumerate(operator.oplist): - if take_norm_of_coeffs: - total_coeff *= op.coeff * np.conj(op.coeff) # type: ignore - else: - total_coeff *= op.coeff # type: ignore - if hasattr(op, "primitive"): - prim = op.primitive # type: ignore - if isinstance(op, StateFn) and isinstance(prim, TensoredOp): - # Check if any of the coefficients in the TensoredOp is a - # ParameterExpression - for prim_op in prim.oplist: - # If a coefficient is a ParameterExpression make sure that the - # coefficients are pulled together correctly - if isinstance(prim_op.coeff, ParameterExpression): - prim_tensored = StateFn( - prim.reduce(), is_measurement=op.is_measurement, coeff=op.coeff - ) - operator.oplist[k] = prim_tensored - return operator.traverse(cls._factor_coeffs_out_of_composed_op) - elif isinstance(prim, ListOp): - raise ValueError( - "This operator was not properly decomposed. " - "By this point, all operator measurements should " - "contain single operators, otherwise the coefficient " - "gradients will not be handled properly." - ) - if hasattr(prim, "coeff"): - if take_norm_of_coeffs: - total_coeff *= prim._coeff * np.conj(prim._coeff) - else: - total_coeff *= prim._coeff - if isinstance(op, OperatorStateFn) and op.is_measurement: - take_norm_of_coeffs = True - return cls._erase_operator_coeffs(operator).mul(total_coeff) - - else: - return operator - - -def _coeff_derivative(coeff, param): - if isinstance(coeff, ParameterExpression) and len(coeff.parameters) > 0: - return coeff.gradient(param) - return 0 diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py deleted file mode 100644 index 045d4695621e..000000000000 --- a/qiskit/opflow/gradients/gradient.py +++ /dev/null @@ -1,231 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The base interface for Opflow's gradient.""" - -from typing import Union, List, Optional -import numpy as np - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from ..expectations.pauli_expectation import PauliExpectation -from .gradient_base import GradientBase -from .derivative_base import _coeff_derivative -from ..list_ops.composed_op import ComposedOp -from ..list_ops.list_op import ListOp -from ..list_ops.summed_op import SummedOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from ..operator_globals import Zero, One -from ..state_fns.circuit_state_fn import CircuitStateFn -from ..exceptions import OpflowError - - -class Gradient(GradientBase): - """Deprecated: Convert an operator expression to the first-order gradient.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, grad_method: Union[str, CircuitGradient] = "param_shift", **kwargs): - super().__init__(grad_method=grad_method, **kwargs) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of. - params: The parameters we are taking the gradient with respect to. If not - explicitly passed, they are inferred from the operator and sorted by name. - - Returns: - An operator whose evaluation yields the Gradient. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - if isinstance(params, (ParameterVector, list)): - param_grads = [self.convert(operator, param) for param in params] - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(param_grads) - - param = params - # Preprocessing - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - return self.get_gradient(cleaned_op, param) - - # pylint: disable=too-many-return-statements - def get_gradient( - self, - operator: OperatorBase, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> OperatorBase: - """Get the gradient for the given operator w.r.t. the given parameters - - Args: - operator: Operator w.r.t. which we take the gradient. - params: Parameters w.r.t. which we compute the gradient. - - Returns: - Operator which represents the gradient w.r.t. the given params. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - OpflowError: If the coefficient of the operator could not be reduced to 1. - OpflowError: If the differentiation of a combo_fn requires JAX but the package is not - installed. - TypeError: If the operator does not include a StateFn given by a quantum circuit - Exception: Unintended code is reached - MissingOptionalLibraryError: jax not installed - """ - - def is_coeff_c(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return expr == c - return coeff == c - - def is_coeff_c_abs(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return np.abs(expr) == c - return np.abs(coeff) == c - - if isinstance(params, (ParameterVector, list)): - param_grads = [self.get_gradient(operator, param) for param in params] - # If get_gradient returns None, then the corresponding parameter was probably not - # present in the operator. This needs to be looked at more carefully as other things can - # probably trigger a return of None. - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(param_grads) - - # By now params is a single parameter - param = params - # Handle Product Rules - if not is_coeff_c(operator._coeff, 1.0) and not is_coeff_c(operator._coeff, 1.0j): - # Separate the operator from the coefficient - coeff = operator._coeff - op = operator / coeff - if np.iscomplex(coeff): - from .circuit_gradients.lin_comb import LinComb - - if isinstance(self.grad_method, LinComb): - op *= 1j - coeff /= 1j - - # Get derivative of the operator (recursively) - d_op = self.get_gradient(op, param) - # ..get derivative of the coeff - d_coeff = _coeff_derivative(coeff, param) - - grad_op = 0 - if d_op != ~Zero @ One and not is_coeff_c(coeff, 0.0): - grad_op += coeff * d_op - if op != ~Zero @ One and not is_coeff_c(d_coeff, 0.0): - grad_op += d_coeff * op - if grad_op == 0: - grad_op = ~Zero @ One - return grad_op - - # Base Case, you've hit a ComposedOp! - # Prior to execution, the composite operator was standardized and coefficients were - # collected. Any operator measurements were converted to Pauli-Z measurements and rotation - # circuits were applied. Additionally, all coefficients within ComposedOps were collected - # and moved out front. - if isinstance(operator, ComposedOp): - - # Gradient of an expectation value - if not is_coeff_c_abs(operator._coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - - # Do some checks to make sure operator is sensible - # TODO add compatibility with sum of circuit state fns - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "The gradient framework is compatible with states that are given as " - "CircuitStateFn" - ) - - return self.grad_method.convert(operator, param) - - elif isinstance(operator, CircuitStateFn): - # Gradient of an a state's sampling probabilities - if not is_coeff_c(operator._coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - return self.grad_method.convert(operator, param) - - # Handle the chain rule - elif isinstance(operator, ListOp): - grad_ops = [self.get_gradient(op, param) for op in operator.oplist] - - # pylint: disable=comparison-with-callable - if operator.combo_fn == ListOp.default_combo_fn: # If using default - return ListOp(oplist=grad_ops) - elif isinstance(operator, SummedOp): - return SummedOp(oplist=[grad for grad in grad_ops if grad != ~Zero @ One]).reduce() - elif isinstance(operator, TensoredOp): - return TensoredOp(oplist=grad_ops) - - if operator.grad_combo_fn: - grad_combo_fn = operator.grad_combo_fn - else: - _optionals.HAS_JAX.require_now("automatic differentiation") - from jax import jit, grad - - grad_combo_fn = jit(grad(operator.combo_fn, holomorphic=True)) - - def chain_rule_combo_fn(x): - result = np.dot(x[1], x[0]) - if isinstance(result, np.ndarray): - result = list(result) - return result - - return ListOp( - [ListOp(operator.oplist, combo_fn=grad_combo_fn), ListOp(grad_ops)], - combo_fn=chain_rule_combo_fn, - ) diff --git a/qiskit/opflow/gradients/gradient_base.py b/qiskit/opflow/gradients/gradient_base.py deleted file mode 100644 index 03b813dd5c09..000000000000 --- a/qiskit/opflow/gradients/gradient_base.py +++ /dev/null @@ -1,77 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The base interface for Aqua's gradient.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from .derivative_base import DerivativeBase - - -class GradientBase(DerivativeBase): - """Deprecated: Base class for first-order operator gradient. - - Convert an operator expression to the first-order gradient. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, grad_method: Union[str, CircuitGradient] = "param_shift", **kwargs): - r""" - Args: - grad_method: The method used to compute the state/probability gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - Ignored for gradients w.r.t observable parameters. - kwargs (dict): Optional parameters for a CircuitGradient - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - if isinstance(grad_method, CircuitGradient): - self._grad_method = grad_method - elif grad_method == "param_shift": - from .circuit_gradients.param_shift import ParamShift - - self._grad_method = ParamShift(analytic=True) - - elif grad_method == "fin_diff": - from .circuit_gradients.param_shift import ParamShift - - epsilon = kwargs.get("epsilon", 1e-6) - self._grad_method = ParamShift(analytic=False, epsilon=epsilon) - - elif grad_method == "lin_comb": - from .circuit_gradients.lin_comb import LinComb - - self._grad_method = LinComb() - else: - raise ValueError( - "Unrecognized input provided for `grad_method`. Please provide" - " a CircuitGradient object or one of the pre-defined string" - " arguments: {'param_shift', 'fin_diff', 'lin_comb'}. " - ) - - @property - def grad_method(self) -> CircuitGradient: - """Returns ``CircuitGradient``. - - Returns: - ``CircuitGradient``. - - """ - return self._grad_method diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py deleted file mode 100644 index 68dc44863a44..000000000000 --- a/qiskit/opflow/gradients/hessian.py +++ /dev/null @@ -1,293 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module to compute Hessians.""" - -from typing import Union, List, Tuple, Optional -import numpy as np - -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from ..operator_globals import Zero, One -from ..state_fns.circuit_state_fn import CircuitStateFn -from ..state_fns.state_fn import StateFn -from ..expectations.pauli_expectation import PauliExpectation -from ..list_ops.list_op import ListOp -from ..list_ops.composed_op import ComposedOp -from ..list_ops.summed_op import SummedOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from .gradient import Gradient -from .derivative_base import _coeff_derivative -from .hessian_base import HessianBase -from ..exceptions import OpflowError -from ...utils.arithmetic import triu_to_dense -from .circuit_gradients.circuit_gradient import CircuitGradient - - -class Hessian(HessianBase): - """Deprecated: Compute the Hessian of an expected value.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, hess_method: Union[str, CircuitGradient] = "param_shift", **kwargs): - super().__init__(hess_method=hess_method, **kwargs) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], - ParameterVector, - ] - ] = None, - ) -> OperatorBase: - """ - Args: - operator: The operator for which we compute the Hessian - params: The parameters we are computing the Hessian with respect to - Either give directly the tuples/list of tuples for which the second order - derivative is to be computed or give a list of parameters to build the - full Hessian for those parameters. If not explicitly passed, the full Hessian is - constructed. The parameters are then inferred from the operator and sorted by - name. - - Returns: - OperatorBase: An operator whose evaluation yields the Hessian - """ - - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - return self.get_hessian(cleaned_op, params) - - # pylint: disable=too-many-return-statements - def get_hessian( - self, - operator: OperatorBase, - params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], - ParameterVector, - ] - ] = None, - ) -> OperatorBase: - """Get the Hessian for the given operator w.r.t. the given parameters - - Args: - operator: Operator w.r.t. which we take the Hessian. - params: Parameters w.r.t. which we compute the Hessian. If not explicitly passed, - the full Hessian is constructed. The parameters are then inferred from the operator - and sorted by name. - - Returns: - Operator which represents the gradient w.r.t. the given params. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - OpflowError: If the coefficient of the operator could not be reduced to 1. - OpflowError: If the differentiation of a combo_fn - requires JAX but the package is not installed. - TypeError: If the operator does not include a StateFn given by a quantum circuit - TypeError: If the parameters were given in an unsupported format. - Exception: Unintended code is reached - MissingOptionalLibraryError: jax not installed - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - # if input is a tuple instead of a list, wrap it into a list - if isinstance(params, (ParameterVector, list)): - # Case: a list of parameters were given, compute the Hessian for all param pairs - if all(isinstance(param, ParameterExpression) for param in params): - return ListOp( - [ - ListOp( - [ - self.get_hessian(operator, (p_i, p_j)) - for i, p_i in enumerate(params[j:], j) - ] - ) - for j, p_j in enumerate(params) - ], - combo_fn=triu_to_dense, - ) - # Case: a list was given containing tuples of parameter pairs. - elif all(isinstance(param, tuple) for param in params): - # Compute the Hessian entries corresponding to these pairs of parameters. - return ListOp([self.get_hessian(operator, param_pair) for param_pair in params]) - - def is_coeff_c(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return expr == c - return coeff == c - - # If a gradient is requested w.r.t a single parameter, then call the - # Gradient().get_gradient method. - if isinstance(params, ParameterExpression): - return Gradient(grad_method=self._hess_method).get_gradient(operator, params) - - if (not isinstance(params, tuple)) or (not len(params) == 2): - raise TypeError("Parameters supplied in unsupported format.") - - # By this point, it's only one parameter tuple - p_0 = params[0] - p_1 = params[1] - - # Handle Product Rules - if not is_coeff_c(operator._coeff, 1.0): - # Separate the operator from the coefficient - coeff = operator._coeff - op = operator / coeff - # Get derivative of the operator (recursively) - d0_op = self.get_hessian(op, p_0) - d1_op = self.get_hessian(op, p_1) - # ..get derivative of the coeff - d0_coeff = _coeff_derivative(coeff, p_0) - d1_coeff = _coeff_derivative(coeff, p_1) - - dd_op = self.get_hessian(op, params) - dd_coeff = _coeff_derivative(d0_coeff, p_1) - - grad_op = 0 - # Avoid creating operators that will evaluate to zero - if dd_op != ~Zero @ One and not is_coeff_c(coeff, 0): - grad_op += coeff * dd_op - if d0_op != ~Zero @ One and not is_coeff_c(d1_coeff, 0): - grad_op += d1_coeff * d0_op - if d1_op != ~Zero @ One and not is_coeff_c(d0_coeff, 0): - grad_op += d0_coeff * d1_op - if not is_coeff_c(dd_coeff, 0): - grad_op += dd_coeff * op - - if grad_op == 0: - return ~Zero @ One - - return grad_op - - # Base Case, you've hit a ComposedOp! - # Prior to execution, the composite operator was standardized and coefficients were - # collected. Any operator measurements were converted to Pauli-Z measurements and rotation - # circuits were applied. Additionally, all coefficients within ComposedOps were collected - # and moved out front. - if isinstance(operator, ComposedOp): - - if not is_coeff_c(operator.coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - - # Do some checks to make sure operator is sensible - # TODO enable compatibility with sum of CircuitStateFn operators - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "The gradient framework is compatible with states that are given as " - "CircuitStateFn" - ) - - return self.hess_method.convert(operator, params) - - # This is the recursive case where the chain rule is handled - elif isinstance(operator, ListOp): - # These operators correspond to (d_op/d θ0,θ1) for op in operator.oplist - # and params = (θ0,θ1) - dd_ops = [self.get_hessian(op, params) for op in operator.oplist] - - # TODO Note that this check to see if the ListOp has a default combo_fn - # will fail if the user manually specifies the default combo_fn. - # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and - # later on jax will try to differentiate it and fail. - # An alternative is to check the byte code of the operator's combo_fn against the - # default one. - # This will work but look very ugly and may have other downsides I'm not aware of - if operator.combo_fn == ListOp([]).combo_fn: - return ListOp(oplist=dd_ops) - elif isinstance(operator, SummedOp): - return SummedOp(oplist=dd_ops) - elif isinstance(operator, TensoredOp): - return TensoredOp(oplist=dd_ops) - - # These operators correspond to (d g_i/d θ0)•(d g_i/d θ1) for op in operator.oplist - # and params = (θ0,θ1) - d1d0_ops = ListOp( - [ - ListOp( - [ - Gradient(grad_method=self._hess_method).convert(op, param) - for param in params - ], - combo_fn=np.prod, - ) - for op in operator.oplist - ] - ) - - _optionals.HAS_JAX.require_now("automatic differentiation") - from jax import grad, jit - - if operator.grad_combo_fn: - first_partial_combo_fn = operator.grad_combo_fn - - second_partial_combo_fn = jit( - grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True) - ) - else: - first_partial_combo_fn = jit(grad(operator.combo_fn, holomorphic=True)) - second_partial_combo_fn = jit( - grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True) - ) - - # For a general combo_fn F(g_0, g_1, ..., g_k) - # dF/d θ0,θ1 = sum_i: (∂F/∂g_i)•(d g_i/ d θ0,θ1) + (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d - # θ1) - - # term1 = (∂F/∂g_i)•(d g_i/ d θ0,θ1) - term1 = ListOp( - [ListOp(operator.oplist, combo_fn=first_partial_combo_fn), ListOp(dd_ops)], - combo_fn=lambda x: np.dot(x[1], x[0]), - ) - # term2 = (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d θ1) - term2 = ListOp( - [ListOp(operator.oplist, combo_fn=second_partial_combo_fn), d1d0_ops], - combo_fn=lambda x: np.dot(x[1], x[0]), - ) - - return SummedOp([term1, term2]) - - elif isinstance(operator, StateFn): - if not operator.is_measurement: - return self.hess_method.convert(operator, params) - - else: - raise TypeError( - "The computation of Hessians is only supported for Operators which " - "represent expectation values or quantum states." - ) - - else: - raise TypeError( - "The computation of Hessians is only supported for Operators which " - "represent expectation values." - ) diff --git a/qiskit/opflow/gradients/hessian_base.py b/qiskit/opflow/gradients/hessian_base.py deleted file mode 100644 index 77f81ca1d6e0..000000000000 --- a/qiskit/opflow/gradients/hessian_base.py +++ /dev/null @@ -1,75 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module to compute Hessians.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from .derivative_base import DerivativeBase - - -class HessianBase(DerivativeBase): - """Deprecated: Base class for the Hessian of an expected value.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, hess_method: Union[str, CircuitGradient] = "param_shift", **kwargs): - r""" - Args: - hess_method: The method used to compute the state/probability gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - Ignored for gradients w.r.t observable parameters. - kwargs (dict): Optional parameters for a CircuitGradient - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - if isinstance(hess_method, CircuitGradient): - self._hess_method = hess_method - elif hess_method == "param_shift": - from .circuit_gradients import ParamShift - - self._hess_method = ParamShift() - - elif hess_method == "fin_diff": - from .circuit_gradients import ParamShift - - epsilon = kwargs.get("epsilon", 1e-6) - self._hess_method = ParamShift(analytic=False, epsilon=epsilon) - - elif hess_method == "lin_comb": - from .circuit_gradients import LinComb - - self._hess_method = LinComb() - - else: - raise ValueError( - "Unrecognized input provided for `hess_method`. Please provide" - " a CircuitGradient object or one of the pre-defined string" - " arguments: {'param_shift', 'fin_diff', 'lin_comb'}. " - ) - - @property - def hess_method(self) -> CircuitGradient: - """Returns ``CircuitGradient``. - - Returns: - ``CircuitGradient``. - - """ - return self._hess_method diff --git a/qiskit/opflow/gradients/natural_gradient.py b/qiskit/opflow/gradients/natural_gradient.py deleted file mode 100644 index 6a2edb5a108b..000000000000 --- a/qiskit/opflow/gradients/natural_gradient.py +++ /dev/null @@ -1,561 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Natural Gradient.""" - -from collections.abc import Iterable -from typing import List, Tuple, Callable, Optional, Union -import numpy as np - -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from ..operator_base import OperatorBase -from ..list_ops.list_op import ListOp -from ..list_ops.composed_op import ComposedOp -from ..state_fns.circuit_state_fn import CircuitStateFn -from .circuit_gradients import CircuitGradient -from .circuit_qfis import CircuitQFI -from .gradient import Gradient -from .gradient_base import GradientBase -from .qfi import QFI - -# Error tolerance variable -ETOL = 1e-8 -# Cut-off ratio for small singular values for least square solver -RCOND = 1e-2 - - -class NaturalGradient(GradientBase): - r"""Deprecated: Convert an operator expression to the first-order gradient. - - Given an ill-posed inverse problem - - x = arg min{||Ax-C||^2} (1) - - one can use regularization schemes can be used to stabilize the system and find a numerical - solution - - x_lambda = arg min{||Ax-C||^2 + lambda*R(x)} (2) - - where R(x) represents the penalization term. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - grad_method: Union[str, CircuitGradient] = "lin_comb", - qfi_method: Union[str, CircuitQFI] = "lin_comb_full", - regularization: Optional[str] = None, - **kwargs, - ): - r""" - Args: - grad_method: The method used to compute the state gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - qfi_method: The method used to compute the QFI. Can be either - ``'lin_comb_full'`` or ``'overlap_block_diag'`` or ``'overlap_diag'``. - regularization: Use the following regularization with a least square method to solve the - underlying system of linear equations - Can be either None or ``'ridge'`` or ``'lasso'`` or ``'perturb_diag'`` - ``'ridge'`` and ``'lasso'`` use an automatic optimal parameter search - If regularization is None but the metric is ill-conditioned or singular then - a least square solver is used without regularization - kwargs (dict): Optional parameters for a CircuitGradient - """ - super().__init__(grad_method) - - self._qfi_method = QFI(qfi_method) - self._regularization = regularization - self._epsilon = kwargs.get("epsilon", 1e-6) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of. - params: The parameters we are taking the gradient with respect to. If not explicitly - passed, they are inferred from the operator and sorted by name. - - Returns: - An operator whose evaluation yields the NaturalGradient. - - Raises: - TypeError: If ``operator`` does not represent an expectation value or the quantum - state is not ``CircuitStateFn``. - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - """ - if not isinstance(operator, ComposedOp): - if not (isinstance(operator, ListOp) and len(operator.oplist) == 1): - raise TypeError( - "Please provide the operator either as ComposedOp or as ListOp of " - "a CircuitStateFn potentially with a combo function." - ) - - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "Please make sure that the operator for which you want to compute " - "Quantum Fisher Information represents an expectation value or a " - "loss function and that the quantum state is given as " - "CircuitStateFn." - ) - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - if not isinstance(params, Iterable): - params = [params] - # Instantiate the gradient - grad = Gradient(self._grad_method, epsilon=self._epsilon).convert(operator, params) - # Instantiate the QFI metric which is used to re-scale the gradient - metric = self._qfi_method.convert(operator[-1], params) * 0.25 - - def combo_fn(x): - return self.nat_grad_combo_fn(x, self.regularization) - - # Define the ListOp which combines the gradient and the QFI according to the combination - # function defined above. - return ListOp([grad, metric], combo_fn=combo_fn) - - @staticmethod - def nat_grad_combo_fn(x: tuple, regularization: Optional[str] = None) -> np.ndarray: - r""" - Natural Gradient Function Implementation. - - Args: - x: Iterable consisting of Gradient, Quantum Fisher Information. - regularization: Regularization method. - - Returns: - Natural Gradient. - - Raises: - ValueError: If the gradient has imaginary components that are non-negligible. - - """ - gradient = x[0] - metric = x[1] - if np.amax(np.abs(np.imag(gradient))) > ETOL: - raise ValueError( - "The imaginary part of the gradient are non-negligible. The largest absolute " - f"imaginary value in the gradient is {np.amax(np.abs(np.imag(gradient)))}. " - "Please increase the number of shots." - ) - gradient = np.real(gradient) - - if np.amax(np.abs(np.imag(metric))) > ETOL: - raise ValueError( - "The imaginary part of the metric are non-negligible. The largest " - "absolute imaginary value in the gradient is " - f"{np.amax(np.abs(np.imag(metric)))}. Please " - "increase the number of shots." - ) - metric = np.real(metric) - - if regularization is not None: - # If a regularization method is chosen then use a regularized solver to - # construct the natural gradient. - nat_grad = NaturalGradient._regularized_sle_solver( - metric, gradient, regularization=regularization - ) - else: - # Check if numerical instabilities lead to a metric which is not positive semidefinite - w, v = np.linalg.eigh(metric) - - if not all(ew >= (-1) * ETOL for ew in w): - raise ValueError( - f"The underlying metric has at least one Eigenvalue < -{ETOL}. " - f"The smallest Eigenvalue is {np.amin(w)} " - "Please use a regularized least-square solver for this problem or " - "increase the number of backend shots.", - ) - if not all(ew >= 0 for ew in w): - # If not all eigenvalues are non-negative, set them to a small positive - # value - w = [max(ETOL, ew) for ew in w] - # Recompose the adapted eigenvalues with the eigenvectors to get a new metric - metric = np.real(v @ np.diag(w) @ np.linalg.inv(v)) - nat_grad = np.linalg.lstsq(metric, gradient, rcond=RCOND)[0] - return nat_grad - - @property - def qfi_method(self) -> CircuitQFI: - """Returns ``CircuitQFI``. - - Returns: ``CircuitQFI``. - - """ - return self._qfi_method.qfi_method - - @property - def regularization(self) -> Optional[str]: - """Returns the regularization option. - - Returns: the regularization option. - - """ - return self._regularization - - @staticmethod - def _reg_term_search( - metric: np.ndarray, - gradient: np.ndarray, - reg_method: Callable[[np.ndarray, np.ndarray, float], float], - lambda1: float = 1e-3, - lambda4: float = 1.0, - tol: float = 1e-8, - ) -> Tuple[float, np.ndarray]: - """ - This method implements a search for a regularization parameter lambda by finding for the - corner of the L-curve. - More explicitly, one has to evaluate a suitable lambda by finding a compromise between - the error in the solution and the norm of the regularization. - This function implements a method presented in - `A simple algorithm to find the L-curve corner in the regularization of inverse problems - ` - - Args: - metric: See (1) and (2). - gradient: See (1) and (2). - reg_method: Given the metric, gradient and lambda the regularization method must return - ``x_lambda`` - see (2). - lambda1: Left starting point for L-curve corner search. - lambda4: Right starting point for L-curve corner search. - tol: Termination threshold. - - Returns: - Regularization coefficient which is the solution to the regularization inverse problem. - """ - - def _get_curvature(x_lambda: List) -> float: - """Calculate Menger curvature - - Menger, K. (1930). Untersuchungen ̈uber Allgemeine Metrik. Math. Ann.,103(1), 466–501 - - Args: - ``x_lambda: [[x_lambdaj], [x_lambdak], [x_lambdal]]`` - ``lambdaj < lambdak < lambdal`` - - Returns: - Menger Curvature - - """ - eps = [] - eta = [] - for x in x_lambda: - try: - eps.append(np.log(np.linalg.norm(np.matmul(metric, x) - gradient) ** 2)) - except ValueError: - eps.append( - np.log(np.linalg.norm(np.matmul(metric, np.transpose(x)) - gradient) ** 2) - ) - eta.append(np.log(max(np.linalg.norm(x) ** 2, ETOL))) - p_temp = 1 - c_k = 0 - for i in range(3): - p_temp *= (eps[np.mod(i + 1, 3)] - eps[i]) ** 2 + ( - eta[np.mod(i + 1, 3)] - eta[i] - ) ** 2 - c_k += eps[i] * eta[np.mod(i + 1, 3)] - eps[np.mod(i + 1, 3)] * eta[i] - c_k = 2 * c_k / max(1e-4, np.sqrt(p_temp)) - return c_k - - def get_lambda2_lambda3(lambda1, lambda4): - gold_sec = (1 + np.sqrt(5)) / 2.0 - lambda2 = 10 ** ((np.log10(lambda4) + np.log10(lambda1) * gold_sec) / (1 + gold_sec)) - lambda3 = 10 ** (np.log10(lambda1) + np.log10(lambda4) - np.log10(lambda2)) - return lambda2, lambda3 - - lambda2, lambda3 = get_lambda2_lambda3(lambda1, lambda4) - lambda_ = [lambda1, lambda2, lambda3, lambda4] - x_lambda = [] - for lam in lambda_: - x_lambda.append(reg_method(metric, gradient, lam)) - counter = 0 - while (lambda_[3] - lambda_[0]) / lambda_[3] >= tol: - counter += 1 - c_2 = _get_curvature(x_lambda[:-1]) - c_3 = _get_curvature(x_lambda[1:]) - while c_3 < 0: - lambda_[3] = lambda_[2] - x_lambda[3] = x_lambda[2] - lambda_[2] = lambda_[1] - x_lambda[2] = x_lambda[1] - lambda2, _ = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[1] = lambda2 - x_lambda[1] = reg_method(metric, gradient, lambda_[1]) - c_3 = _get_curvature(x_lambda[1:]) - - if c_2 > c_3: - lambda_mc = lambda_[1] - x_mc = x_lambda[1] - lambda_[3] = lambda_[2] - x_lambda[3] = x_lambda[2] - lambda_[2] = lambda_[1] - x_lambda[2] = x_lambda[1] - lambda2, _ = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[1] = lambda2 - x_lambda[1] = reg_method(metric, gradient, lambda_[1]) - else: - lambda_mc = lambda_[2] - x_mc = x_lambda[2] - lambda_[0] = lambda_[1] - x_lambda[0] = x_lambda[1] - lambda_[1] = lambda_[2] - x_lambda[1] = x_lambda[2] - _, lambda3 = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[2] = lambda3 - x_lambda[2] = reg_method(metric, gradient, lambda_[2]) - return lambda_mc, x_mc - - @staticmethod - @_optionals.HAS_SKLEARN.require_in_call - def _ridge( - metric: np.ndarray, - gradient: np.ndarray, - lambda_: float = 1.0, - lambda1: float = 1e-4, - lambda4: float = 1e-1, - tol_search: float = 1e-8, - fit_intercept: bool = True, - normalize: bool = False, - copy_a: bool = True, - max_iter: int = 1000, - tol: float = 0.0001, - solver: str = "auto", - random_state: Optional[int] = None, - ) -> Tuple[float, np.ndarray]: - """ - Ridge Regression with automatic search for a good regularization term lambda - x_lambda = arg min{||Ax-C||^2 + lambda*||x||_2^2} (3) - `Scikit Learn Ridge Regression - ` - - Args: - metric: See (1) and (2). - gradient: See (1) and (2). - lambda_ : regularization parameter used if auto_search = False - lambda1: left starting point for L-curve corner search - lambda4: right starting point for L-curve corner search - tol_search: termination threshold for regularization parameter search - fit_intercept: if True calculate intercept - normalize: ignored if fit_intercept=False, if True normalize A for regression - copy_a: if True A is copied, else overwritten - max_iter: max. number of iterations if solver is CG - tol: precision of the regression solution - solver: solver {‘auto’, ‘svd’, ‘cholesky’, ‘lsqr’, ‘sparse_cg’, ‘sag’, ‘saga’} - random_state: seed for the pseudo random number generator used when data is shuffled - - Returns: - regularization coefficient, solution to the regularization inverse problem - - Raises: - MissingOptionalLibraryError: scikit-learn not installed - - """ - from sklearn.linear_model import Ridge - from sklearn.preprocessing import StandardScaler - - reg = Ridge( - alpha=lambda_, - fit_intercept=fit_intercept, - copy_X=copy_a, - max_iter=max_iter, - tol=tol, - solver=solver, - random_state=random_state, - ) - - def reg_method(a, c, alpha): - reg.set_params(alpha=alpha) - if normalize: - reg.fit(StandardScaler().fit_transform(a), c) - else: - reg.fit(a, c) - return reg.coef_ - - lambda_mc, x_mc = NaturalGradient._reg_term_search( - metric, gradient, reg_method, lambda1=lambda1, lambda4=lambda4, tol=tol_search - ) - return lambda_mc, np.transpose(x_mc) - - @staticmethod - @_optionals.HAS_SKLEARN.require_in_call - def _lasso( - metric: np.ndarray, - gradient: np.ndarray, - lambda_: float = 1.0, - lambda1: float = 1e-4, - lambda4: float = 1e-1, - tol_search: float = 1e-8, - fit_intercept: bool = True, - normalize: bool = False, - precompute: Union[bool, Iterable] = False, - copy_a: bool = True, - max_iter: int = 1000, - tol: float = 0.0001, - warm_start: bool = False, - positive: bool = False, - random_state: Optional[int] = None, - selection: str = "random", - ) -> Tuple[float, np.ndarray]: - """ - Lasso Regression with automatic search for a good regularization term lambda - x_lambda = arg min{||Ax-C||^2/(2*n_samples) + lambda*||x||_1} (4) - `Scikit Learn Lasso Regression - ` - - Args: - metric: Matrix of size mxn. - gradient: Vector of size m. - lambda_ : regularization parameter used if auto_search = False - lambda1: left starting point for L-curve corner search - lambda4: right starting point for L-curve corner search - tol_search: termination threshold for regularization parameter search - fit_intercept: if True calculate intercept - normalize: ignored if fit_intercept=False, if True normalize A for regression - precompute: If True compute and use Gram matrix to speed up calculations. - Gram matrix can also be given explicitly - copy_a: if True A is copied, else overwritten - max_iter: max. number of iterations if solver is CG - tol: precision of the regression solution - warm_start: if True reuse solution from previous fit as initialization - positive: if True force positive coefficients - random_state: seed for the pseudo random number generator used when data is shuffled - selection: {'cyclic', 'random'} - - Returns: - regularization coefficient, solution to the regularization inverse problem - - Raises: - MissingOptionalLibraryError: scikit-learn not installed - - """ - from sklearn.linear_model import Lasso - from sklearn.preprocessing import StandardScaler - - reg = Lasso( - alpha=lambda_, - fit_intercept=fit_intercept, - precompute=precompute, - copy_X=copy_a, - max_iter=max_iter, - tol=tol, - warm_start=warm_start, - positive=positive, - random_state=random_state, - selection=selection, - ) - - def reg_method(a, c, alpha): - reg.set_params(alpha=alpha) - if normalize: - reg.fit(StandardScaler().fit_transform(a), c) - else: - reg.fit(a, c) - return reg.coef_ - - lambda_mc, x_mc = NaturalGradient._reg_term_search( - metric, gradient, reg_method, lambda1=lambda1, lambda4=lambda4, tol=tol_search - ) - - return lambda_mc, x_mc - - @staticmethod - def _regularized_sle_solver( - metric: np.ndarray, - gradient: np.ndarray, - regularization: str = "perturb_diag", - lambda1: float = 1e-3, - lambda4: float = 1.0, - alpha: float = 0.0, - tol_norm_x: Tuple[float, float] = (1e-8, 5.0), - tol_cond_a: float = 1000.0, - ) -> np.ndarray: - """ - Solve a linear system of equations with a regularization method and automatic lambda fitting - - Args: - metric: Matrix of size mxn. - gradient: Vector of size m. - regularization: Regularization scheme to be used: 'ridge', 'lasso', - 'perturb_diag_elements' or 'perturb_diag' - lambda1: left starting point for L-curve corner search (for 'ridge' and 'lasso') - lambda4: right starting point for L-curve corner search (for 'ridge' and 'lasso') - alpha: perturbation coefficient for 'perturb_diag_elements' and 'perturb_diag' - tol_norm_x: tolerance for the norm of x - tol_cond_a: tolerance for the condition number of A - - Returns: - solution to the regularized system of linear equations - - """ - if regularization == "ridge": - _, x = NaturalGradient._ridge(metric, gradient, lambda1=lambda1) - elif regularization == "lasso": - _, x = NaturalGradient._lasso(metric, gradient, lambda1=lambda1) - elif regularization == "perturb_diag_elements": - alpha = 1e-7 - while np.linalg.cond(metric + alpha * np.diag(metric)) > tol_cond_a: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric + alpha * np.diag(metric), gradient, rcond=None) - elif regularization == "perturb_diag": - alpha = 1e-7 - while np.linalg.cond(metric + alpha * np.eye(len(gradient))) > tol_cond_a: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq( - metric + alpha * np.eye(len(gradient)), gradient, rcond=None - ) - else: - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric, gradient, rcond=None) - - if np.linalg.norm(x) > tol_norm_x[1] or np.linalg.norm(x) < tol_norm_x[0]: - if regularization == "ridge": - lambda1 = lambda1 / 10.0 - _, x = NaturalGradient._ridge(metric, gradient, lambda1=lambda1, lambda4=lambda4) - elif regularization == "lasso": - lambda1 = lambda1 / 10.0 - _, x = NaturalGradient._lasso(metric, gradient, lambda1=lambda1) - elif regularization == "perturb_diag_elements": - while np.linalg.cond(metric + alpha * np.diag(metric)) > tol_cond_a: - if alpha == 0: - alpha = 1e-7 - else: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric + alpha * np.diag(metric), gradient, rcond=None) - else: - if alpha == 0: - alpha = 1e-7 - else: - alpha *= 10 - while np.linalg.cond(metric + alpha * np.eye(len(gradient))) > tol_cond_a: - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq( - metric + alpha * np.eye(len(gradient)), gradient, rcond=None - ) - alpha *= 10 - return x diff --git a/qiskit/opflow/gradients/qfi.py b/qiskit/opflow/gradients/qfi.py deleted file mode 100644 index ca90bc74165a..000000000000 --- a/qiskit/opflow/gradients/qfi.py +++ /dev/null @@ -1,75 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for Quantum the Fisher Information.""" - -from typing import List, Union, Optional - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.circuit._utils import sort_parameters -from qiskit.utils.deprecation import deprecate_func -from ..list_ops.list_op import ListOp -from ..expectations.pauli_expectation import PauliExpectation -from ..state_fns.circuit_state_fn import CircuitStateFn -from .qfi_base import QFIBase -from .circuit_qfis import CircuitQFI - - -class QFI(QFIBase): - r"""Deprecated: Compute the Quantum Fisher Information (QFI). - - Computes the QFI given a pure, parameterized quantum state, where QFI is: - - .. math:: - - \mathrm{QFI}_{kl}= 4 \mathrm{Re}[\langle \partial_k \psi | \partial_l \psi \rangle - − \langle\partial_k \psi | \psi \rangle \langle\psi | \partial_l \psi \rangle]. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, qfi_method: Union[str, CircuitQFI] = "lin_comb_full"): - super().__init__(qfi_method=qfi_method) - - def convert( - self, - operator: CircuitStateFn, - params: Optional[ - Union[ParameterExpression, ParameterVector, List[ParameterExpression]] - ] = None, - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state \|ψ(ω)〉for which we compute - the QFI - params: The parameters we are computing the QFI wrt: ω - If not explicitly passed, they are inferred from the operator and sorted by name. - - Returns: - ListOp[ListOp] where the operator at position k,l corresponds to QFI_kl - - Raises: - ValueError: If operator is not parameterized. - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - - if params is None: - params = sort_parameters(operator.parameters) - return self.qfi_method.convert(cleaned_op, params) diff --git a/qiskit/opflow/gradients/qfi_base.py b/qiskit/opflow/gradients/qfi_base.py deleted file mode 100644 index b36606f3a440..000000000000 --- a/qiskit/opflow/gradients/qfi_base.py +++ /dev/null @@ -1,79 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The module for Quantum the Fisher Information.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .derivative_base import DerivativeBase -from .circuit_qfis import CircuitQFI - - -class QFIBase(DerivativeBase): - - r"""Deprecated: Base class for Quantum Fisher Information (QFI). - - Compute the Quantum Fisher Information (QFI) given a pure, parameterized quantum state. - - The QFI is: - - [QFI]kl= Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉] * 4. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, qfi_method: Union[str, CircuitQFI] = "lin_comb_full"): - r""" - Args: - qfi_method: The method used to compute the state/probability gradient. Can be either - a :class:`CircuitQFI` instance or one of the following pre-defined strings - ``'lin_comb_full'``, ``'overlap_diag'``` or ``'overlap_block_diag'```. - Raises: - ValueError: if ``qfi_method`` is neither a ``CircuitQFI`` object nor one of the - predefined strings. - """ - super().__init__() - if isinstance(qfi_method, CircuitQFI): - self._qfi_method = qfi_method - - elif qfi_method == "lin_comb_full": - from .circuit_qfis import LinCombFull - - self._qfi_method = LinCombFull() - elif qfi_method == "overlap_block_diag": - from .circuit_qfis import OverlapBlockDiag - - self._qfi_method = OverlapBlockDiag() - elif qfi_method == "overlap_diag": - from .circuit_qfis import OverlapDiag - - self._qfi_method = OverlapDiag() - else: - raise ValueError( - "Unrecognized input provided for `qfi_method`. Please provide" - " a CircuitQFI object or one of the pre-defined string" - " arguments: {'lin_comb_full', 'overlap_diag', " - "'overlap_block_diag'}. " - ) - - @property - def qfi_method(self) -> CircuitQFI: - """Returns ``CircuitQFI``. - - Returns: - ``CircuitQFI``. - """ - return self._qfi_method diff --git a/qiskit/opflow/list_ops/__init__.py b/qiskit/opflow/list_ops/__init__.py deleted file mode 100644 index b4e4a45b3d72..000000000000 --- a/qiskit/opflow/list_ops/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -r""" -List Operators (:mod:`qiskit.opflow.list_ops`) -============================================== - -.. currentmodule:: qiskit.opflow.list_ops - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -List Operators are classes for storing and manipulating lists of Operators, State functions, -or Measurements, and include some rule or ``combo_fn`` defining how the Operator functions of the -list constituents should be combined to form to cumulative Operator function of the -:class:`ListOp`. For example, a :class:`SummedOp` has an addition-based ``combo_fn``, so once -the Operators in its list are evaluated against some bitstring to produce a list of results, -we know to add up those results to produce the final result of the :class:`SummedOp`'s evaluation. -In theory, this ``combo_fn`` can be any function over classical complex values, but for convenience -we've chosen for them to be defined over NumPy arrays and values. This way, large numbers of -evaluations, such as after calling :meth:`~ListOp.to_matrix` on the list constituents, -can be efficiently combined. While the combination function is defined over classical values, -it should be understood as the operation by which each Operators' underlying function is -combined to form the underlying Operator function of the :class:`ListOp`. In this way, the -:mod:`.list_ops` are the basis for constructing large and sophisticated Operators, -State Functions, and Measurements. - - -The base :class:`ListOp` class is particularly interesting, as its ``combo_fn`` is "the identity -list Operation". Meaning, if we understand the ``combo_fn`` as a function from a list of complex -values to some output, one such function is returning the list as\-is. This is powerful for -constructing compact hierarchical Operators which return many measurements in multiple -dimensional lists. For example, if we want to estimate the gradient of some Observable -measurement with respect to some parameters in the State function, we can construct separate -evaluation Operators for each parameter's gradient which we must keep track of ourselves in a -list, or we can construct a single :class:`ListOp` containing the evaluation Operators for each -parameter, so the :meth:`~ListOp.eval` function returns the full gradient vector. Another excellent -example of this power is constructing a Quantum kernel matrix: - -.. code-block:: - - data_sfn_list_op = ListOp(data_circuit_state_fns) - qkernel_op_circuits = ~data_sfn_list_op @ data_sfn_list_op - qkernel_sampled = CircuitSampler(backend).convert(qkernel_op_circuits) - qkernel_sampled.eval() - -This will return the two dimensional Quantum kernel matrix, where each element is the inner product -of some pair of the data State functions, or in other terms, a measurement of one data -:class:`~.state_fns.CircuitStateFn` by another. - -You'll encounter the :class:`ListOp` subclasses (:class:`SummedOp`, :class:`ComposedOp`, -or :class:`TensoredOp`) more often as lazy results of Operator construction operations than as -something you need to explicitly construct. Any time we don't know how to efficiently add, -compose, or tensor two :mod:`.primitive_ops` or :mod:`.state_fns` together, they're returned in -a :class:`SummedOp`, :class:`ComposedOp`, or :class:`TensoredOp`, respectively, so we can still work -with their combined function and perhaps convert them into an efficiently combine-able format later. - -Note: - Combination functions do not always behave predictably, and you must understand the - conversions you're making when you working with :mod:`.list_ops`. Most notably - sampling a sum - of two circuits on Quantum hardware does not incorporate interference between the - wavefunctions! In this case, we're sending our State functions through a depolarizing channel - before adding them, rather than adding them directly before the measurement. - -List Operators --------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ListOp - ComposedOp - SummedOp - TensoredOp - -""" - -from .list_op import ListOp -from .summed_op import SummedOp -from .composed_op import ComposedOp -from .tensored_op import TensoredOp - -__all__ = ["ListOp", "SummedOp", "TensoredOp", "ComposedOp"] diff --git a/qiskit/opflow/list_ops/composed_op.py b/qiskit/opflow/list_ops/composed_op.py deleted file mode 100644 index 6f569e718ad9..000000000000 --- a/qiskit/opflow/list_ops/composed_op.py +++ /dev/null @@ -1,198 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ComposedOp Class""" - -from functools import partial, reduce -from typing import List, Optional, Union, cast, Dict - -from numbers import Number -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class ComposedOp(ListOp): - """Deprecated: A class for lazily representing compositions of Operators. Often Operators cannot be - efficiently composed with one another, but may be manipulated further so that they can be - composed later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be composed, and therefore if they reach a point in which they can be, such as after - conversion to QuantumCircuits or matrices, they can be reduced by composition.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being composed. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=partial(reduce, np.dot), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return self.oplist[0].num_qubits - - @property - def distributive(self) -> bool: - return False - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - # TODO take advantage of the mixed product property, tensorpower each element in the composition - # def tensorpower(self, other): - # """ Tensor product with Self Multiple Times """ - # raise NotImplementedError - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - mat = self.coeff * reduce( - np.dot, [np.asarray(op.to_matrix(massive=massive)) for op in self.oplist] - ) - - # Note: As ComposedOp has a combo function of inner product we can end up here not with - # a matrix (array) but a scalar. In which case we make a single element array of it. - if isinstance(mat, Number): - mat = [mat] - - return np.asarray(mat, dtype=complex) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the composed operator. - - Returns: - The circuit representation of the composed operator. - - Raises: - OpflowError: for operators where a single underlying circuit can not be obtained. - """ - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..primitive_ops.primitive_op import PrimitiveOp - - circuit_op = self.to_circuit_op() - if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): - return circuit_op.to_circuit() - raise OpflowError( - "Conversion to_circuit supported only for operators, where a single " - "underlying circuit can be produced." - ) - - def adjoint(self) -> "ComposedOp": - return ComposedOp([op.adjoint() for op in reversed(self.oplist)], coeff=self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(ComposedOp, new_self) - - if front: - return other.compose(new_self) - # Try composing with last element in list - if isinstance(other, ComposedOp): - return ComposedOp(new_self.oplist + other.oplist, coeff=new_self.coeff * other.coeff) - - # Try composing with last element of oplist. We only try - # this if that last element isn't itself an - # ComposedOp, so we can tell whether composing the - # two elements directly worked. If it doesn't, - # continue to the final return statement below, appending other to the oplist. - if not isinstance(new_self.oplist[-1], ComposedOp): - comp_with_last = new_self.oplist[-1].compose(other) - # Attempt successful - if not isinstance(comp_with_last, ComposedOp): - new_oplist = new_self.oplist[0:-1] + [comp_with_last] - return ComposedOp(new_oplist, coeff=new_self.coeff) - - return ComposedOp(new_self.oplist + [other], coeff=new_self.coeff) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - if self._is_empty(): - return 0.0 - - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - def tree_recursive_eval(r, l_arg): - if isinstance(r, list): - return [tree_recursive_eval(r_op, l_arg) for r_op in r] - else: - return l_arg.eval(r) - - eval_list = self.oplist.copy() - # Only one op needs to be multiplied, so just multiply the first. - eval_list[0] = eval_list[0] * self.coeff # type: ignore - if front and isinstance(front, OperatorBase): - eval_list = eval_list + [front] - elif front: - eval_list = [StateFn(front, is_measurement=True)] + eval_list # type: ignore - - return reduce(tree_recursive_eval, reversed(eval_list)) - - # Try collapsing list or trees of compositions into a single . - def non_distributive_reduce(self) -> OperatorBase: - """Reduce without attempting to expand all distributive compositions. - - Returns: - The reduced Operator. - """ - reduced_ops = [op.reduce() for op in self.oplist] - reduced_ops = reduce(lambda x, y: x.compose(y), reduced_ops) * self.coeff - if isinstance(reduced_ops, ComposedOp) and len(reduced_ops.oplist) > 1: - return reduced_ops - else: - return reduced_ops[0] - - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if len(reduced_ops) == 0: - return self.__class__([], coeff=self.coeff, abelian=self.abelian) - - def distribute_compose(l_arg, r): - if isinstance(l_arg, ListOp) and l_arg.distributive: - # Either ListOp or SummedOp, returns correct type - return l_arg.__class__( - [distribute_compose(l_op * l_arg.coeff, r) for l_op in l_arg.oplist] - ) - if isinstance(r, ListOp) and r.distributive: - return r.__class__([distribute_compose(l_arg, r_op * r.coeff) for r_op in r.oplist]) - else: - return l_arg.compose(r) - - reduced_ops = reduce(distribute_compose, reduced_ops) * self.coeff - if isinstance(reduced_ops, ListOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) diff --git a/qiskit/opflow/list_ops/list_op.py b/qiskit/opflow/list_ops/list_op.py deleted file mode 100644 index 1047cc68fb92..000000000000 --- a/qiskit/opflow/list_ops/list_op.py +++ /dev/null @@ -1,641 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ListOp Operator Class""" - -from functools import reduce -from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Sequence, Union, cast - -import numpy as np -from scipy.sparse import spmatrix - -from qiskit.circuit import ParameterExpression, QuantumCircuit -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils import arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class ListOp(OperatorBase): - """ - Deprecated: A Class for manipulating List Operators, and parent class to ``SummedOp``, - ``ComposedOp`` and ``TensoredOp``. - - List Operators are classes for storing and manipulating lists of Operators, State functions, - or Measurements, and include some rule or ``combo_fn`` defining how the Operator functions - of the list constituents should be combined to form to cumulative Operator function of the - ``ListOp``. For example, a ``SummedOp`` has an addition-based ``combo_fn``, so once the - Operators in its list are evaluated against some bitstring to produce a list of results, - we know to add up those results to produce the final result of the ``SummedOp``'s - evaluation. In theory, this ``combo_fn`` can be any function over classical complex values, - but for convenience we've chosen for them to be defined over NumPy arrays and values. This way, - large numbers of evaluations, such as after calling ``to_matrix`` on the list constituents, - can be efficiently combined. While the combination function is defined over classical - values, it should be understood as the operation by which each Operators' underlying - function is combined to form the underlying Operator function of the ``ListOp``. In this - way, the ``ListOps`` are the basis for constructing large and sophisticated Operators, - State Functions, and Measurements. - - The base ``ListOp`` class is particularly interesting, as its ``combo_fn`` is "the identity - list Operation". Meaning, if we understand the ``combo_fn`` as a function from a list of - complex values to some output, one such function is returning the list as-is. This is - powerful for constructing compact hierarchical Operators which return many measurements in - multiple dimensional lists. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - oplist: Sequence[OperatorBase], - combo_fn: Optional[Callable] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - grad_combo_fn: Optional[Callable] = None, - ) -> None: - """ - Args: - oplist: The list of ``OperatorBases`` defining this Operator's underlying function. - combo_fn: The recombination function to combine classical results of the - ``oplist`` Operators' eval functions (e.g. sum). Default is lambda x: x. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - grad_combo_fn: The gradient of recombination function. If None, the gradient will - be computed automatically. - Note that the default "recombination function" lambda above is essentially the - identity - it accepts the list of values, and returns them in a list. - """ - super().__init__() - self._oplist = self._check_input_types(oplist) - self._combo_fn = combo_fn - self._coeff = coeff - self._abelian = abelian - self._grad_combo_fn = grad_combo_fn - - def _check_input_types(self, oplist): - if all(isinstance(x, OperatorBase) for x in oplist): - return list(oplist) - else: - badval = next(x for x in oplist if not isinstance(x, OperatorBase)) - raise TypeError(f"ListOp expecting objects of type OperatorBase, got {badval}") - - def _state( - self, - coeff: Optional[Union[complex, ParameterExpression]] = None, - combo_fn: Optional[Callable] = None, - abelian: Optional[bool] = None, - grad_combo_fn: Optional[Callable] = None, - ) -> Dict: - return { - "coeff": coeff if coeff is not None else self.coeff, - "combo_fn": combo_fn if combo_fn is not None else self.combo_fn, - "abelian": abelian if abelian is not None else self.abelian, - "grad_combo_fn": grad_combo_fn if grad_combo_fn is not None else self.grad_combo_fn, - } - - @property - def settings(self) -> Dict: - """Return settings.""" - return { - "oplist": self._oplist, - "combo_fn": self._combo_fn, - "coeff": self._coeff, - "abelian": self._abelian, - "grad_combo_fn": self._grad_combo_fn, - } - - @property - def oplist(self) -> List[OperatorBase]: - """The list of ``OperatorBases`` defining the underlying function of this - Operator. - - Returns: - The Operators defining the ListOp - """ - return self._oplist - - @staticmethod - def default_combo_fn(x: Any) -> Any: - """ListOp default combo function i.e. lambda x: x""" - return x - - @property - def combo_fn(self) -> Callable: - """The function defining how to combine ``oplist`` (or Numbers, or NumPy arrays) to - produce the Operator's underlying function. For example, SummedOp's combination function - is to add all of the Operators in ``oplist``. - - Returns: - The combination function. - """ - if self._combo_fn is None: - return ListOp.default_combo_fn - return self._combo_fn - - @property - def grad_combo_fn(self) -> Optional[Callable]: - """The gradient of ``combo_fn``.""" - return self._grad_combo_fn - - @property - def abelian(self) -> bool: - """Whether the Operators in ``oplist`` are known to commute with one another. - - Returns: - A bool indicating whether the ``oplist`` is Abelian. - """ - return self._abelian - - @property - def distributive(self) -> bool: - """Indicates whether the ListOp or subclass is distributive under composition. - ListOp and SummedOp are, meaning that (opv @ op) = (opv[0] @ op + opv[1] @ op) - (using plus for SummedOp, list for ListOp, etc.), while ComposedOp and TensoredOp - do not behave this way. - - Returns: - A bool indicating whether the ListOp is distributive under composition. - """ - return True - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """The scalar coefficient multiplying the Operator. - - Returns: - The coefficient. - """ - return self._coeff - - @property - def coeffs(self) -> List[Union[complex, ParameterExpression]]: - """Return a list of the coefficients of the operators listed. - Raises exception for nested Listops. - """ - if any(isinstance(op, ListOp) for op in self.oplist): - raise TypeError("Coefficients are not returned for nested ListOps.") - return [self.coeff * op.coeff for op in self.oplist] - - def primitive_strings(self) -> Set[str]: - return reduce(set.union, [op.primitive_strings() for op in self.oplist]) - - @property - def num_qubits(self) -> int: - num_qubits0 = self.oplist[0].num_qubits - if not all(num_qubits0 == op.num_qubits for op in self.oplist): - raise ValueError("Operators in ListOp have differing numbers of qubits.") - return num_qubits0 - - def add(self, other: OperatorBase) -> "ListOp": - if self == other: - return self.mul(2.0) - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "ListOp": - # TODO do this lazily? Basically rebuilds the entire tree, and ops and adjoints almost - # always come in pairs, so an AdjointOp holding a reference could save copying. - if self.__class__ == ListOp: - return ListOp( - [op.adjoint() for op in self.oplist], **self._state(coeff=self.coeff.conjugate()) - ) # coeff is conjugated - return self.__class__( - [op.adjoint() for op in self.oplist], coeff=self.coeff.conjugate(), abelian=self.abelian - ) - - def traverse( - self, convert_fn: Callable, coeff: Optional[Union[complex, ParameterExpression]] = None - ) -> "ListOp": - """Apply the convert_fn to each node in the oplist. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted ListOp. - """ - if coeff is None: - coeff = self.coeff - - if self.__class__ == ListOp: - return ListOp([convert_fn(op) for op in self.oplist], **self._state(coeff=coeff)) - return self.__class__( - [convert_fn(op) for op in self.oplist], coeff=coeff, abelian=self.abelian - ) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, type(self)) or not len(self.oplist) == len(other.oplist): - return False - # Note, ordering matters here (i.e. different list orders will return False) - return self.coeff == other.coeff and all( - op1 == op2 for op1, op2 in zip(self.oplist, other.oplist) - ) - - # We need to do this because otherwise Numpy takes over scalar multiplication and wrecks it if - # isinstance(scalar, np.number) - this started happening when we added __get_item__(). - __array_priority__ = 10000 - - def mul(self, scalar: Union[complex, ParameterExpression]) -> "ListOp": - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - if self.__class__ == ListOp: - return ListOp(self.oplist, **self._state(coeff=scalar * self.coeff)) - return self.__class__(self.oplist, coeff=scalar * self.coeff, abelian=self.abelian) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Avoid circular dependency - # pylint: disable=cyclic-import - from .tensored_op import TensoredOp - - return TensoredOp([self, other]) - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - # Hack to make op1^(op2^0) work as intended. - if other == 0: - return 1 - if not isinstance(other, int) or other <= 0: - raise TypeError("Tensorpower can only take positive int arguments") - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .tensored_op import TensoredOp - - return TensoredOp([self] * other) - - def _expand_dim(self, num_qubits: int) -> "ListOp": - oplist = [ - op._expand_dim(num_qubits + self.num_qubits - op.num_qubits) for op in self.oplist - ] - return ListOp(oplist, **self._state()) - - def permute(self, permutation: List[int]) -> "OperatorBase": - """Permute the qubits of the operator. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new ListOp representing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - new_self = self - circuit_size = max(permutation) + 1 - - try: - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - except ValueError: - raise OpflowError( - "Permute is only possible if all operators in the ListOp have the " - "same number of qubits." - ) from ValueError - if self.num_qubits < circuit_size: - # pad the operator with identities - new_self = self._expand_dim(circuit_size - self.num_qubits) - qc = QuantumCircuit(circuit_size) - # extend the indices to match the size of the circuit - permutation = ( - list(filter(lambda x: x not in permutation, range(circuit_size))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - - # pylint: disable=cyclic-import - from ..primitive_ops.circuit_op import CircuitOp - - return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(ListOp, new_self) - - if front: - return other.compose(new_self) - # Avoid circular dependency - # pylint: disable=cyclic-import - from .composed_op import ComposedOp - - return ComposedOp([new_self, other]) - - def power(self, exponent: int) -> OperatorBase: - if not isinstance(exponent, int) or exponent <= 0: - raise TypeError("power can only take positive int arguments") - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .composed_op import ComposedOp - - return ComposedOp([self] * exponent) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - # Combination function must be able to handle classical values. - # Note: this can end up, when we have list operators containing other list operators, as a - # ragged array and numpy 1.19 raises a deprecation warning unless this is explicitly - # done as object type now - was implicit before. - mat = self.combo_fn( - np.asarray( - [op.to_matrix(massive=massive) * self.coeff for op in self.oplist], dtype=object - ) - ) - return np.asarray(mat, dtype=complex) - - def to_spmatrix(self) -> Union[spmatrix, List[spmatrix]]: - """Returns SciPy sparse matrix representation of the Operator. - - Returns: - CSR sparse matrix representation of the Operator, or List thereof. - """ - - # Combination function must be able to handle classical values - return self.combo_fn([op.to_spmatrix() for op in self.oplist]) * self.coeff - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - """ - Evaluate the Operator's underlying function, either on a binary string or another Operator. - A square binary Operator can be defined as a function taking a binary function to another - binary function. This method returns the value of that function for a given StateFn or - binary string. For example, ``op.eval('0110').eval('1110')`` can be seen as querying the - Operator's matrix representation by row 6 and column 14, and will return the complex - value at those "indices." Similarly for a StateFn, ``op.eval('1011')`` will return the - complex value at row 11 of the vector representation of the StateFn, as all StateFns are - defined to be evaluated from Zero implicitly (i.e. it is as if ``.eval('0000')`` is already - called implicitly to always "indexing" from column 0). - - ListOp's eval recursively evaluates each Operator in ``oplist``, - and combines the results using the recombination function ``combo_fn``. - - Args: - front: The bitstring, dict of bitstrings (with values being coefficients), or - StateFn to evaluated by the Operator's underlying function. - - Returns: - The output of the ``oplist`` Operators' evaluation function, combined with the - ``combo_fn``. If either self or front contain proper ``ListOps`` (not ListOp - subclasses), the result is an n-dimensional list of complex or StateFn results, - resulting from the recursive evaluation by each OperatorBase in the ListOps. - - Raises: - NotImplementedError: Raised if called for a subclass which is not distributive. - TypeError: Operators with mixed hierarchies, such as a ListOp containing both - PrimitiveOps and ListOps, are not supported. - NotImplementedError: Attempting to call ListOp's eval from a non-distributive subclass. - - """ - # pylint: disable=cyclic-import - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.vector_state_fn import VectorStateFn - from ..state_fns.sparse_vector_state_fn import SparseVectorStateFn - - # The below code only works for distributive ListOps, e.g. ListOp and SummedOp - if not self.distributive: - raise NotImplementedError( - "ListOp's eval function is only defined for distributive ListOps." - ) - - evals = [op.eval(front) for op in self.oplist] - - # Handle application of combo_fn for DictStateFn resp VectorStateFn operators - if self._combo_fn is not None: # If not using default. - if ( - all(isinstance(op, DictStateFn) for op in evals) - or all(isinstance(op, VectorStateFn) for op in evals) - or all(isinstance(op, SparseVectorStateFn) for op in evals) - ): - if not all( - op.is_measurement == evals[0].is_measurement for op in evals # type: ignore - ): - raise NotImplementedError( - "Combo_fn not yet supported for mixed measurement " - "and non-measurement StateFns" - ) - result = self.combo_fn(evals) - if isinstance(result, list): - multiplied = self.coeff * np.array(result) - return multiplied.tolist() - return self.coeff * result - - if all(isinstance(op, OperatorBase) for op in evals): - return self.__class__(evals) # type: ignore - elif any(isinstance(op, OperatorBase) for op in evals): - raise TypeError("Cannot handle mixed scalar and Operator eval results.") - else: - result = self.combo_fn(evals) - if isinstance(result, list): - multiplied = self.coeff * np.array(result) - return multiplied.tolist() - return self.coeff * result - - def exp_i(self) -> OperatorBase: - """Return an ``OperatorBase`` equivalent to an exponentiation of self * -i, e^(-i*op).""" - # pylint: disable=unidiomatic-typecheck - if type(self) == ListOp: - return ListOp( - [op.exp_i() for op in self.oplist], **self._state(abelian=False) # type: ignore - ) - - # pylint: disable=cyclic-import - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def log_i(self, massive: bool = False) -> OperatorBase: - """Return a ``MatrixOp`` equivalent to log(H)/-i for this operator H. This - function is the effective inverse of exp_i, equivalent to finding the Hermitian - Operator which produces self when exponentiated. For proper ListOps, applies ``log_i`` - to all ops in oplist. - """ - if self.__class__.__name__ == ListOp.__name__: - return ListOp( - [op.log_i(massive=massive) for op in self.oplist], # type: ignore - **self._state(abelian=False), - ) - - return self.to_matrix_op(massive=massive).log_i(massive=massive) - - def __str__(self) -> str: - content_string = ",\n".join([str(op) for op in self.oplist]) - main_string = "{}([\n{}\n])".format( - self.__class__.__name__, self._indent(content_string, indentation=self.INDENTATION) - ) - if self.abelian: - main_string = "Abelian" + main_string - if self.coeff != 1.0: - main_string = f"{self.coeff} * " + main_string - return main_string - - def __repr__(self) -> str: - return "{}({}, coeff={}, abelian={})".format( - self.__class__.__name__, repr(self.oplist), self.coeff, self.abelian - ) - - @property - def parameters(self): - params = set() - for op in self.oplist: - params.update(op.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return self.traverse(lambda x: x.assign_parameters(param_dict), coeff=param_value) - - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if self.__class__ == ListOp: - return ListOp(reduced_ops, **self._state()) - return self.__class__(reduced_ops, coeff=self.coeff, abelian=self.abelian) - - def to_matrix_op(self, massive: bool = False) -> "ListOp": - """Returns an equivalent Operator composed of only NumPy-based primitives, such as - ``MatrixOp`` and ``VectorStateFn``.""" - if self.__class__ == ListOp: - return cast( - ListOp, - ListOp( - [op.to_matrix_op(massive=massive) for op in self.oplist], **self._state() - ).reduce(), - ) - return cast( - ListOp, - self.__class__( - [op.to_matrix_op(massive=massive) for op in self.oplist], - coeff=self.coeff, - abelian=self.abelian, - ).reduce(), - ) - - def to_circuit_op(self) -> OperatorBase: - """Returns an equivalent Operator composed of only QuantumCircuit-based primitives, - such as ``CircuitOp`` and ``CircuitStateFn``.""" - # pylint: disable=cyclic-import - from ..state_fns.operator_state_fn import OperatorStateFn - - if self.__class__ == ListOp: - return ListOp( - [ - op.to_circuit_op() if not isinstance(op, OperatorStateFn) else op - for op in self.oplist - ], - **self._state(), - ).reduce() - return self.__class__( - [ - op.to_circuit_op() if not isinstance(op, OperatorStateFn) else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - - def to_pauli_op(self, massive: bool = False) -> "ListOp": - """Returns an equivalent Operator composed of only Pauli-based primitives, - such as ``PauliOp``.""" - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - if self.__class__ == ListOp: - return ListOp( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - **self._state(), - ).reduce() - return self.__class__( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - - def _is_empty(self): - return len(self.oplist) == 0 - - # Array operations: - - def __getitem__(self, offset: Union[int, slice]) -> OperatorBase: - """Allows array-indexing style access to the Operators in ``oplist``. - - Args: - offset: The index of ``oplist`` desired. - - Returns: - The ``OperatorBase`` at index ``offset`` of ``oplist``, - or another ListOp with the same properties as this one if offset is a slice. - """ - if isinstance(offset, int): - return self.oplist[offset] - - if self.__class__ == ListOp: - return ListOp(oplist=self._oplist[offset], **self._state()) - - return self.__class__(oplist=self._oplist[offset], coeff=self._coeff, abelian=self._abelian) - - def __iter__(self) -> Iterator: - """Returns an iterator over the operators in ``oplist``. - - Returns: - An iterator over the operators in ``oplist`` - """ - return iter(self.oplist) - - def __len__(self) -> int: - """Length of ``oplist``. - - Returns: - An int equal to the length of ``oplist``. - """ - return len(self.oplist) diff --git a/qiskit/opflow/list_ops/summed_op.py b/qiskit/opflow/list_ops/summed_op.py deleted file mode 100644 index 625445e24318..000000000000 --- a/qiskit/opflow/list_ops/summed_op.py +++ /dev/null @@ -1,251 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""SummedOp Class""" - -from typing import List, Union, cast, Dict - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class SummedOp(ListOp): - """Deprecated: A class for lazily representing sums of Operators. Often Operators cannot be - efficiently added to one another, but may be manipulated further so that they can be - later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be added together, and therefore if they reach a point in which they can be, such as after - evaluation or conversion to matrices, they can be reduced by addition.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being summed. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=lambda x: np.sum(x, axis=0), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return self.oplist[0].num_qubits - - @property - def distributive(self) -> bool: - return True - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - def add(self, other: OperatorBase) -> "SummedOp": - """Return Operator addition of ``self`` and ``other``, overloaded by ``+``. - - Note: - This appends ``other`` to ``self.oplist`` without checking ``other`` is already - included or not. If you want to simplify them, please use :meth:`simplify`. - - Args: - other: An ``OperatorBase`` with the same number of qubits as self, and in the same - 'Operator', 'State function', or 'Measurement' category as self (i.e. the same type - of underlying function). - - Returns: - A ``SummedOp`` equivalent to the sum of self and other. - """ - self_new_ops = ( - self.oplist if self.coeff == 1 else [op.mul(self.coeff) for op in self.oplist] - ) - if isinstance(other, SummedOp): - other_new_ops = ( - other.oplist if other.coeff == 1 else [op.mul(other.coeff) for op in other.oplist] - ) - else: - other_new_ops = [other] - return SummedOp(self_new_ops + other_new_ops) - - def collapse_summands(self) -> "SummedOp": - """Return Operator by simplifying duplicate operators. - - E.g., ``SummedOp([2 * X ^ Y, X ^ Y]).collapse_summands() -> SummedOp([3 * X ^ Y])``. - - Returns: - A simplified ``SummedOp`` equivalent to self. - """ - # pylint: disable=cyclic-import - from ..primitive_ops.primitive_op import PrimitiveOp - - oplist = [] # type: List[OperatorBase] - coeffs = [] # type: List[Union[int, float, complex, ParameterExpression]] - for op in self.oplist: - if isinstance(op, PrimitiveOp): - new_op = PrimitiveOp(op.primitive) - new_coeff = op.coeff * self.coeff - if new_op in oplist: - index = oplist.index(new_op) - coeffs[index] += new_coeff - else: - oplist.append(new_op) - coeffs.append(new_coeff) - else: - if op in oplist: - index = oplist.index(op) - coeffs[index] += self.coeff - else: - oplist.append(op) - coeffs.append(self.coeff) - return SummedOp([op * coeff for op, coeff in zip(oplist, coeffs)]) - - # TODO be smarter about the fact that any two ops in oplist could be evaluated for sum. - def reduce(self) -> OperatorBase: - """Try collapsing list or trees of sums. - - Tries to sum up duplicate operators and reduces the operators - in the sum. - - Returns: - A collapsed version of self, if possible. - """ - if len(self.oplist) == 0: - return SummedOp([], coeff=self.coeff, abelian=self.abelian) - - # reduce constituents - reduced_ops = sum(op.reduce() for op in self.oplist) * self.coeff - - # group duplicate operators - if isinstance(reduced_ops, SummedOp): - reduced_ops = reduced_ops.collapse_summands() - - # pylint: disable=cyclic-import - from ..primitive_ops.pauli_sum_op import PauliSumOp - - if isinstance(reduced_ops, PauliSumOp): - reduced_ops = reduced_ops.reduce() - - if isinstance(reduced_ops, SummedOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the SummedOp. In the first step, - the SummedOp is converted to MatrixOp. This is straightforward for most operators, - but it is not supported for operators containing parameterized PrimitiveOps (in that case, - OpflowError is raised). In the next step, the MatrixOp representation of SummedOp is - converted to circuit. In most cases, if the summands themselves are unitary operators, - the SummedOp itself is non-unitary and can not be converted to circuit. In that case, - ExtensionError is raised in the underlying modules. - - Returns: - The circuit representation of the summed operator. - - Raises: - OpflowError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of - parameterized PrimitiveOps). - """ - # pylint: disable=cyclic-import - from ..primitive_ops.matrix_op import MatrixOp - - matrix_op = self.to_matrix_op() - if isinstance(matrix_op, MatrixOp): - return matrix_op.to_circuit() - raise OpflowError( - "The SummedOp can not be converted to circuit, because to_matrix_op did " - "not return a MatrixOp." - ) - - def to_matrix_op(self, massive: bool = False) -> "SummedOp": - """Returns an equivalent Operator composed of only NumPy-based primitives, such as - ``MatrixOp`` and ``VectorStateFn``.""" - accum = self.oplist[0].to_matrix_op(massive=massive) - for i in range(1, len(self.oplist)): - accum += self.oplist[i].to_matrix_op(massive=massive) - - return cast(SummedOp, accum * self.coeff) - - def to_pauli_op(self, massive: bool = False) -> "SummedOp": - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - pauli_sum = SummedOp( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - if isinstance(pauli_sum, SummedOp): - return pauli_sum - return pauli_sum.to_pauli_op() # type: ignore - - def equals(self, other: OperatorBase) -> bool: - """Check if other is equal to self. - - Note: - This is not a mathematical check for equality. - If ``self`` and ``other`` implement the same operation but differ - in the representation (e.g. different type of summands) - ``equals`` will evaluate to ``False``. - - Args: - other: The other operator to check for equality. - - Returns: - True, if other and self are equal, otherwise False. - - Examples: - >>> from qiskit.opflow import X, Z - >>> 2 * X == X + X - True - >>> X + Z == Z + X - True - """ - self_reduced, other_reduced = self.reduce(), other.reduce() - if not isinstance(other_reduced, type(self_reduced)): - return False - - # check if reduced op is still a SummedOp - if not isinstance(self_reduced, SummedOp): - return self_reduced == other_reduced - - self_reduced = cast(SummedOp, self_reduced) - other_reduced = cast(SummedOp, other_reduced) - if len(self_reduced.oplist) != len(other_reduced.oplist): - return False - - # absorb coeffs into the operators - if self_reduced.coeff != 1: - self_reduced = SummedOp([op * self_reduced.coeff for op in self_reduced.oplist]) - if other_reduced.coeff != 1: - other_reduced = SummedOp([op * other_reduced.coeff for op in other_reduced.oplist]) - - # compare independent of order - return all(any(i == j for j in other_reduced) for i in self_reduced) diff --git a/qiskit/opflow/list_ops/tensored_op.py b/qiskit/opflow/list_ops/tensored_op.py deleted file mode 100644 index f3eff0a57f9d..000000000000 --- a/qiskit/opflow/list_ops/tensored_op.py +++ /dev/null @@ -1,130 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""TensoredOp Class""" - -from functools import partial, reduce -from typing import List, Union, cast, Dict - -import numpy as np - -from qiskit.circuit import ParameterExpression, QuantumCircuit -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class TensoredOp(ListOp): - """Deprecated: A class for lazily representing tensor products of Operators. Often Operators - cannot be efficiently tensored to one another, but may be manipulated further so that they can be - later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be tensored together, and therefore if they reach a point in which they can be, such as after - conversion to QuantumCircuits, they can be reduced by tensor product.""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being tensored. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=partial(reduce, np.kron), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return sum(op.num_qubits for op in self.oplist) - - @property - def distributive(self) -> bool: - return False - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - def _expand_dim(self, num_qubits: int) -> "TensoredOp": - """Appends I ^ num_qubits to ``oplist``. Choice of PauliOp as - identity is arbitrary and can be substituted for other PrimitiveOp identity. - - Returns: - TensoredOp expanded with identity operator. - """ - # pylint: disable=cyclic-import - from ..operator_globals import I - - return TensoredOp(self.oplist + [I ^ num_qubits], coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> OperatorBase: - if isinstance(other, TensoredOp): - return TensoredOp(self.oplist + other.oplist, coeff=self.coeff * other.coeff) - return TensoredOp(self.oplist + [other], coeff=self.coeff) - - # TODO eval should partial trace the input into smaller StateFns each of size - # op.num_qubits for each op in oplist. Right now just works through matmul. - def eval( - self, front: Union[str, dict, np.ndarray, OperatorBase, Statevector] = None - ) -> Union[OperatorBase, complex]: - if self._is_empty(): - return 0.0 - return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front)) - - # Try collapsing list or trees of tensor products. - # TODO do this smarter - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if self._is_empty(): - return self.__class__([], coeff=self.coeff, abelian=self.abelian) - reduced_ops = reduce(lambda x, y: x.tensor(y), reduced_ops) * self.coeff - if isinstance(reduced_ops, ListOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the tensored operator. - - Returns: - The circuit representation of the tensored operator. - - Raises: - OpflowError: for operators where a single underlying circuit can not be produced. - """ - circuit_op = self.to_circuit_op() - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..primitive_ops.primitive_op import PrimitiveOp - - if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): - return circuit_op.to_circuit() - raise OpflowError( - "Conversion to_circuit supported only for operators, where a single " - "underlying circuit can be produced." - ) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - mat = self.coeff * reduce(np.kron, [np.asarray(op.to_matrix()) for op in self.oplist]) - return np.asarray(mat, dtype=complex) diff --git a/qiskit/opflow/mixins/__init__.py b/qiskit/opflow/mixins/__init__.py deleted file mode 100644 index 705400f844d5..000000000000 --- a/qiskit/opflow/mixins/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -OpFlow Mixins -""" - -from .star_algebra import StarAlgebraMixin -from .tensor import TensorMixin diff --git a/qiskit/opflow/mixins/star_algebra.py b/qiskit/opflow/mixins/star_algebra.py deleted file mode 100644 index 57994a4cdd88..000000000000 --- a/qiskit/opflow/mixins/star_algebra.py +++ /dev/null @@ -1,133 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The star algebra mixin abstract base class.""" - -from abc import ABC, abstractmethod -from numbers import Integral - -from qiskit.quantum_info.operators.mixins import MultiplyMixin -from qiskit.utils.deprecation import deprecate_func - - -class StarAlgebraMixin(MultiplyMixin, ABC): - """Deprecated: The star algebra mixin class. - Star algebra is an algebra with an adjoint. - - This class overrides: - - ``*``, ``__mul__``, `__rmul__`, -> :meth:`mul` - - ``/``, ``__truediv__``, -> :meth:`mul` - - ``__neg__`` -> :meth:``mul` - - ``+``, ``__add__``, ``__radd__`` -> :meth:`add` - - ``-``, ``__sub__``, `__rsub__`, -> :meth:a`add` - - ``@``, ``__matmul__`` -> :meth:`compose` - - ``**``, ``__pow__`` -> :meth:`power` - - ``~``, ``__invert__`` -> :meth:`adjoint` - - The following abstract methods must be implemented by subclasses: - - :meth:`mul(self, other)` - - :meth:`add(self, other)` - - :meth:`compose(self, other)` - - :meth:`adjoint(self)` - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - pass - - # Scalar multiplication - - @abstractmethod - def mul(self, other: complex): - """Return scalar multiplication of self and other, overloaded by `*`.""" - - def __mul__(self, other: complex): - return self.mul(other) - - def _multiply(self, other: complex): - return self.mul(other) - - # Addition, substitution - - @abstractmethod - def add(self, other): - """Return Operator addition of self and other, overloaded by `+`.""" - - def __add__(self, other): - # Hack to be able to use sum(list_of_ops) nicely because - # sum adds 0 to the first element of the list. - if other == 0: - return self - - return self.add(other) - - def __radd__(self, other): - # Hack to be able to use sum(list_of_ops) nicely because - # sum adds 0 to the first element of the list. - if other == 0: - return self - return self.add(other) - - def __sub__(self, other): - return self.add(-other) - - def __rsub__(self, other): - return self.neg().add(other) - - # Operator multiplication - - @abstractmethod - def compose(self, other): - """Overloads the matrix multiplication operator `@` for self and other. - `Compose` computes operator composition between self and other (linear algebra-style: - A@B(x) = A(B(x))). - """ - - def power(self, exponent: int): - r"""Return Operator composed with self multiple times, overloaded by ``**``.""" - if not isinstance(exponent, Integral): - raise TypeError( - f"Unsupported operand type(s) for **: '{type(self).__name__}' and " - f"'{type(exponent).__name__}'" - ) - - if exponent < 1: - raise ValueError("The input `exponent` must be a positive integer.") - - res = self - for _ in range(1, exponent): - res = res.compose(self) - return res - - def __matmul__(self, other): - return self.compose(other) - - def __pow__(self, exponent: int): - return self.power(exponent) - - # Adjoint - - @abstractmethod - def adjoint(self): - """Returns the complex conjugate transpose (dagger) of self.adjoint - - Returns: - An operator equivalent to self's adjoint. - """ - - def __invert__(self): - """Overload unary `~` to return Operator adjoint.""" - return self.adjoint() diff --git a/qiskit/opflow/mixins/tensor.py b/qiskit/opflow/mixins/tensor.py deleted file mode 100644 index a138bda6009f..000000000000 --- a/qiskit/opflow/mixins/tensor.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The tensor mixin abstract base class.""" - -from abc import ABC, abstractmethod -from numbers import Integral -from qiskit.utils.deprecation import deprecate_func - - -class TensorMixin(ABC): - """Deprecated: The mixin class for tensor operations. - - This class overrides: - - ``^``, ``__xor__``, `__rxor__` -> :meth:`tensor` between two operators and - :meth:`tensorpower` with integer. - The following abstract methods must be implemented by subclasses: - - :meth:``tensor(self, other)`` - - :meth:``tensorpower(self, other: int)`` - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - pass - - def __xor__(self, other): - if isinstance(other, Integral): - return self.tensorpower(other) - else: - return self.tensor(other) - - def __rxor__(self, other): - # a hack to make (I^0)^Z work as intended. - if other == 1: - return self - else: - return other.tensor(self) - - @abstractmethod - def tensor(self, other): - r"""Return tensor product between self and other, overloaded by ``^``.""" - - @abstractmethod - def tensorpower(self, other: int): - r"""Return tensor product with self multiple times, overloaded by ``^``.""" diff --git a/qiskit/opflow/operator_base.py b/qiskit/opflow/operator_base.py deleted file mode 100644 index 818e0f3d960e..000000000000 --- a/qiskit/opflow/operator_base.py +++ /dev/null @@ -1,516 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OperatorBase Class""" - -import itertools -import warnings -from abc import ABC, abstractmethod -from copy import deepcopy -from typing import Dict, List, Optional, Set, Tuple, Union, cast - -import numpy as np -from scipy.sparse import csr_matrix, spmatrix - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.mixins import StarAlgebraMixin, TensorMixin -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class OperatorBase(StarAlgebraMixin, TensorMixin, ABC): - """Deprecated: A base class for all Operators: PrimitiveOps, StateFns, ListOps, etc. Operators are - defined as functions which take one complex binary function to another. These complex binary - functions are represented by StateFns, which are themselves a special class of Operators - taking only the ``Zero`` StateFn to the complex binary function they represent. - - Operators can be used to construct complicated functions and computation, and serve as the - building blocks for algorithms. - - """ - - # Indentation used in string representation of list operators - # Can be changed to use another indentation than two whitespaces - INDENTATION = " " - - _count = itertools.count() - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self) -> None: - super().__init__() - self._instance_id = next(self._count) - - @property - @abstractmethod - def settings(self) -> Dict: - """Return settings of this object in a dictionary. - - You can, for example, use this ``settings`` dictionary to serialize the - object in JSON format, if the JSON encoder you use supports all types in - the dictionary. - - Returns: - Object settings in a dictionary. - """ - raise NotImplementedError - - @property - def instance_id(self) -> int: - """Return the unique instance id.""" - return self._instance_id - - @property - @abstractmethod - def num_qubits(self) -> int: - r"""The number of qubits over which the Operator is defined. If - ``op.num_qubits == 5``, then ``op.eval('1' * 5)`` will be valid, but - ``op.eval('11')`` will not. - - Returns: - The number of qubits accepted by the Operator's underlying function. - """ - raise NotImplementedError - - @abstractmethod - def primitive_strings(self) -> Set[str]: - r"""Return a set of strings describing the primitives contained in the Operator. For - example, ``{'QuantumCircuit', 'Pauli'}``. For hierarchical Operators, such as ``ListOps``, - this can help illuminate the primitives represented in the various recursive levels, - and therefore which conversions can be applied. - - Returns: - A set of strings describing the primitives contained within the Operator. - """ - raise NotImplementedError - - @abstractmethod - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, "OperatorBase", Statevector] - ] = None, - ) -> Union["OperatorBase", complex]: - r""" - Evaluate the Operator's underlying function, either on a binary string or another Operator. - A square binary Operator can be defined as a function taking a binary function to another - binary function. This method returns the value of that function for a given StateFn or - binary string. For example, ``op.eval('0110').eval('1110')`` can be seen as querying the - Operator's matrix representation by row 6 and column 14, and will return the complex - value at those "indices." Similarly for a StateFn, ``op.eval('1011')`` will return the - complex value at row 11 of the vector representation of the StateFn, as all StateFns are - defined to be evaluated from Zero implicitly (i.e. it is as if ``.eval('0000')`` is already - called implicitly to always "indexing" from column 0). - - If ``front`` is None, the matrix-representation of the operator is returned. - - Args: - front: The bitstring, dict of bitstrings (with values being coefficients), or - StateFn to evaluated by the Operator's underlying function, or None. - - Returns: - The output of the Operator's evaluation function. If self is a ``StateFn``, the result - is a float or complex. If self is an Operator (``PrimitiveOp, ComposedOp, SummedOp, - EvolvedOp,`` etc.), the result is a StateFn. - If ``front`` is None, the matrix-representation of the operator is returned, which - is a ``MatrixOp`` for the operators and a ``VectorStateFn`` for state-functions. - If either self or front contain proper - ``ListOps`` (not ListOp subclasses), the result is an n-dimensional list of complex - or StateFn results, resulting from the recursive evaluation by each OperatorBase - in the ListOps. - - """ - raise NotImplementedError - - @abstractmethod - def reduce(self): - r"""Try collapsing the Operator structure, usually after some type of conversion, - e.g. trying to add Operators in a SummedOp or delete needless IGates in a CircuitOp. - If no reduction is available, just returns self. - - Returns: - The reduced ``OperatorBase``. - """ - raise NotImplementedError - - @abstractmethod - def to_matrix(self, massive: bool = False) -> np.ndarray: - r"""Return NumPy representation of the Operator. Represents the evaluation of - the Operator's underlying function on every combination of basis binary strings. - Warn if more than 16 qubits to force having to set ``massive=True`` if such a - large vector is desired. - - Returns: - The NumPy ``ndarray`` equivalent to this Operator. - """ - raise NotImplementedError - - @abstractmethod - def to_matrix_op(self, massive: bool = False) -> "OperatorBase": - """Returns a ``MatrixOp`` equivalent to this Operator.""" - raise NotImplementedError - - @abstractmethod - def to_circuit_op(self) -> "OperatorBase": - """Returns a ``CircuitOp`` equivalent to this Operator.""" - raise NotImplementedError - - def to_spmatrix(self) -> spmatrix: - r"""Return SciPy sparse matrix representation of the Operator. Represents the evaluation of - the Operator's underlying function on every combination of basis binary strings. - - Returns: - The SciPy ``spmatrix`` equivalent to this Operator. - """ - return csr_matrix(self.to_matrix()) - - def is_hermitian(self) -> bool: - """Return True if the operator is hermitian. - - Returns: Boolean value - """ - return (self.to_spmatrix() != self.to_spmatrix().getH()).nnz == 0 - - @staticmethod - def _indent(lines: str, indentation: str = INDENTATION) -> str: - """Indented representation to allow pretty representation of nested operators.""" - indented_str = indentation + lines.replace("\n", f"\n{indentation}") - if indented_str.endswith(f"\n{indentation}"): - indented_str = indented_str[: -len(indentation)] - return indented_str - - # Addition / Subtraction - - @abstractmethod - def add(self, other: "OperatorBase") -> "OperatorBase": - r"""Return Operator addition of self and other, overloaded by ``+``. - - Args: - other: An ``OperatorBase`` with the same number of qubits as self, and in the same - 'Operator', 'State function', or 'Measurement' category as self (i.e. the same type - of underlying function). - - Returns: - An ``OperatorBase`` equivalent to the sum of self and other. - """ - raise NotImplementedError - - # Negation - - def neg(self) -> "OperatorBase": - r"""Return the Operator's negation, effectively just multiplying by -1.0, - overloaded by ``-``. - - Returns: - An ``OperatorBase`` equivalent to the negation of self. - """ - return self.mul(-1.0) - - # Adjoint - - @abstractmethod - def adjoint(self) -> "OperatorBase": - r"""Return a new Operator equal to the Operator's adjoint (conjugate transpose), - overloaded by ``~``. For StateFns, this also turns the StateFn into a measurement. - - Returns: - An ``OperatorBase`` equivalent to the adjoint of self. - """ - raise NotImplementedError - - # Equality - - def __eq__(self, other: object) -> bool: - r"""Overload ``==`` operation to evaluate equality between Operators. - - Args: - other: The ``OperatorBase`` to compare to self. - - Returns: - A bool equal to the equality of self and other. - """ - if not isinstance(other, OperatorBase): - return NotImplemented - return self.equals(cast(OperatorBase, other)) - - @abstractmethod - def equals(self, other: "OperatorBase") -> bool: - r""" - Evaluate Equality between Operators, overloaded by ``==``. Only returns True if self and - other are of the same representation (e.g. a DictStateFn and CircuitStateFn will never be - equal, even if their vector representations are equal), their underlying primitives are - equal (this means for ListOps, OperatorStateFns, or EvolvedOps the equality is evaluated - recursively downwards), and their coefficients are equal. - - Args: - other: The ``OperatorBase`` to compare to self. - - Returns: - A bool equal to the equality of self and other. - - """ - raise NotImplementedError - - # Scalar Multiplication - - @abstractmethod - def mul(self, scalar: Union[complex, ParameterExpression]) -> "OperatorBase": - r""" - Returns the scalar multiplication of the Operator, overloaded by ``*``, including - support for Terra's ``Parameters``, which can be bound to values later (via - ``bind_parameters``). - - Args: - scalar: The real or complex scalar by which to multiply the Operator, - or the ``ParameterExpression`` to serve as a placeholder for a scalar factor. - - Returns: - An ``OperatorBase`` equivalent to product of self and scalar. - """ - raise NotImplementedError - - @abstractmethod - def tensor(self, other: "OperatorBase") -> "OperatorBase": - r"""Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing convention. - Meaning, X.tensor(Y) produces an X on qubit 0 and an Y on qubit 1, or X⨂Y, - but would produce a QuantumCircuit which looks like - - -[Y]- - -[X]- - - Because Terra prints circuits and results with qubit 0 at the end of the string - or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - raise NotImplementedError - - @abstractmethod - def tensorpower(self, other: int) -> Union["OperatorBase", int]: - r"""Return tensor product with self multiple times, overloaded by ``^``. - - Args: - other: The int number of times to tensor product self with itself via ``tensorpower``. - - Returns: - An ``OperatorBase`` equivalent to the tensorpower of self by other. - """ - raise NotImplementedError - - @property - @abstractmethod - def parameters(self): - r"""Return a set of Parameter objects contained in the Operator.""" - raise NotImplementedError - - # Utility functions for parameter binding - - @abstractmethod - def assign_parameters( - self, - param_dict: Dict[ - ParameterExpression, - Union[complex, ParameterExpression, List[Union[complex, ParameterExpression]]], - ], - ) -> "OperatorBase": - """Binds scalar values to any Terra ``Parameters`` in the coefficients or primitives of - the Operator, or substitutes one ``Parameter`` for another. This method differs from - Terra's ``assign_parameters`` in that it also supports lists of values to assign for a - give ``Parameter``, in which case self will be copied for each parameterization in the - binding list(s), and all the copies will be returned in an ``OpList``. If lists of - parameterizations are used, every ``Parameter`` in the param_dict must have the same - length list of parameterizations. - - Args: - param_dict: The dictionary of ``Parameters`` to replace, and values or lists of - values by which to replace them. - - Returns: - The ``OperatorBase`` with the ``Parameters`` in self replaced by the - values or ``Parameters`` in param_dict. If param_dict contains parameterization lists, - this ``OperatorBase`` is an ``OpList``. - """ - raise NotImplementedError - - @abstractmethod - def _expand_dim(self, num_qubits: int) -> "OperatorBase": - """Expands the operator with identity operator of dimension 2**num_qubits. - - Returns: - Operator corresponding to self.tensor(identity_operator), where dimension of identity - operator is 2 ** num_qubits. - """ - raise NotImplementedError - - @abstractmethod - def permute(self, permutation: List[int]) -> "OperatorBase": - """Permutes the qubits of the operator. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new OperatorBase containing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - raise NotImplementedError - - def bind_parameters( - self, - param_dict: Dict[ - ParameterExpression, - Union[complex, ParameterExpression, List[Union[complex, ParameterExpression]]], - ], - ) -> "OperatorBase": - r""" - Same as assign_parameters, but maintained for consistency with QuantumCircuit in - Terra (which has both assign_parameters and bind_parameters). - """ - return self.assign_parameters(param_dict) - - # Mostly copied from terra, but with list unrolling added: - @staticmethod - def _unroll_param_dict( - value_dict: Dict[Union[ParameterExpression, ParameterVector], Union[complex, List[complex]]] - ) -> Union[Dict[ParameterExpression, complex], List[Dict[ParameterExpression, complex]]]: - """Unrolls the ParameterVectors in a param_dict into separate Parameters, and unrolls - parameterization value lists into separate param_dicts without list nesting.""" - unrolled_value_dict = {} - for (param, value) in value_dict.items(): - if isinstance(param, ParameterExpression): - unrolled_value_dict[param] = value - if isinstance(param, ParameterVector) and isinstance(value, (list, np.ndarray)): - if not len(param) == len(value): - raise ValueError( - "ParameterVector {} has length {}, which differs from value list {} of " - "len {}".format(param, len(param), value, len(value)) - ) - unrolled_value_dict.update(zip(param, value)) - if isinstance(list(unrolled_value_dict.values())[0], list): - # check that all are same length - unrolled_value_dict_list = [] - try: - for i in range(len(list(unrolled_value_dict.values())[0])): # type: ignore - unrolled_value_dict_list.append( - OperatorBase._get_param_dict_for_index( - unrolled_value_dict, i # type: ignore - ) - ) - return unrolled_value_dict_list - except IndexError as ex: - raise OpflowError("Parameter binding lists must all be the same length.") from ex - return unrolled_value_dict # type: ignore - - @staticmethod - def _get_param_dict_for_index(unrolled_dict: Dict[ParameterExpression, List[complex]], i: int): - """Gets a single non-list-nested param_dict for a given list index from a nested one.""" - return {k: v[i] for (k, v) in unrolled_dict.items()} - - def _expand_shorter_operator_and_permute( - self, other: "OperatorBase", permutation: Optional[List[int]] = None - ) -> Tuple["OperatorBase", "OperatorBase"]: - if permutation is not None: - other = other.permute(permutation) - new_self = self - if not self.num_qubits == other.num_qubits: - # pylint: disable=cyclic-import - from .operator_globals import Zero - - if other == Zero: - # Zero is special - we'll expand it to the correct qubit number. - other = Zero.__class__("0" * self.num_qubits) - elif other.num_qubits < self.num_qubits: - other = other._expand_dim(self.num_qubits - other.num_qubits) - elif other.num_qubits > self.num_qubits: - new_self = self._expand_dim(other.num_qubits - self.num_qubits) - return new_self, other - - def copy(self) -> "OperatorBase": - """Return a deep copy of the Operator.""" - return deepcopy(self) - - # Composition - - @abstractmethod - def compose( - self, other: "OperatorBase", permutation: Optional[List[int]] = None, front: bool = False - ) -> "OperatorBase": - r"""Return Operator Composition between self and other (linear algebra-style: - A@B(x) = A(B(x))), overloaded by ``@``. - - Note: You must be conscious of Quantum Circuit vs. Linear Algebra ordering - conventions. Meaning, X.compose(Y) - produces an X∘Y on qubit 0, but would produce a QuantumCircuit which looks like - - -[Y]-[X]- - - Because Terra prints circuits with the initial state at the left side of the circuit. - - Args: - other: The ``OperatorBase`` with which to compose self. - permutation: ``List[int]`` which defines permutation on other operator. - front: If front==True, return ``other.compose(self)``. - - Returns: - An ``OperatorBase`` equivalent to the function composition of self and other. - """ - raise NotImplementedError - - @staticmethod - def _check_massive(method: str, matrix: bool, num_qubits: int, massive: bool) -> None: - """ - Checks if matrix or vector generated will be too large. - - Args: - method: Name of the calling method - matrix: True if object is matrix, otherwise vector - num_qubits: number of qubits - massive: True if it is ok to proceed with large matrix - - Raises: - ValueError: Massive is False and number of qubits is greater than 16 - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - if num_qubits > 16 and not massive and not algorithm_globals.massive: - dim = 2**num_qubits - if matrix: - obj_type = "matrix" - dimensions = f"{dim}x{dim}" - else: - obj_type = "vector" - dimensions = f"{dim}" - raise ValueError( - f"'{method}' will return an exponentially large {obj_type}, " - f"in this case '{dimensions}' elements. " - "Set algorithm_globals.massive=True or the method argument massive=True " - "if you want to proceed." - ) - - # Printing - - @abstractmethod - def __str__(self) -> str: - raise NotImplementedError diff --git a/qiskit/opflow/operator_globals.py b/qiskit/opflow/operator_globals.py deleted file mode 100644 index 73e2303b3bfb..000000000000 --- a/qiskit/opflow/operator_globals.py +++ /dev/null @@ -1,80 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Operator Globals -""" - -import warnings - -from qiskit.quantum_info import Pauli -from qiskit.circuit.library import CXGate, SGate, TGate, HGate, SwapGate, CZGate - -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.utils.deprecation import deprecate_func - -# Digits of precision when returning values from eval functions. Without rounding, 1e-17 or 1e-32 -# values often show up in place of 0, etc. -# Note: care needs to be taken in rounding otherwise some behavior may not be as expected. E.g -# evolution is used in QAOA variational form and difference when optimizing may be small - round -# the outcome too much and a small difference may become none and the optimizer gets stuck where -# otherwise it would not. -EVAL_SIG_DIGITS = 18 - -# Immutable convenience objects - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", -) -def make_immutable(obj): - r"""Deprecate\: Delete the __setattr__ property to make the object mostly immutable.""" - - # TODO figure out how to get correct error message - # def throw_immutability_exception(self, *args): - # raise OpflowError('Operator convenience globals are immutable.') - - obj.__setattr__ = None - return obj - - -# All the deprecation warnings triggered by these object creations correctly blame `qiskit.opflow` -# and so are not shown to users by default. However, since they are eagerly triggered at `import -# qiskit.opflow`, they obscure the one "true" warning of the import when downstream testing code is -# running with all warnings showing. The true warning that really needs attention becomes easy to -# overlook because there's so many that the downstream code didn't explicitly call. -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"qiskit\.opflow\.") - - # 1-Qubit Paulis - X = make_immutable(PauliOp(Pauli("X"))) - Y = make_immutable(PauliOp(Pauli("Y"))) - Z = make_immutable(PauliOp(Pauli("Z"))) - I = make_immutable(PauliOp(Pauli("I"))) - - # Clifford+T, and some other common non-parameterized gates - CX = make_immutable(CircuitOp(CXGate())) - S = make_immutable(CircuitOp(SGate())) - H = make_immutable(CircuitOp(HGate())) - T = make_immutable(CircuitOp(TGate())) - Swap = make_immutable(CircuitOp(SwapGate())) - CZ = make_immutable(CircuitOp(CZGate())) - - # 1-Qubit states - Zero = make_immutable(DictStateFn("0")) - One = make_immutable(DictStateFn("1")) - Plus = make_immutable(H.compose(Zero)) - Minus = make_immutable(H.compose(X).compose(Zero)) diff --git a/qiskit/opflow/primitive_ops/__init__.py b/qiskit/opflow/primitive_ops/__init__.py deleted file mode 100644 index 7e5cc72fad6b..000000000000 --- a/qiskit/opflow/primitive_ops/__init__.py +++ /dev/null @@ -1,79 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Primitive Operators (:mod:`qiskit.opflow.primitive_ops`) -======================================================== - -.. currentmodule:: qiskit.opflow.primitive_ops - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Operators are defined to be functions which take State functions to State functions. - -PrimitiveOps are the classes for representing basic Operators, backed by computational -Operator primitives from Terra. These classes (and inheritors) primarily serve to allow the -underlying primitives to "flow" - i.e. interoperability and adherence to the Operator -formalism - while the core computational logic mostly remains in the underlying primitives. -For example, we would not produce an interface in Terra in which -``QuantumCircuit1 + QuantumCircuit2`` equaled the Operator sum of the circuit -unitaries, rather than simply appending the circuits. However, within the Operator -flow summing the unitaries is the expected behavior. - -Note: - All mathematical methods are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - -Primitive Operators -------------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - PrimitiveOp - CircuitOp - MatrixOp - PauliOp - PauliSumOp - TaperedPauliSumOp - -Symmetries ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - Z2Symmetries -""" - -from .primitive_op import PrimitiveOp -from .pauli_op import PauliOp -from .matrix_op import MatrixOp -from .circuit_op import CircuitOp -from .pauli_sum_op import PauliSumOp -from .tapered_pauli_sum_op import TaperedPauliSumOp, Z2Symmetries - -__all__ = [ - "PrimitiveOp", - "PauliOp", - "MatrixOp", - "CircuitOp", - "PauliSumOp", - "TaperedPauliSumOp", - "Z2Symmetries", -] diff --git a/qiskit/opflow/primitive_ops/circuit_op.py b/qiskit/opflow/primitive_ops/circuit_op.py deleted file mode 100644 index c063ffd63cf4..000000000000 --- a/qiskit/opflow/primitive_ops/circuit_op.py +++ /dev/null @@ -1,252 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CircuitOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast -import numpy as np - -import qiskit -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library import IGate -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CircuitOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``QuantumCircuit`` module.""" - - primitive: QuantumCircuit - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[Instruction, QuantumCircuit], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The QuantumCircuit which defines the - behavior of the underlying function. - coeff: A coefficient multiplying the primitive - - Raises: - TypeError: Unsupported primitive, or primitive has ClassicalRegisters. - """ - if isinstance(primitive, Instruction): - qc = QuantumCircuit(primitive.num_qubits) - qc.append(primitive, qargs=range(primitive.num_qubits)) - primitive = qc - - if not isinstance(primitive, QuantumCircuit): - raise TypeError( - "CircuitOp can only be instantiated with " - "QuantumCircuit, not {}".format(type(primitive)) - ) - - if len(primitive.clbits) != 0: - raise TypeError("CircuitOp does not support QuantumCircuits with ClassicalRegisters.") - - super().__init__(primitive, coeff) - self._coeff = coeff - - def primitive_strings(self) -> Set[str]: - return {"QuantumCircuit"} - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, CircuitOp) and self.primitive == other.primitive: - return CircuitOp(self.primitive, coeff=self.coeff + other.coeff) - - # Covers all else. - # pylint: disable=cyclic-import - from ..list_ops.summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "CircuitOp": - return CircuitOp(self.primitive.inverse(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, CircuitOp) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - - def tensor(self, other: OperatorBase) -> Union["CircuitOp", TensoredOp]: - # pylint: disable=cyclic-import - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - other = other.to_circuit_op() - - if isinstance(other, CircuitOp): - new_qc = QuantumCircuit(self.num_qubits + other.num_qubits) - # NOTE!!! REVERSING QISKIT ENDIANNESS HERE - new_qc.append( - other.to_instruction(), qargs=new_qc.qubits[0 : other.primitive.num_qubits] - ) - new_qc.append(self.to_instruction(), qargs=new_qc.qubits[other.primitive.num_qubits :]) - new_qc = new_qc.decompose() - return CircuitOp(new_qc, coeff=self.coeff * other.coeff) - - return TensoredOp([self, other]) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(CircuitOp, new_self) - - if front: - return other.compose(new_self) - # pylint: disable=cyclic-import - from ..operator_globals import Zero - from ..state_fns import CircuitStateFn - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if other == Zero ^ new_self.num_qubits: - return CircuitStateFn(new_self.primitive, coeff=new_self.coeff) - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - other = other.to_circuit_op() - - if isinstance(other, (CircuitOp, CircuitStateFn)): - new_qc = other.primitive.compose(new_self.primitive) - if isinstance(other, CircuitStateFn): - return CircuitStateFn( - new_qc, is_measurement=other.is_measurement, coeff=new_self.coeff * other.coeff - ) - else: - return CircuitOp(new_qc, coeff=new_self.coeff * other.coeff) - - return super(CircuitOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - unitary = qiskit.quantum_info.Operator(self.to_circuit()).data - return unitary * self.coeff - - def __str__(self) -> str: - qc = self.to_circuit() - prim_str = str(qc.draw(output="text")) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - qc = self.primitive - if isinstance(self.coeff, ParameterExpression) or self.primitive.parameters: - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if isinstance(self.coeff, ParameterExpression) and self.coeff.parameters <= set( - unrolled_dict.keys() - ): - param_instersection = set(unrolled_dict.keys()) & self.coeff.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - param_value = float(self.coeff.bind(binds)) - # & is set intersection, check if any parameters in unrolled are present in circuit - # This is different from bind_parameters in Terra because they check for set equality - if set(unrolled_dict.keys()) & self.primitive.parameters: - # Only bind the params found in the circuit - param_instersection = set(unrolled_dict.keys()) & self.primitive.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - qc = self.to_circuit().assign_parameters(binds) - return self.__class__(qc, coeff=param_value) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - from ..state_fns import CircuitStateFn - from ..list_ops import ListOp - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # Composable with circuit - if isinstance(front, (PauliOp, CircuitOp, MatrixOp, CircuitStateFn)): - return self.compose(front) - - return self.to_matrix_op().eval(front) - - def to_circuit(self) -> QuantumCircuit: - return self.primitive - - def to_circuit_op(self) -> "CircuitOp": - return self - - def to_instruction(self) -> Instruction: - return self.primitive.to_instruction() - - # Warning - modifying immutable object!! - def reduce(self) -> OperatorBase: - if self.primitive.data is not None: - # Need to do this from the end because we're deleting items! - for i in reversed(range(len(self.primitive.data))): - gate = self.primitive.data[i].operation - # Check if Identity or empty instruction (need to check that type is exactly - # Instruction because some gates have lazy gate.definition population) - # pylint: disable=unidiomatic-typecheck - if isinstance(gate, IGate) or ( - type(gate) == Instruction and gate.definition.data == [] - ): - del self.primitive.data[i] - return self - - def _expand_dim(self, num_qubits: int) -> "CircuitOp": - return self.permute(list(range(num_qubits, num_qubits + self.num_qubits))) - - def permute(self, permutation: List[int]) -> "CircuitOp": - r""" - Permute the qubits of the circuit. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new CircuitOp containing the permuted circuit. - """ - new_qc = QuantumCircuit(max(permutation) + 1).compose(self.primitive, qubits=permutation) - return CircuitOp(new_qc, coeff=self.coeff) diff --git a/qiskit/opflow/primitive_ops/matrix_op.py b/qiskit/opflow/primitive_ops/matrix_op.py deleted file mode 100644 index c32ef4334268..000000000000 --- a/qiskit/opflow/primitive_ops/matrix_op.py +++ /dev/null @@ -1,237 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""MatrixOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast, get_type_hints -import numpy as np -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library.hamiltonian_gate import HamiltonianGate -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Operator, Statevector -from qiskit.utils import arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class MatrixOp(PrimitiveOp): - """Deprecated: Class for Operators represented by matrices, - backed by Terra's ``Operator`` module.""" - - primitive: Operator - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[list, np.ndarray, spmatrix, Operator], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The matrix-like object which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive - - Raises: - TypeError: invalid parameters. - ValueError: invalid parameters. - """ - primitive_orig = primitive - if isinstance(primitive, spmatrix): - primitive = primitive.toarray() - - if isinstance(primitive, (list, np.ndarray)): - primitive = Operator(primitive) - - if not isinstance(primitive, Operator): - type_hints = get_type_hints(MatrixOp.__init__).get("primitive") - valid_cls = [cls.__name__ for cls in type_hints.__args__] - raise TypeError( - f"MatrixOp can only be instantiated with {valid_cls}, " - f"not '{primitive_orig.__class__.__name__}'" - ) - - if primitive.input_dims() != primitive.output_dims(): - raise ValueError("Cannot handle non-square matrices yet.") - - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return {"Matrix"} - - @property - def num_qubits(self) -> int: - return len(self.primitive.input_dims()) - - def add(self, other: OperatorBase) -> Union["MatrixOp", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, MatrixOp) and self.primitive == other.primitive: - return MatrixOp(self.primitive, coeff=self.coeff + other.coeff) - - # Terra's Operator cannot handle ParameterExpressions - if ( - isinstance(other, MatrixOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return MatrixOp((self.coeff * self.primitive) + (other.coeff * other.primitive)) - - # Covers Paulis, Circuits, and all else. - return SummedOp([self, other]) - - def adjoint(self) -> "MatrixOp": - return MatrixOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, MatrixOp): - return False - if isinstance(self.coeff, ParameterExpression) ^ isinstance( - other.coeff, ParameterExpression - ): - return False - if isinstance(self.coeff, ParameterExpression) and isinstance( - other.coeff, ParameterExpression - ): - return self.coeff == other.coeff and self.primitive == other.primitive - return self.coeff * self.primitive == other.coeff * other.primitive - - def _expand_dim(self, num_qubits: int) -> "MatrixOp": - identity = np.identity(2**num_qubits, dtype=complex) - return MatrixOp(self.primitive.tensor(Operator(identity)), coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> Union["MatrixOp", TensoredOp]: - if isinstance(other, MatrixOp): - return MatrixOp(self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff) - - return TensoredOp([self, other]) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(MatrixOp, new_self) - - if front: - return other.compose(new_self) - if isinstance(other, MatrixOp): - return MatrixOp( - new_self.primitive.compose(other.primitive, front=True), - coeff=new_self.coeff * other.coeff, - ) - - return super(MatrixOp, new_self).compose(other) - - def permute(self, permutation: Optional[List[int]] = None) -> OperatorBase: - """Creates a new MatrixOp that acts on the permuted qubits. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new MatrixOp representing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - new_self = self - new_matrix_size = max(permutation) + 1 - - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - if self.num_qubits < new_matrix_size: - # pad the operator with identities - new_self = self._expand_dim(new_matrix_size - self.num_qubits) - qc = QuantumCircuit(new_matrix_size) - - # extend the indices to match the size of the new matrix - permutation = ( - list(filter(lambda x: x not in permutation, range(new_matrix_size))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - matrix = CircuitOp(qc).to_matrix() - return MatrixOp(matrix.transpose()) @ new_self @ MatrixOp(matrix) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - return self.primitive.data * self.coeff - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - # For other ops' eval we return self.to_matrix_op() here, but that's unnecessary here. - if front is None: - return self - - # pylint: disable=cyclic-import - from ..list_ops import ListOp - from ..state_fns import StateFn, VectorStateFn, OperatorStateFn - - new_front = None - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - new_front = front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - elif isinstance(front, OperatorStateFn): - new_front = OperatorStateFn(self.adjoint().compose(front.to_matrix_op()).compose(self)) - - elif isinstance(front, OperatorBase): - new_front = VectorStateFn(self.to_matrix() @ front.to_matrix()) - - return new_front - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H""" - return CircuitOp(HamiltonianGate(self.primitive, time=self.coeff)) - - # Op Conversions - - def to_matrix_op(self, massive: bool = False) -> "MatrixOp": - return self - - def to_instruction(self) -> Instruction: - return (self.coeff * self.primitive).to_instruction() diff --git a/qiskit/opflow/primitive_ops/pauli_op.py b/qiskit/opflow/primitive_ops/pauli_op.py deleted file mode 100644 index 51ec601297b7..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_op.py +++ /dev/null @@ -1,356 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PauliOp Class""" - -from math import pi -from typing import Dict, List, Optional, Set, Union, cast -import numpy as np -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library import RXGate, RYGate, RZGate, XGate, YGate, ZGate -from qiskit.circuit.library.generalized_gates import PauliGate -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.utils.deprecation import deprecate_func - - -class PauliOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``Pauli`` module.""" - - primitive: Pauli - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__(self, primitive: Pauli, coeff: Union[complex, ParameterExpression] = 1.0) -> None: - """ - Args: - primitive: The Pauli which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive. - - Raises: - TypeError: invalid parameters. - """ - if not isinstance(primitive, Pauli): - raise TypeError(f"PauliOp can only be instantiated with Paulis, not {type(primitive)}") - - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return {"Pauli"} - - @property - def num_qubits(self) -> int: - return len(self.primitive) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, PauliOp) and self.primitive == other.primitive: - return PauliOp(self.primitive, coeff=self.coeff + other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if ( - isinstance(other, PauliOp) - and isinstance(self.coeff, (int, float, complex)) - and isinstance(other.coeff, (int, float, complex)) - ): - return PauliSumOp( - SparsePauliOp(self.primitive, coeffs=[self.coeff]) - + SparsePauliOp(other.primitive, coeffs=[other.coeff]) - ) - - if isinstance(other, PauliSumOp) and isinstance(self.coeff, (int, float, complex)): - return PauliSumOp(SparsePauliOp(self.primitive, coeffs=[self.coeff])) + other - - return SummedOp([self, other]) - - def adjoint(self) -> "PauliOp": - return PauliOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if isinstance(other, PauliOp) and self.coeff == other.coeff: - return self.primitive == other.primitive - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - return other == self - - return False - - def _expand_dim(self, num_qubits: int) -> "PauliOp": - return PauliOp(Pauli("I" * num_qubits).expand(self.primitive), coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Both Paulis - if isinstance(other, PauliOp): - return PauliOp(self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - new_primitive = SparsePauliOp(self.primitive).tensor(other.primitive) - return PauliSumOp(new_primitive, coeff=self.coeff * other.coeff) - - from .circuit_op import CircuitOp - - if isinstance(other, CircuitOp): - return self.to_circuit_op().tensor(other) - - return TensoredOp([self, other]) - - def permute(self, permutation: List[int]) -> "PauliOp": - """Permutes the sequence of Pauli matrices. - - Args: - permutation: A list defining where each Pauli should be permuted. The Pauli at index - j of the primitive should be permuted to position permutation[j]. - - Returns: - A new PauliOp representing the permuted operator. For operator (X ^ Y ^ Z) and - indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - pauli_string = self.primitive.__str__() - length = max(permutation) + 1 # size of list must be +1 larger then its max index - new_pauli_list = ["I"] * length - if len(permutation) != self.num_qubits: - raise OpflowError( - "List of indices to permute must have the same size as Pauli Operator" - ) - for i, index in enumerate(permutation): - new_pauli_list[-index - 1] = pauli_string[-i - 1] - return PauliOp(Pauli("".join(new_pauli_list)), self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(PauliOp, new_self) - - if front: - return other.compose(new_self) - - # Both Paulis - if isinstance(other, PauliOp): - product = new_self.primitive.dot(other.primitive) - return PrimitiveOp(product, coeff=new_self.coeff * other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - return PauliSumOp( - SparsePauliOp(new_self.primitive).dot(other.primitive), - coeff=new_self.coeff * other.coeff, - ) - - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from .circuit_op import CircuitOp - - if isinstance(other, (CircuitOp, CircuitStateFn)): - return new_self.to_circuit_op().compose(other) - - return super(PauliOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - return self.primitive.to_matrix() * self.coeff - - def to_spmatrix(self) -> spmatrix: - """Returns SciPy sparse matrix representation of the Operator. - - Returns: - CSR sparse matrix representation of the Operator. - - Raises: - ValueError: invalid parameters. - """ - return self.primitive.to_matrix(sparse=True) * self.coeff - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self.to_matrix_op() - - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.state_fn import StateFn - from .circuit_op import CircuitOp - - new_front = None - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - new_front = front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - else: - - if self.num_qubits != front.num_qubits: - raise ValueError( - "eval does not support operands with differing numbers of qubits, " - "{} and {}, respectively.".format(self.num_qubits, front.num_qubits) - ) - - if isinstance(front, DictStateFn): - - new_dict: Dict[str, complex] = {} - corrected_x_bits = self.primitive.x[::-1] - corrected_z_bits = self.primitive.z[::-1] - - for bstr, v in front.primitive.items(): - bitstr = np.fromiter(bstr, dtype=int).astype(bool) - new_b_str = np.logical_xor(bitstr, corrected_x_bits) - new_str = "".join(map(str, 1 * new_b_str)) - z_factor = np.prod(1 - 2 * np.logical_and(bitstr, corrected_z_bits)) - y_factor = np.prod( - np.sqrt(1 - 2 * np.logical_and(corrected_x_bits, corrected_z_bits) + 0j) - ) - new_dict[new_str] = (v * z_factor * y_factor) + new_dict.get(new_str, 0) - # The coefficient consists of: - # 1. the coefficient of *this* PauliOp (self) - # 2. the coefficient of the evaluated DictStateFn (front) - # 3. AND acquires the phase of the internal primitive. This is necessary to - # ensure that (X @ Z) and (-iY) return the same result. - new_front = StateFn( - new_dict, coeff=self.coeff * front.coeff * (-1j) ** self.primitive.phase - ) - - elif isinstance(front, StateFn) and front.is_measurement: - raise ValueError("Operator composed with a measurement is undefined.") - - # Composable types with PauliOp - elif isinstance(front, (PauliOp, CircuitOp, CircuitStateFn)): - new_front = self.compose(front) - - # Covers VectorStateFn and OperatorStateFn - elif isinstance(front, StateFn): - new_front = self.to_matrix_op().eval(front.to_matrix_op()) - - return new_front - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H.""" - # if only one qubit is significant, we can perform the evolution - corrected_x = self.primitive.x[::-1] - corrected_z = self.primitive.z[::-1] - sig_qubits = np.logical_or(corrected_x, corrected_z) - if np.sum(sig_qubits) == 0: - # e^I is just a global phase, but we can keep track of it! Should we? - # For now, just return identity - return PauliOp(self.primitive) - if np.sum(sig_qubits) == 1: - sig_qubit_index = sig_qubits.tolist().index(True) - coeff = ( - np.real(self.coeff) - if not isinstance(self.coeff, ParameterExpression) - else self.coeff - ) - - from .circuit_op import CircuitOp - - # Y rotation - if corrected_x[sig_qubit_index] and corrected_z[sig_qubit_index]: - rot_op = CircuitOp(RYGate(2 * coeff)) - # Z rotation - elif corrected_z[sig_qubit_index]: - rot_op = CircuitOp(RZGate(2 * coeff)) - # X rotation - elif corrected_x[sig_qubit_index]: - rot_op = CircuitOp(RXGate(2 * coeff)) - - # pylint: disable=cyclic-import - from ..operator_globals import I - - left_pad = I.tensorpower(sig_qubit_index) - right_pad = I.tensorpower(self.num_qubits - sig_qubit_index - 1) - # Need to use overloaded operators here in case left_pad == I^0 - return left_pad ^ rot_op ^ right_pad - else: - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def to_circuit(self) -> QuantumCircuit: - - pauli = self.primitive.to_label()[-self.num_qubits :] - phase = self.primitive.phase - - qc = QuantumCircuit(self.num_qubits) - if pauli == "I" * self.num_qubits: - qc.global_phase = -phase * pi / 2 - return qc - - if self.num_qubits == 1: - if pauli != "I": - gate = {"X": XGate, "Y": YGate, "Z": ZGate}[pauli] - qc.append(gate(), [0]) - else: - gate = PauliGate(pauli) - qc.append(gate, range(self.num_qubits)) - - if not phase: - return qc - - qc.global_phase = -phase * pi / 2 - return qc - - def to_instruction(self) -> Instruction: - # TODO should we just do the following because performance of adding and deleting IGates - # doesn't matter? - # (Reduce removes extra IGates). - # return PrimitiveOp(self.primitive.to_instruction(), coeff=self.coeff).reduce() - - return self.primitive.to_instruction() - - def to_pauli_op(self, massive: bool = False) -> "PauliOp": - return self diff --git a/qiskit/opflow/primitive_ops/pauli_sum_op.py b/qiskit/opflow/primitive_ops/pauli_sum_op.py deleted file mode 100644 index 37815effb61a..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_sum_op.py +++ /dev/null @@ -1,464 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PauliSumOp Class""" - -from collections import defaultdict -from typing import Dict, List, Optional, Set, Tuple, Union, cast -import numpy as np -from scipy.sparse import spmatrix - -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.quantum_info.operators.custom_iterator import CustomIterator -from qiskit.utils.deprecation import deprecate_func - - -class PauliSumOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``SparsePauliOp`` class.""" - - primitive: SparsePauliOp - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: SparsePauliOp, - coeff: Union[complex, ParameterExpression] = 1.0, - grouping_type: str = "None", - ) -> None: - """ - Args: - primitive: The SparsePauliOp which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive. - grouping_type: The type of grouping. If None, the operator is not grouped. - - Raises: - TypeError: invalid parameters. - """ - if not isinstance(primitive, SparsePauliOp): - raise TypeError( - f"PauliSumOp can only be instantiated with SparsePauliOp, not {type(primitive)}" - ) - - super().__init__(primitive, coeff=coeff) - self._grouping_type = grouping_type - - def primitive_strings(self) -> Set[str]: - return {"SparsePauliOp"} - - @property - def grouping_type(self) -> str: - """ - Returns: Type of Grouping - """ - return self._grouping_type - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - @property - def coeffs(self): - """Return the Pauli coefficients.""" - return self.coeff * self.primitive.coeffs - - @property - def settings(self) -> Dict: - """Return operator settings.""" - data = super().settings - data.update({"grouping_type": self._grouping_type}) - return data - - def matrix_iter(self, sparse=False): - """Return a matrix representation iterator. - - This is a lazy iterator that converts each term in the PauliSumOp - into a matrix as it is used. To convert to a single matrix use the - :meth:`to_matrix` method. - - Args: - sparse (bool): optionally return sparse CSR matrices if True, - otherwise return Numpy array matrices - (Default: False) - - Returns: - MatrixIterator: matrix iterator object for the PauliSumOp. - """ - - class MatrixIterator(CustomIterator): - """Matrix representation iteration and item access.""" - - def __repr__(self): - return f"" - - def __getitem__(self, key): - sumopcoeff = self.obj.coeff * self.obj.primitive.coeffs[key] - return sumopcoeff * self.obj.primitive.paulis[key].to_matrix(sparse=sparse) - - return MatrixIterator(self) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - f"Sum of operators with different numbers of qubits, {self.num_qubits} and " - f"{other.num_qubits}, is not well defined" - ) - - if ( - isinstance(other, PauliSumOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return PauliSumOp(self.coeff * self.primitive + other.coeff * other.primitive, coeff=1) - - if ( - isinstance(other, PauliOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return PauliSumOp( - self.coeff * self.primitive + other.coeff * SparsePauliOp(other.primitive) - ) - - return SummedOp([self, other]) - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if isinstance(scalar, (int, float, complex)) and scalar != 0: - return PauliSumOp(scalar * self.primitive, coeff=self.coeff) - - return PauliSumOp(self.primitive, coeff=self.coeff * scalar) - - def adjoint(self) -> "PauliSumOp": - return PauliSumOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - self_reduced, other_reduced = self.reduce(), other.reduce() - - if isinstance(other_reduced, PauliOp): - other_reduced = PauliSumOp( - SparsePauliOp(other_reduced.primitive, coeffs=[other_reduced.coeff]) - ) - - if not isinstance(other_reduced, PauliSumOp): - return False - - if isinstance(self_reduced.coeff, ParameterExpression) or isinstance( - other_reduced.coeff, ParameterExpression - ): - return self_reduced.coeff == other_reduced.coeff and self_reduced.primitive.equiv( - other_reduced.primitive - ) - return len(self_reduced) == len(other_reduced) and self_reduced.primitive.equiv( - other_reduced.primitive - ) - - def _expand_dim(self, num_qubits: int) -> "PauliSumOp": - return PauliSumOp( - self.primitive.tensor(SparsePauliOp(Pauli("I" * num_qubits))), - coeff=self.coeff, - ) - - def tensor(self, other: OperatorBase) -> Union["PauliSumOp", TensoredOp]: - if isinstance(other, PauliSumOp): - return PauliSumOp( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - ) - if isinstance(other, PauliOp): - return PauliSumOp( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - ) - - return TensoredOp([self, other]) - - def permute(self, permutation: List[int]) -> "PauliSumOp": - """Permutes the sequence of ``PauliSumOp``. - - Args: - permutation: A list defining where each Pauli should be permuted. The Pauli at index - j of the primitive should be permuted to position permutation[j]. - - Returns: - A new PauliSumOp representing the permuted operator. For operator (X ^ Y ^ Z) and - indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - set_perm = set(permutation) - if len(set_perm) != len(permutation) or any(index < 0 for index in set_perm): - raise OpflowError(f"List {permutation} is not a permutation.") - - if len(permutation) != self.num_qubits: - raise OpflowError( - "List of indices to permute must have the same size as Pauli Operator" - ) - length = max(permutation) + 1 - - if length > self.num_qubits: - spop = self.primitive.tensor(SparsePauliOp(Pauli("I" * (length - self.num_qubits)))) - else: - spop = self.primitive.copy() - - permutation = [i for i in range(length) if i not in permutation] + permutation - permu_arr = np.arange(length)[np.argsort(permutation)] - spop.paulis.x = spop.paulis.x[:, permu_arr] - spop.paulis.z = spop.paulis.z[:, permu_arr] - return PauliSumOp(spop, self.coeff) - - def compose( - self, - other: OperatorBase, - permutation: Optional[List[int]] = None, - front: bool = False, - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(PauliSumOp, new_self) - - if front: - return other.compose(new_self) - # If self is identity, just return other. - if not np.any(np.logical_or(new_self.primitive.paulis.x, new_self.primitive.paulis.z)): - return other * new_self.coeff * sum(new_self.primitive.coeffs) - - # Both PauliSumOps - if isinstance(other, PauliSumOp): - return PauliSumOp( - new_self.primitive.dot(other.primitive), - coeff=new_self.coeff * other.coeff, - ) - if isinstance(other, PauliOp): - other_primitive = SparsePauliOp(other.primitive) - return PauliSumOp( - new_self.primitive.dot(other_primitive), - coeff=new_self.coeff * other.coeff, - ) - - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from .circuit_op import CircuitOp - - if isinstance(other, (CircuitOp, CircuitStateFn)): - pauli_op = cast(Union[PauliOp, SummedOp], new_self.to_pauli_op()) - return pauli_op.to_circuit_op().compose(other) - - return super(PauliSumOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - if isinstance(self.coeff, ParameterExpression): - return (self.primitive.to_matrix(sparse=True)).toarray() * self.coeff - return (self.primitive.to_matrix(sparse=True) * self.coeff).toarray() - - def __str__(self) -> str: - def format_sign(x): - return x.real if np.isreal(x) else x - - def format_number(x): - x = format_sign(x) - if isinstance(x, (int, float)) and x < 0: - return f"- {-x}" - return f"+ {x}" - - indent = "" if self.coeff == 1 else " " - prim_list = self.primitive.to_list() - if prim_list: - first = prim_list[0] - if isinstance(first[1], (int, float)) and first[1] < 0: - main_string = indent + f"- {-first[1].real} * {first[0]}" - else: - main_string = indent + f"{format_sign(first[1])} * {first[0]}" - - main_string += "".join([f"\n{indent}{format_number(c)} * {p}" for p, c in prim_list[1:]]) - return f"{main_string}" if self.coeff == 1 else f"{self.coeff} * (\n{main_string}\n)" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self.to_matrix_op() - - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.state_fn import StateFn - from .circuit_op import CircuitOp - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - else: - - if self.num_qubits != front.num_qubits: - raise ValueError( - "eval does not support operands with differing numbers of qubits, " - "{} and {}, respectively.".format(self.num_qubits, front.num_qubits) - ) - - if isinstance(front, DictStateFn): - new_dict: Dict[str, int] = defaultdict(int) - corrected_x_bits = self.primitive.paulis.x[:, ::-1] - corrected_z_bits = self.primitive.paulis.z[:, ::-1] - coeffs = self.primitive.coeffs - for bstr, v in front.primitive.items(): - bitstr = np.fromiter(bstr, dtype=int).astype(bool) - new_b_str = np.logical_xor(bitstr, corrected_x_bits) - new_str = ["".join([str(b) for b in bs]) for bs in new_b_str.astype(int)] - z_factor = np.prod(1 - 2 * np.logical_and(bitstr, corrected_z_bits), axis=1) - y_factor = np.prod( - np.sqrt(1 - 2 * np.logical_and(corrected_x_bits, corrected_z_bits) + 0j), - axis=1, - ) - for i, n_str in enumerate(new_str): - new_dict[n_str] += v * z_factor[i] * y_factor[i] * coeffs[i] - return DictStateFn(new_dict, coeff=self.coeff * front.coeff) - - elif isinstance(front, StateFn) and front.is_measurement: - raise ValueError("Operator composed with a measurement is undefined.") - - # Composable types with PauliOp - elif isinstance(front, (PauliSumOp, PauliOp, CircuitOp, CircuitStateFn)): - return self.compose(front).eval() - - # Covers VectorStateFn and OperatorStateFn - front = cast(StateFn, front) - return self.to_matrix_op().eval(front.to_matrix_op()) - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H.""" - # TODO: optimize for some special cases - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def to_instruction(self) -> Instruction: - return self.to_matrix_op().to_circuit().to_instruction() # type: ignore - - def to_pauli_op(self, massive: bool = False) -> Union[PauliOp, SummedOp]: - def to_native(x): - return x.item() if isinstance(x, np.generic) else x - - if len(self.primitive) == 1: - return PauliOp( - Pauli((self.primitive.paulis.z[0], self.primitive.paulis.x[0])), - to_native(np.real_if_close(self.primitive.coeffs[0])) * self.coeff, - ) - coeffs = np.real_if_close(self.primitive.coeffs) - return SummedOp( - [ - PauliOp(pauli, to_native(coeff)) - for pauli, coeff in zip(self.primitive.paulis, coeffs) - ], - coeff=self.coeff, - ) - - def __getitem__(self, offset: Union[int, slice]) -> "PauliSumOp": - """Allows array-indexing style access to the ``PauliSumOp``. - - Args: - offset: The index of ``PauliSumOp``. - - Returns: - The ``PauliSumOp`` at index ``offset``, - """ - return PauliSumOp(self.primitive[offset], self.coeff) - - def __iter__(self): - for i in range(len(self)): - yield self[i] - - def __len__(self) -> int: - """Length of ``SparsePauliOp``. - - Returns: - An int equal to the length of SparsePauliOp. - """ - return len(self.primitive) - - def reduce(self, atol: Optional[float] = None, rtol: Optional[float] = None) -> "PauliSumOp": - """Simplify the primitive ``SparsePauliOp``. - - Args: - atol: Absolute tolerance for checking if coefficients are zero (Default: 1e-8). - rtol: Relative tolerance for checking if coefficients are zero (Default: 1e-5). - - Returns: - The simplified ``PauliSumOp``. - """ - if isinstance(self.coeff, (int, float, complex)): - primitive = self.coeff * self.primitive - return PauliSumOp(primitive.simplify(atol=atol, rtol=rtol)) - return PauliSumOp(self.primitive.simplify(atol=atol, rtol=rtol), self.coeff) - - def to_spmatrix(self) -> spmatrix: - """Returns SciPy sparse matrix representation of the ``PauliSumOp``. - - Returns: - CSR sparse matrix representation of the ``PauliSumOp``. - - Raises: - ValueError: invalid parameters. - """ - return self.primitive.to_matrix(sparse=True) * self.coeff - - @classmethod - def from_list( - cls, - pauli_list: List[Tuple[str, Union[complex, ParameterExpression]]], - coeff: Union[complex, ParameterExpression] = 1.0, - dtype: type = complex, - ) -> "PauliSumOp": - """Construct from a pauli_list with the form [(pauli_str, coeffs)] - - Args: - pauli_list: A list of Tuple of pauli_str and coefficient. - coeff: A coefficient multiplying the primitive. - dtype: The dtype to use to construct the internal SparsePauliOp. - Defaults to ``complex``. - - Returns: - The PauliSumOp constructed from the pauli_list. - """ - return cls(SparsePauliOp.from_list(pauli_list, dtype=dtype), coeff=coeff) - - def is_zero(self) -> bool: - """ - Return this operator is zero operator or not. - """ - op = self.reduce() - primitive: SparsePauliOp = op.primitive - return op.coeff == 1 and len(op) == 1 and primitive.coeffs[0] == 0 - - def is_hermitian(self): - return np.isreal(self.coeffs).all() and np.all(self.primitive.paulis.phase == 0) diff --git a/qiskit/opflow/primitive_ops/primitive_op.py b/qiskit/opflow/primitive_ops/primitive_op.py deleted file mode 100644 index eb31a1fa461b..000000000000 --- a/qiskit/opflow/primitive_ops/primitive_op.py +++ /dev/null @@ -1,324 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""PrimitiveOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np -import scipy.linalg -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Operator, Pauli, SparsePauliOp, Statevector -from qiskit.utils.deprecation import deprecate_func - - -class PrimitiveOp(OperatorBase): - r""" - Deprecated: A class for representing basic Operators, backed by Operator primitives from - Terra. This class (and inheritors) primarily serves to allow the underlying - primitives to "flow" - i.e. interoperability and adherence to the Operator formalism - - while the core computational logic mostly remains in the underlying primitives. - For example, we would not produce an interface in Terra in which - ``QuantumCircuit1 + QuantumCircuit2`` equaled the Operator sum of the circuit - unitaries, rather than simply appending the circuits. However, within the Operator - flow summing the unitaries is the expected behavior. - - Note that all mathematical methods are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - - """ - - def __init_subclass__(cls): - cls.__new__ = lambda cls, *args, **kwargs: super().__new__(cls) - - @staticmethod - # pylint: disable=unused-argument - def __new__( - cls, - primitive: Union[ - Instruction, QuantumCircuit, List, np.ndarray, spmatrix, Operator, Pauli, SparsePauliOp - ], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> "PrimitiveOp": - """A factory method to produce the correct type of PrimitiveOp subclass - based on the primitive passed in. Primitive and coeff arguments are passed into - subclass's init() as-is automatically by new(). - - Args: - primitive: The operator primitive being wrapped. - coeff: A coefficient multiplying the primitive. - - Returns: - The appropriate PrimitiveOp subclass for ``primitive``. - - Raises: - TypeError: Unsupported primitive type passed. - """ - # pylint: disable=cyclic-import - if isinstance(primitive, (Instruction, QuantumCircuit)): - from .circuit_op import CircuitOp - - return super().__new__(CircuitOp) - - if isinstance(primitive, (list, np.ndarray, spmatrix, Operator)): - from .matrix_op import MatrixOp - - return super().__new__(MatrixOp) - - if isinstance(primitive, Pauli): - from .pauli_op import PauliOp - - return super().__new__(PauliOp) - - if isinstance(primitive, SparsePauliOp): - from .pauli_sum_op import PauliSumOp - - return super().__new__(PauliSumOp) - - raise TypeError( - "Unsupported primitive type {} passed into PrimitiveOp " - "factory constructor".format(type(primitive)) - ) - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[QuantumCircuit, Operator, Pauli, SparsePauliOp, OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The operator primitive being wrapped. - coeff: A coefficient multiplying the primitive. - """ - super().__init__() - self._primitive = primitive - self._coeff = coeff - - @property - def primitive(self) -> Union[QuantumCircuit, Operator, Pauli, SparsePauliOp, OperatorBase]: - """The primitive defining the underlying function of the Operator. - - Returns: - The primitive object. - """ - return self._primitive - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """ - The scalar coefficient multiplying the Operator. - - Returns: - The coefficient. - """ - return self._coeff - - @property - def num_qubits(self) -> int: - raise NotImplementedError - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return {"primitive": self._primitive, "coeff": self._coeff} - - def primitive_strings(self) -> Set[str]: - raise NotImplementedError - - def add(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def adjoint(self) -> OperatorBase: - raise NotImplementedError - - def equals(self, other: OperatorBase) -> bool: - raise NotImplementedError - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - # Need to return self.__class__ in case the object is one of the inherited OpPrimitives - return self.__class__(self.primitive, coeff=self.coeff * scalar) - - def tensor(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - # Hack to make Z^(I^0) work as intended. - if other == 0: - return 1 - if not isinstance(other, int) or other < 0: - raise TypeError("Tensorpower can only take positive int arguments") - temp = PrimitiveOp(self.primitive, coeff=self.coeff) # type: OperatorBase - for _ in range(other - 1): - temp = temp.tensor(self) - return temp - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - # pylint: disable=cyclic-import - from ..list_ops.composed_op import ComposedOp - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - if isinstance(other, ComposedOp): - comp_with_first = new_self.compose(other.oplist[0]) - if not isinstance(comp_with_first, ComposedOp): - new_oplist = [comp_with_first] + other.oplist[1:] - return ComposedOp(new_oplist, coeff=other.coeff) - return ComposedOp([new_self] + other.oplist, coeff=other.coeff) - - return ComposedOp([new_self, other]) - - def _expand_dim(self, num_qubits: int) -> OperatorBase: - raise NotImplementedError - - def permute(self, permutation: List[int]) -> OperatorBase: - raise NotImplementedError - - def exp_i(self) -> OperatorBase: - """Return Operator exponentiation, equaling e^(-i * op)""" - # pylint: disable=cyclic-import - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def log_i(self, massive: bool = False) -> OperatorBase: - """Return a ``MatrixOp`` equivalent to log(H)/-i for this operator H. This - function is the effective inverse of exp_i, equivalent to finding the Hermitian - Operator which produces self when exponentiated.""" - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .matrix_op import MatrixOp - - return MatrixOp( - np.around( - scipy.linalg.logm(self.to_matrix(massive=massive)) / -1j, decimals=EVAL_SIG_DIGITS - ) - ) - - def __str__(self) -> str: - raise NotImplementedError - - def __repr__(self) -> str: - return f"{type(self).__name__}({repr(self.primitive)}, coeff={self.coeff})" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - raise NotImplementedError - - @property - def parameters(self): - params = set() - if isinstance(self.primitive, (OperatorBase, QuantumCircuit)): - params.update(self.primitive.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = complex(self.coeff.bind(binds)) - if abs(param_value.imag) == 0: - param_value = param_value.real - return self.__class__(self.primitive, coeff=param_value) - - # Nothing to collapse here. - def reduce(self) -> OperatorBase: - return self - - def to_matrix(self, massive: bool = False) -> np.ndarray: - raise NotImplementedError - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - """Returns a ``MatrixOp`` equivalent to this Operator.""" - coeff = self.coeff - op = self.copy() - op._coeff = 1 - prim_mat = op.to_matrix(massive=massive) - from .matrix_op import MatrixOp - - return MatrixOp(prim_mat, coeff=coeff) - - def to_instruction(self) -> Instruction: - """Returns an ``Instruction`` equivalent to this Operator.""" - raise NotImplementedError - - def to_circuit(self) -> QuantumCircuit: - """Returns a ``QuantumCircuit`` equivalent to this Operator.""" - qc = QuantumCircuit(self.num_qubits) - qc.append(self.to_instruction(), qargs=range(self.primitive.num_qubits)) - return qc.decompose() - - def to_circuit_op(self) -> OperatorBase: - """Returns a ``CircuitOp`` equivalent to this Operator.""" - from .circuit_op import CircuitOp - - if self.coeff == 0: - return CircuitOp(QuantumCircuit(self.num_qubits), coeff=0) - return CircuitOp(self.to_circuit(), coeff=self.coeff) - - def to_pauli_op(self, massive: bool = False) -> OperatorBase: - """Returns a sum of ``PauliOp`` s equivalent to this Operator.""" - # pylint: disable=cyclic-import - from .matrix_op import MatrixOp - - mat_op = cast(MatrixOp, self.to_matrix_op(massive=massive)) - sparse_pauli = SparsePauliOp.from_operator(mat_op.primitive) - if not sparse_pauli.to_list(): - from ..operator_globals import I - - return (I ^ self.num_qubits) * 0.0 - from .pauli_op import PauliOp - - if len(sparse_pauli) == 1: - label, coeff = sparse_pauli.to_list()[0] - coeff = coeff.real if np.isreal(coeff) else coeff - return PauliOp(Pauli(label), coeff * self.coeff) - - from ..list_ops.summed_op import SummedOp - - return SummedOp( - [ - PrimitiveOp( - Pauli(label), - coeff.real if coeff == coeff.real else coeff, - ) - for (label, coeff) in sparse_pauli.to_list() - ], - self.coeff, - ) diff --git a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py b/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py deleted file mode 100644 index f603a9fbb364..000000000000 --- a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py +++ /dev/null @@ -1,590 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""TaperedPauliSumOp Class and Z2Symmetries""" - -import itertools -import logging -from copy import deepcopy -from typing import Dict, List, Optional, Union, cast - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.utils import commutator -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class TaperedPauliSumOp(PauliSumOp): - """Deprecated: Class for PauliSumOp after tapering""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: SparsePauliOp, - z2_symmetries: "Z2Symmetries", - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The SparsePauliOp which defines the behavior of the underlying function. - z2_symmetries: Z2 symmetries which the Operator has. - coeff: A coefficient multiplying the primitive. - - Raises: - TypeError: invalid parameters. - """ - super().__init__(primitive, coeff) - if not isinstance(z2_symmetries, Z2Symmetries): - raise TypeError( - f"Argument parameter z2_symmetries must be Z2Symmetries, not {type(z2_symmetries)}" - ) - self._z2_symmetries = z2_symmetries - - @property - def z2_symmetries(self) -> "Z2Symmetries": - """ - Z2 symmetries which the Operator has. - - Returns: - The Z2 Symmetries. - """ - return self._z2_symmetries - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return { - "primitive": self._primitive, - "z2_symmetries": self._z2_symmetries, - "coeff": self._coeff, - } - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - pauli_sum = PauliSumOp(self.primitive, self.coeff) - return pauli_sum.assign_parameters(param_dict) - - -class Z2Symmetries: - """Deprecated: Z2 Symmetries""" - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - symmetries: List[Pauli], - sq_paulis: List[Pauli], - sq_list: List[int], - tapering_values: Optional[List[int]] = None, - tol: float = 1e-14, - ): - """ - Args: - symmetries: the list of Pauli objects representing the Z_2 symmetries - sq_paulis: the list of single - qubit Pauli objects to construct the - Clifford operators - sq_list: the list of support of the single-qubit Pauli objects used to build - the Clifford operators - tapering_values: values determines the sector. - tol: Tolerance threshold for ignoring real and complex parts of a coefficient. - - Raises: - OpflowError: Invalid paulis - """ - if len(symmetries) != len(sq_paulis): - raise OpflowError( - "Number of Z2 symmetries has to be the same as number of single-qubit pauli x." - ) - - if len(sq_paulis) != len(sq_list): - raise OpflowError( - "Number of single-qubit pauli x has to be the same as length of single-qubit list." - ) - - if tapering_values is not None: - if len(sq_list) != len(tapering_values): - raise OpflowError( - "The length of single-qubit list has " - "to be the same as length of tapering values." - ) - - self._symmetries = symmetries - self._sq_paulis = sq_paulis - self._sq_list = sq_list - self._tapering_values = tapering_values - self._tol = tol - - @property - def tol(self): - """Tolerance threshold for ignoring real and complex parts of a coefficient.""" - return self._tol - - @tol.setter - def tol(self, value): - """Set the tolerance threshold for ignoring real and complex parts of a coefficient.""" - self._tol = value - - @property - def symmetries(self): - """return symmetries""" - return self._symmetries - - @property - def sq_paulis(self): - """returns sq paulis""" - return self._sq_paulis - - @property - def cliffords(self) -> List[PauliSumOp]: - """ - Get clifford operators, build based on symmetries and single-qubit X. - Returns: - a list of unitaries used to diagonalize the Hamiltonian. - """ - cliffords = [ - (PauliOp(pauli_symm) + PauliOp(sq_pauli)) / np.sqrt(2) - for pauli_symm, sq_pauli in zip(self._symmetries, self._sq_paulis) - ] - return cliffords - - @property - def sq_list(self): - """returns sq list""" - return self._sq_list - - @property - def tapering_values(self): - """returns tapering values""" - return self._tapering_values - - @tapering_values.setter - def tapering_values(self, new_value): - """set tapering values""" - self._tapering_values = new_value - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return { - "symmetries": self._symmetries, - "sq_paulis": self._sq_paulis, - "sq_list": self._sq_list, - "tapering_values": self._tapering_values, - } - - def __str__(self): - ret = ["Z2 symmetries:"] - ret.append("Symmetries:") - for symmetry in self._symmetries: - ret.append(symmetry.to_label()) - ret.append("Single-Qubit Pauli X:") - for x in self._sq_paulis: - ret.append(x.to_label()) - ret.append("Cliffords:") - for c in self.cliffords: - ret.append(str(c)) - ret.append("Qubit index:") - ret.append(str(self._sq_list)) - ret.append("Tapering values:") - if self._tapering_values is None: - possible_values = [ - str(list(coeff)) for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) - ] - possible_values = ", ".join(x for x in possible_values) - ret.append(" - Possible values: " + possible_values) - else: - ret.append(str(self._tapering_values)) - - ret = "\n".join(ret) - return ret - - def copy(self) -> "Z2Symmetries": - """ - Get a copy of self. - Returns: - copy - """ - return deepcopy(self) - - def is_empty(self) -> bool: - """ - Check the z2_symmetries is empty or not. - Returns: - Empty or not - """ - return self._symmetries == [] or self._sq_paulis == [] or self._sq_list == [] - - # pylint: disable=invalid-name - @classmethod - def find_Z2_symmetries(cls, operator: PauliSumOp) -> "Z2Symmetries": - """ - Finds Z2 Pauli-type symmetries of an Operator. - - Returns: - a z2_symmetries object contains symmetries, single-qubit X, single-qubit list. - """ - pauli_symmetries = [] - sq_paulis = [] - sq_list = [] - - stacked_paulis = [] - - if operator.is_zero(): - logger.info("Operator is empty.") - return cls([], [], [], None) - - for pauli in operator: - stacked_paulis.append( - np.concatenate( - (pauli.primitive.paulis.x[0], pauli.primitive.paulis.z[0]), axis=0 - ).astype(int) - ) - - stacked_matrix = np.array(np.stack(stacked_paulis)) - symmetries = _kernel_F2(stacked_matrix) - - if not symmetries: - logger.info("No symmetry is found.") - return cls([], [], [], None) - - stacked_symmetries = np.stack(symmetries) - symm_shape = stacked_symmetries.shape - - for row in range(symm_shape[0]): - - pauli_symmetries.append( - Pauli( - ( - stacked_symmetries[row, : symm_shape[1] // 2], - stacked_symmetries[row, symm_shape[1] // 2 :], - ) - ) - ) - - stacked_symm_del = np.delete(stacked_symmetries, row, axis=0) - for col in range(symm_shape[1] // 2): - # case symmetries other than one at (row) have Z or I on col qubit - Z_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - stacked_symm_del[symm_idx, col] == 0 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] in (0, 1) - ): - Z_or_I = False - if Z_or_I: - if ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 0 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = False - sq_paulis[row].x[col] = True - sq_list.append(col) - break - - # case symmetries other than one at (row) have X or I on col qubit - X_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - stacked_symm_del[symm_idx, col] in (0, 1) - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 0 - ): - X_or_I = False - if X_or_I: - if ( - stacked_symmetries[row, col] == 0 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = True - sq_paulis[row].x[col] = False - sq_list.append(col) - break - - # case symmetries other than one at (row) have Y or I on col qubit - Y_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - ( - stacked_symm_del[symm_idx, col] == 1 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 1 - ) - or ( - stacked_symm_del[symm_idx, col] == 0 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 0 - ) - ): - Y_or_I = False - if Y_or_I: - if ( - stacked_symmetries[row, col] == 0 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 0 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = True - sq_paulis[row].x[col] = True - sq_list.append(col) - break - - return cls(pauli_symmetries, sq_paulis, sq_list, None) - - def convert_clifford(self, operator: PauliSumOp) -> OperatorBase: - """This method operates the first part of the tapering. - It converts the operator by composing it with the clifford unitaries defined in the current - symmetry. - - Args: - operator: to-be-tapered operator - - Returns: - :class:`PauliSumOp` corresponding to the converted operator. - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - - if not operator.is_zero(): - for clifford in self.cliffords: - operator = cast(PauliSumOp, clifford @ operator @ clifford) - operator = operator.reduce(atol=0) - - return operator - - def taper_clifford(self, operator: PauliSumOp) -> OperatorBase: - """This method operates the second part of the tapering. - This function assumes that the input operators have already been transformed using - :meth:`convert_clifford`. The redundant qubits due to the symmetries are dropped and - replaced by their two possible eigenvalues. - The `tapering_values` will be stored into the resulted operator for a record. - - Args: - operator: Partially tapered operator resulting from a call to :meth:`convert_clifford` - - Returns: - If tapering_values is None: [:class:`PauliSumOp`]; otherwise, :class:`PauliSumOp` - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - # If the operator is zero then we can skip the following. We still need to taper the - # operator to reduce its size i.e. the number of qubits so for example 0*"IIII" could - # taper to 0*"II" when symmetries remove two qubits. - if self._tapering_values is None: - tapered_ops_list = [ - self._taper(operator, list(coeff)) - for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) - ] - tapered_ops: OperatorBase = ListOp(tapered_ops_list) - else: - tapered_ops = self._taper(operator, self._tapering_values) - - return tapered_ops - - def taper(self, operator: PauliSumOp) -> OperatorBase: - """ - Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`. - The `tapering_values` will be stored into the resulted operator for a record. - - The tapering is a two-step algorithm which first converts the operator into a - :class:`PauliSumOp` with same eigenvalues but where some qubits are only acted upon - with the Pauli operators I or X. - The number M of these redundant qubits is equal to the number M of identified symmetries. - - The second step of the reduction consists in replacing these qubits with the possible - eigenvalues of the corresponding Pauli X, giving 2^M new operators with M less qubits. - If an eigenvalue sector was previously identified for the solution, then this reduces to - 1 new operator with M less qubits. - - Args: - operator: the to-be-tapered operator - - Returns: - If tapering_values is None: [:class:`PauliSumOp`]; otherwise, :class:`PauliSumOp` - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - - converted_ops = self.convert_clifford(operator) - tapered_ops = self.taper_clifford(converted_ops) - - return tapered_ops - - def _taper(self, op: PauliSumOp, curr_tapering_values: List[int]) -> OperatorBase: - pauli_list = [] - for pauli_term in op: - coeff_out = pauli_term.primitive.coeffs[0] - for idx, qubit_idx in enumerate(self._sq_list): - if ( - pauli_term.primitive.paulis.z[0, qubit_idx] - or pauli_term.primitive.paulis.x[0, qubit_idx] - ): - coeff_out = curr_tapering_values[idx] * coeff_out - z_temp = np.delete(pauli_term.primitive.paulis.z[0].copy(), np.asarray(self._sq_list)) - x_temp = np.delete(pauli_term.primitive.paulis.x[0].copy(), np.asarray(self._sq_list)) - pauli_list.append((Pauli((z_temp, x_temp)).to_label(), coeff_out)) - - spo = SparsePauliOp.from_list(pauli_list).simplify(atol=0.0) - spo = spo.chop(self.tol) - z2_symmetries = self.copy() - z2_symmetries.tapering_values = curr_tapering_values - - return TaperedPauliSumOp(spo, z2_symmetries) - - def consistent_tapering(self, operator: PauliSumOp) -> OperatorBase: - """ - Tapering the `operator` with the same manner of how this tapered operator - is created. i.e., using the same Cliffords and tapering values. - - Args: - operator: the to-be-tapered operator - - Returns: - The tapered operator - - Raises: - OpflowError: The given operator does not commute with the symmetry - """ - for symmetry in self._symmetries: - commutator_op = cast(PauliSumOp, commutator(operator, PauliOp(symmetry))) - if not commutator_op.is_zero(): - raise OpflowError( - "The given operator does not commute with the symmetry, can not taper it." - ) - - return self.taper(operator) - - def __eq__(self, other: object) -> bool: - """ - Overload `==` operation to evaluate equality between Z2Symmetries. - - Args: - other: The `Z2Symmetries` to compare to self. - - Returns: - A bool equal to the equality of self and other. - """ - if not isinstance(other, Z2Symmetries): - return False - - return ( - self.symmetries == other.symmetries - and self.sq_paulis == other.sq_paulis - and self.sq_list == other.sq_list - and self.tapering_values == other.tapering_values - ) - - -def _kernel_F2(matrix_in) -> List[np.ndarray]: # pylint: disable=invalid-name - """ - Computes the kernel of a binary matrix on the binary finite field - Args: - matrix_in (numpy.ndarray): binary matrix - Returns: - The list of kernel vectors - """ - size = matrix_in.shape - kernel = [] - matrix_in_id = np.vstack((matrix_in, np.identity(size[1]))) - matrix_in_id_ech = (_row_echelon_F2(matrix_in_id.transpose())).transpose() - - for col in range(size[1]): - if np.array_equal( - matrix_in_id_ech[0 : size[0], col], np.zeros(size[0]) - ) and not np.array_equal(matrix_in_id_ech[size[0] :, col], np.zeros(size[1])): - kernel.append(matrix_in_id_ech[size[0] :, col]) - - return kernel - - -def _row_echelon_F2(matrix_in) -> np.ndarray: # pylint: disable=invalid-name - """ - Computes the row Echelon form of a binary matrix on the binary finite field - Args: - matrix_in (numpy.ndarray): binary matrix - Returns: - Matrix_in in Echelon row form - """ - size = matrix_in.shape - - for i in range(size[0]): - pivot_index = 0 - for j in range(size[1]): - if matrix_in[i, j] == 1: - pivot_index = j - break - for k in range(size[0]): - if k != i and matrix_in[k, pivot_index] == 1: - matrix_in[k, :] = np.mod(matrix_in[k, :] + matrix_in[i, :], 2) - - matrix_out_temp = deepcopy(matrix_in) - indices = [] - matrix_out = np.zeros(size) - - for i in range(size[0] - 1): - if np.array_equal(matrix_out_temp[i, :], np.zeros(size[1])): - indices.append(i) - for row in np.sort(indices)[::-1]: - matrix_out_temp = np.delete(matrix_out_temp, (row), axis=0) - - matrix_out[0 : size[0] - len(indices), :] = matrix_out_temp - matrix_out = matrix_out.astype(int) - - return matrix_out diff --git a/qiskit/opflow/state_fns/__init__.py b/qiskit/opflow/state_fns/__init__.py deleted file mode 100644 index 69b7d960bc20..000000000000 --- a/qiskit/opflow/state_fns/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -State Functions (:mod:`qiskit.opflow.state_fns`) -================================================ - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -State functions are defined to be complex functions over a single binary -string (as compared to an operator, which is defined as a function over two binary strings, -or a function taking a binary function to another binary function). This function may be -called by the eval() method. - -Measurements are defined to be functionals over StateFns, taking them to real values. -Generally, this real value is interpreted to represent the probability of some classical -state (binary string) being observed from a probabilistic or quantum system represented -by a StateFn. This leads to the equivalent definition, which is that a measurement m is -a function over binary strings producing StateFns, such that the probability of measuring -a given binary string b from a system with StateFn f is equal to the inner -product between f and m(b). - -Note: - All mathematical methods between StateFns are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - -Note: - State functions here are not restricted to wave functions, as there is - no requirement of normalization. - -.. currentmodule:: qiskit.opflow.state_fns - -State Functions ---------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - StateFn - CircuitStateFn - DictStateFn - VectorStateFn - SparseVectorStateFn - OperatorStateFn - CVaRMeasurement - -""" - -from .state_fn import StateFn -from .dict_state_fn import DictStateFn -from .operator_state_fn import OperatorStateFn -from .vector_state_fn import VectorStateFn -from .sparse_vector_state_fn import SparseVectorStateFn -from .circuit_state_fn import CircuitStateFn -from .cvar_measurement import CVaRMeasurement - -__all__ = [ - "StateFn", - "DictStateFn", - "VectorStateFn", - "CircuitStateFn", - "OperatorStateFn", - "CVaRMeasurement", -] diff --git a/qiskit/opflow/state_fns/circuit_state_fn.py b/qiskit/opflow/state_fns/circuit_state_fn.py deleted file mode 100644 index 6a4aa3a2b5e3..000000000000 --- a/qiskit/opflow/state_fns/circuit_state_fn.py +++ /dev/null @@ -1,404 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CircuitStateFn Class""" - - -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np - -from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, transpile -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.exceptions import CircuitError -from qiskit.circuit.library import IGate, StatePreparation -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CircuitStateFn(StateFn): - r""" - Deprecated: A class for state functions and measurements which are defined by the action of a - QuantumCircuit starting from \|0⟩, and stored using Terra's ``QuantumCircuit`` class. - """ - primitive: QuantumCircuit - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[QuantumCircuit, Instruction] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - from_operator: bool = False, - ) -> None: - """ - Args: - primitive: The ``QuantumCircuit`` (or ``Instruction``, which will be converted) which - defines the behavior of the underlying function. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator. - from_operator: if True the StateFn is derived from OperatorStateFn. (Default: False) - - Raises: - TypeError: Unsupported primitive, or primitive has ClassicalRegisters. - """ - if isinstance(primitive, Instruction): - qc = QuantumCircuit(primitive.num_qubits) - qc.append(primitive, qargs=range(primitive.num_qubits)) - primitive = qc - - if not isinstance(primitive, QuantumCircuit): - raise TypeError( - "CircuitStateFn can only be instantiated " - "with QuantumCircuit, not {}".format(type(primitive)) - ) - - if len(primitive.clbits) != 0: - raise TypeError("CircuitOp does not support QuantumCircuits with ClassicalRegisters.") - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - self.from_operator = from_operator - - @staticmethod - def from_dict(density_dict: dict) -> "CircuitStateFn": - """Construct the CircuitStateFn from a dict mapping strings to probability densities. - - Args: - density_dict: The dict representing the desired state. - - Returns: - The CircuitStateFn created from the dict. - """ - # If the dict is sparse (elements <= qubits), don't go - # building a statevector to pass to Qiskit's - # initializer, just create a sum. - if len(density_dict) <= len(list(density_dict.keys())[0]): - statefn_circuits = [] - for bstr, prob in density_dict.items(): - qc = QuantumCircuit(len(bstr)) - # NOTE: Reversing endianness!! - for (index, bit) in enumerate(reversed(bstr)): - if bit == "1": - qc.x(index) - sf_circuit = CircuitStateFn(qc, coeff=prob) - statefn_circuits += [sf_circuit] - if len(statefn_circuits) == 1: - return statefn_circuits[0] - else: - return cast(CircuitStateFn, SummedOp(cast(List[OperatorBase], statefn_circuits))) - else: - sf_dict = StateFn(density_dict) - return CircuitStateFn.from_vector(sf_dict.to_matrix()) - - @staticmethod - def from_vector(statevector: np.ndarray) -> "CircuitStateFn": - """Construct the CircuitStateFn from a vector representing the statevector. - - Args: - statevector: The statevector representing the desired state. - - Returns: - The CircuitStateFn created from the vector. - """ - normalization_coeff = np.linalg.norm(statevector) - normalized_sv = statevector / normalization_coeff - return CircuitStateFn(StatePreparation(normalized_sv), coeff=normalization_coeff) - - def primitive_strings(self) -> Set[str]: - return {"QuantumCircuit"} - - @property - def settings(self) -> Dict: - """Return settings.""" - data = super().settings - data["from_operator"] = self.from_operator - return data - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, " - "{} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, CircuitStateFn) and self.primitive == other.primitive: - return CircuitStateFn(self.primitive, coeff=self.coeff + other.coeff) - - # Covers all else. - return SummedOp([self, other]) - - def adjoint(self) -> "CircuitStateFn": - try: - inverse = self.primitive.inverse() - except CircuitError as missing_inverse: - raise OpflowError( - "Failed to take the inverse of the underlying circuit, the circuit " - "is likely not unitary and can therefore not be inverted." - ) from missing_inverse - - return CircuitStateFn( - inverse, coeff=self.coeff.conjugate(), is_measurement=(not self.is_measurement) - ) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - if not self.is_measurement and not front: - raise ValueError( - "Composition with a Statefunctions in the first operand is not defined." - ) - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self.from_operator = self.from_operator - - if front: - return other.compose(new_self) - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - op_circuit_self = CircuitOp(self.primitive) - - # Avoid reimplementing compose logic - composed_op_circs = cast(CircuitOp, op_circuit_self.compose(other.to_circuit_op())) - - # Returning CircuitStateFn - return CircuitStateFn( - composed_op_circs.primitive, - is_measurement=self.is_measurement, - coeff=self.coeff * other.coeff, - from_operator=self.from_operator, - ) - - if isinstance(other, CircuitStateFn) and self.is_measurement: - # pylint: disable=cyclic-import - from ..operator_globals import Zero - - return self.compose(CircuitOp(other.primitive)).compose( - (Zero ^ self.num_qubits) * other.coeff - ) - - return ComposedOp([new_self, other]) - - def tensor(self, other: OperatorBase) -> Union["CircuitStateFn", TensoredOp]: - r""" - Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing convention. - Meaning, Plus.tensor(Zero) - produces a \|+⟩ on qubit 0 and a \|0⟩ on qubit 1, or \|+⟩⨂\|0⟩, but would produce - a QuantumCircuit like: - - \|0⟩-- - \|+⟩-- - - Because Terra prints circuits and results with qubit 0 at the end of the string or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - if isinstance(other, CircuitStateFn) and other.is_measurement == self.is_measurement: - # Avoid reimplementing tensor, just use CircuitOp's - c_op_self = CircuitOp(self.primitive, self.coeff) - c_op_other = CircuitOp(other.primitive, other.coeff) - c_op = c_op_self.tensor(c_op_other) - if isinstance(c_op, CircuitOp): - return CircuitStateFn( - primitive=c_op.primitive, - coeff=c_op.coeff, - is_measurement=self.is_measurement, - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """ - Return numpy matrix of density operator, warn if more than 16 qubits to - force the user to set - massive=True if they want such a large matrix. Generally big methods like this - should require the use of a - converter, but in this case a convenience method for quick hacking and access - to classical tools is - appropriate. - """ - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - # Rely on VectorStateFn's logic here. - return VectorStateFn(self.to_matrix(massive=massive) * self.coeff).to_density_matrix() - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - - # Need to adjoint to get forward statevector and then reverse - if self.is_measurement: - return np.conj(self.adjoint().to_matrix(massive=massive)) - qc = self.to_circuit(meas=False) - statevector_backend = BasicAer.get_backend("statevector_simulator") - transpiled = transpile(qc, statevector_backend, optimization_level=0) - statevector = statevector_backend.run(transpiled).result().get_statevector() - from ..operator_globals import EVAL_SIG_DIGITS - - return np.round(statevector * self.coeff, decimals=EVAL_SIG_DIGITS) - - def __str__(self) -> str: - qc = cast(CircuitStateFn, self.reduce()).to_circuit() - prim_str = str(qc.draw(output="text")) - if self.coeff == 1.0: - return "{}(\n{}\n)".format( - "CircuitStateFn" if not self.is_measurement else "CircuitMeasurement", prim_str - ) - else: - return "{}(\n{}\n) * {}".format( - "CircuitStateFn" if not self.is_measurement else "CircuitMeasurement", - prim_str, - self.coeff, - ) - - def assign_parameters(self, param_dict: dict) -> Union["CircuitStateFn", ListOp]: - param_value = self.coeff - qc = self.primitive - if isinstance(self.coeff, ParameterExpression) or self.primitive.parameters: - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if isinstance(self.coeff, ParameterExpression) and self.coeff.parameters <= set( - unrolled_dict.keys() - ): - param_instersection = set(unrolled_dict.keys()) & self.coeff.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - param_value = float(self.coeff.bind(binds)) - # & is set intersection, check if any parameters in unrolled are present in circuit - # This is different from bind_parameters in Terra because they check for set equality - if set(unrolled_dict.keys()) & self.primitive.parameters: - # Only bind the params found in the circuit - param_instersection = set(unrolled_dict.keys()) & self.primitive.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - qc = self.to_circuit().assign_parameters(binds) - return self.__class__(qc, coeff=param_value, is_measurement=self.is_measurement) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - vector_state_fn = self.to_matrix_op().eval() - return vector_state_fn - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # Composable with circuit - if isinstance(front, (PauliOp, CircuitOp, MatrixOp, CircuitStateFn)): - new_front = self.compose(front) - return new_front.eval() - - return self.to_matrix_op().eval(front) - - def to_circuit(self, meas: bool = False) -> QuantumCircuit: - """Return QuantumCircuit representing StateFn""" - if meas: - meas_qc = self.primitive.copy() - meas_qc.add_register(ClassicalRegister(self.num_qubits)) - meas_qc.measure(qubit=range(self.num_qubits), cbit=range(self.num_qubits)) - return meas_qc - else: - return self.primitive - - def to_circuit_op(self) -> OperatorBase: - """Return ``StateFnCircuit`` corresponding to this StateFn.""" - return self - - def to_instruction(self): - """Return Instruction corresponding to primitive.""" - return self.primitive.to_instruction() - - # TODO specify backend? - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - """ - Sample the state function as a normalized probability distribution. Returns dict of - bitstrings in order of probability, with values being probability. - """ - OperatorBase._check_massive("sample", False, self.num_qubits, massive) - qc = self.to_circuit(meas=True) - qasm_backend = BasicAer.get_backend("qasm_simulator") - transpiled = transpile(qc, qasm_backend, optimization_level=0) - counts = qasm_backend.run(transpiled, shots=shots).result().get_counts() - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) - - # Warning - modifying primitive!! - def reduce(self) -> "CircuitStateFn": - if self.primitive.data is not None: - # Need to do this from the end because we're deleting items! - for i in reversed(range(len(self.primitive.data))): - gate = self.primitive.data[i].operation - # Check if Identity or empty instruction (need to check that type is exactly - # Instruction because some gates have lazy gate.definition population) - # pylint: disable=unidiomatic-typecheck - if isinstance(gate, IGate) or ( - type(gate) == Instruction and gate.definition.data == [] - ): - del self.primitive.data[i] - return self - - def _expand_dim(self, num_qubits: int) -> "CircuitStateFn": - # this is equivalent to self.tensor(identity_operator), but optimized for better performance - # just like in tensor method, qiskit endianness is reversed here - return self.permute(list(range(num_qubits, num_qubits + self.num_qubits))) - - def permute(self, permutation: List[int]) -> "CircuitStateFn": - r""" - Permute the qubits of the circuit. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new CircuitStateFn containing the permuted circuit. - """ - new_qc = QuantumCircuit(max(permutation) + 1).compose(self.primitive, qubits=permutation) - return CircuitStateFn(new_qc, coeff=self.coeff, is_measurement=self.is_measurement) diff --git a/qiskit/opflow/state_fns/cvar_measurement.py b/qiskit/opflow/state_fns/cvar_measurement.py deleted file mode 100644 index 858d3b87f671..000000000000 --- a/qiskit/opflow/state_fns/cvar_measurement.py +++ /dev/null @@ -1,386 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""CVaRMeasurement class.""" - - -from typing import Callable, Optional, Tuple, Union, cast, Dict - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops import ListOp, SummedOp, TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops import PauliOp, PauliSumOp -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CVaRMeasurement(OperatorStateFn): - r"""Deprecated: A specialized measurement class to compute CVaR expectation values. - See https://arxiv.org/pdf/1907.04769.pdf for further details. - - Used in :class:`~qiskit.opflow.CVaRExpectation`, see there for more details. - """ - - primitive: OperatorBase - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: OperatorBase = None, - alpha: float = 1.0, - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The ``OperatorBase`` which defines the diagonal operator - measurement. - coeff: A coefficient by which to multiply the state function - alpha: A real-valued parameter between 0 and 1 which specifies the - fraction of observed samples to include when computing the - objective value. alpha = 1 corresponds to a standard observable - expectation value. alpha = 0 corresponds to only using the single - sample with the lowest energy. alpha = 0.5 corresponds to ranking each - observation by lowest energy and using the best - - Raises: - ValueError: TODO remove that this raises an error - ValueError: If alpha is not in [0, 1]. - OpflowError: If the primitive is not diagonal. - """ - if primitive is None: - raise ValueError - - if not 0 <= alpha <= 1: - raise ValueError("The parameter alpha must be in [0, 1].") - self._alpha = alpha - - if not _check_is_diagonal(primitive): - raise OpflowError( - "Input operator to CVaRMeasurement must be diagonal, but is not:", str(primitive) - ) - - super().__init__(primitive, coeff=coeff, is_measurement=True) - - @property - def alpha(self) -> float: - """A real-valued parameter between 0 and 1 which specifies the - fraction of observed samples to include when computing the - objective value. alpha = 1 corresponds to a standard observable - expectation value. alpha = 0 corresponds to only using the single - sample with the lowest energy. alpha = 0.5 corresponds to ranking each - observation by lowest energy and using the best half. - - Returns: - The parameter alpha which was given at initialization - """ - return self._alpha - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"primitive": self._primitive, "coeff": self._coeff, "alpha": self._alpha} - - def add(self, other: OperatorBase) -> SummedOp: - return SummedOp([self, other]) - - def adjoint(self): - """The adjoint of a CVaRMeasurement is not defined. - - Returns: - Does not return anything, raises an error. - - Raises: - OpflowError: The adjoint of a CVaRMeasurement is not defined. - """ - raise OpflowError("Adjoint of a CVaR measurement not defined") - - def mul(self, scalar: Union[complex, ParameterExpression]) -> "CVaRMeasurement": - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - - return self.__class__(self.primitive, coeff=self.coeff * scalar, alpha=self._alpha) - - def tensor(self, other: OperatorBase) -> Union["OperatorStateFn", TensoredOp]: - if isinstance(other, OperatorStateFn): - return OperatorStateFn( - self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_matrix_op(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_matrix(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_circuit_op(self): - """Not defined.""" - raise NotImplementedError - - def __str__(self) -> str: - return f"CVaRMeasurement({str(self.primitive)}) * {self.coeff}" - - def eval( - self, front: Union[str, dict, np.ndarray, OperatorBase, Statevector] = None - ) -> complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - CVaR as H_j + 1/α*(sum_i complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - variance of the CVaR estimator as - H_j^2 + 1/α * (sum_i], where H is the diagonal observable and bi - corresponds to measurement outcome i. Given this, E[X^2] = E[^2] - - Args: - front: A StateFn or primitive which specifies the results of evaluating - a quantum state. - - Returns: - The Var[CVaR] of the diagonal observable specified by self.primitive - and the sampled quantum state described by the inputs - (energies, probabilities). For index j (described above), the CVaR - is computed as H_j^2 + 1/α*(sum_i Tuple[list, list]: - r""" - In order to compute the CVaR of an observable expectation, we require - the energies of each sampled measurement outcome as well as the sampling - probability of each measurement outcome. Note that the counts for each - measurement outcome will also suffice (and this is often how the CVaR - is presented). - - Args: - front: A StateFn or a primitive which defines a StateFn. - This input holds the results of a sampled/simulated circuit. - - Returns: - Two lists of equal length. `energies` contains the energy of each - unique measurement outcome computed against the diagonal observable - stored in self.primitive. `probabilities` contains the corresponding - sampling probability for each measurement outcome in `energies`. - - Raises: - ValueError: front isn't a DictStateFn or VectorStateFn - """ - if isinstance(front, CircuitStateFn): - front = cast(StateFn, front.eval()) - - # Standardize the inputs to a dict - if isinstance(front, DictStateFn): - data = front.primitive - elif isinstance(front, VectorStateFn): - vec = front.primitive.data - # Determine how many bits are needed - key_len = int(np.ceil(np.log2(len(vec)))) - # Convert the vector primitive into a dict. The formatting here ensures - # that the proper number of leading `0` characters are added. - data = {format(index, "0" + str(key_len) + "b"): val for index, val in enumerate(vec)} - else: - raise ValueError("Unsupported input to CVaRMeasurement.eval:", type(front)) - - obs = self.primitive - outcomes = list(data.items()) - # add energy evaluation - for i, outcome in enumerate(outcomes): - key = outcome[0] - outcomes[i] += (obs.eval(key).adjoint().eval(key),) # type: ignore - - # Sort each observation based on it's energy - outcomes = sorted(outcomes, key=lambda x: x[2]) # type: ignore - - # Here probabilities are the (root) probabilities of - # observing each state. energies are the expectation - # values of each state with the provided Hamiltonian. - _, root_probabilities, energies = zip(*outcomes) - - # Square the dict values - # (since CircuitSampler takes the root...) - probabilities = [p_i * np.conj(p_i) for p_i in root_probabilities] - return list(energies), probabilities - - def compute_cvar(self, energies: list, probabilities: list) -> complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - CVaR. Note that the sampling probabilities serve as an alternative to knowing - the counts of each observation and that the input energies are assumed to be - sorted in increasing order. - - Consider the outcome with index j, such that only some of the samples with - measurement outcome j will be used in computing CVaR. The CVaR calculation - can then be separated into two parts. First we sum each of the energies for - outcomes i < j, weighted by the probability of observing that outcome (i.e - the normalized counts). Second, we add the energy for outcome j, weighted by - the difference (α - \sum_i alpha: - break - - h_j = energies[j] - cvar = alpha * h_j - - if alpha == 0 or j == 0: - return self.coeff * h_j - - energies = energies[:j] - probabilities = probabilities[:j] - # Let H_i be the energy associated with outcome i - # and let the outcomes be sorted by ascending energy. - # Let p_i be the probability of observing outcome i. - # CVaR = H_j + 1/α*(sum_i OperatorBase: - r""" - Apply the convert_fn to the internal primitive if the primitive is an Operator (as in - the case of ``OperatorStateFn``). Otherwise do nothing. Used by converters. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted StateFn. - """ - if coeff is None: - coeff = self.coeff - - if isinstance(self.primitive, OperatorBase): - return self.__class__(convert_fn(self.primitive), coeff=coeff, alpha=self._alpha) - return self - - def sample(self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False): - raise NotImplementedError - - -def _check_is_diagonal(operator: OperatorBase) -> bool: - """Check whether ``operator`` is diagonal. - - Args: - operator: The operator to check for diagonality. - - Returns: - True, if the operator is diagonal, False otherwise. - - Raises: - OpflowError: If the operator is not diagonal. - """ - if isinstance(operator, PauliOp): - # every X component must be False - return not np.any(operator.primitive.x) - - # For sums (PauliSumOp and SummedOp), we cover the case of sums of diagonal paulis, but don't - # raise since there might be summand canceling the non-diagonal parts. That case is checked - # in the inefficient matrix check at the bottom. - if isinstance(operator, PauliSumOp): - if not np.any(operator.primitive.paulis.x): - return True - - elif isinstance(operator, SummedOp): - if all(isinstance(op, PauliOp) and not np.any(op.primitive.x) for op in operator.oplist): - return True - - elif isinstance(operator, ListOp): - return all(operator.traverse(_check_is_diagonal)) - - # cannot efficiently check if a operator is diagonal, converting to matrix - matrix = operator.to_matrix() - return np.all(matrix == np.diag(np.diagonal(matrix))) diff --git a/qiskit/opflow/state_fns/dict_state_fn.py b/qiskit/opflow/state_fns/dict_state_fn.py deleted file mode 100644 index a1fb52acb486..000000000000 --- a/qiskit/opflow/state_fns/dict_state_fn.py +++ /dev/null @@ -1,346 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""DictStateFn Class""" - -import itertools -import warnings -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np -from scipy import sparse - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.result import Result -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class DictStateFn(StateFn): - """Deprecated: A class for state functions and measurements which are defined by a lookup table, - stored in a dict. - """ - - primitive: Dict[str, complex] - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[str, dict, Result] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - from_operator: bool = False, - ) -> None: - """ - Args: - primitive: The dict, single bitstring (if defining a basis sate), or Qiskit - Result, which defines the behavior of the underlying function. - coeff: A coefficient by which to multiply the state function. - is_measurement: Whether the StateFn is a measurement operator. - from_operator: if True the StateFn is derived from OperatorStateFn. (Default: False) - - Raises: - TypeError: invalid parameters. - """ - # If the initial density is a string, treat this as a density dict - # with only a single basis state. - if isinstance(primitive, str): - primitive = {primitive: 1} - - # NOTE: - # 1) This is not the same as passing in the counts dict directly, as this will - # convert the shot numbers to - # probabilities, whereas passing in the counts dict will not. - # 2) This will extract counts for both shot and statevector simulations. - # To use the statevector, - # simply pass in the statevector. - # 3) This will only extract the first result. - if isinstance(primitive, Result): - counts = primitive.get_counts() - # NOTE: Need to square root to take correct Pauli measurements! - primitive = { - bstr: (shots / sum(counts.values())) ** 0.5 for (bstr, shots) in counts.items() - } - - if not isinstance(primitive, dict): - raise TypeError( - "DictStateFn can only be instantiated with dict, " - "string, or Qiskit Result, not {}".format(type(primitive)) - ) - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - self.from_operator = from_operator - - def primitive_strings(self) -> Set[str]: - return {"Dict"} - - @property - def num_qubits(self) -> int: - return len(next(iter(self.primitive))) - - @property - def settings(self) -> Dict: - """Return settings.""" - data = super().settings - data["from_operator"] = self.from_operator - return data - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, DictStateFn) and self.is_measurement == other.is_measurement: - # TODO add compatibility with vector and Operator? - if self.primitive == other.primitive: - return DictStateFn( - self.primitive, - coeff=self.coeff + other.coeff, - is_measurement=self.is_measurement, - ) - else: - new_dict = { - b: (v * self.coeff) + (other.primitive.get(b, 0) * other.coeff) - for (b, v) in self.primitive.items() - } - new_dict.update( - { - b: v * other.coeff - for (b, v) in other.primitive.items() - if b not in self.primitive - } - ) - return DictStateFn(new_dict, is_measurement=self._is_measurement) - # pylint: disable=cyclic-import - from ..list_ops.summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "DictStateFn": - return DictStateFn( - {b: np.conj(v) for (b, v) in self.primitive.items()}, - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def permute(self, permutation: List[int]) -> "DictStateFn": - new_num_qubits = max(permutation) + 1 - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - - # helper function to permute the key - def perm(key): - list_key = ["0"] * new_num_qubits - for i, k in enumerate(permutation): - list_key[k] = key[i] - return "".join(list_key) - - new_dict = {perm(key): value for key, value in self.primitive.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def _expand_dim(self, num_qubits: int) -> "DictStateFn": - pad = "0" * num_qubits - new_dict = {key + pad: value for key, value in self.primitive.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Both dicts - if isinstance(other, DictStateFn): - new_dict = { - k1 + k2: v1 * v2 - for ( - ( - k1, - v1, - ), - (k2, v2), - ) in itertools.product(self.primitive.items(), other.primitive.items()) - } - return StateFn( - new_dict, coeff=self.coeff * other.coeff, is_measurement=self.is_measurement - ) - # pylint: disable=cyclic-import - from ..list_ops.tensored_op import TensoredOp - - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - states = int(2**self.num_qubits) - return self.to_matrix(massive=massive) * np.eye(states) * self.coeff - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - states = int(2**self.num_qubits) - probs = np.zeros(states) + 0.0j - for k, v in self.primitive.items(): - probs[int(k, 2)] = v - vec = probs * self.coeff - - # Reshape for measurements so np.dot still works for composition. - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_spmatrix(self) -> sparse.spmatrix: - """Same as to_matrix, but returns csr sparse matrix. - - Returns: - CSR sparse matrix representation of the State function. - - Raises: - ValueError: invalid parameters. - """ - - indices = [int(v, 2) for v in self.primitive.keys()] - vals = np.array(list(self.primitive.values())) * self.coeff - spvec = sparse.csr_matrix( - (vals, (np.zeros(len(indices), dtype=int), indices)), shape=(1, 2**self.num_qubits) - ) - return spvec if not self.is_measurement else spvec.transpose() - - def to_spmatrix_op(self) -> OperatorBase: - """Convert this state function to a ``SparseVectorStateFn``.""" - from .sparse_vector_state_fn import SparseVectorStateFn - - return SparseVectorStateFn(self.to_spmatrix(), self.coeff, self.is_measurement) - - def to_circuit_op(self) -> OperatorBase: - """Convert this state function to a ``CircuitStateFn``.""" - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_dict(self.primitive) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "DictStateFn" if not self.is_measurement else "DictMeasurement", prim_str - ) - else: - return "{}({}) * {}".format( - "DictStateFn" if not self.is_measurement else "DictMeasurement", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - sparse_vector_state_fn = self.to_spmatrix_op().eval() - return sparse_vector_state_fn - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - - # If the primitive is a lookup of bitstrings, - # we define all missing strings to have a function value of - # zero. - if isinstance(front, DictStateFn): - # If self is come from operator, it should be expanded as - # = . - front_coeff = ( - front.coeff * front.coeff.conjugate() if self.from_operator else front.coeff - ) - return np.round( - cast( - float, - sum(v * front.primitive.get(b, 0) for (b, v) in self.primitive.items()) - * self.coeff - * front_coeff, - ), - decimals=EVAL_SIG_DIGITS, - ) - - # All remaining possibilities only apply when self.is_measurement is True - - if isinstance(front, VectorStateFn): - # TODO does it need to be this way for measurement? - # return sum([v * front.primitive.data[int(b, 2)] * - # np.conj(front.primitive.data[int(b, 2)]) - return np.round( - cast( - float, - sum(v * front.primitive.data[int(b, 2)] for (b, v) in self.primitive.items()) - * self.coeff, - ), - decimals=EVAL_SIG_DIGITS, - ) - - from .circuit_state_fn import CircuitStateFn - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - self_adjoint = cast(DictStateFn, self.adjoint()) - return np.conj(front.adjoint().eval(self_adjoint.primitive)) * self.coeff - - from .operator_state_fn import OperatorStateFn - - if isinstance(front, OperatorStateFn): - return cast(Union[OperatorBase, complex], front.adjoint().eval(self.adjoint())) - - # All other OperatorBases go here - self_adjoint = cast(DictStateFn, self.adjoint()) - adjointed_eval = cast(OperatorBase, front.adjoint().eval(self_adjoint.primitive)) - return adjointed_eval.adjoint() * self.coeff - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> Dict[str, float]: - probs = np.square(np.abs(np.array(list(self.primitive.values())))) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(self.primitive.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/state_fns/operator_state_fn.py b/qiskit/opflow/state_fns/operator_state_fn.py deleted file mode 100644 index 08f1b3c766b0..000000000000 --- a/qiskit/opflow/state_fns/operator_state_fn.py +++ /dev/null @@ -1,260 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OperatorStateFn Class""" - -from typing import List, Optional, Set, Union, cast - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class OperatorStateFn(StateFn): - r""" - Deprecated: A class for state functions and measurements which are defined by a density Operator, - stored using an ``OperatorBase``. - """ - primitive: OperatorBase - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: OperatorBase, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The ``OperatorBase`` which defines the behavior of the underlying State - function. - coeff: A coefficient by which to multiply the state function - is_measurement: Whether the StateFn is a measurement operator - """ - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return self.primitive.primitive_strings() - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> Union["OperatorStateFn", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, OperatorStateFn) and self.is_measurement == other.is_measurement: - if isinstance(other.primitive, OperatorBase) and self.primitive == other.primitive: - return OperatorStateFn( - self.primitive, - coeff=self.coeff + other.coeff, - is_measurement=self.is_measurement, - ) - # Covers Statevector and custom. - elif isinstance(other, OperatorStateFn): - # Also assumes scalar multiplication is available - return OperatorStateFn( - (self.coeff * self.primitive).add(other.primitive * other.coeff), - is_measurement=self._is_measurement, - ) - - return SummedOp([self, other]) - - def adjoint(self) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive.adjoint(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def _expand_dim(self, num_qubits: int) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive._expand_dim(num_qubits), - coeff=self.coeff, - is_measurement=self.is_measurement, - ) - - def permute(self, permutation: List[int]) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive.permute(permutation), - coeff=self.coeff, - is_measurement=self.is_measurement, - ) - - def tensor(self, other: OperatorBase) -> Union["OperatorStateFn", TensoredOp]: - if isinstance(other, OperatorStateFn): - return OperatorStateFn( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - is_measurement=self.is_measurement, - ) - - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """Return numpy matrix of density operator, warn if more than 16 qubits - to force the user to set - massive=True if they want such a large matrix. Generally big methods like - this should require the use of a - converter, but in this case a convenience method for quick hacking and - access to classical tools is - appropriate.""" - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - return self.primitive.to_matrix() * self.coeff - - def to_matrix_op(self, massive: bool = False) -> "OperatorStateFn": - """Return a MatrixOp for this operator.""" - return OperatorStateFn( - self.primitive.to_matrix_op(massive=massive) * self.coeff, - is_measurement=self.is_measurement, - ) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - r""" - Note: this does not return a density matrix, it returns a classical matrix - containing the quantum or classical vector representing the evaluation of the state - function on each binary basis state. Do not assume this is is a normalized quantum or - classical probability vector. If we allowed this to return a density matrix, - then we would need to change the definition of composition to be ~Op @ StateFn @ Op for - those cases, whereas by this methodology we can ensure that composition always means Op - @ StateFn. - - Return numpy vector of state vector, warn if more than 16 qubits to force the user to set - massive=True if they want such a large vector. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - np.ndarray: Vector of state vector - - Raises: - ValueError: Invalid parameters. - """ - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - # Operator - return diagonal (real values, not complex), - # not rank 1 decomposition (statevector)! - mat = self.primitive.to_matrix(massive=massive) - # TODO change to weighted sum of eigenvectors' StateFns? - - # ListOp primitives can return lists of matrices (or trees for nested ListOps), - # so we need to recurse over the - # possible tree. - def diag_over_tree(op): - if isinstance(op, list): - return [diag_over_tree(o) for o in op] - else: - vec = np.diag(op) * self.coeff - # Reshape for measurements so np.dot still works for composition. - return vec if not self.is_measurement else vec.reshape(1, -1) - - return diag_over_tree(mat) - - def to_circuit_op(self): - r"""Return ``StateFnCircuit`` corresponding to this StateFn. Ignore for now because this is - undefined. TODO maybe call to_pauli_op and diagonalize here, but that could be very - inefficient, e.g. splitting one Stabilizer measurement into hundreds of 1 qubit Paulis.""" - raise NotImplementedError - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "OperatorStateFn" if not self.is_measurement else "OperatorMeasurement", prim_str - ) - else: - return "{}({}) * {}".format( - "OperatorStateFn" if not self.is_measurement else "OperatorMeasurement", - prim_str, - self.coeff, - ) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - if front is None: - matrix = cast(MatrixOp, self.primitive.to_matrix_op()).primitive.data - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - return VectorStateFn(matrix[0, :]) - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - if isinstance(self.primitive, ListOp) and self.primitive.distributive: - evals = [ - OperatorStateFn(op, is_measurement=self.is_measurement).eval(front) - for op in self.primitive.oplist - ] - result = self.primitive.combo_fn(evals) - if isinstance(result, list): - multiplied = self.primitive.coeff * self.coeff * np.array(result) - return multiplied.tolist() - return result * self.coeff * self.primitive.coeff - - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - if isinstance(self.primitive, PauliSumOp) and isinstance(front, VectorStateFn): - return ( - front.primitive.expectation_value(self.primitive.primitive) - * self.coeff - * front.coeff - ) - - # Need an ListOp-specific carve-out here to make sure measurement over a ListOp doesn't - # produce two-dimensional ListOp from composing from both sides of primitive. - # Can't use isinstance because this would include subclasses. - # pylint: disable=unidiomatic-typecheck - if isinstance(front, ListOp) and type(front) == ListOp: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # If we evaluate against a circuit, evaluate it to a vector so we - # make sure to only do the expensive circuit simulation once - if isinstance(front, CircuitStateFn): - front = front.eval() - - return front.adjoint().eval(cast(OperatorBase, self.primitive.eval(front))) * self.coeff - - def sample(self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False): - raise NotImplementedError diff --git a/qiskit/opflow/state_fns/sparse_vector_state_fn.py b/qiskit/opflow/state_fns/sparse_vector_state_fn.py deleted file mode 100644 index b26c6dff9df1..000000000000 --- a/qiskit/opflow/state_fns/sparse_vector_state_fn.py +++ /dev/null @@ -1,234 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""SparseVectorStateFn class.""" - - -from typing import Dict, Optional, Set, Union - -import numpy as np -import scipy - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class SparseVectorStateFn(StateFn): - """Deprecated: A class for sparse state functions and measurements in vector representation. - - This class uses ``scipy.sparse.spmatrix`` for the internal representation. - """ - - primitive: scipy.sparse.spmatrix - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: scipy.sparse.spmatrix, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The underlying sparse vector. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator - - Raises: - ValueError: If the primitive is not a column vector. - ValueError: If the number of elements in the primitive is not a power of 2. - - """ - if primitive.shape[0] != 1: - raise ValueError("The primitive must be a row vector of shape (x, 1).") - - # check if the primitive is a statevector of 2^n elements - self._num_qubits = int(np.log2(primitive.shape[1])) - if np.log2(primitive.shape[1]) != self._num_qubits: - raise ValueError("The number of vector elements must be a power of 2.") - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return {"SparseVector"} - - @property - def num_qubits(self) -> int: - return self._num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, SparseVectorStateFn) and self.is_measurement == other.is_measurement: - # Covers Statevector and custom. - added = self.coeff * self.primitive + other.coeff * other.primitive - return SparseVectorStateFn(added, is_measurement=self._is_measurement) - - return SummedOp([self, other]) - - def adjoint(self) -> "SparseVectorStateFn": - return SparseVectorStateFn( - self.primitive.conjugate(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, SparseVectorStateFn) or not self.coeff == other.coeff: - return False - - if self.primitive.shape != other.primitive.shape: - return False - - if self.primitive.count_nonzero() != other.primitive.count_nonzero(): - return False - - # equal if no elements are different (using != for efficiency) - return (self.primitive != other.primitive).nnz == 0 - - def to_dict_fn(self) -> StateFn: - """Convert this state function to a ``DictStateFn``. - - Returns: - A new DictStateFn equivalent to ``self``. - """ - from .dict_state_fn import DictStateFn - - num_qubits = self.num_qubits - dok = self.primitive.todok() - new_dict = {format(i[1], "b").zfill(num_qubits): v for i, v in dok.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - vec = self.primitive.toarray() * self.coeff - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - return VectorStateFn(self.to_matrix()) - - def to_spmatrix(self) -> OperatorBase: - return self - - def to_circuit_op(self) -> OperatorBase: - """Convert this state function to a ``CircuitStateFn``.""" - # pylint: disable=cyclic-import - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_vector(self.primitive) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "SparseVectorStateFn" if not self.is_measurement else "MeasurementSparseVector", - prim_str, - ) - else: - return "{}({}) * {}".format( - "SparseVectorStateFn" if not self.is_measurement else "SparseMeasurementVector", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, Statevector, OperatorBase] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. " - "Try taking sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .operator_state_fn import OperatorStateFn - from .circuit_state_fn import CircuitStateFn - from .dict_state_fn import DictStateFn - - if isinstance(front, DictStateFn): - return np.round( - sum( - v * self.primitive.data[int(b, 2)] * front.coeff - for (b, v) in front.primitive.items() - ) - * self.coeff, - decimals=EVAL_SIG_DIGITS, - ) - - if isinstance(front, VectorStateFn): - # Need to extract the element or np.array([1]) is returned. - return np.round( - np.dot(self.to_matrix(), front.to_matrix())[0], decimals=EVAL_SIG_DIGITS - ) - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - return np.conj(front.adjoint().eval(self.adjoint().primitive)) * self.coeff - - if isinstance(front, OperatorStateFn): - return front.adjoint().eval(self.primitive) * self.coeff - - return front.adjoint().eval(self.adjoint().primitive).adjoint() * self.coeff # type: ignore - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - as_dict = self.to_dict_fn().primitive - all_states = sum(as_dict.keys()) - deterministic_counts = {key: value / all_states for key, value in as_dict.items()} - # Don't need to square because probabilities_dict already does. - probs = np.array(list(deterministic_counts.values())) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(deterministic_counts.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/state_fns/state_fn.py b/qiskit/opflow/state_fns/state_fn.py deleted file mode 100644 index 6f6575db4105..000000000000 --- a/qiskit/opflow/state_fns/state_fn.py +++ /dev/null @@ -1,460 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""StateFn Class""" - -from typing import Callable, Dict, List, Optional, Set, Tuple, Union - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.result import Result -from qiskit.utils.deprecation import deprecate_func - - -class StateFn(OperatorBase): - r""" - Deprecated: A class for representing state functions and measurements. - - State functions are defined to be complex functions over a single binary string (as - compared to an operator, which is defined as a function over two binary strings, or a - function taking a binary function to another binary function). This function may be - called by the eval() method. - - Measurements are defined to be functionals over StateFns, taking them to real values. - Generally, this real value is interpreted to represent the probability of some classical - state (binary string) being observed from a probabilistic or quantum system represented - by a StateFn. This leads to the equivalent definition, which is that a measurement m is - a function over binary strings producing StateFns, such that the probability of measuring - a given binary string b from a system with StateFn f is equal to the inner - product between f and m(b). - - NOTE: State functions here are not restricted to wave functions, as there is - no requirement of normalization. - """ - - def __init_subclass__(cls): - cls.__new__ = lambda cls, *args, **kwargs: super().__new__(cls) - - @staticmethod - # pylint: disable=unused-argument - def __new__( - cls, - primitive: Union[ - str, - dict, - Result, - list, - np.ndarray, - Statevector, - QuantumCircuit, - Instruction, - OperatorBase, - ] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> "StateFn": - """A factory method to produce the correct type of StateFn subclass - based on the primitive passed in. Primitive, coeff, and is_measurement arguments - are passed into subclass's init() as-is automatically by new(). - - Args: - primitive: The primitive which defines the behavior of the underlying State function. - coeff: A coefficient by which the state function is multiplied. - is_measurement: Whether the StateFn is a measurement operator - - Returns: - The appropriate StateFn subclass for ``primitive``. - - Raises: - TypeError: Unsupported primitive type passed. - """ - - # Prevents infinite recursion when subclasses are created - if cls.__name__ != StateFn.__name__: - return super().__new__(cls) - - # pylint: disable=cyclic-import - if isinstance(primitive, (str, dict, Result)): - from .dict_state_fn import DictStateFn - - return DictStateFn.__new__(DictStateFn) - - if isinstance(primitive, (list, np.ndarray, Statevector)): - from .vector_state_fn import VectorStateFn - - return VectorStateFn.__new__(VectorStateFn) - - if isinstance(primitive, (QuantumCircuit, Instruction)): - from .circuit_state_fn import CircuitStateFn - - return CircuitStateFn.__new__(CircuitStateFn) - - if isinstance(primitive, OperatorBase): - from .operator_state_fn import OperatorStateFn - - return OperatorStateFn.__new__(OperatorStateFn) - - raise TypeError( - "Unsupported primitive type {} passed into StateFn " - "factory constructor".format(type(primitive)) - ) - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[ - str, - dict, - Result, - list, - np.ndarray, - Statevector, - QuantumCircuit, - Instruction, - OperatorBase, - ] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The primitive which defines the behavior of the underlying State function. - coeff: A coefficient by which the state function is multiplied. - is_measurement: Whether the StateFn is a measurement operator - """ - super().__init__() - self._primitive = primitive - self._is_measurement = is_measurement - self._coeff = coeff - - @property - def primitive(self): - """The primitive which defines the behavior of the underlying State function.""" - return self._primitive - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """A coefficient by which the state function is multiplied.""" - return self._coeff - - @property - def is_measurement(self) -> bool: - """Whether the StateFn object is a measurement Operator.""" - return self._is_measurement - - @property - def settings(self) -> Dict: - """Return settings.""" - return { - "primitive": self._primitive, - "coeff": self._coeff, - "is_measurement": self._is_measurement, - } - - def primitive_strings(self) -> Set[str]: - raise NotImplementedError - - @property - def num_qubits(self) -> int: - raise NotImplementedError - - def add(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def adjoint(self) -> OperatorBase: - raise NotImplementedError - - def _expand_dim(self, num_qubits: int) -> "StateFn": - raise NotImplementedError - - def permute(self, permutation: List[int]) -> OperatorBase: - """Permute the qubits of the state function. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new StateFn containing the permuted primitive. - """ - raise NotImplementedError - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, type(self)) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - # Will return NotImplementedError if not supported - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - - if hasattr(self, "from_operator"): - return self.__class__( - self.primitive, - coeff=self.coeff * scalar, - is_measurement=self.is_measurement, - from_operator=self.from_operator, - ) - else: - return self.__class__( - self.primitive, coeff=self.coeff * scalar, is_measurement=self.is_measurement - ) - - def tensor(self, other: OperatorBase) -> OperatorBase: - r""" - Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing - convention. Meaning, Plus.tensor(Zero) - produces a \|+⟩ on qubit 0 and a \|0⟩ on qubit 1, or \|+⟩⨂\|0⟩, but - would produce a QuantumCircuit like - - \|0⟩-- - \|+⟩-- - - Because Terra prints circuits and results with qubit 0 - at the end of the string or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - raise NotImplementedError - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - if not isinstance(other, int) or other <= 0: - raise TypeError("Tensorpower can only take positive int arguments") - temp = StateFn( - self.primitive, coeff=self.coeff, is_measurement=self.is_measurement - ) # type: OperatorBase - for _ in range(other - 1): - temp = temp.tensor(self) - return temp - - def _expand_shorter_operator_and_permute( - self, other: OperatorBase, permutation: Optional[List[int]] = None - ) -> Tuple[OperatorBase, OperatorBase]: - # pylint: disable=cyclic-import - from ..operator_globals import Zero - - if self == StateFn({"0": 1}, is_measurement=True): - # Zero is special - we'll expand it to the correct qubit number. - return StateFn("0" * other.num_qubits, is_measurement=True), other - elif other == Zero: - # Zero is special - we'll expand it to the correct qubit number. - return self, StateFn("0" * self.num_qubits) - - return super()._expand_shorter_operator_and_permute(other, permutation) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - raise NotImplementedError - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """Return matrix representing product of StateFn evaluated on pairs of basis states. - Overridden by child classes. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - The NumPy array representing the density matrix of the State function. - - Raises: - ValueError: If massive is set to False, and exponentially large computation is needed. - """ - raise NotImplementedError - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - r""" - Composition (Linear algebra-style: A@B(x) = A(B(x))) is not well defined for states - in the binary function model, but is well defined for measurements. - - Args: - other: The Operator to compose with self. - permutation: ``List[int]`` which defines permutation on other operator. - front: If front==True, return ``other.compose(self)``. - - Returns: - An Operator equivalent to the function composition of self and other. - - Raises: - ValueError: If self is not a measurement, it cannot be composed from the right. - """ - # TODO maybe allow outers later to produce density operators or projectors, but not yet. - if not self.is_measurement and not front: - raise ValueError( - "Composition with a Statefunction in the first operand is not defined." - ) - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - - if front: - return other.compose(self) - # TODO maybe include some reduction here in the subclasses - vector and Op, op and Op, etc. - from ..primitive_ops.circuit_op import CircuitOp - - if self.primitive == {"0" * self.num_qubits: 1.0} and isinstance(other, CircuitOp): - # Returning CircuitStateFn - return StateFn( - other.primitive, is_measurement=self.is_measurement, coeff=self.coeff * other.coeff - ) - - from ..list_ops.composed_op import ComposedOp - - if isinstance(other, ComposedOp): - return ComposedOp([new_self] + other.oplist, coeff=new_self.coeff * other.coeff) - - return ComposedOp([new_self, other]) - - def power(self, exponent: int) -> OperatorBase: - """Compose with Self Multiple Times, undefined for StateFns. - - Args: - exponent: The number of times to compose self with self. - - Raises: - ValueError: This function is not defined for StateFns. - """ - raise ValueError("Composition power over Statefunctions or Measurements is not defined.") - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "StateFunction" if not self.is_measurement else "Measurement", self.coeff - ) - else: - return "{}({}) * {}".format( - "StateFunction" if not self.is_measurement else "Measurement", self.coeff, prim_str - ) - - def __repr__(self) -> str: - return "{}({}, coeff={}, is_measurement={})".format( - self.__class__.__name__, repr(self.primitive), self.coeff, self.is_measurement - ) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - raise NotImplementedError - - @property - def parameters(self): - params = set() - if isinstance(self.primitive, (OperatorBase, QuantumCircuit)): - params.update(self.primitive.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return self.traverse(lambda x: x.assign_parameters(param_dict), coeff=param_value) - - # Try collapsing primitives where possible. Nothing to collapse here. - def reduce(self) -> OperatorBase: - return self - - def traverse( - self, convert_fn: Callable, coeff: Optional[Union[complex, ParameterExpression]] = None - ) -> OperatorBase: - r""" - Apply the convert_fn to the internal primitive if the primitive is an Operator (as in - the case of ``OperatorStateFn``). Otherwise do nothing. Used by converters. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted StateFn. - """ - if coeff is None: - coeff = self.coeff - - if isinstance(self.primitive, OperatorBase): - return StateFn( - convert_fn(self.primitive), coeff=coeff, is_measurement=self.is_measurement - ) - else: - return self - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - """Return a ``VectorStateFn`` for this ``StateFn``. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - A VectorStateFn equivalent to self. - """ - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - return VectorStateFn(self.to_matrix(massive=massive), is_measurement=self.is_measurement) - - def to_circuit_op(self) -> OperatorBase: - """Returns a ``CircuitOp`` equivalent to this Operator.""" - raise NotImplementedError - - # TODO to_dict_op - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> Dict[str, float]: - """Sample the state function as a normalized probability distribution. Returns dict of - bitstrings in order of probability, with values being probability. - - Args: - shots: The number of samples to take to approximate the State function. - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - reverse_endianness: Whether to reverse the endianness of the bitstrings in the return - dict to match Terra's big-endianness. - - Returns: - A dict containing pairs sampled strings from the State function and sampling - frequency divided by shots. - """ - raise NotImplementedError diff --git a/qiskit/opflow/state_fns/vector_state_fn.py b/qiskit/opflow/state_fns/vector_state_fn.py deleted file mode 100644 index 067070a4f05c..000000000000 --- a/qiskit/opflow/state_fns/vector_state_fn.py +++ /dev/null @@ -1,260 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""VectorStateFn Class""" - -import warnings -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals, arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class VectorStateFn(StateFn): - """Deprecated: A class for state functions and measurements which are defined in vector - representation, and stored using Terra's ``Statevector`` class. - """ - - primitive: Statevector - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: Union[list, np.ndarray, Statevector] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The ``Statevector``, NumPy array, or list, which defines the behavior of - the underlying function. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator - """ - # Lists and Numpy arrays representing statevectors are stored - # in Statevector objects for easier handling. - if isinstance(primitive, (np.ndarray, list)): - primitive = Statevector(primitive) - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return {"Vector"} - - @property - def num_qubits(self) -> int: - return len(self.primitive.dims()) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, VectorStateFn) and self.is_measurement == other.is_measurement: - # Covers Statevector and custom. - return VectorStateFn( - (self.coeff * self.primitive) + (other.primitive * other.coeff), - is_measurement=self._is_measurement, - ) - return SummedOp([self, other]) - - def adjoint(self) -> "VectorStateFn": - return VectorStateFn( - self.primitive.conjugate(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def permute(self, permutation: List[int]) -> "VectorStateFn": - new_self = self - new_num_qubits = max(permutation) + 1 - - if self.num_qubits != len(permutation): - # raise OpflowError("New index must be defined for each qubit of the operator.") - pass - if self.num_qubits < new_num_qubits: - # pad the operator with identities - new_self = self._expand_dim(new_num_qubits - self.num_qubits) - qc = QuantumCircuit(new_num_qubits) - - # extend the permutation indices to match the size of the new matrix - permutation = ( - list(filter(lambda x: x not in permutation, range(new_num_qubits))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - - from ..primitive_ops.circuit_op import CircuitOp - - matrix = CircuitOp(qc).to_matrix() - vector = new_self.primitive.data - new_vector = cast(np.ndarray, matrix.dot(vector)) - return VectorStateFn( - primitive=new_vector, coeff=self.coeff, is_measurement=self.is_measurement - ) - - def to_dict_fn(self) -> StateFn: - """Creates the equivalent state function of type DictStateFn. - - Returns: - A new DictStateFn equivalent to ``self``. - """ - from .dict_state_fn import DictStateFn - - num_qubits = self.num_qubits - new_dict = {format(i, "b").zfill(num_qubits): v for i, v in enumerate(self.primitive.data)} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def _expand_dim(self, num_qubits: int) -> "VectorStateFn": - primitive = np.zeros(2**num_qubits, dtype=complex) - return VectorStateFn( - self.primitive.tensor(primitive), coeff=self.coeff, is_measurement=self.is_measurement - ) - - def tensor(self, other: OperatorBase) -> OperatorBase: - if isinstance(other, VectorStateFn): - return StateFn( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - is_measurement=self.is_measurement, - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - return self.primitive.to_operator().data * self.coeff - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - vec = self.primitive.data * self.coeff - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - return self - - def to_circuit_op(self) -> OperatorBase: - """Return ``StateFnCircuit`` corresponding to this StateFn.""" - # pylint: disable=cyclic-import - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_vector(self.primitive.data) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "VectorStateFn" if not self.is_measurement else "MeasurementVector", prim_str - ) - else: - return "{}({}) * {}".format( - "VectorStateFn" if not self.is_measurement else "MeasurementVector", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, Statevector, OperatorBase] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: # this object is already a VectorStateFn - return self - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .operator_state_fn import OperatorStateFn - from .circuit_state_fn import CircuitStateFn - from .dict_state_fn import DictStateFn - - if isinstance(front, DictStateFn): - return np.round( - sum( - v * self.primitive.data[int(b, 2)] * front.coeff - for (b, v) in front.primitive.items() - ) - * self.coeff, - decimals=EVAL_SIG_DIGITS, - ) - - if isinstance(front, VectorStateFn): - # Need to extract the element or np.array([1]) is returned. - return np.round( - np.dot(self.to_matrix(), front.to_matrix())[0], decimals=EVAL_SIG_DIGITS - ) - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - return np.conj(front.adjoint().eval(self.adjoint().primitive)) * self.coeff - - if isinstance(front, OperatorStateFn): - return front.adjoint().eval(self.primitive) * self.coeff - - return front.adjoint().eval(self.adjoint().primitive).adjoint() * self.coeff # type: ignore - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - deterministic_counts = self.primitive.probabilities_dict() - # Don't need to square because probabilities_dict already does. - probs = np.array(list(deterministic_counts.values())) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(deterministic_counts.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/utils.py b/qiskit/opflow/utils.py deleted file mode 100644 index e766d4f57e32..000000000000 --- a/qiskit/opflow/utils.py +++ /dev/null @@ -1,119 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Utility functions for OperatorFlow""" - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", -) -def commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: - r""" - Deprecated: Compute commutator of `op_a` and `op_b`. - - .. math:: - - AB - BA. - - Args: - op_a: Operator A - op_b: Operator B - Returns: - OperatorBase: the commutator - """ - return (op_a @ op_b - op_b @ op_a).reduce() - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", -) -def anti_commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: - r""" - Deprecated: Compute anti-commutator of `op_a` and `op_b`. - - .. math:: - - AB + BA. - - Args: - op_a: Operator A - op_b: Operator B - Returns: - OperatorBase: the anti-commutator - """ - return (op_a @ op_b + op_b @ op_a).reduce() - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", -) -def double_commutator( - op_a: OperatorBase, - op_b: OperatorBase, - op_c: OperatorBase, - sign: bool = False, -) -> OperatorBase: - r""" - Deprecated: Compute symmetric double commutator of `op_a`, `op_b` and `op_c`. - See McWeeny chapter 13.6 Equation of motion methods (page 479) - - If `sign` is `False`, it returns - - .. math:: - - [[A, B], C]/2 + [A, [B, C]]/2 - = (2ABC + 2CBA - BAC - CAB - ACB - BCA)/2. - - If `sign` is `True`, it returns - - .. math:: - \lbrace[A, B], C\rbrace/2 + \lbrace A, [B, C]\rbrace/2 - = (2ABC - 2CBA - BAC + CAB - ACB + BCA)/2. - - Args: - op_a: Operator A - op_b: Operator B - op_c: Operator C - sign: False anti-commutes, True commutes - Returns: - OperatorBase: the double commutator - """ - sign_num = 1 if sign else -1 - - op_ab = op_a @ op_b - op_ba = op_b @ op_a - op_ac = op_a @ op_c - op_ca = op_c @ op_a - - op_abc = op_ab @ op_c - op_cba = op_c @ op_ba - op_bac = op_ba @ op_c - op_cab = op_c @ op_ab - op_acb = op_ac @ op_b - op_bca = op_b @ op_ca - - res = ( - op_abc - - sign_num * op_cba - + 0.5 * (-op_bac + sign_num * op_cab - op_acb + sign_num * op_bca) - ) - - return res.reduce() diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py index 8f62e4797c2f..eb97a3b95259 100644 --- a/qiskit/primitives/backend_estimator.py +++ b/qiskit/primitives/backend_estimator.py @@ -15,7 +15,6 @@ from __future__ import annotations -import typing from collections.abc import Sequence from itertools import accumulate @@ -41,9 +40,6 @@ from .primitive_job import PrimitiveJob from .utils import _circuit_key, _observable_key, init_observable -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - def _run_circuits( circuits: QuantumCircuit | list[QuantumCircuit], @@ -268,7 +264,7 @@ def _call( def _run( self, circuits: tuple[QuantumCircuit, ...], - observables: tuple[BaseOperator | PauliSumOp, ...], + observables: tuple[BaseOperator, ...], parameter_values: tuple[tuple[float, ...], ...], **run_options, ): diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index b789863a812d..93b497f02049 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -85,7 +85,6 @@ from collections.abc import Sequence from copy import copy from typing import Generic, TypeVar -import typing from qiskit.utils.deprecation import deprecate_func from qiskit.circuit import QuantumCircuit @@ -97,9 +96,6 @@ from .base_primitive import BasePrimitive from . import validation -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - T = TypeVar("T", bound=Job) @@ -149,7 +145,7 @@ def __getattr__(self, name: str) -> any: def run( self, circuits: Sequence[QuantumCircuit] | QuantumCircuit, - observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + observables: Sequence[BaseOperator | str] | BaseOperator | str, parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None = None, **run_options, ) -> T: @@ -218,14 +214,14 @@ def _run( @staticmethod @deprecate_func(since="0.46.0") def _validate_observables( - observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + observables: Sequence[BaseOperator | str] | BaseOperator | str, ) -> tuple[SparsePauliOp, ...]: return validation._validate_observables(observables) @staticmethod @deprecate_func(since="0.46.0") def _cross_validate_circuits_observables( - circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator | PauliSumOp, ...] + circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator, ...] ) -> None: return validation._cross_validate_circuits_observables(circuits, observables) diff --git a/qiskit/primitives/estimator.py b/qiskit/primitives/estimator.py index 69f36e1723f1..9edfe35d7eb2 100644 --- a/qiskit/primitives/estimator.py +++ b/qiskit/primitives/estimator.py @@ -17,7 +17,6 @@ from collections.abc import Sequence from typing import Any -import typing import numpy as np @@ -35,9 +34,6 @@ init_observable, ) -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - class Estimator(BaseEstimator[PrimitiveJob[EstimatorResult]]): """ @@ -130,7 +126,7 @@ def _call( def _run( self, circuits: tuple[QuantumCircuit, ...], - observables: tuple[BaseOperator | PauliSumOp, ...], + observables: tuple[BaseOperator, ...], parameter_values: tuple[tuple[float, ...], ...], **run_options, ): diff --git a/qiskit/primitives/utils.py b/qiskit/primitives/utils.py index c2f915c8f0da..75d25abbbe30 100644 --- a/qiskit/primitives/utils.py +++ b/qiskit/primitives/utils.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -15,22 +15,17 @@ from __future__ import annotations import warnings -import sys -import typing from collections.abc import Iterable import numpy as np -from qiskit.circuit import Instruction, ParameterExpression, QuantumCircuit +from qiskit.circuit import Instruction, QuantumCircuit from qiskit.circuit.bit import Bit from qiskit.circuit.library.data_preparation import Initialize from qiskit.quantum_info import SparsePauliOp, Statevector, PauliList from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info.operators.symplectic.base_pauli import BasePauli -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit: """Initialize state by converting the input to a quantum circuit. @@ -50,7 +45,7 @@ def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit: return qc -def init_observable(observable: BaseOperator | PauliSumOp | str) -> SparsePauliOp: +def init_observable(observable: BaseOperator | str) -> SparsePauliOp: """Initialize observable by converting the input to a :class:`~qiskit.quantum_info.SparsePauliOp`. Args: @@ -59,27 +54,10 @@ def init_observable(observable: BaseOperator | PauliSumOp | str) -> SparsePauliO Returns: The observable as :class:`~qiskit.quantum_info.SparsePauliOp`. - Raises: - TypeError: If the observable is a :class:`~qiskit.opflow.PauliSumOp` and has a parameterized - coefficient. """ - # This dance is to avoid importing the deprecated `qiskit.opflow` if the user hasn't already - # done so. They can't hold a `qiskit.opflow.PauliSumOp` if `qiskit.opflow` hasn't been - # imported, and we don't want unrelated Qiskit library code to be responsible for the first - # import, so the deprecation warnings will show. - if "qiskit.opflow" in sys.modules: - pauli_sum_check = sys.modules["qiskit.opflow"].PauliSumOp - else: - pauli_sum_check = () if isinstance(observable, SparsePauliOp): return observable - elif isinstance(observable, pauli_sum_check): - if isinstance(observable.coeff, ParameterExpression): - raise TypeError( - f"Observable must have numerical coefficient, not {type(observable.coeff)}." - ) - return observable.coeff * observable.primitive elif isinstance(observable, BaseOperator) and not isinstance(observable, BasePauli): return SparsePauliOp.from_operator(observable) else: diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index d2fd4db57f40..6d09bdf98875 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -921,7 +921,7 @@ SPARSE_PAULI_OP_LIST_ELEM ------------------------- -This represents an instance of :class:`.PauliSumOp`. +This represents an instance of :class:`.SparsePauliOp`. .. code-block:: c diff --git a/qiskit/result/sampled_expval.py b/qiskit/result/sampled_expval.py index 4968fc4b7436..b38840531018 100644 --- a/qiskit/result/sampled_expval.py +++ b/qiskit/result/sampled_expval.py @@ -40,7 +40,6 @@ def sampled_expectation_value(dist, oper): """ from .counts import Counts from qiskit.quantum_info import Pauli, SparsePauliOp - from qiskit.opflow import PauliOp, PauliSumOp # This should be removed when these return bit-string keys if isinstance(dist, (QuasiDistribution, ProbDistribution)): @@ -54,13 +53,6 @@ def sampled_expectation_value(dist, oper): elif isinstance(oper, Pauli): oper_strs = [oper.to_label()] coeffs = np.asarray([1.0]) - elif isinstance(oper, PauliOp): - oper_strs = [oper.primitive.to_label()] - coeffs = np.asarray([1.0]) - elif isinstance(oper, PauliSumOp): - spo = oper.primitive - oper_strs = spo.paulis.to_labels() - coeffs = np.asarray(spo.coeffs) * oper.coeff elif isinstance(oper, SparsePauliOp): oper_strs = oper.paulis.to_labels() coeffs = np.asarray(oper.coeffs) 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 65d39abf1ab7..402aa9146f0a 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 @@ -54,8 +54,8 @@ class Commuting2qGateRouter(TransformationPass): .. code-block:: python from qiskit import QuantumCircuit - from qiskit.opflow import PauliSumOp from qiskit.circuit.library import PauliEvolutionGate + from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler import Layout, CouplingMap, PassManager from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla @@ -69,7 +69,7 @@ class Commuting2qGateRouter(TransformationPass): ) # Define the circuit on virtual qubits - op = PauliSumOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]) + op = SparsePauliOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]) circ = QuantumCircuit(4) circ.append(PauliEvolutionGate(op, 1), range(4)) diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py index 8b92c5cdaf53..641b40c9f3e1 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py @@ -113,7 +113,7 @@ def _pauli_to_edge(pauli: Pauli) -> Tuple[int, ...]: return edge def _decompose_to_2q(self, dag: DAGCircuit, op: PauliEvolutionGate) -> DAGCircuit: - """Decompose the PauliSumOp into two-qubit. + """Decompose the SparsePauliOp into two-qubit. Args: dag: The dag needed to get access to qubits. diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 45200413ba2a..216d5693aecb 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -35,22 +35,9 @@ .. autofunction:: summarize_circuits .. autofunction:: get_entangler_map .. autofunction:: validate_entangler_map -.. autofunction:: has_ibmq -.. autofunction:: has_aer .. autofunction:: name_args .. autodata:: algorithm_globals -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - QuantumInstance - -A QuantumInstance holds the Qiskit `backend` as well as a number of compile and -runtime parameters controlling circuit compilation and execution. Quantum -algorithms are run on a device or simulator by passing a QuantumInstance setup -with the desired backend etc. - Optional Dependency Checkers (:mod:`qiskit.utils.optionals`) ============================================================ @@ -58,7 +45,6 @@ .. automodule:: qiskit.utils.optionals """ -from .quantum_instance import QuantumInstance from .deprecation import ( add_deprecation_to_docstring, deprecate_arg, @@ -76,7 +62,6 @@ from .circuit_utils import summarize_circuits from .entangler_map import get_entangler_map, validate_entangler_map -from .backend_utils import has_ibmq, has_aer from .name_unnamed_args import name_args from .algorithm_globals import algorithm_globals @@ -85,12 +70,9 @@ "LazyDependencyManager", "LazyImportTester", "LazySubprocessTester", - "QuantumInstance", "summarize_circuits", "get_entangler_map", "validate_entangler_map", - "has_ibmq", - "has_aer", "name_args", "algorithm_globals", "add_deprecation_to_docstring", diff --git a/qiskit/utils/backend_utils.py b/qiskit/utils/backend_utils.py deleted file mode 100644 index cdce30da8dc6..000000000000 --- a/qiskit/utils/backend_utils.py +++ /dev/null @@ -1,296 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""backend utility functions""" - -import logging -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - -_UNSUPPORTED_BACKENDS = ["unitary_simulator", "clifford_simulator"] - -# pylint: disable=no-name-in-module,unused-import - - -class ProviderCheck: - """Contains Provider verification info.""" - - def __init__(self) -> None: - self.has_ibmq = False - self.checked_ibmq = False - self.has_aer = False - self.checked_aer = False - - -_PROVIDER_CHECK = ProviderCheck() - - -def _get_backend_interface_version(backend): - """Get the backend version int.""" - backend_interface_version = getattr(backend, "version", None) - return backend_interface_version - - -def _get_backend_provider(backend): - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version > 1: - provider = backend.provider - else: - provider = backend.provider() - return provider - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def has_ibmq(): - """Check if IBMQ is installed.""" - if not _PROVIDER_CHECK.checked_ibmq: - try: - from qiskit.providers.ibmq import IBMQFactory - from qiskit.providers.ibmq.accountprovider import AccountProvider - - _PROVIDER_CHECK.has_ibmq = True - except Exception as ex: # pylint: disable=broad-except - _PROVIDER_CHECK.has_ibmq = False - logger.debug("IBMQFactory/AccountProvider not loaded: '%s'", str(ex)) - - _PROVIDER_CHECK.checked_ibmq = True - - return _PROVIDER_CHECK.has_ibmq - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def has_aer(): - """Check if Aer is installed.""" - if not _PROVIDER_CHECK.checked_aer: - try: - from qiskit.providers.aer import AerProvider - - _PROVIDER_CHECK.has_aer = True - except Exception as ex: # pylint: disable=broad-except - _PROVIDER_CHECK.has_aer = False - logger.debug("AerProvider not loaded: '%s'", str(ex)) - - _PROVIDER_CHECK.checked_aer = True - - return _PROVIDER_CHECK.has_aer - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_aer_provider(backend): - """Detect whether or not backend is from Aer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is AerProvider - """ - if has_aer(): - from qiskit.providers.aer import AerProvider - - if isinstance(_get_backend_provider(backend), AerProvider): - return True - from qiskit.providers.aer.backends.aerbackend import AerBackend - - return isinstance(backend, AerBackend) - - return False - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_basicaer_provider(backend): - """Detect whether or not backend is from BasicAer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is BasicAer - """ - from qiskit.providers.basicaer import BasicAerProvider - - return isinstance(_get_backend_provider(backend), BasicAerProvider) - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_ibmq_provider(backend): - """Detect whether or not backend is from IBMQ provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is IBMQ - """ - if has_ibmq(): - from qiskit.providers.ibmq.accountprovider import AccountProvider - - return isinstance(_get_backend_provider(backend), AccountProvider) - - return False - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_aer_statevector_backend(backend): - """ - Return True if backend object is statevector and from Aer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is statevector - """ - return is_statevector_backend(backend) and is_aer_provider(backend) - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_statevector_backend(backend): - """ - Return True if backend object is statevector. - - Args: - backend (Backend): backend instance - Returns: - bool: True is statevector - """ - if backend is None: - return False - backend_interface_version = _get_backend_interface_version(backend) - if has_aer(): - from qiskit.providers.aer.backends import AerSimulator, StatevectorSimulator - - if isinstance(backend, StatevectorSimulator): - return True - if isinstance(backend, AerSimulator): - if backend_interface_version <= 1: - name = backend.name() - else: - name = backend.name - if "aer_simulator_statevector" in name: - return True - if backend_interface_version <= 1: - return backend.name().startswith("statevector") - else: - return backend.name.startswith("statevector") - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_simulator_backend(backend): - """ - Return True if backend is a simulator. - - Args: - backend (Backend): backend instance - Returns: - bool: True is a simulator - """ - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version <= 1: - return backend.configuration().simulator - else: - if "simulator" in backend.name: - return True - return False - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_local_backend(backend): - """ - Return True if backend is a local backend. - - Args: - backend (Backend): backend instance - Returns: - bool: True is a local backend - """ - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version <= 1: - return backend.configuration().local - else: - if "simulator" in backend.name: - return True - return False - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def is_aer_qasm(backend): - """ - Return True if backend is Aer Qasm simulator - Args: - backend (Backend): backend instance - - Returns: - bool: True is Aer Qasm simulator - """ - ret = False - if is_aer_provider(backend): - if not is_statevector_backend(backend): - ret = True - return ret - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def support_backend_options(backend): - """ - Return True if backend supports backend_options - Args: - backend (Backend): backend instance - - Returns: - bool: True is support backend_options - """ - ret = False - if is_basicaer_provider(backend) or is_aer_provider(backend): - ret = True - return ret diff --git a/qiskit/utils/measurement_error_mitigation.py b/qiskit/utils/measurement_error_mitigation.py deleted file mode 100644 index 4702fc0b52b8..000000000000 --- a/qiskit/utils/measurement_error_mitigation.py +++ /dev/null @@ -1,272 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Measurement error mitigation""" - -import copy -from typing import List, Optional, Tuple, Dict, Callable - -from qiskit import compiler -from qiskit.providers import Backend -from qiskit.circuit import QuantumCircuit -from qiskit.qobj import QasmQobj -from qiskit.assembler.run_config import RunConfig -from qiskit.exceptions import QiskitError -from qiskit.utils.mitigation import ( - complete_meas_cal, - tensored_meas_cal, - CompleteMeasFitter, - TensoredMeasFitter, -) -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def get_measured_qubits( - transpiled_circuits: List[QuantumCircuit], -) -> Tuple[List[int], Dict[str, List[int]]]: - """ - Deprecated: Retrieve the measured qubits from transpiled circuits. - - Args: - transpiled_circuits: a list of transpiled circuits - - Returns: - The used and sorted qubit index - Key is qubit index str connected by '_', - value is the experiment index. {str: list[int]} - Raises: - QiskitError: invalid qubit mapping - """ - qubit_index = None - qubit_mappings = {} - for idx, qc in enumerate(transpiled_circuits): - measured_qubits = [] - for instruction in qc.data: - if instruction.operation.name != "measure": - continue - for qreg in qc.qregs: - if instruction.qubits[0] in qreg: - index = qreg[:].index(instruction.qubits[0]) - measured_qubits.append(index) - break - measured_qubits_str = "_".join([str(x) for x in measured_qubits]) - if measured_qubits_str not in qubit_mappings: - qubit_mappings[measured_qubits_str] = [] - qubit_mappings[measured_qubits_str].append(idx) - if qubit_index is None: - qubit_index = measured_qubits - elif set(qubit_index) != set(measured_qubits): - raise QiskitError( - "The used qubit index are different. ({}) vs ({}).\nCurrently, " - "we only support all circuits using the same set of qubits " - "regardless qubit order.".format(qubit_index, measured_qubits) - ) - - return sorted(qubit_index), qubit_mappings - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def get_measured_qubits_from_qobj(qobj: QasmQobj) -> Tuple[List[int], Dict[str, List[int]]]: - """ - Deprecated: Retrieve the measured qubits from transpiled circuits. - - Args: - qobj: qobj - - Returns: - the used and sorted qubit index - key is qubit index str connected by '_', - value is the experiment index. {str: list[int]} - Raises: - QiskitError: invalid qubit mapping - """ - - qubit_index = None - qubit_mappings = {} - - for idx, exp in enumerate(qobj.experiments): - measured_qubits = [] - for instr in exp.instructions: - if instr.name != "measure": - continue - measured_qubits.append(instr.qubits[0]) - measured_qubits_str = "_".join([str(x) for x in measured_qubits]) - if measured_qubits_str not in qubit_mappings: - qubit_mappings[measured_qubits_str] = [] - qubit_mappings[measured_qubits_str].append(idx) - if qubit_index is None: - qubit_index = measured_qubits - else: - if set(qubit_index) != set(measured_qubits): - raise QiskitError( - "The used qubit index are different. ({}) vs ({}).\nCurrently, " - "we only support all circuits using the same set of qubits " - "regardless qubit order.".format(qubit_index, measured_qubits) - ) - - return sorted(qubit_index), qubit_mappings - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def build_measurement_error_mitigation_circuits( - qubit_list: List[int], - fitter_cls: Callable, - backend: Backend, - backend_config: Optional[Dict] = None, - compile_config: Optional[Dict] = None, - mit_pattern: Optional[List[List[int]]] = None, -) -> Tuple[QuantumCircuit, List[str], List[str]]: - """Deprecated: Build measurement error mitigation circuits - Args: - qubit_list: list of ordered qubits used in the algorithm - fitter_cls: CompleteMeasFitter or TensoredMeasFitter - backend: backend instance - backend_config: configuration for backend - compile_config: configuration for compilation - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - Returns: - the circuit - the state labels for build MeasFitter - the labels of the calibration circuits - Raises: - QiskitError: when the fitter_cls is not recognizable. - """ - circlabel = "mcal" - - if not qubit_list: - raise QiskitError("The measured qubit list can not be [].") - - run = False - if fitter_cls == CompleteMeasFitter: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - run = True - elif fitter_cls == TensoredMeasFitter: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - run = True - if not run: - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError as ex: - # If ignis can't be imported we don't have a valid fitter - # class so just fail here with an appropriate error message - raise QiskitError(f"Unknown fitter {fitter_cls}") from ex - if fitter_cls == CompleteMeasFitter_IG: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - elif fitter_cls == TensoredMeasFitter_IG: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - else: - raise QiskitError(f"Unknown fitter {fitter_cls}") - - # the provided `qubit_list` would be used as the initial layout to - # assure the consistent qubit mapping used in the main circuits. - - tmp_compile_config = copy.deepcopy(compile_config) - tmp_compile_config["initial_layout"] = qubit_list - t_meas_calibs_circuits = compiler.transpile( - meas_calibs_circuits, backend, **backend_config, **tmp_compile_config - ) - return t_meas_calibs_circuits, state_labels, circlabel - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def build_measurement_error_mitigation_qobj( - qubit_list: List[int], - fitter_cls: Callable, - backend: Backend, - backend_config: Optional[Dict] = None, - compile_config: Optional[Dict] = None, - run_config: Optional[RunConfig] = None, - mit_pattern: Optional[List[List[int]]] = None, -) -> Tuple[QasmQobj, List[str], List[str]]: - """ - Args: - qubit_list: list of ordered qubits used in the algorithm - fitter_cls: CompleteMeasFitter or TensoredMeasFitter - backend: backend instance - backend_config: configuration for backend - compile_config: configuration for compilation - run_config: configuration for running a circuit - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - Returns: - the Qobj with calibration circuits at the beginning - the state labels for build MeasFitter - the labels of the calibration circuits - - Raises: - QiskitError: when the fitter_cls is not recognizable. - MissingOptionalLibraryError: Qiskit-Ignis not installed - """ - - circlabel = "mcal" - - if not qubit_list: - raise QiskitError("The measured qubit list can not be [].") - - if fitter_cls == CompleteMeasFitter: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - elif fitter_cls == TensoredMeasFitter: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - else: - raise QiskitError(f"Unknown fitter {fitter_cls}") - - # the provided `qubit_list` would be used as the initial layout to - # assure the consistent qubit mapping used in the main circuits. - - tmp_compile_config = copy.deepcopy(compile_config) - tmp_compile_config["initial_layout"] = qubit_list - t_meas_calibs_circuits = compiler.transpile( - meas_calibs_circuits, backend, **backend_config, **tmp_compile_config - ) - cals_qobj = compiler.assemble(t_meas_calibs_circuits, backend, **run_config.to_dict()) - if hasattr(cals_qobj.config, "parameterizations"): - del cals_qobj.config.parameterizations - return cals_qobj, state_labels, circlabel diff --git a/qiskit/utils/mitigation/__init__.py b/qiskit/utils/mitigation/__init__.py deleted file mode 100644 index 54251715424d..000000000000 --- a/qiskit/utils/mitigation/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This code was originally copied from the qiskit-ignis repsoitory see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/__init__.py -# it was migrated as qiskit-ignis is being deprecated - -""" -============================================================= -Measurement Mitigation Utils (:mod:`qiskit.utils.mitigation`) -============================================================= - -.. currentmodule:: qiskit.utils.mitigation - -.. deprecated:: 0.24.0 - This module is deprecated and will be removed no sooner than 3 months - after the release date. For code migration guidelines, - visit https://qisk.it/qi_migration. - -.. warning:: - - The user-facing API stability of this module is not guaranteed except for - its use with the :class:`~qiskit.utils.QuantumInstance` (i.e. using the - :class:`~qiskit.utils.mitigation.CompleteMeasFitter` or - :class:`~qiskit.utils.mitigation.TensoredMeasFitter` classes as values for the - ``meas_error_mitigation_cls``). The rest of this module should be treated as - an internal private API that can not be relied upon. - -Measurement correction -====================== - -The measurement calibration is used to mitigate measurement errors. -The main idea is to prepare all :math:`2^n` basis input states and compute -the probability of measuring counts in the other basis states. -From these calibrations, it is possible to correct the average results -of another experiment of interest. These tools are intended for use solely -with the :class:`~qiskit.utils.QuantumInstance` class as part of -:mod:`qiskit.opflow`. - -.. autosummary:: - :toctree: ../stubs/ - - CompleteMeasFitter - TensoredMeasFitter -""" - -# Measurement correction functions -from .circuits import complete_meas_cal, tensored_meas_cal -from .fitters import CompleteMeasFitter, TensoredMeasFitter diff --git a/qiskit/utils/mitigation/_filters.py b/qiskit/utils/mitigation/_filters.py deleted file mode 100644 index d29700338d4e..000000000000 --- a/qiskit/utils/mitigation/_filters.py +++ /dev/null @@ -1,510 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This code was originally copied from the qiskit-ignis see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/filters.py -# it was migrated as qiskit-ignis is being deprecated - -# pylint: disable=cell-var-from-loop - - -""" -Measurement correction filters. - -""" - -from typing import List -from copy import deepcopy - -import numpy as np - -import qiskit -from qiskit import QiskitError -from qiskit.tools import parallel_map -from qiskit.utils.mitigation.circuits import count_keys -from qiskit.utils.deprecation import deprecate_func - - -class MeasurementFilter: - """ - Deprecated: Measurement error mitigation filter. - - Produced from a measurement calibration fitter and can be applied - to data. - - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", - ) - def __init__(self, cal_matrix: np.matrix, state_labels: list): - """ - Initialize a measurement error mitigation filter using the cal_matrix - from a measurement calibration fitter. - - Args: - cal_matrix: the calibration matrix for applying the correction - state_labels: the states for the ordering of the cal matrix - """ - - self._cal_matrix = cal_matrix - self._state_labels = state_labels - - @property - def cal_matrix(self): - """Return cal_matrix.""" - return self._cal_matrix - - @property - def state_labels(self): - """return the state label ordering of the cal matrix""" - return self._state_labels - - @state_labels.setter - def state_labels(self, new_state_labels): - """set the state label ordering of the cal matrix""" - self._state_labels = new_state_labels - - @cal_matrix.setter - def cal_matrix(self, new_cal_matrix): - """Set cal_matrix.""" - self._cal_matrix = new_cal_matrix - - def apply(self, raw_data, method="least_squares"): - """Apply the calibration matrix to results. - - Args: - raw_data (dict or list): The data to be corrected. Can be in a number of forms: - - Form 1: a counts dictionary from results.get_counts - - Form 2: a list of counts of `length==len(state_labels)` - - Form 3: a list of counts of `length==M*len(state_labels)` where M is an - integer (e.g. for use with the tomography data) - - Form 4: a qiskit Result - - method (str): fitting method. If `None`, then least_squares is used. - - ``pseudo_inverse``: direct inversion of the A matrix - - ``least_squares``: constrained to have physical probabilities - - Returns: - dict or list: The corrected data in the same form as `raw_data` - - Raises: - QiskitError: if `raw_data` is not an integer multiple - of the number of calibrated states. - - """ - from scipy.optimize import minimize - from scipy import linalg as la - - # check forms of raw_data - if isinstance(raw_data, dict): - # counts dictionary - for data_label in raw_data.keys(): - if data_label not in self._state_labels: - raise QiskitError( - f"Unexpected state label '{data_label}'." - " Verify the fitter's state labels correspond to the input data." - ) - - data_format = 0 - # convert to form2 - raw_data2 = [np.zeros(len(self._state_labels), dtype=float)] - for stateidx, state in enumerate(self._state_labels): - raw_data2[0][stateidx] = raw_data.get(state, 0) - - elif isinstance(raw_data, list): - size_ratio = len(raw_data) / len(self._state_labels) - if len(raw_data) == len(self._state_labels): - data_format = 1 - raw_data2 = [raw_data] - elif int(size_ratio) == size_ratio: - data_format = 2 - size_ratio = int(size_ratio) - # make the list into chunks the size of state_labels for easier - # processing - raw_data2 = np.zeros([size_ratio, len(self._state_labels)]) - for i in range(size_ratio): - raw_data2[i][:] = raw_data[ - i * len(self._state_labels) : (i + 1) * len(self._state_labels) - ] - else: - raise QiskitError( - "Data list is not an integer multiple of the number of calibrated states" - ) - - elif isinstance(raw_data, qiskit.result.result.Result): - - # extract out all the counts, re-call the function with the - # counts and push back into the new result - new_result = deepcopy(raw_data) - - new_counts_list = parallel_map( - self._apply_correction, - [resultidx for resultidx, _ in enumerate(raw_data.results)], - task_args=(raw_data, method), - ) - - for resultidx, new_counts in new_counts_list: - new_result.results[resultidx].data.counts = new_counts - - return new_result - - else: - raise QiskitError("Unrecognized type for raw_data.") - - if method == "pseudo_inverse": - pinv_cal_mat = la.pinv(self._cal_matrix) - - # Apply the correction - for data_idx, _ in enumerate(raw_data2): - - if method == "pseudo_inverse": - raw_data2[data_idx] = np.dot(pinv_cal_mat, raw_data2[data_idx]) - - elif method == "least_squares": - nshots = sum(raw_data2[data_idx]) - - def fun(x): - return sum((raw_data2[data_idx] - np.dot(self._cal_matrix, x)) ** 2) - - x0 = np.random.rand(len(self._state_labels)) - x0 = x0 / sum(x0) - cons = {"type": "eq", "fun": lambda x: nshots - sum(x)} - bnds = tuple((0, nshots) for x in x0) - res = minimize(fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6) - raw_data2[data_idx] = res.x - - else: - raise QiskitError("Unrecognized method.") - - if data_format == 2: - # flatten back out the list - raw_data2 = raw_data2.flatten() - - elif data_format == 0: - # convert back into a counts dictionary - new_count_dict = {} - for stateidx, state in enumerate(self._state_labels): - if raw_data2[0][stateidx] != 0: - new_count_dict[state] = raw_data2[0][stateidx] - - raw_data2 = new_count_dict - else: - # TODO: should probably change to: - # raw_data2 = raw_data2[0].tolist() - raw_data2 = raw_data2[0] - return raw_data2 - - def _apply_correction(self, resultidx, raw_data, method): - """Wrapper to call apply with a counts dictionary.""" - new_counts = self.apply(raw_data.get_counts(resultidx), method=method) - return resultidx, new_counts - - -class TensoredFilter: - """ - Deprecated: Tensored measurement error mitigation filter. - - Produced from a tensored measurement calibration fitter and can be applied - to data. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", - ) - def __init__(self, cal_matrices: np.matrix, substate_labels_list: list, mit_pattern: list): - """ - Initialize a tensored measurement error mitigation filter using - the cal_matrices from a tensored measurement calibration fitter. - A simple usage this class is explained [here] - (https://qiskit.org/documentation/tutorials/noise/3_measurement_error_mitigation.html). - - Args: - cal_matrices: the calibration matrices for applying the correction. - substate_labels_list: for each calibration matrix - a list of the states (as strings, states in the subspace) - mit_pattern: for each calibration matrix - a list of the logical qubit indices (as int, states in the subspace) - """ - - self._cal_matrices = cal_matrices - self._qubit_list_sizes = [] - self._indices_list = [] - self._substate_labels_list = [] - self.substate_labels_list = substate_labels_list - self._mit_pattern = mit_pattern - - @property - def cal_matrices(self): - """Return cal_matrices.""" - return self._cal_matrices - - @cal_matrices.setter - def cal_matrices(self, new_cal_matrices): - """Set cal_matrices.""" - self._cal_matrices = deepcopy(new_cal_matrices) - - @property - def substate_labels_list(self): - """Return _substate_labels_list""" - return self._substate_labels_list - - @substate_labels_list.setter - def substate_labels_list(self, new_substate_labels_list): - """Return _substate_labels_list""" - self._substate_labels_list = new_substate_labels_list - - # get the number of qubits in each subspace - self._qubit_list_sizes = [] - for _, substate_label_list in enumerate(self._substate_labels_list): - self._qubit_list_sizes.append(int(np.log2(len(substate_label_list)))) - - # get the indices in the calibration matrix - self._indices_list = [] - for _, sub_labels in enumerate(self._substate_labels_list): - - self._indices_list.append({lab: ind for ind, lab in enumerate(sub_labels)}) - - @property - def qubit_list_sizes(self): - """Return _qubit_list_sizes.""" - return self._qubit_list_sizes - - @property - def nqubits(self): - """Return the number of qubits. See also MeasurementFilter.apply()""" - return sum(self._qubit_list_sizes) - - def apply( - self, - raw_data, - method="least_squares", - meas_layout=None, - ): - """ - Apply the calibration matrices to results. - - Args: - raw_data (dict or Result): The data to be corrected. Can be in one of two forms: - - * A counts dictionary from results.get_counts - - * A Qiskit Result - - method (str): fitting method. The following methods are supported: - - * 'pseudo_inverse': direct inversion of the cal matrices. - Mitigated counts can contain negative values - and the sum of counts would not equal to the shots. - Mitigation is conducted qubit wise: - For each qubit, mitigate the whole counts using the calibration matrices - which affect the corresponding qubit. - For example, assume we are mitigating the 3rd bit of the 4-bit counts - using '2\times 2' calibration matrix `A_3`. - When mitigating the count of '0110' in this step, - the following formula is applied: - `count['0110'] = A_3^{-1}[1, 0]*count['0100'] + A_3^{-1}[1, 1]*count['0110']`. - - The total time complexity of this method is `O(m2^{n + t})`, - where `n` is the size of calibrated qubits, - `m` is the number of sets in `mit_pattern`, - and `t` is the size of largest set of mit_pattern. - If the `mit_pattern` is shaped like `[[0], [1], [2], ..., [n-1]]`, - which corresponds to the tensor product noise model without cross-talk, - then the time complexity would be `O(n2^n)`. - If the `mit_pattern` is shaped like `[[0, 1, 2, ..., n-1]]`, - which exactly corresponds to the complete error mitigation, - then the time complexity would be `O(2^(n+n)) = O(4^n)`. - - - * 'least_squares': constrained to have physical probabilities. - Instead of directly applying inverse calibration matrices, - this method solve a constrained optimization problem to find - the closest probability vector to the result from 'pseudo_inverse' method. - Sequential least square quadratic programming (SLSQP) is used - in the internal process. - Every updating step in SLSQP takes `O(m2^{n+t})` time. - Since this method is using the SLSQP optimization over - the vector with lenght `2^n`, the mitigation for 8 bit counts - with the `mit_pattern = [[0], [1], [2], ..., [n-1]]` would - take 10 seconds or more. - - * If `None`, 'least_squares' is used. - - meas_layout (list of int): the mapping from classical registers to qubits - - * If you measure qubit `2` to clbit `0`, `0` to `1`, and `1` to `2`, - the list becomes `[2, 0, 1]` - - * If `None`, flatten(mit_pattern) is used. - - Returns: - dict or Result: The corrected data in the same form as raw_data - - Raises: - QiskitError: if raw_data is not in a one of the defined forms. - """ - from scipy.optimize import minimize - from scipy import linalg as la - - all_states = count_keys(self.nqubits) - num_of_states = 2**self.nqubits - - if meas_layout is None: - meas_layout = [] - for qubits in self._mit_pattern: - meas_layout += qubits - - # check forms of raw_data - if isinstance(raw_data, dict): - # counts dictionary - # convert to list - raw_data2 = [np.zeros(num_of_states, dtype=float)] - for state, count in raw_data.items(): - stateidx = int(state, 2) - raw_data2[0][stateidx] = count - - elif isinstance(raw_data, qiskit.result.result.Result): - - # extract out all the counts, re-call the function with the - # counts and push back into the new result - new_result = deepcopy(raw_data) - - new_counts_list = parallel_map( - self._apply_correction, - [resultidx for resultidx, _ in enumerate(raw_data.results)], - task_args=(raw_data, method, meas_layout), - ) - - for resultidx, new_counts in new_counts_list: - new_result.results[resultidx].data.counts = new_counts - - return new_result - - else: - raise QiskitError("Unrecognized type for raw_data.") - - if method == "pseudo_inverse": - pinv_cal_matrices = [] - for cal_mat in self._cal_matrices: - pinv_cal_matrices.append(la.pinv(cal_mat)) - - meas_layout = meas_layout[::-1] # reverse endian - qubits_to_clbits = [-1 for _ in range(max(meas_layout) + 1)] - for i, qubit in enumerate(meas_layout): - qubits_to_clbits[qubit] = i - - # Apply the correction - for data_idx, _ in enumerate(raw_data2): - - if method == "pseudo_inverse": - for pinv_cal_mat, pos_qubits, indices in zip( - pinv_cal_matrices, self._mit_pattern, self._indices_list - ): - inv_mat_dot_x = np.zeros([num_of_states], dtype=float) - pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits] - for state_idx, state in enumerate(all_states): - first_index = self.compute_index_of_cal_mat(state, pos_clbits, indices) - for i in range(len(pinv_cal_mat)): # i is index of pinv_cal_mat - source_state = self.flip_state(state, i, pos_clbits) - second_index = self.compute_index_of_cal_mat( - source_state, pos_clbits, indices - ) - inv_mat_dot_x[state_idx] += ( - pinv_cal_mat[first_index, second_index] - * raw_data2[data_idx][int(source_state, 2)] - ) - raw_data2[data_idx] = inv_mat_dot_x - - elif method == "least_squares": - - def fun(x): - mat_dot_x = deepcopy(x) - for cal_mat, pos_qubits, indices in zip( - self._cal_matrices, self._mit_pattern, self._indices_list - ): - res_mat_dot_x = np.zeros([num_of_states], dtype=float) - pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits] - for state_idx, state in enumerate(all_states): - second_index = self.compute_index_of_cal_mat(state, pos_clbits, indices) - for i in range(len(cal_mat)): - target_state = self.flip_state(state, i, pos_clbits) - first_index = self.compute_index_of_cal_mat( - target_state, pos_clbits, indices - ) - res_mat_dot_x[int(target_state, 2)] += ( - cal_mat[first_index, second_index] * mat_dot_x[state_idx] - ) - mat_dot_x = res_mat_dot_x - return sum((raw_data2[data_idx] - mat_dot_x) ** 2) - - x0 = np.random.rand(num_of_states) - x0 = x0 / sum(x0) - nshots = sum(raw_data2[data_idx]) - cons = {"type": "eq", "fun": lambda x: nshots - sum(x)} - bnds = tuple((0, nshots) for x in x0) - res = minimize(fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6) - raw_data2[data_idx] = res.x - - else: - raise QiskitError("Unrecognized method.") - - # convert back into a counts dictionary - new_count_dict = {} - for state_idx, state in enumerate(all_states): - if raw_data2[0][state_idx] != 0: - new_count_dict[state] = raw_data2[0][state_idx] - - return new_count_dict - - def flip_state(self, state: str, mat_index: int, flip_poses: List[int]) -> str: - """Flip the state according to the chosen qubit positions""" - flip_poses = [pos for i, pos in enumerate(flip_poses) if (mat_index >> i) & 1] - flip_poses = sorted(flip_poses) - new_state = "" - pos = 0 - for flip_pos in flip_poses: - new_state += state[pos:flip_pos] - new_state += str(int(state[flip_pos], 2) ^ 1) # flip the state - pos = flip_pos + 1 - new_state += state[pos:] - return new_state - - def compute_index_of_cal_mat(self, state: str, pos_qubits: List[int], indices: dict) -> int: - """Return the index of (pseudo inverse) calibration matrix for the input quantum state""" - sub_state = "" - for pos in pos_qubits: - sub_state += state[pos] - return indices[sub_state] - - def _apply_correction( - self, - resultidx, - raw_data, - method, - meas_layout, - ): - """Wrapper to call apply with a counts dictionary.""" - new_counts = self.apply( - raw_data.get_counts(resultidx), method=method, meas_layout=meas_layout - ) - return resultidx, new_counts diff --git a/qiskit/utils/mitigation/circuits.py b/qiskit/utils/mitigation/circuits.py deleted file mode 100644 index d6a6935d7c47..000000000000 --- a/qiskit/utils/mitigation/circuits.py +++ /dev/null @@ -1,253 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This code was originally copied from the qiskit-ignis repsoitory see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/circuits.py -# it was migrated to qiskit-terra as qiskit-ignis is being deprecated - -""" -Measurement calibration circuits. To apply the measurement mitigation -use the fitters to produce a filter. -""" -from typing import List, Tuple, Union -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def count_keys(num_qubits: int) -> List[str]: - """Deprecated: Return ordered count keys. - - Args: - num_qubits: The number of qubits in the generated list. - Returns: - The strings of all 0/1 combinations of the given number of qubits - Example: - >>> count_keys(3) - ['000', '001', '010', '011', '100', '101', '110', '111'] - """ - return [bin(j)[2:].zfill(num_qubits) for j in range(2**num_qubits)] - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def complete_meas_cal( - qubit_list: List[int] = None, - qr: Union[int, List["QuantumRegister"]] = None, - cr: Union[int, List["ClassicalRegister"]] = None, - circlabel: str = "", -) -> Tuple[List["QuantumCircuit"], List[str]]: - """ - Deprecated: Return a list of measurement calibration circuits for the full - Hilbert space. - - If the circuit contains :math:`n` qubits, then :math:`2^n` calibration circuits - are created, each of which creates a basis state. - - Args: - qubit_list: A list of qubits to perform the measurement correction on. - If `None`, and qr is given then assumed to be performed over the entire - qr. The calibration states will be labelled according to this ordering (default `None`). - - qr: Quantum registers (or their size). - If ``None``, one is created (default ``None``). - - cr: Classical registers (or their size). - If ``None``, one is created(default ``None``). - - circlabel: A string to add to the front of circuit names for - unique identification(default ' '). - - Returns: - A list of QuantumCircuit objects containing the calibration circuits. - - A list of calibration state labels. - - Additional Information: - The returned circuits are named circlabel+cal_XXX - where XXX is the basis state, - e.g., cal_1001. - - Pass the results of these circuits to the CompleteMeasurementFitter - constructor. - - Raises: - QiskitError: if both `qubit_list` and `qr` are `None`. - - """ - # Runtime imports to avoid circular imports causeed by QuantumInstance - # getting initialized by imported utils/__init__ which is imported - # by qiskit.circuit - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.classicalregister import ClassicalRegister - from qiskit.circuit.exceptions import QiskitError - - if qubit_list is None and qr is None: - raise QiskitError("Must give one of a qubit_list or a qr") - - # Create the registers if not already done - if qr is None: - qr = QuantumRegister(max(qubit_list) + 1) - - if isinstance(qr, int): - qr = QuantumRegister(qr) - - if qubit_list is None: - qubit_list = range(len(qr)) - - if isinstance(cr, int): - cr = ClassicalRegister(cr) - - nqubits = len(qubit_list) - - # labels for 2**n qubit states - state_labels = count_keys(nqubits) - - cal_circuits, _ = tensored_meas_cal([qubit_list], qr, cr, circlabel) - - return cal_circuits, state_labels - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def tensored_meas_cal( - mit_pattern: List[List[int]] = None, - qr: Union[int, List["QuantumRegister"]] = None, - cr: Union[int, List["ClassicalRegister"]] = None, - circlabel: str = "", -) -> Tuple[List["QuantumCircuit"], List[List[int]]]: - """ - Deprecated: Return a list of calibration circuits - - Args: - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - qr: A quantum register (or its size). - If `None`, one is created (default `None`). - - cr: A classical register (or its size). - If `None`, one is created (default `None`). - - circlabel: A string to add to the front of circuit names for - unique identification (default ' '). - - Returns: - A list of two QuantumCircuit objects containing the calibration circuits - mit_pattern - - Additional Information: - The returned circuits are named circlabel+cal_XXX - where XXX is the basis state, - e.g., cal_000 and cal_111. - - Pass the results of these circuits to the TensoredMeasurementFitter - constructor. - - Raises: - QiskitError: if both `mit_pattern` and `qr` are None. - QiskitError: if a qubit appears more than once in `mit_pattern`. - - """ - # Runtime imports to avoid circular imports causeed by QuantumInstance - # getting initialized by imported utils/__init__ which is imported - # by qiskit.circuit - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.classicalregister import ClassicalRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit - from qiskit.circuit.exceptions import QiskitError - - if mit_pattern is None and qr is None: - raise QiskitError("Must give one of mit_pattern or qr") - - if isinstance(qr, int): - qr = QuantumRegister(qr) - - qubits_in_pattern = [] - if mit_pattern is not None: - for qubit_list in mit_pattern: - for qubit in qubit_list: - if qubit in qubits_in_pattern: - raise QiskitError( - "mit_pattern cannot contain multiple instances of the same qubit" - ) - qubits_in_pattern.append(qubit) - - # Create the registers if not already done - if qr is None: - qr = QuantumRegister(max(qubits_in_pattern) + 1) - else: - qubits_in_pattern = range(len(qr)) - mit_pattern = [qubits_in_pattern] - - nqubits = len(qubits_in_pattern) - - # create classical bit registers - if cr is None: - cr = ClassicalRegister(nqubits) - - if isinstance(cr, int): - cr = ClassicalRegister(cr) - - qubits_list_sizes = [len(qubit_list) for qubit_list in mit_pattern] - nqubits = sum(qubits_list_sizes) - size_of_largest_group = max(qubits_list_sizes) - largest_labels = count_keys(size_of_largest_group) - - state_labels = [] - for largest_state in largest_labels: - basis_state = "" - for list_size in qubits_list_sizes: - basis_state = largest_state[:list_size] + basis_state - state_labels.append(basis_state) - - cal_circuits = [] - for basis_state in state_labels: - qc_circuit = QuantumCircuit(qr, cr, name=f"{circlabel}cal_{basis_state}") - - end_index = nqubits - for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): - - start_index = end_index - list_size - substate = basis_state[start_index:end_index] - - for qind in range(list_size): - if substate[list_size - qind - 1] == "1": - qc_circuit.x(qr[qubit_list[qind]]) - - end_index = start_index - - qc_circuit.barrier(qr) - - # add measurements - end_index = nqubits - for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): - - for qind in range(list_size): - qc_circuit.measure(qr[qubit_list[qind]], cr[nqubits - (end_index - qind)]) - - end_index -= list_size - - cal_circuits.append(qc_circuit) - - return cal_circuits, mit_pattern diff --git a/qiskit/utils/mitigation/fitters.py b/qiskit/utils/mitigation/fitters.py deleted file mode 100644 index a399b863d102..000000000000 --- a/qiskit/utils/mitigation/fitters.py +++ /dev/null @@ -1,493 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This code was originally copied from the qiskit-ignis see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/fitters.py -# it was migrated as qiskit-ignis is being deprecated - - -""" -Measurement correction fitters. -""" -from typing import List -import copy -import re - -import numpy as np - -from qiskit import QiskitError -from qiskit.utils.mitigation.circuits import count_keys -from qiskit.utils.mitigation._filters import MeasurementFilter, TensoredFilter -from qiskit.utils.deprecation import deprecate_func - - -class CompleteMeasFitter: - """ - Deprecated: Measurement correction fitter for a full calibration - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", - ) - def __init__( - self, - results, - state_labels: List[str], - qubit_list: List[int] = None, - circlabel: str = "", - ): - """ - Initialize a measurement calibration matrix from the results of running - the circuits returned by `measurement_calibration_circuits` - - A wrapper for the tensored fitter - - .. warning:: - - This class is not a public API. The internals are not stable and will - likely change. It is used solely for the - ``measurement_error_mitigation_cls`` kwarg of the - :class:`~qiskit.utils.QuantumInstance` class's constructor (as - a class not an instance). Anything outside of that usage does - not have the normal user-facing API stability. - - Args: - results: the results of running the measurement calibration - circuits. If this is `None` the user will set a calibration - matrix later. - state_labels: list of calibration state labels - returned from `measurement_calibration_circuits`. - The output matrix will obey this ordering. - qubit_list: List of the qubits (for reference and if the - subset is needed). If `None`, the qubit_list will be - created according to the length of state_labels[0]. - circlabel: if the qubits were labeled. - """ - if qubit_list is None: - qubit_list = range(len(state_labels[0])) - self._qubit_list = qubit_list - - self._tens_fitt = TensoredMeasFitter(results, [qubit_list], [state_labels], circlabel) - - @property - def cal_matrix(self): - """Return cal_matrix.""" - return self._tens_fitt.cal_matrices[0] - - @cal_matrix.setter - def cal_matrix(self, new_cal_matrix): - """set cal_matrix.""" - self._tens_fitt.cal_matrices = [copy.deepcopy(new_cal_matrix)] - - @property - def state_labels(self): - """Return state_labels.""" - return self._tens_fitt.substate_labels_list[0] - - @property - def qubit_list(self): - """Return list of qubits.""" - return self._qubit_list - - @state_labels.setter - def state_labels(self, new_state_labels): - """Set state label.""" - self._tens_fitt.substate_labels_list[0] = new_state_labels - - @property - def filter(self): - """Return a measurement filter using the cal matrix.""" - return MeasurementFilter(self.cal_matrix, self.state_labels) - - def add_data(self, new_results, rebuild_cal_matrix=True): - """ - Add measurement calibration data - - Args: - new_results (list or qiskit.result.Result): a single result or list - of result objects. - rebuild_cal_matrix (bool): rebuild the calibration matrix - """ - - self._tens_fitt.add_data(new_results, rebuild_cal_matrix) - - def subset_fitter(self, qubit_sublist): - """ - Return a fitter object that is a subset of the qubits in the original - list. - - Args: - qubit_sublist (list): must be a subset of qubit_list - - Returns: - CompleteMeasFitter: A new fitter that has the calibration for a - subset of qubits - - Raises: - QiskitError: If the calibration matrix is not initialized - """ - - if self._tens_fitt.cal_matrices is None: - raise QiskitError("Calibration matrix is not initialized") - - if qubit_sublist is None: - raise QiskitError("Qubit sublist must be specified") - - for qubit in qubit_sublist: - if qubit not in self._qubit_list: - raise QiskitError("Qubit not in the original set of qubits") - - # build state labels - new_state_labels = count_keys(len(qubit_sublist)) - - # mapping between indices in the state_labels and the qubits in - # the sublist - qubit_sublist_ind = [] - for sqb in qubit_sublist: - for qbind, qubit in enumerate(self._qubit_list): - if qubit == sqb: - qubit_sublist_ind.append(qbind) - - # states in the full calibration which correspond - # to the reduced labels - q_q_mapping = [] - state_labels_reduced = [] - for label in self.state_labels: - tmplabel = [label[index] for index in qubit_sublist_ind] - state_labels_reduced.append("".join(tmplabel)) - - for sub_lab_ind, _ in enumerate(new_state_labels): - q_q_mapping.append([]) - for labelind, label in enumerate(state_labels_reduced): - if label == new_state_labels[sub_lab_ind]: - q_q_mapping[-1].append(labelind) - - new_fitter = CompleteMeasFitter( - results=None, state_labels=new_state_labels, qubit_list=qubit_sublist - ) - - new_cal_matrix = np.zeros([len(new_state_labels), len(new_state_labels)]) - - # do a partial trace - for i in range(len(new_state_labels)): - for j in range(len(new_state_labels)): - - for q_q_i_map in q_q_mapping[i]: - for q_q_j_map in q_q_mapping[j]: - new_cal_matrix[i, j] += self.cal_matrix[q_q_i_map, q_q_j_map] - - new_cal_matrix[i, j] /= len(q_q_mapping[i]) - - new_fitter.cal_matrix = new_cal_matrix - - return new_fitter - - def readout_fidelity(self, label_list=None): - """ - Based on the results, output the readout fidelity which is the - normalized trace of the calibration matrix - - Args: - label_list (bool): If `None`, returns the average assignment fidelity - of a single state. Otherwise it returns the assignment fidelity - to be in any one of these states averaged over the second - index. - - Returns: - numpy.array: readout fidelity (assignment fidelity) - - Additional Information: - The on-diagonal elements of the calibration matrix are the - probabilities of measuring state 'x' given preparation of state - 'x' and so the normalized trace is the average assignment fidelity - """ - return self._tens_fitt.readout_fidelity(0, label_list) - - -class TensoredMeasFitter: - """ - Deprecated: Measurement correction fitter for a tensored calibration. - """ - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", - ) - def __init__( - self, - results, - mit_pattern: List[List[int]], - substate_labels_list: List[List[str]] = None, - circlabel: str = "", - ): - """ - Initialize a measurement calibration matrix from the results of running - the circuits returned by `measurement_calibration_circuits`. - - .. warning:: - - This class is not a public API. The internals are not stable and will - likely change. It is used solely for the - ``measurement_error_mitigation_cls`` kwarg of the - :class:`~qiskit.utils.QuantumInstance` class's constructor (as - a class not an instance). Anything outside of that usage does - not have the normal user-facing API stability. - - Args: - results: the results of running the measurement calibration - circuits. If this is `None`, the user will set calibration - matrices later. - - mit_pattern: qubits to perform the - measurement correction on, divided to groups according to - tensors - - substate_labels_list: for each - calibration matrix, the labels of its rows and columns. - If `None`, the labels are ordered lexicographically - - circlabel: if the qubits were labeled - - Raises: - ValueError: if the mit_pattern doesn't match the - substate_labels_list - """ - - self._result_list = [] - self._cal_matrices = None - self._circlabel = circlabel - self._mit_pattern = mit_pattern - - self._qubit_list_sizes = [len(qubit_list) for qubit_list in mit_pattern] - - self._indices_list = [] - if substate_labels_list is None: - self._substate_labels_list = [] - for list_size in self._qubit_list_sizes: - self._substate_labels_list.append(count_keys(list_size)) - else: - self._substate_labels_list = substate_labels_list - if len(self._qubit_list_sizes) != len(substate_labels_list): - raise ValueError("mit_pattern does not match substate_labels_list") - - self._indices_list = [] - for _, sub_labels in enumerate(self._substate_labels_list): - self._indices_list.append({lab: ind for ind, lab in enumerate(sub_labels)}) - - self.add_data(results) - - @property - def cal_matrices(self): - """Return cal_matrices.""" - return self._cal_matrices - - @cal_matrices.setter - def cal_matrices(self, new_cal_matrices): - """Set _cal_matrices.""" - self._cal_matrices = copy.deepcopy(new_cal_matrices) - - @property - def substate_labels_list(self): - """Return _substate_labels_list.""" - return self._substate_labels_list - - @property - def filter(self): - """Return a measurement filter using the cal matrices.""" - return TensoredFilter(self._cal_matrices, self._substate_labels_list, self._mit_pattern) - - @property - def nqubits(self): - """Return _qubit_list_sizes.""" - return sum(self._qubit_list_sizes) - - def add_data(self, new_results, rebuild_cal_matrix=True): - """ - Add measurement calibration data - - Args: - new_results (list or qiskit.result.Result): a single result or list - of Result objects. - rebuild_cal_matrix (bool): rebuild the calibration matrix - """ - - if new_results is None: - return - - if not isinstance(new_results, list): - new_results = [new_results] - - for result in new_results: - self._result_list.append(result) - - if rebuild_cal_matrix: - self._build_calibration_matrices() - - def readout_fidelity(self, cal_index=0, label_list=None): - """ - Based on the results, output the readout fidelity, which is the average - of the diagonal entries in the calibration matrices. - - Args: - cal_index(integer): readout fidelity for this index in _cal_matrices - label_list (list): Returns the average fidelity over of the groups - f states. In the form of a list of lists of states. If `None`, - then each state used in the construction of the calibration - matrices forms a group of size 1 - - Returns: - numpy.array: The readout fidelity (assignment fidelity) - - Raises: - QiskitError: If the calibration matrix has not been set for the - object. - - Additional Information: - The on-diagonal elements of the calibration matrices are the - probabilities of measuring state 'x' given preparation of state - 'x'. - """ - - if self._cal_matrices is None: - raise QiskitError("Cal matrix has not been set") - - if label_list is None: - label_list = [[label] for label in self._substate_labels_list[cal_index]] - - state_labels = self._substate_labels_list[cal_index] - fidelity_label_list = [] - if label_list is None: - fidelity_label_list = [[label] for label in state_labels] - else: - for fid_sublist in label_list: - fidelity_label_list.append([]) - for fid_statelabl in fid_sublist: - for label_idx, label in enumerate(state_labels): - if fid_statelabl == label: - fidelity_label_list[-1].append(label_idx) - continue - - # fidelity_label_list is a 2D list of indices in the - # cal_matrix, we find the assignment fidelity of each - # row and average over the list - assign_fid_list = [] - - for fid_label_sublist in fidelity_label_list: - assign_fid_list.append(0) - for state_idx_i in fid_label_sublist: - for state_idx_j in fid_label_sublist: - assign_fid_list[-1] += self._cal_matrices[cal_index][state_idx_i][state_idx_j] - assign_fid_list[-1] /= len(fid_label_sublist) - - return np.mean(assign_fid_list) - - def _build_calibration_matrices(self): - """ - Build the measurement calibration matrices from the results of running - the circuits returned by `measurement_calibration`. - """ - - # initialize the set of empty calibration matrices - self._cal_matrices = [] - for list_size in self._qubit_list_sizes: - self._cal_matrices.append(np.zeros([2**list_size, 2**list_size], dtype=float)) - - # go through for each calibration experiment - for result in self._result_list: - for experiment in result.results: - circ_name = experiment.header.name - # extract the state from the circuit name - # this was the prepared state - circ_search = re.search("(?<=" + self._circlabel + "cal_)\\w+", circ_name) - - # this experiment is not one of the calcs so skip - if circ_search is None: - continue - - state = circ_search.group(0) - - # get the counts from the result - state_cnts = result.get_counts(circ_name) - for measured_state, counts in state_cnts.items(): - end_index = self.nqubits - for cal_ind, cal_mat in enumerate(self._cal_matrices): - - start_index = end_index - self._qubit_list_sizes[cal_ind] - - substate_index = self._indices_list[cal_ind][state[start_index:end_index]] - measured_substate_index = self._indices_list[cal_ind][ - measured_state[start_index:end_index] - ] - end_index = start_index - - cal_mat[measured_substate_index][substate_index] += counts - - for mat_index, _ in enumerate(self._cal_matrices): - sums_of_columns = np.sum(self._cal_matrices[mat_index], axis=0) - self._cal_matrices[mat_index] = np.divide( - self._cal_matrices[mat_index], - sums_of_columns, - out=np.zeros_like(self._cal_matrices[mat_index]), - where=sums_of_columns != 0, - ) - - def subset_fitter(self, qubit_sublist): - """Return a fitter object that is a subset of the qubits in the original list. - - This is only a partial implementation of the ``subset_fitter`` method since only - mitigation patterns of length 1 are supported. This corresponds to patterns of the - form ``[[0], [1], [2], ...]``. Note however, that such patterns are a good first - approximation to mitigate readout errors on large quantum circuits. - - Args: - qubit_sublist (list): must be a subset of qubit_list - - Returns: - TensoredMeasFitter: A new fitter that has the calibration for a - subset of qubits - - Raises: - QiskitError: If the calibration matrix is not initialized - QiskitError: If the mit pattern is not a tensor of single-qubit - measurement error mitigation. - QiskitError: If a qubit in the given ``qubit_sublist`` is not in the list of - qubits in the mit. pattern. - """ - if self._cal_matrices is None: - raise QiskitError("Calibration matrices are not initialized.") - - if qubit_sublist is None: - raise QiskitError("Qubit sublist must be specified.") - - if not all(len(tensor) == 1 for tensor in self._mit_pattern): - raise QiskitError( - f"Each element in the mit pattern should have length 1. Found {self._mit_pattern}." - ) - - supported_qubits = {tensor[0] for tensor in self._mit_pattern} - for qubit in qubit_sublist: - if qubit not in supported_qubits: - raise QiskitError(f"Qubit {qubit} is not in the mit pattern {self._mit_pattern}.") - - new_mit_pattern = [[idx] for idx in qubit_sublist] - new_substate_labels_list = [self._substate_labels_list[idx] for idx in qubit_sublist] - - new_fitter = TensoredMeasFitter( - results=None, mit_pattern=new_mit_pattern, substate_labels_list=new_substate_labels_list - ) - - new_fitter.cal_matrices = [self._cal_matrices[idx] for idx in qubit_sublist] - - return new_fitter diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py deleted file mode 100644 index f27050782fa5..000000000000 --- a/qiskit/utils/quantum_instance.py +++ /dev/null @@ -1,947 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Quantum Instance module""" - -from typing import Optional, List, Union, Dict, Callable, Tuple -from enum import Enum -import copy -import logging -import time -import warnings - -import numpy as np - -from qiskit.qobj import QasmQobj, PulseQobj -from qiskit.utils import circuit_utils -from qiskit.exceptions import QiskitError -from qiskit.utils.backend_utils import ( - is_ibmq_provider, - is_statevector_backend, - is_simulator_backend, - is_local_backend, - is_basicaer_provider, - support_backend_options, - _get_backend_provider, - _get_backend_interface_version, -) -from qiskit.utils.mitigation import ( - CompleteMeasFitter, - TensoredMeasFitter, -) -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class _MeasFitterType(Enum): - """Meas Fitter Type.""" - - COMPLETE_MEAS_FITTER = 0 - TENSORED_MEAS_FITTER = 1 - - @staticmethod - def type_from_class(meas_class): - """ - Returns fitter type from class - """ - if meas_class == CompleteMeasFitter: - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif meas_class == TensoredMeasFitter: - return _MeasFitterType.TENSORED_MEAS_FITTER - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError: - pass - if meas_class == CompleteMeasFitter_IG: - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the CompleteMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif meas_class == TensoredMeasFitter_IG: - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the TensoredMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.TENSORED_MEAS_FITTER - else: - raise QiskitError(f"Unknown fitter {meas_class}") - - @staticmethod - def type_from_instance(meas_instance): - """ - Returns fitter type from instance - """ - if isinstance(meas_instance, CompleteMeasFitter): - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif isinstance(meas_instance, TensoredMeasFitter): - return _MeasFitterType.TENSORED_MEAS_FITTER - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError: - pass - if isinstance(meas_instance, CompleteMeasFitter_IG): - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the CompleteMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif isinstance(meas_instance, TensoredMeasFitter_IG): - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the TensoredMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.TENSORED_MEAS_FITTER - else: - raise QiskitError(f"Unknown fitter {meas_instance}") - - -class QuantumInstance: - """Deprecated: Quantum Backend including execution setting.""" - - _BACKEND_CONFIG = ["basis_gates", "coupling_map"] - _COMPILE_CONFIG = ["initial_layout", "seed_transpiler", "optimization_level"] - _RUN_CONFIG = ["shots", "memory", "seed_simulator"] - _QJOB_CONFIG = ["timeout", "wait"] - _NOISE_CONFIG = ["noise_model"] - - # https://github.com/Qiskit/qiskit-aer/blob/master/qiskit/providers/aer/backends/qasm_simulator.py - _BACKEND_OPTIONS_QASM_ONLY = ["statevector_sample_measure_opt", "max_parallel_shots"] - _BACKEND_OPTIONS = [ - "initial_statevector", - "chop_threshold", - "max_parallel_threads", - "max_parallel_experiments", - "statevector_parallel_threshold", - "statevector_hpc_gate_opt", - ] + _BACKEND_OPTIONS_QASM_ONLY - - @deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", - ) - def __init__( - self, - backend, - # run config - shots: Optional[int] = None, - seed_simulator: Optional[int] = None, - # backend properties - basis_gates: Optional[List[str]] = None, - coupling_map=None, - # transpile - initial_layout=None, - pass_manager=None, - bound_pass_manager=None, - seed_transpiler: Optional[int] = None, - optimization_level: Optional[int] = None, - # simulation - backend_options: Optional[Dict] = None, - noise_model=None, - # job - timeout: Optional[float] = None, - wait: float = 5.0, - # others - skip_qobj_validation: bool = True, - measurement_error_mitigation_cls: Optional[Callable] = None, - cals_matrix_refresh_period: int = 30, - measurement_error_mitigation_shots: Optional[int] = None, - job_callback: Optional[Callable] = None, - mit_pattern: Optional[List[List[int]]] = None, - max_job_retries: int = 50, - ) -> None: - """ - Quantum Instance holds a Qiskit Terra backend as well as configuration for circuit - transpilation and execution. When provided to an Aqua algorithm the algorithm will - execute the circuits it needs to run using the instance. - - Args: - backend (Backend): Instance of selected backend - shots: Number of repetitions of each circuit, for sampling. If None, the shots are - extracted from the backend. If the backend has none set, the default is 1024. - seed_simulator: Random seed for simulators - basis_gates: List of basis gate names supported by the - target. Defaults to basis gates of the backend. - coupling_map (Optional[Union['CouplingMap', List[List]]]): - Coupling map (perhaps custom) to target in mapping - initial_layout (Optional[Union['Layout', Dict, List]]): - Initial layout of qubits in mapping - pass_manager (Optional['PassManager']): Pass manager to handle how to compile the circuits. - To run only this pass manager and not the ``bound_pass_manager``, call the - :meth:`~qiskit.utils.QuantumInstance.transpile` method with the argument - ``pass_manager=quantum_instance.unbound_pass_manager``. - bound_pass_manager (Optional['PassManager']): A second pass manager to apply on bound - circuits only, that is, circuits without any free parameters. To only run this pass - manager and not ``pass_manager`` call the - :meth:`~qiskit.utils.QuantumInstance.transpile` method with the argument - ``pass_manager=quantum_instance.bound_pass_manager``. - manager should also be run. - seed_transpiler: The random seed for circuit mapper - optimization_level: How much optimization to perform on the circuits. - Higher levels generate more optimized circuits, at the expense of longer - transpilation time. - backend_options: All running options for backend, please refer - to the provider of the backend for information as to what options it supports. - noise_model (Optional['NoiseModel']): noise model for simulator - timeout: Seconds to wait for job. If None, wait indefinitely. - wait: Seconds between queries for job result - skip_qobj_validation: Bypass Qobj validation to decrease circuit - processing time during submission to backend. - measurement_error_mitigation_cls: The approach to mitigate - measurement errors. The classes :class:`~qiskit.utils.mitigation.CompleteMeasFitter` - or :class:`~qiskit.utils.mitigation.TensoredMeasFitter` from the - :mod:`qiskit.utils.mitigation` module can be used here as exact values, not - instances. ``TensoredMeasFitter`` doesn't support the ``subset_fitter`` method. - cals_matrix_refresh_period: How often to refresh the calibration - matrix in measurement mitigation. in minutes - measurement_error_mitigation_shots: The number of shots number for - building calibration matrix. If None, the main `shots` parameter value is used. - job_callback: Optional user supplied callback which can be used - to monitor job progress as jobs are submitted for processing by an Aqua algorithm. - The callback is provided the following arguments: `job_id, job_status, - queue_position, job` - mit_pattern: Qubits on which to perform the TensoredMeasFitter - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - max_job_retries(int): positive non-zero number of trials for the job set (-1 for - infinite trials) (default: 50) - - Raises: - QiskitError: the shots exceeds the maximum number of shots - QiskitError: set noise model but the backend does not support that - QiskitError: set backend_options but the backend does not support that - """ - self._backend = backend - self._backend_interface_version = _get_backend_interface_version(self._backend) - self._pass_manager = pass_manager - self._bound_pass_manager = bound_pass_manager - - # if the shots are none, try to get them from the backend - if shots is None: - from qiskit.providers.backend import Backend # pylint: disable=cyclic-import - - if isinstance(backend, Backend): - if hasattr(backend, "options"): # should always be true for V1 - backend_shots = backend.options.get("shots", 1024) - if shots != backend_shots: - logger.info( - "Overwriting the number of shots in the quantum instance with " - "the settings from the backend." - ) - shots = backend_shots - - # safeguard if shots are still not set - if shots is None: - shots = 1024 - - # pylint: disable=cyclic-import - from qiskit.assembler.run_config import RunConfig - - run_config = RunConfig(shots=shots) - if seed_simulator is not None: - run_config.seed_simulator = seed_simulator - - self._run_config = run_config - - # setup backend config - if self._backend_interface_version <= 1: - basis_gates = basis_gates or backend.configuration().basis_gates - coupling_map = coupling_map or getattr(backend.configuration(), "coupling_map", None) - self._backend_config = {"basis_gates": basis_gates, "coupling_map": coupling_map} - else: - self._backend_config = {} - - # setup compile config - self._compile_config = { - "initial_layout": initial_layout, - "seed_transpiler": seed_transpiler, - "optimization_level": optimization_level, - } - - # setup job config - self._qjob_config = ( - {"timeout": timeout} if self.is_local else {"timeout": timeout, "wait": wait} - ) - - # setup noise config - self._noise_config = {} - if noise_model is not None: - if is_simulator_backend(self._backend) and not is_basicaer_provider(self._backend): - self._noise_config = {"noise_model": noise_model} - else: - raise QiskitError( - "The noise model is not supported " - "on the selected backend {} ({}) " - "only certain backends, such as Aer qasm simulator " - "support noise.".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - # setup backend options for run - self._backend_options = {} - if backend_options is not None: - if support_backend_options(self._backend): - self._backend_options = {"backend_options": backend_options} - else: - raise QiskitError( - "backend_options can not used with the backends in IBMQ provider." - ) - - # setup measurement error mitigation - self._meas_error_mitigation_cls = None - if self.is_statevector: - if measurement_error_mitigation_cls is not None: - raise QiskitError( - "Measurement error mitigation does not work with the statevector simulation." - ) - else: - self._meas_error_mitigation_cls = measurement_error_mitigation_cls - self._meas_error_mitigation_fitters: Dict[str, Tuple[np.ndarray, float]] = {} - # TODO: support different fitting method in error mitigation? - self._meas_error_mitigation_method = "least_squares" - self._cals_matrix_refresh_period = cals_matrix_refresh_period - self._meas_error_mitigation_shots = measurement_error_mitigation_shots - self._mit_pattern = mit_pattern - - if self._meas_error_mitigation_cls is not None: - logger.info( - "The measurement error mitigation is enabled. " - "It will automatically submit an additional job to help " - "calibrate the result of other jobs. " - "The current approach will submit a job with 2^N circuits " - "to build the calibration matrix, " - "where N is the number of measured qubits. " - "Furthermore, Aqua will re-use the calibration matrix for %s minutes " - "and re-build it after that.", - self._cals_matrix_refresh_period, - ) - - # setup others - if is_ibmq_provider(self._backend): - if skip_qobj_validation: - logger.info( - "skip_qobj_validation was set True but this setting is not " - "supported by IBMQ provider and has been ignored." - ) - skip_qobj_validation = False - self._skip_qobj_validation = skip_qobj_validation - self._circuit_summary = False - self._job_callback = job_callback - self._time_taken = 0.0 - self._max_job_retries = max_job_retries - logger.info(self) - - def __str__(self) -> str: - """Overload string. - - Returns: - str: the info of the object. - """ - from qiskit import __version__ as terra_version - - info = f"\nQiskit Terra version: {terra_version}\n" - info += "Backend: '{} ({})', with following setting:\n{}\n{}\n{}\n{}\n{}\n{}".format( - self.backend_name, - _get_backend_provider(self._backend), - self._backend_config, - self._compile_config, - self._run_config, - self._qjob_config, - self._backend_options, - self._noise_config, - ) - - info += f"\nMeasurement mitigation: {self._meas_error_mitigation_cls}" - - return info - - @property - def unbound_pass_manager(self): - """Return the pass manager for designated for unbound circuits. - - Returns: - Optional['PassManager']: The pass manager for unbound circuits, if it has been set. - """ - return self._pass_manager - - @property - def bound_pass_manager(self): - """Return the pass manager for designated for bound circuits. - - Returns: - Optional['PassManager']: The pass manager for bound circuits, if it has been set. - """ - return self._bound_pass_manager - - def transpile(self, circuits, pass_manager=None): - """A wrapper to transpile circuits to allow algorithm access the transpiled circuits. - - Args: - circuits (Union['QuantumCircuit', List['QuantumCircuit']]): circuits to transpile - pass_manager (Optional['PassManager']): A pass manager to transpile the circuits. If - none is given, but either ``pass_manager`` or ``bound_pass_manager`` has been set - in the initializer, these are run. If none has been provided there either, the - backend and compile configs from the initializer are used. - - Returns: - List['QuantumCircuit']: The transpiled circuits, it is always a list even though - the length is one. - """ - # pylint: disable=cyclic-import - from qiskit import compiler - from qiskit.transpiler import PassManager - - # if no pass manager here is given, check if they have been set in the init - if pass_manager is None: - # if they haven't been set in the init, use the transpile args from the init - if self._pass_manager is None and self._bound_pass_manager is None: - transpiled_circuits = compiler.transpile( - circuits, self._backend, **self._backend_config, **self._compile_config - ) - # it they have been set, run them - else: - pass_manager = PassManager() - if self._pass_manager is not None: - pass_manager += self._pass_manager # check if None - if self._bound_pass_manager is not None: - pass_manager += self._bound_pass_manager - - transpiled_circuits = pass_manager.run(circuits) - # custom pass manager set by user - else: - transpiled_circuits = pass_manager.run(circuits) - - if not isinstance(transpiled_circuits, list): - transpiled_circuits = [transpiled_circuits] - - if logger.isEnabledFor(logging.DEBUG) and self._circuit_summary: - logger.debug("==== Before transpiler ====") - logger.debug(circuit_utils.summarize_circuits(circuits)) - if transpiled_circuits is not None: - logger.debug("==== After transpiler ====") - logger.debug(circuit_utils.summarize_circuits(transpiled_circuits)) - - return transpiled_circuits - - def assemble(self, circuits) -> Union[QasmQobj, PulseQobj]: - """assemble circuits""" - # pylint: disable=cyclic-import - from qiskit import compiler - - return compiler.assemble(circuits, **self._run_config.to_dict()) - - def execute(self, circuits, had_transpiled: bool = False): - """ - A wrapper to interface with quantum backend. - - Args: - circuits (Union['QuantumCircuit', List['QuantumCircuit']]): - circuits to execute - had_transpiled: whether or not circuits had been transpiled - - Raises: - QiskitError: Invalid error mitigation fitter class - QiskitError: TensoredMeasFitter class doesn't support subset fitter - MissingOptionalLibraryError: Ignis not installed - - - Returns: - Result: result object - - TODO: Maybe we can combine the circuits for the main ones and calibration circuits before - assembling to the qobj. - """ - from qiskit.utils.run_circuits import run_circuits - from qiskit.utils.measurement_error_mitigation import ( - get_measured_qubits, - build_measurement_error_mitigation_circuits, - ) - - if had_transpiled: - # Convert to a list or make a copy. - # The measurement mitigation logic expects a list and - # may change it in place. This makes sure that logic works - # and any future logic that may change the input. - # It also makes the code easier: it will always deal with a list. - if isinstance(circuits, list): - circuits = circuits.copy() - else: - circuits = [circuits] - else: - # transpile here, the method always returns a copied list - circuits = self.transpile(circuits) - - if self.is_statevector and "aer_simulator_statevector" in self.backend_name: - try: - from qiskit.providers.aer.library import SaveStatevector - - def _find_save_state(data): - for instruction in reversed(data): - if isinstance(instruction.operation, SaveStatevector): - return True - return False - - if isinstance(circuits, list): - for circuit in circuits: - if not _find_save_state(circuit.data): - circuit.save_statevector() - else: - if not _find_save_state(circuits.data): - circuits.save_statevector() - except ImportError: - pass - - if self._meas_error_mitigation_cls is not None: - qubit_index, qubit_mappings = get_measured_qubits(circuits) - mit_pattern = self._mit_pattern - if mit_pattern is None: - mit_pattern = [[i] for i in range(len(qubit_index))] - qubit_index_str = "_".join([str(x) for x in qubit_index]) + "_{}".format( - self._meas_error_mitigation_shots or self._run_config.shots - ) - meas_error_mitigation_fitter, timestamp = self._meas_error_mitigation_fitters.get( - qubit_index_str, (None, 0.0) - ) - - if meas_error_mitigation_fitter is None: - # check the asked qubit_index are the subset of build matrices - for key, _ in self._meas_error_mitigation_fitters.items(): - stored_qubit_index = [int(x) for x in key.split("_")[:-1]] - stored_shots = int(key.split("_")[-1]) - if len(qubit_index) < len(stored_qubit_index): - tmp = list(set(qubit_index + stored_qubit_index)) - if ( - sorted(tmp) == sorted(stored_qubit_index) - and self._run_config.shots == stored_shots - ): - # the qubit used in current job is the subset and shots are the same - ( - meas_error_mitigation_fitter, - timestamp, - ) = self._meas_error_mitigation_fitters.get(key, (None, 0.0)) - meas_error_mitigation_fitter = ( - meas_error_mitigation_fitter.subset_fitter( - qubit_sublist=qubit_index - ) - ) - logger.info( - "The qubits used in the current job is the subset of " - "previous jobs, " - "reusing the calibration matrix if it is not out-of-date." - ) - - build_cals_matrix = ( - self.maybe_refresh_cals_matrix(timestamp) or meas_error_mitigation_fitter is None - ) - - cal_circuits = None - prepended_calibration_circuits: int = 0 - if build_cals_matrix: - logger.info("Updating to also run measurement error mitigation.") - use_different_shots = not ( - self._meas_error_mitigation_shots is None - or self._meas_error_mitigation_shots == self._run_config.shots - ) - temp_run_config = copy.deepcopy(self._run_config) - if use_different_shots: - temp_run_config.shots = self._meas_error_mitigation_shots - ( - cal_circuits, - state_labels, - circuit_labels, - ) = build_measurement_error_mitigation_circuits( - qubit_index, - self._meas_error_mitigation_cls, - self._backend, - self._backend_config, - self._compile_config, - mit_pattern=mit_pattern, - ) - if use_different_shots: - cals_result = run_circuits( - cal_circuits, - self._backend, - qjob_config=self._qjob_config, - backend_options=self._backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += cals_result.time_taken - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self.run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - else: - circuits[0:0] = cal_circuits - prepended_calibration_circuits = len(cal_circuits) - if hasattr(self.run_config, "parameterizations"): - cal_run_config = copy.deepcopy(self.run_config) - cal_run_config.parameterizations[0:0] = [[]] * len(cal_circuits) - else: - cal_run_config = self.run_config - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=cal_run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - cals_result = result - logger.info("Building calibration matrix for measurement error mitigation.") - meas_type = _MeasFitterType.type_from_class(self._meas_error_mitigation_cls) - if meas_type == _MeasFitterType.COMPLETE_MEAS_FITTER: - meas_error_mitigation_fitter = self._meas_error_mitigation_cls( - cals_result, state_labels, qubit_list=qubit_index, circlabel=circuit_labels - ) - elif meas_type == _MeasFitterType.TENSORED_MEAS_FITTER: - meas_error_mitigation_fitter = self._meas_error_mitigation_cls( - cals_result, mit_pattern=state_labels, circlabel=circuit_labels - ) - self._meas_error_mitigation_fitters[qubit_index_str] = ( - meas_error_mitigation_fitter, - time.time(), - ) - else: - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - - if meas_error_mitigation_fitter is not None: - logger.info("Performing measurement error mitigation.") - if ( - hasattr(self._run_config, "parameterizations") - and len(self._run_config.parameterizations) > 0 - and len(self._run_config.parameterizations[0]) > 0 - and len(self._run_config.parameterizations[0][0]) > 0 - ): - num_circuit_templates = len(self._run_config.parameterizations) - num_param_variations = len(self._run_config.parameterizations[0][0]) - num_circuits = num_circuit_templates * num_param_variations - else: - input_circuits = circuits[prepended_calibration_circuits:] - num_circuits = len(input_circuits) - skip_num_circuits = len(result.results) - num_circuits - # remove the calibration counts from result object to assure the length of - # ExperimentalResult is equal length to input circuits - result.results = result.results[skip_num_circuits:] - tmp_result = copy.deepcopy(result) - for qubit_index_str, c_idx in qubit_mappings.items(): - curr_qubit_index = [int(x) for x in qubit_index_str.split("_")] - tmp_result.results = [result.results[i] for i in c_idx] - if curr_qubit_index == qubit_index: - tmp_fitter = meas_error_mitigation_fitter - elif isinstance(meas_error_mitigation_fitter, TensoredMeasFitter): - # Different from the complete meas. fitter as only the Terra fitter - # implements the ``subset_fitter`` method. - tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index) - elif _MeasFitterType.COMPLETE_MEAS_FITTER == _MeasFitterType.type_from_instance( - meas_error_mitigation_fitter - ): - tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index) - else: - raise QiskitError( - "{} doesn't support subset_fitter.".format( - meas_error_mitigation_fitter.__class__.__name__ - ) - ) - tmp_result = tmp_fitter.filter.apply( - tmp_result, self._meas_error_mitigation_method - ) - for i, n in enumerate(c_idx): - # convert counts to integer and remove 0 values - tmp_result.results[i].data.counts = { - k: round(v) - for k, v in tmp_result.results[i].data.counts.items() - if round(v) != 0 - } - result.results[n] = tmp_result.results[i] - - else: - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - - if self._circuit_summary: - self._circuit_summary = False - - return result - - def set_config(self, **kwargs): - """Set configurations for the quantum instance.""" - for k, v in kwargs.items(): - if k in QuantumInstance._RUN_CONFIG: - setattr(self._run_config, k, v) - elif k in QuantumInstance._QJOB_CONFIG: - self._qjob_config[k] = v - elif k in QuantumInstance._COMPILE_CONFIG: - self._compile_config[k] = v - elif k in QuantumInstance._BACKEND_CONFIG: - self._backend_config[k] = v - elif k in QuantumInstance._BACKEND_OPTIONS: - if not support_backend_options(self._backend): - raise QiskitError( - "backend_options can not be used with this backend " - "{} ({}).".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - if k in QuantumInstance._BACKEND_OPTIONS_QASM_ONLY and self.is_statevector: - raise QiskitError( - "'{}' is only applicable for qasm simulator but " - "statevector simulator is used as the backend." - ) - - if "backend_options" not in self._backend_options: - self._backend_options["backend_options"] = {} - self._backend_options["backend_options"][k] = v - elif k in QuantumInstance._NOISE_CONFIG: - if not is_simulator_backend(self._backend) or is_basicaer_provider(self._backend): - raise QiskitError( - "The noise model is not supported on the selected backend {} ({}) " - "only certain backends, such as Aer qasm support " - "noise.".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - self._noise_config[k] = v - - else: - raise ValueError(f"unknown setting for the key ({k}).") - - @property - def time_taken(self) -> float: - """Accumulated time taken for execution.""" - return self._time_taken - - def reset_execution_results(self) -> None: - """Reset execution results""" - self._time_taken = 0.0 - - @property - def qjob_config(self): - """Getter of qjob_config.""" - return self._qjob_config - - @property - def backend_config(self): - """Getter of backend_config.""" - return self._backend_config - - @property - def compile_config(self): - """Getter of compile_config.""" - return self._compile_config - - @property - def run_config(self): - """Getter of run_config.""" - return self._run_config - - @property - def noise_config(self): - """Getter of noise_config.""" - return self._noise_config - - @property - def backend_options(self): - """Getter of backend_options.""" - return self._backend_options - - @property - def circuit_summary(self): - """Getter of circuit summary.""" - return self._circuit_summary - - @circuit_summary.setter - def circuit_summary(self, new_value): - """sets circuit summary""" - self._circuit_summary = new_value - - @property - def max_job_retries(self): - """Getter of max tries""" - return self._max_job_retries - - @max_job_retries.setter - def max_job_retries(self, new_value): - """Sets the maximum tries""" - if not isinstance(new_value, int): - raise TypeError("max_job_retries parameter must be an integer") - if new_value < -1 or new_value == 0: - raise ValueError( - "max_job_retries must either be a positive integer or -1(for infinite trials)" - ) - if new_value == -1: - self._max_job_retries = int(1e18) - else: - self._max_job_retries = new_value - - @property - def measurement_error_mitigation_cls(self): - """returns measurement error mitigation cls""" - return self._meas_error_mitigation_cls - - @measurement_error_mitigation_cls.setter - def measurement_error_mitigation_cls(self, new_value): - """sets measurement error mitigation cls""" - self._meas_error_mitigation_cls = new_value - - @property - def cals_matrix_refresh_period(self): - """returns matrix refresh period""" - return self._cals_matrix_refresh_period - - @cals_matrix_refresh_period.setter - def cals_matrix_refresh_period(self, new_value): - """sets matrix refresh period""" - self._cals_matrix_refresh_period = new_value - - @property - def measurement_error_mitigation_shots(self): - """returns measurement error mitigation shots""" - return self._meas_error_mitigation_shots - - @measurement_error_mitigation_shots.setter - def measurement_error_mitigation_shots(self, new_value): - """sets measurement error mitigation shots""" - self._meas_error_mitigation_shots = new_value - - @property - def backend(self): - """Return Backend backend object.""" - return self._backend - - @property - def backend_name(self): - """Return backend name.""" - if self._backend_interface_version <= 1: - return self._backend.name() - else: - return self._backend.name - - @property - def is_statevector(self): - """Return True if backend is a statevector-type simulator.""" - return is_statevector_backend(self._backend) - - @property - def is_simulator(self): - """Return True if backend is a simulator.""" - return is_simulator_backend(self._backend) - - @property - def is_local(self): - """Return True if backend is a local backend.""" - return is_local_backend(self._backend) - - @property - def skip_qobj_validation(self): - """checks if skip qobj validation""" - return self._skip_qobj_validation - - @skip_qobj_validation.setter - def skip_qobj_validation(self, new_value): - """sets skip qobj validation flag""" - self._skip_qobj_validation = new_value - - def maybe_refresh_cals_matrix(self, timestamp: Optional[float] = None) -> bool: - """ - Calculate the time difference from the query of last time. - - Args: - timestamp: timestamp - - Returns: - Whether or not refresh the cals_matrix - """ - timestamp = timestamp or 0.0 - ret = False - curr_timestamp = time.time() - difference = int(curr_timestamp - timestamp) / 60.0 - if difference > self._cals_matrix_refresh_period: - ret = True - - return ret - - def cals_matrix( - self, qubit_index: Optional[List[int]] = None - ) -> Optional[Union[Tuple[np.ndarray, float], Dict[str, Tuple[np.ndarray, float]]]]: - """ - Get the stored calibration matrices and its timestamp. - - Args: - qubit_index: the qubit index of corresponding calibration matrix. - If None, return all stored calibration matrices. - - Returns: - The calibration matrix and the creation timestamp if qubit_index - is not None otherwise, return all matrices and their timestamp - in a dictionary. - """ - shots = self._meas_error_mitigation_shots or self._run_config.shots - if qubit_index: - qubit_index_str = "_".join([str(x) for x in qubit_index]) + f"_{shots}" - fitter, timestamp = self._meas_error_mitigation_fitters.get(qubit_index_str, None) - if fitter is not None: - return fitter.cal_matrix, timestamp - else: - return { - k: (v.cal_matrix, t) for k, (v, t) in self._meas_error_mitigation_fitters.items() - } - return None diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py deleted file mode 100644 index ce26c5cf5b93..000000000000 --- a/qiskit/utils/run_circuits.py +++ /dev/null @@ -1,413 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""run circuits functions""" - -from typing import Optional, Dict, Callable, List, Union, Tuple -import sys -import logging -import time -import copy -import os - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.providers import Backend, JobStatus, JobError, Job -from qiskit.providers.jobstatus import JOB_FINAL_STATES -from qiskit.result import Result -from qiskit.utils.deprecation import deprecate_func -from ..exceptions import QiskitError, MissingOptionalLibraryError -from .backend_utils import ( - is_aer_provider, - is_basicaer_provider, - is_simulator_backend, - is_local_backend, - is_ibmq_provider, - _get_backend_interface_version, -) - -MAX_CIRCUITS_PER_JOB = os.environ.get("QISKIT_AQUA_MAX_CIRCUITS_PER_JOB", None) -MAX_GATES_PER_JOB = os.environ.get("QISKIT_AQUA_MAX_GATES_PER_JOB", None) - -logger = logging.getLogger(__name__) - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def find_regs_by_name( - circuit: QuantumCircuit, name: str, qreg: bool = True -) -> Optional[Union[QuantumRegister, ClassicalRegister]]: - """Deprecated: Find the registers in the circuits. - - Args: - circuit: the quantum circuit. - name: name of register - qreg: quantum or classical register - - Returns: - if not found, return None. - - """ - found_reg = None - regs = circuit.qregs if qreg else circuit.cregs - for reg in regs: - if reg.name == name: - found_reg = reg - break - return found_reg - - -def _combine_result_objects(results: List[Result]) -> Result: - """Temporary helper function. - - TODO: This function would be removed after Terra supports job with infinite circuits. - """ - if len(results) == 1: - return results[0] - - new_result = copy.deepcopy(results[0]) - - for idx in range(1, len(results)): - new_result.results.extend(results[idx].results) - - return new_result - - -def _safe_get_job_status(job: Job, job_id: str, max_job_retries: int, wait: float) -> JobStatus: - for _ in range(max_job_retries): - try: - job_status = job.status() - break - except JobError as ex: - logger.warning( - "FAILURE: job id: %s, status: 'FAIL_TO_GET_STATUS' Terra job error: %s", - job_id, - ex, - ) - time.sleep(wait) - except Exception as ex: - raise QiskitError( - f"job id: {job_id}, status: 'FAIL_TO_GET_STATUS' Unknown error: ({ex})" - ) from ex - else: - raise QiskitError(f"Max retry limit reached. Failed to get status for job with id {job_id}") - - return job_status - - -@deprecate_func( - since="0.24.0", - package_name="qiskit-terra", - additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", -) -def run_circuits( - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Backend, - qjob_config: Dict, - backend_options: Optional[Dict] = None, - noise_config: Optional[Dict] = None, - run_config: Optional[Dict] = None, - job_callback: Optional[Callable] = None, - max_job_retries: int = 50, -) -> Result: - """ - Deprecated: An execution wrapper with Qiskit-Terra, with job auto recover capability. - - The auto-recovery feature is only applied for non-simulator backend. - This wrapper will try to get the result no matter how long it takes. - - Args: - circuits: circuits to execute - backend: backend instance - qjob_config: configuration for quantum job object - backend_options: backend options - noise_config: configuration for noise model - run_config: configuration for run - job_callback: callback used in querying info of the submitted job, and providing the - following arguments: job_id, job_status, queue_position, job. - max_job_retries(int): positive non-zero number of trials for the job set (-1 for infinite - trials) (default: 50) - - Returns: - Result object - - Raises: - QiskitError: Any error except for JobError raised by Qiskit Terra - """ - backend_interface_version = _get_backend_interface_version(backend) - - backend_options = backend_options or {} - noise_config = noise_config or {} - run_config = run_config or {} - if backend_interface_version <= 1: - with_autorecover = not is_simulator_backend(backend) - else: - with_autorecover = False - - if MAX_CIRCUITS_PER_JOB is not None: - max_circuits_per_job = int(MAX_CIRCUITS_PER_JOB) - else: - if backend_interface_version <= 1: - if is_local_backend(backend): - max_circuits_per_job = sys.maxsize - else: - max_circuits_per_job = backend.configuration().max_experiments - else: - if backend.max_circuits is not None: - max_circuits_per_job = backend.max_circuits - else: - max_circuits_per_job = sys.maxsize - - if len(circuits) > max_circuits_per_job: - jobs = [] - job_ids = [] - split_circuits = [] - count = 0 - while count < len(circuits): - some_circuits = circuits[count : count + max_circuits_per_job] - split_circuits.append(some_circuits) - job, job_id = _safe_submit_circuits( - some_circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - jobs.append(job) - job_ids.append(job_id) - count += max_circuits_per_job - else: - job, job_id = _safe_submit_circuits( - circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - jobs = [job] - job_ids = [job_id] - split_circuits = [circuits] - results = [] - if with_autorecover: - logger.info("Backend status: %s", backend.status()) - logger.info("There are %s jobs are submitted.", len(jobs)) - logger.info("All job ids:\n%s", job_ids) - for idx, _ in enumerate(jobs): - result = None - logger.info("Backend status: %s", backend.status()) - logger.info("There is one jobs are submitted: id: %s", job_id) - job = jobs[idx] - job_id = job_ids[idx] - for _ in range(max_job_retries): - logger.info("Running job id: %s", job_id) - # try to get result if possible - while True: - job_status = _safe_get_job_status( - job, job_id, max_job_retries, qjob_config["wait"] - ) # if the status was broken, an Exception would be raised anyway - queue_position = 0 - if job_status in JOB_FINAL_STATES: - # do callback again after the job is in the final states - if job_callback is not None: - job_callback(job_id, job_status, queue_position, job) - break - if job_status == JobStatus.QUEUED and hasattr(job, "queue_position"): - queue_position = job.queue_position() - logger.info("Job id: %s is queued at position %s", job_id, queue_position) - else: - logger.info("Job id: %s, status: %s", job_id, job_status) - if job_callback is not None: - job_callback(job_id, job_status, queue_position, job) - time.sleep(qjob_config["wait"]) - - # get result after the status is DONE - if job_status == JobStatus.DONE: - for _ in range(max_job_retries): - result = job.result() - if result.success: - results.append(result) - logger.info("COMPLETED the %s-th job, job id: %s", idx, job_id) - break - - logger.warning("FAILURE: Job id: %s", job_id) - logger.warning( - "Job (%s) is completed anyway, retrieve result from backend again.", - job_id, - ) - job = backend.retrieve_job(job_id) - else: - raise QiskitError( - f"Max retry limit reached. Failed to get result for job id {job_id}" - ) - break - # for other cases, resubmit the circuit until the result is available. - # since if there is no result returned, there is no way algorithm can do any process - if job_status == JobStatus.CANCELLED: - logger.warning( - "FAILURE: Job id: %s is cancelled. Re-submit the circuits.", job_id - ) - elif job_status == JobStatus.ERROR: - logger.warning( - "FAILURE: Job id: %s encounters the error. " - "Error is : %s. Re-submit the circuits.", - job_id, - job.error_message(), - ) - else: - logging.warning( - "FAILURE: Job id: %s. Unknown status: %s. Re-submit the circuits.", - job_id, - job_status, - ) - job, job_id = _safe_submit_circuits( - split_circuits[idx], - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - else: - raise QiskitError( - f"Max retry limit reached. Failed to get result for job with id {job_id} " - ) - else: - results = [] - for job in jobs: - results.append(job.result()) - - result = _combine_result_objects(results) if results else None - # If result was not successful then raise an exception with either the status msg or - # extra information if this was an Aer partial result return - if not result.success: - msg = result.status - if result.status == "PARTIAL COMPLETED": - # Aer can return partial results which Aqua algorithms cannot process and signals - # using partial completed status where each returned result has a success and status. - # We use the status from the first result that was not successful - for res in result.results: - if not res.success: - msg += ", " + res.status - break - raise QiskitError(f"Circuit execution failed: {msg}") - - if not hasattr(result, "time_taken"): - setattr(result, "time_taken", 0.0) - - return result - - -def _safe_submit_circuits( - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Backend, - qjob_config: Dict, - backend_options: Dict, - noise_config: Dict, - run_config: Dict, - max_job_retries: int, -) -> Tuple[Job, str]: - # assure get job ids - for _ in range(max_job_retries): - try: - job = _run_circuits_on_backend( - backend, - circuits, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - ) - job_id = job.job_id() - break - except QiskitError as ex: - failure_warn = True - if is_ibmq_provider(backend): - try: - from qiskit.providers.ibmq import IBMQBackendJobLimitError - except ImportError as ex1: - raise MissingOptionalLibraryError( - libname="qiskit-ibmq-provider", - name="_safe_submit_circuits", - pip_install="pip install qiskit-ibmq-provider", - ) from ex1 - if isinstance(ex, IBMQBackendJobLimitError): - - oldest_running = backend.jobs( - limit=1, descending=False, status=["QUEUED", "VALIDATING", "RUNNING"] - ) - if oldest_running: - oldest_running = oldest_running[0] - logger.warning( - "Job limit reached, waiting for job %s to finish " - "before submitting the next one.", - oldest_running.job_id(), - ) - failure_warn = False # Don't issue a second warning. - try: - oldest_running.wait_for_final_state( - timeout=qjob_config["timeout"], wait=qjob_config["wait"] - ) - except Exception: # pylint: disable=broad-except - # If the wait somehow fails or times out, we'll just re-try - # the job submit and see if it works now. - pass - if failure_warn: - logger.warning( - "FAILURE: Can not get job id, Resubmit the qobj to get job id. " - "Terra job error: %s ", - ex, - ) - except Exception as ex: # pylint: disable=broad-except - logger.warning( - "FAILURE: Can not get job id, Resubmit the qobj to get job id. Error: %s ", ex - ) - else: - raise QiskitError("Max retry limit reached. Failed to submit the qobj correctly") - - return job, job_id - - -def _run_circuits_on_backend( - backend: Backend, - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend_options: Dict, - noise_config: Dict, - run_config: Dict, -) -> Job: - """Run on backend.""" - run_kwargs = {} - if is_aer_provider(backend) or is_basicaer_provider(backend): - for key, value in backend_options.items(): - if key == "backend_options": - for k, v in value.items(): - run_kwargs[k] = v - else: - run_kwargs[key] = value - else: - run_kwargs.update(backend_options) - - run_kwargs.update(noise_config) - run_kwargs.update(run_config) - - if is_basicaer_provider(backend): - # BasicAer emits warning if option is not in its list - for key in list(run_kwargs.keys()): - if not hasattr(backend.options, key): - del run_kwargs[key] - - return backend.run(circuits, **run_kwargs) diff --git a/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml b/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml new file mode 100644 index 000000000000..8157db5a5e46 --- /dev/null +++ b/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml @@ -0,0 +1,20 @@ +--- +upgrade: + - | + The ``qiskit.opflow`` module has been removed, following its deprecation in + Qiskit 0.44. For more context on the deprecation and + detailed migration instructions, you can read the + `Opflow Migration Guide `__. + + + - | + A series of legacy quantum execution utils have been removed, following their deprecation in Qiskit 0.44. + These include the ``qiskit.utils.QuantumInstance`` class, as well the modules: + + - ``qiskit.utils.backend_utils`` + - ``qiskit.utils.mitigation`` + - ``qiskit.utils.measurement_error_mitigation`` + - ``qiskit.utils.run_circuits`` + + Their functionality has been superseded by the use of primitives. For more context on the deprecation and + detailed migration instructions, you can read the `QuantumInstance Migration Guide `__. diff --git a/test/python/circuit/library/test_evolution_gate.py b/test/python/circuit/library/test_evolution_gate.py index adf0e189db32..fb13f5de88ae 100644 --- a/test/python/circuit/library/test_evolution_gate.py +++ b/test/python/circuit/library/test_evolution_gate.py @@ -22,9 +22,13 @@ from qiskit.synthesis import LieTrotter, SuzukiTrotter, MatrixExponential, QDrift from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.opflow import I, X, Y, Z, PauliSumOp from qiskit.quantum_info import Operator, SparsePauliOp, Pauli, Statevector +X = SparsePauliOp("X") +Y = SparsePauliOp("Y") +Z = SparsePauliOp("Z") +I = SparsePauliOp("I") + @ddt class TestEvolutionGate(QiskitTestCase): @@ -37,8 +41,7 @@ def setUp(self): def test_matrix_decomposition(self): """Test the default decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 matrix = op.to_matrix() @@ -50,8 +53,7 @@ def test_matrix_decomposition(self): def test_lie_trotter(self): """Test constructing the circuit with Lie Trotter decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 reps = 4 evo_gate = PauliEvolutionGate(op, time, synthesis=LieTrotter(reps=reps)) @@ -60,32 +62,30 @@ def test_lie_trotter(self): def test_rzx_order(self): """Test ZX and XZ is mapped onto the correct qubits.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) - for op, indices in zip([X ^ Z, Z ^ X], [(0, 1), (1, 0)]): - with self.subTest(op=op, indices=indices): - evo_gate = PauliEvolutionGate(op) - decomposed = evo_gate.definition.decompose() - - # ┌───┐┌───────┐┌───┐ - # q_0: ─────┤ X ├┤ Rz(2) ├┤ X ├───── - # ┌───┐└─┬─┘└───────┘└─┬─┘┌───┐ - # q_1: ┤ H ├──■─────────────■──┤ H ├ - # └───┘ └───┘ - ref = QuantumCircuit(2) - ref.h(indices[1]) - ref.cx(indices[1], indices[0]) - ref.rz(2.0, indices[0]) - ref.cx(indices[1], indices[0]) - ref.h(indices[1]) - - # don't use circuit equality since RZX here decomposes with RZ on the bottom - self.assertTrue(Operator(decomposed).equiv(ref)) + + for op, indices in zip([X ^ Z, Z ^ X], [(0, 1), (1, 0)]): + with self.subTest(op=op, indices=indices): + evo_gate = PauliEvolutionGate(op) + decomposed = evo_gate.definition.decompose() + + # ┌───┐┌───────┐┌───┐ + # q_0: ─────┤ X ├┤ Rz(2) ├┤ X ├───── + # ┌───┐└─┬─┘└───────┘└─┬─┘┌───┐ + # q_1: ┤ H ├──■─────────────■──┤ H ├ + # └───┘ └───┘ + ref = QuantumCircuit(2) + ref.h(indices[1]) + ref.cx(indices[1], indices[0]) + ref.rz(2.0, indices[0]) + ref.cx(indices[1], indices[0]) + ref.h(indices[1]) + + # don't use circuit equality since RZX here decomposes with RZ on the bottom + self.assertTrue(Operator(decomposed).equiv(ref)) def test_suzuki_trotter(self): """Test constructing the circuit with Lie Trotter decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 reps = 4 for order in [2, 4, 6]: @@ -107,8 +107,7 @@ def test_suzuki_trotter(self): def test_suzuki_trotter_manual(self): """Test the evolution circuit of Suzuki Trotter against a manually constructed circuit.""" - with self.assertWarns(DeprecationWarning): - op = X + Y + op = X + Y time = 0.1 reps = 1 evo_gate = PauliEvolutionGate(op, time, synthesis=SuzukiTrotter(order=4, reps=reps)) @@ -156,8 +155,7 @@ def test_qdrift_manual(self, op, time, reps, sampled_ops): def test_qdrift_evolution(self): """Test QDrift on an example.""" - with self.assertWarns(DeprecationWarning): - op = 0.1 * (Z ^ Z) + (X ^ I) + (I ^ X) + 0.2 * (X ^ X) + op = 0.1 * (Z ^ Z) + (X ^ I) + (I ^ X) + 0.2 * (X ^ X) reps = 20 qdrift = PauliEvolutionGate( op, time=0.5 / reps, synthesis=QDrift(reps=reps, seed=self.seed) @@ -171,8 +169,7 @@ def energy(evo): def test_passing_grouped_paulis(self): """Test passing a list of already grouped Paulis.""" - with self.assertWarns(DeprecationWarning): - grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] + grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter()) decomposed = evo_gate.definition.decompose() self.assertEqual(decomposed.count_ops()["rz"], 4) @@ -181,8 +178,7 @@ def test_passing_grouped_paulis(self): def test_list_from_grouped_paulis(self): """Test getting a string representation from grouped Paulis.""" - with self.assertWarns(DeprecationWarning): - grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] + grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter()) pauli_strings = [] @@ -202,8 +198,7 @@ def test_list_from_grouped_paulis(self): def test_dag_conversion(self): """Test constructing a circuit with evolutions yields a DAG with evolution blocks.""" time = Parameter("t") - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate((Z ^ 2) + (X ^ 2), time=time) + evo = PauliEvolutionGate((Z ^ Z) + (X ^ X), time=time) circuit = QuantumCircuit(2) circuit.h(circuit.qubits) @@ -221,8 +216,7 @@ def test_dag_conversion(self): def test_cnot_chain_options(self, option): """Test selecting different kinds of CNOT chains.""" - with self.assertWarns(DeprecationWarning): - op = Z ^ Z ^ Z + op = Z ^ Z ^ Z synthesis = LieTrotter(reps=1, cx_structure=option) evo = PauliEvolutionGate(op, synthesis=synthesis) @@ -247,9 +241,7 @@ def test_cnot_chain_options(self, option): @data( Pauli("XI"), - X ^ I, # PauliOp SparsePauliOp(Pauli("XI")), - PauliSumOp(SparsePauliOp("XI")), ) def test_different_input_types(self, op): """Test all different supported input types and that they yield the same.""" @@ -266,16 +258,14 @@ def test_different_input_types(self, op): def test_pauliop_coefficients_respected(self): """Test that global ``PauliOp`` coefficients are being taken care of.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(5 * (Z ^ I), time=1, synthesis=LieTrotter()) + evo = PauliEvolutionGate(5 * (Z ^ I), time=1, synthesis=LieTrotter()) circuit = evo.definition.decompose() rz_angle = circuit.data[0].operation.params[0] self.assertEqual(rz_angle, 10) def test_paulisumop_coefficients_respected(self): """Test that global ``PauliSumOp`` coefficients are being taken care of.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(5 * (2 * X + 3 * Y - Z), time=1, synthesis=LieTrotter()) + evo = PauliEvolutionGate(5 * (2 * X + 3 * Y - Z), time=1, synthesis=LieTrotter()) circuit = evo.definition.decompose() rz_angles = [ circuit.data[0].operation.params[0], # X @@ -289,8 +279,7 @@ def test_lie_trotter_two_qubit_correct_order(self): Regression test of Qiskit/qiskit-terra#7544. """ - with self.assertWarns(DeprecationWarning): - operator = I ^ Z ^ Z + operator = I ^ Z ^ Z time = 0.5 exact = scipy.linalg.expm(-1j * time * operator.to_matrix()) lie_trotter = PauliEvolutionGate(operator, time, synthesis=LieTrotter()) @@ -310,8 +299,7 @@ def test_paramtrized_op_raises(self): @data(LieTrotter, MatrixExponential) def test_inverse(self, synth_cls): """Test calculating the inverse is correct.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(X + Y, time=0.12, synthesis=synth_cls()) + evo = PauliEvolutionGate(X + Y, time=0.12, synthesis=synth_cls()) circuit = QuantumCircuit(1) circuit.append(evo, circuit.qubits) @@ -321,8 +309,7 @@ def test_inverse(self, synth_cls): def test_labels_and_name(self): """Test the name and labels are correct.""" - with self.assertWarns(DeprecationWarning): - operators = [X, (X + Y), ((I ^ Z) + (Z ^ I) - 0.2 * (X ^ X))] + operators = [X, (X + Y), ((I ^ Z) + (Z ^ I) - 0.2 * (X ^ X))] # note: the labels do not show coefficients! expected_labels = ["X", "(X + Y)", "(IZ + ZI + XX)"] diff --git a/test/python/circuit/library/test_evolved_op_ansatz.py b/test/python/circuit/library/test_evolved_op_ansatz.py index 06bcc206d8a4..000eb8cfb9df 100644 --- a/test/python/circuit/library/test_evolved_op_ansatz.py +++ b/test/python/circuit/library/test_evolved_op_ansatz.py @@ -12,11 +12,9 @@ """Test the evolved operator ansatz.""" -from ddt import ddt, data import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.opflow import X, Y, Z, I, MatrixEvolution from qiskit.quantum_info import SparsePauliOp, Operator, Pauli from qiskit.circuit.library import EvolvedOperatorAnsatz, HamiltonianGate @@ -24,24 +22,15 @@ from qiskit.test import QiskitTestCase -@ddt class TestEvolvedOperatorAnsatz(QiskitTestCase): """Test the evolved operator ansatz.""" - @data(True, False) - def test_evolved_op_ansatz(self, use_opflow): + def test_evolved_op_ansatz(self): """Test the default evolution.""" num_qubits = 3 - if use_opflow: - with self.assertWarns(DeprecationWarning): - ops = [Z ^ num_qubits, Y ^ num_qubits, X ^ num_qubits] - evo = EvolvedOperatorAnsatz(ops, 2) - parameters = evo.parameters - - else: - ops = [Pauli("Z" * num_qubits), Pauli("Y" * num_qubits), Pauli("X" * num_qubits)] - evo = EvolvedOperatorAnsatz(ops, 2) - parameters = evo.parameters + ops = [Pauli("Z" * num_qubits), Pauli("Y" * num_qubits), Pauli("X" * num_qubits)] + evo = EvolvedOperatorAnsatz(ops, 2) + parameters = evo.parameters reference = QuantumCircuit(num_qubits) strings = ["z" * num_qubits, "y" * num_qubits, "x" * num_qubits] * 2 @@ -50,62 +39,47 @@ def test_evolved_op_ansatz(self, use_opflow): self.assertEqual(evo.decompose().decompose(), reference) - @data(True, False) - def test_custom_evolution(self, use_opflow): + def test_custom_evolution(self): """Test using another evolution than the default (e.g. matrix evolution).""" - if use_opflow: - with self.assertWarns(DeprecationWarning): - op = X ^ I ^ Z - matrix = op.to_matrix() - evolution = MatrixEvolution() - evo = EvolvedOperatorAnsatz(op, evolution=evolution) - parameters = evo.parameters - - else: - op = SparsePauliOp(["ZIX"]) - matrix = np.array(op) - evolution = MatrixExponential() - evo = EvolvedOperatorAnsatz(op, evolution=evolution) - parameters = evo.parameters + op = SparsePauliOp(["ZIX"]) + matrix = np.array(op) + evolution = MatrixExponential() + evo = EvolvedOperatorAnsatz(op, evolution=evolution) + parameters = evo.parameters reference = QuantumCircuit(3) reference.append(HamiltonianGate(matrix, parameters[0]), [0, 1, 2]) - decomposed = evo.decompose() - if not use_opflow: - decomposed = decomposed.decompose() - + decomposed = evo.decompose().decompose() self.assertEqual(decomposed, reference) def test_changing_operators(self): """Test rebuilding after the operators changed.""" - ops = [X, Y, Z] - with self.assertWarns(DeprecationWarning): - evo = EvolvedOperatorAnsatz(ops) - evo.operators = [X, Y] - parameters = evo.parameters + ops = [Pauli("X"), Pauli("Y"), Pauli("Z")] + evo = EvolvedOperatorAnsatz(ops) + evo.operators = [Pauli("X"), Pauli("Y")] + parameters = evo.parameters reference = QuantumCircuit(1) reference.rx(2 * parameters[0], 0) reference.ry(2 * parameters[1], 0) - self.assertEqual(evo.decompose(), reference) + self.assertEqual(evo.decompose(reps=2), reference) def test_invalid_reps(self): """Test setting an invalid number of reps.""" with self.assertRaises(ValueError): - _ = EvolvedOperatorAnsatz(X, reps=-1) + _ = EvolvedOperatorAnsatz(Pauli("X"), reps=-1) def test_insert_barriers(self): """Test using insert_barriers.""" - with self.assertWarns(DeprecationWarning): - evo = EvolvedOperatorAnsatz(Z, reps=4, insert_barriers=True) - ref = QuantumCircuit(1) - for parameter in evo.parameters: - ref.rz(2.0 * parameter, 0) - ref.barrier() - self.assertEqual(evo.decompose(), ref) + evo = EvolvedOperatorAnsatz(Pauli("Z"), reps=4, insert_barriers=True) + ref = QuantumCircuit(1) + for parameter in evo.parameters: + ref.rz(2.0 * parameter, 0) + ref.barrier() + self.assertEqual(evo.decompose(reps=2), ref) def test_empty_build_fails(self): """Test setting no operators to evolve raises the appropriate error.""" diff --git a/test/python/circuit/library/test_qaoa_ansatz.py b/test/python/circuit/library/test_qaoa_ansatz.py index dfbf25da69d4..fcd062152bfa 100644 --- a/test/python/circuit/library/test_qaoa_ansatz.py +++ b/test/python/circuit/library/test_qaoa_ansatz.py @@ -18,7 +18,6 @@ from qiskit.circuit import QuantumCircuit, Parameter from qiskit.circuit.library import HGate, RXGate, YGate, RYGate, RZGate from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.opflow import I from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.test import QiskitTestCase @@ -29,8 +28,7 @@ class TestQAOAAnsatz(QiskitTestCase): def test_default_qaoa(self): """Test construction of the default circuit.""" - # To be changed once QAOAAnsatz drops support for opflow - circuit = QAOAAnsatz(I, 1) + circuit = QAOAAnsatz(Pauli("I"), 1) parameters = circuit.parameters circuit = circuit.decompose() diff --git a/test/python/opflow/__init__.py b/test/python/opflow/__init__.py deleted file mode 100644 index 16d56b668594..000000000000 --- a/test/python/opflow/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Opflow test module""" - -from .opflow_test_case import QiskitOpflowTestCase - -__all__ = ["QiskitOpflowTestCase"] diff --git a/test/python/opflow/opflow_test_case.py b/test/python/opflow/opflow_test_case.py deleted file mode 100644 index 142fb1db76b5..000000000000 --- a/test/python/opflow/opflow_test_case.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Opflow Test Case""" - -import warnings -from qiskit.test import QiskitTestCase - - -class QiskitOpflowTestCase(QiskitTestCase): - """Opflow test Case""" - - def setUp(self): - super().setUp() - # ignore opflow msgs - warnings.filterwarnings("ignore", category=DeprecationWarning, message=r".*opflow.*") - - def tearDown(self): - super().tearDown() - # restore opflow msgs - warnings.filterwarnings("error", category=DeprecationWarning, message=r".*opflow.*") diff --git a/test/python/opflow/test_abelian_grouper.py b/test/python/opflow/test_abelian_grouper.py deleted file mode 100644 index 4fd6cf2a85ba..000000000000 --- a/test/python/opflow/test_abelian_grouper.py +++ /dev/null @@ -1,133 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Abelian Grouper""" - -import random -import unittest -from itertools import combinations, product -from test.python.opflow import QiskitOpflowTestCase - -from ddt import data, ddt, unpack - -from qiskit.opflow import AbelianGrouper, commutator, I, OpflowError, Plus, SummedOp, X, Y, Z, Zero - - -@ddt -class TestAbelianGrouper(QiskitOpflowTestCase): - """Abelian Grouper tests.""" - - @data(*product(["h2_op", "generic"], [True, False])) - @unpack - def test_abelian_grouper(self, pauli_op, is_summed_op): - """Abelian grouper test""" - if pauli_op == "h2_op": - paulis = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - num_groups = 2 - else: - paulis = ( - (I ^ I ^ X ^ X * 0.2) - + (Z ^ Z ^ X ^ X * 0.3) - + (Z ^ Z ^ Z ^ Z * 0.4) - + (X ^ X ^ Z ^ Z * 0.5) - + (X ^ X ^ X ^ X * 0.6) - + (I ^ X ^ X ^ X * 0.7) - ) - num_groups = 4 - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper().convert(paulis) - self.assertEqual(len(grouped_sum.oplist), num_groups) - for group in grouped_sum: - for op_1, op_2 in combinations(group, 2): - if is_summed_op: - self.assertEqual(op_1 @ op_2, op_2 @ op_1) - else: - self.assertTrue(commutator(op_1, op_2).is_zero()) - - def test_ablian_grouper_no_commute(self): - """Abelian grouper test when non-PauliOp is given""" - ops = Zero ^ Plus + X ^ Y - with self.assertRaises(OpflowError): - _ = AbelianGrouper.group_subops(ops) - - @data(True, False) - def test_group_subops(self, is_summed_op): - """grouper subroutine test""" - paulis = (I ^ X) + (2 * X ^ X) + (3 * Z ^ Y) - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper.group_subops(paulis) - self.assertEqual(len(grouped_sum), 2) - with self.subTest("test group subops 1"): - if is_summed_op: - expected = SummedOp( - [ - SummedOp([I ^ X, 2.0 * X ^ X], abelian=True), - SummedOp([3.0 * Z ^ Y], abelian=True), - ] - ) - self.assertEqual(grouped_sum, expected) - else: - self.assertSetEqual( - frozenset(frozenset(grouped_sum[i].primitive.to_list()) for i in range(2)), - frozenset({frozenset({("ZY", 3)}), frozenset({("IX", 1), ("XX", 2)})}), - ) - - paulis = X + (2 * Y) + (3 * Z) - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper.group_subops(paulis) - self.assertEqual(len(grouped_sum), 3) - with self.subTest("test group subops 2"): - if is_summed_op: - self.assertEqual(grouped_sum, paulis) - else: - self.assertSetEqual( - frozenset(sum((grouped_sum[i].primitive.to_list() for i in range(3)), [])), - frozenset([("X", 1), ("Y", 2), ("Z", 3)]), - ) - - @data(True, False) - def test_abelian_grouper_random(self, is_summed_op): - """Abelian grouper test with random paulis""" - random.seed(1234) - k = 10 # size of pauli operators - n = 100 # number of pauli operators - num_tests = 20 # number of tests - for _ in range(num_tests): - paulis = [] - for _ in range(n): - pauliop = 1 - for eachop in random.choices([I] * 5 + [X, Y, Z], k=k): - pauliop ^= eachop - paulis.append(pauliop) - pauli_sum = sum(paulis) - if is_summed_op: - pauli_sum = pauli_sum.to_pauli_op() - grouped_sum = AbelianGrouper().convert(pauli_sum) - for group in grouped_sum: - for op_1, op_2 in combinations(group, 2): - if is_summed_op: - self.assertEqual(op_1 @ op_2, op_2 @ op_1) - else: - self.assertTrue(commutator(op_1, op_2).is_zero()) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_aer_pauli_expectation.py b/test/python/opflow/test_aer_pauli_expectation.py deleted file mode 100644 index 808f7bab8716..000000000000 --- a/test/python/opflow/test_aer_pauli_expectation.py +++ /dev/null @@ -1,297 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test AerPauliExpectation""" - -import itertools -import unittest -from test.python.opflow import QiskitOpflowTestCase -import numpy as np - -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow import ( - CX, - AerPauliExpectation, - CircuitSampler, - CircuitStateFn, - H, - I, - ListOp, - Minus, - One, - PauliExpectation, - PauliSumOp, - Plus, - S, - StateFn, - X, - Y, - Z, - Zero, - MatrixOp, -) -from qiskit.utils import QuantumInstance, optionals - - -class TestAerPauliExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def setUp(self) -> None: - super().setUp() - from qiskit_aer import AerSimulator - - self.seed = 97 - self.backend = AerSimulator() - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - self.backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - self.expect = AerPauliExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - op = Z ^ Z - # wvf = (Pl^Pl) + (Ze^Ze) - wvf = CX @ (H ^ I) @ Zero - converted_meas = self.expect.convert(~StateFn(op) @ wvf) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - with self.assertWarns(DeprecationWarning): - plus_mean = converted_meas @ Plus - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - sampled_zero_mean = self.sampler.convert(sum_zero_mean) - - # !!NOTE!!: Depolarizing channel (Sampling) means interference - # does not happen between circuits in sum, so expectation does - # not equal expectation for Zero!! - np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 1]) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - states_op = ListOp([One, Zero, Plus, Minus]) - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertTrue(hasattr(composed_op[0], "execution_results")) - - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - def test_pauli_expect_non_hermitian_matrixop(self): - """pauli expect state vector with non hermitian operator test""" - states_op = ListOp([One, Zero, Plus, Minus]) - op_mat = np.array([[0, 1], [2, 3]]) - op = MatrixOp(op_mat) - converted_meas = self.expect.convert(StateFn(op, is_measurement=True) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), [3, 0, 3, 0], decimal=1) - - def test_pauli_expect_non_hermitian_pauliop(self): - """pauli expect state vector with non hermitian operator test""" - states_op = ListOp([One, Zero, Plus, Minus]) - op = 1j * X - converted_meas = self.expect.convert(StateFn(op, is_measurement=True) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1j, -1j], decimal=1) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [ - [+0, 0, 1, -1], - [+0, 0, 0, 0], - [-1, 1, 0, -0], - [+1, 1, 1, 1], - ] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - - plus_mean = converted_meas @ Plus - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_parameterized_qobj(self): - """grouped pauli expectation test""" - - two_qubit_h2 = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - with self.assertWarns(DeprecationWarning): - aer_sampler = CircuitSampler( - self.sampler.quantum_instance, param_qobj=True, attach_results=True - ) - ansatz = RealAmplitudes() - ansatz.num_qubits = 2 - - observable_meas = self.expect.convert(StateFn(two_qubit_h2, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(ansatz) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - def generate_parameters(num): - param_bindings = {} - for param in ansatz.parameters: - values = [] - for _ in range(num): - values.append(np.random.rand()) - param_bindings[param] = values - return param_bindings - - def validate_sampler(ideal, sut, param_bindings): - with self.assertWarns(DeprecationWarning): - expect_sampled = ideal.convert(expect_op, params=param_bindings).eval() - actual_sampled = sut.convert(expect_op, params=param_bindings).eval() - self.assertTrue( - np.allclose(actual_sampled, expect_sampled), - f"{actual_sampled} != {expect_sampled}", - ) - - def get_circuit_templates(sampler): - return sampler._transpiled_circ_templates - - def validate_aer_binding_used(templates): - self.assertIsNotNone(templates) - - def validate_aer_templates_reused(prev_templates, cur_templates): - self.assertIs(prev_templates, cur_templates) - - validate_sampler(self.sampler, aer_sampler, generate_parameters(1)) - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_binding_used(cur_templates) - - prev_templates = cur_templates - validate_sampler(self.sampler, aer_sampler, generate_parameters(2)) - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_templates_reused(prev_templates, cur_templates) - - prev_templates = cur_templates - validate_sampler(self.sampler, aer_sampler, generate_parameters(2)) # same num of params - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_templates_reused(prev_templates, cur_templates) - - def test_pauli_expectation_param_qobj(self): - """Test PauliExpectation with param_qobj""" - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - self.backend, seed_simulator=self.seed, seed_transpiler=self.seed, shots=10000 - ) - qubit_op = (0.1 * I ^ I) + (0.2 * I ^ Z) + (0.3 * Z ^ I) + (0.4 * Z ^ Z) + (0.5 * X ^ X) - ansatz = RealAmplitudes(qubit_op.num_qubits) - ansatz_circuit_op = CircuitStateFn(ansatz) - observable = PauliExpectation().convert(~StateFn(qubit_op)) - expect_op = observable.compose(ansatz_circuit_op).reduce() - params1 = {} - params2 = {} - for param in ansatz.parameters: - params1[param] = [0] - params2[param] = [0, 0] - - with self.assertWarns(DeprecationWarning): - sampler1 = CircuitSampler(backend=q_instance, param_qobj=False) - samples1 = sampler1.convert(expect_op, params=params1) - val1 = np.real(samples1.eval())[0] - samples2 = sampler1.convert(expect_op, params=params2) - val2 = np.real(samples2.eval()) - sampler2 = CircuitSampler(backend=q_instance, param_qobj=True) - samples3 = sampler2.convert(expect_op, params=params1) - val3 = np.real(samples3.eval()) - samples4 = sampler2.convert(expect_op, params=params2) - val4 = np.real(samples4.eval()) - - np.testing.assert_array_almost_equal([val1] * 2, val2, decimal=2) - np.testing.assert_array_almost_equal(val1, val3, decimal=2) - np.testing.assert_array_almost_equal([val1] * 2, val4, decimal=2) - - def test_list_pauli_sum(self): - """Test AerPauliExpectation for ListOp[PauliSumOp]""" - test_op = ListOp([PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)])]) - observable = AerPauliExpectation().convert(~StateFn(test_op)) - self.assertIsInstance(observable, ListOp) - self.assertIsInstance(observable[0], CircuitStateFn) - self.assertTrue(observable[0].is_measurement) - - def test_expectation_with_coeff(self): - """Test AerPauliExpectation with coefficients.""" - with self.subTest("integer coefficients"): - exp = 3 * ~StateFn(X) @ (2 * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertAlmostEqual(target, -12) - - with self.subTest("complex coefficients"): - exp = 3j * ~StateFn(X) @ (2j * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertAlmostEqual(target, -12j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_cvar.py b/test/python/opflow/test_cvar.py deleted file mode 100644 index 4d38356f2718..000000000000 --- a/test/python/opflow/test_cvar.py +++ /dev/null @@ -1,261 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Conditional Value at Risk (CVaR) measurement.""" - -import unittest -import warnings - -from test.python.opflow import QiskitOpflowTestCase -import numpy as np -from ddt import ddt, data - -from qiskit import QuantumCircuit -from qiskit.utils import algorithm_globals -from qiskit.opflow import ( - CVaRMeasurement, - StateFn, - Z, - I, - X, - Y, - Plus, - PauliSumOp, - PauliExpectation, - MatrixExpectation, - CVaRExpectation, - ListOp, - CircuitOp, - AerPauliExpectation, - MatrixOp, - OpflowError, -) - - -class TestCVaRMeasurement(QiskitOpflowTestCase): - """Test the CVaR measurement.""" - - def expected_cvar(self, statevector, operator, alpha): - """Compute the expected CVaR expected value.""" - - probabilities = statevector * np.conj(statevector) - - # get energies - num_bits = int(np.log2(len(statevector))) - energies = [] - for i, _ in enumerate(probabilities): - basis_state = np.binary_repr(i, num_bits) - energies += [operator.eval(basis_state).eval(basis_state)] - - # sort ascending - i_sorted = np.argsort(energies) - energies = [energies[i] for i in i_sorted] - probabilities = [probabilities[i] for i in i_sorted] - - # add up - result = 0 - accumulated_probabilities = 0 - for energy, probability in zip(energies, probabilities): - accumulated_probabilities += probability - if accumulated_probabilities <= alpha: - result += probability * energy - else: # final term - result += (alpha - accumulated_probabilities + probability) * energy - break - - return result / alpha - - def cleanup_algorithm_globals(self, massive): - """Method used to reset the values of algorithm_globals.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = massive - - def test_cvar_simple(self): - """Test a simple case with a single Pauli.""" - theta = 1.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - for alpha in [0.2, 0.4, 1]: - with self.subTest(alpha=alpha): - cvar = (CVaRMeasurement(Z, alpha) @ statefn).eval() - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - self.assertAlmostEqual(cvar, ref) - - def test_cvar_simple_with_coeff(self): - """Test a simple case with a non-unity coefficient""" - theta = 2.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - alpha = 0.2 - cvar = ((-1 * CVaRMeasurement(Z, alpha)) @ statefn).eval() - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - self.assertAlmostEqual(cvar, -1 * ref) - - def test_add(self): - """Test addition.""" - theta = 2.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - alpha = 0.2 - cvar = -1 * CVaRMeasurement(Z, alpha) - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - - other = ~StateFn(I) - - # test add in both directions - res1 = ((cvar + other) @ statefn).eval() - res2 = ((other + other) @ statefn).eval() - - self.assertAlmostEqual(res1, 1 - ref) - self.assertAlmostEqual(res2, 1 - ref) - - def invalid_input(self): - """Test invalid input raises an error.""" - op = Z - - with self.subTest("alpha < 0"): - with self.assertRaises(ValueError): - _ = CVaRMeasurement(op, alpha=-0.2) - - with self.subTest("alpha > 1"): - with self.assertRaises(ValueError): - _ = CVaRMeasurement(op, alpha=12.3) - - with self.subTest("Single pauli operator not diagonal"): - op = Y - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("Summed pauli operator not diagonal"): - op = X ^ Z + Z ^ I - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("List operator not diagonal"): - op = ListOp([X ^ Z, Z ^ I]) - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("Matrix operator not diagonal"): - op = MatrixOp([[1, 1], [0, 1]]) - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - def test_unsupported_operations(self): - """Assert unsupported operations raise an error.""" - cvar = CVaRMeasurement(Z) - - attrs = ["to_matrix", "to_matrix_op", "to_density_matrix", "to_circuit_op", "sample"] - for attr in attrs: - with self.subTest(attr): - with self.assertRaises(NotImplementedError): - _ = getattr(cvar, attr)() - - with self.subTest("adjoint"): - with self.assertRaises(OpflowError): - cvar.adjoint() - - def test_cvar_on_paulisumop(self): - """Test a large PauliSumOp is checked for diagonality efficiently. - - Regression test for Qiskit/qiskit-terra#7573. - """ - op = PauliSumOp.from_list([("Z" * 30, 1)]) - # assert global algorithm settings do not have massive calculations turned on - # -- which is the default, but better to be sure in the test! - # also add a cleanup so we're sure to reset to the original value after the test, even if - # the test would fail - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - self.addCleanup(self.cleanup_algorithm_globals, algorithm_globals.massive) - algorithm_globals.massive = False - - cvar = CVaRMeasurement(op, alpha=0.1) - fake_probabilities = [0.2, 0.8] - fake_energies = [1, 2] - - expectation = cvar.compute_cvar(fake_energies, fake_probabilities) - self.assertEqual(expectation, 1) - - -@ddt -class TestCVaRExpectation(QiskitOpflowTestCase): - """Test the CVaR expectation object.""" - - def test_construction(self): - """Test the correct operator expression is constructed.""" - - alpha = 0.5 - base_expecation = PauliExpectation() - cvar_expecation = CVaRExpectation(alpha=alpha, expectation=base_expecation) - - with self.subTest("single operator"): - op = ~StateFn(Z) @ Plus - expected = CVaRMeasurement(Z, alpha) @ Plus - cvar = cvar_expecation.convert(op) - self.assertEqual(cvar, expected) - - with self.subTest("list operator"): - op = ~StateFn(ListOp([Z ^ Z, I ^ Z])) @ (Plus ^ Plus) - expected = ListOp( - [ - CVaRMeasurement((Z ^ Z), alpha) @ (Plus ^ Plus), - CVaRMeasurement((I ^ Z), alpha) @ (Plus ^ Plus), - ] - ) - cvar = cvar_expecation.convert(op) - self.assertEqual(cvar, expected) - - def test_unsupported_expectation(self): - """Assert passing an AerPauliExpectation raises an error.""" - expecation = AerPauliExpectation() - with self.assertRaises(NotImplementedError): - _ = CVaRExpectation(alpha=1, expectation=expecation) - - @data(PauliExpectation(), MatrixExpectation()) - def test_underlying_expectation(self, base_expecation): - """Test the underlying expectation works correctly.""" - - cvar_expecation = CVaRExpectation(alpha=0.3, expectation=base_expecation) - circuit = QuantumCircuit(2) - circuit.z(0) - circuit.cp(0.5, 0, 1) - circuit.t(1) - op = ~StateFn(CircuitOp(circuit)) @ (Plus ^ 2) - - cvar = cvar_expecation.convert(op) - expected = base_expecation.convert(op) - - # test if the operators have been transformed in the same manner - self.assertEqual(cvar.oplist[0].primitive, expected.oplist[0].primitive) - - def test_compute_variance(self): - """Test if the compute_variance method works""" - alphas = [0, 0.3, 0.5, 0.7, 1] - correct_vars = [0, 0, 0, 0.8163, 1] - for i, alpha in enumerate(alphas): - base_expecation = PauliExpectation() - cvar_expecation = CVaRExpectation(alpha=alpha, expectation=base_expecation) - op = ~StateFn(Z ^ Z) @ (Plus ^ Plus) - cvar_var = cvar_expecation.compute_variance(op) - np.testing.assert_almost_equal(cvar_var, correct_vars[i], decimal=3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_evolution.py b/test/python/opflow/test_evolution.py deleted file mode 100644 index 55810dd7a114..000000000000 --- a/test/python/opflow/test_evolution.py +++ /dev/null @@ -1,384 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Evolution""" - -import unittest - -from test.python.opflow import QiskitOpflowTestCase -import numpy as np -import scipy.linalg - -import qiskit -from qiskit.circuit import Parameter, ParameterVector -from qiskit.circuit.library import UnitaryGate -from qiskit.opflow import ( - CX, - CircuitOp, - EvolutionFactory, - EvolvedOp, - H, - I, - ListOp, - PauliTrotterEvolution, - QDrift, - SummedOp, - Suzuki, - Trotter, - X, - Y, - Z, - Zero, -) - - -class TestEvolution(QiskitOpflowTestCase): - """Evolution tests.""" - - def test_exp_i(self): - """exponential of Pauli test""" - op = Z.exp_i() - gate = op.to_circuit().data[0].operation - self.assertIsInstance(gate, qiskit.circuit.library.RZGate) - self.assertEqual(gate.params[0], 2) - - def test_trotter_with_identity(self): - """trotterization of operator with identity term""" - op = (2.0 * I ^ I) + (Z ^ Y) - exact_matrix = scipy.linalg.expm(-1j * op.to_matrix()) - evo = PauliTrotterEvolution(trotter_mode="suzuki", reps=2) - with self.subTest("all PauliOp terms"): - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - with self.subTest("MatrixOp identity term"): - op = (2.0 * I ^ I).to_matrix_op() + (Z ^ Y) - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - with self.subTest("CircuitOp identity term"): - op = (2.0 * I ^ I).to_circuit_op() + (Z ^ Y) - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - def test_pauli_evolution(self): - """pauli evolution test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - ) - evolution = EvolutionFactory.build(operator=op) - # wf = (Pl^Pl) + (Ze^Ze) - wf = ((np.pi / 2) * op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - self.assertIsNotNone(mean) - - def test_summedop_pauli_evolution(self): - """SummedOp[PauliOp] evolution test""" - op = SummedOp( - [ - (-1.052373245772859 * I ^ I), - (0.39793742484318045 * I ^ Z), - (0.18093119978423156 * X ^ X), - (-0.39793742484318045 * Z ^ I), - (-0.01128010425623538 * Z ^ Z), - ] - ) - evolution = EvolutionFactory.build(operator=op) - # wf = (Pl^Pl) + (Ze^Ze) - wf = ((np.pi / 2) * op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - self.assertIsNotNone(mean) - - def test_parameterized_evolution(self): - """parameterized evolution test""" - thetas = ParameterVector("θ", length=7) - op = ( - (thetas[0] * I ^ I) - + (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = op * thetas[6] - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - circuit = mean.to_circuit() - # Check that all parameters are in the circuit - for p in thetas: - self.assertIn(p, circuit.parameters) - # Check that the identity-parameters only exist as global phase - self.assertNotIn(thetas[0], circuit._parameter_table.get_keys()) - - def test_bind_parameters(self): - """bind parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - wf = wf.assign_parameters({thetas: np.arange(10, 16)}) - mean = evolution.convert(wf) - circuit_params = mean.to_circuit().parameters - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, circuit_params) - - def test_bind_circuit_parameters(self): - """bind circuit parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - evo = evolution.convert(wf) - mean = evo.assign_parameters({thetas: np.arange(10, 16)}) - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, mean.to_circuit().parameters) - # Check that original circuit is unchanged - for p in thetas: - self.assertIn(p, evo.to_circuit().parameters) - - # TODO test with other Op types than CircuitStateFn - def test_bind_parameter_list(self): - """bind parameters list test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - evo = evolution.convert(wf) - param_list = np.transpose([np.arange(10, 16), np.arange(2, 8), np.arange(30, 36)]).tolist() - means = evo.assign_parameters({thetas: param_list}) - self.assertIsInstance(means, ListOp) - # Check that the no parameters are in the circuit - for p in thetas[1:]: - for circop in means.oplist: - self.assertNotIn(p, circop.to_circuit().parameters) - # Check that original circuit is unchanged - for p in thetas: - self.assertIn(p, evo.to_circuit().parameters) - - def test_bind_parameters_complex(self): - """bind parameters with a complex value test""" - th1 = Parameter("th1") - th2 = Parameter("th2") - - operator = th1 * X + th2 * Y - bound_operator = operator.bind_parameters({th1: 3j, th2: 2}) - - expected_bound_operator = SummedOp([3j * X, (2 + 0j) * Y]) - self.assertEqual(bound_operator, expected_bound_operator) - - def test_qdrift(self): - """QDrift test""" - op = (2 * Z ^ Z) + (3 * X ^ X) - (4 * Y ^ Y) + (0.5 * Z ^ I) - trotterization = QDrift().convert(op) - self.assertGreater(len(trotterization.oplist), 150) - last_coeff = None - # Check that all types are correct and all coefficients are equals - for op in trotterization.oplist: - self.assertIsInstance(op, (EvolvedOp, CircuitOp)) - if isinstance(op, EvolvedOp): - if last_coeff: - self.assertEqual(op.primitive.coeff, last_coeff) - else: - last_coeff = op.primitive.coeff - - def test_qdrift_summed_op(self): - """QDrift test for SummedOp""" - op = SummedOp( - [ - (2 * Z ^ Z), - (3 * X ^ X), - (-4 * Y ^ Y), - (0.5 * Z ^ I), - ] - ) - trotterization = QDrift().convert(op) - self.assertGreater(len(trotterization.oplist), 150) - last_coeff = None - # Check that all types are correct and all coefficients are equals - for op in trotterization.oplist: - self.assertIsInstance(op, (EvolvedOp, CircuitOp)) - if isinstance(op, EvolvedOp): - if last_coeff: - self.assertEqual(op.primitive.coeff, last_coeff) - else: - last_coeff = op.primitive.coeff - - def test_matrix_op_evolution(self): - """MatrixOp evolution test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) * np.pi / 2 - ) - exp_mat = op.to_matrix_op().exp_i().to_matrix() - ref_mat = scipy.linalg.expm(-1j * op.to_matrix()) - np.testing.assert_array_almost_equal(ref_mat, exp_mat) - - def test_log_i(self): - """MatrixOp.log_i() test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) * np.pi / 2 - ) - # Test with CircuitOp - log_exp_op = op.to_matrix_op().exp_i().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with MatrixOp - log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with PauliOp - log_exp_op = op.to_matrix_op().exp_i().to_pauli_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with EvolvedOp - log_exp_op = op.exp_i().to_pauli_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with proper ListOp - op = ListOp( - [ - (0.39793742484318045 * I ^ Z), - (0.18093119978423156 * X ^ X), - (-0.39793742484318045 * Z ^ I), - (-0.01128010425623538 * Z ^ Z) * np.pi / 2, - ] - ) - log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - def test_matrix_op_parameterized_evolution(self): - """parameterized MatrixOp evolution test""" - theta = Parameter("θ") - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - ) - op = op * theta - wf = (op.to_matrix_op().exp_i()) @ CX @ (H ^ I) @ Zero - self.assertIn(theta, wf.to_circuit().parameters) - - op = op.assign_parameters({theta: 1}) - exp_mat = op.to_matrix_op().exp_i().to_matrix() - ref_mat = scipy.linalg.expm(-1j * op.to_matrix()) - np.testing.assert_array_almost_equal(ref_mat, exp_mat) - - wf = wf.assign_parameters({theta: 3}) - self.assertNotIn(theta, wf.to_circuit().parameters) - - def test_mixed_evolution(self): - """bind parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * (I ^ Z).to_matrix_op()) - + (thetas[2] * (X ^ X)).to_matrix_op() - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z).to_circuit_op() - + (thetas[5] * (Z ^ I).to_circuit_op()) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - wf = wf.assign_parameters({thetas: np.arange(10, 16)}) - mean = evolution.convert(wf) - circuit_params = mean.to_circuit().parameters - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, circuit_params) - - def test_reps(self): - """Test reps and order params in Trotterization""" - reps = 7 - trotter = Trotter(reps=reps) - self.assertEqual(trotter.reps, reps) - - order = 5 - suzuki = Suzuki(reps=reps, order=order) - self.assertEqual(suzuki.reps, reps) - self.assertEqual(suzuki.order, order) - - qdrift = QDrift(reps=reps) - self.assertEqual(qdrift.reps, reps) - - def test_suzuki_directly(self): - """Test for Suzuki converter""" - operator = X + Z - - evo = Suzuki() - evolution = evo.convert(operator) - - matrix = np.array( - [[0.29192658 - 0.45464871j, -0.84147098j], [-0.84147098j, 0.29192658 + 0.45464871j]] - ) - np.testing.assert_array_almost_equal(evolution.to_matrix(), matrix) - - def test_evolved_op_to_instruction(self): - """Test calling `to_instruction` on a plain EvolvedOp. - - Regression test of Qiskit/qiskit-terra#8025. - """ - op = EvolvedOp(0.5 * X) - circuit = op.to_instruction() - - unitary = scipy.linalg.expm(-0.5j * X.to_matrix()) - expected = UnitaryGate(unitary) - - self.assertEqual(circuit, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_expectation_factory.py b/test/python/opflow/test_expectation_factory.py deleted file mode 100644 index f03a8517733d..000000000000 --- a/test/python/opflow/test_expectation_factory.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the expectation factory.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliExpectation, AerPauliExpectation, ExpectationFactory, Z, I, X -from qiskit.utils import optionals - - -class TestExpectationFactory(QiskitOpflowTestCase): - """Tests for the expectation factory.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_aer_simulator_pauli_sum(self): - """Test expectation selection with Aer's qasm_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - op = 0.2 * (X ^ X) + 0.1 * (Z ^ I) - with self.assertWarns(DeprecationWarning): - with self.subTest("Defaults"): - expectation = ExpectationFactory.build(op, backend, include_custom=False) - self.assertIsInstance(expectation, PauliExpectation) - - with self.subTest("Include custom"): - expectation = ExpectationFactory.build(op, backend, include_custom=True) - self.assertIsInstance(expectation, AerPauliExpectation) diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py deleted file mode 100644 index fc82475313a0..000000000000 --- a/test/python/opflow/test_gradients.py +++ /dev/null @@ -1,1571 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# ============================================================================= - -"""Test Quantum Gradient Framework""" - -import unittest -import warnings -from test.python.opflow import QiskitOpflowTestCase -from itertools import product -import numpy as np -from ddt import ddt, data, idata, unpack - -from qiskit import QuantumCircuit, QuantumRegister, BasicAer -from qiskit.utils import QuantumInstance -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.utils import algorithm_globals - -from qiskit.opflow import ( - I, - X, - Y, - Z, - StateFn, - CircuitStateFn, - ListOp, - CircuitSampler, - TensoredOp, - SummedOp, -) -from qiskit.opflow.gradients import Gradient, NaturalGradient, Hessian -from qiskit.opflow.gradients.qfi import QFI -from qiskit.opflow.gradients.circuit_gradients import LinComb -from qiskit.opflow.gradients.circuit_qfis import LinCombFull, OverlapBlockDiag, OverlapDiag -from qiskit.circuit import Parameter -from qiskit.circuit import ParameterVector -from qiskit.circuit.library import RealAmplitudes, EfficientSU2 -from qiskit.utils import optionals - -if optionals.HAS_JAX: - import jax.numpy as jnp - - -@ddt -class TestGradients(QiskitOpflowTestCase): - """Test Qiskit Gradient Framework""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - - @data("lin_comb", "param_shift", "fin_diff") - def test_gradient_p(self, method): - """Test the state gradient for p - |psi> = 1/sqrt(2)[[1, exp(ia)]] - Tr(|psi>/da = - 0.5 sin(a) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - params = a - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-0.5 / np.sqrt(2), 0, -0.5] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_gradient_u(self, method): - """Test the state gradient for U - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(b) - d/db = - 1 sin(a)cos(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient2(self, method): - """Test the state gradient 2 - - Tr(|psi>/da = - 0.5 sin(a) - 2 cos(a)sin(a) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - # b = Parameter('b') - params = [a] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-1.353553, -0, -0.5] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient3(self, method): - """Test the state gradient 3 - - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(cos(a)+1) + 1 sin^2(a)cos(cos(a)+1) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - # b = Parameter('b') - params = a - c = np.cos(a) + 1 - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(c, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-1.1220, -0.9093, 0.0403] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient4(self, method): - """Test the state gradient 4 - Tr(|psi>/da0 = - 0.5 sin(a0) - 1 cos(a0)sin(a1) - d/da1 = - 1 sin(a0)cos(a1) - """ - - ham = 0.5 * X - 1 * Z - a = ParameterVector("a", 2) - params = a - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: [np.pi / 4, np.pi]}, - {a: [np.pi / 4, np.pi / 4]}, - {a: [np.pi / 2, np.pi / 4]}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_hessian(self, method): - """Test the state Hessian - - Tr(|psi>/da^2 = - 0.5 cos(a) + 1 sin(a)sin(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/db^2 = + 1 sin(a)sin(b) - """ - - ham = 0.5 * X - 1 * Z - params = ParameterVector("a", 2) - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - state_hess = Hessian(hess_method=method).convert(operator=op) - - values_dict = [ - {params[0]: np.pi / 4, params[1]: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - ] - correct_values = [ - [[-0.5 / np.sqrt(2), 1 / np.sqrt(2)], [1 / np.sqrt(2), 0]], - [[-0.5 / np.sqrt(2) + 0.5, -1 / 2.0], [-1 / 2.0, 0.5]], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_hess.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @unittest.skipIf(not optionals.HAS_JAX, "Skipping test due to missing jax module.") - @data("lin_comb", "param_shift", "fin_diff") - def test_state_hessian_custom_combo_fn(self, method): - """Test the state Hessian with on an operator which includes - a user-defined combo_fn. - - Tr(|psi>/da^2 = - 0.5 cos(a) + 1 sin(a)sin(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/db^2 = + 1 sin(a)sin(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [(a, a), (a, b), (b, b)] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - op = ListOp( - [~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0)], - combo_fn=lambda x: x[0] ** 3 + 4 * x[0], - ) - state_hess = Hessian(hess_method=method).convert(operator=op, params=params) - - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {a: np.pi / 4, b: np.pi / 4}, - {a: np.pi / 2, b: np.pi / 4}, - ] - - correct_values = [ - [-1.28163104, 2.56326208, 1.06066017], - [-0.04495626, -2.40716991, 1.8125], - [2.82842712, -1.5, 1.76776695], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_hess.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_prob_grad(self, method): - """Test the probability gradient - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - prob_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: 0}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi}, - ] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - for i, value_dict in enumerate(values_dict): - for j, prob_grad_result in enumerate(prob_grad.assign_parameters(value_dict).eval()): - np.testing.assert_array_almost_equal( - prob_grad_result, correct_values[i][j], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_prob_hess(self, method): - """Test the probability Hessian using linear combination of unitaries method - - d^2p0/da^2 = - sin(a)sin(b) / 2 - d^2p1/da^2 = sin(a)sin(b) / 2 - d^2p0/dadb = cos(a)cos(b) / 2 - d^2p1/dadb = - cos(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [(a, a), (a, b)] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - prob_hess = Hessian(hess_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4, b: 0}, {a: np.pi / 4, b: np.pi / 4}, {a: np.pi / 2, b: np.pi}] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[-1 / 4, 1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [0, 0]], - ] - for i, value_dict in enumerate(values_dict): - for j, prob_hess_result in enumerate(prob_hess.assign_parameters(value_dict).eval()): - np.testing.assert_array_almost_equal( - prob_hess_result, correct_values[i][j], decimal=1 - ) - - @idata( - product( - ["lin_comb", "param_shift", "fin_diff"], - [None, "lasso", "ridge", "perturb_diag", "perturb_diag_elements"], - ) - ) - @unpack - def test_natural_gradient(self, method, regularization): - """Test the natural gradient""" - try: - for params in (ParameterVector("a", 2), [Parameter("a"), Parameter("b")]): - ham = 0.5 * X - 1 * Z - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - nat_grad = NaturalGradient( - grad_method=method, regularization=regularization - ).convert(operator=op) - values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] - - # reference values obtained by classically computing the natural gradients - correct_values = [[-3.26, 1.63]] if regularization == "ridge" else [[-4.24, 0]] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_natural_gradient2(self): - """Test the natural gradient 2""" - with self.assertRaises(TypeError): - _ = NaturalGradient().convert(None, None) - - @idata( - zip( - ["lin_comb_full", "overlap_block_diag", "overlap_diag"], - [LinCombFull, OverlapBlockDiag, OverlapDiag], - ) - ) - @unpack - def test_natural_gradient3(self, qfi_method, circuit_qfi): - """Test the natural gradient 3""" - nat_grad = NaturalGradient(qfi_method=qfi_method) - self.assertIsInstance(nat_grad.qfi_method, circuit_qfi) - - @idata( - product( - ["lin_comb", "param_shift", "fin_diff"], - ["lin_comb_full", "overlap_block_diag", "overlap_diag"], - [None, "ridge", "perturb_diag", "perturb_diag_elements"], - ) - ) - @unpack - def test_natural_gradient4(self, grad_method, qfi_method, regularization): - """Test the natural gradient 4""" - - # Avoid regularization = lasso intentionally because it does not converge - try: - ham = 0.5 * X - 1 * Z - a = Parameter("a") - params = a - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - nat_grad = NaturalGradient( - grad_method=grad_method, qfi_method=qfi_method, regularization=regularization - ).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}] - correct_values = [[0.0]] if regularization == "ridge" else [[-1.41421342]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=3 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_gradient_p_imag(self): - """Test the imaginary state gradient for p - |psi(a)> = 1/sqrt(2)[[1, exp(ia)]] - = iexp(-ia)/2 <1|H(|0>+exp(ia)|1>) - Im() = 0.5 cos(a). - """ - ham = X - a = Parameter("a") - params = a - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = LinComb(aux_meas_op=(-1) * Y).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [1 / np.sqrt(2), 1, 0] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - def test_qfi_p_imag(self): - """Test the imaginary state QFI for RXRY""" - x = Parameter("x") - y = Parameter("y") - circuit = QuantumCircuit(1) - circuit.ry(y, 0) - circuit.rx(x, 0) - state = StateFn(circuit) - - dx = ( - lambda x, y: (-1) - * 0.5j - * np.array( - [ - [ - -1j * np.sin(x / 2) * np.cos(y / 2) + np.cos(x / 2) * np.sin(y / 2), - np.cos(x / 2) * np.cos(y / 2) - 1j * np.sin(x / 2) * np.sin(y / 2), - ] - ] - ) - ) - dy = ( - lambda x, y: (-1) - * 0.5j - * np.array( - [ - [ - -1j * np.cos(x / 2) * np.sin(y / 2) + np.sin(x / 2) * np.cos(y / 2), - 1j * np.cos(x / 2) * np.cos(y / 2) - 1 * np.sin(x / 2) * np.sin(y / 2), - ] - ] - ) - ) - - state_grad = LinCombFull(aux_meas_op=-1 * Y, phase_fix=False).convert( - operator=state, params=[x, y] - ) - values_dict = [{x: 0, y: np.pi / 4}, {x: 0, y: np.pi / 2}, {x: np.pi / 2, y: 0}] - - for value_dict in values_dict: - x_ = list(value_dict.values())[0] - y_ = list(value_dict.values())[1] - correct_values = [ - [ - 4 * np.imag(np.dot(dx(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - ], - [ - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dy(x_, y_))))[0][0]), - ], - ] - - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values, decimal=3 - ) - - @unittest.skipIf(not optionals.HAS_JAX, "Skipping test due to missing jax module.") - @idata(product(["lin_comb", "param_shift", "fin_diff"], [True, False])) - @unpack - def test_jax_chain_rule(self, method: str, autograd: bool): - """Test the chain rule functionality using Jax - - d/d = 2 - d/d = - sin() - = Tr(|psi> = Tr(|psi>/da = d/d d/da + d/d d/da = - 2 cos(a)sin(a) - - sin(sin(a)sin(b)) * cos(a)sin(b) - d/db = d/d d/db + d/d d/db = - sin(sin(a)sin(b)) * sin(a)cos(b) - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - def combo_fn(x): - return jnp.power(x[0], 2) + jnp.cos(x[1]) - - def grad_combo_fn(x): - return np.array([2 * x[0], -np.sin(x[1])]) - - op = ListOp( - [ - ~StateFn(X) @ CircuitStateFn(primitive=qc, coeff=1.0), - ~StateFn(Z) @ CircuitStateFn(primitive=qc, coeff=1.0), - ], - combo_fn=combo_fn, - grad_combo_fn=None if autograd else grad_combo_fn, - ) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [[-1.0, 0.0], [-1.2397, -0.2397], [0, -0.45936]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_grad_combo_fn_chain_rule(self, method): - """Test the chain rule for a custom gradient combo function.""" - np.random.seed(2) - - def combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - return np.sum(np.log(pdf)) / (-len(amplitudes)) - - def grad_combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - grad = [] - for prob in pdf: - grad += [-1 / prob] - return grad - - qc = RealAmplitudes(2, reps=1) - grad_op = ListOp([StateFn(qc.decompose())], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn) - grad = Gradient(grad_method=method).convert(grad_op) - - value_dict = dict(zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters)))) - correct_values = [ - [(-0.16666259133549044 + 0j)], - [(-7.244949702732864 + 0j)], - [(-2.979791752749964 + 0j)], - [(-5.310186078432614 + 0j)], - ] - np.testing.assert_array_almost_equal( - grad.assign_parameters(value_dict).eval(), correct_values - ) - - def test_grad_combo_fn_chain_rule_nat_grad(self): - """Test the chain rule for a custom gradient combo function.""" - np.random.seed(2) - - def combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - return np.sum(np.log(pdf)) / (-len(amplitudes)) - - def grad_combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - grad = [] - for prob in pdf: - grad += [-1 / prob] - return grad - - try: - qc = RealAmplitudes(2, reps=1) - grad_op = ListOp( - [StateFn(qc.decompose())], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn - ) - grad = NaturalGradient(grad_method="lin_comb", regularization="ridge").convert( - grad_op, qc.ordered_parameters - ) - value_dict = dict( - zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters))) - ) - correct_values = [[0.20777236], [-18.92560338], [-15.89005475], [-10.44002031]] - np.testing.assert_array_almost_equal( - grad.assign_parameters(value_dict).eval(), correct_values, decimal=3 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @data("lin_comb", "param_shift", "fin_diff") - def test_operator_coefficient_gradient(self, method): - """Test the operator coefficient gradient - - Tr( | psi > < psi | Z) = sin(a)sin(b) - Tr( | psi > < psi | X) = cos(a) - """ - a = Parameter("a") - b = Parameter("b") - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - coeff_0 = Parameter("c_0") - coeff_1 = Parameter("c_1") - ham = coeff_0 * X + coeff_1 * Z - op = StateFn(ham, is_measurement=True) @ CircuitStateFn(primitive=qc, coeff=1.0) - gradient_coeffs = [coeff_0, coeff_1] - coeff_grad = Gradient(grad_method=method).convert(op, gradient_coeffs) - values_dict = [ - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi}, - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi / 4}, - ] - correct_values = [[1 / np.sqrt(2), 0], [1 / np.sqrt(2), 1 / 2]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - coeff_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_operator_coefficient_hessian(self, method): - """Test the operator coefficient hessian - - = Tr( | psi > < psi | Z) = sin(a)sin(b) - = Tr( | psi > < psi | X) = cos(a) - d/dc_0 = 2 * c_0 * + c_1 * - d/dc_1 = c_0 * - d^2/dc_0^2 = 2 * - d^2/dc_0dc_1 = - d^2/dc_1dc_0 = - d^2/dc_1^2 = 0 - """ - a = Parameter("a") - b = Parameter("b") - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - coeff_0 = Parameter("c_0") - coeff_1 = Parameter("c_1") - ham = coeff_0 * coeff_0 * X + coeff_1 * coeff_0 * Z - op = StateFn(ham, is_measurement=True) @ CircuitStateFn(primitive=qc, coeff=1.0) - gradient_coeffs = [(coeff_0, coeff_0), (coeff_0, coeff_1), (coeff_1, coeff_1)] - coeff_grad = Hessian(hess_method=method).convert(op, gradient_coeffs) - values_dict = [ - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi}, - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi / 4}, - ] - - correct_values = [[2 / np.sqrt(2), 0, 0], [2 / np.sqrt(2), 1 / 2, 0]] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - coeff_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_circuit_sampler(self, method): - """Test the gradient with circuit sampler - - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(b) - d/db = - 1 sin(a)cos(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - if method == "fin_diff": - np.random.seed(8) - state_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).convert( - operator=op - ) - else: - state_grad = Gradient(grad_method=method).convert(operator=op) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - - with self.assertWarns(DeprecationWarning): - for i, value_dict in enumerate(values_dict): - sampler = CircuitSampler(backend=q_instance).convert( - state_grad, params={k: [v] for k, v in value_dict.items()} - ) - np.testing.assert_array_almost_equal( - sampler.eval()[0], correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_circuit_sampler2(self, method): - """Test the probability gradient with the circuit sampler - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - if method == "fin_diff": - np.random.seed(8) - prob_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).convert( - operator=op, params=params - ) - else: - prob_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: [np.pi / 4], b: [0]}, - {params[0]: [np.pi / 4], params[1]: [np.pi / 4]}, - {params[0]: [np.pi / 2], params[1]: [np.pi]}, - ] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - - with self.assertWarns(DeprecationWarning): - for i, value_dict in enumerate(values_dict): - sampler = CircuitSampler(backend=q_instance).convert(prob_grad, params=value_dict) - result = sampler.eval()[0] - self.assertTrue(np.allclose(result[0].toarray(), correct_values[i][0], atol=0.1)) - self.assertTrue(np.allclose(result[1].toarray(), correct_values[i][1], atol=0.1)) - - @idata(["statevector_simulator", "qasm_simulator"]) - def test_gradient_wrapper(self, backend_type): - """Test the gradient wrapper for probability gradients - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "param_shift" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - backend = BasicAer.get_backend(backend_type) - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - - if method == "fin_diff": - np.random.seed(8) - prob_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - else: - - with self.assertWarns(DeprecationWarning): - prob_grad = Gradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - - values = [[np.pi / 4, 0], [np.pi / 4, np.pi / 4], [np.pi / 2, np.pi]] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - with self.assertWarns(DeprecationWarning): - for i, value in enumerate(values): - result = prob_grad(value) - if backend_type == "qasm_simulator": # sparse result - result = [result[0].toarray(), result[1].toarray()] - - self.assertTrue(np.allclose(result[0], correct_values[i][0], atol=0.1)) - self.assertTrue(np.allclose(result[1], correct_values[i][1], atol=0.1)) - - @data(("statevector_simulator", 1e-7), ("qasm_simulator", 2e-1)) - @unpack - def test_gradient_wrapper2(self, backend_type, atol): - """Test the gradient wrapper for gradients checking that statevector and qasm gives the - same results - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "lin_comb" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - qc = QuantumCircuit(2) - qc.h(1) - qc.h(0) - qc.sdg(1) - qc.cz(0, 1) - qc.ry(params[0], 0) - qc.rz(params[1], 0) - qc.h(1) - - obs = (Z ^ X) - (Y ^ Y) - op = StateFn(obs, is_measurement=True) @ CircuitStateFn(primitive=qc) - - shots = 8192 if backend_type == "qasm_simulator" else 1 - - values = [[0, np.pi / 2], [np.pi / 4, np.pi / 4], [np.pi / 3, np.pi / 9]] - correct_values = [[-4.0, 0], [-2.0, -4.82842712], [-0.68404029, -7.01396121]] - for i, value in enumerate(values): - backend = BasicAer.get_backend(backend_type) - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - grad = NaturalGradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - result = grad(value) - self.assertTrue(np.allclose(result, correct_values[i], atol=atol)) - - def test_qfi_overlap_works_with_bound_parameters(self): - """Test all QFI methods work if the circuit contains a gate with bound parameters.""" - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(np.pi / 4, 0) - circuit.rx(x, 0) - state = StateFn(circuit) - - methods = ["lin_comb_full", "overlap_diag", "overlap_block_diag"] - reference = 0.5 - - for method in methods: - with self.subTest(method): - qfi = QFI(method) - value = np.real(qfi.convert(state, [x]).bind_parameters({x: 0.12}).eval()) - self.assertAlmostEqual(value[0][0], reference) - - -@ddt -class TestParameterGradients(QiskitOpflowTestCase): - """Test taking the gradient of parameter expressions.""" - - def test_grad(self): - """Test taking the gradient of parameter expressions.""" - x, y = Parameter("x"), Parameter("y") - with self.subTest("linear"): - expr = 2 * x + y - - grad = expr.gradient(x) - self.assertEqual(grad, 2) - - grad = expr.gradient(y) - self.assertEqual(grad, 1) - - with self.subTest("polynomial"): - expr = x * x * x - x * y + y * y - - grad = expr.gradient(x) - self.assertEqual(grad, 3 * x * x - y) - - grad = expr.gradient(y) - self.assertEqual(grad, -1 * x + 2 * y) - - def test_converted_to_float_if_bound(self): - """Test the gradient is a float when no free symbols are left.""" - x = Parameter("x") - expr = 2 * x + 1 - grad = expr.gradient(x) - self.assertIsInstance(grad, float) - - def test_converted_to_complex_if_bound(self): - """Test the gradient is a complex when no free symbols are left.""" - x = Parameter("x") - x2 = 1j * x - expr = 2 * x2 + 1 - grad = expr.gradient(x) - self.assertIsInstance(grad, complex) - - -@ddt -class TestQFI(QiskitOpflowTestCase): - """Tests for the quantum Fisher information.""" - - @data("lin_comb_full", "overlap_block_diag", "overlap_diag") - def test_qfi_simple(self, method): - """Test if the quantum fisher information calculation is correct for a simple test case. - - QFI = [[1, 0], [0, 1]] - [[0, 0], [0, cos^2(a)]] - """ - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - # convert the circuit to a QFI object - op = CircuitStateFn(qc) - qfi = QFI(qfi_method=method).convert(operator=op) - - # test for different values - values_dict = [{a: np.pi / 4, b: 0.1}, {a: np.pi, b: 0.1}, {a: np.pi / 2, b: 0.1}] - correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] - - for i, value_dict in enumerate(values_dict): - actual = qfi.assign_parameters(value_dict).eval() - np.testing.assert_array_almost_equal(actual, correct_values[i], decimal=1) - - def test_qfi_phase_fix(self): - """Test the phase-fix argument in a QFI calculation - - QFI = [[1, 0], [0, 1]]. - """ - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - # convert the circuit to a QFI object - op = CircuitStateFn(qc) - qfi = LinCombFull(phase_fix=False).convert(operator=op, params=[a, b]) - - # test for different values - value_dict = {a: np.pi / 4, b: 0.1} - correct_values = [[1, 0], [0, 1]] - - actual = qfi.assign_parameters(value_dict).eval() - np.testing.assert_array_almost_equal(actual, correct_values, decimal=2) - - def test_qfi_maxcut(self): - """Test the QFI for a simple MaxCut problem. - - This is interesting because it contains the same parameters in different gates. - """ - # create maxcut circuit for the hamiltonian - # H = (I ^ I ^ Z ^ Z) + (I ^ Z ^ I ^ Z) + (Z ^ I ^ I ^ Z) + (I ^ Z ^ Z ^ I) - - x = ParameterVector("x", 2) - ansatz = QuantumCircuit(4) - - # initial hadamard layer - ansatz.h(ansatz.qubits) - - # e^{iZZ} layers - def expiz(qubit0, qubit1): - ansatz.cx(qubit0, qubit1) - ansatz.rz(2 * x[0], qubit1) - ansatz.cx(qubit0, qubit1) - - expiz(2, 1) - expiz(3, 0) - expiz(2, 0) - expiz(1, 0) - - # mixer layer with RX gates - for i in range(ansatz.num_qubits): - ansatz.rx(2 * x[1], i) - - point = {x[0]: 0.4, x[1]: 0.69} - - # reference computed via finite difference - reference = np.array([[16.0, -5.551], [-5.551, 18.497]]) - - # QFI from gradient framework - qfi = QFI().convert(CircuitStateFn(ansatz), params=x[:]) - actual = np.array(qfi.bind_parameters(point).eval()).real - np.testing.assert_array_almost_equal(actual, reference, decimal=3) - - def test_qfi_circuit_shared_params(self): - """Test the QFI circuits for parameters shared across some gates.""" - # create the test circuit - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.rx(x, 0) - circuit.rx(x, 0) - - # construct the QFI circuits used in the evaluation - - circuit1 = QuantumCircuit(2) - circuit1.h(1) - circuit1.x(1) - circuit1.cx(1, 0) - circuit1.x(1) - circuit1.cx(1, 0) - # circuit1.rx(x, 0) # trimmed - # circuit1.rx(x, 0) # trimmed - circuit1.h(1) - - circuit2 = QuantumCircuit(2) - circuit2.h(1) - circuit2.x(1) - circuit2.cx(1, 0) - circuit2.x(1) - circuit2.rx(x, 0) - circuit2.cx(1, 0) - # circuit2.rx(x, 0) # trimmed - circuit2.h(1) - - circuit3 = QuantumCircuit(2) - circuit3.h(1) - circuit3.cx(1, 0) - circuit3.x(1) - circuit3.rx(x, 0) - circuit3.cx(1, 0) - # circuit3.rx(x, 0) # trimmed - circuit3.x(1) - circuit3.h(1) - - circuit4 = QuantumCircuit(2) - circuit4.h(1) - circuit4.rx(x, 0) - circuit4.x(1) - circuit4.cx(1, 0) - circuit4.x(1) - circuit4.cx(1, 0) - # circuit4.rx(x, 0) # trimmed - circuit4.h(1) - - # this naming and adding of register is required bc circuit's are only equal if the - # register have the same names - circuit5 = QuantumCircuit(2) - circuit5.h(1) - circuit5.sdg(1) - circuit5.cx(1, 0) - # circuit5.rx(x, 0) # trimmed - circuit5.h(1) - - circuit6 = QuantumCircuit(2) - circuit6.h(1) - circuit6.sdg(1) - circuit6.rx(x, 0) - circuit6.cx(1, 0) - circuit6.h(1) - - # compare - qfi = QFI().convert(StateFn(circuit), params=[x]) - - circuit_sets = ( - [circuit1, circuit2, circuit3, circuit4], - [circuit5, circuit6], - [circuit5, circuit6], - ) - list_ops = ( - qfi.oplist[0].oplist[0].oplist[:-1], - qfi.oplist[0].oplist[0].oplist[-1].oplist[0].oplist, - qfi.oplist[0].oplist[0].oplist[-1].oplist[1].oplist, - ) - - # compose both on the same circuit such that the comparison works - base = QuantumCircuit(2) - - for i, (circuit_set, list_op) in enumerate(zip(circuit_sets, list_ops)): - for j, (reference, composed_op) in enumerate(zip(circuit_set, list_op)): - with self.subTest(f"set {i} circuit {j}"): - primitive = composed_op[1].primitive - self.assertEqual(base.compose(primitive), base.compose(reference)) - - def test_overlap_qfi_bound_parameters(self): - """Test the overlap QFI works on a circuit with multi-parameter bound gates.""" - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.u(1, 2, 3, 0) - circuit.rx(x, 0) - - qfi = QFI("overlap_diag").convert(StateFn(circuit), [x]) - value = qfi.bind_parameters({x: 1}).eval()[0][0] - ref = 0.87737713 - self.assertAlmostEqual(value, ref) - - def test_overlap_qfi_raises_on_multiparam(self): - """Test the overlap QFI raises an appropriate error on multi-param unbound gates.""" - x = ParameterVector("x", 2) - circuit = QuantumCircuit(1) - circuit.u(x[0], x[1], 2, 0) - - with self.assertRaises(NotImplementedError): - _ = QFI("overlap_diag").convert(StateFn(circuit), [x]) - - def test_overlap_qfi_raises_on_unsupported_gate(self): - """Test the overlap QFI raises an appropriate error on multi-param unbound gates.""" - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.p(x, 0) - - with self.assertRaises(NotImplementedError): - _ = QFI("overlap_diag").convert(StateFn(circuit), [x]) - - @data(-Y, Z - 1j * Y) - def test_aux_meas_op(self, aux_meas_op): - """Test various auxiliary measurement operators for probability gradients with LinComb - Gradient. - - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 10000 - - prob_grad = LinComb(aux_meas_op=aux_meas_op).convert(operator=op, params=params) - value_dicts = [{a: [np.pi / 4], b: [0]}, {a: [np.pi / 2], b: [np.pi / 4]}] - if aux_meas_op == -Y: - correct_values = [ - [[-0.5, 0.5], [-1 / (np.sqrt(2) * 2), -1 / (np.sqrt(2) * 2)]], - [[-1 / (np.sqrt(2) * 2), 1 / (np.sqrt(2) * 2)], [0, 0]], - ] - else: - correct_values = [ - [[-0.5j, 0.5j], [(1 - 1j) / (np.sqrt(2) * 2), (-1 - 1j) / (np.sqrt(2) * 2)]], - [ - [-1j / (np.sqrt(2) * 2), 1j / (np.sqrt(2) * 2)], - [1 / (np.sqrt(2) * 2), -1 / (np.sqrt(2) * 2)], - ], - ] - - for backend_type in ["qasm_simulator", "statevector_simulator"]: - - for j, value_dict in enumerate(value_dicts): - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=BasicAer.get_backend(backend_type), shots=shots - ) - result = ( - CircuitSampler(backend=q_instance) - .convert(prob_grad, params=value_dict) - .eval()[0] - ) - if backend_type == "qasm_simulator": # sparse result - result = [result[0].toarray()[0], result[1].toarray()[0]] - for i, item in enumerate(result): - np.testing.assert_array_almost_equal(item, correct_values[j][i], decimal=1) - - def test_unsupported_aux_meas_op(self): - """Test error for unsupported auxiliary measurement operator in LinComb Gradient. - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - - aux_meas_op = X - - with self.assertRaises(ValueError): - prob_grad = LinComb(aux_meas_op=aux_meas_op).convert(operator=op, params=params) - value_dict = {a: [np.pi / 4], b: [0]} - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - CircuitSampler(backend=q_instance).convert(prob_grad, params=value_dict).eval() - - def test_nat_grad_error(self): - """Test the NaturalGradient throws an Error. - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "lin_comb" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - qc = QuantumCircuit(2) - qc.h(1) - qc.h(0) - qc.sdg(1) - qc.cz(0, 1) - qc.ry(params[0], 0) - qc.rz(params[1], 0) - qc.h(1) - - obs = (Z ^ X) - (Y ^ Y) - op = StateFn(obs, is_measurement=True) @ CircuitStateFn(primitive=qc) - - backend_type = "qasm_simulator" - shots = 1 - value = [0, np.pi / 2] - - backend = BasicAer.get_backend(backend_type) - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - - with self.assertWarns(DeprecationWarning): - grad = NaturalGradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - grad(value) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_matrix_expectation.py b/test/python/opflow/test_matrix_expectation.py deleted file mode 100644 index 10448c3a64e1..000000000000 --- a/test/python/opflow/test_matrix_expectation.py +++ /dev/null @@ -1,184 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test MatrixExpectation""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -import itertools -import numpy as np - -from qiskit.utils import QuantumInstance -from qiskit.opflow import ( - X, - Y, - Z, - I, - CX, - H, - S, - ListOp, - Zero, - One, - Plus, - Minus, - StateFn, - MatrixExpectation, - CircuitSampler, -) -from qiskit import BasicAer - - -class TestMatrixExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - def setUp(self) -> None: - super().setUp() - self.seed = 97 - backend = BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - - self.expect = MatrixExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - - op = Z ^ Z - # wf = (Pl^Pl) + (Ze^Ze) - wf = CX @ (H ^ I) @ Zero - converted_meas = self.expect.convert(~StateFn(op) @ wf) - self.assertAlmostEqual(converted_meas.eval(), 0, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - self.assertAlmostEqual(converted_meas.eval(), matmulmean, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - with self.assertWarns(DeprecationWarning): - - plus_mean = converted_meas @ Plus - np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) - sampled_zero = self.sampler.convert(sum_zero) - - np.testing.assert_array_almost_equal( - (converted_meas @ sampled_zero).eval(), [0, 0, 1, 1], decimal=1 - ) - - for i, op in enumerate(paulis_op.oplist): - mat_op = op.to_matrix() - np.testing.assert_array_almost_equal( - zero_mean.eval()[i], - Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - plus_mean.eval()[i], - Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - minus_mean.eval()[i], - Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), - decimal=1, - ) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - - states_op = ListOp([One, Zero, Plus, Minus]) - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [0, 0, 1, -1], decimal=1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertIn("statevector", composed_op[1].execution_results) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op)) - np.testing.assert_array_almost_equal((converted_meas @ states_op).eval(), valids, decimal=1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(states_op) - - np.testing.assert_array_almost_equal((converted_meas @ sampled).eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - - plus_mean = converted_meas @ Plus - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_matrix_expectation_non_hermite_op(self): - """Test MatrixExpectation for non hermitian operator""" - - exp = ~StateFn(1j * Z) @ One - self.assertEqual(self.expect.convert(exp).eval(), 1j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_op_construction.py b/test/python/opflow/test_op_construction.py deleted file mode 100644 index a3dcb7dd6671..000000000000 --- a/test/python/opflow/test_op_construction.py +++ /dev/null @@ -1,1385 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Operator construction, including OpPrimitives and singletons.""" - - -import itertools -import unittest -from math import pi -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np -import scipy -from ddt import data, ddt, unpack -from scipy.sparse import csr_matrix -from scipy.stats import unitary_group - -from qiskit import QiskitError, transpile -from qiskit.circuit import ( - Instruction, - Parameter, - ParameterVector, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.library import CZGate, ZGate -from qiskit.opflow import ( - CX, - CircuitOp, - CircuitStateFn, - ComposedOp, - DictStateFn, - EvolvedOp, - H, - I, - ListOp, - MatrixOp, - Minus, - OperatorBase, - OperatorStateFn, - OpflowError, - PauliOp, - PrimitiveOp, - SparseVectorStateFn, - StateFn, - SummedOp, - T, - TensoredOp, - VectorStateFn, - X, - Y, - Z, - Zero, -) -from qiskit.quantum_info import Operator, Pauli, Statevector - -# pylint: disable=invalid-name - - -@ddt -class TestOpConstruction(QiskitOpflowTestCase): - """Operator Construction tests.""" - - def test_pauli_primitives(self): - """from to file test""" - newop = X ^ Y ^ Z ^ I - self.assertEqual(newop.primitive, Pauli("XYZI")) - - kpower_op = (Y ^ 5) ^ (I ^ 3) - self.assertEqual(kpower_op.primitive, Pauli("YYYYYIII")) - - kpower_op2 = (Y ^ I) ^ 4 - self.assertEqual(kpower_op2.primitive, Pauli("YIYIYIYI")) - - # Check immutability - self.assertEqual(X.primitive, Pauli("X")) - self.assertEqual(Y.primitive, Pauli("Y")) - self.assertEqual(Z.primitive, Pauli("Z")) - self.assertEqual(I.primitive, Pauli("I")) - - def test_composed_eval(self): - """Test eval of ComposedOp""" - self.assertAlmostEqual(Minus.eval("1"), -(0.5**0.5)) - - def test_xz_compose_phase(self): - """Test phase composition""" - self.assertEqual((-1j * Y).eval("0").eval("0"), 0) - self.assertEqual((-1j * Y).eval("0").eval("1"), 1) - self.assertEqual((-1j * Y).eval("1").eval("0"), -1) - self.assertEqual((-1j * Y).eval("1").eval("1"), 0) - self.assertEqual((X @ Z).eval("0").eval("0"), 0) - self.assertEqual((X @ Z).eval("0").eval("1"), 1) - self.assertEqual((X @ Z).eval("1").eval("0"), -1) - self.assertEqual((X @ Z).eval("1").eval("1"), 0) - self.assertEqual((1j * Y).eval("0").eval("0"), 0) - self.assertEqual((1j * Y).eval("0").eval("1"), -1) - self.assertEqual((1j * Y).eval("1").eval("0"), 1) - self.assertEqual((1j * Y).eval("1").eval("1"), 0) - self.assertEqual((Z @ X).eval("0").eval("0"), 0) - self.assertEqual((Z @ X).eval("0").eval("1"), -1) - self.assertEqual((Z @ X).eval("1").eval("0"), 1) - self.assertEqual((Z @ X).eval("1").eval("1"), 0) - - def test_evals(self): - """evals test""" - # TODO: Think about eval names - self.assertEqual(Z.eval("0").eval("0"), 1) - self.assertEqual(Z.eval("1").eval("0"), 0) - self.assertEqual(Z.eval("0").eval("1"), 0) - self.assertEqual(Z.eval("1").eval("1"), -1) - self.assertEqual(X.eval("0").eval("0"), 0) - self.assertEqual(X.eval("1").eval("0"), 1) - self.assertEqual(X.eval("0").eval("1"), 1) - self.assertEqual(X.eval("1").eval("1"), 0) - self.assertEqual(Y.eval("0").eval("0"), 0) - self.assertEqual(Y.eval("1").eval("0"), -1j) - self.assertEqual(Y.eval("0").eval("1"), 1j) - self.assertEqual(Y.eval("1").eval("1"), 0) - - with self.assertRaises(ValueError): - Y.eval("11") - - with self.assertRaises(ValueError): - (X ^ Y).eval("1111") - - with self.assertRaises(ValueError): - Y.eval((X ^ X).to_matrix_op()) - - # Check that Pauli logic eval returns same as matrix logic - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("0").eval("0"), 1) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("1").eval("0"), 0) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("0").eval("1"), 0) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("1").eval("1"), -1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("0").eval("0"), 0) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("1").eval("0"), 1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("0").eval("1"), 1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("1").eval("1"), 0) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("0").eval("0"), 0) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("1").eval("0"), -1j) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("0").eval("1"), 1j) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("1").eval("1"), 0) - - pauli_op = Z ^ I ^ X ^ Y - mat_op = PrimitiveOp(pauli_op.to_matrix()) - full_basis = list(map("".join, itertools.product("01", repeat=pauli_op.num_qubits))) - for bstr1, bstr2 in itertools.product(full_basis, full_basis): - # print('{} {} {} {}'.format(bstr1, bstr2, pauli_op.eval(bstr1, bstr2), - # mat_op.eval(bstr1, bstr2))) - np.testing.assert_array_almost_equal( - pauli_op.eval(bstr1).eval(bstr2), mat_op.eval(bstr1).eval(bstr2) - ) - - gnarly_op = SummedOp( - [ - (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(Z), - PrimitiveOp(Operator.from_label("+r0I")), - 3 * (X ^ CX ^ T), - ], - coeff=3 + 0.2j, - ) - gnarly_mat_op = PrimitiveOp(gnarly_op.to_matrix()) - full_basis = list(map("".join, itertools.product("01", repeat=gnarly_op.num_qubits))) - for bstr1, bstr2 in itertools.product(full_basis, full_basis): - np.testing.assert_array_almost_equal( - gnarly_op.eval(bstr1).eval(bstr2), gnarly_mat_op.eval(bstr1).eval(bstr2) - ) - - def test_circuit_construction(self): - """circuit construction test""" - hadq2 = H ^ I - cz = hadq2.compose(CX).compose(hadq2) - qc = QuantumCircuit(2) - qc.append(cz.primitive, qargs=range(2)) - - ref_cz_mat = PrimitiveOp(CZGate()).to_matrix() - np.testing.assert_array_almost_equal(cz.to_matrix(), ref_cz_mat) - - def test_io_consistency(self): - """consistency test""" - new_op = X ^ Y ^ I - label = "XYI" - # label = new_op.primitive.to_label() - self.assertEqual(str(new_op.primitive), label) - np.testing.assert_array_almost_equal( - new_op.primitive.to_matrix(), Operator.from_label(label).data - ) - self.assertEqual(new_op.primitive, Pauli(label)) - - x_mat = X.primitive.to_matrix() - y_mat = Y.primitive.to_matrix() - i_mat = np.eye(2, 2) - np.testing.assert_array_almost_equal( - new_op.primitive.to_matrix(), np.kron(np.kron(x_mat, y_mat), i_mat) - ) - - hi = np.kron(H.to_matrix(), I.to_matrix()) - hi2 = Operator.from_label("HI").data - hi3 = (H ^ I).to_matrix() - np.testing.assert_array_almost_equal(hi, hi2) - np.testing.assert_array_almost_equal(hi2, hi3) - - xy = np.kron(X.to_matrix(), Y.to_matrix()) - xy2 = Operator.from_label("XY").data - xy3 = (X ^ Y).to_matrix() - np.testing.assert_array_almost_equal(xy, xy2) - np.testing.assert_array_almost_equal(xy2, xy3) - - # Check if numpy array instantiation is the same as from Operator - matrix_op = Operator.from_label("+r") - np.testing.assert_array_almost_equal( - PrimitiveOp(matrix_op).to_matrix(), PrimitiveOp(matrix_op.data).to_matrix() - ) - # Ditto list of lists - np.testing.assert_array_almost_equal( - PrimitiveOp(matrix_op.data.tolist()).to_matrix(), - PrimitiveOp(matrix_op.data).to_matrix(), - ) - - # TODO make sure this works once we resolve endianness mayhem - # qc = QuantumCircuit(3) - # qc.x(2) - # qc.y(1) - # from qiskit import BasicAer, QuantumCircuit, execute - # unitary = execute(qc, BasicAer.get_backend('unitary_simulator')).result().get_unitary() - # np.testing.assert_array_almost_equal(new_op.primitive.to_matrix(), unitary) - - def test_to_matrix(self): - """to matrix text""" - np.testing.assert_array_equal(X.to_matrix(), Operator.from_label("X").data) - np.testing.assert_array_equal(Y.to_matrix(), Operator.from_label("Y").data) - np.testing.assert_array_equal(Z.to_matrix(), Operator.from_label("Z").data) - - op1 = Y + H - np.testing.assert_array_almost_equal(op1.to_matrix(), Y.to_matrix() + H.to_matrix()) - - op2 = op1 * 0.5 - np.testing.assert_array_almost_equal(op2.to_matrix(), op1.to_matrix() * 0.5) - - op3 = (4 - 0.6j) * op2 - np.testing.assert_array_almost_equal(op3.to_matrix(), op2.to_matrix() * (4 - 0.6j)) - - op4 = op3.tensor(X) - np.testing.assert_array_almost_equal( - op4.to_matrix(), np.kron(op3.to_matrix(), X.to_matrix()) - ) - - op5 = op4.compose(H ^ I) - np.testing.assert_array_almost_equal( - op5.to_matrix(), np.dot(op4.to_matrix(), (H ^ I).to_matrix()) - ) - - op6 = op5 + PrimitiveOp(Operator.from_label("+r").data) - np.testing.assert_array_almost_equal( - op6.to_matrix(), op5.to_matrix() + Operator.from_label("+r").data - ) - - param = Parameter("α") - m = np.array([[0, -1j], [1j, 0]]) - op7 = MatrixOp(m, param) - np.testing.assert_array_equal(op7.to_matrix(), m * param) - - param = Parameter("β") - op8 = PauliOp(primitive=Pauli("Y"), coeff=param) - np.testing.assert_array_equal(op8.to_matrix(), m * param) - - param = Parameter("γ") - qc = QuantumCircuit(1) - qc.h(0) - op9 = CircuitOp(qc, coeff=param) - m = np.array([[1, 1], [1, -1]]) / np.sqrt(2) - np.testing.assert_array_equal(op9.to_matrix(), m * param) - - def test_circuit_op_to_matrix(self): - """test CircuitOp.to_matrix""" - qc = QuantumCircuit(1) - qc.rz(1.0, 0) - qcop = CircuitOp(qc) - np.testing.assert_array_almost_equal( - qcop.to_matrix(), scipy.linalg.expm(-0.5j * Z.to_matrix()) - ) - - def test_matrix_to_instruction(self): - """Test MatrixOp.to_instruction yields an Instruction object.""" - matop = (H ^ 3).to_matrix_op() - with self.subTest("assert to_instruction returns Instruction"): - self.assertIsInstance(matop.to_instruction(), Instruction) - - matop = ((H ^ 3) + (Z ^ 3)).to_matrix_op() - with self.subTest("matrix operator is not unitary"): - with self.assertRaises(ValueError): - matop.to_instruction() - - def test_adjoint(self): - """adjoint test""" - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - np.testing.assert_array_almost_equal( - np.conj(np.transpose(gnarly_op.to_matrix())), gnarly_op.adjoint().to_matrix() - ) - - def test_primitive_strings(self): - """get primitives test""" - self.assertEqual(X.primitive_strings(), {"Pauli"}) - - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - self.assertEqual(gnarly_op.primitive_strings(), {"QuantumCircuit", "Matrix"}) - - def test_to_pauli_op(self): - """Test to_pauli_op method""" - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - mat_op = gnarly_op.to_matrix_op() - pauli_op = gnarly_op.to_pauli_op() - self.assertIsInstance(pauli_op, SummedOp) - for p in pauli_op: - self.assertIsInstance(p, PauliOp) - np.testing.assert_array_almost_equal(mat_op.to_matrix(), pauli_op.to_matrix()) - - def test_circuit_permute(self): - r"""Test the CircuitOp's .permute method""" - perm = range(7)[::-1] - c_op = ( - ((CX ^ 3) ^ X) - @ (H ^ 7) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - @ (Y ^ (CX ^ 3)) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - ) - c_op_perm = c_op.permute(perm) - self.assertNotEqual(c_op, c_op_perm) - c_op_id = c_op_perm.permute(perm) - self.assertEqual(c_op, c_op_id) - - def test_summed_op_reduce(self): - """Test SummedOp""" - sum_op = (X ^ X * 2) + (Y ^ Y) # type: PauliSumOp - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 1"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1]) - - sum_op = (X ^ X * 2) + (Y ^ Y) - sum_op += Y ^ Y - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 2-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 2-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 2]) - - sum_op = (X ^ X * 2) + (Y ^ Y) - sum_op += (Y ^ Y) + (X ^ X * 2) - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 3-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY", "XX"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1, 1, 2]) - - sum_op = sum_op.reduce().to_pauli_op() - with self.subTest("SummedOp test 3-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - with self.subTest("SummedOp test 4-a"): - self.assertEqual(sum_op.coeff, 2) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 4-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += Y ^ Y - with self.subTest("SummedOp test 5-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 5-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 3]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += ((X ^ X) * 2 + (Y ^ Y)).to_pauli_op() - with self.subTest("SummedOp test 6-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 6-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [6, 3]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += sum_op - with self.subTest("SummedOp test 7-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 4, 2]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 7-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [8, 4]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) + SummedOp([X ^ X * 2, Z ^ Z], 3) - with self.subTest("SummedOp test 8-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "ZZ"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 6, 3]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 8-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "ZZ"]) - self.assertListEqual([op.coeff for op in sum_op], [10, 2, 3]) - - sum_op = SummedOp([]) - with self.subTest("SummedOp test 9"): - self.assertEqual(sum_op.reduce(), sum_op) - - sum_op = ((Z + I) ^ Z) + (Z ^ X) - with self.subTest("SummedOp test 10"): - expected = SummedOp([PauliOp(Pauli("ZZ")), PauliOp(Pauli("IZ")), PauliOp(Pauli("ZX"))]) - self.assertEqual(sum_op.to_pauli_op(), expected) - - def test_compose_op_of_different_dim(self): - """ - Test if smaller operator expands to correct dim when composed with bigger operator. - Test if PrimitiveOps compose methods are consistent. - """ - # PauliOps of different dim - xy_p = X ^ Y - xyz_p = X ^ Y ^ Z - - pauli_op = xy_p @ xyz_p - expected_result = I ^ I ^ Z - self.assertEqual(pauli_op, expected_result) - - # MatrixOps of different dim - xy_m = xy_p.to_matrix_op() - xyz_m = xyz_p.to_matrix_op() - - matrix_op = xy_m @ xyz_m - self.assertEqual(matrix_op, expected_result.to_matrix_op()) - - # CircuitOps of different dim - xy_c = xy_p.to_circuit_op() - xyz_c = xyz_p.to_circuit_op() - - circuit_op = xy_c @ xyz_c - - self.assertTrue(np.array_equal(pauli_op.to_matrix(), matrix_op.to_matrix())) - self.assertTrue(np.allclose(pauli_op.to_matrix(), circuit_op.to_matrix(), rtol=1e-14)) - self.assertTrue(np.allclose(matrix_op.to_matrix(), circuit_op.to_matrix(), rtol=1e-14)) - - def test_permute_on_primitive_op(self): - """Test if permute methods of PrimitiveOps are consistent and work as expected.""" - indices = [1, 2, 4] - - # PauliOp - pauli_op = X ^ Y ^ Z - permuted_pauli_op = pauli_op.permute(indices) - expected_pauli_op = X ^ I ^ Y ^ Z ^ I - - self.assertEqual(permuted_pauli_op, expected_pauli_op) - - # CircuitOp - circuit_op = pauli_op.to_circuit_op() - permuted_circuit_op = circuit_op.permute(indices) - expected_circuit_op = expected_pauli_op.to_circuit_op() - - self.assertEqual( - Operator(permuted_circuit_op.primitive), Operator(expected_circuit_op.primitive) - ) - - # MatrixOp - matrix_op = pauli_op.to_matrix_op() - permuted_matrix_op = matrix_op.permute(indices) - expected_matrix_op = expected_pauli_op.to_matrix_op() - - equal = np.allclose(permuted_matrix_op.to_matrix(), expected_matrix_op.to_matrix()) - self.assertTrue(equal) - - def test_permute_on_list_op(self): - """Test if ListOp permute method is consistent with PrimitiveOps permute methods.""" - - op1 = (X ^ Y ^ Z).to_circuit_op() - op2 = Z ^ X ^ Y - - # ComposedOp - indices = [1, 2, 0] - primitive_op = op1 @ op2 - primitive_op_perm = primitive_op.permute(indices) # CircuitOp.permute - - composed_op = ComposedOp([op1, op2]) - composed_op_perm = composed_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - to_primitive = composed_op_perm.oplist[0] @ composed_op_perm.oplist[1] - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - # TensoredOp - indices = [3, 5, 4, 0, 2, 1] - primitive_op = op1 ^ op2 - primitive_op_perm = primitive_op.permute(indices) - - tensored_op = TensoredOp([op1, op2]) - tensored_op_perm = tensored_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - composed_oplist = tensored_op_perm.oplist - to_primitive = ( - composed_oplist[0] - @ (composed_oplist[1].oplist[0] ^ composed_oplist[1].oplist[1]) - @ composed_oplist[2] - ) - - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - # SummedOp - primitive_op = X ^ Y ^ Z - summed_op = SummedOp([primitive_op]) - - indices = [1, 2, 0] - primitive_op_perm = primitive_op.permute(indices) # PauliOp.permute - summed_op_perm = summed_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - to_primitive = summed_op_perm.oplist[0] @ primitive_op @ summed_op_perm.oplist[2] - - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - def test_expand_on_list_op(self): - """Test if expanded ListOp has expected num_qubits.""" - add_qubits = 3 - - # ComposedOp - composed_op = ComposedOp([(X ^ Y ^ Z), (H ^ T), (Z ^ X ^ Y ^ Z).to_matrix_op()]) - expanded = composed_op._expand_dim(add_qubits) - self.assertEqual(composed_op.num_qubits + add_qubits, expanded.num_qubits) - - # TensoredOp - tensored_op = TensoredOp([(X ^ Y), (Z ^ I)]) - expanded = tensored_op._expand_dim(add_qubits) - self.assertEqual(tensored_op.num_qubits + add_qubits, expanded.num_qubits) - - # SummedOp - summed_op = SummedOp([(X ^ Y), (Z ^ I ^ Z)]) - expanded = summed_op._expand_dim(add_qubits) - self.assertEqual(summed_op.num_qubits + add_qubits, expanded.num_qubits) - - def test_expand_on_state_fn(self): - """Test if expanded StateFn has expected num_qubits.""" - num_qubits = 3 - add_qubits = 2 - - # case CircuitStateFn, with primitive QuantumCircuit - qc2 = QuantumCircuit(num_qubits) - qc2.cx(0, 1) - - cfn = CircuitStateFn(qc2, is_measurement=True) - - cfn_exp = cfn._expand_dim(add_qubits) - self.assertEqual(cfn_exp.num_qubits, add_qubits + num_qubits) - - # case OperatorStateFn, with OperatorBase primitive, in our case CircuitStateFn - osfn = OperatorStateFn(cfn) - osfn_exp = osfn._expand_dim(add_qubits) - - self.assertEqual(osfn_exp.num_qubits, add_qubits + num_qubits) - - # case DictStateFn - dsfn = DictStateFn("1" * num_qubits, is_measurement=True) - self.assertEqual(dsfn.num_qubits, num_qubits) - - dsfn_exp = dsfn._expand_dim(add_qubits) - self.assertEqual(dsfn_exp.num_qubits, num_qubits + add_qubits) - - # case VectorStateFn - vsfn = VectorStateFn(np.ones(2**num_qubits, dtype=complex)) - self.assertEqual(vsfn.num_qubits, num_qubits) - - vsfn_exp = vsfn._expand_dim(add_qubits) - self.assertEqual(vsfn_exp.num_qubits, num_qubits + add_qubits) - - def test_permute_on_state_fn(self): - """Test if StateFns permute are consistent.""" - - num_qubits = 4 - dim = 2**num_qubits - primitive_list = [1.0 / (i + 1) for i in range(dim)] - primitive_dict = {format(i, "b").zfill(num_qubits): 1.0 / (i + 1) for i in range(dim)} - - dict_fn = DictStateFn(primitive=primitive_dict, is_measurement=True) - vec_fn = VectorStateFn(primitive=primitive_list, is_measurement=True) - - # check if dict_fn and vec_fn are equivalent - equivalent = np.allclose(dict_fn.to_matrix(), vec_fn.to_matrix()) - self.assertTrue(equivalent) - - # permute - indices = [2, 3, 0, 1] - permute_dict = dict_fn.permute(indices) - permute_vect = vec_fn.permute(indices) - - equivalent = np.allclose(permute_dict.to_matrix(), permute_vect.to_matrix()) - self.assertTrue(equivalent) - - def test_compose_consistency(self): - """Test if PrimitiveOp @ ComposedOp is consistent with ComposedOp @ PrimitiveOp.""" - - # PauliOp - op1 = X ^ Y ^ Z - op2 = X ^ Y ^ Z - op3 = (X ^ Y ^ Z).to_circuit_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - # CircitOp - op1 = op1.to_circuit_op() - op2 = op2.to_circuit_op() - op3 = op3.to_matrix_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - # MatrixOp - op1 = op1.to_matrix_op() - op2 = op2.to_matrix_op() - op3 = op3.to_pauli_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - def test_compose_with_indices(self): - """Test compose method using its permutation feature.""" - - pauli_op = X ^ Y ^ Z - circuit_op = T ^ H - matrix_op = (X ^ Y ^ H ^ T).to_matrix_op() - evolved_op = EvolvedOp(matrix_op) - - # composition of PrimitiveOps - num_qubits = 4 - primitive_op = pauli_op @ circuit_op @ matrix_op - composed_op = pauli_op @ circuit_op @ evolved_op - self.assertEqual(primitive_op.num_qubits, num_qubits) - self.assertEqual(composed_op.num_qubits, num_qubits) - - # with permutation - num_qubits = 5 - indices = [1, 4] - permuted_primitive_op = evolved_op @ circuit_op.permute(indices) @ pauli_op @ matrix_op - composed_primitive_op = ( - evolved_op @ pauli_op.compose(circuit_op, permutation=indices, front=True) @ matrix_op - ) - - self.assertTrue( - np.allclose(permuted_primitive_op.to_matrix(), composed_primitive_op.to_matrix()) - ) - self.assertEqual(num_qubits, permuted_primitive_op.num_qubits) - - # ListOp - num_qubits = 6 - tensored_op = TensoredOp([pauli_op, circuit_op]) - summed_op = pauli_op + circuit_op.permute([2, 1]) - composed_op = circuit_op @ evolved_op @ matrix_op - - list_op = summed_op @ composed_op.compose( - tensored_op, permutation=[1, 2, 3, 5, 4], front=True - ) - self.assertEqual(num_qubits, list_op.num_qubits) - - num_qubits = 4 - circuit_fn = CircuitStateFn(primitive=circuit_op.primitive, is_measurement=True) - operator_fn = OperatorStateFn(primitive=circuit_op ^ circuit_op, is_measurement=True) - - no_perm_op = circuit_fn @ operator_fn - self.assertEqual(no_perm_op.num_qubits, num_qubits) - - indices = [0, 4] - perm_op = operator_fn.compose(circuit_fn, permutation=indices, front=True) - self.assertEqual(perm_op.num_qubits, max(indices) + 1) - - # StateFn - num_qubits = 3 - dim = 2**num_qubits - vec = [1.0 / (i + 1) for i in range(dim)] - dic = {format(i, "b").zfill(num_qubits): 1.0 / (i + 1) for i in range(dim)} - - is_measurement = True - op_state_fn = OperatorStateFn(matrix_op, is_measurement=is_measurement) # num_qubit = 4 - vec_state_fn = VectorStateFn(vec, is_measurement=is_measurement) # 3 - dic_state_fn = DictStateFn(dic, is_measurement=is_measurement) # 3 - circ_state_fn = CircuitStateFn(circuit_op.to_circuit(), is_measurement=is_measurement) # 2 - - composed_op = op_state_fn @ vec_state_fn @ dic_state_fn @ circ_state_fn - self.assertEqual(composed_op.num_qubits, op_state_fn.num_qubits) - - # with permutation - perm = [2, 4, 6] - composed = ( - op_state_fn - @ dic_state_fn.compose(vec_state_fn, permutation=perm, front=True) - @ circ_state_fn - ) - self.assertEqual(composed.num_qubits, max(perm) + 1) - - def test_summed_op_equals(self): - """Test corner cases of SummedOp's equals function.""" - with self.subTest("multiplicative factor"): - self.assertEqual(2 * X, X + X) - - with self.subTest("commutative"): - self.assertEqual(X + Z, Z + X) - - with self.subTest("circuit and paulis"): - z = CircuitOp(ZGate()) - self.assertEqual(Z + z, z + Z) - - with self.subTest("matrix op and paulis"): - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(Z + z, z + Z) - - with self.subTest("matrix multiplicative"): - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(2 * z, z + z) - - with self.subTest("parameter coefficients"): - expr = Parameter("theta") - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(expr * z, expr * z) - - with self.subTest("different coefficient types"): - expr = Parameter("theta") - z = MatrixOp([[1, 0], [0, -1]]) - self.assertNotEqual(expr * z, 2 * z) - - with self.subTest("additions aggregation"): - z = MatrixOp([[1, 0], [0, -1]]) - a = z + z + Z - b = 2 * z + Z - c = z + Z + z - self.assertEqual(a, b) - self.assertEqual(b, c) - self.assertEqual(a, c) - - def test_circuit_compose_register_independent(self): - """Test that CircuitOp uses combines circuits independent of the register. - - I.e. that is uses ``QuantumCircuit.compose`` over ``combine`` or ``extend``. - """ - op = Z ^ 2 - qr = QuantumRegister(2, "my_qr") - circuit = QuantumCircuit(qr) - composed = op.compose(CircuitOp(circuit)) - - self.assertEqual(composed.num_qubits, 2) - - def test_matrix_op_conversions(self): - """Test to reveal QiskitError when to_instruction or to_circuit method is called on - parameterized matrix op.""" - m = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [1, 0, 0, 0], [0, -1, 0, 0]]) - matrix_op = MatrixOp(m, Parameter("beta")) - for method in ["to_instruction", "to_circuit"]: - with self.subTest(method): - # QiskitError: multiplication of Operator with ParameterExpression isn't implemented - self.assertRaises(QiskitError, getattr(matrix_op, method)) - - def test_list_op_to_circuit(self): - """Test if unitary ListOps transpile to circuit.""" - - # generate unitary matrices of dimension 2,4,8, seed is fixed - np.random.seed(233423) - u2 = unitary_group.rvs(2) - u4 = unitary_group.rvs(4) - u8 = unitary_group.rvs(8) - - # pauli matrices as numpy.arrays - x = np.array([[0.0, 1.0], [1.0, 0.0]]) - y = np.array([[0.0, -1.0j], [1.0j, 0.0]]) - z = np.array([[1.0, 0.0], [0.0, -1.0]]) - - # create MatrixOp and CircuitOp out of matrices - op2 = MatrixOp(u2) - op4 = MatrixOp(u4) - op8 = MatrixOp(u8) - c2 = op2.to_circuit_op() - - # algorithm using only matrix operations on numpy.arrays - xu4 = np.kron(x, u4) - zc2 = np.kron(z, u2) - zc2y = np.kron(zc2, y) - matrix = np.matmul(xu4, zc2y) - matrix = np.matmul(matrix, u8) - matrix = np.kron(matrix, u2) - operator = Operator(matrix) - - # same algorithm as above, but using PrimitiveOps - list_op = ((X ^ op4) @ (Z ^ c2 ^ Y) @ op8) ^ op2 - circuit = list_op.to_circuit() - - # verify that ListOp.to_circuit() outputs correct quantum circuit - self.assertTrue(operator.equiv(circuit), "ListOp.to_circuit() outputs wrong circuit!") - - def test_composed_op_to_circuit(self): - """ - Test if unitary ComposedOp transpile to circuit and represents expected operator. - Test if to_circuit on non-unitary ListOp raises exception. - """ - - x = np.array([[0.0, 1.0], [1.0, 0.0]]) # Pauli X as numpy array - y = np.array([[0.0, -1.0j], [1.0j, 0.0]]) # Pauli Y as numpy array - - m1 = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [0, 0, 0, 0], [0, 0, 0, 0]]) # non-unitary - m2 = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0], [0, -1, 0, 0]]) # non-unitary - - m_op1 = MatrixOp(m1) - m_op2 = MatrixOp(m2) - - pm1 = (X ^ Y) ^ m_op1 # non-unitary TensoredOp - pm2 = (X ^ Y) ^ m_op2 # non-unitary TensoredOp - - self.assertRaises(ValueError, pm1.to_circuit) - self.assertRaises(ValueError, pm2.to_circuit) - - summed_op = pm1 + pm2 # unitary SummedOp([TensoredOp, TensoredOp]) - circuit = summed_op.to_circuit() # should transpile without any exception - - # same algorithm that leads to summed_op above, but using only arrays and matrix operations - unitary = np.kron(np.kron(x, y), m1 + m2) - - self.assertTrue(Operator(unitary).equiv(circuit)) - - def test_pauli_op_to_circuit(self): - """Test PauliOp.to_circuit()""" - with self.subTest("single Pauli"): - pauli = PauliOp(Pauli("Y")) - expected = QuantumCircuit(1) - expected.y(0) - self.assertEqual(pauli.to_circuit(), expected) - - with self.subTest("single Pauli with phase"): - pauli = PauliOp(Pauli("-iX")) - expected = QuantumCircuit(1) - expected.x(0) - expected.global_phase = -pi / 2 - self.assertEqual(Operator(pauli.to_circuit()), Operator(expected)) - - with self.subTest("two qubit"): - pauli = PauliOp(Pauli("IX")) - expected = QuantumCircuit(2) - expected.pauli("IX", range(2)) - self.assertEqual(pauli.to_circuit(), expected) - expected = QuantumCircuit(2) - expected.x(0) - self.assertEqual(pauli.to_circuit().decompose(), expected) - - with self.subTest("Pauli identity"): - pauli = PauliOp(Pauli("I")) - expected = QuantumCircuit(1) - self.assertEqual(pauli.to_circuit(), expected) - - with self.subTest("two qubit with phase"): - pauli = PauliOp(Pauli("iXZ")) - expected = QuantumCircuit(2) - expected.pauli("XZ", range(2)) - expected.global_phase = pi / 2 - self.assertEqual(pauli.to_circuit(), expected) - expected = QuantumCircuit(2) - expected.z(0) - expected.x(1) - expected.global_phase = pi / 2 - self.assertEqual(pauli.to_circuit().decompose(), expected) - - def test_op_to_circuit_with_parameters(self): - """On parameterized SummedOp, to_matrix_op returns ListOp, instead of MatrixOp. To avoid - the infinite recursion, OpflowError is raised.""" - m1 = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [0, 0, 0, 0], [0, 0, 0, 0]]) # non-unitary - m2 = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0], [0, -1, 0, 0]]) # non-unitary - - op1_with_param = MatrixOp(m1, Parameter("alpha")) # non-unitary - op2_with_param = MatrixOp(m2, Parameter("beta")) # non-unitary - - summed_op_with_param = op1_with_param + op2_with_param # unitary - # should raise OpflowError error - self.assertRaises(OpflowError, summed_op_with_param.to_circuit) - - def test_permute_list_op_with_inconsistent_num_qubits(self): - """Test if permute raises error if ListOp contains operators with different num_qubits.""" - list_op = ListOp([X, X ^ X]) - self.assertRaises(OpflowError, list_op.permute, [0, 1]) - - @data(Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, -1]])) - def test_op_indent(self, op): - """Test that indentation correctly adds INDENTATION at the beginning of each line""" - initial_str = str(op) - indented_str = op._indent(initial_str) - starts_with_indent = indented_str.startswith(op.INDENTATION) - self.assertTrue(starts_with_indent) - indented_str_content = (indented_str[len(op.INDENTATION) :]).split(f"\n{op.INDENTATION}") - self.assertListEqual(indented_str_content, initial_str.split("\n")) - - def test_composed_op_immutable_under_eval(self): - """Test ``ComposedOp.eval`` does not change the operator instance.""" - op = 2 * ComposedOp([X]) - _ = op.eval() - # previous bug: after op.eval(), op was 2 * ComposedOp([2 * X]) - self.assertEqual(op, 2 * ComposedOp([X])) - - def test_op_parameters(self): - """Test that Parameters are stored correctly""" - phi = Parameter("φ") - theta = ParameterVector(name="θ", length=2) - - qc = QuantumCircuit(2) - qc.rz(phi, 0) - qc.rz(phi, 1) - for i in range(2): - qc.rx(theta[i], i) - qc.h(0) - qc.x(1) - - l = Parameter("λ") - op = PrimitiveOp(qc, coeff=l) - - params = {phi, l, *theta.params} - - self.assertEqual(params, op.parameters) - self.assertEqual(params, StateFn(op).parameters) - self.assertEqual(params, StateFn(qc, coeff=l).parameters) - - def test_list_op_parameters(self): - """Test that Parameters are stored correctly in a List Operator""" - lam = Parameter("λ") - phi = Parameter("φ") - omega = Parameter("ω") - - mat_op = PrimitiveOp([[0, 1], [1, 0]], coeff=omega) - - qc = QuantumCircuit(1) - qc.rx(phi, 0) - qc_op = PrimitiveOp(qc) - - op1 = SummedOp([mat_op, qc_op]) - - params = [phi, omega] - self.assertEqual(op1.parameters, set(params)) - - # check list nesting case - op2 = PrimitiveOp([[1, 0], [0, -1]], coeff=lam) - - list_op = ListOp([op1, op2]) - - params.append(lam) - self.assertEqual(list_op.parameters, set(params)) - - @data( - VectorStateFn([1, 0]), - CircuitStateFn(QuantumCircuit(1)), - OperatorStateFn(I), - OperatorStateFn(MatrixOp([[1, 0], [0, 1]])), - OperatorStateFn(CircuitOp(QuantumCircuit(1))), - ) - def test_statefn_eval(self, op): - """Test calling eval on StateFn returns the statevector.""" - expected = Statevector([1, 0]) - self.assertEqual(op.eval().primitive, expected) - - def test_sparse_eval(self): - """Test calling eval on a DictStateFn returns a sparse statevector.""" - op = DictStateFn({"0": 1}) - expected = scipy.sparse.csr_matrix([[1, 0]]) - self.assertFalse((op.eval().primitive != expected).toarray().any()) - - def test_sparse_to_dict(self): - """Test converting a sparse vector state function to a dict state function.""" - isqrt2 = 1 / np.sqrt(2) - sparse = scipy.sparse.csr_matrix([[0, isqrt2, 0, isqrt2]]) - sparse_fn = SparseVectorStateFn(sparse) - dict_fn = DictStateFn({"01": isqrt2, "11": isqrt2}) - - with self.subTest("sparse to dict"): - self.assertEqual(dict_fn, sparse_fn.to_dict_fn()) - - with self.subTest("dict to sparse"): - self.assertEqual(dict_fn.to_spmatrix_op(), sparse_fn) - - def test_to_circuit_op(self): - """Test to_circuit_op method.""" - vector = np.array([2, 2]) - vsfn = VectorStateFn([1, 1], coeff=2) - dsfn = DictStateFn({"0": 1, "1": 1}, coeff=2) - - for sfn in [vsfn, dsfn]: - np.testing.assert_array_almost_equal(sfn.to_circuit_op().eval().primitive.data, vector) - - def test_invalid_primitive(self): - """Test invalid MatrixOp construction""" - msg = ( - "MatrixOp can only be instantiated with " - "['list', 'ndarray', 'spmatrix', 'Operator'], not " - ) - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp("invalid") - - self.assertEqual(str(cm.exception), msg + "'str'") - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp(None) - - self.assertEqual(str(cm.exception), msg + "'NoneType'") - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp(2.0) - - self.assertEqual(str(cm.exception), msg + "'float'") - - def test_summedop_equals(self): - """Test SummedOp.equals""" - ops = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, -1]]), Zero, Minus] - sum_op = sum(ops + [ListOp(ops)]) - self.assertEqual(sum_op, sum_op) - self.assertEqual(sum_op + sum_op, 2 * sum_op) - self.assertEqual(sum_op + sum_op + sum_op, 3 * sum_op) - ops2 = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, 1]]), Zero, Minus] - sum_op2 = sum(ops2 + [ListOp(ops)]) - self.assertNotEqual(sum_op, sum_op2) - self.assertEqual(sum_op2, sum_op2) - sum_op3 = sum(ops) - self.assertNotEqual(sum_op, sum_op3) - self.assertNotEqual(sum_op2, sum_op3) - self.assertEqual(sum_op3, sum_op3) - - def test_empty_listops(self): - """Test reduce and eval on ListOp with empty oplist.""" - with self.subTest("reduce empty ComposedOp "): - self.assertEqual(ComposedOp([]).reduce(), ComposedOp([])) - with self.subTest("reduce empty TensoredOp "): - self.assertEqual(TensoredOp([]).reduce(), TensoredOp([])) - with self.subTest("eval empty ComposedOp "): - self.assertEqual(ComposedOp([]).eval(), 0.0) - with self.subTest("eval empty TensoredOp "): - self.assertEqual(TensoredOp([]).eval(), 0.0) - - def test_composed_op_to_matrix_with_coeff(self): - """Test coefficients are properly handled. - - Regression test of Qiskit/qiskit-terra#9283. - """ - x = MatrixOp(X.to_matrix()) - composed = 0.5 * (x @ X) - - expected = 0.5 * np.eye(2) - - np.testing.assert_almost_equal(composed.to_matrix(), expected) - - def test_composed_op_to_matrix_with_vector(self): - """Test a matrix-vector composed op can be cast to matrix. - - Regression test of Qiskit/qiskit-terra#9283. - """ - x = MatrixOp(X.to_matrix()) - composed = x @ Zero - - expected = np.array([0, 1]) - - np.testing.assert_almost_equal(composed.to_matrix(), expected) - - def test_tensored_op_to_matrix(self): - """Test tensored operators to matrix works correctly with a global coefficient. - - Regression test of Qiskit/qiskit-terra#9398. - """ - op = TensoredOp([X, I], coeff=0.5) - expected = 1 / 2 * np.kron(X.to_matrix(), I.to_matrix()) - np.testing.assert_almost_equal(op.to_matrix(), expected) - - -class TestOpMethods(QiskitOpflowTestCase): - """Basic method tests.""" - - def test_listop_num_qubits(self): - """Test that ListOp.num_qubits checks that all operators have the same number of qubits.""" - op = ListOp([X ^ Y, Y ^ Z]) - with self.subTest("All operators have the same numbers of qubits"): - self.assertEqual(op.num_qubits, 2) - - op = ListOp([X ^ Y, Y]) - with self.subTest("Operators have different numbers of qubits"): - with self.assertRaises(ValueError): - op.num_qubits # pylint: disable=pointless-statement - - with self.assertRaises(ValueError): - X @ op # pylint: disable=pointless-statement - - def test_is_hermitian(self): - """Test is_hermitian method.""" - with self.subTest("I"): - self.assertTrue(I.is_hermitian()) - - with self.subTest("X"): - self.assertTrue(X.is_hermitian()) - - with self.subTest("Y"): - self.assertTrue(Y.is_hermitian()) - - with self.subTest("Z"): - self.assertTrue(Z.is_hermitian()) - - with self.subTest("XY"): - self.assertFalse((X @ Y).is_hermitian()) - - with self.subTest("CX"): - self.assertTrue(CX.is_hermitian()) - - with self.subTest("T"): - self.assertFalse(T.is_hermitian()) - - -@ddt -class TestListOpMethods(QiskitOpflowTestCase): - """Test ListOp accessing methods""" - - @data(ListOp, SummedOp, ComposedOp, TensoredOp) - def test_indexing(self, list_op_type): - """Test indexing and slicing""" - coeff = 3 + 0.2j - states_op = list_op_type([X, Y, Z, I], coeff=coeff) - - single_op = states_op[1] - self.assertIsInstance(single_op, OperatorBase) - self.assertNotIsInstance(single_op, ListOp) - - list_one_element = states_op[1:2] - self.assertIsInstance(list_one_element, list_op_type) - self.assertEqual(len(list_one_element), 1) - self.assertEqual(list_one_element[0], Y) - - list_two_elements = states_op[::2] - self.assertIsInstance(list_two_elements, list_op_type) - self.assertEqual(len(list_two_elements), 2) - self.assertEqual(list_two_elements[0], X) - self.assertEqual(list_two_elements[1], Z) - - self.assertEqual(list_one_element.coeff, coeff) - self.assertEqual(list_two_elements.coeff, coeff) - - -class TestListOpComboFn(QiskitOpflowTestCase): - """Test combo fn is propagated.""" - - def setUp(self): - super().setUp() - self.combo_fn = lambda x: [x_i**2 for x_i in x] - self.listop = ListOp([X], combo_fn=self.combo_fn) - - def assertComboFnPreserved(self, processed_op): - """Assert the quadratic combo_fn is preserved.""" - x = [1, 2, 3] - self.assertListEqual(processed_op.combo_fn(x), self.combo_fn(x)) - - def test_at_conversion(self): - """Test after conversion the combo_fn is preserved.""" - for method in ["to_matrix_op", "to_pauli_op", "to_circuit_op"]: - with self.subTest(method): - converted = getattr(self.listop, method)() - self.assertComboFnPreserved(converted) - - def test_after_mul(self): - """Test after multiplication the combo_fn is preserved.""" - self.assertComboFnPreserved(2 * self.listop) - - def test_at_traverse(self): - """Test after traversing the combo_fn is preserved.""" - - def traverse_fn(op): - return -op - - traversed = self.listop.traverse(traverse_fn) - self.assertComboFnPreserved(traversed) - - def test_after_adjoint(self): - """Test after traversing the combo_fn is preserved.""" - self.assertComboFnPreserved(self.listop.adjoint()) - - def test_after_reduce(self): - """Test after reducing the combo_fn is preserved.""" - self.assertComboFnPreserved(self.listop.reduce()) - - -def pauli_group_labels(nq, full_group=True): - """Generate list of the N-qubit pauli group string labels""" - labels = ["".join(i) for i in itertools.product(("I", "X", "Y", "Z"), repeat=nq)] - if full_group: - labels = ["".join(i) for i in itertools.product(("", "-i", "-", "i"), labels)] - return labels - - -def operator_from_label(label): - """Construct operator from full Pauli group label""" - return Operator(Pauli(label)) - - -@ddt -class TestPauliOp(QiskitOpflowTestCase): - """PauliOp tests.""" - - def test_construct(self): - """constructor test""" - pauli = Pauli("XYZX") - coeff = 3.0 - pauli_op = PauliOp(pauli, coeff) - self.assertIsInstance(pauli_op, PauliOp) - self.assertEqual(pauli_op.primitive, pauli) - self.assertEqual(pauli_op.coeff, coeff) - self.assertEqual(pauli_op.num_qubits, 4) - - def test_add(self): - """add test""" - pauli_sum = X + Y - summed_op = SummedOp([X, Y]) - self.assertEqual(pauli_sum, summed_op) - - a = Parameter("a") - b = Parameter("b") - actual = PauliOp(Pauli("X"), a) + PauliOp(Pauli("Y"), b) - expected = SummedOp([PauliOp(Pauli("X"), a), PauliOp(Pauli("Y"), b)]) - self.assertEqual(actual, expected) - - def test_adjoint(self): - """adjoint test""" - pauli_op = PauliOp(Pauli("XYZX"), coeff=3) - expected = PauliOp(Pauli("XYZX"), coeff=3) - - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("XXY"), coeff=2j) - expected = PauliOp(Pauli("XXY"), coeff=-2j) - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("XYZX"), coeff=2 + 3j) - expected = PauliOp(Pauli("XYZX"), coeff=2 - 3j) - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("iXYZX"), coeff=2 + 3j) - expected = PauliOp(Pauli("-iXYZX"), coeff=2 - 3j) - self.assertEqual(~pauli_op, expected) - - @data(*itertools.product(pauli_group_labels(2, full_group=True), repeat=2)) - @unpack - def test_compose(self, label1, label2): - """compose test""" - p1 = PauliOp(Pauli(label1)) - p2 = PauliOp(Pauli(label2)) - value = Operator(p1 @ p2) - op1 = operator_from_label(label1) - op2 = operator_from_label(label2) - target = op1 @ op2 - self.assertEqual(value, target) - - def test_equals(self): - """equality test""" - - self.assertEqual(I @ X, X) - self.assertEqual(X, I @ X) - - theta = Parameter("theta") - pauli_op = theta * X ^ Z - expected = PauliOp( - Pauli("XZ"), - coeff=1.0 * theta, - ) - self.assertEqual(pauli_op, expected) - - def test_eval(self): - """eval test""" - target0 = (X ^ Y ^ Z).eval("000") - target1 = (X ^ Y ^ Z).eval(Zero ^ 3) - expected = DictStateFn({"110": 1j}) - self.assertEqual(target0, expected) - self.assertEqual(target1, expected) - - def test_exp_i(self): - """exp_i test""" - target = (2 * X ^ Z).exp_i() - expected = EvolvedOp(PauliOp(Pauli("XZ"), coeff=2.0), coeff=1.0) - self.assertEqual(target, expected) - - @data(([1, 2, 4], "XIYZI"), ([2, 1, 0], "ZYX")) - @unpack - def test_permute(self, permutation, expected_pauli): - """Test the permute method.""" - pauli_op = PauliOp(Pauli("XYZ"), coeff=1.0) - expected = PauliOp(Pauli(expected_pauli), coeff=1.0) - permuted = pauli_op.permute(permutation) - - with self.subTest(msg="test permutated object"): - self.assertEqual(permuted, expected) - - with self.subTest(msg="test original object is unchanged"): - original = PauliOp(Pauli("XYZ")) - self.assertEqual(pauli_op, original) - - def test_primitive_strings(self): - """primitive strings test""" - target = (2 * X ^ Z).primitive_strings() - expected = {"Pauli"} - self.assertEqual(target, expected) - - def test_tensor(self): - """tensor test""" - pauli_op = X ^ Y ^ Z - tensored_op = PauliOp(Pauli("XYZ")) - self.assertEqual(pauli_op, tensored_op) - - def test_to_instruction(self): - """to_instruction test""" - target = (X ^ Z).to_instruction() - qc = QuantumCircuit(2) - qc.u(0, 0, np.pi, 0) - qc.u(np.pi, 0, np.pi, 1) - qc_out = QuantumCircuit(2) - qc_out.append(target, qc_out.qubits) - qc_out = transpile(qc_out, basis_gates=["u"]) - self.assertEqual(qc, qc_out) - - def test_to_matrix(self): - """to_matrix test""" - target = (X ^ Y).to_matrix() - expected = np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, -1j], [1j, 0.0]])) - np.testing.assert_array_equal(target, expected) - - def test_to_spmatrix(self): - """to_spmatrix test""" - target = X ^ Y - expected = csr_matrix( - np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, -1j], [1j, 0.0]])) - ) - self.assertEqual((target.to_spmatrix() - expected).nnz, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_basis_change.py b/test/python/opflow/test_pauli_basis_change.py deleted file mode 100644 index 5b9f9f324c97..000000000000 --- a/test/python/opflow/test_pauli_basis_change.py +++ /dev/null @@ -1,156 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Pauli Change of Basis Converter""" - -import itertools -import unittest -from functools import reduce -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import ( - ComposedOp, - I, - OperatorStateFn, - PauliSumOp, - SummedOp, - X, - Y, - Z, -) -from qiskit.opflow.converters import PauliBasisChange -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestPauliCoB(QiskitOpflowTestCase): - """Pauli Change of Basis Converter tests.""" - - def test_pauli_cob_singles(self): - """from to file test""" - singles = [X, Y, Z] - dests = [None, Y] - for pauli, dest in itertools.product(singles, dests): - # print(pauli) - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_two_qubit(self): - """pauli cob two qubit test""" - multis = [Y ^ X, Z ^ Y, I ^ Z, Z ^ I, X ^ X, I ^ X] - for pauli, dest in itertools.product(multis, reversed(multis)): - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_multiqubit(self): - """pauli cob multi qubit test""" - # Helpful prints for debugging commented out below. - multis = [Y ^ X ^ I ^ I, I ^ Z ^ Y ^ X, X ^ Y ^ I ^ Z, I ^ I ^ I ^ X, X ^ X ^ X ^ X] - for pauli, dest in itertools.product(multis, reversed(multis)): - # print(pauli) - # print(dest) - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - # print(inst) - # print(pauli.to_matrix()) - # print(np.round(inst.adjoint().to_matrix() @ cob.to_matrix())) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_traverse(self): - """pauli cob traverse test""" - # Helpful prints for debugging commented out below. - multis = [(X ^ Y) + (I ^ Z) + (Z ^ Z), (Y ^ X ^ I ^ I) + (I ^ Z ^ Y ^ X)] - dests = [Y ^ Y, I ^ I ^ I ^ Z] - for paulis, dest in zip(multis, dests): - converter = PauliBasisChange(destination_basis=dest, traverse=True) - - cob = converter.convert(paulis) - self.assertIsInstance(cob, SummedOp) - inst = [None] * len(paulis) - ret_dest = [None] * len(paulis) - cob_mat = [None] * len(paulis) - for i, pauli in enumerate(paulis): - inst[i], ret_dest[i] = converter.get_cob_circuit(pauli.to_pauli_op().primitive) - self.assertEqual(dest, ret_dest[i]) - - self.assertIsInstance(cob.oplist[i], ComposedOp) - cob_mat[i] = cob.oplist[i].to_matrix() - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob_mat[i]) - np.testing.assert_array_almost_equal(paulis.to_matrix(), sum(cob_mat)) - - def test_grouped_pauli(self): - """grouped pauli test""" - pauli = 2 * (I ^ I) + (X ^ I) + 3 * (X ^ Y) - grouped_pauli = PauliSumOp(pauli.primitive, grouping_type="TPB") - - converter = PauliBasisChange() - cob = converter.convert(grouped_pauli) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - - origin_x = reduce(np.logical_or, pauli.primitive.paulis.x) - origin_z = reduce(np.logical_or, pauli.primitive.paulis.z) - origin_pauli = Pauli((origin_z, origin_x)) - inst, dest = converter.get_cob_circuit(origin_pauli) - self.assertEqual(str(dest), "ZZ") - expected_inst = np.array( - [ - [0.5, -0.5j, 0.5, -0.5j], - [0.5, 0.5j, 0.5, 0.5j], - [0.5, -0.5j, -0.5, 0.5j], - [0.5, 0.5j, -0.5, -0.5j], - ] - ) - np.testing.assert_array_almost_equal(inst.to_matrix(), expected_inst) - - def test_grouped_pauli_statefn(self): - """grouped pauli test with statefn""" - grouped_pauli = PauliSumOp(SparsePauliOp(["Y"]), grouping_type="TPB") - observable = OperatorStateFn(grouped_pauli, is_measurement=True) - - converter = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - cob = converter.convert(observable) - - expected = PauliSumOp(SparsePauliOp(["Z"]), grouping_type="TPB") - self.assertEqual(cob[0].primitive, expected) - circuit = QuantumCircuit(1) - circuit.sdg(0) - circuit.h(0) - self.assertEqual(cob[1].primitive, circuit) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_expectation.py b/test/python/opflow/test_pauli_expectation.py deleted file mode 100644 index 19821aab56da..000000000000 --- a/test/python/opflow/test_pauli_expectation.py +++ /dev/null @@ -1,317 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test PauliExpectation""" - -import itertools -import unittest -import warnings -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np - -from qiskit import BasicAer -from qiskit.opflow import ( - CX, - CircuitSampler, - H, - I, - ListOp, - Minus, - One, - PauliExpectation, - PauliSumOp, - Plus, - S, - StateFn, - X, - Y, - Z, - Zero, -) -from qiskit.utils import QuantumInstance, algorithm_globals - - -# pylint: disable=invalid-name - - -class TestPauliExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - def setUp(self) -> None: - super().setUp() - self.seed = 97 - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - self.expect = PauliExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - - op = Z ^ Z - # wf = (Pl^Pl) + (Ze^Ze) - wf = CX @ (H ^ I) @ Zero - - converted_meas = self.expect.convert(~StateFn(op) @ wf) - self.assertAlmostEqual(converted_meas.eval(), 0, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - self.assertAlmostEqual(converted_meas.eval(), matmulmean, delta=0.1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - plus_mean = converted_meas @ Plus - np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) - - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) - - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) - - sampled_zero_mean = self.sampler.convert(sum_zero_mean) - - # !!NOTE!!: Depolarizing channel (Sampling) means interference - # does not happen between circuits in sum, so expectation does - # not equal expectation for Zero!! - np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 1], decimal=1) - - for i, op in enumerate(paulis_op.oplist): - mat_op = op.to_matrix() - np.testing.assert_array_almost_equal( - zero_mean.eval()[i], - Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - plus_mean.eval()[i], - Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - minus_mean.eval()[i], - Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), - decimal=1, - ) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - - states_op = ListOp([One, Zero, Plus, Minus]) - - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [0, 0, 1, -1], decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertIn("counts", composed_op[1].execution_results) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), valids, decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_to_matrix_called(self): - """test to matrix called in different situations""" - qs = 45 - - states_op = ListOp([Zero ^ qs, One ^ qs, (Zero ^ qs) + (One ^ qs)]) - paulis_op = ListOp([Z ^ qs, (I ^ Z ^ I) ^ int(qs / 3)]) - - # 45 qubit calculation - throws exception if to_matrix is called - # massive is False - with self.assertRaises(ValueError): - states_op.to_matrix() - paulis_op.to_matrix() - - # now set global variable or argument - try: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = True - with self.assertRaises(MemoryError): - states_op.to_matrix() - paulis_op.to_matrix() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = False - with self.assertRaises(MemoryError): - states_op.to_matrix(massive=True) - paulis_op.to_matrix(massive=True) - finally: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = False - - def test_not_to_matrix_called(self): - """45 qubit calculation - literally will not work if to_matrix is - somehow called (in addition to massive=False throwing an error)""" - - qs = 45 - states_op = ListOp([Zero ^ qs, One ^ qs, (Zero ^ qs) + (One ^ qs)]) - paulis_op = ListOp([Z ^ qs, (I ^ Z ^ I) ^ int(qs / 3)]) - - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [[1, -1, 0], [1, -1, 0]]) - - def test_grouped_pauli_expectation(self): - """grouped pauli expectation test""" - - two_qubit_H2 = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - wf = CX @ (H ^ I) @ Zero - expect_op = PauliExpectation(group_paulis=False).convert(~StateFn(two_qubit_H2) @ wf) - self.sampler._extract_circuitstatefns(expect_op) - num_circuits_ungrouped = len(self.sampler._circuit_ops_cache) - self.assertEqual(num_circuits_ungrouped, 5) - - expect_op_grouped = PauliExpectation(group_paulis=True).convert(~StateFn(two_qubit_H2) @ wf) - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - sampler = CircuitSampler(q_instance) - sampler._extract_circuitstatefns(expect_op_grouped) - num_circuits_grouped = len(sampler._circuit_ops_cache) - - self.assertEqual(num_circuits_grouped, 2) - - @unittest.skip(reason="IBMQ testing not available in general.") - def test_ibmq_grouped_pauli_expectation(self): - """pauli expect op vector state vector test""" - from qiskit import IBMQ - - p = IBMQ.load_account() - backend = p.get_backend("ibmq_qasm_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - - with self.assertWarns(DeprecationWarning): - sampled = CircuitSampler(q_instance).convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - plus_mean = converted_meas @ Plus - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_pauli_expectation_non_hermite_op(self): - """Test PauliExpectation for non hermitian operator""" - - exp = ~StateFn(1j * Z) @ One - self.assertEqual(self.expect.convert(exp).eval(), 1j) - - def test_list_pauli_sum_op(self): - """Test PauliExpectation for List[PauliSumOp]""" - - test_op = ListOp([~StateFn(PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)]))]) - observable = self.expect.convert(test_op) - self.assertIsInstance(observable, ListOp) - self.assertIsInstance(observable[0][0][0].primitive, PauliSumOp) - self.assertIsInstance(observable[0][1][0].primitive, PauliSumOp) - - def test_expectation_with_coeff(self): - """Test PauliExpectation with coefficients.""" - - with self.subTest("integer coefficients"): - exp = 3 * ~StateFn(X) @ (2 * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertEqual(target, -12) - - with self.subTest("complex coefficients"): - exp = 3j * ~StateFn(X) @ (2j * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertEqual(target, -12j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_sum_op.py b/test/python/opflow/test_pauli_sum_op.py deleted file mode 100644 index 7b97756ae3a6..000000000000 --- a/test/python/opflow/test_pauli_sum_op.py +++ /dev/null @@ -1,365 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test PauliSumOp.""" - -import unittest -from itertools import product -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np -from ddt import data, ddt, unpack -from scipy.sparse import csr_matrix -from sympy import Symbol - -from qiskit import QuantumCircuit, transpile -from qiskit.circuit import Parameter, ParameterExpression, ParameterVector -from qiskit.opflow import ( - CX, - CircuitStateFn, - DictStateFn, - H, - I, - One, - OperatorStateFn, - OpflowError, - PauliSumOp, - SummedOp, - X, - Y, - Z, - Zero, -) -from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp - - -@ddt -class TestPauliSumOp(QiskitOpflowTestCase): - """PauliSumOp tests.""" - - def test_construct(self): - """constructor test""" - sparse_pauli = SparsePauliOp(Pauli("XYZX"), coeffs=[2.0]) - coeff = 3.0 - pauli_sum = PauliSumOp(sparse_pauli, coeff=coeff) - self.assertIsInstance(pauli_sum, PauliSumOp) - self.assertEqual(pauli_sum.primitive, sparse_pauli) - self.assertEqual(pauli_sum.coeff, coeff) - self.assertEqual(pauli_sum.num_qubits, 4) - - def test_coeffs(self): - """ListOp.coeffs test""" - sum1 = SummedOp( - [(0 + 1j) * X, (1 / np.sqrt(2) + 1j / np.sqrt(2)) * Z], 0.5 - ).collapse_summands() - self.assertAlmostEqual(sum1.coeffs[0], 0.5j) - self.assertAlmostEqual(sum1.coeffs[1], (1 + 1j) / (2 * np.sqrt(2))) - - a_param = Parameter("a") - b_param = Parameter("b") - param_exp = ParameterExpression({a_param: 1, b_param: 0}, Symbol("a") ** 2 + Symbol("b")) - sum2 = SummedOp([X, (1 / np.sqrt(2) - 1j / np.sqrt(2)) * Y], param_exp).collapse_summands() - self.assertIsInstance(sum2.coeffs[0], ParameterExpression) - self.assertIsInstance(sum2.coeffs[1], ParameterExpression) - - # Nested ListOp - sum_nested = SummedOp([X, sum1]) - self.assertRaises(TypeError, lambda: sum_nested.coeffs) - - def test_add(self): - """add test""" - pauli_sum = 3 * X + Y - self.assertIsInstance(pauli_sum, PauliSumOp) - expected = PauliSumOp(3.0 * SparsePauliOp(Pauli("X")) + SparsePauliOp(Pauli("Y"))) - self.assertEqual(pauli_sum, expected) - - pauli_sum = X + Y - summed_op = SummedOp([X, Y]) - self.assertEqual(pauli_sum, summed_op) - - a = Parameter("a") - b = Parameter("b") - actual = a * PauliSumOp.from_list([("X", 2)]) + b * PauliSumOp.from_list([("Y", 1)]) - expected = SummedOp( - [PauliSumOp.from_list([("X", 2)], a), PauliSumOp.from_list([("Y", 1)], b)] - ) - self.assertEqual(actual, expected) - - def test_mul(self): - """multiplication test""" - target = 2 * (X + Z) - self.assertEqual(target.coeff, 1) - self.assertListEqual(target.primitive.to_list(), [("X", (2 + 0j)), ("Z", (2 + 0j))]) - - target = 0 * (X + Z) - self.assertEqual(target.coeff, 0) - self.assertListEqual(target.primitive.to_list(), [("X", (1 + 0j)), ("Z", (1 + 0j))]) - - beta = Parameter("β") - target = beta * (X + Z) - self.assertEqual(target.coeff, 1.0 * beta) - self.assertListEqual(target.primitive.to_list(), [("X", (1 + 0j)), ("Z", (1 + 0j))]) - - def test_adjoint(self): - """adjoint test""" - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("XYZX"), coeffs=[2]), coeff=3) - expected = PauliSumOp(SparsePauliOp(Pauli("XYZX")), coeff=6) - - self.assertEqual(pauli_sum.adjoint(), expected) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("XYZY"), coeffs=[2]), coeff=3j) - expected = PauliSumOp(SparsePauliOp(Pauli("XYZY")), coeff=-6j) - self.assertEqual(pauli_sum.adjoint(), expected) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("X"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("Y"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("Z"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = (Z ^ Z) + (Y ^ I) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - def test_equals(self): - """equality test""" - - self.assertNotEqual((X ^ X) + (Y ^ Y), X + Y) - self.assertEqual((X ^ X) + (Y ^ Y), (Y ^ Y) + (X ^ X)) - self.assertEqual(0 * X + I, I) - self.assertEqual(I, 0 * X + I) - - theta = ParameterVector("theta", 2) - pauli_sum0 = theta[0] * (X + Z) - pauli_sum1 = theta[1] * (X + Z) - expected = PauliSumOp( - SparsePauliOp(Pauli("X")) + SparsePauliOp(Pauli("Z")), - coeff=1.0 * theta[0], - ) - self.assertEqual(pauli_sum0, expected) - self.assertNotEqual(pauli_sum1, expected) - - def test_tensor(self): - """Test for tensor operation""" - with self.subTest("Test 1"): - pauli_sum = ((I - Z) ^ (I - Z)) + ((X - Y) ^ (X + Y)) - expected = (I ^ I) - (I ^ Z) - (Z ^ I) + (Z ^ Z) + (X ^ X) + (X ^ Y) - (Y ^ X) - (Y ^ Y) - self.assertEqual(pauli_sum, expected) - - with self.subTest("Test 2"): - pauli_sum = (Z + I) ^ Z - expected = (Z ^ Z) + (I ^ Z) - self.assertEqual(pauli_sum, expected) - - with self.subTest("Test 3"): - pauli_sum = Z ^ (Z + I) - expected = (Z ^ Z) + (Z ^ I) - self.assertEqual(pauli_sum, expected) - - @data(([1, 2, 4], "XIYZI"), ([2, 1, 0], "ZYX")) - @unpack - def test_permute(self, permutation, expected_pauli): - """Test the permute method.""" - pauli_sum = PauliSumOp(SparsePauliOp.from_list([("XYZ", 1)])) - expected = PauliSumOp(SparsePauliOp.from_list([(expected_pauli, 1)])) - permuted = pauli_sum.permute(permutation) - - with self.subTest(msg="test permutated object"): - self.assertEqual(permuted, expected) - - with self.subTest(msg="test original object is unchanged"): - original = PauliSumOp(SparsePauliOp.from_list([("XYZ", 1)])) - self.assertEqual(pauli_sum, original) - - @data([1, 2, 1], [1, 2, -1]) - def test_permute_invalid(self, permutation): - """Test the permute method raises an error on invalid permutations.""" - pauli_sum = PauliSumOp(SparsePauliOp((X ^ Y ^ Z).primitive)) - - with self.assertRaises(OpflowError): - pauli_sum.permute(permutation) - - def test_compose(self): - """compose test""" - target = (X + Z) @ (Y + Z) - expected = 1j * Z - 1j * Y - 1j * X + I - self.assertEqual(target, expected) - - observable = (X ^ X) + (Y ^ Y) + (Z ^ Z) - state = CircuitStateFn((CX @ (X ^ H @ X)).to_circuit()) - self.assertAlmostEqual((~OperatorStateFn(observable) @ state).eval(), -3) - - def test_to_matrix(self): - """test for to_matrix method""" - target = (Z + Y).to_matrix() - expected = np.array([[1.0, -1j], [1j, -1]]) - np.testing.assert_array_equal(target, expected) - - def test_str(self): - """str test""" - target = 3.0 * (X + 2.0 * Y - 4.0 * Z) - expected = "3.0 * X\n+ 6.0 * Y\n- 12.0 * Z" - self.assertEqual(str(target), expected) - - alpha = Parameter("α") - target = alpha * (X + 2.0 * Y - 4.0 * Z) - expected = "1.0*α * (\n 1.0 * X\n + 2.0 * Y\n - 4.0 * Z\n)" - self.assertEqual(str(target), expected) - - def test_eval(self): - """eval test""" - target0 = (2 * (X ^ Y ^ Z) + 3 * (X ^ X ^ Z)).eval("000") - target1 = (2 * (X ^ Y ^ Z) + 3 * (X ^ X ^ Z)).eval(Zero ^ 3) - expected = DictStateFn({"110": (3 + 2j)}) - self.assertEqual(target0, expected) - self.assertEqual(target1, expected) - - phi = 0.5 * ((One + Zero) ^ 2) - zero_op = (Z + I) / 2 - one_op = (I - Z) / 2 - h1 = one_op ^ I - h2 = one_op ^ (one_op + zero_op) - h2a = one_op ^ one_op - h2b = one_op ^ zero_op - self.assertEqual((~OperatorStateFn(h1) @ phi).eval(), 0.5) - self.assertEqual((~OperatorStateFn(h2) @ phi).eval(), 0.5) - self.assertEqual((~OperatorStateFn(h2a) @ phi).eval(), 0.25) - self.assertEqual((~OperatorStateFn(h2b) @ phi).eval(), 0.25) - - pauli_op = (Z ^ I ^ X) + (I ^ I ^ Y) - mat_op = pauli_op.to_matrix_op() - full_basis = ["".join(b) for b in product("01", repeat=pauli_op.num_qubits)] - for bstr1, bstr2 in product(full_basis, full_basis): - self.assertEqual(pauli_op.eval(bstr1).eval(bstr2), mat_op.eval(bstr1).eval(bstr2)) - - def test_exp_i(self): - """exp_i test""" - # TODO: add tests when special methods are added - pass - - def test_to_instruction(self): - """test for to_instruction""" - target = ((X + Z) / np.sqrt(2)).to_instruction() - qc = QuantumCircuit(1) - qc.u(np.pi / 2, 0, np.pi, 0) - qc_out = transpile(target.definition, basis_gates=["u"]) - self.assertEqual(qc_out, qc) - - def test_to_pauli_op(self): - """test to_pauli_op method""" - target = X + Y - self.assertIsInstance(target, PauliSumOp) - expected = SummedOp([X, Y]) - self.assertEqual(target.to_pauli_op(), expected) - - def test_getitem(self): - """test get item method""" - target = X + Z - self.assertEqual(target[0], PauliSumOp(SparsePauliOp(X.primitive))) - self.assertEqual(target[1], PauliSumOp(SparsePauliOp(Z.primitive))) - - def test_len(self): - """test len""" - target = X + Y + Z - self.assertEqual(len(target), 3) - - def test_reduce(self): - """test reduce""" - target = X + X + Z - self.assertEqual(len(target.reduce()), 2) - - def test_to_spmatrix(self): - """test to_spmatrix""" - target = X + Y - expected = csr_matrix([[0, 1 - 1j], [1 + 1j, 0]]) - self.assertEqual((target.to_spmatrix() - expected).nnz, 0) - - def test_from_list(self): - """test from_list""" - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - expected = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.assertEqual(target, expected) - - a = Parameter("a") - target = PauliSumOp.from_list([("X", 0.5 * a), ("Y", -0.5j * a)], dtype=object) - expected = PauliSumOp( - SparsePauliOp.from_list([("X", 0.5 * a), ("Y", -0.5j * a)], dtype=object) - ) - self.assertEqual(target.primitive, expected.primitive) - - def test_matrix_iter(self): - """Test PauliSumOp dense matrix_iter method.""" - labels = ["III", "IXI", "IYY", "YIZ", "XYZ", "III"] - coeffs = np.array([1, 2, 3, 4, 5, 6]) - paulis = PauliList(labels) - coeff = 10 - op = PauliSumOp(SparsePauliOp(paulis, coeffs), coeff) - for idx, i in enumerate(op.matrix_iter()): - self.assertTrue(np.array_equal(i, coeff * coeffs[idx] * Pauli(labels[idx]).to_matrix())) - - def test_matrix_iter_sparse(self): - """Test PauliSumOp sparse matrix_iter method.""" - labels = ["III", "IXI", "IYY", "YIZ", "XYZ", "III"] - coeffs = np.array([1, 2, 3, 4, 5, 6]) - coeff = 10 - paulis = PauliList(labels) - op = PauliSumOp(SparsePauliOp(paulis, coeffs), coeff) - for idx, i in enumerate(op.matrix_iter(sparse=True)): - self.assertTrue( - np.array_equal(i.toarray(), coeff * coeffs[idx] * Pauli(labels[idx]).to_matrix()) - ) - - def test_is_hermitian(self): - """Test is_hermitian method""" - with self.subTest("True test"): - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - self.assertTrue(target.is_hermitian()) - - with self.subTest("False test"): - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045j), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - self.assertFalse(target.is_hermitian()) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_state_construction.py b/test/python/opflow/test_state_construction.py deleted file mode 100644 index 05d595c18da4..000000000000 --- a/test/python/opflow/test_state_construction.py +++ /dev/null @@ -1,250 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Operator construction, including OpPrimitives and singletons.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -import numpy as np - -from qiskit import QuantumCircuit, BasicAer, execute -from qiskit.circuit import ParameterVector -from qiskit.quantum_info import Statevector - -from qiskit.opflow import ( - StateFn, - Zero, - One, - Plus, - Minus, - PrimitiveOp, - CircuitOp, - SummedOp, - H, - I, - Z, - X, - Y, - CX, - CircuitStateFn, - DictToCircuitSum, -) - - -class TestStateConstruction(QiskitOpflowTestCase): - """State Construction tests.""" - - def test_state_singletons(self): - """state singletons test""" - self.assertEqual(Zero.primitive, {"0": 1}) - self.assertEqual(One.primitive, {"1": 1}) - - self.assertEqual((Zero ^ 5).primitive, {"00000": 1}) - self.assertEqual((One ^ 5).primitive, {"11111": 1}) - self.assertEqual(((Zero ^ One) ^ 3).primitive, {"010101": 1}) - - def test_zero_broadcast(self): - """zero broadcast test""" - np.testing.assert_array_almost_equal(((H ^ 5) @ Zero).to_matrix(), (Plus ^ 5).to_matrix()) - - def test_state_to_matrix(self): - """state to matrix test""" - np.testing.assert_array_equal(Zero.to_matrix(), np.array([1, 0])) - np.testing.assert_array_equal(One.to_matrix(), np.array([0, 1])) - np.testing.assert_array_almost_equal( - Plus.to_matrix(), (Zero.to_matrix() + One.to_matrix()) / (np.sqrt(2)) - ) - np.testing.assert_array_almost_equal( - Minus.to_matrix(), (Zero.to_matrix() - One.to_matrix()) / (np.sqrt(2)) - ) - - # TODO Not a great test because doesn't test against validated values - # or test internal representation. Fix this. - gnarly_state = (One ^ Plus ^ Zero ^ Minus * 0.3) @ StateFn( - Statevector.from_label("r0+l") - ) + (StateFn(X ^ Z ^ Y ^ I) * 0.1j) - gnarly_mat = gnarly_state.to_matrix() - gnarly_mat_separate = (One ^ Plus ^ Zero ^ Minus * 0.3).to_matrix() - gnarly_mat_separate = np.dot( - gnarly_mat_separate, StateFn(Statevector.from_label("r0+l")).to_matrix() - ) - gnarly_mat_separate = gnarly_mat_separate + (StateFn(X ^ Z ^ Y ^ I) * 0.1j).to_matrix() - np.testing.assert_array_almost_equal(gnarly_mat, gnarly_mat_separate) - - def test_qiskit_result_instantiation(self): - """qiskit result instantiation test""" - qc = QuantumCircuit(3) - # REMEMBER: This is Qubit 2 in Operator land. - qc.h(0) - sv_res = execute(qc, BasicAer.get_backend("statevector_simulator")).result() - sv_vector = sv_res.get_statevector() - qc_op = PrimitiveOp(qc) @ Zero - - qasm_res = execute( - qc_op.to_circuit(meas=True), BasicAer.get_backend("qasm_simulator") - ).result() - - np.testing.assert_array_almost_equal( - StateFn(sv_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - StateFn(sv_vector).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - StateFn(qasm_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0], decimal=1 - ) - - np.testing.assert_array_almost_equal( - ((I ^ I ^ H) @ Zero).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - qc_op.to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - - def test_state_meas_composition(self): - """state meas composition test""" - pass - # print((~Zero^4).eval(Zero^4)) - # print((~One^4).eval(Zero^4)) - # print((~One ^ 4).eval(One ^ 4)) - # print(StateFn(I^Z, is_measurement=True).eval(One^2)) - - def test_add_direct(self): - """add direct test""" - wf = StateFn({"101010": 0.5, "111111": 0.3}) + (Zero ^ 6) - self.assertEqual(wf.primitive, {"101010": 0.5, "111111": 0.3, "000000": 1.0}) - wf = (4 * StateFn({"101010": 0.5, "111111": 0.3})) + ((3 + 0.1j) * (Zero ^ 6)) - self.assertEqual( - wf.primitive, {"000000": (3 + 0.1j), "101010": (2 + 0j), "111111": (1.2 + 0j)} - ) - - def test_circuit_state_fn_from_dict_as_sum(self): - """state fn circuit from dict as sum test""" - statedict = {"1010101": 0.5, "1000000": 0.1, "0000000": 0.2j, "1111111": 0.5j} - sfc_sum = CircuitStateFn.from_dict(statedict) - self.assertIsInstance(sfc_sum, SummedOp) - for sfc_op in sfc_sum.oplist: - self.assertIsInstance(sfc_op, CircuitStateFn) - samples = sfc_op.sample() - self.assertIn(list(samples.keys())[0], statedict) - self.assertEqual(sfc_op.coeff, statedict[list(samples.keys())[0]]) - np.testing.assert_array_almost_equal(StateFn(statedict).to_matrix(), sfc_sum.to_matrix()) - - def test_circuit_state_fn_from_dict_initialize(self): - """state fn circuit from dict initialize test""" - statedict = {"101": 0.5, "100": 0.1, "000": 0.2, "111": 0.5} - sfc = CircuitStateFn.from_dict(statedict) - self.assertIsInstance(sfc, CircuitStateFn) - samples = sfc.sample() - np.testing.assert_array_almost_equal( - StateFn(statedict).to_matrix(), np.round(sfc.to_matrix(), decimals=1) - ) - for k, v in samples.items(): - self.assertIn(k, statedict) - # It's ok if these are far apart because the dict is sampled. - self.assertAlmostEqual(v, np.abs(statedict[k]) ** 0.5, delta=0.5) - - # Follows same code path as above, but testing to be thorough - sfc_vector = CircuitStateFn.from_vector(StateFn(statedict).to_matrix()) - np.testing.assert_array_almost_equal(StateFn(statedict).to_matrix(), sfc_vector.to_matrix()) - - # #1276 - def test_circuit_state_fn_from_complex_vector_initialize(self): - """state fn circuit from complex vector initialize test""" - sfc = CircuitStateFn.from_vector(np.array([1j / np.sqrt(2), 0, 1j / np.sqrt(2), 0])) - self.assertIsInstance(sfc, CircuitStateFn) - - def test_sampling(self): - """state fn circuit from dict initialize test""" - statedict = {"101": 0.5 + 1.0j, "100": 0.1 + 2.0j, "000": 0.2 + 0.0j, "111": 0.5 + 1.0j} - sfc = CircuitStateFn.from_dict(statedict) - circ_samples = sfc.sample() - dict_samples = StateFn(statedict).sample() - vec_samples = StateFn(statedict).to_matrix_op().sample() - for k, v in circ_samples.items(): - self.assertIn(k, dict_samples) - self.assertIn(k, vec_samples) - # It's ok if these are far apart because the dict is sampled. - self.assertAlmostEqual(v, np.abs(dict_samples[k]) ** 0.5, delta=0.5) - self.assertAlmostEqual(v, np.abs(vec_samples[k]) ** 0.5, delta=0.5) - - def test_dict_to_circuit_sum(self): - """Test DictToCircuitSum converter.""" - # Test qubits < entires, so dict is converted to Initialize CircuitStateFn - dict_state_3q = StateFn({"101": 0.5, "100": 0.1, "000": 0.2, "111": 0.5}) - circuit_state_3q = DictToCircuitSum().convert(dict_state_3q) - self.assertIsInstance(circuit_state_3q, CircuitStateFn) - np.testing.assert_array_almost_equal( - dict_state_3q.to_matrix(), circuit_state_3q.to_matrix() - ) - - # Test qubits >= entires, so dict is converted to Initialize CircuitStateFn - dict_state_4q = dict_state_3q ^ Zero - circuit_state_4q = DictToCircuitSum().convert(dict_state_4q) - self.assertIsInstance(circuit_state_4q, SummedOp) - np.testing.assert_array_almost_equal( - dict_state_4q.to_matrix(), circuit_state_4q.to_matrix() - ) - - # Test VectorStateFn conversion - vect_state_3q = dict_state_3q.to_matrix_op() - circuit_state_3q_vect = DictToCircuitSum().convert(vect_state_3q) - self.assertIsInstance(circuit_state_3q_vect, CircuitStateFn) - np.testing.assert_array_almost_equal( - vect_state_3q.to_matrix(), circuit_state_3q_vect.to_matrix() - ) - - def test_circuit_permute(self): - r"""Test the CircuitStateFn's .permute method""" - perm = range(7)[::-1] - c_op = ( - ((CX ^ 3) ^ X) - @ (H ^ 7) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - @ (Y ^ (CX ^ 3)) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - ) @ Zero - c_op_perm = c_op.permute(perm) - self.assertNotEqual(c_op, c_op_perm) - c_op_id = c_op_perm.permute(perm) - self.assertEqual(c_op, c_op_id) - - def test_primitive_param_binding(self): - """Test that assign_parameters binds parameters of both the underlying primitive and coeffs.""" - theta = ParameterVector("theta", 2) - # only OperatorStateFn can have a primitive with a parameterized coefficient - op = StateFn(theta[0] * X) * theta[1] - bound = op.assign_parameters(dict(zip(theta, [0.2, 0.3]))) - self.assertEqual(bound.coeff, 0.3) - self.assertEqual(bound.primitive.coeff, 0.2) - - # #6003 - def test_flatten_statefn_composed_with_composed_op(self): - """Test that composing a StateFn with a ComposedOp constructs a single ComposedOp""" - circuit = QuantumCircuit(1) - vector = [1, 0] - ex = ~StateFn(I) @ (CircuitOp(circuit) @ StateFn(vector)) - self.assertEqual(len(ex), 3) - self.assertEqual(ex.eval(), 1) - - def test_tensorstate_to_matrix(self): - """Test tensored states to matrix works correctly with a global coefficient. - - Regression test of Qiskit/qiskit-terra#9398. - """ - state = 0.5 * (Plus ^ Zero) - expected = 1 / (2 * np.sqrt(2)) * np.array([1, 0, 1, 0]) - np.testing.assert_almost_equal(state.to_matrix(), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_state_op_meas_evals.py b/test/python/opflow/test_state_op_meas_evals.py deleted file mode 100644 index e6d23390ea78..000000000000 --- a/test/python/opflow/test_state_op_meas_evals.py +++ /dev/null @@ -1,248 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=no-name-in-module - - -"""Test Operator construction, including OpPrimitives and singletons.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -from ddt import ddt, data -import numpy - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.utils import QuantumInstance -from qiskit.opflow import StateFn, Zero, One, H, X, I, Z, Plus, Minus, CircuitSampler, ListOp -from qiskit.opflow.exceptions import OpflowError -from qiskit.quantum_info import Statevector - - -@ddt -class TestStateOpMeasEvals(QiskitOpflowTestCase): - """Tests of evals of Meas-Operator-StateFn combos.""" - - def test_statefn_overlaps(self): - """state functions overlaps test""" - wf = (4 * StateFn({"101010": 0.5, "111111": 0.3})) + ((3 + 0.1j) * (Zero ^ 6)) - wf_vec = StateFn(wf.to_matrix()) - self.assertAlmostEqual(wf.adjoint().eval(wf), 14.45) - self.assertAlmostEqual(wf_vec.adjoint().eval(wf_vec), 14.45) - self.assertAlmostEqual(wf_vec.adjoint().eval(wf), 14.45) - self.assertAlmostEqual(wf.adjoint().eval(wf_vec), 14.45) - - def test_wf_evals_x(self): - """wf evals x test""" - qbits = 4 - - wf = ((Zero ^ qbits) + (One ^ qbits)) * (1 / 2**0.5) - # Note: wf = Plus^qbits fails because TensoredOp can't handle it. - wf_vec = StateFn(wf.to_matrix()) - op = X ^ qbits - # op = I^6 - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf)), 1) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf)), 1) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf_vec)), 1) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf_vec)), 1) - - # op = (H^X^Y)^2 - op = H ^ 6 - wf = ((Zero ^ 6) + (One ^ 6)) * (1 / 2**0.5) - wf_vec = StateFn(wf.to_matrix()) - # print(wf.adjoint().to_matrix() @ op.to_matrix() @ wf.to_matrix()) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf)), 0.25) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf)), 0.25) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf_vec)), 0.25) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf_vec)), 0.25) - - def test_coefficients_correctly_propagated(self): - """Test that the coefficients in SummedOp and states are correctly used.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - with self.subTest("zero coeff in SummedOp"): - op = 0 * (I + Z) - state = Plus - self.assertEqual((~StateFn(op) @ state).eval(), 0j) - - backend = Aer.get_backend("aer_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend, seed_simulator=97, seed_transpiler=97) - op = I - with self.subTest("zero coeff in summed StateFn and CircuitSampler"): - with self.assertWarns(DeprecationWarning): - state = 0 * (Plus + Minus) - sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) - self.assertEqual(sampler.eval(), 0j) - - with self.subTest("coeff gets squared in CircuitSampler shot-based readout"): - with self.assertWarns(DeprecationWarning): - state = (Plus + Minus) / numpy.sqrt(2) - sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) - self.assertAlmostEqual(sampler.eval(), 1 + 0j) - - def test_is_measurement_correctly_propagated(self): - """Test if is_measurement property of StateFn is propagated to converted StateFn.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend) # no seeds needed since no values are compared - state = Plus - sampler = CircuitSampler(q_instance).convert(~state @ state) - self.assertTrue(sampler.oplist[0].is_measurement) - - def test_parameter_binding_on_listop(self): - """Test passing a ListOp with differing parameters works with the circuit sampler.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - x, y = Parameter("x"), Parameter("y") - - circuit1 = QuantumCircuit(1) - circuit1.p(0.2, 0) - circuit2 = QuantumCircuit(1) - circuit2.p(x, 0) - circuit3 = QuantumCircuit(1) - circuit3.p(y, 0) - - with self.assertWarns(DeprecationWarning): - bindings = {x: -0.4, y: 0.4} - listop = ListOp([StateFn(circuit) for circuit in [circuit1, circuit2, circuit3]]) - sampler = CircuitSampler(Aer.get_backend("aer_simulator")) - sampled = sampler.convert(listop, params=bindings) - - self.assertTrue(all(len(op.parameters) == 0 for op in sampled.oplist)) - - def test_list_op_eval_coeff_with_nonlinear_combofn(self): - """Test evaluating a ListOp with non-linear combo function works with coefficients.""" - - state = One - op = ListOp(5 * [I], coeff=2, combo_fn=numpy.prod) - expr1 = ~StateFn(op) @ state - - expr2 = ListOp(5 * [~state @ I @ state], coeff=2, combo_fn=numpy.prod) - - self.assertEqual(expr1.eval(), 2) # if the coeff is propagated too far the result is 4 - self.assertEqual(expr2.eval(), 2) - - def test_single_parameter_binds(self): - """Test passing parameter binds as a dictionary to the circuit sampler.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(x, 0) - - with self.assertWarns(DeprecationWarning): - expr = ~StateFn(H) @ StateFn(circuit) - sampler = CircuitSampler(Aer.get_backend("aer_simulator_statevector")) - res = sampler.convert(expr, params={x: 0}).eval() - - self.assertIsInstance(res, complex) - - @data("all", "last") - def test_circuit_sampler_caching(self, caching): - """Test caching all operators works.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(x, 0) - - with self.assertWarns(DeprecationWarning): - - expr1 = ~StateFn(H) @ StateFn(circuit) - expr2 = ~StateFn(X) @ StateFn(circuit) - sampler = CircuitSampler(Aer.get_backend("aer_simulator_statevector"), caching=caching) - - res1 = sampler.convert(expr1, params={x: 0}).eval() - res2 = sampler.convert(expr2, params={x: 0}).eval() - res3 = sampler.convert(expr1, params={x: 0}).eval() - res4 = sampler.convert(expr2, params={x: 0}).eval() - - self.assertEqual(res1, res3) - self.assertEqual(res2, res4) - if caching == "last": - self.assertEqual(len(sampler._cached_ops.keys()), 1) - else: - self.assertEqual(len(sampler._cached_ops.keys()), 2) - - def test_adjoint_nonunitary_circuit_raises(self): - """Test adjoint on a non-unitary circuit raises a OpflowError instead of CircuitError.""" - circuit = QuantumCircuit(1) - circuit.reset(0) - - with self.assertRaises(OpflowError): - _ = StateFn(circuit).adjoint() - - def test_evaluating_nonunitary_circuit_state(self): - """Test evaluating a circuit works even if it contains non-unitary instruction (resets). - - TODO: allow this for (~StateFn(circuit) @ op @ StateFn(circuit)), but this requires - refactoring how the AerPauliExpectation works, since that currently relies on - composing with CircuitMeasurements - """ - circuit = QuantumCircuit(1) - circuit.initialize([0, 1], [0]) - - op = Z - res = (~StateFn(op) @ StateFn(circuit)).eval() - - self.assertAlmostEqual(-1 + 0j, res) - - def test_quantum_instance_with_backend_shots(self): - """Test sampling a circuit where the backend has shots attached.""" - try: - from qiskit.providers.aer import AerSimulator - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - - backend = AerSimulator(shots=10) - - with self.assertWarns(DeprecationWarning): - sampler = CircuitSampler(backend) - res = sampler.convert(~Plus @ Plus).eval() - self.assertAlmostEqual(res, 1 + 0j, places=2) - - def test_adjoint_vector_to_circuit_fn(self): - """Test it is possible to adjoint a VectorStateFn that was converted to a CircuitStateFn.""" - - left = StateFn([0, 1]) - left_circuit = left.to_circuit_op().primitive - - right_circuit = QuantumCircuit(1) - right_circuit.x(0) - - circuit = left_circuit.inverse().compose(right_circuit) - - self.assertTrue(Statevector(circuit).equiv([1, 0])) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_tapered_pauli.py b/test/python/opflow/test_tapered_pauli.py deleted file mode 100644 index 967ca82373b8..000000000000 --- a/test/python/opflow/test_tapered_pauli.py +++ /dev/null @@ -1,57 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test TaperedPauliSumOp""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestZ2Symmetries(QiskitOpflowTestCase): - """Z2Symmetries tests.""" - - def setUp(self): - super().setUp() - z2_symmetries = Z2Symmetries( - [Pauli("IIZI"), Pauli("ZIII")], [Pauli("IIXI"), Pauli("XIII")], [1, 3], [-1, 1] - ) - self.primitive = SparsePauliOp.from_list( - [ - ("II", (-1.052373245772859)), - ("ZI", (-0.39793742484318007)), - ("IZ", (0.39793742484318007)), - ("ZZ", (-0.01128010425623538)), - ("XX", (0.18093119978423142)), - ] - ) - self.tapered_qubit_op = TaperedPauliSumOp(self.primitive, z2_symmetries) - - def test_multiply_parameter(self): - """test for multiplication of parameter""" - param = Parameter("c") - expected = PauliSumOp(self.primitive, coeff=param) - self.assertEqual(param * self.tapered_qubit_op, expected) - - def test_assign_parameters(self): - """test assign_parameters""" - param = Parameter("c") - parameterized_op = param * self.tapered_qubit_op - expected = PauliSumOp(self.primitive, coeff=46) - self.assertEqual(parameterized_op.assign_parameters({param: 46}), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_two_qubit_reduction.py b/test/python/opflow/test_two_qubit_reduction.py deleted file mode 100644 index 859d63446ebc..000000000000 --- a/test/python/opflow/test_two_qubit_reduction.py +++ /dev/null @@ -1,64 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test TwoQubitReduction""" - -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliSumOp, TwoQubitReduction, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestTwoQubitReduction(QiskitOpflowTestCase): - """TwoQubitReduction tests.""" - - def test_convert(self): - """convert test""" - - qubit_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("IIIZ", 0.17218393261915552), - ("IIZZ", -0.22575349222402472), - ("IZZI", 0.1721839326191556), - ("ZZII", -0.22575349222402466), - ("IIZI", 0.1209126326177663), - ("IZZZ", 0.16892753870087912), - ("IXZX", -0.045232799946057854), - ("ZXIX", 0.045232799946057854), - ("IXIX", 0.045232799946057854), - ("ZXZX", -0.045232799946057854), - ("ZZIZ", 0.16614543256382414), - ("IZIZ", 0.16614543256382414), - ("ZZZZ", 0.17464343068300453), - ("ZIZI", 0.1209126326177663), - ] - ) - tapered_qubit_op = TwoQubitReduction(num_particles=2).convert(qubit_op) - self.assertIsInstance(tapered_qubit_op, TaperedPauliSumOp) - - primitive = SparsePauliOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", -0.39793742484318007), - ("IZ", 0.39793742484318007), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423142), - ] - ) - symmetries = [Pauli("IIZI"), Pauli("ZIII")] - sq_paulis = [Pauli("IIXI"), Pauli("XIII")] - sq_list = [1, 3] - tapering_values = [-1, 1] - z2_symmetries = Z2Symmetries(symmetries, sq_paulis, sq_list, tapering_values) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_qubit_op, expected_op) diff --git a/test/python/opflow/test_z2_symmetries.py b/test/python/opflow/test_z2_symmetries.py deleted file mode 100644 index 37e8e0e16a27..000000000000 --- a/test/python/opflow/test_z2_symmetries.py +++ /dev/null @@ -1,112 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Z2Symmetries""" - -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliSumOp, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestZ2Symmetries(QiskitOpflowTestCase): - """Z2Symmetries tests.""" - - def test_find_Z2_symmetries(self): - """test for find_Z2_symmetries""" - - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - self.assertEqual(z2_symmetries.symmetries, [Pauli("ZZ")]) - self.assertEqual(z2_symmetries.sq_paulis, [Pauli("IX")]) - self.assertEqual(z2_symmetries.sq_list, [0]) - self.assertEqual(z2_symmetries.tapering_values, None) - - tapered_op = z2_symmetries.taper(qubit_op)[1] - self.assertEqual(tapered_op.z2_symmetries.symmetries, [Pauli("ZZ")]) - self.assertEqual(tapered_op.z2_symmetries.sq_paulis, [Pauli("IX")]) - self.assertEqual(tapered_op.z2_symmetries.sq_list, [0]) - self.assertEqual(tapered_op.z2_symmetries.tapering_values, [-1]) - - z2_symmetries.tapering_values = [-1] - primitive = SparsePauliOp.from_list( - [ - ("I", -1.0424710218959303), - ("Z", -0.7879673588770277), - ("X", -0.18128880821149604), - ] - ) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_op, expected_op) - - def test_taper_empty_operator(self): - """Test tapering of empty operator""" - z2_symmetries = Z2Symmetries( - symmetries=[Pauli("IIZI"), Pauli("IZIZ"), Pauli("ZIII")], - sq_paulis=[Pauli("IIXI"), Pauli("IIIX"), Pauli("XIII")], - sq_list=[1, 0, 3], - tapering_values=[1, -1, -1], - ) - empty_op = PauliSumOp.from_list([("IIII", 0.0)]) - tapered_op = z2_symmetries.taper(empty_op) - expected_op = PauliSumOp.from_list([("I", 0.0)]) - self.assertEqual(tapered_op, expected_op) - - def test_truncate_tapered_op(self): - """Test setting cutoff tolerances for the tapered operator works.""" - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - z2_symmetries.tol = 0.2 # removes the X part of the tapered op which is < 0.2 - - tapered_op = z2_symmetries.taper(qubit_op)[1] - primitive = SparsePauliOp.from_list( - [ - ("I", -1.0424710218959303), - ("Z", -0.7879673588770277), - ] - ) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_op, expected_op) - - def test_twostep_tapering(self): - """Test the two-step tapering""" - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - tapered_op = z2_symmetries.taper(qubit_op) - - tapered_op_firststep = z2_symmetries.convert_clifford(qubit_op) - tapered_op_secondstep = z2_symmetries.taper_clifford(tapered_op_firststep) - self.assertEqual(tapered_op, tapered_op_secondstep) diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index eaa1c7d922fd..7a190cbe0034 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -20,7 +20,6 @@ from qiskit.circuit import Parameter, QuantumCircuit from qiskit.circuit.library import RealAmplitudes from qiskit.exceptions import QiskitError -from qiskit.opflow import PauliSumOp from qiskit.primitives import Estimator, EstimatorResult from qiskit.primitives.base import validation from qiskit.primitives.utils import _observable_key @@ -350,7 +349,6 @@ class TestObservableValidation(QiskitTestCase): ("IXYZ", (SparsePauliOp("IXYZ"),)), (Pauli("IXYZ"), (SparsePauliOp("IXYZ"),)), (SparsePauliOp("IXYZ"), (SparsePauliOp("IXYZ"),)), - (PauliSumOp(SparsePauliOp("IXYZ")), (SparsePauliOp("IXYZ"),)), ( ["IXYZ", "ZYXI"], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), @@ -363,10 +361,6 @@ class TestObservableValidation(QiskitTestCase): [SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), ), - ( - [PauliSumOp(SparsePauliOp("IXYZ")), PauliSumOp(SparsePauliOp("ZYXI"))], - (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), - ), ) @unpack def test_validate_observables(self, obsevables, expected): diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index f7cb06c5b86b..cb68099b310e 100644 --- a/test/python/result/test_sampled_expval.py +++ b/test/python/result/test_sampled_expval.py @@ -15,7 +15,6 @@ import unittest from qiskit.result import Counts, QuasiDistribution, ProbDistribution, sampled_expectation_value from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.opflow import PauliOp, PauliSumOp from qiskit.test import QiskitTestCase @@ -82,17 +81,9 @@ def test_same(self): exp2 = sampled_expectation_value(counts, Pauli(oper)) self.assertAlmostEqual(exp2, ans) - with self.assertWarns(DeprecationWarning): - exp3 = sampled_expectation_value(counts, PauliOp(Pauli(oper))) - self.assertAlmostEqual(exp3, ans) - spo = SparsePauliOp([oper], coeffs=[1]) - with self.assertWarns(DeprecationWarning): - exp4 = sampled_expectation_value(counts, PauliSumOp(spo, coeff=2)) - self.assertAlmostEqual(exp4, 2 * ans) - - exp5 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) - self.assertAlmostEqual(exp5, ans) + exp3 = sampled_expectation_value(counts, spo) + self.assertAlmostEqual(exp3, ans) def test_asym_ops(self): """Test that asymmetric exp values work""" diff --git a/test/python/utils/mitigation/__init__.py b/test/python/utils/mitigation/__init__.py deleted file mode 100644 index ce3f4ee22ee0..000000000000 --- a/test/python/utils/mitigation/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Qiskit mitigation utils unit tests.""" diff --git a/test/python/utils/mitigation/test_meas.py b/test/python/utils/mitigation/test_meas.py deleted file mode 100644 index a5105b5ba4d5..000000000000 --- a/test/python/utils/mitigation/test_meas.py +++ /dev/null @@ -1,709 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name - -""" -Test of measurement calibration: -1) Preparation of the basis states, generating the calibration circuits -(without noise), computing the calibration matrices, -and validating that they equal -to the identity matrices -2) Generating ideal (equally distributed) results, computing -the calibration output (without noise), -and validating that it is equally distributed -3) Testing the the measurement calibration on a circuit -(without noise), verifying that it is close to the -expected (equally distributed) result -4) Testing the fitters on pre-generated data with noise -""" - -import unittest -import numpy as np - -import qiskit -from qiskit.test import QiskitTestCase -from qiskit.result.result import Result -from qiskit.utils.mitigation import ( - CompleteMeasFitter, - TensoredMeasFitter, - complete_meas_cal, - tensored_meas_cal, -) -from qiskit.utils.mitigation._filters import MeasurementFilter -from qiskit.utils.mitigation.circuits import count_keys - -from qiskit.utils import optionals - -if optionals.HAS_AER: - # pylint: disable=no-name-in-module - from qiskit_aer import AerSimulator - from qiskit_aer.noise import NoiseModel - from qiskit_aer.noise.errors.standard_errors import pauli_error - -# fixed seed for tests - for both simulator and transpiler -SEED = 42 - - -def convert_ndarray_to_list_in_data(data: np.ndarray): - """ - converts ndarray format into list format (keeps all the dicts in the array) - also convert inner ndarrays into lists (recursively) - Args: - data: ndarray containing dicts or ndarrays in it - - Returns: - list: same array, converted to list format (in order to save it as json) - - """ - new_data = [] - for item in data: - if isinstance(item, np.ndarray): - new_item = convert_ndarray_to_list_in_data(item) - elif isinstance(item, dict): - new_item = {} - for key, value in item.items(): - new_item[key] = value.tolist() - else: - new_item = item - new_data.append(new_item) - - return new_data - - -def meas_calib_circ_creation(): - """ - create measurement calibration circuits and a GHZ state circuit for the tests - - Returns: - QuantumCircuit: the measurement calibrations circuits - list[str]: the mitigation pattern - QuantumCircuit: ghz circuit with 5 qubits (3 are used) - - """ - qubit_list = [1, 2, 3] - total_number_of_qubit = 5 - meas_calibs, state_labels = complete_meas_cal(qubit_list=qubit_list, qr=total_number_of_qubit) - - # Choose 3 qubits - qubit_1 = qubit_list[0] - qubit_2 = qubit_list[1] - qubit_3 = qubit_list[2] - ghz = qiskit.QuantumCircuit(total_number_of_qubit, len(qubit_list)) - ghz.h(qubit_1) - ghz.cx(qubit_1, qubit_2) - ghz.cx(qubit_1, qubit_3) - for i in qubit_list: - ghz.measure(i, i - 1) - return meas_calibs, state_labels, ghz - - -def tensored_calib_circ_creation(): - """ - create tensored measurement calibration circuits and a GHZ state circuit for the tests - - Returns: - QuantumCircuit: the tensored measurement calibration circuit - list[list[int]]: the mitigation pattern - QuantumCircuit: ghz circuit with 5 qubits (3 are used) - - """ - mit_pattern = [[2], [4, 1]] - meas_layout = [2, 4, 1] - qr = qiskit.QuantumRegister(5) - # Generate the calibration circuits - meas_calibs, mit_pattern = tensored_meas_cal(mit_pattern, qr=qr) - - cr = qiskit.ClassicalRegister(3) - ghz_circ = qiskit.QuantumCircuit(qr, cr) - ghz_circ.h(mit_pattern[0][0]) - ghz_circ.cx(mit_pattern[0][0], mit_pattern[1][0]) - ghz_circ.cx(mit_pattern[0][0], mit_pattern[1][1]) - ghz_circ.measure(mit_pattern[0][0], cr[0]) - ghz_circ.measure(mit_pattern[1][0], cr[1]) - ghz_circ.measure(mit_pattern[1][1], cr[2]) - return meas_calibs, mit_pattern, ghz_circ, meas_layout - - -def meas_calibration_circ_execution(shots: int, seed: int): - """ - create measurement calibration circuits and simulate them with noise - Args: - shots (int): number of shots per simulation - seed (int): the seed to use in the simulations - - Returns: - list: list of Results of the measurement calibration simulations - list: list of all the possible states with this amount of qubits - dict: dictionary of results counts of GHZ circuit simulation with measurement errors - """ - # define the circuits - meas_calibs, state_labels, ghz = meas_calib_circ_creation() - - # define noise - prob = 0.2 - error_meas = pauli_error([("X", prob), ("I", 1 - prob)]) - noise_model = NoiseModel() - noise_model.add_all_qubit_quantum_error(error_meas, "measure") - - # run the circuits multiple times - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - ghz_results = ( - qiskit.execute( - ghz, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ) - .result() - .get_counts() - ) - - return cal_results, state_labels, ghz_results - - -def tensored_calib_circ_execution(shots: int, seed: int): - """ - create tensored measurement calibration circuits and simulate them with noise - Args: - shots (int): number of shots per simulation - seed (int): the seed to use in the simulations - - Returns: - list: list of Results of the measurement calibration simulations - list: the mitigation pattern - dict: dictionary of results counts of GHZ circuit simulation with measurement errors - """ - # define the circuits - meas_calibs, mit_pattern, ghz_circ, meas_layout = tensored_calib_circ_creation() - # define noise - prob = 0.2 - error_meas = pauli_error([("X", prob), ("I", 1 - prob)]) - noise_model = NoiseModel() - noise_model.add_all_qubit_quantum_error(error_meas, "measure") - - # run the circuits multiple times - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - ghz_results = qiskit.execute( - ghz_circ, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - return cal_results, mit_pattern, ghz_results, meas_layout - - -@unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") -class TestMeasCal(QiskitTestCase): - """The test class.""" - - def setUp(self): - super().setUp() - self.nq_list = [1, 2, 3, 4, 5] # Test up to 5 qubits - self.shots = 1024 # Number of shots (should be a power of 2) - - @staticmethod - def choose_calibration(nq, pattern_type): - """ - Generate a calibration circuit - - Args: - nq (int): number of qubits - pattern_type (int): a pattern in range(1, 2**nq) - - Returns: - qubits: a list of qubits according to the given pattern - weight: the weight of the pattern_type, - equals to the number of qubits - - Additional Information: - qr[i] exists if and only if the i-th bit in the binary - expression of - pattern_type equals 1 - """ - qubits = [] - weight = 0 - for i in range(nq): - pattern_bit = pattern_type & 1 - pattern_type = pattern_type >> 1 - if pattern_bit == 1: - qubits.append(i) - weight += 1 - return qubits, weight - - def generate_ideal_results(self, state_labels, weight): - """ - Generate ideal equally distributed results - - Args: - state_labels (list): a list of calibration state labels - weight (int): the number of qubits - - Returns: - results_dict: a dictionary of equally distributed results - results_list: a list of equally distributed results - - Additional Information: - for each state in state_labels: - result_dict[state] = #shots/len(state_labels) - """ - results_dict = {} - results_list = [0] * (2**weight) - state_num = len(state_labels) - for state in state_labels: - shots_per_state = self.shots / state_num - results_dict[state] = shots_per_state - # converting state (binary) to an integer - place = int(state, 2) - results_list[place] = shots_per_state - return results_dict, results_list - - def test_ideal_meas_cal(self): - """Test ideal execution, without noise.""" - for nq in self.nq_list: - for pattern_type in range(1, 2**nq): - - # Generate the quantum register according to the pattern - qubits, weight = self.choose_calibration(nq, pattern_type) - - with self.assertWarns(DeprecationWarning): - # Generate the calibration circuits - meas_calibs, state_labels = complete_meas_cal( - qubit_list=qubits, circlabel="test" - ) - - # Perform an ideal execution on the generated circuits - backend = AerSimulator() - job = qiskit.execute(meas_calibs, backend=backend, shots=self.shots) - cal_results = job.result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = CompleteMeasFitter(cal_results, state_labels, circlabel="test") - - # Assert that the calibration matrix is equal to identity - IdentityMatrix = np.identity(2**weight) - self.assertListEqual( - meas_cal.cal_matrix.tolist(), - IdentityMatrix.tolist(), - "Error: the calibration matrix is not equal to identity", - ) - - # Assert that the readout fidelity is equal to 1 - self.assertEqual( - meas_cal.readout_fidelity(), - 1.0, - "Error: the average fidelity is not equal to 1", - ) - - # Generate ideal (equally distributed) results - results_dict, results_list = self.generate_ideal_results(state_labels, weight) - - with self.assertWarns(DeprecationWarning): - # Output the filter - meas_filter = meas_cal.filter - - # Apply the calibration matrix to results - # in list and dict forms using different methods - results_dict_1 = meas_filter.apply(results_dict, method="least_squares") - results_dict_0 = meas_filter.apply(results_dict, method="pseudo_inverse") - results_list_1 = meas_filter.apply(results_list, method="least_squares") - results_list_0 = meas_filter.apply(results_list, method="pseudo_inverse") - - # Assert that the results are equally distributed - self.assertListEqual(results_list, results_list_0.tolist()) - self.assertListEqual(results_list, np.round(results_list_1).tolist()) - self.assertDictEqual(results_dict, results_dict_0) - round_results = {} - for key, val in results_dict_1.items(): - round_results[key] = np.round(val) - self.assertDictEqual(results_dict, round_results) - - def test_meas_cal_on_circuit(self): - """Test an execution on a circuit.""" - # Generate the calibration circuits - with self.assertWarns(DeprecationWarning): - meas_calibs, state_labels, ghz = meas_calib_circ_creation() - - # Run the calibration circuits - backend = AerSimulator() - job = qiskit.execute( - meas_calibs, - backend=backend, - shots=self.shots, - seed_simulator=SEED, - seed_transpiler=SEED, - ) - cal_results = job.result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = CompleteMeasFitter(cal_results, state_labels) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity() - - job = qiskit.execute( - [ghz], backend=backend, shots=self.shots, seed_simulator=SEED, seed_transpiler=SEED - ) - results = job.result() - - # Predicted equally distributed results - predicted_results = {"000": 0.5, "111": 0.5} - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - results, method="pseudo_inverse" - ).get_counts(0) - output_results_least_square = meas_filter.apply(results, method="least_squares").get_counts( - 0 - ) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, 1.0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"] / self.shots, predicted_results["111"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["111"] / self.shots, predicted_results["111"], places=1 - ) - - def test_ideal_tensored_meas_cal(self): - """Test ideal execution, without noise.""" - - mit_pattern = [[1, 2], [3, 4, 5], [6]] - meas_layout = [1, 2, 3, 4, 5, 6] - - # Generate the calibration circuits - with self.assertWarns(DeprecationWarning): - meas_calibs, _ = tensored_meas_cal(mit_pattern=mit_pattern) - - # Perform an ideal execution on the generated circuits - backend = AerSimulator() - cal_results = qiskit.execute(meas_calibs, backend=backend, shots=self.shots).result() - - with self.assertWarns(DeprecationWarning): - # Make calibration matrices - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - - # Assert that the calibration matrices are equal to identity - cal_matrices = meas_cal.cal_matrices - self.assertEqual( - len(mit_pattern), len(cal_matrices), "Wrong number of calibration matrices" - ) - for qubit_list, cal_mat in zip(mit_pattern, cal_matrices): - IdentityMatrix = np.identity(2 ** len(qubit_list)) - self.assertListEqual( - cal_mat.tolist(), - IdentityMatrix.tolist(), - "Error: the calibration matrix is not equal to identity", - ) - - # Assert that the readout fidelity is equal to 1 - self.assertEqual( - meas_cal.readout_fidelity(), - 1.0, - "Error: the average fidelity is not equal to 1", - ) - - with self.assertWarns(DeprecationWarning): - # Generate ideal (equally distributed) results - results_dict, _ = self.generate_ideal_results(count_keys(6), 6) - # Output the filter - meas_filter = meas_cal.filter - # Apply the calibration matrix to results - # in list and dict forms using different methods - results_dict_1 = meas_filter.apply( - results_dict, method="least_squares", meas_layout=meas_layout - ) - results_dict_0 = meas_filter.apply( - results_dict, method="pseudo_inverse", meas_layout=meas_layout - ) - - # Assert that the results are equally distributed - self.assertDictEqual(results_dict, results_dict_0) - round_results = {} - for key, val in results_dict_1.items(): - round_results[key] = np.round(val) - self.assertDictEqual(results_dict, round_results) - - def test_tensored_meas_cal_on_circuit(self): - """Test an execution on a circuit.""" - - with self.assertWarns(DeprecationWarning): - # Generate the calibration circuits - meas_calibs, mit_pattern, ghz, meas_layout = tensored_calib_circ_creation() - - # Run the calibration circuits - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, - backend=backend, - shots=self.shots, - seed_simulator=SEED, - seed_transpiler=SEED, - ).result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity(0) * meas_cal.readout_fidelity(1) - - results = qiskit.execute( - [ghz], backend=backend, shots=self.shots, seed_simulator=SEED, seed_transpiler=SEED - ).result() - - # Predicted equally distributed results - predicted_results = {"000": 0.5, "111": 0.5} - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - results, method="pseudo_inverse", meas_layout=meas_layout - ).get_counts(0) - output_results_least_square = meas_filter.apply( - results, method="least_squares", meas_layout=meas_layout - ).get_counts(0) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, 1.0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"] / self.shots, predicted_results["111"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["111"] / self.shots, predicted_results["111"], places=1 - ) - - def test_meas_fitter_with_noise(self): - """Test the MeasurementFitter with noise.""" - tests = [] - runs = 3 - with self.assertWarns(DeprecationWarning): - for run in range(runs): - cal_results, state_labels, circuit_results = meas_calibration_circ_execution( - 1000, SEED + run - ) - - meas_cal = CompleteMeasFitter(cal_results, state_labels) - meas_filter = MeasurementFilter(meas_cal.cal_matrix, state_labels) - - # Calculate the results after mitigation - results_pseudo_inverse = meas_filter.apply(circuit_results, method="pseudo_inverse") - results_least_square = meas_filter.apply(circuit_results, method="least_squares") - tests.append( - { - "cal_matrix": convert_ndarray_to_list_in_data(meas_cal.cal_matrix), - "fidelity": meas_cal.readout_fidelity(), - "results": circuit_results, - "results_pseudo_inverse": results_pseudo_inverse, - "results_least_square": results_least_square, - } - ) - # Set the state labels - state_labels = ["000", "001", "010", "011", "100", "101", "110", "111"] - meas_cal = CompleteMeasFitter(None, state_labels, circlabel="test") - - for tst_index, _ in enumerate(tests): - # Set the calibration matrix - meas_cal.cal_matrix = tests[tst_index]["cal_matrix"] - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity() - - meas_filter = MeasurementFilter(tests[tst_index]["cal_matrix"], state_labels) - - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - tests[tst_index]["results"], method="pseudo_inverse" - ) - output_results_least_square = meas_filter.apply( - tests[tst_index]["results"], method="least_squares" - ) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, tests[tst_index]["fidelity"], places=0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - tests[tst_index]["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square["000"], - tests[tst_index]["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - tests[tst_index]["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square["111"], - tests[tst_index]["results_least_square"]["111"], - places=0, - ) - - def test_tensored_meas_fitter_with_noise(self): - """Test the TensoredFitter with noise.""" - with self.assertWarns(DeprecationWarning): - cal_results, mit_pattern, circuit_results, meas_layout = tensored_calib_circ_execution( - 1000, SEED - ) - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - meas_filter = meas_cal.filter - # Calculate the results after mitigation - results_pseudo_inverse = meas_filter.apply( - circuit_results.get_counts(), method="pseudo_inverse", meas_layout=meas_layout - ) - results_least_square = meas_filter.apply( - circuit_results.get_counts(), method="least_squares", meas_layout=meas_layout - ) - - saved_info = { - "cal_results": cal_results.to_dict(), - "results": circuit_results.to_dict(), - "mit_pattern": mit_pattern, - "meas_layout": meas_layout, - "fidelity": meas_cal.readout_fidelity(), - "results_pseudo_inverse": results_pseudo_inverse, - "results_least_square": results_least_square, - } - - saved_info["cal_results"] = Result.from_dict(saved_info["cal_results"]) - saved_info["results"] = Result.from_dict(saved_info["results"]) - - with self.assertWarns(DeprecationWarning): - meas_cal = TensoredMeasFitter( - saved_info["cal_results"], mit_pattern=saved_info["mit_pattern"] - ) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity(0) * meas_cal.readout_fidelity(1) - # Compare with expected fidelity and expected results - - self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0) - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - saved_info["results"].get_counts(0), - method="pseudo_inverse", - meas_layout=saved_info["meas_layout"], - ) - output_results_least_square = meas_filter.apply( - saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"] - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - saved_info["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["000"], - saved_info["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - saved_info["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["111"], - saved_info["results_least_square"]["111"], - places=0, - ) - - substates_list = [] - with self.assertWarns(DeprecationWarning): - for qubit_list in saved_info["mit_pattern"]: - substates_list.append(count_keys(len(qubit_list))[::-1]) - fitter_other_order = TensoredMeasFitter( - saved_info["cal_results"], - substate_labels_list=substates_list, - mit_pattern=saved_info["mit_pattern"], - ) - - fidelity = fitter_other_order.readout_fidelity(0) * meas_cal.readout_fidelity(1) - - self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0) - - with self.assertWarns(DeprecationWarning): - meas_filter = fitter_other_order.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - saved_info["results"].get_counts(0), - method="pseudo_inverse", - meas_layout=saved_info["meas_layout"], - ) - output_results_least_square = meas_filter.apply( - saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"] - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - saved_info["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["000"], - saved_info["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - saved_info["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["111"], - saved_info["results_least_square"]["111"], - places=0, - ) - - -if __name__ == "__main__": - unittest.main() From 4d4604ec68faa246c6c44a08eeb5ca28976aba90 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 24 Nov 2023 13:48:35 +0000 Subject: [PATCH 06/17] Relax CI pin on Numpy < 1.25 (#11314) The release of Numpy 1.26.1 included an upstream patch to fix a bug in the compiled SIMD versions of complex multiplication loops on macOS, and we have fixed the deprecation warnings and new CI flakiness on our side caused by it, so it should now be safe to relax the pin. --- constraints.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/constraints.txt b/constraints.txt index ef4197223893..413e1f94b057 100644 --- a/constraints.txt +++ b/constraints.txt @@ -2,11 +2,6 @@ # 4.0+. The pin can be removed after nbformat is updated. jsonschema==3.2.0 -# Numpy 1.25 deprecated some behaviours that we used, and caused the isometry -# tests to flake. See https://github.com/Qiskit/qiskit-terra/issues/10305, -# remove pin when resolving that. -numpy<1.25; python_version<'3.12' - # Scipy 1.11 seems to have caused an instability in the Weyl coordinates # eigensystem code for one of the test cases. See # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. From 9df8de8505b9b5361ba47e054aecba34784a596e Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 24 Nov 2023 14:49:55 +0100 Subject: [PATCH 07/17] Cleaning up some allow_DeprecationWarning (#11312) --- qiskit/test/base.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/qiskit/test/base.py b/qiskit/test/base.py index 839e15b0191f..1dde885bd67f 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -202,9 +202,6 @@ def setUpClass(cls): warnings.filterwarnings("error", category=DeprecationWarning) allow_DeprecationWarning_modules = [ - "test.python.pulse.test_parameters", - "test.python.pulse.test_transforms", - "test.python.circuit.test_gate_power", "test.python.pulse.test_builder", "test.python.pulse.test_block", "test.python.quantum_info.operators.symplectic.test_legacy_pauli", @@ -218,8 +215,6 @@ def setUpClass(cls): "qiskit.pulse.instructions.play", "qiskit.pulse.library.parametric_pulses", "qiskit.quantum_info.operators.symplectic.pauli", - "test.python.dagcircuit.test_dagcircuit", - "importlib_metadata", ] for mod in allow_DeprecationWarning_modules: warnings.filterwarnings("default", category=DeprecationWarning, module=mod) @@ -228,14 +223,8 @@ def setUpClass(cls): r"The jsonschema validation included in qiskit-terra.*", r"The DerivativeBase.parameter_expression_grad method.*", r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", - r"The CXDirection pass has been deprecated", # Caused by internal scikit-learn scipy usage r"The 'sym_pos' keyword is deprecated and should be replaced by using", - # jupyter_client 7.4.8 uses deprecated shims in pyzmq that raise warnings with pyzmq 25. - # These are due to be fixed by jupyter_client 8, see: - # - https://github.com/jupyter/jupyter_client/issues/913 - # - https://github.com/jupyter/jupyter_client/pull/842 - r"zmq\.eventloop\.ioloop is deprecated in pyzmq .*", ] for msg in allow_DeprecationWarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) From 8b38060c7e8553b52e2909233fb1bedf67d3f3ce Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 24 Nov 2023 13:53:44 +0000 Subject: [PATCH 08/17] Relax CI pin on Aer <0.13 (#11313) Now that Aer 0.13.1 is released, we expect that all the bugs and issues we had Qiskit-side are resolved. This releases the pin on the lower version of Aer to get us testing properly in CI again. --- constraints.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/constraints.txt b/constraints.txt index 413e1f94b057..a879e5945844 100644 --- a/constraints.txt +++ b/constraints.txt @@ -6,8 +6,3 @@ jsonschema==3.2.0 # eigensystem code for one of the test cases. See # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. scipy<1.11; python_version<'3.12' - -# Aer 0.13 causes several randomised tests to begin failing, and some -# `QuantumInstance` use of noise models to raise exceptions. These need fixes -# on Terra. -qiskit-aer==0.12.2 From ba5a1a62f89b5e9f870a64a0b12a019bb38ffe0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 24 Nov 2023 15:46:11 +0100 Subject: [PATCH 09/17] Remove alg_globals and validation (#11252) --- qiskit/utils/__init__.py | 4 - qiskit/utils/algorithm_globals.py | 170 -------------- qiskit/utils/validation.py | 211 ------------------ ...move-algorithm-utils-707648b69af439dc.yaml | 14 ++ 4 files changed, 14 insertions(+), 385 deletions(-) delete mode 100644 qiskit/utils/algorithm_globals.py delete mode 100644 qiskit/utils/validation.py create mode 100644 releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 216d5693aecb..e12d30b5653b 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -36,7 +36,6 @@ .. autofunction:: get_entangler_map .. autofunction:: validate_entangler_map .. autofunction:: name_args -.. autodata:: algorithm_globals Optional Dependency Checkers (:mod:`qiskit.utils.optionals`) @@ -63,8 +62,6 @@ from .circuit_utils import summarize_circuits from .entangler_map import get_entangler_map, validate_entangler_map from .name_unnamed_args import name_args -from .algorithm_globals import algorithm_globals - __all__ = [ "LazyDependencyManager", @@ -74,7 +71,6 @@ "get_entangler_map", "validate_entangler_map", "name_args", - "algorithm_globals", "add_deprecation_to_docstring", "deprecate_arg", "deprecate_arguments", diff --git a/qiskit/utils/algorithm_globals.py b/qiskit/utils/algorithm_globals.py deleted file mode 100644 index eb1dd4f492e0..000000000000 --- a/qiskit/utils/algorithm_globals.py +++ /dev/null @@ -1,170 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Algorithm Globals""" - -from typing import Optional -import logging - -import numpy as np - -from qiskit.tools import parallel -from qiskit.utils.deprecation import deprecate_func -from ..user_config import get_config -from ..exceptions import QiskitError - - -logger = logging.getLogger(__name__) - - -class QiskitAlgorithmGlobals: - """Class for global properties.""" - - CPU_COUNT = parallel.local_hardware_info()["cpus"] - - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - ) - def __init__(self) -> None: - self._random_seed = None # type: Optional[int] - self._num_processes = QiskitAlgorithmGlobals.CPU_COUNT - self._random = None - self._massive = False - try: - settings = get_config() - self.num_processes = settings.get("num_processes", QiskitAlgorithmGlobals.CPU_COUNT) - except Exception as ex: # pylint: disable=broad-except - logger.debug("User Config read error %s", str(ex)) - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random_seed(self) -> Optional[int]: - """Return random seed.""" - return self._random_seed - - @random_seed.setter - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random_seed(self, seed: Optional[int]) -> None: - """Set random seed.""" - self._random_seed = seed - self._random = None - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def num_processes(self) -> int: - """Return num processes.""" - return self._num_processes - - @num_processes.setter - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def num_processes(self, num_processes: Optional[int]) -> None: - """Set num processes. - If 'None' is passed, it resets to QiskitAlgorithmGlobals.CPU_COUNT - """ - if num_processes is None: - num_processes = QiskitAlgorithmGlobals.CPU_COUNT - elif num_processes < 1: - raise QiskitError(f"Invalid Number of Processes {num_processes}.") - elif num_processes > QiskitAlgorithmGlobals.CPU_COUNT: - raise QiskitError( - "Number of Processes {} cannot be greater than cpu count {}.".format( - num_processes, QiskitAlgorithmGlobals.CPU_COUNT - ) - ) - self._num_processes = num_processes - # TODO: change Terra CPU_COUNT until issue - # gets resolved: https://github.com/Qiskit/qiskit-terra/issues/1963 - try: - parallel.CPU_COUNT = self.num_processes - except Exception as ex: # pylint: disable=broad-except - logger.warning( - "Failed to set qiskit.tools.parallel.CPU_COUNT to value: '%s': Error: '%s'", - self.num_processes, - str(ex), - ) - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random(self) -> np.random.Generator: - """Return a numpy np.random.Generator (default_rng).""" - if self._random is None: - self._random = np.random.default_rng(self._random_seed) - return self._random - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def massive(self) -> bool: - """Return massive to allow processing of large matrices or vectors.""" - return self._massive - - @massive.setter - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def massive(self, massive: bool) -> None: - """Set massive to allow processing of large matrices or vectors.""" - self._massive = massive - - -# Global instance to be used as the entry point for globals. -algorithm_globals = QiskitAlgorithmGlobals() diff --git a/qiskit/utils/validation.py b/qiskit/utils/validation.py deleted file mode 100644 index 0d3d59340aae..000000000000 --- a/qiskit/utils/validation.py +++ /dev/null @@ -1,211 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Validation module -""" - -from typing import Set -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_in_set(name: str, value: object, values: Set[object]) -> None: - """ - Args: - name: value name. - value: value to check. - values: set that should contain value. - Raises: - ValueError: invalid value - """ - if value not in values: - raise ValueError(f"{name} must be one of '{values}', was '{value}'.") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_min(name: str, value: float, minimum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum: - raise ValueError(f"{name} must have value >= {minimum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_min_exclusive(name: str, value: float, minimum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum: - raise ValueError(f"{name} must have value > {minimum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_max(name: str, value: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value > maximum: - raise ValueError(f"{name} must have value <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_max_exclusive(name: str, value: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value >= maximum: - raise ValueError(f"{name} must have value < {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum or value > maximum: - raise ValueError(f"{name} must have value >= {minimum} and <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum or value >= maximum: - raise ValueError(f"{name} must have value > {minimum} and < {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive_min(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum or value > maximum: - raise ValueError(f"{name} must have value > {minimum} and <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive_max(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum or value >= maximum: - raise ValueError(f"{name} must have value >= {minimum} and < {maximum}, was {value}") diff --git a/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml new file mode 100644 index 000000000000..3e0dd0863026 --- /dev/null +++ b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + The following algorithm utilities in :mod:`qiskit.utils` have been removed + from the codebase: + + * ``algorithm_globals`` + * ``qiskit.utils.validation`` + + They were deprecated in Qiskit 0.45 as a consequence of the migration + of ``qiskit.algorithms`` to a standalone + `package `_, where + these utils have also been migrated. The can be found in the new package + under ``qiskit_algorithms.utils``. From 09ccfcfa9c8967708444e1cafb8a467fa5a8f0ad Mon Sep 17 00:00:00 2001 From: "Duong H. D" <79622476+gluonhiggs@users.noreply.github.com> Date: Fri, 24 Nov 2023 22:06:44 +0700 Subject: [PATCH 10/17] Remove links to qiskit.org in API docs (#11294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove links to qiskit.org in API docs * remove release note and fix cross reference * Update qiskit/transpiler/passes/basis/basis_translator.py Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * use direct link of translation_errors --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Elena Peña Tapia --- qiskit/circuit/library/phase_estimation.py | 3 ++- qiskit/circuit/quantumcircuit.py | 2 +- qiskit/qasm3/__init__.py | 2 +- qiskit/qpy/__init__.py | 2 +- qiskit/transpiler/passes/basis/basis_translator.py | 8 ++++---- .../passes/scheduling/alignments/pulse_gate_validation.py | 2 +- qiskit/utils/optionals.py | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/phase_estimation.py b/qiskit/circuit/library/phase_estimation.py index 453afb4d19e7..ab23ee2218ab 100644 --- a/qiskit/circuit/library/phase_estimation.py +++ b/qiskit/circuit/library/phase_estimation.py @@ -44,7 +44,8 @@ class PhaseEstimation(QuantumCircuit): Cambridge University Press, New York, NY, USA. [3]: Qiskit - `textbook `_ + `textbook `_ """ diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index d968d3dc1cc4..4d406daf8189 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1005,7 +1005,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu Remember that in the little-endian convention the leftmost operation will be at the bottom of the circuit. See also - `the docs `__ + `the docs `__ for more information. .. parsed-literal:: diff --git a/qiskit/qasm3/__init__.py b/qiskit/qasm3/__init__.py index 36bb4dcc2843..8e3de893ea3c 100644 --- a/qiskit/qasm3/__init__.py +++ b/qiskit/qasm3/__init__.py @@ -83,7 +83,7 @@ All features enabled by the experimental flags are naturally transient. If it becomes necessary to remove flags, they will be subject to `the standard Qiskit deprecation policy - `__. We will leave these experimental + `__. We will leave these experimental flags in place for as long as is reasonable. However, we cannot guarantee any support windows for *consumers* of OpenQASM 3 code generated diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 6d09bdf98875..e16894b3e35d 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -480,7 +480,7 @@ With the support of :class:`.~ScheduleBlock`, now :class:`~.QuantumCircuit` can be serialized together with :attr:`~.QuantumCircuit.calibrations`, or -`Pulse Gates `_. +`Pulse Gates `_. In QPY version 5 and above, :ref:`qpy_circuit_calibrations` payload is packed after the :ref:`qpy_instructions` block. diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 3033ab398cfa..aed57ee34206 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -202,8 +202,8 @@ def run(self, dag): "target basis is not universal or there are additional equivalence rules " "needed in the EquivalenceLibrary being used. For more details on this " "error see: " - "https://qiskit.org/documentation/stubs/qiskit.transpiler.passes." - "BasisTranslator.html#translation_errors" + "https://docs.quantum-computing.ibm.com/api/qiskit/qiskit.transpiler.passes." + "BasisTranslator#translation-errors" ) qarg_local_basis_transforms[qarg] = local_basis_transforms @@ -220,8 +220,8 @@ def run(self, dag): f"basis: {list(target_basis)}. This likely means the target basis is not universal " "or there are additional equivalence rules needed in the EquivalenceLibrary being " "used. For more details on this error see: " - "https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.BasisTranslator." - "html#translation_errors" + "https://docs.quantum-computing.ibm.com/api/qiskit/qiskit.transpiler.passes." + "BasisTranslator#translation-errors" ) # Compose found path into a set of instruction substitution rules. diff --git a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py index 697d36fa71b6..e4bcdecbb666 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +++ b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py @@ -30,7 +30,7 @@ class ValidatePulseGates(AnalysisPass): In Qiskit SDK, we can define the pulse-level implementation of custom quantum gate instructions, as a `pulse gate - `__, + `__, thus user gates should satisfy all waveform memory constraints imposed by the backend. This pass validates all attached calibration entries and raises ``TranspilerError`` to diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index 0321a1cbc7e5..07dc6e4376e5 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -103,7 +103,7 @@ `__ library as a core dependency, and during the change-over period, it was sometimes convenient to convert things into the Python-only `NetworkX `__ format. Some tests of application modules, such as - `Qiskit Nature `__ still use NetworkX. + `Qiskit Nature `__ still use NetworkX. * - .. py:data:: HAS_NLOPT - `NLopt `__ is a nonlinear optimization library, From 31c2089a1ec764910b9b4eafdbbbf03b4eaa7b8f Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Fri, 24 Nov 2023 22:36:25 +0200 Subject: [PATCH 11/17] Fix deprecation policy link in `CONTRIBUTING.md` (#11276) * Fix deprecation policy link * Update CONTRIBUTING.md Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87b434413449..e50d61f73c76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -651,7 +651,7 @@ def test_method2(self): self.assertEqual(result, ) ``` -`test_method1_deprecated` can be removed after `Obj.method1` is removed (following the [Qiskit Deprecation Policy](https://qiskit.org/documentation/contributing_to_qiskit.html#deprecation-policy)). +`test_method1_deprecated` can be removed after `Obj.method1` is removed (following the [Qiskit Deprecation Policy](./DEPRECATION.md)). ## Using dependencies From 89642bd1bda1a26aaa3527304cad3d25c8731925 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 27 Nov 2023 10:26:33 +0000 Subject: [PATCH 12/17] Remove unnecessary installation of build requirements (#11246) Since we are using `build`, a basic PEP 517 builder, we do not need to manually install the build requirements; our `pyproject.toml` specifies them, and they'll be automatically pulled in. With the pivot to PyPI trusted publishers in gh-10999, we no longer use `twine` manually either. --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 41b15d89653b..74f4f4a63daf 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -185,7 +185,7 @@ jobs: with: python-version: '3.10' - name: Install deps - run: pip install -U twine setuptools-rust wheel build + run: pip install -U build - name: Build sdist run: python -m build . --sdist - name: Publish package distributions to PyPI @@ -208,7 +208,7 @@ jobs: with: python-version: '3.10' - name: Install deps - run: pip install -U twine setuptools-rust wheel build + run: pip install -U build - name: Build packages run: | set -e From d3b08adf692f54b70b809264d3798ab4b81527ff Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Mon, 27 Nov 2023 13:00:45 -0500 Subject: [PATCH 13/17] more transpiler docs improvements (#11300) * more transpiler docs improvements * Update qiskit/transpiler/__init__.py Co-authored-by: Matthew Treinish * use double ticks and remove extra line of code --------- Co-authored-by: Matthew Treinish --- qiskit/transpiler/__init__.py | 43 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index b91662c26f82..d6ce7b31ca4c 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -105,28 +105,35 @@ .. code-block:: python - from qiskit.circuit.library import XGate, HGate, RXGate, PhaseGate, TGate, TdgGate + import numpy as np + from qiskit.circuit.library import HGate, PhaseGate, RXGate, TdgGate, TGate, XGate from qiskit.transpiler import PassManager - from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling - from qiskit.transpiler.passes import CXCancellation, InverseCancellation + from qiskit.transpiler.passes import ( + ALAPScheduleAnalysis, + CXCancellation, + InverseCancellation, + PadDynamicalDecoupling, + ) - backend_durations = backend.target.durations() dd_sequence = [XGate(), XGate()] - scheduling_pm = PassManager([ - ALAPScheduleAnalysis(backend_durations), - PadDynamicalDecoupling(backend_durations, dd_sequence), - ]) + scheduling_pm = PassManager( + [ + ALAPScheduleAnalysis(target=backend.target), + PadDynamicalDecoupling(target=backend.target, dd_sequence=dd_sequence), + ] + ) inverse_gate_list = [ HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)), (PhaseGate(np.pi / 4), PhaseGate(-np.pi / 4)), (TGate(), TdgGate()), - - ]) - logical_opt = PassManager([ - CXCancellation(), - InverseCancellation([HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)) - ]) + ] + logical_opt = PassManager( + [ + CXCancellation(), + InverseCancellation(inverse_gate_list), + ] + ) # Add pre-layout stage to run extra logical optimization @@ -134,11 +141,9 @@ # Set scheduling stage to custom pass manager pass_manager.scheduling = scheduling_pm - -Then when :meth:`~.StagedPassManager.run` is called on ``pass_manager`` the -``logical_opt`` :class:`~.PassManager` will be called prior to the ``layout`` stage -and for the ``scheduling`` stage our custom :class:`~.PassManager` -``scheduling_pm`` will be used. +Now, when the staged pass manager is run via the :meth:`~.StagedPassManager.run` method, +the ``logical_opt`` pass manager will be called before the ``layout`` stage, and the +``scheduling_pm`` pass manager will be used for the ``scheduling`` stage instead of the default. Custom Pass Managers ==================== From 5bbda4a263121671f0a277d29487cc0544987f19 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 27 Nov 2023 18:14:19 +0000 Subject: [PATCH 14/17] Update `Instruction.condition_bits` for runtime classical expressions (#11325) I didn't even know this property existed and it wasn't tested directly, but the IBM provider uses it during its custom scheduling passes, so until removal, it should be kept updated. --- qiskit/circuit/instruction.py | 7 ++- ...ction-condition-bits-17694f98628b30ad.yaml | 5 +++ test/python/circuit/test_instructions.py | 44 ++++++++++++++----- 3 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 84e3f344bf07..9fbe77a04f82 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -618,12 +618,11 @@ def repeat(self, n): @property def condition_bits(self) -> List[Clbit]: """Get Clbits in condition.""" + from qiskit.circuit.controlflow import condition_resources # pylint: disable=cyclic-import + if self.condition is None: return [] - if isinstance(self.condition[0], Clbit): - return [self.condition[0]] - else: # ClassicalRegister - return list(self.condition[0]) + return list(condition_resources(self.condition).clbits) @property def name(self): diff --git a/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml b/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml new file mode 100644 index 000000000000..4f044b6a468f --- /dev/null +++ b/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The property :attr:`.Instruction.condition_bits` will now correctly handle runtime classical + expressions (:mod:`qiskit.circuit.classical`). diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index fc60be60fdc1..d111d2d21181 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -18,16 +18,20 @@ import numpy as np -from qiskit.circuit import Gate -from qiskit.circuit import Parameter -from qiskit.circuit import Instruction, InstructionSet -from qiskit.circuit import QuantumCircuit -from qiskit.circuit import QuantumRegister, ClassicalRegister, Qubit, Clbit -from qiskit.circuit.library.standard_gates.h import HGate -from qiskit.circuit.library.standard_gates.rz import RZGate -from qiskit.circuit.library.standard_gates.x import CXGate -from qiskit.circuit.library.standard_gates.s import SGate -from qiskit.circuit.library.standard_gates.t import TGate +from qiskit.circuit import ( + Gate, + Parameter, + Instruction, + InstructionSet, + QuantumCircuit, + QuantumRegister, + ClassicalRegister, + Qubit, + Clbit, + IfElseOp, +) +from qiskit.circuit.library import HGate, RZGate, CXGate, SGate, TGate +from qiskit.circuit.classical import expr from qiskit.test import QiskitTestCase from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.random import random_circuit @@ -426,6 +430,26 @@ def test_repr_of_instructions(self): ), ) + def test_instruction_condition_bits(self): + """Test that the ``condition_bits`` property behaves correctly until it is deprecated and + removed.""" + bits = [Clbit(), Clbit()] + cr1 = ClassicalRegister(2, "cr1") + cr2 = ClassicalRegister(2, "cr2") + body = QuantumCircuit(cr1, cr2, bits) + + def key(bit): + return body.find_bit(bit).index + + op = IfElseOp((bits[0], False), body) + self.assertEqual(op.condition_bits, [bits[0]]) + + op = IfElseOp((cr1, 3), body) + self.assertEqual(op.condition_bits, list(cr1)) + + op = IfElseOp(expr.logic_and(bits[1], expr.equal(cr2, 3)), body) + self.assertEqual(sorted(op.condition_bits, key=key), sorted([bits[1]] + list(cr2), key=key)) + def test_instructionset_c_if_direct_resource(self): """Test that using :meth:`.InstructionSet.c_if` with an exact classical resource always works, and produces the expected condition.""" From e91947e1b55f6e661c683fba72ab66c5f92d3aec Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 27 Nov 2023 20:54:38 +0000 Subject: [PATCH 15/17] Replace `qiskit` metapackage with `qiskit-terra` (#11271) * Replace `qiskit` metapackage with `qiskit-terra` This commit completely removes the concept of the `qiskit-terra` package from Qiskit main. The hitherto metapackage `qiskit` is promoted to be the concrete code package. This is a completely breaking change for packaging purposes for users, as there is no clean upgrade path from `qiskit-terra` to `qiskit`; PyPI and pip do not give us the tools to mark that the former obsoletes and supersedes the latter. We intend to follow this PR with a technical blog post somewhere explaining the changes, how users should adapt ("to install Qiskit 1.0, you must start a new venv"), and why we needed to do this. The "why", in part, is: - the metapackage legacy causes awkward upgrade paths on every release; some packages still depend on `qiskit-terra`, some on `qiskit`, and pip will happily make those two get out-of-sync when upgrading a transitive dependency with only a warning that users are used to ignoring. - with the 1.0 release, we (believe we) will have some leeway from users to make such a breaking change. - having the metapackage split makes it difficult for downstream projects and developers to test against `main` - they always must install both `qiskit-terra` and `qiskit`, and the latter has no meaning in editable installs, so needs re-installing after each version bump. Problems surrounding this have already broken CI for Qiskit, for at least one internal IBM repo, and at least my dev install of Qiskit. This could be improved a bit with more education, but it's still always going to increase the barrier to entry and make it much harder to do the right thing. We will not publish any 1.0 or above version of `qiskit-terra`. All dependents on Qiskit should switch their requirements to `qiskit`. * Add manual uninstall for Neko * Fix Windows paths Co-authored-by: Matthew Treinish * Refer to stdlib documentation for odd shells --------- Co-authored-by: Matthew Treinish --- .azure/lint-linux.yml | 5 +- .azure/test-linux.yml | 4 +- .azure/test-macos.yml | 1 - .azure/test-windows.yml | 1 - .github/workflows/coverage.yml | 2 +- .github/workflows/neko.yml | 4 +- .github/workflows/slow.yml | 2 +- .github/workflows/wheels.yml | 30 +------- qiskit_pkg/LICENSE.txt | 1 - qiskit_pkg/MANIFEST.in | 1 - qiskit_pkg/README.md | 1 - qiskit_pkg/setup.py | 74 ------------------- .../notes/terra-nullius-7ef598626d8118c1.yaml | 30 ++++++++ setup.py | 2 +- tools/deploy_translatable_strings.sh | 5 +- tox.ini | 16 ++-- 16 files changed, 48 insertions(+), 131 deletions(-) delete mode 120000 qiskit_pkg/LICENSE.txt delete mode 100644 qiskit_pkg/MANIFEST.in delete mode 120000 qiskit_pkg/README.md delete mode 100644 qiskit_pkg/setup.py create mode 100644 releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml diff --git a/.azure/lint-linux.yml b/.azure/lint-linux.yml index 5855fe1e6e67..a1809bd829bd 100644 --- a/.azure/lint-linux.yml +++ b/.azure/lint-linux.yml @@ -26,7 +26,6 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - ./qiskit_pkg \ -e . # Build and install both qiskit and qiskit-terra so that any optionals # depending on `qiskit` will resolve correctly. @@ -38,7 +37,7 @@ jobs: set -e source test-job/bin/activate echo "Running black, any errors reported can be fixed with 'tox -eblack'" - black --check qiskit test tools examples setup.py qiskit_pkg + black --check qiskit test tools examples setup.py echo "Running rustfmt check, any errors reported can be fixed with 'cargo fmt'" cargo fmt --check displayName: "Formatting" @@ -47,7 +46,7 @@ jobs: set -e source test-job/bin/activate echo "Running ruff" - ruff qiskit test tools examples setup.py qiskit_pkg/setup.py + ruff qiskit test tools examples setup.py echo "Running pylint" pylint -rn qiskit test tools echo "Running Cargo Clippy" diff --git a/.azure/test-linux.yml b/.azure/test-linux.yml index eb456f8497ad..512e1d773c35 100644 --- a/.azure/test-linux.yml +++ b/.azure/test-linux.yml @@ -74,12 +74,11 @@ jobs: python -m pip install -U pip python -m pip install -U build python -m build --sdist . - python -m build --sdist qiskit_pkg python -m pip install -U \ -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - dist/qiskit*.tar.gz + dist/qiskit-*.tar.gz # Build and install both qiskit and qiskit-terra so that any optionals # depending on `qiskit` will resolve correctly. displayName: "Install Terra from sdist" @@ -92,7 +91,6 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - ./qiskit_pkg \ -e . # Build and install both qiskit and qiskit-terra so that any optionals # depending on `qiskit` will resolve correctly. diff --git a/.azure/test-macos.yml b/.azure/test-macos.yml index cbf2fc8b0e08..c241f51f57e9 100644 --- a/.azure/test-macos.yml +++ b/.azure/test-macos.yml @@ -43,7 +43,6 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - ./qiskit_pkg \ -e . # Build and install both qiskit and qiskit-terra so that any optionals # depending on `qiskit` will resolve correctly. diff --git a/.azure/test-windows.yml b/.azure/test-windows.yml index 6ba2e442946c..f546fdb41dc3 100644 --- a/.azure/test-windows.yml +++ b/.azure/test-windows.yml @@ -42,7 +42,6 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - ./qiskit_pkg \ -e . # Build and install both qiskit and qiskit-terra so that any optionals # depending on `qiskit` will resolve correctly. diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 221840b4e7c6..de3485c5fdce 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -42,7 +42,7 @@ jobs: run: python -m pip install -c constraints.txt --upgrade pip setuptools wheel - name: Build and install qiskit-terra - run: python -m pip install -c constraints.txt -e . ./qiskit_pkg + run: python -m pip install -c constraints.txt -e . env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Cinstrument-coverage" diff --git a/.github/workflows/neko.yml b/.github/workflows/neko.yml index bda60fd39790..30921c8b051b 100644 --- a/.github/workflows/neko.yml +++ b/.github/workflows/neko.yml @@ -16,4 +16,6 @@ jobs: - uses: Qiskit/qiskit-neko@main with: test_selection: terra - repo_install_command: "pip install -c constraints.txt ." + # We have to forcibly uninstall any old version of qiskit or qiskit-terra during the + # changeover, because it's not possible to safely upgrade an existing installation to 1.0. + repo_install_command: "pip uninstall qiskit qiskit-terra && pip install -c constraints.txt ." diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 528305740d95..62fa9ec19c62 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -19,7 +19,7 @@ jobs: python -m pip install -U pip setuptools wheel python -m pip install -U -r requirements.txt -c constraints.txt python -m pip install -U -r requirements-dev.txt -c constraints.txt - python -m pip install -c constraints.txt -e . ./qiskit_pkg + python -m pip install -c constraints.txt -e . python -m pip install "qiskit-aer" "z3-solver" "cplex" -c constraints.txt env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 74f4f4a63daf..99a46a97b63e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -168,7 +168,7 @@ jobs: with: packages-dir: wheelhouse/ sdist: - name: Build and publish terra sdist + name: Build and publish sdist runs-on: ${{ matrix.os }} needs: ["upload_shared_wheels"] environment: release @@ -190,31 +190,3 @@ jobs: run: python -m build . --sdist - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - metapackage: - name: Build and publish terra sdist - runs-on: ${{ matrix.os }} - needs: ["sdist"] - environment: release - permissions: - id-token: write - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: '3.10' - - name: Install deps - run: pip install -U build - - name: Build packages - run: | - set -e - cd qiskit_pkg - python -m build . - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: qiskit_pkg/dist diff --git a/qiskit_pkg/LICENSE.txt b/qiskit_pkg/LICENSE.txt deleted file mode 120000 index 4ab43736a839..000000000000 --- a/qiskit_pkg/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.txt \ No newline at end of file diff --git a/qiskit_pkg/MANIFEST.in b/qiskit_pkg/MANIFEST.in deleted file mode 100644 index 42eb4101e514..000000000000 --- a/qiskit_pkg/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include LICENSE.txt diff --git a/qiskit_pkg/README.md b/qiskit_pkg/README.md deleted file mode 120000 index 32d46ee883b5..000000000000 --- a/qiskit_pkg/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/qiskit_pkg/setup.py b/qiskit_pkg/setup.py deleted file mode 100644 index ecff044d2ac5..000000000000 --- a/qiskit_pkg/setup.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This file is the setup.py file for the qiskit package. Because python -# packaging doesn't offer a mechanism to have qiskit supersede qiskit-terra -# and cleanly upgrade from one to the other, there needs to be a separate -# package shim to ensure no matter how people installed qiskit < 0.45.0 the -# upgrade works. - -import os - -from setuptools import setup - -README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md") -with open(README_PATH) as readme_file: - README = readme_file.read() - -requirements = ["qiskit-terra==1.0.0"] - -setup( - name="qiskit", - version="1.0.0", - description="Software for developing quantum computing programs", - long_description=README, - long_description_content_type="text/markdown", - url="https://qiskit.org/", - author="Qiskit Development Team", - author_email="hello@qiskit.org", - license="Apache 2.0", - py_modules=[], - packages=[], - classifiers=[ - "Environment :: Console", - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Scientific/Engineering", - ], - keywords="qiskit sdk quantum", - install_requires=requirements, - project_urls={ - "Bug Tracker": "https://github.com/Qiskit/qiskit/issues", - "Documentation": "https://qiskit.org/documentation/", - "Source Code": "https://github.com/Qiskit/qiskit", - }, - include_package_data=True, - python_requires=">=3.8", - extras_require={ - "qasm3-import": ["qiskit-terra[qasm3-import]"], - "visualization": ["qiskit-terra[visualization]"], - "crosstalk-pass": ["qiskit-terra[crosstalk-pass]"], - "csp-layout-pass": ["qiskit-terra[csp-layout-pass]"], - "all": ["qiskit-terra[all]"], - }, -) diff --git a/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml b/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml new file mode 100644 index 000000000000..562929a17b9f --- /dev/null +++ b/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml @@ -0,0 +1,30 @@ +--- +critical: + - | + You cannot upgrade in place to Qiskit 1.0. You must begin a new virtual environment. + + From Qiskit 1.0, Qiskit is comprised of exactly one Python package: ``qiskit``. Previously, + as a legacy of the "component elements" of early Qiskit, the ``qiskit`` package was a + dependency-only "metapackage", and the core code of Qiskit was in a package called ``qiskit-terra``. + As Qiskit grew, the other elements split off into their own packages (such as ``qiskit-aer``) + until only the core was left in the metapackage. For Qiskit 1.0, we are removing the metapackage + entirely, and replacing it with the actual Qiskit code. + + This means that you cannot upgrade an existing installation to Qiskit 1.0. Instead, you must + create a new Python virtual environment. Using the built-in ``venv`` module, you can do (Linux + and Mac): + + .. code-block:: bash + + # Create the new environment (only once). + python -m venv ~/qiskit-1.0-venv + # Activate the environment (every session). + source ~/qiskit-1.0-venv/bin/activate + # Install Qiskit (only once). + pip install 'qiskit>=1.0' + + For other platforms, or more unusual shells, refer to `the Python standard-library documentation + on activating virtual environments `__. + + If you are a library author, or have code that depends on Qiskit, you should update any old + dependencies on ``qiskit-terra`` to instead depend on ``qiskit``. diff --git a/setup.py b/setup.py index 751a151f21fc..55950efef410 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ setup( - name="qiskit-terra", + name="qiskit", version="1.0.0", description="Software for developing quantum computing programs", long_description=README, diff --git a/tools/deploy_translatable_strings.sh b/tools/deploy_translatable_strings.sh index afac6869447d..1ae60b5f7072 100755 --- a/tools/deploy_translatable_strings.sh +++ b/tools/deploy_translatable_strings.sh @@ -84,8 +84,9 @@ rm -rf \ echo "+ 'cp' wanted files from source to target" # Copy the new rendered files and add them to the commit. cp -r "$SOURCE_PO_DIR/." "$TARGET_PO_DIR" -# Copy files necessary to build the Qiskit metapackage. -cp "$SOURCE_REPO_ROOT/qiskit_pkg/setup.py" "${TARGET_REPO_ROOT}" +# Copy files necessary to build the Qiskit package. +cp "$SOURCE_REPO_ROOT/setup.py" "${TARGET_REPO_ROOT}" +cp "$SOURCE_REPO_ROOT/pyproject.toml" "${TARGET_REPO_ROOT}" cat "$SOURCE_REPO_ROOT/requirements-dev.txt" "$SOURCE_REPO_ROOT/requirements-optional.txt" \ > "${TARGET_REPO_ROOT}/requirements-dev.txt" cp "$SOURCE_REPO_ROOT/constraints.txt" "${TARGET_REPO_ROOT}" diff --git a/tox.ini b/tox.ini index 5c517b1d2cc9..df46820fc026 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,6 @@ envlist = py38, py39, py310, py311, py312, lint-incr isolated_build = true [testenv] -# We pretend that we're not actually installing the package, because we need tox to let us have two -# packages ('qiskit' and 'qiskit-terra') under test at the same time. For that, we have to stuff -# them into 'deps'. -skip_install = true install_command = pip install -c{toxinidir}/constraints.txt -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} @@ -22,16 +18,14 @@ deps = setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt - -e . - -e ./qiskit_pkg commands = stestr run {posargs} [testenv:lint] basepython = python3 commands = - ruff check qiskit test tools examples setup.py qiskit_pkg - black --check {posargs} qiskit test tools examples setup.py qiskit_pkg + ruff check qiskit test tools examples setup.py + black --check {posargs} qiskit test tools examples setup.py pylint -rn qiskit test tools # This line is commented out until #6649 merges. We can't run this currently # via tox because tox doesn't support globbing @@ -45,8 +39,8 @@ commands = basepython = python3 allowlist_externals = git commands = - ruff check qiskit test tools examples setup.py qiskit_pkg - black --check {posargs} qiskit test tools examples setup.py qiskit_pkg + ruff check qiskit test tools examples setup.py + black --check {posargs} qiskit test tools examples setup.py -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --disable='invalid-name,missing-module-docstring,redefined-outer-name' --paths :(glob,top)examples/python/*.py @@ -59,7 +53,7 @@ commands = skip_install = true deps = -r requirements-dev.txt -commands = black {posargs} qiskit test tools examples setup.py qiskit_pkg +commands = black {posargs} qiskit test tools examples setup.py [testenv:coverage] basepython = python3 From 7f809a9ff8545bf0dc7e22f0c0baed042e2105ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:04:22 +0100 Subject: [PATCH 16/17] Remove leftover algorithm/opflow utils (#11324) * Remove utils * Update reno * Fix test, reno * Update reno --- qiskit/utils/__init__.py | 16 -- qiskit/utils/arithmetic.py | 152 ------------------ qiskit/utils/circuit_utils.py | 69 -------- qiskit/utils/entangler_map.py | 111 ------------- qiskit/utils/name_unnamed_args.py | 73 --------- ...move-algorithm-utils-707648b69af439dc.yaml | 23 ++- test/python/test_util.py | 15 +- 7 files changed, 19 insertions(+), 440 deletions(-) delete mode 100644 qiskit/utils/arithmetic.py delete mode 100644 qiskit/utils/circuit_utils.py delete mode 100644 qiskit/utils/entangler_map.py delete mode 100644 qiskit/utils/name_unnamed_args.py diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index e12d30b5653b..a9b73b85f95d 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -29,14 +29,6 @@ .. autofunction:: detach_prefix .. autofunction:: wrap_method -Algorithm Utilities -=================== - -.. autofunction:: summarize_circuits -.. autofunction:: get_entangler_map -.. autofunction:: validate_entangler_map -.. autofunction:: name_args - Optional Dependency Checkers (:mod:`qiskit.utils.optionals`) ============================================================ @@ -59,18 +51,10 @@ from . import optionals -from .circuit_utils import summarize_circuits -from .entangler_map import get_entangler_map, validate_entangler_map -from .name_unnamed_args import name_args - __all__ = [ "LazyDependencyManager", "LazyImportTester", "LazySubprocessTester", - "summarize_circuits", - "get_entangler_map", - "validate_entangler_map", - "name_args", "add_deprecation_to_docstring", "deprecate_arg", "deprecate_arguments", diff --git a/qiskit/utils/arithmetic.py b/qiskit/utils/arithmetic.py deleted file mode 100644 index 23a838721f9e..000000000000 --- a/qiskit/utils/arithmetic.py +++ /dev/null @@ -1,152 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Arithmetic Utilities -""" - -from typing import List, Tuple -import numpy as np - - -def normalize_vector(vector): - """ - Normalize the input state vector. - """ - return vector / np.linalg.norm(vector) - - -def is_power_of_2(num): - """ - Check if the input number is a power of 2. - """ - return num != 0 and ((num & (num - 1)) == 0) - - -def log2(num): - """ - Compute the log2 of the input number. Use bit operation if the input is a power of 2. - """ - if is_power_of_2(num): - ret = 0 - while True: - if num >> ret == 1: - return ret - else: - ret += 1 - else: - return np.log2(num) - - -def is_power(num, return_decomposition=False): - """ - Check if num is a perfect power in O(n^3) time, n=ceil(logN) - """ - b = 2 - while (2**b) <= num: - a = 1 - c = num - while (c - a) >= 2: - m = int((a + c) / 2) - - if (m**b) < (num + 1): - p = int(m**b) - else: - p = int(num + 1) - - if int(p) == int(num): - if return_decomposition: - return True, int(m), int(b) - else: - return True - - if p < num: - a = int(m) - else: - c = int(m) - b = b + 1 - if return_decomposition: - return False, num, 1 - else: - return False - - -def next_power_of_2_base(n): - """ - Return the base of the smallest power of 2 no less than the input number - """ - base = 0 - if n and not (n & (n - 1)): # pylint: disable=superfluous-parens - return log2(n) - - while n != 0: - n >>= 1 - base += 1 - - return base - - -def transpositions(permutation: List[int]) -> List[Tuple[int, int]]: - """Return a sequence of transpositions, corresponding to the permutation. - - Args: - permutation: The ``List[int]`` defining the permutation. An element at index ``j`` should be - permuted to index ``permutation[j]``. - - Returns: - List of transpositions, corresponding to the permutation. For permutation = [3, 0, 2, 1], - returns [(0,1), (0,3)] - """ - unchecked = [True] * len(permutation) - cyclic_form = [] - for i in range(len(permutation)): - if unchecked[i]: - cycle = [i] - unchecked[i] = False - j = i - while unchecked[permutation[j]]: - j = permutation[j] - cycle.append(j) - unchecked[j] = False - if len(cycle) > 1: - cyclic_form.append(cycle) - cyclic_form.sort() - res = [] - for x in cyclic_form: - len_x = len(x) - if len_x == 2: - res.append((x[0], x[1])) - elif len_x > 2: - first = x[0] - for y in x[len_x - 1 : 0 : -1]: - res.append((first, y)) - return res - - -def triu_to_dense(triu: np.ndarray) -> np.ndarray: - """Converts upper triangular part of matrix to dense matrix. - - Args: - triu: array in the form [[A, B, C], [D, E], [F]] - - Returns: - Array [[A, B, C], [B, D, E], [C, E, F]] - """ - dim = len(triu) - matrix = np.empty((dim, dim), dtype=complex) - for i in range(dim): - for j in range(dim - i): - matrix[i, i + j] = triu[i][j] - if j != 0: - matrix[i + j, i] = triu[i][j] - - return matrix diff --git a/qiskit/utils/circuit_utils.py b/qiskit/utils/circuit_utils.py deleted file mode 100644 index 2fe140d3780d..000000000000 --- a/qiskit/utils/circuit_utils.py +++ /dev/null @@ -1,69 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Circuit utility functions""" - -import numpy as np - - -def summarize_circuits(circuits): - """Summarize circuits based on QuantumCircuit, and five metrics are summarized. - - Number of qubits - - Number of classical bits - - Number of operations - - Depth of circuits - - Counts of different gate operations - - The average statistic of the first four is provided if multiple circuits are provided. - - Args: - circuits (QuantumCircuit or [QuantumCircuit]): the to-be-summarized circuits - - Returns: - str: a formatted string records the summary - """ - if not isinstance(circuits, list): - circuits = [circuits] - ret = "" - ret += f"Submitting {len(circuits)} circuits.\n" - ret += "============================================================================\n" - stats = np.zeros(4) - for i, circuit in enumerate(circuits): - depth = circuit.depth() - size = circuit.size() - num_qubits = sum(reg.size for reg in circuit.qregs) - num_clbits = sum(reg.size for reg in circuit.cregs) - op_counts = circuit.count_ops() - stats[0] += num_qubits - stats[1] += num_clbits - stats[2] += size - stats[3] += depth - ret = "".join( - [ - ret, - "{}-th circuit: {} qubits, {} classical bits and {} " - "operations with depth {}\nop_counts: {}\n".format( - i, num_qubits, num_clbits, size, depth, op_counts - ), - ] - ) - if len(circuits) > 1: - stats /= len(circuits) - ret = "".join( - [ - ret, - "Average: {:.2f} qubits, {:.2f} classical bits and {:.2f} " - "operations with depth {:.2f}\n".format(stats[0], stats[1], stats[2], stats[3]), - ] - ) - ret += "============================================================================\n" - return ret diff --git a/qiskit/utils/entangler_map.py b/qiskit/utils/entangler_map.py deleted file mode 100644 index 1cd750398ccb..000000000000 --- a/qiskit/utils/entangler_map.py +++ /dev/null @@ -1,111 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -This module contains the definition of creating and validating entangler map -based on the number of qubits. -""" - - -def get_entangler_map(map_type, num_qubits, offset=0): - """Utility method to get an entangler map among qubits. - - Args: - map_type (str): 'full' entangles each qubit with all the subsequent ones - 'linear' entangles each qubit with the next - 'sca' (shifted circular alternating entanglement) is a - circular entanglement where the 'long' entanglement is - shifted by one position every block and every block the - role or control/target qubits alternate - num_qubits (int): Number of qubits for which the map is needed - offset (int): Some map_types (e.g. 'sca') can shift the gates in - the entangler map by the specified integer offset. - - Returns: - list: A map of qubit index to an array of indexes to which this should be entangled - - Raises: - ValueError: if map_type is not valid. - """ - ret = [] - - if num_qubits > 1: - if map_type == "full": - ret = [[i, j] for i in range(num_qubits) for j in range(i + 1, num_qubits)] - elif map_type == "linear": - ret = [[i, i + 1] for i in range(num_qubits - 1)] - elif map_type == "sca": - offset_idx = offset % num_qubits - if offset_idx % 2 == 0: # even block numbers - for i in reversed(range(offset_idx)): - ret += [[i, i + 1]] - - ret += [[num_qubits - 1, 0]] - - for i in reversed(range(offset_idx + 1, num_qubits)): - ret += [[i - 1, i]] - - else: # odd block numbers - for i in range(num_qubits - offset_idx - 1, num_qubits - 1): - ret += [[i + 1, i]] - - ret += [[0, num_qubits - 1]] - - for i in range(num_qubits - offset_idx - 1): - ret += [[i + 1, i]] - else: - raise ValueError("map_type only supports 'full', 'linear' or 'sca' type.") - return ret - - -def validate_entangler_map(entangler_map, num_qubits, allow_double_entanglement=False): - """Validate a user supplied entangler map and converts entries to ints. - - Args: - entangler_map (list[list]) : An entangler map, keys are source qubit index (int), - value is array - of target qubit index(es) (int) - num_qubits (int) : Number of qubits - allow_double_entanglement (bool): If we allow in two qubits can be entangled each other - - Returns: - list: Validated/converted map - - Raises: - TypeError: entangler map is not list type or list of list - ValueError: the index of entangler map is out of range - ValueError: the qubits are cross-entangled. - - """ - - if isinstance(entangler_map, dict): - raise TypeError("The type of entangler map is changed to list of list.") - - if not isinstance(entangler_map, list): - raise TypeError("Entangler map type 'list' expected") - - for src_to_targ in entangler_map: - if not isinstance(src_to_targ, list): - raise TypeError(f"Entangle index list expected but got {type(src_to_targ)}") - - ret_map = [] - ret_map = [[int(src), int(targ)] for src, targ in entangler_map] - - for src, targ in ret_map: - if src < 0 or src >= num_qubits: - raise ValueError(f"Qubit entangle source value {src} invalid for {num_qubits} qubits") - if targ < 0 or targ >= num_qubits: - raise ValueError(f"Qubit entangle target value {targ} invalid for {num_qubits} qubits") - if not allow_double_entanglement and [targ, src] in ret_map: - raise ValueError(f"Qubit {src} and {targ} cross-entangled.") - - return ret_map diff --git a/qiskit/utils/name_unnamed_args.py b/qiskit/utils/name_unnamed_args.py deleted file mode 100644 index 4e153dcfefd2..000000000000 --- a/qiskit/utils/name_unnamed_args.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tool to name unnamed arguments.""" - -import functools - - -def name_args(mapping, skip=0): - """Decorator to convert unnamed arguments to named ones. - - Can be used to deprecate old signatures of a function, e.g. - - .. code-block:: - - old_f(a: TypeA, b: TypeB, c: TypeC) - new_f(a: TypeA, d: TypeD, b: TypeB=None, c: TypeC=None) - - Then, to support the old signature this decorator can be used as - - .. code-block:: - - @name_args([ - ('a'), # stays the same - ('d', {TypeB: 'b'}), # if arg is of type TypeB, call if 'b' else 'd' - ('b', {TypeC: 'c'}) - ]) - def new_f(a: TypeA, d: TypeD, b: TypeB=None, c: TypeC=None): - if b is not None: - # raise warning, this is deprecated! - if c is not None: - # raise warning, this is deprecated! - - """ - - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - # turn args into kwargs - for arg, replacement in zip(args[skip:], mapping): - default_name = replacement[0] - if len(replacement) == 1: # just renaming, no special cases - if default_name in kwargs: - raise ValueError(f"Name collapse on {default_name}") - kwargs[default_name] = arg - else: - # check if we find a special name - name = None - for special_type, special_name in replacement[1].items(): - if isinstance(arg, special_type): - name = special_name - break - if name is None: - name = default_name - - if name in kwargs: - raise ValueError(f"Name collapse on {default_name}") - kwargs[name] = arg - - return func(*args[:skip], **kwargs) - - return wrapper - - return decorator diff --git a/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml index 3e0dd0863026..67c551dad70f 100644 --- a/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml +++ b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml @@ -1,14 +1,27 @@ --- upgrade: - | - The following algorithm utilities in :mod:`qiskit.utils` have been removed - from the codebase: + The following tools in :mod:`qiskit.utils` have been removed from the codebase: + * Utils in ``qiskit.utils.arithmetic`` + * Utils in ``qiskit.utils.circuit_utils`` + * Utils in ``qiskit.utils.entangler_map`` + * Utils in ``qiskit.utils.name_unnamed_args`` + + These functions were used exclusively in the context of ``qiskit.algorithms`` and + ``qiskit.opflow``, and were deprecated in Qiskit 0.46. ``qiskit.algorithms`` and + ``qiskit.opflow`` have been deprecated since Qiskit 0.45 and Qiskit Terra 0.24 + respectively. + + The following utilities have also been removed: + + * Utils in ``qiskit.utils.validation`` * ``algorithm_globals`` - * ``qiskit.utils.validation`` - They were deprecated in Qiskit 0.45 as a consequence of the migration + These were deprecated in Qiskit 0.45 as a consequence of the migration of ``qiskit.algorithms`` to a standalone `package `_, where - these utils have also been migrated. The can be found in the new package + these utils have also been migrated. They can be found in the new package under ``qiskit_algorithms.utils``. + + diff --git a/test/python/test_util.py b/test/python/test_util.py index b574ff8390b0..d60aad284c18 100644 --- a/test/python/test_util.py +++ b/test/python/test_util.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -13,11 +13,9 @@ """Tests for qiskit/utils""" from unittest import mock -import numpy as np from qiskit.utils.multiprocessing import local_hardware_info from qiskit.test import QiskitTestCase -from qiskit.utils.arithmetic import triu_to_dense class TestUtil(QiskitTestCase): @@ -31,14 +29,3 @@ def test_local_hardware_none_cpu_count(self, cpu_count_mock, vmem_mock, platform del cpu_count_mock, vmem_mock, platform_mock # unused result = local_hardware_info() self.assertEqual(1, result["cpus"]) - - def test_triu_to_dense(self): - """Test conversion of upper triangular matrix to dense matrix.""" - np.random.seed(50) - n = np.random.randint(5, 15) - m = np.random.randint(-100, 100, size=(n, n)) - symm = (m + m.T) / 2 - - triu = [[symm[i, j] for i in range(j, n)] for j in range(n)] - - self.assertTrue(np.array_equal(symm, triu_to_dense(triu))) From 50e813746702a27954eeb700e5b43ebf3e1d8244 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 28 Nov 2023 15:11:41 +0000 Subject: [PATCH 17/17] Add representation of storage-owning `Var` nodes (#10944) * Add representation of storage-owning `Var` nodes This adds the representation of `expr.Var` nodes that own their own storage locations, and consequently are not backed by existing Qiskit objects (`Clbit` or `ClassicalRegister`). This is the base of the ability for Qiskit to represent manual classical-value storage in `QuantumCircuit`, and the base for how manual storage will be implemented. * Minor documentation tweaks Co-authored-by: Matthew Treinish --------- Co-authored-by: Matthew Treinish --- qiskit/circuit/classical/expr/__init__.py | 15 +++++-- qiskit/circuit/classical/expr/expr.py | 43 ++++++++++++++++--- .../circuit/classical/test_expr_properties.py | 42 ++++++++++++++++++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py index d2cd4bc5044e..b2b4d138fca7 100644 --- a/qiskit/circuit/classical/expr/__init__.py +++ b/qiskit/circuit/classical/expr/__init__.py @@ -39,10 +39,11 @@ These objects are mutable and should not be reused in a different location without a copy. -The entry point from general circuit objects to the expression system is by wrapping the object -in a :class:`Var` node and associating a :class:`~.types.Type` with it. +The base for dynamic variables is the :class:`Var`, which can be either an arbitrarily typed runtime +variable, or a wrapper around a :class:`.Clbit` or :class:`.ClassicalRegister`. .. autoclass:: Var + :members: var, name Similarly, literals used in comparison (such as integers) should be lifted to :class:`Value` nodes with associated types. @@ -86,10 +87,18 @@ The functions and methods described in this section are a more user-friendly way to build the expression tree, while staying close to the internal representation. All these functions will automatically lift valid Python scalar values into corresponding :class:`Var` or :class:`Value` -objects, and will resolve any required implicit casts on your behalf. +objects, and will resolve any required implicit casts on your behalf. If you want to directly use +some scalar value as an :class:`Expr` node, you can manually :func:`lift` it yourself. .. autofunction:: lift +Typically you should create memory-owning :class:`Var` instances by using the +:meth:`.QuantumCircuit.add_var` method to declare them in some circuit context, since a +:class:`.QuantumCircuit` will not accept an :class:`Expr` that contains variables that are not +already declared in it, since it needs to know how to allocate the storage and how the variable will +be initialized. However, should you want to do this manually, you should use the low-level +:meth:`Var.new` call to safely generate a named variable for usage. + You can manually specify casts in cases where the cast is allowed in explicit form, but may be lossy (such as the cast of a higher precision :class:`~.types.Uint` to a lower precision one). diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index b9e9aad4a2b7..3adbacfd6926 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -31,6 +31,7 @@ import abc import enum import typing +import uuid from .. import types @@ -108,24 +109,56 @@ def __repr__(self): @typing.final class Var(Expr): - """A classical variable.""" + """A classical variable. - __slots__ = ("var",) + These variables take two forms: a new-style variable that owns its storage location and has an + associated name; and an old-style variable that wraps a :class:`.Clbit` or + :class:`.ClassicalRegister` instance that is owned by some containing circuit. In general, + construction of variables for use in programs should use :meth:`Var.new` or + :meth:`.QuantumCircuit.add_var`.""" + + __slots__ = ("var", "name") def __init__( - self, var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister, type: types.Type + self, + var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID, + type: types.Type, + *, + name: str | None = None, ): self.type = type self.var = var + """A reference to the backing data storage of the :class:`Var` instance. When lifting + old-style :class:`.Clbit` or :class:`.ClassicalRegister` instances into a :class:`Var`, + this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`. If the variable is a + new-style classical variable (one that owns its own storage separate to the old + :class:`.Clbit`/:class:`.ClassicalRegister` model), this field will be a :class:`~uuid.UUID` + to uniquely identify it.""" + self.name = name + """The name of the variable. This is required to exist if the backing :attr:`var` attribute + is a :class:`~uuid.UUID`, i.e. if it is a new-style variable, and must be ``None`` if it is + an old-style variable.""" + + @classmethod + def new(cls, name: str, type: types.Type) -> typing.Self: + """Generate a new named variable that owns its own backing storage.""" + return cls(uuid.uuid4(), type, name=name) def accept(self, visitor, /): return visitor.visit_var(self) def __eq__(self, other): - return isinstance(other, Var) and self.type == other.type and self.var == other.var + return ( + isinstance(other, Var) + and self.type == other.type + and self.var == other.var + and self.name == other.name + ) def __repr__(self): - return f"Var({self.var}, {self.type})" + if self.name is None: + return f"Var({self.var}, {self.type})" + return f"Var({self.var}, {self.type}, name='{self.name}')" @typing.final diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index a6873153c0eb..efda6ba37758 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -56,3 +56,45 @@ def test_expr_can_be_cloned(self, obj): self.assertEqual(obj, copy.copy(obj)) self.assertEqual(obj, copy.deepcopy(obj)) self.assertEqual(obj, pickle.loads(pickle.dumps(obj))) + + def test_var_equality(self): + """Test that various types of :class:`.expr.Var` equality work as expected both in equal and + unequal cases.""" + var_a_bool = expr.Var.new("a", types.Bool()) + self.assertEqual(var_a_bool, var_a_bool) + + # Allocating a new variable should not compare equal, despite the name match. A semantic + # equality checker can choose to key these variables on only their names and types, if it + # knows that that check is valid within the semantic context. + self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) + + # Manually constructing the same object with the same UUID should cause it compare equal, + # though, for serialisation ease. + self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) + + # This is a badly constructed variable because it's using a different type to refer to the + # same storage location (the UUID) as another variable. It is an IR error to generate this + # sort of thing, but we can't fully be responsible for that and a pass would need to go out + # of its way to do this incorrectly, but we can still ensure that the direct equality check + # would spot the error. + self.assertNotEqual( + var_a_bool, expr.Var(var_a_bool.var, types.Uint(8), name=var_a_bool.name) + ) + + # This is also badly constructed because it uses a different name to refer to the "same" + # storage location. + self.assertNotEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="b")) + + # Obviously, two variables of different types and names should compare unequal. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(8))) + # As should two variables of the same name but different storage locations and types. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("a", types.Uint(8))) + + def test_var_uuid_clone(self): + """Test that :class:`.expr.Var` instances that have an associated UUID and name roundtrip + through pickle and copy operations to produce values that compare equal.""" + var_a_u8 = expr.Var.new("a", types.Uint(8)) + + self.assertEqual(var_a_u8, pickle.loads(pickle.dumps(var_a_u8))) + self.assertEqual(var_a_u8, copy.copy(var_a_u8)) + self.assertEqual(var_a_u8, copy.deepcopy(var_a_u8))