From ef51d8b8feedd125cc5bb60f5a8f23d0987c8491 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Thu, 5 Aug 2021 01:25:13 +0200 Subject: [PATCH 01/11] Switch type from list to dict for aux_operators --- .../algorithms/eigen_solvers/eigen_solver.py | 4 +-- .../eigen_solvers/numpy_eigen_solver.py | 26 ++++++++-------- .../minimum_eigen_solver.py | 4 +-- .../numpy_minimum_eigen_solver.py | 10 +++--- .../algorithms/minimum_eigen_solvers/vqe.py | 31 ++++++++++--------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index 5f34d10e0c16..a1092719b92b 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -13,7 +13,7 @@ """The Eigensolver interface""" from abc import ABC, abstractmethod -from typing import List, Optional +from typing import Dict, Optional, Any import numpy as np from qiskit.opflow import OperatorBase @@ -30,7 +30,7 @@ class Eigensolver(ABC): @abstractmethod def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None ) -> "EigensolverResult": """ Computes eigenvalues. Operator and aux_operators can be supplied here and diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index c9ab86e8a1a0..70e978a90cd2 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -13,7 +13,7 @@ """The Eigensolver algorithm.""" import logging -from typing import List, Optional, Union, Tuple, Callable +from typing import List, Optional, Union, Tuple, Callable, Dict import numpy as np from scipy import sparse as scisparse @@ -46,7 +46,7 @@ def __init__( self, k: int = 1, filter_criterion: Callable[ - [Union[List, np.ndarray], float, Optional[List[float]]], bool + [Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool ] = None, ) -> None: """ @@ -92,7 +92,7 @@ def filter_criterion( def filter_criterion( self, filter_criterion: Optional[ - Callable[[Union[List, np.ndarray], float, Optional[List[float]]], bool] + Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool] ], ) -> None: """set the filter criterion""" @@ -141,7 +141,7 @@ def _get_ground_state_energy(self, operator: OperatorBase) -> None: self._solve(operator) def _get_energies( - self, operator: OperatorBase, aux_operators: Optional[List[OperatorBase]] + self, operator: OperatorBase, aux_operators: Optional[Dict[str, OperatorBase]] ) -> None: if self._ret.eigenvalues is None or self._ret.eigenstates is None: self._solve(operator) @@ -156,12 +156,12 @@ def _get_energies( @staticmethod def _eval_aux_operators( - aux_operators: List[OperatorBase], wavefn, threshold: float = 1e-12 - ) -> np.ndarray: - values = [] # type: List[Tuple[float, int]] - for operator in aux_operators: + aux_operators: Dict[str, OperatorBase], wavefn, threshold: float = 1e-12 + ) -> Dict[str, Optional[Tuple[float, int]]]: + values = {} # type: Dict[str, Optional[Tuple[float, int]]] + for key, operator in aux_operators.items(): if operator is None: - values.append(None) + values[key] = None continue value = 0.0 if operator.coeff != 0: @@ -174,11 +174,11 @@ def _eval_aux_operators( else: value = StateFn(operator, is_measurement=True).eval(wavefn) value = value.real if abs(value.real) > threshold else 0.0 - values.append((value, 0)) - return np.array(values, dtype=object) + values[key] = (value, 0) + return values def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None ) -> EigensolverResult: super().compute_eigenvalues(operator, aux_operators) @@ -189,7 +189,7 @@ def compute_eigenvalues( if aux_operators: zero_op = I.tensorpower(operator.num_qubits) * 0.0 # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in aux_operators] + aux_operators = {key: zero_op if op == 0 else op for key, op in aux_operators.items()} else: aux_operators = None diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 3c96dbae3ca8..67b2c35ca49c 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -13,7 +13,7 @@ """The Minimum Eigensolver interface""" from abc import ABC, abstractmethod -from typing import List, Optional +from typing import Dict, Optional import numpy as np from qiskit.opflow import OperatorBase @@ -30,7 +30,7 @@ class MinimumEigensolver(ABC): @abstractmethod def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None ) -> "MinimumEigensolverResult": """ Computes minimum eigenvalue. Operator and aux_operators can be supplied here and diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py index 1d2b29fd12cc..63fbeaaa950e 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py @@ -12,7 +12,7 @@ """The Numpy Minimum Eigensolver algorithm.""" -from typing import List, Optional, Union, Callable +from typing import List, Optional, Union, Callable, Dict import logging import numpy as np @@ -31,7 +31,7 @@ class NumPyMinimumEigensolver(MinimumEigensolver): def __init__( self, filter_criterion: Callable[ - [Union[List, np.ndarray], float, Optional[List[float]]], bool + [Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool ] = None, ) -> None: """ @@ -49,7 +49,7 @@ def __init__( @property def filter_criterion( self, - ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[List[float]]], bool]]: + ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool]]: """returns the filter criterion if set""" return self._ces.filter_criterion @@ -57,7 +57,7 @@ def filter_criterion( def filter_criterion( self, filter_criterion: Optional[ - Callable[[Union[List, np.ndarray], float, Optional[List[float]]], bool] + Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool] ], ) -> None: """set the filter criterion""" @@ -68,7 +68,7 @@ def supports_aux_operators(cls) -> bool: return NumPyEigensolver.supports_aux_operators() def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None ) -> MinimumEigensolverResult: super().compute_minimum_eigenvalue(operator, aux_operators) result_ces = self._ces.compute_eigenvalues(operator, aux_operators) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index c8a92821bf21..f6d8adbaa724 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -411,32 +411,33 @@ def supports_aux_operators(cls) -> bool: def _eval_aux_ops( self, parameters: np.ndarray, - aux_operators: List[OperatorBase], + aux_operators: Dict[str, OperatorBase], expectation: ExpectationBase, threshold: float = 1e-12, ) -> np.ndarray: # Create new CircuitSampler to avoid breaking existing one's caches. sampler = CircuitSampler(self.quantum_instance) - aux_op_meas = expectation.convert(StateFn(ListOp(aux_operators), is_measurement=True)) + # Assumption is made that the order of operators in ListOp does not affect the measurement results + aux_op_meas = expectation.convert(StateFn(ListOp(list(aux_operators.values())), is_measurement=True)) aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.ansatz.bind_parameters(parameters))) values = np.real(sampler.convert(aux_op_expect).eval()) # Discard values below threshold - aux_op_results = values * (np.abs(values) > threshold) + clean_values = values * (np.abs(values) > threshold) + aux_op_results = {key: value for key, value in zip(aux_operators, clean_values)} # Deal with the aux_op behavior where there can be Nones or Zero qubit Paulis in the list - _aux_op_nones = [op is None for op in aux_operators] - aux_operator_eigenvalues = [ - None if is_none else [result] - for (is_none, result) in zip(_aux_op_nones, aux_op_results) - ] + aux_operator_eigenvalues = { + key: None if aux_operators[key] is None else result + for key, result in aux_op_results.items() + } # As this has mixed types, since it can included None, it needs to explicitly pass object # data type to avoid numpy 1.19 warning message about implicit conversion being deprecated aux_operator_eigenvalues = np.array([aux_operator_eigenvalues], dtype=object) return aux_operator_eigenvalues def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None ) -> MinimumEigensolverResult: super().compute_minimum_eigenvalue(operator, aux_operators) @@ -458,15 +459,15 @@ def compute_minimum_eigenvalue( # We need to handle the array entries being Optional i.e. having value None if aux_operators: zero_op = I.tensorpower(operator.num_qubits) * 0.0 - converted = [] - for op in aux_operators: + converted = {} + for key, op in aux_operators.items(): if op is None: - converted.append(zero_op) + converted[key] = zero_op else: - converted.append(op) + converted[key] = op # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in converted] + aux_operators = {key: zero_op if op == 0 else op for key, op in converted.items()} else: aux_operators = None @@ -517,7 +518,7 @@ def compute_minimum_eigenvalue( if aux_operators is not None: aux_values = self._eval_aux_ops(opt_params, aux_operators, expectation=expectation) - result.aux_operator_eigenvalues = aux_values[0] + result.aux_operator_eigenvalues = aux_values return result From 23d0b3166810bdab89d585464a043c0c327d6623 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Sat, 7 Aug 2021 13:40:32 +0200 Subject: [PATCH 02/11] Restored List support for aux_operators --- .../algorithms/eigen_solvers/eigen_solver.py | 8 ++- .../eigen_solvers/numpy_eigen_solver.py | 37 +++++++----- .../minimum_eigen_solver.py | 8 ++- .../numpy_minimum_eigen_solver.py | 10 ++-- .../algorithms/minimum_eigen_solvers/vqe.py | 60 ++++++++++++------- 5 files changed, 80 insertions(+), 43 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index a1092719b92b..0953fba7215a 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -13,12 +13,16 @@ """The Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional, Any +from typing import Dict, Optional, Any, List, Union, TypeVar import numpy as np from qiskit.opflow import OperatorBase from ..algorithm_result import AlgorithmResult +# Introduced new type to maintain readability. +T = TypeVar('T') +ListOrDict = Union[List[Optional[T]], Dict[Any, T]] + class Eigensolver(ABC): """The Eigensolver Interface. @@ -30,7 +34,7 @@ class Eigensolver(ABC): @abstractmethod def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] = None ) -> "EigensolverResult": """ Computes eigenvalues. Operator and aux_operators can be supplied here and diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index 70e978a90cd2..05271f499b9a 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -13,14 +13,14 @@ """The Eigensolver algorithm.""" import logging -from typing import List, Optional, Union, Tuple, Callable, Dict +from typing import List, Optional, Union, Tuple, Callable, Dict, Any, TypeVar import numpy as np from scipy import sparse as scisparse from qiskit.opflow import OperatorBase, I, StateFn, ListOp from qiskit.utils.validation import validate_min -from .eigen_solver import Eigensolver, EigensolverResult +from .eigen_solver import Eigensolver, EigensolverResult, ListOrDict from ..exceptions import AlgorithmError logger = logging.getLogger(__name__) @@ -46,7 +46,7 @@ def __init__( self, k: int = 1, filter_criterion: Callable[ - [Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool + [Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool ] = None, ) -> None: """ @@ -84,7 +84,7 @@ def k(self, k: int) -> None: @property def filter_criterion( self, - ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[List[float]]], bool]]: + ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool]]: """returns the filter criterion if set""" return self._filter_criterion @@ -92,7 +92,7 @@ def filter_criterion( def filter_criterion( self, filter_criterion: Optional[ - Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool] + Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] ], ) -> None: """set the filter criterion""" @@ -141,7 +141,7 @@ def _get_ground_state_energy(self, operator: OperatorBase) -> None: self._solve(operator) def _get_energies( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, OperatorBase]] + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] ) -> None: if self._ret.eigenvalues is None or self._ret.eigenstates is None: self._solve(operator) @@ -156,12 +156,19 @@ def _get_energies( @staticmethod def _eval_aux_operators( - aux_operators: Dict[str, OperatorBase], wavefn, threshold: float = 1e-12 - ) -> Dict[str, Optional[Tuple[float, int]]]: - values = {} # type: Dict[str, Optional[Tuple[float, int]]] - for key, operator in aux_operators.items(): + aux_operators: ListOrDict[OperatorBase], wavefn, threshold: float = 1e-12 + ) -> ListOrDict[Tuple[float, int]]: + + # If aux_operators is a list, it can contain None operators for which None values are returned. + # If aux_operators is a dict, the None operators have been dropped in compute_eigenvalues. + if isinstance(aux_operators, list): + values = [None] * len(aux_operators) # type: ListOrDict[Tuple[float, int]] + key_op_iterator = enumerate(aux_operators) + else: + values = {} + key_op_iterator = aux_operators.items() + for key, operator in key_op_iterator: if operator is None: - values[key] = None continue value = 0.0 if operator.coeff != 0: @@ -178,7 +185,7 @@ def _eval_aux_operators( return values def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] = None ) -> EigensolverResult: super().compute_eigenvalues(operator, aux_operators) @@ -186,10 +193,12 @@ def compute_eigenvalues( raise AlgorithmError("Operator was never provided") self._check_set_k(operator) - if aux_operators: + if isinstance(aux_operators, list): zero_op = I.tensorpower(operator.num_qubits) * 0.0 # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = {key: zero_op if op == 0 else op for key, op in aux_operators.items()} + aux_operators = [zero_op if op == 0 else op for key, op in aux_operators] + elif isinstance(aux_operators, dict): + aux_operators = {key: op for key, op in aux_operators.items() if not op} else: aux_operators = None diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 67b2c35ca49c..5ee41333fe73 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -13,12 +13,16 @@ """The Minimum Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional +from typing import Dict, Optional, Any, List, Union, TypeVar import numpy as np from qiskit.opflow import OperatorBase from ..algorithm_result import AlgorithmResult +# Introduced new type to maintain readability. +T = TypeVar('T') +ListOrDict = Union[List[Optional[T]], Dict[Any, T]] + class MinimumEigensolver(ABC): """The Minimum Eigensolver Interface. @@ -30,7 +34,7 @@ class MinimumEigensolver(ABC): @abstractmethod def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] = None ) -> "MinimumEigensolverResult": """ Computes minimum eigenvalue. Operator and aux_operators can be supplied here and diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py index 63fbeaaa950e..4d2cb5948fb5 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py @@ -18,7 +18,7 @@ from qiskit.opflow import OperatorBase from ..eigen_solvers.numpy_eigen_solver import NumPyEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult +from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult, ListOrDict logger = logging.getLogger(__name__) @@ -31,7 +31,7 @@ class NumPyMinimumEigensolver(MinimumEigensolver): def __init__( self, filter_criterion: Callable[ - [Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool + [Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool ] = None, ) -> None: """ @@ -49,7 +49,7 @@ def __init__( @property def filter_criterion( self, - ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool]]: + ) -> Optional[Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool]]: """returns the filter criterion if set""" return self._ces.filter_criterion @@ -57,7 +57,7 @@ def filter_criterion( def filter_criterion( self, filter_criterion: Optional[ - Callable[[Union[List, np.ndarray], float, Optional[Dict[str, float]]], bool] + Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] ], ) -> None: """set the filter criterion""" @@ -68,7 +68,7 @@ def supports_aux_operators(cls) -> bool: return NumPyEigensolver.supports_aux_operators() def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] = None ) -> MinimumEigensolverResult: super().compute_minimum_eigenvalue(operator, aux_operators) result_ces = self._ces.compute_eigenvalues(operator, aux_operators) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index f6d8adbaa724..ac77e4906bdc 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -42,7 +42,7 @@ from qiskit.utils import QuantumInstance, algorithm_globals from ..optimizers import Optimizer, SLSQP from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult +from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult, ListOrDict from ..exceptions import AlgorithmError logger = logging.getLogger(__name__) @@ -411,33 +411,49 @@ def supports_aux_operators(cls) -> bool: def _eval_aux_ops( self, parameters: np.ndarray, - aux_operators: Dict[str, OperatorBase], + aux_operators: ListOrDict[OperatorBase], expectation: ExpectationBase, threshold: float = 1e-12, ) -> np.ndarray: # Create new CircuitSampler to avoid breaking existing one's caches. sampler = CircuitSampler(self.quantum_instance) - # Assumption is made that the order of operators in ListOp does not affect the measurement results - aux_op_meas = expectation.convert(StateFn(ListOp(list(aux_operators.values())), is_measurement=True)) + if isinstance(aux_operators, dict): + list_op = ListOp(list(aux_operators.values())) + else: + list_op = ListOp(aux_operators) + + aux_op_meas = expectation.convert(StateFn(list_op, is_measurement=True)) aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.ansatz.bind_parameters(parameters))) values = np.real(sampler.convert(aux_op_expect).eval()) # Discard values below threshold - clean_values = values * (np.abs(values) > threshold) - aux_op_results = {key: value for key, value in zip(aux_operators, clean_values)} - # Deal with the aux_op behavior where there can be Nones or Zero qubit Paulis in the list - aux_operator_eigenvalues = { - key: None if aux_operators[key] is None else result - for key, result in aux_op_results.items() - } + aux_op_results = values * (np.abs(values) > threshold) + + # Return None eigenvalues for None operators if aux_operators is a list. + # If aux_operators is a dict, the None operators should have been dropped in compute_minimum_eigenvalue + if isinstance(aux_operators, list): + aux_operator_eigenvalues = [None] * len(aux_operators) + key_value_iterator = enumerate(aux_op_results) + else: + aux_operator_eigenvalues = {} + key_value_iterator = zip(aux_operators.keys(), aux_op_results) + + for key, value in key_value_iterator: + if aux_operators[key] is not None: + aux_operator_eigenvalues[key] = value + # # Deal with the aux_op behavior where there can be Nones or Zero qubit Paulis in the list + # aux_operator_eigenvalues = { + # key: None if aux_operators[key] is None else result + # for key, result in aux_op_results.items() + # } # As this has mixed types, since it can included None, it needs to explicitly pass object # data type to avoid numpy 1.19 warning message about implicit conversion being deprecated aux_operator_eigenvalues = np.array([aux_operator_eigenvalues], dtype=object) return aux_operator_eigenvalues def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: Optional[Dict[str, Optional[OperatorBase]]] = None + self, operator: OperatorBase, aux_operators: Optional[ListOrDict[OperatorBase]] = None ) -> MinimumEigensolverResult: super().compute_minimum_eigenvalue(operator, aux_operators) @@ -455,19 +471,23 @@ def compute_minimum_eigenvalue( initial_point = _validate_initial_point(self.initial_point, self.ansatz) bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being Optional i.e. having value None if aux_operators: zero_op = I.tensorpower(operator.num_qubits) * 0.0 - converted = {} - for key, op in aux_operators.items(): - if op is None: - converted[key] = zero_op - else: + + # Convert the None operators when aux_operators is a list. Drop them is aux_operators is a dict. + if isinstance(aux_operators, list): + key_op_iterator = enumerate(aux_operators) + converted = [zero_op] * len(aux_operators) + else: + key_op_iterator = aux_operators.items() + converted = {} + for key, op in key_op_iterator: + if op is not None: converted[key] = op - # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = {key: zero_op if op == 0 else op for key, op in converted.items()} + aux_operators = converted + else: aux_operators = None From 11ef00e9d6c7d25ef6c7bc52185376baabb9c7c5 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Sat, 7 Aug 2021 15:17:16 +0200 Subject: [PATCH 03/11] Fixed a typo and two conditional checks --- qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index 05271f499b9a..9aa495302e45 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -193,11 +193,11 @@ def compute_eigenvalues( raise AlgorithmError("Operator was never provided") self._check_set_k(operator) - if isinstance(aux_operators, list): + if isinstance(aux_operators, list) and len(aux_operators) > 0: zero_op = I.tensorpower(operator.num_qubits) * 0.0 # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for key, op in aux_operators] - elif isinstance(aux_operators, dict): + aux_operators = [zero_op if op == 0 else op for op in aux_operators] + elif isinstance(aux_operators, dict) and len(aux_operators) > 0: aux_operators = {key: op for key, op in aux_operators.items() if not op} else: aux_operators = None From 7eb96e1b439735e261f4a13e8cd43d8d84ffc114 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Tue, 10 Aug 2021 21:08:05 +0200 Subject: [PATCH 04/11] Added new unittest for NumPyMinimumEigenSolver and fixed lint issues --- .../algorithms/eigen_solvers/eigen_solver.py | 4 +- .../eigen_solvers/numpy_eigen_solver.py | 8 +-- .../minimum_eigen_solver.py | 4 +- .../numpy_minimum_eigen_solver.py | 2 +- .../algorithms/minimum_eigen_solvers/vqe.py | 5 +- .../test_numpy_minimum_eigen_solver.py | 50 ++++++++++++++++--- 6 files changed, 55 insertions(+), 18 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index 0953fba7215a..75dcc71534a3 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -20,8 +20,8 @@ from ..algorithm_result import AlgorithmResult # Introduced new type to maintain readability. -T = TypeVar('T') -ListOrDict = Union[List[Optional[T]], Dict[Any, T]] +_T = TypeVar("_T") # Pylint does not allow single character class names. +ListOrDict = Union[List[Optional[_T]], Dict[Any, _T]] class Eigensolver(ABC): diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index 9aa495302e45..848d3e361712 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -13,7 +13,7 @@ """The Eigensolver algorithm.""" import logging -from typing import List, Optional, Union, Tuple, Callable, Dict, Any, TypeVar +from typing import List, Optional, Union, Tuple, Callable import numpy as np from scipy import sparse as scisparse @@ -159,8 +159,8 @@ def _eval_aux_operators( aux_operators: ListOrDict[OperatorBase], wavefn, threshold: float = 1e-12 ) -> ListOrDict[Tuple[float, int]]: - # If aux_operators is a list, it can contain None operators for which None values are returned. - # If aux_operators is a dict, the None operators have been dropped in compute_eigenvalues. + # As a list, aux_operators can contain None operators for which None values are returned. + # As a dict, the None operators in aux_operators have been dropped in compute_eigenvalues. if isinstance(aux_operators, list): values = [None] * len(aux_operators) # type: ListOrDict[Tuple[float, int]] key_op_iterator = enumerate(aux_operators) @@ -198,7 +198,7 @@ def compute_eigenvalues( # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. aux_operators = [zero_op if op == 0 else op for op in aux_operators] elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = {key: op for key, op in aux_operators.items() if not op} + aux_operators = {key: op for key, op in aux_operators.items() if op} else: aux_operators = None diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 5ee41333fe73..f52ee0ff654e 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -20,8 +20,8 @@ from ..algorithm_result import AlgorithmResult # Introduced new type to maintain readability. -T = TypeVar('T') -ListOrDict = Union[List[Optional[T]], Dict[Any, T]] +_T = TypeVar("_T") # Pylint does not allow single character class names. +ListOrDict = Union[List[Optional[_T]], Dict[Any, _T]] class MinimumEigensolver(ABC): diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py index 4d2cb5948fb5..369485976cdf 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py @@ -12,7 +12,7 @@ """The Numpy Minimum Eigensolver algorithm.""" -from typing import List, Optional, Union, Callable, Dict +from typing import List, Optional, Union, Callable import logging import numpy as np diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index ac77e4906bdc..a4c420d13279 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -431,7 +431,7 @@ def _eval_aux_ops( aux_op_results = values * (np.abs(values) > threshold) # Return None eigenvalues for None operators if aux_operators is a list. - # If aux_operators is a dict, the None operators should have been dropped in compute_minimum_eigenvalue + # None operators are already dropped in compute_minimum_eigenvalue if aux_operators is a dict. if isinstance(aux_operators, list): aux_operator_eigenvalues = [None] * len(aux_operators) key_value_iterator = enumerate(aux_op_results) @@ -475,7 +475,8 @@ def compute_minimum_eigenvalue( if aux_operators: zero_op = I.tensorpower(operator.num_qubits) * 0.0 - # Convert the None operators when aux_operators is a list. Drop them is aux_operators is a dict. + # Convert the None operators when aux_operators is a list. + # Drop None operators when aux_operators is a dict. if isinstance(aux_operators, list): key_op_iterator = enumerate(aux_operators) converted = [zero_op] * len(aux_operators) diff --git a/test/python/algorithms/test_numpy_minimum_eigen_solver.py b/test/python/algorithms/test_numpy_minimum_eigen_solver.py index 4971d7863bd2..dca81d9de8a1 100644 --- a/test/python/algorithms/test_numpy_minimum_eigen_solver.py +++ b/test/python/algorithms/test_numpy_minimum_eigen_solver.py @@ -40,12 +40,15 @@ def setUp(self): aux_op1 = PauliSumOp.from_list([("II", 2.0)]) aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - self.aux_ops = [aux_op1, aux_op2] + self.aux_ops_list = [aux_op1, aux_op2] + self.aux_ops_dict = {"aux_op1": aux_op1, "aux_op2": aux_op2} def test_cme(self): """Basic test""" algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=self.aux_ops) + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_list + ) self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) self.assertEqual(len(result.aux_operator_eigenvalues), 2) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) @@ -60,7 +63,9 @@ def test_cme_reuse(self): self.assertIsNone(result.aux_operator_eigenvalues) # Add aux_operators and go again - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=self.aux_ops) + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_list + ) self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) self.assertEqual(len(result.aux_operator_eigenvalues), 2) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) @@ -72,14 +77,16 @@ def test_cme_reuse(self): self.assertIsNone(result.aux_operator_eigenvalues) # Set aux_operators and go again - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=self.aux_ops) + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_list + ) self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) self.assertEqual(len(result.aux_operator_eigenvalues), 2) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) # Finally just set one of aux_operators and main operator, remove aux_operators - result = algo.compute_minimum_eigenvalue(operator=self.aux_ops[0], aux_operators=[]) + result = algo.compute_minimum_eigenvalue(operator=self.aux_ops_list[0], aux_operators=[]) self.assertAlmostEqual(result.eigenvalue, 2 + 0j) self.assertIsNone(result.aux_operator_eigenvalues) @@ -92,7 +99,9 @@ def criterion(x, v, a_v): return v >= -0.5 algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=self.aux_ops) + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_list + ) self.assertAlmostEqual(result.eigenvalue, -0.22491125 + 0j) self.assertEqual(len(result.aux_operator_eigenvalues), 2) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) @@ -107,7 +116,9 @@ def criterion(x, v, a_v): return False algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=self.aux_ops) + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_list + ) self.assertEqual(result.eigenvalue, None) self.assertEqual(result.eigenstate, None) self.assertEqual(result.aux_operator_eigenvalues, None) @@ -119,6 +130,31 @@ def test_cme_1q(self, op): result = algo.compute_minimum_eigenvalue(operator=op) self.assertAlmostEqual(result.eigenvalue, -1) + def test_cme_aux_ops_dict(self): + """Test dictionary compatibility of aux_operators""" + # Start with an empty dictionary + algo = NumPyMinimumEigensolver() + result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators={}) + self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) + self.assertIsNone(result.aux_operator_eigenvalues) + + # Add aux_operators dictionary and go again + result = algo.compute_minimum_eigenvalue( + operator=self.qubit_op, aux_operators=self.aux_ops_dict + ) + self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) + self.assertEqual(len(result.aux_operator_eigenvalues), 2) + np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) + np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) + + # Add None and zero operators and go again + extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} + result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=extra_ops) + self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) + self.assertEqual(len(result.aux_operator_eigenvalues), 2) + np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) + np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) + if __name__ == "__main__": unittest.main() From 9be9006bedaebf26b199ba8c4b4b184405933c0f Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Sat, 14 Aug 2021 23:40:09 +0200 Subject: [PATCH 05/11] Added VQE unittest for testing aux_operators dictionaries --- .../minimum_eigen_solver.py | 4 +-- .../algorithms/minimum_eigen_solvers/vqe.py | 21 +++++--------- test/python/algorithms/test_vqe.py | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index f52ee0ff654e..2fc2e649eb70 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -98,11 +98,11 @@ def eigenstate(self, value: np.ndarray) -> None: self._eigenstate = value @property - def aux_operator_eigenvalues(self) -> Optional[np.ndarray]: + def aux_operator_eigenvalues(self) -> Optional[ListOrDict[Union[float, complex]]]: """return aux operator eigen values""" return self._aux_operator_eigenvalues @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: np.ndarray) -> None: + def aux_operator_eigenvalues(self, value: ListOrDict[Union[float, complex]]) -> None: """set aux operator eigen values""" self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index a4c420d13279..f2f1e80866c7 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -414,7 +414,7 @@ def _eval_aux_ops( aux_operators: ListOrDict[OperatorBase], expectation: ExpectationBase, threshold: float = 1e-12, - ) -> np.ndarray: + ) -> ListOrDict[complex]: # Create new CircuitSampler to avoid breaking existing one's caches. sampler = CircuitSampler(self.quantum_instance) @@ -440,16 +440,9 @@ def _eval_aux_ops( key_value_iterator = zip(aux_operators.keys(), aux_op_results) for key, value in key_value_iterator: - if aux_operators[key] is not None: + if aux_operators[key]: aux_operator_eigenvalues[key] = value - # # Deal with the aux_op behavior where there can be Nones or Zero qubit Paulis in the list - # aux_operator_eigenvalues = { - # key: None if aux_operators[key] is None else result - # for key, result in aux_op_results.items() - # } - # As this has mixed types, since it can included None, it needs to explicitly pass object - # data type to avoid numpy 1.19 warning message about implicit conversion being deprecated - aux_operator_eigenvalues = np.array([aux_operator_eigenvalues], dtype=object) + return aux_operator_eigenvalues def compute_minimum_eigenvalue( @@ -471,12 +464,12 @@ def compute_minimum_eigenvalue( initial_point = _validate_initial_point(self.initial_point, self.ansatz) bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being Optional i.e. having value None + # We need to handle the array entries being zero or Optional i.e. having value None if aux_operators: zero_op = I.tensorpower(operator.num_qubits) * 0.0 - # Convert the None operators when aux_operators is a list. - # Drop None operators when aux_operators is a dict. + # Convert the None and zero values when aux_operators is a list. + # Drop None and zero values when aux_operators is a dict. if isinstance(aux_operators, list): key_op_iterator = enumerate(aux_operators) converted = [zero_op] * len(aux_operators) @@ -484,7 +477,7 @@ def compute_minimum_eigenvalue( key_op_iterator = aux_operators.items() converted = {} for key, op in key_op_iterator: - if op is not None: + if op: converted[key] = op aux_operators = converted diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py index 3a84acfec7c8..b2219ad92e1c 100644 --- a/test/python/algorithms/test_vqe.py +++ b/test/python/algorithms/test_vqe.py @@ -473,6 +473,34 @@ def wrapped_run(circuits, **kwargs): self.assertEqual(callcount["count"], expected) + def test_aux_operators_dict(self): + """Test dictionary compatibility of aux_operators""" + wavefunction = self.ry_wavefunction + vqe = VQE(ansatz=wavefunction, quantum_instance=self.statevector_simulator) + + # Start with an empty dictionary + result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) + self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) + self.assertIsNone(result.aux_operator_eigenvalues) + + # Go again with two auxiliary operators + aux_op1 = PauliSumOp.from_list([("II", 2.0)]) + aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) + aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} + result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) + self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) + self.assertEqual(len(result.aux_operator_eigenvalues), 2) + self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"], 2, places=6) + self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"], 0, places=6) + + # Go again with additional None and zero operators + extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} + result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) + self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) + self.assertEqual(len(result.aux_operator_eigenvalues), 2) + self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"], 2, places=6) + self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"], 0, places=6) + if __name__ == "__main__": unittest.main() From 4e09082a866def08ba6ace2ad671b06b0b14f0b8 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Sun, 29 Aug 2021 15:51:13 +0200 Subject: [PATCH 06/11] Updated aux_operator_eigenvalues type hint --- qiskit/algorithms/eigen_solvers/eigen_solver.py | 4 ++-- .../algorithms/minimum_eigen_solvers/minimum_eigen_solver.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index 75dcc71534a3..cc97d7a502bd 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -94,11 +94,11 @@ def eigenstates(self, value: np.ndarray) -> None: self._eigenstates = value @property - def aux_operator_eigenvalues(self) -> Optional[np.ndarray]: + def aux_operator_eigenvalues(self) -> Optional[List[ListOrDict[complex]]]: """return aux operator eigen values""" return self._aux_operator_eigenvalues @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: np.ndarray) -> None: + def aux_operator_eigenvalues(self, value: List[ListOrDict[complex]]) -> None: """set aux operator eigen values""" self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 2fc2e649eb70..c830572234bc 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -98,11 +98,11 @@ def eigenstate(self, value: np.ndarray) -> None: self._eigenstate = value @property - def aux_operator_eigenvalues(self) -> Optional[ListOrDict[Union[float, complex]]]: + def aux_operator_eigenvalues(self) -> Optional[ListOrDict[complex]]: """return aux operator eigen values""" return self._aux_operator_eigenvalues @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: ListOrDict[Union[float, complex]]) -> None: + def aux_operator_eigenvalues(self, value: ListOrDict[complex]) -> None: """set aux operator eigen values""" self._aux_operator_eigenvalues = value From ccf1dc59e5ad12b2131cc0827c53847ae6a37beb Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Tue, 31 Aug 2021 00:42:18 +0200 Subject: [PATCH 07/11] Minor fix regarding VQE aux_operators --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index f2f1e80866c7..8e3585619f1b 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -440,7 +440,7 @@ def _eval_aux_ops( key_value_iterator = zip(aux_operators.keys(), aux_op_results) for key, value in key_value_iterator: - if aux_operators[key]: + if aux_operators[key] is not None: aux_operator_eigenvalues[key] = value return aux_operator_eigenvalues @@ -469,7 +469,7 @@ def compute_minimum_eigenvalue( zero_op = I.tensorpower(operator.num_qubits) * 0.0 # Convert the None and zero values when aux_operators is a list. - # Drop None and zero values when aux_operators is a dict. + # Drop None and convert zero values when aux_operators is a dict. if isinstance(aux_operators, list): key_op_iterator = enumerate(aux_operators) converted = [zero_op] * len(aux_operators) @@ -477,8 +477,8 @@ def compute_minimum_eigenvalue( key_op_iterator = aux_operators.items() converted = {} for key, op in key_op_iterator: - if op: - converted[key] = op + if op is not None: + converted[key] = zero_op if op == 0 else op aux_operators = converted From 682435176c1b67af3d6e271463b3372b9b6f7d9c Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Tue, 31 Aug 2021 23:14:18 +0200 Subject: [PATCH 08/11] Update VQE and NumpyMinimumEigensolver unittests --- qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py | 8 ++++++-- test/python/algorithms/test_numpy_minimum_eigen_solver.py | 3 ++- test/python/algorithms/test_vqe.py | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index 848d3e361712..b7991dee81b4 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -193,12 +193,16 @@ def compute_eigenvalues( raise AlgorithmError("Operator was never provided") self._check_set_k(operator) + zero_op = I.tensorpower(operator.num_qubits) * 0.0 if isinstance(aux_operators, list) and len(aux_operators) > 0: - zero_op = I.tensorpower(operator.num_qubits) * 0.0 # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. aux_operators = [zero_op if op == 0 else op for op in aux_operators] elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = {key: op for key, op in aux_operators.items() if op} + aux_operators = { + key: zero_op if op == 0 else op # Convert zero values to zero operators + for key, op in aux_operators.items() + if op is not None # Discard None values + } else: aux_operators = None diff --git a/test/python/algorithms/test_numpy_minimum_eigen_solver.py b/test/python/algorithms/test_numpy_minimum_eigen_solver.py index dca81d9de8a1..cd08185bd577 100644 --- a/test/python/algorithms/test_numpy_minimum_eigen_solver.py +++ b/test/python/algorithms/test_numpy_minimum_eigen_solver.py @@ -151,9 +151,10 @@ def test_cme_aux_ops_dict(self): extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=extra_ops) self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) + self.assertEqual(len(result.aux_operator_eigenvalues), 3) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) + self.assertEqual(result.aux_operator_eigenvalues["zero_op"], (0.0, 0)) if __name__ == "__main__": diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py index b2219ad92e1c..aecc7189be24 100644 --- a/test/python/algorithms/test_vqe.py +++ b/test/python/algorithms/test_vqe.py @@ -497,9 +497,10 @@ def test_aux_operators_dict(self): extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) + self.assertEqual(len(result.aux_operator_eigenvalues), 3) self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"], 2, places=6) self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"], 0, places=6) + self.assertEqual(result.aux_operator_eigenvalues["zero_operator"], 0.0) if __name__ == "__main__": From 23cfa63c6c0c1e028e29b7f6914e223a13ef6e6f Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Fri, 10 Sep 2021 01:32:16 +0200 Subject: [PATCH 09/11] Added a releasenote --- .../support-dict-for-aux-operators-c3c9ad380c208afd.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/support-dict-for-aux-operators-c3c9ad380c208afd.yaml diff --git a/releasenotes/notes/support-dict-for-aux-operators-c3c9ad380c208afd.yaml b/releasenotes/notes/support-dict-for-aux-operators-c3c9ad380c208afd.yaml new file mode 100644 index 000000000000..a487e76601a0 --- /dev/null +++ b/releasenotes/notes/support-dict-for-aux-operators-c3c9ad380c208afd.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The ``EigenSolver`` and ``MinimumEigenSolver`` interfaces now support the type + ``Dict[str, Optional[OperatorBase]]`` for the ``aux_operators`` parameter in the respective + ``compute_eigenvalues`` and ``compute_minimum_eigenvalue`` methods. + In this case, the auxiliary eigenvalues are also stored in a dictionary under the same keys + provided by the `aux_operators` dictionary. Keys that correspond to an operator that does not commute + with the main operator are dropped. \ No newline at end of file From 68895c9b8a591145268df4a9329b14fadfa1ce24 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Wed, 13 Oct 2021 22:13:12 +0200 Subject: [PATCH 10/11] Updated ListOrDict typehint --- qiskit/algorithms/eigen_solvers/eigen_solver.py | 2 +- qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index cc97d7a502bd..ab9a19b78a82 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -21,7 +21,7 @@ # Introduced new type to maintain readability. _T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[Any, _T]] +ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] class Eigensolver(ABC): diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index c830572234bc..4919367b0e62 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -21,7 +21,7 @@ # Introduced new type to maintain readability. _T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[Any, _T]] +ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] class MinimumEigensolver(ABC): From 2b3a6d2f8d27dc961d1a54c99d71f7325188fae8 Mon Sep 17 00:00:00 2001 From: CisterMoke Date: Wed, 13 Oct 2021 23:58:23 +0200 Subject: [PATCH 11/11] Remove unused imports --- qiskit/algorithms/eigen_solvers/eigen_solver.py | 2 +- qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index ab9a19b78a82..fb46137459bb 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -13,7 +13,7 @@ """The Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional, Any, List, Union, TypeVar +from typing import Dict, Optional, List, Union, TypeVar import numpy as np from qiskit.opflow import OperatorBase diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 4919367b0e62..62335b9715c0 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -13,7 +13,7 @@ """The Minimum Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional, Any, List, Union, TypeVar +from typing import Dict, Optional, List, Union, TypeVar import numpy as np from qiskit.opflow import OperatorBase