From b7723c8be9aebe33b71ed7450d9b13a44f86ddda Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 15 Jun 2020 11:23:15 +0200 Subject: [PATCH 1/7] compute eigenvalues more efficiently, if possible --- .../algorithms/minimum_eigen_optimizer.py | 88 ++++++++++++------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 7c1d898b20..9b605f248b 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -16,11 +16,14 @@ """A wrapper for minimum eigen solvers from Aqua to be used within the optimization module.""" from typing import Optional, Any, Union, Tuple, List, cast +import warnings import numpy as np -from qiskit import QuantumCircuit, BasicAer, execute +from qiskit.providers import BaseBackend +from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import MinimumEigensolver -from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator, StateFn, DictStateFn +from qiskit.aqua.operators import (LegacyBaseOperator, OperatorBase, StateFn, DictStateFn, + CircuitSampler) from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from ..problems.quadratic_program import QuadraticProgram @@ -160,7 +163,8 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: eigen_results = self._min_eigen_solver.compute_minimum_eigenvalue(operator) # analyze results - samples = eigenvector_to_solutions(eigen_results.eigenstate, operator) + backend = getattr(self._min_eigen_solver, 'quantum_instance', None) + samples = eigenvector_to_solutions(eigen_results.eigenstate, operator, backend=backend) samples = [(res[0], problem_.objective.sense.value * (res[1] + offset), res[2]) for res in samples] samples.sort(key=lambda x: problem_.objective.sense.value * x[1]) @@ -183,29 +187,39 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], - operator: Union[WeightedPauliOperator, MatrixOperator], - min_probability: float = 1e-6) -> List[Tuple[str, float, float]]: + operator: Union[OperatorBase, LegacyBaseOperator], + min_probability: float = 1e-6, + backend: Optional[Union[BaseBackend, QuantumInstance]] = None, + ) -> List[Tuple[str, float, float]]: """Convert the eigenvector to the bitstrings and corresponding eigenvalues. - Examples: - >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) - >>> eigenvectors = {'0': 12, '1': 1} - >>> print(eigenvector_to_solutions(eigenvectors, op)) - [('0', 0.7071067811865475, 0.9230769230769231), ('1', -0.7071067811865475, 0.07692307692307693)] - - >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) - >>> eigenvectors = numpy.array([1, 1] / numpy.sqrt(2), dtype=complex) - >>> print(eigenvector_to_solutions(eigenvectors, op)) - [('0', 0.7071067811865475, 0.4999999999999999), ('1', -0.7071067811865475, 0.4999999999999999)] + Args: + eigenvector: The eigenvector from which the solution states are extracted. + operator: The operator to compute the eigenvalues. + min_probability: Only consider states where the amplitude exceeds this threshold. + backend: The backend to use to compute the eigevalues expression. If None, the evaluation + is done exactly by converting the operator to a matrix. Returns: For each computational basis state contained in the eigenvector, return the basis state as bitstring along with the operator evaluated at that bitstring and the probability of sampling this bitstring from the eigenvector. - Raises: - TypeError: Invalid Argument + Examples: + >>> op = MatrixOp(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = {'0': 12, '1': 1} + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.9230769230769231), + ('1', -0.7071067811865475, 0.07692307692307693)] + + >>> op = MatrixOp(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = numpy.array([1, 1] / numpy.sqrt(2), dtype=complex) + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.4999999999999999), + ('1', -0.7071067811865475, 0.4999999999999999)] + Raises: + TypeError: If the type of eigenvector is not supported. """ if isinstance(eigenvector, DictStateFn): eigenvector = {bitstr: val**2 for (bitstr, val) in eigenvector.primitive.items()} @@ -221,7 +235,7 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], # add the bitstring, if the sampling probability exceeds the threshold if sampling_probability > 0: if sampling_probability >= min_probability: - value = eval_operator_at_bitstring(operator, bitstr) + value = eval_operator_at_bitstring(operator, bitstr, backend) solutions += [(bitstr, value, sampling_probability)] elif isinstance(eigenvector, np.ndarray): @@ -235,7 +249,7 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], if sampling_probability > 0: if sampling_probability >= min_probability: bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1] - value = eval_operator_at_bitstring(operator, bitstr) + value = eval_operator_at_bitstring(operator, bitstr, backend) solutions += [(bitstr, value, sampling_probability)] else: @@ -244,8 +258,9 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], return solutions -def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOperator], - bitstr: str) -> float: +def eval_operator_at_bitstring(operator: Union[OperatorBase, LegacyBaseOperator], bitstr: str, + backend: Optional[Union[BaseBackend, QuantumInstance]] = None + ) -> float: """Evaluate an Aqua operator at a given bitstring. This simulates a circuit representing the bitstring. Note that this will not be needed @@ -254,20 +269,25 @@ def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOper Args: operator: The operator which is evaluated. bitstr: The bitstring at which the operator is evaluated. + backend: The backend to use to evaluate the expression. If None, the evaluation is done + exactly by converting the operator to a matrix. Returns: The operator evaluated with the quantum state the bitstring describes. """ - # TODO check that operator size and bitstr are compatible - circuit = QuantumCircuit(len(bitstr)) - for i, bit in enumerate(bitstr): - if bit == '1': - circuit.x(i) - - # simulate the circuit - result = execute(circuit, BasicAer.get_backend('statevector_simulator')).result() - - # evaluate the operator - value = np.real(operator.evaluate_with_statevector(result.get_statevector())[0]) - - return value + if isinstance(operator, LegacyBaseOperator): + warnings.warn('Passing a LegacyBaseOperator to eval_operator_at_bitring is deprecated as ' + 'of 0.9.0 and will be removed no earlier than 3 months after the release. ' + 'You should pass an OperatorBase object instead.', DeprecationWarning, + stacklevel=2) + operator = operator.to_opflow() + + state = StateFn(bitstr[::-1]) + expr = StateFn(operator).adjoint() @ state + + # lazy evaluation, not very efficient for large number of qubits! + if backend is None: + return expr.eval().real + else: + sampler = CircuitSampler(backend).convert(expr) + return sampler.eval().real From 5aeda79184ffcd92c6ec2a600f97a8c52829e7c1 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 15 Jun 2020 11:25:23 +0200 Subject: [PATCH 2/7] remove note --- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 9b605f248b..d884505aba 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -263,9 +263,6 @@ def eval_operator_at_bitstring(operator: Union[OperatorBase, LegacyBaseOperator] ) -> float: """Evaluate an Aqua operator at a given bitstring. - This simulates a circuit representing the bitstring. Note that this will not be needed - with the Operator logic introduced in 0.7.0. - Args: operator: The operator which is evaluated. bitstr: The bitstring at which the operator is evaluated. From 9d1466aad96db902b347929956886601d82da05f Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 15 Jun 2020 13:52:00 +0200 Subject: [PATCH 3/7] fix spell --- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index d884505aba..efc77cc241 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -197,7 +197,7 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], eigenvector: The eigenvector from which the solution states are extracted. operator: The operator to compute the eigenvalues. min_probability: Only consider states where the amplitude exceeds this threshold. - backend: The backend to use to compute the eigevalues expression. If None, the evaluation + backend: The backend to use to compute the eigenvalues expression. If None, the evaluation is done exactly by converting the operator to a matrix. Returns: From 016863c4534db36fb4e09604c6942e8670aa81e9 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 15 Jun 2020 17:59:21 +0200 Subject: [PATCH 4/7] use qubo to compute eigenvalue, not operator --- .../algorithms/minimum_eigen_optimizer.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index efc77cc241..2f0838c986 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -163,10 +163,11 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: eigen_results = self._min_eigen_solver.compute_minimum_eigenvalue(operator) # analyze results - backend = getattr(self._min_eigen_solver, 'quantum_instance', None) - samples = eigenvector_to_solutions(eigen_results.eigenstate, operator, backend=backend) - samples = [(res[0], problem_.objective.sense.value * (res[1] + offset), res[2]) - for res in samples] + # backend = getattr(self._min_eigen_solver, 'quantum_instance', None) + samples = _eigenvector_to_solutions(eigen_results.eigenstate, problem_) + # print(offset, samples) + # samples = [(res[0], problem_.objective.sense.value * (res[1] + offset), res[2]) + # for res in samples] samples.sort(key=lambda x: problem_.objective.sense.value * x[1]) x = samples[0][0] fval = samples[0][1] @@ -186,19 +187,16 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: return opt_res -def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], - operator: Union[OperatorBase, LegacyBaseOperator], - min_probability: float = 1e-6, - backend: Optional[Union[BaseBackend, QuantumInstance]] = None, - ) -> List[Tuple[str, float, float]]: +def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], + qubo: QuadraticProgram, + min_probability: float = 1e-6, + ) -> List[Tuple[str, float, float]]: """Convert the eigenvector to the bitstrings and corresponding eigenvalues. Args: eigenvector: The eigenvector from which the solution states are extracted. - operator: The operator to compute the eigenvalues. + qubo: The QUBO to evaluate at the bitstring. min_probability: Only consider states where the amplitude exceeds this threshold. - backend: The backend to use to compute the eigenvalues expression. If None, the evaluation - is done exactly by converting the operator to a matrix. Returns: For each computational basis state contained in the eigenvector, return the basis @@ -235,7 +233,7 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], # add the bitstring, if the sampling probability exceeds the threshold if sampling_probability > 0: if sampling_probability >= min_probability: - value = eval_operator_at_bitstring(operator, bitstr, backend) + value = qubo.objective.evaluate([int(bit) for bit in bitstr]) solutions += [(bitstr, value, sampling_probability)] elif isinstance(eigenvector, np.ndarray): @@ -249,7 +247,7 @@ def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], if sampling_probability > 0: if sampling_probability >= min_probability: bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1] - value = eval_operator_at_bitstring(operator, bitstr, backend) + value = qubo.objective.evaluate([int(bit) for bit in bitstr]) solutions += [(bitstr, value, sampling_probability)] else: From 285b3c0120d6605c6b19a119f0235bd8f39aa9e5 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 15 Jun 2020 19:09:03 +0200 Subject: [PATCH 5/7] remove deprecation warning --- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 2f0838c986..1bf0ebd18b 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -16,7 +16,6 @@ """A wrapper for minimum eigen solvers from Aqua to be used within the optimization module.""" from typing import Optional, Any, Union, Tuple, List, cast -import warnings import numpy as np from qiskit.providers import BaseBackend @@ -271,10 +270,6 @@ def eval_operator_at_bitstring(operator: Union[OperatorBase, LegacyBaseOperator] The operator evaluated with the quantum state the bitstring describes. """ if isinstance(operator, LegacyBaseOperator): - warnings.warn('Passing a LegacyBaseOperator to eval_operator_at_bitring is deprecated as ' - 'of 0.9.0 and will be removed no earlier than 3 months after the release. ' - 'You should pass an OperatorBase object instead.', DeprecationWarning, - stacklevel=2) operator = operator.to_opflow() state = StateFn(bitstr[::-1]) From a07aea45c8d5d23745e6e06ab583131aa5687a15 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 16 Jun 2020 10:02:23 +0200 Subject: [PATCH 6/7] remove eval_operator_at_bitstring --- .../algorithms/minimum_eigen_optimizer.py | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 1bf0ebd18b..f5a5ed2de5 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -18,11 +18,8 @@ from typing import Optional, Any, Union, Tuple, List, cast import numpy as np -from qiskit.providers import BaseBackend -from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import MinimumEigensolver -from qiskit.aqua.operators import (LegacyBaseOperator, OperatorBase, StateFn, DictStateFn, - CircuitSampler) +from qiskit.aqua.operators import StateFn, DictStateFn from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from ..problems.quadratic_program import QuadraticProgram @@ -253,31 +250,3 @@ def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], raise TypeError('Unsupported format of eigenvector. Provide a dict or numpy.ndarray.') return solutions - - -def eval_operator_at_bitstring(operator: Union[OperatorBase, LegacyBaseOperator], bitstr: str, - backend: Optional[Union[BaseBackend, QuantumInstance]] = None - ) -> float: - """Evaluate an Aqua operator at a given bitstring. - - Args: - operator: The operator which is evaluated. - bitstr: The bitstring at which the operator is evaluated. - backend: The backend to use to evaluate the expression. If None, the evaluation is done - exactly by converting the operator to a matrix. - - Returns: - The operator evaluated with the quantum state the bitstring describes. - """ - if isinstance(operator, LegacyBaseOperator): - operator = operator.to_opflow() - - state = StateFn(bitstr[::-1]) - expr = StateFn(operator).adjoint() @ state - - # lazy evaluation, not very efficient for large number of qubits! - if backend is None: - return expr.eval().real - else: - sampler = CircuitSampler(backend).convert(expr) - return sampler.eval().real From b30ee70f844f9839241af75975255a437d5e7710 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 16 Jun 2020 15:46:29 +0200 Subject: [PATCH 7/7] Update qiskit/optimization/algorithms/minimum_eigen_optimizer.py --- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index f5a5ed2de5..cd7fe36bd8 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -196,7 +196,7 @@ def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], Returns: For each computational basis state contained in the eigenvector, return the basis - state as bitstring along with the operator evaluated at that bitstring and the + state as bitstring along with the QUBO evaluated at that bitstring and the probability of sampling this bitstring from the eigenvector. Examples: