From 962f19e22b515cd08a8b1b224330d28cfee0b512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 23 Oct 2023 16:02:30 +0200 Subject: [PATCH 01/12] Remove code, tests, fix tests --- .../library/evolved_operator_ansatz.py | 34 +- qiskit/circuit/library/pauli_evolution.py | 24 +- qiskit/opflow/__init__.py | 332 ---- qiskit/opflow/converters/__init__.py | 85 - qiskit/opflow/converters/abelian_grouper.py | 164 -- qiskit/opflow/converters/circuit_sampler.py | 468 ----- qiskit/opflow/converters/converter_base.py | 51 - .../opflow/converters/dict_to_circuit_sum.py | 68 - .../opflow/converters/pauli_basis_change.py | 554 ------ .../opflow/converters/two_qubit_reduction.py | 100 - qiskit/opflow/evolutions/__init__.py | 108 -- qiskit/opflow/evolutions/evolution_base.py | 57 - qiskit/opflow/evolutions/evolution_factory.py | 57 - qiskit/opflow/evolutions/evolved_op.py | 179 -- qiskit/opflow/evolutions/matrix_evolution.py | 73 - .../evolutions/pauli_trotter_evolution.py | 203 --- .../evolutions/trotterizations/__init__.py | 24 - .../evolutions/trotterizations/qdrift.py | 87 - .../evolutions/trotterizations/suzuki.py | 121 -- .../evolutions/trotterizations/trotter.py | 34 - .../trotterizations/trotterization_base.py | 67 - .../trotterizations/trotterization_factory.py | 52 - qiskit/opflow/exceptions.py | 28 - qiskit/opflow/expectations/__init__.py | 80 - .../expectations/aer_pauli_expectation.py | 162 -- .../opflow/expectations/cvar_expectation.py | 126 -- .../opflow/expectations/expectation_base.py | 72 - .../expectations/expectation_factory.py | 126 -- .../opflow/expectations/matrix_expectation.py | 76 - .../opflow/expectations/pauli_expectation.py | 117 -- qiskit/opflow/gradients/__init__.py | 194 -- .../gradients/circuit_gradients/__init__.py | 20 - .../circuit_gradients/circuit_gradient.py | 108 -- .../gradients/circuit_gradients/lin_comb.py | 909 --------- .../circuit_gradients/param_shift.py | 429 ----- .../opflow/gradients/circuit_qfis/__init__.py | 20 - .../gradients/circuit_qfis/circuit_qfi.py | 65 - .../gradients/circuit_qfis/lin_comb_full.py | 227 --- .../circuit_qfis/overlap_block_diag.py | 194 -- .../gradients/circuit_qfis/overlap_diag.py | 274 --- qiskit/opflow/gradients/derivative_base.py | 244 --- qiskit/opflow/gradients/gradient.py | 230 --- qiskit/opflow/gradients/gradient_base.py | 76 - qiskit/opflow/gradients/hessian.py | 292 --- qiskit/opflow/gradients/hessian_base.py | 74 - qiskit/opflow/gradients/natural_gradient.py | 560 ------ qiskit/opflow/gradients/qfi.py | 74 - qiskit/opflow/gradients/qfi_base.py | 78 - qiskit/opflow/list_ops/__init__.py | 96 - qiskit/opflow/list_ops/composed_op.py | 197 -- qiskit/opflow/list_ops/list_op.py | 640 ------- qiskit/opflow/list_ops/summed_op.py | 250 --- qiskit/opflow/list_ops/tensored_op.py | 129 -- qiskit/opflow/mixins/__init__.py | 18 - qiskit/opflow/mixins/star_algebra.py | 132 -- qiskit/opflow/mixins/tensor.py | 57 - qiskit/opflow/operator_base.py | 515 ------ qiskit/opflow/operator_globals.py | 79 - qiskit/opflow/primitive_ops/__init__.py | 79 - qiskit/opflow/primitive_ops/circuit_op.py | 251 --- qiskit/opflow/primitive_ops/matrix_op.py | 236 --- qiskit/opflow/primitive_ops/pauli_op.py | 355 ---- qiskit/opflow/primitive_ops/pauli_sum_op.py | 463 ----- qiskit/opflow/primitive_ops/primitive_op.py | 323 ---- .../primitive_ops/tapered_pauli_sum_op.py | 588 ------ qiskit/opflow/state_fns/__init__.py | 78 - qiskit/opflow/state_fns/circuit_state_fn.py | 403 ---- qiskit/opflow/state_fns/cvar_measurement.py | 386 ---- qiskit/opflow/state_fns/dict_state_fn.py | 345 ---- qiskit/opflow/state_fns/operator_state_fn.py | 259 --- .../state_fns/sparse_vector_state_fn.py | 234 --- qiskit/opflow/state_fns/state_fn.py | 459 ----- qiskit/opflow/state_fns/vector_state_fn.py | 259 --- qiskit/opflow/utils.py | 116 -- qiskit/primitives/backend_estimator.py | 6 +- qiskit/primitives/base/base_estimator.py | 6 +- qiskit/primitives/estimator.py | 6 +- qiskit/primitives/utils.py | 23 +- qiskit/result/sampled_expval.py | 8 - .../commuting_2q_gate_router.py | 4 +- qiskit/utils/__init__.py | 21 +- qiskit/utils/backend_utils.py | 279 --- qiskit/utils/measurement_error_mitigation.py | 268 --- qiskit/utils/mitigation/__init__.py | 58 - qiskit/utils/mitigation/_filters.py | 508 ------ qiskit/utils/mitigation/circuits.py | 250 --- qiskit/utils/mitigation/fitters.py | 491 ----- qiskit/utils/quantum_instance.py | 946 ---------- qiskit/utils/run_circuits.py | 411 ----- .../circuit/library/test_evolution_gate.py | 92 +- .../circuit/library/test_evolved_op_ansatz.py | 70 +- .../circuit/library/test_qaoa_ansatz.py | 3 +- 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 | 1618 ----------------- 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 | 6 - test/python/pulse/test_block.py | 4 +- test/python/result/test_sampled_expval.py | 7 +- test/python/utils/mitigation/__init__.py | 13 - test/python/utils/mitigation/test_meas.py | 709 -------- 115 files changed, 87 insertions(+), 24763 deletions(-) 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 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/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index a3832bfb71a6..8cac2d3d5333 100644 --- a/qiskit/circuit/library/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/evolved_operator_ansatz.py @@ -44,15 +44,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 +112,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 +122,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 +140,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 +167,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 +236,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 fa1d1842a8ed..000000000000 --- a/qiskit/opflow/converters/abelian_grouper.py +++ /dev/null @@ -1,164 +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", - 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 e8e5938617e7..000000000000 --- a/qiskit/opflow/converters/circuit_sampler.py +++ /dev/null @@ -1,468 +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", - 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 231f3f2daad7..000000000000 --- a/qiskit/opflow/converters/converter_base.py +++ /dev/null @@ -1,51 +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", - 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 b52f16f185c6..000000000000 --- a/qiskit/opflow/converters/dict_to_circuit_sum.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. - -"""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", - 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 6a39f2ef73cc..000000000000 --- a/qiskit/opflow/converters/pauli_basis_change.py +++ /dev/null @@ -1,554 +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", - 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 10f596f10494..000000000000 --- a/qiskit/opflow/converters/two_qubit_reduction.py +++ /dev/null @@ -1,100 +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", - 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 1123c36c34a7..000000000000 --- a/qiskit/opflow/evolutions/evolution_base.py +++ /dev/null @@ -1,57 +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", - 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 fffad684e816..000000000000 --- a/qiskit/opflow/evolutions/evolution_factory.py +++ /dev/null @@ -1,57 +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", - 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 03771aa52fa8..000000000000 --- a/qiskit/opflow/evolutions/evolved_op.py +++ /dev/null @@ -1,179 +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", - 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 8c18a6a57728..000000000000 --- a/qiskit/opflow/evolutions/matrix_evolution.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. - -"""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", - 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 414ffd5777ca..000000000000 --- a/qiskit/opflow/evolutions/pauli_trotter_evolution.py +++ /dev/null @@ -1,203 +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", - 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 8a31fde01c93..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/qdrift.py +++ /dev/null @@ -1,87 +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", - 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 cfe59acac5a8..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/suzuki.py +++ /dev/null @@ -1,121 +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", - 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 eb1c48d7e27d..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotter.py +++ /dev/null @@ -1,34 +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", - 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 222d338dfdce..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py +++ /dev/null @@ -1,67 +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", - 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 f8d119140502..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.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. - -"""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", - 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 27bc0f6cc14d..000000000000 --- a/qiskit/opflow/exceptions.py +++ /dev/null @@ -1,28 +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", - 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 b68054281457..000000000000 --- a/qiskit/opflow/expectations/aer_pauli_expectation.py +++ /dev/null @@ -1,162 +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", - 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 9a6711c498ae..000000000000 --- a/qiskit/opflow/expectations/cvar_expectation.py +++ /dev/null @@ -1,126 +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", - 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 593c43b83833..000000000000 --- a/qiskit/opflow/expectations/expectation_base.py +++ /dev/null @@ -1,72 +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", - 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 58a72ed6daca..000000000000 --- a/qiskit/opflow/expectations/expectation_factory.py +++ /dev/null @@ -1,126 +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", - 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 5265eff6e59d..000000000000 --- a/qiskit/opflow/expectations/matrix_expectation.py +++ /dev/null @@ -1,76 +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", - 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 7c8d9697db16..000000000000 --- a/qiskit/opflow/expectations/pauli_expectation.py +++ /dev/null @@ -1,117 +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", - 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 284cd7da7eb7..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.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. - -"""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", - 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 1e06512e763e..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ /dev/null @@ -1,909 +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", - 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 = circuit._data.copy() - 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 5329cc4c14c0..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ /dev/null @@ -1,429 +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", - 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 9a11d619e6c7..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ /dev/null @@ -1,65 +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", - 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 71f4eea3d8c2..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ /dev/null @@ -1,227 +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", - 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 86b0bd1094cf..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.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. - -"""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", - 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 3f630fe304b9..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ /dev/null @@ -1,274 +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", - 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 9ffbc6f0cdf0..000000000000 --- a/qiskit/opflow/gradients/derivative_base.py +++ /dev/null @@ -1,244 +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", - 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", 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 f9762da93e41..000000000000 --- a/qiskit/opflow/gradients/gradient.py +++ /dev/null @@ -1,230 +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", - 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 f8da278acc93..000000000000 --- a/qiskit/opflow/gradients/gradient_base.py +++ /dev/null @@ -1,76 +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", - 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 3ec39f5674ca..000000000000 --- a/qiskit/opflow/gradients/hessian.py +++ /dev/null @@ -1,292 +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", - 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 2230ec28d824..000000000000 --- a/qiskit/opflow/gradients/hessian_base.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. - -"""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", - 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 39c6cb8f4b73..000000000000 --- a/qiskit/opflow/gradients/natural_gradient.py +++ /dev/null @@ -1,560 +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", - 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 b16cd3b3ecc5..000000000000 --- a/qiskit/opflow/gradients/qfi.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. - -"""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", - 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 b09f9170dbad..000000000000 --- a/qiskit/opflow/gradients/qfi_base.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. - -"""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", - 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 d47a81fbd796..000000000000 --- a/qiskit/opflow/list_ops/composed_op.py +++ /dev/null @@ -1,197 +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", - 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 abf2d2326d0c..000000000000 --- a/qiskit/opflow/list_ops/list_op.py +++ /dev/null @@ -1,640 +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", - 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 52f7faa15083..000000000000 --- a/qiskit/opflow/list_ops/summed_op.py +++ /dev/null @@ -1,250 +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", - 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 e3eb30cfaa41..000000000000 --- a/qiskit/opflow/list_ops/tensored_op.py +++ /dev/null @@ -1,129 +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", - 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 642086d5ba04..000000000000 --- a/qiskit/opflow/mixins/star_algebra.py +++ /dev/null @@ -1,132 +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", - 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 3535db439f09..000000000000 --- a/qiskit/opflow/mixins/tensor.py +++ /dev/null @@ -1,57 +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", - 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 e79741a5dac3..000000000000 --- a/qiskit/opflow/operator_base.py +++ /dev/null @@ -1,515 +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", - 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 ac0c624c7287..000000000000 --- a/qiskit/opflow/operator_globals.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. - -""" -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", - 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 1a6104a4cac3..000000000000 --- a/qiskit/opflow/primitive_ops/circuit_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. - -"""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", - 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 5afe0ac54570..000000000000 --- a/qiskit/opflow/primitive_ops/matrix_op.py +++ /dev/null @@ -1,236 +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", - 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 623608754671..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_op.py +++ /dev/null @@ -1,355 +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", - 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 b1bb9b7242a9..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_sum_op.py +++ /dev/null @@ -1,463 +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", - 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 de3ef06c3add..000000000000 --- a/qiskit/opflow/primitive_ops/primitive_op.py +++ /dev/null @@ -1,323 +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", - 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 9411afc71f26..000000000000 --- a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py +++ /dev/null @@ -1,588 +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", - 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", - 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 3f23f89a1cee..000000000000 --- a/qiskit/opflow/state_fns/circuit_state_fn.py +++ /dev/null @@ -1,403 +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", - 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 940de844748a..000000000000 --- a/qiskit/opflow/state_fns/dict_state_fn.py +++ /dev/null @@ -1,345 +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", - 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 a9cac679deef..000000000000 --- a/qiskit/opflow/state_fns/operator_state_fn.py +++ /dev/null @@ -1,259 +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", - 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 f24592bdaf55..000000000000 --- a/qiskit/opflow/state_fns/state_fn.py +++ /dev/null @@ -1,459 +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", - 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 711d505612ca..000000000000 --- a/qiskit/opflow/state_fns/vector_state_fn.py +++ /dev/null @@ -1,259 +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", - 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 0979fc0bc3e7..000000000000 --- a/qiskit/opflow/utils.py +++ /dev/null @@ -1,116 +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", - 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", - 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", - 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 2ffe5bb7a989..f55c8abe671c 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], @@ -273,7 +269,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 1dec010d7b12..0d66875c77b0 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -84,7 +84,6 @@ from collections.abc import Sequence from copy import copy from typing import Generic, TypeVar -import typing from qiskit.circuit import QuantumCircuit from qiskit.circuit.parametertable import ParameterView @@ -95,9 +94,6 @@ from ..utils import init_observable from .base_primitive import BasePrimitive -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - T = TypeVar("T", bound=Job) @@ -129,7 +125,7 @@ def __init__( 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: diff --git a/qiskit/primitives/estimator.py b/qiskit/primitives/estimator.py index 52e519e56c29..7a8e13530aef 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]]): """ @@ -127,7 +123,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 611fbe86662c..1c3a3185abe2 100644 --- a/qiskit/primitives/utils.py +++ b/qiskit/primitives/utils.py @@ -15,7 +15,6 @@ from __future__ import annotations import sys -import typing from collections.abc import Iterable import numpy as np @@ -27,9 +26,6 @@ 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. @@ -49,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: @@ -58,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/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 60b5cf9e3ad3..26ec62258c4c 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/utils/__init__.py b/qiskit/utils/__init__.py index 789b8c0da9b0..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,23 +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 -:mod:`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`) ============================================================ @@ -59,7 +45,6 @@ .. automodule:: qiskit.utils.optionals """ -from .quantum_instance import QuantumInstance from .deprecation import ( add_deprecation_to_docstring, deprecate_arg, @@ -77,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 @@ -86,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 df89d7f194da..000000000000 --- a/qiskit/utils/backend_utils.py +++ /dev/null @@ -1,279 +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", - 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", - 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", - 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", - 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", - 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", - 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", - 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", - 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 - return False - - -@deprecate_func( - since="0.24.0", - 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 - return False - - -@deprecate_func( - since="0.24.0", - 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", - 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 0ef09c997ec5..000000000000 --- a/qiskit/utils/measurement_error_mitigation.py +++ /dev/null @@ -1,268 +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", - 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", - 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", - 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", - 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 c79b107cdc0f..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.algorithms` and :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 5d566f625116..000000000000 --- a/qiskit/utils/mitigation/_filters.py +++ /dev/null @@ -1,508 +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", - 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", - 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 2fdeaa6372a6..000000000000 --- a/qiskit/utils/mitigation/circuits.py +++ /dev/null @@ -1,250 +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", - 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", - 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", - 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 65357d5dc5b5..000000000000 --- a/qiskit/utils/mitigation/fitters.py +++ /dev/null @@ -1,491 +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", - 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", - 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 94c1bed535d4..000000000000 --- a/qiskit/utils/quantum_instance.py +++ /dev/null @@ -1,946 +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", - 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 9714234ca694..000000000000 --- a/qiskit/utils/run_circuits.py +++ /dev/null @@ -1,411 +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", - 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", - 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/test/python/circuit/library/test_evolution_gate.py b/test/python/circuit/library/test_evolution_gate.py index adf0e189db32..a9571b882cdb 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,8 @@ def test_cnot_chain_options(self, option): @data( Pauli("XI"), - X ^ I, # PauliOp + X ^ I, # SparsePauliOp 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 +259,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 +280,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 +300,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 +310,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..cbc99a28826c 100644 --- a/test/python/circuit/library/test_evolved_op_ansatz.py +++ b/test/python/circuit/library/test_evolved_op_ansatz.py @@ -16,7 +16,6 @@ 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 @@ -28,20 +27,12 @@ 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 +41,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..d4b952fb0a2f 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 @@ -30,7 +29,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 b0fee1233bf5..000000000000 --- a/test/python/opflow/test_gradients.py +++ /dev/null @@ -1,1618 +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.test import slow_test -from qiskit.utils import QuantumInstance -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.utils import algorithm_globals -from qiskit.algorithms import VQE -from qiskit.algorithms.optimizers import CG -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)) - - @slow_test - def test_vqe(self): - """Test VQE with gradients""" - - method = "lin_comb" - backend = "qasm_simulator" - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - BasicAer.get_backend(backend), seed_simulator=79, seed_transpiler=2 - ) - - # Define the Hamiltonian - h2_hamiltonian = ( - -1.05 * (I ^ I) + 0.39 * (I ^ Z) - 0.39 * (Z ^ I) - 0.01 * (Z ^ Z) + 0.18 * (X ^ X) - ) - h2_energy = -1.85727503 - - # Define the Ansatz - wavefunction = QuantumCircuit(2) - params = ParameterVector("theta", length=8) - itr = iter(params) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - wavefunction.cx(0, 1) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - - # Conjugate Gradient algorithm - optimizer = CG(maxiter=10) - - grad = Gradient(grad_method=method) - - # Gradient callable - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, optimizer=optimizer, gradient=grad, quantum_instance=q_instance - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - np.testing.assert_almost_equal(result.optimal_value, h2_energy, decimal=0) - - 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 32c7bfe5b85c..4821afc670de 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -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 BaseEstimator, Estimator, EstimatorResult from qiskit.primitives.utils import _observable_key from qiskit.providers import JobV1 @@ -350,7 +349,6 @@ class TestObservableValidation(QiskitTestCase): (Pauli("IXYZ"), (SparsePauliOp("IXYZ"),)), (PauliList("IXYZ"), (SparsePauliOp("IXYZ"),)), (SparsePauliOp("IXYZ"), (SparsePauliOp("IXYZ"),)), - (PauliSumOp(SparsePauliOp("IXYZ")), (SparsePauliOp("IXYZ"),)), ( ["IXYZ", "ZYXI"], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), @@ -367,10 +365,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/pulse/test_block.py b/test/python/pulse/test_block.py index 0dc56fbe6b27..2a61f304ea75 100644 --- a/test/python/pulse/test_block.py +++ b/test/python/pulse/test_block.py @@ -21,7 +21,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeArmonk -from qiskit.utils import has_aer +from qiskit.utils import optionals as _optionals class BaseTestBlock(QiskitTestCase): @@ -372,7 +372,7 @@ def test_inherit_from(self): self.assertEqual(new_sched.name, ref_name) self.assertDictEqual(new_sched.metadata, ref_metadata) - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") + @unittest.skipUnless(_optionals.HAS_AER, "qiskit-aer doesn't appear to be installed.") def test_execute_block(self): """Test executing a ScheduleBlock on a Pulse backend""" diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index f7cb06c5b86b..448d96ba9816 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,13 +81,11 @@ 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))) + exp3 = sampled_expectation_value(counts, Pauli(oper)) self.assertAlmostEqual(exp3, ans) spo = SparsePauliOp([oper], coeffs=[1]) - with self.assertWarns(DeprecationWarning): - exp4 = sampled_expectation_value(counts, PauliSumOp(spo, coeff=2)) + exp4 = sampled_expectation_value(counts, spo) self.assertAlmostEqual(exp4, 2 * ans) exp5 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) 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 01d27fb3d3c1db29b402ef4aedec8f61ab866449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 23 Oct 2023 16:26:53 +0200 Subject: [PATCH 02/12] Remove opflow from docs --- docs/apidoc/opflow.rst | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 docs/apidoc/opflow.rst 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: From 98782bc47a4e74aaca5a897962249dfa76f7a585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 23 Oct 2023 16:34:37 +0200 Subject: [PATCH 03/12] Remove opflow references --- docs/apidoc/index.rst | 13 ------------- qiskit/qpy/__init__.py | 2 +- .../pauli_2q_evolution_commutation.py | 2 +- test/python/circuit/library/test_qaoa_ansatz.py | 1 - 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 60c30286fa8c..9a8819ed982d 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -46,16 +46,3 @@ API Reference 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 - - algorithms - opflow 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/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/test/python/circuit/library/test_qaoa_ansatz.py b/test/python/circuit/library/test_qaoa_ansatz.py index d4b952fb0a2f..fcd062152bfa 100644 --- a/test/python/circuit/library/test_qaoa_ansatz.py +++ b/test/python/circuit/library/test_qaoa_ansatz.py @@ -28,7 +28,6 @@ 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(Pauli("I"), 1) parameters = circuit.parameters From e75881f93733e2fe224a3015d90a0776a941538a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 25 Oct 2023 17:47:42 +0200 Subject: [PATCH 04/12] Fix lint, fix docs --- docs/apidoc/utils_mitigation.rst | 6 ------ qiskit/circuit/library/evolved_operator_ansatz.py | 1 - qiskit/primitives/base/base_estimator.py | 4 ++-- qiskit/primitives/utils.py | 3 +-- test/python/circuit/library/test_evolved_op_ansatz.py | 2 -- 5 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 docs/apidoc/utils_mitigation.rst 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/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index 8cac2d3d5333..931f98e06be7 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 diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index 0d66875c77b0..9802ac7f7135 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -200,7 +200,7 @@ def _run( @staticmethod def _validate_observables( - observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + observables: Sequence[BaseOperator | str] | BaseOperator | str, ) -> tuple[SparsePauliOp, ...]: if isinstance(observables, str) or not isinstance(observables, Sequence): observables = (observables,) @@ -210,7 +210,7 @@ def _validate_observables( @staticmethod def _cross_validate_circuits_observables( - circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator | PauliSumOp, ...] + circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator, ...] ) -> None: if len(circuits) != len(observables): raise ValueError( diff --git a/qiskit/primitives/utils.py b/qiskit/primitives/utils.py index 1c3a3185abe2..6179c4477af7 100644 --- a/qiskit/primitives/utils.py +++ b/qiskit/primitives/utils.py @@ -14,12 +14,11 @@ """ from __future__ import annotations -import sys 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 diff --git a/test/python/circuit/library/test_evolved_op_ansatz.py b/test/python/circuit/library/test_evolved_op_ansatz.py index cbc99a28826c..000eb8cfb9df 100644 --- a/test/python/circuit/library/test_evolved_op_ansatz.py +++ b/test/python/circuit/library/test_evolved_op_ansatz.py @@ -12,7 +12,6 @@ """Test the evolved operator ansatz.""" -from ddt import ddt, data import numpy as np from qiskit.circuit import QuantumCircuit @@ -23,7 +22,6 @@ from qiskit.test import QiskitTestCase -@ddt class TestEvolvedOperatorAnsatz(QiskitTestCase): """Test the evolved operator ansatz.""" From 9e2b9e3303eccb2d83220aa6c0fc818b18b2d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 25 Oct 2023 17:49:00 +0200 Subject: [PATCH 05/12] Fix test --- test/python/result/test_sampled_expval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index 448d96ba9816..9d022fd37005 100644 --- a/test/python/result/test_sampled_expval.py +++ b/test/python/result/test_sampled_expval.py @@ -86,7 +86,7 @@ def test_same(self): spo = SparsePauliOp([oper], coeffs=[1]) exp4 = sampled_expectation_value(counts, spo) - self.assertAlmostEqual(exp4, 2 * ans) + self.assertAlmostEqual(exp4, ans) exp5 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) self.assertAlmostEqual(exp5, ans) From ea435c43d1df9b995a4d8792ecf847c0fbf95d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 9 Nov 2023 11:11:26 +0100 Subject: [PATCH 06/12] Remove redundant test --- test/python/result/test_sampled_expval.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index 9d022fd37005..48a6a925190c 100644 --- a/test/python/result/test_sampled_expval.py +++ b/test/python/result/test_sampled_expval.py @@ -81,16 +81,13 @@ def test_same(self): exp2 = sampled_expectation_value(counts, Pauli(oper)) self.assertAlmostEqual(exp2, ans) - exp3 = sampled_expectation_value(counts, Pauli(oper)) + spo = SparsePauliOp([oper], coeffs=[1]) + exp3 = sampled_expectation_value(counts, spo) self.assertAlmostEqual(exp3, ans) - spo = SparsePauliOp([oper], coeffs=[1]) - exp4 = sampled_expectation_value(counts, spo) + exp4 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) self.assertAlmostEqual(exp4, ans) - exp5 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) - self.assertAlmostEqual(exp5, ans) - def test_asym_ops(self): """Test that asymmetric exp values work""" dist = QuasiDistribution(PROBS) From 055da15c05a3ce7d1fa096757dc47a2573001367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 9 Nov 2023 11:44:42 +0100 Subject: [PATCH 07/12] Add release note --- ...move-opflow-qi-utils-3debd943c65b17da.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml 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 `__. From 0f23edcf4d689e51140f7db1c520b7a62edcff94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 9 Nov 2023 16:05:04 +0100 Subject: [PATCH 08/12] Remove redirects --- docs/api_redirects.txt | 9 --------- 1 file changed, 9 deletions(-) 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 From aabba8c2595e98c5f06bb611297955f9a5a7bbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 13 Nov 2023 09:46:35 +0100 Subject: [PATCH 09/12] Update docs index --- docs/apidoc/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 9a8819ed982d..eb5ed9e3c2c5 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -44,5 +44,4 @@ API Reference transpiler_synthesis_plugins transpiler_builtin_plugins utils - utils_mitigation exceptions From e60adb5743b823c8991481b030de7d6257974a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 13 Nov 2023 09:49:38 +0100 Subject: [PATCH 10/12] Fix conflict --- docs/apidoc/index.rst | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 3c8841ef6f19..eb5ed9e3c2c5 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -45,18 +45,3 @@ API Reference transpiler_builtin_plugins utils exceptions -<<<<<<< HEAD -======= - -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 ->>>>>>> 7ef9547447c62dc891a49f58a928b1d3a626083f From 53ce114c9023639f1d49f3e2ad6a1fa775ff76d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 13 Nov 2023 10:14:14 +0100 Subject: [PATCH 11/12] Fix docs --- docs/migration_guides/opflow_migration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 0858bc1c47aabd34f1b576465f4a52ca9ad72c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:06:46 +0100 Subject: [PATCH 12/12] Apply suggestions from Jake's code review Co-authored-by: Jake Lishman --- qiskit/circuit/library/evolved_operator_ansatz.py | 2 +- test/python/circuit/library/test_evolution_gate.py | 1 - test/python/result/test_sampled_expval.py | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index 931f98e06be7..572213089451 100644 --- a/qiskit/circuit/library/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/evolved_operator_ansatz.py @@ -139,7 +139,7 @@ def operators(self): def operators(self, operators=None) -> None: """Set the operators to be evolved. - operators (Optional[Union[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). diff --git a/test/python/circuit/library/test_evolution_gate.py b/test/python/circuit/library/test_evolution_gate.py index a9571b882cdb..fb13f5de88ae 100644 --- a/test/python/circuit/library/test_evolution_gate.py +++ b/test/python/circuit/library/test_evolution_gate.py @@ -241,7 +241,6 @@ def test_cnot_chain_options(self, option): @data( Pauli("XI"), - X ^ I, # SparsePauliOp SparsePauliOp(Pauli("XI")), ) def test_different_input_types(self, op): diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index 48a6a925190c..cb68099b310e 100644 --- a/test/python/result/test_sampled_expval.py +++ b/test/python/result/test_sampled_expval.py @@ -85,9 +85,6 @@ def test_same(self): exp3 = sampled_expectation_value(counts, spo) self.assertAlmostEqual(exp3, ans) - exp4 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) - self.assertAlmostEqual(exp4, ans) - def test_asym_ops(self): """Test that asymmetric exp values work""" dist = QuasiDistribution(PROBS)