From 40080942792d8f8fd9e195d1b55e31db64c8ecce Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 31 Aug 2022 18:38:30 -0400 Subject: [PATCH] Rewrite Amplitude Estimators with Primitives --- qiskit/algorithms/amplitude_estimators/ae.py | 89 ++++++++++++--- qiskit/algorithms/amplitude_estimators/fae.py | 101 +++++++++++++++--- qiskit/algorithms/amplitude_estimators/iae.py | 97 ++++++++++++++--- .../algorithms/amplitude_estimators/mlae.py | 91 +++++++++++++--- .../algorithms/test_amplitude_estimators.py | 52 +++++++-- 5 files changed, 361 insertions(+), 69 deletions(-) diff --git a/qiskit/algorithms/amplitude_estimators/ae.py b/qiskit/algorithms/amplitude_estimators/ae.py index 4d4c89873235..2c62c5c204b2 100644 --- a/qiskit/algorithms/amplitude_estimators/ae.py +++ b/qiskit/algorithms/amplitude_estimators/ae.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2022. # # 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 @@ -12,18 +12,23 @@ """The Quantum Phase Estimation-based Amplitude Estimation algorithm.""" -from typing import Optional, Union, List, Tuple, Dict +from __future__ import annotations +from typing import Union, List, Tuple, Dict from collections import OrderedDict +import warnings import numpy as np from scipy.stats import chi2, norm from scipy.optimize import bisect from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, JobStatus +from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance +from qiskit.utils.deprecation import deprecate_function from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .ae_utils import pdf_a, derivative_log_pdf_a, bisect_max from .estimation_problem import EstimationProblem +from ..exceptions import AlgorithmError class AmplitudeEstimation(AmplitudeEstimator): @@ -56,9 +61,10 @@ class AmplitudeEstimation(AmplitudeEstimator): def __init__( self, num_eval_qubits: int, - phase_estimation_circuit: Optional[QuantumCircuit] = None, - iqft: Optional[QuantumCircuit] = None, - quantum_instance: Optional[Union[QuantumInstance, Backend]] = None, + phase_estimation_circuit: None | QuantumCircuit = None, + iqft: None | QuantumCircuit = None, + quantum_instance: None | Union[QuantumInstance, Backend] = None, + sampler: None | BaseSampler = None, ) -> None: r""" Args: @@ -68,7 +74,9 @@ def __init__( `qiskit.circuit.library.PhaseEstimation` when None. iqft: The inverse quantum Fourier transform component, defaults to using a standard implementation from `qiskit.circuit.library.QFT` when None. - quantum_instance: The backend (or `QuantumInstance`) to execute the circuits on. + quantum_instance: Pending deprecation\: The backend (or `QuantumInstance`) to execute + the circuits on. + sampler: base sampler Raises: ValueError: If the number of evaluation qubits is smaller than 1. @@ -79,7 +87,16 @@ def __init__( super().__init__() # set quantum instance - self.quantum_instance = quantum_instance + if quantum_instance is not None: + warnings.warn( + "The quantum_instance argument has been superseded by the sampler argument. " + "This argument will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.quantum_instance = quantum_instance # get parameters self._m = num_eval_qubits # pylint: disable=invalid-name @@ -87,10 +104,17 @@ def __init__( self._iqft = iqft self._pec = phase_estimation_circuit + self._sampler = sampler @property - def quantum_instance(self) -> Optional[QuantumInstance]: - """Get the quantum instance. + @deprecate_function( + "The AmplitudeEstimation.quantum_instance getter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + def quantum_instance(self) -> None | QuantumInstance: + """Pending deprecation: Get the quantum instance. Returns: The quantum instance used to run this algorithm. @@ -98,8 +122,14 @@ def quantum_instance(self) -> Optional[QuantumInstance]: return self._quantum_instance @quantum_instance.setter + @deprecate_function( + "The AmplitudeEstimation.quantum_instance setter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Set quantum instance. + """Pending deprecation: Set quantum instance. Args: quantum_instance: The quantum instance used to run this algorithm. @@ -201,7 +231,9 @@ def _evaluate_count_results(self, counts): # construct probabilities measurements = OrderedDict() samples = OrderedDict() - shots = self._quantum_instance._run_config.shots + shots = ( + self._quantum_instance._run_config.shots if self._quantum_instance is not None else 1 + ) for state, count in counts.items(): y = int(state.replace(" ", "")[: self._m][::-1], 2) @@ -283,12 +315,16 @@ def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatio Raises: ValueError: If `state_preparation` or `objective_qubits` are not set in the `estimation_problem`. + ValueError: A quantum instance or Sampler must be provided. + AlgorithmError: Sampler run error """ # check if A factory or state_preparation has been set if estimation_problem.state_preparation is None: raise ValueError( "The state_preparation property of the estimation problem must be set." ) + if self._quantum_instance is None and self._sampler is None: + raise ValueError("A quantum instance or sampler must be provided.") if estimation_problem.objective_qubits is None: raise ValueError("The objective_qubits property of the estimation problem must be set.") @@ -297,15 +333,37 @@ def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatio result.num_evaluation_qubits = self._m result.post_processing = estimation_problem.post_processing - if self._quantum_instance.is_statevector: + measurements = OrderedDict() + samples = OrderedDict() + if self._sampler is not None: + circuit = self.construct_circuit(estimation_problem, measurement=True) + job = self._sampler.run([circuit]) + if job.status() is not JobStatus.DONE: + raise AlgorithmError("The job was not completed successfully. ") + ret = job.result() + result.shots = 1 + circuit_results = { + np.binary_repr(k, circuit.num_qubits): v for k, v in ret.quasi_dists[0].items() + } + for state, probability in circuit_results.items(): + state = state[::-1] + y = int(state[: self._m], 2) + measurements[y] = probability + a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) + samples[a] = samples.get(a, 0.0) + probability + # cutoff probabilities below the threshold + threshold: float = 1e-6 + samples = {a: p for a, p in samples.items() if p > threshold} + measurements = {y: p for y, p in measurements.items() if p > threshold} + elif self._quantum_instance.is_statevector: circuit = self.construct_circuit(estimation_problem, measurement=False) # run circuit on statevector simulator statevector = self._quantum_instance.execute(circuit).get_statevector() result.circuit_results = statevector - # store number of shots: convention is 1 shot for statevector, # needed so that MLE works! result.shots = 1 + samples, measurements = self.evaluate_measurements(result.circuit_results) else: # run circuit on QASM simulator circuit = self.construct_circuit(estimation_problem, measurement=True) @@ -314,8 +372,7 @@ def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatio # store shots result.shots = sum(counts.values()) - - samples, measurements = self.evaluate_measurements(result.circuit_results) + samples, measurements = self.evaluate_measurements(result.circuit_results) result.samples = samples result.samples_processed = { diff --git a/qiskit/algorithms/amplitude_estimators/fae.py b/qiskit/algorithms/amplitude_estimators/fae.py index db8ee9151d50..7fe023e3240e 100644 --- a/qiskit/algorithms/amplitude_estimators/fae.py +++ b/qiskit/algorithms/amplitude_estimators/fae.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017, 2022. # # 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 @@ -12,12 +12,16 @@ """Faster Amplitude Estimation.""" -from typing import Optional, Union, List, Tuple +from __future__ import annotations +from typing import Union, List, Tuple +import warnings import numpy as np from qiskit.circuit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, JobStatus +from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance +from qiskit.utils.deprecation import deprecate_function from qiskit.algorithms.exceptions import AlgorithmError from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult @@ -50,14 +54,17 @@ def __init__( delta: float, maxiter: int, rescale: bool = True, - quantum_instance: Optional[Union[QuantumInstance, Backend]] = None, + quantum_instance: None | Union[QuantumInstance, Backend] = None, + sampler: None | BaseSampler = None, ) -> None: r""" Args: delta: The probability that the true value is outside of the final confidence interval. maxiter: The number of iterations, the maximal power of Q is `2 ** (maxiter - 1)`. rescale: Whether to rescale the problem passed to `estimate`. - quantum_instance: The quantum instance or backend to run the circuits. + quantum_instance: Pending deprecation\: The quantum instance or backend + to run the circuits. + sampler: base sampler .. note:: @@ -66,16 +73,33 @@ def __init__( """ super().__init__() - self.quantum_instance = quantum_instance + # set quantum instance + if quantum_instance is not None: + warnings.warn( + "The quantum_instance argument has been superseded by the sampler argument. " + "This argument will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.quantum_instance = quantum_instance self._shots = (int(1944 * np.log(2 / delta)), int(972 * np.log(2 / delta))) self._rescale = rescale self._delta = delta self._maxiter = maxiter self._num_oracle_calls = 0 + self._sampler = sampler @property - def quantum_instance(self) -> Optional[QuantumInstance]: - """Get the quantum instance. + @deprecate_function( + "The FasterAmplitudeEstimation.quantum_instance getter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + def quantum_instance(self) -> None | QuantumInstance: + """Pending deprecation: Get the quantum instance. Returns: The quantum instance used to run this algorithm. @@ -83,8 +107,14 @@ def quantum_instance(self) -> Optional[QuantumInstance]: return self._quantum_instance @quantum_instance.setter + @deprecate_function( + "The FasterAmplitudeEstimation.quantum_instance setter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Set quantum instance. + """Pending deprecation: Set quantum instance. Args: quantum_instance: The quantum instance used to run this algorithm. @@ -94,10 +124,30 @@ def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> self._quantum_instance = quantum_instance def _cos_estimate(self, estimation_problem, k, shots): - if self._quantum_instance is None: - raise AlgorithmError("Quantum instance must be set.") + if self._quantum_instance is None and self._sampler is None: + raise ValueError("A quantum instance or sampler must be provided.") - if self._quantum_instance.is_statevector: + if self._sampler is not None: + circuit = self.construct_circuit(estimation_problem, k, measurement=True) + job = self._sampler.run([circuit]) + if job.status() is not JobStatus.DONE: + raise AlgorithmError("The job was not completed successfully. ") + result = job.result() + + # sum over all probabilities where the objective qubits are 1 + prob = 0 + for bit, probabilities in result.quasi_dists[0].items(): + i = int(bit) + # get bitstring of objective qubits + full_state = bin(i)[2:].zfill(circuit.num_qubits)[::-1] + state = "".join([full_state[i] for i in estimation_problem.objective_qubits]) + + # check if it is a good state + if estimation_problem.is_good_state(state[::-1]): + prob = prob + probabilities + + cos_estimate = 1 - 2 * prob + elif self._quantum_instance.is_statevector: circuit = self.construct_circuit(estimation_problem, k, measurement=False) statevector = self._quantum_instance.execute(circuit).get_statevector() @@ -179,21 +229,37 @@ def construct_circuit( return circuit def estimate(self, estimation_problem: EstimationProblem) -> "FasterAmplitudeEstimationResult": + """Run the amplitude estimation algorithm on provided estimation problem. + + Args: + estimation_problem: The estimation problem. + + Returns: + An amplitude estimation results object. + + Raises: + ValueError: A quantum instance or Sampler must be provided. + AlgorithmError: Sampler run error + """ + if self._quantum_instance is None and self._sampler is None: + raise ValueError("A quantum instance or sampler must be provided.") + self._num_oracle_calls = 0 - user_defined_shots = self.quantum_instance._run_config.shots + user_defined_shots = ( + self._quantum_instance._run_config.shots if self._quantum_instance is not None else 0 + ) if self._rescale: problem = estimation_problem.rescale(0.25) else: problem = estimation_problem - if self._quantum_instance.is_statevector: + if self._sampler is not None or self._quantum_instance.is_statevector: cos = self._cos_estimate(problem, k=0, shots=1) theta = np.arccos(cos) / 2 theta_ci = [theta, theta] theta_cis = [theta_ci] num_steps = num_first_stage_steps = 1 - else: theta_ci = [0, np.arcsin(0.25)] first_stage = True @@ -240,7 +306,7 @@ def cos_estimate(power, shots): result.num_oracle_queries = self._num_oracle_calls result.num_steps = num_steps result.num_first_state_steps = num_first_stage_steps - if self._quantum_instance.is_statevector: + if self._sampler is not None or self._quantum_instance.is_statevector: result.success_probability = 1 else: result.success_probability = 1 - (2 * self._maxiter - j_0) * self._delta @@ -252,7 +318,8 @@ def cos_estimate(power, shots): result.theta_intervals = theta_cis # reset shots to what the user had defined - self.quantum_instance._run_config.shots = user_defined_shots + if self._quantum_instance is not None: + self._quantum_instance._run_config.shots = user_defined_shots return result diff --git a/qiskit/algorithms/amplitude_estimators/iae.py b/qiskit/algorithms/amplitude_estimators/iae.py index 42727cc5aea0..7e5c196e160c 100644 --- a/qiskit/algorithms/amplitude_estimators/iae.py +++ b/qiskit/algorithms/amplitude_estimators/iae.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2022. # # 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 @@ -12,13 +12,17 @@ """The Iterative Quantum Amplitude Estimation Algorithm.""" -from typing import Optional, Union, List, Tuple, Dict, cast +from __future__ import annotations +from typing import Union, List, Tuple, Dict, cast +import warnings import numpy as np from scipy.stats import beta from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.providers import Backend +from qiskit.providers import Backend, JobStatus +from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance +from qiskit.utils.deprecation import deprecate_function from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .estimation_problem import EstimationProblem @@ -52,7 +56,8 @@ def __init__( alpha: float, confint_method: str = "beta", min_ratio: float = 2, - quantum_instance: Optional[Union[QuantumInstance, Backend]] = None, + quantum_instance: None | [Union[QuantumInstance, Backend]] = None, + sampler: None | BaseSampler = None, ) -> None: r""" The output of the algorithm is an estimate for the amplitude `a`, that with at least @@ -66,7 +71,8 @@ def __init__( each iteration, can be 'chernoff' for the Chernoff intervals or 'beta' for the Clopper-Pearson intervals (default) min_ratio: Minimal q-ratio (:math:`K_{i+1} / K_i`) for FindNextK - quantum_instance: Quantum Instance or Backend + quantum_instance: Pending deprecation\: Quantum Instance or Backend + sampler: base sampler Raises: AlgorithmError: if the method to compute the confidence intervals is not supported @@ -89,17 +95,33 @@ def __init__( super().__init__() # set quantum instance - self.quantum_instance = quantum_instance + if quantum_instance is not None: + warnings.warn( + "The quantum_instance argument has been superseded by the sampler argument. " + "This argument will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.quantum_instance = quantum_instance # store parameters self._epsilon = epsilon_target self._alpha = alpha self._min_ratio = min_ratio self._confint_method = confint_method + self._sampler = sampler @property - def quantum_instance(self) -> Optional[QuantumInstance]: - """Get the quantum instance. + @deprecate_function( + "The IterativeAmplitudeEstimation.quantum_instance getter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + def quantum_instance(self) -> None | QuantumInstance: + """Pending deprecation: Get the quantum instance. Returns: The quantum instance used to run this algorithm. @@ -107,8 +129,14 @@ def quantum_instance(self) -> Optional[QuantumInstance]: return self._quantum_instance @quantum_instance.setter + @deprecate_function( + "The IterativeAmplitudeEstimation.quantum_instance setter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Set quantum instance. + """Pending deprecation: Set quantum instance. Args: quantum_instance: The quantum instance used to run this algorithm. @@ -279,6 +307,21 @@ def _good_state_probability( def estimate( self, estimation_problem: EstimationProblem ) -> "IterativeAmplitudeEstimationResult": + """Run the amplitude estimation algorithm on provided estimation problem. + + Args: + estimation_problem: The estimation problem. + + Returns: + An amplitude estimation results object. + + Raises: + ValueError: A quantum instance or Sampler must be provided. + AlgorithmError: Sampler run error + """ + if self._quantum_instance is None and self._sampler is None: + raise ValueError("A quantum instance or sampler must be provided.") + # initialize memory variables powers = [0] # list of powers k: Q^k, (called 'k' in paper) ratios = [] # list of multiplication factors (called 'q' in paper) @@ -293,9 +336,39 @@ def estimate( ) upper_half_circle = True # initially theta is in the upper half-circle - # for statevector we can directly return the probability to measure 1 - # note, that no iterations here are necessary - if self._quantum_instance.is_statevector: + if self._sampler is not None: + circuit = self.construct_circuit(estimation_problem, k=0, measurement=True) + job = self._sampler.run([circuit]) + if job.status() is not JobStatus.DONE: + raise AlgorithmError("The job was not completed successfully. ") + result = job.result() + + # calculate the probability of measuring '1' + num_qubits = circuit.num_qubits - circuit.num_ancillas + prob = 0 + for bit, probabilities in result.quasi_dists[0].items(): + i = int(bit) + # get bitstring of objective qubits + full_state = bin(i)[2:].zfill(circuit.num_qubits)[::-1] + state = "".join([full_state[i] for i in estimation_problem.objective_qubits]) + + # check if it is a good state + if estimation_problem.is_good_state(state[::-1]): + prob = prob + probabilities + + prob = cast(float, prob) # tell MyPy it's a float and not Tuple[int, float ] + + a_confidence_interval = [prob, prob] # type: List[float] + a_intervals.append(a_confidence_interval) + + theta_i_interval = [ + np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval # type: ignore + ] + theta_intervals.append(theta_i_interval) + num_oracle_queries = 0 # no Q-oracle call, only a single one to A + elif self._quantum_instance.is_statevector: + # for statevector we can directly return the probability to measure 1 + # note, that no iterations here are necessary # simulate circuit circuit = self.construct_circuit(estimation_problem, k=0, measurement=False) ret = self._quantum_instance.execute(circuit) diff --git a/qiskit/algorithms/amplitude_estimators/mlae.py b/qiskit/algorithms/amplitude_estimators/mlae.py index 21ca0cb35d1a..1e2f1aa9a0cc 100644 --- a/qiskit/algorithms/amplitude_estimators/mlae.py +++ b/qiskit/algorithms/amplitude_estimators/mlae.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2022. # # 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 @@ -12,14 +12,18 @@ """The Maximum Likelihood Amplitude Estimation algorithm.""" -from typing import Optional, List, Union, Tuple, Dict, Callable +from __future__ import annotations +from typing import List, Union, Tuple, Dict, Callable +import warnings import numpy as np from scipy.optimize import brute from scipy.stats import norm, chi2 -from qiskit.providers import Backend +from qiskit.providers import Backend, JobStatus from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.utils import QuantumInstance +from qiskit.primitives import BaseSampler +from qiskit.utils.deprecation import deprecate_function from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .estimation_problem import EstimationProblem @@ -50,8 +54,9 @@ class in named ``MaximumLikelihoodAmplitudeEstimation``. def __init__( self, evaluation_schedule: Union[List[int], int], - minimizer: Optional[MINIMIZER] = None, - quantum_instance: Optional[Union[QuantumInstance, Backend]] = None, + minimizer: None | MINIMIZER = None, + quantum_instance: None | Union[QuantumInstance, Backend] = None, + sampler: None | BaseSampler = None, ) -> None: r""" Args: @@ -64,7 +69,8 @@ def __init__( according to ``evaluation_schedule``. The minimizer takes a function as first argument and a list of (float, float) tuples (as bounds) as second argument and returns a single float which is the found minimum. - quantum_instance: Quantum Instance or Backend + quantum_instance: Pending deprecation\: Quantum Instance or Backend + sampler: base sampler Raises: ValueError: If the number of oracle circuits is smaller than 1. @@ -73,7 +79,16 @@ def __init__( super().__init__() # set quantum instance - self.quantum_instance = quantum_instance + if quantum_instance is not None: + warnings.warn( + "The quantum_instance argument has been superseded by the sampler argument. " + "This argument will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.quantum_instance = quantum_instance # get parameters if isinstance(evaluation_schedule, int): @@ -98,9 +113,17 @@ def default_minimizer(objective_fn, bounds): else: self._minimizer = minimizer + self._sampler = sampler + @property - def quantum_instance(self) -> Optional[QuantumInstance]: - """Get the quantum instance. + @deprecate_function( + "The MaximumLikelihoodAmplitudeEstimation.quantum_instance getter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) + def quantum_instance(self) -> None | QuantumInstance: + """Pending deprecation: Get the quantum instance. Returns: The quantum instance used to run this algorithm. @@ -108,8 +131,14 @@ def quantum_instance(self) -> Optional[QuantumInstance]: return self._quantum_instance @quantum_instance.setter + @deprecate_function( + "The MaximumLikelihoodAmplitudeEstimation.quantum_instance setter is pending deprecation. " + "This property will be deprecated in a future release and subsequently " + "removed after that.", + category=PendingDeprecationWarning, + ) def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Set quantum instance. + """Pending deprecation: Set quantum instance. Args: quantum_instance: The quantum instance used to run this algorithm. @@ -148,7 +177,7 @@ def construct_circuits( qc_0.compose(estimation_problem.state_preparation, inplace=True) for k in self._evaluation_schedule: - qc_k = qc_0.copy(name="qc_a_q_%s" % k) + qc_k = qc_0.copy(name=f"qc_a_q_{k}") if k != 0: qc_k.compose(estimation_problem.grover_operator.power(k), inplace=True) @@ -218,7 +247,7 @@ def compute_mle( self, circuit_results: Union[List[Dict[str, int]], List[np.ndarray]], estimation_problem: EstimationProblem, - num_state_qubits: Optional[int] = None, + num_state_qubits: None | int = None, return_counts: bool = False, ) -> Union[float, Tuple[float, List[float]]]: """Compute the MLE via a grid-search. @@ -259,10 +288,25 @@ def loglikelihood(theta): def estimate( self, estimation_problem: EstimationProblem ) -> "MaximumLikelihoodAmplitudeEstimationResult": + """Run the amplitude estimation algorithm on provided estimation problem. + + Args: + estimation_problem: The estimation problem. + + Returns: + An amplitude estimation results object. + + Raises: + ValueError: A quantum instance or Sampler must be provided. + AlgorithmError: If `state_preparation` is not set in + `estimation_problem`. + AlgorithmError: Sampler run error + """ + if self._quantum_instance is None and self._sampler is None: + raise ValueError("A quantum instance or sampler must be provided.") if estimation_problem.state_preparation is None: raise AlgorithmError( - "Either the state_preparation variable or the a_factory " - "(deprecated) must be set to run the algorithm." + "The state_preparation property of the estimation problem must be set." ) result = MaximumLikelihoodAmplitudeEstimationResult() @@ -270,7 +314,20 @@ def estimate( result.minimizer = self._minimizer result.post_processing = estimation_problem.post_processing - if self._quantum_instance.is_statevector: + if self._sampler is not None: + circuits = self.construct_circuits(estimation_problem, measurement=True) + job = self._sampler.run(circuits) + if job.status() is not JobStatus.DONE: + raise AlgorithmError("The job was not completed successfully. ") + ret = job.result() + result.circuit_results = [] + for i, quasi_dist in enumerate(ret.quasi_dists): + circuit_result = { + np.binary_repr(k, circuits[i].num_qubits): v for k, v in quasi_dist.items() + } + result.circuit_results.append(circuit_result) + result.shots = 1 + elif self._quantum_instance.is_statevector: # run circuit on statevector simulator circuits = self.construct_circuits(estimation_problem, measurement=False) ret = self._quantum_instance.execute(circuits) @@ -397,7 +454,7 @@ def _safe_max(array, default=(np.pi / 2)): def _compute_fisher_information( result: "MaximumLikelihoodAmplitudeEstimationResult", - num_sum_terms: Optional[int] = None, + num_sum_terms: None | int = None, observed: bool = False, ) -> float: """Compute the Fisher information. @@ -489,7 +546,7 @@ def _fisher_confint( def _likelihood_ratio_confint( result: MaximumLikelihoodAmplitudeEstimationResult, alpha: float = 0.05, - nevals: Optional[int] = None, + nevals: None | int = None, ) -> List[float]: """Compute the likelihood-ratio confidence interval. diff --git a/test/python/algorithms/test_amplitude_estimators.py b/test/python/algorithms/test_amplitude_estimators.py index 0b41e863aed2..811801f5a178 100644 --- a/test/python/algorithms/test_amplitude_estimators.py +++ b/test/python/algorithms/test_amplitude_estimators.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2022. # # 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 @@ -27,6 +27,7 @@ EstimationProblem, ) from qiskit.quantum_info import Operator, Statevector +from qiskit.primitives import Sampler class BernoulliStateIn(QuantumCircuit): @@ -94,12 +95,6 @@ def setUp(self): seed_simulator=2, seed_transpiler=2, ) - self._unitary = QuantumInstance( - backend=BasicAer.get_backend("unitary_simulator"), - shots=1, - seed_simulator=42, - seed_transpiler=91, - ) def qasm(shots=100): return QuantumInstance( @@ -136,6 +131,49 @@ def test_statevector(self, prob, qae, expect): value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" ) + @idata( + [ + [0.2, AmplitudeEstimation(2, sampler=Sampler()), {"estimation": 0.5, "mle": 0.2}], + [0.49, AmplitudeEstimation(3, sampler=Sampler()), {"estimation": 0.5, "mle": 0.49}], + [ + 0.2, + MaximumLikelihoodAmplitudeEstimation([0, 1, 2], sampler=Sampler()), + {"estimation": 0.2}, + ], + [ + 0.49, + MaximumLikelihoodAmplitudeEstimation(3, sampler=Sampler()), + {"estimation": 0.49}, + ], + [0.2, IterativeAmplitudeEstimation(0.1, 0.1, sampler=Sampler()), {"estimation": 0.2}], + [ + 0.49, + IterativeAmplitudeEstimation(0.001, 0.01, sampler=Sampler()), + {"estimation": 0.49}, + ], + [ + 0.2, + FasterAmplitudeEstimation(0.1, 3, rescale=False, sampler=Sampler()), + {"estimation": 0.2}, + ], + [ + 0.12, + FasterAmplitudeEstimation(0.1, 2, rescale=False, sampler=Sampler()), + {"estimation": 0.12}, + ], + ] + ) + @unpack + def test_sampler(self, prob, qae, expect): + """sampler test""" + problem = EstimationProblem(BernoulliStateIn(prob), 0, BernoulliGrover(prob)) + + result = qae.estimate(problem) + for key, value in expect.items(): + self.assertAlmostEqual( + value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" + ) + @idata( [ [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.193888}],