diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py index 513df7179bdf..e8ff52503df4 100644 --- a/qiskit/circuit/parametervector.py +++ b/qiskit/circuit/parametervector.py @@ -35,6 +35,10 @@ def params(self): """Returns the list of parameters in the ParameterVector.""" return self._params + def index(self, value): + """Returns first index of value.""" + return self._params.index(value) + def __getitem__(self, key): if isinstance(key, slice): start, stop, step = key.indices(self._size) diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py index c34e736b0b18..1463249f7b2c 100644 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py @@ -53,13 +53,13 @@ class LinComb(CircuitGradient): see e.g. https://arxiv.org/pdf/1811.11184.pdf """ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]]]] - = None, + params: Union[ParameterExpression, ParameterVector, + List[ParameterExpression], + Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """ Convert the given operator into an operator object that represents the gradient w.r.t. params @@ -83,11 +83,10 @@ def convert(self, # pylint: disable=too-many-return-statements def _prepare_operator(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, - ParameterExpression]]]] = None + params: Union[ParameterExpression, ParameterVector, + List[ParameterExpression], + Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """ Traverse through the given operator to get back the adapted operator representing the gradient diff --git a/qiskit/opflow/gradients/circuit_gradients/param_shift.py b/qiskit/opflow/gradients/circuit_gradients/param_shift.py index b4cc51271d64..82b83fd92f24 100644 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ b/qiskit/opflow/gradients/circuit_gradients/param_shift.py @@ -15,7 +15,7 @@ from collections.abc import Iterable from copy import deepcopy from functools import partial -from typing import List, Union, Optional, Tuple, Dict +from typing import List, Union, Tuple, Dict import numpy as np from qiskit import transpile, QuantumCircuit @@ -78,14 +78,13 @@ def epsilon(self) -> float: """ return self._epsilon - # pylint: disable=arguments-differ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, - ParameterExpression]]]] = None) -> 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 @@ -152,8 +151,6 @@ def _parameter_shift(self, # By this point, it's only one parameter param = params - if not isinstance(param, ParameterExpression): - raise ValueError if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): return_op = operator.traverse(partial(self._parameter_shift, params=param)) diff --git a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py index 23d1e8437873..0a368a77c762 100644 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py @@ -13,7 +13,7 @@ """ CircuitQFI Class """ from abc import abstractmethod -from typing import List, Optional, Union +from typing import List, Union from qiskit.circuit import ParameterExpression, ParameterVector from ...converters.converter_base import ConverterBase @@ -39,8 +39,7 @@ class CircuitQFI(ConverterBase): @abstractmethod def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None, + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> OperatorBase: r""" Args: diff --git a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py index 0510fef22e12..3f3ece4d0a5e 100644 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py @@ -13,7 +13,7 @@ """The module for Quantum the Fisher Information.""" from copy import deepcopy -from typing import List, Union, Optional, Tuple +from typing import List, Union, Tuple import numpy as np from qiskit.circuit import Gate, Qubit @@ -41,8 +41,7 @@ class LinCombFull(CircuitQFI): def convert(self, operator: CircuitStateFn, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None, + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -70,8 +69,9 @@ def convert(self, 'LinCombFull is only compatible with states that are given as CircuitStateFn') # If a single parameter is given wrap it into a list. - if not isinstance(params, (list, np.ndarray)): + if isinstance(params, ParameterExpression): params = [params] + state_qc = operator.primitive # First, the operators are computed which can compensate for a potential phase-mismatch diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py index bca57ce0fac4..77b57babbcdc 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py @@ -12,7 +12,7 @@ """The module for Quantum the Fisher Information.""" -from typing import List, Union, Optional +from typing import List, Union import numpy as np from scipy.linalg import block_diag @@ -39,8 +39,7 @@ class OverlapBlockDiag(CircuitQFI): def convert(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -61,8 +60,9 @@ def convert(self, def _block_diag_approx(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, + ParameterVector, + List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -81,6 +81,10 @@ def _block_diag_approx(self, """ + # 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) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py index 06b3ec78dfa3..1d0bc9f1400a 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py @@ -12,13 +12,12 @@ """The module for Quantum the Fisher Information.""" import copy -from typing import List, Union, Optional +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 ...operator_base import OperatorBase from ...list_ops.list_op import ListOp from ...primitive_ops.circuit_op import CircuitOp from ...expectations.pauli_expectation import PauliExpectation @@ -39,8 +38,7 @@ class OverlapDiag(CircuitQFI): def convert(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -66,8 +64,8 @@ def convert(self, # This should be fixed. def _diagonal_approx(self, operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List] = None - ) -> OperatorBase: + params: Union[ParameterExpression, ParameterVector, List] + ) -> ListOp: """ Args: operator: The operator corresponding to the quantum state |ψ(ω)〉for which we compute @@ -88,6 +86,10 @@ def _diagonal_approx(self, if not isinstance(operator, CircuitStateFn): raise NotImplementedError('operator must be a 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$ diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py index 26ddd683c60b..be70f72ad21b 100644 --- a/qiskit/opflow/gradients/gradient.py +++ b/qiskit/opflow/gradients/gradient.py @@ -10,9 +10,9 @@ # 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.""" +"""The base interface for Opflow's gradient.""" -from typing import Union, List, Optional +from typing import Union, List import numpy as np from qiskit.exceptions import MissingOptionalLibraryError @@ -38,10 +38,10 @@ class Gradient(GradientBase): """Convert an operator expression to the first-order gradient.""" + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterVector, ParameterExpression, - List[ParameterExpression]]] = None + params: Union[ParameterVector, ParameterExpression, List[ParameterExpression]] ) -> OperatorBase: r""" Args: @@ -55,9 +55,6 @@ def convert(self, ValueError: If ``params`` contains a parameter not present in ``operator``. """ - if params is None: - raise ValueError("No parameters were provided to differentiate") - if isinstance(params, (ParameterVector, list)): param_grads = [self.convert(operator, param) for param in params] absent_params = [params[i] diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index 03ece8c5fa07..f7ea7ef08493 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -12,7 +12,7 @@ """The module to compute Hessians.""" -from typing import Optional, Union, List, Tuple +from typing import Union, List, Tuple import numpy as np from qiskit.exceptions import MissingOptionalLibraryError @@ -40,11 +40,12 @@ class Hessian(HessianBase): """Compute the Hessian of an expected value.""" + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], ParameterVector]] = None + params: Union[Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]], + List[ParameterExpression], ParameterVector] ) -> OperatorBase: """ Args: @@ -56,14 +57,8 @@ def convert(self, Returns: OperatorBase: An operator whose evaluation yields the Hessian - - Raises: - ValueError: If `params` is not set. """ # if input is a tuple instead of a list, wrap it into a list - if params is None: - raise ValueError("No parameters were provided to differentiate") - 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): @@ -81,9 +76,8 @@ def convert(self, # pylint: disable=too-many-return-statements def get_hessian(self, operator: OperatorBase, - params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]]]] - = None + params: Union[Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """Get the Hessian for the given operator w.r.t. the given parameters diff --git a/qiskit/opflow/gradients/natural_gradient.py b/qiskit/opflow/gradients/natural_gradient.py index 83e5f87790c8..b8050afeddff 100644 --- a/qiskit/opflow/gradients/natural_gradient.py +++ b/qiskit/opflow/gradients/natural_gradient.py @@ -68,11 +68,10 @@ def __init__(self, self._regularization = regularization self._epsilon = kwargs.get('epsilon', 1e-6) - # pylint: disable=arguments-differ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterVector, ParameterExpression, - List[ParameterExpression]]] = None + params: Union[ParameterVector, ParameterExpression, List[ParameterExpression]] ) -> OperatorBase: r""" Args: diff --git a/qiskit/opflow/gradients/qfi.py b/qiskit/opflow/gradients/qfi.py index c93df73c4379..4a33c04f2d33 100644 --- a/qiskit/opflow/gradients/qfi.py +++ b/qiskit/opflow/gradients/qfi.py @@ -12,7 +12,7 @@ """The module for Quantum the Fisher Information.""" -from typing import List, Union, Optional +from typing import List, Union from qiskit.circuit import (ParameterExpression, ParameterVector) from ..list_ops.list_op import ListOp @@ -33,10 +33,10 @@ class QFI(QFIBase): """ + # pylint: disable=signature-differs def convert(self, operator: CircuitStateFn, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index d99573c671d7..de92a6a96b8d 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -371,24 +371,21 @@ def test_state_hessian(self, method): """ ham = 0.5 * X - 1 * Z - a = Parameter('a') - b = Parameter('b') - params = [(a, a), (a, b), (b, b)] + params = ParameterVector('a', 2) q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) + qc.rz(params[0], q[0]) + qc.rx(params[1], q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) 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 = [[-0.5 / np.sqrt(2), 1 / np.sqrt(2), 0], - [-0.5 / np.sqrt(2) + 0.5, -1 / 2., 0.5], - [1 / np.sqrt(2), 0, 1 / np.sqrt(2)]] + 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.], [-1 / 2., 0.5]]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal(state_hess.assign_parameters(value_dict).eval(), @@ -507,25 +504,23 @@ def test_qfi(self, method): QFI = [[1, 0], [0, 1]] - [[0, 0], [0, cos^2(a)]] """ + for params in (ParameterVector('a', 2), + [Parameter('a'), Parameter('b')]): + q = QuantumRegister(1) + qc = QuantumCircuit(q) + qc.h(q) + qc.rz(params[0], q[0]) + qc.rx(params[1], q[0]) - 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.) - qfi = QFI(qfi_method=method).convert(operator=op, params=params) - values_dict = [{params[0]: np.pi / 4, params[1]: 0.1}, {params[0]: np.pi, params[1]: 0.1}, - {params[0]: np.pi / 2, params[1]: 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): - np.testing.assert_array_almost_equal(qfi.assign_parameters(value_dict).eval(), - correct_values[i], decimal=1) + op = CircuitStateFn(primitive=qc, coeff=1.) + qfi = QFI(qfi_method=method).convert(operator=op, params=params) + values_dict = [{params[0]: np.pi / 4, params[1]: 0.1}, + {params[0]: np.pi, params[1]: 0.1}, + {params[0]: np.pi / 2, params[1]: 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): + np.testing.assert_array_almost_equal(qfi.assign_parameters(value_dict).eval(), + correct_values[i], decimal=1) @idata(product(['lin_comb', 'param_shift', 'fin_diff'], [None, 'lasso', 'ridge', 'perturb_diag', 'perturb_diag_elements'])) @@ -533,35 +528,34 @@ def test_qfi(self, method): def test_natural_gradient(self, method, regularization): """Test the natural gradient""" try: - 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.) - nat_grad = NaturalGradient(grad_method=method, - regularization=regularization).convert(operator=op, - params=params) - values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] - correct_values = [[-2.36003979, 2.06503481]] \ - if regularization == 'ridge' else [[-4.2, 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=0) + 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.) + nat_grad = NaturalGradient(grad_method=method, regularization=regularization)\ + .convert(operator=op, params=params) + values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] + correct_values = [[-2.36003979, 2.06503481]] \ + if regularization == 'ridge' else [[-4.2, 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=0) 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) + _ = NaturalGradient().convert(None, None) @idata(zip(['lin_comb_full', 'overlap_block_diag', 'overlap_diag'], [LinCombFull, OverlapBlockDiag, OverlapDiag]))